From 5ab11df551fee1792ff6b6d70a451b720ec01f6f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 24 Feb 2021 22:28:02 +0100 Subject: [PATCH 001/831] Bump version to 2021.4.0dev0 (#47017) --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index a0aafaad3ce..f8d2507d051 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,6 +1,6 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 -MINOR_VERSION = 3 +MINOR_VERSION = 4 PATCH_VERSION = "0.dev0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 557ec374f176d219b53b12e999e334f0006e8aa8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 24 Feb 2021 13:37:31 -0800 Subject: [PATCH 002/831] Convert discovery helper to use dispatcher (#47008) --- .../components/discovery/__init__.py | 9 +- .../components/octoprint/__init__.py | 8 -- homeassistant/const.py | 4 - homeassistant/helpers/discovery.py | 120 +++++++----------- tests/common.py | 18 --- tests/helpers/test_discovery.py | 57 ++++----- tests/test_setup.py | 12 +- 7 files changed, 79 insertions(+), 149 deletions(-) diff --git a/homeassistant/components/discovery/__init__.py b/homeassistant/components/discovery/__init__.py index b97202033bd..2b293179888 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -1,11 +1,4 @@ -""" -Starts a service to scan in intervals for new devices. - -Will emit EVENT_PLATFORM_DISCOVERED whenever a new service has been discovered. - -Knows which components handle certain types, will make sure they are -loaded before the EVENT_PLATFORM_DISCOVERED is fired. -""" +"""Starts a service to scan in intervals for new devices.""" from datetime import timedelta import json import logging diff --git a/homeassistant/components/octoprint/__init__.py b/homeassistant/components/octoprint/__init__.py index 6f178f26578..66b804927c7 100644 --- a/homeassistant/components/octoprint/__init__.py +++ b/homeassistant/components/octoprint/__init__.py @@ -6,7 +6,6 @@ from aiohttp.hdrs import CONTENT_TYPE import requests import voluptuous as vol -from homeassistant.components.discovery import SERVICE_OCTOPRINT from homeassistant.const import ( CONF_API_KEY, CONF_BINARY_SENSORS, @@ -22,7 +21,6 @@ from homeassistant.const import ( TEMP_CELSIUS, TIME_SECONDS, ) -from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform from homeassistant.util import slugify as util_slugify @@ -132,12 +130,6 @@ def setup(hass, config): printers = hass.data[DOMAIN] = {} success = False - def device_discovered(service, info): - """Get called when an Octoprint server has been discovered.""" - _LOGGER.debug("Found an Octoprint server: %s", info) - - discovery.listen(hass, SERVICE_OCTOPRINT, device_discovered) - if DOMAIN not in config: # Skip the setup if there is no configuration present return True diff --git a/homeassistant/const.py b/homeassistant/const.py index f8d2507d051..712f7ede0d3 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -210,7 +210,6 @@ EVENT_HOMEASSISTANT_STARTED = "homeassistant_started" EVENT_HOMEASSISTANT_STOP = "homeassistant_stop" EVENT_HOMEASSISTANT_FINAL_WRITE = "homeassistant_final_write" EVENT_LOGBOOK_ENTRY = "logbook_entry" -EVENT_PLATFORM_DISCOVERED = "platform_discovered" EVENT_SERVICE_REGISTERED = "service_registered" EVENT_SERVICE_REMOVED = "service_removed" EVENT_STATE_CHANGED = "state_changed" @@ -313,9 +312,6 @@ CONF_UNIT_SYSTEM_IMPERIAL: str = "imperial" # Electrical attributes ATTR_VOLTAGE = "voltage" -# Contains the information that is discovered -ATTR_DISCOVERED = "discovered" - # Location of the device/sensor ATTR_LOCATION = "location" diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index 0770e6798f1..7ee72759d65 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -5,62 +5,55 @@ There are two different types of discoveries that can be fired/listened for. - listen_platform/discover_platform is for platforms. These are used by components to allow discovery of their platforms. """ -from typing import Any, Callable, Collection, Dict, Optional, Union +from typing import Any, Callable, Dict, Optional, TypedDict from homeassistant import core, setup -from homeassistant.const import ATTR_DISCOVERED, ATTR_SERVICE, EVENT_PLATFORM_DISCOVERED from homeassistant.core import CALLBACK_TYPE -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.loader import bind_hass -from homeassistant.util.async_ import run_callback_threadsafe +from .dispatcher import async_dispatcher_connect, async_dispatcher_send +from .typing import ConfigType, DiscoveryInfoType + +SIGNAL_PLATFORM_DISCOVERED = "discovery.platform_discovered_{}" EVENT_LOAD_PLATFORM = "load_platform.{}" ATTR_PLATFORM = "platform" +ATTR_DISCOVERED = "discovered" # mypy: disallow-any-generics -@bind_hass -def listen( - hass: core.HomeAssistant, - service: Union[str, Collection[str]], - callback: CALLBACK_TYPE, -) -> None: - """Set up listener for discovery of specific service. +class DiscoveryDict(TypedDict): + """Discovery data.""" - Service can be a string or a list/tuple. - """ - run_callback_threadsafe(hass.loop, async_listen, hass, service, callback).result() + service: str + platform: Optional[str] + discovered: Optional[DiscoveryInfoType] @core.callback @bind_hass def async_listen( hass: core.HomeAssistant, - service: Union[str, Collection[str]], + service: str, callback: CALLBACK_TYPE, ) -> None: """Set up listener for discovery of specific service. Service can be a string or a list/tuple. """ - if isinstance(service, str): - service = (service,) - else: - service = tuple(service) - job = core.HassJob(callback) - async def discovery_event_listener(event: core.Event) -> None: + async def discovery_event_listener(discovered: DiscoveryDict) -> None: """Listen for discovery events.""" - if ATTR_SERVICE in event.data and event.data[ATTR_SERVICE] in service: - task = hass.async_run_hass_job( - job, event.data[ATTR_SERVICE], event.data.get(ATTR_DISCOVERED) - ) - if task: - await task + task = hass.async_run_hass_job( + job, discovered["service"], discovered["discovered"] + ) + if task: + await task - hass.bus.async_listen(EVENT_PLATFORM_DISCOVERED, discovery_event_listener) + async_dispatcher_connect( + hass, SIGNAL_PLATFORM_DISCOVERED.format(service), discovery_event_listener + ) @bind_hass @@ -91,22 +84,13 @@ async def async_discover( if component is not None and component not in hass.config.components: await setup.async_setup_component(hass, component, hass_config) - data: Dict[str, Any] = {ATTR_SERVICE: service} + data: DiscoveryDict = { + "service": service, + "platform": None, + "discovered": discovered, + } - if discovered is not None: - data[ATTR_DISCOVERED] = discovered - - hass.bus.async_fire(EVENT_PLATFORM_DISCOVERED, data) - - -@bind_hass -def listen_platform( - hass: core.HomeAssistant, component: str, callback: CALLBACK_TYPE -) -> None: - """Register a platform loader listener.""" - run_callback_threadsafe( - hass.loop, async_listen_platform, hass, component, callback - ).result() + async_dispatcher_send(hass, SIGNAL_PLATFORM_DISCOVERED.format(service), data) @bind_hass @@ -122,21 +106,20 @@ def async_listen_platform( service = EVENT_LOAD_PLATFORM.format(component) job = core.HassJob(callback) - async def discovery_platform_listener(event: core.Event) -> None: + async def discovery_platform_listener(discovered: DiscoveryDict) -> None: """Listen for platform discovery events.""" - if event.data.get(ATTR_SERVICE) != service: - return - - platform = event.data.get(ATTR_PLATFORM) + platform = discovered["platform"] if not platform: return - task = hass.async_run_hass_job(job, platform, event.data.get(ATTR_DISCOVERED)) + task = hass.async_run_hass_job(job, platform, discovered.get("discovered")) if task: await task - hass.bus.async_listen(EVENT_PLATFORM_DISCOVERED, discovery_platform_listener) + async_dispatcher_connect( + hass, SIGNAL_PLATFORM_DISCOVERED.format(service), discovery_platform_listener + ) @bind_hass @@ -147,16 +130,7 @@ def load_platform( discovered: DiscoveryInfoType, hass_config: ConfigType, ) -> None: - """Load a component and platform dynamically. - - Target components will be loaded and an EVENT_PLATFORM_DISCOVERED will be - fired to load the platform. The event will contain: - { ATTR_SERVICE = EVENT_LOAD_PLATFORM + '.' + <> - ATTR_PLATFORM = <> - ATTR_DISCOVERED = <> } - - Use `listen_platform` to register a callback for these events. - """ + """Load a component and platform dynamically.""" hass.add_job( async_load_platform( # type: ignore hass, component, platform, discovered, hass_config @@ -174,18 +148,10 @@ async def async_load_platform( ) -> None: """Load a component and platform dynamically. - Target components will be loaded and an EVENT_PLATFORM_DISCOVERED will be - fired to load the platform. The event will contain: - { ATTR_SERVICE = EVENT_LOAD_PLATFORM + '.' + <> - ATTR_PLATFORM = <> - ATTR_DISCOVERED = <> } - - Use `listen_platform` to register a callback for these events. + Use `async_listen_platform` to register a callback for these events. Warning: Do not await this inside a setup method to avoid a dead lock. Use `hass.async_create_task(async_load_platform(..))` instead. - - This method is a coroutine. """ assert hass_config, "You need to pass in the real hass config" @@ -194,16 +160,16 @@ async def async_load_platform( if component not in hass.config.components: setup_success = await setup.async_setup_component(hass, component, hass_config) - # No need to fire event if we could not set up component + # No need to send signal if we could not set up component if not setup_success: return - data: Dict[str, Any] = { - ATTR_SERVICE: EVENT_LOAD_PLATFORM.format(component), - ATTR_PLATFORM: platform, + service = EVENT_LOAD_PLATFORM.format(component) + + data: DiscoveryDict = { + "service": service, + "platform": platform, + "discovered": discovered, } - if discovered is not None: - data[ATTR_DISCOVERED] = discovered - - hass.bus.async_fire(EVENT_PLATFORM_DISCOVERED, data) + async_dispatcher_send(hass, SIGNAL_PLATFORM_DISCOVERED.format(service), data) diff --git a/tests/common.py b/tests/common.py index 52d368853b3..0ae6f7ef5c7 100644 --- a/tests/common.py +++ b/tests/common.py @@ -36,11 +36,8 @@ from homeassistant.components.device_automation import ( # noqa: F401 from homeassistant.components.mqtt.models import Message from homeassistant.config import async_process_component_config from homeassistant.const import ( - ATTR_DISCOVERED, - ATTR_SERVICE, DEVICE_DEFAULT_NAME, EVENT_HOMEASSISTANT_CLOSE, - EVENT_PLATFORM_DISCOVERED, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, STATE_OFF, @@ -387,21 +384,6 @@ def async_fire_time_changed(hass, datetime_, fire_all=False): fire_time_changed = threadsafe_callback_factory(async_fire_time_changed) -def fire_service_discovered(hass, service, info): - """Fire the MQTT message.""" - hass.bus.fire( - EVENT_PLATFORM_DISCOVERED, {ATTR_SERVICE: service, ATTR_DISCOVERED: info} - ) - - -@ha.callback -def async_fire_service_discovered(hass, service, info): - """Fire the MQTT message.""" - hass.bus.async_fire( - EVENT_PLATFORM_DISCOVERED, {ATTR_SERVICE: service, ATTR_DISCOVERED: info} - ) - - def load_fixture(filename): """Load a fixture.""" path = os.path.join(os.path.dirname(__file__), "fixtures", filename) diff --git a/tests/helpers/test_discovery.py b/tests/helpers/test_discovery.py index 64f39fb13bd..d0d8580d69d 100644 --- a/tests/helpers/test_discovery.py +++ b/tests/helpers/test_discovery.py @@ -4,6 +4,8 @@ from unittest.mock import patch from homeassistant import setup from homeassistant.core import callback from homeassistant.helpers import discovery +from homeassistant.helpers.dispatcher import dispatcher_send +from homeassistant.util.async_ import run_callback_threadsafe from tests.common import ( MockModule, @@ -31,23 +33,22 @@ class TestHelpersDiscovery: """Test discovery listen/discover combo.""" helpers = self.hass.helpers calls_single = [] - calls_multi = [] @callback def callback_single(service, info): """Service discovered callback.""" calls_single.append((service, info)) - @callback - def callback_multi(service, info): - """Service discovered callback.""" - calls_multi.append((service, info)) + self.hass.add_job( + helpers.discovery.async_listen, "test service", callback_single + ) - helpers.discovery.listen("test service", callback_single) - helpers.discovery.listen(["test service", "another service"], callback_multi) - - helpers.discovery.discover( - "test service", "discovery info", "test_component", {} + self.hass.add_job( + helpers.discovery.async_discover, + "test service", + "discovery info", + "test_component", + {}, ) self.hass.block_till_done() @@ -56,15 +57,6 @@ class TestHelpersDiscovery: assert len(calls_single) == 1 assert calls_single[0] == ("test service", "discovery info") - helpers.discovery.discover( - "another service", "discovery info", "test_component", {} - ) - self.hass.block_till_done() - - assert len(calls_single) == 1 - assert len(calls_multi) == 2 - assert ["test service", "another service"] == [info[0] for info in calls_multi] - @patch("homeassistant.setup.async_setup_component", return_value=mock_coro(True)) def test_platform(self, mock_setup_component): """Test discover platform method.""" @@ -75,7 +67,13 @@ class TestHelpersDiscovery: """Platform callback method.""" calls.append((platform, info)) - discovery.listen_platform(self.hass, "test_component", platform_callback) + run_callback_threadsafe( + self.hass.loop, + discovery.async_listen_platform, + self.hass, + "test_component", + platform_callback, + ).result() discovery.load_platform( self.hass, @@ -105,13 +103,10 @@ class TestHelpersDiscovery: assert len(calls) == 1 assert calls[0] == ("test_platform", "discovery info") - self.hass.bus.fire( - discovery.EVENT_PLATFORM_DISCOVERED, - { - discovery.ATTR_SERVICE: discovery.EVENT_LOAD_PLATFORM.format( - "test_component" - ) - }, + dispatcher_send( + self.hass, + discovery.SIGNAL_PLATFORM_DISCOVERED, + {"service": discovery.EVENT_LOAD_PLATFORM.format("test_component")}, ) self.hass.block_till_done() @@ -179,10 +174,12 @@ class TestHelpersDiscovery: """ component_calls = [] - def component1_setup(hass, config): + async def component1_setup(hass, config): """Set up mock component.""" print("component1 setup") - discovery.discover(hass, "test_component2", {}, "test_component2", {}) + await discovery.async_discover( + hass, "test_component2", {}, "test_component2", {} + ) return True def component2_setup(hass, config): @@ -191,7 +188,7 @@ class TestHelpersDiscovery: return True mock_integration( - self.hass, MockModule("test_component1", setup=component1_setup) + self.hass, MockModule("test_component1", async_setup=component1_setup) ) mock_integration( diff --git a/tests/test_setup.py b/tests/test_setup.py index 539ed3f1442..abb8f756989 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -441,10 +441,14 @@ class TestSetup: """Test all init work done till start.""" call_order = [] - def component1_setup(hass, config): + async def component1_setup(hass, config): """Set up mock component.""" - discovery.discover(hass, "test_component2", {}, "test_component2", {}) - discovery.discover(hass, "test_component3", {}, "test_component3", {}) + await discovery.async_discover( + hass, "test_component2", {}, "test_component2", {} + ) + await discovery.async_discover( + hass, "test_component3", {}, "test_component3", {} + ) return True def component_track_setup(hass, config): @@ -453,7 +457,7 @@ class TestSetup: return True mock_integration( - self.hass, MockModule("test_component1", setup=component1_setup) + self.hass, MockModule("test_component1", async_setup=component1_setup) ) mock_integration( From bb7f1b748f77a8801cd0833573a255b83978f939 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 25 Feb 2021 00:05:20 +0000 Subject: [PATCH 003/831] [ci skip] Translation update --- .../components/arcam_fmj/translations/nl.json | 1 + .../components/asuswrt/translations/nl.json | 1 + .../components/august/translations/nl.json | 3 +- .../components/blink/translations/nl.json | 1 + .../components/bond/translations/ca.json | 4 +-- .../components/bond/translations/et.json | 4 +-- .../components/bond/translations/fr.json | 4 +-- .../components/bond/translations/nl.json | 1 + .../components/bond/translations/no.json | 4 +-- .../components/bond/translations/pl.json | 4 +-- .../components/bond/translations/ru.json | 4 +-- .../components/bond/translations/zh-Hant.json | 4 +-- .../components/broadlink/translations/nl.json | 1 + .../components/climacell/translations/af.json | 10 ++++++ .../components/climacell/translations/ca.json | 34 +++++++++++++++++++ .../components/climacell/translations/en.json | 34 +++++++++++++++++++ .../components/climacell/translations/et.json | 34 +++++++++++++++++++ .../components/climacell/translations/fr.json | 34 +++++++++++++++++++ .../components/climacell/translations/nl.json | 32 +++++++++++++++++ .../components/climacell/translations/no.json | 34 +++++++++++++++++++ .../components/climacell/translations/ru.json | 34 +++++++++++++++++++ .../components/daikin/translations/nl.json | 4 ++- .../components/enocean/translations/nl.json | 7 ++++ .../faa_delays/translations/en.json | 12 ++++--- .../faa_delays/translations/fr.json | 21 ++++++++++++ .../faa_delays/translations/nl.json | 21 ++++++++++++ .../fireservicerota/translations/nl.json | 3 +- .../fireservicerota/translations/no.json | 2 +- .../components/firmata/translations/nl.json | 7 ++++ .../flunearyou/translations/nl.json | 3 ++ .../components/fritzbox/translations/nl.json | 1 + .../components/goalzero/translations/nl.json | 1 + .../home_connect/translations/nl.json | 3 +- .../components/homekit/translations/ca.json | 8 ++--- .../components/homekit/translations/en.json | 21 ++++++++++-- .../components/homekit/translations/et.json | 10 +++--- .../components/homekit/translations/fr.json | 8 ++--- .../components/homekit/translations/no.json | 8 ++--- .../components/homekit/translations/pl.json | 8 ++--- .../components/homekit/translations/ru.json | 8 ++--- .../homekit/translations/zh-Hant.json | 8 ++--- .../huawei_lte/translations/nl.json | 1 + .../components/icloud/translations/nl.json | 6 ++-- .../components/ifttt/translations/nl.json | 3 +- .../components/insteon/translations/nl.json | 3 ++ .../keenetic_ndms2/translations/nl.json | 3 +- .../components/kmtronic/translations/fr.json | 21 ++++++++++++ .../components/kmtronic/translations/pl.json | 21 ++++++++++++ .../components/litejet/translations/ca.json | 19 +++++++++++ .../components/litejet/translations/fr.json | 16 +++++++++ .../components/litejet/translations/no.json | 19 +++++++++++ .../components/litejet/translations/pl.json | 19 +++++++++++ .../components/litejet/translations/ru.json | 19 +++++++++++ .../components/litejet/translations/tr.json | 9 +++++ .../litejet/translations/zh-Hant.json | 19 +++++++++++ .../litterrobot/translations/fr.json | 20 +++++++++++ .../litterrobot/translations/no.json | 20 +++++++++++ .../litterrobot/translations/pl.json | 20 +++++++++++ .../components/locative/translations/nl.json | 3 +- .../components/mailgun/translations/nl.json | 3 +- .../components/mazda/translations/nl.json | 3 +- .../components/mullvad/translations/ca.json | 22 ++++++++++++ .../components/mullvad/translations/en.json | 6 ++++ .../components/mullvad/translations/et.json | 22 ++++++++++++ .../components/mullvad/translations/fr.json | 22 ++++++++++++ .../components/mullvad/translations/nl.json | 22 ++++++++++++ .../components/mullvad/translations/no.json | 22 ++++++++++++ .../components/mullvad/translations/ru.json | 22 ++++++++++++ .../components/mullvad/translations/tr.json | 12 +++++++ .../components/netatmo/translations/et.json | 22 ++++++++++++ .../components/netatmo/translations/fr.json | 22 ++++++++++++ .../components/netatmo/translations/nl.json | 25 +++++++++++++- .../components/netatmo/translations/ru.json | 22 ++++++++++++ .../components/netatmo/translations/tr.json | 16 +++++++++ .../nightscout/translations/nl.json | 1 + .../components/nzbget/translations/nl.json | 1 + .../plum_lightpad/translations/nl.json | 3 +- .../components/poolsense/translations/nl.json | 3 +- .../translations/fr.json | 21 ++++++++++++ .../components/rpi_power/translations/nl.json | 5 +++ .../ruckus_unleashed/translations/nl.json | 1 + .../components/sharkiq/translations/nl.json | 1 + .../components/smappee/translations/nl.json | 3 +- .../components/sms/translations/nl.json | 3 +- .../components/somfy/translations/nl.json | 1 + .../speedtestdotnet/translations/nl.json | 5 +++ .../components/spider/translations/nl.json | 3 ++ .../components/subaru/translations/fr.json | 31 +++++++++++++++++ .../components/syncthru/translations/nl.json | 3 +- .../components/tile/translations/nl.json | 3 +- .../components/toon/translations/nl.json | 2 ++ .../totalconnect/translations/fr.json | 11 +++++- .../totalconnect/translations/nl.json | 5 +++ .../totalconnect/translations/no.json | 17 ++++++++-- .../totalconnect/translations/pl.json | 17 ++++++++-- .../xiaomi_miio/translations/no.json | 1 + .../xiaomi_miio/translations/pl.json | 1 + .../components/zwave_js/translations/ca.json | 7 +++- .../components/zwave_js/translations/et.json | 7 +++- .../components/zwave_js/translations/fr.json | 7 +++- .../components/zwave_js/translations/no.json | 7 +++- .../components/zwave_js/translations/pl.json | 7 +++- .../components/zwave_js/translations/ru.json | 7 +++- .../zwave_js/translations/zh-Hant.json | 7 +++- 104 files changed, 1060 insertions(+), 81 deletions(-) create mode 100644 homeassistant/components/climacell/translations/af.json create mode 100644 homeassistant/components/climacell/translations/ca.json create mode 100644 homeassistant/components/climacell/translations/en.json create mode 100644 homeassistant/components/climacell/translations/et.json create mode 100644 homeassistant/components/climacell/translations/fr.json create mode 100644 homeassistant/components/climacell/translations/nl.json create mode 100644 homeassistant/components/climacell/translations/no.json create mode 100644 homeassistant/components/climacell/translations/ru.json create mode 100644 homeassistant/components/enocean/translations/nl.json create mode 100644 homeassistant/components/faa_delays/translations/fr.json create mode 100644 homeassistant/components/faa_delays/translations/nl.json create mode 100644 homeassistant/components/firmata/translations/nl.json create mode 100644 homeassistant/components/kmtronic/translations/fr.json create mode 100644 homeassistant/components/kmtronic/translations/pl.json create mode 100644 homeassistant/components/litejet/translations/ca.json create mode 100644 homeassistant/components/litejet/translations/fr.json create mode 100644 homeassistant/components/litejet/translations/no.json create mode 100644 homeassistant/components/litejet/translations/pl.json create mode 100644 homeassistant/components/litejet/translations/ru.json create mode 100644 homeassistant/components/litejet/translations/tr.json create mode 100644 homeassistant/components/litejet/translations/zh-Hant.json create mode 100644 homeassistant/components/litterrobot/translations/fr.json create mode 100644 homeassistant/components/litterrobot/translations/no.json create mode 100644 homeassistant/components/litterrobot/translations/pl.json create mode 100644 homeassistant/components/mullvad/translations/ca.json create mode 100644 homeassistant/components/mullvad/translations/et.json create mode 100644 homeassistant/components/mullvad/translations/fr.json create mode 100644 homeassistant/components/mullvad/translations/nl.json create mode 100644 homeassistant/components/mullvad/translations/no.json create mode 100644 homeassistant/components/mullvad/translations/ru.json create mode 100644 homeassistant/components/mullvad/translations/tr.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/fr.json create mode 100644 homeassistant/components/subaru/translations/fr.json diff --git a/homeassistant/components/arcam_fmj/translations/nl.json b/homeassistant/components/arcam_fmj/translations/nl.json index 5607b426cc9..03465d5c53d 100644 --- a/homeassistant/components/arcam_fmj/translations/nl.json +++ b/homeassistant/components/arcam_fmj/translations/nl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kan geen verbinding maken" }, "error": { diff --git a/homeassistant/components/asuswrt/translations/nl.json b/homeassistant/components/asuswrt/translations/nl.json index 1128a820cd5..9d1e76aaf2b 100644 --- a/homeassistant/components/asuswrt/translations/nl.json +++ b/homeassistant/components/asuswrt/translations/nl.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Kan geen verbinding maken", "invalid_host": "Ongeldige hostnaam of IP-adres", + "ssh_not_file": "SSH-sleutelbestand niet gevonden", "unknown": "Onverwachte fout" }, "step": { diff --git a/homeassistant/components/august/translations/nl.json b/homeassistant/components/august/translations/nl.json index 1697f634d9a..e48d27801cc 100644 --- a/homeassistant/components/august/translations/nl.json +++ b/homeassistant/components/august/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Account al geconfigureerd" + "already_configured": "Account al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "cannot_connect": "Verbinding mislukt, probeer het opnieuw", diff --git a/homeassistant/components/blink/translations/nl.json b/homeassistant/components/blink/translations/nl.json index 4067bf75f83..f1f1ce7888b 100644 --- a/homeassistant/components/blink/translations/nl.json +++ b/homeassistant/components/blink/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_access_token": "Ongeldig toegangstoken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" diff --git a/homeassistant/components/bond/translations/ca.json b/homeassistant/components/bond/translations/ca.json index 3903ea77c34..1d1df915630 100644 --- a/homeassistant/components/bond/translations/ca.json +++ b/homeassistant/components/bond/translations/ca.json @@ -9,13 +9,13 @@ "old_firmware": "Hi ha un programari antic i no compatible al dispositiu Bond - actualitza'l abans de continuar", "unknown": "Error inesperat" }, - "flow_title": "Bond: {bond_id} ({host})", + "flow_title": "Bond: {name} ({host})", "step": { "confirm": { "data": { "access_token": "Token d'acc\u00e9s" }, - "description": "Vols configurar {bond_id}?" + "description": "Vols configurar {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/et.json b/homeassistant/components/bond/translations/et.json index dc6a8414bce..5e9a8e4493f 100644 --- a/homeassistant/components/bond/translations/et.json +++ b/homeassistant/components/bond/translations/et.json @@ -9,13 +9,13 @@ "old_firmware": "Bondi seadme ei toeta vana p\u00fcsivara - uuenda enne j\u00e4tkamist", "unknown": "Tundmatu viga" }, - "flow_title": "Bond: {bond_id} ( {host} )", + "flow_title": "Bond: {name} ( {host} )", "step": { "confirm": { "data": { "access_token": "Juurdep\u00e4\u00e4sut\u00f5end" }, - "description": "Kas soovid seadistada teenuse {bond_id} ?" + "description": "Kas soovid seadistada teenust {name} ?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/fr.json b/homeassistant/components/bond/translations/fr.json index 496a21339cb..d9eb14b1a62 100644 --- a/homeassistant/components/bond/translations/fr.json +++ b/homeassistant/components/bond/translations/fr.json @@ -9,13 +9,13 @@ "old_firmware": "Ancien micrologiciel non pris en charge sur l'appareil Bond - veuillez mettre \u00e0 niveau avant de continuer", "unknown": "Erreur inattendue" }, - "flow_title": "Bond : {bond_id} ({h\u00f4te})", + "flow_title": "Lien : {name} ({host})", "step": { "confirm": { "data": { "access_token": "Jeton d'acc\u00e8s" }, - "description": "Voulez-vous configurer {bond_id} ?" + "description": "Voulez-vous configurer {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/nl.json b/homeassistant/components/bond/translations/nl.json index 8010dfc2e78..b5d8c593ea9 100644 --- a/homeassistant/components/bond/translations/nl.json +++ b/homeassistant/components/bond/translations/nl.json @@ -12,6 +12,7 @@ "step": { "user": { "data": { + "access_token": "Toegangstoken", "host": "Host" } } diff --git a/homeassistant/components/bond/translations/no.json b/homeassistant/components/bond/translations/no.json index 01ff745eed3..c09b7a17635 100644 --- a/homeassistant/components/bond/translations/no.json +++ b/homeassistant/components/bond/translations/no.json @@ -9,13 +9,13 @@ "old_firmware": "Gammel fastvare som ikke st\u00f8ttes p\u00e5 Bond-enheten \u2013 vennligst oppgrader f\u00f8r du fortsetter", "unknown": "Uventet feil" }, - "flow_title": "", + "flow_title": "Obligasjon: {name} ({host})", "step": { "confirm": { "data": { "access_token": "Tilgangstoken" }, - "description": "Vil du konfigurere {bond_id}?" + "description": "Vil du konfigurere {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/pl.json b/homeassistant/components/bond/translations/pl.json index c50c270b74c..6f5f2d276ff 100644 --- a/homeassistant/components/bond/translations/pl.json +++ b/homeassistant/components/bond/translations/pl.json @@ -9,13 +9,13 @@ "old_firmware": "Stare, nieobs\u0142ugiwane oprogramowanie na urz\u0105dzeniu Bond - zaktualizuj przed kontynuowaniem", "unknown": "Nieoczekiwany b\u0142\u0105d" }, - "flow_title": "Bond: {bond_id} ({host})", + "flow_title": "Bond: {name} ({host})", "step": { "confirm": { "data": { "access_token": "Token dost\u0119pu" }, - "description": "Czy chcesz skonfigurowa\u0107 {bond_id}?" + "description": "Czy chcesz skonfigurowa\u0107 {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/ru.json b/homeassistant/components/bond/translations/ru.json index e6c4067d8ac..cdc37fc27f7 100644 --- a/homeassistant/components/bond/translations/ru.json +++ b/homeassistant/components/bond/translations/ru.json @@ -9,13 +9,13 @@ "old_firmware": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u043f\u0440\u043e\u0448\u0438\u0432\u043a\u0443 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0443\u0441\u0442\u0430\u0440\u0435\u043b\u0430 \u0438 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0435\u0439.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, - "flow_title": "Bond {bond_id} ({host})", + "flow_title": "Bond: {name} ({host})", "step": { "confirm": { "data": { "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430" }, - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {bond_id}?" + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/zh-Hant.json b/homeassistant/components/bond/translations/zh-Hant.json index af652c54509..1c5327dc662 100644 --- a/homeassistant/components/bond/translations/zh-Hant.json +++ b/homeassistant/components/bond/translations/zh-Hant.json @@ -9,13 +9,13 @@ "old_firmware": "Bond \u88dd\u7f6e\u4f7f\u7528\u4e0d\u652f\u63f4\u7684\u820a\u7248\u672c\u97cc\u9ad4 - \u8acb\u66f4\u65b0\u5f8c\u518d\u7e7c\u7e8c", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, - "flow_title": "Bond\uff1a{bond_id} ({host})", + "flow_title": "Bond\uff1a{name} ({host})", "step": { "confirm": { "data": { "access_token": "\u5b58\u53d6\u5bc6\u9470" }, - "description": "\u662f\u5426\u8981\u8a2d\u5b9a {bond_id}\uff1f" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f" }, "user": { "data": { diff --git a/homeassistant/components/broadlink/translations/nl.json b/homeassistant/components/broadlink/translations/nl.json index 2f3a7313f75..7f85335d7bb 100644 --- a/homeassistant/components/broadlink/translations/nl.json +++ b/homeassistant/components/broadlink/translations/nl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kon niet verbinden", "invalid_host": "Ongeldige hostnaam of IP-adres", "not_supported": "Apparaat wordt niet ondersteund", diff --git a/homeassistant/components/climacell/translations/af.json b/homeassistant/components/climacell/translations/af.json new file mode 100644 index 00000000000..b62fc7023a4 --- /dev/null +++ b/homeassistant/components/climacell/translations/af.json @@ -0,0 +1,10 @@ +{ + "options": { + "step": { + "init": { + "title": "Update ClimaCell opties" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/ca.json b/homeassistant/components/climacell/translations/ca.json new file mode 100644 index 00000000000..23afb6a3d90 --- /dev/null +++ b/homeassistant/components/climacell/translations/ca.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_api_key": "Clau API inv\u00e0lida", + "rate_limited": "Freq\u00fc\u00e8ncia limitada temporalment, torna-ho a provar m\u00e9s tard.", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "api_key": "Clau API", + "latitude": "Latitud", + "longitude": "Longitud", + "name": "Nom" + }, + "description": "Si no es proporcionen la Latitud i Longitud, s'utilitzaran els valors per defecte de la configuraci\u00f3 de Home Assistant. Es crear\u00e0 una entitat per a cada tipus de previsi\u00f3, per\u00f2 nom\u00e9s s'habilitaran les que seleccionis." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Tipus de previsi\u00f3", + "timestep": "Minuts entre previsions NowCast" + }, + "description": "Si decideixes activar l'entitat de predicci\u00f3 \"nowcast\", podr\u00e0s configurar l'interval en minuts entre cada previsi\u00f3. El nombre de previsions proporcionades dep\u00e8n d'aquest interval de minuts.", + "title": "Actualitzaci\u00f3 de les opcions de ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/en.json b/homeassistant/components/climacell/translations/en.json new file mode 100644 index 00000000000..ed3ead421e1 --- /dev/null +++ b/homeassistant/components/climacell/translations/en.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "Failed to connect", + "invalid_api_key": "Invalid API key", + "rate_limited": "Currently rate limited, please try again later.", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "api_key": "API Key", + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Name" + }, + "description": "If Latitude and Longitude are not provided, the default values in the Home Assistant configuration will be used. An entity will be created for each forecast type but only the ones you select will be enabled by default." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Forecast Type(s)", + "timestep": "Min. Between NowCast Forecasts" + }, + "description": "If you choose to enable the `nowcast` forecast entity, you can configure the number of minutes between each forecast. The number of forecasts provided depends on the number of minutes chosen between forecasts.", + "title": "Update ClimaCell Options" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/et.json b/homeassistant/components/climacell/translations/et.json new file mode 100644 index 00000000000..3722c258afa --- /dev/null +++ b/homeassistant/components/climacell/translations/et.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_api_key": "Vale API v\u00f5ti", + "rate_limited": "Hetkel on p\u00e4ringud piiratud, proovi hiljem uuesti.", + "unknown": "Tundmatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "api_key": "API v\u00f5ti", + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad", + "name": "Nimi" + }, + "description": "Kui [%key:component::climacell::config::step::user::d ata::latitude%] ja [%key:component::climacell::config::step::user::d ata::longitude%] andmed pole sisestatud kasutatakse Home Assistanti vaikev\u00e4\u00e4rtusi. Olem luuakse iga prognoosit\u00fc\u00fcbi jaoks kuid vaikimisi lubatakse ainult need, mille valid." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Prognoosi t\u00fc\u00fcp (t\u00fc\u00fcbid)", + "timestep": "Minuteid NowCasti prognooside vahel" + }, + "description": "Kui otsustad lubada \"nowcast\" prognoosi\u00fcksuse, saad seadistada minutite arvu iga prognoosi vahel. Esitatavate prognooside arv s\u00f5ltub prognooside vahel valitud minutite arvust.", + "title": "V\u00e4rskenda ClimaCell suvandeid" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/fr.json b/homeassistant/components/climacell/translations/fr.json new file mode 100644 index 00000000000..8fd3f7b7122 --- /dev/null +++ b/homeassistant/components/climacell/translations/fr.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_api_key": "Cl\u00e9 API invalide", + "rate_limited": "Currently rate limited, please try again later.", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "api_key": "Cl\u00e9 d'API", + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Nom" + }, + "description": "Si Latitude et Longitude ne sont pas fournis, les valeurs par d\u00e9faut de la configuration de Home Assistant seront utilis\u00e9es. Une entit\u00e9 sera cr\u00e9\u00e9e pour chaque type de pr\u00e9vision, mais seules celles que vous s\u00e9lectionnez seront activ\u00e9es par d\u00e9faut." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Type(s) de pr\u00e9vision", + "timestep": "Min. Entre les pr\u00e9visions NowCast" + }, + "description": "Si vous choisissez d'activer l'entit\u00e9 de pr\u00e9vision \u00abnowcast\u00bb, vous pouvez configurer le nombre de minutes entre chaque pr\u00e9vision. Le nombre de pr\u00e9visions fournies d\u00e9pend du nombre de minutes choisies entre les pr\u00e9visions.", + "title": "Mettre \u00e0 jour les options de ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/nl.json b/homeassistant/components/climacell/translations/nl.json new file mode 100644 index 00000000000..488a43ae24e --- /dev/null +++ b/homeassistant/components/climacell/translations/nl.json @@ -0,0 +1,32 @@ +{ + "config": { + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_api_key": "Ongeldige API-sleutel", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "api_key": "API-sleutel", + "latitude": "Breedtegraad", + "longitude": "Lengtegraad", + "name": "Naam" + }, + "description": "Indien Breedtegraad en Lengtegraad niet worden opgegeven, worden de standaardwaarden in de Home Assistant-configuratie gebruikt. Er wordt een entiteit gemaakt voor elk voorspellingstype maar alleen degene die u selecteert worden standaard ingeschakeld." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Voorspellingstype(n)" + }, + "description": "Als u ervoor kiest om de `nowcast` voorspellingsentiteit in te schakelen, kan u het aantal minuten tussen elke voorspelling configureren. Het aantal voorspellingen hangt af van het aantal gekozen minuten tussen de voorspellingen.", + "title": "Update ClimaCell Opties" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/no.json b/homeassistant/components/climacell/translations/no.json new file mode 100644 index 00000000000..64845ff7697 --- /dev/null +++ b/homeassistant/components/climacell/translations/no.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_api_key": "Ugyldig API-n\u00f8kkel", + "rate_limited": "Prisen er for \u00f8yeblikket begrenset. Pr\u00f8v igjen senere.", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "api_key": "API-n\u00f8kkel", + "latitude": "Breddegrad", + "longitude": "Lengdegrad", + "name": "Navn" + }, + "description": "Hvis Breddegrad and Lengdegrad er ikke gitt, vil standardverdiene i Home Assistant-konfigurasjonen bli brukt. Det blir opprettet en enhet for hver prognosetype, men bare de du velger blir aktivert som standard." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Prognosetype(r)", + "timestep": "Min. Mellom NowCast Prognoser" + }, + "description": "Hvis du velger \u00e5 aktivere \u00abnowcast\u00bb -varselenheten, kan du konfigurere antall minutter mellom hver prognose. Antall angitte prognoser avhenger av antall minutter som er valgt mellom prognosene.", + "title": "Oppdater ClimaCell Alternativer" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/ru.json b/homeassistant/components/climacell/translations/ru.json new file mode 100644 index 00000000000..2cce63d95ea --- /dev/null +++ b/homeassistant/components/climacell/translations/ru.json @@ -0,0 +1,34 @@ +{ + "config": { + "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_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API.", + "rate_limited": "\u041f\u0440\u0435\u0432\u044b\u0448\u0435\u043d\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u043f\u044b\u0442\u043e\u043a, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" + }, + "description": "\u0415\u0441\u043b\u0438 \u0428\u0438\u0440\u043e\u0442\u0430 \u0438 \u0414\u043e\u043b\u0433\u043e\u0442\u0430 \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u044b, \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0431\u0443\u0434\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 Home Assistant. \u041e\u0431\u044a\u0435\u043a\u0442\u044b \u0431\u0443\u0434\u0443\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u044b \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0442\u0438\u043f\u0430 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430, \u043d\u043e \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0431\u0443\u0434\u0443\u0442 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0435 \u0412\u0430\u043c\u0438." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "\u0422\u0438\u043f(\u044b) \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430", + "timestep": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f (\u0432 \u043c\u0438\u043d\u0443\u0442\u0430\u0445)" + }, + "description": "\u0415\u0441\u043b\u0438 \u0412\u044b \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0443\u0435\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430 'nowcast', \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430.", + "title": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/daikin/translations/nl.json b/homeassistant/components/daikin/translations/nl.json index 69d52436beb..e4cf54eb365 100644 --- a/homeassistant/components/daikin/translations/nl.json +++ b/homeassistant/components/daikin/translations/nl.json @@ -5,7 +5,9 @@ "cannot_connect": "Kon niet verbinden" }, "error": { - "invalid_auth": "Ongeldige authenticatie" + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" }, "step": { "user": { diff --git a/homeassistant/components/enocean/translations/nl.json b/homeassistant/components/enocean/translations/nl.json new file mode 100644 index 00000000000..79aaec23123 --- /dev/null +++ b/homeassistant/components/enocean/translations/nl.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/en.json b/homeassistant/components/faa_delays/translations/en.json index 48e9e1c8993..e78b15c68cb 100644 --- a/homeassistant/components/faa_delays/translations/en.json +++ b/homeassistant/components/faa_delays/translations/en.json @@ -4,16 +4,18 @@ "already_configured": "This airport is already configured." }, "error": { - "invalid_airport": "Airport code is not valid" + "cannot_connect": "Failed to connect", + "invalid_airport": "Airport code is not valid", + "unknown": "Unexpected error" }, "step": { "user": { - "title": "FAA Delays", - "description": "Enter a US Airport Code in IATA Format", "data": { "id": "Airport" - } + }, + "description": "Enter a US Airport Code in IATA Format", + "title": "FAA Delays" } } } -} +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/fr.json b/homeassistant/components/faa_delays/translations/fr.json new file mode 100644 index 00000000000..996a22c8422 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/fr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Cet a\u00e9roport est d\u00e9j\u00e0 configur\u00e9." + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_airport": "Le code de l'a\u00e9roport n'est pas valide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "id": "A\u00e9roport" + }, + "description": "Entrez un code d'a\u00e9roport am\u00e9ricain au format IATA", + "title": "D\u00e9lais FAA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/nl.json b/homeassistant/components/faa_delays/translations/nl.json new file mode 100644 index 00000000000..3dbc55f5b1b --- /dev/null +++ b/homeassistant/components/faa_delays/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Deze luchthaven is al geconfigureerd." + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_airport": "Luchthavencode is ongeldig", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "id": "Luchthaven" + }, + "description": "Voer een Amerikaanse luchthavencode in IATA-indeling in", + "title": "FAA-vertragingen" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fireservicerota/translations/nl.json b/homeassistant/components/fireservicerota/translations/nl.json index 7289d53e71f..3a6ba936dee 100644 --- a/homeassistant/components/fireservicerota/translations/nl.json +++ b/homeassistant/components/fireservicerota/translations/nl.json @@ -14,7 +14,8 @@ "reauth": { "data": { "password": "Wachtwoord" - } + }, + "description": "Authenticatietokens zijn ongeldig geworden, log in om ze opnieuw te maken." }, "user": { "data": { diff --git a/homeassistant/components/fireservicerota/translations/no.json b/homeassistant/components/fireservicerota/translations/no.json index af1ceba2c97..be485577e65 100644 --- a/homeassistant/components/fireservicerota/translations/no.json +++ b/homeassistant/components/fireservicerota/translations/no.json @@ -15,7 +15,7 @@ "data": { "password": "Passord" }, - "description": "Godkjenningstokener ble ugyldige, logg inn for \u00e5 gjenopprette dem" + "description": "Autentiseringstokener ble ugyldige, logg inn for \u00e5 gjenskape dem." }, "user": { "data": { diff --git a/homeassistant/components/firmata/translations/nl.json b/homeassistant/components/firmata/translations/nl.json new file mode 100644 index 00000000000..7cb0141826a --- /dev/null +++ b/homeassistant/components/firmata/translations/nl.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cannot_connect": "Kan geen verbinding maken" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/nl.json b/homeassistant/components/flunearyou/translations/nl.json index c63a59e18e7..0ff044abc5e 100644 --- a/homeassistant/components/flunearyou/translations/nl.json +++ b/homeassistant/components/flunearyou/translations/nl.json @@ -3,6 +3,9 @@ "abort": { "already_configured": "Deze co\u00f6rdinaten zijn al geregistreerd." }, + "error": { + "unknown": "Onverwachte fout" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/fritzbox/translations/nl.json b/homeassistant/components/fritzbox/translations/nl.json index 71a80dbd577..9bfe2ef6be6 100644 --- a/homeassistant/components/fritzbox/translations/nl.json +++ b/homeassistant/components/fritzbox/translations/nl.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Deze AVM FRITZ!Box is al geconfigureerd.", "already_in_progress": "AVM FRITZ!Box configuratie is al bezig.", + "no_devices_found": "Geen apparaten gevonden op het netwerk", "not_supported": "Verbonden met AVM FRITZ! Box, maar het kan geen Smart Home-apparaten bedienen.", "reauth_successful": "Herauthenticatie was succesvol" }, diff --git a/homeassistant/components/goalzero/translations/nl.json b/homeassistant/components/goalzero/translations/nl.json index 86958670d70..4d9b5a397dd 100644 --- a/homeassistant/components/goalzero/translations/nl.json +++ b/homeassistant/components/goalzero/translations/nl.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "host": "Host", "name": "Naam" }, "description": "Eerst moet u de Goal Zero-app downloaden: https://www.goalzero.com/product-features/yeti-app/ \n\n Volg de instructies om je Yeti te verbinden met je wifi-netwerk. Haal dan de host-ip van uw router. DHCP moet zijn ingesteld in uw routerinstellingen voor het apparaat om ervoor te zorgen dat het host-ip niet verandert. Raadpleeg de gebruikershandleiding van uw router." diff --git a/homeassistant/components/home_connect/translations/nl.json b/homeassistant/components/home_connect/translations/nl.json index 41b27cc387f..25a81209607 100644 --- a/homeassistant/components/home_connect/translations/nl.json +++ b/homeassistant/components/home_connect/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "missing_configuration": "Het component is niet geconfigureerd. Volg de documentatie." + "missing_configuration": "Het component is niet geconfigureerd. Volg de documentatie.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" }, "create_entry": { "default": "Succesvol geverifieerd" diff --git a/homeassistant/components/homekit/translations/ca.json b/homeassistant/components/homekit/translations/ca.json index 0870b05a6d1..dbd83622d8a 100644 --- a/homeassistant/components/homekit/translations/ca.json +++ b/homeassistant/components/homekit/translations/ca.json @@ -19,7 +19,7 @@ "title": "Selecciona els dominis a incloure" }, "pairing": { - "description": "Tan aviat com {name} estigui llest, la vinculaci\u00f3 estar\u00e0 disponible a \"Notificacions\" com a \"Configuraci\u00f3 de l'enlla\u00e7 HomeKit\".", + "description": "Per completar la vinculaci\u00f3, segueix les instruccions a \"Configuraci\u00f3 de l'enlla\u00e7 HomeKit\" sota \"Notificacions\".", "title": "Vinculaci\u00f3 HomeKit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "Dominis a incloure", "mode": "Mode" }, - "description": "La integraci\u00f3 HomeKit et permetr\u00e0 l'acc\u00e9s a les teves entitats de Home Assistant a HomeKit. En mode enlla\u00e7, els enlla\u00e7os HomeKit estan limitats a un m\u00e0xim de 150 accessoris per inst\u00e0ncia (incl\u00f2s el propi enlla\u00e7). Si volguessis enlla\u00e7ar m\u00e9s accessoris que el m\u00e0xim perm\u00e8s, \u00e9s recomanable que utilitzis diferents enlla\u00e7os HomeKit per a dominis diferents. La configuraci\u00f3 avan\u00e7ada d'entitat nom\u00e9s est\u00e0 disponible en YAML. Per obtenir el millor rendiment i evitar errors de disponibilitat inesperats , crea i vincula una inst\u00e0ncia HomeKit en mode accessori per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", - "title": "Activaci\u00f3 de HomeKit" + "description": "Selecciona els dominis a incloure. S'inclouran totes les entitats del domini compatibles. Es crear\u00e0 una inst\u00e0ncia HomeKit en mode accessori per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", + "title": "Selecciona els dominis a incloure" } } }, @@ -55,7 +55,7 @@ "entities": "Entitats", "mode": "Mode" }, - "description": "Tria les entitats que vulguis incloure. En mode accessori, nom\u00e9s s'inclou una sola entitat. En mode enlla\u00e7 inclusiu, s'exposaran totes les entitats del domini tret de que se'n seleccionin algunes en concret. En mode enlla\u00e7 excusiu, s'inclouran totes les entitats del domini excepte les entitats excloses. Per obtenir el millor rendiment i evitar errors de disponibilitat inesperats , crea i vincula una inst\u00e0ncia HomeKit en mode accessori per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", + "description": "Tria les entitats que vulguis incloure. En mode accessori, nom\u00e9s s'inclou una sola entitat. En mode enlla\u00e7 inclusiu, s'exposaran totes les entitats del domini tret de que se'n seleccionin algunes en concret. En mode enlla\u00e7 excusiu, s'inclouran totes les entitats del domini excepte les entitats excloses. Per obtenir el millor rendiment, es crea una inst\u00e0ncia HomeKit per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", "title": "Selecciona les entitats a incloure" }, "init": { diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json index e9aaeb60df8..3b0129567c4 100644 --- a/homeassistant/components/homekit/translations/en.json +++ b/homeassistant/components/homekit/translations/en.json @@ -4,13 +4,29 @@ "port_name_in_use": "An accessory or bridge with the same name or port is already configured." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Entity" + }, + "description": "Choose the entity to be included. In accessory mode, only a single entity is included.", + "title": "Select entity to be included" + }, + "bridge_mode": { + "data": { + "include_domains": "Domains to include" + }, + "description": "Choose the domains to be included. All supported entities in the domain will be included.", + "title": "Select domains to be included" + }, "pairing": { "description": "To complete pairing following the instructions in \u201cNotifications\u201d under \u201cHomeKit Pairing\u201d.", "title": "Pair HomeKit" }, "user": { "data": { - "include_domains": "Domains to include" + "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", + "include_domains": "Domains to include", + "mode": "Mode" }, "description": "Choose the domains to be included. All supported entities in the domain will be included. A separate HomeKit instance in accessory mode will be created for each tv media player and camera.", "title": "Select domains to be included" @@ -21,7 +37,8 @@ "step": { "advanced": { "data": { - "auto_start": "Autostart (disable if you are calling the homekit.start service manually)" + "auto_start": "Autostart (disable if you are calling the homekit.start service manually)", + "safe_mode": "Safe Mode (enable only if pairing fails)" }, "description": "These settings only need to be adjusted if HomeKit is not functional.", "title": "Advanced Configuration" diff --git a/homeassistant/components/homekit/translations/et.json b/homeassistant/components/homekit/translations/et.json index 37bff5f9b70..8c24d2d2251 100644 --- a/homeassistant/components/homekit/translations/et.json +++ b/homeassistant/components/homekit/translations/et.json @@ -19,7 +19,7 @@ "title": "Vali kaasatavad domeenid" }, "pairing": { - "description": "Niipea kui {name} on valmis, on sidumine saadaval jaotises \"Notifications\" kui \"HomeKit Bridge Setup\".", + "description": "Sidumise l\u00f5puleviimiseks j\u00e4rgi jaotises \"HomeKiti sidumine\" toodud juhiseid alajaotises \"Teatised\".", "title": "HomeKiti sidumine" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "Kaasatavad domeenid", "mode": "Re\u017eiim" }, - "description": "HomeKiti integreerimine v\u00f5imaldab teil p\u00e4\u00e4seda juurde HomeKiti \u00fcksustele Home Assistant. Sildire\u017eiimis on HomeKit Bridges piiratud 150 lisaseadmega, sealhulgas sild ise. Kui soovid \u00fchendada rohkem lisatarvikuid, on soovitatav kasutada erinevate domeenide jaoks mitut HomeKiti silda. \u00dcksuse \u00fcksikasjalik konfiguratsioon on esmase silla jaoks saadaval ainult YAML-i kaudu. Parema tulemuse saavutamiseks ja ootamatute seadmete kadumise v\u00e4ltimiseks loo ja seo eraldi HomeKiti seade tarviku re\u017eiimis kga meediaesitaja ja kaamera jaoks.", - "title": "Aktiveeri HomeKit" + "description": "Vali kaasatavad domeenid. Kaasatakse k\u00f5ik domeenis toetatud olemid. Iga telemeedia pleieri ja kaamera jaoks luuakse eraldi HomeKiti eksemplar tarvikure\u017eiimis.", + "title": "Vali kaasatavad domeenid" } } }, @@ -55,12 +55,12 @@ "entities": "Olemid", "mode": "Re\u017eiim" }, - "description": "Vali kaasatavad olemid. Tarvikute re\u017eiimis on kaasatav ainult \u00fcks olem. Silla re\u017eiimis, kuvatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud juhul, kui valitud on kindlad olemid. Silla v\u00e4listamisre\u017eiimis kaasatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud v\u00e4listatud olemid.", + "description": "Vali kaasatavad olemid. Tarvikute re\u017eiimis on kaasatav ainult \u00fcks olem. Silla re\u017eiimis, kuvatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud juhul, kui valitud on kindlad olemid. Silla v\u00e4listamisre\u017eiimis kaasatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud v\u00e4listatud olemid. Parima kasutuskogemuse jaoks on eraldi HomeKit seadmed iga meediumim\u00e4ngija ja kaamera jaoks.", "title": "Vali kaasatavd olemid" }, "init": { "data": { - "include_domains": "Kaasatavad domeenid", + "include_domains": "Kaasatud domeenid", "mode": "Re\u017eiim" }, "description": "HomeKiti saab seadistada silla v\u00f5i \u00fche lisaseadme avaldamiseks. Lisare\u017eiimis saab kasutada ainult \u00fchte \u00fcksust. Teleriseadmete klassiga meediumipleierite n\u00f5uetekohaseks toimimiseks on vaja lisare\u017eiimi. \u201eKaasatavate domeenide\u201d \u00fcksused puutuvad kokku HomeKitiga. J\u00e4rgmisel ekraanil saad valida, millised \u00fcksused sellesse loendisse lisada v\u00f5i sellest v\u00e4lja j\u00e4tta.", diff --git a/homeassistant/components/homekit/translations/fr.json b/homeassistant/components/homekit/translations/fr.json index a0f10c9684a..4721514e615 100644 --- a/homeassistant/components/homekit/translations/fr.json +++ b/homeassistant/components/homekit/translations/fr.json @@ -19,7 +19,7 @@ "title": "S\u00e9lectionnez les domaines \u00e0 inclure" }, "pairing": { - "description": "D\u00e8s que le pont {name} est pr\u00eat, l'appairage sera disponible dans \"Notifications\" sous \"Configuration de la Passerelle HomeKit\".", + "description": "Pour compl\u00e9ter l'appariement, suivez les instructions dans les \"Notifications\" sous \"Appariement HomeKit\".", "title": "Appairage de la Passerelle Homekit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "Domaines \u00e0 inclure", "mode": "Mode" }, - "description": "La passerelle HomeKit vous permettra d'acc\u00e9der \u00e0 vos entit\u00e9s Home Assistant dans HomeKit. Les passerelles HomeKit sont limit\u00e9es \u00e0 150 accessoires par instance, y compris la passerelle elle-m\u00eame. Si vous souhaitez connecter plus que le nombre maximum d'accessoires, il est recommand\u00e9 d'utiliser plusieurs passerelles HomeKit pour diff\u00e9rents domaines. La configuration d\u00e9taill\u00e9e des entit\u00e9s est uniquement disponible via YAML pour la passerelle principale.", - "title": "Activer la Passerelle HomeKit" + "description": "Choisissez les domaines \u00e0 inclure. Toutes les entit\u00e9s prises en charge dans le domaine seront incluses. Une instance HomeKit distincte en mode accessoire sera cr\u00e9\u00e9e pour chaque lecteur multim\u00e9dia TV et cam\u00e9ra.", + "title": "S\u00e9lectionnez les domaines \u00e0 inclure" } } }, @@ -60,7 +60,7 @@ }, "init": { "data": { - "include_domains": "Domaine \u00e0 inclure", + "include_domains": "Domaines \u00e0 inclure", "mode": "Mode" }, "description": "Les entit\u00e9s des \u00abdomaines \u00e0 inclure\u00bb seront pont\u00e9es vers HomeKit. Vous pourrez s\u00e9lectionner les entit\u00e9s \u00e0 exclure de cette liste sur l'\u00e9cran suivant.", diff --git a/homeassistant/components/homekit/translations/no.json b/homeassistant/components/homekit/translations/no.json index 9a64def4156..4748fe63af2 100644 --- a/homeassistant/components/homekit/translations/no.json +++ b/homeassistant/components/homekit/translations/no.json @@ -19,7 +19,7 @@ "title": "Velg domener som skal inkluderes" }, "pairing": { - "description": "S\u00e5 snart {name} er klart, vil sammenkobling v\u00e6re tilgjengelig i \"Notifications\" som \"HomeKit Bridge Setup\".", + "description": "For \u00e5 fullf\u00f8re sammenkoblingen ved \u00e5 f\u00f8lge instruksjonene i \"Varsler\" under \"Sammenkobling av HomeKit\".", "title": "Koble sammen HomeKit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "Domener \u00e5 inkludere", "mode": "Modus" }, - "description": "HomeKit-integrasjonen gir deg tilgang til Home Assistant-enhetene dine i HomeKit. I bromodus er HomeKit Bridges begrenset til 150 tilbeh\u00f8r per forekomst inkludert selve broen. Hvis du \u00f8nsker \u00e5 bygge bro over maksimalt antall tilbeh\u00f8r, anbefales det at du bruker flere HomeKit-broer for forskjellige domener. Detaljert enhetskonfigurasjon er bare tilgjengelig via YAML. For best ytelse og for \u00e5 forhindre uventet utilgjengelighet, opprett og par sammen en egen HomeKit-forekomst i tilbeh\u00f8rsmodus for hver tv-mediaspiller og kamera.", - "title": "Aktiver HomeKit" + "description": "Velg domenene som skal inkluderes. Alle st\u00f8ttede enheter i domenet vil bli inkludert. Det opprettes en egen HomeKit-forekomst i tilbeh\u00f8rsmodus for hver tv-mediaspiller og kamera.", + "title": "Velg domener som skal inkluderes" } } }, @@ -55,7 +55,7 @@ "entities": "Entiteter", "mode": "Modus" }, - "description": "Velg enhetene som skal inkluderes. I tilbeh\u00f8rsmodus er bare \u00e9n enkelt enhet inkludert. I bridge include-modus inkluderes alle enheter i domenet med mindre bestemte enheter er valgt. I brounnlatingsmodus inkluderes alle enheter i domenet, med unntak av de utelatte enhetene. For best mulig ytelse, og for \u00e5 forhindre uventet utilgjengelighet, opprett og par en separat HomeKit-forekomst i tilbeh\u00f8rsmodus for hver tv-mediespiller og kamera.", + "description": "Velg enhetene som skal inkluderes. I tilbeh\u00f8rsmodus er bare en enkelt enhet inkludert. I bridge-inkluderingsmodus vil alle enheter i domenet bli inkludert, med mindre spesifikke enheter er valgt. I bridge-ekskluderingsmodus vil alle enheter i domenet bli inkludert, bortsett fra de ekskluderte enhetene. For best ytelse vil et eget HomeKit-tilbeh\u00f8r v\u00e6re TV-mediaspiller og kamera.", "title": "Velg enheter som skal inkluderes" }, "init": { diff --git a/homeassistant/components/homekit/translations/pl.json b/homeassistant/components/homekit/translations/pl.json index 2679a4de20a..ef35ff667c4 100644 --- a/homeassistant/components/homekit/translations/pl.json +++ b/homeassistant/components/homekit/translations/pl.json @@ -19,7 +19,7 @@ "title": "Wybierz uwzgl\u0119dniane domeny" }, "pairing": { - "description": "Gdy tylko {name} b\u0119dzie gotowy, opcja parowania b\u0119dzie dost\u0119pna w \u201ePowiadomieniach\u201d jako \u201eKonfiguracja mostka HomeKit\u201d.", + "description": "Aby doko\u0144czy\u0107 parowanie, post\u0119puj wg instrukcji \u201eParowanie HomeKit\u201d w \u201ePowiadomieniach\u201d.", "title": "Parowanie z HomeKit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "Domeny do uwzgl\u0119dnienia", "mode": "Tryb" }, - "description": "Integracja HomeKit pozwala na dost\u0119p do Twoich encji Home Assistant w HomeKit. W trybie \"Mostka\", mostki HomeKit s\u0105 ograniczone do 150 urz\u0105dze\u0144, w\u0142\u0105czaj\u0105c w to sam mostek. Je\u015bli chcesz wi\u0119cej ni\u017c dozwolona maksymalna liczba urz\u0105dze\u0144, zaleca si\u0119 u\u017cywanie wielu most\u00f3w HomeKit dla r\u00f3\u017cnych domen. Szczeg\u00f3\u0142owa konfiguracja encji jest dost\u0119pna tylko w trybie YAML dla g\u0142\u00f3wnego mostka. Dla najlepszej wydajno\u015bci oraz by zapobiec nieprzewidzianej niedost\u0119pno\u015bci urz\u0105dzenia, utw\u00f3rz i sparuj oddzieln\u0105 instancj\u0119 HomeKit w trybie akcesorium dla ka\u017cdego media playera oraz kamery.", - "title": "Aktywacja HomeKit" + "description": "Wybierz domeny do uwzgl\u0119dnienia. Wszystkie wspierane encje w danej domenie b\u0119d\u0105 uwzgl\u0119dnione. W trybie akcesorium, oddzielna instancja HomeKit zostanie utworzona dla ka\u017cdego tv media playera oraz kamery.", + "title": "Wybierz uwzgl\u0119dniane domeny" } } }, @@ -55,7 +55,7 @@ "entities": "Encje", "mode": "Tryb" }, - "description": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 uwzgl\u0119dnione. W trybie \"Akcesorium\" tylko jedna encja jest uwzgl\u0119dniona. W trybie \"Uwzgl\u0119dnij mostek\", wszystkie encje w danej domenie b\u0119d\u0105 uwzgl\u0119dnione, chyba \u017ce wybrane s\u0105 tylko konkretne encje. W trybie \"Wyklucz mostek\", wszystkie encje b\u0119d\u0105 uwzgl\u0119dnione, z wyj\u0105tkiem tych wybranych. Dla najlepszej wydajno\u015bci oraz by zapobiec nieprzewidzianej niedost\u0119pno\u015bci urz\u0105dzenia, utw\u00f3rz i sparuj oddzieln\u0105 instancj\u0119 HomeKit w trybie akcesorium dla ka\u017cdego media playera oraz kamery.", + "description": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 uwzgl\u0119dnione. W trybie \"Akcesorium\" tylko jedna encja jest uwzgl\u0119dniona. W trybie \"Uwzgl\u0119dnij mostek\", wszystkie encje w danej domenie b\u0119d\u0105 uwzgl\u0119dnione, chyba \u017ce wybrane s\u0105 tylko konkretne encje. W trybie \"Wyklucz mostek\", wszystkie encje b\u0119d\u0105 uwzgl\u0119dnione, z wyj\u0105tkiem tych wybranych. Dla najlepszej wydajno\u015bci, zostanie utworzone oddzielne akcesorium HomeKit dla ka\u017cdego tv media playera oraz kamery.", "title": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 uwzgl\u0119dnione" }, "init": { diff --git a/homeassistant/components/homekit/translations/ru.json b/homeassistant/components/homekit/translations/ru.json index 84346aed2ef..d00744e4cb4 100644 --- a/homeassistant/components/homekit/translations/ru.json +++ b/homeassistant/components/homekit/translations/ru.json @@ -19,7 +19,7 @@ "title": "\u0412\u044b\u0431\u043e\u0440 \u0434\u043e\u043c\u0435\u043d\u043e\u0432 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit" }, "pairing": { - "description": "\u041a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e {name} \u0431\u0443\u0434\u0435\u0442 \u0433\u043e\u0442\u043e\u0432\u043e, \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e \u0432 \"\u0423\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f\u0445\" \u043a\u0430\u043a \"HomeKit Bridge Setup\".", + "description": "\u0414\u043b\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u044f \u0441\u043b\u0435\u0434\u0443\u0439\u0442\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u043c \u0432 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0438 \"HomeKit Pairing\".", "title": "\u0421\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0441 HomeKit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u044b", "mode": "\u0420\u0435\u0436\u0438\u043c" }, - "description": "\u042d\u0442\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c Home Assistant \u0447\u0435\u0440\u0435\u0437 HomeKit. HomeKit Bridge \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d 150 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430\u043c\u0438 \u043d\u0430 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0441\u0430\u043c \u0431\u0440\u0438\u0434\u0436. \u0415\u0441\u043b\u0438 \u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0435, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e HomeKit Bridge \u0434\u043b\u044f \u0440\u0430\u0437\u043d\u044b\u0445 \u0434\u043e\u043c\u0435\u043d\u043e\u0432. \u0414\u0435\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0435\u0440\u0435\u0437 YAML. \u0414\u043b\u044f \u043b\u0443\u0447\u0448\u0435\u0439 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043d\u0435\u0438\u0441\u043f\u0440\u0430\u0432\u043d\u043e\u0441\u0442\u0435\u0439 \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b.", - "title": "HomeKit" + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u043e\u043c\u0435\u043d\u044b. \u0411\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0438\u0437 \u0434\u043e\u043c\u0435\u043d\u0430. \u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430.", + "title": "\u0412\u044b\u0431\u043e\u0440 \u0434\u043e\u043c\u0435\u043d\u043e\u0432 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit" } } }, @@ -55,7 +55,7 @@ "entities": "\u041e\u0431\u044a\u0435\u043a\u0442\u044b", "mode": "\u0420\u0435\u0436\u0438\u043c" }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u043c\u043e\u0441\u0442\u0430 \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u043a\u0440\u043e\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445. \u0414\u043b\u044f \u043b\u0443\u0447\u0448\u0435\u0439 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043d\u0435\u0438\u0441\u043f\u0440\u0430\u0432\u043d\u043e\u0441\u0442\u0435\u0439 \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b.", + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u043c\u043e\u0441\u0442\u0430 \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u043a\u0440\u043e\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445. \u0414\u043b\u044f \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b.", "title": "\u0412\u044b\u0431\u043e\u0440 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit" }, "init": { diff --git a/homeassistant/components/homekit/translations/zh-Hant.json b/homeassistant/components/homekit/translations/zh-Hant.json index 605263c4489..95a0782cf12 100644 --- a/homeassistant/components/homekit/translations/zh-Hant.json +++ b/homeassistant/components/homekit/translations/zh-Hant.json @@ -19,7 +19,7 @@ "title": "\u9078\u64c7\u8981\u5305\u542b\u7684\u7db2\u57df" }, "pairing": { - "description": "\u65bc {name} \u5c31\u7dd2\u5f8c\u3001\u5c07\u6703\u65bc\u300c\u901a\u77e5\u300d\u4e2d\u986f\u793a\u300cHomeKit Bridge \u8a2d\u5b9a\u300d\u7684\u914d\u5c0d\u8cc7\u8a0a\u3002", + "description": "\u6b32\u5b8c\u6210\u914d\u5c0d\u3001\u8acb\u8ddf\u96a8\u300c\u901a\u77e5\u300d\u5167\u7684\u300cHomekit \u914d\u5c0d\u300d\u6307\u5f15\u3002", "title": "\u914d\u5c0d HomeKit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "\u5305\u542b\u7db2\u57df", "mode": "\u6a21\u5f0f" }, - "description": "HomeKit \u6574\u5408\u5c07\u53ef\u5141\u8a31\u65bc Homekit \u4e2d\u4f7f\u7528 Home Assistant \u5be6\u9ad4\u3002\u65bc\u6a4b\u63a5\u6a21\u5f0f\u4e0b\u3001HomeKit Bridges \u6700\u9ad8\u9650\u5236\u70ba 150 \u500b\u914d\u4ef6\u3001\u5305\u542b Bridge \u672c\u8eab\u3002\u5047\u5982\u60f3\u8981\u4f7f\u7528\u8d85\u904e\u9650\u5236\u4ee5\u4e0a\u7684\u914d\u4ef6\uff0c\u5efa\u8b70\u53ef\u4ee5\u4e0d\u540c\u7db2\u57df\u4f7f\u7528\u591a\u500b HomeKit bridges \u9054\u5230\u6b64\u9700\u6c42\u3002\u50c5\u80fd\u65bc\u4e3b Bridge \u4ee5 YAML \u8a2d\u5b9a\u8a73\u7d30\u5be6\u9ad4\u3002\u70ba\u53d6\u5f97\u6700\u4f73\u6548\u80fd\u3001\u4e26\u907f\u514d\u672a\u9810\u671f\u7121\u6cd5\u4f7f\u7528\u72c0\u614b\uff0c\u96fb\u8996\u5a92\u9ad4\u64ad\u653e\u5668\u8207\u651d\u5f71\u6a5f\uff0c\u8acb\u65bc Homekit \u914d\u4ef6\u6a21\u5f0f\u4e2d\u5206\u5225\u9032\u884c\u914d\u5c0d\u3002", - "title": "\u555f\u7528 HomeKit" + "description": "\u9078\u64c7\u6240\u8981\u5305\u542b\u7684\u7db2\u57df\uff0c\u6240\u6709\u8a72\u7db2\u57df\u5167\u652f\u63f4\u7684\u5be6\u9ad4\u90fd\u5c07\u6703\u88ab\u5305\u542b\u3002 \u5176\u4ed6 Homekit \u5a92\u9ad4\u64ad\u653e\u5668\u8207\u651d\u5f71\u6a5f\u5be6\u4f8b\uff0c\u5c07\u6703\u4ee5\u914d\u4ef6\u6a21\u5f0f\u65b0\u589e\u3002", + "title": "\u9078\u64c7\u8981\u5305\u542b\u7684\u7db2\u57df" } } }, @@ -55,7 +55,7 @@ "entities": "\u5be6\u9ad4", "mode": "\u6a21\u5f0f" }, - "description": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4\u3002\u65bc\u914d\u4ef6\u6a21\u5f0f\u4e0b\u3001\u50c5\u6709\u55ae\u4e00\u5be6\u9ad4\u5c07\u6703\u5305\u542b\u3002\u65bc\u6a4b\u63a5\u5305\u542b\u6a21\u5f0f\u4e0b\u3001\u6240\u6709\u7db2\u57df\u7684\u5be6\u9ad4\u90fd\u5c07\u5305\u542b\uff0c\u9664\u975e\u9078\u64c7\u7279\u5b9a\u7684\u5be6\u9ad4\u3002\u65bc\u6a4b\u63a5\u6392\u9664\u6a21\u5f0f\u4e2d\u3001\u6240\u6709\u7db2\u57df\u4e2d\u7684\u5be6\u9ad4\u90fd\u5c07\u5305\u542b\uff0c\u9664\u4e86\u6392\u9664\u7684\u5be6\u9ad4\u3002\u70ba\u53d6\u5f97\u6700\u4f73\u6548\u80fd\u3001\u4e26\u907f\u514d\u672a\u9810\u671f\u7121\u6cd5\u4f7f\u7528\u72c0\u614b\uff0c\u96fb\u8996\u5a92\u9ad4\u64ad\u653e\u5668\u8207\u651d\u5f71\u6a5f\uff0c\u8acb\u65bc Homekit \u914d\u4ef6\u6a21\u5f0f\u4e2d\u5206\u5225\u9032\u884c\u914d\u5c0d\u3002", + "description": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4\u3002\u65bc\u914d\u4ef6\u6a21\u5f0f\u4e0b\u3001\u50c5\u6709\u55ae\u4e00\u5be6\u9ad4\u5c07\u6703\u5305\u542b\u3002\u65bc\u6a4b\u63a5\u5305\u542b\u6a21\u5f0f\u4e0b\u3001\u6240\u6709\u7db2\u57df\u7684\u5be6\u9ad4\u90fd\u5c07\u5305\u542b\uff0c\u9664\u975e\u9078\u64c7\u7279\u5b9a\u7684\u5be6\u9ad4\u3002\u65bc\u6a4b\u63a5\u6392\u9664\u6a21\u5f0f\u4e2d\u3001\u6240\u6709\u7db2\u57df\u4e2d\u7684\u5be6\u9ad4\u90fd\u5c07\u5305\u542b\uff0c\u9664\u4e86\u6392\u9664\u7684\u5be6\u9ad4\u3002\u70ba\u53d6\u5f97\u6700\u4f73\u6548\u80fd\u3001\u96fb\u8996\u5a92\u9ad4\u64ad\u653e\u5668\u8207\u651d\u5f71\u6a5f\uff0c\u5c07\u65bc Homekit \u914d\u4ef6\u6a21\u5f0f\u9032\u884c\u3002", "title": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4" }, "init": { diff --git a/homeassistant/components/huawei_lte/translations/nl.json b/homeassistant/components/huawei_lte/translations/nl.json index dd51fdc1bc5..d420093996c 100644 --- a/homeassistant/components/huawei_lte/translations/nl.json +++ b/homeassistant/components/huawei_lte/translations/nl.json @@ -19,6 +19,7 @@ "user": { "data": { "password": "Wachtwoord", + "url": "URL", "username": "Gebruikersnaam" }, "description": "Voer de toegangsgegevens van het apparaat in. Opgeven van gebruikersnaam en wachtwoord is optioneel, maar biedt ondersteuning voor meer integratiefuncties. Aan de andere kant kan het gebruik van een geautoriseerde verbinding problemen veroorzaken bij het openen van het webinterface van het apparaat buiten de Home Assitant, terwijl de integratie actief is en andersom.", diff --git a/homeassistant/components/icloud/translations/nl.json b/homeassistant/components/icloud/translations/nl.json index 97673069054..b150c8d5b16 100644 --- a/homeassistant/components/icloud/translations/nl.json +++ b/homeassistant/components/icloud/translations/nl.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "Account reeds geconfigureerd", - "no_device": "Op geen van uw apparaten is \"Find my iPhone\" geactiveerd" + "no_device": "Op geen van uw apparaten is \"Find my iPhone\" geactiveerd", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "invalid_auth": "Ongeldige authenticatie", @@ -14,7 +15,8 @@ "data": { "password": "Wachtwoord" }, - "description": "Uw eerder ingevoerde wachtwoord voor {username} werkt niet meer. Update uw wachtwoord om deze integratie te blijven gebruiken." + "description": "Uw eerder ingevoerde wachtwoord voor {username} werkt niet meer. Update uw wachtwoord om deze integratie te blijven gebruiken.", + "title": "Verifieer de integratie opnieuw" }, "trusted_device": { "data": { diff --git a/homeassistant/components/ifttt/translations/nl.json b/homeassistant/components/ifttt/translations/nl.json index e7da47dd658..82006860db3 100644 --- a/homeassistant/components/ifttt/translations/nl.json +++ b/homeassistant/components/ifttt/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", + "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." }, "create_entry": { "default": "Om evenementen naar de Home Assistant te verzenden, moet u de actie \"Een webverzoek doen\" gebruiken vanuit de [IFTTT Webhook-applet]({applet_url}). \n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\nZie [the documentation]({docs_url}) voor informatie over het configureren van automatiseringen om inkomende gegevens te verwerken." diff --git a/homeassistant/components/insteon/translations/nl.json b/homeassistant/components/insteon/translations/nl.json index d2f73fca37b..e4f7d4a8102 100644 --- a/homeassistant/components/insteon/translations/nl.json +++ b/homeassistant/components/insteon/translations/nl.json @@ -28,6 +28,9 @@ "title": "Insteon Hub versie 2" }, "plm": { + "data": { + "device": "USB-apparaatpad" + }, "description": "Configureer de Insteon PowerLink Modem (PLM).", "title": "Insteon PLM" }, diff --git a/homeassistant/components/keenetic_ndms2/translations/nl.json b/homeassistant/components/keenetic_ndms2/translations/nl.json index f422e2641f6..c3c08575052 100644 --- a/homeassistant/components/keenetic_ndms2/translations/nl.json +++ b/homeassistant/components/keenetic_ndms2/translations/nl.json @@ -21,7 +21,8 @@ "step": { "user": { "data": { - "interfaces": "Kies interfaces om te scannen" + "interfaces": "Kies interfaces om te scannen", + "scan_interval": "Scaninterval" } } } diff --git a/homeassistant/components/kmtronic/translations/fr.json b/homeassistant/components/kmtronic/translations/fr.json new file mode 100644 index 00000000000..45620fe7795 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/fr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "host": "H\u00f4te", + "password": "Mot de passe", + "username": "Nom d'utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/pl.json b/homeassistant/components/kmtronic/translations/pl.json new file mode 100644 index 00000000000..25dab56796c --- /dev/null +++ b/homeassistant/components/kmtronic/translations/pl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/ca.json b/homeassistant/components/litejet/translations/ca.json new file mode 100644 index 00000000000..39e2a56dc4d --- /dev/null +++ b/homeassistant/components/litejet/translations/ca.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." + }, + "error": { + "open_failed": "No s'ha pogut obrir el port s\u00e8rie especificat." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "description": "Connecta el port RS232-2 LiteJet a l'ordinador i introdueix la ruta al port s\u00e8rie del dispositiu.\n\nEl LiteJet MCP ha d'estar configurat amb una velocitat de 19.2 k baudis, 8 bits de dades, 1 bit de parada, sense paritat i ha de transmetre un 'CR' despr\u00e9s de cada resposta.", + "title": "Connexi\u00f3 amb LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/fr.json b/homeassistant/components/litejet/translations/fr.json new file mode 100644 index 00000000000..455ba7fdc0c --- /dev/null +++ b/homeassistant/components/litejet/translations/fr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "description": "Connectez le port RS232-2 du LiteJet \u00e0 votre ordinateur et entrez le chemin d'acc\u00e8s au p\u00e9riph\u00e9rique de port s\u00e9rie. \n\n Le LiteJet MCP doit \u00eatre configur\u00e9 pour 19,2 K bauds, 8 bits de donn\u00e9es, 1 bit d'arr\u00eat, sans parit\u00e9 et pour transmettre un \u00abCR\u00bb apr\u00e8s chaque r\u00e9ponse.", + "title": "Connectez-vous \u00e0 LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/no.json b/homeassistant/components/litejet/translations/no.json new file mode 100644 index 00000000000..26ccd333546 --- /dev/null +++ b/homeassistant/components/litejet/translations/no.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." + }, + "error": { + "open_failed": "Kan ikke \u00e5pne den angitte serielle porten." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "description": "Koble LiteJets RS232-2-port til datamaskinen og skriv stien til den serielle portenheten. \n\n LiteJet MCP m\u00e5 konfigureres for 19,2 K baud, 8 databiter, 1 stoppbit, ingen paritet, og for \u00e5 overf\u00f8re en 'CR' etter hvert svar.", + "title": "Koble til LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/pl.json b/homeassistant/components/litejet/translations/pl.json new file mode 100644 index 00000000000..20e5d68288d --- /dev/null +++ b/homeassistant/components/litejet/translations/pl.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." + }, + "error": { + "open_failed": "Nie mo\u017cna otworzy\u0107 okre\u015blonego portu szeregowego." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "description": "Pod\u0142\u0105cz port RS232-2 LiteJet do komputera i wprowad\u017a \u015bcie\u017ck\u0119 do urz\u0105dzenia portu szeregowego. \n\nLiteJet MCP musi by\u0107 skonfigurowany dla szybko\u015bci 19,2K, 8 bit\u00f3w danych, 1 bit stopu, brak parzysto\u015bci i przesy\u0142anie \u201eCR\u201d po ka\u017cdej odpowiedzi.", + "title": "Po\u0142\u0105czenie z LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/ru.json b/homeassistant/components/litejet/translations/ru.json new file mode 100644 index 00000000000..c90e6956301 --- /dev/null +++ b/homeassistant/components/litejet/translations/ru.json @@ -0,0 +1,19 @@ +{ + "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." + }, + "error": { + "open_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u0442." + }, + "step": { + "user": { + "data": { + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0435 \u043f\u043e\u0440\u0442 RS232-2 LiteJet \u043a \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0443, \u0438 \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u0443\u0442\u044c \u043a \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u043c\u0443 \u043f\u043e\u0440\u0442\u0443. \n\nLiteJet MCP \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d \u043d\u0430 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c 19,2 \u041a\u0431\u043e\u0434, 8 \u0431\u0438\u0442 \u0434\u0430\u043d\u043d\u044b\u0445, 1 \u0441\u0442\u043e\u043f\u043e\u0432\u044b\u0439 \u0431\u0438\u0442, \u0431\u0435\u0437 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f \u0447\u0435\u0442\u043d\u043e\u0441\u0442\u0438 \u0438 \u043d\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0443 'CR' \u043f\u043e\u0441\u043b\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043e\u0442\u0432\u0435\u0442\u0430.", + "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/tr.json b/homeassistant/components/litejet/translations/tr.json new file mode 100644 index 00000000000..de4ea12cb6f --- /dev/null +++ b/homeassistant/components/litejet/translations/tr.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "LiteJet'e Ba\u011flan\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/zh-Hant.json b/homeassistant/components/litejet/translations/zh-Hant.json new file mode 100644 index 00000000000..8a268f3db49 --- /dev/null +++ b/homeassistant/components/litejet/translations/zh-Hant.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + }, + "error": { + "open_failed": "\u7121\u6cd5\u958b\u555f\u6307\u5b9a\u7684\u5e8f\u5217\u57e0" + }, + "step": { + "user": { + "data": { + "port": "\u901a\u8a0a\u57e0" + }, + "description": "\u9023\u7dda\u81f3\u96fb\u8166\u4e0a\u7684 LiteJet RS232-2 \u57e0\uff0c\u4e26\u8f38\u5165\u5e8f\u5217\u57e0\u88dd\u7f6e\u7684\u8def\u5f91\u3002\n\nLiteJet MCP \u5fc5\u9808\u8a2d\u5b9a\u70ba\u901a\u8a0a\u901f\u7387 19.2 K baud\u30018 \u6578\u64da\u4f4d\u5143\u30011 \u505c\u6b62\u4f4d\u5143\u3001\u7121\u540c\u4f4d\u4f4d\u5143\u4e26\u65bc\u6bcf\u500b\u56de\u5fa9\u5f8c\u50b3\u9001 'CR'\u3002", + "title": "\u9023\u7dda\u81f3 LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/fr.json b/homeassistant/components/litterrobot/translations/fr.json new file mode 100644 index 00000000000..aa84ec33d8c --- /dev/null +++ b/homeassistant/components/litterrobot/translations/fr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "password": "Mot de passe", + "username": "Nom d'utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/no.json b/homeassistant/components/litterrobot/translations/no.json new file mode 100644 index 00000000000..4ea7b2401c3 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/no.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "password": "Passord", + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/pl.json b/homeassistant/components/litterrobot/translations/pl.json new file mode 100644 index 00000000000..8a08a06c699 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/pl.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/translations/nl.json b/homeassistant/components/locative/translations/nl.json index e02378432ab..16cbbc77277 100644 --- a/homeassistant/components/locative/translations/nl.json +++ b/homeassistant/components/locative/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", + "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." }, "create_entry": { "default": "Om locaties naar Home Assistant te sturen, moet u de Webhook-functie instellen in de Locative app. \n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Methode: POST \n\n Zie [de documentatie]({docs_url}) voor meer informatie." diff --git a/homeassistant/components/mailgun/translations/nl.json b/homeassistant/components/mailgun/translations/nl.json index 772a67c118e..dea33946af5 100644 --- a/homeassistant/components/mailgun/translations/nl.json +++ b/homeassistant/components/mailgun/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", + "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." }, "create_entry": { "default": "Om evenementen naar Home Assistant te verzenden, moet u [Webhooks with Mailgun]({mailgun_url}) instellen. \n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Methode: POST \n - Inhoudstype: application/json \n\n Zie [de documentatie]({docs_url}) voor informatie over het configureren van automatiseringen om binnenkomende gegevens te verwerken." diff --git a/homeassistant/components/mazda/translations/nl.json b/homeassistant/components/mazda/translations/nl.json index 86f1e656e51..3198bfb4192 100644 --- a/homeassistant/components/mazda/translations/nl.json +++ b/homeassistant/components/mazda/translations/nl.json @@ -25,5 +25,6 @@ } } } - } + }, + "title": "Mazda Connected Services" } \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/ca.json b/homeassistant/components/mullvad/translations/ca.json new file mode 100644 index 00000000000..f81781cbc0f --- /dev/null +++ b/homeassistant/components/mullvad/translations/ca.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3", + "password": "Contrasenya", + "username": "Nom d'usuari" + }, + "description": "Vols configurar la integraci\u00f3 Mullvad VPN?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/en.json b/homeassistant/components/mullvad/translations/en.json index 45664554aed..fcfa89ef082 100644 --- a/homeassistant/components/mullvad/translations/en.json +++ b/homeassistant/components/mullvad/translations/en.json @@ -5,10 +5,16 @@ }, "error": { "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, "step": { "user": { + "data": { + "host": "Host", + "password": "Password", + "username": "Username" + }, "description": "Set up the Mullvad VPN integration?" } } diff --git a/homeassistant/components/mullvad/translations/et.json b/homeassistant/components/mullvad/translations/et.json new file mode 100644 index 00000000000..671d18a2cd3 --- /dev/null +++ b/homeassistant/components/mullvad/translations/et.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendumine nurjus", + "invalid_auth": "Tuvastamise viga", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + }, + "description": "Kas seadistada Mullvad VPN sidumine?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/fr.json b/homeassistant/components/mullvad/translations/fr.json new file mode 100644 index 00000000000..1a8b10de809 --- /dev/null +++ b/homeassistant/components/mullvad/translations/fr.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "host": "H\u00f4te", + "password": "Mot de passe", + "username": "Nom d'utilisateur" + }, + "description": "Configurez l'int\u00e9gration VPN Mullvad?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/nl.json b/homeassistant/components/mullvad/translations/nl.json new file mode 100644 index 00000000000..aa4d80ac71d --- /dev/null +++ b/homeassistant/components/mullvad/translations/nl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Wachtwoord", + "username": "Gebruikersnaam" + }, + "description": "De Mullvad VPN-integratie instellen?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/no.json b/homeassistant/components/mullvad/translations/no.json new file mode 100644 index 00000000000..d33f2640445 --- /dev/null +++ b/homeassistant/components/mullvad/translations/no.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "host": "Vert", + "password": "Passord", + "username": "Brukernavn" + }, + "description": "Sette opp Mullvad VPN-integrasjon?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/ru.json b/homeassistant/components/mullvad/translations/ru.json new file mode 100644 index 00000000000..ff34530e4a9 --- /dev/null +++ b/homeassistant/components/mullvad/translations/ru.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant." + }, + "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": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u041b\u043e\u0433\u0438\u043d" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Mullvad VPN." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/tr.json b/homeassistant/components/mullvad/translations/tr.json new file mode 100644 index 00000000000..0f3ddabfc4f --- /dev/null +++ b/homeassistant/components/mullvad/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/et.json b/homeassistant/components/netatmo/translations/et.json index 99e062b3842..8725eb48016 100644 --- a/homeassistant/components/netatmo/translations/et.json +++ b/homeassistant/components/netatmo/translations/et.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "eemal", + "hg": "k\u00fclmumiskaitse", + "schedule": "ajastus" + }, + "trigger_type": { + "alarm_started": "{entity_name} tuvastas h\u00e4ire", + "animal": "{entity_name} tuvastas looma", + "cancel_set_point": "{entity_name} on oma ajakava j\u00e4tkanud", + "human": "{entity_name} tuvastas inimese", + "movement": "{entity_name} tuvastas liikumise", + "outdoor": "{entity_name} tuvastas v\u00e4lise s\u00fcndmuse", + "person": "{entity_name} tuvastas isiku", + "person_away": "{entity_name} tuvastas inimese eemaldumise", + "set_point": "Sihttemperatuur {entity_name} on k\u00e4sitsi m\u00e4\u00e4ratud", + "therm_mode": "{entity_name} l\u00fclitus olekusse {subtype}.", + "turned_off": "{entity_name} l\u00fclitus v\u00e4lja", + "turned_on": "{entity_name} l\u00fclitus sisse", + "vehicle": "{entity_name} tuvastas s\u00f5iduki" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/fr.json b/homeassistant/components/netatmo/translations/fr.json index fe8fc74d273..6c294d467ab 100644 --- a/homeassistant/components/netatmo/translations/fr.json +++ b/homeassistant/components/netatmo/translations/fr.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "absent", + "hg": "garde-gel", + "schedule": "horaire" + }, + "trigger_type": { + "alarm_started": "{entity_name} a d\u00e9tect\u00e9 une alarme", + "animal": "{entity_name} a d\u00e9tect\u00e9 un animal", + "cancel_set_point": "{entity_name} a repris son programme", + "human": "{entity_name} a d\u00e9tect\u00e9 une personne", + "movement": "{entity_name} a d\u00e9tect\u00e9 un mouvement", + "outdoor": "{entity_name} a d\u00e9tect\u00e9 un \u00e9v\u00e9nement ext\u00e9rieur", + "person": "{entity_name} a d\u00e9tect\u00e9 une personne", + "person_away": "{entity_name} a d\u00e9tect\u00e9 qu\u2019une personne est partie", + "set_point": "Temp\u00e9rature cible {entity_name} d\u00e9finie manuellement", + "therm_mode": "{entity_name} est pass\u00e9 \u00e0 \"{subtype}\"", + "turned_off": "{entity_name} d\u00e9sactiv\u00e9", + "turned_on": "{entity_name} activ\u00e9", + "vehicle": "{entity_name} a d\u00e9tect\u00e9 un v\u00e9hicule" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/nl.json b/homeassistant/components/netatmo/translations/nl.json index eab1d9741ad..431f105df3d 100644 --- a/homeassistant/components/netatmo/translations/nl.json +++ b/homeassistant/components/netatmo/translations/nl.json @@ -2,7 +2,8 @@ "config": { "abort": { "authorize_url_timeout": "Time-out genereren autorisatie-URL.", - "missing_configuration": "Het component is niet geconfigureerd. Volg de documentatie." + "missing_configuration": "Het component is niet geconfigureerd. Volg de documentatie.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" }, "create_entry": { "default": "Succesvol geauthenticeerd met Netatmo." @@ -13,6 +14,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "afwezig", + "hg": "vorstbescherming", + "schedule": "schema" + }, + "trigger_type": { + "alarm_started": "{entity_name} heeft een alarm gedetecteerd", + "animal": "{entity_name} heeft een dier gedetecteerd", + "cancel_set_point": "{entity_name} heeft zijn schema hervat", + "human": "{entity_name} heeft een mens gedetecteerd", + "movement": "{entity_name} heeft beweging gedetecteerd", + "outdoor": "{entity_name} heeft een buitengebeurtenis gedetecteerd", + "person": "{entity_name} heeft een persoon gedetecteerd", + "person_away": "{entity_name} heeft gedetecteerd dat een persoon is vertrokken", + "set_point": "Doeltemperatuur {entity_name} handmatig ingesteld", + "therm_mode": "{entity_name} is overgeschakeld naar \"{subtype}\"", + "turned_off": "{entity_name} uitgeschakeld", + "turned_on": "{entity_name} ingeschakeld", + "vehicle": "{entity_name} heeft een voertuig gedetecteerd" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/ru.json b/homeassistant/components/netatmo/translations/ru.json index c9be7e60825..b25e0843967 100644 --- a/homeassistant/components/netatmo/translations/ru.json +++ b/homeassistant/components/netatmo/translations/ru.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "\u043d\u0435 \u0434\u043e\u043c\u0430", + "hg": "\u0437\u0430\u0449\u0438\u0442\u0430 \u043e\u0442 \u0437\u0430\u043c\u0435\u0440\u0437\u0430\u043d\u0438\u044f", + "schedule": "\u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435" + }, + "trigger_type": { + "alarm_started": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u0440\u0435\u0432\u043e\u0433\u0443", + "animal": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0436\u0438\u0432\u043e\u0442\u043d\u043e\u0435", + "cancel_set_point": "{entity_name} \u0432\u043e\u0437\u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u0441\u0432\u043e\u0435 \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435", + "human": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u0430", + "movement": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0435", + "outdoor": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043d\u0430 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u043c \u0432\u043e\u0437\u0434\u0443\u0445\u0435", + "person": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u0435\u0440\u0441\u043e\u043d\u0443", + "person_away": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442, \u0447\u0442\u043e \u043f\u0435\u0440\u0441\u043e\u043d\u0430 \u0443\u0448\u043b\u0430", + "set_point": "\u0426\u0435\u043b\u0435\u0432\u0430\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 {entity_name} \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0430 \u0432\u0440\u0443\u0447\u043d\u0443\u044e", + "therm_mode": "{entity_name} \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \"{subtype}\"", + "turned_off": "{entity_name} \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", + "turned_on": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", + "vehicle": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043d\u043e\u0435 \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u043e" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/tr.json b/homeassistant/components/netatmo/translations/tr.json index 94dd5b3fb0f..69646be2292 100644 --- a/homeassistant/components/netatmo/translations/tr.json +++ b/homeassistant/components/netatmo/translations/tr.json @@ -4,6 +4,22 @@ "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." } }, + "device_automation": { + "trigger_subtype": { + "away": "uzakta", + "hg": "donma korumas\u0131", + "schedule": "Zamanlama" + }, + "trigger_type": { + "alarm_started": "{entity_name} bir alarm alg\u0131lad\u0131", + "animal": "{entity_name} bir hayvan tespit etti", + "cancel_set_point": "{entity_name} zamanlamas\u0131na devam etti", + "human": "{entity_name} bir insan alg\u0131lad\u0131", + "movement": "{entity_name} hareket alg\u0131lad\u0131", + "turned_off": "{entity_name} kapat\u0131ld\u0131", + "turned_on": "{entity_name} a\u00e7\u0131ld\u0131" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/nightscout/translations/nl.json b/homeassistant/components/nightscout/translations/nl.json index 208299fd442..0146996dce5 100644 --- a/homeassistant/components/nightscout/translations/nl.json +++ b/homeassistant/components/nightscout/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/nzbget/translations/nl.json b/homeassistant/components/nzbget/translations/nl.json index f5f1bfd39ed..89d58d14292 100644 --- a/homeassistant/components/nzbget/translations/nl.json +++ b/homeassistant/components/nzbget/translations/nl.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "host": "Host", "name": "Naam", "password": "Wachtwoord", "port": "Poort", diff --git a/homeassistant/components/plum_lightpad/translations/nl.json b/homeassistant/components/plum_lightpad/translations/nl.json index 7f0f85b7326..8410cabbbb9 100644 --- a/homeassistant/components/plum_lightpad/translations/nl.json +++ b/homeassistant/components/plum_lightpad/translations/nl.json @@ -9,7 +9,8 @@ "step": { "user": { "data": { - "password": "Wachtwoord" + "password": "Wachtwoord", + "username": "E-mail" } } } diff --git a/homeassistant/components/poolsense/translations/nl.json b/homeassistant/components/poolsense/translations/nl.json index 46fc915d7bd..38ef34d5afc 100644 --- a/homeassistant/components/poolsense/translations/nl.json +++ b/homeassistant/components/poolsense/translations/nl.json @@ -11,7 +11,8 @@ "data": { "email": "E-mail", "password": "Wachtwoord" - } + }, + "description": "Wil je beginnen met instellen?" } } } diff --git a/homeassistant/components/rituals_perfume_genie/translations/fr.json b/homeassistant/components/rituals_perfume_genie/translations/fr.json new file mode 100644 index 00000000000..2a1fb9c8bb8 --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/fr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Mot de passe" + }, + "title": "Connectez-vous \u00e0 votre compte Rituals" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/nl.json b/homeassistant/components/rpi_power/translations/nl.json index 72f9ff82ba4..8c15279dca8 100644 --- a/homeassistant/components/rpi_power/translations/nl.json +++ b/homeassistant/components/rpi_power/translations/nl.json @@ -2,6 +2,11 @@ "config": { "abort": { "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + }, + "step": { + "confirm": { + "description": "Wil je beginnen met instellen?" + } } }, "title": "Raspberry Pi Voeding Checker" diff --git a/homeassistant/components/ruckus_unleashed/translations/nl.json b/homeassistant/components/ruckus_unleashed/translations/nl.json index 0569c39321a..8ad15260b0d 100644 --- a/homeassistant/components/ruckus_unleashed/translations/nl.json +++ b/homeassistant/components/ruckus_unleashed/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/sharkiq/translations/nl.json b/homeassistant/components/sharkiq/translations/nl.json index 96c10f3e2f0..3acfdbdf074 100644 --- a/homeassistant/components/sharkiq/translations/nl.json +++ b/homeassistant/components/sharkiq/translations/nl.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Account is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken", + "reauth_successful": "Herauthenticatie was succesvol", "unknown": "Onverwachte fout" }, "error": { diff --git a/homeassistant/components/smappee/translations/nl.json b/homeassistant/components/smappee/translations/nl.json index ebcc16dafac..10a4fe2efab 100644 --- a/homeassistant/components/smappee/translations/nl.json +++ b/homeassistant/components/smappee/translations/nl.json @@ -6,7 +6,8 @@ "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", "cannot_connect": "Kan geen verbinding maken", "invalid_mdns": "Niet-ondersteund apparaat voor de Smappee-integratie.", - "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen." + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" }, "step": { "local": { diff --git a/homeassistant/components/sms/translations/nl.json b/homeassistant/components/sms/translations/nl.json index 75dd593982a..ddcc54d239f 100644 --- a/homeassistant/components/sms/translations/nl.json +++ b/homeassistant/components/sms/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "error": { "cannot_connect": "Kon niet verbinden", diff --git a/homeassistant/components/somfy/translations/nl.json b/homeassistant/components/somfy/translations/nl.json index 423dbb6a2bb..b7f077f2c73 100644 --- a/homeassistant/components/somfy/translations/nl.json +++ b/homeassistant/components/somfy/translations/nl.json @@ -3,6 +3,7 @@ "abort": { "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", "missing_configuration": "Het Somfy-component is niet geconfigureerd. Gelieve de documentatie te volgen.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "create_entry": { diff --git a/homeassistant/components/speedtestdotnet/translations/nl.json b/homeassistant/components/speedtestdotnet/translations/nl.json index 0c0c184b5fe..1fe99195f7a 100644 --- a/homeassistant/components/speedtestdotnet/translations/nl.json +++ b/homeassistant/components/speedtestdotnet/translations/nl.json @@ -3,6 +3,11 @@ "abort": { "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "wrong_server_id": "Server-ID is niet geldig" + }, + "step": { + "user": { + "description": "Wil je beginnen met instellen?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/spider/translations/nl.json b/homeassistant/components/spider/translations/nl.json index f0b4ddf59a9..bc7683ac0a4 100644 --- a/homeassistant/components/spider/translations/nl.json +++ b/homeassistant/components/spider/translations/nl.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, "error": { "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" diff --git a/homeassistant/components/subaru/translations/fr.json b/homeassistant/components/subaru/translations/fr.json new file mode 100644 index 00000000000..a6bf6902aab --- /dev/null +++ b/homeassistant/components/subaru/translations/fr.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", + "cannot_connect": "\u00c9chec de connexion" + }, + "error": { + "bad_pin_format": "Le code PIN doit \u00eatre compos\u00e9 de 4 chiffres", + "cannot_connect": "\u00c9chec de connexion", + "incorrect_pin": "PIN incorrect", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "Veuillez entrer votre NIP MySubaru\nREMARQUE : Tous les v\u00e9hicules en compte doivent avoir le m\u00eame NIP", + "title": "Configuration de Subaru Starlink" + }, + "user": { + "data": { + "country": "Choisissez le pays", + "password": "Mot de passe", + "username": "Nom d'utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/nl.json b/homeassistant/components/syncthru/translations/nl.json index 349b4b2818e..b1beb4058bc 100644 --- a/homeassistant/components/syncthru/translations/nl.json +++ b/homeassistant/components/syncthru/translations/nl.json @@ -10,7 +10,8 @@ "step": { "user": { "data": { - "name": "Naam" + "name": "Naam", + "url": "Webinterface URL" } } } diff --git a/homeassistant/components/tile/translations/nl.json b/homeassistant/components/tile/translations/nl.json index c160ac631ee..236d250122a 100644 --- a/homeassistant/components/tile/translations/nl.json +++ b/homeassistant/components/tile/translations/nl.json @@ -9,7 +9,8 @@ "step": { "user": { "data": { - "password": "Wachtwoord" + "password": "Wachtwoord", + "username": "E-mail" }, "title": "Tegel configureren" } diff --git a/homeassistant/components/toon/translations/nl.json b/homeassistant/components/toon/translations/nl.json index cf77b94d025..a0cda915172 100644 --- a/homeassistant/components/toon/translations/nl.json +++ b/homeassistant/components/toon/translations/nl.json @@ -3,6 +3,8 @@ "abort": { "already_configured": "De geselecteerde overeenkomst is al geconfigureerd.", "authorize_url_fail": "Onbekende fout bij het genereren van een autorisatie-URL.", + "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", "no_agreements": "Dit account heeft geen Toon schermen.", "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout [check the help section] ( {docs_url} )", "unknown_authorize_url_generation": "Onbekende fout bij het genereren van een autorisatie-URL." diff --git a/homeassistant/components/totalconnect/translations/fr.json b/homeassistant/components/totalconnect/translations/fr.json index 6526d0a9800..40ca767b4ac 100644 --- a/homeassistant/components/totalconnect/translations/fr.json +++ b/homeassistant/components/totalconnect/translations/fr.json @@ -1,12 +1,21 @@ { "config": { "abort": { - "already_configured": "Compte d\u00e9j\u00e0 configur\u00e9" + "already_configured": "Compte d\u00e9j\u00e0 configur\u00e9", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { "invalid_auth": "Authentification invalide" }, "step": { + "locations": { + "data": { + "location": "Emplacement" + } + }, + "reauth_confirm": { + "title": "R\u00e9-authentifier l'int\u00e9gration" + }, "user": { "data": { "password": "Mot de passe", diff --git a/homeassistant/components/totalconnect/translations/nl.json b/homeassistant/components/totalconnect/translations/nl.json index 1f4fb5490d1..94d8e3ac01e 100644 --- a/homeassistant/components/totalconnect/translations/nl.json +++ b/homeassistant/components/totalconnect/translations/nl.json @@ -8,6 +8,11 @@ "invalid_auth": "Ongeldige authenticatie" }, "step": { + "locations": { + "data": { + "location": "Locatie" + } + }, "reauth_confirm": { "title": "Verifieer de integratie opnieuw" }, diff --git a/homeassistant/components/totalconnect/translations/no.json b/homeassistant/components/totalconnect/translations/no.json index e80f86696fc..9c98d6ad1e7 100644 --- a/homeassistant/components/totalconnect/translations/no.json +++ b/homeassistant/components/totalconnect/translations/no.json @@ -1,12 +1,25 @@ { "config": { "abort": { - "already_configured": "Kontoen er allerede konfigurert" + "already_configured": "Kontoen er allerede konfigurert", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" }, "error": { - "invalid_auth": "Ugyldig godkjenning" + "invalid_auth": "Ugyldig godkjenning", + "usercode": "Brukerkode er ikke gyldig for denne brukeren p\u00e5 dette stedet" }, "step": { + "locations": { + "data": { + "location": "Plassering" + }, + "description": "Angi brukerkoden for denne brukeren p\u00e5 denne plasseringen", + "title": "Brukerkoder for plassering" + }, + "reauth_confirm": { + "description": "Total Connect m\u00e5 godkjenne kontoen din p\u00e5 nytt", + "title": "Godkjenne integrering p\u00e5 nytt" + }, "user": { "data": { "password": "Passord", diff --git a/homeassistant/components/totalconnect/translations/pl.json b/homeassistant/components/totalconnect/translations/pl.json index 530d632040c..ff2ca2351e6 100644 --- a/homeassistant/components/totalconnect/translations/pl.json +++ b/homeassistant/components/totalconnect/translations/pl.json @@ -1,12 +1,25 @@ { "config": { "abort": { - "already_configured": "Konto jest ju\u017c skonfigurowane" + "already_configured": "Konto jest ju\u017c skonfigurowane", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" }, "error": { - "invalid_auth": "Niepoprawne uwierzytelnienie" + "invalid_auth": "Niepoprawne uwierzytelnienie", + "usercode": "Nieprawid\u0142owy kod u\u017cytkownika dla u\u017cytkownika w tej lokalizacji" }, "step": { + "locations": { + "data": { + "location": "Lokalizacja" + }, + "description": "Wprowad\u017a kod u\u017cytkownika dla u\u017cytkownika w tej lokalizacji", + "title": "Kody lokalizacji u\u017cytkownika" + }, + "reauth_confirm": { + "description": "Integracja Total Connect wymaga ponownego uwierzytelnienia Twojego konta", + "title": "Ponownie uwierzytelnij integracj\u0119" + }, "user": { "data": { "password": "Has\u0142o", diff --git a/homeassistant/components/xiaomi_miio/translations/no.json b/homeassistant/components/xiaomi_miio/translations/no.json index 3375cac31d5..0a6cf433d87 100644 --- a/homeassistant/components/xiaomi_miio/translations/no.json +++ b/homeassistant/components/xiaomi_miio/translations/no.json @@ -14,6 +14,7 @@ "device": { "data": { "host": "IP adresse", + "model": "Enhetsmodell (valgfritt)", "name": "Navnet p\u00e5 enheten", "token": "API-token" }, diff --git a/homeassistant/components/xiaomi_miio/translations/pl.json b/homeassistant/components/xiaomi_miio/translations/pl.json index 50eb4d16887..80528b71370 100644 --- a/homeassistant/components/xiaomi_miio/translations/pl.json +++ b/homeassistant/components/xiaomi_miio/translations/pl.json @@ -14,6 +14,7 @@ "device": { "data": { "host": "Adres IP", + "model": "Model urz\u0105dzenia (opcjonalnie)", "name": "Nazwa urz\u0105dzenia", "token": "Token API" }, diff --git a/homeassistant/components/zwave_js/translations/ca.json b/homeassistant/components/zwave_js/translations/ca.json index 6806b5072c1..731c0bbcea8 100644 --- a/homeassistant/components/zwave_js/translations/ca.json +++ b/homeassistant/components/zwave_js/translations/ca.json @@ -6,6 +6,7 @@ "addon_install_failed": "No s'ha pogut instal\u00b7lar el complement Z-Wave JS.", "addon_missing_discovery_info": "Falta la informaci\u00f3 de descobriment del complement Z-Wave JS.", "addon_set_config_failed": "No s'ha pogut establir la configuraci\u00f3 de Z-Wave JS.", + "addon_start_failed": "No s'ha pogut iniciar el complement Z-Wave JS.", "already_configured": "El dispositiu ja est\u00e0 configurat", "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "cannot_connect": "Ha fallat la connexi\u00f3" @@ -17,7 +18,8 @@ "unknown": "Error inesperat" }, "progress": { - "install_addon": "Espera mentre finalitza la instal\u00b7laci\u00f3 del complement Z-Wave JS. Pot tardar uns quants minuts." + "install_addon": "Espera mentre finalitza la instal\u00b7laci\u00f3 del complement Z-Wave JS. Pot tardar uns quants minuts.", + "start_addon": "Espera mentre es completa la inicialitzaci\u00f3 del complement Z-Wave JS. Pot tardar uns segons." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Vols utilitzar el complement Supervisor de Z-Wave JS?", "title": "Selecciona el m\u00e8tode de connexi\u00f3" }, + "start_addon": { + "title": "El complement Z-Wave JS s'est\u00e0 iniciant." + }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/et.json b/homeassistant/components/zwave_js/translations/et.json index d51507b616f..4c68e63530f 100644 --- a/homeassistant/components/zwave_js/translations/et.json +++ b/homeassistant/components/zwave_js/translations/et.json @@ -6,6 +6,7 @@ "addon_install_failed": "Z-Wave JS lisandmooduli paigaldamine nurjus.", "addon_missing_discovery_info": "Z-Wave JS lisandmooduli tuvastusteave puudub.", "addon_set_config_failed": "Z-Wave JS konfiguratsiooni m\u00e4\u00e4ramine nurjus.", + "addon_start_failed": "Z-Wave JS-i lisandmooduli k\u00e4ivitamine nurjus.", "already_configured": "Seade on juba h\u00e4\u00e4lestatud", "already_in_progress": "Seadistamine on juba k\u00e4imas", "cannot_connect": "\u00dchendamine nurjus" @@ -17,7 +18,8 @@ "unknown": "Ootamatu t\u00f5rge" }, "progress": { - "install_addon": "Palun oota kuni Z-Wave JS lisandmoodul on paigaldatud. See v\u00f5ib v\u00f5tta mitu minutit." + "install_addon": "Palun oota kuni Z-Wave JS lisandmoodul on paigaldatud. See v\u00f5ib v\u00f5tta mitu minutit.", + "start_addon": "Palun oota kuni Z-Wave JS lisandmooduli ak\u00e4ivitumine l\u00f5ppeb. See v\u00f5ib v\u00f5tta m\u00f5ned sekundid." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Kas soovid kasutada Z-Wave JSi halduri lisandmoodulit?", "title": "Vali \u00fchendusviis" }, + "start_addon": { + "title": "Z-Wave JS lisandmoodul k\u00e4ivitub." + }, "user": { "data": { "url": "" diff --git a/homeassistant/components/zwave_js/translations/fr.json b/homeassistant/components/zwave_js/translations/fr.json index 040e3997ac1..9cc8bf822b8 100644 --- a/homeassistant/components/zwave_js/translations/fr.json +++ b/homeassistant/components/zwave_js/translations/fr.json @@ -6,6 +6,7 @@ "addon_install_failed": "\u00c9chec de l'installation du module compl\u00e9mentaire Z-Wave JS.", "addon_missing_discovery_info": "Informations manquantes sur la d\u00e9couverte du module compl\u00e9mentaire Z-Wave JS.", "addon_set_config_failed": "\u00c9chec de la d\u00e9finition de la configuration Z-Wave JS.", + "addon_start_failed": "\u00c9chec du d\u00e9marrage du module compl\u00e9mentaire Z-Wave JS.", "already_configured": "Le p\u00e9riph\u00e9rique est d\u00e9j\u00e0 configur\u00e9", "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", "cannot_connect": "\u00c9chec de la connexion " @@ -17,7 +18,8 @@ "unknown": "Erreur inattendue" }, "progress": { - "install_addon": "Veuillez patienter pendant l'installation du module compl\u00e9mentaire Z-Wave JS. Cela peut prendre plusieurs minutes." + "install_addon": "Veuillez patienter pendant l'installation du module compl\u00e9mentaire Z-Wave JS. Cela peut prendre plusieurs minutes.", + "start_addon": "Veuillez patienter pendant le d\u00e9marrage du module compl\u00e9mentaire Z-Wave JS. Cela peut prendre quelques secondes." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Voulez-vous utiliser le module compl\u00e9mentaire Z-Wave JS Supervisor?", "title": "S\u00e9lectionner la m\u00e9thode de connexion" }, + "start_addon": { + "title": "Le module compl\u00e9mentaire Z-Wave JS est d\u00e9marr\u00e9." + }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/no.json b/homeassistant/components/zwave_js/translations/no.json index b724fb34e48..acd049fc561 100644 --- a/homeassistant/components/zwave_js/translations/no.json +++ b/homeassistant/components/zwave_js/translations/no.json @@ -6,6 +6,7 @@ "addon_install_failed": "Kunne ikke installere Z-Wave JS-tillegg", "addon_missing_discovery_info": "Manglende oppdagelsesinformasjon for Z-Wave JS-tillegg", "addon_set_config_failed": "Kunne ikke angi Z-Wave JS-konfigurasjon", + "addon_start_failed": "Kunne ikke starte Z-Wave JS-tillegget.", "already_configured": "Enheten er allerede konfigurert", "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "cannot_connect": "Tilkobling mislyktes" @@ -17,7 +18,8 @@ "unknown": "Uventet feil" }, "progress": { - "install_addon": "Vent mens installasjonen av Z-Wave JS-tillegg er ferdig. Dette kan ta flere minutter." + "install_addon": "Vent mens installasjonen av Z-Wave JS-tillegg er ferdig. Dette kan ta flere minutter.", + "start_addon": "Vent mens Z-Wave JS-tilleggsstarten er fullf\u00f8rt. Dette kan ta noen sekunder." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Vil du bruke Z-Wave JS Supervisor-tillegg?", "title": "Velg tilkoblingsmetode" }, + "start_addon": { + "title": "Z-Wave JS-tillegget starter." + }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/pl.json b/homeassistant/components/zwave_js/translations/pl.json index b139b0dacc6..2bfd994132b 100644 --- a/homeassistant/components/zwave_js/translations/pl.json +++ b/homeassistant/components/zwave_js/translations/pl.json @@ -6,6 +6,7 @@ "addon_install_failed": "Nie uda\u0142o si\u0119 zainstalowa\u0107 dodatku Z-Wave JS", "addon_missing_discovery_info": "Brak informacji wykrywania dodatku Z-Wave JS", "addon_set_config_failed": "Nie uda\u0142o si\u0119 skonfigurowa\u0107 Z-Wave JS", + "addon_start_failed": "Nie uda\u0142o si\u0119 uruchomi\u0107 dodatku Z-Wave JS.", "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "already_in_progress": "Konfiguracja jest ju\u017c w toku", "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" @@ -17,7 +18,8 @@ "unknown": "Nieoczekiwany b\u0142\u0105d" }, "progress": { - "install_addon": "Poczekaj, a\u017c zako\u0144czy si\u0119 instalacja dodatku Z-Wave JS. Mo\u017ce to zaj\u0105\u0107 kilka minut." + "install_addon": "Poczekaj, a\u017c zako\u0144czy si\u0119 instalacja dodatku Z-Wave JS. Mo\u017ce to zaj\u0105\u0107 kilka minut.", + "start_addon": "Poczekaj, a\u017c zako\u0144czy si\u0119 uruchamianie dodatku Z-Wave JS. Mo\u017ce to zaj\u0105\u0107 chwil\u0119." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Czy chcesz skorzysta\u0107 z dodatku Z-Wave JS Supervisor?", "title": "Wybierz metod\u0119 po\u0142\u0105czenia" }, + "start_addon": { + "title": "Dodatek Z-Wave JS uruchamia si\u0119." + }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/ru.json b/homeassistant/components/zwave_js/translations/ru.json index 5b7e5f47017..1a65ce3ea71 100644 --- a/homeassistant/components/zwave_js/translations/ru.json +++ b/homeassistant/components/zwave_js/translations/ru.json @@ -6,6 +6,7 @@ "addon_install_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Z-Wave JS.", "addon_missing_discovery_info": "\u041d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 Z-Wave JS.", "addon_set_config_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e Z-Wave JS.", + "addon_start_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Z-Wave JS.", "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", "already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." @@ -17,7 +18,8 @@ "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "progress": { - "install_addon": "\u041f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043d\u0443\u0442." + "install_addon": "\u041f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043d\u0443\u0442.", + "start_addon": "\u041f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f \u0437\u0430\u043f\u0443\u0441\u043a \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0435\u043a\u0443\u043d\u0434." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "\u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Supervisor Z-Wave JS?", "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f" }, + "start_addon": { + "title": "\u0414\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Z-Wave JS \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f" + }, "user": { "data": { "url": "URL-\u0430\u0434\u0440\u0435\u0441" diff --git a/homeassistant/components/zwave_js/translations/zh-Hant.json b/homeassistant/components/zwave_js/translations/zh-Hant.json index b9ff1b41920..f1495b1aeda 100644 --- a/homeassistant/components/zwave_js/translations/zh-Hant.json +++ b/homeassistant/components/zwave_js/translations/zh-Hant.json @@ -6,6 +6,7 @@ "addon_install_failed": "Z-Wave JS add-on \u5b89\u88dd\u5931\u6557\u3002", "addon_missing_discovery_info": "\u7f3a\u5c11 Z-Wave JS add-on \u63a2\u7d22\u8cc7\u8a0a\u3002", "addon_set_config_failed": "Z-Wave JS add-on \u8a2d\u5b9a\u5931\u6557\u3002", + "addon_start_failed": "Z-Wave JS add-on \u555f\u59cb\u5931\u6557\u3002", "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "cannot_connect": "\u9023\u7dda\u5931\u6557" @@ -17,7 +18,8 @@ "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "progress": { - "install_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS add-on \u5b89\u88dd\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002" + "install_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS add-on \u5b89\u88dd\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002", + "start_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS add-on \u555f\u59cb\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002" }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "\u662f\u5426\u8981\u4f7f\u7528 Z-Wave JS Supervisor add-on\uff1f", "title": "\u9078\u64c7\u9023\u7dda\u985e\u578b" }, + "start_addon": { + "title": "Z-Wave JS add-on \u555f\u59cb\u4e2d\u3002" + }, "user": { "data": { "url": "\u7db2\u5740" From 7e35af5d4e96312237fc6ce6d4764f1d97d131f7 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 25 Feb 2021 01:09:33 +0100 Subject: [PATCH 004/831] Upgrade TwitterAPI to 2.6.8 (#47019) --- homeassistant/components/twitter/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/twitter/manifest.json b/homeassistant/components/twitter/manifest.json index 873c9e12ab1..297f990e9df 100644 --- a/homeassistant/components/twitter/manifest.json +++ b/homeassistant/components/twitter/manifest.json @@ -2,6 +2,6 @@ "domain": "twitter", "name": "Twitter", "documentation": "https://www.home-assistant.io/integrations/twitter", - "requirements": ["TwitterAPI==2.6.6"], + "requirements": ["TwitterAPI==2.6.8"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 677ce0d0c2f..b170a784cf3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -81,7 +81,7 @@ RtmAPI==0.7.2 TravisPy==0.3.5 # homeassistant.components.twitter -TwitterAPI==2.6.6 +TwitterAPI==2.6.8 # homeassistant.components.tof # VL53L1X2==0.1.5 From 7dc907177614a497aae715256294e241907d6e1c Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 25 Feb 2021 04:25:06 +0100 Subject: [PATCH 005/831] Add Xiaomi Miio fan config flow (#46866) * Miio fan config flow * fix styling and imports * Update homeassistant/components/xiaomi_miio/fan.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/xiaomi_miio/fan.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/xiaomi_miio/fan.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/xiaomi_miio/fan.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/xiaomi_miio/fan.py Co-authored-by: Martin Hjelmare * rename device -> entity * fix indent Co-authored-by: Martin Hjelmare --- .../components/xiaomi_miio/__init__.py | 4 + .../components/xiaomi_miio/config_flow.py | 1 + homeassistant/components/xiaomi_miio/const.py | 50 +++- homeassistant/components/xiaomi_miio/fan.py | 270 ++++++++---------- 4 files changed, 172 insertions(+), 153 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index a8b32a31576..42b491c9b55 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -16,6 +16,7 @@ from .const import ( CONF_MODEL, DOMAIN, KEY_COORDINATOR, + MODELS_FAN, MODELS_SWITCH, MODELS_VACUUM, ) @@ -25,6 +26,7 @@ _LOGGER = logging.getLogger(__name__) GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "light"] SWITCH_PLATFORMS = ["switch"] +FAN_PLATFORMS = ["fan"] VACUUM_PLATFORMS = ["vacuum"] @@ -122,6 +124,8 @@ async def async_setup_device_entry( platforms = [] if model in MODELS_SWITCH: platforms = SWITCH_PLATFORMS + elif model in MODELS_FAN: + platforms = FAN_PLATFORMS for vacuum_model in MODELS_VACUUM: if model.startswith(vacuum_model): platforms = VACUUM_PLATFORMS diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py index d7e2198f72f..c9c363b61eb 100644 --- a/homeassistant/components/xiaomi_miio/config_flow.py +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -73,6 +73,7 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) return await self.async_step_device() + for device_model in MODELS_ALL_DEVICES: if name.startswith(device_model.replace(".", "-")): unique_id = format_mac(self.mac) diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index d6c39146f6a..5dc381b17fb 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -9,6 +9,53 @@ CONF_MAC = "mac" KEY_COORDINATOR = "coordinator" +# Fam Models +MODEL_AIRPURIFIER_V1 = "zhimi.airpurifier.v1" +MODEL_AIRPURIFIER_V2 = "zhimi.airpurifier.v2" +MODEL_AIRPURIFIER_V3 = "zhimi.airpurifier.v3" +MODEL_AIRPURIFIER_V5 = "zhimi.airpurifier.v5" +MODEL_AIRPURIFIER_PRO = "zhimi.airpurifier.v6" +MODEL_AIRPURIFIER_PRO_V7 = "zhimi.airpurifier.v7" +MODEL_AIRPURIFIER_M1 = "zhimi.airpurifier.m1" +MODEL_AIRPURIFIER_M2 = "zhimi.airpurifier.m2" +MODEL_AIRPURIFIER_MA1 = "zhimi.airpurifier.ma1" +MODEL_AIRPURIFIER_MA2 = "zhimi.airpurifier.ma2" +MODEL_AIRPURIFIER_SA1 = "zhimi.airpurifier.sa1" +MODEL_AIRPURIFIER_SA2 = "zhimi.airpurifier.sa2" +MODEL_AIRPURIFIER_2S = "zhimi.airpurifier.mc1" +MODEL_AIRPURIFIER_3 = "zhimi.airpurifier.ma4" +MODEL_AIRPURIFIER_3H = "zhimi.airpurifier.mb3" + +MODEL_AIRHUMIDIFIER_V1 = "zhimi.humidifier.v1" +MODEL_AIRHUMIDIFIER_CA1 = "zhimi.humidifier.ca1" +MODEL_AIRHUMIDIFIER_CA4 = "zhimi.humidifier.ca4" +MODEL_AIRHUMIDIFIER_CB1 = "zhimi.humidifier.cb1" + +MODEL_AIRFRESH_VA2 = "zhimi.airfresh.va2" + +MODELS_PURIFIER_MIOT = [MODEL_AIRPURIFIER_3, MODEL_AIRPURIFIER_3H] +MODELS_HUMIDIFIER_MIOT = [MODEL_AIRHUMIDIFIER_CA4] +MODELS_FAN_MIIO = [ + MODEL_AIRPURIFIER_V1, + MODEL_AIRPURIFIER_V2, + MODEL_AIRPURIFIER_V3, + MODEL_AIRPURIFIER_V5, + MODEL_AIRPURIFIER_PRO, + MODEL_AIRPURIFIER_PRO_V7, + MODEL_AIRPURIFIER_M1, + MODEL_AIRPURIFIER_M2, + MODEL_AIRPURIFIER_MA1, + MODEL_AIRPURIFIER_MA2, + MODEL_AIRPURIFIER_SA1, + MODEL_AIRPURIFIER_SA2, + MODEL_AIRPURIFIER_2S, + MODEL_AIRHUMIDIFIER_V1, + MODEL_AIRHUMIDIFIER_CA1, + MODEL_AIRHUMIDIFIER_CB1, + MODEL_AIRFRESH_VA2, +] + +# Model lists MODELS_GATEWAY = ["lumi.gateway", "lumi.acpartner"] MODELS_SWITCH = [ "chuangmi.plug.v1", @@ -23,9 +70,10 @@ MODELS_SWITCH = [ "chuangmi.plug.hmi206", "lumi.acpartner.v3", ] +MODELS_FAN = MODELS_FAN_MIIO + MODELS_HUMIDIFIER_MIOT + MODELS_PURIFIER_MIOT MODELS_VACUUM = ["roborock.vacuum"] -MODELS_ALL_DEVICES = MODELS_SWITCH + MODELS_VACUUM +MODELS_ALL_DEVICES = MODELS_SWITCH + MODELS_FAN + MODELS_VACUUM MODELS_ALL = MODELS_ALL_DEVICES + MODELS_GATEWAY # Fan Services diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index 0d07654e61b..055690a4264 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -10,7 +10,6 @@ from miio import ( # pylint: disable=import-error AirHumidifierMiot, AirPurifier, AirPurifierMiot, - Device, DeviceException, ) from miio.airfresh import ( # pylint: disable=import-error, import-error @@ -44,6 +43,7 @@ from homeassistant.components.fan import ( SUPPORT_SET_SPEED, FanEntity, ) +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_MODE, @@ -51,11 +51,24 @@ from homeassistant.const import ( CONF_NAME, CONF_TOKEN, ) -from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from .const import ( + CONF_DEVICE, + CONF_FLOW_TYPE, DOMAIN, + MODEL_AIRHUMIDIFIER_CA1, + MODEL_AIRHUMIDIFIER_CA4, + MODEL_AIRHUMIDIFIER_CB1, + MODEL_AIRPURIFIER_2S, + MODEL_AIRPURIFIER_3, + MODEL_AIRPURIFIER_3H, + MODEL_AIRPURIFIER_PRO, + MODEL_AIRPURIFIER_PRO_V7, + MODEL_AIRPURIFIER_V3, + MODELS_FAN, + MODELS_HUMIDIFIER_MIOT, + MODELS_PURIFIER_MIOT, SERVICE_RESET_FILTER, SERVICE_SET_AUTO_DETECT_OFF, SERVICE_SET_AUTO_DETECT_ON, @@ -77,6 +90,7 @@ from .const import ( SERVICE_SET_TARGET_HUMIDITY, SERVICE_SET_VOLUME, ) +from .device import XiaomiMiioEntity _LOGGER = logging.getLogger(__name__) @@ -84,58 +98,14 @@ DEFAULT_NAME = "Xiaomi Miio Device" DATA_KEY = "fan.xiaomi_miio" CONF_MODEL = "model" -MODEL_AIRPURIFIER_V1 = "zhimi.airpurifier.v1" -MODEL_AIRPURIFIER_V2 = "zhimi.airpurifier.v2" -MODEL_AIRPURIFIER_V3 = "zhimi.airpurifier.v3" -MODEL_AIRPURIFIER_V5 = "zhimi.airpurifier.v5" -MODEL_AIRPURIFIER_PRO = "zhimi.airpurifier.v6" -MODEL_AIRPURIFIER_PRO_V7 = "zhimi.airpurifier.v7" -MODEL_AIRPURIFIER_M1 = "zhimi.airpurifier.m1" -MODEL_AIRPURIFIER_M2 = "zhimi.airpurifier.m2" -MODEL_AIRPURIFIER_MA1 = "zhimi.airpurifier.ma1" -MODEL_AIRPURIFIER_MA2 = "zhimi.airpurifier.ma2" -MODEL_AIRPURIFIER_SA1 = "zhimi.airpurifier.sa1" -MODEL_AIRPURIFIER_SA2 = "zhimi.airpurifier.sa2" -MODEL_AIRPURIFIER_2S = "zhimi.airpurifier.mc1" -MODEL_AIRPURIFIER_3 = "zhimi.airpurifier.ma4" -MODEL_AIRPURIFIER_3H = "zhimi.airpurifier.mb3" -MODEL_AIRHUMIDIFIER_V1 = "zhimi.humidifier.v1" -MODEL_AIRHUMIDIFIER_CA1 = "zhimi.humidifier.ca1" -MODEL_AIRHUMIDIFIER_CA4 = "zhimi.humidifier.ca4" -MODEL_AIRHUMIDIFIER_CB1 = "zhimi.humidifier.cb1" - -MODEL_AIRFRESH_VA2 = "zhimi.airfresh.va2" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, vol.Required(CONF_TOKEN): vol.All(cv.string, vol.Length(min=32, max=32)), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_MODEL): vol.In( - [ - MODEL_AIRPURIFIER_V1, - MODEL_AIRPURIFIER_V2, - MODEL_AIRPURIFIER_V3, - MODEL_AIRPURIFIER_V5, - MODEL_AIRPURIFIER_PRO, - MODEL_AIRPURIFIER_PRO_V7, - MODEL_AIRPURIFIER_M1, - MODEL_AIRPURIFIER_M2, - MODEL_AIRPURIFIER_MA1, - MODEL_AIRPURIFIER_MA2, - MODEL_AIRPURIFIER_SA1, - MODEL_AIRPURIFIER_SA2, - MODEL_AIRPURIFIER_2S, - MODEL_AIRPURIFIER_3, - MODEL_AIRPURIFIER_3H, - MODEL_AIRHUMIDIFIER_V1, - MODEL_AIRHUMIDIFIER_CA1, - MODEL_AIRHUMIDIFIER_CA4, - MODEL_AIRHUMIDIFIER_CB1, - MODEL_AIRFRESH_VA2, - ] - ), + vol.Optional(CONF_MODEL): vol.In(MODELS_FAN), } ) @@ -193,9 +163,6 @@ ATTR_FAULT = "fault" # Air Fresh ATTR_CO2 = "co2" -PURIFIER_MIOT = [MODEL_AIRPURIFIER_3, MODEL_AIRPURIFIER_3H] -HUMIDIFIER_MIOT = [MODEL_AIRHUMIDIFIER_CA4] - # Map attributes to properties of the state object AVAILABLE_ATTRIBUTES_AIRPURIFIER_COMMON = { ATTR_TEMPERATURE: "temperature", @@ -553,104 +520,114 @@ SERVICE_TO_METHOD = { async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the miio fan device from config.""" - if DATA_KEY not in hass.data: - hass.data[DATA_KEY] = {} + """Import Miio configuration from YAML.""" + _LOGGER.warning( + "Loading Xiaomi Miio Fan via platform setup is deprecated. " + "Please remove it from your configuration" + ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) + ) - host = config[CONF_HOST] - token = config[CONF_TOKEN] - name = config[CONF_NAME] - model = config.get(CONF_MODEL) - _LOGGER.info("Initializing with host %s (token %s...)", host, token[:5]) - unique_id = None +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Fan from a config entry.""" + entities = [] - if model is None: - try: - miio_device = Device(host, token) - device_info = await hass.async_add_executor_job(miio_device.info) - model = device_info.model - unique_id = f"{model}-{device_info.mac_address}" - _LOGGER.info( - "%s %s %s detected", - model, - device_info.firmware_version, - device_info.hardware_version, + if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: + if DATA_KEY not in hass.data: + hass.data[DATA_KEY] = {} + + host = config_entry.data[CONF_HOST] + token = config_entry.data[CONF_TOKEN] + name = config_entry.title + model = config_entry.data[CONF_MODEL] + unique_id = config_entry.unique_id + + _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) + + if model in MODELS_PURIFIER_MIOT: + air_purifier = AirPurifierMiot(host, token) + entity = XiaomiAirPurifierMiot(name, air_purifier, config_entry, unique_id) + elif model.startswith("zhimi.airpurifier."): + air_purifier = AirPurifier(host, token) + entity = XiaomiAirPurifier(name, air_purifier, config_entry, unique_id) + elif model in MODELS_HUMIDIFIER_MIOT: + air_humidifier = AirHumidifierMiot(host, token) + entity = XiaomiAirHumidifierMiot( + name, air_humidifier, config_entry, unique_id ) - except DeviceException as ex: - raise PlatformNotReady from ex - - if model in PURIFIER_MIOT: - air_purifier = AirPurifierMiot(host, token) - device = XiaomiAirPurifierMiot(name, air_purifier, model, unique_id) - elif model.startswith("zhimi.airpurifier."): - air_purifier = AirPurifier(host, token) - device = XiaomiAirPurifier(name, air_purifier, model, unique_id) - elif model in HUMIDIFIER_MIOT: - air_humidifier = AirHumidifierMiot(host, token) - device = XiaomiAirHumidifierMiot(name, air_humidifier, model, unique_id) - elif model.startswith("zhimi.humidifier."): - air_humidifier = AirHumidifier(host, token, model=model) - device = XiaomiAirHumidifier(name, air_humidifier, model, unique_id) - elif model.startswith("zhimi.airfresh."): - air_fresh = AirFresh(host, token) - device = XiaomiAirFresh(name, air_fresh, model, unique_id) - else: - _LOGGER.error( - "Unsupported device found! Please create an issue at " - "https://github.com/syssi/xiaomi_airpurifier/issues " - "and provide the following data: %s", - model, - ) - return False - - hass.data[DATA_KEY][host] = device - async_add_entities([device], update_before_add=True) - - async def async_service_handler(service): - """Map services to methods on XiaomiAirPurifier.""" - method = SERVICE_TO_METHOD.get(service.service) - params = { - key: value for key, value in service.data.items() if key != ATTR_ENTITY_ID - } - entity_ids = service.data.get(ATTR_ENTITY_ID) - if entity_ids: - devices = [ - device - for device in hass.data[DATA_KEY].values() - if device.entity_id in entity_ids - ] + elif model.startswith("zhimi.humidifier."): + air_humidifier = AirHumidifier(host, token, model=model) + entity = XiaomiAirHumidifier(name, air_humidifier, config_entry, unique_id) + elif model.startswith("zhimi.airfresh."): + air_fresh = AirFresh(host, token) + entity = XiaomiAirFresh(name, air_fresh, config_entry, unique_id) else: - devices = hass.data[DATA_KEY].values() + _LOGGER.error( + "Unsupported device found! Please create an issue at " + "https://github.com/syssi/xiaomi_airpurifier/issues " + "and provide the following data: %s", + model, + ) + return - update_tasks = [] - for device in devices: - if not hasattr(device, method["method"]): - continue - await getattr(device, method["method"])(**params) - update_tasks.append(device.async_update_ha_state(True)) + hass.data[DATA_KEY][host] = entity + entities.append(entity) - if update_tasks: - await asyncio.wait(update_tasks) + async def async_service_handler(service): + """Map services to methods on XiaomiAirPurifier.""" + method = SERVICE_TO_METHOD[service.service] + params = { + key: value + for key, value in service.data.items() + if key != ATTR_ENTITY_ID + } + entity_ids = service.data.get(ATTR_ENTITY_ID) + if entity_ids: + entities = [ + entity + for entity in hass.data[DATA_KEY].values() + if entity.entity_id in entity_ids + ] + else: + entities = hass.data[DATA_KEY].values() - for air_purifier_service in SERVICE_TO_METHOD: - schema = SERVICE_TO_METHOD[air_purifier_service].get( - "schema", AIRPURIFIER_SERVICE_SCHEMA - ) - hass.services.async_register( - DOMAIN, air_purifier_service, async_service_handler, schema=schema - ) + update_tasks = [] + + for entity in entities: + entity_method = getattr(entity, method["method"], None) + if not entity_method: + continue + await entity_method(**params) + update_tasks.append( + hass.async_create_task(entity.async_update_ha_state(True)) + ) + + if update_tasks: + await asyncio.wait(update_tasks) + + for air_purifier_service in SERVICE_TO_METHOD: + schema = SERVICE_TO_METHOD[air_purifier_service].get( + "schema", AIRPURIFIER_SERVICE_SCHEMA + ) + hass.services.async_register( + DOMAIN, air_purifier_service, async_service_handler, schema=schema + ) + + async_add_entities(entities, update_before_add=True) -class XiaomiGenericDevice(FanEntity): +class XiaomiGenericDevice(XiaomiMiioEntity, FanEntity): """Representation of a generic Xiaomi device.""" - def __init__(self, name, device, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the generic Xiaomi device.""" - self._name = name - self._device = device - self._model = model - self._unique_id = unique_id + super().__init__(name, device, entry, unique_id) self._available = False self._state = None @@ -668,16 +645,6 @@ class XiaomiGenericDevice(FanEntity): """Poll the device.""" return True - @property - def unique_id(self): - """Return an unique ID.""" - return self._unique_id - - @property - def name(self): - """Return the name of the device if any.""" - return self._name - @property def available(self): """Return true when state is known.""" @@ -803,9 +770,9 @@ class XiaomiGenericDevice(FanEntity): class XiaomiAirPurifier(XiaomiGenericDevice): """Representation of a Xiaomi Air Purifier.""" - def __init__(self, name, device, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the plug switch.""" - super().__init__(name, device, model, unique_id) + super().__init__(name, device, entry, unique_id) if self._model == MODEL_AIRPURIFIER_PRO: self._device_features = FEATURE_FLAGS_AIRPURIFIER_PRO @@ -1056,9 +1023,9 @@ class XiaomiAirPurifierMiot(XiaomiAirPurifier): class XiaomiAirHumidifier(XiaomiGenericDevice): """Representation of a Xiaomi Air Humidifier.""" - def __init__(self, name, device, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the plug switch.""" - super().__init__(name, device, model, unique_id) + super().__init__(name, device, entry, unique_id) if self._model in [MODEL_AIRHUMIDIFIER_CA1, MODEL_AIRHUMIDIFIER_CB1]: self._device_features = FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB @@ -1214,7 +1181,6 @@ class XiaomiAirHumidifierMiot(XiaomiAirHumidifier): async def async_set_speed(self, speed: str) -> None: """Set the speed of the fan.""" - await self._try_command( "Setting operation mode of the miio device failed.", self._device.set_mode, @@ -1247,9 +1213,9 @@ class XiaomiAirHumidifierMiot(XiaomiAirHumidifier): class XiaomiAirFresh(XiaomiGenericDevice): """Representation of a Xiaomi Air Fresh.""" - def __init__(self, name, device, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the miio device.""" - super().__init__(name, device, model, unique_id) + super().__init__(name, device, entry, unique_id) self._device_features = FEATURE_FLAGS_AIRFRESH self._available_attributes = AVAILABLE_ATTRIBUTES_AIRFRESH From a43f3c1a0c360e358e5de8cf2072fe5fe3a0b5c8 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 25 Feb 2021 02:14:32 -0500 Subject: [PATCH 006/831] Fix zwave_js unique ID migration logic (#47031) --- homeassistant/components/zwave_js/__init__.py | 74 +++++++++++++----- .../components/zwave_js/discovery.py | 5 -- homeassistant/components/zwave_js/entity.py | 2 +- tests/components/zwave_js/test_init.py | 77 ++++++++++++++++++- 4 files changed, 130 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index cc58e31066a..75bc95b7fe4 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -85,6 +85,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: dev_reg = await device_registry.async_get_registry(hass) ent_reg = entity_registry.async_get(hass) + @callback + def migrate_entity(platform: str, old_unique_id: str, new_unique_id: str) -> None: + """Check if entity with old unique ID exists, and if so migrate it to new ID.""" + if entity_id := ent_reg.async_get_entity_id(platform, DOMAIN, old_unique_id): + LOGGER.debug( + "Migrating entity %s from old unique ID '%s' to new unique ID '%s'", + entity_id, + old_unique_id, + new_unique_id, + ) + ent_reg.async_update_entity( + entity_id, + new_unique_id=new_unique_id, + ) + @callback def async_on_node_ready(node: ZwaveNode) -> None: """Handle node ready event.""" @@ -97,26 +112,49 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: for disc_info in async_discover_values(node): LOGGER.debug("Discovered entity: %s", disc_info) - # This migration logic was added in 2021.3 to handle breaking change to - # value_id format. Some time in the future, this code block - # (and get_old_value_id helper) can be removed. - old_value_id = get_old_value_id(disc_info.primary_value) - old_unique_id = get_unique_id( - client.driver.controller.home_id, old_value_id + # This migration logic was added in 2021.3 to handle a breaking change to + # the value_id format. Some time in the future, this code block + # (as well as get_old_value_id helper and migrate_entity closure) can be + # removed. + value_ids = [ + # 2021.2.* format + get_old_value_id(disc_info.primary_value), + # 2021.3.0b0 format + disc_info.primary_value.value_id, + ] + + new_unique_id = get_unique_id( + client.driver.controller.home_id, + disc_info.primary_value.value_id, ) - if entity_id := ent_reg.async_get_entity_id( - disc_info.platform, DOMAIN, old_unique_id - ): - LOGGER.debug( - "Entity %s is using old unique ID, migrating to new one", entity_id - ) - ent_reg.async_update_entity( - entity_id, - new_unique_id=get_unique_id( - client.driver.controller.home_id, - disc_info.primary_value.value_id, - ), + + for value_id in value_ids: + old_unique_id = get_unique_id( + client.driver.controller.home_id, + f"{disc_info.primary_value.node.node_id}.{value_id}", ) + # Most entities have the same ID format, but notification binary sensors + # have a state key in their ID so we need to handle them differently + if ( + disc_info.platform == "binary_sensor" + and disc_info.platform_hint == "notification" + ): + for state_key in disc_info.primary_value.metadata.states: + # ignore idle key (0) + if state_key == "0": + continue + + migrate_entity( + disc_info.platform, + f"{old_unique_id}.{state_key}", + f"{new_unique_id}.{state_key}", + ) + + # Once we've iterated through all state keys, we can move on to the + # next item + continue + + migrate_entity(disc_info.platform, old_unique_id, new_unique_id) async_dispatcher_send( hass, f"{DOMAIN}_{entry.entry_id}_add_{disc_info.platform}", disc_info diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 248a34547b5..a40eb10de8b 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -24,11 +24,6 @@ class ZwaveDiscoveryInfo: # hint for the platform about this discovered entity platform_hint: Optional[str] = "" - @property - def value_id(self) -> str: - """Return the unique value_id belonging to primary value.""" - return f"{self.node.node_id}.{self.primary_value.value_id}" - @dataclass class ZWaveValueDiscoverySchema: diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 685fe50c9b6..d0ed9eb5291 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -32,7 +32,7 @@ class ZWaveBaseEntity(Entity): self.info = info self._name = self.generate_name() self._unique_id = get_unique_id( - self.client.driver.controller.home_id, self.info.value_id + self.client.driver.controller.home_id, self.info.primary_value.value_id ) # entities requiring additional values, can add extra ids to this list self.watched_value_ids = {self.info.primary_value.value_id} diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 3634454544f..f2815bec7f6 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -18,7 +18,7 @@ from homeassistant.config_entries import ( from homeassistant.const import STATE_UNAVAILABLE from homeassistant.helpers import device_registry, entity_registry -from .common import AIR_TEMPERATURE_SENSOR +from .common import AIR_TEMPERATURE_SENSOR, NOTIFICATION_MOTION_BINARY_SENSOR from tests.common import MockConfigEntry @@ -124,13 +124,15 @@ async def test_on_node_added_ready( ) -async def test_unique_id_migration(hass, multisensor_6_state, client, integration): - """Test unique ID is migrated from old format to new.""" +async def test_unique_id_migration_v1(hass, multisensor_6_state, client, integration): + """Test unique ID is migrated from old format to new (version 1).""" ent_reg = entity_registry.async_get(hass) + + # Migrate version 1 entity_name = AIR_TEMPERATURE_SENSOR.split(".")[1] # Create entity RegistryEntry using old unique ID format - old_unique_id = f"{client.driver.controller.home_id}.52-49-00-Air temperature-00" + old_unique_id = f"{client.driver.controller.home_id}.52.52-49-00-Air temperature-00" entity_entry = ent_reg.async_get_or_create( "sensor", DOMAIN, @@ -155,6 +157,73 @@ async def test_unique_id_migration(hass, multisensor_6_state, client, integratio assert entity_entry.unique_id == new_unique_id +async def test_unique_id_migration_v2(hass, multisensor_6_state, client, integration): + """Test unique ID is migrated from old format to new (version 2).""" + ent_reg = entity_registry.async_get(hass) + # Migrate version 2 + ILLUMINANCE_SENSOR = "sensor.multisensor_6_illuminance" + entity_name = ILLUMINANCE_SENSOR.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = f"{client.driver.controller.home_id}.52.52-49-0-Illuminance-00-00" + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == ILLUMINANCE_SENSOR + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, multisensor_6_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + await hass.async_block_till_done() + + # Check that new RegistryEntry is using new unique ID format + entity_entry = ent_reg.async_get(ILLUMINANCE_SENSOR) + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Illuminance-00-00" + assert entity_entry.unique_id == new_unique_id + + +async def test_unique_id_migration_notification_binary_sensor( + hass, multisensor_6_state, client, integration +): + """Test unique ID is migrated from old format to new for a notification binary sensor.""" + ent_reg = entity_registry.async_get(hass) + + entity_name = NOTIFICATION_MOTION_BINARY_SENSOR.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = f"{client.driver.controller.home_id}.52.52-113-00-Home Security-Motion sensor status.8" + entity_entry = ent_reg.async_get_or_create( + "binary_sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == NOTIFICATION_MOTION_BINARY_SENSOR + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, multisensor_6_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + await hass.async_block_till_done() + + # Check that new RegistryEntry is using new unique ID format + entity_entry = ent_reg.async_get(NOTIFICATION_MOTION_BINARY_SENSOR) + new_unique_id = f"{client.driver.controller.home_id}.52-113-0-Home Security-Motion sensor status-Motion sensor status.8" + assert entity_entry.unique_id == new_unique_id + + async def test_on_node_added_not_ready( hass, multisensor_6_state, client, integration, device_registry ): From 72263abfa986b30c778158067601a7aeecce61ff Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 25 Feb 2021 01:16:20 -0600 Subject: [PATCH 007/831] Ensure doorbird events are re-registered when changing options (#46860) - Fixed the update listener not being unsubscribed - DRY up some of the code - Fix sync code being called in async - Reduce executor jumps --- homeassistant/components/doorbird/__init__.py | 43 ++++++++++--------- homeassistant/components/doorbird/const.py | 2 + homeassistant/components/doorbird/switch.py | 5 ++- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/doorbird/__init__.py b/homeassistant/components/doorbird/__init__.py index 1dc5bf56c86..22db3c76273 100644 --- a/homeassistant/components/doorbird/__init__.py +++ b/homeassistant/components/doorbird/__init__.py @@ -34,6 +34,7 @@ from .const import ( DOOR_STATION_EVENT_ENTITY_IDS, DOOR_STATION_INFO, PLATFORMS, + UNDO_UPDATE_LISTENER, ) from .util import get_doorstation_by_token @@ -128,8 +129,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): device = DoorBird(device_ip, username, password) try: - status = await hass.async_add_executor_job(device.ready) - info = await hass.async_add_executor_job(device.info) + status, info = await hass.async_add_executor_job(_init_doorbird_device, device) except urllib.error.HTTPError as err: if err.code == HTTP_UNAUTHORIZED: _LOGGER.error( @@ -154,18 +154,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): custom_url = doorstation_config.get(CONF_CUSTOM_URL) name = doorstation_config.get(CONF_NAME) events = doorstation_options.get(CONF_EVENTS, []) - doorstation = ConfiguredDoorBird(device, name, events, custom_url, token) + doorstation = ConfiguredDoorBird(device, name, custom_url, token) + doorstation.update_events(events) # Subscribe to doorbell or motion events if not await _async_register_events(hass, doorstation): raise ConfigEntryNotReady + undo_listener = entry.add_update_listener(_update_listener) + hass.data[DOMAIN][config_entry_id] = { DOOR_STATION: doorstation, DOOR_STATION_INFO: info, + UNDO_UPDATE_LISTENER: undo_listener, } - entry.add_update_listener(_update_listener) - for component in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) @@ -174,9 +176,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): return True +def _init_doorbird_device(device): + return device.ready(), device.info() + + async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" + hass.data[DOMAIN][entry.entry_id][UNDO_UPDATE_LISTENER]() + unload_ok = all( await asyncio.gather( *[ @@ -195,7 +203,7 @@ async def _async_register_events(hass, doorstation): try: await hass.async_add_executor_job(doorstation.register_events, hass) except HTTPError: - hass.components.persistent_notification.create( + hass.components.persistent_notification.async_create( "Doorbird configuration failed. Please verify that API " "Operator permission is enabled for the Doorbird user. " "A restart will be required once permissions have been " @@ -212,8 +220,7 @@ async def _update_listener(hass: HomeAssistant, entry: ConfigEntry): """Handle options update.""" config_entry_id = entry.entry_id doorstation = hass.data[DOMAIN][config_entry_id][DOOR_STATION] - - doorstation.events = entry.options[CONF_EVENTS] + doorstation.update_events(entry.options[CONF_EVENTS]) # Subscribe to doorbell or motion events await _async_register_events(hass, doorstation) @@ -234,14 +241,19 @@ def _async_import_options_from_data_if_missing(hass: HomeAssistant, entry: Confi class ConfiguredDoorBird: """Attach additional information to pass along with configured device.""" - def __init__(self, device, name, events, custom_url, token): + def __init__(self, device, name, custom_url, token): """Initialize configured device.""" self._name = name self._device = device self._custom_url = custom_url + self.events = None + self.doorstation_events = None + self._token = token + + def update_events(self, events): + """Update the doorbird events.""" self.events = events self.doorstation_events = [self._get_event_name(event) for event in self.events] - self._token = token @property def name(self): @@ -305,16 +317,7 @@ class ConfiguredDoorBird: def webhook_is_registered(self, url, favs=None) -> bool: """Return whether the given URL is registered as a device favorite.""" - favs = favs if favs else self.device.favorites() - - if "http" not in favs: - return False - - for fav in favs["http"].values(): - if fav["value"] == url: - return True - - return False + return self.get_webhook_id(url, favs) is not None def get_webhook_id(self, url, favs=None) -> str or None: """ diff --git a/homeassistant/components/doorbird/const.py b/homeassistant/components/doorbird/const.py index af847dac673..46a95f0d500 100644 --- a/homeassistant/components/doorbird/const.py +++ b/homeassistant/components/doorbird/const.py @@ -17,3 +17,5 @@ DOORBIRD_INFO_KEY_DEVICE_TYPE = "DEVICE-TYPE" DOORBIRD_INFO_KEY_RELAYS = "RELAYS" DOORBIRD_INFO_KEY_PRIMARY_MAC_ADDR = "PRIMARY_MAC_ADDR" DOORBIRD_INFO_KEY_WIFI_MAC_ADDR = "WIFI_MAC_ADDR" + +UNDO_UPDATE_LISTENER = "undo_update_listener" diff --git a/homeassistant/components/doorbird/switch.py b/homeassistant/components/doorbird/switch.py index f1f146aebb9..424bb79092f 100644 --- a/homeassistant/components/doorbird/switch.py +++ b/homeassistant/components/doorbird/switch.py @@ -17,8 +17,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities = [] config_entry_id = config_entry.entry_id - doorstation = hass.data[DOMAIN][config_entry_id][DOOR_STATION] - doorstation_info = hass.data[DOMAIN][config_entry_id][DOOR_STATION_INFO] + data = hass.data[DOMAIN][config_entry_id] + doorstation = data[DOOR_STATION] + doorstation_info = data[DOOR_STATION_INFO] relays = doorstation_info["RELAYS"] relays.append(IR_RELAY) From 633a7aeb22f140d9defb61bc82cf0cbb3b0befd1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 25 Feb 2021 00:48:19 -0800 Subject: [PATCH 008/831] Remove deprecated credstash + keyring (#47033) --- homeassistant/scripts/check_config.py | 7 +-- homeassistant/scripts/credstash.py | 74 --------------------------- homeassistant/scripts/keyring.py | 62 ---------------------- homeassistant/util/yaml/__init__.py | 3 +- homeassistant/util/yaml/const.py | 2 - homeassistant/util/yaml/loader.py | 53 +------------------ requirements_all.txt | 9 ---- requirements_test_all.txt | 9 ---- script/gen_requirements_all.py | 3 +- tests/util/yaml/test_init.py | 44 ---------------- 10 files changed, 4 insertions(+), 262 deletions(-) delete mode 100644 homeassistant/scripts/credstash.py delete mode 100644 homeassistant/scripts/keyring.py diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index 992fce2ac87..f75594a546e 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -141,12 +141,7 @@ def run(script_args: List) -> int: if sval is None: print(" -", skey + ":", color("red", "not found")) continue - print( - " -", - skey + ":", - sval, - color("cyan", "[from:", flatsecret.get(skey, "keyring") + "]"), - ) + print(" -", skey + ":", sval) return len(res["except"]) diff --git a/homeassistant/scripts/credstash.py b/homeassistant/scripts/credstash.py deleted file mode 100644 index 99227d81b66..00000000000 --- a/homeassistant/scripts/credstash.py +++ /dev/null @@ -1,74 +0,0 @@ -"""Script to get, put and delete secrets stored in credstash.""" -import argparse -import getpass - -from homeassistant.util.yaml import _SECRET_NAMESPACE - -# mypy: allow-untyped-defs - -REQUIREMENTS = ["credstash==1.15.0"] - - -def run(args): - """Handle credstash script.""" - parser = argparse.ArgumentParser( - description=( - "Modify Home Assistant secrets in credstash." - "Use the secrets in configuration files with: " - "!secret " - ) - ) - parser.add_argument("--script", choices=["credstash"]) - parser.add_argument( - "action", - choices=["get", "put", "del", "list"], - help="Get, put or delete a secret, or list all available secrets", - ) - parser.add_argument("name", help="Name of the secret", nargs="?", default=None) - parser.add_argument( - "value", help="The value to save when putting a secret", nargs="?", default=None - ) - - # pylint: disable=import-error, no-member, import-outside-toplevel - import credstash - - args = parser.parse_args(args) - table = _SECRET_NAMESPACE - - try: - credstash.listSecrets(table=table) - except Exception: # pylint: disable=broad-except - credstash.createDdbTable(table=table) - - if args.action == "list": - secrets = [i["name"] for i in credstash.listSecrets(table=table)] - deduped_secrets = sorted(set(secrets)) - - print("Saved secrets:") - for secret in deduped_secrets: - print(secret) - return 0 - - if args.name is None: - parser.print_help() - return 1 - - if args.action == "put": - if args.value: - the_secret = args.value - else: - the_secret = getpass.getpass(f"Please enter the secret for {args.name}: ") - current_version = credstash.getHighestVersion(args.name, table=table) - credstash.putSecret( - args.name, the_secret, version=int(current_version) + 1, table=table - ) - print(f"Secret {args.name} put successfully") - elif args.action == "get": - the_secret = credstash.getSecret(args.name, table=table) - if the_secret is None: - print(f"Secret {args.name} not found") - else: - print(f"Secret {args.name}={the_secret}") - elif args.action == "del": - credstash.deleteSecrets(args.name, table=table) - print(f"Deleted secret {args.name}") diff --git a/homeassistant/scripts/keyring.py b/homeassistant/scripts/keyring.py deleted file mode 100644 index 0166d41ce0c..00000000000 --- a/homeassistant/scripts/keyring.py +++ /dev/null @@ -1,62 +0,0 @@ -"""Script to get, set and delete secrets stored in the keyring.""" -import argparse -import getpass -import os - -from homeassistant.util.yaml import _SECRET_NAMESPACE - -# mypy: allow-untyped-defs -REQUIREMENTS = ["keyring==21.2.0", "keyrings.alt==3.4.0"] - - -def run(args): - """Handle keyring script.""" - parser = argparse.ArgumentParser( - description=( - "Modify Home Assistant secrets in the default keyring. " - "Use the secrets in configuration files with: " - "!secret " - ) - ) - parser.add_argument("--script", choices=["keyring"]) - parser.add_argument( - "action", - choices=["get", "set", "del", "info"], - help="Get, set or delete a secret", - ) - parser.add_argument("name", help="Name of the secret", nargs="?", default=None) - - import keyring # pylint: disable=import-outside-toplevel - - # pylint: disable=import-outside-toplevel - from keyring.util import platform_ as platform - - args = parser.parse_args(args) - - if args.action == "info": - keyr = keyring.get_keyring() - print("Keyring version {}\n".format(REQUIREMENTS[0].split("==")[1])) - print(f"Active keyring : {keyr.__module__}") - config_name = os.path.join(platform.config_root(), "keyringrc.cfg") - print(f"Config location : {config_name}") - print(f"Data location : {platform.data_root()}\n") - elif args.name is None: - parser.print_help() - return 1 - - if args.action == "set": - entered_secret = getpass.getpass(f"Please enter the secret for {args.name}: ") - keyring.set_password(_SECRET_NAMESPACE, args.name, entered_secret) - print(f"Secret {args.name} set successfully") - elif args.action == "get": - the_secret = keyring.get_password(_SECRET_NAMESPACE, args.name) - if the_secret is None: - print(f"Secret {args.name} not found") - else: - print(f"Secret {args.name}={the_secret}") - elif args.action == "del": - try: - keyring.delete_password(_SECRET_NAMESPACE, args.name) - print(f"Deleted secret {args.name}") - except keyring.errors.PasswordDeleteError: - print(f"Secret {args.name} not found") diff --git a/homeassistant/util/yaml/__init__.py b/homeassistant/util/yaml/__init__.py index ac4ac2f9a16..a152086ea82 100644 --- a/homeassistant/util/yaml/__init__.py +++ b/homeassistant/util/yaml/__init__.py @@ -1,5 +1,5 @@ """YAML utility functions.""" -from .const import _SECRET_NAMESPACE, SECRET_YAML +from .const import SECRET_YAML from .dumper import dump, save_yaml from .input import UndefinedSubstitution, extract_inputs, substitute from .loader import clear_secret_cache, load_yaml, parse_yaml, secret_yaml @@ -7,7 +7,6 @@ from .objects import Input __all__ = [ "SECRET_YAML", - "_SECRET_NAMESPACE", "Input", "dump", "save_yaml", diff --git a/homeassistant/util/yaml/const.py b/homeassistant/util/yaml/const.py index bf1615edb93..9d930b50fd6 100644 --- a/homeassistant/util/yaml/const.py +++ b/homeassistant/util/yaml/const.py @@ -1,4 +1,2 @@ """Constants.""" SECRET_YAML = "secrets.yaml" - -_SECRET_NAMESPACE = "homeassistant" diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index 294cd0ac570..7d713c9f0c0 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -10,20 +10,9 @@ import yaml from homeassistant.exceptions import HomeAssistantError -from .const import _SECRET_NAMESPACE, SECRET_YAML +from .const import SECRET_YAML from .objects import Input, NodeListClass, NodeStrClass -try: - import keyring -except ImportError: - keyring = None - -try: - import credstash -except ImportError: - credstash = None - - # mypy: allow-untyped-calls, no-warn-return-any JSON_TYPE = Union[List, Dict, str] # pylint: disable=invalid-name @@ -32,9 +21,6 @@ DICT_T = TypeVar("DICT_T", bound=Dict) # pylint: disable=invalid-name _LOGGER = logging.getLogger(__name__) __SECRET_CACHE: Dict[str, JSON_TYPE] = {} -CREDSTASH_WARN = False -KEYRING_WARN = False - def clear_secret_cache() -> None: """Clear the secret cache. @@ -299,43 +285,6 @@ def secret_yaml(loader: SafeLineLoader, node: yaml.nodes.Node) -> JSON_TYPE: if not os.path.exists(secret_path) or len(secret_path) < 5: break # Somehow we got past the .homeassistant config folder - if keyring: - # do some keyring stuff - pwd = keyring.get_password(_SECRET_NAMESPACE, node.value) - if pwd: - global KEYRING_WARN # pylint: disable=global-statement - - if not KEYRING_WARN: - KEYRING_WARN = True - _LOGGER.warning( - "Keyring is deprecated and will be removed in March 2021." - ) - - _LOGGER.debug("Secret %s retrieved from keyring", node.value) - return pwd - - global credstash # pylint: disable=invalid-name, global-statement - - if credstash: - # pylint: disable=no-member - try: - pwd = credstash.getSecret(node.value, table=_SECRET_NAMESPACE) - if pwd: - global CREDSTASH_WARN # pylint: disable=global-statement - - if not CREDSTASH_WARN: - CREDSTASH_WARN = True - _LOGGER.warning( - "Credstash is deprecated and will be removed in March 2021." - ) - _LOGGER.debug("Secret %s retrieved from credstash", node.value) - return pwd - except credstash.ItemNotFound: - pass - except Exception: # pylint: disable=broad-except - # Catch if package installed and no config - credstash = None - raise HomeAssistantError(f"Secret {node.value} not defined") diff --git a/requirements_all.txt b/requirements_all.txt index b170a784cf3..c88761fce24 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -448,9 +448,6 @@ construct==2.10.56 # homeassistant.components.coronavirus coronavirus==1.1.1 -# homeassistant.scripts.credstash -# credstash==1.15.0 - # homeassistant.components.datadog datadog==0.15.0 @@ -844,12 +841,6 @@ kaiterra-async-client==0.0.2 # homeassistant.components.keba keba-kecontact==1.1.0 -# homeassistant.scripts.keyring -keyring==21.2.0 - -# homeassistant.scripts.keyring -keyrings.alt==3.4.0 - # homeassistant.components.kiwi kiwiki-client==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 28478c4349a..6cfb1119aad 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -239,9 +239,6 @@ construct==2.10.56 # homeassistant.components.coronavirus coronavirus==1.1.1 -# homeassistant.scripts.credstash -# credstash==1.15.0 - # homeassistant.components.datadog datadog==0.15.0 @@ -455,12 +452,6 @@ influxdb==5.2.3 # homeassistant.components.verisure jsonpath==0.82 -# homeassistant.scripts.keyring -keyring==21.2.0 - -# homeassistant.scripts.keyring -keyrings.alt==3.4.0 - # homeassistant.components.konnected konnected==1.2.0 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 7dd4924dac8..94365be9a50 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -21,7 +21,6 @@ COMMENT_REQUIREMENTS = ( "blinkt", "bluepy", "bme680", - "credstash", "decora", "decora_wifi", "envirophat", @@ -47,7 +46,7 @@ COMMENT_REQUIREMENTS = ( "VL53L1X2", ) -IGNORE_PIN = ("colorlog>2.1,<3", "keyring>=9.3,<10.0", "urllib3") +IGNORE_PIN = ("colorlog>2.1,<3", "urllib3") URL_PIN = ( "https://developers.home-assistant.io/docs/" diff --git a/tests/util/yaml/test_init.py b/tests/util/yaml/test_init.py index e28a12acf71..b3a8ca4e486 100644 --- a/tests/util/yaml/test_init.py +++ b/tests/util/yaml/test_init.py @@ -1,6 +1,5 @@ """Test Home Assistant yaml loader.""" import io -import logging import os import unittest from unittest.mock import patch @@ -15,14 +14,6 @@ from homeassistant.util.yaml import loader as yaml_loader from tests.common import get_test_config_dir, patch_yaml_files -@pytest.fixture(autouse=True) -def mock_credstash(): - """Mock credstash so it doesn't connect to the internet.""" - with patch.object(yaml_loader, "credstash") as mock_credstash: - mock_credstash.getSecret.return_value = None - yield mock_credstash - - def test_simple_list(): """Test simple list.""" conf = "config:\n - simple\n - list" @@ -294,20 +285,6 @@ def load_yaml(fname, string): return load_yaml_config_file(fname) -class FakeKeyring: - """Fake a keyring class.""" - - def __init__(self, secrets_dict): - """Store keyring dictionary.""" - self._secrets = secrets_dict - - # pylint: disable=protected-access - def get_password(self, domain, name): - """Retrieve password.""" - assert domain == yaml._SECRET_NAMESPACE - return self._secrets.get(name) - - class TestSecrets(unittest.TestCase): """Test the secrets parameter in the yaml utility.""" @@ -395,27 +372,6 @@ class TestSecrets(unittest.TestCase): "http:\n api_password: !secret test", ) - def test_secrets_keyring(self): - """Test keyring fallback & get_password.""" - yaml_loader.keyring = None # Ensure its not there - yaml_str = "http:\n api_password: !secret http_pw_keyring" - with pytest.raises(HomeAssistantError): - load_yaml(self._yaml_path, yaml_str) - - yaml_loader.keyring = FakeKeyring({"http_pw_keyring": "yeah"}) - _yaml = load_yaml(self._yaml_path, yaml_str) - assert {"http": {"api_password": "yeah"}} == _yaml - - @patch.object(yaml_loader, "credstash") - def test_secrets_credstash(self, mock_credstash): - """Test credstash fallback & get_password.""" - mock_credstash.getSecret.return_value = "yeah" - yaml_str = "http:\n api_password: !secret http_pw_credstash" - _yaml = load_yaml(self._yaml_path, yaml_str) - log = logging.getLogger() - log.error(_yaml["http"]) - assert {"api_password": "yeah"} == _yaml["http"] - def test_secrets_logger_removed(self): """Ensure logger: debug was removed.""" with pytest.raises(HomeAssistantError): From 47938a1355e8a5b8de0294b1017c02972d1d9189 Mon Sep 17 00:00:00 2001 From: Florian Heilmann Date: Thu, 25 Feb 2021 09:49:52 +0100 Subject: [PATCH 009/831] hm climate: Return PRESET_NONE instead of None (#47003) Signed-off-by: Florian Heilmann --- homeassistant/components/homematic/climate.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homematic/climate.py b/homeassistant/components/homematic/climate.py index 3b77b90ff25..aa5fb4a8e44 100644 --- a/homeassistant/components/homematic/climate.py +++ b/homeassistant/components/homematic/climate.py @@ -7,6 +7,7 @@ from homeassistant.components.climate.const import ( PRESET_BOOST, PRESET_COMFORT, PRESET_ECO, + PRESET_NONE, SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, ) @@ -92,14 +93,14 @@ class HMThermostat(HMDevice, ClimateEntity): return "boost" if not self._hm_control_mode: - return None + return PRESET_NONE mode = HM_ATTRIBUTE_SUPPORT[HM_CONTROL_MODE][1][self._hm_control_mode] mode = mode.lower() # Filter HVAC states if mode not in (HVAC_MODE_AUTO, HVAC_MODE_HEAT): - return None + return PRESET_NONE return mode @property From 65a2f07a014cb8b6fe13abd4d4482cac3dc220c4 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Thu, 25 Feb 2021 09:51:18 +0100 Subject: [PATCH 010/831] Fix missing Shelly external input (#47028) * Add support for external input (Shelly 1/1pm add-on) * Make external sensor naming consistent * Fix case consistency --- homeassistant/components/shelly/binary_sensor.py | 7 ++++++- homeassistant/components/shelly/sensor.py | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index 8f99e6a7a6e..18220fc9e3a 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -73,6 +73,11 @@ SENSORS = { default_enabled=False, removal_condition=is_momentary_input, ), + ("sensor", "extInput"): BlockAttributeDescription( + name="External Input", + device_class=DEVICE_CLASS_POWER, + default_enabled=False, + ), ("sensor", "motion"): BlockAttributeDescription( name="Motion", device_class=DEVICE_CLASS_MOTION ), @@ -86,7 +91,7 @@ REST_SENSORS = { default_enabled=False, ), "fwupdate": RestAttributeDescription( - name="Firmware update", + name="Firmware Update", icon="mdi:update", value=lambda status, _: status["update"]["has_update"], default_enabled=False, diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index 32fb33877d3..472f3be4dae 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -133,7 +133,7 @@ SENSORS = { available=lambda block: block.sensorOp == "normal", ), ("sensor", "extTemp"): BlockAttributeDescription( - name="Temperature", + name="External Temperature", unit=temperature_unit, value=lambda value: round(value, 1), device_class=sensor.DEVICE_CLASS_TEMPERATURE, @@ -155,7 +155,7 @@ SENSORS = { icon="mdi:angle-acute", ), ("relay", "totalWorkTime"): BlockAttributeDescription( - name="Lamp life", + name="Lamp Life", unit=PERCENTAGE, icon="mdi:progress-wrench", value=lambda value: round(100 - (value / 3600 / SHAIR_MAX_WORK_HOURS), 1), From 6b0c569a70672d04af6257eec4ec143842544a1d Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 25 Feb 2021 09:54:46 +0100 Subject: [PATCH 011/831] Normally there should only be one battery sensor per device from deCONZ. (#46761) With these Danfoss devices each endpoint can report its own battery state. --- homeassistant/components/deconz/sensor.py | 13 +- tests/components/deconz/test_sensor.py | 137 ++++++++++++++++++++++ 2 files changed, 149 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index 9d71fd0a9f9..e3a7e6a001a 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -200,7 +200,18 @@ class DeconzBattery(DeconzDevice): @property def unique_id(self): - """Return a unique identifier for this device.""" + """Return a unique identifier for this device. + + Normally there should only be one battery sensor per device from deCONZ. + With specific Danfoss devices each endpoint can report its own battery state. + """ + if self._device.manufacturer == "Danfoss" and self._device.modelid in [ + "0x8030", + "0x8031", + "0x8034", + "0x8035", + ]: + return f"{super().unique_id}-battery" return f"{self.serial}-battery" @property diff --git a/tests/components/deconz/test_sensor.py b/tests/components/deconz/test_sensor.py index 8a00385ccb9..1a521946335 100644 --- a/tests/components/deconz/test_sensor.py +++ b/tests/components/deconz/test_sensor.py @@ -4,6 +4,7 @@ from copy import deepcopy from homeassistant.components.deconz.const import CONF_ALLOW_CLIP_SENSOR from homeassistant.components.deconz.gateway import get_gateway_from_config_entry +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, @@ -241,6 +242,142 @@ async def test_add_battery_later(hass, aioclient_mock): assert hass.states.get("sensor.switch_1_battery_level") +async def test_special_danfoss_battery_creation(hass, aioclient_mock): + """Test the special Danfoss battery creation works. + + Normally there should only be one battery sensor per device from deCONZ. + With specific Danfoss devices each endpoint can report its own battery state. + """ + data = deepcopy(DECONZ_WEB_REQUEST) + data["sensors"] = { + "1": { + "config": { + "battery": 70, + "heatsetpoint": 2300, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 1, + "etag": "982d9acc38bee5b251e24a9be26558e4", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": { + "lastupdated": "2021-02-15T12:23:07.994", + "on": False, + "temperature": 2307, + }, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-01-0201", + }, + "2": { + "config": { + "battery": 86, + "heatsetpoint": 2300, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 2, + "etag": "62f12749f9f51c950086aff37dd02b61", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": { + "lastupdated": "2021-02-15T12:23:22.399", + "on": False, + "temperature": 2316, + }, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-02-0201", + }, + "3": { + "config": { + "battery": 86, + "heatsetpoint": 2350, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 3, + "etag": "f50061174bb7f18a3d95789bab8b646d", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": { + "lastupdated": "2021-02-15T12:23:25.466", + "on": False, + "temperature": 2337, + }, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-03-0201", + }, + "4": { + "config": { + "battery": 85, + "heatsetpoint": 2300, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 4, + "etag": "eea97adf8ce1b971b8b6a3a31793f96b", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": { + "lastupdated": "2021-02-15T12:23:41.939", + "on": False, + "temperature": 2333, + }, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-04-0201", + }, + "5": { + "config": { + "battery": 83, + "heatsetpoint": 2300, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 5, + "etag": "1f7cd1a5d66dc27ac5eb44b8c47362fb", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": {"lastupdated": "none", "on": False, "temperature": 2325}, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-05-0201", + }, + } + await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) + + assert len(hass.states.async_all()) == 10 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 5 + + async def test_air_quality_sensor(hass, aioclient_mock): """Test successful creation of air quality sensor entities.""" data = deepcopy(DECONZ_WEB_REQUEST) From 7a691f9d26e0788d540326bcf23ba1dec982995a Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 25 Feb 2021 11:11:40 +0100 Subject: [PATCH 012/831] Upgrade icmplib to 2.0.2 (#47039) --- homeassistant/components/ping/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ping/manifest.json b/homeassistant/components/ping/manifest.json index 258a75caa02..bf997d8a4c9 100644 --- a/homeassistant/components/ping/manifest.json +++ b/homeassistant/components/ping/manifest.json @@ -3,6 +3,6 @@ "name": "Ping (ICMP)", "documentation": "https://www.home-assistant.io/integrations/ping", "codeowners": [], - "requirements": ["icmplib==2.0"], + "requirements": ["icmplib==2.0.2"], "quality_scale": "internal" } diff --git a/requirements_all.txt b/requirements_all.txt index c88761fce24..bdd2e440fc6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -811,7 +811,7 @@ ibm-watson==4.0.1 ibmiotf==0.3.4 # homeassistant.components.ping -icmplib==2.0 +icmplib==2.0.2 # homeassistant.components.iglo iglo==1.2.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6cfb1119aad..871f3406fe4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -440,7 +440,7 @@ hyperion-py==0.7.0 iaqualink==0.3.4 # homeassistant.components.ping -icmplib==2.0 +icmplib==2.0.2 # homeassistant.components.influxdb influxdb-client==1.14.0 From 1989b8c07d791a60f57c2eb440dbf578f093c98d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 25 Feb 2021 03:19:21 -0800 Subject: [PATCH 013/831] Clean up discovery integration (#47022) * Clean up discovery integration * Fix tests * Remove discovery step from freebox --- .../components/daikin/config_flow.py | 10 +-------- .../components/discovery/__init__.py | 22 ++++++++++--------- homeassistant/components/freebox/__init__.py | 20 ++--------------- .../components/freebox/config_flow.py | 4 ---- .../components/freebox/manifest.json | 2 +- homeassistant/generated/zeroconf.py | 5 +++++ tests/components/daikin/test_config_flow.py | 10 ++------- tests/components/discovery/test_init.py | 6 ++--- tests/components/freebox/test_config_flow.py | 13 +---------- 9 files changed, 27 insertions(+), 65 deletions(-) diff --git a/homeassistant/components/daikin/config_flow.py b/homeassistant/components/daikin/config_flow.py index b9956a87af0..155fdd18376 100644 --- a/homeassistant/components/daikin/config_flow.py +++ b/homeassistant/components/daikin/config_flow.py @@ -12,7 +12,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PASSWORD -from .const import CONF_UUID, KEY_IP, KEY_MAC, TIMEOUT +from .const import CONF_UUID, KEY_MAC, TIMEOUT _LOGGER = logging.getLogger(__name__) @@ -124,14 +124,6 @@ class FlowHandler(config_entries.ConfigFlow): return await self.async_step_user() return await self._create_device(host) - async def async_step_discovery(self, discovery_info): - """Initialize step from discovery.""" - _LOGGER.debug("Discovered device: %s", discovery_info) - await self.async_set_unique_id(discovery_info[KEY_MAC]) - self._abort_if_unique_id_configured() - self.host = discovery_info[KEY_IP] - return await self.async_step_user() - async def async_step_zeroconf(self, discovery_info): """Prepare configuration for a discovered Daikin device.""" _LOGGER.debug("Zeroconf user_input: %s", discovery_info) diff --git a/homeassistant/components/discovery/__init__.py b/homeassistant/components/discovery/__init__.py index 2b293179888..0a3deeef33b 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -38,25 +38,17 @@ SERVICE_WEMO = "belkin_wemo" SERVICE_WINK = "wink" SERVICE_XIAOMI_GW = "xiaomi_gw" +# These have custom protocols CONFIG_ENTRY_HANDLERS = { - SERVICE_DAIKIN: "daikin", SERVICE_TELLDUSLIVE: "tellduslive", "logitech_mediaserver": "squeezebox", } +# These have no config flows SERVICE_HANDLERS = { - SERVICE_MOBILE_APP: ("mobile_app", None), - SERVICE_HASS_IOS_APP: ("ios", None), SERVICE_NETGEAR: ("device_tracker", None), - SERVICE_HASSIO: ("hassio", None), - SERVICE_APPLE_TV: ("apple_tv", None), SERVICE_ENIGMA2: ("media_player", "enigma2"), - SERVICE_WINK: ("wink", None), SERVICE_SABNZBD: ("sabnzbd", None), - SERVICE_SAMSUNG_PRINTER: ("sensor", None), - SERVICE_KONNECTED: ("konnected", None), - SERVICE_OCTOPRINT: ("octoprint", None), - SERVICE_FREEBOX: ("freebox", None), "yamaha": ("media_player", "yamaha"), "frontier_silicon": ("media_player", "frontier_silicon"), "openhome": ("media_player", "openhome"), @@ -69,20 +61,30 @@ SERVICE_HANDLERS = { OPTIONAL_SERVICE_HANDLERS = {SERVICE_DLNA_DMR: ("media_player", "dlna_dmr")} MIGRATED_SERVICE_HANDLERS = [ + SERVICE_APPLE_TV, "axis", "deconz", + SERVICE_DAIKIN, "denonavr", "esphome", + SERVICE_FREEBOX, "google_cast", + SERVICE_HASS_IOS_APP, + SERVICE_HASSIO, SERVICE_HEOS, "harmony", "homekit", "ikea_tradfri", "kodi", + SERVICE_KONNECTED, + SERVICE_MOBILE_APP, + SERVICE_OCTOPRINT, "philips_hue", + SERVICE_SAMSUNG_PRINTER, "sonos", "songpal", SERVICE_WEMO, + SERVICE_WINK, SERVICE_XIAOMI_GW, "volumio", SERVICE_YEELIGHT, diff --git a/homeassistant/components/freebox/__init__.py b/homeassistant/components/freebox/__init__.py index 9120c7d0866..35e89eb2b09 100644 --- a/homeassistant/components/freebox/__init__.py +++ b/homeassistant/components/freebox/__init__.py @@ -4,10 +4,9 @@ import logging import voluptuous as vol -from homeassistant.components.discovery import SERVICE_FREEBOX -from homeassistant.config_entries import SOURCE_DISCOVERY, SOURCE_IMPORT, ConfigEntry +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP -from homeassistant.helpers import config_validation as cv, discovery +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.typing import HomeAssistantType from .const import DOMAIN, PLATFORMS @@ -29,21 +28,6 @@ async def async_setup(hass, config): """Set up the Freebox component.""" conf = config.get(DOMAIN) - async def discovery_dispatch(service, discovery_info): - if conf is None: - host = discovery_info.get("properties", {}).get("api_domain") - port = discovery_info.get("properties", {}).get("https_port") - _LOGGER.info("Discovered Freebox server: %s:%s", host, port) - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_DISCOVERY}, - data={CONF_HOST: host, CONF_PORT: port}, - ) - ) - - discovery.async_listen(hass, SERVICE_FREEBOX, discovery_dispatch) - if conf is None: return True diff --git a/homeassistant/components/freebox/config_flow.py b/homeassistant/components/freebox/config_flow.py index 2ee52884c88..49354f16705 100644 --- a/homeassistant/components/freebox/config_flow.py +++ b/homeassistant/components/freebox/config_flow.py @@ -105,7 +105,3 @@ class FreeboxFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_import(self, user_input=None): """Import a config entry.""" return await self.async_step_user(user_input) - - async def async_step_discovery(self, discovery_info): - """Initialize step from discovery.""" - return await self.async_step_user(discovery_info) diff --git a/homeassistant/components/freebox/manifest.json b/homeassistant/components/freebox/manifest.json index 2739849b547..e6a7a17a119 100644 --- a/homeassistant/components/freebox/manifest.json +++ b/homeassistant/components/freebox/manifest.json @@ -4,6 +4,6 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/freebox", "requirements": ["freebox-api==0.0.9"], - "after_dependencies": ["discovery"], + "zeroconf": ["_fbx-api._tcp.local."], "codeowners": ["@hacf-fr", "@Quentame"] } diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index 5521ab9da8f..d3a976e78ea 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -59,6 +59,11 @@ ZEROCONF = { "domain": "esphome" } ], + "_fbx-api._tcp.local.": [ + { + "domain": "freebox" + } + ], "_googlecast._tcp.local.": [ { "domain": "cast" diff --git a/tests/components/daikin/test_config_flow.py b/tests/components/daikin/test_config_flow.py index a7165b2cb9b..076f9f54878 100644 --- a/tests/components/daikin/test_config_flow.py +++ b/tests/components/daikin/test_config_flow.py @@ -7,13 +7,8 @@ from aiohttp import ClientError from aiohttp.web_exceptions import HTTPForbidden import pytest -from homeassistant.components.daikin.const import KEY_IP, KEY_MAC -from homeassistant.config_entries import ( - SOURCE_DISCOVERY, - SOURCE_IMPORT, - SOURCE_USER, - SOURCE_ZEROCONF, -) +from homeassistant.components.daikin.const import KEY_MAC +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER, SOURCE_ZEROCONF from homeassistant.const import CONF_HOST from homeassistant.data_entry_flow import ( RESULT_TYPE_ABORT, @@ -132,7 +127,6 @@ async def test_device_abort(hass, mock_daikin, s_effect, reason): @pytest.mark.parametrize( "source, data, unique_id", [ - (SOURCE_DISCOVERY, {KEY_IP: HOST, KEY_MAC: MAC}, MAC), (SOURCE_ZEROCONF, {CONF_HOST: HOST}, MAC), ], ) diff --git a/tests/components/discovery/test_init.py b/tests/components/discovery/test_init.py index 2c1e41e8285..4dd77c98187 100644 --- a/tests/components/discovery/test_init.py +++ b/tests/components/discovery/test_init.py @@ -16,8 +16,8 @@ from tests.common import async_fire_time_changed, mock_coro SERVICE = "yamaha" SERVICE_COMPONENT = "media_player" -SERVICE_NO_PLATFORM = "hass_ios" -SERVICE_NO_PLATFORM_COMPONENT = "ios" +SERVICE_NO_PLATFORM = "netgear_router" +SERVICE_NO_PLATFORM_COMPONENT = "device_tracker" SERVICE_INFO = {"key": "value"} # Can be anything UNKNOWN_SERVICE = "this_service_will_never_be_supported" @@ -39,7 +39,7 @@ async def mock_discovery(hass, discoveries, config=BASE_CONFIG): with patch("homeassistant.components.zeroconf.async_get_instance"), patch( "homeassistant.components.zeroconf.async_setup", return_value=True ), patch.object(discovery, "_discover", discoveries), patch( - "homeassistant.components.discovery.async_discover", return_value=mock_coro() + "homeassistant.components.discovery.async_discover" ) as mock_discover, patch( "homeassistant.components.discovery.async_load_platform", return_value=mock_coro(), diff --git a/tests/components/freebox/test_config_flow.py b/tests/components/freebox/test_config_flow.py index 197be7bd3a6..ad935c47cc4 100644 --- a/tests/components/freebox/test_config_flow.py +++ b/tests/components/freebox/test_config_flow.py @@ -10,7 +10,7 @@ import pytest from homeassistant import data_entry_flow from homeassistant.components.freebox.const import DOMAIN -from homeassistant.config_entries import SOURCE_DISCOVERY, SOURCE_IMPORT, SOURCE_USER +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.const import CONF_HOST, CONF_PORT from tests.common import MockConfigEntry @@ -66,17 +66,6 @@ async def test_import(hass): assert result["step_id"] == "link" -async def test_discovery(hass): - """Test discovery step.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_DISCOVERY}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "link" - - async def test_link(hass, connect): """Test linking.""" result = await hass.config_entries.flow.async_init( From 5ec4360ac11fb042fa886de9e902d130449d3049 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 25 Feb 2021 13:08:44 +0100 Subject: [PATCH 014/831] Upgrade pyowm to 3.2.0 (#47042) --- homeassistant/components/openweathermap/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/openweathermap/manifest.json b/homeassistant/components/openweathermap/manifest.json index e355e2e4752..27cda9fb26d 100644 --- a/homeassistant/components/openweathermap/manifest.json +++ b/homeassistant/components/openweathermap/manifest.json @@ -3,6 +3,6 @@ "name": "OpenWeatherMap", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/openweathermap", - "requirements": ["pyowm==3.1.1"], + "requirements": ["pyowm==3.2.0"], "codeowners": ["@fabaff", "@freekode", "@nzapponi"] } diff --git a/requirements_all.txt b/requirements_all.txt index bdd2e440fc6..afe8c65b532 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1611,7 +1611,7 @@ pyotgw==1.0b1 pyotp==2.3.0 # homeassistant.components.openweathermap -pyowm==3.1.1 +pyowm==3.2.0 # homeassistant.components.onewire pyownet==0.10.0.post1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 871f3406fe4..ba5c9222a65 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -859,7 +859,7 @@ pyotgw==1.0b1 pyotp==2.3.0 # homeassistant.components.openweathermap -pyowm==3.1.1 +pyowm==3.2.0 # homeassistant.components.onewire pyownet==0.10.0.post1 From c9d7a2be88084cafa8aabcf4c9a2145a2e66d5fd Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 25 Feb 2021 13:09:52 +0100 Subject: [PATCH 015/831] Upgrade sendgrid to 6.6.0 (#47041) --- homeassistant/components/sendgrid/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sendgrid/manifest.json b/homeassistant/components/sendgrid/manifest.json index 1a93040837a..21ebcd828c2 100644 --- a/homeassistant/components/sendgrid/manifest.json +++ b/homeassistant/components/sendgrid/manifest.json @@ -2,6 +2,6 @@ "domain": "sendgrid", "name": "SendGrid", "documentation": "https://www.home-assistant.io/integrations/sendgrid", - "requirements": ["sendgrid==6.5.0"], + "requirements": ["sendgrid==6.6.0"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index afe8c65b532..b45cf562fc1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2016,7 +2016,7 @@ schiene==0.23 scsgate==0.1.0 # homeassistant.components.sendgrid -sendgrid==6.5.0 +sendgrid==6.6.0 # homeassistant.components.sensehat sense-hat==2.2.0 From 372ed2db910cd6c73c6681fcc34a69645819cf0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gabriel?= Date: Thu, 25 Feb 2021 09:40:01 -0300 Subject: [PATCH 016/831] Add remote control platform to Panasonic Viera (#42450) * Adding remote platform * Update homeassistant/components/panasonic_viera/remote.py Simplify entity creation Co-authored-by: Martin Hjelmare * Use Pytest fixture * Use Pytest fixtures and assert service calls * Adding conftest.py and organizing tests * Reorganizing tests Co-authored-by: Martin Hjelmare --- .../components/panasonic_viera/__init__.py | 4 +- .../components/panasonic_viera/remote.py | 90 ++++++ tests/components/panasonic_viera/conftest.py | 104 +++++++ .../panasonic_viera/test_config_flow.py | 257 ++++-------------- tests/components/panasonic_viera/test_init.py | 196 ++++++------- .../components/panasonic_viera/test_remote.py | 58 ++++ 6 files changed, 386 insertions(+), 323 deletions(-) create mode 100644 homeassistant/components/panasonic_viera/remote.py create mode 100644 tests/components/panasonic_viera/conftest.py create mode 100644 tests/components/panasonic_viera/test_remote.py diff --git a/homeassistant/components/panasonic_viera/__init__.py b/homeassistant/components/panasonic_viera/__init__.py index 3305c935890..9902d39a56c 100644 --- a/homeassistant/components/panasonic_viera/__init__.py +++ b/homeassistant/components/panasonic_viera/__init__.py @@ -8,6 +8,7 @@ from panasonic_viera import EncryptionRequired, Keys, RemoteControl, SOAPError import voluptuous as vol from homeassistant.components.media_player.const import DOMAIN as MEDIA_PLAYER_DOMAIN +from homeassistant.components.remote import DOMAIN as REMOTE_DOMAIN from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON import homeassistant.helpers.config_validation as cv @@ -46,7 +47,7 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) -PLATFORMS = [MEDIA_PLAYER_DOMAIN] +PLATFORMS = [MEDIA_PLAYER_DOMAIN, REMOTE_DOMAIN] async def async_setup(hass, config): @@ -219,6 +220,7 @@ class Remote: """Turn off the TV.""" if self.state != STATE_OFF: await self.async_send_key(Keys.power) + self.state = STATE_OFF await self.async_update() async def async_set_mute(self, enable): diff --git a/homeassistant/components/panasonic_viera/remote.py b/homeassistant/components/panasonic_viera/remote.py new file mode 100644 index 00000000000..8f3fab80215 --- /dev/null +++ b/homeassistant/components/panasonic_viera/remote.py @@ -0,0 +1,90 @@ +"""Remote control support for Panasonic Viera TV.""" +import logging + +from homeassistant.components.remote import RemoteEntity +from homeassistant.const import CONF_NAME, STATE_ON + +from .const import ( + ATTR_DEVICE_INFO, + ATTR_MANUFACTURER, + ATTR_MODEL_NUMBER, + ATTR_REMOTE, + ATTR_UDN, + DEFAULT_MANUFACTURER, + DEFAULT_MODEL_NUMBER, + DOMAIN, +) + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Panasonic Viera TV Remote from a config entry.""" + + config = config_entry.data + + remote = hass.data[DOMAIN][config_entry.entry_id][ATTR_REMOTE] + name = config[CONF_NAME] + device_info = config[ATTR_DEVICE_INFO] + + async_add_entities([PanasonicVieraRemoteEntity(remote, name, device_info)]) + + +class PanasonicVieraRemoteEntity(RemoteEntity): + """Representation of a Panasonic Viera TV Remote.""" + + def __init__(self, remote, name, device_info): + """Initialize the entity.""" + # Save a reference to the imported class + self._remote = remote + self._name = name + self._device_info = device_info + + @property + def unique_id(self): + """Return the unique ID of the device.""" + if self._device_info is None: + return None + return self._device_info[ATTR_UDN] + + @property + def device_info(self): + """Return device specific attributes.""" + if self._device_info is None: + return None + return { + "name": self._name, + "identifiers": {(DOMAIN, self._device_info[ATTR_UDN])}, + "manufacturer": self._device_info.get( + ATTR_MANUFACTURER, DEFAULT_MANUFACTURER + ), + "model": self._device_info.get(ATTR_MODEL_NUMBER, DEFAULT_MODEL_NUMBER), + } + + @property + def name(self): + """Return the name of the device.""" + return self._name + + @property + def available(self): + """Return True if the device is available.""" + return self._remote.available + + @property + def is_on(self): + """Return true if device is on.""" + return self._remote.state == STATE_ON + + async def async_turn_on(self, **kwargs): + """Turn the device on.""" + await self._remote.async_turn_on(context=self._context) + + async def async_turn_off(self, **kwargs): + """Turn the device off.""" + await self._remote.async_turn_off() + + async def async_send_command(self, command, **kwargs): + """Send a command to one device.""" + for cmd in command: + await self._remote.async_send_key(cmd) diff --git a/tests/components/panasonic_viera/conftest.py b/tests/components/panasonic_viera/conftest.py new file mode 100644 index 00000000000..d1444f01477 --- /dev/null +++ b/tests/components/panasonic_viera/conftest.py @@ -0,0 +1,104 @@ +"""Test helpers for Panasonic Viera.""" + +from unittest.mock import Mock, patch + +from panasonic_viera import TV_TYPE_ENCRYPTED, TV_TYPE_NONENCRYPTED +import pytest + +from homeassistant.components.panasonic_viera.const import ( + ATTR_FRIENDLY_NAME, + ATTR_MANUFACTURER, + ATTR_MODEL_NUMBER, + ATTR_UDN, + CONF_APP_ID, + CONF_ENCRYPTION_KEY, + CONF_ON_ACTION, + DEFAULT_MANUFACTURER, + DEFAULT_MODEL_NUMBER, + DEFAULT_NAME, + DEFAULT_PORT, +) +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT + +MOCK_BASIC_DATA = { + CONF_HOST: "0.0.0.0", + CONF_NAME: DEFAULT_NAME, +} + +MOCK_CONFIG_DATA = { + **MOCK_BASIC_DATA, + CONF_PORT: DEFAULT_PORT, + CONF_ON_ACTION: None, +} + +MOCK_ENCRYPTION_DATA = { + CONF_APP_ID: "mock-app-id", + CONF_ENCRYPTION_KEY: "mock-encryption-key", +} + +MOCK_DEVICE_INFO = { + ATTR_FRIENDLY_NAME: DEFAULT_NAME, + ATTR_MANUFACTURER: DEFAULT_MANUFACTURER, + ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER, + ATTR_UDN: "mock-unique-id", +} + + +def get_mock_remote( + request_error=None, + authorize_error=None, + encrypted=False, + app_id=None, + encryption_key=None, + device_info=MOCK_DEVICE_INFO, +): + """Return a mock remote.""" + mock_remote = Mock() + + mock_remote.type = TV_TYPE_ENCRYPTED if encrypted else TV_TYPE_NONENCRYPTED + mock_remote.app_id = app_id + mock_remote.enc_key = encryption_key + + def request_pin_code(name=None): + if request_error is not None: + raise request_error + + mock_remote.request_pin_code = request_pin_code + + def authorize_pin_code(pincode): + if pincode == "1234": + return + + if authorize_error is not None: + raise authorize_error + + mock_remote.authorize_pin_code = authorize_pin_code + + def get_device_info(): + return device_info + + mock_remote.get_device_info = get_device_info + + def send_key(key): + return + + mock_remote.send_key = Mock(send_key) + + def get_volume(key): + return 100 + + mock_remote.get_volume = Mock(get_volume) + + return mock_remote + + +@pytest.fixture(name="mock_remote") +def mock_remote_fixture(): + """Patch the library remote.""" + mock_remote = get_mock_remote() + + with patch( + "homeassistant.components.panasonic_viera.RemoteControl", + return_value=mock_remote, + ): + yield mock_remote diff --git a/tests/components/panasonic_viera/test_config_flow.py b/tests/components/panasonic_viera/test_config_flow.py index e099862604a..dd7f629c29b 100644 --- a/tests/components/panasonic_viera/test_config_flow.py +++ b/tests/components/panasonic_viera/test_config_flow.py @@ -1,90 +1,28 @@ """Test the Panasonic Viera config flow.""" -from unittest.mock import Mock, patch +from unittest.mock import patch -from panasonic_viera import TV_TYPE_ENCRYPTED, TV_TYPE_NONENCRYPTED, SOAPError -import pytest +from panasonic_viera import SOAPError from homeassistant import config_entries from homeassistant.components.panasonic_viera.const import ( ATTR_DEVICE_INFO, - ATTR_FRIENDLY_NAME, - ATTR_MANUFACTURER, - ATTR_MODEL_NUMBER, - ATTR_UDN, - CONF_APP_ID, - CONF_ENCRYPTION_KEY, - CONF_ON_ACTION, - DEFAULT_MANUFACTURER, - DEFAULT_MODEL_NUMBER, DEFAULT_NAME, - DEFAULT_PORT, DOMAIN, ERROR_INVALID_PIN_CODE, ) -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PIN, CONF_PORT +from homeassistant.const import CONF_PIN + +from .conftest import ( + MOCK_BASIC_DATA, + MOCK_CONFIG_DATA, + MOCK_DEVICE_INFO, + MOCK_ENCRYPTION_DATA, + get_mock_remote, +) from tests.common import MockConfigEntry -@pytest.fixture(name="panasonic_viera_setup", autouse=True) -def panasonic_viera_setup_fixture(): - """Mock panasonic_viera setup.""" - with patch( - "homeassistant.components.panasonic_viera.async_setup", return_value=True - ), patch( - "homeassistant.components.panasonic_viera.async_setup_entry", - return_value=True, - ): - yield - - -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=DEFAULT_MANUFACTURER, - model_number=DEFAULT_MODEL_NUMBER, - unique_id="mock-unique-id", -): - """Return a mock remote.""" - mock_remote = Mock() - - mock_remote.type = TV_TYPE_ENCRYPTED if encrypted else TV_TYPE_NONENCRYPTED - mock_remote.app_id = app_id - mock_remote.enc_key = encryption_key - - def request_pin_code(name=None): - if request_error is not None: - raise request_error - - mock_remote.request_pin_code = request_pin_code - - def authorize_pin_code(pincode): - if pincode == "1234": - return - - if authorize_error is not None: - raise authorize_error - - mock_remote.authorize_pin_code = authorize_pin_code - - def get_device_info(): - return { - ATTR_FRIENDLY_NAME: name, - ATTR_MANUFACTURER: manufacturer, - ATTR_MODEL_NUMBER: model_number, - ATTR_UDN: unique_id, - } - - mock_remote.get_device_info = get_device_info - - return mock_remote - - async def test_flow_non_encrypted(hass): """Test flow without encryption.""" @@ -103,23 +41,12 @@ async def test_flow_non_encrypted(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + MOCK_BASIC_DATA, ) assert result["type"] == "create_entry" assert result["title"] == DEFAULT_NAME - assert result["data"] == { - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: None, - ATTR_DEVICE_INFO: { - ATTR_FRIENDLY_NAME: DEFAULT_NAME, - ATTR_MANUFACTURER: DEFAULT_MANUFACTURER, - ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER, - ATTR_UDN: "mock-unique-id", - }, - } + assert result["data"] == {**MOCK_CONFIG_DATA, ATTR_DEVICE_INFO: MOCK_DEVICE_INFO} async def test_flow_not_connected_error(hass): @@ -138,7 +65,7 @@ async def test_flow_not_connected_error(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + MOCK_BASIC_DATA, ) assert result["type"] == "form" @@ -162,7 +89,7 @@ async def test_flow_unknown_abort(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + MOCK_BASIC_DATA, ) assert result["type"] == "abort" @@ -187,7 +114,7 @@ async def test_flow_encrypted_not_connected_pin_code_request(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + MOCK_BASIC_DATA, ) assert result["type"] == "abort" @@ -212,7 +139,7 @@ async def test_flow_encrypted_unknown_pin_code_request(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + MOCK_BASIC_DATA, ) assert result["type"] == "abort" @@ -231,8 +158,8 @@ async def test_flow_encrypted_valid_pin_code(hass): mock_remote = get_mock_remote( encrypted=True, - app_id="test-app-id", - encryption_key="test-encryption-key", + app_id="mock-app-id", + encryption_key="mock-encryption-key", ) with patch( @@ -241,7 +168,7 @@ async def test_flow_encrypted_valid_pin_code(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + MOCK_BASIC_DATA, ) assert result["type"] == "form" @@ -255,18 +182,9 @@ async def test_flow_encrypted_valid_pin_code(hass): assert result["type"] == "create_entry" assert result["title"] == DEFAULT_NAME assert result["data"] == { - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: None, - CONF_APP_ID: "test-app-id", - CONF_ENCRYPTION_KEY: "test-encryption-key", - ATTR_DEVICE_INFO: { - ATTR_FRIENDLY_NAME: DEFAULT_NAME, - ATTR_MANUFACTURER: DEFAULT_MANUFACTURER, - ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER, - ATTR_UDN: "mock-unique-id", - }, + **MOCK_CONFIG_DATA, + **MOCK_ENCRYPTION_DATA, + ATTR_DEVICE_INFO: MOCK_DEVICE_INFO, } @@ -288,7 +206,7 @@ async def test_flow_encrypted_invalid_pin_code_error(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + MOCK_BASIC_DATA, ) assert result["type"] == "form" @@ -326,7 +244,7 @@ async def test_flow_encrypted_not_connected_abort(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + MOCK_BASIC_DATA, ) assert result["type"] == "form" @@ -359,7 +277,7 @@ async def test_flow_encrypted_unknown_abort(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + MOCK_BASIC_DATA, ) assert result["type"] == "form" @@ -379,14 +297,14 @@ async def test_flow_non_encrypted_already_configured_abort(hass): MockConfigEntry( domain=DOMAIN, - unique_id="1.2.3.4", - data={CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME, CONF_PORT: DEFAULT_PORT}, + unique_id="0.0.0.0", + data=MOCK_CONFIG_DATA, ).add_to_hass(hass) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, - data={CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + data=MOCK_BASIC_DATA, ) assert result["type"] == "abort" @@ -398,20 +316,14 @@ async def test_flow_encrypted_already_configured_abort(hass): MockConfigEntry( domain=DOMAIN, - unique_id="1.2.3.4", - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_APP_ID: "test-app-id", - CONF_ENCRYPTION_KEY: "test-encryption-key", - }, + unique_id="0.0.0.0", + data={**MOCK_CONFIG_DATA, **MOCK_ENCRYPTION_DATA}, ).add_to_hass(hass) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, - data={CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + data=MOCK_BASIC_DATA, ) assert result["type"] == "abort" @@ -430,28 +342,12 @@ async def test_imported_flow_non_encrypted(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - }, + data=MOCK_CONFIG_DATA, ) assert result["type"] == "create_entry" assert result["title"] == DEFAULT_NAME - assert result["data"] == { - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - ATTR_DEVICE_INFO: { - ATTR_FRIENDLY_NAME: DEFAULT_NAME, - ATTR_MANUFACTURER: DEFAULT_MANUFACTURER, - ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER, - ATTR_UDN: "mock-unique-id", - }, - } + assert result["data"] == {**MOCK_CONFIG_DATA, ATTR_DEVICE_INFO: MOCK_DEVICE_INFO} async def test_imported_flow_encrypted_valid_pin_code(hass): @@ -459,8 +355,8 @@ async def test_imported_flow_encrypted_valid_pin_code(hass): mock_remote = get_mock_remote( encrypted=True, - app_id="test-app-id", - encryption_key="test-encryption-key", + app_id="mock-app-id", + encryption_key="mock-encryption-key", ) with patch( @@ -470,12 +366,7 @@ async def test_imported_flow_encrypted_valid_pin_code(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - }, + data=MOCK_CONFIG_DATA, ) assert result["type"] == "form" @@ -489,18 +380,9 @@ async def test_imported_flow_encrypted_valid_pin_code(hass): assert result["type"] == "create_entry" assert result["title"] == DEFAULT_NAME assert result["data"] == { - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - CONF_APP_ID: "test-app-id", - CONF_ENCRYPTION_KEY: "test-encryption-key", - ATTR_DEVICE_INFO: { - ATTR_FRIENDLY_NAME: DEFAULT_NAME, - ATTR_MANUFACTURER: DEFAULT_MANUFACTURER, - ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER, - ATTR_UDN: "mock-unique-id", - }, + **MOCK_CONFIG_DATA, + **MOCK_ENCRYPTION_DATA, + ATTR_DEVICE_INFO: MOCK_DEVICE_INFO, } @@ -516,12 +398,7 @@ async def test_imported_flow_encrypted_invalid_pin_code_error(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - }, + data=MOCK_CONFIG_DATA, ) assert result["type"] == "form" @@ -553,12 +430,7 @@ async def test_imported_flow_encrypted_not_connected_abort(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - }, + data=MOCK_CONFIG_DATA, ) assert result["type"] == "form" @@ -585,12 +457,7 @@ async def test_imported_flow_encrypted_unknown_abort(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - }, + data=MOCK_CONFIG_DATA, ) assert result["type"] == "form" @@ -615,12 +482,7 @@ async def test_imported_flow_not_connected_error(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - }, + data=MOCK_CONFIG_DATA, ) assert result["type"] == "form" @@ -638,12 +500,7 @@ async def test_imported_flow_unknown_abort(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - }, + data=MOCK_CONFIG_DATA, ) assert result["type"] == "abort" @@ -655,19 +512,14 @@ async def test_imported_flow_non_encrypted_already_configured_abort(hass): MockConfigEntry( domain=DOMAIN, - unique_id="1.2.3.4", - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - }, + unique_id="0.0.0.0", + data=MOCK_CONFIG_DATA, ).add_to_hass(hass) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + data=MOCK_BASIC_DATA, ) assert result["type"] == "abort" @@ -679,21 +531,14 @@ async def test_imported_flow_encrypted_already_configured_abort(hass): MockConfigEntry( domain=DOMAIN, - unique_id="1.2.3.4", - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - CONF_APP_ID: "test-app-id", - CONF_ENCRYPTION_KEY: "test-encryption-key", - }, + unique_id="0.0.0.0", + data={**MOCK_CONFIG_DATA, **MOCK_ENCRYPTION_DATA}, ).add_to_hass(hass) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + data=MOCK_BASIC_DATA, ) assert result["type"] == "abort" diff --git a/tests/components/panasonic_viera/test_init.py b/tests/components/panasonic_viera/test_init.py index 5c9bf183c6f..7351b4e5544 100644 --- a/tests/components/panasonic_viera/test_init.py +++ b/tests/components/panasonic_viera/test_init.py @@ -1,65 +1,27 @@ """Test the Panasonic Viera setup process.""" -from unittest.mock import Mock, patch +from unittest.mock import patch from homeassistant.components.panasonic_viera.const import ( ATTR_DEVICE_INFO, - ATTR_FRIENDLY_NAME, - ATTR_MANUFACTURER, - ATTR_MODEL_NUMBER, ATTR_UDN, - CONF_APP_ID, - CONF_ENCRYPTION_KEY, - CONF_ON_ACTION, - DEFAULT_MANUFACTURER, - DEFAULT_MODEL_NUMBER, DEFAULT_NAME, - DEFAULT_PORT, DOMAIN, ) from homeassistant.config_entries import ENTRY_STATE_NOT_LOADED -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, STATE_UNAVAILABLE +from homeassistant.const import CONF_HOST, STATE_UNAVAILABLE from homeassistant.setup import async_setup_component +from .conftest import ( + MOCK_CONFIG_DATA, + MOCK_DEVICE_INFO, + MOCK_ENCRYPTION_DATA, + get_mock_remote, +) + from tests.common import MockConfigEntry -MOCK_CONFIG_DATA = { - CONF_HOST: "0.0.0.0", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: None, -} -MOCK_ENCRYPTION_DATA = { - CONF_APP_ID: "mock-app-id", - CONF_ENCRYPTION_KEY: "mock-encryption-key", -} - -MOCK_DEVICE_INFO = { - ATTR_FRIENDLY_NAME: DEFAULT_NAME, - ATTR_MANUFACTURER: DEFAULT_MANUFACTURER, - ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER, - ATTR_UDN: "mock-unique-id", -} - - -def get_mock_remote(device_info=MOCK_DEVICE_INFO): - """Return a mock remote.""" - mock_remote = Mock() - - async def async_create_remote_control(during_setup=False): - return - - mock_remote.async_create_remote_control = async_create_remote_control - - async def async_get_device_info(): - return device_info - - mock_remote.async_get_device_info = async_get_device_info - - return mock_remote - - -async def test_setup_entry_encrypted(hass): +async def test_setup_entry_encrypted(hass, mock_remote): """Test setup with encrypted config entry.""" mock_entry = MockConfigEntry( domain=DOMAIN, @@ -69,22 +31,20 @@ async def test_setup_entry_encrypted(hass): mock_entry.add_to_hass(hass) - mock_remote = get_mock_remote() + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() - with patch( - "homeassistant.components.panasonic_viera.Remote", - return_value=mock_remote, - ): - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + state_tv = hass.states.get("media_player.panasonic_viera_tv") + state_remote = hass.states.get("remote.panasonic_viera_tv") - state = hass.states.get("media_player.panasonic_viera_tv") + assert state_tv + assert state_tv.name == DEFAULT_NAME - assert state - assert state.name == DEFAULT_NAME + assert state_remote + assert state_remote.name == DEFAULT_NAME -async def test_setup_entry_encrypted_missing_device_info(hass): +async def test_setup_entry_encrypted_missing_device_info(hass, mock_remote): """Test setup with encrypted config entry and missing device info.""" mock_entry = MockConfigEntry( domain=DOMAIN, @@ -94,22 +54,20 @@ async def test_setup_entry_encrypted_missing_device_info(hass): mock_entry.add_to_hass(hass) - mock_remote = get_mock_remote() + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() - with patch( - "homeassistant.components.panasonic_viera.Remote", - return_value=mock_remote, - ): - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + assert mock_entry.data[ATTR_DEVICE_INFO] == MOCK_DEVICE_INFO + assert mock_entry.unique_id == MOCK_DEVICE_INFO[ATTR_UDN] - assert mock_entry.data[ATTR_DEVICE_INFO] == MOCK_DEVICE_INFO - assert mock_entry.unique_id == MOCK_DEVICE_INFO[ATTR_UDN] + state_tv = hass.states.get("media_player.panasonic_viera_tv") + state_remote = hass.states.get("remote.panasonic_viera_tv") - state = hass.states.get("media_player.panasonic_viera_tv") + assert state_tv + assert state_tv.name == DEFAULT_NAME - assert state - assert state.name == DEFAULT_NAME + assert state_remote + assert state_remote.name == DEFAULT_NAME async def test_setup_entry_encrypted_missing_device_info_none(hass): @@ -125,7 +83,7 @@ async def test_setup_entry_encrypted_missing_device_info_none(hass): mock_remote = get_mock_remote(device_info=None) with patch( - "homeassistant.components.panasonic_viera.Remote", + "homeassistant.components.panasonic_viera.RemoteControl", return_value=mock_remote, ): await hass.config_entries.async_setup(mock_entry.entry_id) @@ -134,13 +92,17 @@ async def test_setup_entry_encrypted_missing_device_info_none(hass): assert mock_entry.data[ATTR_DEVICE_INFO] is None assert mock_entry.unique_id == MOCK_CONFIG_DATA[CONF_HOST] - state = hass.states.get("media_player.panasonic_viera_tv") + state_tv = hass.states.get("media_player.panasonic_viera_tv") + state_remote = hass.states.get("remote.panasonic_viera_tv") - assert state - assert state.name == DEFAULT_NAME + assert state_tv + assert state_tv.name == DEFAULT_NAME + + assert state_remote + assert state_remote.name == DEFAULT_NAME -async def test_setup_entry_unencrypted(hass): +async def test_setup_entry_unencrypted(hass, mock_remote): """Test setup with unencrypted config entry.""" mock_entry = MockConfigEntry( domain=DOMAIN, @@ -150,22 +112,20 @@ async def test_setup_entry_unencrypted(hass): mock_entry.add_to_hass(hass) - mock_remote = get_mock_remote() + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() - with patch( - "homeassistant.components.panasonic_viera.Remote", - return_value=mock_remote, - ): - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + state_tv = hass.states.get("media_player.panasonic_viera_tv") + state_remote = hass.states.get("remote.panasonic_viera_tv") - state = hass.states.get("media_player.panasonic_viera_tv") + assert state_tv + assert state_tv.name == DEFAULT_NAME - assert state - assert state.name == DEFAULT_NAME + assert state_remote + assert state_remote.name == DEFAULT_NAME -async def test_setup_entry_unencrypted_missing_device_info(hass): +async def test_setup_entry_unencrypted_missing_device_info(hass, mock_remote): """Test setup with unencrypted config entry and missing device info.""" mock_entry = MockConfigEntry( domain=DOMAIN, @@ -175,22 +135,20 @@ async def test_setup_entry_unencrypted_missing_device_info(hass): mock_entry.add_to_hass(hass) - mock_remote = get_mock_remote() + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() - with patch( - "homeassistant.components.panasonic_viera.Remote", - return_value=mock_remote, - ): - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + assert mock_entry.data[ATTR_DEVICE_INFO] == MOCK_DEVICE_INFO + assert mock_entry.unique_id == MOCK_DEVICE_INFO[ATTR_UDN] - assert mock_entry.data[ATTR_DEVICE_INFO] == MOCK_DEVICE_INFO - assert mock_entry.unique_id == MOCK_DEVICE_INFO[ATTR_UDN] + state_tv = hass.states.get("media_player.panasonic_viera_tv") + state_remote = hass.states.get("remote.panasonic_viera_tv") - state = hass.states.get("media_player.panasonic_viera_tv") + assert state_tv + assert state_tv.name == DEFAULT_NAME - assert state - assert state.name == DEFAULT_NAME + assert state_remote + assert state_remote.name == DEFAULT_NAME async def test_setup_entry_unencrypted_missing_device_info_none(hass): @@ -206,7 +164,7 @@ async def test_setup_entry_unencrypted_missing_device_info_none(hass): mock_remote = get_mock_remote(device_info=None) with patch( - "homeassistant.components.panasonic_viera.Remote", + "homeassistant.components.panasonic_viera.RemoteControl", return_value=mock_remote, ): await hass.config_entries.async_setup(mock_entry.entry_id) @@ -215,10 +173,14 @@ async def test_setup_entry_unencrypted_missing_device_info_none(hass): assert mock_entry.data[ATTR_DEVICE_INFO] is None assert mock_entry.unique_id == MOCK_CONFIG_DATA[CONF_HOST] - state = hass.states.get("media_player.panasonic_viera_tv") + state_tv = hass.states.get("media_player.panasonic_viera_tv") + state_remote = hass.states.get("remote.panasonic_viera_tv") - assert state - assert state.name == DEFAULT_NAME + assert state_tv + assert state_tv.name == DEFAULT_NAME + + assert state_remote + assert state_remote.name == DEFAULT_NAME async def test_setup_config_flow_initiated(hass): @@ -235,7 +197,7 @@ async def test_setup_config_flow_initiated(hass): assert len(hass.config_entries.flow.async_progress()) == 1 -async def test_setup_unload_entry(hass): +async def test_setup_unload_entry(hass, mock_remote): """Test if config entry is unloaded.""" mock_entry = MockConfigEntry( domain=DOMAIN, unique_id=MOCK_DEVICE_INFO[ATTR_UDN], data=MOCK_CONFIG_DATA @@ -243,21 +205,23 @@ async def test_setup_unload_entry(hass): mock_entry.add_to_hass(hass) - mock_remote = get_mock_remote() - - with patch( - "homeassistant.components.panasonic_viera.Remote", - return_value=mock_remote, - ): - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() await hass.config_entries.async_unload(mock_entry.entry_id) assert mock_entry.state == ENTRY_STATE_NOT_LOADED - state = hass.states.get("media_player.panasonic_viera_tv") - assert state.state == STATE_UNAVAILABLE + + state_tv = hass.states.get("media_player.panasonic_viera_tv") + state_remote = hass.states.get("remote.panasonic_viera_tv") + + assert state_tv.state == STATE_UNAVAILABLE + assert state_remote.state == STATE_UNAVAILABLE await hass.config_entries.async_remove(mock_entry.entry_id) await hass.async_block_till_done() - state = hass.states.get("media_player.panasonic_viera_tv") - assert state is None + + state_tv = hass.states.get("media_player.panasonic_viera_tv") + state_remote = hass.states.get("remote.panasonic_viera_tv") + + assert state_tv is None + assert state_remote is None diff --git a/tests/components/panasonic_viera/test_remote.py b/tests/components/panasonic_viera/test_remote.py new file mode 100644 index 00000000000..6bfd7dee8eb --- /dev/null +++ b/tests/components/panasonic_viera/test_remote.py @@ -0,0 +1,58 @@ +"""Test the Panasonic Viera remote entity.""" + +from unittest.mock import call + +from panasonic_viera import Keys + +from homeassistant.components.panasonic_viera.const import ATTR_UDN, DOMAIN +from homeassistant.components.remote import ( + ATTR_COMMAND, + DOMAIN as REMOTE_DOMAIN, + SERVICE_SEND_COMMAND, +) +from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON + +from .conftest import MOCK_CONFIG_DATA, MOCK_DEVICE_INFO, MOCK_ENCRYPTION_DATA + +from tests.common import MockConfigEntry + + +async def setup_panasonic_viera(hass): + """Initialize integration for tests.""" + mock_entry = MockConfigEntry( + domain=DOMAIN, + unique_id=MOCK_DEVICE_INFO[ATTR_UDN], + data={**MOCK_CONFIG_DATA, **MOCK_ENCRYPTION_DATA, **MOCK_DEVICE_INFO}, + ) + + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + +async def test_onoff(hass, mock_remote): + """Test the on/off service calls.""" + + await setup_panasonic_viera(hass) + + data = {ATTR_ENTITY_ID: "remote.panasonic_viera_tv"} + + await hass.services.async_call(REMOTE_DOMAIN, SERVICE_TURN_OFF, data) + await hass.services.async_call(REMOTE_DOMAIN, SERVICE_TURN_ON, data) + await hass.async_block_till_done() + + power = getattr(Keys.power, "value", Keys.power) + assert mock_remote.send_key.call_args_list == [call(power), call(power)] + + +async def test_send_command(hass, mock_remote): + """Test the send_command service call.""" + + await setup_panasonic_viera(hass) + + data = {ATTR_ENTITY_ID: "remote.panasonic_viera_tv", ATTR_COMMAND: "command"} + await hass.services.async_call(REMOTE_DOMAIN, SERVICE_SEND_COMMAND, data) + await hass.async_block_till_done() + + assert mock_remote.send_key.call_args == call("command") From 09bafafee2238d53d845a1f6f51c7fe08c51645e Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Thu, 25 Feb 2021 15:26:00 +0100 Subject: [PATCH 017/831] Bump gios library to version 0.2.0 (#47050) --- homeassistant/components/gios/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/gios/manifest.json b/homeassistant/components/gios/manifest.json index 468e22260b5..ff1f82e6ce3 100644 --- a/homeassistant/components/gios/manifest.json +++ b/homeassistant/components/gios/manifest.json @@ -3,7 +3,7 @@ "name": "GIOŚ", "documentation": "https://www.home-assistant.io/integrations/gios", "codeowners": ["@bieniu"], - "requirements": ["gios==0.1.5"], + "requirements": ["gios==0.2.0"], "config_flow": true, "quality_scale": "platinum" } diff --git a/requirements_all.txt b/requirements_all.txt index b45cf562fc1..948f786269b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -655,7 +655,7 @@ georss_qld_bushfire_alert_client==0.3 getmac==0.8.2 # homeassistant.components.gios -gios==0.1.5 +gios==0.2.0 # homeassistant.components.gitter gitterpy==0.1.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ba5c9222a65..9749e1c8574 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -349,7 +349,7 @@ georss_qld_bushfire_alert_client==0.3 getmac==0.8.2 # homeassistant.components.gios -gios==0.1.5 +gios==0.2.0 # homeassistant.components.glances glances_api==0.2.0 From 5bba532dd46be758bc2879fb666b9f2f4e9d15c7 Mon Sep 17 00:00:00 2001 From: Johan Josua Storm Date: Thu, 25 Feb 2021 15:38:45 +0100 Subject: [PATCH 018/831] Replace wrong domain returned from xbox api 2.0 (#47021) * Change solution to use yarl lib * Add check if base url needs changing * Actively remove padding query instead of omitting * Fixed popping the wrong query * Change explaination about removing mode query --- homeassistant/components/xbox/base_sensor.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/xbox/base_sensor.py b/homeassistant/components/xbox/base_sensor.py index 028f1d4c9ec..d19fbfb918d 100644 --- a/homeassistant/components/xbox/base_sensor.py +++ b/homeassistant/components/xbox/base_sensor.py @@ -1,6 +1,8 @@ """Base Sensor for the Xbox Integration.""" from typing import Optional +from yarl import URL + from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import PresenceData, XboxUpdateCoordinator @@ -44,7 +46,17 @@ class XboxBaseSensorEntity(CoordinatorEntity): if not self.data: return None - return self.data.display_pic.replace("&mode=Padding", "") + # Xbox sometimes returns a domain that uses a wrong certificate which creates issues + # with loading the image. + # The correct domain is images-eds-ssl which can just be replaced + # to point to the correct image, with the correct domain and certificate. + # We need to also remove the 'mode=Padding' query because with it, it results in an error 400. + url = URL(self.data.display_pic) + if url.host == "images-eds.xboxlive.com": + url = url.with_host("images-eds-ssl.xboxlive.com") + query = dict(url.query) + query.pop("mode", None) + return str(url.with_query(query)) @property def entity_registry_enabled_default(self) -> bool: From 125206adbf85cf49bf72336db658f7776b924fe5 Mon Sep 17 00:00:00 2001 From: Quentame Date: Thu, 25 Feb 2021 16:50:58 +0100 Subject: [PATCH 019/831] Add zeroconf discovery to Freebox (#47045) * Add zeroconf discovery to Freebox - remove deprecated discovery - tried with SSDP too but the presentation URL is not the same (*.fbxos.fr for zeroconf, http://mafreebox.freebox.fr/ for SSDP) - so config entry unique_id should be the MAC (included into SSDP, but not zeroconf, can be retrieve from `fbx.system.get_config()`) - DHCP discovery might be added in the future too * host and port are required on zeroconf * cleanup in other PR --- .../components/discovery/__init__.py | 2 -- homeassistant/components/freebox/__init__.py | 22 +++++-------- .../components/freebox/config_flow.py | 6 ++++ tests/components/freebox/test_config_flow.py | 32 ++++++++++++++++++- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/discovery/__init__.py b/homeassistant/components/discovery/__init__.py index 0a3deeef33b..883958226d8 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -22,7 +22,6 @@ SERVICE_APPLE_TV = "apple_tv" SERVICE_DAIKIN = "daikin" SERVICE_DLNA_DMR = "dlna_dmr" SERVICE_ENIGMA2 = "enigma2" -SERVICE_FREEBOX = "freebox" SERVICE_HASS_IOS_APP = "hass_ios" SERVICE_HASSIO = "hassio" SERVICE_HEOS = "heos" @@ -67,7 +66,6 @@ MIGRATED_SERVICE_HANDLERS = [ SERVICE_DAIKIN, "denonavr", "esphome", - SERVICE_FREEBOX, "google_cast", SERVICE_HASS_IOS_APP, SERVICE_HASSIO, diff --git a/homeassistant/components/freebox/__init__.py b/homeassistant/components/freebox/__init__.py index 35e89eb2b09..f36a2303b6d 100644 --- a/homeassistant/components/freebox/__init__.py +++ b/homeassistant/components/freebox/__init__.py @@ -25,26 +25,20 @@ CONFIG_SCHEMA = vol.Schema( async def async_setup(hass, config): - """Set up the Freebox component.""" - conf = config.get(DOMAIN) - - if conf is None: - return True - - for freebox_conf in conf: - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data=freebox_conf, + """Set up the Freebox integration.""" + if DOMAIN in config: + for entry_config in config[DOMAIN]: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=entry_config + ) ) - ) return True async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): - """Set up Freebox component.""" + """Set up Freebox entry.""" router = FreeboxRouter(hass, entry) await router.setup() diff --git a/homeassistant/components/freebox/config_flow.py b/homeassistant/components/freebox/config_flow.py index 49354f16705..f4fde23473f 100644 --- a/homeassistant/components/freebox/config_flow.py +++ b/homeassistant/components/freebox/config_flow.py @@ -105,3 +105,9 @@ class FreeboxFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_import(self, user_input=None): """Import a config entry.""" return await self.async_step_user(user_input) + + async def async_step_zeroconf(self, discovery_info: dict): + """Initialize flow from zeroconf.""" + host = discovery_info["properties"]["api_domain"] + port = discovery_info["properties"]["https_port"] + return await self.async_step_user({CONF_HOST: host, CONF_PORT: port}) diff --git a/tests/components/freebox/test_config_flow.py b/tests/components/freebox/test_config_flow.py index ad935c47cc4..5f3aace9465 100644 --- a/tests/components/freebox/test_config_flow.py +++ b/tests/components/freebox/test_config_flow.py @@ -10,7 +10,7 @@ import pytest from homeassistant import data_entry_flow from homeassistant.components.freebox.const import DOMAIN -from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER, SOURCE_ZEROCONF from homeassistant.const import CONF_HOST, CONF_PORT from tests.common import MockConfigEntry @@ -18,6 +18,25 @@ from tests.common import MockConfigEntry HOST = "myrouter.freeboxos.fr" PORT = 1234 +MOCK_ZEROCONF_DATA = { + "host": "192.168.0.254", + "port": 80, + "hostname": "Freebox-Server.local.", + "type": "_fbx-api._tcp.local.", + "name": "Freebox Server._fbx-api._tcp.local.", + "properties": { + "api_version": "8.0", + "device_type": "FreeboxServer1,2", + "api_base_url": "/api/", + "uid": "b15ab20debb399f95001a9ca207d2777", + "https_available": "1", + "https_port": f"{PORT}", + "box_model": "fbxgw-r2/full", + "box_model_name": "Freebox Server (r2)", + "api_domain": HOST, + }, +} + @pytest.fixture(name="connect") def mock_controller_connect(): @@ -66,6 +85,17 @@ async def test_import(hass): assert result["step_id"] == "link" +async def test_zeroconf(hass): + """Test zeroconf step.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_ZEROCONF}, + data=MOCK_ZEROCONF_DATA, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "link" + + async def test_link(hass, connect): """Test linking.""" result = await hass.config_entries.flow.async_init( From 4e09d7ee0feadb25f3c318c7dc931586bb40c427 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Thu, 25 Feb 2021 16:53:59 +0100 Subject: [PATCH 020/831] Clean up Netatmo webhook handler (#47037) --- homeassistant/components/netatmo/__init__.py | 10 ++++---- homeassistant/components/netatmo/webhook.py | 25 ++++++++++---------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py index cdbd34991f2..5827486429a 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -43,7 +43,7 @@ from .const import ( OAUTH2_TOKEN, ) from .data_handler import NetatmoDataHandler -from .webhook import handle_webhook +from .webhook import async_handle_webhook _LOGGER = logging.getLogger(__name__) @@ -157,18 +157,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): try: webhook_register( - hass, DOMAIN, "Netatmo", entry.data[CONF_WEBHOOK_ID], handle_webhook + hass, + DOMAIN, + "Netatmo", + entry.data[CONF_WEBHOOK_ID], + async_handle_webhook, ) async def handle_event(event): """Handle webhook events.""" if event["data"]["push_type"] == "webhook_activation": if activation_listener is not None: - _LOGGER.debug("sub called") activation_listener() if activation_timeout is not None: - _LOGGER.debug("Unsub called") activation_timeout() activation_listener = async_dispatcher_connect( diff --git a/homeassistant/components/netatmo/webhook.py b/homeassistant/components/netatmo/webhook.py index 1fe7302038e..5ecc3d41789 100644 --- a/homeassistant/components/netatmo/webhook.py +++ b/homeassistant/components/netatmo/webhook.py @@ -1,14 +1,13 @@ """The Netatmo integration.""" import logging -from homeassistant.const import ATTR_DEVICE_ID, ATTR_ID +from homeassistant.const import ATTR_DEVICE_ID, ATTR_ID, ATTR_NAME from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import ( ATTR_EVENT_TYPE, ATTR_FACE_URL, ATTR_IS_KNOWN, - ATTR_NAME, ATTR_PERSONS, DATA_DEVICE_IDS, DATA_PERSONS, @@ -20,13 +19,13 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -EVENT_TYPE_MAP = { +SUBEVENT_TYPE_MAP = { "outdoor": "", "therm_mode": "", } -async def handle_webhook(hass, webhook_id, request): +async def async_handle_webhook(hass, webhook_id, request): """Handle webhook callback.""" try: data = await request.json() @@ -38,17 +37,17 @@ async def handle_webhook(hass, webhook_id, request): event_type = data.get(ATTR_EVENT_TYPE) - if event_type in EVENT_TYPE_MAP: - await async_send_event(hass, event_type, data) + if event_type in SUBEVENT_TYPE_MAP: + async_send_event(hass, event_type, data) - for event_data in data.get(EVENT_TYPE_MAP[event_type], []): - await async_evaluate_event(hass, event_data) + for event_data in data.get(SUBEVENT_TYPE_MAP[event_type], []): + async_evaluate_event(hass, event_data) else: - await async_evaluate_event(hass, data) + async_evaluate_event(hass, data) -async def async_evaluate_event(hass, event_data): +def async_evaluate_event(hass, event_data): """Evaluate events from webhook.""" event_type = event_data.get(ATTR_EVENT_TYPE) @@ -62,13 +61,13 @@ async def async_evaluate_event(hass, event_data): person_event_data[ATTR_IS_KNOWN] = person.get(ATTR_IS_KNOWN) person_event_data[ATTR_FACE_URL] = person.get(ATTR_FACE_URL) - await async_send_event(hass, event_type, person_event_data) + async_send_event(hass, event_type, person_event_data) else: - await async_send_event(hass, event_type, event_data) + async_send_event(hass, event_type, event_data) -async def async_send_event(hass, event_type, data): +def async_send_event(hass, event_type, data): """Send events.""" _LOGGER.debug("%s: %s", event_type, data) async_dispatcher_send( From 3a829174007edd171b1b187d47a83934fa26f15e Mon Sep 17 00:00:00 2001 From: Ron Klinkien Date: Thu, 25 Feb 2021 17:39:57 +0100 Subject: [PATCH 021/831] Bump python-garminconnect to 0.1.19 to fix broken api (#47020) --- homeassistant/components/garmin_connect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/garmin_connect/manifest.json b/homeassistant/components/garmin_connect/manifest.json index c7880f9b416..59597750ce8 100644 --- a/homeassistant/components/garmin_connect/manifest.json +++ b/homeassistant/components/garmin_connect/manifest.json @@ -2,7 +2,7 @@ "domain": "garmin_connect", "name": "Garmin Connect", "documentation": "https://www.home-assistant.io/integrations/garmin_connect", - "requirements": ["garminconnect==0.1.16"], + "requirements": ["garminconnect==0.1.19"], "codeowners": ["@cyberjunky"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 948f786269b..341487e3e35 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -623,7 +623,7 @@ fritzconnection==1.4.0 gTTS==2.2.2 # homeassistant.components.garmin_connect -garminconnect==0.1.16 +garminconnect==0.1.19 # homeassistant.components.geizhals geizhals==0.0.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9749e1c8574..5633b1557de 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -323,7 +323,7 @@ fritzconnection==1.4.0 gTTS==2.2.2 # homeassistant.components.garmin_connect -garminconnect==0.1.16 +garminconnect==0.1.19 # homeassistant.components.geo_json_events # homeassistant.components.usgs_earthquakes_feed From f4db74fe732bdda2dfa7d806b01e8e17a8354e0e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 25 Feb 2021 11:08:04 -0600 Subject: [PATCH 022/831] Fix bond typing in config flow (#47055) --- homeassistant/components/bond/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/bond/config_flow.py b/homeassistant/components/bond/config_flow.py index f81e3a0be5c..f4e9babdff4 100644 --- a/homeassistant/components/bond/config_flow.py +++ b/homeassistant/components/bond/config_flow.py @@ -59,7 +59,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize config flow.""" - self._discovered: dict = None + self._discovered: Optional[dict] = None async def _async_try_automatic_configure(self): """Try to auto configure the device. From 7d90cdea1e751436ad148cd27cdc48455ca5b827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 25 Feb 2021 19:52:11 +0100 Subject: [PATCH 023/831] Use dispatch instead of eventbus for supervisor events (#46986) Co-authored-by: Martin Hjelmare Co-authored-by: Paulus Schoutsen --- homeassistant/components/hassio/const.py | 3 +- .../components/hassio/websocket_api.py | 27 ++++- tests/components/hassio/__init__.py | 45 +++++++ tests/components/hassio/test_init.py | 114 +----------------- tests/components/hassio/test_websocket_api.py | 90 ++++++++++++++ 5 files changed, 164 insertions(+), 115 deletions(-) create mode 100644 tests/components/hassio/test_websocket_api.py diff --git a/homeassistant/components/hassio/const.py b/homeassistant/components/hassio/const.py index a3e4451312a..b2878c8143f 100644 --- a/homeassistant/components/hassio/const.py +++ b/homeassistant/components/hassio/const.py @@ -33,7 +33,8 @@ X_HASS_IS_ADMIN = "X-Hass-Is-Admin" WS_TYPE = "type" WS_ID = "id" -WS_TYPE_EVENT = "supervisor/event" WS_TYPE_API = "supervisor/api" +WS_TYPE_EVENT = "supervisor/event" +WS_TYPE_SUBSCRIBE = "supervisor/subscribe" EVENT_SUPERVISOR_EVENT = "supervisor_event" diff --git a/homeassistant/components/hassio/websocket_api.py b/homeassistant/components/hassio/websocket_api.py index d2c0bc9ed10..387aa926489 100644 --- a/homeassistant/components/hassio/websocket_api.py +++ b/homeassistant/components/hassio/websocket_api.py @@ -7,6 +7,10 @@ from homeassistant.components import websocket_api from homeassistant.components.websocket_api.connection import ActiveConnection from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from .const import ( ATTR_DATA, @@ -20,6 +24,7 @@ from .const import ( WS_TYPE, WS_TYPE_API, WS_TYPE_EVENT, + WS_TYPE_SUBSCRIBE, ) from .handler import HassIO @@ -36,6 +41,26 @@ def async_load_websocket_api(hass: HomeAssistant): """Set up the websocket API.""" websocket_api.async_register_command(hass, websocket_supervisor_event) websocket_api.async_register_command(hass, websocket_supervisor_api) + websocket_api.async_register_command(hass, websocket_subscribe) + + +@websocket_api.require_admin +@websocket_api.async_response +@websocket_api.websocket_command({vol.Required(WS_TYPE): WS_TYPE_SUBSCRIBE}) +async def websocket_subscribe( + hass: HomeAssistant, connection: ActiveConnection, msg: dict +): + """Subscribe to supervisor events.""" + + @callback + def forward_messages(data): + """Forward events to websocket.""" + connection.send_message(websocket_api.event_message(msg[WS_ID], data)) + + connection.subscriptions[msg[WS_ID]] = async_dispatcher_connect( + hass, EVENT_SUPERVISOR_EVENT, forward_messages + ) + connection.send_message(websocket_api.result_message(msg[WS_ID])) @websocket_api.async_response @@ -49,7 +74,7 @@ async def websocket_supervisor_event( hass: HomeAssistant, connection: ActiveConnection, msg: dict ): """Publish events from the Supervisor.""" - hass.bus.async_fire(EVENT_SUPERVISOR_EVENT, msg[ATTR_DATA]) + async_dispatcher_send(hass, EVENT_SUPERVISOR_EVENT, msg[ATTR_DATA]) connection.send_result(msg[WS_ID]) diff --git a/tests/components/hassio/__init__.py b/tests/components/hassio/__init__.py index ad9829f17ff..f3f35b62562 100644 --- a/tests/components/hassio/__init__.py +++ b/tests/components/hassio/__init__.py @@ -1,3 +1,48 @@ """Tests for Hass.io component.""" +import pytest HASSIO_TOKEN = "123456" + + +@pytest.fixture(autouse=True) +def mock_all(aioclient_mock): + """Mock all setup requests.""" + aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"}) + aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"}) + aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"}) + aioclient_mock.get( + "http://127.0.0.1/info", + json={ + "result": "ok", + "data": {"supervisor": "222", "homeassistant": "0.110.0", "hassos": None}, + }, + ) + aioclient_mock.get( + "http://127.0.0.1/host/info", + json={ + "result": "ok", + "data": { + "result": "ok", + "data": { + "chassis": "vm", + "operating_system": "Debian GNU/Linux 10 (buster)", + "kernel": "4.19.0-6-amd64", + }, + }, + }, + ) + aioclient_mock.get( + "http://127.0.0.1/core/info", + json={"result": "ok", "data": {"version_latest": "1.0.0"}}, + ) + aioclient_mock.get( + "http://127.0.0.1/os/info", + json={"result": "ok", "data": {"version_latest": "1.0.0"}}, + ) + aioclient_mock.get( + "http://127.0.0.1/supervisor/info", + json={"result": "ok", "data": {"version_latest": "1.0.0"}}, + ) + aioclient_mock.get( + "http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}} + ) diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index eaeed74fbf7..2efb5b0744e 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -2,73 +2,16 @@ import os from unittest.mock import patch -import pytest - from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.components import frontend from homeassistant.components.hassio import STORAGE_KEY -from homeassistant.components.hassio.const import ( - ATTR_DATA, - ATTR_ENDPOINT, - ATTR_METHOD, - EVENT_SUPERVISOR_EVENT, - WS_ID, - WS_TYPE, - WS_TYPE_API, - WS_TYPE_EVENT, -) -from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component -from tests.common import async_capture_events +from . import mock_all # noqa MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"} -@pytest.fixture(autouse=True) -def mock_all(aioclient_mock): - """Mock all setup requests.""" - aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"}) - aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"}) - aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"}) - aioclient_mock.get( - "http://127.0.0.1/info", - json={ - "result": "ok", - "data": {"supervisor": "222", "homeassistant": "0.110.0", "hassos": None}, - }, - ) - aioclient_mock.get( - "http://127.0.0.1/host/info", - json={ - "result": "ok", - "data": { - "result": "ok", - "data": { - "chassis": "vm", - "operating_system": "Debian GNU/Linux 10 (buster)", - "kernel": "4.19.0-6-amd64", - }, - }, - }, - ) - aioclient_mock.get( - "http://127.0.0.1/core/info", - json={"result": "ok", "data": {"version_latest": "1.0.0"}}, - ) - aioclient_mock.get( - "http://127.0.0.1/os/info", - json={"result": "ok", "data": {"version_latest": "1.0.0"}}, - ) - aioclient_mock.get( - "http://127.0.0.1/supervisor/info", - json={"result": "ok", "data": {"version_latest": "1.0.0"}}, - ) - aioclient_mock.get( - "http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}} - ) - - async def test_setup_api_ping(hass, aioclient_mock): """Test setup with API ping.""" with patch.dict(os.environ, MOCK_ENVIRON): @@ -359,58 +302,3 @@ async def test_service_calls_core(hassio_env, hass, aioclient_mock): assert mock_check_config.called assert aioclient_mock.call_count == 5 - - -async def test_websocket_supervisor_event( - hassio_env, hass: HomeAssistant, hass_ws_client -): - """Test Supervisor websocket event.""" - assert await async_setup_component(hass, "hassio", {}) - websocket_client = await hass_ws_client(hass) - - test_event = async_capture_events(hass, EVENT_SUPERVISOR_EVENT) - - await websocket_client.send_json( - {WS_ID: 1, WS_TYPE: WS_TYPE_EVENT, ATTR_DATA: {"event": "test"}} - ) - - assert await websocket_client.receive_json() - await hass.async_block_till_done() - - assert test_event[0].data == {"event": "test"} - - -async def test_websocket_supervisor_api( - hassio_env, hass: HomeAssistant, hass_ws_client, aioclient_mock -): - """Test Supervisor websocket api.""" - assert await async_setup_component(hass, "hassio", {}) - websocket_client = await hass_ws_client(hass) - aioclient_mock.post( - "http://127.0.0.1/snapshots/new/partial", - json={"result": "ok", "data": {"slug": "sn_slug"}}, - ) - - await websocket_client.send_json( - { - WS_ID: 1, - WS_TYPE: WS_TYPE_API, - ATTR_ENDPOINT: "/snapshots/new/partial", - ATTR_METHOD: "post", - } - ) - - msg = await websocket_client.receive_json() - assert msg["result"]["slug"] == "sn_slug" - - await websocket_client.send_json( - { - WS_ID: 2, - WS_TYPE: WS_TYPE_API, - ATTR_ENDPOINT: "/supervisor/info", - ATTR_METHOD: "get", - } - ) - - msg = await websocket_client.receive_json() - assert msg["result"]["version_latest"] == "1.0.0" diff --git a/tests/components/hassio/test_websocket_api.py b/tests/components/hassio/test_websocket_api.py new file mode 100644 index 00000000000..18da5df13ea --- /dev/null +++ b/tests/components/hassio/test_websocket_api.py @@ -0,0 +1,90 @@ +"""Test websocket API.""" +from homeassistant.components.hassio.const import ( + ATTR_DATA, + ATTR_ENDPOINT, + ATTR_METHOD, + ATTR_WS_EVENT, + EVENT_SUPERVISOR_EVENT, + WS_ID, + WS_TYPE, + WS_TYPE_API, + WS_TYPE_SUBSCRIBE, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.setup import async_setup_component + +from . import mock_all # noqa + +from tests.common import async_mock_signal + + +async def test_ws_subscription(hassio_env, hass: HomeAssistant, hass_ws_client): + """Test websocket subscription.""" + assert await async_setup_component(hass, "hassio", {}) + client = await hass_ws_client(hass) + await client.send_json({WS_ID: 5, WS_TYPE: WS_TYPE_SUBSCRIBE}) + response = await client.receive_json() + assert response["success"] + + calls = async_mock_signal(hass, EVENT_SUPERVISOR_EVENT) + async_dispatcher_send(hass, EVENT_SUPERVISOR_EVENT, {"lorem": "ipsum"}) + + response = await client.receive_json() + assert response["event"]["lorem"] == "ipsum" + assert len(calls) == 1 + + await client.send_json( + { + WS_ID: 6, + WS_TYPE: "supervisor/event", + ATTR_DATA: {ATTR_WS_EVENT: "test", "lorem": "ipsum"}, + } + ) + response = await client.receive_json() + assert response["success"] + assert len(calls) == 2 + + response = await client.receive_json() + assert response["event"]["lorem"] == "ipsum" + + # Unsubscribe + await client.send_json({WS_ID: 7, WS_TYPE: "unsubscribe_events", "subscription": 5}) + response = await client.receive_json() + assert response["success"] + + +async def test_websocket_supervisor_api( + hassio_env, hass: HomeAssistant, hass_ws_client, aioclient_mock +): + """Test Supervisor websocket api.""" + assert await async_setup_component(hass, "hassio", {}) + websocket_client = await hass_ws_client(hass) + aioclient_mock.post( + "http://127.0.0.1/snapshots/new/partial", + json={"result": "ok", "data": {"slug": "sn_slug"}}, + ) + + await websocket_client.send_json( + { + WS_ID: 1, + WS_TYPE: WS_TYPE_API, + ATTR_ENDPOINT: "/snapshots/new/partial", + ATTR_METHOD: "post", + } + ) + + msg = await websocket_client.receive_json() + assert msg["result"]["slug"] == "sn_slug" + + await websocket_client.send_json( + { + WS_ID: 2, + WS_TYPE: WS_TYPE_API, + ATTR_ENDPOINT: "/supervisor/info", + ATTR_METHOD: "get", + } + ) + + msg = await websocket_client.receive_json() + assert msg["result"]["version_latest"] == "1.0.0" From d0842910554d6d4eea22a65857b6a65314c342f2 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 25 Feb 2021 21:34:04 +0100 Subject: [PATCH 024/831] Updated frontend to 20210225.0 (#47059) --- 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 623aaf42ca5..e8e9c44ae78 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20210224.0" + "home-assistant-frontend==20210225.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index e4f84f0c8ef..8f84546371e 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210224.0 +home-assistant-frontend==20210225.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 341487e3e35..780dca13d7a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -763,7 +763,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210224.0 +home-assistant-frontend==20210225.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5633b1557de..d587cb98a0a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -412,7 +412,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210224.0 +home-assistant-frontend==20210225.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From e3105c7eb14af02b773fca5316f271b7301f078c Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 26 Feb 2021 00:28:22 +0100 Subject: [PATCH 025/831] Revert CORS changes for my home assistant (#47064) * Revert CORS changes for my home assistant * Update test_init.py * Update test_init.py --- homeassistant/components/api/__init__.py | 1 - homeassistant/components/http/__init__.py | 2 +- tests/components/http/test_init.py | 5 +---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index a82309094e3..e40a9332c38 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -178,7 +178,6 @@ class APIDiscoveryView(HomeAssistantView): requires_auth = False url = URL_API_DISCOVERY_INFO name = "api:discovery" - cors_allowed = True async def get(self, request): """Get discovery information.""" diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index d09cfe754a9..993d466ae18 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -59,7 +59,7 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_DEVELOPMENT = "0" # Cast to be able to load custom cards. # My to be able to check url and version info. -DEFAULT_CORS = ["https://cast.home-assistant.io", "https://my.home-assistant.io"] +DEFAULT_CORS = ["https://cast.home-assistant.io"] NO_LOGIN_ATTEMPT_THRESHOLD = -1 MAX_CLIENT_SIZE: int = 1024 ** 2 * 16 diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index 9621b269081..993f0dba1fd 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -175,10 +175,7 @@ async def test_cors_defaults(hass): assert await async_setup_component(hass, "http", {}) assert len(mock_setup.mock_calls) == 1 - assert mock_setup.mock_calls[0][1][1] == [ - "https://cast.home-assistant.io", - "https://my.home-assistant.io", - ] + assert mock_setup.mock_calls[0][1][1] == ["https://cast.home-assistant.io"] async def test_storing_config(hass, aiohttp_client, aiohttp_unused_port): From 989d3e5c872b45e9808f2df8d205377feee58f42 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 26 Feb 2021 00:06:13 +0000 Subject: [PATCH 026/831] [ci skip] Translation update --- .../components/airvisual/translations/lb.json | 9 ++++- .../components/bond/translations/cs.json | 2 +- .../components/climacell/translations/cs.json | 20 ++++++++++ .../components/climacell/translations/es.json | 30 +++++++++++++++ .../climacell/translations/zh-Hant.json | 34 +++++++++++++++++ .../faa_delays/translations/cs.json | 8 ++++ .../faa_delays/translations/es.json | 19 ++++++++++ .../faa_delays/translations/et.json | 21 +++++++++++ .../faa_delays/translations/ru.json | 21 +++++++++++ .../faa_delays/translations/zh-Hant.json | 21 +++++++++++ .../components/homekit/translations/cs.json | 2 +- .../components/kmtronic/translations/cs.json | 21 +++++++++++ .../components/litejet/translations/es.json | 16 ++++++++ .../components/mazda/translations/es.json | 1 + .../components/mullvad/translations/cs.json | 21 +++++++++++ .../components/mullvad/translations/es.json | 9 +++++ .../mullvad/translations/zh-Hant.json | 22 +++++++++++ .../components/netatmo/translations/es.json | 22 +++++++++++ .../netatmo/translations/zh-Hant.json | 22 +++++++++++ .../translations/es.json | 9 +++++ .../components/shelly/translations/nl.json | 4 +- .../components/soma/translations/cs.json | 2 +- .../components/spotify/translations/cs.json | 2 +- .../components/subaru/translations/cs.json | 28 ++++++++++++++ .../components/subaru/translations/es.json | 37 +++++++++++++++++++ .../totalconnect/translations/cs.json | 8 +++- .../totalconnect/translations/es.json | 10 ++++- .../wolflink/translations/sensor.nl.json | 16 ++++++++ .../xiaomi_miio/translations/es.json | 1 + .../components/zwave_js/translations/es.json | 7 +++- 30 files changed, 435 insertions(+), 10 deletions(-) create mode 100644 homeassistant/components/climacell/translations/cs.json create mode 100644 homeassistant/components/climacell/translations/es.json create mode 100644 homeassistant/components/climacell/translations/zh-Hant.json create mode 100644 homeassistant/components/faa_delays/translations/cs.json create mode 100644 homeassistant/components/faa_delays/translations/es.json create mode 100644 homeassistant/components/faa_delays/translations/et.json create mode 100644 homeassistant/components/faa_delays/translations/ru.json create mode 100644 homeassistant/components/faa_delays/translations/zh-Hant.json create mode 100644 homeassistant/components/kmtronic/translations/cs.json create mode 100644 homeassistant/components/litejet/translations/es.json create mode 100644 homeassistant/components/mullvad/translations/cs.json create mode 100644 homeassistant/components/mullvad/translations/es.json create mode 100644 homeassistant/components/mullvad/translations/zh-Hant.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/es.json create mode 100644 homeassistant/components/subaru/translations/cs.json create mode 100644 homeassistant/components/subaru/translations/es.json create mode 100644 homeassistant/components/wolflink/translations/sensor.nl.json diff --git a/homeassistant/components/airvisual/translations/lb.json b/homeassistant/components/airvisual/translations/lb.json index 5e45098c11d..d6799ba6e37 100644 --- a/homeassistant/components/airvisual/translations/lb.json +++ b/homeassistant/components/airvisual/translations/lb.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "Feeler beim verbannen", "general_error": "Onerwaarte Feeler", - "invalid_api_key": "Ong\u00ebltegen API Schl\u00ebssel" + "invalid_api_key": "Ong\u00ebltegen API Schl\u00ebssel", + "location_not_found": "Standuert net fonnt." }, "step": { "geography": { @@ -19,6 +20,12 @@ "description": "Benotz Airvisual cloud API fir eng geografescher Lag z'iwwerwaachen.", "title": "Geografie ariichten" }, + "geography_by_name": { + "data": { + "city": "Stad", + "country": "Land" + } + }, "node_pro": { "data": { "ip_address": "Host", diff --git a/homeassistant/components/bond/translations/cs.json b/homeassistant/components/bond/translations/cs.json index 677c7e80236..13135dbf53e 100644 --- a/homeassistant/components/bond/translations/cs.json +++ b/homeassistant/components/bond/translations/cs.json @@ -15,7 +15,7 @@ "data": { "access_token": "P\u0159\u00edstupov\u00fd token" }, - "description": "Chcete nastavit {bond_id} ?" + "description": "Chcete nastavit {name}?" }, "user": { "data": { diff --git a/homeassistant/components/climacell/translations/cs.json b/homeassistant/components/climacell/translations/cs.json new file mode 100644 index 00000000000..1ae29deb08c --- /dev/null +++ b/homeassistant/components/climacell/translations/cs.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_api_key": "Neplatn\u00fd kl\u00ed\u010d API", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "api_key": "Kl\u00ed\u010d API", + "latitude": "Zem\u011bpisn\u00e1 \u0161\u00ed\u0159ka", + "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka", + "name": "Jm\u00e9no" + } + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/es.json b/homeassistant/components/climacell/translations/es.json new file mode 100644 index 00000000000..4c4d8fcc9bb --- /dev/null +++ b/homeassistant/components/climacell/translations/es.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "rate_limited": "Actualmente la tarifa est\u00e1 limitada, por favor int\u00e9ntelo m\u00e1s tarde." + }, + "step": { + "user": { + "data": { + "latitude": "Latitud", + "longitude": "Longitud", + "name": "Nombre" + }, + "description": "Si no se proporcionan Latitud y Longitud , se utilizar\u00e1n los valores predeterminados en la configuraci\u00f3n de Home Assistant. Se crear\u00e1 una entidad para cada tipo de pron\u00f3stico, pero solo las que seleccione estar\u00e1n habilitadas de forma predeterminada." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Tipo(s) de pron\u00f3stico", + "timestep": "Min. Entre pron\u00f3sticos de NowCast" + }, + "description": "Si elige habilitar la entidad de pron\u00f3stico \"nowcast\", puede configurar el n\u00famero de minutos entre cada pron\u00f3stico. El n\u00famero de pron\u00f3sticos proporcionados depende del n\u00famero de minutos elegidos entre los pron\u00f3sticos.", + "title": "Actualizar las opciones ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/zh-Hant.json b/homeassistant/components/climacell/translations/zh-Hant.json new file mode 100644 index 00000000000..76eaf50b932 --- /dev/null +++ b/homeassistant/components/climacell/translations/zh-Hant.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_api_key": "API \u5bc6\u9470\u7121\u6548", + "rate_limited": "\u9054\u5230\u9650\u5236\u983b\u7387\u3001\u8acb\u7a0d\u5019\u518d\u8a66\u3002", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "api_key": "API \u5bc6\u9470", + "latitude": "\u7def\u5ea6", + "longitude": "\u7d93\u5ea6", + "name": "\u540d\u7a31" + }, + "description": "\u5047\u5982\u672a\u63d0\u4f9b\u7def\u5ea6\u8207\u7d93\u5ea6\uff0c\u5c07\u6703\u4f7f\u7528 Home Assistant \u8a2d\u5b9a\u4f5c\u70ba\u9810\u8a2d\u503c\u3002\u6bcf\u4e00\u500b\u9810\u5831\u985e\u578b\u90fd\u6703\u7522\u751f\u4e00\u7d44\u5be6\u9ad4\uff0c\u6216\u8005\u9810\u8a2d\u70ba\u6240\u9078\u64c7\u555f\u7528\u7684\u9810\u5831\u3002" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "\u9810\u5831\u985e\u578b", + "timestep": "NowCast \u9810\u5831\u9593\u9694\u5206\u9418" + }, + "description": "\u5047\u5982\u9078\u64c7\u958b\u555f `nowcast` \u9810\u5831\u5be6\u9ad4\u3001\u5c07\u53ef\u4ee5\u8a2d\u5b9a\u9810\u5831\u983b\u7387\u9593\u9694\u5206\u9418\u6578\u3002\u6839\u64da\u6240\u8f38\u5165\u7684\u9593\u9694\u6642\u9593\u5c07\u6c7a\u5b9a\u9810\u5831\u7684\u6578\u76ee\u3002", + "title": "\u66f4\u65b0 ClimaCell \u9078\u9805" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/cs.json b/homeassistant/components/faa_delays/translations/cs.json new file mode 100644 index 00000000000..60e4aed57a2 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/cs.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/es.json b/homeassistant/components/faa_delays/translations/es.json new file mode 100644 index 00000000000..94eca99dda3 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/es.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Este aeropuerto ya est\u00e1 configurado." + }, + "error": { + "invalid_airport": "El c\u00f3digo del aeropuerto no es v\u00e1lido" + }, + "step": { + "user": { + "data": { + "id": "Aeropuerto" + }, + "description": "Introduzca un c\u00f3digo de aeropuerto estadounidense en formato IATA", + "title": "Retrasos de la FAA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/et.json b/homeassistant/components/faa_delays/translations/et.json new file mode 100644 index 00000000000..75b52558374 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/et.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "See lennujaam on juba seadistatud." + }, + "error": { + "cannot_connect": "\u00dchendumine nurjus", + "invalid_airport": "Lennujaama kood ei sobi", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "id": "Lennujaam" + }, + "description": "Sisesta USA lennujaama kood IATA vormingus", + "title": "" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/ru.json b/homeassistant/components/faa_delays/translations/ru.json new file mode 100644 index 00000000000..d68810fc957 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/ru.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0430\u044d\u0440\u043e\u043f\u043e\u0440\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\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_airport": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043e\u0434 \u0430\u044d\u0440\u043e\u043f\u043e\u0440\u0442\u0430.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "user": { + "data": { + "id": "\u0410\u044d\u0440\u043e\u043f\u043e\u0440\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u0434 \u0430\u044d\u0440\u043e\u043f\u043e\u0440\u0442\u0430 \u0421\u0428\u0410 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 IATA.", + "title": "FAA Delays" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/zh-Hant.json b/homeassistant/components/faa_delays/translations/zh-Hant.json new file mode 100644 index 00000000000..f2585bb790f --- /dev/null +++ b/homeassistant/components/faa_delays/translations/zh-Hant.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u6b64\u6a5f\u5834\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_airport": "\u6a5f\u5834\u4ee3\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "id": "\u6a5f\u5834" + }, + "description": "\u8f38\u5165\u7f8e\u570b\u6a5f\u5834 IATA \u4ee3\u78bc", + "title": "FAA \u822a\u73ed\u5ef6\u8aa4" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/cs.json b/homeassistant/components/homekit/translations/cs.json index faf1b1d74fc..cdfaed9183c 100644 --- a/homeassistant/components/homekit/translations/cs.json +++ b/homeassistant/components/homekit/translations/cs.json @@ -17,7 +17,7 @@ "include_domains": "Dom\u00e9ny, kter\u00e9 maj\u00ed b\u00fdt zahrnuty", "mode": "Re\u017eim" }, - "title": "Aktivace HomeKit" + "title": "Vyberte dom\u00e9ny, kter\u00e9 chcete zahrnout" } } }, diff --git a/homeassistant/components/kmtronic/translations/cs.json b/homeassistant/components/kmtronic/translations/cs.json new file mode 100644 index 00000000000..0f02cd974c2 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/cs.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "host": "Hostitel", + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/es.json b/homeassistant/components/litejet/translations/es.json new file mode 100644 index 00000000000..b0641022bf0 --- /dev/null +++ b/homeassistant/components/litejet/translations/es.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "open_failed": "No se puede abrir el puerto serie especificado." + }, + "step": { + "user": { + "data": { + "port": "Puerto" + }, + "description": "Conecte el puerto RS232-2 del LiteJet a su computadora e ingrese la ruta al dispositivo del puerto serial. \n\nEl LiteJet MCP debe configurarse para 19,2 K baudios, 8 bits de datos, 1 bit de parada, sin paridad y para transmitir un 'CR' despu\u00e9s de cada respuesta.", + "title": "Conectarse a LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/es.json b/homeassistant/components/mazda/translations/es.json index 72fc9ce7389..868ae0d770e 100644 --- a/homeassistant/components/mazda/translations/es.json +++ b/homeassistant/components/mazda/translations/es.json @@ -6,6 +6,7 @@ "step": { "reauth": { "data": { + "password": "Contrase\u00f1a", "region": "Regi\u00f3n" }, "description": "Ha fallado la autenticaci\u00f3n para los Servicios Conectados de Mazda. Por favor, introduce tus credenciales actuales.", diff --git a/homeassistant/components/mullvad/translations/cs.json b/homeassistant/components/mullvad/translations/cs.json new file mode 100644 index 00000000000..0f02cd974c2 --- /dev/null +++ b/homeassistant/components/mullvad/translations/cs.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "host": "Hostitel", + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/es.json b/homeassistant/components/mullvad/translations/es.json new file mode 100644 index 00000000000..d6a17561c3d --- /dev/null +++ b/homeassistant/components/mullvad/translations/es.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "description": "\u00bfConfigurar la integraci\u00f3n VPN de Mullvad?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/zh-Hant.json b/homeassistant/components/mullvad/translations/zh-Hant.json new file mode 100644 index 00000000000..d78c36b72d7 --- /dev/null +++ b/homeassistant/components/mullvad/translations/zh-Hant.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "description": "\u8a2d\u5b9a Mullvad VPN \u6574\u5408\uff1f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/es.json b/homeassistant/components/netatmo/translations/es.json index 556fe2626d4..b1159c1dd9d 100644 --- a/homeassistant/components/netatmo/translations/es.json +++ b/homeassistant/components/netatmo/translations/es.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "fuera", + "hg": "protector contra las heladas", + "schedule": "Horario" + }, + "trigger_type": { + "alarm_started": "{entity_name} ha detectado una alarma", + "animal": "{entity_name} ha detectado un animal", + "cancel_set_point": "{entity_name} ha reanudado su programaci\u00f3n", + "human": "{entity_name} ha detectado una persona", + "movement": "{entity_name} ha detectado movimiento", + "outdoor": "{entity_name} ha detectado un evento en el exterior", + "person": "{entity_name} ha detectado una persona", + "person_away": "{entity_name} ha detectado que una persona se ha ido", + "set_point": "Temperatura objetivo {entity_name} fijada manualmente", + "therm_mode": "{entity_name} cambi\u00f3 a \" {subtype} \"", + "turned_off": "{entity_name} desactivado", + "turned_on": "{entity_name} activado", + "vehicle": "{entity_name} ha detectado un veh\u00edculo" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/zh-Hant.json b/homeassistant/components/netatmo/translations/zh-Hant.json index e396deabb68..e62836f9a7e 100644 --- a/homeassistant/components/netatmo/translations/zh-Hant.json +++ b/homeassistant/components/netatmo/translations/zh-Hant.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "\u96e2\u5bb6", + "hg": "\u9632\u51cd\u6a21\u5f0f", + "schedule": "\u6392\u7a0b" + }, + "trigger_type": { + "alarm_started": "{entity_name}\u5075\u6e2c\u5230\u8b66\u5831", + "animal": "{entity_name}\u5075\u6e2c\u5230\u52d5\u7269", + "cancel_set_point": "{entity_name}\u5df2\u6062\u5fa9\u5176\u6392\u7a0b", + "human": "{entity_name}\u5075\u6e2c\u5230\u4eba\u985e", + "movement": "{entity_name}\u5075\u6e2c\u5230\u52d5\u4f5c", + "outdoor": "{entity_name}\u5075\u6e2c\u5230\u6236\u5916\u52d5\u4f5c", + "person": "{entity_name}\u5075\u6e2c\u5230\u4eba\u54e1", + "person_away": "{entity_name}\u5075\u6e2c\u5230\u4eba\u54e1\u5df2\u96e2\u958b", + "set_point": "\u624b\u52d5\u8a2d\u5b9a{entity_name}\u76ee\u6a19\u6eab\u5ea6", + "therm_mode": "{entity_name}\u5207\u63db\u81f3 \"{subtype}\"", + "turned_off": "{entity_name}\u5df2\u95dc\u9589", + "turned_on": "{entity_name}\u5df2\u958b\u555f", + "vehicle": "{entity_name}\u5075\u6e2c\u5230\u8eca\u8f1b" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/rituals_perfume_genie/translations/es.json b/homeassistant/components/rituals_perfume_genie/translations/es.json new file mode 100644 index 00000000000..bc74ecfd7ea --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/es.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "Con\u00e9ctese a su cuenta de Rituals" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/nl.json b/homeassistant/components/shelly/translations/nl.json index 7084a972e29..c486b9c6bfe 100644 --- a/homeassistant/components/shelly/translations/nl.json +++ b/homeassistant/components/shelly/translations/nl.json @@ -8,7 +8,7 @@ "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, - "flow_title": "Shelly: {name}", + "flow_title": "{name}", "step": { "confirm_discovery": { "description": "Wilt u het {model} bij {host} instellen? Voordat apparaten op batterijen kunnen worden ingesteld, moet het worden gewekt door op de knop op het apparaat te drukken." @@ -16,7 +16,7 @@ "credentials": { "data": { "password": "Wachtwoord", - "username": "Benutzername" + "username": "Gebruikersnaam" } }, "user": { diff --git a/homeassistant/components/soma/translations/cs.json b/homeassistant/components/soma/translations/cs.json index 5a27562df71..ba1261c1100 100644 --- a/homeassistant/components/soma/translations/cs.json +++ b/homeassistant/components/soma/translations/cs.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_setup": "M\u016f\u017eete nastavit pouze jeden \u00fa\u010det Soma.", - "authorize_url_timeout": "\u010casov\u00fd limit autoriza\u010dn\u00edho URL vypr\u0161el", + "authorize_url_timeout": "\u010casov\u00fd limit autoriza\u010dn\u00edho URL vypr\u0161el.", "connection_error": "P\u0159ipojen\u00ed k za\u0159\u00edzen\u00ed SOMA Connect se nezda\u0159ilo.", "missing_configuration": "Integrace Soma nen\u00ed nastavena. Postupujte podle dokumentace.", "result_error": "SOMA Connect odpov\u011bd\u011blo chybov\u00fdm stavem." diff --git a/homeassistant/components/spotify/translations/cs.json b/homeassistant/components/spotify/translations/cs.json index f8f122e63e2..69cd1b1623a 100644 --- a/homeassistant/components/spotify/translations/cs.json +++ b/homeassistant/components/spotify/translations/cs.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "authorize_url_timeout": "\u010casov\u00fd limit autoriza\u010dn\u00edho URL vypr\u0161el", + "authorize_url_timeout": "\u010casov\u00fd limit autoriza\u010dn\u00edho URL vypr\u0161el.", "missing_configuration": "Integrace Spotify nen\u00ed nastavena. Postupujte podle dokumentace.", "no_url_available": "Nen\u00ed k dispozici \u017e\u00e1dn\u00e1 adresa URL. Informace o t\u00e9to chyb\u011b naleznete [v sekci n\u00e1pov\u011bdy]({docs_url})" }, diff --git a/homeassistant/components/subaru/translations/cs.json b/homeassistant/components/subaru/translations/cs.json new file mode 100644 index 00000000000..ee3bf7347ca --- /dev/null +++ b/homeassistant/components/subaru/translations/cs.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "\u00da\u010det je ji\u017e nastaven", + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" + }, + "error": { + "bad_pin_format": "PIN by m\u011bl m\u00edt 4 \u010d\u00edslice", + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "incorrect_pin": "Nespr\u00e1vn\u00fd PIN", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "pin": { + "data": { + "pin": "PIN k\u00f3d" + } + }, + "user": { + "data": { + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/es.json b/homeassistant/components/subaru/translations/es.json new file mode 100644 index 00000000000..deccc23c75d --- /dev/null +++ b/homeassistant/components/subaru/translations/es.json @@ -0,0 +1,37 @@ +{ + "config": { + "error": { + "bad_pin_format": "El PIN debe tener 4 d\u00edgitos", + "incorrect_pin": "PIN incorrecto" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "Por favor, introduzca su PIN de MySubaru\nNOTA: Todos los veh\u00edculos de la cuenta deben tener el mismo PIN", + "title": "Configuraci\u00f3n de Subaru Starlink" + }, + "user": { + "data": { + "country": "Seleccionar pa\u00eds", + "password": "Contrase\u00f1a", + "username": "Nombre de usuario" + }, + "description": "Por favor, introduzca sus credenciales de MySubaru\nNOTA: La configuraci\u00f3n inicial puede tardar hasta 30 segundos", + "title": "Configuraci\u00f3n de Subaru Starlink" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "Habilitar el sondeo de veh\u00edculos" + }, + "description": "Cuando est\u00e1 habilitado, el sondeo de veh\u00edculos enviar\u00e1 un comando remoto a su veh\u00edculo cada 2 horas para obtener nuevos datos del sensor. Sin sondeo del veh\u00edculo, los nuevos datos del sensor solo se reciben cuando el veh\u00edculo env\u00eda datos autom\u00e1ticamente (normalmente despu\u00e9s de apagar el motor).", + "title": "Opciones de Subaru Starlink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/cs.json b/homeassistant/components/totalconnect/translations/cs.json index 60e2196b387..74dece0c54e 100644 --- a/homeassistant/components/totalconnect/translations/cs.json +++ b/homeassistant/components/totalconnect/translations/cs.json @@ -1,12 +1,18 @@ { "config": { "abort": { - "already_configured": "\u00da\u010det je ji\u017e nastaven" + "already_configured": "\u00da\u010det je ji\u017e nastaven", + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" }, "error": { "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" }, "step": { + "locations": { + "data": { + "location": "Um\u00edst\u011bn\u00ed" + } + }, "user": { "data": { "password": "Heslo", diff --git a/homeassistant/components/totalconnect/translations/es.json b/homeassistant/components/totalconnect/translations/es.json index 48af1bed0f4..090d9271dee 100644 --- a/homeassistant/components/totalconnect/translations/es.json +++ b/homeassistant/components/totalconnect/translations/es.json @@ -4,9 +4,17 @@ "already_configured": "La cuenta ya ha sido configurada" }, "error": { - "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "usercode": "El c\u00f3digo de usuario no es v\u00e1lido para este usuario en esta ubicaci\u00f3n" }, "step": { + "locations": { + "description": "Ingrese el c\u00f3digo de usuario para este usuario en esta ubicaci\u00f3n", + "title": "C\u00f3digos de usuario de ubicaci\u00f3n" + }, + "reauth_confirm": { + "description": "Total Connect necesita volver a autentificar tu cuenta" + }, "user": { "data": { "password": "Contrase\u00f1a", diff --git a/homeassistant/components/wolflink/translations/sensor.nl.json b/homeassistant/components/wolflink/translations/sensor.nl.json new file mode 100644 index 00000000000..da03cc43b4b --- /dev/null +++ b/homeassistant/components/wolflink/translations/sensor.nl.json @@ -0,0 +1,16 @@ +{ + "state": { + "wolflink__state": { + "frost_warmwasser": "DHW vorst", + "frostschutz": "Vorstbescherming", + "gasdruck": "Gasdruk", + "glt_betrieb": "BMS-modus", + "heizbetrieb": "Verwarmingsmodus", + "heizgerat_mit_speicher": "Boiler met cilinder", + "heizung": "Verwarmen", + "initialisierung": "Initialisatie", + "kalibration": "Kalibratie", + "kalibration_heizbetrieb": "Kalibratie verwarmingsmodus" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/es.json b/homeassistant/components/xiaomi_miio/translations/es.json index fd4b8c36a8b..60a989ade0d 100644 --- a/homeassistant/components/xiaomi_miio/translations/es.json +++ b/homeassistant/components/xiaomi_miio/translations/es.json @@ -13,6 +13,7 @@ "step": { "device": { "data": { + "model": "Modelo de dispositivo (opcional)", "name": "Nombre del dispositivo" }, "description": "Necesitar\u00e1 la clave de 32 caracteres Token API, consulte https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token para obtener instrucciones. Tenga en cuenta que esta Token API es diferente de la clave utilizada por la integraci\u00f3n de Xiaomi Aqara.", diff --git a/homeassistant/components/zwave_js/translations/es.json b/homeassistant/components/zwave_js/translations/es.json index 32d7a6d2e6d..26fd155a0ad 100644 --- a/homeassistant/components/zwave_js/translations/es.json +++ b/homeassistant/components/zwave_js/translations/es.json @@ -6,6 +6,7 @@ "addon_install_failed": "No se ha podido instalar el complemento Z-Wave JS.", "addon_missing_discovery_info": "Falta informaci\u00f3n de descubrimiento del complemento Z-Wave JS.", "addon_set_config_failed": "Fallo en la configuraci\u00f3n de Z-Wave JS.", + "addon_start_failed": "No se ha podido iniciar el complemento Z-Wave JS.", "already_configured": "El dispositivo ya est\u00e1 configurado", "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", "cannot_connect": "No se pudo conectar" @@ -17,7 +18,8 @@ "unknown": "Error inesperado" }, "progress": { - "install_addon": "Espera mientras termina la instalaci\u00f3n del complemento Z-Wave JS. Puede tardar varios minutos." + "install_addon": "Espera mientras termina la instalaci\u00f3n del complemento Z-Wave JS. Puede tardar varios minutos.", + "start_addon": "Espere mientras se completa el inicio del complemento Z-Wave JS. Esto puede tardar unos segundos." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "\u00bfQuieres utilizar el complemento Z-Wave JS Supervisor?", "title": "Selecciona el m\u00e9todo de conexi\u00f3n" }, + "start_addon": { + "title": "Se est\u00e1 iniciando el complemento Z-Wave JS." + }, "user": { "data": { "url": "URL" From 7118b7169c097509022a67ab6b2d17d914e83d18 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 25 Feb 2021 20:41:54 -0500 Subject: [PATCH 027/831] catch ValueError when unique ID update fails because its taken and remove the duplicate entity (#47072) --- homeassistant/components/zwave_js/__init__.py | 18 +++++-- tests/components/zwave_js/test_init.py | 53 +++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 75bc95b7fe4..93d511875af 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -95,10 +95,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: old_unique_id, new_unique_id, ) - ent_reg.async_update_entity( - entity_id, - new_unique_id=new_unique_id, - ) + try: + ent_reg.async_update_entity( + entity_id, + new_unique_id=new_unique_id, + ) + except ValueError: + LOGGER.debug( + ( + "Entity %s can't be migrated because the unique ID is taken. " + "Cleaning it up since it is likely no longer valid." + ), + entity_id, + ) + ent_reg.async_remove(entity_id) @callback def async_on_node_ready(node: ZwaveNode) -> None: diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index f2815bec7f6..bff2ecd198c 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -124,6 +124,59 @@ async def test_on_node_added_ready( ) +async def test_unique_id_migration_dupes( + hass, multisensor_6_state, client, integration +): + """Test we remove an entity when .""" + ent_reg = entity_registry.async_get(hass) + + entity_name = AIR_TEMPERATURE_SENSOR.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id_1 = ( + f"{client.driver.controller.home_id}.52.52-49-00-Air temperature-00" + ) + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id_1, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == AIR_TEMPERATURE_SENSOR + assert entity_entry.unique_id == old_unique_id_1 + + # Create entity RegistryEntry using b0 unique ID format + old_unique_id_2 = ( + f"{client.driver.controller.home_id}.52.52-49-0-Air temperature-00-00" + ) + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id_2, + suggested_object_id=f"{entity_name}_1", + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == f"{AIR_TEMPERATURE_SENSOR}_1" + assert entity_entry.unique_id == old_unique_id_2 + + # Add a ready node, unique ID should be migrated + node = Node(client, multisensor_6_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + await hass.async_block_till_done() + + # Check that new RegistryEntry is using new unique ID format + entity_entry = ent_reg.async_get(AIR_TEMPERATURE_SENSOR) + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Air temperature-00-00" + assert entity_entry.unique_id == new_unique_id + + assert ent_reg.async_get(f"{AIR_TEMPERATURE_SENSOR}_1") is None + + async def test_unique_id_migration_v1(hass, multisensor_6_state, client, integration): """Test unique ID is migrated from old format to new (version 1).""" ent_reg = entity_registry.async_get(hass) From 8ab163eda853481ac4bd23226c65f37e34c5a8f1 Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Fri, 26 Feb 2021 00:11:06 -0500 Subject: [PATCH 028/831] Fix Z-Wave JS API docstrings (#47061) --- homeassistant/components/zwave_js/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index a48eadfad1d..12ec66906a8 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -350,7 +350,7 @@ async def websocket_set_config_parameter( def websocket_get_config_parameters( hass: HomeAssistant, connection: ActiveConnection, msg: dict ) -> None: - """Get a list of configuration parameterss for a Z-Wave node.""" + """Get a list of configuration parameters for a Z-Wave node.""" entry_id = msg[ENTRY_ID] node_id = msg[NODE_ID] client = hass.data[DOMAIN][entry_id][DATA_CLIENT] @@ -446,7 +446,7 @@ async def websocket_update_log_config( async def websocket_get_log_config( hass: HomeAssistant, connection: ActiveConnection, msg: dict ) -> None: - """Cancel removing a node from the Z-Wave network.""" + """Get log configuration for the Z-Wave JS driver.""" entry_id = msg[ENTRY_ID] client = hass.data[DOMAIN][entry_id][DATA_CLIENT] result = await client.driver.async_get_log_config() From 6af67c9558c7f216455ee599d6e218cd569e64c1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 25 Feb 2021 23:58:35 -0600 Subject: [PATCH 029/831] Ensure hue options show the defaults when the config options have not yet been saved (#47067) --- homeassistant/components/hue/config_flow.py | 6 ++++-- homeassistant/components/hue/const.py | 2 +- tests/components/hue/test_config_flow.py | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index ecb3fd8c489..580b69251c2 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -18,6 +18,8 @@ from .bridge import authenticate_bridge from .const import ( # pylint: disable=unused-import CONF_ALLOW_HUE_GROUPS, CONF_ALLOW_UNREACHABLE, + DEFAULT_ALLOW_HUE_GROUPS, + DEFAULT_ALLOW_UNREACHABLE, DOMAIN, LOGGER, ) @@ -246,13 +248,13 @@ class HueOptionsFlowHandler(config_entries.OptionsFlow): vol.Optional( CONF_ALLOW_HUE_GROUPS, default=self.config_entry.options.get( - CONF_ALLOW_HUE_GROUPS, False + CONF_ALLOW_HUE_GROUPS, DEFAULT_ALLOW_HUE_GROUPS ), ): bool, vol.Optional( CONF_ALLOW_UNREACHABLE, default=self.config_entry.options.get( - CONF_ALLOW_UNREACHABLE, False + CONF_ALLOW_UNREACHABLE, DEFAULT_ALLOW_UNREACHABLE ), ): bool, } diff --git a/homeassistant/components/hue/const.py b/homeassistant/components/hue/const.py index 4fa11f2ad58..593f74331ec 100644 --- a/homeassistant/components/hue/const.py +++ b/homeassistant/components/hue/const.py @@ -12,6 +12,6 @@ CONF_ALLOW_UNREACHABLE = "allow_unreachable" DEFAULT_ALLOW_UNREACHABLE = False CONF_ALLOW_HUE_GROUPS = "allow_hue_groups" -DEFAULT_ALLOW_HUE_GROUPS = True +DEFAULT_ALLOW_HUE_GROUPS = False DEFAULT_SCENE_TRANSITION = 4 diff --git a/tests/components/hue/test_config_flow.py b/tests/components/hue/test_config_flow.py index c7dc83183ae..57f4bd7fbca 100644 --- a/tests/components/hue/test_config_flow.py +++ b/tests/components/hue/test_config_flow.py @@ -640,6 +640,15 @@ async def test_options_flow(hass): assert result["type"] == "form" assert result["step_id"] == "init" + schema = result["data_schema"].schema + assert ( + _get_schema_default(schema, const.CONF_ALLOW_HUE_GROUPS) + == const.DEFAULT_ALLOW_HUE_GROUPS + ) + assert ( + _get_schema_default(schema, const.CONF_ALLOW_UNREACHABLE) + == const.DEFAULT_ALLOW_UNREACHABLE + ) result = await hass.config_entries.options.async_configure( result["flow_id"], @@ -654,3 +663,11 @@ async def test_options_flow(hass): const.CONF_ALLOW_HUE_GROUPS: True, const.CONF_ALLOW_UNREACHABLE: True, } + + +def _get_schema_default(schema, key_name): + """Iterate schema to find a key.""" + for schema_key in schema: + if schema_key == key_name: + return schema_key.default() + raise KeyError(f"{key_name} not found in schema") From 6bd253094f1888e57549a70a28d83f77bffda862 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 25 Feb 2021 22:01:08 -0800 Subject: [PATCH 030/831] Bump Z-Wave JS Server Python to 0.20.0 (#47076) --- homeassistant/components/zwave_js/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zwave_js/test_config_flow.py | 2 ++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index f5d9461e9e0..9e57a3b72e2 100644 --- a/homeassistant/components/zwave_js/manifest.json +++ b/homeassistant/components/zwave_js/manifest.json @@ -3,7 +3,7 @@ "name": "Z-Wave JS", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zwave_js", - "requirements": ["zwave-js-server-python==0.19.0"], + "requirements": ["zwave-js-server-python==0.20.0"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index 780dca13d7a..ed93abbda60 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2397,4 +2397,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.19.0 +zwave-js-server-python==0.20.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d587cb98a0a..36d2f295e9c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1234,4 +1234,4 @@ zigpy-znp==0.4.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.19.0 +zwave-js-server-python==0.20.0 diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index 73057f3fe21..08b0ffe3080 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -144,6 +144,8 @@ def mock_get_server_version(server_version_side_effect, server_version_timeout): driver_version="mock-driver-version", server_version="mock-server-version", home_id=1234, + min_schema_version=0, + max_schema_version=1, ) with patch( "homeassistant.components.zwave_js.config_flow.get_server_version", From 3a3c12bb17ab589dab39ea8b0f09d65c6a9cc982 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Feb 2021 00:16:11 -0800 Subject: [PATCH 031/831] Upgrade aiohttp to 3.7.4 (#47077) --- 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 8f84546371e..10cf300b76b 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -1,6 +1,6 @@ PyJWT==1.7.1 PyNaCl==1.3.0 -aiohttp==3.7.3 +aiohttp==3.7.4 aiohttp_cors==0.7.0 astral==1.10.1 async-upnp-client==0.14.13 diff --git a/requirements.txt b/requirements.txt index 14ebf2708ad..0a5b754dbfc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -c homeassistant/package_constraints.txt # Home Assistant Core -aiohttp==3.7.3 +aiohttp==3.7.4 astral==1.10.1 async_timeout==3.0.1 attrs==19.3.0 diff --git a/setup.py b/setup.py index 6dbe35760a6..ce7d6b6883d 100755 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ PROJECT_URLS = { PACKAGES = find_packages(exclude=["tests", "tests.*"]) REQUIRES = [ - "aiohttp==3.7.3", + "aiohttp==3.7.4", "astral==1.10.1", "async_timeout==3.0.1", "attrs==19.3.0", From afb6e313936371e957119e44cadc0d11f3f8cdfe Mon Sep 17 00:00:00 2001 From: chpego <38792705+chpego@users.noreply.github.com> Date: Fri, 26 Feb 2021 10:39:31 +0100 Subject: [PATCH 032/831] Upgrade youtube_dl to version 2021.02.22 (#47078) * Upgrade youtube_dl to version 2021.02.22 * Update requirements_all.txt --- homeassistant/components/media_extractor/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_extractor/manifest.json b/homeassistant/components/media_extractor/manifest.json index c6ee6ccb8a4..48589b37bd2 100644 --- a/homeassistant/components/media_extractor/manifest.json +++ b/homeassistant/components/media_extractor/manifest.json @@ -2,7 +2,7 @@ "domain": "media_extractor", "name": "Media Extractor", "documentation": "https://www.home-assistant.io/integrations/media_extractor", - "requirements": ["youtube_dl==2021.01.24.1"], + "requirements": ["youtube_dl==2021.02.22"], "dependencies": ["media_player"], "codeowners": [], "quality_scale": "internal" diff --git a/requirements_all.txt b/requirements_all.txt index ed93abbda60..f6ab9f62d14 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2355,7 +2355,7 @@ yeelight==0.5.4 yeelightsunflower==0.0.10 # homeassistant.components.media_extractor -youtube_dl==2021.01.24.1 +youtube_dl==2021.02.22 # homeassistant.components.onvif zeep[async]==4.0.0 From 5780615251ec4153edb9a7109c9ad6015be22f52 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 26 Feb 2021 10:47:22 +0100 Subject: [PATCH 033/831] Bump pychromecast to 8.1.2 (#47085) --- homeassistant/components/cast/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index 5963e93cf8c..28ccb78d5b9 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==8.1.0"], + "requirements": ["pychromecast==8.1.2"], "after_dependencies": ["cloud", "http", "media_source", "plex", "tts", "zeroconf"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"] diff --git a/requirements_all.txt b/requirements_all.txt index f6ab9f62d14..ea5abe0f221 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1305,7 +1305,7 @@ pycfdns==1.2.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==8.1.0 +pychromecast==8.1.2 # homeassistant.components.pocketcasts pycketcasts==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 36d2f295e9c..f2054da1aa6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -688,7 +688,7 @@ pybotvac==0.0.20 pycfdns==1.2.1 # homeassistant.components.cast -pychromecast==8.1.0 +pychromecast==8.1.2 # homeassistant.components.climacell pyclimacell==0.14.0 From d5ee49cd4e0b468a2fcd874d40f6131989564a43 Mon Sep 17 00:00:00 2001 From: CurrentThread <62957822+CurrentThread@users.noreply.github.com> Date: Fri, 26 Feb 2021 11:52:47 +0100 Subject: [PATCH 034/831] Add support for Shelly SHBTN-2 device triggers (#46644) --- homeassistant/components/shelly/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index b4148801b35..0058374cfe7 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -111,7 +111,7 @@ def get_device_channel_name( def is_momentary_input(settings: dict, block: aioshelly.Block) -> bool: """Return true if input button settings is set to a momentary type.""" # Shelly Button type is fixed to momentary and no btn_type - if settings["device"]["type"] == "SHBTN-1": + if settings["device"]["type"] in ("SHBTN-1", "SHBTN-2"): return True button = settings.get("relays") or settings.get("lights") or settings.get("inputs") @@ -158,7 +158,7 @@ def get_input_triggers( else: subtype = f"button{int(block.channel)+1}" - if device.settings["device"]["type"] == "SHBTN-1": + if device.settings["device"]["type"] in ("SHBTN-1", "SHBTN-2"): trigger_types = SHBTN_1_INPUTS_EVENTS_TYPES elif device.settings["device"]["type"] == "SHIX3-1": trigger_types = SHIX3_1_INPUTS_EVENTS_TYPES From dfbb6531073f93afe3f8a90abe6814a48c31392b Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 26 Feb 2021 13:43:53 +0100 Subject: [PATCH 035/831] Bump pychromecast to 9.0.0 (#47086) * Adapt to Pychromecast 9.0.0 * Bump pychromecast to 9.0.0 * Fix lint issues --- homeassistant/components/cast/discovery.py | 91 ++++------- homeassistant/components/cast/helpers.py | 19 +-- homeassistant/components/cast/manifest.json | 2 +- homeassistant/components/cast/media_player.py | 16 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/cast/test_media_player.py | 148 ++++++++++-------- 7 files changed, 134 insertions(+), 146 deletions(-) diff --git a/homeassistant/components/cast/discovery.py b/homeassistant/components/cast/discovery.py index 4858d37f732..81048b35a97 100644 --- a/homeassistant/components/cast/discovery.py +++ b/homeassistant/components/cast/discovery.py @@ -9,6 +9,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import dispatcher_send from .const import ( + DEFAULT_PORT, INTERNAL_DISCOVERY_RUNNING_KEY, KNOWN_CHROMECAST_INFO_KEY, SIGNAL_CAST_DISCOVERED, @@ -19,8 +20,17 @@ from .helpers import ChromecastInfo, ChromeCastZeroconf _LOGGER = logging.getLogger(__name__) -def discover_chromecast(hass: HomeAssistant, info: ChromecastInfo): +def discover_chromecast(hass: HomeAssistant, device_info): """Discover a Chromecast.""" + + info = ChromecastInfo( + services=device_info.services, + uuid=device_info.uuid, + model_name=device_info.model_name, + friendly_name=device_info.friendly_name, + is_audio_group=device_info.port != DEFAULT_PORT, + ) + if info.uuid is None: _LOGGER.error("Discovered chromecast without uuid %s", info) return @@ -51,72 +61,39 @@ def setup_internal_discovery(hass: HomeAssistant) -> None: # Internal discovery is already running return - def internal_add_update_callback(uuid, service_name): - """Handle zeroconf discovery of a new or updated chromecast.""" - service = listener.services[uuid] + class CastListener(pychromecast.discovery.AbstractCastListener): + """Listener for discovering chromecasts.""" - # For support of deprecated IP based white listing - zconf = ChromeCastZeroconf.get_zeroconf() - service_info = None - tries = 0 - while service_info is None and tries < 4: - try: - service_info = zconf.get_service_info( - "_googlecast._tcp.local.", service_name - ) - except OSError: - # If the zeroconf fails to receive the necessary data we abort - # adding the service - break - tries += 1 + def add_cast(self, uuid, _): + """Handle zeroconf discovery of a new chromecast.""" + discover_chromecast(hass, browser.devices[uuid]) - if not service_info: - _LOGGER.warning( - "setup_internal_discovery failed to get info for %s, %s", - uuid, - service_name, + def update_cast(self, uuid, _): + """Handle zeroconf discovery of an updated chromecast.""" + discover_chromecast(hass, browser.devices[uuid]) + + def remove_cast(self, uuid, service, cast_info): + """Handle zeroconf discovery of a removed chromecast.""" + _remove_chromecast( + hass, + ChromecastInfo( + services=cast_info.services, + uuid=cast_info.uuid, + model_name=cast_info.model_name, + friendly_name=cast_info.friendly_name, + ), ) - return - - addresses = service_info.parsed_addresses() - host = addresses[0] if addresses else service_info.server - - discover_chromecast( - hass, - ChromecastInfo( - services=service[0], - uuid=service[1], - model_name=service[2], - friendly_name=service[3], - host=host, - port=service_info.port, - ), - ) - - def internal_remove_callback(uuid, service_name, service): - """Handle zeroconf discovery of a removed chromecast.""" - _remove_chromecast( - hass, - ChromecastInfo( - services=service[0], - uuid=service[1], - model_name=service[2], - friendly_name=service[3], - ), - ) _LOGGER.debug("Starting internal pychromecast discovery") - listener = pychromecast.CastListener( - internal_add_update_callback, - internal_remove_callback, - internal_add_update_callback, + browser = pychromecast.discovery.CastBrowser( + CastListener(), ChromeCastZeroconf.get_zeroconf() ) - browser = pychromecast.start_discovery(listener, ChromeCastZeroconf.get_zeroconf()) + browser.start_discovery() def stop_discovery(event): """Stop discovery of new chromecasts.""" _LOGGER.debug("Stopping internal pychromecast discovery") - pychromecast.discovery.stop_discovery(browser) + browser.stop_discovery() hass.data[INTERNAL_DISCOVERY_RUNNING_KEY].release() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_discovery) diff --git a/homeassistant/components/cast/helpers.py b/homeassistant/components/cast/helpers.py index b8742ec2b5e..91382c69591 100644 --- a/homeassistant/components/cast/helpers.py +++ b/homeassistant/components/cast/helpers.py @@ -7,8 +7,6 @@ import attr from pychromecast import dial from pychromecast.const import CAST_MANUFACTURERS -from .const import DEFAULT_PORT - @attr.s(slots=True, frozen=True) class ChromecastInfo: @@ -18,21 +16,15 @@ class ChromecastInfo: """ services: Optional[set] = attr.ib() - host: Optional[str] = attr.ib(default=None) - port: Optional[int] = attr.ib(default=0) uuid: Optional[str] = attr.ib( converter=attr.converters.optional(str), default=None ) # always convert UUID to string if not None _manufacturer = attr.ib(type=Optional[str], default=None) model_name: str = attr.ib(default="") friendly_name: Optional[str] = attr.ib(default=None) + is_audio_group = attr.ib(type=Optional[bool], default=False) is_dynamic_group = attr.ib(type=Optional[bool], default=None) - @property - def is_audio_group(self) -> bool: - """Return if this is an audio group.""" - return self.port != DEFAULT_PORT - @property def is_information_complete(self) -> bool: """Return if all information is filled out.""" @@ -74,7 +66,7 @@ class ChromecastInfo: http_group_status = None if self.uuid: http_group_status = dial.get_multizone_status( - self.host, + None, services=self.services, zconf=ChromeCastZeroconf.get_zeroconf(), ) @@ -86,17 +78,16 @@ class ChromecastInfo: return ChromecastInfo( services=self.services, - host=self.host, - port=self.port, uuid=self.uuid, friendly_name=self.friendly_name, model_name=self.model_name, + is_audio_group=True, is_dynamic_group=is_dynamic_group, ) # Fill out some missing information (friendly_name, uuid) via HTTP dial. http_device_status = dial.get_device_status( - self.host, services=self.services, zconf=ChromeCastZeroconf.get_zeroconf() + None, services=self.services, zconf=ChromeCastZeroconf.get_zeroconf() ) if http_device_status is None: # HTTP dial didn't give us any new information. @@ -104,8 +95,6 @@ class ChromecastInfo: return ChromecastInfo( services=self.services, - host=self.host, - port=self.port, uuid=(self.uuid or http_device_status.uuid), friendly_name=(self.friendly_name or http_device_status.friendly_name), manufacturer=(self.manufacturer or http_device_status.manufacturer), diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index 28ccb78d5b9..ac728b4ec45 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==8.1.2"], + "requirements": ["pychromecast==9.0.0"], "after_dependencies": ["cloud", "http", "media_source", "plex", "tts", "zeroconf"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"] diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 981d67f0caa..235c7ab4479 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -154,15 +154,15 @@ async def _async_setup_platform( hass.data.setdefault(ADDED_CAST_DEVICES_KEY, set()) hass.data.setdefault(KNOWN_CHROMECAST_INFO_KEY, {}) - info = None + wanted_uuid = None if CONF_UUID in config: - info = ChromecastInfo(uuid=config[CONF_UUID], services=None) + wanted_uuid = config[CONF_UUID] @callback def async_cast_discovered(discover: ChromecastInfo) -> None: """Handle discovery of a new chromecast.""" - # If info is set, we're handling a specific cast device identified by UUID - if info is not None and (info.uuid is not None and info.uuid != discover.uuid): + # If wanted_uuid is set, we're handling a specific cast device identified by UUID + if wanted_uuid is not None and wanted_uuid != discover.uuid: # UUID not matching, this is not it. return @@ -251,8 +251,8 @@ class CastDevice(MediaPlayerEntity): self.services, ) chromecast = await self.hass.async_add_executor_job( - pychromecast.get_chromecast_from_service, - ( + pychromecast.get_chromecast_from_cast_info, + pychromecast.discovery.CastInfo( self.services, self._cast_info.uuid, self._cast_info.model_name, @@ -875,8 +875,8 @@ class DynamicCastGroup: self.services, ) chromecast = await self.hass.async_add_executor_job( - pychromecast.get_chromecast_from_service, - ( + pychromecast.get_chromecast_from_cast_info, + pychromecast.discovery.CastInfo( self.services, self._cast_info.uuid, self._cast_info.model_name, diff --git a/requirements_all.txt b/requirements_all.txt index ea5abe0f221..135e2c74f63 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1305,7 +1305,7 @@ pycfdns==1.2.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==8.1.2 +pychromecast==9.0.0 # homeassistant.components.pocketcasts pycketcasts==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f2054da1aa6..1499679fd5d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -688,7 +688,7 @@ pybotvac==0.0.20 pycfdns==1.2.1 # homeassistant.components.cast -pychromecast==8.1.2 +pychromecast==9.0.0 # homeassistant.components.climacell pyclimacell==0.14.0 diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index be24afcb538..51c49484c50 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -6,6 +6,7 @@ from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch from uuid import UUID import attr +import pychromecast import pytest from homeassistant.components import tts @@ -47,6 +48,12 @@ def dial_mock(): return dial_mock +@pytest.fixture() +def castbrowser_mock(): + """Mock pychromecast CastBrowser.""" + return MagicMock() + + @pytest.fixture() def mz_mock(): """Mock pychromecast MultizoneManager.""" @@ -54,10 +61,13 @@ def mz_mock(): @pytest.fixture() -def pycast_mock(): +def pycast_mock(castbrowser_mock): """Mock pychromecast.""" pycast_mock = MagicMock() - pycast_mock.start_discovery.return_value = (None, Mock()) + pycast_mock.discovery.CastBrowser.return_value = castbrowser_mock + pycast_mock.discovery.AbstractCastListener = ( + pychromecast.discovery.AbstractCastListener + ) return pycast_mock @@ -97,7 +107,7 @@ FakeGroupUUID = UUID("57355bce-9364-4aa6-ac1e-eb849dccf9e3") def get_fake_chromecast(info: ChromecastInfo): """Generate a Fake Chromecast object with the specified arguments.""" - mock = MagicMock(host=info.host, port=info.port, uuid=info.uuid) + mock = MagicMock(uuid=info.uuid) mock.media_controller.status = None return mock @@ -106,12 +116,35 @@ def get_fake_chromecast_info( host="192.168.178.42", port=8009, uuid: Optional[UUID] = FakeUUID ): """Generate a Fake ChromecastInfo with the specified arguments.""" - return ChromecastInfo( + + @attr.s(slots=True, frozen=True, eq=False) + class ExtendedChromecastInfo(ChromecastInfo): + host: Optional[str] = attr.ib(default=None) + port: Optional[int] = attr.ib(default=0) + + def __eq__(self, other): + if isinstance(other, ChromecastInfo): + return ( + ChromecastInfo( + services=self.services, + uuid=self.uuid, + manufacturer=self.manufacturer, + model_name=self.model_name, + friendly_name=self.friendly_name, + is_audio_group=self.is_audio_group, + is_dynamic_group=self.is_dynamic_group, + ) + == other + ) + return super().__eq__(other) + + return ExtendedChromecastInfo( host=host, port=port, uuid=uuid, friendly_name="Speaker", services={"the-service"}, + is_audio_group=port != 8009, ) @@ -141,32 +174,30 @@ async def async_setup_cast(hass, config=None): async def async_setup_cast_internal_discovery(hass, config=None): """Set up the cast platform and the discovery.""" - listener = MagicMock(services={}) - browser = MagicMock(zc={}) + browser = MagicMock(devices={}, zc={}) with patch( - "homeassistant.components.cast.discovery.pychromecast.CastListener", - return_value=listener, - ) as cast_listener, patch( - "homeassistant.components.cast.discovery.pychromecast.start_discovery", + "homeassistant.components.cast.discovery.pychromecast.discovery.CastBrowser", return_value=browser, - ) as start_discovery: + ) as cast_browser: add_entities = await async_setup_cast(hass, config) await hass.async_block_till_done() await hass.async_block_till_done() - assert start_discovery.call_count == 1 + assert browser.start_discovery.call_count == 1 - discovery_callback = cast_listener.call_args[0][0] - remove_callback = cast_listener.call_args[0][1] + discovery_callback = cast_browser.call_args[0][0].add_cast + remove_callback = cast_browser.call_args[0][0].remove_cast def discover_chromecast(service_name: str, info: ChromecastInfo) -> None: """Discover a chromecast device.""" - listener.services[info.uuid] = ( + browser.devices[info.uuid] = pychromecast.discovery.CastInfo( {service_name}, info.uuid, info.model_name, info.friendly_name, + info.host, + info.port, ) discovery_callback(info.uuid, service_name) @@ -175,7 +206,14 @@ async def async_setup_cast_internal_discovery(hass, config=None): remove_callback( info.uuid, service_name, - (set(), info.uuid, info.model_name, info.friendly_name), + pychromecast.discovery.CastInfo( + set(), + info.uuid, + info.model_name, + info.friendly_name, + info.host, + info.port, + ), ) return discover_chromecast, remove_chromecast, add_entities @@ -183,21 +221,17 @@ async def async_setup_cast_internal_discovery(hass, config=None): async def async_setup_media_player_cast(hass: HomeAssistantType, info: ChromecastInfo): """Set up the cast platform with async_setup_component.""" - listener = MagicMock(services={}) - browser = MagicMock(zc={}) + browser = MagicMock(devices={}, zc={}) chromecast = get_fake_chromecast(info) zconf = get_fake_zconf(host=info.host, port=info.port) with patch( - "homeassistant.components.cast.discovery.pychromecast.get_chromecast_from_service", + "homeassistant.components.cast.discovery.pychromecast.get_chromecast_from_cast_info", return_value=chromecast, ) as get_chromecast, patch( - "homeassistant.components.cast.discovery.pychromecast.CastListener", - return_value=listener, - ) as cast_listener, patch( - "homeassistant.components.cast.discovery.pychromecast.start_discovery", + "homeassistant.components.cast.discovery.pychromecast.discovery.CastBrowser", return_value=browser, - ), patch( + ) as cast_browser, patch( "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", return_value=zconf, ): @@ -205,15 +239,18 @@ async def async_setup_media_player_cast(hass: HomeAssistantType, info: Chromecas hass, "cast", {"cast": {"media_player": {"uuid": info.uuid}}} ) await hass.async_block_till_done() + await hass.async_block_till_done() - discovery_callback = cast_listener.call_args[0][0] + discovery_callback = cast_browser.call_args[0][0].add_cast service_name = "the-service" - listener.services[info.uuid] = ( + browser.devices[info.uuid] = pychromecast.discovery.CastInfo( {service_name}, info.uuid, info.model_name, info.friendly_name, + info.host, + info.port, ) discovery_callback(info.uuid, service_name) @@ -223,11 +260,13 @@ async def async_setup_media_player_cast(hass: HomeAssistantType, info: Chromecas def discover_chromecast(service_name: str, info: ChromecastInfo) -> None: """Discover a chromecast device.""" - listener.services[info.uuid] = ( + browser.devices[info.uuid] = pychromecast.discovery.CastInfo( {service_name}, info.uuid, info.model_name, info.friendly_name, + info.host, + info.port, ) discovery_callback(info.uuid, service_name) @@ -253,18 +292,13 @@ def get_status_callbacks(chromecast_mock, mz_mock=None): return cast_status_cb, conn_status_cb, media_status_cb, group_media_status_cb -async def test_start_discovery_called_once(hass): +async def test_start_discovery_called_once(hass, castbrowser_mock): """Test pychromecast.start_discovery called exactly once.""" - with patch( - "homeassistant.components.cast.discovery.pychromecast.start_discovery", - return_value=Mock(), - ) as start_discovery: - await async_setup_cast(hass) + await async_setup_cast(hass) + assert castbrowser_mock.start_discovery.call_count == 1 - assert start_discovery.call_count == 1 - - await async_setup_cast(hass) - assert start_discovery.call_count == 1 + await async_setup_cast(hass) + assert castbrowser_mock.start_discovery.call_count == 1 async def test_internal_discovery_callback_fill_out(hass): @@ -350,7 +384,6 @@ async def test_internal_discovery_callback_fill_out_fail(hass): # when called with incomplete info, it should use HTTP to get missing discover = signal.mock_calls[0][1][0] assert discover == full_info - # assert 1 == 2 async def test_internal_discovery_callback_fill_out_group(hass): @@ -384,27 +417,16 @@ async def test_internal_discovery_callback_fill_out_group(hass): assert discover == full_info -async def test_stop_discovery_called_on_stop(hass): +async def test_stop_discovery_called_on_stop(hass, castbrowser_mock): """Test pychromecast.stop_discovery called on shutdown.""" - browser = MagicMock(zc={}) + # start_discovery should be called with empty config + await async_setup_cast(hass, {}) + assert castbrowser_mock.start_discovery.call_count == 1 - with patch( - "homeassistant.components.cast.discovery.pychromecast.start_discovery", - return_value=browser, - ) as start_discovery: - # start_discovery should be called with empty config - await async_setup_cast(hass, {}) - - assert start_discovery.call_count == 1 - - with patch( - "homeassistant.components.cast.discovery.pychromecast.discovery.stop_discovery" - ) as stop_discovery: - # stop discovery should be called on shutdown - hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) - await hass.async_block_till_done() - - stop_discovery.assert_called_once_with(browser) + # stop discovery should be called on shutdown + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + assert castbrowser_mock.stop_discovery.call_count == 1 async def test_create_cast_device_without_uuid(hass): @@ -539,7 +561,7 @@ async def test_discover_dynamic_group(hass, dial_mock, pycast_mock, caplog): tmp2.uuid = FakeUUID2 dial_mock.get_multizone_status.return_value.dynamic_groups = [tmp1, tmp2] - pycast_mock.get_chromecast_from_service.assert_not_called() + pycast_mock.get_chromecast_from_cast_info.assert_not_called() discover_cast, remove_cast, add_dev1 = await async_setup_cast_internal_discovery( hass ) @@ -552,8 +574,8 @@ async def test_discover_dynamic_group(hass, dial_mock, pycast_mock, caplog): discover_cast("service", cast_1) await hass.async_block_till_done() await hass.async_block_till_done() # having tasks that add jobs - pycast_mock.get_chromecast_from_service.assert_called() - pycast_mock.get_chromecast_from_service.reset_mock() + pycast_mock.get_chromecast_from_cast_info.assert_called() + pycast_mock.get_chromecast_from_cast_info.reset_mock() assert add_dev1.call_count == 0 assert reg.async_get_entity_id("media_player", "cast", cast_1.uuid) is None @@ -565,8 +587,8 @@ async def test_discover_dynamic_group(hass, dial_mock, pycast_mock, caplog): discover_cast("service", cast_2) await hass.async_block_till_done() await hass.async_block_till_done() # having tasks that add jobs - pycast_mock.get_chromecast_from_service.assert_called() - pycast_mock.get_chromecast_from_service.reset_mock() + pycast_mock.get_chromecast_from_cast_info.assert_called() + pycast_mock.get_chromecast_from_cast_info.reset_mock() assert add_dev1.call_count == 0 assert reg.async_get_entity_id("media_player", "cast", cast_1.uuid) is None @@ -578,7 +600,7 @@ async def test_discover_dynamic_group(hass, dial_mock, pycast_mock, caplog): discover_cast("service", cast_1) await hass.async_block_till_done() await hass.async_block_till_done() # having tasks that add jobs - pycast_mock.get_chromecast_from_service.assert_not_called() + pycast_mock.get_chromecast_from_cast_info.assert_not_called() assert add_dev1.call_count == 0 assert reg.async_get_entity_id("media_player", "cast", cast_1.uuid) is None From 56673f7edff97dd60f3b8ffa5ec22d49aad4d603 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 26 Feb 2021 07:45:21 -0500 Subject: [PATCH 036/831] Remove flaky climacell test (#47080) --- tests/components/climacell/test_init.py | 46 +------------------------ 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/tests/components/climacell/test_init.py b/tests/components/climacell/test_init.py index 1456a068d77..f3d7e490090 100644 --- a/tests/components/climacell/test_init.py +++ b/tests/components/climacell/test_init.py @@ -1,7 +1,5 @@ """Tests for Climacell init.""" -from datetime import timedelta import logging -from unittest.mock import patch import pytest @@ -12,11 +10,10 @@ from homeassistant.components.climacell.config_flow import ( from homeassistant.components.climacell.const import DOMAIN from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN from homeassistant.helpers.typing import HomeAssistantType -from homeassistant.util import dt as dt_util from .const import MIN_CONFIG -from tests.common import MockConfigEntry, async_fire_time_changed +from tests.common import MockConfigEntry _LOGGER = logging.getLogger(__name__) @@ -39,44 +36,3 @@ async def test_load_and_unload( assert await hass.config_entries.async_remove(config_entry.entry_id) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(WEATHER_DOMAIN)) == 0 - - -async def test_update_interval( - hass: HomeAssistantType, - climacell_config_entry_update: pytest.fixture, -) -> None: - """Test that update_interval changes based on number of entries.""" - now = dt_util.utcnow() - async_fire_time_changed(hass, now) - config = _get_config_schema(hass)(MIN_CONFIG) - for i in range(1, 3): - config_entry = MockConfigEntry( - domain=DOMAIN, data=config, unique_id=_get_unique_id(hass, config) + str(i) - ) - config_entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - with patch("homeassistant.components.climacell.ClimaCell.realtime") as mock_api: - # First entry refresh will happen in 7 minutes due to original update interval. - # Next refresh for this entry will happen at 20 minutes due to the update interval - # change. - mock_api.return_value = {} - async_fire_time_changed(hass, now + timedelta(minutes=7)) - await hass.async_block_till_done() - assert mock_api.call_count == 1 - - # Second entry refresh will happen in 13 minutes due to the update interval set - # when it was set up. Next refresh for this entry will happen at 26 minutes due to the - # update interval change. - mock_api.reset_mock() - async_fire_time_changed(hass, now + timedelta(minutes=13)) - await hass.async_block_till_done() - assert not mock_api.call_count == 1 - - # 19 minutes should be after the first update for each config entry and before the - # second update for the first config entry - mock_api.reset_mock() - async_fire_time_changed(hass, now + timedelta(minutes=19)) - await hass.async_block_till_done() - assert not mock_api.call_count == 0 From 9c67f83f4e058f86ba6c4a2d6a0b4fb4e6d99ae6 Mon Sep 17 00:00:00 2001 From: rikroe <42204099+rikroe@users.noreply.github.com> Date: Fri, 26 Feb 2021 13:57:47 +0100 Subject: [PATCH 037/831] Bump bimmer_connected to 0.7.15 and fix bugs (#47066) Co-authored-by: rikroe --- homeassistant/components/bmw_connected_drive/__init__.py | 2 +- .../components/bmw_connected_drive/device_tracker.py | 4 ++-- homeassistant/components/bmw_connected_drive/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index 9d794ace5be..a8bebfbc617 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -122,7 +122,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): def _update_all() -> None: """Update all BMW accounts.""" - for entry in hass.data[DOMAIN][DATA_ENTRIES].values(): + for entry in hass.data[DOMAIN][DATA_ENTRIES].copy().values(): entry[CONF_ACCOUNT].update() # Add update listener for config entry changes (options) diff --git a/homeassistant/components/bmw_connected_drive/device_tracker.py b/homeassistant/components/bmw_connected_drive/device_tracker.py index 7f069e741b8..25adf6cb09f 100644 --- a/homeassistant/components/bmw_connected_drive/device_tracker.py +++ b/homeassistant/components/bmw_connected_drive/device_tracker.py @@ -42,12 +42,12 @@ class BMWDeviceTracker(BMWConnectedDriveBaseEntity, TrackerEntity): @property def latitude(self): """Return latitude value of the device.""" - return self._location[0] + return self._location[0] if self._location else None @property def longitude(self): """Return longitude value of the device.""" - return self._location[1] + return self._location[1] if self._location else None @property def name(self): diff --git a/homeassistant/components/bmw_connected_drive/manifest.json b/homeassistant/components/bmw_connected_drive/manifest.json index c1d90f713f4..bbff139187e 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.14"], + "requirements": ["bimmer_connected==0.7.15"], "codeowners": ["@gerard33", "@rikroe"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 135e2c74f63..2353797b8b8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -343,7 +343,7 @@ beautifulsoup4==4.9.3 bellows==0.21.0 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.7.14 +bimmer_connected==0.7.15 # homeassistant.components.bizkaibus bizkaibus==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1499679fd5d..b6cfca171e2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -196,7 +196,7 @@ base36==0.1.1 bellows==0.21.0 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.7.14 +bimmer_connected==0.7.15 # homeassistant.components.blebox blebox_uniapi==1.3.2 From 71cf982a281a37e13a8f8f261654a485cf65ee26 Mon Sep 17 00:00:00 2001 From: Guido Schmitz Date: Fri, 26 Feb 2021 15:48:36 +0100 Subject: [PATCH 038/831] Add suggested_area support to devolo Home Control (#47063) --- homeassistant/components/devolo_home_control/devolo_device.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/devolo_home_control/devolo_device.py b/homeassistant/components/devolo_home_control/devolo_device.py index d0be9543bf4..6aef842ffff 100644 --- a/homeassistant/components/devolo_home_control/devolo_device.py +++ b/homeassistant/components/devolo_home_control/devolo_device.py @@ -18,6 +18,7 @@ class DevoloDeviceEntity(Entity): self._unique_id = element_uid self._homecontrol = homecontrol self._name = device_instance.settings_property["general_device_settings"].name + self._area = device_instance.settings_property["general_device_settings"].zone self._device_class = None self._value = None self._unit = None @@ -59,6 +60,7 @@ class DevoloDeviceEntity(Entity): "name": self._name, "manufacturer": self._brand, "model": self._model, + "suggested_area": self._area, } @property From 6e77ca70fcb7370189d7dc667394af71168d537a Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 26 Feb 2021 15:49:33 +0100 Subject: [PATCH 039/831] Add new machine generic-x86-64 to build matrix (#47095) The Intel NUC machine runs on most UEFI capable x86-64 machines today. Lets start a new machine generic-x86-64 which will replace intel-nuc over time. --- azure-pipelines-release.yml | 4 +++- machine/generic-x86-64 | 34 ++++++++++++++++++++++++++++++++++ machine/intel-nuc | 3 +++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 machine/generic-x86-64 diff --git a/azure-pipelines-release.yml b/azure-pipelines-release.yml index 418fdf5b26c..5fe91325582 100644 --- a/azure-pipelines-release.yml +++ b/azure-pipelines-release.yml @@ -114,10 +114,12 @@ stages: pool: vmImage: 'ubuntu-latest' strategy: - maxParallel: 15 + maxParallel: 17 matrix: qemux86-64: buildMachine: 'qemux86-64' + generic-x86-64: + buildMachine: 'generic-x86-64' intel-nuc: buildMachine: 'intel-nuc' qemux86: diff --git a/machine/generic-x86-64 b/machine/generic-x86-64 new file mode 100644 index 00000000000..e858c382221 --- /dev/null +++ b/machine/generic-x86-64 @@ -0,0 +1,34 @@ +ARG BUILD_VERSION +FROM agners/amd64-homeassistant:$BUILD_VERSION + +RUN apk --no-cache add \ + libva-intel-driver \ + usbutils + +## +# Build libcec for HDMI-CEC +ARG LIBCEC_VERSION=6.0.2 +RUN apk add --no-cache \ + eudev-libs \ + p8-platform \ + && apk add --no-cache --virtual .build-dependencies \ + build-base \ + cmake \ + eudev-dev \ + swig \ + p8-platform-dev \ + linux-headers \ + && git clone --depth 1 -b libcec-${LIBCEC_VERSION} https://github.com/Pulse-Eight/libcec /usr/src/libcec \ + && cd /usr/src/libcec \ + && mkdir -p /usr/src/libcec/build \ + && cd /usr/src/libcec/build \ + && cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr/local \ + -DPYTHON_LIBRARY="/usr/local/lib/libpython3.8.so" \ + -DPYTHON_INCLUDE_DIR="/usr/local/include/python3.8" \ + -DHAVE_LINUX_API=1 \ + .. \ + && make -j$(nproc) \ + && make install \ + && echo "cec" > "/usr/local/lib/python3.8/site-packages/cec.pth" \ + && apk del .build-dependencies \ + && rm -rf /usr/src/libcec* diff --git a/machine/intel-nuc b/machine/intel-nuc index 4c83228387d..b5538b8ccad 100644 --- a/machine/intel-nuc +++ b/machine/intel-nuc @@ -1,6 +1,9 @@ ARG BUILD_VERSION FROM homeassistant/amd64-homeassistant:$BUILD_VERSION +# NOTE: intel-nuc will be replaced by generic-x86-64. Make sure to apply +# changes in generic-x86-64 as well. + RUN apk --no-cache add \ libva-intel-driver \ usbutils From d8633f94f6b94b77003dfd83aece47707bbac1db Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Fri, 26 Feb 2021 10:07:50 -0500 Subject: [PATCH 040/831] Guard zwave_js missing nodes in websocket api (#47096) --- homeassistant/components/zwave_js/api.py | 14 ++++++++++-- tests/components/zwave_js/test_api.py | 27 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 12ec66906a8..599183eba7e 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -106,7 +106,12 @@ def websocket_node_status( entry_id = msg[ENTRY_ID] client = hass.data[DOMAIN][entry_id][DATA_CLIENT] node_id = msg[NODE_ID] - node = client.driver.controller.nodes[node_id] + node = client.driver.controller.nodes.get(node_id) + + if node is None: + connection.send_error(msg[ID], ERR_NOT_FOUND, f"Node {node_id} not found") + return + data = { "node_id": node.node_id, "is_routing": node.is_routing, @@ -354,7 +359,12 @@ def websocket_get_config_parameters( entry_id = msg[ENTRY_ID] node_id = msg[NODE_ID] client = hass.data[DOMAIN][entry_id][DATA_CLIENT] - node = client.driver.controller.nodes[node_id] + node = client.driver.controller.nodes.get(node_id) + + if node is None: + connection.send_error(msg[ID], ERR_NOT_FOUND, f"Node {node_id} not found") + return + values = node.get_configuration_values() result = {} for value_id, zwave_value in values.items(): diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index dcbd924c86e..9760e1a06b7 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -6,6 +6,7 @@ from zwave_js_server.const import LogLevel from zwave_js_server.event import Event from zwave_js_server.exceptions import InvalidNewValue, NotFoundError, SetValueFailed +from homeassistant.components.websocket_api.const import ERR_NOT_FOUND from homeassistant.components.zwave_js.api import ( CONFIG, ENABLED, @@ -76,6 +77,32 @@ async def test_websocket_api(hass, integration, multisensor_6, hass_ws_client): assert result[key]["configuration_value_type"] == "enumerated" assert result[key]["metadata"]["states"] + # Test getting non-existent node fails + await ws_client.send_json( + { + ID: 5, + TYPE: "zwave_js/node_status", + ENTRY_ID: entry.entry_id, + NODE_ID: 99999, + } + ) + msg = await ws_client.receive_json() + assert not msg["success"] + assert msg["error"]["code"] == ERR_NOT_FOUND + + # Test getting non-existent node config params fails + await ws_client.send_json( + { + ID: 6, + TYPE: "zwave_js/get_config_parameters", + ENTRY_ID: entry.entry_id, + NODE_ID: 99999, + } + ) + msg = await ws_client.receive_json() + assert not msg["success"] + assert msg["error"]["code"] == ERR_NOT_FOUND + async def test_add_node( hass, integration, client, hass_ws_client, nortek_thermostat_added_event From 7ab2d91bf09dededf76e20c3797ae2188e87d3ec Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Feb 2021 10:35:09 -0600 Subject: [PATCH 041/831] Add suggested area to hue (#47056) --- homeassistant/components/hue/const.py | 5 + homeassistant/components/hue/light.py | 151 ++++++++++++++++++++------ tests/components/hue/test_light.py | 99 ++++++++++++----- 3 files changed, 191 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/hue/const.py b/homeassistant/components/hue/const.py index 593f74331ec..b782ce70193 100644 --- a/homeassistant/components/hue/const.py +++ b/homeassistant/components/hue/const.py @@ -15,3 +15,8 @@ CONF_ALLOW_HUE_GROUPS = "allow_hue_groups" DEFAULT_ALLOW_HUE_GROUPS = False DEFAULT_SCENE_TRANSITION = 4 + +GROUP_TYPE_LIGHT_GROUP = "LightGroup" +GROUP_TYPE_ROOM = "Room" +GROUP_TYPE_LUMINAIRE = "Luminaire" +GROUP_TYPE_LIGHT_SOURCE = "LightSource" diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index 821d482ec25..6384e47b45e 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -36,7 +36,14 @@ from homeassistant.helpers.update_coordinator import ( ) from homeassistant.util import color -from .const import DOMAIN as HUE_DOMAIN, REQUEST_REFRESH_DELAY +from .const import ( + DOMAIN as HUE_DOMAIN, + GROUP_TYPE_LIGHT_GROUP, + GROUP_TYPE_LIGHT_SOURCE, + GROUP_TYPE_LUMINAIRE, + GROUP_TYPE_ROOM, + REQUEST_REFRESH_DELAY, +) from .helpers import remove_devices SCAN_INTERVAL = timedelta(seconds=5) @@ -74,24 +81,35 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= """ -def create_light(item_class, coordinator, bridge, is_group, api, item_id): +def create_light(item_class, coordinator, bridge, is_group, rooms, api, item_id): """Create the light.""" + api_item = api[item_id] + if is_group: supported_features = 0 - for light_id in api[item_id].lights: + for light_id in api_item.lights: if light_id not in bridge.api.lights: continue light = bridge.api.lights[light_id] supported_features |= SUPPORT_HUE.get(light.type, SUPPORT_HUE_EXTENDED) supported_features = supported_features or SUPPORT_HUE_EXTENDED else: - supported_features = SUPPORT_HUE.get(api[item_id].type, SUPPORT_HUE_EXTENDED) - return item_class(coordinator, bridge, is_group, api[item_id], supported_features) + supported_features = SUPPORT_HUE.get(api_item.type, SUPPORT_HUE_EXTENDED) + return item_class( + coordinator, bridge, is_group, api_item, supported_features, rooms + ) async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Hue lights from a config entry.""" bridge = hass.data[HUE_DOMAIN][config_entry.entry_id] + api_version = tuple(int(v) for v in bridge.api.config.apiversion.split(".")) + rooms = {} + + allow_groups = bridge.allow_groups + supports_groups = api_version >= GROUP_MIN_API_VERSION + if allow_groups and not supports_groups: + _LOGGER.warning("Please update your Hue bridge to support groups") light_coordinator = DataUpdateCoordinator( hass, @@ -111,27 +129,20 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if not light_coordinator.last_update_success: raise PlatformNotReady - update_lights = partial( - async_update_items, - bridge, - bridge.api.lights, - {}, - async_add_entities, - partial(create_light, HueLight, light_coordinator, bridge, False), - ) - - # We add a listener after fetching the data, so manually trigger listener - bridge.reset_jobs.append(light_coordinator.async_add_listener(update_lights)) - update_lights() - - api_version = tuple(int(v) for v in bridge.api.config.apiversion.split(".")) - - allow_groups = bridge.allow_groups - if allow_groups and api_version < GROUP_MIN_API_VERSION: - _LOGGER.warning("Please update your Hue bridge to support groups") - allow_groups = False - - if not allow_groups: + if not supports_groups: + update_lights_without_group_support = partial( + async_update_items, + bridge, + bridge.api.lights, + {}, + async_add_entities, + partial(create_light, HueLight, light_coordinator, bridge, False, rooms), + None, + ) + # We add a listener after fetching the data, so manually trigger listener + bridge.reset_jobs.append( + light_coordinator.async_add_listener(update_lights_without_group_support) + ) return group_coordinator = DataUpdateCoordinator( @@ -145,17 +156,69 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ), ) - update_groups = partial( + if allow_groups: + update_groups = partial( + async_update_items, + bridge, + bridge.api.groups, + {}, + async_add_entities, + partial(create_light, HueLight, group_coordinator, bridge, True, None), + None, + ) + + bridge.reset_jobs.append(group_coordinator.async_add_listener(update_groups)) + + cancel_update_rooms_listener = None + + @callback + def _async_update_rooms(): + """Update rooms.""" + nonlocal cancel_update_rooms_listener + rooms.clear() + for item_id in bridge.api.groups: + group = bridge.api.groups[item_id] + if group.type != GROUP_TYPE_ROOM: + continue + for light_id in group.lights: + rooms[light_id] = group.name + + # Once we do a rooms update, we cancel the listener + # until the next time lights are added + bridge.reset_jobs.remove(cancel_update_rooms_listener) + cancel_update_rooms_listener() # pylint: disable=not-callable + cancel_update_rooms_listener = None + + @callback + def _setup_rooms_listener(): + nonlocal cancel_update_rooms_listener + if cancel_update_rooms_listener is not None: + # If there are new lights added before _async_update_rooms + # is called we should not add another listener + return + + cancel_update_rooms_listener = group_coordinator.async_add_listener( + _async_update_rooms + ) + bridge.reset_jobs.append(cancel_update_rooms_listener) + + _setup_rooms_listener() + await group_coordinator.async_refresh() + + update_lights_with_group_support = partial( async_update_items, bridge, - bridge.api.groups, + bridge.api.lights, {}, async_add_entities, - partial(create_light, HueLight, group_coordinator, bridge, True), + partial(create_light, HueLight, light_coordinator, bridge, False, rooms), + _setup_rooms_listener, ) - - bridge.reset_jobs.append(group_coordinator.async_add_listener(update_groups)) - await group_coordinator.async_refresh() + # We add a listener after fetching the data, so manually trigger listener + bridge.reset_jobs.append( + light_coordinator.async_add_listener(update_lights_with_group_support) + ) + update_lights_with_group_support() async def async_safe_fetch(bridge, fetch_method): @@ -171,7 +234,9 @@ async def async_safe_fetch(bridge, fetch_method): @callback -def async_update_items(bridge, api, current, async_add_entities, create_item): +def async_update_items( + bridge, api, current, async_add_entities, create_item, new_items_callback +): """Update items.""" new_items = [] @@ -185,6 +250,9 @@ def async_update_items(bridge, api, current, async_add_entities, create_item): bridge.hass.async_create_task(remove_devices(bridge, api, current)) if new_items: + # This is currently used to setup the listener to update rooms + if new_items_callback: + new_items_callback() async_add_entities(new_items) @@ -201,13 +269,14 @@ def hass_to_hue_brightness(value): class HueLight(CoordinatorEntity, LightEntity): """Representation of a Hue light.""" - def __init__(self, coordinator, bridge, is_group, light, supported_features): + def __init__(self, coordinator, bridge, is_group, light, supported_features, rooms): """Initialize the light.""" super().__init__(coordinator) self.light = light self.bridge = bridge self.is_group = is_group self._supported_features = supported_features + self._rooms = rooms if is_group: self.is_osram = False @@ -355,10 +424,15 @@ class HueLight(CoordinatorEntity, LightEntity): @property def device_info(self): """Return the device info.""" - if self.light.type in ("LightGroup", "Room", "Luminaire", "LightSource"): + if self.light.type in ( + GROUP_TYPE_LIGHT_GROUP, + GROUP_TYPE_ROOM, + GROUP_TYPE_LUMINAIRE, + GROUP_TYPE_LIGHT_SOURCE, + ): return None - return { + info = { "identifiers": {(HUE_DOMAIN, self.device_id)}, "name": self.name, "manufacturer": self.light.manufacturername, @@ -370,6 +444,11 @@ class HueLight(CoordinatorEntity, LightEntity): "via_device": (HUE_DOMAIN, self.bridge.api.config.bridgeid), } + if self.light.id in self._rooms: + info["suggested_area"] = self._rooms[self.light.id] + + return info + async def async_turn_on(self, **kwargs): """Turn the specified or all lights on.""" command = {"on": True} diff --git a/tests/components/hue/test_light.py b/tests/components/hue/test_light.py index 629a9a4c98b..39b9a5a23fc 100644 --- a/tests/components/hue/test_light.py +++ b/tests/components/hue/test_light.py @@ -7,6 +7,12 @@ import aiohue from homeassistant import config_entries from homeassistant.components import hue from homeassistant.components.hue import light as hue_light +from homeassistant.helpers.device_registry import ( + async_get_registry as async_get_device_registry, +) +from homeassistant.helpers.entity_registry import ( + async_get_registry as async_get_entity_registry, +) from homeassistant.util import color HUE_LIGHT_NS = "homeassistant.components.light.hue." @@ -211,8 +217,10 @@ async def test_no_lights_or_groups(hass, mock_bridge): async def test_lights(hass, mock_bridge): """Test the update_lights function with some lights.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) + await setup_bridge(hass, mock_bridge) - assert len(mock_bridge.mock_requests) == 1 + assert len(mock_bridge.mock_requests) == 2 # 2 lights assert len(hass.states.async_all()) == 2 @@ -230,6 +238,8 @@ async def test_lights(hass, mock_bridge): async def test_lights_color_mode(hass, mock_bridge): """Test that lights only report appropriate color mode.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) + await setup_bridge(hass, mock_bridge) lamp_1 = hass.states.get("light.hue_lamp_1") @@ -249,8 +259,8 @@ async def test_lights_color_mode(hass, mock_bridge): await hass.services.async_call( "light", "turn_on", {"entity_id": "light.hue_lamp_2"}, blocking=True ) - # 2x light update, 1 turn on request - assert len(mock_bridge.mock_requests) == 3 + # 2x light update, 1 group update, 1 turn on request + assert len(mock_bridge.mock_requests) == 4 lamp_1 = hass.states.get("light.hue_lamp_1") assert lamp_1 is not None @@ -332,9 +342,10 @@ async def test_new_group_discovered(hass, mock_bridge): async def test_new_light_discovered(hass, mock_bridge): """Test if 2nd update has a new light.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) await setup_bridge(hass, mock_bridge) - assert len(mock_bridge.mock_requests) == 1 + assert len(mock_bridge.mock_requests) == 2 assert len(hass.states.async_all()) == 2 new_light_response = dict(LIGHT_RESPONSE) @@ -366,8 +377,8 @@ async def test_new_light_discovered(hass, mock_bridge): await hass.services.async_call( "light", "turn_on", {"entity_id": "light.hue_lamp_1"}, blocking=True ) - # 2x light update, 1 turn on request - assert len(mock_bridge.mock_requests) == 3 + # 2x light update, 1 group update, 1 turn on request + assert len(mock_bridge.mock_requests) == 4 assert len(hass.states.async_all()) == 3 light = hass.states.get("light.hue_lamp_3") @@ -407,9 +418,10 @@ async def test_group_removed(hass, mock_bridge): async def test_light_removed(hass, mock_bridge): """Test if 2nd update has removed light.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) await setup_bridge(hass, mock_bridge) - assert len(mock_bridge.mock_requests) == 1 + assert len(mock_bridge.mock_requests) == 2 assert len(hass.states.async_all()) == 2 mock_bridge.mock_light_responses.clear() @@ -420,8 +432,8 @@ async def test_light_removed(hass, mock_bridge): "light", "turn_on", {"entity_id": "light.hue_lamp_1"}, blocking=True ) - # 2x light update, 1 turn on request - assert len(mock_bridge.mock_requests) == 3 + # 2x light update, 1 group update, 1 turn on request + assert len(mock_bridge.mock_requests) == 4 assert len(hass.states.async_all()) == 1 light = hass.states.get("light.hue_lamp_1") @@ -487,9 +499,10 @@ async def test_other_group_update(hass, mock_bridge): async def test_other_light_update(hass, mock_bridge): """Test changing one light that will impact state of other light.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) await setup_bridge(hass, mock_bridge) - assert len(mock_bridge.mock_requests) == 1 + assert len(mock_bridge.mock_requests) == 2 assert len(hass.states.async_all()) == 2 lamp_2 = hass.states.get("light.hue_lamp_2") @@ -526,8 +539,8 @@ async def test_other_light_update(hass, mock_bridge): await hass.services.async_call( "light", "turn_on", {"entity_id": "light.hue_lamp_1"}, blocking=True ) - # 2x light update, 1 turn on request - assert len(mock_bridge.mock_requests) == 3 + # 2x light update, 1 group update, 1 turn on request + assert len(mock_bridge.mock_requests) == 4 assert len(hass.states.async_all()) == 2 lamp_2 = hass.states.get("light.hue_lamp_2") @@ -549,7 +562,6 @@ async def test_update_timeout(hass, mock_bridge): async def test_update_unauthorized(hass, mock_bridge): """Test bridge marked as not authorized if unauthorized during update.""" mock_bridge.api.lights.update = Mock(side_effect=aiohue.Unauthorized) - mock_bridge.api.groups.update = Mock(side_effect=aiohue.Unauthorized) await setup_bridge(hass, mock_bridge) assert len(mock_bridge.mock_requests) == 0 assert len(hass.states.async_all()) == 0 @@ -559,6 +571,8 @@ async def test_update_unauthorized(hass, mock_bridge): async def test_light_turn_on_service(hass, mock_bridge): """Test calling the turn on service on a light.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) + await setup_bridge(hass, mock_bridge) light = hass.states.get("light.hue_lamp_2") assert light is not None @@ -575,10 +589,10 @@ async def test_light_turn_on_service(hass, mock_bridge): {"entity_id": "light.hue_lamp_2", "brightness": 100, "color_temp": 300}, blocking=True, ) - # 2x light update, 1 turn on request - assert len(mock_bridge.mock_requests) == 3 + # 2x light update, 1 group update, 1 turn on request + assert len(mock_bridge.mock_requests) == 4 - assert mock_bridge.mock_requests[1]["json"] == { + assert mock_bridge.mock_requests[2]["json"] == { "bri": 100, "on": True, "ct": 300, @@ -599,9 +613,9 @@ async def test_light_turn_on_service(hass, mock_bridge): blocking=True, ) - assert len(mock_bridge.mock_requests) == 5 + assert len(mock_bridge.mock_requests) == 6 - assert mock_bridge.mock_requests[3]["json"] == { + assert mock_bridge.mock_requests[4]["json"] == { "on": True, "xy": (0.138, 0.08), "alert": "none", @@ -611,6 +625,8 @@ async def test_light_turn_on_service(hass, mock_bridge): async def test_light_turn_off_service(hass, mock_bridge): """Test calling the turn on service on a light.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) + await setup_bridge(hass, mock_bridge) light = hass.states.get("light.hue_lamp_1") assert light is not None @@ -624,10 +640,11 @@ async def test_light_turn_off_service(hass, mock_bridge): await hass.services.async_call( "light", "turn_off", {"entity_id": "light.hue_lamp_1"}, blocking=True ) - # 2x light update, 1 turn on request - assert len(mock_bridge.mock_requests) == 3 - assert mock_bridge.mock_requests[1]["json"] == {"on": False, "alert": "none"} + # 2x light update, 1 for group update, 1 turn on request + assert len(mock_bridge.mock_requests) == 4 + + assert mock_bridge.mock_requests[2]["json"] == {"on": False, "alert": "none"} assert len(hass.states.async_all()) == 2 @@ -649,6 +666,7 @@ def test_available(): bridge=Mock(allow_unreachable=False), is_group=False, supported_features=hue_light.SUPPORT_HUE_EXTENDED, + rooms={}, ) assert light.available is False @@ -664,6 +682,7 @@ def test_available(): bridge=Mock(allow_unreachable=True), is_group=False, supported_features=hue_light.SUPPORT_HUE_EXTENDED, + rooms={}, ) assert light.available is True @@ -679,6 +698,7 @@ def test_available(): bridge=Mock(allow_unreachable=False), is_group=True, supported_features=hue_light.SUPPORT_HUE_EXTENDED, + rooms={}, ) assert light.available is True @@ -697,6 +717,7 @@ def test_hs_color(): bridge=Mock(), is_group=False, supported_features=hue_light.SUPPORT_HUE_EXTENDED, + rooms={}, ) assert light.hs_color is None @@ -712,6 +733,7 @@ def test_hs_color(): bridge=Mock(), is_group=False, supported_features=hue_light.SUPPORT_HUE_EXTENDED, + rooms={}, ) assert light.hs_color is None @@ -727,6 +749,7 @@ def test_hs_color(): bridge=Mock(), is_group=False, supported_features=hue_light.SUPPORT_HUE_EXTENDED, + rooms={}, ) assert light.hs_color == color.color_xy_to_hs(0.4, 0.5, LIGHT_GAMUT) @@ -742,7 +765,7 @@ async def test_group_features(hass, mock_bridge): "1": { "name": "Group 1", "lights": ["1", "2"], - "type": "Room", + "type": "LightGroup", "action": { "on": True, "bri": 254, @@ -757,8 +780,8 @@ async def test_group_features(hass, mock_bridge): "state": {"any_on": True, "all_on": False}, }, "2": { - "name": "Group 2", - "lights": ["3", "4"], + "name": "Living Room", + "lights": ["2", "3"], "type": "Room", "action": { "on": True, @@ -774,8 +797,8 @@ async def test_group_features(hass, mock_bridge): "state": {"any_on": True, "all_on": False}, }, "3": { - "name": "Group 3", - "lights": ["1", "3"], + "name": "Dining Room", + "lights": ["4"], "type": "Room", "action": { "on": True, @@ -900,6 +923,7 @@ async def test_group_features(hass, mock_bridge): mock_bridge.mock_light_responses.append(light_response) mock_bridge.mock_group_responses.append(group_response) await setup_bridge(hass, mock_bridge) + assert len(mock_bridge.mock_requests) == 2 color_temp_feature = hue_light.SUPPORT_HUE["Color temperature light"] extended_color_feature = hue_light.SUPPORT_HUE["Extended color light"] @@ -907,8 +931,27 @@ async def test_group_features(hass, mock_bridge): group_1 = hass.states.get("light.group_1") assert group_1.attributes["supported_features"] == color_temp_feature - group_2 = hass.states.get("light.group_2") + group_2 = hass.states.get("light.living_room") assert group_2.attributes["supported_features"] == extended_color_feature - group_3 = hass.states.get("light.group_3") + group_3 = hass.states.get("light.dining_room") assert group_3.attributes["supported_features"] == extended_color_feature + + entity_registry = await async_get_entity_registry(hass) + device_registry = await async_get_device_registry(hass) + + entry = entity_registry.async_get("light.hue_lamp_1") + device_entry = device_registry.async_get(entry.device_id) + assert device_entry.suggested_area is None + + entry = entity_registry.async_get("light.hue_lamp_2") + device_entry = device_registry.async_get(entry.device_id) + assert device_entry.suggested_area == "Living Room" + + entry = entity_registry.async_get("light.hue_lamp_3") + device_entry = device_registry.async_get(entry.device_id) + assert device_entry.suggested_area == "Living Room" + + entry = entity_registry.async_get("light.hue_lamp_4") + device_entry = device_registry.async_get(entry.device_id) + assert device_entry.suggested_area == "Dining Room" From e12eba1989b7319cc24af82be8e8ffbc449a6d04 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Fri, 26 Feb 2021 18:34:40 +0100 Subject: [PATCH 042/831] Add support for v6 features to philips js integration (#46422) --- .../components/philips_js/__init__.py | 54 ++- .../components/philips_js/config_flow.py | 135 ++++-- homeassistant/components/philips_js/const.py | 3 + .../components/philips_js/manifest.json | 2 +- .../components/philips_js/media_player.py | 409 ++++++++++++++---- .../components/philips_js/strings.json | 6 +- .../philips_js/translations/en.json | 4 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/philips_js/__init__.py | 60 ++- tests/components/philips_js/conftest.py | 13 +- .../components/philips_js/test_config_flow.py | 144 +++++- .../philips_js/test_device_trigger.py | 6 +- 13 files changed, 702 insertions(+), 138 deletions(-) diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index 11e84b6cd82..f3c2eb59789 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -8,8 +8,13 @@ from haphilipsjs import ConnectionFailure, PhilipsTV from homeassistant.components.automation import AutomationActionType from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_API_VERSION, CONF_HOST -from homeassistant.core import Context, HassJob, HomeAssistant, callback +from homeassistant.const import ( + CONF_API_VERSION, + CONF_HOST, + CONF_PASSWORD, + CONF_USERNAME, +) +from homeassistant.core import CALLBACK_TYPE, Context, HassJob, HomeAssistant, callback from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator @@ -30,7 +35,12 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Philips TV from a config entry.""" - tvapi = PhilipsTV(entry.data[CONF_HOST], entry.data[CONF_API_VERSION]) + tvapi = PhilipsTV( + entry.data[CONF_HOST], + entry.data[CONF_API_VERSION], + username=entry.data.get(CONF_USERNAME), + password=entry.data.get(CONF_PASSWORD), + ) coordinator = PhilipsTVDataUpdateCoordinator(hass, tvapi) @@ -103,7 +113,9 @@ class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]): def __init__(self, hass, api: PhilipsTV) -> None: """Set up the coordinator.""" self.api = api + self._notify_future: Optional[asyncio.Task] = None + @callback def _update_listeners(): for update_callback in self._listeners: update_callback() @@ -120,9 +132,43 @@ class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]): ), ) + async def _notify_task(self): + while self.api.on and self.api.notify_change_supported: + if await self.api.notifyChange(130): + self.async_set_updated_data(None) + + @callback + def _async_notify_stop(self): + if self._notify_future: + self._notify_future.cancel() + self._notify_future = None + + @callback + def _async_notify_schedule(self): + if ( + (self._notify_future is None or self._notify_future.done()) + and self.api.on + and self.api.notify_change_supported + ): + self._notify_future = self.hass.loop.create_task(self._notify_task()) + + @callback + def async_remove_listener(self, update_callback: CALLBACK_TYPE) -> None: + """Remove data update.""" + super().async_remove_listener(update_callback) + if not self._listeners: + self._async_notify_stop() + + @callback + def _async_stop_refresh(self, event: asyncio.Event) -> None: + super()._async_stop_refresh(event) + self._async_notify_stop() + + @callback async def _async_update_data(self): """Fetch the latest data from the source.""" try: - await self.hass.async_add_executor_job(self.api.update) + await self.api.update() + self._async_notify_schedule() except ConnectionFailure: pass diff --git a/homeassistant/components/philips_js/config_flow.py b/homeassistant/components/philips_js/config_flow.py index 523918daa7c..778bcba282b 100644 --- a/homeassistant/components/philips_js/config_flow.py +++ b/homeassistant/components/philips_js/config_flow.py @@ -1,35 +1,47 @@ """Config flow for Philips TV integration.""" -import logging -from typing import Any, Dict, Optional, TypedDict +import platform +from typing import Any, Dict, Optional, Tuple, TypedDict -from haphilipsjs import ConnectionFailure, PhilipsTV +from haphilipsjs import ConnectionFailure, PairingFailure, PhilipsTV import voluptuous as vol from homeassistant import config_entries, core -from homeassistant.const import CONF_API_VERSION, CONF_HOST +from homeassistant.const import ( + CONF_API_VERSION, + CONF_HOST, + CONF_PASSWORD, + CONF_PIN, + CONF_USERNAME, +) -from .const import DOMAIN # pylint:disable=unused-import - -_LOGGER = logging.getLogger(__name__) +from . import LOGGER +from .const import ( # pylint:disable=unused-import + CONF_SYSTEM, + CONST_APP_ID, + CONST_APP_NAME, + DOMAIN, +) class FlowUserDict(TypedDict): """Data for user step.""" host: str - api_version: int -async def validate_input(hass: core.HomeAssistant, data: FlowUserDict): +async def validate_input( + hass: core.HomeAssistant, host: str, api_version: int +) -> Tuple[Dict, PhilipsTV]: """Validate the user input allows us to connect.""" - hub = PhilipsTV(data[CONF_HOST], data[CONF_API_VERSION]) + hub = PhilipsTV(host, api_version) - await hass.async_add_executor_job(hub.getSystem) + await hub.getSystem() + await hub.setTransport(hub.secured_transport) - if hub.system is None: - raise ConnectionFailure + if not hub.system: + raise ConnectionFailure("System data is empty") - return hub.system + return hub class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -38,7 +50,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL - _default = {} + _current = {} + _hub: PhilipsTV + _pair_state: Any async def async_step_import(self, conf: Dict[str, Any]): """Import a configuration from config.yaml.""" @@ -53,34 +67,99 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): } ) + async def _async_create_current(self): + + system = self._current[CONF_SYSTEM] + return self.async_create_entry( + title=f"{system['name']} ({system['serialnumber']})", + data=self._current, + ) + + async def async_step_pair(self, user_input: Optional[Dict] = None): + """Attempt to pair with device.""" + assert self._hub + + errors = {} + schema = vol.Schema( + { + vol.Required(CONF_PIN): str, + } + ) + + if not user_input: + try: + self._pair_state = await self._hub.pairRequest( + CONST_APP_ID, + CONST_APP_NAME, + platform.node(), + platform.system(), + "native", + ) + except PairingFailure as exc: + LOGGER.debug(str(exc)) + return self.async_abort( + reason="pairing_failure", + description_placeholders={"error_id": exc.data.get("error_id")}, + ) + return self.async_show_form( + step_id="pair", data_schema=schema, errors=errors + ) + + try: + username, password = await self._hub.pairGrant( + self._pair_state, user_input[CONF_PIN] + ) + except PairingFailure as exc: + LOGGER.debug(str(exc)) + if exc.data.get("error_id") == "INVALID_PIN": + errors[CONF_PIN] = "invalid_pin" + return self.async_show_form( + step_id="pair", data_schema=schema, errors=errors + ) + + return self.async_abort( + reason="pairing_failure", + description_placeholders={"error_id": exc.data.get("error_id")}, + ) + + self._current[CONF_USERNAME] = username + self._current[CONF_PASSWORD] = password + return await self._async_create_current() + async def async_step_user(self, user_input: Optional[FlowUserDict] = None): """Handle the initial step.""" errors = {} if user_input: - self._default = user_input + self._current = user_input try: - system = await validate_input(self.hass, user_input) - except ConnectionFailure: + hub = await validate_input( + self.hass, user_input[CONF_HOST], user_input[CONF_API_VERSION] + ) + except ConnectionFailure as exc: + LOGGER.error(str(exc)) errors["base"] = "cannot_connect" except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") + LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: - await self.async_set_unique_id(system["serialnumber"]) - self._abort_if_unique_id_configured(updates=user_input) - data = {**user_input, "system": system} + await self.async_set_unique_id(hub.system["serialnumber"]) + self._abort_if_unique_id_configured() - return self.async_create_entry( - title=f"{system['name']} ({system['serialnumber']})", data=data - ) + self._current[CONF_SYSTEM] = hub.system + self._current[CONF_API_VERSION] = hub.api_version + self._hub = hub + + if hub.pairing_type == "digest_auth_pairing": + return await self.async_step_pair() + return await self._async_create_current() schema = vol.Schema( { - vol.Required(CONF_HOST, default=self._default.get(CONF_HOST)): str, + vol.Required(CONF_HOST, default=self._current.get(CONF_HOST)): str, vol.Required( - CONF_API_VERSION, default=self._default.get(CONF_API_VERSION) - ): vol.In([1, 6]), + CONF_API_VERSION, default=self._current.get(CONF_API_VERSION, 1) + ): vol.In([1, 5, 6]), } ) return self.async_show_form(step_id="user", data_schema=schema, errors=errors) diff --git a/homeassistant/components/philips_js/const.py b/homeassistant/components/philips_js/const.py index 893766b0083..5769a8979ce 100644 --- a/homeassistant/components/philips_js/const.py +++ b/homeassistant/components/philips_js/const.py @@ -2,3 +2,6 @@ DOMAIN = "philips_js" CONF_SYSTEM = "system" + +CONST_APP_ID = "homeassistant.io" +CONST_APP_NAME = "Home Assistant" diff --git a/homeassistant/components/philips_js/manifest.json b/homeassistant/components/philips_js/manifest.json index e41aa348732..e1e1fa69b6b 100644 --- a/homeassistant/components/philips_js/manifest.json +++ b/homeassistant/components/philips_js/manifest.json @@ -3,7 +3,7 @@ "name": "Philips TV", "documentation": "https://www.home-assistant.io/integrations/philips_js", "requirements": [ - "ha-philipsjs==0.1.0" + "ha-philipsjs==2.3.0" ], "codeowners": [ "@elupus" diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index 20ef6ed9c0f..2b2714b20ce 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -1,6 +1,7 @@ """Media Player component to integrate TVs exposing the Joint Space API.""" -from typing import Any, Dict +from typing import Any, Dict, Optional +from haphilipsjs import ConnectionFailure import voluptuous as vol from homeassistant import config_entries @@ -11,15 +12,21 @@ from homeassistant.components.media_player import ( MediaPlayerEntity, ) from homeassistant.components.media_player.const import ( + MEDIA_CLASS_APP, MEDIA_CLASS_CHANNEL, MEDIA_CLASS_DIRECTORY, + MEDIA_TYPE_APP, + MEDIA_TYPE_APPS, MEDIA_TYPE_CHANNEL, MEDIA_TYPE_CHANNELS, SUPPORT_BROWSE_MEDIA, SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, + SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, @@ -27,7 +34,6 @@ from homeassistant.components.media_player.const import ( SUPPORT_VOLUME_STEP, ) from homeassistant.components.media_player.errors import BrowseError -from homeassistant.components.philips_js import PhilipsTVDataUpdateCoordinator from homeassistant.const import ( CONF_API_VERSION, CONF_HOST, @@ -40,7 +46,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import LOGGER as _LOGGER +from . import LOGGER as _LOGGER, PhilipsTVDataUpdateCoordinator from .const import CONF_SYSTEM, DOMAIN SUPPORT_PHILIPS_JS = ( @@ -53,16 +59,15 @@ SUPPORT_PHILIPS_JS = ( | SUPPORT_PREVIOUS_TRACK | SUPPORT_PLAY_MEDIA | SUPPORT_BROWSE_MEDIA + | SUPPORT_PLAY + | SUPPORT_PAUSE + | SUPPORT_STOP ) CONF_ON_ACTION = "turn_on_action" DEFAULT_API_VERSION = 1 -PREFIX_SEPARATOR = ": " -PREFIX_SOURCE = "Input" -PREFIX_CHANNEL = "Channel" - PLATFORM_SCHEMA = vol.All( cv.deprecated(CONF_HOST), cv.deprecated(CONF_NAME), @@ -131,12 +136,19 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): self._supports = SUPPORT_PHILIPS_JS self._system = system self._unique_id = unique_id + self._state = STATE_OFF + self._media_content_type: Optional[str] = None + self._media_content_id: Optional[str] = None + self._media_title: Optional[str] = None + self._media_channel: Optional[str] = None + super().__init__(coordinator) self._update_from_coordinator() - def _update_soon(self): + async def _async_update_soon(self): """Reschedule update task.""" - self.hass.add_job(self.coordinator.async_request_refresh) + self.async_write_ha_state() + await self.coordinator.async_request_refresh() @property def name(self): @@ -147,7 +159,9 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): def supported_features(self): """Flag media player features that are supported.""" supports = self._supports - if self._coordinator.turn_on: + if self._coordinator.turn_on or ( + self._tv.on and self._tv.powerstate is not None + ): supports |= SUPPORT_TURN_ON return supports @@ -155,7 +169,8 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): def state(self): """Get the device state. An exception means OFF state.""" if self._tv.on: - return STATE_ON + if self._tv.powerstate == "On" or self._tv.powerstate is None: + return STATE_ON return STATE_OFF @property @@ -168,22 +183,12 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): """List of available input sources.""" return list(self._sources.values()) - def select_source(self, source): + async def async_select_source(self, source): """Set the input source.""" - data = source.split(PREFIX_SEPARATOR, 1) - if data[0] == PREFIX_SOURCE: # Legacy way to set source - source_id = _inverted(self._sources).get(data[1]) - if source_id: - self._tv.setSource(source_id) - elif data[0] == PREFIX_CHANNEL: # Legacy way to set channel - channel_id = _inverted(self._channels).get(data[1]) - if channel_id: - self._tv.setChannel(channel_id) - else: - source_id = _inverted(self._sources).get(source) - if source_id: - self._tv.setSource(source_id) - self._update_soon() + source_id = _inverted(self._sources).get(source) + if source_id: + await self._tv.setSource(source_id) + await self._async_update_soon() @property def volume_level(self): @@ -197,78 +202,118 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): async def async_turn_on(self): """Turn on the device.""" - await self._coordinator.turn_on.async_run(self.hass, self._context) + if self._tv.on and self._tv.powerstate: + await self._tv.setPowerState("On") + self._state = STATE_ON + else: + await self._coordinator.turn_on.async_run(self.hass, self._context) + await self._async_update_soon() - def turn_off(self): + async def async_turn_off(self): """Turn off the device.""" - self._tv.sendKey("Standby") - self._tv.on = False - self._update_soon() + await self._tv.sendKey("Standby") + self._state = STATE_OFF + await self._async_update_soon() - def volume_up(self): + async def async_volume_up(self): """Send volume up command.""" - self._tv.sendKey("VolumeUp") - self._update_soon() + await self._tv.sendKey("VolumeUp") + await self._async_update_soon() - def volume_down(self): + async def async_volume_down(self): """Send volume down command.""" - self._tv.sendKey("VolumeDown") - self._update_soon() + await self._tv.sendKey("VolumeDown") + await self._async_update_soon() - def mute_volume(self, mute): + async def async_mute_volume(self, mute): """Send mute command.""" - self._tv.setVolume(None, mute) - self._update_soon() + if self._tv.muted != mute: + await self._tv.sendKey("Mute") + await self._async_update_soon() + else: + _LOGGER.debug("Ignoring request when already in expected state") - def set_volume_level(self, volume): + async def async_set_volume_level(self, volume): """Set volume level, range 0..1.""" - self._tv.setVolume(volume, self._tv.muted) - self._update_soon() + await self._tv.setVolume(volume, self._tv.muted) + await self._async_update_soon() - def media_previous_track(self): + async def async_media_previous_track(self): """Send rewind command.""" - self._tv.sendKey("Previous") - self._update_soon() + await self._tv.sendKey("Previous") + await self._async_update_soon() - def media_next_track(self): + async def async_media_next_track(self): """Send fast forward command.""" - self._tv.sendKey("Next") - self._update_soon() + await self._tv.sendKey("Next") + await self._async_update_soon() + + async def async_media_play_pause(self): + """Send pause command to media player.""" + if self._tv.quirk_playpause_spacebar: + await self._tv.sendUnicode(" ") + else: + await self._tv.sendKey("PlayPause") + await self._async_update_soon() + + async def async_media_play(self): + """Send pause command to media player.""" + await self._tv.sendKey("Play") + await self._async_update_soon() + + async def async_media_pause(self): + """Send play command to media player.""" + await self._tv.sendKey("Pause") + await self._async_update_soon() + + async def async_media_stop(self): + """Send play command to media player.""" + await self._tv.sendKey("Stop") + await self._async_update_soon() @property def media_channel(self): """Get current channel if it's a channel.""" - if self.media_content_type == MEDIA_TYPE_CHANNEL: - return self._channels.get(self._tv.channel_id) - return None + return self._media_channel @property def media_title(self): """Title of current playing media.""" - if self.media_content_type == MEDIA_TYPE_CHANNEL: - return self._channels.get(self._tv.channel_id) - return self._sources.get(self._tv.source_id) + return self._media_title @property def media_content_type(self): """Return content type of playing media.""" - if self._tv.source_id == "tv" or self._tv.source_id == "11": - return MEDIA_TYPE_CHANNEL - if self._tv.source_id is None and self._tv.channels: - return MEDIA_TYPE_CHANNEL - return None + return self._media_content_type @property def media_content_id(self): """Content type of current playing media.""" - if self.media_content_type == MEDIA_TYPE_CHANNEL: - return self._channels.get(self._tv.channel_id) + return self._media_content_id + + @property + def media_image_url(self): + """Image url of current playing media.""" + if self._media_content_id and self._media_content_type in ( + MEDIA_CLASS_APP, + MEDIA_CLASS_CHANNEL, + ): + return self.get_browse_image_url( + self._media_content_type, self._media_content_id, media_image_id=None + ) return None @property - def device_state_attributes(self): - """Return the state attributes.""" - return {"channel_list": list(self._channels.values())} + def app_id(self): + """ID of the current running app.""" + return self._tv.application_id + + @property + def app_name(self): + """Name of the current running app.""" + app = self._tv.applications.get(self._tv.application_id) + if app: + return app.get("label") @property def device_class(self): @@ -293,57 +338,243 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): "sw_version": self._system.get("softwareversion"), } - def play_media(self, media_type, media_id, **kwargs): + async def async_play_media(self, media_type, media_id, **kwargs): """Play a piece of media.""" _LOGGER.debug("Call play media type <%s>, Id <%s>", media_type, media_id) if media_type == MEDIA_TYPE_CHANNEL: - channel_id = _inverted(self._channels).get(media_id) + list_id, _, channel_id = media_id.partition("/") if channel_id: - self._tv.setChannel(channel_id) - self._update_soon() + await self._tv.setChannel(channel_id, list_id) + await self._async_update_soon() else: _LOGGER.error("Unable to find channel <%s>", media_id) + elif media_type == MEDIA_TYPE_APP: + app = self._tv.applications.get(media_id) + if app: + await self._tv.setApplication(app["intent"]) + await self._async_update_soon() + else: + _LOGGER.error("Unable to find application <%s>", media_id) else: _LOGGER.error("Unsupported media type <%s>", media_type) - async def async_browse_media(self, media_content_type=None, media_content_id=None): - """Implement the websocket media browsing helper.""" - if media_content_id not in (None, ""): - raise BrowseError( - f"Media not found: {media_content_type} / {media_content_id}" - ) + async def async_browse_media_channels(self, expanded): + """Return channel media objects.""" + if expanded: + children = [ + BrowseMedia( + title=channel.get("name", f"Channel: {channel_id}"), + media_class=MEDIA_CLASS_CHANNEL, + media_content_id=f"alltv/{channel_id}", + media_content_type=MEDIA_TYPE_CHANNEL, + can_play=True, + can_expand=False, + thumbnail=self.get_browse_image_url( + MEDIA_TYPE_APP, channel_id, media_image_id=None + ), + ) + for channel_id, channel in self._tv.channels.items() + ] + else: + children = None return BrowseMedia( title="Channels", media_class=MEDIA_CLASS_DIRECTORY, - media_content_id="", + media_content_id="channels", media_content_type=MEDIA_TYPE_CHANNELS, + children_media_class=MEDIA_TYPE_CHANNEL, + can_play=False, + can_expand=True, + children=children, + ) + + async def async_browse_media_favorites(self, list_id, expanded): + """Return channel media objects.""" + if expanded: + favorites = await self._tv.getFavoriteList(list_id) + if favorites: + + def get_name(channel): + channel_data = self._tv.channels.get(str(channel["ccid"])) + if channel_data: + return channel_data["name"] + return f"Channel: {channel['ccid']}" + + children = [ + BrowseMedia( + title=get_name(channel), + media_class=MEDIA_CLASS_CHANNEL, + media_content_id=f"{list_id}/{channel['ccid']}", + media_content_type=MEDIA_TYPE_CHANNEL, + can_play=True, + can_expand=False, + thumbnail=self.get_browse_image_url( + MEDIA_TYPE_APP, channel, media_image_id=None + ), + ) + for channel in favorites + ] + else: + children = None + else: + children = None + + favorite = self._tv.favorite_lists[list_id] + return BrowseMedia( + title=favorite.get("name", f"Favorites {list_id}"), + media_class=MEDIA_CLASS_DIRECTORY, + media_content_id=f"favorites/{list_id}", + media_content_type=MEDIA_TYPE_CHANNELS, + children_media_class=MEDIA_TYPE_CHANNEL, + can_play=False, + can_expand=True, + children=children, + ) + + async def async_browse_media_applications(self, expanded): + """Return application media objects.""" + if expanded: + children = [ + BrowseMedia( + title=application["label"], + media_class=MEDIA_CLASS_APP, + media_content_id=application_id, + media_content_type=MEDIA_TYPE_APP, + can_play=True, + can_expand=False, + thumbnail=self.get_browse_image_url( + MEDIA_TYPE_APP, application_id, media_image_id=None + ), + ) + for application_id, application in self._tv.applications.items() + ] + else: + children = None + + return BrowseMedia( + title="Applications", + media_class=MEDIA_CLASS_DIRECTORY, + media_content_id="applications", + media_content_type=MEDIA_TYPE_APPS, + children_media_class=MEDIA_TYPE_APP, + can_play=False, + can_expand=True, + children=children, + ) + + async def async_browse_media_favorite_lists(self, expanded): + """Return favorite media objects.""" + if self._tv.favorite_lists and expanded: + children = [ + await self.async_browse_media_favorites(list_id, False) + for list_id in self._tv.favorite_lists + ] + else: + children = None + + return BrowseMedia( + title="Favorites", + media_class=MEDIA_CLASS_DIRECTORY, + media_content_id="favorite_lists", + media_content_type=MEDIA_TYPE_CHANNELS, + children_media_class=MEDIA_TYPE_CHANNEL, + can_play=False, + can_expand=True, + children=children, + ) + + async def async_browse_media_root(self): + """Return root media objects.""" + + return BrowseMedia( + title="Library", + media_class=MEDIA_CLASS_DIRECTORY, + media_content_id="", + media_content_type="", can_play=False, can_expand=True, children=[ - BrowseMedia( - title=channel, - media_class=MEDIA_CLASS_CHANNEL, - media_content_id=channel, - media_content_type=MEDIA_TYPE_CHANNEL, - can_play=True, - can_expand=False, - ) - for channel in self._channels.values() + await self.async_browse_media_channels(False), + await self.async_browse_media_applications(False), + await self.async_browse_media_favorite_lists(False), ], ) + async def async_browse_media(self, media_content_type=None, media_content_id=None): + """Implement the websocket media browsing helper.""" + if not self._tv.on: + raise BrowseError("Can't browse when tv is turned off") + + if media_content_id in (None, ""): + return await self.async_browse_media_root() + path = media_content_id.partition("/") + if path[0] == "channels": + return await self.async_browse_media_channels(True) + if path[0] == "applications": + return await self.async_browse_media_applications(True) + if path[0] == "favorite_lists": + return await self.async_browse_media_favorite_lists(True) + if path[0] == "favorites": + return await self.async_browse_media_favorites(path[2], True) + + raise BrowseError(f"Media not found: {media_content_type} / {media_content_id}") + + async def async_get_browse_image( + self, media_content_type, media_content_id, media_image_id=None + ): + """Serve album art. Returns (content, content_type).""" + try: + if media_content_type == MEDIA_TYPE_APP and media_content_id: + return await self._tv.getApplicationIcon(media_content_id) + if media_content_type == MEDIA_TYPE_CHANNEL and media_content_id: + return await self._tv.getChannelLogo(media_content_id) + except ConnectionFailure: + _LOGGER.warning("Failed to fetch image") + return None, None + + async def async_get_media_image(self): + """Serve album art. Returns (content, content_type).""" + return await self.async_get_browse_image( + self.media_content_type, self.media_content_id, None + ) + + @callback def _update_from_coordinator(self): + + if self._tv.on: + if self._tv.powerstate in ("Standby", "StandbyKeep"): + self._state = STATE_OFF + else: + self._state = STATE_ON + else: + self._state = STATE_OFF + self._sources = { srcid: source.get("name") or f"Source {srcid}" for srcid, source in (self._tv.sources or {}).items() } - self._channels = { - chid: channel.get("name") or f"Channel {chid}" - for chid, channel in (self._tv.channels or {}).items() - } + if self._tv.channel_active: + self._media_content_type = MEDIA_TYPE_CHANNEL + self._media_content_id = f"all/{self._tv.channel_id}" + self._media_title = self._tv.channels.get(self._tv.channel_id, {}).get( + "name" + ) + self._media_channel = self._media_title + elif self._tv.application_id: + self._media_content_type = MEDIA_TYPE_APP + self._media_content_id = self._tv.application_id + self._media_title = self._tv.applications.get( + self._tv.application_id, {} + ).get("label") + self._media_channel = None + else: + self._media_content_type = None + self._media_content_id = None + self._media_title = self._sources.get(self._tv.source_id) + self._media_channel = None @callback def _handle_coordinator_update(self) -> None: diff --git a/homeassistant/components/philips_js/strings.json b/homeassistant/components/philips_js/strings.json index 2267315501f..df65d453f2b 100644 --- a/homeassistant/components/philips_js/strings.json +++ b/homeassistant/components/philips_js/strings.json @@ -10,8 +10,10 @@ }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "unknown": "[%key:common::config_flow::error::unknown%]" - }, + "unknown": "[%key:common::config_flow::error::unknown%]", + "pairing_failure": "Unable to pair: {error_id}", + "invalid_pin": "Invalid PIN" +}, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } diff --git a/homeassistant/components/philips_js/translations/en.json b/homeassistant/components/philips_js/translations/en.json index 249fe5a892d..b2022a01824 100644 --- a/homeassistant/components/philips_js/translations/en.json +++ b/homeassistant/components/philips_js/translations/en.json @@ -5,7 +5,9 @@ }, "error": { "cannot_connect": "Failed to connect", - "unknown": "Unexpected error" + "unknown": "Unexpected error", + "pairing_failure": "Unable to pair: {error_id}", + "invalid_pin": "Invalid PIN" }, "step": { "user": { diff --git a/requirements_all.txt b/requirements_all.txt index 2353797b8b8..482e0aeaa87 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -721,7 +721,7 @@ guppy3==3.1.0 ha-ffmpeg==3.0.2 # homeassistant.components.philips_js -ha-philipsjs==0.1.0 +ha-philipsjs==2.3.0 # homeassistant.components.habitica habitipy==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b6cfca171e2..e394c54ebbf 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -382,7 +382,7 @@ guppy3==3.1.0 ha-ffmpeg==3.0.2 # homeassistant.components.philips_js -ha-philipsjs==0.1.0 +ha-philipsjs==2.3.0 # homeassistant.components.habitica habitipy==0.2.0 diff --git a/tests/components/philips_js/__init__.py b/tests/components/philips_js/__init__.py index 1c96a6d4e55..9dea390a600 100644 --- a/tests/components/philips_js/__init__.py +++ b/tests/components/philips_js/__init__.py @@ -3,6 +3,9 @@ MOCK_SERIAL_NO = "1234567890" MOCK_NAME = "Philips TV" +MOCK_USERNAME = "mock_user" +MOCK_PASSWORD = "mock_password" + MOCK_SYSTEM = { "menulanguage": "English", "name": MOCK_NAME, @@ -12,14 +15,63 @@ MOCK_SYSTEM = { "model": "modelname", } -MOCK_USERINPUT = { - "host": "1.1.1.1", - "api_version": 1, +MOCK_SYSTEM_UNPAIRED = { + "menulanguage": "Dutch", + "name": "55PUS7181/12", + "country": "Netherlands", + "serialnumber": "ABCDEFGHIJKLF", + "softwareversion": "TPM191E_R.101.001.208.001", + "model": "65OLED855/12", + "deviceid": "1234567890", + "nettvversion": "6.0.2", + "epgsource": "one", + "api_version": {"Major": 6, "Minor": 2, "Patch": 0}, + "featuring": { + "jsonfeatures": { + "editfavorites": ["TVChannels", "SatChannels"], + "recordings": ["List", "Schedule", "Manage"], + "ambilight": ["LoungeLight", "Hue", "Ambilight"], + "menuitems": ["Setup_Menu"], + "textentry": [ + "context_based", + "initial_string_available", + "editor_info_available", + ], + "applications": ["TV_Apps", "TV_Games", "TV_Settings"], + "pointer": ["not_available"], + "inputkey": ["key"], + "activities": ["intent"], + "channels": ["preset_string"], + "mappings": ["server_mapping"], + }, + "systemfeatures": { + "tvtype": "consumer", + "content": ["dmr", "dms_tad"], + "tvsearch": "intent", + "pairing_type": "digest_auth_pairing", + "secured_transport": "True", + }, + }, } +MOCK_USERINPUT = { + "host": "1.1.1.1", +} + +MOCK_IMPORT = {"host": "1.1.1.1", "api_version": 6} + MOCK_CONFIG = { - **MOCK_USERINPUT, + "host": "1.1.1.1", + "api_version": 1, "system": MOCK_SYSTEM, } +MOCK_CONFIG_PAIRED = { + "host": "1.1.1.1", + "api_version": 6, + "username": MOCK_USERNAME, + "password": MOCK_PASSWORD, + "system": MOCK_SYSTEM_UNPAIRED, +} + MOCK_ENTITY_ID = "media_player.philips_tv" diff --git a/tests/components/philips_js/conftest.py b/tests/components/philips_js/conftest.py index 549ad77fb06..4b6150f9f81 100644 --- a/tests/components/philips_js/conftest.py +++ b/tests/components/philips_js/conftest.py @@ -1,6 +1,7 @@ """Standard setup for tests.""" -from unittest.mock import Mock, patch +from unittest.mock import create_autospec, patch +from haphilipsjs import PhilipsTV from pytest import fixture from homeassistant import setup @@ -20,10 +21,18 @@ async def setup_notification(hass): @fixture(autouse=True) def mock_tv(): """Disable component actual use.""" - tv = Mock(autospec="philips_js.PhilipsTV") + tv = create_autospec(PhilipsTV) tv.sources = {} tv.channels = {} + tv.application = None + tv.applications = {} tv.system = MOCK_SYSTEM + tv.api_version = 1 + tv.api_version_detected = None + tv.on = True + tv.notify_change_supported = False + tv.pairing_type = None + tv.powerstate = None with patch( "homeassistant.components.philips_js.config_flow.PhilipsTV", return_value=tv diff --git a/tests/components/philips_js/test_config_flow.py b/tests/components/philips_js/test_config_flow.py index 75caff78891..45e896319f1 100644 --- a/tests/components/philips_js/test_config_flow.py +++ b/tests/components/philips_js/test_config_flow.py @@ -1,12 +1,21 @@ """Test the Philips TV config flow.""" -from unittest.mock import patch +from unittest.mock import ANY, patch +from haphilipsjs import PairingFailure from pytest import fixture from homeassistant import config_entries from homeassistant.components.philips_js.const import DOMAIN -from . import MOCK_CONFIG, MOCK_USERINPUT +from . import ( + MOCK_CONFIG, + MOCK_CONFIG_PAIRED, + MOCK_IMPORT, + MOCK_PASSWORD, + MOCK_SYSTEM_UNPAIRED, + MOCK_USERINPUT, + MOCK_USERNAME, +) @fixture(autouse=True) @@ -27,12 +36,26 @@ def mock_setup_entry(): yield mock_setup_entry +@fixture +async def mock_tv_pairable(mock_tv): + """Return a mock tv that is pariable.""" + mock_tv.system = MOCK_SYSTEM_UNPAIRED + mock_tv.pairing_type = "digest_auth_pairing" + mock_tv.api_version = 6 + mock_tv.api_version_detected = 6 + mock_tv.secured_transport = True + + mock_tv.pairRequest.return_value = {} + mock_tv.pairGrant.return_value = MOCK_USERNAME, MOCK_PASSWORD + return mock_tv + + async def test_import(hass, mock_setup, mock_setup_entry): """Test we get an item on import.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data=MOCK_USERINPUT, + data=MOCK_IMPORT, ) assert result["type"] == "create_entry" @@ -47,7 +70,7 @@ async def test_import_exist(hass, mock_config_entry): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data=MOCK_USERINPUT, + data=MOCK_IMPORT, ) assert result["type"] == "abort" @@ -103,3 +126,116 @@ async def test_form_unexpected_error(hass, mock_tv): assert result["type"] == "form" assert result["errors"] == {"base": "unknown"} + + +async def test_pairing(hass, mock_tv_pairable, mock_setup, mock_setup_entry): + """Test we get the form.""" + mock_tv = mock_tv_pairable + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + MOCK_USERINPUT, + ) + + assert result["type"] == "form" + assert result["errors"] == {} + + mock_tv.setTransport.assert_called_with(True) + mock_tv.pairRequest.assert_called() + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"pin": "1234"} + ) + + assert result == { + "flow_id": ANY, + "type": "create_entry", + "description": None, + "description_placeholders": None, + "handler": "philips_js", + "result": ANY, + "title": "55PUS7181/12 (ABCDEFGHIJKLF)", + "data": MOCK_CONFIG_PAIRED, + "version": 1, + } + + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_pair_request_failed( + hass, mock_tv_pairable, mock_setup, mock_setup_entry +): + """Test we get the form.""" + mock_tv = mock_tv_pairable + mock_tv.pairRequest.side_effect = PairingFailure({}) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + MOCK_USERINPUT, + ) + + assert result == { + "flow_id": ANY, + "description_placeholders": {"error_id": None}, + "handler": "philips_js", + "reason": "pairing_failure", + "type": "abort", + } + + +async def test_pair_grant_failed(hass, mock_tv_pairable, mock_setup, mock_setup_entry): + """Test we get the form.""" + mock_tv = mock_tv_pairable + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + MOCK_USERINPUT, + ) + assert result["type"] == "form" + assert result["errors"] == {} + + mock_tv.setTransport.assert_called_with(True) + mock_tv.pairRequest.assert_called() + + # Test with invalid pin + mock_tv.pairGrant.side_effect = PairingFailure({"error_id": "INVALID_PIN"}) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"pin": "1234"} + ) + + assert result["type"] == "form" + assert result["errors"] == {"pin": "invalid_pin"} + + # Test with unexpected failure + mock_tv.pairGrant.side_effect = PairingFailure({}) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"pin": "1234"} + ) + + assert result == { + "flow_id": ANY, + "description_placeholders": {"error_id": None}, + "handler": "philips_js", + "reason": "pairing_failure", + "type": "abort", + } diff --git a/tests/components/philips_js/test_device_trigger.py b/tests/components/philips_js/test_device_trigger.py index 43c7c424cf9..ebda40f13e5 100644 --- a/tests/components/philips_js/test_device_trigger.py +++ b/tests/components/philips_js/test_device_trigger.py @@ -33,9 +33,13 @@ async def test_get_triggers(hass, mock_device): assert_lists_same(triggers, expected_triggers) -async def test_if_fires_on_turn_on_request(hass, calls, mock_entity, mock_device): +async def test_if_fires_on_turn_on_request( + hass, calls, mock_tv, mock_entity, mock_device +): """Test for turn_on and turn_off triggers firing.""" + mock_tv.on = False + assert await async_setup_component( hass, automation.DOMAIN, From 8971ab2edccb604c2ef46b53280c5db0245e373e Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Fri, 26 Feb 2021 20:07:53 +0100 Subject: [PATCH 043/831] Bump aioshelly to 0.6.1 (#47088) --- homeassistant/components/shelly/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index c38869b3e0d..a757947c5cf 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -3,7 +3,7 @@ "name": "Shelly", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/shelly", - "requirements": ["aioshelly==0.6.0"], + "requirements": ["aioshelly==0.6.1"], "zeroconf": [{ "type": "_http._tcp.local.", "name": "shelly*" }], "codeowners": ["@balloob", "@bieniu", "@thecode", "@chemelli74"] } diff --git a/requirements_all.txt b/requirements_all.txt index 482e0aeaa87..ea49c7b232e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -221,7 +221,7 @@ aiopylgtv==0.3.3 aiorecollect==1.0.1 # homeassistant.components.shelly -aioshelly==0.6.0 +aioshelly==0.6.1 # homeassistant.components.switcher_kis aioswitcher==1.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e394c54ebbf..bf56c1699d4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -140,7 +140,7 @@ aiopylgtv==0.3.3 aiorecollect==1.0.1 # homeassistant.components.shelly -aioshelly==0.6.0 +aioshelly==0.6.1 # homeassistant.components.switcher_kis aioswitcher==1.2.1 From 7ca148f65ddd27f13a4b4dcdac907fd9e9479721 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Fri, 26 Feb 2021 20:19:23 +0100 Subject: [PATCH 044/831] Fix Z-Wave JS discovery schema for thermostat devices (#47087) Co-authored-by: Martin Hjelmare --- .../components/zwave_js/discovery.py | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index a40eb10de8b..f5f3d9e5c5b 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -75,6 +75,8 @@ class ZWaveDiscoverySchema: device_class_specific: Optional[Set[Union[str, int]]] = None # [optional] additional values that ALL need to be present on the node for this scheme to pass required_values: Optional[List[ZWaveValueDiscoverySchema]] = None + # [optional] additional values that MAY NOT be present on the node for this scheme to pass + absent_values: Optional[List[ZWaveValueDiscoverySchema]] = None # [optional] bool to specify if this primary value may be discovered by multiple platforms allow_multi: bool = False @@ -186,36 +188,30 @@ DISCOVERY_SCHEMAS = [ ), ), # climate + # thermostats supporting mode (and optional setpoint) ZWaveDiscoverySchema( platform="climate", - device_class_generic={"Thermostat"}, - device_class_specific={ - "Setback Thermostat", - "Thermostat General", - "Thermostat General V2", - "General Thermostat", - "General Thermostat V2", - }, primary_value=ZWaveValueDiscoverySchema( command_class={CommandClass.THERMOSTAT_MODE}, property={"mode"}, type={"number"}, ), ), - # climate - # setpoint thermostats + # thermostats supporting setpoint only (and thus not mode) ZWaveDiscoverySchema( platform="climate", - device_class_generic={"Thermostat"}, - device_class_specific={ - "Setpoint Thermostat", - "Unused", - }, primary_value=ZWaveValueDiscoverySchema( command_class={CommandClass.THERMOSTAT_SETPOINT}, property={"setpoint"}, type={"number"}, ), + absent_values=[ # mode must not be present to prevent dupes + ZWaveValueDiscoverySchema( + command_class={CommandClass.THERMOSTAT_MODE}, + property={"mode"}, + type={"number"}, + ), + ], ), # binary sensors ZWaveDiscoverySchema( @@ -436,6 +432,13 @@ def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None for val_scheme in schema.required_values ): continue + # check for values that may not be present + if schema.absent_values is not None: + if any( + any(check_value(val, val_scheme) for val in node.values.values()) + for val_scheme in schema.absent_values + ): + continue # all checks passed, this value belongs to an entity yield ZwaveDiscoveryInfo( node=value.node, From b1898cc1767ef20bb2e17bd931e8ec52e57fa1fc Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Fri, 26 Feb 2021 11:20:32 -0800 Subject: [PATCH 045/831] Bump google-nest-sdm to v0.2.12 to improve API call error messages (#47108) --- homeassistant/components/nest/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nest/manifest.json b/homeassistant/components/nest/manifest.json index c68dbe6ee2f..734261d9b08 100644 --- a/homeassistant/components/nest/manifest.json +++ b/homeassistant/components/nest/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "dependencies": ["ffmpeg", "http"], "documentation": "https://www.home-assistant.io/integrations/nest", - "requirements": ["python-nest==4.1.0", "google-nest-sdm==0.2.10"], + "requirements": ["python-nest==4.1.0", "google-nest-sdm==0.2.12"], "codeowners": ["@allenporter"], "quality_scale": "platinum", "dhcp": [{"macaddress":"18B430*"}] diff --git a/requirements_all.txt b/requirements_all.txt index ea49c7b232e..a2f3a6e45c8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -682,7 +682,7 @@ google-cloud-pubsub==2.1.0 google-cloud-texttospeech==0.4.0 # homeassistant.components.nest -google-nest-sdm==0.2.10 +google-nest-sdm==0.2.12 # homeassistant.components.google_travel_time googlemaps==2.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bf56c1699d4..e5b4a3d04b1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -367,7 +367,7 @@ google-api-python-client==1.6.4 google-cloud-pubsub==2.1.0 # homeassistant.components.nest -google-nest-sdm==0.2.10 +google-nest-sdm==0.2.12 # homeassistant.components.gree greeclimate==0.10.3 From 7c2545af6e874951ce9ef9262cef1de3399443a9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Feb 2021 13:28:52 -0800 Subject: [PATCH 046/831] Use async_capture_events to avoid running in executor (#47111) --- tests/components/alexa/test_smart_home.py | 10 ++----- tests/components/automation/test_init.py | 12 ++++---- tests/components/demo/test_notify.py | 6 ++-- .../google_assistant/test_smart_home.py | 29 +++++++++---------- .../components/google_assistant/test_trait.py | 5 ++-- tests/components/homeassistant/test_scene.py | 7 ++--- tests/components/homekit/conftest.py | 9 ++---- tests/components/shelly/conftest.py | 12 ++++---- 8 files changed, 39 insertions(+), 51 deletions(-) diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 657bc407fb0..c018e07c264 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -26,7 +26,7 @@ from homeassistant.components.media_player.const import ( import homeassistant.components.vacuum as vacuum from homeassistant.config import async_process_ha_core_config from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT -from homeassistant.core import Context, callback +from homeassistant.core import Context from homeassistant.helpers import entityfilter from homeassistant.setup import async_setup_component @@ -42,17 +42,13 @@ from . import ( reported_properties, ) -from tests.common import async_mock_service +from tests.common import async_capture_events, async_mock_service @pytest.fixture def events(hass): """Fixture that catches alexa events.""" - events = [] - hass.bus.async_listen( - smart_home.EVENT_ALEXA_SMART_HOME, callback(lambda e: events.append(e)) - ) - yield events + return async_capture_events(hass, smart_home.EVENT_ALEXA_SMART_HOME) @pytest.fixture diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 3e498b52a08..91531481a99 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -30,7 +30,12 @@ from homeassistant.exceptions import HomeAssistantError, Unauthorized from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from tests.common import assert_setup_component, async_mock_service, mock_restore_cache +from tests.common import ( + assert_setup_component, + async_capture_events, + async_mock_service, + mock_restore_cache, +) from tests.components.logbook.test_init import MockLazyEventPartialState @@ -496,10 +501,7 @@ async def test_reload_config_service(hass, calls, hass_admin_user, hass_read_onl assert len(calls) == 1 assert calls[0].data.get("event") == "test_event" - test_reload_event = [] - hass.bus.async_listen( - EVENT_AUTOMATION_RELOADED, lambda event: test_reload_event.append(event) - ) + test_reload_event = async_capture_events(hass, EVENT_AUTOMATION_RELOADED) with patch( "homeassistant.config.load_yaml_config_file", diff --git a/tests/components/demo/test_notify.py b/tests/components/demo/test_notify.py index 7c7f83312dd..153f065235c 100644 --- a/tests/components/demo/test_notify.py +++ b/tests/components/demo/test_notify.py @@ -12,7 +12,7 @@ from homeassistant.core import callback from homeassistant.helpers import discovery from homeassistant.setup import async_setup_component -from tests.common import assert_setup_component +from tests.common import assert_setup_component, async_capture_events CONFIG = {notify.DOMAIN: {"platform": "demo"}} @@ -20,9 +20,7 @@ CONFIG = {notify.DOMAIN: {"platform": "demo"}} @pytest.fixture def events(hass): """Fixture that catches notify events.""" - events = [] - hass.bus.async_listen(demo.EVENT_NOTIFY, callback(lambda e: events.append(e))) - yield events + return async_capture_events(hass, demo.EVENT_NOTIFY) @pytest.fixture diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py index 9c8f9a48338..9531602ef0c 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -30,7 +30,12 @@ from homeassistant.setup import async_setup_component from . import BASIC_CONFIG, MockConfig -from tests.common import mock_area_registry, mock_device_registry, mock_registry +from tests.common import ( + async_capture_events, + mock_area_registry, + mock_device_registry, + mock_registry, +) REQ_ID = "ff36a3cc-ec34-11e6-b1a0-64510650abcf" @@ -77,8 +82,7 @@ async def test_sync_message(hass): }, ) - events = [] - hass.bus.async_listen(EVENT_SYNC_RECEIVED, events.append) + events = async_capture_events(hass, EVENT_SYNC_RECEIVED) result = await sh.async_handle_message( hass, @@ -192,8 +196,7 @@ async def test_sync_in_area(area_on_device, hass, registries): config = MockConfig(should_expose=lambda _: True, entity_config={}) - events = [] - hass.bus.async_listen(EVENT_SYNC_RECEIVED, events.append) + events = async_capture_events(hass, EVENT_SYNC_RECEIVED) result = await sh.async_handle_message( hass, @@ -295,8 +298,7 @@ async def test_query_message(hass): light3.entity_id = "light.color_temp_light" await light3.async_update_ha_state() - events = [] - hass.bus.async_listen(EVENT_QUERY_RECEIVED, events.append) + events = async_capture_events(hass, EVENT_QUERY_RECEIVED) result = await sh.async_handle_message( hass, @@ -387,11 +389,8 @@ async def test_execute(hass): "light", "turn_off", {"entity_id": "light.ceiling_lights"}, blocking=True ) - events = [] - hass.bus.async_listen(EVENT_COMMAND_RECEIVED, events.append) - - service_events = [] - hass.bus.async_listen(EVENT_CALL_SERVICE, service_events.append) + events = async_capture_events(hass, EVENT_COMMAND_RECEIVED) + service_events = async_capture_events(hass, EVENT_CALL_SERVICE) result = await sh.async_handle_message( hass, @@ -570,8 +569,7 @@ async def test_raising_error_trait(hass): {ATTR_MIN_TEMP: 15, ATTR_MAX_TEMP: 30, ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}, ) - events = [] - hass.bus.async_listen(EVENT_COMMAND_RECEIVED, events.append) + events = async_capture_events(hass, EVENT_COMMAND_RECEIVED) await hass.async_block_till_done() result = await sh.async_handle_message( @@ -660,8 +658,7 @@ async def test_unavailable_state_does_sync(hass): light._available = False # pylint: disable=protected-access await light.async_update_ha_state() - events = [] - hass.bus.async_listen(EVENT_SYNC_RECEIVED, events.append) + events = async_capture_events(hass, EVENT_SYNC_RECEIVED) result = await sh.async_handle_message( hass, diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index a9b1e9a97fb..ba189020513 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -54,7 +54,7 @@ from homeassistant.util import color from . import BASIC_CONFIG, MockConfig -from tests.common import async_mock_service +from tests.common import async_capture_events, async_mock_service REQ_ID = "ff36a3cc-ec34-11e6-b1a0-64510650abcf" @@ -84,8 +84,7 @@ async def test_brightness_light(hass): assert trt.query_attributes() == {"brightness": 95} - events = [] - hass.bus.async_listen(EVENT_CALL_SERVICE, events.append) + events = async_capture_events(hass, EVENT_CALL_SERVICE) calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON) await trt.execute( diff --git a/tests/components/homeassistant/test_scene.py b/tests/components/homeassistant/test_scene.py index 30985432718..610bc371b25 100644 --- a/tests/components/homeassistant/test_scene.py +++ b/tests/components/homeassistant/test_scene.py @@ -8,17 +8,14 @@ from homeassistant.components.homeassistant import scene as ha_scene from homeassistant.components.homeassistant.scene import EVENT_SCENE_RELOADED from homeassistant.setup import async_setup_component -from tests.common import async_mock_service +from tests.common import async_capture_events, async_mock_service async def test_reload_config_service(hass): """Test the reload config service.""" assert await async_setup_component(hass, "scene", {}) - test_reloaded_event = [] - hass.bus.async_listen( - EVENT_SCENE_RELOADED, lambda event: test_reloaded_event.append(event) - ) + test_reloaded_event = async_capture_events(hass, EVENT_SCENE_RELOADED) with patch( "homeassistant.config.load_yaml_config_file", diff --git a/tests/components/homekit/conftest.py b/tests/components/homekit/conftest.py index ac51c4e6368..228b5f07837 100644 --- a/tests/components/homekit/conftest.py +++ b/tests/components/homekit/conftest.py @@ -5,7 +5,8 @@ from pyhap.accessory_driver import AccessoryDriver import pytest from homeassistant.components.homekit.const import EVENT_HOMEKIT_CHANGED -from homeassistant.core import callback as ha_callback + +from tests.common import async_capture_events @pytest.fixture @@ -24,8 +25,4 @@ def hk_driver(loop): @pytest.fixture def events(hass): """Yield caught homekit_changed events.""" - events = [] - hass.bus.async_listen( - EVENT_HOMEKIT_CHANGED, ha_callback(lambda e: events.append(e)) - ) - yield events + return async_capture_events(hass, EVENT_HOMEKIT_CHANGED) diff --git a/tests/components/shelly/conftest.py b/tests/components/shelly/conftest.py index 7e7bd068842..51659cf7736 100644 --- a/tests/components/shelly/conftest.py +++ b/tests/components/shelly/conftest.py @@ -10,10 +10,14 @@ from homeassistant.components.shelly.const import ( DOMAIN, EVENT_SHELLY_CLICK, ) -from homeassistant.core import callback as ha_callback from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry, async_mock_service, mock_device_registry +from tests.common import ( + MockConfigEntry, + async_capture_events, + async_mock_service, + mock_device_registry, +) MOCK_SETTINGS = { "name": "Test name", @@ -81,9 +85,7 @@ def calls(hass): @pytest.fixture def events(hass): """Yield caught shelly_click events.""" - ha_events = [] - hass.bus.async_listen(EVENT_SHELLY_CLICK, ha_callback(ha_events.append)) - yield ha_events + return async_capture_events(hass, EVENT_SHELLY_CLICK) @pytest.fixture From e9b8e035b42c1c222def6dc4d1d96767fecbb7ad Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sat, 27 Feb 2021 00:28:16 +0200 Subject: [PATCH 047/831] Fix Shelly RGBW (#47116) --- homeassistant/components/shelly/const.py | 4 +- homeassistant/components/shelly/light.py | 47 ++++++++++-------------- 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/shelly/const.py b/homeassistant/components/shelly/const.py index 9d1c333b201..4fda656e7b4 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -74,5 +74,5 @@ INPUTS_EVENTS_SUBTYPES = { # Kelvin value for colorTemp KELVIN_MAX_VALUE = 6500 -KELVIN_MIN_VALUE = 2700 -KELVIN_MIN_VALUE_SHBLB_1 = 3000 +KELVIN_MIN_VALUE_WHITE = 2700 +KELVIN_MIN_VALUE_COLOR = 3000 diff --git a/homeassistant/components/shelly/light.py b/homeassistant/components/shelly/light.py index 848ef990340..0379bfec1cf 100644 --- a/homeassistant/components/shelly/light.py +++ b/homeassistant/components/shelly/light.py @@ -28,20 +28,13 @@ from .const import ( DATA_CONFIG_ENTRY, DOMAIN, KELVIN_MAX_VALUE, - KELVIN_MIN_VALUE, - KELVIN_MIN_VALUE_SHBLB_1, + KELVIN_MIN_VALUE_COLOR, + KELVIN_MIN_VALUE_WHITE, ) from .entity import ShellyBlockEntity from .utils import async_remove_shelly_entity -def min_kelvin(model: str): - """Kelvin (min) for colorTemp.""" - if model in ["SHBLB-1"]: - return KELVIN_MIN_VALUE_SHBLB_1 - return KELVIN_MIN_VALUE - - async def async_setup_entry(hass, config_entry, async_add_entities): """Set up lights for device.""" wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][COAP] @@ -76,6 +69,8 @@ class ShellyLight(ShellyBlockEntity, LightEntity): self.control_result = None self.mode_result = None self._supported_features = 0 + self._min_kelvin = KELVIN_MIN_VALUE_WHITE + self._max_kelvin = KELVIN_MAX_VALUE if hasattr(block, "brightness") or hasattr(block, "gain"): self._supported_features |= SUPPORT_BRIGHTNESS @@ -85,6 +80,7 @@ class ShellyLight(ShellyBlockEntity, LightEntity): self._supported_features |= SUPPORT_WHITE_VALUE if hasattr(block, "red") and hasattr(block, "green") and hasattr(block, "blue"): self._supported_features |= SUPPORT_COLOR + self._min_kelvin = KELVIN_MIN_VALUE_COLOR @property def supported_features(self) -> int: @@ -168,22 +164,19 @@ class ShellyLight(ShellyBlockEntity, LightEntity): else: color_temp = self.block.colorTemp - # If you set DUO to max mireds in Shelly app, 2700K, - # It reports 0 temp - if color_temp == 0: - return min_kelvin(self.wrapper.model) + color_temp = min(self._max_kelvin, max(self._min_kelvin, color_temp)) return int(color_temperature_kelvin_to_mired(color_temp)) @property def min_mireds(self) -> int: """Return the coldest color_temp that this light supports.""" - return int(color_temperature_kelvin_to_mired(KELVIN_MAX_VALUE)) + return int(color_temperature_kelvin_to_mired(self._max_kelvin)) @property def max_mireds(self) -> int: """Return the warmest color_temp that this light supports.""" - return int(color_temperature_kelvin_to_mired(min_kelvin(self.wrapper.model))) + return int(color_temperature_kelvin_to_mired(self._min_kelvin)) async def async_turn_on(self, **kwargs) -> None: """Turn on light.""" @@ -192,6 +185,7 @@ class ShellyLight(ShellyBlockEntity, LightEntity): self.async_write_ha_state() return + set_mode = None params = {"turn": "on"} if ATTR_BRIGHTNESS in kwargs: tmp_brightness = int(kwargs[ATTR_BRIGHTNESS] / 255 * 100) @@ -201,27 +195,26 @@ class ShellyLight(ShellyBlockEntity, LightEntity): params["brightness"] = tmp_brightness if ATTR_COLOR_TEMP in kwargs: color_temp = color_temperature_mired_to_kelvin(kwargs[ATTR_COLOR_TEMP]) - color_temp = min( - KELVIN_MAX_VALUE, max(min_kelvin(self.wrapper.model), color_temp) - ) + color_temp = min(self._max_kelvin, max(self._min_kelvin, color_temp)) # Color temperature change - used only in white mode, switch device mode to white - if self.mode == "color": - self.mode_result = await self.wrapper.device.switch_light_mode("white") - params["red"] = params["green"] = params["blue"] = 255 + set_mode = "white" + params["red"] = params["green"] = params["blue"] = 255 params["temp"] = int(color_temp) - elif ATTR_HS_COLOR in kwargs: + if ATTR_HS_COLOR in kwargs: red, green, blue = color_hs_to_RGB(*kwargs[ATTR_HS_COLOR]) # Color channels change - used only in color mode, switch device mode to color - if self.mode == "white": - self.mode_result = await self.wrapper.device.switch_light_mode("color") + set_mode = "color" params["red"] = red params["green"] = green params["blue"] = blue - elif ATTR_WHITE_VALUE in kwargs: + if ATTR_WHITE_VALUE in kwargs: # White channel change - used only in color mode, switch device mode device to color - if self.mode == "white": - self.mode_result = await self.wrapper.device.switch_light_mode("color") + set_mode = "color" params["white"] = int(kwargs[ATTR_WHITE_VALUE]) + + if set_mode and self.mode != set_mode: + self.mode_result = await self.wrapper.device.switch_light_mode(set_mode) + self.control_result = await self.block.set_state(**params) self.async_write_ha_state() From 43621091b7fad7b2c42ed2d3cdcf38df11d2c42b Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sat, 27 Feb 2021 00:05:45 +0000 Subject: [PATCH 048/831] [ci skip] Translation update --- .../accuweather/translations/nl.json | 4 ++ .../alarmdecoder/translations/nl.json | 2 +- .../azure_devops/translations/nl.json | 14 +++++- .../components/bond/translations/it.json | 4 +- .../components/bond/translations/nl.json | 5 +++ .../components/broadlink/translations/nl.json | 9 ++++ .../components/climacell/translations/it.json | 34 ++++++++++++++ .../components/climacell/translations/no.json | 2 +- .../components/cover/translations/nl.json | 3 +- .../faa_delays/translations/it.json | 21 +++++++++ .../faa_delays/translations/no.json | 21 +++++++++ .../components/gogogate2/translations/nl.json | 2 +- .../components/hlk_sw16/translations/nl.json | 1 + .../components/homekit/translations/it.json | 8 ++-- .../components/homekit/translations/nl.json | 6 +-- .../components/insteon/translations/nl.json | 3 ++ .../components/kmtronic/translations/it.json | 21 +++++++++ .../components/kodi/translations/nl.json | 7 ++- .../components/litejet/translations/it.json | 19 ++++++++ .../litterrobot/translations/it.json | 20 +++++++++ .../media_player/translations/nl.json | 2 +- .../components/mqtt/translations/nl.json | 1 + .../components/mullvad/translations/it.json | 22 ++++++++++ .../components/netatmo/translations/it.json | 22 ++++++++++ .../components/netatmo/translations/nl.json | 7 ++- .../components/netatmo/translations/no.json | 22 ++++++++++ .../philips_js/translations/en.json | 4 +- .../philips_js/translations/et.json | 2 + .../rainmachine/translations/nl.json | 2 +- .../components/risco/translations/nl.json | 4 +- .../components/sentry/translations/nl.json | 3 ++ .../simplisafe/translations/nl.json | 6 ++- .../somfy_mylink/translations/nl.json | 2 +- .../components/spotify/translations/nl.json | 2 +- .../components/subaru/translations/it.json | 44 +++++++++++++++++++ .../components/syncthru/translations/nl.json | 5 +++ .../totalconnect/translations/it.json | 17 ++++++- .../components/volumio/translations/nl.json | 1 + .../wolflink/translations/sensor.nl.json | 11 ++++- .../xiaomi_miio/translations/it.json | 1 + .../xiaomi_miio/translations/nl.json | 1 + .../zoneminder/translations/nl.json | 2 +- .../components/zwave_js/translations/it.json | 7 ++- .../components/zwave_js/translations/nl.json | 2 +- 44 files changed, 365 insertions(+), 33 deletions(-) create mode 100644 homeassistant/components/climacell/translations/it.json create mode 100644 homeassistant/components/faa_delays/translations/it.json create mode 100644 homeassistant/components/faa_delays/translations/no.json create mode 100644 homeassistant/components/kmtronic/translations/it.json create mode 100644 homeassistant/components/litejet/translations/it.json create mode 100644 homeassistant/components/litterrobot/translations/it.json create mode 100644 homeassistant/components/mullvad/translations/it.json create mode 100644 homeassistant/components/subaru/translations/it.json diff --git a/homeassistant/components/accuweather/translations/nl.json b/homeassistant/components/accuweather/translations/nl.json index ff0d81f94d3..4bf5f9fce45 100644 --- a/homeassistant/components/accuweather/translations/nl.json +++ b/homeassistant/components/accuweather/translations/nl.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_api_key": "API-sleutel", "requests_exceeded": "Het toegestane aantal verzoeken aan de Accuweather API is overschreden. U moet wachten of de API-sleutel wijzigen." }, diff --git a/homeassistant/components/alarmdecoder/translations/nl.json b/homeassistant/components/alarmdecoder/translations/nl.json index 1af1e8d803c..1ea9cb98b56 100644 --- a/homeassistant/components/alarmdecoder/translations/nl.json +++ b/homeassistant/components/alarmdecoder/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "AlarmDecoder-apparaat is al geconfigureerd." + "already_configured": "Apparaat is al geconfigureerd" }, "create_entry": { "default": "Succesvol verbonden met AlarmDecoder." diff --git a/homeassistant/components/azure_devops/translations/nl.json b/homeassistant/components/azure_devops/translations/nl.json index aef6a717895..07dc59e1a56 100644 --- a/homeassistant/components/azure_devops/translations/nl.json +++ b/homeassistant/components/azure_devops/translations/nl.json @@ -1,11 +1,21 @@ { "config": { "abort": { - "already_configured": "Account is al geconfigureerd" + "already_configured": "Account is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "cannot_connect": "Kan geen verbinding maken", - "invalid_auth": "Ongeldige authenticatie" + "invalid_auth": "Ongeldige authenticatie", + "project_error": "Kon geen projectinformatie ophalen." + }, + "step": { + "user": { + "data": { + "organization": "Organisatie", + "project": "Project" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/bond/translations/it.json b/homeassistant/components/bond/translations/it.json index d3ac1ab6b49..e22ad82e1fd 100644 --- a/homeassistant/components/bond/translations/it.json +++ b/homeassistant/components/bond/translations/it.json @@ -9,13 +9,13 @@ "old_firmware": "Firmware precedente non supportato sul dispositivo Bond - si prega di aggiornare prima di continuare", "unknown": "Errore imprevisto" }, - "flow_title": "Bond: {bond_id} ({host})", + "flow_title": "Bond: {name} ({host})", "step": { "confirm": { "data": { "access_token": "Token di accesso" }, - "description": "Vuoi configurare {bond_id}?" + "description": "Vuoi configurare {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/nl.json b/homeassistant/components/bond/translations/nl.json index b5d8c593ea9..a76c7a69d7f 100644 --- a/homeassistant/components/bond/translations/nl.json +++ b/homeassistant/components/bond/translations/nl.json @@ -10,6 +10,11 @@ }, "flow_title": "Bond: {bond_id} ({host})", "step": { + "confirm": { + "data": { + "access_token": "Toegangstoken" + } + }, "user": { "data": { "access_token": "Toegangstoken", diff --git a/homeassistant/components/broadlink/translations/nl.json b/homeassistant/components/broadlink/translations/nl.json index 7f85335d7bb..d2db5476555 100644 --- a/homeassistant/components/broadlink/translations/nl.json +++ b/homeassistant/components/broadlink/translations/nl.json @@ -18,6 +18,15 @@ "finish": { "data": { "name": "Naam" + }, + "title": "Kies een naam voor het apparaat" + }, + "reset": { + "title": "Ontgrendel het apparaat" + }, + "unlock": { + "data": { + "unlock": "Ja, doe het." } }, "user": { diff --git a/homeassistant/components/climacell/translations/it.json b/homeassistant/components/climacell/translations/it.json new file mode 100644 index 00000000000..cc7df4f8ab3 --- /dev/null +++ b/homeassistant/components/climacell/translations/it.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_api_key": "Chiave API non valida", + "rate_limited": "Al momento la tariffa \u00e8 limitata, riprova pi\u00f9 tardi.", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "api_key": "Chiave API", + "latitude": "Latitudine", + "longitude": "Logitudine", + "name": "Nome" + }, + "description": "Se Latitudine e Logitudine non vengono forniti, verranno utilizzati i valori predefiniti nella configurazione di Home Assistant. Verr\u00e0 creata un'entit\u00e0 per ogni tipo di previsione, ma solo quelli selezionati saranno abilitati per impostazione predefinita." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Tipo(i) di previsione", + "timestep": "Minuti tra le previsioni di NowCast" + }, + "description": "Se scegli di abilitare l'entit\u00e0 di previsione `nowcast`, puoi configurare il numero di minuti tra ogni previsione. Il numero di previsioni fornite dipende dal numero di minuti scelti tra le previsioni.", + "title": "Aggiorna le opzioni di ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/no.json b/homeassistant/components/climacell/translations/no.json index 64845ff7697..af07ce716d0 100644 --- a/homeassistant/components/climacell/translations/no.json +++ b/homeassistant/components/climacell/translations/no.json @@ -14,7 +14,7 @@ "longitude": "Lengdegrad", "name": "Navn" }, - "description": "Hvis Breddegrad and Lengdegrad er ikke gitt, vil standardverdiene i Home Assistant-konfigurasjonen bli brukt. Det blir opprettet en enhet for hver prognosetype, men bare de du velger blir aktivert som standard." + "description": "Hvis Breddegrad og Lengdegrad ikke er oppgitt, vil standardverdiene i Home Assistant-konfigurasjonen bli brukt. Det blir opprettet en enhet for hver prognosetype, men bare de du velger blir aktivert som standard." } } }, diff --git a/homeassistant/components/cover/translations/nl.json b/homeassistant/components/cover/translations/nl.json index 679d9360a82..8b1ca3c3500 100644 --- a/homeassistant/components/cover/translations/nl.json +++ b/homeassistant/components/cover/translations/nl.json @@ -6,7 +6,8 @@ "open": "Open {entity_name}", "open_tilt": "Open de kanteling {entity_name}", "set_position": "Stel de positie van {entity_name} in", - "set_tilt_position": "Stel de {entity_name} kantelpositie in" + "set_tilt_position": "Stel de {entity_name} kantelpositie in", + "stop": "Stop {entity_name}" }, "condition_type": { "is_closed": "{entity_name} is gesloten", diff --git a/homeassistant/components/faa_delays/translations/it.json b/homeassistant/components/faa_delays/translations/it.json new file mode 100644 index 00000000000..e1bf6ad0646 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/it.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Questo aeroporto \u00e8 gi\u00e0 configurato." + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_airport": "Il codice dell'aeroporto non \u00e8 valido", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "id": "Aeroporto" + }, + "description": "Immettere un codice aeroporto statunitense in formato IATA", + "title": "Ritardi FAA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/no.json b/homeassistant/components/faa_delays/translations/no.json new file mode 100644 index 00000000000..c481f90bf75 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/no.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Denne flyplassen er allerede konfigurert." + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_airport": "Flyplasskoden er ikke gyldig", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "id": "Flyplass" + }, + "description": "Skriv inn en amerikansk flyplasskode i IATA-format", + "title": "FAA forsinkelser" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/nl.json b/homeassistant/components/gogogate2/translations/nl.json index ad8e894d093..5418735ec07 100644 --- a/homeassistant/components/gogogate2/translations/nl.json +++ b/homeassistant/components/gogogate2/translations/nl.json @@ -15,7 +15,7 @@ "username": "Gebruikersnaam" }, "description": "Geef hieronder de vereiste informatie op.", - "title": "Stel GogoGate2 in" + "title": "Stel GogoGate2 of iSmartGate in" } } } diff --git a/homeassistant/components/hlk_sw16/translations/nl.json b/homeassistant/components/hlk_sw16/translations/nl.json index 0569c39321a..8ad15260b0d 100644 --- a/homeassistant/components/hlk_sw16/translations/nl.json +++ b/homeassistant/components/hlk_sw16/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/homekit/translations/it.json b/homeassistant/components/homekit/translations/it.json index 9a85d1e6e9f..fee64457652 100644 --- a/homeassistant/components/homekit/translations/it.json +++ b/homeassistant/components/homekit/translations/it.json @@ -19,7 +19,7 @@ "title": "Seleziona i domini da includere" }, "pairing": { - "description": "Non appena il {name} \u00e8 pronto, l'associazione sar\u00e0 disponibile in \"Notifiche\" come \"Configurazione HomeKit Bridge\".", + "description": "Per completare l'associazione, seguire le istruzioni in \"Notifiche\" sotto \"Associazione HomeKit\".", "title": "Associa HomeKit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "Domini da includere", "mode": "Modalit\u00e0" }, - "description": "L'integrazione di HomeKit ti consentir\u00e0 di accedere alle entit\u00e0 di Home Assistant in HomeKit. In modalit\u00e0 bridge, i bridge HomeKit sono limitati a 150 accessori per istanza, incluso il bridge stesso. Se desideri eseguire il bridge di un numero di accessori superiore a quello massimo, si consiglia di utilizzare pi\u00f9 bridge HomeKit per domini diversi. La configurazione dettagliata dell'entit\u00e0 \u00e8 disponibile solo tramite YAML per il bridge principale.", - "title": "Attiva HomeKit" + "description": "Scegli i domini da includere. Verranno incluse tutte le entit\u00e0 supportate nel dominio. Verr\u00e0 creata un'istanza HomeKit separata in modalit\u00e0 accessorio per ogni lettore multimediale TV e telecamera.", + "title": "Seleziona i domini da includere" } } }, @@ -55,7 +55,7 @@ "entities": "Entit\u00e0", "mode": "Modalit\u00e0" }, - "description": "Scegliere le entit\u00e0 da includere. In modalit\u00e0 accessorio, \u00e8 inclusa una sola entit\u00e0. In modalit\u00e0 di inclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, a meno che non siano selezionate entit\u00e0 specifiche. In modalit\u00e0 di esclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, ad eccezione delle entit\u00e0 escluse. Per prestazioni ottimali e per evitare una indisponibilit\u00e0 imprevista, creare e associare un'istanza HomeKit separata in modalit\u00e0 accessorio per ogni lettore multimediale, TV e videocamera.", + "description": "Scegliere le entit\u00e0 da includere. In modalit\u00e0 accessorio, \u00e8 inclusa una sola entit\u00e0. In modalit\u00e0 di inclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, a meno che non siano selezionate entit\u00e0 specifiche. In modalit\u00e0 di esclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, ad eccezione delle entit\u00e0 escluse. Per prestazioni ottimali, ci sar\u00e0 una HomeKit separata in modalit\u00e0 accessorio per ogni lettore multimediale, TV e videocamera.", "title": "Seleziona le entit\u00e0 da includere" }, "init": { diff --git a/homeassistant/components/homekit/translations/nl.json b/homeassistant/components/homekit/translations/nl.json index bcf61fe9868..9013723ac6c 100644 --- a/homeassistant/components/homekit/translations/nl.json +++ b/homeassistant/components/homekit/translations/nl.json @@ -11,7 +11,7 @@ }, "pairing": { "description": "Zodra de {name} klaar is, is het koppelen beschikbaar in \"Meldingen\" als \"HomeKit Bridge Setup\".", - "title": "Koppel HomeKit Bridge" + "title": "Koppel HomeKit" }, "user": { "data": { @@ -20,7 +20,7 @@ "mode": "Mode" }, "description": "De HomeKit-integratie geeft u toegang tot uw Home Assistant-entiteiten in HomeKit. In bridge-modus zijn HomeKit-bruggen beperkt tot 150 accessoires per exemplaar, inclusief de brug zelf. Als u meer dan het maximale aantal accessoires wilt overbruggen, is het aan te raden om meerdere HomeKit-bridges voor verschillende domeinen te gebruiken. Gedetailleerde entiteitsconfiguratie is alleen beschikbaar via YAML voor de primaire bridge.", - "title": "Activeer HomeKit Bridge" + "title": "Selecteer domeinen die u wilt opnemen" } } }, @@ -57,7 +57,7 @@ }, "yaml": { "description": "Deze invoer wordt beheerd via YAML", - "title": "Pas de HomeKit Bridge-opties aan" + "title": "Pas de HomeKit-opties aan" } } } diff --git a/homeassistant/components/insteon/translations/nl.json b/homeassistant/components/insteon/translations/nl.json index e4f7d4a8102..98a27fb1139 100644 --- a/homeassistant/components/insteon/translations/nl.json +++ b/homeassistant/components/insteon/translations/nl.json @@ -87,6 +87,9 @@ "remove_override": "Verwijder een apparaatoverschrijving.", "remove_x10": "Verwijder een X10-apparaat." } + }, + "remove_x10": { + "title": "Insteon" } } } diff --git a/homeassistant/components/kmtronic/translations/it.json b/homeassistant/components/kmtronic/translations/it.json new file mode 100644 index 00000000000..e9356485e08 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/it.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "username": "Nome utente" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/nl.json b/homeassistant/components/kodi/translations/nl.json index 8eb4a39cfb6..57476791b8f 100644 --- a/homeassistant/components/kodi/translations/nl.json +++ b/homeassistant/components/kodi/translations/nl.json @@ -11,6 +11,7 @@ "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, + "flow_title": "Kodi: {name}", "step": { "credentials": { "data": { @@ -19,11 +20,15 @@ }, "description": "Voer uw Kodi gebruikersnaam en wachtwoord in. Deze zijn te vinden in Systeem / Instellingen / Netwerk / Services." }, + "discovery_confirm": { + "description": "Wil je Kodi (`{name}`) toevoegen aan Home Assistant?", + "title": "Kodi ontdekt" + }, "user": { "data": { "host": "Host", "port": "Poort", - "ssl": "Maak verbinding via SSL" + "ssl": "Gebruik een SSL-certificaat" }, "description": "Kodi-verbindingsinformatie. Zorg ervoor dat u \"Controle van Kodi via HTTP toestaan\" in Systeem / Instellingen / Netwerk / Services inschakelt." }, diff --git a/homeassistant/components/litejet/translations/it.json b/homeassistant/components/litejet/translations/it.json new file mode 100644 index 00000000000..5b3dc46753d --- /dev/null +++ b/homeassistant/components/litejet/translations/it.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + }, + "error": { + "open_failed": "Impossibile aprire la porta seriale specificata." + }, + "step": { + "user": { + "data": { + "port": "Porta" + }, + "description": "Collega la porta RS232-2 del LiteJet al tuo computer e inserisci il percorso del dispositivo della porta seriale. \n\nL'MCP LiteJet deve essere configurato per 19,2 K baud, 8 bit di dati, 1 bit di stop, nessuna parit\u00e0 e per trasmettere un \"CR\" dopo ogni risposta.", + "title": "Connetti a LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/it.json b/homeassistant/components/litterrobot/translations/it.json new file mode 100644 index 00000000000..843262aa318 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/it.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "password": "Password", + "username": "Nome utente" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/media_player/translations/nl.json b/homeassistant/components/media_player/translations/nl.json index 37c1d6b4d9e..6ad22742533 100644 --- a/homeassistant/components/media_player/translations/nl.json +++ b/homeassistant/components/media_player/translations/nl.json @@ -22,7 +22,7 @@ "on": "Aan", "paused": "Gepauzeerd", "playing": "Afspelen", - "standby": "Standby" + "standby": "Stand-by" } }, "title": "Mediaspeler" diff --git a/homeassistant/components/mqtt/translations/nl.json b/homeassistant/components/mqtt/translations/nl.json index a0ab0e497da..3b3ebf9fe3b 100644 --- a/homeassistant/components/mqtt/translations/nl.json +++ b/homeassistant/components/mqtt/translations/nl.json @@ -63,6 +63,7 @@ }, "options": { "data": { + "birth_enable": "Geboortebericht inschakelen", "birth_payload": "Birth message payload", "birth_topic": "Birth message onderwerp" } diff --git a/homeassistant/components/mullvad/translations/it.json b/homeassistant/components/mullvad/translations/it.json new file mode 100644 index 00000000000..47cd8290f21 --- /dev/null +++ b/homeassistant/components/mullvad/translations/it.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "username": "Nome utente" + }, + "description": "Configurare l'integrazione VPN Mullvad?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/it.json b/homeassistant/components/netatmo/translations/it.json index 46c2d7d2721..152f7d47597 100644 --- a/homeassistant/components/netatmo/translations/it.json +++ b/homeassistant/components/netatmo/translations/it.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "Fuori casa", + "hg": "protezione antigelo", + "schedule": "programma" + }, + "trigger_type": { + "alarm_started": "{entity_name} ha rilevato un allarme", + "animal": "{entity_name} ha rilevato un animale", + "cancel_set_point": "{entity_name} ha ripreso il suo programma", + "human": "{entity_name} ha rilevato un essere umano", + "movement": "{entity_name} ha rilevato un movimento", + "outdoor": "{entity_name} ha rilevato un evento all'esterno", + "person": "{entity_name} ha rilevato una persona", + "person_away": "{entity_name} ha rilevato che una persona \u00e8 uscita", + "set_point": "{entity_name} temperatura desiderata impostata manualmente", + "therm_mode": "{entity_name} \u00e8 passato a \"{subtype}\"", + "turned_off": "{entity_name} disattivato", + "turned_on": "{entity_name} attivato", + "vehicle": "{entity_name} ha rilevato un veicolo" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/nl.json b/homeassistant/components/netatmo/translations/nl.json index 431f105df3d..0bdc3170a5a 100644 --- a/homeassistant/components/netatmo/translations/nl.json +++ b/homeassistant/components/netatmo/translations/nl.json @@ -3,7 +3,8 @@ "abort": { "authorize_url_timeout": "Time-out genereren autorisatie-URL.", "missing_configuration": "Het component is niet geconfigureerd. Volg de documentatie.", - "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "create_entry": { "default": "Succesvol geauthenticeerd met Netatmo." @@ -41,6 +42,10 @@ "public_weather": { "data": { "area_name": "Naam van het gebied", + "lat_ne": "Breedtegraad Noordoostelijke hoek", + "lat_sw": "Breedtegraad Zuidwestelijke hoek", + "lon_ne": "Lengtegraad Noordoostelijke hoek", + "lon_sw": "Lengtegraad Zuidwestelijke hoek", "mode": "Berekening", "show_on_map": "Toon op kaart" } diff --git a/homeassistant/components/netatmo/translations/no.json b/homeassistant/components/netatmo/translations/no.json index 387dbe7b26c..9e3e24d5771 100644 --- a/homeassistant/components/netatmo/translations/no.json +++ b/homeassistant/components/netatmo/translations/no.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "borte", + "hg": "frostvakt", + "schedule": "Tidsplan" + }, + "trigger_type": { + "alarm_started": "{entity_name} oppdaget en alarm", + "animal": "{entity_name} oppdaget et dyr", + "cancel_set_point": "{entity_name} har gjenopptatt tidsplanen", + "human": "{entity_name} oppdaget et menneske", + "movement": "{entity_name} oppdaget bevegelse", + "outdoor": "{entity_name} oppdaget en utend\u00f8rs hendelse", + "person": "{entity_name} oppdaget en person", + "person_away": "{entity_name} oppdaget at en person har forlatt", + "set_point": "M\u00e5ltemperatur {entity_name} angis manuelt", + "therm_mode": "{entity_name} byttet til \"{subtype}\"", + "turned_off": "{entity_name} sl\u00e5tt av", + "turned_on": "{entity_name} sl\u00e5tt p\u00e5", + "vehicle": "{entity_name} oppdaget et kj\u00f8ret\u00f8y" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/philips_js/translations/en.json b/homeassistant/components/philips_js/translations/en.json index b2022a01824..65d4f417b9f 100644 --- a/homeassistant/components/philips_js/translations/en.json +++ b/homeassistant/components/philips_js/translations/en.json @@ -5,9 +5,9 @@ }, "error": { "cannot_connect": "Failed to connect", - "unknown": "Unexpected error", + "invalid_pin": "Invalid PIN", "pairing_failure": "Unable to pair: {error_id}", - "invalid_pin": "Invalid PIN" + "unknown": "Unexpected error" }, "step": { "user": { diff --git a/homeassistant/components/philips_js/translations/et.json b/homeassistant/components/philips_js/translations/et.json index c77ef726411..9953df9c272 100644 --- a/homeassistant/components/philips_js/translations/et.json +++ b/homeassistant/components/philips_js/translations/et.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "\u00dchendamine nurjus", + "invalid_pin": "Vale PIN kood", + "pairing_failure": "Sidumine nurjus: {error_id}", "unknown": "Ootamatu t\u00f5rge" }, "step": { diff --git a/homeassistant/components/rainmachine/translations/nl.json b/homeassistant/components/rainmachine/translations/nl.json index 02411ea999f..119e4c641af 100644 --- a/homeassistant/components/rainmachine/translations/nl.json +++ b/homeassistant/components/rainmachine/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Deze RainMachine controller is al geconfigureerd." + "already_configured": "Apparaat is al geconfigureerd" }, "error": { "invalid_auth": "Ongeldige authenticatie" diff --git a/homeassistant/components/risco/translations/nl.json b/homeassistant/components/risco/translations/nl.json index 34bcb4ab98a..97d0d454a4f 100644 --- a/homeassistant/components/risco/translations/nl.json +++ b/homeassistant/components/risco/translations/nl.json @@ -30,8 +30,8 @@ }, "init": { "data": { - "code_arm_required": "Pincode vereist om in te schakelen", - "code_disarm_required": "Pincode vereist om uit te schakelen" + "code_arm_required": "PIN-code vereist om in te schakelen", + "code_disarm_required": "PIN-code vereist om uit te schakelen" }, "title": "Configureer opties" }, diff --git a/homeassistant/components/sentry/translations/nl.json b/homeassistant/components/sentry/translations/nl.json index 37437dfe836..64b7f1b73f7 100644 --- a/homeassistant/components/sentry/translations/nl.json +++ b/homeassistant/components/sentry/translations/nl.json @@ -9,6 +9,9 @@ }, "step": { "user": { + "data": { + "dsn": "DSN" + }, "description": "Voer uw Sentry DSN in", "title": "Sentry" } diff --git a/homeassistant/components/simplisafe/translations/nl.json b/homeassistant/components/simplisafe/translations/nl.json index b285b288525..d3196c591cb 100644 --- a/homeassistant/components/simplisafe/translations/nl.json +++ b/homeassistant/components/simplisafe/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Dit SimpliSafe-account is al in gebruik." + "already_configured": "Dit SimpliSafe-account is al in gebruik.", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "identifier_exists": "Account bestaat al", @@ -13,7 +14,8 @@ "data": { "password": "Wachtwoord" }, - "description": "Uw toegangstoken is verlopen of ingetrokken. Voer uw wachtwoord in om uw account opnieuw te koppelen." + "description": "Uw toegangstoken is verlopen of ingetrokken. Voer uw wachtwoord in om uw account opnieuw te koppelen.", + "title": "Verifieer de integratie opnieuw" }, "user": { "data": { diff --git a/homeassistant/components/somfy_mylink/translations/nl.json b/homeassistant/components/somfy_mylink/translations/nl.json index a63320919c6..b0ae5c9d3ad 100644 --- a/homeassistant/components/somfy_mylink/translations/nl.json +++ b/homeassistant/components/somfy_mylink/translations/nl.json @@ -8,7 +8,7 @@ "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, - "flow_title": "Somfy MyLink {mac} ( {ip} )", + "flow_title": "Somfy MyLink {mac} ({ip})", "step": { "user": { "data": { diff --git a/homeassistant/components/spotify/translations/nl.json b/homeassistant/components/spotify/translations/nl.json index bdc86919f74..46b18857fe8 100644 --- a/homeassistant/components/spotify/translations/nl.json +++ b/homeassistant/components/spotify/translations/nl.json @@ -15,7 +15,7 @@ }, "reauth_confirm": { "description": "De Spotify integratie moet opnieuw worden geverifieerd met Spotify voor account: {account}", - "title": "Verifieer opnieuw met Spotify" + "title": "Verifieer de integratie opnieuw" } } } diff --git a/homeassistant/components/subaru/translations/it.json b/homeassistant/components/subaru/translations/it.json new file mode 100644 index 00000000000..6dbb0702f46 --- /dev/null +++ b/homeassistant/components/subaru/translations/it.json @@ -0,0 +1,44 @@ +{ + "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "cannot_connect": "Impossibile connettersi" + }, + "error": { + "bad_pin_format": "Il PIN deve essere di 4 cifre", + "cannot_connect": "Impossibile connettersi", + "incorrect_pin": "PIN errato", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "Inserisci il tuo PIN MySubaru\nNOTA: tutti i veicoli nell'account devono avere lo stesso PIN", + "title": "Configurazione Subaru Starlink" + }, + "user": { + "data": { + "country": "Seleziona il paese", + "password": "Password", + "username": "Nome utente" + }, + "description": "Inserisci le tue credenziali MySubaru\nNOTA: la configurazione iniziale pu\u00f2 richiedere fino a 30 secondi", + "title": "Configurazione Subaru Starlink" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "Abilita l'interrogazione del veicolo" + }, + "description": "Quando abilitata, l'interrogazione del veicolo invier\u00e0 un comando remoto al tuo veicolo ogni 2 ore per ottenere nuovi dati del sensore. Senza l'interrogazione del veicolo, i nuovi dati del sensore verranno ricevuti solo quando il veicolo invier\u00e0 automaticamente i dati (normalmente dopo lo spegnimento del motore).", + "title": "Opzioni Subaru Starlink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/nl.json b/homeassistant/components/syncthru/translations/nl.json index b1beb4058bc..799e19ea371 100644 --- a/homeassistant/components/syncthru/translations/nl.json +++ b/homeassistant/components/syncthru/translations/nl.json @@ -8,6 +8,11 @@ }, "flow_title": "Samsung SyncThru Printer: {name}", "step": { + "confirm": { + "data": { + "name": "Naam" + } + }, "user": { "data": { "name": "Naam", diff --git a/homeassistant/components/totalconnect/translations/it.json b/homeassistant/components/totalconnect/translations/it.json index 2a12d00f57d..18ecf648310 100644 --- a/homeassistant/components/totalconnect/translations/it.json +++ b/homeassistant/components/totalconnect/translations/it.json @@ -1,12 +1,25 @@ { "config": { "abort": { - "already_configured": "L'account \u00e8 gi\u00e0 configurato" + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "error": { - "invalid_auth": "Autenticazione non valida" + "invalid_auth": "Autenticazione non valida", + "usercode": "Codice utente non valido per questo utente in questa posizione" }, "step": { + "locations": { + "data": { + "location": "Posizione" + }, + "description": "Immettere il codice utente per questo utente in questa posizione", + "title": "Codici utente posizione" + }, + "reauth_confirm": { + "description": "Total Connect deve autenticare nuovamente il tuo account", + "title": "Autenticare nuovamente l'integrazione" + }, "user": { "data": { "password": "Password", diff --git a/homeassistant/components/volumio/translations/nl.json b/homeassistant/components/volumio/translations/nl.json index 9179418def9..9e11dbad82b 100644 --- a/homeassistant/components/volumio/translations/nl.json +++ b/homeassistant/components/volumio/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "unknown": "Onverwachte fout" }, "step": { diff --git a/homeassistant/components/wolflink/translations/sensor.nl.json b/homeassistant/components/wolflink/translations/sensor.nl.json index da03cc43b4b..ae205d79aef 100644 --- a/homeassistant/components/wolflink/translations/sensor.nl.json +++ b/homeassistant/components/wolflink/translations/sensor.nl.json @@ -10,7 +10,16 @@ "heizung": "Verwarmen", "initialisierung": "Initialisatie", "kalibration": "Kalibratie", - "kalibration_heizbetrieb": "Kalibratie verwarmingsmodus" + "kalibration_heizbetrieb": "Kalibratie verwarmingsmodus", + "permanent": "Permanent", + "standby": "Stand-by", + "start": "Start", + "storung": "Fout", + "test": "Test", + "tpw": "TPW", + "urlaubsmodus": "Vakantiemodus", + "ventilprufung": "Kleptest", + "warmwasser": "DHW" } } } \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/it.json b/homeassistant/components/xiaomi_miio/translations/it.json index 68202e1631e..aa48ba7cfa8 100644 --- a/homeassistant/components/xiaomi_miio/translations/it.json +++ b/homeassistant/components/xiaomi_miio/translations/it.json @@ -14,6 +14,7 @@ "device": { "data": { "host": "Indirizzo IP", + "model": "Modello del dispositivo (opzionale)", "name": "Nome del dispositivo", "token": "Token API" }, diff --git a/homeassistant/components/xiaomi_miio/translations/nl.json b/homeassistant/components/xiaomi_miio/translations/nl.json index 3ea12e3a465..66209e61ee6 100644 --- a/homeassistant/components/xiaomi_miio/translations/nl.json +++ b/homeassistant/components/xiaomi_miio/translations/nl.json @@ -8,6 +8,7 @@ "cannot_connect": "Kan geen verbinding maken", "no_device_selected": "Geen apparaat geselecteerd, selecteer 1 apparaat alstublieft" }, + "flow_title": "Xiaomi Miio: {name}", "step": { "device": { "data": { diff --git a/homeassistant/components/zoneminder/translations/nl.json b/homeassistant/components/zoneminder/translations/nl.json index f4f071d9097..8aed5085391 100644 --- a/homeassistant/components/zoneminder/translations/nl.json +++ b/homeassistant/components/zoneminder/translations/nl.json @@ -23,7 +23,7 @@ "password": "Wachtwoord", "path": "ZM-pad", "path_zms": "ZMS-pad", - "ssl": "Gebruik SSL voor verbindingen met ZoneMinder", + "ssl": "Gebruik een SSL-certificaat", "username": "Gebruikersnaam", "verify_ssl": "Verifieer SSL-certificaat" }, diff --git a/homeassistant/components/zwave_js/translations/it.json b/homeassistant/components/zwave_js/translations/it.json index 5f0868a3f74..abe0ab066fb 100644 --- a/homeassistant/components/zwave_js/translations/it.json +++ b/homeassistant/components/zwave_js/translations/it.json @@ -6,6 +6,7 @@ "addon_install_failed": "Impossibile installare il componente aggiuntivo Z-Wave JS.", "addon_missing_discovery_info": "Informazioni sul rilevamento del componente aggiuntivo Z-Wave JS mancanti.", "addon_set_config_failed": "Impossibile impostare la configurazione di Z-Wave JS.", + "addon_start_failed": "Impossibile avviare il componente aggiuntivo Z-Wave JS.", "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "cannot_connect": "Impossibile connettersi" @@ -17,7 +18,8 @@ "unknown": "Errore imprevisto" }, "progress": { - "install_addon": "Attendi il termine dell'installazione del componente aggiuntivo Z-Wave JS. Questa operazione pu\u00f2 richiedere diversi minuti." + "install_addon": "Attendi il termine dell'installazione del componente aggiuntivo Z-Wave JS. Questa operazione pu\u00f2 richiedere diversi minuti.", + "start_addon": "Attendi il completamento dell'avvio del componente aggiuntivo Z-Wave JS. L'operazione potrebbe richiedere alcuni secondi." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Desideri utilizzare il componente aggiuntivo Z-Wave JS Supervisor?", "title": "Seleziona il metodo di connessione" }, + "start_addon": { + "title": "Il componente aggiuntivo Z-Wave JS si sta avviando." + }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/nl.json b/homeassistant/components/zwave_js/translations/nl.json index 7f46c02ece5..c15cfd26f31 100644 --- a/homeassistant/components/zwave_js/translations/nl.json +++ b/homeassistant/components/zwave_js/translations/nl.json @@ -5,7 +5,7 @@ "addon_info_failed": "Ophalen van Z-Wave JS add-on-info is mislukt.", "addon_install_failed": "Kan de Z-Wave JS add-on niet installeren.", "addon_missing_discovery_info": "De Z-Wave JS addon mist ontdekkings informatie", - "addon_set_config_failed": "Instellen van de Z-Wave JS-configuratie is mislukt.", + "addon_set_config_failed": "Instellen van de Z-Wave JS configuratie is mislukt.", "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kan geen verbinding maken" From 84e01baa5a104fb17ca1270cadc2a99aad2e1011 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Sat, 27 Feb 2021 01:32:51 +0100 Subject: [PATCH 049/831] Update frontend to 20210226.0 (#47123) --- 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 e8e9c44ae78..01f1c72f8d6 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20210225.0" + "home-assistant-frontend==20210226.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 10cf300b76b..9506171303b 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210225.0 +home-assistant-frontend==20210226.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index a2f3a6e45c8..57e0d027ea2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -763,7 +763,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210225.0 +home-assistant-frontend==20210226.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e5b4a3d04b1..ab133763afe 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -412,7 +412,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210225.0 +home-assistant-frontend==20210226.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 86f8cd80379f6c2b2f59a2963786d80151d22257 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Feb 2021 18:33:13 -0600 Subject: [PATCH 050/831] Provide a human readable exception for the percentage util (#47121) --- homeassistant/util/percentage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/util/percentage.py b/homeassistant/util/percentage.py index 10a72a85dff..949af7dbb32 100644 --- a/homeassistant/util/percentage.py +++ b/homeassistant/util/percentage.py @@ -19,7 +19,7 @@ def ordered_list_item_to_percentage(ordered_list: List[str], item: str) -> int: """ if item not in ordered_list: - raise ValueError + raise ValueError(f'The item "{item}"" is not in "{ordered_list}"') list_len = len(ordered_list) list_position = ordered_list.index(item) + 1 @@ -42,7 +42,7 @@ def percentage_to_ordered_list_item(ordered_list: List[str], percentage: int) -> """ list_len = len(ordered_list) if not list_len: - raise ValueError + raise ValueError("The ordered list is empty") for offset, speed in enumerate(ordered_list): list_position = offset + 1 From 49315a90d9f16e208b957d6526cdb6267020b680 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Feb 2021 18:33:31 -0600 Subject: [PATCH 051/831] Handle lutron_caseta fan speed being none (#47120) --- homeassistant/components/lutron_caseta/fan.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/lutron_caseta/fan.py b/homeassistant/components/lutron_caseta/fan.py index 330ff81d1d2..57b87b18320 100644 --- a/homeassistant/components/lutron_caseta/fan.py +++ b/homeassistant/components/lutron_caseta/fan.py @@ -44,6 +44,8 @@ class LutronCasetaFan(LutronCasetaDevice, FanEntity): @property def percentage(self) -> str: """Return the current speed percentage.""" + if self._device["fan_speed"] is None: + return None return ordered_list_item_to_percentage( ORDERED_NAMED_FAN_SPEEDS, self._device["fan_speed"] ) From 97b59023d18be22c5e639193f9c1ac26cd80a747 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sat, 27 Feb 2021 11:20:58 -0800 Subject: [PATCH 052/831] Improve handling for recording start of nest cam stream (#47144) * Improve handling for start of nest cam stream Add negative_cts_offsets to segment container options in order to better handle recording at the start of a stream. Nest streams start off with a negative offset, and if the segment container does not support it, then it adjusts the timestamps making it out of order with the next segment as described in issue #46968 * Update homeassistant/components/stream/__init__.py Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> --- homeassistant/components/stream/__init__.py | 1 + homeassistant/components/stream/worker.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 2d115c6978d..c2bf63063e5 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -258,6 +258,7 @@ class Stream: recorder.video_path = video_path self.start() + _LOGGER.debug("Started a stream recording of %s seconds", duration) # Take advantage of lookback hls = self.outputs().get("hls") diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index d5760877c43..773170449e1 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -25,7 +25,7 @@ def create_stream_buffer(video_stream, audio_stream, sequence): segment = io.BytesIO() container_options = { # Removed skip_sidx - see https://github.com/home-assistant/core/pull/39970 - "movflags": "frag_custom+empty_moov+default_base_moof+frag_discont", + "movflags": "frag_custom+empty_moov+default_base_moof+frag_discont+negative_cts_offsets", "avoid_negative_ts": "disabled", "fragment_index": str(sequence), } From da309ce342bc09377e21b87cb80cf5774a7d37e1 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 27 Feb 2021 21:09:25 +0100 Subject: [PATCH 053/831] Change device class of window covers to shade (#47129) --- homeassistant/components/deconz/cover.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/deconz/cover.py b/homeassistant/components/deconz/cover.py index 6e57d08302a..301d1753591 100644 --- a/homeassistant/components/deconz/cover.py +++ b/homeassistant/components/deconz/cover.py @@ -2,7 +2,8 @@ from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, - DEVICE_CLASS_WINDOW, + DEVICE_CLASS_DAMPER, + DEVICE_CLASS_SHADE, DOMAIN, SUPPORT_CLOSE, SUPPORT_CLOSE_TILT, @@ -80,9 +81,9 @@ class DeconzCover(DeconzDevice, CoverEntity): def device_class(self): """Return the class of the cover.""" if self._device.type in DAMPERS: - return "damper" + return DEVICE_CLASS_DAMPER if self._device.type in WINDOW_COVERS: - return DEVICE_CLASS_WINDOW + return DEVICE_CLASS_SHADE @property def current_cover_position(self): From eb7220ff262fe81d53c929884107bcedc8af7850 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sun, 28 Feb 2021 00:07:08 +0000 Subject: [PATCH 054/831] [ci skip] Translation update --- .../components/climacell/translations/no.json | 2 +- .../faa_delays/translations/ca.json | 21 ++++++++++++++++++ .../faa_delays/translations/no.json | 2 +- .../components/litejet/translations/fr.json | 3 +++ .../components/litejet/translations/no.json | 2 +- .../components/netatmo/translations/ca.json | 22 +++++++++++++++++++ .../philips_js/translations/ca.json | 2 ++ .../philips_js/translations/fr.json | 2 ++ .../philips_js/translations/no.json | 2 ++ .../philips_js/translations/ru.json | 2 ++ .../components/subaru/translations/fr.json | 15 ++++++++++++- .../components/subaru/translations/no.json | 2 +- .../totalconnect/translations/fr.json | 8 +++++-- .../xiaomi_miio/translations/fr.json | 1 + .../components/zwave_js/translations/no.json | 4 ++-- 15 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 homeassistant/components/faa_delays/translations/ca.json diff --git a/homeassistant/components/climacell/translations/no.json b/homeassistant/components/climacell/translations/no.json index af07ce716d0..d59f5590518 100644 --- a/homeassistant/components/climacell/translations/no.json +++ b/homeassistant/components/climacell/translations/no.json @@ -23,7 +23,7 @@ "init": { "data": { "forecast_types": "Prognosetype(r)", - "timestep": "Min. Mellom NowCast Prognoser" + "timestep": "Min. mellom NowCast prognoser" }, "description": "Hvis du velger \u00e5 aktivere \u00abnowcast\u00bb -varselenheten, kan du konfigurere antall minutter mellom hver prognose. Antall angitte prognoser avhenger av antall minutter som er valgt mellom prognosene.", "title": "Oppdater ClimaCell Alternativer" diff --git a/homeassistant/components/faa_delays/translations/ca.json b/homeassistant/components/faa_delays/translations/ca.json new file mode 100644 index 00000000000..e7e600f7f07 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/ca.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Aeroport ja est\u00e0 configurat." + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_airport": "Codi d'aeroport inv\u00e0lid", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "id": "Aeroport" + }, + "description": "Introdueix codi d'un aeroport dels EUA en format IATA", + "title": "FAA Delays" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/no.json b/homeassistant/components/faa_delays/translations/no.json index c481f90bf75..5a5aac723ad 100644 --- a/homeassistant/components/faa_delays/translations/no.json +++ b/homeassistant/components/faa_delays/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Denne flyplassen er allerede konfigurert." + "already_configured": "Denne flyplassen er allerede konfigurert" }, "error": { "cannot_connect": "Tilkobling mislyktes", diff --git a/homeassistant/components/litejet/translations/fr.json b/homeassistant/components/litejet/translations/fr.json index 455ba7fdc0c..89459d1829f 100644 --- a/homeassistant/components/litejet/translations/fr.json +++ b/homeassistant/components/litejet/translations/fr.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, + "error": { + "open_failed": "Impossible d'ouvrir le port s\u00e9rie sp\u00e9cifi\u00e9." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/litejet/translations/no.json b/homeassistant/components/litejet/translations/no.json index 26ccd333546..d3206ca2897 100644 --- a/homeassistant/components/litejet/translations/no.json +++ b/homeassistant/components/litejet/translations/no.json @@ -4,7 +4,7 @@ "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "error": { - "open_failed": "Kan ikke \u00e5pne den angitte serielle porten." + "open_failed": "Kan ikke \u00e5pne den angitte serielle porten" }, "step": { "user": { diff --git a/homeassistant/components/netatmo/translations/ca.json b/homeassistant/components/netatmo/translations/ca.json index a6b8b5c2b82..809223a04ae 100644 --- a/homeassistant/components/netatmo/translations/ca.json +++ b/homeassistant/components/netatmo/translations/ca.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "a fora", + "hg": "protecci\u00f3 contra gelades", + "schedule": "programaci\u00f3" + }, + "trigger_type": { + "alarm_started": "{entity_name} ha detectat una alarma", + "animal": "{entity_name} ha detectat un animal", + "cancel_set_point": "{entity_name} ha repr\u00e8s la programaci\u00f3", + "human": "{entity_name} ha detectat un hum\u00e0", + "movement": "{entity_name} ha detectat moviment", + "outdoor": "{entity_name} ha detectat un esdeveniment a fora", + "person": "{entity_name} ha detectat una persona", + "person_away": "{entity_name} ha detectat una marxant", + "set_point": "Temperatura objectiu {entity_name} configurada manualment", + "therm_mode": "{entity_name} ha canviar a \"{subtype}\"", + "turned_off": "{entity_name} s'ha apagat", + "turned_on": "{entity_name} s'ha engegat", + "vehicle": "{entity_name} ha detectat un vehicle" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/philips_js/translations/ca.json b/homeassistant/components/philips_js/translations/ca.json index 505a6472ea8..980bb6800e1 100644 --- a/homeassistant/components/philips_js/translations/ca.json +++ b/homeassistant/components/philips_js/translations/ca.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_pin": "PIN inv\u00e0lid", + "pairing_failure": "No s'ha pogut vincular: {error_id}", "unknown": "Error inesperat" }, "step": { diff --git a/homeassistant/components/philips_js/translations/fr.json b/homeassistant/components/philips_js/translations/fr.json index 9ae65c18fa4..25c28edcf1d 100644 --- a/homeassistant/components/philips_js/translations/fr.json +++ b/homeassistant/components/philips_js/translations/fr.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "\u00c9chec de connexion", + "invalid_pin": "NIP invalide", + "pairing_failure": "Association impossible: {error_id}", "unknown": "Erreur inattendue" }, "step": { diff --git a/homeassistant/components/philips_js/translations/no.json b/homeassistant/components/philips_js/translations/no.json index dadf15fb67a..a9c647a644b 100644 --- a/homeassistant/components/philips_js/translations/no.json +++ b/homeassistant/components/philips_js/translations/no.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "Tilkobling mislyktes", + "invalid_pin": "Ugyldig PIN", + "pairing_failure": "Kan ikke parre: {error_id}", "unknown": "Uventet feil" }, "step": { diff --git a/homeassistant/components/philips_js/translations/ru.json b/homeassistant/components/philips_js/translations/ru.json index 9306ecf7a29..83511ff246a 100644 --- a/homeassistant/components/philips_js/translations/ru.json +++ b/homeassistant/components/philips_js/translations/ru.json @@ -5,6 +5,8 @@ }, "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_pin": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 PIN-\u043a\u043e\u0434.", + "pairing_failure": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435: {error_id}.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "step": { diff --git a/homeassistant/components/subaru/translations/fr.json b/homeassistant/components/subaru/translations/fr.json index a6bf6902aab..25544534297 100644 --- a/homeassistant/components/subaru/translations/fr.json +++ b/homeassistant/components/subaru/translations/fr.json @@ -24,7 +24,20 @@ "country": "Choisissez le pays", "password": "Mot de passe", "username": "Nom d'utilisateur" - } + }, + "description": "Veuillez saisir vos identifiants MySubaru\n REMARQUE: la configuration initiale peut prendre jusqu'\u00e0 30 secondes", + "title": "Configuration de Subaru Starlink" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "Activer l'interrogation des v\u00e9hicules" + }, + "description": "Lorsqu'elle est activ\u00e9e, l'interrogation du v\u00e9hicule enverra une commande \u00e0 distance \u00e0 votre v\u00e9hicule toutes les 2 heures pour obtenir de nouvelles donn\u00e9es de capteur. Sans interrogation du v\u00e9hicule, les nouvelles donn\u00e9es de capteur ne sont re\u00e7ues que lorsque le v\u00e9hicule pousse automatiquement les donn\u00e9es (normalement apr\u00e8s l'arr\u00eat du moteur).", + "title": "Options de Subaru Starlink" } } } diff --git a/homeassistant/components/subaru/translations/no.json b/homeassistant/components/subaru/translations/no.json index f1a263d5cb4..25b0f7bec29 100644 --- a/homeassistant/components/subaru/translations/no.json +++ b/homeassistant/components/subaru/translations/no.json @@ -37,7 +37,7 @@ "update_enabled": "Aktiver polling av kj\u00f8ret\u00f8y" }, "description": "N\u00e5r dette er aktivert, sender polling av kj\u00f8ret\u00f8y en fjernkommando til kj\u00f8ret\u00f8yet annenhver time for \u00e5 skaffe nye sensordata. Uten kj\u00f8ret\u00f8yoppm\u00e5ling mottas nye sensordata bare n\u00e5r kj\u00f8ret\u00f8yet automatisk skyver data (normalt etter motorstans).", - "title": "Subaru Starlink Alternativer" + "title": "Subaru Starlink alternativer" } } } diff --git a/homeassistant/components/totalconnect/translations/fr.json b/homeassistant/components/totalconnect/translations/fr.json index 40ca767b4ac..b46bf127963 100644 --- a/homeassistant/components/totalconnect/translations/fr.json +++ b/homeassistant/components/totalconnect/translations/fr.json @@ -5,15 +5,19 @@ "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { - "invalid_auth": "Authentification invalide" + "invalid_auth": "Authentification invalide", + "usercode": "Code d'utilisateur non valide pour cet utilisateur \u00e0 cet emplacement" }, "step": { "locations": { "data": { "location": "Emplacement" - } + }, + "description": "Saisissez le code d'utilisateur de cet utilisateur \u00e0 cet emplacement", + "title": "Codes d'utilisateur de l'emplacement" }, "reauth_confirm": { + "description": "Total Connect doit r\u00e9-authentifier votre compte", "title": "R\u00e9-authentifier l'int\u00e9gration" }, "user": { diff --git a/homeassistant/components/xiaomi_miio/translations/fr.json b/homeassistant/components/xiaomi_miio/translations/fr.json index 10ce9972818..30def127e7a 100644 --- a/homeassistant/components/xiaomi_miio/translations/fr.json +++ b/homeassistant/components/xiaomi_miio/translations/fr.json @@ -14,6 +14,7 @@ "device": { "data": { "host": "Adresse IP", + "model": "Mod\u00e8le d'appareil (facultatif)", "name": "Nom de l'appareil", "token": "Jeton d'API" }, diff --git a/homeassistant/components/zwave_js/translations/no.json b/homeassistant/components/zwave_js/translations/no.json index acd049fc561..f893d2d7684 100644 --- a/homeassistant/components/zwave_js/translations/no.json +++ b/homeassistant/components/zwave_js/translations/no.json @@ -19,7 +19,7 @@ }, "progress": { "install_addon": "Vent mens installasjonen av Z-Wave JS-tillegg er ferdig. Dette kan ta flere minutter.", - "start_addon": "Vent mens Z-Wave JS-tilleggsstarten er fullf\u00f8rt. Dette kan ta noen sekunder." + "start_addon": "Vent mens Z-Wave JS-tillegget er ferdig startet. Dette kan ta noen sekunder." }, "step": { "configure_addon": { @@ -48,7 +48,7 @@ "title": "Velg tilkoblingsmetode" }, "start_addon": { - "title": "Z-Wave JS-tillegget starter." + "title": "Z-Wave JS-tillegget starter" }, "user": { "data": { From 66027bcef5a0f8d425ebb64ded7339eaa4fef72a Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Sun, 28 Feb 2021 12:53:13 +0100 Subject: [PATCH 055/831] Bump airly library to version 1.1.0 (#47163) --- homeassistant/components/airly/__init__.py | 6 ++++++ homeassistant/components/airly/manifest.json | 2 +- homeassistant/components/airly/strings.json | 4 +++- .../components/airly/system_health.py | 9 ++++++++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/airly/test_system_health.py | 20 +++++++++++++++---- 7 files changed, 36 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/airly/__init__.py b/homeassistant/components/airly/__init__.py index 6a9c23624f0..fd1defc64f6 100644 --- a/homeassistant/components/airly/__init__.py +++ b/homeassistant/components/airly/__init__.py @@ -144,6 +144,12 @@ class AirlyDataUpdateCoordinator(DataUpdateCoordinator): except (AirlyError, ClientConnectorError) as error: raise UpdateFailed(error) from error + _LOGGER.debug( + "Requests remaining: %s/%s", + self.airly.requests_remaining, + self.airly.requests_per_day, + ) + values = measurements.current["values"] index = measurements.current["indexes"][0] standards = measurements.current["standards"] diff --git a/homeassistant/components/airly/manifest.json b/homeassistant/components/airly/manifest.json index 77de843ffce..a5ff485d1d0 100644 --- a/homeassistant/components/airly/manifest.json +++ b/homeassistant/components/airly/manifest.json @@ -3,7 +3,7 @@ "name": "Airly", "documentation": "https://www.home-assistant.io/integrations/airly", "codeowners": ["@bieniu"], - "requirements": ["airly==1.0.0"], + "requirements": ["airly==1.1.0"], "config_flow": true, "quality_scale": "platinum" } diff --git a/homeassistant/components/airly/strings.json b/homeassistant/components/airly/strings.json index afda73ae887..c6b6f1e6a41 100644 --- a/homeassistant/components/airly/strings.json +++ b/homeassistant/components/airly/strings.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "Reach Airly server" + "can_reach_server": "Reach Airly server", + "requests_remaining": "Remaining allowed requests", + "requests_per_day": "Allowed requests per day" } } } diff --git a/homeassistant/components/airly/system_health.py b/homeassistant/components/airly/system_health.py index 6b683518ebd..3f2ed8e8d65 100644 --- a/homeassistant/components/airly/system_health.py +++ b/homeassistant/components/airly/system_health.py @@ -4,6 +4,8 @@ from airly import Airly from homeassistant.components import system_health from homeassistant.core import HomeAssistant, callback +from .const import DOMAIN + @callback def async_register( @@ -15,8 +17,13 @@ def async_register( async def system_health_info(hass): """Get info for the info page.""" + requests_remaining = list(hass.data[DOMAIN].values())[0].airly.requests_remaining + requests_per_day = list(hass.data[DOMAIN].values())[0].airly.requests_per_day + return { "can_reach_server": system_health.async_check_can_reach_url( hass, Airly.AIRLY_API_URL - ) + ), + "requests_remaining": requests_remaining, + "requests_per_day": requests_per_day, } diff --git a/requirements_all.txt b/requirements_all.txt index 57e0d027ea2..326fc962388 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -233,7 +233,7 @@ aiounifi==26 aioymaps==1.1.0 # homeassistant.components.airly -airly==1.0.0 +airly==1.1.0 # homeassistant.components.aladdin_connect aladdin_connect==0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ab133763afe..561b877a48f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -152,7 +152,7 @@ aiounifi==26 aioymaps==1.1.0 # homeassistant.components.airly -airly==1.0.0 +airly==1.1.0 # homeassistant.components.ambiclimate ambiclimate==0.2.1 diff --git a/tests/components/airly/test_system_health.py b/tests/components/airly/test_system_health.py index 02ee67ae452..42fc50ed051 100644 --- a/tests/components/airly/test_system_health.py +++ b/tests/components/airly/test_system_health.py @@ -18,7 +18,11 @@ async def test_airly_system_health(hass, aioclient_mock): hass.data[DOMAIN] = {} hass.data[DOMAIN]["0123xyz"] = Mock( - airly=Mock(AIRLY_API_URL="https://airapi.airly.eu/v2/") + airly=Mock( + AIRLY_API_URL="https://airapi.airly.eu/v2/", + requests_remaining=42, + requests_per_day=100, + ) ) info = await get_system_health_info(hass, DOMAIN) @@ -27,7 +31,9 @@ async def test_airly_system_health(hass, aioclient_mock): if asyncio.iscoroutine(val): info[key] = await val - assert info == {"can_reach_server": "ok"} + assert info["can_reach_server"] == "ok" + assert info["requests_remaining"] == 42 + assert info["requests_per_day"] == 100 async def test_airly_system_health_fail(hass, aioclient_mock): @@ -38,7 +44,11 @@ async def test_airly_system_health_fail(hass, aioclient_mock): hass.data[DOMAIN] = {} hass.data[DOMAIN]["0123xyz"] = Mock( - airly=Mock(AIRLY_API_URL="https://airapi.airly.eu/v2/") + airly=Mock( + AIRLY_API_URL="https://airapi.airly.eu/v2/", + requests_remaining=0, + requests_per_day=1000, + ) ) info = await get_system_health_info(hass, DOMAIN) @@ -47,4 +57,6 @@ async def test_airly_system_health_fail(hass, aioclient_mock): if asyncio.iscoroutine(val): info[key] = await val - assert info == {"can_reach_server": {"type": "failed", "error": "unreachable"}} + assert info["can_reach_server"] == {"type": "failed", "error": "unreachable"} + assert info["requests_remaining"] == 0 + assert info["requests_per_day"] == 1000 From fa1d91d1fe30f07a5433b6723919c25374df70ea Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 28 Feb 2021 08:16:37 -0500 Subject: [PATCH 056/831] Clean up mqtt_room (#46882) --- homeassistant/components/mqtt_room/sensor.py | 11 ++++++++--- tests/components/mqtt_room/test_sensor.py | 5 +---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/mqtt_room/sensor.py b/homeassistant/components/mqtt_room/sensor.py index 0d07133b396..3b61003e601 100644 --- a/homeassistant/components/mqtt_room/sensor.py +++ b/homeassistant/components/mqtt_room/sensor.py @@ -8,7 +8,14 @@ import voluptuous as vol from homeassistant.components import mqtt from homeassistant.components.mqtt import CONF_STATE_TOPIC from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ATTR_ID, CONF_NAME, CONF_TIMEOUT, STATE_NOT_HOME +from homeassistant.const import ( + ATTR_DEVICE_ID, + ATTR_ID, + CONF_DEVICE_ID, + CONF_NAME, + CONF_TIMEOUT, + STATE_NOT_HOME, +) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -16,11 +23,9 @@ from homeassistant.util import dt, slugify _LOGGER = logging.getLogger(__name__) -ATTR_DEVICE_ID = "device_id" ATTR_DISTANCE = "distance" ATTR_ROOM = "room" -CONF_DEVICE_ID = "device_id" CONF_AWAY_TIMEOUT = "away_timeout" DEFAULT_AWAY_TIMEOUT = 0 diff --git a/tests/components/mqtt_room/test_sensor.py b/tests/components/mqtt_room/test_sensor.py index ca5f9420dc5..c3b8704c754 100644 --- a/tests/components/mqtt_room/test_sensor.py +++ b/tests/components/mqtt_room/test_sensor.py @@ -5,7 +5,7 @@ from unittest.mock import patch from homeassistant.components.mqtt import CONF_QOS, CONF_STATE_TOPIC, DEFAULT_QOS import homeassistant.components.sensor as sensor -from homeassistant.const import CONF_NAME, CONF_PLATFORM +from homeassistant.const import CONF_DEVICE_ID, CONF_NAME, CONF_PLATFORM, CONF_TIMEOUT from homeassistant.setup import async_setup_component from homeassistant.util import dt @@ -21,9 +21,6 @@ LIVING_ROOM_TOPIC = f"room_presence/{LIVING_ROOM}" SENSOR_STATE = f"sensor.{NAME}" -CONF_DEVICE_ID = "device_id" -CONF_TIMEOUT = "timeout" - NEAR_MESSAGE = {"id": DEVICE_ID, "name": NAME, "distance": 1} FAR_MESSAGE = {"id": DEVICE_ID, "name": NAME, "distance": 10} From f4189510e9e0abe142403d59cade98b961fbaf96 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sun, 28 Feb 2021 14:33:48 +0100 Subject: [PATCH 057/831] Bump builder to get generic-x86-64 nightly builds (#47164) --- azure-pipelines-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines-release.yml b/azure-pipelines-release.yml index 5fe91325582..74aa05e58f3 100644 --- a/azure-pipelines-release.yml +++ b/azure-pipelines-release.yml @@ -14,7 +14,7 @@ schedules: always: true variables: - name: versionBuilder - value: '2020.11.0' + value: '2021.02.0' - group: docker - group: github - group: twine From 98be703d90e44efe43b1a17c7e5243e5097b00b1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 28 Feb 2021 05:41:06 -0800 Subject: [PATCH 058/831] Fix the updater schema (#47128) --- homeassistant/components/updater/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index 9d65bb4c5d4..81910db38d6 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -27,7 +27,7 @@ UPDATER_URL = "https://updater.home-assistant.io/" CONFIG_SCHEMA = vol.Schema( { - DOMAIN: { + vol.Optional(DOMAIN, default={}): { vol.Optional(CONF_REPORTING, default=True): cv.boolean, vol.Optional(CONF_COMPONENT_REPORTING, default=False): cv.boolean, } @@ -56,13 +56,13 @@ async def async_setup(hass, config): # This component only makes sense in release versions _LOGGER.info("Running on 'dev', only analytics will be submitted") - conf = config.get(DOMAIN, {}) - if conf.get(CONF_REPORTING): + conf = config[DOMAIN] + if conf[CONF_REPORTING]: huuid = await hass.helpers.instance_id.async_get() else: huuid = None - include_components = conf.get(CONF_COMPONENT_REPORTING) + include_components = conf[CONF_COMPONENT_REPORTING] async def check_new_version() -> Updater: """Check if a new version is available and report if one is.""" From 4853a81366759da792a218b8b4e677a1464661fc Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Sun, 28 Feb 2021 10:55:14 -0500 Subject: [PATCH 059/831] Bump ZHA quirks to 0.0.54 (#47172) --- 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 ad2bf5f17c5..d7bb0dbe5bc 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -7,7 +7,7 @@ "bellows==0.21.0", "pyserial==3.5", "pyserial-asyncio==0.5", - "zha-quirks==0.0.53", + "zha-quirks==0.0.54", "zigpy-cc==0.5.2", "zigpy-deconz==0.11.1", "zigpy==0.32.0", diff --git a/requirements_all.txt b/requirements_all.txt index 326fc962388..1c5f27fd4cf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2367,7 +2367,7 @@ zengge==0.2 zeroconf==0.28.8 # homeassistant.components.zha -zha-quirks==0.0.53 +zha-quirks==0.0.54 # homeassistant.components.zhong_hong zhong_hong_hvac==1.0.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 561b877a48f..923cf92c3fd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1213,7 +1213,7 @@ zeep[async]==4.0.0 zeroconf==0.28.8 # homeassistant.components.zha -zha-quirks==0.0.53 +zha-quirks==0.0.54 # homeassistant.components.zha zigpy-cc==0.5.2 From da5902e4f8b986f7cc1afa47b9f309e5425e27be Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 28 Feb 2021 17:48:23 +0100 Subject: [PATCH 060/831] Tweak Tasmota fan typing (#47175) --- homeassistant/components/tasmota/fan.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/tasmota/fan.py b/homeassistant/components/tasmota/fan.py index 77b4532c001..876d1a4cf60 100644 --- a/homeassistant/components/tasmota/fan.py +++ b/homeassistant/components/tasmota/fan.py @@ -1,7 +1,5 @@ """Support for Tasmota fans.""" -from typing import Optional - from hatasmota import const as tasmota_const from homeassistant.components import fan @@ -59,7 +57,7 @@ class TasmotaFan( ) @property - def speed_count(self) -> Optional[int]: + def speed_count(self) -> int: """Return the number of speeds the fan supports.""" return len(ORDERED_NAMED_FAN_SPEEDS) From 261d86f06b2ced3ce6ce3401354f95c0bff0708b Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sun, 28 Feb 2021 19:19:50 +0100 Subject: [PATCH 061/831] Apply recommendations to synology_dsm (#47178) --- .../components/synology_dsm/__init__.py | 64 ++++++++----------- 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 6f0476b403c..50921944a6d 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -191,7 +191,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): try: await api.async_setup() except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: - _LOGGER.debug("async_setup_entry() - Unable to connect to DSM: %s", err) + _LOGGER.debug("Unable to connect to DSM during setup: %s", err) raise ConfigEntryNotReady from err hass.data.setdefault(DOMAIN, {}) @@ -225,9 +225,6 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): async with async_timeout.timeout(10): await hass.async_add_executor_job(surveillance_station.update) except SynologyDSMAPIErrorException as err: - _LOGGER.debug( - "async_coordinator_update_data_cameras() - exception: %s", err - ) raise UpdateFailed(f"Error communicating with API: {err}") from err return { @@ -241,9 +238,6 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): try: await api.async_update() except Exception as err: - _LOGGER.debug( - "async_coordinator_update_data_central() - exception: %s", err - ) raise UpdateFailed(f"Error communicating with API: {err}") from err return None @@ -338,15 +332,13 @@ async def _async_setup_services(hass: HomeAssistantType): serial = next(iter(dsm_devices)) else: _LOGGER.error( - "service_handler - more than one DSM configured, must specify one of serials %s", + "More than one DSM configured, must specify one of serials %s", sorted(dsm_devices), ) return if not dsm_device: - _LOGGER.error( - "service_handler - DSM with specified serial %s not found", serial - ) + _LOGGER.error("DSM with specified serial %s not found", serial) return _LOGGER.debug("%s DSM with serial %s", call.service, serial) @@ -409,11 +401,11 @@ class SynoApi: self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY) ) _LOGGER.debug( - "SynoAPI.async_setup() - self._with_surveillance_station:%s", + "State of Surveillance_station during setup:%s", self._with_surveillance_station, ) - self._setup_api_requests() + self._async_setup_api_requests() await self._hass.async_add_executor_job(self._fetch_device_configuration) await self.async_update() @@ -422,7 +414,7 @@ class SynoApi: def subscribe(self, api_key, unique_id): """Subscribe an entity to API fetches.""" _LOGGER.debug( - "SynoAPI.subscribe() - api_key:%s, unique_id:%s", api_key, unique_id + "Subscribe new entity - api_key:%s, unique_id:%s", api_key, unique_id ) if api_key not in self._fetching_entities: self._fetching_entities[api_key] = set() @@ -432,7 +424,7 @@ class SynoApi: def unsubscribe() -> None: """Unsubscribe an entity from API fetches (when disable).""" _LOGGER.debug( - "SynoAPI.unsubscribe() - api_key:%s, unique_id:%s", api_key, unique_id + "Unsubscribe new entity - api_key:%s, unique_id:%s", api_key, unique_id ) self._fetching_entities[api_key].remove(unique_id) if len(self._fetching_entities[api_key]) == 0: @@ -441,13 +433,11 @@ class SynoApi: return unsubscribe @callback - def _setup_api_requests(self): + def _async_setup_api_requests(self): """Determine if we should fetch each API, if one entity needs it.""" # Entities not added yet, fetch all if not self._fetching_entities: - _LOGGER.debug( - "SynoAPI._setup_api_requests() - Entities not added yet, fetch all" - ) + _LOGGER.debug("Entities not added yet, fetch all") return # Determine if we should fetch an API @@ -470,34 +460,32 @@ class SynoApi: # Reset not used API, information is not reset since it's used in device_info if not self._with_security: - _LOGGER.debug("SynoAPI._setup_api_requests() - disable security") + _LOGGER.debug("Disable security api from being updated") self.dsm.reset(self.security) self.security = None if not self._with_storage: - _LOGGER.debug("SynoAPI._setup_api_requests() - disable storage") + _LOGGER.debug("Disable storage api from being updated") self.dsm.reset(self.storage) self.storage = None if not self._with_system: - _LOGGER.debug("SynoAPI._setup_api_requests() - disable system") + _LOGGER.debug("Disable system api from being updated") self.dsm.reset(self.system) self.system = None if not self._with_upgrade: - _LOGGER.debug("SynoAPI._setup_api_requests() - disable upgrade") + _LOGGER.debug("Disable upgrade api from being updated") self.dsm.reset(self.upgrade) self.upgrade = None if not self._with_utilisation: - _LOGGER.debug("SynoAPI._setup_api_requests() - disable utilisation") + _LOGGER.debug("Disable utilisation api from being updated") self.dsm.reset(self.utilisation) self.utilisation = None if not self._with_surveillance_station: - _LOGGER.debug( - "SynoAPI._setup_api_requests() - disable surveillance_station" - ) + _LOGGER.debug("Disable surveillance_station api from being updated") self.dsm.reset(self.surveillance_station) self.surveillance_station = None @@ -508,29 +496,27 @@ class SynoApi: self.network.update() if self._with_security: - _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch security") + _LOGGER.debug("Enable security api for updates") self.security = self.dsm.security if self._with_storage: - _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch storage") + _LOGGER.debug("Enable storage api for updates") self.storage = self.dsm.storage if self._with_upgrade: - _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch upgrade") + _LOGGER.debug("Enable upgrade api for updates") self.upgrade = self.dsm.upgrade if self._with_system: - _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch system") + _LOGGER.debug("Enable system api for updates") self.system = self.dsm.system if self._with_utilisation: - _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch utilisation") + _LOGGER.debug("Enable utilisation api for updates") self.utilisation = self.dsm.utilisation if self._with_surveillance_station: - _LOGGER.debug( - "SynoAPI._fetch_device_configuration() - fetch surveillance_station" - ) + _LOGGER.debug("Enable surveillance_station api for updates") self.surveillance_station = self.dsm.surveillance_station async def async_reboot(self): @@ -558,17 +544,17 @@ class SynoApi: async def async_update(self, now=None): """Update function for updating API information.""" - _LOGGER.debug("SynoAPI.async_update()") - self._setup_api_requests() + _LOGGER.debug("Start data update") + self._async_setup_api_requests() try: await self._hass.async_add_executor_job( self.dsm.update, self._with_information ) except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: _LOGGER.warning( - "async_update - connection error during update, fallback by reloading the entry" + "Connection error during update, fallback by reloading the entry" ) - _LOGGER.debug("SynoAPI.async_update() - exception: %s", err) + _LOGGER.debug("Connection error during update with exception: %s", err) await self._hass.config_entries.async_reload(self._entry.entry_id) return From 5cc8302e6a3912a889aadfd12c12bfd1e6392190 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 28 Feb 2021 10:25:07 -0800 Subject: [PATCH 062/831] Fix flaky hls keepalive test (#47186) Remove a call to stream.start() which is issued before the test is fully setup (e.g. keepalive is not set to True, and mock calls are not registered) --- tests/components/stream/test_hls.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index c11576d2570..b554ee6b20a 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -229,7 +229,6 @@ async def test_stream_keepalive(hass): stream = create_stream(hass, source) track = stream.add_provider("hls") track.num_segments = 2 - stream.start() cur_time = 0 From 6ff3eb0569c4fdec81996c39368a137b74468b47 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 28 Feb 2021 12:27:36 -0600 Subject: [PATCH 063/831] Update HAP-python to 3.3.1 (#47180) Fixes disconnect when setting a single char fails https://github.com/ikalchev/HAP-python/compare/v3.3.0...v3.3.1 --- homeassistant/components/homekit/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/manifest.json b/homeassistant/components/homekit/manifest.json index acc61408a48..4d1598c728c 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit", "documentation": "https://www.home-assistant.io/integrations/homekit", "requirements": [ - "HAP-python==3.3.0", + "HAP-python==3.3.1", "fnvhash==0.1.0", "PyQRCode==1.2.1", "base36==0.1.1", diff --git a/requirements_all.txt b/requirements_all.txt index 1c5f27fd4cf..673cbf4551d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -17,7 +17,7 @@ Adafruit-SHT31==1.0.2 # Adafruit_BBIO==1.1.1 # homeassistant.components.homekit -HAP-python==3.3.0 +HAP-python==3.3.1 # homeassistant.components.mastodon Mastodon.py==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 923cf92c3fd..57d3dcf739b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ AEMET-OpenData==0.1.8 # homeassistant.components.homekit -HAP-python==3.3.0 +HAP-python==3.3.1 # homeassistant.components.flick_electric PyFlick==0.0.2 From 19cd29affa5690692c375a81af82288a244e9de1 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 28 Feb 2021 21:19:27 +0100 Subject: [PATCH 064/831] Fix MQTT trigger where wanted payload may be parsed as an integer (#47162) --- .../components/mqtt/device_trigger.py | 12 ++- homeassistant/components/mqtt/trigger.py | 6 +- tests/components/mqtt/test_device_trigger.py | 75 +++++++++++++++++++ tests/components/mqtt/test_trigger.py | 25 +++++++ 4 files changed, 114 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index 8969072553c..d6e2ee0fc65 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -13,6 +13,7 @@ from homeassistant.const import ( CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE, + CONF_VALUE_TEMPLATE, ) from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError @@ -66,10 +67,11 @@ TRIGGER_DISCOVERY_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( { vol.Required(CONF_AUTOMATION_TYPE): str, vol.Required(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_PAYLOAD, default=None): vol.Any(None, cv.string), - vol.Required(CONF_TYPE): cv.string, vol.Required(CONF_SUBTYPE): cv.string, + vol.Required(CONF_TOPIC): cv.string, + vol.Required(CONF_TYPE): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE, default=None): vol.Any(None, cv.string), }, validate_device_has_at_least_one_identifier, ) @@ -96,6 +98,8 @@ class TriggerInstance: } if self.trigger.payload: mqtt_config[CONF_PAYLOAD] = self.trigger.payload + if self.trigger.value_template: + mqtt_config[CONF_VALUE_TEMPLATE] = self.trigger.value_template mqtt_config = mqtt_trigger.TRIGGER_SCHEMA(mqtt_config) if self.remove: @@ -121,6 +125,7 @@ class Trigger: subtype: str = attr.ib() topic: str = attr.ib() type: str = attr.ib() + value_template: str = attr.ib() trigger_instances: List[TriggerInstance] = attr.ib(factory=list) async def add_trigger(self, action, automation_info): @@ -153,6 +158,7 @@ class Trigger: self.qos = config[CONF_QOS] topic_changed = self.topic != config[CONF_TOPIC] self.topic = config[CONF_TOPIC] + self.value_template = config[CONF_VALUE_TEMPLATE] # Unsubscribe+subscribe if this trigger is in use and topic has changed # If topic is same unsubscribe+subscribe will execute in the wrong order @@ -245,6 +251,7 @@ async def async_setup_trigger(hass, config, config_entry, discovery_data): payload=config[CONF_PAYLOAD], qos=config[CONF_QOS], remove_signal=remove_signal, + value_template=config[CONF_VALUE_TEMPLATE], ) else: await hass.data[DEVICE_TRIGGERS][discovery_id].update_trigger( @@ -325,6 +332,7 @@ async def async_attach_trigger( topic=None, payload=None, qos=None, + value_template=None, ) return await hass.data[DEVICE_TRIGGERS][discovery_id].add_trigger( action, automation_info diff --git a/homeassistant/components/mqtt/trigger.py b/homeassistant/components/mqtt/trigger.py index 459adabd418..82f7885b85d 100644 --- a/homeassistant/components/mqtt/trigger.py +++ b/homeassistant/components/mqtt/trigger.py @@ -48,11 +48,13 @@ async def async_attach_trigger(hass, config, action, automation_info): template.attach(hass, wanted_payload) if wanted_payload: - wanted_payload = wanted_payload.async_render(variables, limited=True) + wanted_payload = wanted_payload.async_render( + variables, limited=True, parse_result=False + ) template.attach(hass, topic) if isinstance(topic, template.Template): - topic = topic.async_render(variables, limited=True) + topic = topic.async_render(variables, limited=True, parse_result=False) topic = mqtt.util.valid_subscribe_topic(topic) template.attach(hass, value_template) diff --git a/tests/components/mqtt/test_device_trigger.py b/tests/components/mqtt/test_device_trigger.py index f200de6a274..210dac19e0c 100644 --- a/tests/components/mqtt/test_device_trigger.py +++ b/tests/components/mqtt/test_device_trigger.py @@ -290,6 +290,81 @@ async def test_if_fires_on_mqtt_message(hass, device_reg, calls, mqtt_mock): assert calls[1].data["some"] == "long_press" +async def test_if_fires_on_mqtt_message_template(hass, device_reg, calls, mqtt_mock): + """Test triggers firing.""" + data1 = ( + '{ "automation_type":"trigger",' + ' "device":{"identifiers":["0AFFD2"]},' + " \"payload\": \"{{ 'foo_press'|regex_replace('foo', 'short') }}\"," + ' "topic": "foobar/triggers/button{{ sqrt(16)|round }}",' + ' "type": "button_short_press",' + ' "subtype": "button_1",' + ' "value_template": "{{ value_json.button }}"}' + ) + data2 = ( + '{ "automation_type":"trigger",' + ' "device":{"identifiers":["0AFFD2"]},' + " \"payload\": \"{{ 'foo_press'|regex_replace('foo', 'long') }}\"," + ' "topic": "foobar/triggers/button{{ sqrt(16)|round }}",' + ' "type": "button_long_press",' + ' "subtype": "button_2",' + ' "value_template": "{{ value_json.button }}"}' + ) + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data1) + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla2/config", data2) + await hass.async_block_till_done() + device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}) + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": device_entry.id, + "discovery_id": "bla1", + "type": "button_short_press", + "subtype": "button_1", + }, + "action": { + "service": "test.automation", + "data_template": {"some": ("short_press")}, + }, + }, + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": device_entry.id, + "discovery_id": "bla2", + "type": "button_1", + "subtype": "button_long_press", + }, + "action": { + "service": "test.automation", + "data_template": {"some": ("long_press")}, + }, + }, + ] + }, + ) + + # Fake short press. + async_fire_mqtt_message(hass, "foobar/triggers/button4", '{"button":"short_press"}') + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data["some"] == "short_press" + + # Fake long press. + async_fire_mqtt_message(hass, "foobar/triggers/button4", '{"button":"long_press"}') + await hass.async_block_till_done() + assert len(calls) == 2 + assert calls[1].data["some"] == "long_press" + + async def test_if_fires_on_mqtt_message_late_discover( hass, device_reg, calls, mqtt_mock ): diff --git a/tests/components/mqtt/test_trigger.py b/tests/components/mqtt/test_trigger.py index 23078b9ba23..d0a86e08655 100644 --- a/tests/components/mqtt/test_trigger.py +++ b/tests/components/mqtt/test_trigger.py @@ -81,6 +81,31 @@ async def test_if_fires_on_topic_and_payload_match(hass, calls): assert len(calls) == 1 +async def test_if_fires_on_topic_and_payload_match2(hass, calls): + """Test if message is fired on topic and payload match. + + Make sure a payload which would render as a non string can still be matched. + """ + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "mqtt", + "topic": "test-topic", + "payload": "0", + }, + "action": {"service": "test.automation"}, + } + }, + ) + + async_fire_mqtt_message(hass, "test-topic", "0") + await hass.async_block_till_done() + assert len(calls) == 1 + + async def test_if_fires_on_templated_topic_and_payload_match(hass, calls): """Test if message is fired on templated topic and payload match.""" assert await async_setup_component( From 72b82449d885323d707513c15bdeaa9d955fc899 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 28 Feb 2021 21:20:09 +0100 Subject: [PATCH 065/831] Update MQTT device triggers with support for templates (#47142) From 1c9a9be197dae67f0428d2599fa47f882cc5ef81 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sun, 28 Feb 2021 21:25:40 +0100 Subject: [PATCH 066/831] Fix Xiaomi Miio discovery (#47134) --- .../components/xiaomi_miio/config_flow.py | 17 +++++++---- .../components/xiaomi_miio/strings.json | 28 +++++++++---------- .../xiaomi_miio/test_config_flow.py | 14 ++++------ 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py index c9c363b61eb..9eaf4c1effa 100644 --- a/homeassistant/components/xiaomi_miio/config_flow.py +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -1,5 +1,6 @@ """Config flow to configure Xiaomi Miio.""" import logging +from re import search import voluptuous as vol @@ -24,7 +25,6 @@ from .device import ConnectXiaomiDevice _LOGGER = logging.getLogger(__name__) DEFAULT_GATEWAY_NAME = "Xiaomi Gateway" -DEFAULT_DEVICE_NAME = "Xiaomi Device" DEVICE_SETTINGS = { vol.Required(CONF_TOKEN): vol.All(str, vol.Length(min=32, max=32)), @@ -57,14 +57,21 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): name = discovery_info.get("name") self.host = discovery_info.get("host") self.mac = discovery_info.get("properties", {}).get("mac") + if self.mac is None: + poch = discovery_info.get("properties", {}).get("poch", "") + result = search(r"mac=\w+", poch) + if result is not None: + self.mac = result.group(0).split("=")[1] if not name or not self.host or not self.mac: return self.async_abort(reason="not_xiaomi_miio") + self.mac = format_mac(self.mac) + # Check which device is discovered. for gateway_model in MODELS_GATEWAY: if name.startswith(gateway_model.replace(".", "-")): - unique_id = format_mac(self.mac) + unique_id = self.mac await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured({CONF_HOST: self.host}) @@ -76,12 +83,12 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): for device_model in MODELS_ALL_DEVICES: if name.startswith(device_model.replace(".", "-")): - unique_id = format_mac(self.mac) + unique_id = self.mac await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured({CONF_HOST: self.host}) self.context.update( - {"title_placeholders": {"name": f"Miio Device {self.host}"}} + {"title_placeholders": {"name": f"{device_model} {self.host}"}} ) return await self.async_step_device() @@ -133,7 +140,7 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) # Setup all other Miio Devices - name = user_input.get(CONF_NAME, DEFAULT_DEVICE_NAME) + name = user_input.get(CONF_NAME, model) for device_model in MODELS_ALL_DEVICES: if model.startswith(device_model): diff --git a/homeassistant/components/xiaomi_miio/strings.json b/homeassistant/components/xiaomi_miio/strings.json index 90710baebca..e3d9376bc31 100644 --- a/homeassistant/components/xiaomi_miio/strings.json +++ b/homeassistant/components/xiaomi_miio/strings.json @@ -1,24 +1,24 @@ { "config": { - "flow_title": "Xiaomi Miio: {name}", - "step": { - "device": { - "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway", - "description": "You will need the 32 character [%key:common::config_flow::data::api_token%], see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions. Please note, that this [%key:common::config_flow::data::api_token%] is different from the key used by the Xiaomi Aqara integration.", - "data": { - "host": "[%key:common::config_flow::data::ip%]", - "token": "[%key:common::config_flow::data::api_token%]", - "model": "Device model (Optional)" - } - } + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]" }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "unknown_device": "The device model is not known, not able to setup the device using config flow." }, - "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]" + "flow_title": "Xiaomi Miio: {name}", + "step": { + "device": { + "data": { + "host": "[%key:common::config_flow::data::ip%]", + "model": "Device model (Optional)", + "token": "[%key:common::config_flow::data::api_token%]" + }, + "description": "You will need the 32 character [%key:common::config_flow::data::api_token%], see https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instructions. Please note, that this [%key:common::config_flow::data::api_token%] is different from the key used by the Xiaomi Aqara integration.", + "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway" + } } } } diff --git a/tests/components/xiaomi_miio/test_config_flow.py b/tests/components/xiaomi_miio/test_config_flow.py index f4f7b5e2b46..f53fe6e40b4 100644 --- a/tests/components/xiaomi_miio/test_config_flow.py +++ b/tests/components/xiaomi_miio/test_config_flow.py @@ -6,10 +6,7 @@ from miio import DeviceException from homeassistant import config_entries from homeassistant.components import zeroconf from homeassistant.components.xiaomi_miio import const -from homeassistant.components.xiaomi_miio.config_flow import ( - DEFAULT_DEVICE_NAME, - DEFAULT_GATEWAY_NAME, -) +from homeassistant.components.xiaomi_miio.config_flow import DEFAULT_GATEWAY_NAME from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN ZEROCONF_NAME = "name" @@ -21,6 +18,7 @@ TEST_TOKEN = "12345678901234567890123456789012" TEST_NAME = "Test_Gateway" TEST_MODEL = const.MODELS_GATEWAY[0] TEST_MAC = "ab:cd:ef:gh:ij:kl" +TEST_MAC_DEVICE = "abcdefghijkl" TEST_GATEWAY_ID = TEST_MAC TEST_HARDWARE_VERSION = "AB123" TEST_FIRMWARE_VERSION = "1.2.3_456" @@ -294,7 +292,7 @@ async def test_config_flow_step_device_manual_model_succes(hass): ) assert result["type"] == "create_entry" - assert result["title"] == DEFAULT_DEVICE_NAME + assert result["title"] == overwrite_model assert result["data"] == { const.CONF_FLOW_TYPE: const.CONF_DEVICE, CONF_HOST: TEST_HOST, @@ -328,7 +326,7 @@ async def config_flow_device_success(hass, model_to_test): ) assert result["type"] == "create_entry" - assert result["title"] == DEFAULT_DEVICE_NAME + assert result["title"] == model_to_test assert result["data"] == { const.CONF_FLOW_TYPE: const.CONF_DEVICE, CONF_HOST: TEST_HOST, @@ -346,7 +344,7 @@ async def zeroconf_device_success(hass, zeroconf_name_to_test, model_to_test): data={ zeroconf.ATTR_HOST: TEST_HOST, ZEROCONF_NAME: zeroconf_name_to_test, - ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC}, + ZEROCONF_PROP: {"poch": f"0:mac={TEST_MAC_DEVICE}\x00"}, }, ) @@ -368,7 +366,7 @@ async def zeroconf_device_success(hass, zeroconf_name_to_test, model_to_test): ) assert result["type"] == "create_entry" - assert result["title"] == DEFAULT_DEVICE_NAME + assert result["title"] == model_to_test assert result["data"] == { const.CONF_FLOW_TYPE: const.CONF_DEVICE, CONF_HOST: TEST_HOST, From b8c8fe0820dd37083430a727b85375dec0e162e2 Mon Sep 17 00:00:00 2001 From: AJ Schmidt Date: Sun, 28 Feb 2021 18:21:04 -0500 Subject: [PATCH 067/831] Update AlarmDecoder dependency (#46841) --- homeassistant/components/alarmdecoder/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/alarmdecoder/manifest.json b/homeassistant/components/alarmdecoder/manifest.json index 1697858718d..c3e72e407c2 100644 --- a/homeassistant/components/alarmdecoder/manifest.json +++ b/homeassistant/components/alarmdecoder/manifest.json @@ -2,7 +2,7 @@ "domain": "alarmdecoder", "name": "AlarmDecoder", "documentation": "https://www.home-assistant.io/integrations/alarmdecoder", - "requirements": ["adext==0.3"], + "requirements": ["adext==0.4.1"], "codeowners": ["@ajschmidt8"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 673cbf4551d..277d42022e2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -108,7 +108,7 @@ adafruit-circuitpython-mcp230xx==2.2.2 adb-shell[async]==0.2.1 # homeassistant.components.alarmdecoder -adext==0.3 +adext==0.4.1 # homeassistant.components.adguard adguardhome==0.4.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 57d3dcf739b..9ace25468ef 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -48,7 +48,7 @@ accuweather==0.1.0 adb-shell[async]==0.2.1 # homeassistant.components.alarmdecoder -adext==0.3 +adext==0.4.1 # homeassistant.components.adguard adguardhome==0.4.2 From bab66a5cb968bcc7c6ef7787c9dc645885569429 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Sun, 28 Feb 2021 15:48:30 -0800 Subject: [PATCH 068/831] Remove turn_on and turn_off from SmartTub pump switches (#47184) --- homeassistant/components/smarttub/switch.py | 14 ------------- tests/components/smarttub/test_switch.py | 22 +++++++-------------- 2 files changed, 7 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/smarttub/switch.py b/homeassistant/components/smarttub/switch.py index 7e4c83f6feb..736611e5411 100644 --- a/homeassistant/components/smarttub/switch.py +++ b/homeassistant/components/smarttub/switch.py @@ -61,20 +61,6 @@ class SmartTubPump(SmartTubEntity, SwitchEntity): """Return True if the pump is on.""" return self.pump.state != SpaPump.PumpState.OFF - async def async_turn_on(self, **kwargs) -> None: - """Turn the pump on.""" - - # the API only supports toggling - if not self.is_on: - await self.async_toggle() - - async def async_turn_off(self, **kwargs) -> None: - """Turn the pump off.""" - - # the API only supports toggling - if self.is_on: - await self.async_toggle() - async def async_toggle(self, **kwargs) -> None: """Toggle the pump on or off.""" async with async_timeout.timeout(API_TIMEOUT): diff --git a/tests/components/smarttub/test_switch.py b/tests/components/smarttub/test_switch.py index 8750bf79747..1ef84631196 100644 --- a/tests/components/smarttub/test_switch.py +++ b/tests/components/smarttub/test_switch.py @@ -18,21 +18,13 @@ async def test_pumps(spa, setup_entry, hass): assert state is not None if pump.state == SpaPump.PumpState.OFF: assert state.state == "off" - - await hass.services.async_call( - "switch", - "turn_on", - {"entity_id": entity_id}, - blocking=True, - ) - pump.toggle.assert_called() else: assert state.state == "on" - await hass.services.async_call( - "switch", - "turn_off", - {"entity_id": entity_id}, - blocking=True, - ) - pump.toggle.assert_called() + await hass.services.async_call( + "switch", + "toggle", + {"entity_id": entity_id}, + blocking=True, + ) + pump.toggle.assert_called() From c6223873f42df945f3ad792d9826784bd0c67272 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Sun, 28 Feb 2021 15:49:25 -0800 Subject: [PATCH 069/831] Move SmartTub climate constants to module level (#47190) --- homeassistant/components/smarttub/climate.py | 34 ++++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/smarttub/climate.py b/homeassistant/components/smarttub/climate.py index 66c03a22e1f..3e18bc12672 100644 --- a/homeassistant/components/smarttub/climate.py +++ b/homeassistant/components/smarttub/climate.py @@ -23,6 +23,19 @@ _LOGGER = logging.getLogger(__name__) PRESET_DAY = "day" +PRESET_MODES = { + Spa.HeatMode.AUTO: PRESET_NONE, + Spa.HeatMode.ECONOMY: PRESET_ECO, + Spa.HeatMode.DAY: PRESET_DAY, +} + +HEAT_MODES = {v: k for k, v in PRESET_MODES.items()} + +HVAC_ACTIONS = { + "OFF": CURRENT_HVAC_IDLE, + "ON": CURRENT_HVAC_HEAT, +} + async def async_setup_entry(hass, entry, async_add_entities): """Set up climate entity for the thermostat in the tub.""" @@ -39,19 +52,6 @@ async def async_setup_entry(hass, entry, async_add_entities): class SmartTubThermostat(SmartTubEntity, ClimateEntity): """The target water temperature for the spa.""" - PRESET_MODES = { - Spa.HeatMode.AUTO: PRESET_NONE, - Spa.HeatMode.ECONOMY: PRESET_ECO, - Spa.HeatMode.DAY: PRESET_DAY, - } - - HEAT_MODES = {v: k for k, v in PRESET_MODES.items()} - - HVAC_ACTIONS = { - "OFF": CURRENT_HVAC_IDLE, - "ON": CURRENT_HVAC_HEAT, - } - def __init__(self, coordinator, spa): """Initialize the entity.""" super().__init__(coordinator, spa, "thermostat") @@ -64,7 +64,7 @@ class SmartTubThermostat(SmartTubEntity, ClimateEntity): @property def hvac_action(self): """Return the current running hvac operation.""" - return self.HVAC_ACTIONS.get(self.spa_status.heater) + return HVAC_ACTIONS.get(self.spa_status.heater) @property def hvac_modes(self): @@ -112,12 +112,12 @@ class SmartTubThermostat(SmartTubEntity, ClimateEntity): @property def preset_mode(self): """Return the current preset mode.""" - return self.PRESET_MODES[self.spa_status.heat_mode] + return PRESET_MODES[self.spa_status.heat_mode] @property def preset_modes(self): """Return the available preset modes.""" - return list(self.PRESET_MODES.values()) + return list(PRESET_MODES.values()) @property def current_temperature(self): @@ -137,6 +137,6 @@ class SmartTubThermostat(SmartTubEntity, ClimateEntity): async def async_set_preset_mode(self, preset_mode: str): """Activate the specified preset mode.""" - heat_mode = self.HEAT_MODES[preset_mode] + heat_mode = HEAT_MODES[preset_mode] await self.spa.set_heat_mode(heat_mode) await self.coordinator.async_refresh() From 1d7660f0716b4212a80a853da8e7b4cddaa4cef3 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Sun, 28 Feb 2021 15:51:43 -0800 Subject: [PATCH 070/831] Explain why should_pool is True initially for wemo (#47191) --- homeassistant/components/wemo/entity.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/wemo/entity.py b/homeassistant/components/wemo/entity.py index 4fac786af9a..d9b4b0548f8 100644 --- a/homeassistant/components/wemo/entity.py +++ b/homeassistant/components/wemo/entity.py @@ -142,8 +142,15 @@ class WemoSubscriptionEntity(WemoEntity): def should_poll(self) -> bool: """Return True if the the device requires local polling, False otherwise. + It is desirable to allow devices to enter periods of polling when the + callback subscription (device push) is not working. To work with the + entity platform polling logic, this entity needs to report True for + should_poll initially. That is required to cause the entity platform + logic to start the polling task (see the discussion in #47182). + Polling can be disabled if three conditions are met: - 1. The device has polled to get the initial state (self._has_polled). + 1. The device has polled to get the initial state (self._has_polled) and + to satisfy the entity platform constraint mentioned above. 2. The polling was successful and the device is in a healthy state (self.available). 3. The pywemo subscription registry reports that there is an active From 277c3cb6616d48a6ab9b00ae98ecb34f71ea8544 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Sun, 28 Feb 2021 15:53:57 -0800 Subject: [PATCH 071/831] Cleanup SmartTub filtration cycles (#47192) --- homeassistant/components/smarttub/sensor.py | 8 +++++--- tests/components/smarttub/test_sensor.py | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/smarttub/sensor.py b/homeassistant/components/smarttub/sensor.py index be3d60c0241..44f09989a99 100644 --- a/homeassistant/components/smarttub/sensor.py +++ b/homeassistant/components/smarttub/sensor.py @@ -7,9 +7,11 @@ from .entity import SmartTubSensorBase _LOGGER = logging.getLogger(__name__) +# the desired duration, in hours, of the cycle ATTR_DURATION = "duration" -ATTR_LAST_UPDATED = "last_updated" +ATTR_CYCLE_LAST_UPDATED = "cycle_last_updated" ATTR_MODE = "mode" +# the hour of the day at which to start the cycle (0-23) ATTR_START_HOUR = "start_hour" @@ -73,7 +75,7 @@ class SmartTubPrimaryFiltrationCycle(SmartTubSensor): state = self._state return { ATTR_DURATION: state.duration, - ATTR_LAST_UPDATED: state.last_updated.isoformat(), + ATTR_CYCLE_LAST_UPDATED: state.last_updated.isoformat(), ATTR_MODE: state.mode.name.lower(), ATTR_START_HOUR: state.start_hour, } @@ -98,6 +100,6 @@ class SmartTubSecondaryFiltrationCycle(SmartTubSensor): """Return the state attributes.""" state = self._state return { - ATTR_LAST_UPDATED: state.last_updated.isoformat(), + ATTR_CYCLE_LAST_UPDATED: state.last_updated.isoformat(), ATTR_MODE: state.mode.name.lower(), } diff --git a/tests/components/smarttub/test_sensor.py b/tests/components/smarttub/test_sensor.py index 5b0163daf26..2d52d6d07a5 100644 --- a/tests/components/smarttub/test_sensor.py +++ b/tests/components/smarttub/test_sensor.py @@ -47,7 +47,7 @@ async def test_sensors(spa, setup_entry, hass): assert state is not None assert state.state == "inactive" assert state.attributes["duration"] == 4 - assert state.attributes["last_updated"] is not None + assert state.attributes["cycle_last_updated"] is not None assert state.attributes["mode"] == "normal" assert state.attributes["start_hour"] == 2 @@ -55,5 +55,5 @@ async def test_sensors(spa, setup_entry, hass): state = hass.states.get(entity_id) assert state is not None assert state.state == "inactive" - assert state.attributes["last_updated"] is not None + assert state.attributes["cycle_last_updated"] is not None assert state.attributes["mode"] == "away" From 44ed6cda40639faf12f70ead990bc45e8e651f02 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Mon, 1 Mar 2021 00:09:01 +0000 Subject: [PATCH 072/831] [ci skip] Translation update --- .../components/aemet/translations/de.json | 19 ++++++++++++ .../components/airly/translations/ca.json | 4 ++- .../components/airly/translations/en.json | 4 ++- .../components/airly/translations/et.json | 4 ++- .../components/airly/translations/ru.json | 4 ++- .../airly/translations/zh-Hant.json | 4 ++- .../components/asuswrt/translations/de.json | 24 +++++++++++++++ .../awair/translations/zh-Hant.json | 10 +++---- .../blink/translations/zh-Hant.json | 2 +- .../components/bond/translations/zh-Hant.json | 4 +-- .../components/climacell/translations/de.json | 19 ++++++++++++ .../components/climacell/translations/he.json | 17 +++++++++++ .../cloudflare/translations/zh-Hant.json | 4 +-- .../components/econet/translations/de.json | 21 ++++++++++++++ .../faa_delays/translations/de.json | 8 +++++ .../faa_delays/translations/he.json | 18 ++++++++++++ .../fireservicerota/translations/zh-Hant.json | 2 +- .../components/fritzbox/translations/de.json | 2 +- .../components/habitica/translations/de.json | 17 +++++++++++ .../components/homekit/translations/he.json | 10 +++++++ .../huisbaasje/translations/de.json | 21 ++++++++++++++ .../hyperion/translations/zh-Hant.json | 16 +++++----- .../juicenet/translations/zh-Hant.json | 4 +-- .../keenetic_ndms2/translations/de.json | 21 ++++++++++++++ .../components/kmtronic/translations/de.json | 21 ++++++++++++++ .../components/litejet/translations/de.json | 14 +++++++++ .../components/litejet/translations/he.json | 11 +++++++ .../litterrobot/translations/de.json | 20 +++++++++++++ .../lutron_caseta/translations/de.json | 7 +++++ .../components/lyric/translations/de.json | 16 ++++++++++ .../components/mazda/translations/de.json | 29 +++++++++++++++++++ .../media_player/translations/de.json | 7 +++++ .../melcloud/translations/zh-Hant.json | 2 +- .../components/mullvad/translations/de.json | 21 ++++++++++++++ .../components/mullvad/translations/he.json | 21 ++++++++++++++ .../components/mysensors/translations/de.json | 16 ++++++++++ .../components/netatmo/translations/he.json | 11 +++++++ .../nightscout/translations/et.json | 2 +- .../components/nuki/translations/de.json | 18 ++++++++++++ .../components/nuki/translations/zh-Hant.json | 2 +- .../philips_js/translations/de.json | 20 +++++++++++++ .../philips_js/translations/he.json | 7 +++++ .../philips_js/translations/zh-Hant.json | 2 ++ .../components/plaato/translations/de.json | 1 + .../plaato/translations/zh-Hant.json | 8 ++--- .../components/plex/translations/zh-Hant.json | 8 ++--- .../point/translations/zh-Hant.json | 2 +- .../components/powerwall/translations/de.json | 7 +++-- .../components/powerwall/translations/et.json | 2 +- .../translations/de.json | 20 +++++++++++++ .../components/roku/translations/de.json | 1 + .../simplisafe/translations/zh-Hant.json | 2 +- .../smartthings/translations/et.json | 2 +- .../smartthings/translations/zh-Hant.json | 12 ++++---- .../components/smarttub/translations/de.json | 20 +++++++++++++ .../components/subaru/translations/de.json | 28 ++++++++++++++++++ .../components/tesla/translations/de.json | 4 +++ .../tibber/translations/zh-Hant.json | 6 ++-- .../totalconnect/translations/de.json | 11 ++++++- .../components/tuya/translations/et.json | 4 +-- .../components/unifi/translations/de.json | 4 ++- .../vilfo/translations/zh-Hant.json | 4 +-- .../vizio/translations/zh-Hant.json | 6 ++-- .../xiaomi_miio/translations/de.json | 7 +++++ .../xiaomi_miio/translations/en.json | 2 +- .../xiaomi_miio/translations/zh-Hant.json | 8 ++--- .../components/zwave_js/translations/de.json | 14 ++++++++- 67 files changed, 621 insertions(+), 68 deletions(-) create mode 100644 homeassistant/components/aemet/translations/de.json create mode 100644 homeassistant/components/asuswrt/translations/de.json create mode 100644 homeassistant/components/climacell/translations/de.json create mode 100644 homeassistant/components/climacell/translations/he.json create mode 100644 homeassistant/components/econet/translations/de.json create mode 100644 homeassistant/components/faa_delays/translations/de.json create mode 100644 homeassistant/components/faa_delays/translations/he.json create mode 100644 homeassistant/components/habitica/translations/de.json create mode 100644 homeassistant/components/huisbaasje/translations/de.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/de.json create mode 100644 homeassistant/components/kmtronic/translations/de.json create mode 100644 homeassistant/components/litejet/translations/de.json create mode 100644 homeassistant/components/litejet/translations/he.json create mode 100644 homeassistant/components/litterrobot/translations/de.json create mode 100644 homeassistant/components/lyric/translations/de.json create mode 100644 homeassistant/components/mazda/translations/de.json create mode 100644 homeassistant/components/mullvad/translations/de.json create mode 100644 homeassistant/components/mullvad/translations/he.json create mode 100644 homeassistant/components/mysensors/translations/de.json create mode 100644 homeassistant/components/netatmo/translations/he.json create mode 100644 homeassistant/components/nuki/translations/de.json create mode 100644 homeassistant/components/philips_js/translations/de.json create mode 100644 homeassistant/components/philips_js/translations/he.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/de.json create mode 100644 homeassistant/components/smarttub/translations/de.json create mode 100644 homeassistant/components/subaru/translations/de.json diff --git a/homeassistant/components/aemet/translations/de.json b/homeassistant/components/aemet/translations/de.json new file mode 100644 index 00000000000..d7254aea92f --- /dev/null +++ b/homeassistant/components/aemet/translations/de.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Standort ist bereits konfiguriert" + }, + "error": { + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel" + }, + "step": { + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "latitude": "Breitengrad", + "longitude": "L\u00e4ngengrad" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airly/translations/ca.json b/homeassistant/components/airly/translations/ca.json index 95400de23b4..e76cec94f4c 100644 --- a/homeassistant/components/airly/translations/ca.json +++ b/homeassistant/components/airly/translations/ca.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "Servidor d'Airly accessible" + "can_reach_server": "Servidor d'Airly accessible", + "requests_per_day": "Sol\u00b7licituds per dia permeses", + "requests_remaining": "Sol\u00b7licituds permeses restants" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/en.json b/homeassistant/components/airly/translations/en.json index 720f68f8349..0a5426c87d8 100644 --- a/homeassistant/components/airly/translations/en.json +++ b/homeassistant/components/airly/translations/en.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "Reach Airly server" + "can_reach_server": "Reach Airly server", + "requests_per_day": "Allowed requests per day", + "requests_remaining": "Remaining allowed requests" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/et.json b/homeassistant/components/airly/translations/et.json index 8cbfd138257..c5c9359c67f 100644 --- a/homeassistant/components/airly/translations/et.json +++ b/homeassistant/components/airly/translations/et.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "\u00dchendus Airly serveriga" + "can_reach_server": "\u00dchendus Airly serveriga", + "requests_per_day": "Lubatud taotlusi p\u00e4evas", + "requests_remaining": "J\u00e4\u00e4nud lubatud taotlusi" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/ru.json b/homeassistant/components/airly/translations/ru.json index b1469af787e..41ca90a8c02 100644 --- a/homeassistant/components/airly/translations/ru.json +++ b/homeassistant/components/airly/translations/ru.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "\u0414\u043e\u0441\u0442\u0443\u043f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 Airly" + "can_reach_server": "\u0414\u043e\u0441\u0442\u0443\u043f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 Airly", + "requests_per_day": "\u0420\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432 \u0434\u0435\u043d\u044c", + "requests_remaining": "\u0421\u0447\u0451\u0442\u0447\u0438\u043a \u043e\u0441\u0442\u0430\u0432\u0448\u0438\u0445\u0441\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/zh-Hant.json b/homeassistant/components/airly/translations/zh-Hant.json index 4d60b158c4c..19ef2ae7532 100644 --- a/homeassistant/components/airly/translations/zh-Hant.json +++ b/homeassistant/components/airly/translations/zh-Hant.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "\u9023\u7dda Airly \u4f3a\u670d\u5668" + "can_reach_server": "\u9023\u7dda Airly \u4f3a\u670d\u5668", + "requests_per_day": "\u6bcf\u65e5\u5141\u8a31\u7684\u8acb\u6c42", + "requests_remaining": "\u5176\u9918\u5141\u8a31\u7684\u8acb\u6c42" } } } \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/de.json b/homeassistant/components/asuswrt/translations/de.json new file mode 100644 index 00000000000..433bf17b814 --- /dev/null +++ b/homeassistant/components/asuswrt/translations/de.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_host": "Ung\u00fcltiger Hostname oder IP-Adresse", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "mode": "Modus", + "name": "Name", + "password": "Passwort", + "port": "Port", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/awair/translations/zh-Hant.json b/homeassistant/components/awair/translations/zh-Hant.json index 11fe9ff88b3..0bd7749c65f 100644 --- a/homeassistant/components/awair/translations/zh-Hant.json +++ b/homeassistant/components/awair/translations/zh-Hant.json @@ -6,23 +6,23 @@ "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" }, "error": { - "invalid_access_token": "\u5b58\u53d6\u5bc6\u9470\u7121\u6548", + "invalid_access_token": "\u5b58\u53d6\u6b0a\u6756\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { "reauth": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470", + "access_token": "\u5b58\u53d6\u6b0a\u6756", "email": "\u96fb\u5b50\u90f5\u4ef6" }, - "description": "\u8acb\u91cd\u65b0\u8f38\u5165 Awair \u958b\u767c\u8005\u5b58\u53d6\u5bc6\u9470\u3002" + "description": "\u8acb\u91cd\u65b0\u8f38\u5165 Awair \u958b\u767c\u8005\u5b58\u53d6\u6b0a\u6756\u3002" }, "user": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470", + "access_token": "\u5b58\u53d6\u6b0a\u6756", "email": "\u96fb\u5b50\u90f5\u4ef6" }, - "description": "\u5fc5\u9808\u5148\u8a3b\u518a Awair \u958b\u767c\u8005\u5b58\u53d6\u5bc6\u9470\uff1ahttps://developer.getawair.com/onboard/login" + "description": "\u5fc5\u9808\u5148\u8a3b\u518a Awair \u958b\u767c\u8005\u5b58\u53d6\u6b0a\u6756\uff1ahttps://developer.getawair.com/onboard/login" } } } diff --git a/homeassistant/components/blink/translations/zh-Hant.json b/homeassistant/components/blink/translations/zh-Hant.json index 3d05dc82abc..d2c42bf5531 100644 --- a/homeassistant/components/blink/translations/zh-Hant.json +++ b/homeassistant/components/blink/translations/zh-Hant.json @@ -5,7 +5,7 @@ }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", - "invalid_access_token": "\u5b58\u53d6\u5bc6\u9470\u7121\u6548", + "invalid_access_token": "\u5b58\u53d6\u6b0a\u6756\u7121\u6548", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, diff --git a/homeassistant/components/bond/translations/zh-Hant.json b/homeassistant/components/bond/translations/zh-Hant.json index 1c5327dc662..8bb8e178869 100644 --- a/homeassistant/components/bond/translations/zh-Hant.json +++ b/homeassistant/components/bond/translations/zh-Hant.json @@ -13,13 +13,13 @@ "step": { "confirm": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470" + "access_token": "\u5b58\u53d6\u6b0a\u6756" }, "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f" }, "user": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470", + "access_token": "\u5b58\u53d6\u6b0a\u6756", "host": "\u4e3b\u6a5f\u7aef" } } diff --git a/homeassistant/components/climacell/translations/de.json b/homeassistant/components/climacell/translations/de.json new file mode 100644 index 00000000000..f18197e1cca --- /dev/null +++ b/homeassistant/components/climacell/translations/de.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "latitude": "Breitengrad", + "longitude": "L\u00e4ngengrad", + "name": "Name" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/he.json b/homeassistant/components/climacell/translations/he.json new file mode 100644 index 00000000000..81a4b5c1fce --- /dev/null +++ b/homeassistant/components/climacell/translations/he.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "user": { + "data": { + "api_key": "\u05de\u05e4\u05ea\u05d7 API", + "latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1", + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da", + "name": "\u05e9\u05dd" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloudflare/translations/zh-Hant.json b/homeassistant/components/cloudflare/translations/zh-Hant.json index 1be70def034..d9a05269748 100644 --- a/homeassistant/components/cloudflare/translations/zh-Hant.json +++ b/homeassistant/components/cloudflare/translations/zh-Hant.json @@ -19,9 +19,9 @@ }, "user": { "data": { - "api_token": "API \u5bc6\u9470" + "api_token": "API \u6b0a\u6756" }, - "description": "\u6b64\u6574\u5408\u9700\u8981\u5e33\u865f\u4e2d\u6240\u6709\u5340\u57df Zone:Zone:Read \u8207 Zone:DNS:Edit \u6b0a\u9650 API \u5bc6\u9470\u3002", + "description": "\u6b64\u6574\u5408\u9700\u8981\u5e33\u865f\u4e2d\u6240\u6709\u5340\u57df Zone:Zone:Read \u8207 Zone:DNS:Edit \u6b0a\u9650 API \u6b0a\u6756\u3002", "title": "\u9023\u7dda\u81f3 Cloudflare" }, "zone": { diff --git a/homeassistant/components/econet/translations/de.json b/homeassistant/components/econet/translations/de.json new file mode 100644 index 00000000000..854d61f1790 --- /dev/null +++ b/homeassistant/components/econet/translations/de.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, + "step": { + "user": { + "data": { + "email": "E-Mail", + "password": "Passwort" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/de.json b/homeassistant/components/faa_delays/translations/de.json new file mode 100644 index 00000000000..72b837c862c --- /dev/null +++ b/homeassistant/components/faa_delays/translations/de.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/he.json b/homeassistant/components/faa_delays/translations/he.json new file mode 100644 index 00000000000..af8d410eb18 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/he.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "\u05e0\u05de\u05dc \u05ea\u05e2\u05d5\u05e4\u05d4 \u05d6\u05d4 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8" + }, + "error": { + "cannot_connect": "\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "user": { + "data": { + "id": "\u05e0\u05de\u05dc \u05ea\u05e2\u05d5\u05e4\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fireservicerota/translations/zh-Hant.json b/homeassistant/components/fireservicerota/translations/zh-Hant.json index af3cba40dc6..8e5f4d9f20d 100644 --- a/homeassistant/components/fireservicerota/translations/zh-Hant.json +++ b/homeassistant/components/fireservicerota/translations/zh-Hant.json @@ -15,7 +15,7 @@ "data": { "password": "\u5bc6\u78bc" }, - "description": "\u8a8d\u8b49\u5bc6\u9470\u5df2\u7d93\u5931\u6548\uff0c\u8acb\u767b\u5165\u91cd\u65b0\u65b0\u589e\u3002" + "description": "\u8a8d\u8b49\u6b0a\u6756\u5df2\u7d93\u5931\u6548\uff0c\u8acb\u767b\u5165\u91cd\u65b0\u65b0\u589e\u3002" }, "user": { "data": { diff --git a/homeassistant/components/fritzbox/translations/de.json b/homeassistant/components/fritzbox/translations/de.json index 8e79076bda6..16263722482 100644 --- a/homeassistant/components/fritzbox/translations/de.json +++ b/homeassistant/components/fritzbox/translations/de.json @@ -24,7 +24,7 @@ "password": "Passwort", "username": "Benutzername" }, - "description": "Aktualisiere deine Anmeldeinformationen f\u00fcr {name} ." + "description": "Aktualisiere deine Anmeldeinformationen f\u00fcr {name}." }, "user": { "data": { diff --git a/homeassistant/components/habitica/translations/de.json b/homeassistant/components/habitica/translations/de.json new file mode 100644 index 00000000000..04f985946fb --- /dev/null +++ b/homeassistant/components/habitica/translations/de.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "invalid_credentials": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "url": "URL" + } + } + } + }, + "title": "Habitica" +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/he.json b/homeassistant/components/homekit/translations/he.json index 87ad743dca5..6acebca0ca4 100644 --- a/homeassistant/components/homekit/translations/he.json +++ b/homeassistant/components/homekit/translations/he.json @@ -1,6 +1,16 @@ { "options": { "step": { + "include_exclude": { + "data": { + "mode": "\u05de\u05e6\u05d1" + } + }, + "init": { + "data": { + "mode": "\u05de\u05e6\u05d1" + } + }, "yaml": { "description": "\u05d9\u05e9\u05d5\u05ea \u05d6\u05d5 \u05e0\u05e9\u05dc\u05d8\u05ea \u05d1\u05d0\u05de\u05e6\u05e2\u05d5\u05ea YAML" } diff --git a/homeassistant/components/huisbaasje/translations/de.json b/homeassistant/components/huisbaasje/translations/de.json new file mode 100644 index 00000000000..ca3f90536d4 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/de.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "connection_exception": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unauthenticated_exception": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/zh-Hant.json b/homeassistant/components/hyperion/translations/zh-Hant.json index ed003131bf2..bb8eacd5376 100644 --- a/homeassistant/components/hyperion/translations/zh-Hant.json +++ b/homeassistant/components/hyperion/translations/zh-Hant.json @@ -3,8 +3,8 @@ "abort": { "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", - "auth_new_token_not_granted_error": "\u65b0\u5275\u5bc6\u9470\u672a\u7372\u5f97 Hyperion UI \u6838\u51c6", - "auth_new_token_not_work_error": "\u4f7f\u7528\u65b0\u5275\u5bc6\u9470\u8a8d\u8b49\u5931\u6557", + "auth_new_token_not_granted_error": "\u65b0\u5275\u6b0a\u6756\u672a\u7372\u5f97 Hyperion UI \u6838\u51c6", + "auth_new_token_not_work_error": "\u4f7f\u7528\u65b0\u5275\u6b0a\u6756\u8a8d\u8b49\u5931\u6557", "auth_required_error": "\u7121\u6cd5\u5224\u5b9a\u662f\u5426\u9700\u8981\u9a57\u8b49", "cannot_connect": "\u9023\u7dda\u5931\u6557", "no_id": "Hyperion Ambilight \u5be6\u9ad4\u672a\u56de\u5831\u5176 ID", @@ -12,13 +12,13 @@ }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", - "invalid_access_token": "\u5b58\u53d6\u5bc6\u9470\u7121\u6548" + "invalid_access_token": "\u5b58\u53d6\u6b0a\u6756\u7121\u6548" }, "step": { "auth": { "data": { - "create_token": "\u81ea\u52d5\u65b0\u5275\u5bc6\u9470", - "token": "\u6216\u63d0\u4f9b\u73fe\u6709\u5bc6\u9470" + "create_token": "\u81ea\u52d5\u65b0\u5275\u6b0a\u6756", + "token": "\u6216\u63d0\u4f9b\u73fe\u6709\u6b0a\u6756" }, "description": "\u8a2d\u5b9a Hyperion Ambilight \u4f3a\u670d\u5668\u8a8d\u8b49" }, @@ -27,11 +27,11 @@ "title": "\u78ba\u8a8d\u9644\u52a0 Hyperion Ambilight \u670d\u52d9" }, "create_token": { - "description": "\u9ede\u9078\u4e0b\u65b9 **\u50b3\u9001** \u4ee5\u8acb\u6c42\u65b0\u8a8d\u8b49\u5bc6\u9470\u3002\u5c07\u6703\u91cd\u65b0\u5c0e\u5411\u81f3 Hyperion UI \u4ee5\u6838\u51c6\u8981\u6c42\u3002\u8acb\u78ba\u8a8d\u986f\u793a ID \u70ba \"{auth_id}\"", - "title": "\u81ea\u52d5\u65b0\u5275\u8a8d\u8b49\u5bc6\u9470" + "description": "\u9ede\u9078\u4e0b\u65b9 **\u50b3\u9001** \u4ee5\u8acb\u6c42\u65b0\u8a8d\u8b49\u6b0a\u6756\u3002\u5c07\u6703\u91cd\u65b0\u5c0e\u5411\u81f3 Hyperion UI \u4ee5\u6838\u51c6\u8981\u6c42\u3002\u8acb\u78ba\u8a8d\u986f\u793a ID \u70ba \"{auth_id}\"", + "title": "\u81ea\u52d5\u65b0\u5275\u8a8d\u8b49\u6b0a\u6756" }, "create_token_external": { - "title": "\u63a5\u53d7 Hyperion UI \u4e2d\u7684\u65b0\u5bc6\u9470" + "title": "\u63a5\u53d7 Hyperion UI \u4e2d\u7684\u65b0\u6b0a\u6756" }, "user": { "data": { diff --git a/homeassistant/components/juicenet/translations/zh-Hant.json b/homeassistant/components/juicenet/translations/zh-Hant.json index 815edb1fb27..f310babfd80 100644 --- a/homeassistant/components/juicenet/translations/zh-Hant.json +++ b/homeassistant/components/juicenet/translations/zh-Hant.json @@ -11,9 +11,9 @@ "step": { "user": { "data": { - "api_token": "API \u5bc6\u9470" + "api_token": "API \u6b0a\u6756" }, - "description": "\u5c07\u9700\u8981\u7531 https://home.juice.net/Manage \u53d6\u5f97 API \u5bc6\u9470\u3002", + "description": "\u5c07\u9700\u8981\u7531 https://home.juice.net/Manage \u53d6\u5f97 API \u6b0a\u6756\u3002", "title": "\u9023\u7dda\u81f3 JuiceNet" } } diff --git a/homeassistant/components/keenetic_ndms2/translations/de.json b/homeassistant/components/keenetic_ndms2/translations/de.json new file mode 100644 index 00000000000..71ce0154639 --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/de.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Name", + "password": "Passwort", + "port": "Port", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/de.json b/homeassistant/components/kmtronic/translations/de.json new file mode 100644 index 00000000000..625c7372347 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/de.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/de.json b/homeassistant/components/litejet/translations/de.json new file mode 100644 index 00000000000..492314e5cc6 --- /dev/null +++ b/homeassistant/components/litejet/translations/de.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/he.json b/homeassistant/components/litejet/translations/he.json new file mode 100644 index 00000000000..a06c89f1d2a --- /dev/null +++ b/homeassistant/components/litejet/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "\u05e4\u05d5\u05e8\u05d8" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/de.json b/homeassistant/components/litterrobot/translations/de.json new file mode 100644 index 00000000000..0eee2778d05 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/de.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/de.json b/homeassistant/components/lutron_caseta/translations/de.json index 13f8c6bd800..b6aacf2d0ef 100644 --- a/homeassistant/components/lutron_caseta/translations/de.json +++ b/homeassistant/components/lutron_caseta/translations/de.json @@ -6,6 +6,13 @@ }, "error": { "cannot_connect": "Verbindung fehlgeschlagen" + }, + "step": { + "user": { + "data": { + "host": "Host" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/de.json b/homeassistant/components/lyric/translations/de.json new file mode 100644 index 00000000000..5bab6ed132b --- /dev/null +++ b/homeassistant/components/lyric/translations/de.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen." + }, + "create_entry": { + "default": "Erfolgreich authentifiziert" + }, + "step": { + "pick_implementation": { + "title": "W\u00e4hle die Authentifizierungsmethode" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/de.json b/homeassistant/components/mazda/translations/de.json new file mode 100644 index 00000000000..4e23becb8af --- /dev/null +++ b/homeassistant/components/mazda/translations/de.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "reauth": { + "data": { + "email": "E-Mail", + "password": "Passwort", + "region": "Region" + } + }, + "user": { + "data": { + "email": "E-Mail", + "password": "Passwort", + "region": "Region" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/media_player/translations/de.json b/homeassistant/components/media_player/translations/de.json index a7f25fa9d7c..4909c85d053 100644 --- a/homeassistant/components/media_player/translations/de.json +++ b/homeassistant/components/media_player/translations/de.json @@ -6,6 +6,13 @@ "is_on": "{entity_name} ist eingeschaltet", "is_paused": "{entity_name} ist pausiert", "is_playing": "{entity_name} spielt" + }, + "trigger_type": { + "idle": "{entity_name} wird inaktiv", + "paused": "{entity_name} ist angehalten", + "playing": "{entity_name} beginnt zu spielen", + "turned_off": "{entity_name} ausgeschaltet", + "turned_on": "{entity_name} eingeschaltet" } }, "state": { diff --git a/homeassistant/components/melcloud/translations/zh-Hant.json b/homeassistant/components/melcloud/translations/zh-Hant.json index 9947b5ac990..27f4d0e5d7f 100644 --- a/homeassistant/components/melcloud/translations/zh-Hant.json +++ b/homeassistant/components/melcloud/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u5df2\u4f7f\u7528\u6b64\u90f5\u4ef6\u8a2d\u5b9a MELCloud \u6574\u5408\u3002\u5b58\u53d6\u5bc6\u9470\u5df2\u66f4\u65b0\u3002" + "already_configured": "\u5df2\u4f7f\u7528\u6b64\u90f5\u4ef6\u8a2d\u5b9a MELCloud \u6574\u5408\u3002\u5b58\u53d6\u6b0a\u6756\u5df2\u66f4\u65b0\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/mullvad/translations/de.json b/homeassistant/components/mullvad/translations/de.json new file mode 100644 index 00000000000..625c7372347 --- /dev/null +++ b/homeassistant/components/mullvad/translations/de.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/he.json b/homeassistant/components/mullvad/translations/he.json new file mode 100644 index 00000000000..7f60f15d598 --- /dev/null +++ b/homeassistant/components/mullvad/translations/he.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u05d4\u05de\u05db\u05e9\u05d9\u05e8 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8" + }, + "error": { + "cannot_connect": "\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", + "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05ea\u05e7\u05d9\u05df", + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "user": { + "data": { + "host": "\u05de\u05d0\u05e8\u05d7", + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/de.json b/homeassistant/components/mysensors/translations/de.json new file mode 100644 index 00000000000..189226f29d5 --- /dev/null +++ b/homeassistant/components/mysensors/translations/de.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "error": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/he.json b/homeassistant/components/netatmo/translations/he.json new file mode 100644 index 00000000000..54bef84c30a --- /dev/null +++ b/homeassistant/components/netatmo/translations/he.json @@ -0,0 +1,11 @@ +{ + "device_automation": { + "trigger_type": { + "animal": "\u05d6\u05d9\u05d4\u05d4 \u05d1\u05e2\u05dc-\u05d7\u05d9\u05d9\u05dd", + "human": "\u05d6\u05d9\u05d4\u05d4 \u05d0\u05d3\u05dd", + "movement": "\u05d6\u05d9\u05d4\u05d4 \u05ea\u05e0\u05d5\u05e2\u05d4", + "turned_off": "\u05db\u05d1\u05d4", + "turned_on": "\u05e0\u05d3\u05dc\u05e7" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nightscout/translations/et.json b/homeassistant/components/nightscout/translations/et.json index 361b4789328..0d00cebb6a5 100644 --- a/homeassistant/components/nightscout/translations/et.json +++ b/homeassistant/components/nightscout/translations/et.json @@ -15,7 +15,7 @@ "api_key": "API v\u00f5ti", "url": "" }, - "description": "- URL: NightScout eksemplari aadress. St: https://myhomeassistant.duckdns.org:5423\n - API v\u00f5ti (valikuline): kasuta ainult siis kui teie eksemplar on kaitstud (auth_default_roles! = readable).", + "description": "- URL: NightScout eksemplari aadress. St: https://myhomeassistant.duckdns.org:5423\n - API v\u00f5ti (valikuline): kasuta ainult siis kui eksemplar on kaitstud (auth_default_roles! = readable).", "title": "Sisesta oma Nightscouti serveri teave." } } diff --git a/homeassistant/components/nuki/translations/de.json b/homeassistant/components/nuki/translations/de.json new file mode 100644 index 00000000000..30d7e6865cd --- /dev/null +++ b/homeassistant/components/nuki/translations/de.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "token": "Zugangstoken" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/zh-Hant.json b/homeassistant/components/nuki/translations/zh-Hant.json index 662d7ed6ed9..4bf21552952 100644 --- a/homeassistant/components/nuki/translations/zh-Hant.json +++ b/homeassistant/components/nuki/translations/zh-Hant.json @@ -10,7 +10,7 @@ "data": { "host": "\u4e3b\u6a5f\u7aef", "port": "\u901a\u8a0a\u57e0", - "token": "\u5b58\u53d6\u5bc6\u9470" + "token": "\u5b58\u53d6\u6b0a\u6756" } } } diff --git a/homeassistant/components/philips_js/translations/de.json b/homeassistant/components/philips_js/translations/de.json new file mode 100644 index 00000000000..f59a17bce49 --- /dev/null +++ b/homeassistant/components/philips_js/translations/de.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_pin": "Ung\u00fcltige PIN", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "api_version": "API-Version", + "host": "Host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/he.json b/homeassistant/components/philips_js/translations/he.json new file mode 100644 index 00000000000..04648fe5845 --- /dev/null +++ b/homeassistant/components/philips_js/translations/he.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "pairing_failure": "\u05e6\u05d9\u05de\u05d5\u05d3 \u05e0\u05db\u05e9\u05dc" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/zh-Hant.json b/homeassistant/components/philips_js/translations/zh-Hant.json index af161b6b16b..13bfd52e980 100644 --- a/homeassistant/components/philips_js/translations/zh-Hant.json +++ b/homeassistant/components/philips_js/translations/zh-Hant.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_pin": "PIN \u78bc\u7121\u6548", + "pairing_failure": "\u7121\u6cd5\u914d\u5c0d\uff1a{error_id}", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { diff --git a/homeassistant/components/plaato/translations/de.json b/homeassistant/components/plaato/translations/de.json index 5171baab654..eaf68b507f9 100644 --- a/homeassistant/components/plaato/translations/de.json +++ b/homeassistant/components/plaato/translations/de.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "Konto wurde bereits konfiguriert", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "webhook_not_internet_accessible": "Deine Home Assistant-Instanz muss \u00fcber das Internet erreichbar sein, um Webhook-Nachrichten empfangen zu k\u00f6nnen." }, diff --git a/homeassistant/components/plaato/translations/zh-Hant.json b/homeassistant/components/plaato/translations/zh-Hant.json index 2890c5c31c6..26d7b728771 100644 --- a/homeassistant/components/plaato/translations/zh-Hant.json +++ b/homeassistant/components/plaato/translations/zh-Hant.json @@ -10,16 +10,16 @@ }, "error": { "invalid_webhook_device": "\u6240\u9078\u64c7\u7684\u88dd\u7f6e\u4e0d\u652f\u63f4\u50b3\u9001\u8cc7\u6599\u81f3 Webhook\u3001AirLock \u50c5\u652f\u63f4\u6b64\u985e\u578b", - "no_api_method": "\u9700\u8981\u65b0\u589e\u6388\u6b0a\u5bc6\u9470\u6216\u9078\u64c7 Webhook", - "no_auth_token": "\u9700\u8981\u65b0\u589e\u6388\u6b0a\u5bc6\u9470" + "no_api_method": "\u9700\u8981\u65b0\u589e\u6388\u6b0a\u6b0a\u6756\u6216\u9078\u64c7 Webhook", + "no_auth_token": "\u9700\u8981\u65b0\u589e\u6388\u6b0a\u6b0a\u6756" }, "step": { "api_method": { "data": { - "token": "\u65bc\u6b64\u8cbc\u4e0a\u6388\u6b0a\u5bc6\u9470", + "token": "\u65bc\u6b64\u8cbc\u4e0a\u6388\u6b0a\u6b0a\u6756", "use_webhook": "\u4f7f\u7528 Webhook" }, - "description": "\u9700\u8981\u6388\u6b0a\u5bc6\u8981 `auth_token` \u65b9\u80fd\u67e5\u8a62 API\u3002\u7372\u5f97\u7684\u65b9\u6cd5\u8acb [\u53c3\u95b1](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) \u6559\u5b78\n\n\u9078\u64c7\u7684\u88dd\u7f6e\uff1a**{device_type}** \n\n\u5047\u5982\u9078\u64c7\u5167\u5efa Webhook \u65b9\u6cd5\uff08Airlock \u552f\u4e00\u652f\u63f4\uff09\uff0c\u8acb\u6aa2\u67e5\u4e0b\u65b9\u6838\u9078\u76d2\u4e26\u78ba\u5b9a\u4fdd\u6301\u6388\u6b0a\u5bc6\u9470\u6b04\u4f4d\u7a7a\u767d", + "description": "\u9700\u8981\u6388\u6b0a\u5bc6\u8981 `auth_token` \u65b9\u80fd\u67e5\u8a62 API\u3002\u7372\u5f97\u7684\u65b9\u6cd5\u8acb [\u53c3\u95b1](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) \u6559\u5b78\n\n\u9078\u64c7\u7684\u88dd\u7f6e\uff1a**{device_type}** \n\n\u5047\u5982\u9078\u64c7\u5167\u5efa Webhook \u65b9\u6cd5\uff08Airlock \u552f\u4e00\u652f\u63f4\uff09\uff0c\u8acb\u6aa2\u67e5\u4e0b\u65b9\u6838\u9078\u76d2\u4e26\u78ba\u5b9a\u4fdd\u6301\u6388\u6b0a\u6b0a\u6756\u6b04\u4f4d\u7a7a\u767d", "title": "\u9078\u64c7 API \u65b9\u5f0f" }, "user": { diff --git a/homeassistant/components/plex/translations/zh-Hant.json b/homeassistant/components/plex/translations/zh-Hant.json index 137b953a145..7f19fa0d035 100644 --- a/homeassistant/components/plex/translations/zh-Hant.json +++ b/homeassistant/components/plex/translations/zh-Hant.json @@ -5,12 +5,12 @@ "already_configured": "Plex \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f", - "token_request_timeout": "\u53d6\u5f97\u5bc6\u9470\u903e\u6642", + "token_request_timeout": "\u53d6\u5f97\u6b0a\u6756\u903e\u6642", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "error": { - "faulty_credentials": "\u9a57\u8b49\u5931\u6557\u3001\u78ba\u8a8d\u5bc6\u9470", - "host_or_token": "\u5fc5\u9808\u81f3\u5c11\u63d0\u4f9b\u4e3b\u6a5f\u7aef\u6216\u5bc6\u9470", + "faulty_credentials": "\u9a57\u8b49\u5931\u6557\u3001\u78ba\u8a8d\u6b0a\u6756", + "host_or_token": "\u5fc5\u9808\u81f3\u5c11\u63d0\u4f9b\u4e3b\u6a5f\u7aef\u6216\u6b0a\u6756", "no_servers": "Plex \u5e33\u865f\u672a\u7d81\u5b9a\u4efb\u4f55\u4f3a\u670d\u5668", "not_found": "\u627e\u4e0d\u5230 Plex \u4f3a\u670d\u5668", "ssl_error": "SSL \u8a8d\u8b49\u554f\u984c" @@ -22,7 +22,7 @@ "host": "\u4e3b\u6a5f\u7aef", "port": "\u901a\u8a0a\u57e0", "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", - "token": "\u5bc6\u9470\uff08\u9078\u9805\uff09", + "token": "\u6b0a\u6756\uff08\u9078\u9805\uff09", "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" }, "title": "Plex \u624b\u52d5\u8a2d\u5b9a" diff --git a/homeassistant/components/point/translations/zh-Hant.json b/homeassistant/components/point/translations/zh-Hant.json index 710d363f771..2bb1a8fc239 100644 --- a/homeassistant/components/point/translations/zh-Hant.json +++ b/homeassistant/components/point/translations/zh-Hant.json @@ -13,7 +13,7 @@ }, "error": { "follow_link": "\u8acb\u65bc\u50b3\u9001\u524d\uff0c\u5148\u4f7f\u7528\u9023\u7d50\u4e26\u9032\u884c\u8a8d\u8b49\u3002", - "no_token": "\u5b58\u53d6\u5bc6\u9470\u7121\u6548" + "no_token": "\u5b58\u53d6\u6b0a\u6756\u7121\u6548" }, "step": { "auth": { diff --git a/homeassistant/components/powerwall/translations/de.json b/homeassistant/components/powerwall/translations/de.json index c30286d8744..0ccd42c812b 100644 --- a/homeassistant/components/powerwall/translations/de.json +++ b/homeassistant/components/powerwall/translations/de.json @@ -1,17 +1,20 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "flow_title": "Tesla Powerwall ({ip_address})", "step": { "user": { "data": { - "ip_address": "IP-Adresse" + "ip_address": "IP-Adresse", + "password": "Passwort" }, "title": "Stellen Sie eine Verbindung zur Powerwall her" } diff --git a/homeassistant/components/powerwall/translations/et.json b/homeassistant/components/powerwall/translations/et.json index 4a937029296..8811b870316 100644 --- a/homeassistant/components/powerwall/translations/et.json +++ b/homeassistant/components/powerwall/translations/et.json @@ -8,7 +8,7 @@ "cannot_connect": "\u00dchenduse loomine nurjus. Proovi uuesti", "invalid_auth": "Vigane autentimine", "unknown": "Ootamatu t\u00f5rge", - "wrong_version": "Teie Powerwall kasutab tarkvaraversiooni, mida ei toetata. Kaaluge tarkvara uuendamist v\u00f5i probleemist teavitamist, et see saaks lahendatud." + "wrong_version": "Powerwall kasutab tarkvaraversiooni, mida ei toetata. Kaaluge tarkvara uuendamist v\u00f5i probleemist teavitamist, et see saaks lahendatud." }, "flow_title": "Tesla Powerwall ( {ip_address} )", "step": { diff --git a/homeassistant/components/rituals_perfume_genie/translations/de.json b/homeassistant/components/rituals_perfume_genie/translations/de.json new file mode 100644 index 00000000000..67b8ed59e0b --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/de.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "email": "E-Mail", + "password": "Passwort" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/de.json b/homeassistant/components/roku/translations/de.json index 4bfb3c7503d..152161cb27f 100644 --- a/homeassistant/components/roku/translations/de.json +++ b/homeassistant/components/roku/translations/de.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Das Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "unknown": "Unerwarteter Fehler" }, "error": { diff --git a/homeassistant/components/simplisafe/translations/zh-Hant.json b/homeassistant/components/simplisafe/translations/zh-Hant.json index ad5323d3957..27064ed1055 100644 --- a/homeassistant/components/simplisafe/translations/zh-Hant.json +++ b/homeassistant/components/simplisafe/translations/zh-Hant.json @@ -19,7 +19,7 @@ "data": { "password": "\u5bc6\u78bc" }, - "description": "\u5b58\u53d6\u5bc6\u9470\u5df2\u7d93\u904e\u671f\u6216\u53d6\u6d88\uff0c\u8acb\u8f38\u5165\u5bc6\u78bc\u4ee5\u91cd\u65b0\u9023\u7d50\u5e33\u865f\u3002", + "description": "\u5b58\u53d6\u6b0a\u6756\u5df2\u7d93\u904e\u671f\u6216\u53d6\u6d88\uff0c\u8acb\u8f38\u5165\u5bc6\u78bc\u4ee5\u91cd\u65b0\u9023\u7d50\u5e33\u865f\u3002", "title": "\u91cd\u65b0\u8a8d\u8b49\u6574\u5408" }, "user": { diff --git a/homeassistant/components/smartthings/translations/et.json b/homeassistant/components/smartthings/translations/et.json index 04cd0d70218..18d6076898d 100644 --- a/homeassistant/components/smartthings/translations/et.json +++ b/homeassistant/components/smartthings/translations/et.json @@ -19,7 +19,7 @@ "data": { "access_token": "Juurdep\u00e4\u00e4sut\u00f5end" }, - "description": "Sisesta SmartThingsi [isiklik juurdep\u00e4\u00e4suluba] ( {token_url} ), mis on loodud vastavalt [juhistele] ( {component_url} ). Seda kasutatakse Home Assistanti sidumise loomiseks teie SmartThingsi kontol.", + "description": "Sisesta SmartThingsi [isiklik juurdep\u00e4\u00e4suluba] ( {token_url} ), mis on loodud vastavalt [juhistele] ( {component_url} ). Seda kasutatakse Home Assistanti sidumise loomiseks SmartThingsi kontol.", "title": "Sisesta isiklik juurdep\u00e4\u00e4suluba (PAT)" }, "select_location": { diff --git a/homeassistant/components/smartthings/translations/zh-Hant.json b/homeassistant/components/smartthings/translations/zh-Hant.json index d9a17e46058..88360c75678 100644 --- a/homeassistant/components/smartthings/translations/zh-Hant.json +++ b/homeassistant/components/smartthings/translations/zh-Hant.json @@ -6,9 +6,9 @@ }, "error": { "app_setup_error": "\u7121\u6cd5\u8a2d\u5b9a SmartApp\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002", - "token_forbidden": "\u5bc6\u9470\u4e0d\u5177\u6240\u9700\u7684 OAuth \u7bc4\u570d\u3002", - "token_invalid_format": "\u5bc6\u9470\u5fc5\u9808\u70ba UID/GUID \u683c\u5f0f", - "token_unauthorized": "\u5bc6\u9470\u7121\u6548\u6216\u4e0d\u518d\u5177\u6709\u6388\u6b0a\u3002", + "token_forbidden": "\u6b0a\u6756\u4e0d\u5177\u6240\u9700\u7684 OAuth \u7bc4\u570d\u3002", + "token_invalid_format": "\u6b0a\u6756\u5fc5\u9808\u70ba UID/GUID \u683c\u5f0f", + "token_unauthorized": "\u6b0a\u6756\u7121\u6548\u6216\u4e0d\u518d\u5177\u6709\u6388\u6b0a\u3002", "webhook_error": "SmartThings \u7121\u6cd5\u8a8d\u8b49 Webhook URL\u3002\u8acb\u78ba\u8a8d Webhook URL \u53ef\u7531\u7db2\u8def\u5b58\u53d6\u5f8c\u518d\u8a66\u4e00\u6b21\u3002" }, "step": { @@ -17,10 +17,10 @@ }, "pat": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470" + "access_token": "\u5b58\u53d6\u6b0a\u6756" }, - "description": "\u8acb\u8f38\u5165\u8ddf\u96a8\u6b64[\u6559\u5b78]({component_url}) \u6240\u5efa\u7acb\u7684 SmartThings [\u500b\u4eba\u5b58\u53d6\u5bc6\u9470]({token_url})\u3002\u5c07\u4f7f\u7528 SmartThings \u5e33\u865f\u65b0\u589e Home Assistant \u6574\u5408\u3002", - "title": "\u8f38\u5165\u500b\u4eba\u5b58\u53d6\u5bc6\u9470" + "description": "\u8acb\u8f38\u5165\u8ddf\u96a8\u6b64[\u6559\u5b78]({component_url}) \u6240\u5efa\u7acb\u7684 SmartThings [\u500b\u4eba\u5b58\u53d6\u6b0a\u6756]({token_url})\u3002\u5c07\u4f7f\u7528 SmartThings \u5e33\u865f\u65b0\u589e Home Assistant \u6574\u5408\u3002", + "title": "\u8f38\u5165\u500b\u4eba\u5b58\u53d6\u6b0a\u6756" }, "select_location": { "data": { diff --git a/homeassistant/components/smarttub/translations/de.json b/homeassistant/components/smarttub/translations/de.json new file mode 100644 index 00000000000..fbb3411a6c5 --- /dev/null +++ b/homeassistant/components/smarttub/translations/de.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "email": "E-Mail", + "password": "Passwort" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/de.json b/homeassistant/components/subaru/translations/de.json new file mode 100644 index 00000000000..1c162d61e99 --- /dev/null +++ b/homeassistant/components/subaru/translations/de.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen" + }, + "error": { + "bad_pin_format": "Die PIN sollte 4-stellig sein", + "cannot_connect": "Verbindung fehlgeschlagen", + "incorrect_pin": "Falsche PIN", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + } + }, + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/translations/de.json b/homeassistant/components/tesla/translations/de.json index 558209af411..2fd964fe013 100644 --- a/homeassistant/components/tesla/translations/de.json +++ b/homeassistant/components/tesla/translations/de.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, "error": { "already_configured": "Konto wurde bereits konfiguriert", "cannot_connect": "Verbindung fehlgeschlagen", diff --git a/homeassistant/components/tibber/translations/zh-Hant.json b/homeassistant/components/tibber/translations/zh-Hant.json index ce10615a289..e4d0ec10e23 100644 --- a/homeassistant/components/tibber/translations/zh-Hant.json +++ b/homeassistant/components/tibber/translations/zh-Hant.json @@ -5,15 +5,15 @@ }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", - "invalid_access_token": "\u5b58\u53d6\u5bc6\u9470\u7121\u6548", + "invalid_access_token": "\u5b58\u53d6\u6b0a\u6756\u7121\u6548", "timeout": "\u9023\u7dda\u81f3 Tibber \u903e\u6642" }, "step": { "user": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470" + "access_token": "\u5b58\u53d6\u6b0a\u6756" }, - "description": "\u8f38\u5165\u7531 https://developer.tibber.com/settings/accesstoken \u6240\u7372\u5f97\u7684\u5b58\u53d6\u5bc6\u9470", + "description": "\u8f38\u5165\u7531 https://developer.tibber.com/settings/accesstoken \u6240\u7372\u5f97\u7684\u5b58\u53d6\u6b0a\u6756", "title": "Tibber" } } diff --git a/homeassistant/components/totalconnect/translations/de.json b/homeassistant/components/totalconnect/translations/de.json index 530fef95af2..3fb5bb8f3e1 100644 --- a/homeassistant/components/totalconnect/translations/de.json +++ b/homeassistant/components/totalconnect/translations/de.json @@ -1,12 +1,21 @@ { "config": { "abort": { - "already_configured": "Konto wurde bereits konfiguriert" + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "step": { + "locations": { + "data": { + "location": "Standort" + } + }, + "reauth_confirm": { + "title": "Integration erneut authentifizieren" + }, "user": { "data": { "password": "Passwort", diff --git a/homeassistant/components/tuya/translations/et.json b/homeassistant/components/tuya/translations/et.json index 0fc1297ce7c..48161f552b8 100644 --- a/homeassistant/components/tuya/translations/et.json +++ b/homeassistant/components/tuya/translations/et.json @@ -12,9 +12,9 @@ "step": { "user": { "data": { - "country_code": "Teie konto riigikood (nt 1 USA v\u00f5i 372 Eesti)", + "country_code": "Konto riigikood (nt 1 USA v\u00f5i 372 Eesti)", "password": "Salas\u00f5na", - "platform": "\u00c4pp kus teie konto registreeriti", + "platform": "\u00c4pp kus konto registreeriti", "username": "Kasutajanimi" }, "description": "Sisesta oma Tuya konto andmed.", diff --git a/homeassistant/components/unifi/translations/de.json b/homeassistant/components/unifi/translations/de.json index be38ddf1a4d..05dd66fe56c 100644 --- a/homeassistant/components/unifi/translations/de.json +++ b/homeassistant/components/unifi/translations/de.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "already_configured": "Controller-Site ist bereits konfiguriert" + "already_configured": "Controller-Site ist bereits konfiguriert", + "configuration_updated": "Konfiguration aktualisiert.", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { "faulty_credentials": "Ung\u00fcltige Authentifizierung", diff --git a/homeassistant/components/vilfo/translations/zh-Hant.json b/homeassistant/components/vilfo/translations/zh-Hant.json index b266e25b39c..88180f9bacf 100644 --- a/homeassistant/components/vilfo/translations/zh-Hant.json +++ b/homeassistant/components/vilfo/translations/zh-Hant.json @@ -11,10 +11,10 @@ "step": { "user": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470", + "access_token": "\u5b58\u53d6\u6b0a\u6756", "host": "\u4e3b\u6a5f\u7aef" }, - "description": "\u8a2d\u5b9a Vilfo \u8def\u7531\u5668\u6574\u5408\u3002\u9700\u8981\u8f38\u5165 Vilfo \u8def\u7531\u5668\u4e3b\u6a5f\u540d\u7a31/IP \u4f4d\u5740\u3001API \u5b58\u53d6\u5bc6\u9470\u3002\u5176\u4ed6\u6574\u5408\u76f8\u95dc\u8cc7\u8a0a\uff0c\u8acb\u53c3\u8003\uff1ahttps://www.home-assistant.io/integrations/vilfo", + "description": "\u8a2d\u5b9a Vilfo \u8def\u7531\u5668\u6574\u5408\u3002\u9700\u8981\u8f38\u5165 Vilfo \u8def\u7531\u5668\u4e3b\u6a5f\u540d\u7a31/IP \u4f4d\u5740\u3001API \u5b58\u53d6\u6b0a\u6756\u3002\u5176\u4ed6\u6574\u5408\u76f8\u95dc\u8cc7\u8a0a\uff0c\u8acb\u53c3\u8003\uff1ahttps://www.home-assistant.io/integrations/vilfo", "title": "\u9023\u7dda\u81f3 Vilfo \u8def\u7531\u5668" } } diff --git a/homeassistant/components/vizio/translations/zh-Hant.json b/homeassistant/components/vizio/translations/zh-Hant.json index 257ed829b6a..5f21dd0c2b6 100644 --- a/homeassistant/components/vizio/translations/zh-Hant.json +++ b/homeassistant/components/vizio/translations/zh-Hant.json @@ -23,17 +23,17 @@ "title": "\u914d\u5c0d\u5b8c\u6210" }, "pairing_complete_import": { - "description": "VIZIO SmartCast \u88dd\u7f6e \u5df2\u9023\u7dda\u81f3 Home Assistant\u3002\n\n\u5b58\u53d6\u5bc6\u9470\u70ba '**{access_token}**'\u3002", + "description": "VIZIO SmartCast \u88dd\u7f6e \u5df2\u9023\u7dda\u81f3 Home Assistant\u3002\n\n\u5b58\u53d6\u6b0a\u6756\u70ba '**{access_token}**'\u3002", "title": "\u914d\u5c0d\u5b8c\u6210" }, "user": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470", + "access_token": "\u5b58\u53d6\u6b0a\u6756", "device_class": "\u88dd\u7f6e\u985e\u5225", "host": "\u4e3b\u6a5f\u7aef", "name": "\u540d\u7a31" }, - "description": "\u6b64\u96fb\u8996\u50c5\u9700\u5b58\u53d6\u5bc6\u9470\u5047\u5982\u60a8\u6b63\u5728\u8a2d\u5b9a\u96fb\u8996\u3001\u5c1a\u672a\u53d6\u5f97\u5b58\u53d6\u5bc6\u9470 \uff0c\u4fdd\u6301\u7a7a\u767d\u4ee5\u9032\u884c\u914d\u5c0d\u904e\u7a0b\u3002", + "description": "\u6b64\u96fb\u8996\u50c5\u9700\u5b58\u53d6\u6b0a\u6756\u5047\u5982\u60a8\u6b63\u5728\u8a2d\u5b9a\u96fb\u8996\u3001\u5c1a\u672a\u53d6\u5f97\u5b58\u53d6\u6b0a\u6756 \uff0c\u4fdd\u6301\u7a7a\u767d\u4ee5\u9032\u884c\u914d\u5c0d\u904e\u7a0b\u3002", "title": "VIZIO SmartCast \u88dd\u7f6e" } } diff --git a/homeassistant/components/xiaomi_miio/translations/de.json b/homeassistant/components/xiaomi_miio/translations/de.json index d56a81e14d4..7cf11a1085e 100644 --- a/homeassistant/components/xiaomi_miio/translations/de.json +++ b/homeassistant/components/xiaomi_miio/translations/de.json @@ -10,6 +10,13 @@ }, "flow_title": "Xiaomi Miio: {name}", "step": { + "device": { + "data": { + "host": "IP-Adresse", + "name": "Name des Ger\u00e4ts", + "token": "API-Token" + } + }, "gateway": { "data": { "host": "IP-Adresse", diff --git a/homeassistant/components/xiaomi_miio/translations/en.json b/homeassistant/components/xiaomi_miio/translations/en.json index 951ae546b56..3d893ade2f0 100644 --- a/homeassistant/components/xiaomi_miio/translations/en.json +++ b/homeassistant/components/xiaomi_miio/translations/en.json @@ -18,7 +18,7 @@ "name": "Name of the device", "token": "API Token" }, - "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.", + "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.", "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway" }, "gateway": { diff --git a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json index dce2002faa9..3b0a89b7485 100644 --- a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json +++ b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json @@ -16,18 +16,18 @@ "host": "IP \u4f4d\u5740", "model": "\u88dd\u7f6e\u578b\u865f\uff08\u9078\u9805\uff09", "name": "\u88dd\u7f6e\u540d\u7a31", - "token": "API \u5bc6\u9470" + "token": "API \u6b0a\u6756" }, - "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u5bc6\u9470\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u5bc6\u9470\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64API \u5bc6\u9470\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u5bc6\u9470\u4e0d\u540c\u3002", + "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u6b0a\u6756\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u6b0a\u6756\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64API \u6b0a\u6756\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u6b0a\u6756\u4e0d\u540c\u3002", "title": "\u9023\u7dda\u81f3\u5c0f\u7c73 MIIO \u88dd\u7f6e\u6216\u5c0f\u7c73\u7db2\u95dc" }, "gateway": { "data": { "host": "IP \u4f4d\u5740", "name": "\u7db2\u95dc\u540d\u7a31", - "token": "API \u5bc6\u9470" + "token": "API \u6b0a\u6756" }, - "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u5bc6\u9470\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u5bc6\u9470\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64API \u5bc6\u9470\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u5bc6\u9470\u4e0d\u540c\u3002", + "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u6b0a\u6756\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u6b0a\u6756\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64API \u6b0a\u6756\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u6b0a\u6756\u4e0d\u540c\u3002", "title": "\u9023\u7dda\u81f3\u5c0f\u7c73\u7db2\u95dc" }, "user": { diff --git a/homeassistant/components/zwave_js/translations/de.json b/homeassistant/components/zwave_js/translations/de.json index d4903bc8c6d..9ff130605ef 100644 --- a/homeassistant/components/zwave_js/translations/de.json +++ b/homeassistant/components/zwave_js/translations/de.json @@ -1,13 +1,25 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "cannot_connect": "Verbindung fehlgeschlagen" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "step": { + "configure_addon": { + "data": { + "usb_path": "USB-Ger\u00e4te-Pfad" + } + }, + "manual": { + "data": { + "url": "URL" + } + }, "user": { "data": { "url": "URL" From 715a254913dfdf24c6bf90d56e9ecbf4186191e7 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 28 Feb 2021 18:01:28 -0800 Subject: [PATCH 073/831] Handle stream failures in recorder (#47151) * Handle stream failures in recorder Fail gracefully with an error message when the recorder is invoked with no segments due to a stream failure. * Update homeassistant/components/stream/recorder.py Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> --- homeassistant/components/stream/recorder.py | 5 +++++ tests/components/stream/test_recorder.py | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py index 0344e220647..f61211340ef 100644 --- a/homeassistant/components/stream/recorder.py +++ b/homeassistant/components/stream/recorder.py @@ -21,6 +21,11 @@ def async_setup_recorder(hass): def recorder_save_worker(file_out: str, segments: Deque[Segment]): """Handle saving stream.""" + + if not segments: + _LOGGER.error("Recording failed to capture anything") + return + if not os.path.exists(os.path.dirname(file_out)): os.makedirs(os.path.dirname(file_out), exist_ok=True) diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py index 199020097bd..48fe48d3337 100644 --- a/tests/components/stream/test_recorder.py +++ b/tests/components/stream/test_recorder.py @@ -193,6 +193,18 @@ async def test_recorder_discontinuity(tmpdir): assert os.path.exists(filename) +async def test_recorder_no_segements(tmpdir): + """Test recorder behavior with a stream failure which causes no segments.""" + # Setup + filename = f"{tmpdir}/test.mp4" + + # Run + recorder_save_worker("unused-file", []) + + # Assert + assert not os.path.exists(filename) + + async def test_record_stream_audio( hass, hass_client, stream_worker_sync, record_worker_sync ): From 5784e14d0cde5e61d09a6005b298e5ced495c97c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 28 Feb 2021 20:16:30 -0600 Subject: [PATCH 074/831] Enforce typing in bond (#47187) Co-authored-by: Martin Hjelmare --- homeassistant/components/bond/__init__.py | 7 ++-- homeassistant/components/bond/config_flow.py | 17 +++++---- homeassistant/components/bond/cover.py | 12 ++++--- homeassistant/components/bond/entity.py | 20 ++++++----- homeassistant/components/bond/fan.py | 10 +++--- homeassistant/components/bond/light.py | 12 +++---- homeassistant/components/bond/switch.py | 4 +-- homeassistant/components/bond/utils.py | 36 +++++++++++--------- setup.cfg | 2 +- 9 files changed, 63 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/bond/__init__.py b/homeassistant/components/bond/__init__.py index 9d0a613000a..ae9cc2111a2 100644 --- a/homeassistant/components/bond/__init__.py +++ b/homeassistant/components/bond/__init__.py @@ -19,13 +19,13 @@ PLATFORMS = ["cover", "fan", "light", "switch"] _API_TIMEOUT = SLOW_UPDATE_WARNING - 1 -async def async_setup(hass: HomeAssistant, config: dict): +async def async_setup(hass: HomeAssistant, config: dict) -> bool: """Set up the Bond component.""" hass.data.setdefault(DOMAIN, {}) return True -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Bond from a config entry.""" host = entry.data[CONF_HOST] token = entry.data[CONF_ACCESS_TOKEN] @@ -50,6 +50,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): if not entry.unique_id: hass.config_entries.async_update_entry(entry, unique_id=hub.bond_id) + assert hub.bond_id is not None hub_name = hub.name or hub.bond_id device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( @@ -96,7 +97,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @callback def _async_remove_old_device_identifiers( config_entry_id: str, device_registry: dr.DeviceRegistry, hub: BondHub -): +) -> None: """Remove the non-unique device registry entries.""" for device in hub.devices: dev = device_registry.async_get_device(identifiers={(DOMAIN, device.device_id)}) diff --git a/homeassistant/components/bond/config_flow.py b/homeassistant/components/bond/config_flow.py index f4e9babdff4..af889b803b5 100644 --- a/homeassistant/components/bond/config_flow.py +++ b/homeassistant/components/bond/config_flow.py @@ -13,6 +13,7 @@ from homeassistant.const import ( CONF_NAME, HTTP_UNAUTHORIZED, ) +from homeassistant.helpers.typing import DiscoveryInfoType from .const import DOMAIN # pylint:disable=unused-import from .utils import BondHub @@ -27,7 +28,7 @@ DISCOVERY_SCHEMA = vol.Schema({vol.Required(CONF_ACCESS_TOKEN): str}) TOKEN_SCHEMA = vol.Schema({}) -async def _validate_input(data: Dict[str, Any]) -> Tuple[str, Optional[str]]: +async def _validate_input(data: Dict[str, Any]) -> Tuple[str, str]: """Validate the user input allows us to connect.""" bond = Bond(data[CONF_HOST], data[CONF_ACCESS_TOKEN]) @@ -57,11 +58,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH - def __init__(self): + def __init__(self) -> None: """Initialize config flow.""" - self._discovered: Optional[dict] = None + self._discovered: Dict[str, str] = {} - async def _async_try_automatic_configure(self): + async def _async_try_automatic_configure(self) -> None: """Try to auto configure the device. Failure is acceptable here since the device may have been @@ -82,9 +83,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): _, hub_name = await _validate_input(self._discovered) self._discovered[CONF_NAME] = hub_name - async def async_step_zeroconf( - self, discovery_info: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + async def async_step_zeroconf(self, discovery_info: DiscoveryInfoType) -> Dict[str, Any]: # type: ignore """Handle a flow initialized by zeroconf discovery.""" name: str = discovery_info[CONF_NAME] host: str = discovery_info[CONF_HOST] @@ -107,7 +106,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_confirm() async def async_step_confirm( - self, user_input: Dict[str, Any] = None + self, user_input: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Handle confirmation flow for discovered bond hub.""" errors = {} @@ -148,7 +147,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) async def async_step_user( - self, user_input: Dict[str, Any] = None + self, user_input: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Handle a flow initialized by the user.""" errors = {} diff --git a/homeassistant/components/bond/cover.py b/homeassistant/components/bond/cover.py index 6b3c8d6bc02..0c73bdbc8f9 100644 --- a/homeassistant/components/bond/cover.py +++ b/homeassistant/components/bond/cover.py @@ -23,7 +23,7 @@ async def async_setup_entry( hub: BondHub = data[HUB] bpup_subs: BPUPSubscriptions = data[BPUP_SUBS] - covers = [ + covers: List[Entity] = [ BondCover(hub, device, bpup_subs) for device in hub.devices if device.type == DeviceType.MOTORIZED_SHADES @@ -35,13 +35,15 @@ async def async_setup_entry( class BondCover(BondEntity, CoverEntity): """Representation of a Bond cover.""" - def __init__(self, hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscriptions): + def __init__( + self, hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscriptions + ) -> None: """Create HA entity representing Bond cover.""" super().__init__(hub, device, bpup_subs) self._closed: Optional[bool] = None - def _apply_state(self, state: dict): + def _apply_state(self, state: dict) -> None: cover_open = state.get("open") self._closed = True if cover_open == 0 else False if cover_open == 1 else None @@ -51,7 +53,7 @@ class BondCover(BondEntity, CoverEntity): return DEVICE_CLASS_SHADE @property - def is_closed(self): + def is_closed(self) -> Optional[bool]: """Return if the cover is closed or not.""" return self._closed @@ -63,6 +65,6 @@ class BondCover(BondEntity, CoverEntity): """Close cover.""" await self._hub.bond.action(self._device.device_id, Action.close()) - async def async_stop_cover(self, **kwargs): + async def async_stop_cover(self, **kwargs: Any) -> None: """Hold cover.""" await self._hub.bond.action(self._device.device_id, Action.hold()) diff --git a/homeassistant/components/bond/entity.py b/homeassistant/components/bond/entity.py index 5b2e27b94cc..6c56b47a9dc 100644 --- a/homeassistant/components/bond/entity.py +++ b/homeassistant/components/bond/entity.py @@ -38,7 +38,7 @@ class BondEntity(Entity): self._sub_device = sub_device self._available = True self._bpup_subs = bpup_subs - self._update_lock = None + self._update_lock: Optional[Lock] = None self._initialized = False @property @@ -58,7 +58,7 @@ class BondEntity(Entity): return self._device.name @property - def should_poll(self): + def should_poll(self) -> bool: """No polling needed.""" return False @@ -96,15 +96,16 @@ class BondEntity(Entity): """Report availability of this entity based on last API call results.""" return self._available - async def async_update(self): + async def async_update(self) -> None: """Fetch assumed state of the cover from the hub using API.""" await self._async_update_from_api() - async def _async_update_if_bpup_not_alive(self, *_): + async def _async_update_if_bpup_not_alive(self, *_: Any) -> None: """Fetch via the API if BPUP is not alive.""" if self._bpup_subs.alive and self._initialized: return + assert self._update_lock is not None if self._update_lock.locked(): _LOGGER.warning( "Updating %s took longer than the scheduled update interval %s", @@ -117,7 +118,7 @@ class BondEntity(Entity): await self._async_update_from_api() self.async_write_ha_state() - async def _async_update_from_api(self): + async def _async_update_from_api(self) -> None: """Fetch via the API.""" try: state: dict = await self._hub.bond.device_state(self._device_id) @@ -131,11 +132,11 @@ class BondEntity(Entity): self._async_state_callback(state) @abstractmethod - def _apply_state(self, state: dict): + def _apply_state(self, state: dict) -> None: raise NotImplementedError @callback - def _async_state_callback(self, state): + def _async_state_callback(self, state: dict) -> None: """Process a state change.""" self._initialized = True if not self._available: @@ -147,16 +148,17 @@ class BondEntity(Entity): self._apply_state(state) @callback - def _async_bpup_callback(self, state): + def _async_bpup_callback(self, state: dict) -> None: """Process a state change from BPUP.""" self._async_state_callback(state) self.async_write_ha_state() - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Subscribe to BPUP and start polling.""" await super().async_added_to_hass() self._update_lock = Lock() self._bpup_subs.subscribe(self._device_id, self._async_bpup_callback) + assert self.hass is not None self.async_on_remove( async_track_time_interval( self.hass, self._async_update_if_bpup_not_alive, _FALLBACK_SCAN_INTERVAL diff --git a/homeassistant/components/bond/fan.py b/homeassistant/components/bond/fan.py index 5ff7e0c7065..1c94a6f3e9a 100644 --- a/homeassistant/components/bond/fan.py +++ b/homeassistant/components/bond/fan.py @@ -38,7 +38,7 @@ async def async_setup_entry( hub: BondHub = data[HUB] bpup_subs: BPUPSubscriptions = data[BPUP_SUBS] - fans = [ + fans: List[Entity] = [ BondFan(hub, device, bpup_subs) for device in hub.devices if DeviceType.is_fan(device.type) @@ -58,7 +58,7 @@ class BondFan(BondEntity, FanEntity): self._speed: Optional[int] = None self._direction: Optional[int] = None - def _apply_state(self, state: dict): + def _apply_state(self, state: dict) -> None: self._power = state.get("power") self._speed = state.get("speed") self._direction = state.get("direction") @@ -80,7 +80,7 @@ class BondFan(BondEntity, FanEntity): return (1, self._device.props.get("max_speed", 3)) @property - def percentage(self) -> Optional[str]: + def percentage(self) -> int: """Return the current speed percentage for the fan.""" if not self._speed or not self._power: return 0 @@ -128,7 +128,7 @@ class BondFan(BondEntity, FanEntity): speed: Optional[str] = None, percentage: Optional[int] = None, preset_mode: Optional[str] = None, - **kwargs, + **kwargs: Any, ) -> None: """Turn on the fan.""" _LOGGER.debug("Fan async_turn_on called with percentage %s", percentage) @@ -142,7 +142,7 @@ class BondFan(BondEntity, FanEntity): """Turn the fan off.""" await self._hub.bond.action(self._device.device_id, Action.turn_off()) - async def async_set_direction(self, direction: str): + async def async_set_direction(self, direction: str) -> None: """Set fan rotation direction.""" bond_direction = ( Direction.REVERSE if direction == DIRECTION_REVERSE else Direction.FORWARD diff --git a/homeassistant/components/bond/light.py b/homeassistant/components/bond/light.py index 194a009a857..d5f7cb29207 100644 --- a/homeassistant/components/bond/light.py +++ b/homeassistant/components/bond/light.py @@ -114,7 +114,7 @@ class BondLight(BondBaseLight, BondEntity, LightEntity): super().__init__(hub, device, bpup_subs, sub_device) self._brightness: Optional[int] = None - def _apply_state(self, state: dict): + def _apply_state(self, state: dict) -> None: self._light = state.get("light") self._brightness = state.get("brightness") @@ -126,7 +126,7 @@ class BondLight(BondBaseLight, BondEntity, LightEntity): return 0 @property - def brightness(self) -> int: + def brightness(self) -> Optional[int]: """Return the brightness of this light between 1..255.""" brightness_value = ( round(self._brightness * 255 / 100) if self._brightness else None @@ -152,7 +152,7 @@ class BondLight(BondBaseLight, BondEntity, LightEntity): class BondDownLight(BondBaseLight, BondEntity, LightEntity): """Representation of a Bond light.""" - def _apply_state(self, state: dict): + def _apply_state(self, state: dict) -> None: self._light = state.get("down_light") and state.get("light") async def async_turn_on(self, **kwargs: Any) -> None: @@ -171,7 +171,7 @@ class BondDownLight(BondBaseLight, BondEntity, LightEntity): class BondUpLight(BondBaseLight, BondEntity, LightEntity): """Representation of a Bond light.""" - def _apply_state(self, state: dict): + def _apply_state(self, state: dict) -> None: self._light = state.get("up_light") and state.get("light") async def async_turn_on(self, **kwargs: Any) -> None: @@ -198,7 +198,7 @@ class BondFireplace(BondEntity, LightEntity): # Bond flame level, 0-100 self._flame: Optional[int] = None - def _apply_state(self, state: dict): + def _apply_state(self, state: dict) -> None: self._power = state.get("power") self._flame = state.get("flame") @@ -230,7 +230,7 @@ class BondFireplace(BondEntity, LightEntity): await self._hub.bond.action(self._device.device_id, Action.turn_off()) @property - def brightness(self): + def brightness(self) -> Optional[int]: """Return the flame of this fireplace converted to HA brightness between 0..255.""" return round(self._flame * 255 / 100) if self._flame else None diff --git a/homeassistant/components/bond/switch.py b/homeassistant/components/bond/switch.py index 8319d31c714..abbc2e2b44c 100644 --- a/homeassistant/components/bond/switch.py +++ b/homeassistant/components/bond/switch.py @@ -23,7 +23,7 @@ async def async_setup_entry( hub: BondHub = data[HUB] bpup_subs: BPUPSubscriptions = data[BPUP_SUBS] - switches = [ + switches: List[Entity] = [ BondSwitch(hub, device, bpup_subs) for device in hub.devices if DeviceType.is_generic(device.type) @@ -41,7 +41,7 @@ class BondSwitch(BondEntity, SwitchEntity): self._power: Optional[bool] = None - def _apply_state(self, state: dict): + def _apply_state(self, state: dict) -> None: self._power = state.get("power") @property diff --git a/homeassistant/components/bond/utils.py b/homeassistant/components/bond/utils.py index 225eec87d98..28580ae415e 100644 --- a/homeassistant/components/bond/utils.py +++ b/homeassistant/components/bond/utils.py @@ -1,7 +1,7 @@ """Reusable utilities for the Bond component.""" import asyncio import logging -from typing import List, Optional, Set +from typing import Any, Dict, List, Optional, Set, cast from aiohttp import ClientResponseError from bond_api import Action, Bond @@ -14,13 +14,15 @@ _LOGGER = logging.getLogger(__name__) class BondDevice: """Helper device class to hold ID and attributes together.""" - def __init__(self, device_id: str, attrs: dict, props: dict): + def __init__( + self, device_id: str, attrs: Dict[str, Any], props: Dict[str, Any] + ) -> None: """Create a helper device from ID and attributes returned by API.""" self.device_id = device_id self.props = props self._attrs = attrs - def __repr__(self): + def __repr__(self) -> str: """Return readable representation of a bond device.""" return { "device_id": self.device_id, @@ -31,25 +33,25 @@ class BondDevice: @property def name(self) -> str: """Get the name of this device.""" - return self._attrs["name"] + return cast(str, self._attrs["name"]) @property def type(self) -> str: """Get the type of this device.""" - return self._attrs["type"] + return cast(str, self._attrs["type"]) @property - def location(self) -> str: + def location(self) -> Optional[str]: """Get the location of this device.""" return self._attrs.get("location") @property - def template(self) -> str: + def template(self) -> Optional[str]: """Return this model template.""" return self._attrs.get("template") @property - def branding_profile(self) -> str: + def branding_profile(self) -> Optional[str]: """Return this branding profile.""" return self.props.get("branding_profile") @@ -58,7 +60,7 @@ class BondDevice: """Check if Trust State is turned on.""" return self.props.get("trust_state", False) - def _has_any_action(self, actions: Set[str]): + def _has_any_action(self, actions: Set[str]) -> bool: """Check to see if the device supports any of the actions.""" supported_actions: List[str] = self._attrs["actions"] for action in supported_actions: @@ -99,11 +101,11 @@ class BondHub: def __init__(self, bond: Bond): """Initialize Bond Hub.""" self.bond: Bond = bond - self._bridge: Optional[dict] = None - self._version: Optional[dict] = None - self._devices: Optional[List[BondDevice]] = None + self._bridge: Dict[str, Any] = {} + self._version: Dict[str, Any] = {} + self._devices: List[BondDevice] = [] - async def setup(self, max_devices=None): + async def setup(self, max_devices: Optional[int] = None) -> None: """Read hub version information.""" self._version = await self.bond.version() _LOGGER.debug("Bond reported the following version info: %s", self._version) @@ -135,12 +137,12 @@ class BondHub: return self._version.get("bondid") @property - def target(self) -> str: + def target(self) -> Optional[str]: """Return this hub target.""" return self._version.get("target") @property - def model(self) -> str: + def model(self) -> Optional[str]: """Return this hub model.""" return self._version.get("model") @@ -154,7 +156,7 @@ class BondHub: """Get the name of this bridge.""" if not self.is_bridge and self._devices: return self._devices[0].name - return self._bridge["name"] + return cast(str, self._bridge["name"]) @property def location(self) -> Optional[str]: @@ -164,7 +166,7 @@ class BondHub: return self._bridge.get("location") @property - def fw_ver(self) -> str: + def fw_ver(self) -> Optional[str]: """Return this hub firmware version.""" return self._version.get("fw_ver") diff --git a/setup.cfg b/setup.cfg index 7761ff2d67e..69cf931185e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -42,7 +42,7 @@ warn_redundant_casts = true warn_unused_configs = true -[mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*,tests.components.hyperion.*] +[mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.bond.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*,tests.components.hyperion.*] strict = true ignore_errors = false warn_unreachable = true From 0e9f2dc2720edda035d7909eb3e2220d0a9fe8f3 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sun, 28 Feb 2021 21:41:09 -0700 Subject: [PATCH 075/831] Bump simplisafe-python to 9.6.7 (#47206) --- 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 b18bafb0bbf..de5199ccd4c 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.6.4"], + "requirements": ["simplisafe-python==9.6.7"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index 277d42022e2..d03417a1bc5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2044,7 +2044,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==9.6.4 +simplisafe-python==9.6.7 # homeassistant.components.sisyphus sisyphus-control==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9ace25468ef..67ebea6ab2c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1051,7 +1051,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==9.6.4 +simplisafe-python==9.6.7 # homeassistant.components.slack slackclient==2.5.0 From 853da40e703933b17646863be373c3b5653de8f0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 28 Feb 2021 22:42:09 -0600 Subject: [PATCH 076/831] Increment the homekit config version when restarting (#47209) If an entity changes between restart the iOS/controller device may have cached the old chars for the accessory. To force the iOS/controller to reload the chars, we increment the config version when Home Assistant restarts --- homeassistant/components/homekit/__init__.py | 7 +++++-- tests/components/homekit/test_homekit.py | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 534ea3c6f95..c042872f4cd 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -491,8 +491,11 @@ class HomeKit: # as pyhap uses a random one until state is restored if os.path.exists(persist_file): self.driver.load() - else: - self.driver.persist() + self.driver.state.config_version += 1 + if self.driver.state.config_version > 65535: + self.driver.state.config_version = 1 + + self.driver.persist() def reset_accessories(self, entity_ids): """Reset the accessory to load the latest configuration.""" diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index ec324602684..9ce3e96f06f 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -540,6 +540,7 @@ async def test_homekit_start(hass, hk_driver, device_reg): assert (device_registry.CONNECTION_NETWORK_MAC, formatted_mac) in device.connections assert len(device_reg.devices) == 1 + assert homekit.driver.state.config_version == 2 async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroconf): From cb94e7949b1486784f3809911cf0c26fffe49e75 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Mar 2021 02:00:31 -0600 Subject: [PATCH 077/831] Bump HAP-python to 3.3.2 to fix unavailable condition on restart (#47213) Fixes https://github.com/ikalchev/HAP-python/compare/v3.3.1...v3.3.2 --- homeassistant/components/homekit/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/manifest.json b/homeassistant/components/homekit/manifest.json index 4d1598c728c..ac3fb0251e2 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit", "documentation": "https://www.home-assistant.io/integrations/homekit", "requirements": [ - "HAP-python==3.3.1", + "HAP-python==3.3.2", "fnvhash==0.1.0", "PyQRCode==1.2.1", "base36==0.1.1", diff --git a/requirements_all.txt b/requirements_all.txt index d03417a1bc5..d4e7be4d98e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -17,7 +17,7 @@ Adafruit-SHT31==1.0.2 # Adafruit_BBIO==1.1.1 # homeassistant.components.homekit -HAP-python==3.3.1 +HAP-python==3.3.2 # homeassistant.components.mastodon Mastodon.py==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 67ebea6ab2c..78f234e9dff 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ AEMET-OpenData==0.1.8 # homeassistant.components.homekit -HAP-python==3.3.1 +HAP-python==3.3.2 # homeassistant.components.flick_electric PyFlick==0.0.2 From 16dcbf1467246d18e7c55575f53f01032e4c1ece Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 1 Mar 2021 09:09:01 +0100 Subject: [PATCH 078/831] Update pylint (#47205) --- .../auth/providers/trusted_networks.py | 2 +- homeassistant/components/alexa/intent.py | 2 ++ .../components/apple_tv/config_flow.py | 2 +- .../components/isy994/binary_sensor.py | 8 ++--- homeassistant/components/isy994/helpers.py | 12 +++---- homeassistant/components/knx/const.py | 2 ++ homeassistant/components/slack/notify.py | 36 +++++++++---------- homeassistant/components/stream/__init__.py | 2 +- .../components/telegram_bot/__init__.py | 2 +- .../components/universal/media_player.py | 2 +- homeassistant/components/vicare/__init__.py | 1 + homeassistant/components/withings/common.py | 14 +++----- .../components/zha/core/channels/general.py | 2 +- .../components/zha/core/channels/hvac.py | 2 +- homeassistant/components/zha/core/const.py | 1 + homeassistant/components/zha/cover.py | 2 +- .../components/zhong_hong/climate.py | 2 +- homeassistant/core.py | 3 ++ homeassistant/helpers/typing.py | 1 + requirements_test.txt | 4 +-- 20 files changed, 50 insertions(+), 52 deletions(-) diff --git a/homeassistant/auth/providers/trusted_networks.py b/homeassistant/auth/providers/trusted_networks.py index 2afdbf98196..c45cca4bd1a 100644 --- a/homeassistant/auth/providers/trusted_networks.py +++ b/homeassistant/auth/providers/trusted_networks.py @@ -111,7 +111,7 @@ class TrustedNetworksAuthProvider(AuthProvider): if ( user.id in user_list or any( - [group.id in flattened_group_list for group in user.groups] + group.id in flattened_group_list for group in user.groups ) ) ] diff --git a/homeassistant/components/alexa/intent.py b/homeassistant/components/alexa/intent.py index f64031250e2..edc7ec6fa98 100644 --- a/homeassistant/components/alexa/intent.py +++ b/homeassistant/components/alexa/intent.py @@ -18,6 +18,7 @@ INTENTS_API_ENDPOINT = "/api/alexa" class SpeechType(enum.Enum): + # pylint: disable=invalid-name """The Alexa speech types.""" plaintext = "PlainText" @@ -28,6 +29,7 @@ SPEECH_MAPPINGS = {"plain": SpeechType.plaintext, "ssml": SpeechType.ssml} class CardType(enum.Enum): + # pylint: disable=invalid-name """The Alexa card types.""" simple = "Simple" diff --git a/homeassistant/components/apple_tv/config_flow.py b/homeassistant/components/apple_tv/config_flow.py index ef0a0cfe59e..ad56561ef9b 100644 --- a/homeassistant/components/apple_tv/config_flow.py +++ b/homeassistant/components/apple_tv/config_flow.py @@ -44,7 +44,7 @@ async def device_scan(identifier, loop, cache=None): return True if identifier == dev.name: return True - return any([service.identifier == identifier for service in dev.services]) + return any(service.identifier == identifier for service in dev.services) def _host_filter(): try: diff --git a/homeassistant/components/isy994/binary_sensor.py b/homeassistant/components/isy994/binary_sensor.py index 6355a9bcece..e8c08d98aa7 100644 --- a/homeassistant/components/isy994/binary_sensor.py +++ b/homeassistant/components/isy994/binary_sensor.py @@ -127,7 +127,7 @@ async def async_setup_entry( if ( device_class == DEVICE_CLASS_MOTION and device_type is not None - and any([device_type.startswith(t) for t in TYPE_INSTEON_MOTION]) + and any(device_type.startswith(t) for t in TYPE_INSTEON_MOTION) ): # Special cases for Insteon Motion Sensors I & II: # Some subnodes never report status until activated, so @@ -194,10 +194,8 @@ def _detect_device_type_and_class(node: Union[Group, Node]) -> (str, str): # Other devices (incl Insteon.) for device_class in [*BINARY_SENSOR_DEVICE_TYPES_ISY]: if any( - [ - device_type.startswith(t) - for t in set(BINARY_SENSOR_DEVICE_TYPES_ISY[device_class]) - ] + device_type.startswith(t) + for t in set(BINARY_SENSOR_DEVICE_TYPES_ISY[device_class]) ): return device_class, device_type return (None, device_type) diff --git a/homeassistant/components/isy994/helpers.py b/homeassistant/components/isy994/helpers.py index e1ab689eb7a..f9834586583 100644 --- a/homeassistant/components/isy994/helpers.py +++ b/homeassistant/components/isy994/helpers.py @@ -97,10 +97,8 @@ def _check_for_insteon_type( platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform] for platform in platforms: if any( - [ - device_type.startswith(t) - for t in set(NODE_FILTERS[platform][FILTER_INSTEON_TYPE]) - ] + device_type.startswith(t) + for t in set(NODE_FILTERS[platform][FILTER_INSTEON_TYPE]) ): # Hacky special-cases for certain devices with different platforms @@ -164,10 +162,8 @@ def _check_for_zwave_cat( platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform] for platform in platforms: if any( - [ - device_type.startswith(t) - for t in set(NODE_FILTERS[platform][FILTER_ZWAVE_CAT]) - ] + device_type.startswith(t) + for t in set(NODE_FILTERS[platform][FILTER_ZWAVE_CAT]) ): hass_isy_data[ISY994_NODES][platform].append(node) diff --git a/homeassistant/components/knx/const.py b/homeassistant/components/knx/const.py index 83ffc2557c2..1829826834c 100644 --- a/homeassistant/components/knx/const.py +++ b/homeassistant/components/knx/const.py @@ -24,6 +24,7 @@ CONF_RESET_AFTER = "reset_after" class ColorTempModes(Enum): + # pylint: disable=invalid-name """Color temperature modes for config validation.""" ABSOLUTE = "DPT-7.600" @@ -31,6 +32,7 @@ class ColorTempModes(Enum): class SupportedPlatforms(Enum): + # pylint: disable=invalid-name """Supported platforms.""" BINARY_SENSOR = "binary_sensor" diff --git a/homeassistant/components/slack/notify.py b/homeassistant/components/slack/notify.py index 985f59a6715..fb7b949e3ed 100644 --- a/homeassistant/components/slack/notify.py +++ b/homeassistant/components/slack/notify.py @@ -4,7 +4,7 @@ from __future__ import annotations import asyncio import logging import os -from typing import Any, List, Optional, TypedDict +from typing import Any, TypedDict from urllib.parse import urlparse from aiohttp import BasicAuth, FormData @@ -106,14 +106,14 @@ class MessageT(TypedDict, total=False): username: str # Optional key icon_url: str # Optional key icon_emoji: str # Optional key - blocks: List[Any] # Optional key + blocks: list[Any] # Optional key async def async_get_service( hass: HomeAssistantType, config: ConfigType, - discovery_info: Optional[DiscoveryInfoType] = None, -) -> Optional[SlackNotificationService]: + discovery_info: DiscoveryInfoType | None = None, +) -> SlackNotificationService | None: """Set up the Slack notification service.""" session = aiohttp_client.async_get_clientsession(hass) client = WebClient(token=config[CONF_API_KEY], run_async=True, session=session) @@ -147,7 +147,7 @@ def _async_get_filename_from_url(url: str) -> str: @callback -def _async_sanitize_channel_names(channel_list: List[str]) -> List[str]: +def _async_sanitize_channel_names(channel_list: list[str]) -> list[str]: """Remove any # symbols from a channel list.""" return [channel.lstrip("#") for channel in channel_list] @@ -174,8 +174,8 @@ class SlackNotificationService(BaseNotificationService): hass: HomeAssistantType, client: WebClient, default_channel: str, - username: Optional[str], - icon: Optional[str], + username: str | None, + icon: str | None, ) -> None: """Initialize.""" self._client = client @@ -187,9 +187,9 @@ class SlackNotificationService(BaseNotificationService): async def _async_send_local_file_message( self, path: str, - targets: List[str], + targets: list[str], message: str, - title: Optional[str], + title: str | None, ) -> None: """Upload a local file (with message) to Slack.""" if not self._hass.config.is_allowed_path(path): @@ -213,12 +213,12 @@ class SlackNotificationService(BaseNotificationService): async def _async_send_remote_file_message( self, url: str, - targets: List[str], + targets: list[str], message: str, - title: Optional[str], + title: str | None, *, - username: Optional[str] = None, - password: Optional[str] = None, + username: str | None = None, + password: str | None = None, ) -> None: """Upload a remote file (with message) to Slack. @@ -263,13 +263,13 @@ class SlackNotificationService(BaseNotificationService): async def _async_send_text_only_message( self, - targets: List[str], + targets: list[str], message: str, - title: Optional[str], + title: str | None, *, - username: Optional[str] = None, - icon: Optional[str] = None, - blocks: Optional[Any] = None, + username: str | None = None, + icon: str | None = None, + blocks: Any | None = None, ) -> None: """Send a text-only message.""" message_dict: MessageT = {"link_names": True, "text": message} diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index c2bf63063e5..184ed1f2719 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -157,7 +157,7 @@ class Stream: def check_idle(self): """Reset access token if all providers are idle.""" - if all([p.idle for p in self._outputs.values()]): + if all(p.idle for p in self._outputs.values()): self.access_token = None def start(self): diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index b6ca7881615..86bf4c24407 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -330,7 +330,7 @@ async def async_setup(hass, config): attribute_templ = data.get(attribute) if attribute_templ: if any( - [isinstance(attribute_templ, vtype) for vtype in [float, int, str]] + isinstance(attribute_templ, vtype) for vtype in [float, int, str] ): data[attribute] = attribute_templ else: diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index 1834d22855c..2702f26a3d3 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -470,7 +470,7 @@ class UniversalMediaPlayer(MediaPlayerEntity): if SERVICE_MEDIA_PREVIOUS_TRACK in self._cmds: flags |= SUPPORT_PREVIOUS_TRACK - if any([cmd in self._cmds for cmd in [SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN]]): + if any(cmd in self._cmds for cmd in [SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN]): flags |= SUPPORT_VOLUME_STEP if SERVICE_VOLUME_SET in self._cmds: flags |= SUPPORT_VOLUME_SET diff --git a/homeassistant/components/vicare/__init__.py b/homeassistant/components/vicare/__init__.py index 2b1a367215b..940e076c813 100644 --- a/homeassistant/components/vicare/__init__.py +++ b/homeassistant/components/vicare/__init__.py @@ -33,6 +33,7 @@ DEFAULT_HEATING_TYPE = "generic" class HeatingType(enum.Enum): + # pylint: disable=invalid-name """Possible options for heating type.""" generic = "generic" diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index c08ddddf4a5..e2e0b06e342 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -1070,11 +1070,9 @@ def get_data_manager_by_webhook_id( def get_all_data_managers(hass: HomeAssistant) -> Tuple[DataManager, ...]: """Get all configured data managers.""" return tuple( - [ - config_entry_data[const.DATA_MANAGER] - for config_entry_data in hass.data[const.DOMAIN].values() - if const.DATA_MANAGER in config_entry_data - ] + config_entry_data[const.DATA_MANAGER] + for config_entry_data in hass.data[const.DOMAIN].values() + if const.DATA_MANAGER in config_entry_data ) @@ -1101,11 +1099,7 @@ async def async_create_entities( def get_platform_attributes(platform: str) -> Tuple[WithingsAttribute, ...]: """Get withings attributes used for a specific platform.""" return tuple( - [ - attribute - for attribute in WITHINGS_ATTRIBUTES - if attribute.platform == platform - ] + attribute for attribute in WITHINGS_ATTRIBUTES if attribute.platform == platform ) diff --git a/homeassistant/components/zha/core/channels/general.py b/homeassistant/components/zha/core/channels/general.py index d105572c182..1c1ab28ba5d 100644 --- a/homeassistant/components/zha/core/channels/general.py +++ b/homeassistant/components/zha/core/channels/general.py @@ -91,7 +91,7 @@ class AnalogOutput(ZigbeeChannel): self.error("Could not set value: %s", ex) return False if isinstance(res, list) and all( - [record.status == Status.SUCCESS for record in res[0]] + record.status == Status.SUCCESS for record in res[0] ): return True return False diff --git a/homeassistant/components/zha/core/channels/hvac.py b/homeassistant/components/zha/core/channels/hvac.py index 1647c5ce52d..e02f6c01836 100644 --- a/homeassistant/components/zha/core/channels/hvac.py +++ b/homeassistant/components/zha/core/channels/hvac.py @@ -434,7 +434,7 @@ class ThermostatChannel(ZigbeeChannel): if not isinstance(res, list): return False - return all([record.status == Status.SUCCESS for record in res[0]]) + return all(record.status == Status.SUCCESS for record in res[0]) @registries.ZIGBEE_CHANNEL_REGISTRY.register(hvac.UserInterface.cluster_id) diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 1d3f767353b..ac4f53d2a8c 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -176,6 +176,7 @@ POWER_BATTERY_OR_UNKNOWN = "Battery or Unknown" class RadioType(enum.Enum): + # pylint: disable=invalid-name """Possible options for radio type.""" znp = ( diff --git a/homeassistant/components/zha/cover.py b/homeassistant/components/zha/cover.py index 45114c677af..e202def46c5 100644 --- a/homeassistant/components/zha/cover.py +++ b/homeassistant/components/zha/cover.py @@ -301,7 +301,7 @@ class KeenVent(Shade): self._on_off_channel.on(), ] results = await asyncio.gather(*tasks, return_exceptions=True) - if any([isinstance(result, Exception) for result in results]): + if any(isinstance(result, Exception) for result in results): self.debug("couldn't open cover") return diff --git a/homeassistant/components/zhong_hong/climate.py b/homeassistant/components/zhong_hong/climate.py index 68247537847..f20c9f7e328 100644 --- a/homeassistant/components/zhong_hong/climate.py +++ b/homeassistant/components/zhong_hong/climate.py @@ -95,7 +95,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): async def startup(): """Start hub socket after all climate entity is set up.""" nonlocal hub_is_initialized - if not all([device.is_initialized for device in devices]): + if not all(device.is_initialized for device in devices): return if hub_is_initialized: diff --git a/homeassistant/core.py b/homeassistant/core.py index b62dd1ee7d5..3483dc96069 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -153,6 +153,7 @@ def is_callback(func: Callable[..., Any]) -> bool: @enum.unique class HassJobType(enum.Enum): + # pylint: disable=invalid-name """Represent a job type.""" Coroutinefunction = 1 @@ -198,6 +199,7 @@ def _get_callable_job_type(target: Callable) -> HassJobType: class CoreState(enum.Enum): + # pylint: disable=invalid-name """Represent the current state of Home Assistant.""" not_running = "NOT_RUNNING" @@ -589,6 +591,7 @@ class Context: class EventOrigin(enum.Enum): + # pylint: disable=invalid-name """Represent the origin of an event.""" local = "LOCAL" diff --git a/homeassistant/helpers/typing.py b/homeassistant/helpers/typing.py index 279bc0f686f..ffc32efeb2a 100644 --- a/homeassistant/helpers/typing.py +++ b/homeassistant/helpers/typing.py @@ -20,6 +20,7 @@ QueryType = Any class UndefinedType(Enum): + # pylint: disable=invalid-name """Singleton type for use with not set sentinel values.""" _singleton = 0 diff --git a/requirements_test.txt b/requirements_test.txt index 12f215f177a..1f0a3c4dba2 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -10,8 +10,8 @@ jsonpickle==1.4.1 mock-open==1.4.0 mypy==0.812 pre-commit==2.10.1 -pylint==2.6.0 -astroid==2.4.2 +pylint==2.7.2 +astroid==2.5.1 pipdeptree==1.0.0 pylint-strict-informational==0.1 pytest-aiohttp==0.3.0 From d2db58d138cfe9eba7073c7f32f0faa6ef606fdc Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 1 Mar 2021 09:17:41 +0100 Subject: [PATCH 079/831] Fix generic-x86-64 build (#47214) Replace the wrong Docker Hub repository slipped in during testing. --- machine/generic-x86-64 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machine/generic-x86-64 b/machine/generic-x86-64 index e858c382221..4c83228387d 100644 --- a/machine/generic-x86-64 +++ b/machine/generic-x86-64 @@ -1,5 +1,5 @@ ARG BUILD_VERSION -FROM agners/amd64-homeassistant:$BUILD_VERSION +FROM homeassistant/amd64-homeassistant:$BUILD_VERSION RUN apk --no-cache add \ libva-intel-driver \ From 0592309b652eaf541e281d64e3149b021cb2f919 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 1 Mar 2021 03:41:04 -0500 Subject: [PATCH 080/831] Add hassio addon_update service and hassio config entry with addon and OS devices and entities (#46342) * add addon_update service, use config flow to set up config entry, create disabled sensors * move most of entity logic to common entity class, improve device info, get rid of config_flow user step * fix setup logic * additional refactor * fix refactored logic * fix config flow tests * add test for addon_update service and get_addons_info * add entry setup and unload test and fix update coordinator * handle if entry setup calls unload * return nothing for coordinator if entry is being reloaded because coordinator will get recreated anyway * remove entry when HA instance is no longer hassio and add corresponding test * handle adding and removing device registry entries * better config entry reload logic * fix comment * bugfix * fix flake error * switch pass to return * use repository attribute for model and fallback to url * use custom 'system' source since hassio source is misleading * Update homeassistant/components/hassio/entity.py Co-authored-by: Franck Nijhof * update remove addons function name * Update homeassistant/components/hassio/__init__.py Co-authored-by: Franck Nijhof * fix import * pop coordinator after unload * additional fixes * always pass in sensor name when creating entity * prefix one more function with async and fix tests * use supervisor info for addons since list is already filtered on what's installed * remove unused service * update sensor names * remove added handler function * use walrus * add OS device and sensors * fix * re-add addon_update service schema * add more test coverage and exclude entities from tests * check if instance is using hass OS in order to create OS entities Co-authored-by: Franck Nijhof --- .coveragerc | 3 + homeassistant/components/hassio/__init__.py | 165 ++++++++++++- .../components/hassio/binary_sensor.py | 50 ++++ .../components/hassio/config_flow.py | 22 ++ homeassistant/components/hassio/const.py | 9 +- homeassistant/components/hassio/entity.py | 93 +++++++ homeassistant/components/hassio/sensor.py | 52 ++++ homeassistant/components/hassio/services.yaml | 12 + tests/components/hassio/test_config_flow.py | 36 +++ tests/components/hassio/test_init.py | 229 +++++++++++++++++- 10 files changed, 661 insertions(+), 10 deletions(-) create mode 100644 homeassistant/components/hassio/binary_sensor.py create mode 100644 homeassistant/components/hassio/config_flow.py create mode 100644 homeassistant/components/hassio/entity.py create mode 100644 homeassistant/components/hassio/sensor.py create mode 100644 tests/components/hassio/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 50fcf151821..281c8d5be83 100644 --- a/.coveragerc +++ b/.coveragerc @@ -377,6 +377,9 @@ omit = homeassistant/components/harmony/data.py homeassistant/components/harmony/remote.py homeassistant/components/harmony/util.py + homeassistant/components/hassio/binary_sensor.py + homeassistant/components/hassio/entity.py + homeassistant/components/hassio/sensor.py homeassistant/components/haveibeenpwned/sensor.py homeassistant/components/hdmi_cec/* homeassistant/components/heatmiser/climate.py diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index fdeb10bcafe..82797874445 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -1,24 +1,29 @@ """Support for Hass.io.""" +import asyncio from datetime import timedelta import logging import os -from typing import Optional +from typing import Any, Dict, List, Optional import voluptuous as vol from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.components.homeassistant import SERVICE_CHECK_CONFIG import homeassistant.config as conf_util +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_NAME, + ATTR_SERVICE, EVENT_CORE_CONFIG_UPDATE, SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP, ) -from homeassistant.core import DOMAIN as HASS_DOMAIN, callback +from homeassistant.core import DOMAIN as HASS_DOMAIN, Config, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.device_registry import DeviceRegistry, async_get_registry from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.loader import bind_hass from homeassistant.util.dt import utcnow @@ -32,7 +37,11 @@ from .const import ( ATTR_HOMEASSISTANT, ATTR_INPUT, ATTR_PASSWORD, + ATTR_REPOSITORY, + ATTR_SLUG, ATTR_SNAPSHOT, + ATTR_URL, + ATTR_VERSION, DOMAIN, ) from .discovery import async_setup_discovery_view @@ -46,6 +55,7 @@ _LOGGER = logging.getLogger(__name__) STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 +PLATFORMS = ["binary_sensor", "sensor"] CONF_FRONTEND_REPO = "development_repo" @@ -62,9 +72,12 @@ DATA_OS_INFO = "hassio_os_info" DATA_SUPERVISOR_INFO = "hassio_supervisor_info" HASSIO_UPDATE_INTERVAL = timedelta(minutes=55) +ADDONS_COORDINATOR = "hassio_addons_coordinator" + SERVICE_ADDON_START = "addon_start" SERVICE_ADDON_STOP = "addon_stop" SERVICE_ADDON_RESTART = "addon_restart" +SERVICE_ADDON_UPDATE = "addon_update" SERVICE_ADDON_STDIN = "addon_stdin" SERVICE_HOST_SHUTDOWN = "host_shutdown" SERVICE_HOST_REBOOT = "host_reboot" @@ -110,6 +123,7 @@ MAP_SERVICE_API = { SERVICE_ADDON_START: ("/addons/{addon}/start", SCHEMA_ADDON, 60, False), SERVICE_ADDON_STOP: ("/addons/{addon}/stop", SCHEMA_ADDON, 60, False), SERVICE_ADDON_RESTART: ("/addons/{addon}/restart", SCHEMA_ADDON, 60, False), + SERVICE_ADDON_UPDATE: ("/addons/{addon}/update", SCHEMA_ADDON, 60, False), SERVICE_ADDON_STDIN: ("/addons/{addon}/stdin", SCHEMA_ADDON_STDIN, 60, False), SERVICE_HOST_SHUTDOWN: ("/host/shutdown", SCHEMA_NO_DATA, 60, False), SERVICE_HOST_REBOOT: ("/host/reboot", SCHEMA_NO_DATA, 60, False), @@ -286,13 +300,17 @@ def get_supervisor_ip(): return os.environ["SUPERVISOR"].partition(":")[0] -async def async_setup(hass, config): +async def async_setup(hass: HomeAssistant, config: Config) -> bool: """Set up the Hass.io component.""" # Check local setup for env in ("HASSIO", "HASSIO_TOKEN"): if os.environ.get(env): continue _LOGGER.error("Missing %s environment variable", env) + if config_entries := hass.config_entries.async_entries(DOMAIN): + hass.async_create_task( + hass.config_entries.async_remove(config_entries[0].entry_id) + ) return False async_load_websocket_api(hass) @@ -402,6 +420,8 @@ async def async_setup(hass, config): hass.data[DATA_CORE_INFO] = await hassio.get_core_info() hass.data[DATA_SUPERVISOR_INFO] = await hassio.get_supervisor_info() hass.data[DATA_OS_INFO] = await hassio.get_os_info() + if ADDONS_COORDINATOR in hass.data: + await hass.data[ADDONS_COORDINATOR].async_refresh() except HassioAPIError as err: _LOGGER.warning("Can't read last version: %s", err) @@ -455,4 +475,143 @@ async def async_setup(hass, config): # Init add-on ingress panels await async_setup_addon_panel(hass, hassio) + hass.async_create_task( + hass.config_entries.flow.async_init(DOMAIN, context={"source": "system"}) + ) + return True + + +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Set up a config entry.""" + dev_reg = await async_get_registry(hass) + coordinator = HassioDataUpdateCoordinator(hass, config_entry, dev_reg) + hass.data[ADDONS_COORDINATOR] = coordinator + await coordinator.async_refresh() + + for platform in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, platform) + ) + + return True + + +async def async_unload_entry( + hass: HomeAssistantType, config_entry: ConfigEntry +) -> bool: + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS + ] + ) + ) + + # Pop add-on data + hass.data.pop(ADDONS_COORDINATOR, None) + + return unload_ok + + +@callback +def async_register_addons_in_dev_reg( + entry_id: str, dev_reg: DeviceRegistry, addons: List[Dict[str, Any]] +) -> None: + """Register addons in the device registry.""" + for addon in addons: + dev_reg.async_get_or_create( + config_entry_id=entry_id, + identifiers={(DOMAIN, addon[ATTR_SLUG])}, + manufacturer=addon.get(ATTR_REPOSITORY) or addon.get(ATTR_URL) or "unknown", + model="Home Assistant Add-on", + sw_version=addon[ATTR_VERSION], + name=addon[ATTR_NAME], + entry_type=ATTR_SERVICE, + ) + + +@callback +def async_register_os_in_dev_reg( + entry_id: str, dev_reg: DeviceRegistry, os_dict: Dict[str, Any] +) -> None: + """Register OS in the device registry.""" + dev_reg.async_get_or_create( + config_entry_id=entry_id, + identifiers={(DOMAIN, "OS")}, + manufacturer="Home Assistant", + model="Home Assistant Operating System", + sw_version=os_dict[ATTR_VERSION], + name="Home Assistant Operating System", + entry_type=ATTR_SERVICE, + ) + + +@callback +def async_remove_addons_from_dev_reg( + dev_reg: DeviceRegistry, addons: List[Dict[str, Any]] +) -> None: + """Remove addons from the device registry.""" + for addon_slug in addons: + if dev := dev_reg.async_get_device({(DOMAIN, addon_slug)}): + dev_reg.async_remove_device(dev.id) + + +class HassioDataUpdateCoordinator(DataUpdateCoordinator): + """Class to retrieve Hass.io status.""" + + def __init__( + self, hass: HomeAssistant, config_entry: ConfigEntry, dev_reg: DeviceRegistry + ) -> None: + """Initialize coordinator.""" + super().__init__( + hass, + _LOGGER, + name=DOMAIN, + update_method=self._async_update_data, + ) + self.data = {} + self.entry_id = config_entry.entry_id + self.dev_reg = dev_reg + self.is_hass_os = "hassos" in get_info(self.hass) + + async def _async_update_data(self) -> Dict[str, Any]: + """Update data via library.""" + new_data = {} + addon_data = get_supervisor_info(self.hass) + + new_data["addons"] = { + addon[ATTR_SLUG]: addon for addon in addon_data.get("addons", []) + } + if self.is_hass_os: + new_data["os"] = get_os_info(self.hass) + + # If this is the initial refresh, register all addons and return the dict + if not self.data: + async_register_addons_in_dev_reg( + self.entry_id, self.dev_reg, new_data["addons"].values() + ) + if self.is_hass_os: + async_register_os_in_dev_reg( + self.entry_id, self.dev_reg, new_data["os"] + ) + return new_data + + # Remove add-ons that are no longer installed from device registry + if removed_addons := list( + set(self.data["addons"].keys()) - set(new_data["addons"].keys()) + ): + async_remove_addons_from_dev_reg(self.dev_reg, removed_addons) + + # If there are new add-ons, we should reload the config entry so we can + # create new devices and entities. We can return an empty dict because + # coordinator will be recreated. + if list(set(new_data["addons"].keys()) - set(self.data["addons"].keys())): + self.hass.async_create_task( + self.hass.config_entries.async_reload(self.entry_id) + ) + return {} + + return new_data diff --git a/homeassistant/components/hassio/binary_sensor.py b/homeassistant/components/hassio/binary_sensor.py new file mode 100644 index 00000000000..c3daaa07f28 --- /dev/null +++ b/homeassistant/components/hassio/binary_sensor.py @@ -0,0 +1,50 @@ +"""Binary sensor platform for Hass.io addons.""" +from typing import Callable, List + +from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity + +from . import ADDONS_COORDINATOR +from .const import ATTR_UPDATE_AVAILABLE +from .entity import HassioAddonEntity, HassioOSEntity + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: Callable[[List[Entity], bool], None], +) -> None: + """Binary sensor set up for Hass.io config entry.""" + coordinator = hass.data[ADDONS_COORDINATOR] + + entities = [ + HassioAddonBinarySensor( + coordinator, addon, ATTR_UPDATE_AVAILABLE, "Update Available" + ) + for addon in coordinator.data.values() + ] + if coordinator.is_hass_os: + entities.append( + HassioOSBinarySensor(coordinator, ATTR_UPDATE_AVAILABLE, "Update Available") + ) + async_add_entities(entities) + + +class HassioAddonBinarySensor(HassioAddonEntity, BinarySensorEntity): + """Binary sensor to track whether an update is available for a Hass.io add-on.""" + + @property + def is_on(self) -> bool: + """Return true if the binary sensor is on.""" + return self.addon_info[self.attribute_name] + + +class HassioOSBinarySensor(HassioOSEntity, BinarySensorEntity): + """Binary sensor to track whether an update is available for Hass.io OS.""" + + @property + def is_on(self) -> bool: + """Return true if the binary sensor is on.""" + return self.os_info[self.attribute_name] diff --git a/homeassistant/components/hassio/config_flow.py b/homeassistant/components/hassio/config_flow.py new file mode 100644 index 00000000000..56c7d324a61 --- /dev/null +++ b/homeassistant/components/hassio/config_flow.py @@ -0,0 +1,22 @@ +"""Config flow for Home Assistant Supervisor integration.""" +import logging + +from homeassistant import config_entries + +from . import DOMAIN # pylint:disable=unused-import + +_LOGGER = logging.getLogger(__name__) + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Home Assistant Supervisor.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + + async def async_step_system(self, user_input=None): + """Handle the initial step.""" + # We only need one Hass.io config entry + await self.async_set_unique_id(DOMAIN) + self._abort_if_unique_id_configured() + return self.async_create_entry(title=DOMAIN.title(), data={}) diff --git a/homeassistant/components/hassio/const.py b/homeassistant/components/hassio/const.py index b2878c8143f..417a62a1a8c 100644 --- a/homeassistant/components/hassio/const.py +++ b/homeassistant/components/hassio/const.py @@ -29,7 +29,6 @@ X_INGRESS_PATH = "X-Ingress-Path" X_HASS_USER_ID = "X-Hass-User-ID" X_HASS_IS_ADMIN = "X-Hass-Is-Admin" - WS_TYPE = "type" WS_ID = "id" @@ -38,3 +37,11 @@ WS_TYPE_EVENT = "supervisor/event" WS_TYPE_SUBSCRIBE = "supervisor/subscribe" EVENT_SUPERVISOR_EVENT = "supervisor_event" + +# Add-on keys +ATTR_VERSION = "version" +ATTR_VERSION_LATEST = "version_latest" +ATTR_UPDATE_AVAILABLE = "update_available" +ATTR_SLUG = "slug" +ATTR_URL = "url" +ATTR_REPOSITORY = "repository" diff --git a/homeassistant/components/hassio/entity.py b/homeassistant/components/hassio/entity.py new file mode 100644 index 00000000000..daadeb514a2 --- /dev/null +++ b/homeassistant/components/hassio/entity.py @@ -0,0 +1,93 @@ +"""Base for Hass.io entities.""" +from typing import Any, Dict + +from homeassistant.const import ATTR_NAME +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from . import DOMAIN, HassioDataUpdateCoordinator +from .const import ATTR_SLUG + + +class HassioAddonEntity(CoordinatorEntity): + """Base entity for a Hass.io add-on.""" + + def __init__( + self, + coordinator: HassioDataUpdateCoordinator, + addon: Dict[str, Any], + attribute_name: str, + sensor_name: str, + ) -> None: + """Initialize base entity.""" + self.addon_slug = addon[ATTR_SLUG] + self.addon_name = addon[ATTR_NAME] + self._data_key = "addons" + self.attribute_name = attribute_name + self.sensor_name = sensor_name + super().__init__(coordinator) + + @property + def addon_info(self) -> Dict[str, Any]: + """Return add-on info.""" + return self.coordinator.data[self._data_key][self.addon_slug] + + @property + def name(self) -> str: + """Return entity name.""" + return f"{self.addon_name}: {self.sensor_name}" + + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + return False + + @property + def unique_id(self) -> str: + """Return unique ID for entity.""" + return f"{self.addon_slug}_{self.attribute_name}" + + @property + def device_info(self) -> Dict[str, Any]: + """Return device specific attributes.""" + return {"identifiers": {(DOMAIN, self.addon_slug)}} + + +class HassioOSEntity(CoordinatorEntity): + """Base Entity for Hass.io OS.""" + + def __init__( + self, + coordinator: HassioDataUpdateCoordinator, + attribute_name: str, + sensor_name: str, + ) -> None: + """Initialize base entity.""" + self._data_key = "os" + self.attribute_name = attribute_name + self.sensor_name = sensor_name + super().__init__(coordinator) + + @property + def os_info(self) -> Dict[str, Any]: + """Return OS info.""" + return self.coordinator.data[self._data_key] + + @property + def name(self) -> str: + """Return entity name.""" + return f"Home Assistant Operating System: {self.sensor_name}" + + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + return False + + @property + def unique_id(self) -> str: + """Return unique ID for entity.""" + return f"home_assistant_os_{self.attribute_name}" + + @property + def device_info(self) -> Dict[str, Any]: + """Return device specific attributes.""" + return {"identifiers": {(DOMAIN, "OS")}} diff --git a/homeassistant/components/hassio/sensor.py b/homeassistant/components/hassio/sensor.py new file mode 100644 index 00000000000..857f4831587 --- /dev/null +++ b/homeassistant/components/hassio/sensor.py @@ -0,0 +1,52 @@ +"""Sensor platform for Hass.io addons.""" +from typing import Callable, List + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity + +from . import ADDONS_COORDINATOR +from .const import ATTR_VERSION, ATTR_VERSION_LATEST +from .entity import HassioAddonEntity, HassioOSEntity + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: Callable[[List[Entity], bool], None], +) -> None: + """Sensor set up for Hass.io config entry.""" + coordinator = hass.data[ADDONS_COORDINATOR] + + entities = [] + + for attribute_name, sensor_name in ( + (ATTR_VERSION, "Version"), + (ATTR_VERSION_LATEST, "Newest Version"), + ): + for addon in coordinator.data.values(): + entities.append( + HassioAddonSensor(coordinator, addon, attribute_name, sensor_name) + ) + if coordinator.is_hass_os: + entities.append(HassioOSSensor(coordinator, attribute_name, sensor_name)) + + async_add_entities(entities) + + +class HassioAddonSensor(HassioAddonEntity): + """Sensor to track a Hass.io add-on attribute.""" + + @property + def state(self) -> str: + """Return state of entity.""" + return self.addon_info[self.attribute_name] + + +class HassioOSSensor(HassioOSEntity): + """Sensor to track a Hass.io add-on attribute.""" + + @property + def state(self) -> str: + """Return state of entity.""" + return self.os_info[self.attribute_name] diff --git a/homeassistant/components/hassio/services.yaml b/homeassistant/components/hassio/services.yaml index 3570a857c55..0652b65d6e2 100644 --- a/homeassistant/components/hassio/services.yaml +++ b/homeassistant/components/hassio/services.yaml @@ -46,6 +46,18 @@ addon_stop: selector: addon: +addon_update: + name: Update add-on. + description: Update add-on. This service should be used with caution since add-on updates can contain breaking changes. It is highly recommended that you review release notes/change logs before updating an add-on. + fields: + addon: + name: Add-on + required: true + description: The add-on slug. + example: core_ssh + selector: + addon: + host_reboot: name: Reboot the host system. description: Reboot the host system. diff --git a/tests/components/hassio/test_config_flow.py b/tests/components/hassio/test_config_flow.py new file mode 100644 index 00000000000..c2d306183f0 --- /dev/null +++ b/tests/components/hassio/test_config_flow.py @@ -0,0 +1,36 @@ +"""Test the Home Assistant Supervisor config flow.""" +from unittest.mock import patch + +from homeassistant import setup +from homeassistant.components.hassio import DOMAIN + + +async def test_config_flow(hass): + """Test we get the form.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch( + "homeassistant.components.hassio.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.hassio.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "system"} + ) + assert result["type"] == "create_entry" + assert result["title"] == DOMAIN.title() + assert result["data"] == {} + await hass.async_block_till_done() + + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_multiple_entries(hass): + """Test creating multiple hassio entries.""" + await test_config_flow(hass) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "system"} + ) + assert result["type"] == "abort" + assert result["reason"] == "already_configured" diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index 2efb5b0744e..bd9eb30be5c 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -1,17 +1,91 @@ """The tests for the hassio component.""" +from datetime import timedelta import os from unittest.mock import patch +import pytest + from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.components import frontend -from homeassistant.components.hassio import STORAGE_KEY +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN +from homeassistant.components.hassio import ADDONS_COORDINATOR, DOMAIN, STORAGE_KEY +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.helpers.device_registry import async_get from homeassistant.setup import async_setup_component +from homeassistant.util import dt as dt_util -from . import mock_all # noqa +from tests.common import MockConfigEntry, async_fire_time_changed MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"} +@pytest.fixture(autouse=True) +def mock_all(aioclient_mock, request): + """Mock all setup requests.""" + aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"}) + aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"}) + aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"}) + aioclient_mock.get( + "http://127.0.0.1/info", + json={ + "result": "ok", + "data": {"supervisor": "222", "homeassistant": "0.110.0", "hassos": None}, + }, + ) + aioclient_mock.get( + "http://127.0.0.1/host/info", + json={ + "result": "ok", + "data": { + "result": "ok", + "data": { + "chassis": "vm", + "operating_system": "Debian GNU/Linux 10 (buster)", + "kernel": "4.19.0-6-amd64", + }, + }, + }, + ) + aioclient_mock.get( + "http://127.0.0.1/core/info", + json={"result": "ok", "data": {"version_latest": "1.0.0"}}, + ) + aioclient_mock.get( + "http://127.0.0.1/os/info", + json={"result": "ok", "data": {"version_latest": "1.0.0"}}, + ) + aioclient_mock.get( + "http://127.0.0.1/supervisor/info", + json={ + "result": "ok", + "data": {"version_latest": "1.0.0"}, + "addons": [ + { + "name": "test", + "slug": "test", + "installed": True, + "update_available": False, + "version": "1.0.0", + "version_latest": "1.0.0", + "url": "https://github.com/home-assistant/addons/test", + }, + { + "name": "test2", + "slug": "test2", + "installed": True, + "update_available": False, + "version": "1.0.0", + "version_latest": "1.0.0", + "url": "https://github.com", + }, + ], + }, + ) + aioclient_mock.get( + "http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}} + ) + + async def test_setup_api_ping(hass, aioclient_mock): """Test setup with API ping.""" with patch.dict(os.environ, MOCK_ENVIRON): @@ -193,6 +267,7 @@ async def test_service_register(hassio_env, hass): assert hass.services.has_service("hassio", "addon_start") assert hass.services.has_service("hassio", "addon_stop") assert hass.services.has_service("hassio", "addon_restart") + assert hass.services.has_service("hassio", "addon_update") assert hass.services.has_service("hassio", "addon_stdin") assert hass.services.has_service("hassio", "host_shutdown") assert hass.services.has_service("hassio", "host_reboot") @@ -210,6 +285,7 @@ async def test_service_calls(hassio_env, hass, aioclient_mock): aioclient_mock.post("http://127.0.0.1/addons/test/start", json={"result": "ok"}) aioclient_mock.post("http://127.0.0.1/addons/test/stop", json={"result": "ok"}) aioclient_mock.post("http://127.0.0.1/addons/test/restart", json={"result": "ok"}) + aioclient_mock.post("http://127.0.0.1/addons/test/update", json={"result": "ok"}) aioclient_mock.post("http://127.0.0.1/addons/test/stdin", json={"result": "ok"}) aioclient_mock.post("http://127.0.0.1/host/shutdown", json={"result": "ok"}) aioclient_mock.post("http://127.0.0.1/host/reboot", json={"result": "ok"}) @@ -225,19 +301,20 @@ async def test_service_calls(hassio_env, hass, aioclient_mock): await hass.services.async_call("hassio", "addon_start", {"addon": "test"}) await hass.services.async_call("hassio", "addon_stop", {"addon": "test"}) await hass.services.async_call("hassio", "addon_restart", {"addon": "test"}) + await hass.services.async_call("hassio", "addon_update", {"addon": "test"}) await hass.services.async_call( "hassio", "addon_stdin", {"addon": "test", "input": "test"} ) await hass.async_block_till_done() - assert aioclient_mock.call_count == 7 + assert aioclient_mock.call_count == 8 assert aioclient_mock.mock_calls[-1][2] == "test" await hass.services.async_call("hassio", "host_shutdown", {}) await hass.services.async_call("hassio", "host_reboot", {}) await hass.async_block_till_done() - assert aioclient_mock.call_count == 9 + assert aioclient_mock.call_count == 10 await hass.services.async_call("hassio", "snapshot_full", {}) await hass.services.async_call( @@ -247,7 +324,7 @@ async def test_service_calls(hassio_env, hass, aioclient_mock): ) await hass.async_block_till_done() - assert aioclient_mock.call_count == 11 + assert aioclient_mock.call_count == 12 assert aioclient_mock.mock_calls[-1][2] == { "addons": ["test"], "folders": ["ssl"], @@ -268,7 +345,7 @@ async def test_service_calls(hassio_env, hass, aioclient_mock): ) await hass.async_block_till_done() - assert aioclient_mock.call_count == 13 + assert aioclient_mock.call_count == 14 assert aioclient_mock.mock_calls[-1][2] == { "addons": ["test"], "folders": ["ssl"], @@ -302,3 +379,143 @@ async def test_service_calls_core(hassio_env, hass, aioclient_mock): assert mock_check_config.called assert aioclient_mock.call_count == 5 + + +async def test_entry_load_and_unload(hass): + """Test loading and unloading config entry.""" + with patch.dict(os.environ, MOCK_ENVIRON): + config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert SENSOR_DOMAIN in hass.config.components + assert BINARY_SENSOR_DOMAIN in hass.config.components + assert ADDONS_COORDINATOR in hass.data + + assert await config_entry.async_unload(hass) + await hass.async_block_till_done() + assert ADDONS_COORDINATOR not in hass.data + + +async def test_migration_off_hassio(hass): + """Test that when a user moves instance off Hass.io, config entry gets cleaned up.""" + config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) + config_entry.add_to_hass(hass) + assert not await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert hass.config_entries.async_entries(DOMAIN) == [] + + +async def test_device_registry_calls(hass): + """Test device registry entries for hassio.""" + dev_reg = async_get(hass) + supervisor_mock_data = { + "addons": [ + { + "name": "test", + "slug": "test", + "installed": True, + "update_available": False, + "version": "1.0.0", + "version_latest": "1.0.0", + "repository": "test", + "url": "https://github.com/home-assistant/addons/test", + }, + { + "name": "test2", + "slug": "test2", + "installed": True, + "update_available": False, + "version": "1.0.0", + "version_latest": "1.0.0", + "url": "https://github.com", + }, + ] + } + os_mock_data = { + "board": "odroid-n2", + "boot": "A", + "update_available": False, + "version": "5.12", + "version_latest": "5.12", + } + + with patch.dict(os.environ, MOCK_ENVIRON), patch( + "homeassistant.components.hassio.HassIO.get_supervisor_info", + return_value=supervisor_mock_data, + ), patch( + "homeassistant.components.hassio.HassIO.get_os_info", + return_value=os_mock_data, + ): + config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert len(dev_reg.devices) == 3 + + supervisor_mock_data = { + "addons": [ + { + "name": "test2", + "slug": "test2", + "installed": True, + "update_available": False, + "version": "1.0.0", + "version_latest": "1.0.0", + "url": "https://github.com", + }, + ] + } + + # Test that when addon is removed, next update will remove the add-on and subsequent updates won't + with patch( + "homeassistant.components.hassio.HassIO.get_supervisor_info", + return_value=supervisor_mock_data, + ), patch( + "homeassistant.components.hassio.HassIO.get_os_info", + return_value=os_mock_data, + ): + async_fire_time_changed(hass, dt_util.now() + timedelta(hours=1)) + await hass.async_block_till_done() + assert len(dev_reg.devices) == 2 + + async_fire_time_changed(hass, dt_util.now() + timedelta(hours=2)) + await hass.async_block_till_done() + assert len(dev_reg.devices) == 2 + + supervisor_mock_data = { + "addons": [ + { + "name": "test2", + "slug": "test2", + "installed": True, + "update_available": False, + "version": "1.0.0", + "version_latest": "1.0.0", + "url": "https://github.com", + }, + { + "name": "test3", + "slug": "test3", + "installed": True, + "update_available": False, + "version": "1.0.0", + "version_latest": "1.0.0", + "url": "https://github.com", + }, + ] + } + + # Test that when addon is added, next update will reload the entry so we register + # a new device + with patch( + "homeassistant.components.hassio.HassIO.get_supervisor_info", + return_value=supervisor_mock_data, + ), patch( + "homeassistant.components.hassio.HassIO.get_os_info", + return_value=os_mock_data, + ): + async_fire_time_changed(hass, dt_util.now() + timedelta(hours=3)) + await hass.async_block_till_done() + assert len(dev_reg.devices) == 3 From e1d24c69b81f1da5df2ffd7929037a825cd98321 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 1 Mar 2021 10:28:41 +0100 Subject: [PATCH 081/831] Improve CI workflow (#46696) --- .github/workflows/ci.yaml | 277 ++++++++++++++++---------------------- 1 file changed, 117 insertions(+), 160 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 28410123914..b7599340506 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,7 +12,7 @@ on: env: CACHE_VERSION: 1 DEFAULT_PYTHON: 3.8 - PRE_COMMIT_HOME: ~/.cache/pre-commit + PRE_COMMIT_CACHE: ~/.cache/pre-commit jobs: # Separate job to pre-populate the base dependency cache @@ -20,6 +20,9 @@ jobs: prepare-base: name: Prepare base dependencies runs-on: ubuntu-latest + outputs: + python-key: ${{ steps.generate-python-key.outputs.key }} + pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }} steps: - name: Check out code from GitHub uses: actions/checkout@v2 @@ -28,21 +31,25 @@ jobs: uses: actions/setup-python@v2.2.1 with: python-version: ${{ env.DEFAULT_PYTHON }} + - name: Generate partial Python venv restore key + id: generate-python-key + run: >- + echo "::set-output name=key::base-venv-${{ env.CACHE_VERSION }}-${{ + hashFiles('requirements.txt') }}-${{ + hashFiles('requirements_test.txt') }}-${{ + hashFiles('homeassistant/package_constraints.txt') }}" - name: Restore base Python virtual environment id: cache-venv uses: actions/cache@v2.1.4 with: path: venv key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + steps.generate-python-key.outputs.key }} restore-keys: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements_test.txt') }}- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements.txt') }} - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements_test.txt') }}- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements.txt') }}- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}- - name: Create Python virtual environment if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -50,15 +57,20 @@ jobs: . venv/bin/activate pip install -U "pip<20.3" setuptools pip install -r requirements.txt -r requirements_test.txt + - name: Generate partial pre-commit restore key + id: generate-pre-commit-key + run: >- + echo "::set-output name=key::pre-commit-${{ env.CACHE_VERSION }}-${{ + hashFiles('.pre-commit-config.yaml') }}" - name: Restore pre-commit environment from cache id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} + path: ${{ env.PRE_COMMIT_CACHE }} + key: >- + ${{ runner.os }}-${{ steps.generate-pre-commit-key.outputs.key }} restore-keys: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit- + ${{ runner.os }}-pre-commit-${{ env.CACHE_VERSION }}- - name: Install pre-commit dependencies if: steps.cache-precommit.outputs.cache-hit != 'true' run: | @@ -82,12 +94,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -97,13 +105,12 @@ jobs: id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Fail job if cache restore failed - if: steps.cache-venv.outputs.cache-hit != 'true' + path: ${{ env.PRE_COMMIT_CACHE }} + key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} + - name: Fail job if pre-commit cache restore failed + if: steps.cache-precommit.outputs.cache-hit != 'true' run: | - echo "Failed to restore Python virtual environment from cache" + echo "Failed to restore pre-commit environment from cache" exit 1 - name: Run bandit run: | @@ -127,12 +134,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -142,13 +145,12 @@ jobs: id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Fail job if cache restore failed - if: steps.cache-venv.outputs.cache-hit != 'true' + path: ${{ env.PRE_COMMIT_CACHE }} + key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} + - name: Fail job if pre-commit cache restore failed + if: steps.cache-precommit.outputs.cache-hit != 'true' run: | - echo "Failed to restore Python virtual environment from cache" + echo "Failed to restore pre-commit environment from cache" exit 1 - name: Run black run: | @@ -172,12 +174,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -187,13 +185,12 @@ jobs: id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Fail job if cache restore failed - if: steps.cache-venv.outputs.cache-hit != 'true' + path: ${{ env.PRE_COMMIT_CACHE }} + key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} + - name: Fail job if pre-commit cache restore failed + if: steps.cache-precommit.outputs.cache-hit != 'true' run: | - echo "Failed to restore Python virtual environment from cache" + echo "Failed to restore pre-commit environment from cache" exit 1 - name: Register codespell problem matcher run: | @@ -239,12 +236,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -254,13 +247,12 @@ jobs: id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Fail job if cache restore failed - if: steps.cache-venv.outputs.cache-hit != 'true' + path: ${{ env.PRE_COMMIT_CACHE }} + key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} + - name: Fail job if pre-commit cache restore failed + if: steps.cache-precommit.outputs.cache-hit != 'true' run: | - echo "Failed to restore Python virtual environment from cache" + echo "Failed to restore pre-commit environment from cache" exit 1 - name: Register check executables problem matcher run: | @@ -287,12 +279,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -302,13 +290,12 @@ jobs: id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Fail job if cache restore failed - if: steps.cache-venv.outputs.cache-hit != 'true' + path: ${{ env.PRE_COMMIT_CACHE }} + key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} + - name: Fail job if pre-commit cache restore failed + if: steps.cache-precommit.outputs.cache-hit != 'true' run: | - echo "Failed to restore Python virtual environment from cache" + echo "Failed to restore pre-commit environment from cache" exit 1 - name: Register flake8 problem matcher run: | @@ -335,12 +322,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -350,13 +333,12 @@ jobs: id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Fail job if cache restore failed - if: steps.cache-venv.outputs.cache-hit != 'true' + path: ${{ env.PRE_COMMIT_CACHE }} + key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} + - name: Fail job if pre-commit cache restore failed + if: steps.cache-precommit.outputs.cache-hit != 'true' run: | - echo "Failed to restore Python virtual environment from cache" + echo "Failed to restore pre-commit environment from cache" exit 1 - name: Run isort run: | @@ -380,12 +362,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -395,13 +373,12 @@ jobs: id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Fail job if cache restore failed - if: steps.cache-venv.outputs.cache-hit != 'true' + path: ${{ env.PRE_COMMIT_CACHE }} + key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} + - name: Fail job if pre-commit cache restore failed + if: steps.cache-precommit.outputs.cache-hit != 'true' run: | - echo "Failed to restore Python virtual environment from cache" + echo "Failed to restore pre-commit environment from cache" exit 1 - name: Register check-json problem matcher run: | @@ -428,12 +405,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -443,13 +416,12 @@ jobs: id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Fail job if cache restore failed - if: steps.cache-venv.outputs.cache-hit != 'true' + path: ${{ env.PRE_COMMIT_CACHE }} + key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} + - name: Fail job if pre-commit cache restore failed + if: steps.cache-precommit.outputs.cache-hit != 'true' run: | - echo "Failed to restore Python virtual environment from cache" + echo "Failed to restore pre-commit environment from cache" exit 1 - name: Run pyupgrade run: | @@ -484,12 +456,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -499,13 +467,12 @@ jobs: id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Fail job if cache restore failed - if: steps.cache-venv.outputs.cache-hit != 'true' + path: ${{ env.PRE_COMMIT_CACHE }} + key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} + - name: Fail job if pre-commit cache restore failed + if: steps.cache-precommit.outputs.cache-hit != 'true' run: | - echo "Failed to restore Python virtual environment from cache" + echo "Failed to restore pre-commit environment from cache" exit 1 - name: Register yamllint problem matcher run: | @@ -531,11 +498,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ - matrix.python-version }}-${{ hashFiles('requirements_test.txt') - }}-${{ hashFiles('requirements_all.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ matrix.python-version }}-${{ + needs.prepare-tests.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -563,12 +527,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -585,24 +545,31 @@ jobs: strategy: matrix: python-version: [3.8, 3.9] + outputs: + python-key: ${{ steps.generate-python-key.outputs.key }} container: homeassistant/ci-azure:${{ matrix.python-version }} steps: - name: Check out code from GitHub uses: actions/checkout@v2 + - name: Generate partial Python venv restore key + id: generate-python-key + run: >- + echo "::set-output name=key::venv-${{ env.CACHE_VERSION }}-${{ + hashFiles('requirements_test.txt') }}-${{ + hashFiles('requirements_all.txt') }}-${{ + hashFiles('homeassistant/package_constraints.txt') }}" - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv uses: actions/cache@v2.1.4 with: path: venv key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ - matrix.python-version }}-${{ hashFiles('requirements_test.txt') - }}-${{ hashFiles('requirements_all.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + ${{ runner.os }}-${{ matrix.python-version }}-${{ + steps.generate-python-key.outputs.key }} restore-keys: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ matrix.python-version }}-${{ hashFiles('requirements_test.txt') }}-${{ hashFiles('requirements_all.txt') }} - ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ matrix.python-version }}-${{ hashFiles('requirements_test.txt') }} - ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ matrix.python-version }}- + ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements_test.txt') }}-${{ hashFiles('requirements_all.txt') }}- + ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements_test.txt') }}- + ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}- - name: Create full Python ${{ matrix.python-version }} virtual environment if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -633,11 +600,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ - matrix.python-version }}-${{ hashFiles('requirements_test.txt') - }}-${{ hashFiles('requirements_all.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ matrix.python-version }}-${{ + needs.prepare-tests.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -667,11 +631,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ - matrix.python-version }}-${{ hashFiles('requirements_test.txt') - }}-${{ hashFiles('requirements_all.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ matrix.python-version }}-${{ + needs.prepare-tests.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -689,6 +650,7 @@ jobs: runs-on: ubuntu-latest needs: prepare-tests strategy: + fail-fast: false matrix: group: [1, 2, 3, 4] python-version: [3.8, 3.9] @@ -703,11 +665,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ - matrix.python-version }}-${{ hashFiles('requirements_test.txt') - }}-${{ hashFiles('requirements_all.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ matrix.python-version }}-${{ + needs.prepare-tests.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -735,6 +694,7 @@ jobs: --test-group-count 4 \ --test-group=${{ matrix.group }} \ --cov homeassistant \ + --cov-report= \ -o console_output_style=count \ -p no:sugar \ tests @@ -750,7 +710,7 @@ jobs: coverage: name: Process test coverage runs-on: ubuntu-latest - needs: pytest + needs: ["prepare-tests", "pytest"] strategy: matrix: python-version: [3.8] @@ -763,11 +723,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ - matrix.python-version }}-${{ hashFiles('requirements_test.txt') - }}-${{ hashFiles('requirements_all.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ matrix.python-version }}-${{ + needs.prepare-tests.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | From 732db3b67c88a920c1028f25c406b7175850f431 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Mon, 1 Mar 2021 10:31:13 +0100 Subject: [PATCH 082/831] Revert const replacement in fritzbox_callmonitor (#47211) --- .../components/fritzbox_callmonitor/config_flow.py | 4 ++-- homeassistant/components/fritzbox_callmonitor/const.py | 1 + tests/components/fritzbox_callmonitor/test_config_flow.py | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/fritzbox_callmonitor/config_flow.py b/homeassistant/components/fritzbox_callmonitor/config_flow.py index 01a43f7c7ef..a08450e20a1 100644 --- a/homeassistant/components/fritzbox_callmonitor/config_flow.py +++ b/homeassistant/components/fritzbox_callmonitor/config_flow.py @@ -7,7 +7,6 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import ( - ATTR_NAME, CONF_HOST, CONF_NAME, CONF_PASSWORD, @@ -28,6 +27,7 @@ from .const import ( DEFAULT_USERNAME, DOMAIN, FRITZ_ACTION_GET_INFO, + FRITZ_ATTR_NAME, FRITZ_ATTR_SERIAL_NUMBER, FRITZ_SERVICE_DEVICE_INFO, SERIAL_NUMBER, @@ -119,7 +119,7 @@ class FritzBoxCallMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): phonebook_info = await self.hass.async_add_executor_job( self._fritzbox_phonebook.fph.phonebook_info, phonebook_id ) - return phonebook_info[ATTR_NAME] + return phonebook_info[FRITZ_ATTR_NAME] async def _get_list_of_phonebook_names(self): """Return list of names for all available phonebooks.""" diff --git a/homeassistant/components/fritzbox_callmonitor/const.py b/homeassistant/components/fritzbox_callmonitor/const.py index 6f0c87f5273..a71f14401b3 100644 --- a/homeassistant/components/fritzbox_callmonitor/const.py +++ b/homeassistant/components/fritzbox_callmonitor/const.py @@ -15,6 +15,7 @@ ICON_PHONE = "mdi:phone" ATTR_PREFIXES = "prefixes" FRITZ_ACTION_GET_INFO = "GetInfo" +FRITZ_ATTR_NAME = "name" FRITZ_ATTR_SERIAL_NUMBER = "NewSerialNumber" FRITZ_SERVICE_DEVICE_INFO = "DeviceInfo" diff --git a/tests/components/fritzbox_callmonitor/test_config_flow.py b/tests/components/fritzbox_callmonitor/test_config_flow.py index cde30b615eb..00bc1e18679 100644 --- a/tests/components/fritzbox_callmonitor/test_config_flow.py +++ b/tests/components/fritzbox_callmonitor/test_config_flow.py @@ -14,12 +14,12 @@ from homeassistant.components.fritzbox_callmonitor.const import ( CONF_PHONEBOOK, CONF_PREFIXES, DOMAIN, + FRITZ_ATTR_NAME, FRITZ_ATTR_SERIAL_NUMBER, SERIAL_NUMBER, ) from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.const import ( - ATTR_NAME, CONF_HOST, CONF_NAME, CONF_PASSWORD, @@ -69,8 +69,8 @@ MOCK_YAML_CONFIG = { CONF_NAME: MOCK_NAME, } MOCK_DEVICE_INFO = {FRITZ_ATTR_SERIAL_NUMBER: MOCK_SERIAL_NUMBER} -MOCK_PHONEBOOK_INFO_1 = {ATTR_NAME: MOCK_PHONEBOOK_NAME_1} -MOCK_PHONEBOOK_INFO_2 = {ATTR_NAME: MOCK_PHONEBOOK_NAME_2} +MOCK_PHONEBOOK_INFO_1 = {FRITZ_ATTR_NAME: MOCK_PHONEBOOK_NAME_1} +MOCK_PHONEBOOK_INFO_2 = {FRITZ_ATTR_NAME: MOCK_PHONEBOOK_NAME_2} MOCK_UNIQUE_ID = f"{MOCK_SERIAL_NUMBER}-{MOCK_PHONEBOOK_ID}" From 2de01ddaeb67aab200c17df8d1befd62e6f06cd5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Mar 2021 03:35:23 -0600 Subject: [PATCH 083/831] Remove griddy integration (#47218) --- CODEOWNERS | 1 - homeassistant/components/griddy/__init__.py | 96 ------------------- .../components/griddy/config_flow.py | 75 --------------- homeassistant/components/griddy/const.py | 7 -- homeassistant/components/griddy/manifest.json | 8 -- homeassistant/components/griddy/sensor.py | 48 ---------- homeassistant/components/griddy/strings.json | 16 ---- .../components/griddy/translations/ca.json | 20 ---- .../components/griddy/translations/cs.json | 11 --- .../components/griddy/translations/de.json | 20 ---- .../components/griddy/translations/en.json | 20 ---- .../griddy/translations/es-419.json | 20 ---- .../components/griddy/translations/es.json | 20 ---- .../components/griddy/translations/et.json | 20 ---- .../components/griddy/translations/fr.json | 20 ---- .../components/griddy/translations/it.json | 20 ---- .../components/griddy/translations/ko.json | 20 ---- .../components/griddy/translations/lb.json | 20 ---- .../components/griddy/translations/nl.json | 20 ---- .../components/griddy/translations/no.json | 20 ---- .../components/griddy/translations/pl.json | 20 ---- .../components/griddy/translations/pt-BR.json | 8 -- .../components/griddy/translations/pt.json | 11 --- .../components/griddy/translations/ru.json | 20 ---- .../components/griddy/translations/sl.json | 20 ---- .../components/griddy/translations/sv.json | 8 -- .../components/griddy/translations/tr.json | 11 --- .../components/griddy/translations/uk.json | 20 ---- .../griddy/translations/zh-Hant.json | 20 ---- homeassistant/generated/config_flows.py | 1 - requirements_all.txt | 3 - requirements_test_all.txt | 3 - tests/components/griddy/__init__.py | 1 - tests/components/griddy/test_config_flow.py | 56 ----------- tests/components/griddy/test_sensor.py | 39 -------- 35 files changed, 743 deletions(-) delete mode 100644 homeassistant/components/griddy/__init__.py delete mode 100644 homeassistant/components/griddy/config_flow.py delete mode 100644 homeassistant/components/griddy/const.py delete mode 100644 homeassistant/components/griddy/manifest.json delete mode 100644 homeassistant/components/griddy/sensor.py delete mode 100644 homeassistant/components/griddy/strings.json delete mode 100644 homeassistant/components/griddy/translations/ca.json delete mode 100644 homeassistant/components/griddy/translations/cs.json delete mode 100644 homeassistant/components/griddy/translations/de.json delete mode 100644 homeassistant/components/griddy/translations/en.json delete mode 100644 homeassistant/components/griddy/translations/es-419.json delete mode 100644 homeassistant/components/griddy/translations/es.json delete mode 100644 homeassistant/components/griddy/translations/et.json delete mode 100644 homeassistant/components/griddy/translations/fr.json delete mode 100644 homeassistant/components/griddy/translations/it.json delete mode 100644 homeassistant/components/griddy/translations/ko.json delete mode 100644 homeassistant/components/griddy/translations/lb.json delete mode 100644 homeassistant/components/griddy/translations/nl.json delete mode 100644 homeassistant/components/griddy/translations/no.json delete mode 100644 homeassistant/components/griddy/translations/pl.json delete mode 100644 homeassistant/components/griddy/translations/pt-BR.json delete mode 100644 homeassistant/components/griddy/translations/pt.json delete mode 100644 homeassistant/components/griddy/translations/ru.json delete mode 100644 homeassistant/components/griddy/translations/sl.json delete mode 100644 homeassistant/components/griddy/translations/sv.json delete mode 100644 homeassistant/components/griddy/translations/tr.json delete mode 100644 homeassistant/components/griddy/translations/uk.json delete mode 100644 homeassistant/components/griddy/translations/zh-Hant.json delete mode 100644 tests/components/griddy/__init__.py delete mode 100644 tests/components/griddy/test_config_flow.py delete mode 100644 tests/components/griddy/test_sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index b0a31203009..5324c15db6a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -180,7 +180,6 @@ homeassistant/components/google_cloud/* @lufton homeassistant/components/gpsd/* @fabaff homeassistant/components/gree/* @cmroche homeassistant/components/greeneye_monitor/* @jkeljo -homeassistant/components/griddy/* @bdraco homeassistant/components/group/* @home-assistant/core homeassistant/components/growatt_server/* @indykoning homeassistant/components/guardian/* @bachya diff --git a/homeassistant/components/griddy/__init__.py b/homeassistant/components/griddy/__init__.py deleted file mode 100644 index fb5079b00f8..00000000000 --- a/homeassistant/components/griddy/__init__.py +++ /dev/null @@ -1,96 +0,0 @@ -"""The Griddy Power integration.""" -import asyncio -from datetime import timedelta -import logging - -from griddypower.async_api import LOAD_ZONES, AsyncGriddy -import voluptuous as vol - -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import aiohttp_client -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator - -from .const import CONF_LOADZONE, DOMAIN, UPDATE_INTERVAL - -_LOGGER = logging.getLogger(__name__) - -CONFIG_SCHEMA = vol.Schema( - {DOMAIN: vol.Schema({vol.Required(CONF_LOADZONE): vol.In(LOAD_ZONES)})}, - extra=vol.ALLOW_EXTRA, -) - -PLATFORMS = ["sensor"] - - -async def async_setup(hass: HomeAssistant, config: dict): - """Set up the Griddy Power component.""" - - hass.data.setdefault(DOMAIN, {}) - conf = config.get(DOMAIN) - - if not conf: - return True - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={CONF_LOADZONE: conf.get(CONF_LOADZONE)}, - ) - ) - return True - - -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): - """Set up Griddy Power from a config entry.""" - - entry_data = entry.data - - async_griddy = AsyncGriddy( - aiohttp_client.async_get_clientsession(hass), - settlement_point=entry_data[CONF_LOADZONE], - ) - - async def async_update_data(): - """Fetch data from API endpoint.""" - return await async_griddy.async_getnow() - - coordinator = DataUpdateCoordinator( - hass, - _LOGGER, - name="Griddy getnow", - update_method=async_update_data, - update_interval=timedelta(seconds=UPDATE_INTERVAL), - ) - - await coordinator.async_refresh() - - if not coordinator.last_update_success: - raise ConfigEntryNotReady - - hass.data[DOMAIN][entry.entry_id] = coordinator - - for component in PLATFORMS: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) - ) - - return True - - -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): - """Unload a config entry.""" - unload_ok = all( - await asyncio.gather( - *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS - ] - ) - ) - if unload_ok: - hass.data[DOMAIN].pop(entry.entry_id) - - return unload_ok diff --git a/homeassistant/components/griddy/config_flow.py b/homeassistant/components/griddy/config_flow.py deleted file mode 100644 index 675e48cc999..00000000000 --- a/homeassistant/components/griddy/config_flow.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Config flow for Griddy Power integration.""" -import asyncio -import logging - -from aiohttp import ClientError -from griddypower.async_api import LOAD_ZONES, AsyncGriddy -import voluptuous as vol - -from homeassistant import config_entries, core, exceptions -from homeassistant.helpers import aiohttp_client - -from .const import CONF_LOADZONE -from .const import DOMAIN # pylint:disable=unused-import - -_LOGGER = logging.getLogger(__name__) - -DATA_SCHEMA = vol.Schema({vol.Required(CONF_LOADZONE): vol.In(LOAD_ZONES)}) - - -async def validate_input(hass: core.HomeAssistant, data): - """Validate the user input allows us to connect. - - Data has the keys from DATA_SCHEMA with values provided by the user. - """ - client_session = aiohttp_client.async_get_clientsession(hass) - - try: - await AsyncGriddy( - client_session, settlement_point=data[CONF_LOADZONE] - ).async_getnow() - except (asyncio.TimeoutError, ClientError) as err: - raise CannotConnect from err - - # Return info that you want to store in the config entry. - return {"title": f"Load Zone {data[CONF_LOADZONE]}"} - - -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - """Handle a config flow for Griddy Power.""" - - VERSION = 1 - CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL - - async def async_step_user(self, user_input=None): - """Handle the initial step.""" - errors = {} - info = None - if user_input is not None: - try: - info = await validate_input(self.hass, user_input) - except CannotConnect: - errors["base"] = "cannot_connect" - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" - - if "base" not in errors: - await self.async_set_unique_id(user_input[CONF_LOADZONE]) - self._abort_if_unique_id_configured() - return self.async_create_entry(title=info["title"], data=user_input) - - return self.async_show_form( - step_id="user", data_schema=DATA_SCHEMA, errors=errors - ) - - async def async_step_import(self, user_input): - """Handle import.""" - await self.async_set_unique_id(user_input[CONF_LOADZONE]) - self._abort_if_unique_id_configured() - - return await self.async_step_user(user_input) - - -class CannotConnect(exceptions.HomeAssistantError): - """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/griddy/const.py b/homeassistant/components/griddy/const.py deleted file mode 100644 index 034567a806e..00000000000 --- a/homeassistant/components/griddy/const.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Constants for the Griddy Power integration.""" - -DOMAIN = "griddy" - -UPDATE_INTERVAL = 90 - -CONF_LOADZONE = "loadzone" diff --git a/homeassistant/components/griddy/manifest.json b/homeassistant/components/griddy/manifest.json deleted file mode 100644 index 1e31b1b7aa8..00000000000 --- a/homeassistant/components/griddy/manifest.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "domain": "griddy", - "name": "Griddy Power", - "config_flow": true, - "documentation": "https://www.home-assistant.io/integrations/griddy", - "requirements": ["griddypower==0.1.0"], - "codeowners": ["@bdraco"] -} diff --git a/homeassistant/components/griddy/sensor.py b/homeassistant/components/griddy/sensor.py deleted file mode 100644 index f8a900d92be..00000000000 --- a/homeassistant/components/griddy/sensor.py +++ /dev/null @@ -1,48 +0,0 @@ -"""Support for August sensors.""" -from homeassistant.const import CURRENCY_CENT, ENERGY_KILO_WATT_HOUR -from homeassistant.helpers.update_coordinator import CoordinatorEntity - -from .const import CONF_LOADZONE, DOMAIN - - -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up the August sensors.""" - coordinator = hass.data[DOMAIN][config_entry.entry_id] - - settlement_point = config_entry.data[CONF_LOADZONE] - - async_add_entities([GriddyPriceSensor(settlement_point, coordinator)], True) - - -class GriddyPriceSensor(CoordinatorEntity): - """Representation of an August sensor.""" - - def __init__(self, settlement_point, coordinator): - """Initialize the sensor.""" - super().__init__(coordinator) - self._settlement_point = settlement_point - - @property - def unit_of_measurement(self): - """Return the unit of measurement.""" - return f"{CURRENCY_CENT}/{ENERGY_KILO_WATT_HOUR}" - - @property - def name(self): - """Device Name.""" - return f"{self._settlement_point} Price Now" - - @property - def icon(self): - """Device Ice.""" - return "mdi:currency-usd" - - @property - def unique_id(self): - """Device Uniqueid.""" - return f"{self._settlement_point}_price_now" - - @property - def state(self): - """Get the current price.""" - return round(float(self.coordinator.data.now.price_cents_kwh), 4) diff --git a/homeassistant/components/griddy/strings.json b/homeassistant/components/griddy/strings.json deleted file mode 100644 index 99bd8946c34..00000000000 --- a/homeassistant/components/griddy/strings.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "config": { - "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "unknown": "[%key:common::config_flow::error::unknown%]" - }, - "step": { - "user": { - "description": "Your Load Zone is in your Griddy account under \u201cAccount > Meter > Load Zone.\u201d", - "data": { "loadzone": "Load Zone (Settlement Point)" }, - "title": "Setup your Griddy Load Zone" - } - }, - "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_location%]" } - } -} diff --git a/homeassistant/components/griddy/translations/ca.json b/homeassistant/components/griddy/translations/ca.json deleted file mode 100644 index 33aca3cd302..00000000000 --- a/homeassistant/components/griddy/translations/ca.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "La ubicaci\u00f3 ja est\u00e0 configurada" - }, - "error": { - "cannot_connect": "Ha fallat la connexi\u00f3", - "unknown": "Error inesperat" - }, - "step": { - "user": { - "data": { - "loadzone": "Zona de c\u00e0rrega (Load Zone)" - }, - "description": "La teva zona de c\u00e0rrega (Load Zone) est\u00e0 al teu compte de Griddy v\u00e9s a \"Account > Meter > Load Zone\".", - "title": "Configuraci\u00f3 de la zona de c\u00e0rrega (Load Zone) de Griddy" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/cs.json b/homeassistant/components/griddy/translations/cs.json deleted file mode 100644 index fa5918fa5da..00000000000 --- a/homeassistant/components/griddy/translations/cs.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Um\u00edst\u011bn\u00ed je ji\u017e nastaveno" - }, - "error": { - "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", - "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/de.json b/homeassistant/components/griddy/translations/de.json deleted file mode 100644 index 4a6c477059c..00000000000 --- a/homeassistant/components/griddy/translations/de.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Standort ist bereits konfiguriert" - }, - "error": { - "cannot_connect": "Verbindung fehlgeschlagen", - "unknown": "Unerwarteter Fehler" - }, - "step": { - "user": { - "data": { - "loadzone": "Ladezone (Abwicklungspunkt)" - }, - "description": "Ihre Ladezone befindet sich in Ihrem Griddy-Konto unter \"Konto > Messger\u00e4t > Ladezone\".", - "title": "Richten Sie Ihre Griddy Ladezone ein" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/en.json b/homeassistant/components/griddy/translations/en.json deleted file mode 100644 index 2a82421dd7c..00000000000 --- a/homeassistant/components/griddy/translations/en.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Location is already configured" - }, - "error": { - "cannot_connect": "Failed to connect", - "unknown": "Unexpected error" - }, - "step": { - "user": { - "data": { - "loadzone": "Load Zone (Settlement Point)" - }, - "description": "Your Load Zone is in your Griddy account under \u201cAccount > Meter > Load Zone.\u201d", - "title": "Setup your Griddy Load Zone" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/es-419.json b/homeassistant/components/griddy/translations/es-419.json deleted file mode 100644 index 652c8484b4e..00000000000 --- a/homeassistant/components/griddy/translations/es-419.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Esta zona de carga ya est\u00e1 configurada" - }, - "error": { - "cannot_connect": "No se pudo conectar, intente nuevamente", - "unknown": "Error inesperado" - }, - "step": { - "user": { - "data": { - "loadzone": "Zona de carga (punto de asentamiento)" - }, - "description": "Su zona de carga est\u00e1 en su cuenta de Griddy en \"Cuenta > Medidor > Zona de carga\".", - "title": "Configura tu zona de carga Griddy" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/es.json b/homeassistant/components/griddy/translations/es.json deleted file mode 100644 index a3727721b2d..00000000000 --- a/homeassistant/components/griddy/translations/es.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Esta Zona de Carga ya est\u00e1 configurada" - }, - "error": { - "cannot_connect": "No se ha podido conectar, por favor, int\u00e9ntalo de nuevo.", - "unknown": "Error inesperado" - }, - "step": { - "user": { - "data": { - "loadzone": "Zona de Carga (Punto del Asentamiento)" - }, - "description": "Tu Zona de Carga est\u00e1 en tu cuenta de Griddy en \"Account > Meter > Load Zone\"", - "title": "Configurar tu Zona de Carga de Griddy" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/et.json b/homeassistant/components/griddy/translations/et.json deleted file mode 100644 index 82d2232b04e..00000000000 --- a/homeassistant/components/griddy/translations/et.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "See Load Zone on juba m\u00e4\u00e4ratud" - }, - "error": { - "cannot_connect": "\u00dchendamine nurjus", - "unknown": "Tundmatu viga" - }, - "step": { - "user": { - "data": { - "loadzone": "Load Zone (arvelduspunkt)" - }, - "description": "Load Zone asub Griddy konto valikutes \u201cAccount > Meter > Load Zone.\u201d", - "title": "Seadista oma Griddy Load Zone" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/fr.json b/homeassistant/components/griddy/translations/fr.json deleted file mode 100644 index c2fd4d8d627..00000000000 --- a/homeassistant/components/griddy/translations/fr.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Cette zone de chargement est d\u00e9j\u00e0 configur\u00e9e" - }, - "error": { - "cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer", - "unknown": "Erreur inattendue" - }, - "step": { - "user": { - "data": { - "loadzone": "Zone de charge (point d'\u00e9tablissement)" - }, - "description": "Votre zone de charge se trouve dans votre compte Griddy sous \"Compte > Compteur > Zone de charge\".", - "title": "Configurez votre zone de charge Griddy" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/it.json b/homeassistant/components/griddy/translations/it.json deleted file mode 100644 index 40fa69b1229..00000000000 --- a/homeassistant/components/griddy/translations/it.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "La posizione \u00e8 gi\u00e0 configurata" - }, - "error": { - "cannot_connect": "Impossibile connettersi", - "unknown": "Errore imprevisto" - }, - "step": { - "user": { - "data": { - "loadzone": "Zona di Carico (Punto di insediamento)" - }, - "description": "La tua Zona di Carico si trova nel tuo account Griddy in \"Account > Meter > Load zone\".", - "title": "Configurazione della Zona di Carico Griddy" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/ko.json b/homeassistant/components/griddy/translations/ko.json deleted file mode 100644 index df9178fab93..00000000000 --- a/homeassistant/components/griddy/translations/ko.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." - }, - "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" - }, - "step": { - "user": { - "data": { - "loadzone": "\uc804\ub825 \uacf5\uae09 \uc9c0\uc5ed (\uc815\uc0b0\uc810)" - }, - "description": "\uc804\ub825 \uacf5\uae09 \uc9c0\uc5ed\uc740 Griddy \uacc4\uc815\uc758 \"Account > Meter > Load Zone\"\uc5d0\uc11c \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "title": "Griddy \uc804\ub825 \uacf5\uae09 \uc9c0\uc5ed \uc124\uc815\ud558\uae30" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/lb.json b/homeassistant/components/griddy/translations/lb.json deleted file mode 100644 index 84511186f88..00000000000 --- a/homeassistant/components/griddy/translations/lb.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Standuert ass scho konfigur\u00e9iert" - }, - "error": { - "cannot_connect": "Feeler beim verbannen", - "unknown": "Onerwaarte Feeler" - }, - "step": { - "user": { - "data": { - "loadzone": "Lued Zone (Punkt vum R\u00e9glement)" - }, - "description": "Deng Lued Zon ass an dengem Griddy Kont enner \"Account > Meter > Load Zone.\"", - "title": "Griddy Lued Zon ariichten" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/nl.json b/homeassistant/components/griddy/translations/nl.json deleted file mode 100644 index bd97b9ccf7c..00000000000 --- a/homeassistant/components/griddy/translations/nl.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Deze laadzone is al geconfigureerd" - }, - "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", - "unknown": "Onverwachte fout" - }, - "step": { - "user": { - "data": { - "loadzone": "Laadzone (vestigingspunt)" - }, - "description": "Uw Load Zone staat op uw Griddy account onder \"Account > Meter > Load Zone\".", - "title": "Stel uw Griddy Load Zone in" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/no.json b/homeassistant/components/griddy/translations/no.json deleted file mode 100644 index 7f01fa198a3..00000000000 --- a/homeassistant/components/griddy/translations/no.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Plasseringen er allerede konfigurert" - }, - "error": { - "cannot_connect": "Tilkobling mislyktes", - "unknown": "Uventet feil" - }, - "step": { - "user": { - "data": { - "loadzone": "Load Zone (settlingspunkt)" - }, - "description": "Din Load Zone er p\u00e5 din Griddy-konto under \"Konto > M\u00e5ler > Lastesone.\"", - "title": "Sett opp din Griddy Load Zone" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/pl.json b/homeassistant/components/griddy/translations/pl.json deleted file mode 100644 index 035521336f6..00000000000 --- a/homeassistant/components/griddy/translations/pl.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Lokalizacja jest ju\u017c skonfigurowana" - }, - "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", - "unknown": "Nieoczekiwany b\u0142\u0105d" - }, - "step": { - "user": { - "data": { - "loadzone": "Strefa obci\u0105\u017cenia (punkt rozliczenia)" - }, - "description": "Twoja strefa obci\u0105\u017cenia znajduje si\u0119 na twoim koncie Griddy w sekcji \"Konto > Licznik > Strefa obci\u0105\u017cenia\".", - "title": "Konfiguracja strefy obci\u0105\u017cenia Griddy" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/pt-BR.json b/homeassistant/components/griddy/translations/pt-BR.json deleted file mode 100644 index dc9c1362dc4..00000000000 --- a/homeassistant/components/griddy/translations/pt-BR.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "config": { - "error": { - "cannot_connect": "Falha ao conectar, tente novamente", - "unknown": "Erro inesperado" - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/pt.json b/homeassistant/components/griddy/translations/pt.json deleted file mode 100644 index 9b067d35f89..00000000000 --- a/homeassistant/components/griddy/translations/pt.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "A localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" - }, - "error": { - "cannot_connect": "Falha na liga\u00e7\u00e3o", - "unknown": "Erro inesperado" - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/ru.json b/homeassistant/components/griddy/translations/ru.json deleted file mode 100644 index 3483953fce1..00000000000 --- a/homeassistant/components/griddy/translations/ru.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\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.", - "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." - }, - "step": { - "user": { - "data": { - "loadzone": "\u0417\u043e\u043d\u0430 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438 (\u0440\u0430\u0441\u0447\u0435\u0442\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430)" - }, - "description": "\u0417\u043e\u043d\u0430 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Griddy \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 Account > Meter > Load Zone.", - "title": "Griddy" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/sl.json b/homeassistant/components/griddy/translations/sl.json deleted file mode 100644 index 8df85c6dc67..00000000000 --- a/homeassistant/components/griddy/translations/sl.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Ta obremenitvena cona je \u017ee konfigurirana" - }, - "error": { - "cannot_connect": "Povezava ni uspela, poskusite znova", - "unknown": "Nepri\u010dakovana napaka" - }, - "step": { - "user": { - "data": { - "loadzone": "Obremenitvena cona (poselitvena to\u010dka)" - }, - "description": "Va\u0161a obremenitvena cona je v va\u0161em ra\u010dunu Griddy pod \"Ra\u010dun > Merilnik > Nalo\u017ei cono.\"", - "title": "Nastavite svojo Griddy Load Cono" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/sv.json b/homeassistant/components/griddy/translations/sv.json deleted file mode 100644 index e9ddacf2714..00000000000 --- a/homeassistant/components/griddy/translations/sv.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "config": { - "error": { - "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", - "unknown": "Ov\u00e4ntat fel" - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/tr.json b/homeassistant/components/griddy/translations/tr.json deleted file mode 100644 index 26e0fa73065..00000000000 --- a/homeassistant/components/griddy/translations/tr.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" - }, - "error": { - "cannot_connect": "Ba\u011flant\u0131 kurulamad\u0131, l\u00fctfen tekrar deneyin", - "unknown": "Beklenmeyen hata" - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/uk.json b/homeassistant/components/griddy/translations/uk.json deleted file mode 100644 index e366f0e8b24..00000000000 --- a/homeassistant/components/griddy/translations/uk.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." - }, - "error": { - "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", - "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" - }, - "step": { - "user": { - "data": { - "loadzone": "\u0417\u043e\u043d\u0430 \u043d\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f (\u0440\u043e\u0437\u0440\u0430\u0445\u0443\u043d\u043a\u043e\u0432\u0430 \u0442\u043e\u0447\u043a\u0430)" - }, - "description": "\u0417\u043e\u043d\u0430 \u043d\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f \u0437\u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0443 \u0432\u0430\u0448\u043e\u043c\u0443 \u043f\u0440\u043e\u0444\u0456\u043b\u0456 Griddy \u0432 \u0440\u043e\u0437\u0434\u0456\u043b\u0456 Account > Meter > Load Zone.", - "title": "Griddy" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/zh-Hant.json b/homeassistant/components/griddy/translations/zh-Hant.json deleted file mode 100644 index 4918c1e818e..00000000000 --- a/homeassistant/components/griddy/translations/zh-Hant.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "\u5ea7\u6a19\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" - }, - "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557", - "unknown": "\u672a\u9810\u671f\u932f\u8aa4" - }, - "step": { - "user": { - "data": { - "loadzone": "\u8ca0\u8f09\u5340\u57df\uff08\u5c45\u4f4f\u9ede\uff09" - }, - "description": "\u8ca0\u8f09\u5340\u57df\u986f\u793a\u65bc Griddy \u5e33\u865f\uff0c\u4f4d\u65bc \u201cAccount > Meter > Load Zone\u201d\u3002", - "title": "\u8a2d\u5b9a Griddy \u8ca0\u8f09\u5340\u57df" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index c3d629ebe29..a0a846c0d44 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -86,7 +86,6 @@ FLOWS = [ "gogogate2", "gpslogger", "gree", - "griddy", "guardian", "habitica", "hangouts", diff --git a/requirements_all.txt b/requirements_all.txt index d4e7be4d98e..ff8b5ba4c60 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -705,9 +705,6 @@ greeneye_monitor==2.1 # homeassistant.components.greenwave greenwavereality==0.5.1 -# homeassistant.components.griddy -griddypower==0.1.0 - # homeassistant.components.growatt_server growattServer==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 78f234e9dff..9c6ef98fb69 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -372,9 +372,6 @@ google-nest-sdm==0.2.12 # homeassistant.components.gree greeclimate==0.10.3 -# homeassistant.components.griddy -griddypower==0.1.0 - # homeassistant.components.profiler guppy3==3.1.0 diff --git a/tests/components/griddy/__init__.py b/tests/components/griddy/__init__.py deleted file mode 100644 index 415ddc3ba5c..00000000000 --- a/tests/components/griddy/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the Griddy Power integration.""" diff --git a/tests/components/griddy/test_config_flow.py b/tests/components/griddy/test_config_flow.py deleted file mode 100644 index cfc2b23a8ed..00000000000 --- a/tests/components/griddy/test_config_flow.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Test the Griddy Power config flow.""" -import asyncio -from unittest.mock import MagicMock, patch - -from homeassistant import config_entries, setup -from homeassistant.components.griddy.const import DOMAIN - - -async def test_form(hass): - """Test we get the form.""" - await setup.async_setup_component(hass, "persistent_notification", {}) - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - assert result["type"] == "form" - assert result["errors"] == {} - - with patch( - "homeassistant.components.griddy.config_flow.AsyncGriddy.async_getnow", - return_value=MagicMock(), - ), patch( - "homeassistant.components.griddy.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.griddy.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {"loadzone": "LZ_HOUSTON"}, - ) - await hass.async_block_till_done() - - assert result2["type"] == "create_entry" - assert result2["title"] == "Load Zone LZ_HOUSTON" - assert result2["data"] == {"loadzone": "LZ_HOUSTON"} - assert len(mock_setup.mock_calls) == 1 - assert len(mock_setup_entry.mock_calls) == 1 - - -async def test_form_cannot_connect(hass): - """Test we handle cannot connect error.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - with patch( - "homeassistant.components.griddy.config_flow.AsyncGriddy.async_getnow", - side_effect=asyncio.TimeoutError, - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {"loadzone": "LZ_NORTH"}, - ) - - assert result2["type"] == "form" - assert result2["errors"] == {"base": "cannot_connect"} diff --git a/tests/components/griddy/test_sensor.py b/tests/components/griddy/test_sensor.py deleted file mode 100644 index 46f8d238c49..00000000000 --- a/tests/components/griddy/test_sensor.py +++ /dev/null @@ -1,39 +0,0 @@ -"""The sensor tests for the griddy platform.""" -import json -import os -from unittest.mock import patch - -from griddypower.async_api import GriddyPriceData - -from homeassistant.components.griddy import CONF_LOADZONE, DOMAIN -from homeassistant.setup import async_setup_component - -from tests.common import load_fixture - - -async def _load_json_fixture(hass, path): - fixture = await hass.async_add_executor_job( - load_fixture, os.path.join("griddy", path) - ) - return json.loads(fixture) - - -def _mock_get_config(): - """Return a default griddy config.""" - return {DOMAIN: {CONF_LOADZONE: "LZ_HOUSTON"}} - - -async def test_houston_loadzone(hass): - """Test creation of the houston load zone.""" - - getnow_json = await _load_json_fixture(hass, "getnow.json") - griddy_price_data = GriddyPriceData(getnow_json) - with patch( - "homeassistant.components.griddy.AsyncGriddy.async_getnow", - return_value=griddy_price_data, - ): - assert await async_setup_component(hass, DOMAIN, _mock_get_config()) - await hass.async_block_till_done() - - sensor_lz_houston_price_now = hass.states.get("sensor.lz_houston_price_now") - assert sensor_lz_houston_price_now.state == "1.269" From 4c42e469b3eaa1ef4edb70dd85b55971d83e4882 Mon Sep 17 00:00:00 2001 From: Max Chodorowski Date: Mon, 1 Mar 2021 09:38:07 +0000 Subject: [PATCH 084/831] Fix number of reported issues by github integration (#47203) --- homeassistant/components/github/sensor.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/github/sensor.py b/homeassistant/components/github/sensor.py index 312e726b91d..80d05ae1b9c 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -228,18 +228,25 @@ class GitHubData: self.stargazers = repo.stargazers_count self.forks = repo.forks_count - open_issues = repo.get_issues(state="open", sort="created") - if open_issues is not None: - self.open_issue_count = open_issues.totalCount - if open_issues.totalCount > 0: - self.latest_open_issue_url = open_issues[0].html_url - open_pull_requests = repo.get_pulls(state="open", sort="created") if open_pull_requests is not None: self.pull_request_count = open_pull_requests.totalCount if open_pull_requests.totalCount > 0: self.latest_open_pr_url = open_pull_requests[0].html_url + open_issues = repo.get_issues(state="open", sort="created") + if open_issues is not None: + if self.pull_request_count is None: + self.open_issue_count = open_issues.totalCount + else: + # pull requests are treated as issues too so we need to reduce the received count + self.open_issue_count = ( + open_issues.totalCount - self.pull_request_count + ) + + if open_issues.totalCount > 0: + self.latest_open_issue_url = open_issues[0].html_url + latest_commit = repo.get_commits()[0] self.latest_commit_sha = latest_commit.sha self.latest_commit_message = latest_commit.commit.message From 92afcb6b4bafef0f5004cd6d6cf073e8e5d30266 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Mon, 1 Mar 2021 11:51:59 +0100 Subject: [PATCH 085/831] KNX services send and event_register accept multiple group addresses (#46908) * send and event_register service accept lists of group addresses * remove lambda * object selector for lists * knx.read takes lists too --- homeassistant/components/knx/__init__.py | 82 +++++++++++++--------- homeassistant/components/knx/services.yaml | 14 ++-- 2 files changed, 56 insertions(+), 40 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index b8feb010e29..c32755b7f45 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -1,6 +1,7 @@ """Support KNX devices.""" import asyncio import logging +from typing import Union import voluptuous as vol from xknx import XKNX @@ -148,7 +149,10 @@ CONFIG_SCHEMA = vol.Schema( SERVICE_KNX_SEND_SCHEMA = vol.Any( vol.Schema( { - vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(CONF_ADDRESS): vol.All( + cv.ensure_list, + [ga_validator], + ), vol.Required(SERVICE_KNX_ATTR_PAYLOAD): cv.match_all, vol.Required(SERVICE_KNX_ATTR_TYPE): vol.Any(int, float, str), } @@ -156,7 +160,10 @@ SERVICE_KNX_SEND_SCHEMA = vol.Any( vol.Schema( # without type given payload is treated as raw bytes { - vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(CONF_ADDRESS): vol.All( + cv.ensure_list, + [ga_validator], + ), vol.Required(SERVICE_KNX_ATTR_PAYLOAD): vol.Any( cv.positive_int, [cv.positive_int] ), @@ -175,7 +182,10 @@ SERVICE_KNX_READ_SCHEMA = vol.Schema( SERVICE_KNX_EVENT_REGISTER_SCHEMA = vol.Schema( { - vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(CONF_ADDRESS): vol.All( + cv.ensure_list, + [ga_validator], + ), vol.Optional(SERVICE_KNX_ATTR_REMOVE, default=False): cv.boolean, } ) @@ -401,21 +411,26 @@ class KNXModule: async def service_event_register_modify(self, call): """Service for adding or removing a GroupAddress to the knx_event filter.""" - group_address = GroupAddress(call.data[CONF_ADDRESS]) + attr_address = call.data.get(CONF_ADDRESS) + group_addresses = map(GroupAddress, attr_address) + if call.data.get(SERVICE_KNX_ATTR_REMOVE): - try: - self._knx_event_callback.group_addresses.remove(group_address) - except ValueError: - _LOGGER.warning( - "Service event_register could not remove event for '%s'", - group_address, - ) - elif group_address not in self._knx_event_callback.group_addresses: - self._knx_event_callback.group_addresses.append(group_address) - _LOGGER.debug( - "Service event_register registered event for '%s'", - group_address, - ) + for group_address in group_addresses: + try: + self._knx_event_callback.group_addresses.remove(group_address) + except ValueError: + _LOGGER.warning( + "Service event_register could not remove event for '%s'", + str(group_address), + ) + else: + for group_address in group_addresses: + if group_address not in self._knx_event_callback.group_addresses: + self._knx_event_callback.group_addresses.append(group_address) + _LOGGER.debug( + "Service event_register registered event for '%s'", + str(group_address), + ) async def service_exposure_register_modify(self, call): """Service for adding or removing an exposure to KNX bus.""" @@ -450,26 +465,27 @@ class KNXModule: async def service_send_to_knx_bus(self, call): """Service for sending an arbitrary KNX message to the KNX bus.""" - attr_payload = call.data.get(SERVICE_KNX_ATTR_PAYLOAD) attr_address = call.data.get(CONF_ADDRESS) + attr_payload = call.data.get(SERVICE_KNX_ATTR_PAYLOAD) attr_type = call.data.get(SERVICE_KNX_ATTR_TYPE) - def calculate_payload(attr_payload): - """Calculate payload depending on type of attribute.""" - if attr_type is not None: - transcoder = DPTBase.parse_transcoder(attr_type) - if transcoder is None: - raise ValueError(f"Invalid type for knx.send service: {attr_type}") - return DPTArray(transcoder.to_knx(attr_payload)) - if isinstance(attr_payload, int): - return DPTBinary(attr_payload) - return DPTArray(attr_payload) + payload: Union[DPTBinary, DPTArray] + if attr_type is not None: + transcoder = DPTBase.parse_transcoder(attr_type) + if transcoder is None: + raise ValueError(f"Invalid type for knx.send service: {attr_type}") + payload = DPTArray(transcoder.to_knx(attr_payload)) + elif isinstance(attr_payload, int): + payload = DPTBinary(attr_payload) + else: + payload = DPTArray(attr_payload) - telegram = Telegram( - destination_address=GroupAddress(attr_address), - payload=GroupValueWrite(calculate_payload(attr_payload)), - ) - await self.xknx.telegrams.put(telegram) + for address in attr_address: + telegram = Telegram( + destination_address=GroupAddress(address), + payload=GroupValueWrite(payload), + ) + await self.xknx.telegrams.put(telegram) async def service_read_to_knx_bus(self, call): """Service for sending a GroupValueRead telegram to the KNX bus.""" diff --git a/homeassistant/components/knx/services.yaml b/homeassistant/components/knx/services.yaml index 3fae7dfce0e..c13abb23d94 100644 --- a/homeassistant/components/knx/services.yaml +++ b/homeassistant/components/knx/services.yaml @@ -4,11 +4,11 @@ send: fields: address: name: "Group address" - description: "Group address(es) to write to." + description: "Group address(es) to write to. Lists will send to multiple group addresses successively." required: true example: "1/1/0" selector: - text: + object: payload: name: "Payload" description: "Payload to send to the bus. Integers are treated as DPT 1/2/3 payloads. For DPTs > 6 bits send a list. Each value represents 1 octet (0-255). Pad with 0 to DPT byte length." @@ -33,21 +33,21 @@ read: required: true example: "1/1/0" selector: - text: + object: event_register: name: "Register knx_event" - description: "Add or remove single group address to knx_event filter for triggering `knx_event`s. Only addresses added with this service can be removed." + description: "Add or remove group addresses to knx_event filter for triggering `knx_event`s. Only addresses added with this service can be removed." fields: address: name: "Group address" - description: "Group address that shall be added or removed." + description: "Group address(es) that shall be added or removed. Lists are allowed." required: true example: "1/1/0" selector: - text: + object: remove: name: "Remove event registration" - description: "Optional. If `True` the group address will be removed." + description: "Optional. If `True` the group address(es) will be removed." default: false selector: boolean: From dadc99dbd3f8cba62d9e9879f4374f386dfa4645 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Mon, 1 Mar 2021 11:55:55 +0100 Subject: [PATCH 086/831] Deprecate knx config_file (#46874) * deprecate config_file * removed cv.deprecated for now, added persistent notification * Update homeassistant/components/knx/__init__.py Co-authored-by: Philip Allgaier * remove notification, add cv.deprecated again * Update homeassistant/components/knx/__init__.py Co-authored-by: Franck Nijhof * remove cv.deprecated again Co-authored-by: Philip Allgaier Co-authored-by: Franck Nijhof --- homeassistant/components/knx/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index c32755b7f45..ca1c19c3f5c 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -78,6 +78,7 @@ SERVICE_KNX_READ = "read" CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.All( + # deprecated since 2021.2 cv.deprecated(CONF_KNX_FIRE_EVENT), cv.deprecated("fire_event_filter", replacement_key=CONF_KNX_EVENT_FILTER), vol.Schema( @@ -242,6 +243,15 @@ async def async_setup(hass, config): "https://www.home-assistant.io/blog/2020/09/17/release-115/#breaking-changes" ) + # deprecation warning since 2021.3 + if CONF_KNX_CONFIG in config[DOMAIN]: + _LOGGER.warning( + "The 'config_file' option is deprecated. Please replace it with Home Assistant config schema " + "directly in `configuration.yaml` (see https://www.home-assistant.io/integrations/knx/). \n" + "An online configuration converter tool for your `%s` is available at https://xknx.io/config-converter/", + config[DOMAIN][CONF_KNX_CONFIG], + ) + hass.services.async_register( DOMAIN, SERVICE_KNX_SEND, From 003fee2a355368d6d2c5920ff1f27ab767feb6d4 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 1 Mar 2021 12:38:49 +0100 Subject: [PATCH 087/831] Fix race when disabling config entries (#47210) * Fix race when disabling config entries * Remove unused constant --- homeassistant/config_entries.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index dbc0dd01454..b54300faaa7 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -69,8 +69,6 @@ ENTRY_STATE_SETUP_RETRY = "setup_retry" ENTRY_STATE_NOT_LOADED = "not_loaded" # An error occurred when trying to unload the entry ENTRY_STATE_FAILED_UNLOAD = "failed_unload" -# The config entry is disabled -ENTRY_STATE_DISABLED = "disabled" UNRECOVERABLE_STATES = (ENTRY_STATE_MIGRATION_ERROR, ENTRY_STATE_FAILED_UNLOAD) @@ -802,11 +800,14 @@ class ConfigEntries: entry.disabled_by = disabled_by self._async_schedule_save() + # Unload the config entry, then fire an event + reload_result = await self.async_reload(entry_id) + self.hass.bus.async_fire( EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, {"config_entry_id": entry_id} ) - return await self.async_reload(entry_id) + return reload_result @callback def async_update_entry( From 084cfa4a1d83dca58fe7fa134025e007eb382a0e Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 1 Mar 2021 12:46:02 +0100 Subject: [PATCH 088/831] Fix Xiaomi Miio flow unique_id for non discovery flows (#47222) --- homeassistant/components/xiaomi_miio/config_flow.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py index 9eaf4c1effa..efb668f0196 100644 --- a/homeassistant/components/xiaomi_miio/config_flow.py +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -126,7 +126,9 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): for gateway_model in MODELS_GATEWAY: if model.startswith(gateway_model): unique_id = self.mac - await self.async_set_unique_id(unique_id) + await self.async_set_unique_id( + unique_id, raise_on_progress=False + ) self._abort_if_unique_id_configured() return self.async_create_entry( title=DEFAULT_GATEWAY_NAME, @@ -145,7 +147,9 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): for device_model in MODELS_ALL_DEVICES: if model.startswith(device_model): unique_id = self.mac - await self.async_set_unique_id(unique_id) + await self.async_set_unique_id( + unique_id, raise_on_progress=False + ) self._abort_if_unique_id_configured() return self.async_create_entry( title=name, From a2b13785c2606e5a67739262082966926dab5f15 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 1 Mar 2021 13:40:46 +0100 Subject: [PATCH 089/831] Restore pylint concurrency (#47221) --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 445f13e8724..731b7f15730 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,8 +22,7 @@ ignore = [ ] # Use a conservative default here; 2 should speed up most setups and not hurt # any too bad. Override on command line as appropriate. -# Disabled for now: https://github.com/PyCQA/pylint/issues/3584 -#jobs = 2 +jobs = 2 load-plugins = [ "pylint_strict_informational", ] From 947f6ea51e29e2b3f3133ba3203a7be6f53d583c Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Mon, 1 Mar 2021 04:53:57 -0800 Subject: [PATCH 090/831] Parameterize SmartTub tests (#47189) * Parameterize SmartTub tests * parameterize light service calls * remove stray print() * add comment --- homeassistant/components/smarttub/light.py | 2 +- tests/components/smarttub/test_light.py | 65 ++++++++++++---------- tests/components/smarttub/test_sensor.py | 56 ++++++++----------- tests/components/smarttub/test_switch.py | 44 +++++++-------- 4 files changed, 81 insertions(+), 86 deletions(-) diff --git a/homeassistant/components/smarttub/light.py b/homeassistant/components/smarttub/light.py index a4ada7c3024..1baf7e527eb 100644 --- a/homeassistant/components/smarttub/light.py +++ b/homeassistant/components/smarttub/light.py @@ -137,5 +137,5 @@ class SmartTubLight(SmartTubEntity, LightEntity): async def async_turn_off(self, **kwargs): """Turn the light off.""" - await self.light.set_mode(self.light.LightMode.OFF, 0) + await self.light.set_mode(SpaLight.LightMode.OFF, 0) await self.coordinator.async_request_refresh() diff --git a/tests/components/smarttub/test_light.py b/tests/components/smarttub/test_light.py index 5e9d9459eab..fe178278bee 100644 --- a/tests/components/smarttub/test_light.py +++ b/tests/components/smarttub/test_light.py @@ -1,38 +1,45 @@ """Test the SmartTub light platform.""" +import pytest from smarttub import SpaLight -async def test_light(spa, setup_entry, hass): +# the light in light_zone should have initial state light_state. we will call +# service_name with service_params, and expect the resultant call to +# SpaLight.set_mode to have set_mode_args parameters +@pytest.mark.parametrize( + "light_zone,light_state,service_name,service_params,set_mode_args", + [ + (1, "off", "turn_on", {}, (SpaLight.LightMode.PURPLE, 50)), + (1, "off", "turn_on", {"brightness": 255}, (SpaLight.LightMode.PURPLE, 100)), + (2, "on", "turn_off", {}, (SpaLight.LightMode.OFF, 0)), + ], +) +async def test_light( + spa, + setup_entry, + hass, + light_zone, + light_state, + service_name, + service_params, + set_mode_args, +): """Test light entity.""" - for light in spa.get_lights.return_value: - entity_id = f"light.{spa.brand}_{spa.model}_light_{light.zone}" - state = hass.states.get(entity_id) - assert state is not None - if light.mode == SpaLight.LightMode.OFF: - assert state.state == "off" - await hass.services.async_call( - "light", - "turn_on", - {"entity_id": entity_id}, - blocking=True, - ) - light.set_mode.assert_called() + entity_id = f"light.{spa.brand}_{spa.model}_light_{light_zone}" + state = hass.states.get(entity_id) + assert state is not None + assert state.state == light_state - await hass.services.async_call( - "light", - "turn_on", - {"entity_id": entity_id, "brightness": 255}, - blocking=True, - ) - light.set_mode.assert_called_with(SpaLight.LightMode.PURPLE, 100) + light: SpaLight = next( + light for light in await spa.get_lights() if light.zone == light_zone + ) - else: - assert state.state == "on" - await hass.services.async_call( - "light", - "turn_off", - {"entity_id": entity_id}, - blocking=True, - ) + await hass.services.async_call( + "light", + service_name, + {"entity_id": entity_id, **service_params}, + blocking=True, + ) + light.set_mode.assert_called_with(*set_mode_args) diff --git a/tests/components/smarttub/test_sensor.py b/tests/components/smarttub/test_sensor.py index 2d52d6d07a5..4f179b24910 100644 --- a/tests/components/smarttub/test_sensor.py +++ b/tests/components/smarttub/test_sensor.py @@ -1,46 +1,30 @@ """Test the SmartTub sensor platform.""" -from . import trigger_update +import pytest -async def test_sensors(spa, setup_entry, hass): - """Test the sensors.""" +@pytest.mark.parametrize( + "entity_suffix,expected_state", + [ + ("state", "normal"), + ("flow_switch", "open"), + ("ozone", "off"), + ("uv", "off"), + ("blowout_cycle", "inactive"), + ("cleanup_cycle", "inactive"), + ], +) +async def test_sensor(spa, setup_entry, hass, entity_suffix, expected_state): + """Test simple sensors.""" - entity_id = f"sensor.{spa.brand}_{spa.model}_state" + entity_id = f"sensor.{spa.brand}_{spa.model}_{entity_suffix}" state = hass.states.get(entity_id) assert state is not None - assert state.state == "normal" + assert state.state == expected_state - spa.get_status.return_value.state = "BAD" - await trigger_update(hass) - state = hass.states.get(entity_id) - assert state is not None - assert state.state == "bad" - entity_id = f"sensor.{spa.brand}_{spa.model}_flow_switch" - state = hass.states.get(entity_id) - assert state is not None - assert state.state == "open" - - entity_id = f"sensor.{spa.brand}_{spa.model}_ozone" - state = hass.states.get(entity_id) - assert state is not None - assert state.state == "off" - - entity_id = f"sensor.{spa.brand}_{spa.model}_uv" - state = hass.states.get(entity_id) - assert state is not None - assert state.state == "off" - - entity_id = f"sensor.{spa.brand}_{spa.model}_blowout_cycle" - state = hass.states.get(entity_id) - assert state is not None - assert state.state == "inactive" - - entity_id = f"sensor.{spa.brand}_{spa.model}_cleanup_cycle" - state = hass.states.get(entity_id) - assert state is not None - assert state.state == "inactive" +async def test_primary_filtration(spa, setup_entry, hass): + """Test the primary filtration cycle sensor.""" entity_id = f"sensor.{spa.brand}_{spa.model}_primary_filtration_cycle" state = hass.states.get(entity_id) @@ -51,6 +35,10 @@ async def test_sensors(spa, setup_entry, hass): assert state.attributes["mode"] == "normal" assert state.attributes["start_hour"] == 2 + +async def test_secondary_filtration(spa, setup_entry, hass): + """Test the secondary filtration cycle sensor.""" + entity_id = f"sensor.{spa.brand}_{spa.model}_secondary_filtration_cycle" state = hass.states.get(entity_id) assert state is not None diff --git a/tests/components/smarttub/test_switch.py b/tests/components/smarttub/test_switch.py index 1ef84631196..89e0ec03b23 100644 --- a/tests/components/smarttub/test_switch.py +++ b/tests/components/smarttub/test_switch.py @@ -1,30 +1,30 @@ """Test the SmartTub switch platform.""" -from smarttub import SpaPump +import pytest -async def test_pumps(spa, setup_entry, hass): +@pytest.mark.parametrize( + "pump_id,entity_suffix,pump_state", + [ + ("CP", "circulation_pump", "off"), + ("P1", "jet_p1", "off"), + ("P2", "jet_p2", "on"), + ], +) +async def test_pumps(spa, setup_entry, hass, pump_id, pump_state, entity_suffix): """Test pump entities.""" - for pump in spa.get_pumps.return_value: - if pump.type == SpaPump.PumpType.CIRCULATION: - entity_id = f"switch.{spa.brand}_{spa.model}_circulation_pump" - elif pump.type == SpaPump.PumpType.JET: - entity_id = f"switch.{spa.brand}_{spa.model}_jet_{pump.id.lower()}" - else: - raise NotImplementedError("Unknown pump type") + pump = next(pump for pump in await spa.get_pumps() if pump.id == pump_id) - state = hass.states.get(entity_id) - assert state is not None - if pump.state == SpaPump.PumpState.OFF: - assert state.state == "off" - else: - assert state.state == "on" + entity_id = f"switch.{spa.brand}_{spa.model}_{entity_suffix}" + state = hass.states.get(entity_id) + assert state is not None + assert state.state == pump_state - await hass.services.async_call( - "switch", - "toggle", - {"entity_id": entity_id}, - blocking=True, - ) - pump.toggle.assert_called() + await hass.services.async_call( + "switch", + "toggle", + {"entity_id": entity_id}, + blocking=True, + ) + pump.toggle.assert_called() From 93d4e46bb69f487da4e635b46d7521984ec49b66 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 1 Mar 2021 15:37:03 +0100 Subject: [PATCH 091/831] Upgrade coverage to 5.5 (#47227) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 1f0a3c4dba2..c906543f54c 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ -c homeassistant/package_constraints.txt -r requirements_test_pre_commit.txt codecov==2.1.10 -coverage==5.4 +coverage==5.5 jsonpickle==1.4.1 mock-open==1.4.0 mypy==0.812 From 5cb2fdb6f7f7511bc0732bde51ec9dbc6c9d4766 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 1 Mar 2021 16:02:55 +0100 Subject: [PATCH 092/831] Upgrade spotipy to 2.17.1 (#47228) --- 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 c4d7378c060..bd92217e9cf 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.1"], + "requirements": ["spotipy==2.17.1"], "zeroconf": ["_spotify-connect._tcp.local."], "dependencies": ["http"], "codeowners": ["@frenck"], diff --git a/requirements_all.txt b/requirements_all.txt index ff8b5ba4c60..cdd2f51cc0d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2112,7 +2112,7 @@ spiderpy==1.4.2 spotcrime==1.0.4 # homeassistant.components.spotify -spotipy==2.16.1 +spotipy==2.17.1 # homeassistant.components.recorder # homeassistant.components.sql diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9c6ef98fb69..27e2068936b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1087,7 +1087,7 @@ speedtest-cli==2.1.2 spiderpy==1.4.2 # homeassistant.components.spotify -spotipy==2.16.1 +spotipy==2.17.1 # homeassistant.components.recorder # homeassistant.components.sql From 3ebd5aff98fa2bc4a910ee4caea02af148dd6571 Mon Sep 17 00:00:00 2001 From: jdeath <17914369+jdeath@users.noreply.github.com> Date: Mon, 1 Mar 2021 10:35:47 -0500 Subject: [PATCH 093/831] Bump mcstatus to 5.1.1 (#47169) --- homeassistant/components/minecraft_server/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/minecraft_server/manifest.json b/homeassistant/components/minecraft_server/manifest.json index 03710520b90..2c4a2ae4b8e 100644 --- a/homeassistant/components/minecraft_server/manifest.json +++ b/homeassistant/components/minecraft_server/manifest.json @@ -3,7 +3,7 @@ "name": "Minecraft Server", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/minecraft_server", - "requirements": ["aiodns==2.0.0", "getmac==0.8.2", "mcstatus==2.3.0"], + "requirements": ["aiodns==2.0.0", "getmac==0.8.2", "mcstatus==5.1.1"], "codeowners": ["@elmurato"], "quality_scale": "silver" } diff --git a/requirements_all.txt b/requirements_all.txt index cdd2f51cc0d..5bd96e37683 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -917,7 +917,7 @@ maxcube-api==0.3.0 mbddns==0.1.2 # homeassistant.components.minecraft_server -mcstatus==2.3.0 +mcstatus==5.1.1 # homeassistant.components.message_bird messagebird==1.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 27e2068936b..510bb91b265 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -474,7 +474,7 @@ luftdaten==0.6.4 mbddns==0.1.2 # homeassistant.components.minecraft_server -mcstatus==2.3.0 +mcstatus==5.1.1 # homeassistant.components.meteo_france meteofrance-api==1.0.1 From be8584c0bcc64de0bca12e8a38d01165221169f0 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Mon, 1 Mar 2021 08:27:04 -0800 Subject: [PATCH 094/831] Overhaul command_line tests (#46682) --- .../components/command_line/cover.py | 11 +- .../components/command_line/sensor.py | 7 +- .../components/command_line/switch.py | 3 - .../command_line/test_binary_sensor.py | 101 +++-- tests/components/command_line/test_cover.py | 171 ++++---- tests/components/command_line/test_notify.py | 204 +++++----- tests/components/command_line/test_sensor.py | 369 ++++++++++-------- tests/components/command_line/test_switch.py | 368 +++++++++++------ 8 files changed, 699 insertions(+), 535 deletions(-) diff --git a/homeassistant/components/command_line/cover.py b/homeassistant/components/command_line/cover.py index 05d2b9634f2..961d9a31f4e 100644 --- a/homeassistant/components/command_line/cover.py +++ b/homeassistant/components/command_line/cover.py @@ -107,11 +107,6 @@ class CommandCover(CoverEntity): return success - def _query_state_value(self, command): - """Execute state command for return value.""" - _LOGGER.info("Running state value command: %s", command) - return check_output_or_log(command, self._timeout) - @property def should_poll(self): """Only poll if we have state command.""" @@ -138,10 +133,8 @@ class CommandCover(CoverEntity): def _query_state(self): """Query for the state.""" - if not self._command_state: - _LOGGER.error("No state command specified") - return - return self._query_state_value(self._command_state) + _LOGGER.info("Running state value command: %s", self._command_state) + return check_output_or_log(self._command_state, self._timeout) def update(self): """Update device state.""" diff --git a/homeassistant/components/command_line/sensor.py b/homeassistant/components/command_line/sensor.py index 35f7c5a4811..3e6f384cca3 100644 --- a/homeassistant/components/command_line/sensor.py +++ b/homeassistant/components/command_line/sensor.py @@ -145,19 +145,14 @@ class CommandSensorData: def update(self): """Get the latest data with a shell command.""" command = self.command - cache = {} - if command in cache: - prog, args, args_compiled = cache[command] - elif " " not in command: + if " " not in command: prog = command args = None args_compiled = None - cache[command] = (prog, args, args_compiled) else: prog, args = command.split(" ", 1) args_compiled = template.Template(args, self.hass) - cache[command] = (prog, args, args_compiled) if args_compiled: try: diff --git a/homeassistant/components/command_line/switch.py b/homeassistant/components/command_line/switch.py index ce46cd4f2cd..ae6c1c0c925 100644 --- a/homeassistant/components/command_line/switch.py +++ b/homeassistant/components/command_line/switch.py @@ -144,9 +144,6 @@ class CommandSwitch(SwitchEntity): def _query_state(self): """Query for state.""" - if not self._command_state: - _LOGGER.error("No state command specified") - return if self._value_template: return self._query_state_value(self._command_state) return self._query_state_code(self._command_state) diff --git a/tests/components/command_line/test_binary_sensor.py b/tests/components/command_line/test_binary_sensor.py index 90871faaf78..21209a8b60d 100644 --- a/tests/components/command_line/test_binary_sensor.py +++ b/tests/components/command_line/test_binary_sensor.py @@ -1,68 +1,65 @@ """The tests for the Command line Binary sensor platform.""" -import unittest - -from homeassistant.components.command_line import binary_sensor as command_line +from homeassistant import setup +from homeassistant.components.binary_sensor import DOMAIN from homeassistant.const import STATE_OFF, STATE_ON -from homeassistant.helpers import template - -from tests.common import get_test_home_assistant +from homeassistant.helpers.typing import Any, Dict, HomeAssistantType -class TestCommandSensorBinarySensor(unittest.TestCase): - """Test the Command line Binary sensor.""" +async def setup_test_entity( + hass: HomeAssistantType, config_dict: Dict[str, Any] +) -> None: + """Set up a test command line binary_sensor entity.""" + assert await setup.async_setup_component( + hass, + DOMAIN, + {DOMAIN: {"platform": "command_line", "name": "Test", **config_dict}}, + ) + await hass.async_block_till_done() - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.addCleanup(self.hass.stop) - def test_setup(self): - """Test sensor setup.""" - config = { - "name": "Test", +async def test_setup(hass: HomeAssistantType) -> None: + """Test sensor setup.""" + await setup_test_entity( + hass, + { "command": "echo 1", "payload_on": "1", "payload_off": "0", - "command_timeout": 15, - } + }, + ) - devices = [] + entity_state = hass.states.get("binary_sensor.test") + assert entity_state + assert entity_state.state == STATE_ON + assert entity_state.name == "Test" - def add_dev_callback(devs, update): - """Add callback to add devices.""" - for dev in devs: - devices.append(dev) - command_line.setup_platform(self.hass, config, add_dev_callback) +async def test_template(hass: HomeAssistantType) -> None: + """Test setting the state with a template.""" - assert 1 == len(devices) - entity = devices[0] - entity.update() - assert "Test" == entity.name - assert STATE_ON == entity.state + await setup_test_entity( + hass, + { + "command": "echo 10", + "payload_on": "1.0", + "payload_off": "0", + "value_template": "{{ value | multiply(0.1) }}", + }, + ) - def test_template(self): - """Test setting the state with a template.""" - data = command_line.CommandSensorData(self.hass, "echo 10", 15) + entity_state = hass.states.get("binary_sensor.test") + assert entity_state.state == STATE_ON - entity = command_line.CommandBinarySensor( - self.hass, - data, - "test", - None, - "1.0", - "0", - template.Template("{{ value | multiply(0.1) }}", self.hass), - ) - entity.update() - assert STATE_ON == entity.state - def test_sensor_off(self): - """Test setting the state with a template.""" - data = command_line.CommandSensorData(self.hass, "echo 0", 15) - - entity = command_line.CommandBinarySensor( - self.hass, data, "test", None, "1", "0", None - ) - entity.update() - assert STATE_OFF == entity.state +async def test_sensor_off(hass: HomeAssistantType) -> None: + """Test setting the state with a template.""" + await setup_test_entity( + hass, + { + "command": "echo 0", + "payload_on": "1", + "payload_off": "0", + }, + ) + entity_state = hass.states.get("binary_sensor.test") + assert entity_state.state == STATE_OFF diff --git a/tests/components/command_line/test_cover.py b/tests/components/command_line/test_cover.py index ee692413bcd..093c1e86212 100644 --- a/tests/components/command_line/test_cover.py +++ b/tests/components/command_line/test_cover.py @@ -1,15 +1,10 @@ """The tests the cover command line platform.""" import os -from os import path import tempfile -from unittest import mock from unittest.mock import patch -import pytest - -from homeassistant import config as hass_config -import homeassistant.components.command_line.cover as cmd_rs -from homeassistant.components.cover import DOMAIN +from homeassistant import config as hass_config, setup +from homeassistant.components.cover import DOMAIN, SCAN_INTERVAL from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_CLOSE_COVER, @@ -17,101 +12,128 @@ from homeassistant.const import ( SERVICE_RELOAD, SERVICE_STOP_COVER, ) -from homeassistant.setup import async_setup_component +from homeassistant.helpers.typing import Any, Dict, HomeAssistantType +import homeassistant.util.dt as dt_util + +from tests.common import async_fire_time_changed -@pytest.fixture -def rs(hass): - """Return CommandCover instance.""" - return cmd_rs.CommandCover( +async def setup_test_entity( + hass: HomeAssistantType, config_dict: Dict[str, Any] +) -> None: + """Set up a test command line notify service.""" + assert await setup.async_setup_component( hass, - "foo", - "command_open", - "command_close", - "command_stop", - "command_state", - None, - 15, + DOMAIN, + { + DOMAIN: [ + {"platform": "command_line", "covers": config_dict}, + ] + }, ) + await hass.async_block_till_done() -def test_should_poll_new(rs): - """Test the setting of polling.""" - assert rs.should_poll is True - rs._command_state = None - assert rs.should_poll is False +async def test_no_covers(caplog: Any, hass: HomeAssistantType) -> None: + """Test that the cover does not polls when there's no state command.""" + + with patch( + "homeassistant.components.command_line.subprocess.check_output", + return_value=b"50\n", + ): + await setup_test_entity(hass, {}) + assert "No covers added" in caplog.text -def test_query_state_value(rs): - """Test with state value.""" - with mock.patch("subprocess.check_output") as mock_run: - mock_run.return_value = b" foo bar " - result = rs._query_state_value("runme") - assert "foo bar" == result - assert mock_run.call_count == 1 - assert mock_run.call_args == mock.call( - "runme", shell=True, timeout=15 # nosec # shell by design +async def test_no_poll_when_cover_has_no_command_state(hass: HomeAssistantType) -> None: + """Test that the cover does not polls when there's no state command.""" + + with patch( + "homeassistant.components.command_line.subprocess.check_output", + return_value=b"50\n", + ) as check_output: + await setup_test_entity(hass, {"test": {}}) + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + assert not check_output.called + + +async def test_poll_when_cover_has_command_state(hass: HomeAssistantType) -> None: + """Test that the cover polls when there's a state command.""" + + with patch( + "homeassistant.components.command_line.subprocess.check_output", + return_value=b"50\n", + ) as check_output: + await setup_test_entity(hass, {"test": {"command_state": "echo state"}}) + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + check_output.assert_called_once_with( + "echo state", shell=True, timeout=15 # nosec # shell by design ) -async def test_state_value(hass): +async def test_state_value(hass: HomeAssistantType) -> None: """Test with state value.""" with tempfile.TemporaryDirectory() as tempdirname: path = os.path.join(tempdirname, "cover_status") - test_cover = { - "command_state": f"cat {path}", - "command_open": f"echo 1 > {path}", - "command_close": f"echo 1 > {path}", - "command_stop": f"echo 0 > {path}", - "value_template": "{{ value }}", - } - assert ( - await async_setup_component( - hass, - DOMAIN, - {"cover": {"platform": "command_line", "covers": {"test": test_cover}}}, - ) - is True + await setup_test_entity( + hass, + { + "test": { + "command_state": f"cat {path}", + "command_open": f"echo 1 > {path}", + "command_close": f"echo 1 > {path}", + "command_stop": f"echo 0 > {path}", + "value_template": "{{ value }}", + } + }, ) - await hass.async_block_till_done() - assert "unknown" == hass.states.get("cover.test").state + entity_state = hass.states.get("cover.test") + assert entity_state + assert entity_state.state == "unknown" await hass.services.async_call( DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: "cover.test"}, blocking=True ) - assert "open" == hass.states.get("cover.test").state + entity_state = hass.states.get("cover.test") + assert entity_state + assert entity_state.state == "open" await hass.services.async_call( DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: "cover.test"}, blocking=True ) - assert "open" == hass.states.get("cover.test").state + entity_state = hass.states.get("cover.test") + assert entity_state + assert entity_state.state == "open" await hass.services.async_call( DOMAIN, SERVICE_STOP_COVER, {ATTR_ENTITY_ID: "cover.test"}, blocking=True ) - assert "closed" == hass.states.get("cover.test").state + entity_state = hass.states.get("cover.test") + assert entity_state + assert entity_state.state == "closed" -async def test_reload(hass): +async def test_reload(hass: HomeAssistantType) -> None: """Verify we can reload command_line covers.""" - test_cover = { - "command_state": "echo open", - "value_template": "{{ value }}", - } - await async_setup_component( + await setup_test_entity( hass, - DOMAIN, - {"cover": {"platform": "command_line", "covers": {"test": test_cover}}}, + { + "test": { + "command_state": "echo open", + "value_template": "{{ value }}", + } + }, ) - await hass.async_block_till_done() + entity_state = hass.states.get("cover.test") + assert entity_state + assert entity_state.state == "unknown" - assert len(hass.states.async_all()) == 1 - assert hass.states.get("cover.test").state - - yaml_path = path.join( - _get_fixtures_base_path(), + yaml_path = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "fixtures", "command_line/configuration.yaml", ) @@ -126,9 +148,18 @@ async def test_reload(hass): assert len(hass.states.async_all()) == 1 - assert hass.states.get("cover.test") is None + assert not hass.states.get("cover.test") assert hass.states.get("cover.from_yaml") -def _get_fixtures_base_path(): - return path.dirname(path.dirname(path.dirname(__file__))) +async def test_move_cover_failure(caplog: Any, hass: HomeAssistantType) -> None: + """Test with state value.""" + + await setup_test_entity( + hass, + {"test": {"command_open": "exit 1"}}, + ) + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: "cover.test"}, blocking=True + ) + assert "Command failed" in caplog.text diff --git a/tests/components/command_line/test_notify.py b/tests/components/command_line/test_notify.py index 3dcb521cfd2..4166b9e8bbf 100644 --- a/tests/components/command_line/test_notify.py +++ b/tests/components/command_line/test_notify.py @@ -1,117 +1,115 @@ """The tests for the command line notification platform.""" import os +import subprocess import tempfile -import unittest from unittest.mock import patch -import homeassistant.components.notify as notify -from homeassistant.setup import async_setup_component, setup_component - -from tests.common import assert_setup_component, get_test_home_assistant +from homeassistant import setup +from homeassistant.components.notify import DOMAIN +from homeassistant.helpers.typing import Any, Dict, HomeAssistantType -class TestCommandLine(unittest.TestCase): - """Test the command line notifications.""" - - def setUp(self): # pylint: disable=invalid-name - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.addCleanup(self.tear_down_cleanup) - - def tear_down_cleanup(self): - """Stop down everything that was started.""" - self.hass.stop() - - def test_setup(self): - """Test setup.""" - with assert_setup_component(1) as handle_config: - assert setup_component( - self.hass, - "notify", - { - "notify": { - "name": "test", - "platform": "command_line", - "command": "echo $(cat); exit 1", - } - }, - ) - assert handle_config[notify.DOMAIN] - - def test_bad_config(self): - """Test set up the platform with bad/missing configuration.""" - config = {notify.DOMAIN: {"name": "test", "platform": "command_line"}} - with assert_setup_component(0) as handle_config: - assert setup_component(self.hass, notify.DOMAIN, config) - assert not handle_config[notify.DOMAIN] - - def test_command_line_output(self): - """Test the command line output.""" - with tempfile.TemporaryDirectory() as tempdirname: - filename = os.path.join(tempdirname, "message.txt") - message = "one, two, testing, testing" - with assert_setup_component(1) as handle_config: - assert setup_component( - self.hass, - notify.DOMAIN, - { - "notify": { - "name": "test", - "platform": "command_line", - "command": f"echo $(cat) > {filename}", - } - }, - ) - assert handle_config[notify.DOMAIN] - - assert self.hass.services.call( - "notify", "test", {"message": message}, blocking=True - ) - - with open(filename) as fil: - # the echo command adds a line break - assert fil.read() == f"{message}\n" - - @patch("homeassistant.components.command_line.notify._LOGGER.error") - def test_error_for_none_zero_exit_code(self, mock_error): - """Test if an error is logged for non zero exit codes.""" - with assert_setup_component(1) as handle_config: - assert setup_component( - self.hass, - notify.DOMAIN, - { - "notify": { - "name": "test", - "platform": "command_line", - "command": "echo $(cat); exit 1", - } - }, - ) - assert handle_config[notify.DOMAIN] - - assert self.hass.services.call( - "notify", "test", {"message": "error"}, blocking=True - ) - assert mock_error.call_count == 1 - - -async def test_timeout(hass, caplog): - """Test we do not block forever.""" - assert await async_setup_component( +async def setup_test_service( + hass: HomeAssistantType, config_dict: Dict[str, Any] +) -> None: + """Set up a test command line notify service.""" + assert await setup.async_setup_component( hass, - notify.DOMAIN, + DOMAIN, { - "notify": { - "name": "test", - "platform": "command_line", - "command": "sleep 10000", - "command_timeout": 0.0000001, - } + DOMAIN: [ + {"platform": "command_line", "name": "Test", **config_dict}, + ] }, ) await hass.async_block_till_done() - assert await hass.services.async_call( - "notify", "test", {"message": "error"}, blocking=True + + +async def test_setup(hass: HomeAssistantType) -> None: + """Test sensor setup.""" + await setup_test_service(hass, {"command": "exit 0"}) + assert hass.services.has_service(DOMAIN, "test") + + +async def test_bad_config(hass: HomeAssistantType) -> None: + """Test set up the platform with bad/missing configuration.""" + await setup_test_service(hass, {}) + assert not hass.services.has_service(DOMAIN, "test") + + +async def test_command_line_output(hass: HomeAssistantType) -> None: + """Test the command line output.""" + with tempfile.TemporaryDirectory() as tempdirname: + filename = os.path.join(tempdirname, "message.txt") + message = "one, two, testing, testing" + await setup_test_service( + hass, + { + "command": f"cat > {filename}", + }, + ) + + assert hass.services.has_service(DOMAIN, "test") + + assert await hass.services.async_call( + DOMAIN, "test", {"message": message}, blocking=True + ) + with open(filename) as handle: + # the echo command adds a line break + assert message == handle.read() + + +async def test_error_for_none_zero_exit_code( + caplog: Any, hass: HomeAssistantType +) -> None: + """Test if an error is logged for non zero exit codes.""" + await setup_test_service( + hass, + { + "command": "exit 1", + }, + ) + + assert await hass.services.async_call( + DOMAIN, "test", {"message": "error"}, blocking=True + ) + assert "Command failed" in caplog.text + + +async def test_timeout(caplog: Any, hass: HomeAssistantType) -> None: + """Test blocking is not forever.""" + await setup_test_service( + hass, + { + "command": "sleep 10000", + "command_timeout": 0.0000001, + }, + ) + assert await hass.services.async_call( + DOMAIN, "test", {"message": "error"}, blocking=True ) - await hass.async_block_till_done() assert "Timeout" in caplog.text + + +async def test_subprocess_exceptions(caplog: Any, hass: HomeAssistantType) -> None: + """Test that notify subprocess exceptions are handled correctly.""" + + with patch( + "homeassistant.components.command_line.notify.subprocess.Popen", + side_effect=[ + subprocess.TimeoutExpired("cmd", 10), + subprocess.SubprocessError(), + ], + ) as check_output: + await setup_test_service(hass, {"command": "exit 0"}) + assert await hass.services.async_call( + DOMAIN, "test", {"message": "error"}, blocking=True + ) + assert check_output.call_count == 1 + assert "Timeout for command" in caplog.text + + assert await hass.services.async_call( + DOMAIN, "test", {"message": "error"}, blocking=True + ) + assert check_output.call_count == 2 + assert "Error trying to exec command" in caplog.text diff --git a/tests/components/command_line/test_sensor.py b/tests/components/command_line/test_sensor.py index 042c9acf432..66472c5feba 100644 --- a/tests/components/command_line/test_sensor.py +++ b/tests/components/command_line/test_sensor.py @@ -1,201 +1,224 @@ """The tests for the Command line sensor platform.""" -import unittest from unittest.mock import patch -from homeassistant.components.command_line import sensor as command_line -from homeassistant.helpers.template import Template - -from tests.common import get_test_home_assistant +from homeassistant import setup +from homeassistant.components.sensor import DOMAIN +from homeassistant.helpers.typing import Any, Dict, HomeAssistantType -class TestCommandSensorSensor(unittest.TestCase): - """Test the Command line sensor.""" +async def setup_test_entities( + hass: HomeAssistantType, config_dict: Dict[str, Any] +) -> None: + """Set up a test command line sensor entity.""" + assert await setup.async_setup_component( + hass, + DOMAIN, + { + DOMAIN: [ + { + "platform": "template", + "sensors": { + "template_sensor": { + "value_template": "template_value", + } + }, + }, + {"platform": "command_line", "name": "Test", **config_dict}, + ] + }, + ) + await hass.async_block_till_done() - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.addCleanup(self.hass.stop) - def update_side_effect(self, data): - """Side effect function for mocking CommandSensorData.update().""" - self.commandline.data = data - - def test_setup(self): - """Test sensor setup.""" - config = { - "name": "Test", - "unit_of_measurement": "in", +async def test_setup(hass: HomeAssistantType) -> None: + """Test sensor setup.""" + await setup_test_entities( + hass, + { "command": "echo 5", - "command_timeout": 15, - } - devices = [] + "unit_of_measurement": "in", + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert entity_state.state == "5" + assert entity_state.name == "Test" + assert entity_state.attributes["unit_of_measurement"] == "in" - def add_dev_callback(devs, update): - """Add callback to add devices.""" - for dev in devs: - devices.append(dev) - command_line.setup_platform(self.hass, config, add_dev_callback) +async def test_template(hass: HomeAssistantType) -> None: + """Test command sensor with template.""" + await setup_test_entities( + hass, + { + "command": "echo 50", + "unit_of_measurement": "in", + "value_template": "{{ value | multiply(0.1) }}", + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert float(entity_state.state) == 5 - assert len(devices) == 1 - entity = devices[0] - entity.update() - assert entity.name == "Test" - assert entity.unit_of_measurement == "in" - assert entity.state == "5" - def test_template(self): - """Test command sensor with template.""" - data = command_line.CommandSensorData(self.hass, "echo 50", 15) +async def test_template_render(hass: HomeAssistantType) -> None: + """Ensure command with templates get rendered properly.""" - entity = command_line.CommandSensor( - self.hass, - data, - "test", - "in", - Template("{{ value | multiply(0.1) }}", self.hass), - [], + await setup_test_entities( + hass, + { + "command": "echo {{ states.sensor.template_sensor.state }}", + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert entity_state.state == "template_value" + + +async def test_template_render_with_quote(hass: HomeAssistantType) -> None: + """Ensure command with templates and quotes get rendered properly.""" + + with patch( + "homeassistant.components.command_line.subprocess.check_output", + return_value=b"Works\n", + ) as check_output: + await setup_test_entities( + hass, + { + "command": 'echo "{{ states.sensor.template_sensor.state }}" "3 4"', + }, ) - entity.update() - assert float(entity.state) == 5 - - def test_template_render(self): - """Ensure command with templates get rendered properly.""" - self.hass.states.set("sensor.test_state", "Works") - data = command_line.CommandSensorData( - self.hass, "echo {{ states.sensor.test_state.state }}", 15 - ) - data.update() - - assert data.value == "Works" - - def test_template_render_with_quote(self): - """Ensure command with templates and quotes get rendered properly.""" - self.hass.states.set("sensor.test_state", "Works 2") - with patch( - "homeassistant.components.command_line.subprocess.check_output", - return_value=b"Works\n", - ) as check_output: - data = command_line.CommandSensorData( - self.hass, - 'echo "{{ states.sensor.test_state.state }}" "3 4"', - 15, - ) - data.update() - - assert data.value == "Works" check_output.assert_called_once_with( - 'echo "Works 2" "3 4"', shell=True, timeout=15 # nosec # shell by design + 'echo "template_value" "3 4"', + shell=True, # nosec # shell by design + timeout=15, ) - def test_bad_command(self): - """Test bad command.""" - data = command_line.CommandSensorData(self.hass, "asdfasdf", 15) - data.update() - assert data.value is None +async def test_bad_template_render(caplog: Any, hass: HomeAssistantType) -> None: + """Test rendering a broken template.""" - def test_update_with_json_attrs(self): - """Test attributes get extracted from a JSON result.""" - data = command_line.CommandSensorData( - self.hass, - ( - 'echo { \\"key\\": \\"some_json_value\\", \\"another_key\\":\ - \\"another_json_value\\", \\"key_three\\": \\"value_three\\" }' - ), - 15, - ) + await setup_test_entities( + hass, + { + "command": "echo {{ this template doesn't parse", + }, + ) - self.sensor = command_line.CommandSensor( - self.hass, data, "test", None, None, ["key", "another_key", "key_three"] - ) - self.sensor.update() - assert self.sensor.device_state_attributes["key"] == "some_json_value" - assert ( - self.sensor.device_state_attributes["another_key"] == "another_json_value" - ) - assert self.sensor.device_state_attributes["key_three"] == "value_three" + assert "Error rendering command template" in caplog.text - @patch("homeassistant.components.command_line.sensor._LOGGER") - def test_update_with_json_attrs_no_data(self, mock_logger): - """Test attributes when no JSON result fetched.""" - data = command_line.CommandSensorData(self.hass, "echo ", 15) - self.sensor = command_line.CommandSensor( - self.hass, data, "test", None, None, ["key"] - ) - self.sensor.update() - assert {} == self.sensor.device_state_attributes - assert mock_logger.warning.called - @patch("homeassistant.components.command_line.sensor._LOGGER") - def test_update_with_json_attrs_not_dict(self, mock_logger): - """Test attributes get extracted from a JSON result.""" - data = command_line.CommandSensorData(self.hass, "echo [1, 2, 3]", 15) - self.sensor = command_line.CommandSensor( - self.hass, data, "test", None, None, ["key"] - ) - self.sensor.update() - assert {} == self.sensor.device_state_attributes - assert mock_logger.warning.called +async def test_bad_command(hass: HomeAssistantType) -> None: + """Test bad command.""" + await setup_test_entities( + hass, + { + "command": "asdfasdf", + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert entity_state.state == "unknown" - @patch("homeassistant.components.command_line.sensor._LOGGER") - def test_update_with_json_attrs_bad_JSON(self, mock_logger): - """Test attributes get extracted from a JSON result.""" - data = command_line.CommandSensorData( - self.hass, "echo This is text rather than JSON data.", 15 - ) - self.sensor = command_line.CommandSensor( - self.hass, data, "test", None, None, ["key"] - ) - self.sensor.update() - assert {} == self.sensor.device_state_attributes - assert mock_logger.warning.called - def test_update_with_missing_json_attrs(self): - """Test attributes get extracted from a JSON result.""" - data = command_line.CommandSensorData( - self.hass, - ( - 'echo { \\"key\\": \\"some_json_value\\", \\"another_key\\":\ - \\"another_json_value\\", \\"key_three\\": \\"value_three\\" }' - ), - 15, - ) +async def test_update_with_json_attrs(hass: HomeAssistantType) -> None: + """Test attributes get extracted from a JSON result.""" + await setup_test_entities( + hass, + { + "command": 'echo { \\"key\\": \\"some_json_value\\", \\"another_key\\":\ + \\"another_json_value\\", \\"key_three\\": \\"value_three\\" }', + "json_attributes": ["key", "another_key", "key_three"], + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert entity_state.attributes["key"] == "some_json_value" + assert entity_state.attributes["another_key"] == "another_json_value" + assert entity_state.attributes["key_three"] == "value_three" - self.sensor = command_line.CommandSensor( - self.hass, - data, - "test", - None, - None, - ["key", "another_key", "key_three", "special_key"], - ) - self.sensor.update() - assert self.sensor.device_state_attributes["key"] == "some_json_value" - assert ( - self.sensor.device_state_attributes["another_key"] == "another_json_value" - ) - assert self.sensor.device_state_attributes["key_three"] == "value_three" - assert "special_key" not in self.sensor.device_state_attributes - def test_update_with_unnecessary_json_attrs(self): - """Test attributes get extracted from a JSON result.""" - data = command_line.CommandSensorData( - self.hass, - ( - 'echo { \\"key\\": \\"some_json_value\\", \\"another_key\\":\ - \\"another_json_value\\", \\"key_three\\": \\"value_three\\" }' - ), - 15, - ) +async def test_update_with_json_attrs_no_data(caplog, hass: HomeAssistantType) -> None: # type: ignore[no-untyped-def] + """Test attributes when no JSON result fetched.""" - self.sensor = command_line.CommandSensor( - self.hass, data, "test", None, None, ["key", "another_key"] - ) - self.sensor.update() - assert self.sensor.device_state_attributes["key"] == "some_json_value" - assert ( - self.sensor.device_state_attributes["another_key"] == "another_json_value" - ) - assert "key_three" not in self.sensor.device_state_attributes + await setup_test_entities( + hass, + { + "command": "echo", + "json_attributes": ["key"], + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert "key" not in entity_state.attributes + assert "Empty reply found when expecting JSON data" in caplog.text + + +async def test_update_with_json_attrs_not_dict(caplog, hass: HomeAssistantType) -> None: # type: ignore[no-untyped-def] + """Test attributes when the return value not a dict.""" + + await setup_test_entities( + hass, + { + "command": "echo [1, 2, 3]", + "json_attributes": ["key"], + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert "key" not in entity_state.attributes + assert "JSON result was not a dictionary" in caplog.text + + +async def test_update_with_json_attrs_bad_json(caplog, hass: HomeAssistantType) -> None: # type: ignore[no-untyped-def] + """Test attributes when the return value is invalid JSON.""" + + await setup_test_entities( + hass, + { + "command": "echo This is text rather than JSON data.", + "json_attributes": ["key"], + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert "key" not in entity_state.attributes + assert "Unable to parse output as JSON" in caplog.text + + +async def test_update_with_missing_json_attrs(caplog, hass: HomeAssistantType) -> None: # type: ignore[no-untyped-def] + """Test attributes when an expected key is missing.""" + + await setup_test_entities( + hass, + { + "command": 'echo { \\"key\\": \\"some_json_value\\", \\"another_key\\":\ + \\"another_json_value\\", \\"key_three\\": \\"value_three\\" }', + "json_attributes": ["key", "another_key", "key_three", "missing_key"], + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert entity_state.attributes["key"] == "some_json_value" + assert entity_state.attributes["another_key"] == "another_json_value" + assert entity_state.attributes["key_three"] == "value_three" + assert "missing_key" not in entity_state.attributes + + +async def test_update_with_unnecessary_json_attrs(caplog, hass: HomeAssistantType) -> None: # type: ignore[no-untyped-def] + """Test attributes when an expected key is missing.""" + + await setup_test_entities( + hass, + { + "command": 'echo { \\"key\\": \\"some_json_value\\", \\"another_key\\":\ + \\"another_json_value\\", \\"key_three\\": \\"value_three\\" }', + "json_attributes": ["key", "another_key"], + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert entity_state.attributes["key"] == "some_json_value" + assert entity_state.attributes["another_key"] == "another_json_value" + assert "key_three" not in entity_state.attributes diff --git a/tests/components/command_line/test_switch.py b/tests/components/command_line/test_switch.py index c6d315b05b5..0e31999f928 100644 --- a/tests/components/command_line/test_switch.py +++ b/tests/components/command_line/test_switch.py @@ -1,10 +1,12 @@ """The tests for the Command line switch platform.""" import json import os +import subprocess import tempfile +from unittest.mock import patch -import homeassistant.components.command_line.switch as command_line -import homeassistant.components.switch as switch +from homeassistant import setup +from homeassistant.components.switch import DOMAIN, SCAN_INTERVAL from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_TURN_OFF, @@ -12,230 +14,358 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, ) -from homeassistant.setup import async_setup_component +from homeassistant.helpers.typing import Any, Dict, HomeAssistantType +import homeassistant.util.dt as dt_util + +from tests.common import async_fire_time_changed -async def test_state_none(hass): +async def setup_test_entity( + hass: HomeAssistantType, config_dict: Dict[str, Any] +) -> None: + """Set up a test command line switch entity.""" + assert await setup.async_setup_component( + hass, + DOMAIN, + { + DOMAIN: [ + {"platform": "command_line", "switches": config_dict}, + ] + }, + ) + await hass.async_block_till_done() + + +async def test_state_none(hass: HomeAssistantType) -> None: """Test with none state.""" with tempfile.TemporaryDirectory() as tempdirname: path = os.path.join(tempdirname, "switch_status") - test_switch = { - "command_on": f"echo 1 > {path}", - "command_off": f"echo 0 > {path}", - } - assert await async_setup_component( + await setup_test_entity( hass, - switch.DOMAIN, { - "switch": { - "platform": "command_line", - "switches": {"test": test_switch}, + "test": { + "command_on": f"echo 1 > {path}", + "command_off": f"echo 0 > {path}", } }, ) - await hass.async_block_till_done() - state = hass.states.get("switch.test") - assert STATE_OFF == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_OFF await hass.services.async_call( - switch.DOMAIN, + DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: "switch.test"}, blocking=True, ) - state = hass.states.get("switch.test") - assert STATE_ON == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_ON await hass.services.async_call( - switch.DOMAIN, + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "switch.test"}, blocking=True, ) - state = hass.states.get("switch.test") - assert STATE_OFF == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_OFF -async def test_state_value(hass): +async def test_state_value(hass: HomeAssistantType) -> None: """Test with state value.""" with tempfile.TemporaryDirectory() as tempdirname: path = os.path.join(tempdirname, "switch_status") - test_switch = { - "command_state": f"cat {path}", - "command_on": f"echo 1 > {path}", - "command_off": f"echo 0 > {path}", - "value_template": '{{ value=="1" }}', - } - assert await async_setup_component( + await setup_test_entity( hass, - switch.DOMAIN, { - "switch": { - "platform": "command_line", - "switches": {"test": test_switch}, + "test": { + "command_state": f"cat {path}", + "command_on": f"echo 1 > {path}", + "command_off": f"echo 0 > {path}", + "value_template": '{{ value=="1" }}', } }, ) - await hass.async_block_till_done() - state = hass.states.get("switch.test") - assert STATE_OFF == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_OFF await hass.services.async_call( - switch.DOMAIN, + DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: "switch.test"}, blocking=True, ) - state = hass.states.get("switch.test") - assert STATE_ON == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_ON await hass.services.async_call( - switch.DOMAIN, + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "switch.test"}, blocking=True, ) - state = hass.states.get("switch.test") - assert STATE_OFF == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_OFF -async def test_state_json_value(hass): +async def test_state_json_value(hass: HomeAssistantType) -> None: """Test with state JSON value.""" with tempfile.TemporaryDirectory() as tempdirname: path = os.path.join(tempdirname, "switch_status") oncmd = json.dumps({"status": "ok"}) offcmd = json.dumps({"status": "nope"}) - test_switch = { - "command_state": f"cat {path}", - "command_on": f"echo '{oncmd}' > {path}", - "command_off": f"echo '{offcmd}' > {path}", - "value_template": '{{ value_json.status=="ok" }}', - } - assert await async_setup_component( + + await setup_test_entity( hass, - switch.DOMAIN, { - "switch": { - "platform": "command_line", - "switches": {"test": test_switch}, + "test": { + "command_state": f"cat {path}", + "command_on": f"echo '{oncmd}' > {path}", + "command_off": f"echo '{offcmd}' > {path}", + "value_template": '{{ value_json.status=="ok" }}', } }, ) - await hass.async_block_till_done() - state = hass.states.get("switch.test") - assert STATE_OFF == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_OFF await hass.services.async_call( - switch.DOMAIN, + DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: "switch.test"}, blocking=True, ) - state = hass.states.get("switch.test") - assert STATE_ON == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_ON await hass.services.async_call( - switch.DOMAIN, + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "switch.test"}, blocking=True, ) - state = hass.states.get("switch.test") - assert STATE_OFF == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_OFF -async def test_state_code(hass): +async def test_state_code(hass: HomeAssistantType) -> None: """Test with state code.""" with tempfile.TemporaryDirectory() as tempdirname: path = os.path.join(tempdirname, "switch_status") - test_switch = { - "command_state": f"cat {path}", - "command_on": f"echo 1 > {path}", - "command_off": f"echo 0 > {path}", - } - assert await async_setup_component( + await setup_test_entity( hass, - switch.DOMAIN, { - "switch": { - "platform": "command_line", - "switches": {"test": test_switch}, + "test": { + "command_state": f"cat {path}", + "command_on": f"echo 1 > {path}", + "command_off": f"echo 0 > {path}", } }, ) - await hass.async_block_till_done() - state = hass.states.get("switch.test") - assert STATE_OFF == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_OFF await hass.services.async_call( - switch.DOMAIN, + DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: "switch.test"}, blocking=True, ) - state = hass.states.get("switch.test") - assert STATE_ON == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_ON await hass.services.async_call( - switch.DOMAIN, + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "switch.test"}, blocking=True, ) - state = hass.states.get("switch.test") - assert STATE_ON == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_ON -def test_assumed_state_should_be_true_if_command_state_is_none(hass): +async def test_assumed_state_should_be_true_if_command_state_is_none( + hass: HomeAssistantType, +) -> None: """Test with state value.""" - # args: hass, device_name, friendly_name, command_on, command_off, - # command_state, value_template - init_args = [ + + await setup_test_entity( hass, - "test_device_name", - "Test friendly name!", - "echo 'on command'", - "echo 'off command'", - None, - None, - 15, - ] - - no_state_device = command_line.CommandSwitch(*init_args) - assert no_state_device.assumed_state - - # Set state command - init_args[-3] = "cat {}" - - state_device = command_line.CommandSwitch(*init_args) - assert not state_device.assumed_state + { + "test": { + "command_on": "echo 'on command'", + "command_off": "echo 'off command'", + } + }, + ) + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.attributes["assumed_state"] -def test_entity_id_set_correctly(hass): - """Test that entity_id is set correctly from object_id.""" - init_args = [ +async def test_assumed_state_should_absent_if_command_state_present( + hass: HomeAssistantType, +) -> None: + """Test with state value.""" + + await setup_test_entity( hass, - "test_device_name", - "Test friendly name!", - "echo 'on command'", - "echo 'off command'", - False, - None, - 15, - ] + { + "test": { + "command_on": "echo 'on command'", + "command_off": "echo 'off command'", + "command_state": "cat {}", + } + }, + ) + entity_state = hass.states.get("switch.test") + assert entity_state + assert "assumed_state" not in entity_state.attributes - test_switch = command_line.CommandSwitch(*init_args) - assert test_switch.entity_id == "switch.test_device_name" - assert test_switch.name == "Test friendly name!" + +async def test_name_is_set_correctly(hass: HomeAssistantType) -> None: + """Test that name is set correctly.""" + await setup_test_entity( + hass, + { + "test": { + "command_on": "echo 'on command'", + "command_off": "echo 'off command'", + "friendly_name": "Test friendly name!", + } + }, + ) + + entity_state = hass.states.get("switch.test") + assert entity_state.name == "Test friendly name!" + + +async def test_switch_command_state_fail(caplog: Any, hass: HomeAssistantType) -> None: + """Test that switch failures are handled correctly.""" + await setup_test_entity( + hass, + { + "test": { + "command_on": "exit 0", + "command_off": "exit 0'", + "command_state": "echo 1", + } + }, + ) + + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + + entity_state = hass.states.get("switch.test") + assert entity_state.state == "on" + + await hass.services.async_call( + DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.test"}, + blocking=True, + ) + await hass.async_block_till_done() + + entity_state = hass.states.get("switch.test") + assert entity_state.state == "on" + + assert "Command failed" in caplog.text + + +async def test_switch_command_state_code_exceptions( + caplog: Any, hass: HomeAssistantType +) -> None: + """Test that switch state code exceptions are handled correctly.""" + + with patch( + "homeassistant.components.command_line.subprocess.check_output", + side_effect=[ + subprocess.TimeoutExpired("cmd", 10), + subprocess.SubprocessError(), + ], + ) as check_output: + await setup_test_entity( + hass, + { + "test": { + "command_on": "exit 0", + "command_off": "exit 0'", + "command_state": "echo 1", + } + }, + ) + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + assert check_output.called + assert "Timeout for command" in caplog.text + + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL * 2) + await hass.async_block_till_done() + assert check_output.called + assert "Error trying to exec command" in caplog.text + + +async def test_switch_command_state_value_exceptions( + caplog: Any, hass: HomeAssistantType +) -> None: + """Test that switch state value exceptions are handled correctly.""" + + with patch( + "homeassistant.components.command_line.subprocess.check_output", + side_effect=[ + subprocess.TimeoutExpired("cmd", 10), + subprocess.SubprocessError(), + ], + ) as check_output: + await setup_test_entity( + hass, + { + "test": { + "command_on": "exit 0", + "command_off": "exit 0'", + "command_state": "echo 1", + "value_template": '{{ value=="1" }}', + } + }, + ) + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + assert check_output.call_count == 1 + assert "Timeout for command" in caplog.text + + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL * 2) + await hass.async_block_till_done() + assert check_output.call_count == 2 + assert "Error trying to exec command" in caplog.text + + +async def test_no_switches(caplog: Any, hass: HomeAssistantType) -> None: + """Test with no switches.""" + + await setup_test_entity(hass, {}) + assert "No switches" in caplog.text From adad4a7785f168acf383cdaafd3e67902d7bd972 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Mon, 1 Mar 2021 18:27:43 +0200 Subject: [PATCH 095/831] Fix Shelly Polling (#47224) --- homeassistant/components/shelly/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index d4423dc3a88..ccb52127525 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -108,6 +108,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): "Setup for device %s will resume when device is online", entry.title ) device.subscribe_updates(_async_device_online) + await device.coap_request("s") else: # Restore sensors for sleeping device _LOGGER.debug("Setting up offline device %s", entry.title) From 61f509bdd82045db362cd482cc0d0a76bc5639b5 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Mon, 1 Mar 2021 09:10:28 -0800 Subject: [PATCH 096/831] Minor Hyperion mypy cleanups (#45765) --- homeassistant/components/hyperion/__init__.py | 3 ++- homeassistant/components/hyperion/light.py | 19 ++++++++++++++----- homeassistant/components/hyperion/switch.py | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/hyperion/__init__.py b/homeassistant/components/hyperion/__init__.py index 9e35ae2e6b8..8c001c2b467 100644 --- a/homeassistant/components/hyperion/__init__.py +++ b/homeassistant/components/hyperion/__init__.py @@ -286,7 +286,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b ] ) assert hyperion_client - await async_instances_to_clients_raw(hyperion_client.instances) + if hyperion_client.instances is not None: + await async_instances_to_clients_raw(hyperion_client.instances) hass.data[DOMAIN][config_entry.entry_id][CONF_ON_UNLOAD].append( config_entry.add_update_listener(_async_entry_updated) ) diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index 7bb8a75dfc7..c49a65b2bfd 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -4,7 +4,7 @@ from __future__ import annotations import functools import logging from types import MappingProxyType -from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple +from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple from hyperion import client, const @@ -147,7 +147,7 @@ class HyperionBaseLight(LightEntity): self._static_effect_list += list(const.KEY_COMPONENTID_EXTERNAL_SOURCES) self._effect_list: List[str] = self._static_effect_list[:] - self._client_callbacks = { + self._client_callbacks: Mapping[str, Callable[[Dict[str, Any]], None]] = { f"{const.KEY_ADJUSTMENT}-{const.KEY_UPDATE}": self._update_adjustment, f"{const.KEY_COMPONENTS}-{const.KEY_UPDATE}": self._update_components, f"{const.KEY_EFFECTS}-{const.KEY_UPDATE}": self._update_effect_list, @@ -236,7 +236,7 @@ class HyperionBaseLight(LightEntity): # == Set brightness == if ATTR_BRIGHTNESS in kwargs: brightness = kwargs[ATTR_BRIGHTNESS] - for item in self._client.adjustment: + for item in self._client.adjustment or []: if const.KEY_ID in item: if not await self._client.async_send_set_adjustment( **{ @@ -423,7 +423,12 @@ class HyperionBaseLight(LightEntity): def _get_priority_entry_that_dictates_state(self) -> Optional[Dict[str, Any]]: """Get the relevant Hyperion priority entry to consider.""" # Return the visible priority (whether or not it is the HA priority). - return self._client.visible_priority # type: ignore[no-any-return] + + # Explicit type specifier to ensure this works when the underlying (typed) + # library is installed along with the tests. Casts would trigger a + # redundant-cast warning in this case. + priority: Optional[Dict[str, Any]] = self._client.visible_priority + return priority # pylint: disable=no-self-use def _allow_priority_update(self, priority: Optional[Dict[str, Any]] = None) -> bool: @@ -530,7 +535,11 @@ class HyperionPriorityLight(HyperionBaseLight): if candidate[const.KEY_PRIORITY] == self._get_option( CONF_PRIORITY ) and candidate.get(const.KEY_ACTIVE, False): - return candidate # type: ignore[no-any-return] + # Explicit type specifier to ensure this works when the underlying + # (typed) library is installed along with the tests. Casts would trigger + # a redundant-cast warning in this case. + output: Dict[str, Any] = candidate + return output return None @classmethod diff --git a/homeassistant/components/hyperion/switch.py b/homeassistant/components/hyperion/switch.py index 9d90e1e12ef..0412018650a 100644 --- a/homeassistant/components/hyperion/switch.py +++ b/homeassistant/components/hyperion/switch.py @@ -157,7 +157,7 @@ class HyperionComponentSwitch(SwitchEntity): @property def is_on(self) -> bool: """Return true if the switch is on.""" - for component in self._client.components: + for component in self._client.components or []: if component[KEY_NAME] == self._component_name: return bool(component.setdefault(KEY_ENABLED, False)) return False From 3c290c9a44e377ac9cfbf9452e52558d9e16aad4 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 1 Mar 2021 12:10:51 -0500 Subject: [PATCH 097/831] Address late hassio review (#47229) * hassio code cleanup to address comments in #46342 * fix code --- homeassistant/components/hassio/__init__.py | 45 +++++++++++---------- tests/components/hassio/test_init.py | 2 +- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 82797874445..4e9a78e75a8 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -522,15 +522,17 @@ def async_register_addons_in_dev_reg( ) -> None: """Register addons in the device registry.""" for addon in addons: - dev_reg.async_get_or_create( - config_entry_id=entry_id, - identifiers={(DOMAIN, addon[ATTR_SLUG])}, - manufacturer=addon.get(ATTR_REPOSITORY) or addon.get(ATTR_URL) or "unknown", - model="Home Assistant Add-on", - sw_version=addon[ATTR_VERSION], - name=addon[ATTR_NAME], - entry_type=ATTR_SERVICE, - ) + params = { + "config_entry_id": entry_id, + "identifiers": {(DOMAIN, addon[ATTR_SLUG])}, + "model": "Home Assistant Add-on", + "sw_version": addon[ATTR_VERSION], + "name": addon[ATTR_NAME], + "entry_type": ATTR_SERVICE, + } + if manufacturer := addon.get(ATTR_REPOSITORY) or addon.get(ATTR_URL): + params["manufacturer"] = manufacturer + dev_reg.async_get_or_create(**params) @callback @@ -538,15 +540,16 @@ def async_register_os_in_dev_reg( entry_id: str, dev_reg: DeviceRegistry, os_dict: Dict[str, Any] ) -> None: """Register OS in the device registry.""" - dev_reg.async_get_or_create( - config_entry_id=entry_id, - identifiers={(DOMAIN, "OS")}, - manufacturer="Home Assistant", - model="Home Assistant Operating System", - sw_version=os_dict[ATTR_VERSION], - name="Home Assistant Operating System", - entry_type=ATTR_SERVICE, - ) + params = { + "config_entry_id": entry_id, + "identifiers": {(DOMAIN, "OS")}, + "manufacturer": "Home Assistant", + "model": "Home Assistant Operating System", + "sw_version": os_dict[ATTR_VERSION], + "name": "Home Assistant Operating System", + "entry_type": ATTR_SERVICE, + } + dev_reg.async_get_or_create(**params) @callback @@ -600,15 +603,13 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator): return new_data # Remove add-ons that are no longer installed from device registry - if removed_addons := list( - set(self.data["addons"].keys()) - set(new_data["addons"].keys()) - ): + if removed_addons := list(set(self.data["addons"]) - set(new_data["addons"])): async_remove_addons_from_dev_reg(self.dev_reg, removed_addons) # If there are new add-ons, we should reload the config entry so we can # create new devices and entities. We can return an empty dict because # coordinator will be recreated. - if list(set(new_data["addons"].keys()) - set(self.data["addons"].keys())): + if list(set(new_data["addons"]) - set(self.data["addons"])): self.hass.async_create_task( self.hass.config_entries.async_reload(self.entry_id) ) diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index bd9eb30be5c..5bf8a45ab52 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -393,7 +393,7 @@ async def test_entry_load_and_unload(hass): assert BINARY_SENSOR_DOMAIN in hass.config.components assert ADDONS_COORDINATOR in hass.data - assert await config_entry.async_unload(hass) + assert await hass.config_entries.async_unload(config_entry.entry_id) await hass.async_block_till_done() assert ADDONS_COORDINATOR not in hass.data From 3fda9fd0c636b93ef091002f9035e21d8e19a381 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Mon, 1 Mar 2021 21:59:36 +0100 Subject: [PATCH 098/831] KNX address constant (#47196) --- homeassistant/components/knx/__init__.py | 21 ++++++++++----------- homeassistant/components/knx/const.py | 7 +++++-- homeassistant/components/knx/expose.py | 4 ++-- homeassistant/components/knx/factory.py | 16 ++++++++-------- homeassistant/components/knx/schema.py | 24 ++++++++++++------------ 5 files changed, 37 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index ca1c19c3f5c..697b4cfb524 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -18,7 +18,6 @@ from xknx.telegram import AddressFilter, GroupAddress, Telegram from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite from homeassistant.const import ( - CONF_ADDRESS, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP, @@ -32,7 +31,7 @@ from homeassistant.helpers.reload import async_integration_yaml_config from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.typing import ServiceCallType -from .const import DOMAIN, SupportedPlatforms +from .const import DOMAIN, KNX_ADDRESS, SupportedPlatforms from .expose import create_knx_exposure from .factory import create_knx_device from .schema import ( @@ -150,7 +149,7 @@ CONFIG_SCHEMA = vol.Schema( SERVICE_KNX_SEND_SCHEMA = vol.Any( vol.Schema( { - vol.Required(CONF_ADDRESS): vol.All( + vol.Required(KNX_ADDRESS): vol.All( cv.ensure_list, [ga_validator], ), @@ -161,7 +160,7 @@ SERVICE_KNX_SEND_SCHEMA = vol.Any( vol.Schema( # without type given payload is treated as raw bytes { - vol.Required(CONF_ADDRESS): vol.All( + vol.Required(KNX_ADDRESS): vol.All( cv.ensure_list, [ga_validator], ), @@ -174,7 +173,7 @@ SERVICE_KNX_SEND_SCHEMA = vol.Any( SERVICE_KNX_READ_SCHEMA = vol.Schema( { - vol.Required(CONF_ADDRESS): vol.All( + vol.Required(KNX_ADDRESS): vol.All( cv.ensure_list, [ga_validator], ) @@ -183,7 +182,7 @@ SERVICE_KNX_READ_SCHEMA = vol.Schema( SERVICE_KNX_EVENT_REGISTER_SCHEMA = vol.Schema( { - vol.Required(CONF_ADDRESS): vol.All( + vol.Required(KNX_ADDRESS): vol.All( cv.ensure_list, [ga_validator], ), @@ -200,7 +199,7 @@ SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA = vol.Any( vol.Schema( # for removing only `address` is required { - vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(KNX_ADDRESS): ga_validator, vol.Required(SERVICE_KNX_ATTR_REMOVE): vol.All(cv.boolean, True), }, extra=vol.ALLOW_EXTRA, @@ -421,7 +420,7 @@ class KNXModule: async def service_event_register_modify(self, call): """Service for adding or removing a GroupAddress to the knx_event filter.""" - attr_address = call.data.get(CONF_ADDRESS) + attr_address = call.data.get(KNX_ADDRESS) group_addresses = map(GroupAddress, attr_address) if call.data.get(SERVICE_KNX_ATTR_REMOVE): @@ -444,7 +443,7 @@ class KNXModule: async def service_exposure_register_modify(self, call): """Service for adding or removing an exposure to KNX bus.""" - group_address = call.data.get(CONF_ADDRESS) + group_address = call.data.get(KNX_ADDRESS) if call.data.get(SERVICE_KNX_ATTR_REMOVE): try: @@ -475,7 +474,7 @@ class KNXModule: async def service_send_to_knx_bus(self, call): """Service for sending an arbitrary KNX message to the KNX bus.""" - attr_address = call.data.get(CONF_ADDRESS) + attr_address = call.data.get(KNX_ADDRESS) attr_payload = call.data.get(SERVICE_KNX_ATTR_PAYLOAD) attr_type = call.data.get(SERVICE_KNX_ATTR_TYPE) @@ -499,7 +498,7 @@ class KNXModule: async def service_read_to_knx_bus(self, call): """Service for sending a GroupValueRead telegram to the KNX bus.""" - for address in call.data.get(CONF_ADDRESS): + for address in call.data.get(KNX_ADDRESS): telegram = Telegram( destination_address=GroupAddress(address), payload=GroupValueRead(), diff --git a/homeassistant/components/knx/const.py b/homeassistant/components/knx/const.py index 1829826834c..268f766bfc2 100644 --- a/homeassistant/components/knx/const.py +++ b/homeassistant/components/knx/const.py @@ -17,11 +17,16 @@ from homeassistant.components.climate.const import ( DOMAIN = "knx" +# Address is used for configuration and services by the same functions so the key has to match +KNX_ADDRESS = "address" + CONF_INVERT = "invert" CONF_STATE_ADDRESS = "state_address" CONF_SYNC_STATE = "sync_state" CONF_RESET_AFTER = "reset_after" +ATTR_COUNTER = "counter" + class ColorTempModes(Enum): # pylint: disable=invalid-name @@ -66,5 +71,3 @@ PRESET_MODES = { "Standby": PRESET_AWAY, "Comfort": PRESET_COMFORT, } - -ATTR_COUNTER = "counter" diff --git a/homeassistant/components/knx/expose.py b/homeassistant/components/knx/expose.py index 5abc58f82cc..3a010810862 100644 --- a/homeassistant/components/knx/expose.py +++ b/homeassistant/components/knx/expose.py @@ -5,7 +5,6 @@ from xknx import XKNX from xknx.devices import DateTime, ExposeSensor from homeassistant.const import ( - CONF_ADDRESS, CONF_ENTITY_ID, STATE_OFF, STATE_ON, @@ -16,6 +15,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.typing import ConfigType +from .const import KNX_ADDRESS from .schema import ExposeSchema @@ -24,7 +24,7 @@ def create_knx_exposure( hass: HomeAssistant, xknx: XKNX, config: ConfigType ) -> Union["KNXExposeSensor", "KNXExposeTime"]: """Create exposures from config.""" - address = config[CONF_ADDRESS] + address = config[KNX_ADDRESS] attribute = config.get(ExposeSchema.CONF_KNX_EXPOSE_ATTRIBUTE) entity_id = config.get(CONF_ENTITY_ID) expose_type = config.get(ExposeSchema.CONF_KNX_EXPOSE_TYPE) diff --git a/homeassistant/components/knx/factory.py b/homeassistant/components/knx/factory.py index 51a94bc06e3..893ac1e55e3 100644 --- a/homeassistant/components/knx/factory.py +++ b/homeassistant/components/knx/factory.py @@ -17,10 +17,10 @@ from xknx.devices import ( Weather as XknxWeather, ) -from homeassistant.const import CONF_ADDRESS, CONF_DEVICE_CLASS, CONF_NAME, CONF_TYPE +from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME, CONF_TYPE from homeassistant.helpers.typing import ConfigType -from .const import ColorTempModes, SupportedPlatforms +from .const import KNX_ADDRESS, ColorTempModes, SupportedPlatforms from .schema import ( BinarySensorSchema, ClimateSchema, @@ -99,7 +99,7 @@ def _create_light_color( """Load color configuration from configuration structure.""" if "individual_colors" in config and color in config["individual_colors"]: sub_config = config["individual_colors"][color] - group_address_switch = sub_config.get(CONF_ADDRESS) + group_address_switch = sub_config.get(KNX_ADDRESS) group_address_switch_state = sub_config.get(LightSchema.CONF_STATE_ADDRESS) group_address_brightness = sub_config.get(LightSchema.CONF_BRIGHTNESS_ADDRESS) group_address_brightness_state = sub_config.get( @@ -160,7 +160,7 @@ def _create_light(knx_module: XKNX, config: ConfigType) -> XknxLight: return XknxLight( knx_module, name=config[CONF_NAME], - group_address_switch=config.get(CONF_ADDRESS), + group_address_switch=config.get(KNX_ADDRESS), group_address_switch_state=config.get(LightSchema.CONF_STATE_ADDRESS), group_address_brightness=config.get(LightSchema.CONF_BRIGHTNESS_ADDRESS), group_address_brightness_state=config.get( @@ -275,7 +275,7 @@ def _create_switch(knx_module: XKNX, config: ConfigType) -> XknxSwitch: return XknxSwitch( knx_module, name=config[CONF_NAME], - group_address=config[CONF_ADDRESS], + group_address=config[KNX_ADDRESS], group_address_state=config.get(SwitchSchema.CONF_STATE_ADDRESS), invert=config.get(SwitchSchema.CONF_INVERT), ) @@ -298,7 +298,7 @@ def _create_notify(knx_module: XKNX, config: ConfigType) -> XknxNotification: return XknxNotification( knx_module, name=config[CONF_NAME], - group_address=config[CONF_ADDRESS], + group_address=config[KNX_ADDRESS], ) @@ -307,7 +307,7 @@ def _create_scene(knx_module: XKNX, config: ConfigType) -> XknxScene: return XknxScene( knx_module, name=config[CONF_NAME], - group_address=config[CONF_ADDRESS], + group_address=config[KNX_ADDRESS], scene_number=config[SceneSchema.CONF_SCENE_NUMBER], ) @@ -372,7 +372,7 @@ def _create_fan(knx_module: XKNX, config: ConfigType) -> XknxFan: fan = XknxFan( knx_module, name=config[CONF_NAME], - group_address_speed=config.get(CONF_ADDRESS), + group_address_speed=config.get(KNX_ADDRESS), group_address_speed_state=config.get(FanSchema.CONF_STATE_ADDRESS), group_address_oscillation=config.get(FanSchema.CONF_OSCILLATION_ADDRESS), group_address_oscillation_state=config.get( diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index 08a8c62adc4..c57539b501f 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -5,7 +5,6 @@ from xknx.io import DEFAULT_MCAST_PORT from xknx.telegram.address import GroupAddress, IndividualAddress from homeassistant.const import ( - CONF_ADDRESS, CONF_DEVICE_CLASS, CONF_ENTITY_ID, CONF_HOST, @@ -21,6 +20,7 @@ from .const import ( CONF_STATE_ADDRESS, CONF_SYNC_STATE, CONTROLLER_MODES, + KNX_ADDRESS, PRESET_MODES, ColorTempModes, ) @@ -256,7 +256,7 @@ class ExposeSchema: SCHEMA = vol.Schema( { vol.Required(CONF_KNX_EXPOSE_TYPE): vol.Any(int, float, str), - vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(KNX_ADDRESS): ga_validator, vol.Optional(CONF_ENTITY_ID): cv.entity_id, vol.Optional(CONF_KNX_EXPOSE_ATTRIBUTE): cv.string, vol.Optional(CONF_KNX_EXPOSE_DEFAULT): cv.match_all, @@ -277,7 +277,7 @@ class FanSchema: SCHEMA = vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(KNX_ADDRESS): ga_validator, vol.Optional(CONF_STATE_ADDRESS): ga_validator, vol.Optional(CONF_OSCILLATION_ADDRESS): ga_validator, vol.Optional(CONF_OSCILLATION_STATE_ADDRESS): ga_validator, @@ -315,7 +315,7 @@ class LightSchema: COLOR_SCHEMA = vol.Schema( { - vol.Optional(CONF_ADDRESS): ga_validator, + vol.Optional(KNX_ADDRESS): ga_validator, vol.Optional(CONF_STATE_ADDRESS): ga_validator, vol.Required(CONF_BRIGHTNESS_ADDRESS): ga_validator, vol.Optional(CONF_BRIGHTNESS_STATE_ADDRESS): ga_validator, @@ -326,7 +326,7 @@ class LightSchema: vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_ADDRESS): ga_validator, + vol.Optional(KNX_ADDRESS): ga_validator, vol.Optional(CONF_STATE_ADDRESS): ga_validator, vol.Optional(CONF_BRIGHTNESS_ADDRESS): ga_validator, vol.Optional(CONF_BRIGHTNESS_STATE_ADDRESS): ga_validator, @@ -358,16 +358,16 @@ class LightSchema: vol.Schema( { vol.Required(CONF_INDIVIDUAL_COLORS): { - vol.Required(CONF_RED): {vol.Required(CONF_ADDRESS): object}, - vol.Required(CONF_GREEN): {vol.Required(CONF_ADDRESS): object}, - vol.Required(CONF_BLUE): {vol.Required(CONF_ADDRESS): object}, + vol.Required(CONF_RED): {vol.Required(KNX_ADDRESS): object}, + vol.Required(CONF_GREEN): {vol.Required(KNX_ADDRESS): object}, + vol.Required(CONF_BLUE): {vol.Required(KNX_ADDRESS): object}, }, }, extra=vol.ALLOW_EXTRA, ), vol.Schema( { - vol.Required(CONF_ADDRESS): object, + vol.Required(KNX_ADDRESS): object, }, extra=vol.ALLOW_EXTRA, ), @@ -383,7 +383,7 @@ class NotifySchema: SCHEMA = vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(KNX_ADDRESS): ga_validator, } ) @@ -397,7 +397,7 @@ class SceneSchema: SCHEMA = vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(KNX_ADDRESS): ga_validator, vol.Required(CONF_SCENE_NUMBER): cv.positive_int, } ) @@ -432,7 +432,7 @@ class SwitchSchema: SCHEMA = vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(KNX_ADDRESS): ga_validator, vol.Optional(CONF_STATE_ADDRESS): ga_validator, vol.Optional(CONF_INVERT): cv.boolean, } From dd9e926689557eaf70e41a171f3ec7393998eeae Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 1 Mar 2021 23:34:26 +0100 Subject: [PATCH 099/831] Pass variables to initial evaluation of template trigger (#47236) * Pass variables to initial evaluation of template trigger * Add test * Clarify test --- homeassistant/components/template/trigger.py | 4 ++- tests/components/template/test_trigger.py | 38 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/template/trigger.py b/homeassistant/components/template/trigger.py index 9e6ee086c73..1f378c59335 100644 --- a/homeassistant/components/template/trigger.py +++ b/homeassistant/components/template/trigger.py @@ -41,7 +41,9 @@ async def async_attach_trigger( # Arm at setup if the template is already false. try: - if not result_as_boolean(value_template.async_render()): + if not result_as_boolean( + value_template.async_render(automation_info["variables"]) + ): armed = True except exceptions.TemplateError as ex: _LOGGER.warning( diff --git a/tests/components/template/test_trigger.py b/tests/components/template/test_trigger.py index 3ba79e85bf2..55311005201 100644 --- a/tests/components/template/test_trigger.py +++ b/tests/components/template/test_trigger.py @@ -135,6 +135,44 @@ async def test_if_not_fires_when_true_at_setup(hass, calls): assert len(calls) == 0 +async def test_if_not_fires_when_true_at_setup_variables(hass, calls): + """Test for not firing during startup + trigger_variables.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger_variables": {"entity": "test.entity"}, + "trigger": { + "platform": "template", + "value_template": '{{ is_state(entity|default("test.entity2"), "hello") }}', + }, + "action": {"service": "test.automation"}, + } + }, + ) + + assert len(calls) == 0 + + # Assert that the trigger doesn't fire immediately when it's setup + # If trigger_variable 'entity' is not passed to initial check at setup, the + # trigger will immediately fire + hass.states.async_set("test.entity", "hello", force_update=True) + await hass.async_block_till_done() + assert len(calls) == 0 + + hass.states.async_set("test.entity", "goodbye", force_update=True) + await hass.async_block_till_done() + assert len(calls) == 0 + + # Assert that the trigger fires after state change + # If trigger_variable 'entity' is not passed to the template trigger, the + # trigger will never fire because it falls back to 'test.entity2' + hass.states.async_set("test.entity", "hello", force_update=True) + await hass.async_block_till_done() + assert len(calls) == 1 + + async def test_if_not_fires_because_fail(hass, calls): """Test for not firing after TemplateError.""" hass.states.async_set("test.number", "1") From 96cc17b462c107117382a30bbc30ede246e723ed Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 2 Mar 2021 00:18:18 +0100 Subject: [PATCH 100/831] Add support for a list of known hosts to Google Cast (#47232) --- homeassistant/components/cast/config_flow.py | 149 +++++++++++++++--- homeassistant/components/cast/const.py | 4 + homeassistant/components/cast/discovery.py | 17 +- homeassistant/components/cast/manifest.json | 2 +- homeassistant/components/cast/media_player.py | 8 +- homeassistant/components/cast/strings.json | 26 ++- .../components/cast/translations/en.json | 24 ++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/cast/conftest.py | 76 +++++++++ tests/components/cast/test_init.py | 140 +++++++++++++++- tests/components/cast/test_media_player.py | 69 +------- 12 files changed, 417 insertions(+), 102 deletions(-) create mode 100644 tests/components/cast/conftest.py diff --git a/homeassistant/components/cast/config_flow.py b/homeassistant/components/cast/config_flow.py index e00048a7589..4a4426a5db1 100644 --- a/homeassistant/components/cast/config_flow.py +++ b/homeassistant/components/cast/config_flow.py @@ -1,35 +1,136 @@ """Config flow for Cast.""" -import functools - -from pychromecast.discovery import discover_chromecasts, stop_discovery +import voluptuous as vol from homeassistant import config_entries -from homeassistant.components import zeroconf -from homeassistant.helpers import config_entry_flow +from homeassistant.helpers import config_validation as cv -from .const import DOMAIN -from .helpers import ChromeCastZeroconf +from .const import CONF_KNOWN_HOSTS, DOMAIN + +KNOWN_HOSTS_SCHEMA = vol.Schema(vol.All(cv.ensure_list, [cv.string])) -async def _async_has_devices(hass): - """ - Return if there are devices that can be discovered. +class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow.""" - This function will be called if no devices are already found through the zeroconf - integration. - """ + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH - zeroconf_instance = ChromeCastZeroconf.get_zeroconf() - if zeroconf_instance is None: - zeroconf_instance = await zeroconf.async_get_instance(hass) + def __init__(self): + """Initialize flow.""" + self._known_hosts = None - casts, browser = await hass.async_add_executor_job( - functools.partial(discover_chromecasts, zeroconf_instance=zeroconf_instance) - ) - stop_discovery(browser) - return casts + @staticmethod + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return CastOptionsFlowHandler(config_entry) + + async def async_step_import(self, import_data=None): + """Import data.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + data = {CONF_KNOWN_HOSTS: self._known_hosts} + return self.async_create_entry(title="Google Cast", data=data) + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + return await self.async_step_config() + + async def async_step_zeroconf(self, discovery_info): + """Handle a flow initialized by zeroconf discovery.""" + if self._async_in_progress() or self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + await self.async_set_unique_id(DOMAIN) + + return await self.async_step_confirm() + + async def async_step_config(self, user_input=None): + """Confirm the setup.""" + errors = {} + data = {CONF_KNOWN_HOSTS: self._known_hosts} + + if user_input is not None: + bad_hosts = False + known_hosts = user_input[CONF_KNOWN_HOSTS] + known_hosts = [x.strip() for x in known_hosts.split(",") if x.strip()] + try: + known_hosts = KNOWN_HOSTS_SCHEMA(known_hosts) + except vol.Invalid: + errors["base"] = "invalid_known_hosts" + bad_hosts = True + else: + data[CONF_KNOWN_HOSTS] = known_hosts + if not bad_hosts: + return self.async_create_entry(title="Google Cast", data=data) + + fields = {} + fields[vol.Optional(CONF_KNOWN_HOSTS, default="")] = str + + return self.async_show_form( + step_id="config", data_schema=vol.Schema(fields), errors=errors + ) + + async def async_step_confirm(self, user_input=None): + """Confirm the setup.""" + + data = {CONF_KNOWN_HOSTS: self._known_hosts} + + if user_input is not None: + return self.async_create_entry(title="Google Cast", data=data) + + return self.async_show_form(step_id="confirm") -config_entry_flow.register_discovery_flow( - DOMAIN, "Google Cast", _async_has_devices, config_entries.CONN_CLASS_LOCAL_PUSH -) +class CastOptionsFlowHandler(config_entries.OptionsFlow): + """Handle Google Cast options.""" + + def __init__(self, config_entry): + """Initialize MQTT options flow.""" + self.config_entry = config_entry + self.broker_config = {} + self.options = dict(config_entry.options) + + async def async_step_init(self, user_input=None): + """Manage the Cast options.""" + return await self.async_step_options() + + async def async_step_options(self, user_input=None): + """Manage the MQTT options.""" + errors = {} + current_config = self.config_entry.data + if user_input is not None: + bad_hosts = False + + known_hosts = user_input.get(CONF_KNOWN_HOSTS, "") + known_hosts = [x.strip() for x in known_hosts.split(",") if x.strip()] + try: + known_hosts = KNOWN_HOSTS_SCHEMA(known_hosts) + except vol.Invalid: + errors["base"] = "invalid_known_hosts" + bad_hosts = True + if not bad_hosts: + updated_config = {} + updated_config[CONF_KNOWN_HOSTS] = known_hosts + self.hass.config_entries.async_update_entry( + self.config_entry, data=updated_config + ) + return self.async_create_entry(title="", data=None) + + fields = {} + known_hosts_string = "" + if current_config.get(CONF_KNOWN_HOSTS): + known_hosts_string = ",".join(current_config.get(CONF_KNOWN_HOSTS)) + fields[ + vol.Optional( + "known_hosts", description={"suggested_value": known_hosts_string} + ) + ] = str + + return self.async_show_form( + step_id="options", + data_schema=vol.Schema(fields), + errors=errors, + ) diff --git a/homeassistant/components/cast/const.py b/homeassistant/components/cast/const.py index c6164484dbb..993315b5518 100644 --- a/homeassistant/components/cast/const.py +++ b/homeassistant/components/cast/const.py @@ -13,6 +13,8 @@ KNOWN_CHROMECAST_INFO_KEY = "cast_known_chromecasts" ADDED_CAST_DEVICES_KEY = "cast_added_cast_devices" # Stores an audio group manager. CAST_MULTIZONE_MANAGER_KEY = "cast_multizone_manager" +# Store a CastBrowser +CAST_BROWSER_KEY = "cast_browser" # Dispatcher signal fired with a ChromecastInfo every time we discover a new # Chromecast or receive it through configuration @@ -24,3 +26,5 @@ SIGNAL_CAST_REMOVED = "cast_removed" # Dispatcher signal fired when a Chromecast should show a Home Assistant Cast view. SIGNAL_HASS_CAST_SHOW_VIEW = "cast_show_view" + +CONF_KNOWN_HOSTS = "known_hosts" diff --git a/homeassistant/components/cast/discovery.py b/homeassistant/components/cast/discovery.py index 81048b35a97..fcae28b5bfe 100644 --- a/homeassistant/components/cast/discovery.py +++ b/homeassistant/components/cast/discovery.py @@ -9,6 +9,8 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import dispatcher_send from .const import ( + CAST_BROWSER_KEY, + CONF_KNOWN_HOSTS, DEFAULT_PORT, INTERNAL_DISCOVERY_RUNNING_KEY, KNOWN_CHROMECAST_INFO_KEY, @@ -52,7 +54,7 @@ def _remove_chromecast(hass: HomeAssistant, info: ChromecastInfo): dispatcher_send(hass, SIGNAL_CAST_REMOVED, info) -def setup_internal_discovery(hass: HomeAssistant) -> None: +def setup_internal_discovery(hass: HomeAssistant, config_entry) -> None: """Set up the pychromecast internal discovery.""" if INTERNAL_DISCOVERY_RUNNING_KEY not in hass.data: hass.data[INTERNAL_DISCOVERY_RUNNING_KEY] = threading.Lock() @@ -86,8 +88,11 @@ def setup_internal_discovery(hass: HomeAssistant) -> None: _LOGGER.debug("Starting internal pychromecast discovery") browser = pychromecast.discovery.CastBrowser( - CastListener(), ChromeCastZeroconf.get_zeroconf() + CastListener(), + ChromeCastZeroconf.get_zeroconf(), + config_entry.data.get(CONF_KNOWN_HOSTS), ) + hass.data[CAST_BROWSER_KEY] = browser browser.start_discovery() def stop_discovery(event): @@ -97,3 +102,11 @@ def setup_internal_discovery(hass: HomeAssistant) -> None: hass.data[INTERNAL_DISCOVERY_RUNNING_KEY].release() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_discovery) + + config_entry.add_update_listener(config_entry_updated) + + +async def config_entry_updated(hass, config_entry): + """Handle config entry being updated.""" + browser = hass.data[CAST_BROWSER_KEY] + browser.host_browser.update_hosts(config_entry.data.get(CONF_KNOWN_HOSTS)) diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index ac728b4ec45..0c9d0dfc4a5 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==9.0.0"], + "requirements": ["pychromecast==9.1.1"], "after_dependencies": ["cloud", "http", "media_source", "plex", "tts", "zeroconf"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"] diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 235c7ab4479..fbb08437830 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -134,7 +134,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): # no pending task done, _ = await asyncio.wait( [ - _async_setup_platform(hass, ENTITY_SCHEMA(cfg), async_add_entities) + _async_setup_platform( + hass, ENTITY_SCHEMA(cfg), async_add_entities, config_entry + ) for cfg in config ] ) @@ -146,7 +148,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async def _async_setup_platform( - hass: HomeAssistantType, config: ConfigType, async_add_entities + hass: HomeAssistantType, config: ConfigType, async_add_entities, config_entry ): """Set up the cast platform.""" # Import CEC IGNORE attributes @@ -177,7 +179,7 @@ async def _async_setup_platform( async_cast_discovered(chromecast) ChromeCastZeroconf.set_zeroconf(await zeroconf.async_get_instance(hass)) - hass.async_add_executor_job(setup_internal_discovery, hass) + hass.async_add_executor_job(setup_internal_discovery, hass, config_entry) class CastDevice(MediaPlayerEntity): diff --git a/homeassistant/components/cast/strings.json b/homeassistant/components/cast/strings.json index ad8f0f41ae7..7cd07518db8 100644 --- a/homeassistant/components/cast/strings.json +++ b/homeassistant/components/cast/strings.json @@ -3,11 +3,33 @@ "step": { "confirm": { "description": "[%key:common::config_flow::description::confirm_setup%]" + }, + "config": { + "title": "Google Cast", + "description": "Please enter the Google Cast configuration.", + "data": { + "known_hosts": "Optional list of known hosts if mDNS discovery is not working." + } } }, "abort": { - "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" + }, + "error": { + "invalid_known_hosts": "Known hosts must be a comma separated list of hosts." + } + }, + "options": { + "step": { + "options": { + "description": "Please enter the Google Cast configuration.", + "data": { + "known_hosts": "Optional list of known hosts if mDNS discovery is not working." + } + } + }, + "error": { + "invalid_known_hosts": "Known hosts must be a comma separated list of hosts." } } } diff --git a/homeassistant/components/cast/translations/en.json b/homeassistant/components/cast/translations/en.json index f05becffed3..9ef2f10cda2 100644 --- a/homeassistant/components/cast/translations/en.json +++ b/homeassistant/components/cast/translations/en.json @@ -1,13 +1,35 @@ { "config": { "abort": { - "no_devices_found": "No devices found on the network", "single_instance_allowed": "Already configured. Only a single configuration possible." }, + "error": { + "invalid_known_hosts": "Known hosts must be a comma separated list of hosts." + }, "step": { + "config": { + "data": { + "known_hosts": "Optional list of known hosts if mDNS discovery is not working." + }, + "description": "Please enter the Google Cast configuration.", + "title": "Google Cast" + }, "confirm": { "description": "Do you want to start set up?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Known hosts must be a comma separated list of hosts." + }, + "step": { + "options": { + "data": { + "known_hosts": "Optional list of known hosts if mDNS discovery is not working." + }, + "description": "Please enter the Google Cast configuration." + } + } } } \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index 5bd96e37683..761c47e34da 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1302,7 +1302,7 @@ pycfdns==1.2.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==9.0.0 +pychromecast==9.1.1 # homeassistant.components.pocketcasts pycketcasts==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 510bb91b265..9f84817843c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -685,7 +685,7 @@ pybotvac==0.0.20 pycfdns==1.2.1 # homeassistant.components.cast -pychromecast==9.0.0 +pychromecast==9.1.1 # homeassistant.components.climacell pyclimacell==0.14.0 diff --git a/tests/components/cast/conftest.py b/tests/components/cast/conftest.py new file mode 100644 index 00000000000..875d831afa9 --- /dev/null +++ b/tests/components/cast/conftest.py @@ -0,0 +1,76 @@ +"""Test fixtures for the cast integration.""" +# pylint: disable=protected-access +from unittest.mock import AsyncMock, MagicMock, patch + +import pychromecast +import pytest + + +@pytest.fixture() +def dial_mock(): + """Mock pychromecast dial.""" + dial_mock = MagicMock() + dial_mock.get_device_status.return_value.uuid = "fake_uuid" + dial_mock.get_device_status.return_value.manufacturer = "fake_manufacturer" + dial_mock.get_device_status.return_value.model_name = "fake_model_name" + dial_mock.get_device_status.return_value.friendly_name = "fake_friendly_name" + dial_mock.get_multizone_status.return_value.dynamic_groups = [] + return dial_mock + + +@pytest.fixture() +def castbrowser_mock(): + """Mock pychromecast CastBrowser.""" + return MagicMock() + + +@pytest.fixture() +def castbrowser_constructor_mock(): + """Mock pychromecast CastBrowser constructor.""" + return MagicMock() + + +@pytest.fixture() +def mz_mock(): + """Mock pychromecast MultizoneManager.""" + return MagicMock() + + +@pytest.fixture() +def pycast_mock(castbrowser_mock, castbrowser_constructor_mock): + """Mock pychromecast.""" + pycast_mock = MagicMock() + pycast_mock.discovery.CastBrowser = castbrowser_constructor_mock + pycast_mock.discovery.CastBrowser.return_value = castbrowser_mock + pycast_mock.discovery.AbstractCastListener = ( + pychromecast.discovery.AbstractCastListener + ) + return pycast_mock + + +@pytest.fixture() +def quick_play_mock(): + """Mock pychromecast quick_play.""" + return MagicMock() + + +@pytest.fixture(autouse=True) +def cast_mock(dial_mock, mz_mock, pycast_mock, quick_play_mock): + """Mock pychromecast.""" + with patch( + "homeassistant.components.cast.media_player.pychromecast", pycast_mock + ), patch( + "homeassistant.components.cast.discovery.pychromecast", pycast_mock + ), patch( + "homeassistant.components.cast.helpers.dial", dial_mock + ), patch( + "homeassistant.components.cast.media_player.MultizoneManager", + return_value=mz_mock, + ), patch( + "homeassistant.components.cast.media_player.zeroconf.async_get_instance", + AsyncMock(), + ), patch( + "homeassistant.components.cast.media_player.quick_play", + quick_play_mock, + ): + yield diff --git a/tests/components/cast/test_init.py b/tests/components/cast/test_init.py index d364256b703..77268e7de97 100644 --- a/tests/components/cast/test_init.py +++ b/tests/components/cast/test_init.py @@ -1,11 +1,14 @@ """Tests for the Cast config flow.""" +from unittest.mock import ANY, patch -from unittest.mock import patch +import pytest from homeassistant import config_entries, data_entry_flow from homeassistant.components import cast from homeassistant.setup import async_setup_component +from tests.common import MockConfigEntry + async def test_creating_entry_sets_up_media_player(hass): """Test setting up Cast loads the media player.""" @@ -54,3 +57,138 @@ async def test_not_configuring_cast_not_creates_entry(hass): await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 0 + + +@pytest.mark.parametrize("source", ["import", "user", "zeroconf"]) +async def test_single_instance(hass, source): + """Test we only allow a single config flow.""" + MockConfigEntry(domain="cast").add_to_hass(hass) + await hass.async_block_till_done() + + result = await hass.config_entries.flow.async_init( + "cast", context={"source": source} + ) + assert result["type"] == "abort" + assert result["reason"] == "single_instance_allowed" + + +async def test_user_setup(hass, mqtt_mock): + """Test we can finish a config flow.""" + result = await hass.config_entries.flow.async_init( + "cast", context={"source": "user"} + ) + assert result["type"] == "form" + + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + + users = await hass.auth.async_get_users() + assert len(users) == 1 + assert result["type"] == "create_entry" + assert result["result"].data == { + "known_hosts": [], + "user_id": users[0].id, # Home Assistant cast user + } + + +async def test_user_setup_options(hass, mqtt_mock): + """Test we can finish a config flow.""" + result = await hass.config_entries.flow.async_init( + "cast", context={"source": "user"} + ) + assert result["type"] == "form" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"known_hosts": "192.168.0.1, , 192.168.0.2 "} + ) + + users = await hass.auth.async_get_users() + assert len(users) == 1 + assert result["type"] == "create_entry" + assert result["result"].data == { + "known_hosts": ["192.168.0.1", "192.168.0.2"], + "user_id": users[0].id, # Home Assistant cast user + } + + +async def test_zeroconf_setup(hass): + """Test we can finish a config flow through zeroconf.""" + result = await hass.config_entries.flow.async_init( + "cast", context={"source": "zeroconf"} + ) + assert result["type"] == "form" + + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + + users = await hass.auth.async_get_users() + assert len(users) == 1 + assert result["type"] == "create_entry" + assert result["result"].data == { + "known_hosts": None, + "user_id": users[0].id, # Home Assistant cast user + } + + +def get_suggested(schema, key): + """Get suggested value for key in voluptuous schema.""" + for k in schema.keys(): + if k == key: + if k.description is None or "suggested_value" not in k.description: + return None + return k.description["suggested_value"] + + +async def test_option_flow(hass): + """Test config flow options.""" + config_entry = MockConfigEntry( + domain="cast", data={"known_hosts": ["192.168.0.10", "192.168.0.11"]} + ) + config_entry.add_to_hass(hass) + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init(config_entry.entry_id) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "options" + data_schema = result["data_schema"].schema + assert get_suggested(data_schema, "known_hosts") == "192.168.0.10,192.168.0.11" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={"known_hosts": "192.168.0.1, , 192.168.0.2 "}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"] is None + assert config_entry.data == {"known_hosts": ["192.168.0.1", "192.168.0.2"]} + + +async def test_known_hosts(hass, castbrowser_mock, castbrowser_constructor_mock): + """Test known hosts is passed to pychromecasts.""" + result = await hass.config_entries.flow.async_init( + "cast", context={"source": "user"} + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"known_hosts": "192.168.0.1, 192.168.0.2"} + ) + assert result["type"] == "create_entry" + await hass.async_block_till_done() + config_entry = hass.config_entries.async_entries("cast")[0] + + assert castbrowser_mock.start_discovery.call_count == 1 + castbrowser_constructor_mock.assert_called_once_with( + ANY, ANY, ["192.168.0.1", "192.168.0.2"] + ) + castbrowser_mock.reset_mock() + castbrowser_constructor_mock.reset_mock() + + result = await hass.config_entries.options.async_init(config_entry.entry_id) + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={"known_hosts": "192.168.0.11, 192.168.0.12"}, + ) + + await hass.async_block_till_done() + + castbrowser_mock.start_discovery.assert_not_called() + castbrowser_constructor_mock.assert_not_called() + castbrowser_mock.host_browser.update_hosts.assert_called_once_with( + ["192.168.0.11", "192.168.0.12"] + ) diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 51c49484c50..27f817b6771 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -2,7 +2,7 @@ # pylint: disable=protected-access import json from typing import Optional -from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch +from unittest.mock import ANY, MagicMock, Mock, patch from uuid import UUID import attr @@ -35,70 +35,6 @@ from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry, assert_setup_component from tests.components.media_player import common - -@pytest.fixture() -def dial_mock(): - """Mock pychromecast dial.""" - dial_mock = MagicMock() - dial_mock.get_device_status.return_value.uuid = "fake_uuid" - dial_mock.get_device_status.return_value.manufacturer = "fake_manufacturer" - dial_mock.get_device_status.return_value.model_name = "fake_model_name" - dial_mock.get_device_status.return_value.friendly_name = "fake_friendly_name" - dial_mock.get_multizone_status.return_value.dynamic_groups = [] - return dial_mock - - -@pytest.fixture() -def castbrowser_mock(): - """Mock pychromecast CastBrowser.""" - return MagicMock() - - -@pytest.fixture() -def mz_mock(): - """Mock pychromecast MultizoneManager.""" - return MagicMock() - - -@pytest.fixture() -def pycast_mock(castbrowser_mock): - """Mock pychromecast.""" - pycast_mock = MagicMock() - pycast_mock.discovery.CastBrowser.return_value = castbrowser_mock - pycast_mock.discovery.AbstractCastListener = ( - pychromecast.discovery.AbstractCastListener - ) - return pycast_mock - - -@pytest.fixture() -def quick_play_mock(): - """Mock pychromecast quick_play.""" - return MagicMock() - - -@pytest.fixture(autouse=True) -def cast_mock(dial_mock, mz_mock, pycast_mock, quick_play_mock): - """Mock pychromecast.""" - with patch( - "homeassistant.components.cast.media_player.pychromecast", pycast_mock - ), patch( - "homeassistant.components.cast.discovery.pychromecast", pycast_mock - ), patch( - "homeassistant.components.cast.helpers.dial", dial_mock - ), patch( - "homeassistant.components.cast.media_player.MultizoneManager", - return_value=mz_mock, - ), patch( - "homeassistant.components.cast.media_player.zeroconf.async_get_instance", - AsyncMock(), - ), patch( - "homeassistant.components.cast.media_player.quick_play", - quick_play_mock, - ): - yield - - # pylint: disable=invalid-name FakeUUID = UUID("57355bce-9364-4aa6-ac1e-eb849dccf9e2") FakeUUID2 = UUID("57355bce-9364-4aa6-ac1e-eb849dccf9e4") @@ -482,7 +418,8 @@ async def test_replay_past_chromecasts(hass): assert add_dev1.call_count == 1 add_dev2 = Mock() - await cast._async_setup_platform(hass, {"host": "host2"}, add_dev2) + entry = hass.config_entries.async_entries("cast")[0] + await cast._async_setup_platform(hass, {"host": "host2"}, add_dev2, entry) await hass.async_block_till_done() assert add_dev2.call_count == 1 From 2e65a60624fef94ee42c24408a0a1bb120328279 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Mar 2021 17:18:47 -0600 Subject: [PATCH 101/831] Fix lutron caseta fan handling of speed off (#47244) --- homeassistant/components/lutron_caseta/fan.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/lutron_caseta/fan.py b/homeassistant/components/lutron_caseta/fan.py index 57b87b18320..edda379aedc 100644 --- a/homeassistant/components/lutron_caseta/fan.py +++ b/homeassistant/components/lutron_caseta/fan.py @@ -46,6 +46,8 @@ class LutronCasetaFan(LutronCasetaDevice, FanEntity): """Return the current speed percentage.""" if self._device["fan_speed"] is None: return None + if self._device["fan_speed"] == FAN_OFF: + return 0 return ordered_list_item_to_percentage( ORDERED_NAMED_FAN_SPEEDS, self._device["fan_speed"] ) From c6cfcc2abb34a569eecdcc06f94bc9b272463252 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Tue, 2 Mar 2021 00:21:10 +0100 Subject: [PATCH 102/831] Add remote control support to philips_js (#47249) --- .../components/philips_js/__init__.py | 2 +- homeassistant/components/philips_js/remote.py | 108 ++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/philips_js/remote.py diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index f3c2eb59789..dd3e8d194e9 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -21,7 +21,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import DOMAIN -PLATFORMS = ["media_player"] +PLATFORMS = ["media_player", "remote"] LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/philips_js/remote.py b/homeassistant/components/philips_js/remote.py new file mode 100644 index 00000000000..30b46499e28 --- /dev/null +++ b/homeassistant/components/philips_js/remote.py @@ -0,0 +1,108 @@ +"""Remote control support for Apple TV.""" + +import asyncio + +from haphilipsjs.typing import SystemType + +from homeassistant.components.remote import ( + ATTR_DELAY_SECS, + ATTR_NUM_REPEATS, + DEFAULT_DELAY_SECS, + RemoteEntity, +) + +from . import LOGGER, PhilipsTVDataUpdateCoordinator +from .const import CONF_SYSTEM, DOMAIN + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the configuration entry.""" + coordinator = hass.data[DOMAIN][config_entry.entry_id] + async_add_entities( + [ + PhilipsTVRemote( + coordinator, + config_entry.data[CONF_SYSTEM], + config_entry.unique_id, + ) + ] + ) + + +class PhilipsTVRemote(RemoteEntity): + """Device that sends commands.""" + + def __init__( + self, + coordinator: PhilipsTVDataUpdateCoordinator, + system: SystemType, + unique_id: str, + ): + """Initialize the Philips TV.""" + self._tv = coordinator.api + self._coordinator = coordinator + self._system = system + self._unique_id = unique_id + + @property + def name(self): + """Return the device name.""" + return self._system["name"] + + @property + def is_on(self): + """Return true if device is on.""" + if self._tv.on: + if self._tv.powerstate == "On" or self._tv.powerstate is None: + return True + return False + + @property + def should_poll(self): + """No polling needed for Apple TV.""" + return False + + @property + def unique_id(self): + """Return unique identifier if known.""" + return self._unique_id + + @property + def device_info(self): + """Return a device description for device registry.""" + return { + "name": self._system["name"], + "identifiers": { + (DOMAIN, self._unique_id), + }, + "model": self._system.get("model"), + "manufacturer": "Philips", + "sw_version": self._system.get("softwareversion"), + } + + async def async_turn_on(self, **kwargs): + """Turn the device on.""" + if self._tv.on and self._tv.powerstate: + await self._tv.setPowerState("On") + else: + await self._coordinator.turn_on.async_run(self.hass, self._context) + self.async_write_ha_state() + + async def async_turn_off(self, **kwargs): + """Turn the device off.""" + if self._tv.on: + await self._tv.sendKey("Standby") + self.async_write_ha_state() + else: + LOGGER.debug("Tv was already turned off") + + async def async_send_command(self, command, **kwargs): + """Send a command to one device.""" + num_repeats = kwargs[ATTR_NUM_REPEATS] + delay = kwargs.get(ATTR_DELAY_SECS, DEFAULT_DELAY_SECS) + + for _ in range(num_repeats): + for single_command in command: + LOGGER.debug("Sending command %s", single_command) + await self._tv.sendKey(single_command) + await asyncio.sleep(delay) From 3e34bb3e89c83b3ba39602713721b689d874ea3f Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 1 Mar 2021 18:24:55 -0500 Subject: [PATCH 103/831] Add suggested area for zwave_js devices (#47250) --- homeassistant/components/zwave_js/__init__.py | 19 ++++++++++-------- tests/components/zwave_js/common.py | 3 +++ tests/components/zwave_js/conftest.py | 6 ++---- tests/components/zwave_js/test_init.py | 20 ++++++++++++++++++- tests/components/zwave_js/test_light.py | 8 +++++--- .../zwave_js/eaton_rf9640_dimmer_state.json | 2 +- 6 files changed, 41 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 93d511875af..798fd9fda2c 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -67,14 +67,17 @@ def register_node_in_dev_reg( node: ZwaveNode, ) -> None: """Register node in dev reg.""" - device = dev_reg.async_get_or_create( - config_entry_id=entry.entry_id, - identifiers={get_device_id(client, node)}, - sw_version=node.firmware_version, - name=node.name or node.device_config.description or f"Node {node.node_id}", - model=node.device_config.label, - manufacturer=node.device_config.manufacturer, - ) + params = { + "config_entry_id": entry.entry_id, + "identifiers": {get_device_id(client, node)}, + "sw_version": node.firmware_version, + "name": node.name or node.device_config.description or f"Node {node.node_id}", + "model": node.device_config.label, + "manufacturer": node.device_config.manufacturer, + } + if node.location: + params["suggested_area"] = node.location + device = dev_reg.async_get_or_create(**params) async_dispatcher_send(hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device) diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index ebba16136a0..a5ee628754e 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -16,3 +16,6 @@ PROPERTY_DOOR_STATUS_BINARY_SENSOR = ( CLIMATE_RADIO_THERMOSTAT_ENTITY = "climate.z_wave_thermostat" CLIMATE_DANFOSS_LC13_ENTITY = "climate.living_connect_z_thermostat" CLIMATE_FLOOR_THERMOSTAT_ENTITY = "climate.floor_thermostat" +BULB_6_MULTI_COLOR_LIGHT_ENTITY = "light.bulb_6_multi_color" +EATON_RF9640_ENTITY = "light.allloaddimmer" +AEON_SMART_SWITCH_LIGHT_ENTITY = "light.smart_switch_6" diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index e0bc588abf4..72835fb17c1 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -10,9 +10,7 @@ from zwave_js_server.model.driver import Driver from zwave_js_server.model.node import Node from zwave_js_server.version import VersionInfo -from homeassistant.helpers.device_registry import ( - async_get_registry as async_get_device_registry, -) +from homeassistant.helpers.device_registry import async_get as async_get_device_registry from tests.common import MockConfigEntry, load_fixture @@ -20,7 +18,7 @@ from tests.common import MockConfigEntry, load_fixture @pytest.fixture(name="device_registry") async def device_registry_fixture(hass): """Return the device registry.""" - return await async_get_device_registry(hass) + return async_get_device_registry(hass) @pytest.fixture(name="controller_state", scope="session") diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index bff2ecd198c..2a2f249c361 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -18,7 +18,11 @@ from homeassistant.config_entries import ( from homeassistant.const import STATE_UNAVAILABLE from homeassistant.helpers import device_registry, entity_registry -from .common import AIR_TEMPERATURE_SENSOR, NOTIFICATION_MOTION_BINARY_SENSOR +from .common import ( + AIR_TEMPERATURE_SENSOR, + EATON_RF9640_ENTITY, + NOTIFICATION_MOTION_BINARY_SENSOR, +) from tests.common import MockConfigEntry @@ -467,3 +471,17 @@ async def test_removed_device(hass, client, multiple_devices, integration): ) assert len(entity_entries) == 15 assert dev_reg.async_get_device({get_device_id(client, old_node)}) is None + + +async def test_suggested_area(hass, client, eaton_rf9640_dimmer): + """Test that suggested area works.""" + dev_reg = device_registry.async_get(hass) + ent_reg = entity_registry.async_get(hass) + + entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"}) + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + entity = ent_reg.async_get(EATON_RF9640_ENTITY) + assert dev_reg.async_get(entity.device_id).area_id is not None diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index ca36ea35393..d6d6c030d34 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -12,9 +12,11 @@ from homeassistant.components.light import ( ) from homeassistant.const import ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON -BULB_6_MULTI_COLOR_LIGHT_ENTITY = "light.bulb_6_multi_color" -EATON_RF9640_ENTITY = "light.allloaddimmer" -AEON_SMART_SWITCH_LIGHT_ENTITY = "light.smart_switch_6" +from .common import ( + AEON_SMART_SWITCH_LIGHT_ENTITY, + BULB_6_MULTI_COLOR_LIGHT_ENTITY, + EATON_RF9640_ENTITY, +) async def test_light(hass, client, bulb_6_multi_color, integration): diff --git a/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json b/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json index db815506a6b..b11d2bfd180 100644 --- a/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json +++ b/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json @@ -27,7 +27,7 @@ "nodeType": 0, "roleType": 5, "name": "AllLoadDimmer", - "location": "", + "location": "LivingRoom", "deviceConfig": { "manufacturerId": 26, "manufacturer": "Eaton", From 036ce55bea285e73e67f546f534f23ba874a1f7f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 2 Mar 2021 00:32:39 +0100 Subject: [PATCH 104/831] Update frontend to 20210301.0 (#47252) --- 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 01f1c72f8d6..e8f9ff2698d 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20210226.0" + "home-assistant-frontend==20210301.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 9506171303b..cb211fb1962 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210226.0 +home-assistant-frontend==20210301.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 761c47e34da..a59048ec4b1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210226.0 +home-assistant-frontend==20210301.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9f84817843c..520325e1aef 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210226.0 +home-assistant-frontend==20210301.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 3ee589d973d5317104e2433a6fa33df52fe91899 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 1 Mar 2021 17:08:36 -0700 Subject: [PATCH 105/831] Bump simplisafe-python to 9.6.8 (#47241) --- 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 de5199ccd4c..6122428ea98 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.6.7"], + "requirements": ["simplisafe-python==9.6.8"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index a59048ec4b1..75e660f0fb0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2041,7 +2041,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==9.6.7 +simplisafe-python==9.6.8 # homeassistant.components.sisyphus sisyphus-control==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 520325e1aef..2037a55bd49 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1048,7 +1048,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==9.6.7 +simplisafe-python==9.6.8 # homeassistant.components.slack slackclient==2.5.0 From cc6293623f603240aa05508f4a89976598b3d20d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 1 Mar 2021 16:12:48 -0800 Subject: [PATCH 106/831] Revert "Fix the updater schema (#47128)" (#47254) This reverts commit 98be703d90e44efe43b1a17c7e5243e5097b00b1. --- homeassistant/components/updater/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index 81910db38d6..9d65bb4c5d4 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -27,7 +27,7 @@ UPDATER_URL = "https://updater.home-assistant.io/" CONFIG_SCHEMA = vol.Schema( { - vol.Optional(DOMAIN, default={}): { + DOMAIN: { vol.Optional(CONF_REPORTING, default=True): cv.boolean, vol.Optional(CONF_COMPONENT_REPORTING, default=False): cv.boolean, } @@ -56,13 +56,13 @@ async def async_setup(hass, config): # This component only makes sense in release versions _LOGGER.info("Running on 'dev', only analytics will be submitted") - conf = config[DOMAIN] - if conf[CONF_REPORTING]: + conf = config.get(DOMAIN, {}) + if conf.get(CONF_REPORTING): huuid = await hass.helpers.instance_id.async_get() else: huuid = None - include_components = conf[CONF_COMPONENT_REPORTING] + include_components = conf.get(CONF_COMPONENT_REPORTING) async def check_new_version() -> Updater: """Check if a new version is available and report if one is.""" From 4f9f870e7de481a479caf9fea2d4308dfcad9098 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Mar 2021 01:27:26 +0100 Subject: [PATCH 107/831] Fix duplicate template handling in Persistent Notifications (#47217) --- .../persistent_notification/__init__.py | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/persistent_notification/__init__.py b/homeassistant/components/persistent_notification/__init__.py index 5f08f79dc00..589cc97baea 100644 --- a/homeassistant/components/persistent_notification/__init__.py +++ b/homeassistant/components/persistent_notification/__init__.py @@ -11,6 +11,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import TemplateError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id +from homeassistant.helpers.template import Template from homeassistant.loader import bind_hass from homeassistant.util import slugify import homeassistant.util.dt as dt_util @@ -35,8 +36,8 @@ SERVICE_MARK_READ = "mark_read" SCHEMA_SERVICE_CREATE = vol.Schema( { - vol.Required(ATTR_MESSAGE): cv.template, - vol.Optional(ATTR_TITLE): cv.template, + vol.Required(ATTR_MESSAGE): vol.Any(cv.dynamic_template, cv.string), + vol.Optional(ATTR_TITLE): vol.Any(cv.dynamic_template, cv.string), vol.Optional(ATTR_NOTIFICATION_ID): cv.string, } ) @@ -118,22 +119,24 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool: attr = {} if title is not None: - try: - title.hass = hass - title = title.async_render(parse_result=False) - except TemplateError as ex: - _LOGGER.error("Error rendering title %s: %s", title, ex) - title = title.template + if isinstance(title, Template): + try: + title.hass = hass + title = title.async_render(parse_result=False) + except TemplateError as ex: + _LOGGER.error("Error rendering title %s: %s", title, ex) + title = title.template attr[ATTR_TITLE] = title attr[ATTR_FRIENDLY_NAME] = title - try: - message.hass = hass - message = message.async_render(parse_result=False) - except TemplateError as ex: - _LOGGER.error("Error rendering message %s: %s", message, ex) - message = message.template + if isinstance(message, Template): + try: + message.hass = hass + message = message.async_render(parse_result=False) + except TemplateError as ex: + _LOGGER.error("Error rendering message %s: %s", message, ex) + message = message.template attr[ATTR_MESSAGE] = message From d1afef843bcb7a08ba12d23e6ef5faeb5557687d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Mar 2021 01:34:14 +0100 Subject: [PATCH 108/831] Deprecate LIFX Legacy integration (#47235) --- homeassistant/components/lifx/light.py | 7 ------- homeassistant/components/lifx_legacy/light.py | 13 +++++++++++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/lifx/light.py b/homeassistant/components/lifx/light.py index f06a7720bb2..9f1c5747aa8 100644 --- a/homeassistant/components/lifx/light.py +++ b/homeassistant/components/lifx/light.py @@ -4,7 +4,6 @@ from datetime import timedelta from functools import partial import logging import math -import sys import aiolifx as aiolifx_module import aiolifx_effects as aiolifx_effects_module @@ -166,12 +165,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async def async_setup_entry(hass, config_entry, async_add_entities): """Set up LIFX from a config entry.""" - if sys.platform == "win32": - _LOGGER.warning( - "The lifx platform is known to not work on Windows. " - "Consider using the lifx_legacy platform instead" - ) - # Priority 1: manual config interfaces = hass.data[LIFX_DOMAIN].get(DOMAIN) if not interfaces: diff --git a/homeassistant/components/lifx_legacy/light.py b/homeassistant/components/lifx_legacy/light.py index f0ed9105b99..4d50ecbecf2 100644 --- a/homeassistant/components/lifx_legacy/light.py +++ b/homeassistant/components/lifx_legacy/light.py @@ -46,13 +46,22 @@ SUPPORT_LIFX = ( SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_COLOR | SUPPORT_TRANSITION ) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - {vol.Optional(CONF_SERVER): cv.string, vol.Optional(CONF_BROADCAST): cv.string} +PLATFORM_SCHEMA = vol.All( + cv.deprecated(CONF_SERVER), + cv.deprecated(CONF_BROADCAST), + PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_SERVER): cv.string, vol.Optional(CONF_BROADCAST): cv.string} + ), ) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the LIFX platform.""" + _LOGGER.warning( + "The LIFX Legacy platform is deprecated and will be removed in " + "Home Assistant Core 2021.6.0. Use the LIFX integration instead." + ) + server_addr = config.get(CONF_SERVER) broadcast_addr = config.get(CONF_BROADCAST) From cb99969845b77097c1b3f5f350ef04b54be70394 Mon Sep 17 00:00:00 2001 From: Milan Meulemans Date: Tue, 2 Mar 2021 01:34:39 +0100 Subject: [PATCH 109/831] Fix typo in plaato strings (#47245) --- homeassistant/components/plaato/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/plaato/strings.json b/homeassistant/components/plaato/strings.json index 852ecc88dde..85bc39a8d83 100644 --- a/homeassistant/components/plaato/strings.json +++ b/homeassistant/components/plaato/strings.json @@ -23,7 +23,7 @@ } }, "error": { - "invalid_webhook_device": "You have selected a device that doesn't not support sending data to a webhook. It is only available for the Airlock", + "invalid_webhook_device": "You have selected a device that does not support sending data to a webhook. It is only available for the Airlock", "no_auth_token": "You need to add an auth token", "no_api_method": "You need to add an auth token or select webhook" }, From d02218ff307dc41ffae25bffee7d6b7fb616d63d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Mar 2021 18:56:42 -0600 Subject: [PATCH 110/831] Fix harmony failing to switch activities when a switch is in progress (#47212) Co-authored-by: Paulus Schoutsen --- homeassistant/components/harmony/data.py | 28 +++++++++++-------- .../components/harmony/subscriber.py | 15 ++++++++++ 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/harmony/data.py b/homeassistant/components/harmony/data.py index 8c1d137bc85..340596ff1ef 100644 --- a/homeassistant/components/harmony/data.py +++ b/homeassistant/components/harmony/data.py @@ -22,17 +22,8 @@ class HarmonyData(HarmonySubscriberMixin): self._name = name self._unique_id = unique_id self._available = False - - callbacks = { - "config_updated": self._config_updated, - "connect": self._connected, - "disconnect": self._disconnected, - "new_activity_starting": self._activity_starting, - "new_activity": self._activity_started, - } - self._client = HarmonyClient( - ip_address=address, callbacks=ClientCallbackType(**callbacks) - ) + self._client = None + self._address = address @property def activities(self): @@ -105,6 +96,18 @@ class HarmonyData(HarmonySubscriberMixin): async def connect(self) -> bool: """Connect to the Harmony Hub.""" _LOGGER.debug("%s: Connecting", self._name) + + callbacks = { + "config_updated": self._config_updated, + "connect": self._connected, + "disconnect": self._disconnected, + "new_activity_starting": self._activity_starting, + "new_activity": self._activity_started, + } + self._client = HarmonyClient( + ip_address=self._address, callbacks=ClientCallbackType(**callbacks) + ) + try: if not await self._client.connect(): _LOGGER.warning("%s: Unable to connect to HUB", self._name) @@ -113,6 +116,7 @@ class HarmonyData(HarmonySubscriberMixin): except aioexc.TimeOut: _LOGGER.warning("%s: Connection timed-out", self._name) return False + return True async def shutdown(self): @@ -159,10 +163,12 @@ class HarmonyData(HarmonySubscriberMixin): ) return + await self.async_lock_start_activity() try: await self._client.start_activity(activity_id) except aioexc.TimeOut: _LOGGER.error("%s: Starting activity %s timed-out", self.name, activity) + self.async_unlock_start_activity() async def async_power_off(self): """Start the PowerOff activity.""" diff --git a/homeassistant/components/harmony/subscriber.py b/homeassistant/components/harmony/subscriber.py index d3bed33d560..b2652cc43d1 100644 --- a/homeassistant/components/harmony/subscriber.py +++ b/homeassistant/components/harmony/subscriber.py @@ -1,5 +1,6 @@ """Mixin class for handling harmony callback subscriptions.""" +import asyncio import logging from typing import Any, Callable, NamedTuple, Optional @@ -29,6 +30,17 @@ class HarmonySubscriberMixin: super().__init__() self._hass = hass self._subscriptions = [] + self._activity_lock = asyncio.Lock() + + async def async_lock_start_activity(self): + """Acquire the lock.""" + await self._activity_lock.acquire() + + @callback + def async_unlock_start_activity(self): + """Release the lock.""" + if self._activity_lock.locked(): + self._activity_lock.release() @callback def async_subscribe(self, update_callbacks: HarmonyCallback) -> Callable: @@ -51,11 +63,13 @@ class HarmonySubscriberMixin: def _connected(self, _=None) -> None: _LOGGER.debug("connected") + self.async_unlock_start_activity() self._available = True self._call_callbacks("connected") def _disconnected(self, _=None) -> None: _LOGGER.debug("disconnected") + self.async_unlock_start_activity() self._available = False self._call_callbacks("disconnected") @@ -65,6 +79,7 @@ class HarmonySubscriberMixin: def _activity_started(self, activity_info: tuple) -> None: _LOGGER.debug("activity %s started", activity_info) + self.async_unlock_start_activity() self._call_callbacks("activity_started", activity_info) def _call_callbacks(self, callback_func_name: str, argument: tuple = None): From 7bc232880268d5f2a3facd5aaa8c53f49b0b9c1d Mon Sep 17 00:00:00 2001 From: stephan192 Date: Tue, 2 Mar 2021 02:00:42 +0100 Subject: [PATCH 111/831] Remove rounding from The Things Network (#47157) --- homeassistant/components/thethingsnetwork/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/thethingsnetwork/sensor.py b/homeassistant/components/thethingsnetwork/sensor.py index 2e7b7f9499b..0afec8f7510 100644 --- a/homeassistant/components/thethingsnetwork/sensor.py +++ b/homeassistant/components/thethingsnetwork/sensor.py @@ -81,8 +81,8 @@ class TtnDataSensor(Entity): """Return the state of the entity.""" if self._ttn_data_storage.data is not None: try: - return round(self._state[self._value], 1) - except (KeyError, TypeError): + return self._state[self._value] + except KeyError: return None return None From 853d9ac4a9fbe67fa0a9d859441fde97a6ac1dbb Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Tue, 2 Mar 2021 02:12:49 +0100 Subject: [PATCH 112/831] Update color logic for zwave_js light platform (#47110) Co-authored-by: Raman Gupta <7243222+raman325@users.noreply.github.com> --- homeassistant/components/zwave_js/light.py | 159 +++++++++++++-------- tests/components/zwave_js/test_light.py | 45 +++--- 2 files changed, 122 insertions(+), 82 deletions(-) diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index 6ed0286e184..d9c31210bea 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -1,6 +1,6 @@ """Support for Z-Wave lights.""" import logging -from typing import Any, Callable, Optional, Tuple +from typing import Any, Callable, Dict, Optional, Tuple from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import ColorComponent, CommandClass @@ -30,6 +30,17 @@ from .entity import ZWaveBaseEntity LOGGER = logging.getLogger(__name__) +MULTI_COLOR_MAP = { + ColorComponent.WARM_WHITE: "warmWhite", + ColorComponent.COLD_WHITE: "coldWhite", + ColorComponent.RED: "red", + ColorComponent.GREEN: "green", + ColorComponent.BLUE: "blue", + ColorComponent.AMBER: "amber", + ColorComponent.CYAN: "cyan", + ColorComponent.PURPLE: "purple", +} + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable @@ -149,21 +160,21 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): # RGB/HS color hs_color = kwargs.get(ATTR_HS_COLOR) if hs_color is not None and self._supports_color: - # set white levels to 0 when setting rgb - await self._async_set_color("Warm White", 0) - await self._async_set_color("Cold White", 0) red, green, blue = color_util.color_hs_to_RGB(*hs_color) - await self._async_set_color("Red", red) - await self._async_set_color("Green", green) - await self._async_set_color("Blue", blue) + colors = { + ColorComponent.RED: red, + ColorComponent.GREEN: green, + ColorComponent.BLUE: blue, + } + if self._supports_color_temp: + # turn of white leds when setting rgb + colors[ColorComponent.WARM_WHITE] = 0 + colors[ColorComponent.COLD_WHITE] = 0 + await self._async_set_colors(colors) # Color temperature color_temp = kwargs.get(ATTR_COLOR_TEMP) if color_temp is not None and self._supports_color_temp: - # turn off rgb when setting white values - await self._async_set_color("Red", 0) - await self._async_set_color("Green", 0) - await self._async_set_color("Blue", 0) # Limit color temp to min/max values cold = max( 0, @@ -177,17 +188,28 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): ), ) warm = 255 - cold - await self._async_set_color("Warm White", warm) - await self._async_set_color("Cold White", cold) + await self._async_set_colors( + { + # turn off color leds when setting color temperature + ColorComponent.RED: 0, + ColorComponent.GREEN: 0, + ColorComponent.BLUE: 0, + ColorComponent.WARM_WHITE: warm, + ColorComponent.COLD_WHITE: cold, + } + ) # White value white_value = kwargs.get(ATTR_WHITE_VALUE) if white_value is not None and self._supports_white_value: - # turn off rgb when setting white values - await self._async_set_color("Red", 0) - await self._async_set_color("Green", 0) - await self._async_set_color("Blue", 0) - await self._async_set_color("Warm White", white_value) + # white led brightness is controlled by white level + # rgb leds (if any) can be on at the same time + await self._async_set_colors( + { + ColorComponent.WARM_WHITE: white_value, + ColorComponent.COLD_WHITE: white_value, + } + ) # set brightness await self._async_set_brightness( @@ -198,24 +220,33 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): """Turn the light off.""" await self._async_set_brightness(0, kwargs.get(ATTR_TRANSITION)) - async def _async_set_color(self, color_name: str, new_value: int) -> None: - """Set defined color to given value.""" - try: - property_key = ColorComponent[color_name.upper().replace(" ", "_")].value - except KeyError: - raise ValueError( - "Illegal color name specified, color must be one of " - f"{','.join([color.name for color in ColorComponent])}" - ) from None - cur_zwave_value = self.get_zwave_value( - "currentColor", + async def _async_set_colors(self, colors: Dict[ColorComponent, int]) -> None: + """Set (multiple) defined colors to given value(s).""" + # prefer the (new) combined color property + # https://github.com/zwave-js/node-zwave-js/pull/1782 + combined_color_val = self.get_zwave_value( + "targetColor", CommandClass.SWITCH_COLOR, - value_property_key=property_key.key, - value_property_key_name=property_key.name, + value_property_key=None, + value_property_key_name=None, ) - # guard for unsupported command - if cur_zwave_value is None: + if combined_color_val and isinstance(combined_color_val.value, dict): + colors_dict = {} + for color, value in colors.items(): + color_name = MULTI_COLOR_MAP[color] + colors_dict[color_name] = value + # set updated color object + await self.info.node.async_set_value(combined_color_val, colors_dict) return + + # fallback to setting the color(s) one by one if multicolor fails + # not sure this is needed at all, but just in case + for color, value in colors.items(): + await self._async_set_color(color, value) + + async def _async_set_color(self, color: ColorComponent, new_value: int) -> None: + """Set defined color to given value.""" + property_key = color.value # actually set the new color value target_zwave_value = self.get_zwave_value( "targetColor", @@ -224,6 +255,7 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): value_property_key_name=property_key.name, ) if target_zwave_value is None: + # guard for unsupported color return await self.info.node.async_set_value(target_zwave_value, new_value) @@ -231,9 +263,6 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): self, brightness: Optional[int], transition: Optional[int] = None ) -> None: """Set new brightness to light.""" - if brightness is None and self.info.primary_value.value: - # there is no point in setting default brightness when light is already on - return if brightness is None: # Level 255 means to set it to previous value. zwave_brightness = 255 @@ -282,8 +311,9 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): @callback def _calculate_color_values(self) -> None: """Calculate light colors.""" - - # RGB support + # NOTE: We lookup all values here (instead of relying on the multicolor one) + # to find out what colors are supported + # as this is a simple lookup by key, this not heavy red_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, @@ -302,19 +332,6 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): value_property_key=ColorComponent.BLUE.value.key, value_property_key_name=ColorComponent.BLUE.value.name, ) - if red_val and green_val and blue_val: - self._supports_color = True - # convert to HS - if ( - red_val.value is not None - and green_val.value is not None - and blue_val.value is not None - ): - self._hs_color = color_util.color_RGB_to_hs( - red_val.value, green_val.value, blue_val.value - ) - - # White colors ww_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, @@ -327,23 +344,47 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): value_property_key=ColorComponent.COLD_WHITE.value.key, value_property_key_name=ColorComponent.COLD_WHITE.value.name, ) + # prefer the (new) combined color property + # https://github.com/zwave-js/node-zwave-js/pull/1782 + combined_color_val = self.get_zwave_value( + "currentColor", + CommandClass.SWITCH_COLOR, + value_property_key=None, + value_property_key_name=None, + ) + if combined_color_val and isinstance(combined_color_val.value, dict): + multi_color = combined_color_val.value + else: + multi_color = {} + + # RGB support + if red_val and green_val and blue_val: + # prefer values from the multicolor property + red = multi_color.get("red", red_val.value) + green = multi_color.get("green", green_val.value) + blue = multi_color.get("blue", blue_val.value) + self._supports_color = True + # convert to HS + self._hs_color = color_util.color_RGB_to_hs(red, green, blue) + + # color temperature support if ww_val and cw_val: - # Color temperature (CW + WW) Support self._supports_color_temp = True + warm_white = multi_color.get("warmWhite", ww_val.value) + cold_white = multi_color.get("coldWhite", cw_val.value) # Calculate color temps based on whites - cold_level = cw_val.value or 0 - if cold_level or ww_val.value is not None: + if cold_white or warm_white: self._color_temp = round( self._max_mireds - - ((cold_level / 255) * (self._max_mireds - self._min_mireds)) + - ((cold_white / 255) * (self._max_mireds - self._min_mireds)) ) else: self._color_temp = None + # only one white channel (warm white) = white_level support elif ww_val: - # only one white channel (warm white) self._supports_white_value = True - self._white_value = ww_val.value + self._white_value = multi_color.get("warmWhite", ww_val.value) + # only one white channel (cool white) = white_level support elif cw_val: - # only one white channel (cool white) self._supports_white_value = True - self._white_value = cw_val.value + self._white_value = multi_color.get("coldWhite", cw_val.value) diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index d6d6c030d34..c16e2474980 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -139,62 +139,62 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 5 - warm_args = client.async_send_command_no_wait.call_args_list[0][0][ - 0 - ] # warm white 0 + assert len(client.async_send_command_no_wait.call_args_list) == 6 + warm_args = client.async_send_command_no_wait.call_args_list[0][0][0] # red 255 assert warm_args["command"] == "node.set_value" assert warm_args["nodeId"] == 39 assert warm_args["valueId"]["commandClassName"] == "Color Switch" assert warm_args["valueId"]["commandClass"] == 51 assert warm_args["valueId"]["endpoint"] == 0 - assert warm_args["valueId"]["metadata"]["label"] == "Target value (Warm White)" + assert warm_args["valueId"]["metadata"]["label"] == "Target value (Red)" assert warm_args["valueId"]["property"] == "targetColor" assert warm_args["valueId"]["propertyName"] == "targetColor" - assert warm_args["value"] == 0 + assert warm_args["value"] == 255 - cold_args = client.async_send_command_no_wait.call_args_list[1][0][ - 0 - ] # cold white 0 + cold_args = client.async_send_command_no_wait.call_args_list[1][0][0] # green 76 assert cold_args["command"] == "node.set_value" assert cold_args["nodeId"] == 39 assert cold_args["valueId"]["commandClassName"] == "Color Switch" assert cold_args["valueId"]["commandClass"] == 51 assert cold_args["valueId"]["endpoint"] == 0 - assert cold_args["valueId"]["metadata"]["label"] == "Target value (Cold White)" + assert cold_args["valueId"]["metadata"]["label"] == "Target value (Green)" assert cold_args["valueId"]["property"] == "targetColor" assert cold_args["valueId"]["propertyName"] == "targetColor" - assert cold_args["value"] == 0 - red_args = client.async_send_command_no_wait.call_args_list[2][0][0] # red 255 + assert cold_args["value"] == 76 + red_args = client.async_send_command_no_wait.call_args_list[2][0][0] # blue 255 assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 assert red_args["valueId"]["commandClassName"] == "Color Switch" assert red_args["valueId"]["commandClass"] == 51 assert red_args["valueId"]["endpoint"] == 0 - assert red_args["valueId"]["metadata"]["label"] == "Target value (Red)" + assert red_args["valueId"]["metadata"]["label"] == "Target value (Blue)" assert red_args["valueId"]["property"] == "targetColor" assert red_args["valueId"]["propertyName"] == "targetColor" assert red_args["value"] == 255 - green_args = client.async_send_command_no_wait.call_args_list[3][0][0] # green 76 + green_args = client.async_send_command_no_wait.call_args_list[3][0][ + 0 + ] # warm white 0 assert green_args["command"] == "node.set_value" assert green_args["nodeId"] == 39 assert green_args["valueId"]["commandClassName"] == "Color Switch" assert green_args["valueId"]["commandClass"] == 51 assert green_args["valueId"]["endpoint"] == 0 - assert green_args["valueId"]["metadata"]["label"] == "Target value (Green)" + assert green_args["valueId"]["metadata"]["label"] == "Target value (Warm White)" assert green_args["valueId"]["property"] == "targetColor" assert green_args["valueId"]["propertyName"] == "targetColor" - assert green_args["value"] == 76 - blue_args = client.async_send_command_no_wait.call_args_list[4][0][0] # blue 255 + assert green_args["value"] == 0 + blue_args = client.async_send_command_no_wait.call_args_list[4][0][ + 0 + ] # cold white 0 assert blue_args["command"] == "node.set_value" assert blue_args["nodeId"] == 39 assert blue_args["valueId"]["commandClassName"] == "Color Switch" assert blue_args["valueId"]["commandClass"] == 51 assert blue_args["valueId"]["endpoint"] == 0 - assert blue_args["valueId"]["metadata"]["label"] == "Target value (Blue)" + assert blue_args["valueId"]["metadata"]["label"] == "Target value (Cold White)" assert blue_args["valueId"]["property"] == "targetColor" assert blue_args["valueId"]["propertyName"] == "targetColor" - assert blue_args["value"] == 255 + assert blue_args["value"] == 0 # Test rgb color update from value updated event red_event = Event( @@ -234,7 +234,6 @@ async def test_light(hass, client, bulb_6_multi_color, integration): state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY) assert state.state == STATE_ON assert state.attributes[ATTR_BRIGHTNESS] == 255 - assert state.attributes[ATTR_COLOR_TEMP] == 370 assert state.attributes[ATTR_RGB_COLOR] == (255, 76, 255) client.async_send_command_no_wait.reset_mock() @@ -247,7 +246,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 5 + assert len(client.async_send_command_no_wait.call_args_list) == 6 client.async_send_command_no_wait.reset_mock() @@ -259,7 +258,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 5 + assert len(client.async_send_command_no_wait.call_args_list) == 6 red_args = client.async_send_command_no_wait.call_args_list[0][0][0] # red 0 assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 @@ -369,7 +368,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 5 + assert len(client.async_send_command_no_wait.call_args_list) == 6 client.async_send_command_no_wait.reset_mock() From 7e71050669dd1809d0206cfba8f89408b0724289 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 2 Mar 2021 04:34:37 +0100 Subject: [PATCH 113/831] Add battery sensor for gogogate2 wireless door sensor (#47145) Co-authored-by: J. Nick Koston --- .../components/gogogate2/__init__.py | 25 +- homeassistant/components/gogogate2/common.py | 48 +++- homeassistant/components/gogogate2/cover.py | 41 +-- homeassistant/components/gogogate2/sensor.py | 59 ++++ tests/components/gogogate2/test_sensor.py | 261 ++++++++++++++++++ 5 files changed, 388 insertions(+), 46 deletions(-) create mode 100644 homeassistant/components/gogogate2/sensor.py create mode 100644 tests/components/gogogate2/test_sensor.py diff --git a/homeassistant/components/gogogate2/__init__.py b/homeassistant/components/gogogate2/__init__.py index 93f000e6a3a..6d0318b99ad 100644 --- a/homeassistant/components/gogogate2/__init__.py +++ b/homeassistant/components/gogogate2/__init__.py @@ -1,5 +1,8 @@ """The gogogate2 component.""" -from homeassistant.components.cover import DOMAIN as COVER_DOMAIN +import asyncio + +from homeassistant.components.cover import DOMAIN as COVER +from homeassistant.components.sensor import DOMAIN as SENSOR from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_DEVICE from homeassistant.core import HomeAssistant @@ -8,6 +11,8 @@ from homeassistant.exceptions import ConfigEntryNotReady from .common import get_data_update_coordinator from .const import DEVICE_TYPE_GOGOGATE2 +PLATFORMS = [COVER, SENSOR] + async def async_setup(hass: HomeAssistant, base_config: dict) -> bool: """Set up for Gogogate2 controllers.""" @@ -34,17 +39,23 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b if not data_update_coordinator.last_update_success: raise ConfigEntryNotReady() - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, COVER_DOMAIN) - ) + for platform in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, platform) + ) return True async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Unload Gogogate2 config entry.""" - hass.async_create_task( - hass.config_entries.async_forward_entry_unload(config_entry, COVER_DOMAIN) + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS + ] + ) ) - return True + return unload_ok diff --git a/homeassistant/components/gogogate2/common.py b/homeassistant/components/gogogate2/common.py index 2817c351013..761f9211921 100644 --- a/homeassistant/components/gogogate2/common.py +++ b/homeassistant/components/gogogate2/common.py @@ -4,7 +4,7 @@ import logging from typing import Awaitable, Callable, NamedTuple, Optional from gogogate2_api import AbstractGateApi, GogoGate2Api, ISmartGateApi -from gogogate2_api.common import AbstractDoor +from gogogate2_api.common import AbstractDoor, get_door_by_id from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -15,9 +15,13 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.helpers.debounce import Debouncer -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) -from .const import DATA_UPDATE_COORDINATOR, DEVICE_TYPE_ISMARTGATE, DOMAIN +from .const import DATA_UPDATE_COORDINATOR, DEVICE_TYPE_ISMARTGATE, DOMAIN, MANUFACTURER _LOGGER = logging.getLogger(__name__) @@ -57,6 +61,44 @@ class DeviceDataUpdateCoordinator(DataUpdateCoordinator): self.api = api +class GoGoGate2Entity(CoordinatorEntity): + """Base class for gogogate2 entities.""" + + def __init__( + self, + config_entry: ConfigEntry, + data_update_coordinator: DeviceDataUpdateCoordinator, + door: AbstractDoor, + ) -> None: + """Initialize gogogate2 base entity.""" + super().__init__(data_update_coordinator) + self._config_entry = config_entry + self._door = door + self._unique_id = cover_unique_id(config_entry, door) + + @property + def unique_id(self) -> Optional[str]: + """Return a unique ID.""" + return self._unique_id + + def _get_door(self) -> AbstractDoor: + door = get_door_by_id(self._door.door_id, self.coordinator.data) + self._door = door or self._door + return self._door + + @property + def device_info(self): + """Device info for the controller.""" + data = self.coordinator.data + return { + "identifiers": {(DOMAIN, self._config_entry.unique_id)}, + "name": self._config_entry.title, + "manufacturer": MANUFACTURER, + "model": data.model, + "sw_version": data.firmwareversion, + } + + def get_data_update_coordinator( hass: HomeAssistant, config_entry: ConfigEntry ) -> DeviceDataUpdateCoordinator: diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index 8b83073d0c8..f2e05b10599 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -1,12 +1,7 @@ """Support for Gogogate2 garage Doors.""" from typing import Callable, List, Optional -from gogogate2_api.common import ( - AbstractDoor, - DoorStatus, - get_configured_doors, - get_door_by_id, -) +from gogogate2_api.common import AbstractDoor, DoorStatus, get_configured_doors import voluptuous as vol from homeassistant.components.cover import ( @@ -26,14 +21,13 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from homeassistant.helpers.update_coordinator import CoordinatorEntity from .common import ( DeviceDataUpdateCoordinator, - cover_unique_id, + GoGoGate2Entity, get_data_update_coordinator, ) -from .const import DEVICE_TYPE_GOGOGATE2, DEVICE_TYPE_ISMARTGATE, DOMAIN, MANUFACTURER +from .const import DEVICE_TYPE_GOGOGATE2, DEVICE_TYPE_ISMARTGATE, DOMAIN COVER_SCHEMA = vol.Schema( { @@ -74,7 +68,7 @@ async def async_setup_entry( ) -class DeviceCover(CoordinatorEntity, CoverEntity): +class DeviceCover(GoGoGate2Entity, CoverEntity): """Cover entity for goggate2.""" def __init__( @@ -84,18 +78,10 @@ class DeviceCover(CoordinatorEntity, CoverEntity): door: AbstractDoor, ) -> None: """Initialize the object.""" - super().__init__(data_update_coordinator) - self._config_entry = config_entry - self._door = door + super().__init__(config_entry, data_update_coordinator, door) self._api = data_update_coordinator.api - self._unique_id = cover_unique_id(config_entry, door) self._is_available = True - @property - def unique_id(self) -> Optional[str]: - """Return a unique ID.""" - return self._unique_id - @property def name(self): """Return the name of the door.""" @@ -141,20 +127,3 @@ class DeviceCover(CoordinatorEntity, CoverEntity): attrs = super().state_attributes attrs["door_id"] = self._get_door().door_id return attrs - - def _get_door(self) -> AbstractDoor: - door = get_door_by_id(self._door.door_id, self.coordinator.data) - self._door = door or self._door - return self._door - - @property - def device_info(self): - """Device info for the controller.""" - data = self.coordinator.data - return { - "identifiers": {(DOMAIN, self._config_entry.unique_id)}, - "name": self._config_entry.title, - "manufacturer": MANUFACTURER, - "model": data.model, - "sw_version": data.firmwareversion, - } diff --git a/homeassistant/components/gogogate2/sensor.py b/homeassistant/components/gogogate2/sensor.py new file mode 100644 index 00000000000..55aacd2fdbb --- /dev/null +++ b/homeassistant/components/gogogate2/sensor.py @@ -0,0 +1,59 @@ +"""Support for Gogogate2 garage Doors.""" +from typing import Callable, List, Optional + +from gogogate2_api.common import get_configured_doors + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import DEVICE_CLASS_BATTERY +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity + +from .common import GoGoGate2Entity, get_data_update_coordinator + +SENSOR_ID_WIRED = "WIRE" + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: Callable[[List[Entity], Optional[bool]], None], +) -> None: + """Set up the config entry.""" + data_update_coordinator = get_data_update_coordinator(hass, config_entry) + + async_add_entities( + [ + DoorSensor(config_entry, data_update_coordinator, door) + for door in get_configured_doors(data_update_coordinator.data) + if door.sensorid and door.sensorid != SENSOR_ID_WIRED + ] + ) + + +class DoorSensor(GoGoGate2Entity): + """Sensor entity for goggate2.""" + + @property + def name(self): + """Return the name of the door.""" + return f"{self._get_door().name} battery" + + @property + def device_class(self): + """Return the class of this device, from component DEVICE_CLASSES.""" + return DEVICE_CLASS_BATTERY + + @property + def state(self): + """Return the state of the entity.""" + door = self._get_door() + return door.voltage # This is a percentage, not an absolute voltage + + @property + def state_attributes(self): + """Return the state attributes.""" + attrs = super().state_attributes or {} + door = self._get_door() + if door.sensorid is not None: + attrs["sensorid"] = door.door_id + return attrs diff --git a/tests/components/gogogate2/test_sensor.py b/tests/components/gogogate2/test_sensor.py new file mode 100644 index 00000000000..0bd67dfc92a --- /dev/null +++ b/tests/components/gogogate2/test_sensor.py @@ -0,0 +1,261 @@ +"""Tests for the GogoGate2 component.""" +from datetime import timedelta +from unittest.mock import MagicMock, patch + +from gogogate2_api import GogoGate2Api, ISmartGateApi +from gogogate2_api.common import ( + DoorMode, + DoorStatus, + GogoGate2ActivateResponse, + GogoGate2Door, + GogoGate2InfoResponse, + ISmartGateDoor, + ISmartGateInfoResponse, + Network, + Outputs, + Wifi, +) + +from homeassistant.components.gogogate2.const import DEVICE_TYPE_ISMARTGATE, DOMAIN +from homeassistant.config_entries import SOURCE_USER +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + CONF_DEVICE, + CONF_IP_ADDRESS, + CONF_PASSWORD, + CONF_USERNAME, + DEVICE_CLASS_BATTERY, + STATE_UNAVAILABLE, + STATE_UNKNOWN, +) +from homeassistant.core import HomeAssistant +from homeassistant.util.dt import utcnow + +from tests.common import MockConfigEntry, async_fire_time_changed + + +def _mocked_gogogate_sensor_response(battery_level: int): + return GogoGate2InfoResponse( + user="user1", + gogogatename="gogogatename0", + model="", + apiversion="", + remoteaccessenabled=False, + remoteaccess="abc123.blah.blah", + firmwareversion="", + apicode="", + door1=GogoGate2Door( + door_id=1, + permission=True, + name="Door1", + gate=False, + mode=DoorMode.GARAGE, + status=DoorStatus.OPENED, + sensor=True, + sensorid="ABCD", + camera=False, + events=2, + temperature=None, + voltage=battery_level, + ), + door2=GogoGate2Door( + door_id=2, + permission=True, + name="Door2", + gate=True, + mode=DoorMode.GARAGE, + status=DoorStatus.UNDEFINED, + sensor=True, + sensorid="WIRE", + camera=False, + events=0, + temperature=None, + voltage=battery_level, + ), + door3=GogoGate2Door( + door_id=3, + permission=True, + name="Door3", + gate=False, + mode=DoorMode.GARAGE, + status=DoorStatus.UNDEFINED, + sensor=True, + sensorid=None, + camera=False, + events=0, + temperature=None, + voltage=battery_level, + ), + outputs=Outputs(output1=True, output2=False, output3=True), + network=Network(ip=""), + wifi=Wifi(SSID="", linkquality="", signal=""), + ) + + +def _mocked_ismartgate_sensor_response(battery_level: int): + return ISmartGateInfoResponse( + user="user1", + ismartgatename="ismartgatename0", + model="ismartgatePRO", + apiversion="", + remoteaccessenabled=False, + remoteaccess="abc321.blah.blah", + firmwareversion="555", + pin=123, + lang="en", + newfirmware=False, + door1=ISmartGateDoor( + door_id=1, + permission=True, + name="Door1", + gate=False, + mode=DoorMode.GARAGE, + status=DoorStatus.CLOSED, + sensor=True, + sensorid="ABCD", + camera=False, + events=2, + temperature=None, + enabled=True, + apicode="apicode0", + customimage=False, + voltage=battery_level, + ), + door2=ISmartGateDoor( + door_id=2, + permission=True, + name="Door2", + gate=True, + mode=DoorMode.GARAGE, + status=DoorStatus.CLOSED, + sensor=True, + sensorid="WIRE", + camera=False, + events=2, + temperature=None, + enabled=True, + apicode="apicode0", + customimage=False, + voltage=battery_level, + ), + door3=ISmartGateDoor( + door_id=3, + permission=True, + name="Door3", + gate=False, + mode=DoorMode.GARAGE, + status=DoorStatus.UNDEFINED, + sensor=True, + sensorid=None, + camera=False, + events=0, + temperature=None, + enabled=True, + apicode="apicode0", + customimage=False, + voltage=battery_level, + ), + network=Network(ip=""), + wifi=Wifi(SSID="", linkquality="", signal=""), + ) + + +@patch("homeassistant.components.gogogate2.common.GogoGate2Api") +async def test_sensor_update(gogogate2api_mock, hass: HomeAssistant) -> None: + """Test data update.""" + + api = MagicMock(GogoGate2Api) + api.async_activate.return_value = GogoGate2ActivateResponse(result=True) + api.async_info.return_value = _mocked_gogogate_sensor_response(25) + gogogate2api_mock.return_value = api + + config_entry = MockConfigEntry( + domain=DOMAIN, + source=SOURCE_USER, + data={ + CONF_IP_ADDRESS: "127.0.0.1", + CONF_USERNAME: "admin", + CONF_PASSWORD: "password", + }, + ) + config_entry.add_to_hass(hass) + + assert hass.states.get("cover.door1") is None + assert hass.states.get("cover.door2") is None + assert hass.states.get("cover.door2") is None + assert hass.states.get("sensor.door1_battery") is None + assert hass.states.get("sensor.door2_battery") is None + assert hass.states.get("sensor.door2_battery") is None + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert hass.states.get("cover.door1") + assert hass.states.get("cover.door2") + assert hass.states.get("cover.door2") + assert hass.states.get("sensor.door1_battery").state == "25" + assert hass.states.get("sensor.door2_battery") is None + assert hass.states.get("sensor.door2_battery") is None + + api.async_info.return_value = _mocked_gogogate_sensor_response(40) + async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) + await hass.async_block_till_done() + assert hass.states.get("sensor.door1_battery").state == "40" + + api.async_info.return_value = _mocked_gogogate_sensor_response(None) + async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) + await hass.async_block_till_done() + assert hass.states.get("sensor.door1_battery").state == STATE_UNKNOWN + + assert await hass.config_entries.async_unload(config_entry.entry_id) + assert not hass.states.async_entity_ids(DOMAIN) + + +@patch("homeassistant.components.gogogate2.common.ISmartGateApi") +async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None: + """Test availability.""" + sensor_response = _mocked_ismartgate_sensor_response(35) + api = MagicMock(ISmartGateApi) + api.async_info.return_value = sensor_response + ismartgateapi_mock.return_value = api + + config_entry = MockConfigEntry( + domain=DOMAIN, + source=SOURCE_USER, + data={ + CONF_DEVICE: DEVICE_TYPE_ISMARTGATE, + CONF_IP_ADDRESS: "127.0.0.1", + CONF_USERNAME: "admin", + CONF_PASSWORD: "password", + }, + ) + config_entry.add_to_hass(hass) + + assert hass.states.get("cover.door1") is None + assert hass.states.get("cover.door2") is None + assert hass.states.get("cover.door2") is None + assert hass.states.get("sensor.door1_battery") is None + assert hass.states.get("sensor.door2_battery") is None + assert hass.states.get("sensor.door2_battery") is None + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert hass.states.get("cover.door1") + assert hass.states.get("cover.door2") + assert hass.states.get("cover.door2") + assert hass.states.get("sensor.door1_battery").state == "35" + assert hass.states.get("sensor.door2_battery") is None + assert hass.states.get("sensor.door2_battery") is None + assert ( + hass.states.get("sensor.door1_battery").attributes[ATTR_DEVICE_CLASS] + == DEVICE_CLASS_BATTERY + ) + + api.async_info.side_effect = Exception("Error") + + async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) + await hass.async_block_till_done() + assert hass.states.get("sensor.door1_battery").state == STATE_UNAVAILABLE + + api.async_info.side_effect = None + api.async_info.return_value = sensor_response + async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) + await hass.async_block_till_done() + assert hass.states.get("sensor.door1_battery").state == "35" From 38a2f196b8f28d7c9e9921b74f47c223d34d2bca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Mar 2021 01:32:24 -0600 Subject: [PATCH 114/831] Fix typing on fan percentage (#47259) --- homeassistant/components/comfoconnect/fan.py | 3 ++- homeassistant/components/demo/fan.py | 4 ++-- homeassistant/components/esphome/fan.py | 2 +- homeassistant/components/insteon/fan.py | 2 +- homeassistant/components/isy994/fan.py | 6 +++--- homeassistant/components/lutron_caseta/fan.py | 3 ++- homeassistant/components/smartthings/fan.py | 2 +- homeassistant/components/wemo/fan.py | 2 +- homeassistant/components/wilight/fan.py | 4 +++- 9 files changed, 16 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/comfoconnect/fan.py b/homeassistant/components/comfoconnect/fan.py index 26abd85522a..d248bf74ac4 100644 --- a/homeassistant/components/comfoconnect/fan.py +++ b/homeassistant/components/comfoconnect/fan.py @@ -1,6 +1,7 @@ """Platform to control a Zehnder ComfoAir Q350/450/600 ventilation unit.""" import logging import math +from typing import Optional from pycomfoconnect import ( CMD_FAN_MODE_AWAY, @@ -95,7 +96,7 @@ class ComfoConnectFan(FanEntity): return SUPPORT_SET_SPEED @property - def percentage(self) -> str: + def percentage(self) -> Optional[int]: """Return the current speed percentage.""" speed = self._ccb.data.get(SENSOR_FAN_SPEED_MODE) if speed is None: diff --git a/homeassistant/components/demo/fan.py b/homeassistant/components/demo/fan.py index c79b53c0918..77d6f39a794 100644 --- a/homeassistant/components/demo/fan.py +++ b/homeassistant/components/demo/fan.py @@ -211,7 +211,7 @@ class DemoPercentageFan(BaseDemoFan, FanEntity): """A demonstration fan component that uses percentages.""" @property - def percentage(self) -> str: + def percentage(self) -> Optional[int]: """Return the current speed.""" return self._percentage @@ -271,7 +271,7 @@ class AsyncDemoPercentageFan(BaseDemoFan, FanEntity): """An async demonstration fan component that uses percentages.""" @property - def percentage(self) -> str: + def percentage(self) -> Optional[int]: """Return the current speed.""" return self._percentage diff --git a/homeassistant/components/esphome/fan.py b/homeassistant/components/esphome/fan.py index df23f37cb63..8cf28d6b2aa 100644 --- a/homeassistant/components/esphome/fan.py +++ b/homeassistant/components/esphome/fan.py @@ -111,7 +111,7 @@ class EsphomeFan(EsphomeEntity, FanEntity): return self._state.state @esphome_state_property - def percentage(self) -> Optional[str]: + def percentage(self) -> Optional[int]: """Return the current speed percentage.""" if not self._static_info.supports_speed: return None diff --git a/homeassistant/components/insteon/fan.py b/homeassistant/components/insteon/fan.py index a641d353450..cef19f57a9f 100644 --- a/homeassistant/components/insteon/fan.py +++ b/homeassistant/components/insteon/fan.py @@ -41,7 +41,7 @@ class InsteonFanEntity(InsteonEntity, FanEntity): """An INSTEON fan entity.""" @property - def percentage(self) -> str: + def percentage(self) -> int: """Return the current speed percentage.""" if self._insteon_device_group.value is None: return None diff --git a/homeassistant/components/isy994/fan.py b/homeassistant/components/isy994/fan.py index f565383f007..43323cc5546 100644 --- a/homeassistant/components/isy994/fan.py +++ b/homeassistant/components/isy994/fan.py @@ -1,6 +1,6 @@ """Support for ISY994 fans.""" import math -from typing import Callable +from typing import Callable, Optional from pyisy.constants import ISY_VALUE_UNKNOWN, PROTO_INSTEON @@ -43,7 +43,7 @@ class ISYFanEntity(ISYNodeEntity, FanEntity): """Representation of an ISY994 fan device.""" @property - def percentage(self) -> str: + def percentage(self) -> Optional[int]: """Return the current speed percentage.""" if self._node.status == ISY_VALUE_UNKNOWN: return None @@ -97,7 +97,7 @@ class ISYFanProgramEntity(ISYProgramEntity, FanEntity): """Representation of an ISY994 fan program.""" @property - def percentage(self) -> str: + def percentage(self) -> Optional[int]: """Return the current speed percentage.""" if self._node.status == ISY_VALUE_UNKNOWN: return None diff --git a/homeassistant/components/lutron_caseta/fan.py b/homeassistant/components/lutron_caseta/fan.py index edda379aedc..55799315ba0 100644 --- a/homeassistant/components/lutron_caseta/fan.py +++ b/homeassistant/components/lutron_caseta/fan.py @@ -1,5 +1,6 @@ """Support for Lutron Caseta fans.""" import logging +from typing import Optional from pylutron_caseta import FAN_HIGH, FAN_LOW, FAN_MEDIUM, FAN_MEDIUM_HIGH, FAN_OFF @@ -42,7 +43,7 @@ class LutronCasetaFan(LutronCasetaDevice, FanEntity): """Representation of a Lutron Caseta fan. Including Fan Speed.""" @property - def percentage(self) -> str: + def percentage(self) -> Optional[int]: """Return the current speed percentage.""" if self._device["fan_speed"] is None: return None diff --git a/homeassistant/components/smartthings/fan.py b/homeassistant/components/smartthings/fan.py index 4cd451e2416..ec133a1f6aa 100644 --- a/homeassistant/components/smartthings/fan.py +++ b/homeassistant/components/smartthings/fan.py @@ -76,7 +76,7 @@ class SmartThingsFan(SmartThingsEntity, FanEntity): return self._device.status.switch @property - def percentage(self) -> str: + def percentage(self) -> int: """Return the current speed percentage.""" return ranged_value_to_percentage(SPEED_RANGE, self._device.status.fan_speed) diff --git a/homeassistant/components/wemo/fan.py b/homeassistant/components/wemo/fan.py index 1f45194659d..d8f54057557 100644 --- a/homeassistant/components/wemo/fan.py +++ b/homeassistant/components/wemo/fan.py @@ -127,7 +127,7 @@ class WemoHumidifier(WemoSubscriptionEntity, FanEntity): } @property - def percentage(self) -> str: + def percentage(self) -> int: """Return the current speed percentage.""" return ranged_value_to_percentage(SPEED_RANGE, self._fan_mode) diff --git a/homeassistant/components/wilight/fan.py b/homeassistant/components/wilight/fan.py index d663dc39ded..35727b19927 100644 --- a/homeassistant/components/wilight/fan.py +++ b/homeassistant/components/wilight/fan.py @@ -1,5 +1,7 @@ """Support for WiLight Fan.""" +from typing import Optional + from pywilight.const import ( FAN_V1, ITEM_FAN, @@ -77,7 +79,7 @@ class WiLightFan(WiLightDevice, FanEntity): return self._status.get("direction", WL_DIRECTION_OFF) != WL_DIRECTION_OFF @property - def percentage(self) -> str: + def percentage(self) -> Optional[int]: """Return the current speed percentage.""" if "direction" in self._status: if self._status["direction"] == WL_DIRECTION_OFF: From dc880118a40f91be2172e6b07d2597b701555e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Tue, 2 Mar 2021 10:02:04 +0200 Subject: [PATCH 115/831] Lint suppression cleanups (#47248) * Unused pylint suppression cleanups * Remove outdated pylint bug references * Add flake8-noqa config and note to run it every now and then * Add codes to noqa's * Unused noqa cleanups --- .pre-commit-config.yaml | 3 +++ homeassistant/auth/permissions/models.py | 3 +-- homeassistant/components/automation/__init__.py | 2 +- homeassistant/components/avion/light.py | 2 -- homeassistant/components/awair/config_flow.py | 2 +- homeassistant/components/bbb_gpio/__init__.py | 1 - homeassistant/components/bh1750/sensor.py | 2 +- homeassistant/components/blinkt/light.py | 1 - homeassistant/components/blueprint/__init__.py | 8 ++++---- .../bluetooth_le_tracker/device_tracker.py | 2 +- .../components/bluetooth_tracker/device_tracker.py | 3 +-- homeassistant/components/bme280/sensor.py | 2 +- homeassistant/components/bme680/sensor.py | 3 +-- homeassistant/components/braviatv/config_flow.py | 2 +- homeassistant/components/cert_expiry/helper.py | 3 +-- homeassistant/components/cmus/media_player.py | 1 - homeassistant/components/decora/light.py | 6 ++---- .../components/device_tracker/__init__.py | 14 ++++---------- .../components/environment_canada/camera.py | 2 +- .../components/environment_canada/sensor.py | 2 +- .../components/environment_canada/weather.py | 2 +- homeassistant/components/eq3btsmart/climate.py | 2 +- homeassistant/components/fan/__init__.py | 1 - homeassistant/components/flume/sensor.py | 2 +- homeassistant/components/foscam/config_flow.py | 3 +-- homeassistant/components/fritzbox/config_flow.py | 1 - homeassistant/components/gc100/__init__.py | 1 - homeassistant/components/geniushub/__init__.py | 1 - homeassistant/components/google/calendar.py | 2 +- homeassistant/components/google_pubsub/__init__.py | 4 +--- homeassistant/components/habitica/__init__.py | 2 +- homeassistant/components/hassio/config_flow.py | 2 +- homeassistant/components/hdmi_cec/__init__.py | 14 +++++--------- homeassistant/components/hdmi_cec/media_player.py | 8 ++------ .../components/here_travel_time/sensor.py | 3 --- homeassistant/components/homekit/__init__.py | 1 - homeassistant/components/homekit/config_flow.py | 2 +- homeassistant/components/htu21d/sensor.py | 2 +- .../components/hvv_departures/binary_sensor.py | 2 +- .../components/hvv_departures/config_flow.py | 7 +------ homeassistant/components/hyperion/config_flow.py | 1 - homeassistant/components/hyperion/light.py | 1 - homeassistant/components/hyperion/switch.py | 2 -- homeassistant/components/icloud/config_flow.py | 2 +- homeassistant/components/knx/const.py | 2 -- homeassistant/components/lcn/__init__.py | 2 +- homeassistant/components/lirc/__init__.py | 2 +- homeassistant/components/lovelace/__init__.py | 2 +- homeassistant/components/mcp23017/binary_sensor.py | 8 ++++---- homeassistant/components/mcp23017/switch.py | 8 ++++---- homeassistant/components/meteo_france/sensor.py | 1 - homeassistant/components/miflora/sensor.py | 2 +- homeassistant/components/mjpeg/camera.py | 2 -- homeassistant/components/mqtt/__init__.py | 4 ++-- homeassistant/components/mqtt/binary_sensor.py | 1 - homeassistant/components/mqtt/sensor.py | 1 - homeassistant/components/mullvad/binary_sensor.py | 2 +- homeassistant/components/mullvad/config_flow.py | 2 +- homeassistant/components/mysensors/config_flow.py | 2 -- homeassistant/components/neato/config_flow.py | 1 - homeassistant/components/noaa_tides/sensor.py | 2 +- homeassistant/components/omnilogic/config_flow.py | 2 +- homeassistant/components/onewire/config_flow.py | 2 +- homeassistant/components/owntracks/config_flow.py | 2 +- homeassistant/components/ozw/config_flow.py | 3 +-- .../components/pi4ioe5v9xxxx/binary_sensor.py | 2 +- homeassistant/components/pi4ioe5v9xxxx/switch.py | 2 +- homeassistant/components/plex/config_flow.py | 2 +- homeassistant/components/plex/server.py | 2 +- homeassistant/components/ps4/__init__.py | 2 +- homeassistant/components/rest/__init__.py | 2 +- homeassistant/components/rpi_rf/switch.py | 1 - .../components/sharkiq/update_coordinator.py | 2 +- homeassistant/components/sms/config_flow.py | 2 +- homeassistant/components/sms/gateway.py | 8 +++----- homeassistant/components/sms/notify.py | 8 ++++---- homeassistant/components/sms/sensor.py | 4 ++-- .../components/somfy_mylink/config_flow.py | 2 +- .../components/speedtestdotnet/config_flow.py | 2 +- homeassistant/components/switchbot/switch.py | 2 +- homeassistant/components/switchmate/switch.py | 2 +- homeassistant/components/syncthru/config_flow.py | 1 - homeassistant/components/tasmota/config_flow.py | 6 +----- .../components/tensorflow/image_processing.py | 3 +-- homeassistant/components/tuya/config_flow.py | 2 +- homeassistant/components/updater/__init__.py | 2 +- homeassistant/components/vera/config_flow.py | 6 +----- homeassistant/components/websocket_api/__init__.py | 10 +++++----- homeassistant/components/websocket_api/const.py | 2 +- homeassistant/components/xbox/media_source.py | 3 +-- .../components/xiaomi_miio/device_tracker.py | 2 +- homeassistant/components/xiaomi_miio/fan.py | 12 ++++++------ homeassistant/components/xiaomi_miio/light.py | 10 ++++++++-- homeassistant/components/xiaomi_miio/remote.py | 2 +- homeassistant/components/xiaomi_miio/sensor.py | 3 +-- homeassistant/components/xiaomi_miio/switch.py | 5 ++--- homeassistant/components/xiaomi_miio/vacuum.py | 2 +- homeassistant/components/zha/__init__.py | 2 +- .../components/zha/core/channels/__init__.py | 2 +- homeassistant/components/zha/core/store.py | 1 - homeassistant/components/zone/config_flow.py | 2 +- homeassistant/components/zwave/__init__.py | 2 -- homeassistant/components/zwave/node_entity.py | 1 - homeassistant/components/zwave_js/discovery.py | 1 - homeassistant/config_entries.py | 5 ++--- homeassistant/core.py | 1 - homeassistant/data_entry_flow.py | 3 +-- homeassistant/exceptions.py | 2 +- homeassistant/helpers/entity_platform.py | 2 +- homeassistant/helpers/entity_registry.py | 2 +- homeassistant/helpers/service.py | 2 +- homeassistant/helpers/sun.py | 2 +- homeassistant/helpers/template.py | 2 +- homeassistant/util/color.py | 3 ++- homeassistant/util/ssl.py | 4 ++-- setup.cfg | 1 + tests/common.py | 2 +- tests/components/abode/conftest.py | 2 +- .../alarm_control_panel/test_device_action.py | 2 +- .../alarm_control_panel/test_device_condition.py | 2 +- .../alarm_control_panel/test_device_trigger.py | 2 +- tests/components/arcam_fmj/test_device_trigger.py | 2 +- tests/components/automation/conftest.py | 2 +- tests/components/axis/conftest.py | 2 +- .../binary_sensor/test_device_condition.py | 2 +- .../binary_sensor/test_device_trigger.py | 2 +- tests/components/blebox/conftest.py | 2 +- tests/components/bond/conftest.py | 2 +- tests/components/climate/test_device_action.py | 2 +- tests/components/climate/test_device_condition.py | 2 +- tests/components/climate/test_device_trigger.py | 2 +- tests/components/config/test_automation.py | 2 +- tests/components/config/test_device_registry.py | 2 +- tests/components/cover/test_device_action.py | 2 +- tests/components/cover/test_device_condition.py | 2 +- tests/components/cover/test_device_trigger.py | 2 +- tests/components/deconz/conftest.py | 2 +- tests/components/deconz/test_device_trigger.py | 2 +- tests/components/default_config/test_init.py | 2 +- tests/components/demo/conftest.py | 2 +- tests/components/device_automation/test_init.py | 2 +- .../device_tracker/test_device_condition.py | 2 +- .../device_tracker/test_device_trigger.py | 2 +- tests/components/dynalite/conftest.py | 2 +- tests/components/elgato/conftest.py | 2 +- tests/components/everlights/conftest.py | 2 +- tests/components/fan/test_device_action.py | 2 +- tests/components/fan/test_device_condition.py | 2 +- tests/components/fan/test_device_trigger.py | 2 +- tests/components/geo_location/test_trigger.py | 2 +- tests/components/google_assistant/test_helpers.py | 2 +- tests/components/group/conftest.py | 2 +- tests/components/hassio/test_websocket_api.py | 2 +- .../components/homeassistant/triggers/conftest.py | 2 +- tests/components/homekit_controller/conftest.py | 2 +- .../homekit_controller/test_device_trigger.py | 2 +- tests/components/homematicip_cloud/conftest.py | 2 +- tests/components/hue/conftest.py | 2 +- tests/components/hue/test_device_trigger.py | 2 +- tests/components/humidifier/test_device_action.py | 2 +- .../components/humidifier/test_device_condition.py | 2 +- tests/components/humidifier/test_device_trigger.py | 2 +- tests/components/hyperion/conftest.py | 2 +- tests/components/kodi/test_device_trigger.py | 2 +- tests/components/light/test_device_action.py | 2 +- tests/components/light/test_device_condition.py | 2 +- tests/components/light/test_device_trigger.py | 2 +- tests/components/litejet/test_trigger.py | 2 +- tests/components/lock/test_device_action.py | 2 +- tests/components/lock/test_device_condition.py | 2 +- tests/components/lock/test_device_trigger.py | 2 +- .../media_player/test_device_condition.py | 2 +- .../components/media_player/test_device_trigger.py | 2 +- tests/components/mochad/conftest.py | 2 +- tests/components/mqtt/conftest.py | 2 +- tests/components/mqtt/test_device_trigger.py | 2 +- tests/components/mqtt/test_trigger.py | 2 +- tests/components/number/test_device_action.py | 2 +- tests/components/ozw/conftest.py | 2 +- tests/components/philips_js/test_device_trigger.py | 2 +- tests/components/remote/test_device_action.py | 2 +- tests/components/remote/test_device_condition.py | 2 +- tests/components/remote/test_device_trigger.py | 2 +- tests/components/rflink/conftest.py | 2 +- tests/components/rfxtrx/conftest.py | 2 +- tests/components/ring/conftest.py | 2 +- tests/components/search/test_init.py | 2 +- tests/components/sensor/test_device_condition.py | 2 +- tests/components/sensor/test_device_trigger.py | 2 +- tests/components/smartthings/conftest.py | 2 +- tests/components/sun/test_trigger.py | 2 +- tests/components/switch/conftest.py | 2 +- tests/components/switch/test_device_action.py | 2 +- tests/components/switch/test_device_condition.py | 2 +- tests/components/switch/test_device_trigger.py | 2 +- tests/components/tag/test_trigger.py | 2 +- tests/components/tasmota/conftest.py | 2 +- tests/components/tasmota/test_device_trigger.py | 2 +- tests/components/template/conftest.py | 2 +- tests/components/template/test_trigger.py | 2 +- tests/components/tplink/conftest.py | 2 +- tests/components/tradfri/conftest.py | 2 +- tests/components/vacuum/test_device_action.py | 2 +- tests/components/vacuum/test_device_condition.py | 2 +- tests/components/vacuum/test_device_trigger.py | 2 +- tests/components/vera/conftest.py | 2 +- .../components/water_heater/test_device_action.py | 2 +- tests/components/webhook/test_trigger.py | 2 +- tests/components/wilight/conftest.py | 2 +- tests/components/wled/conftest.py | 2 +- tests/components/yeelight/conftest.py | 2 +- tests/components/zerproc/conftest.py | 2 +- tests/components/zha/conftest.py | 2 +- tests/components/zha/test_device_action.py | 2 +- tests/components/zha/test_device_trigger.py | 2 +- tests/components/zone/test_trigger.py | 2 +- tests/components/zwave/conftest.py | 2 +- 217 files changed, 237 insertions(+), 306 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6b650ebac79..8e2fb5cb3b3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,6 +28,9 @@ repos: - id: flake8 additional_dependencies: - flake8-docstrings==1.5.0 + # Temporarily every now and then for noqa cleanup; not done by + # default yet due to https://github.com/plinss/flake8-noqa/issues/1 + # - flake8-noqa==1.1.0 - pydocstyle==5.1.1 files: ^(homeassistant|script|tests)/.+\.py$ - repo: https://github.com/PyCQA/bandit diff --git a/homeassistant/auth/permissions/models.py b/homeassistant/auth/permissions/models.py index 2542be14cc6..b2d1955865a 100644 --- a/homeassistant/auth/permissions/models.py +++ b/homeassistant/auth/permissions/models.py @@ -4,8 +4,7 @@ from typing import TYPE_CHECKING import attr if TYPE_CHECKING: - # pylint: disable=unused-import - from homeassistant.helpers import ( # noqa: F401 + from homeassistant.helpers import ( device_registry as dev_reg, entity_registry as ent_reg, ) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 7f006d929b1..1b1a927d147 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -59,7 +59,7 @@ from homeassistant.loader import bind_hass from homeassistant.util.dt import parse_datetime # Not used except by packages to check config structure -from .config import PLATFORM_SCHEMA # noqa +from .config import PLATFORM_SCHEMA # noqa: F401 from .config import async_validate_config_item from .const import ( CONF_ACTION, diff --git a/homeassistant/components/avion/light.py b/homeassistant/components/avion/light.py index 0af2f15b34e..0d242b952dd 100644 --- a/homeassistant/components/avion/light.py +++ b/homeassistant/components/avion/light.py @@ -41,7 +41,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( def setup_platform(hass, config, add_entities, discovery_info=None): """Set up an Avion switch.""" - # pylint: disable=no-member avion = importlib.import_module("avion") lights = [] @@ -111,7 +110,6 @@ class AvionLight(LightEntity): def set_state(self, brightness): """Set the state of this lamp to the provided brightness.""" - # pylint: disable=no-member avion = importlib.import_module("avion") # Bluetooth LE is unreliable, and the connection may drop at any diff --git a/homeassistant/components/awair/config_flow.py b/homeassistant/components/awair/config_flow.py index a4768014f96..c28ac55f216 100644 --- a/homeassistant/components/awair/config_flow.py +++ b/homeassistant/components/awair/config_flow.py @@ -10,7 +10,7 @@ from homeassistant.config_entries import CONN_CLASS_CLOUD_POLL, ConfigFlow from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.helpers.aiohttp_client import async_get_clientsession -from .const import DOMAIN, LOGGER # pylint: disable=unused-import +from .const import DOMAIN, LOGGER class AwairFlowHandler(ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/bbb_gpio/__init__.py b/homeassistant/components/bbb_gpio/__init__.py index 61d98c7413e..f7d146e073e 100644 --- a/homeassistant/components/bbb_gpio/__init__.py +++ b/homeassistant/components/bbb_gpio/__init__.py @@ -8,7 +8,6 @@ DOMAIN = "bbb_gpio" def setup(hass, config): """Set up the BeagleBone Black GPIO component.""" - # pylint: disable=import-error def cleanup_gpio(event): """Stuff to do before stopping.""" diff --git a/homeassistant/components/bh1750/sensor.py b/homeassistant/components/bh1750/sensor.py index 7680b8b09ad..eb9bbc2dccc 100644 --- a/homeassistant/components/bh1750/sensor.py +++ b/homeassistant/components/bh1750/sensor.py @@ -3,7 +3,7 @@ from functools import partial import logging from i2csense.bh1750 import BH1750 # pylint: disable=import-error -import smbus # pylint: disable=import-error +import smbus import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA diff --git a/homeassistant/components/blinkt/light.py b/homeassistant/components/blinkt/light.py index 0d1aff7b826..bb9bbf315e4 100644 --- a/homeassistant/components/blinkt/light.py +++ b/homeassistant/components/blinkt/light.py @@ -26,7 +26,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Blinkt Light platform.""" - # pylint: disable=no-member blinkt = importlib.import_module("blinkt") # ensure that the lights are off when exiting diff --git a/homeassistant/components/blueprint/__init__.py b/homeassistant/components/blueprint/__init__.py index 9e8b1260eff..309365710ad 100644 --- a/homeassistant/components/blueprint/__init__.py +++ b/homeassistant/components/blueprint/__init__.py @@ -1,7 +1,7 @@ """The blueprint integration.""" from . import websocket_api -from .const import DOMAIN # noqa -from .errors import ( # noqa +from .const import DOMAIN # noqa: F401 +from .errors import ( # noqa: F401 BlueprintException, BlueprintWithNameException, FailedToLoad, @@ -9,8 +9,8 @@ from .errors import ( # noqa InvalidBlueprintInputs, MissingInput, ) -from .models import Blueprint, BlueprintInputs, DomainBlueprints # noqa -from .schemas import is_blueprint_instance_config # noqa +from .models import Blueprint, BlueprintInputs, DomainBlueprints # noqa: F401 +from .schemas import is_blueprint_instance_config # noqa: F401 async def async_setup(hass, config): diff --git a/homeassistant/components/bluetooth_le_tracker/device_tracker.py b/homeassistant/components/bluetooth_le_tracker/device_tracker.py index 76df4e65ac7..9ac79afde2c 100644 --- a/homeassistant/components/bluetooth_le_tracker/device_tracker.py +++ b/homeassistant/components/bluetooth_le_tracker/device_tracker.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta import logging from uuid import UUID -import pygatt # pylint: disable=import-error +import pygatt import voluptuous as vol from homeassistant.components.device_tracker import PLATFORM_SCHEMA diff --git a/homeassistant/components/bluetooth_tracker/device_tracker.py b/homeassistant/components/bluetooth_tracker/device_tracker.py index 380d8091bd6..9bc2e630ba0 100644 --- a/homeassistant/components/bluetooth_tracker/device_tracker.py +++ b/homeassistant/components/bluetooth_tracker/device_tracker.py @@ -3,8 +3,7 @@ import asyncio import logging from typing import List, Optional, Set, Tuple -# pylint: disable=import-error -import bluetooth +import bluetooth # pylint: disable=import-error from bt_proximity import BluetoothRSSI import voluptuous as vol diff --git a/homeassistant/components/bme280/sensor.py b/homeassistant/components/bme280/sensor.py index 265ec01b6db..d3d97881035 100644 --- a/homeassistant/components/bme280/sensor.py +++ b/homeassistant/components/bme280/sensor.py @@ -4,7 +4,7 @@ from functools import partial import logging from i2csense.bme280 import BME280 # pylint: disable=import-error -import smbus # pylint: disable=import-error +import smbus import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA diff --git a/homeassistant/components/bme680/sensor.py b/homeassistant/components/bme680/sensor.py index 49d95bbc53b..6af9d46c0bc 100644 --- a/homeassistant/components/bme680/sensor.py +++ b/homeassistant/components/bme680/sensor.py @@ -4,7 +4,7 @@ import threading from time import monotonic, sleep import bme680 # pylint: disable=import-error -from smbus import SMBus # pylint: disable=import-error +from smbus import SMBus import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA @@ -131,7 +131,6 @@ def _setup_bme680(config): sensor_handler = None sensor = None try: - # pylint: disable=no-member i2c_address = config[CONF_I2C_ADDRESS] bus = SMBus(config[CONF_I2C_BUS]) sensor = bme680.BME680(i2c_address, bus) diff --git a/homeassistant/components/braviatv/config_flow.py b/homeassistant/components/braviatv/config_flow.py index d8831dd1494..1ac31972f33 100644 --- a/homeassistant/components/braviatv/config_flow.py +++ b/homeassistant/components/braviatv/config_flow.py @@ -12,7 +12,7 @@ from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PIN from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from .const import ( # pylint:disable=unused-import +from .const import ( ATTR_CID, ATTR_MAC, ATTR_MODEL, diff --git a/homeassistant/components/cert_expiry/helper.py b/homeassistant/components/cert_expiry/helper.py index 6c49e9e26b9..c00a99c8e86 100644 --- a/homeassistant/components/cert_expiry/helper.py +++ b/homeassistant/components/cert_expiry/helper.py @@ -19,8 +19,7 @@ def get_cert(host, port): address = (host, port) with socket.create_connection(address, timeout=TIMEOUT) as sock: with ctx.wrap_socket(sock, server_hostname=address[0]) as ssock: - # pylint disable: https://github.com/PyCQA/pylint/issues/3166 - cert = ssock.getpeercert() # pylint: disable=no-member + cert = ssock.getpeercert() return cert diff --git a/homeassistant/components/cmus/media_player.py b/homeassistant/components/cmus/media_player.py index 49c10ab92a5..3968ebbe9d7 100644 --- a/homeassistant/components/cmus/media_player.py +++ b/homeassistant/components/cmus/media_player.py @@ -98,7 +98,6 @@ class CmusRemote: class CmusDevice(MediaPlayerEntity): """Representation of a running cmus.""" - # pylint: disable=no-member def __init__(self, device, name, server): """Initialize the CMUS device.""" diff --git a/homeassistant/components/decora/light.py b/homeassistant/components/decora/light.py index 2fc436eda20..96126bfcc98 100644 --- a/homeassistant/components/decora/light.py +++ b/homeassistant/components/decora/light.py @@ -4,10 +4,8 @@ from functools import wraps import logging import time -from bluepy.btle import ( # pylint: disable=import-error, no-member, no-name-in-module - BTLEException, -) -import decora # pylint: disable=import-error, no-member +from bluepy.btle import BTLEException # pylint: disable=import-error +import decora # pylint: disable=import-error import voluptuous as vol from homeassistant.components.light import ( diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index d785ee826e8..688213bbcb3 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -1,16 +1,10 @@ """Provide functionality to keep track of devices.""" -from homeassistant.const import ( # noqa: F401 pylint: disable=unused-import - ATTR_GPS_ACCURACY, - STATE_HOME, -) +from homeassistant.const import ATTR_GPS_ACCURACY, STATE_HOME # noqa: F401 from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.loader import bind_hass -from .config_entry import ( # noqa: F401 pylint: disable=unused-import - async_setup_entry, - async_unload_entry, -) -from .const import ( # noqa: F401 pylint: disable=unused-import +from .config_entry import async_setup_entry, async_unload_entry # noqa: F401 +from .const import ( # noqa: F401 ATTR_ATTRIBUTES, ATTR_BATTERY, ATTR_DEV_ID, @@ -29,7 +23,7 @@ from .const import ( # noqa: F401 pylint: disable=unused-import SOURCE_TYPE_GPS, SOURCE_TYPE_ROUTER, ) -from .legacy import ( # noqa: F401 pylint: disable=unused-import +from .legacy import ( # noqa: F401 PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, SERVICE_SEE, diff --git a/homeassistant/components/environment_canada/camera.py b/homeassistant/components/environment_canada/camera.py index 66079ac73ff..7021d637cfd 100644 --- a/homeassistant/components/environment_canada/camera.py +++ b/homeassistant/components/environment_canada/camera.py @@ -1,7 +1,7 @@ """Support for the Environment Canada radar imagery.""" import datetime -from env_canada import ECRadar # pylint: disable=import-error +from env_canada import ECRadar import voluptuous as vol from homeassistant.components.camera import PLATFORM_SCHEMA, Camera diff --git a/homeassistant/components/environment_canada/sensor.py b/homeassistant/components/environment_canada/sensor.py index a8772909f68..20eb6fac9ee 100644 --- a/homeassistant/components/environment_canada/sensor.py +++ b/homeassistant/components/environment_canada/sensor.py @@ -3,7 +3,7 @@ from datetime import datetime, timedelta import logging import re -from env_canada import ECData # pylint: disable=import-error +from env_canada import ECData import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA diff --git a/homeassistant/components/environment_canada/weather.py b/homeassistant/components/environment_canada/weather.py index dd2252a585f..9abbc33bc93 100644 --- a/homeassistant/components/environment_canada/weather.py +++ b/homeassistant/components/environment_canada/weather.py @@ -2,7 +2,7 @@ import datetime import re -from env_canada import ECData # pylint: disable=import-error +from env_canada import ECData import voluptuous as vol from homeassistant.components.weather import ( diff --git a/homeassistant/components/eq3btsmart/climate.py b/homeassistant/components/eq3btsmart/climate.py index 737b3fe357a..43c6bd9f35b 100644 --- a/homeassistant/components/eq3btsmart/climate.py +++ b/homeassistant/components/eq3btsmart/climate.py @@ -1,7 +1,7 @@ """Support for eQ-3 Bluetooth Smart thermostats.""" import logging -from bluepy.btle import BTLEException # pylint: disable=import-error, no-name-in-module +from bluepy.btle import BTLEException # pylint: disable=import-error import eq3bt as eq3 # pylint: disable=import-error import voluptuous as vol diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 18f46b3d619..25911eb2d06 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -344,7 +344,6 @@ class FanEntity(ToggleEntity): """Turn on the fan.""" raise NotImplementedError() - # pylint: disable=arguments-differ async def async_turn_on_compat( self, speed: Optional[str] = None, diff --git a/homeassistant/components/flume/sensor.py b/homeassistant/components/flume/sensor.py index 6b19c9c5476..65584239910 100644 --- a/homeassistant/components/flume/sensor.py +++ b/homeassistant/components/flume/sensor.py @@ -175,7 +175,7 @@ def _create_flume_device_coordinator(hass, flume_device): _LOGGER.debug("Updating Flume data") try: await hass.async_add_executor_job(flume_device.update_force) - except Exception as ex: # pylint: disable=broad-except + except Exception as ex: raise UpdateFailed(f"Error communicating with flume API: {ex}") from ex _LOGGER.debug( "Flume update details: %s", diff --git a/homeassistant/components/foscam/config_flow.py b/homeassistant/components/foscam/config_flow.py index bfeefb9e406..bfd13c730f8 100644 --- a/homeassistant/components/foscam/config_flow.py +++ b/homeassistant/components/foscam/config_flow.py @@ -17,8 +17,7 @@ from homeassistant.const import ( ) from homeassistant.data_entry_flow import AbortFlow -from .const import CONF_RTSP_PORT, CONF_STREAM, LOGGER -from .const import DOMAIN # pylint:disable=unused-import +from .const import CONF_RTSP_PORT, CONF_STREAM, DOMAIN, LOGGER STREAMS = ["Main", "Sub"] diff --git a/homeassistant/components/fritzbox/config_flow.py b/homeassistant/components/fritzbox/config_flow.py index 904081ef99f..a462f885484 100644 --- a/homeassistant/components/fritzbox/config_flow.py +++ b/homeassistant/components/fritzbox/config_flow.py @@ -13,7 +13,6 @@ from homeassistant.components.ssdp import ( ) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -# pylint:disable=unused-import from .const import DEFAULT_HOST, DEFAULT_USERNAME, DOMAIN DATA_SCHEMA_USER = vol.Schema( diff --git a/homeassistant/components/gc100/__init__.py b/homeassistant/components/gc100/__init__.py index 77380afc2b5..51abd9d5693 100644 --- a/homeassistant/components/gc100/__init__.py +++ b/homeassistant/components/gc100/__init__.py @@ -25,7 +25,6 @@ CONFIG_SCHEMA = vol.Schema( ) -# pylint: disable=no-member def setup(hass, base_config): """Set up the gc100 component.""" config = base_config[DOMAIN] diff --git a/homeassistant/components/geniushub/__init__.py b/homeassistant/components/geniushub/__init__.py index 909de81521c..def964157e4 100644 --- a/homeassistant/components/geniushub/__init__.py +++ b/homeassistant/components/geniushub/__init__.py @@ -173,7 +173,6 @@ class GeniusBroker: @property def hub_uid(self) -> int: """Return the Hub UID (MAC address).""" - # pylint: disable=no-member return self._hub_uid if self._hub_uid is not None else self.client.uid async def async_update(self, now, **kwargs) -> None: diff --git a/homeassistant/components/google/calendar.py b/homeassistant/components/google/calendar.py index 2fcde78354b..9040d37935d 100644 --- a/homeassistant/components/google/calendar.py +++ b/homeassistant/components/google/calendar.py @@ -3,7 +3,7 @@ import copy from datetime import timedelta import logging -from httplib2 import ServerNotFoundError # pylint: disable=import-error +from httplib2 import ServerNotFoundError from homeassistant.components.calendar import ( ENTITY_ID_FORMAT, diff --git a/homeassistant/components/google_pubsub/__init__.py b/homeassistant/components/google_pubsub/__init__.py index 8d7a675860c..c832c87318c 100644 --- a/homeassistant/components/google_pubsub/__init__.py +++ b/homeassistant/components/google_pubsub/__init__.py @@ -57,9 +57,7 @@ def setup(hass: HomeAssistant, yaml_config: Dict[str, Any]): service_principal_path ) - topic_path = publisher.topic_path( # pylint: disable=no-member - project_id, topic_name - ) + topic_path = publisher.topic_path(project_id, topic_name) encoder = DateTimeJSONEncoder() diff --git a/homeassistant/components/habitica/__init__.py b/homeassistant/components/habitica/__init__.py index 64680a56bb3..c70d374dfe9 100644 --- a/homeassistant/components/habitica/__init__.py +++ b/homeassistant/components/habitica/__init__.py @@ -46,7 +46,7 @@ INSTANCE_SCHEMA = vol.All( ), ) -has_unique_values = vol.Schema(vol.Unique()) # pylint: disable=invalid-name +has_unique_values = vol.Schema(vol.Unique()) # because we want a handy alias diff --git a/homeassistant/components/hassio/config_flow.py b/homeassistant/components/hassio/config_flow.py index 56c7d324a61..8b2c68d752d 100644 --- a/homeassistant/components/hassio/config_flow.py +++ b/homeassistant/components/hassio/config_flow.py @@ -3,7 +3,7 @@ import logging from homeassistant import config_entries -from . import DOMAIN # pylint:disable=unused-import +from . import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/hdmi_cec/__init__.py b/homeassistant/components/hdmi_cec/__init__.py index c272ad19c8d..c9a5d27a3be 100644 --- a/homeassistant/components/hdmi_cec/__init__.py +++ b/homeassistant/components/hdmi_cec/__init__.py @@ -4,13 +4,9 @@ from functools import reduce import logging import multiprocessing -from pycec.cec import CecAdapter # pylint: disable=import-error -from pycec.commands import ( # pylint: disable=import-error - CecCommand, - KeyPressCommand, - KeyReleaseCommand, -) -from pycec.const import ( # pylint: disable=import-error +from pycec.cec import CecAdapter +from pycec.commands import CecCommand, KeyPressCommand, KeyReleaseCommand +from pycec.const import ( ADDR_AUDIOSYSTEM, ADDR_BROADCAST, ADDR_UNREGISTERED, @@ -25,8 +21,8 @@ from pycec.const import ( # pylint: disable=import-error STATUS_STILL, STATUS_STOP, ) -from pycec.network import HDMINetwork, PhysicalAddress # pylint: disable=import-error -from pycec.tcp import TcpAdapter # pylint: disable=import-error +from pycec.network import HDMINetwork, PhysicalAddress +from pycec.tcp import TcpAdapter import voluptuous as vol from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER diff --git a/homeassistant/components/hdmi_cec/media_player.py b/homeassistant/components/hdmi_cec/media_player.py index f81ee20afe3..c3cab6a8f98 100644 --- a/homeassistant/components/hdmi_cec/media_player.py +++ b/homeassistant/components/hdmi_cec/media_player.py @@ -1,12 +1,8 @@ """Support for HDMI CEC devices as media players.""" import logging -from pycec.commands import ( # pylint: disable=import-error - CecCommand, - KeyPressCommand, - KeyReleaseCommand, -) -from pycec.const import ( # pylint: disable=import-error +from pycec.commands import CecCommand, KeyPressCommand, KeyReleaseCommand +from pycec.const import ( KEY_BACKWARD, KEY_FORWARD, KEY_MUTE_TOGGLE, diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index e51e7a067fc..e9506fceed5 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -457,11 +457,9 @@ class HERETravelTimeData: _LOGGER.debug("Raw response is: %s", response.response) - # pylint: disable=no-member source_attribution = response.response.get("sourceAttribution") if source_attribution is not None: self.attribution = self._build_hass_attribution(source_attribution) - # pylint: disable=no-member route = response.response["route"] summary = route[0]["summary"] waypoint = route[0]["waypoint"] @@ -477,7 +475,6 @@ class HERETravelTimeData: else: # Convert to kilometers self.distance = distance / 1000 - # pylint: disable=no-member self.route = response.route_short self.origin_name = waypoint[0]["mappedRoadName"] self.destination_name = waypoint[1]["mappedRoadName"] diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index c042872f4cd..0dfe7336bcf 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -42,7 +42,6 @@ from homeassistant.helpers.reload import async_integration_yaml_config from homeassistant.loader import IntegrationNotFound, async_get_integration from homeassistant.util import get_local_ip -# pylint: disable=unused-import from . import ( # noqa: F401 type_cameras, type_covers, diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index c21c27fba83..2eabdd98e79 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -36,13 +36,13 @@ from .const import ( DEFAULT_AUTO_START, DEFAULT_CONFIG_FLOW_PORT, DEFAULT_HOMEKIT_MODE, + DOMAIN, HOMEKIT_MODE_ACCESSORY, HOMEKIT_MODE_BRIDGE, HOMEKIT_MODES, SHORT_BRIDGE_NAME, VIDEO_CODEC_COPY, ) -from .const import DOMAIN # pylint:disable=unused-import from .util import async_find_next_available_port, state_needs_accessory_mode CONF_CAMERA_COPY = "camera_copy" diff --git a/homeassistant/components/htu21d/sensor.py b/homeassistant/components/htu21d/sensor.py index a9b235faa0b..615177ed6af 100644 --- a/homeassistant/components/htu21d/sensor.py +++ b/homeassistant/components/htu21d/sensor.py @@ -4,7 +4,7 @@ from functools import partial import logging from i2csense.htu21d import HTU21D # pylint: disable=import-error -import smbus # pylint: disable=import-error +import smbus import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA diff --git a/homeassistant/components/hvv_departures/binary_sensor.py b/homeassistant/components/hvv_departures/binary_sensor.py index 7d19fcc8fdf..f625ef7c344 100644 --- a/homeassistant/components/hvv_departures/binary_sensor.py +++ b/homeassistant/components/hvv_departures/binary_sensor.py @@ -92,7 +92,7 @@ async def async_setup_entry(hass, entry, async_add_entities): raise UpdateFailed(f"Authentication failed: {err}") from err except ClientConnectorError as err: raise UpdateFailed(f"Network not available: {err}") from err - except Exception as err: # pylint: disable=broad-except + except Exception as err: raise UpdateFailed(f"Error occurred while fetching data: {err}") from err coordinator = DataUpdateCoordinator( diff --git a/homeassistant/components/hvv_departures/config_flow.py b/homeassistant/components/hvv_departures/config_flow.py index 1a49bffd2f5..556505101eb 100644 --- a/homeassistant/components/hvv_departures/config_flow.py +++ b/homeassistant/components/hvv_departures/config_flow.py @@ -11,12 +11,7 @@ from homeassistant.core import callback from homeassistant.helpers import aiohttp_client import homeassistant.helpers.config_validation as cv -from .const import ( # pylint:disable=unused-import - CONF_FILTER, - CONF_REAL_TIME, - CONF_STATION, - DOMAIN, -) +from .const import CONF_FILTER, CONF_REAL_TIME, CONF_STATION, DOMAIN from .hub import GTIHub _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/hyperion/config_flow.py b/homeassistant/components/hyperion/config_flow.py index 642bc0e93fd..8d02028dc38 100644 --- a/homeassistant/components/hyperion/config_flow.py +++ b/homeassistant/components/hyperion/config_flow.py @@ -221,7 +221,6 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): return self.async_abort(reason="cannot_connect") return await self._advance_to_auth_step_if_necessary(hyperion_client) - # pylint: disable=arguments-differ async def async_step_user( self, user_input: Optional[ConfigType] = None, diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index c49a65b2bfd..838b69cd52b 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -553,7 +553,6 @@ class HyperionPriorityLight(HyperionBaseLight): return True return False - # pylint: disable=no-self-use def _allow_priority_update(self, priority: Optional[Dict[str, Any]] = None) -> bool: """Determine whether to allow a Hyperion priority to update entity attributes.""" # Black is treated as 'off' (and Home Assistant does not support selecting black diff --git a/homeassistant/components/hyperion/switch.py b/homeassistant/components/hyperion/switch.py index 0412018650a..e2da80e5093 100644 --- a/homeassistant/components/hyperion/switch.py +++ b/homeassistant/components/hyperion/switch.py @@ -178,12 +178,10 @@ class HyperionComponentSwitch(SwitchEntity): } ) - # pylint: disable=unused-argument async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the switch.""" await self._async_send_set_component(True) - # pylint: disable=unused-argument async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the switch.""" await self._async_send_set_component(False) diff --git a/homeassistant/components/icloud/config_flow.py b/homeassistant/components/icloud/config_flow.py index c79024c4f64..28570f3d93c 100644 --- a/homeassistant/components/icloud/config_flow.py +++ b/homeassistant/components/icloud/config_flow.py @@ -21,10 +21,10 @@ from .const import ( DEFAULT_GPS_ACCURACY_THRESHOLD, DEFAULT_MAX_INTERVAL, DEFAULT_WITH_FAMILY, + DOMAIN, STORAGE_KEY, STORAGE_VERSION, ) -from .const import DOMAIN # pylint: disable=unused-import CONF_TRUSTED_DEVICE = "trusted_device" CONF_VERIFICATION_CODE = "verification_code" diff --git a/homeassistant/components/knx/const.py b/homeassistant/components/knx/const.py index 268f766bfc2..dfe357ef33c 100644 --- a/homeassistant/components/knx/const.py +++ b/homeassistant/components/knx/const.py @@ -29,7 +29,6 @@ ATTR_COUNTER = "counter" class ColorTempModes(Enum): - # pylint: disable=invalid-name """Color temperature modes for config validation.""" ABSOLUTE = "DPT-7.600" @@ -37,7 +36,6 @@ class ColorTempModes(Enum): class SupportedPlatforms(Enum): - # pylint: disable=invalid-name """Supported platforms.""" BINARY_SENSOR = "binary_sensor" diff --git a/homeassistant/components/lcn/__init__.py b/homeassistant/components/lcn/__init__.py index cc1e47d71fc..fe9195584ea 100644 --- a/homeassistant/components/lcn/__init__.py +++ b/homeassistant/components/lcn/__init__.py @@ -27,7 +27,7 @@ from .const import ( DATA_LCN, DOMAIN, ) -from .schemas import CONFIG_SCHEMA # noqa: 401 +from .schemas import CONFIG_SCHEMA # noqa: F401 from .services import ( DynText, Led, diff --git a/homeassistant/components/lirc/__init__.py b/homeassistant/components/lirc/__init__.py index bf939fb7535..b52cf3cf107 100644 --- a/homeassistant/components/lirc/__init__.py +++ b/homeassistant/components/lirc/__init__.py @@ -1,5 +1,5 @@ """Support for LIRC devices.""" -# pylint: disable=no-member, import-error +# pylint: disable=import-error import logging import threading import time diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index e673b2a470b..45011239f16 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -34,7 +34,7 @@ from .const import ( STORAGE_DASHBOARD_UPDATE_FIELDS, url_slug, ) -from .system_health import system_health_info # NOQA +from .system_health import system_health_info # noqa: F401 _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/mcp23017/binary_sensor.py b/homeassistant/components/mcp23017/binary_sensor.py index f6dafad43ac..c650393a26f 100644 --- a/homeassistant/components/mcp23017/binary_sensor.py +++ b/homeassistant/components/mcp23017/binary_sensor.py @@ -1,8 +1,8 @@ """Support for binary sensor using I2C MCP23017 chip.""" -from adafruit_mcp230xx.mcp23017 import MCP23017 # pylint: disable=import-error -import board # pylint: disable=import-error -import busio # pylint: disable=import-error -import digitalio # pylint: disable=import-error +from adafruit_mcp230xx.mcp23017 import MCP23017 +import board +import busio +import digitalio import voluptuous as vol from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity diff --git a/homeassistant/components/mcp23017/switch.py b/homeassistant/components/mcp23017/switch.py index d22593a4c3e..6b1ced540ae 100644 --- a/homeassistant/components/mcp23017/switch.py +++ b/homeassistant/components/mcp23017/switch.py @@ -1,8 +1,8 @@ """Support for switch sensor using I2C MCP23017 chip.""" -from adafruit_mcp230xx.mcp23017 import MCP23017 # pylint: disable=import-error -import board # pylint: disable=import-error -import busio # pylint: disable=import-error -import digitalio # pylint: disable=import-error +from adafruit_mcp230xx.mcp23017 import MCP23017 +import board +import busio +import digitalio import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA diff --git a/homeassistant/components/meteo_france/sensor.py b/homeassistant/components/meteo_france/sensor.py index 201cca7ae9d..4802c20c1e5 100644 --- a/homeassistant/components/meteo_france/sensor.py +++ b/homeassistant/components/meteo_france/sensor.py @@ -193,7 +193,6 @@ class MeteoFranceRainSensor(MeteoFranceSensor): class MeteoFranceAlertSensor(MeteoFranceSensor): """Representation of a Meteo-France alert sensor.""" - # pylint: disable=super-init-not-called def __init__(self, sensor_type: str, coordinator: DataUpdateCoordinator): """Initialize the Meteo-France sensor.""" super().__init__(sensor_type, coordinator) diff --git a/homeassistant/components/miflora/sensor.py b/homeassistant/components/miflora/sensor.py index 6583f5f7e0c..fa1d8b57734 100644 --- a/homeassistant/components/miflora/sensor.py +++ b/homeassistant/components/miflora/sensor.py @@ -5,7 +5,7 @@ import logging import btlewrap from btlewrap import BluetoothBackendException -from miflora import miflora_poller # pylint: disable=import-error +from miflora import miflora_poller import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA diff --git a/homeassistant/components/mjpeg/camera.py b/homeassistant/components/mjpeg/camera.py index 1a8b1327093..15f7f5c80bf 100644 --- a/homeassistant/components/mjpeg/camera.py +++ b/homeassistant/components/mjpeg/camera.py @@ -144,8 +144,6 @@ class MjpegCamera(Camera): else: req = requests.get(self._mjpeg_url, stream=True, timeout=10) - # https://github.com/PyCQA/pylint/issues/1437 - # pylint: disable=no-member with closing(req) as response: return extract_image_from_mjpeg(response.iter_content(102400)) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 29f7e24da00..503083b0067 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -668,7 +668,7 @@ class MQTT: will_message = None if will_message is not None: - self._mqttc.will_set( # pylint: disable=no-value-for-parameter + self._mqttc.will_set( topic=will_message.topic, payload=will_message.payload, qos=will_message.qos, @@ -833,7 +833,7 @@ class MQTT: async def publish_birth_message(birth_message): await self._ha_started.wait() # Wait for Home Assistant to start await self._discovery_cooldown() # Wait for MQTT discovery to cool down - await self.async_publish( # pylint: disable=no-value-for-parameter + await self.async_publish( topic=birth_message.topic, payload=birth_message.payload, qos=birth_message.qos, diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index b9fb297cd5c..a1f13e7873e 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -242,7 +242,6 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity): def available(self) -> bool: """Return true if the device is available and value has not expired.""" expire_after = self._config.get(CONF_EXPIRE_AFTER) - # pylint: disable=no-member return MqttAvailability.available.fget(self) and ( expire_after is None or not self._expired ) diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index d017cb2ce85..cb263febe13 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -197,7 +197,6 @@ class MqttSensor(MqttEntity, Entity): def available(self) -> bool: """Return true if the device is available and value has not expired.""" expire_after = self._config.get(CONF_EXPIRE_AFTER) - # pylint: disable=no-member return MqttAvailability.available.fget(self) and ( expire_after is None or not self._expired ) diff --git a/homeassistant/components/mullvad/binary_sensor.py b/homeassistant/components/mullvad/binary_sensor.py index f85820cd7d0..1a91bba2038 100644 --- a/homeassistant/components/mullvad/binary_sensor.py +++ b/homeassistant/components/mullvad/binary_sensor.py @@ -29,7 +29,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class MullvadBinarySensor(CoordinatorEntity, BinarySensorEntity): """Represents a Mullvad binary sensor.""" - def __init__(self, coordinator, sensor): # pylint: disable=super-init-not-called + def __init__(self, coordinator, sensor): """Initialize the Mullvad binary sensor.""" super().__init__(coordinator) self.id = sensor[CONF_ID] diff --git a/homeassistant/components/mullvad/config_flow.py b/homeassistant/components/mullvad/config_flow.py index 674308c1d1a..50f67d10e25 100644 --- a/homeassistant/components/mullvad/config_flow.py +++ b/homeassistant/components/mullvad/config_flow.py @@ -5,7 +5,7 @@ from mullvad_api import MullvadAPI, MullvadAPIError from homeassistant import config_entries -from .const import DOMAIN # pylint:disable=unused-import +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/mysensors/config_flow.py b/homeassistant/components/mysensors/config_flow.py index d2cd7f3bccd..7bf30e4cab5 100644 --- a/homeassistant/components/mysensors/config_flow.py +++ b/homeassistant/components/mysensors/config_flow.py @@ -23,8 +23,6 @@ from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from . import CONF_RETAIN, CONF_VERSION, DEFAULT_VERSION - -# pylint: disable=unused-import from .const import ( CONF_BAUD_RATE, CONF_GATEWAY_TYPE, diff --git a/homeassistant/components/neato/config_flow.py b/homeassistant/components/neato/config_flow.py index 1f2f575ae50..5f7487fe5cc 100644 --- a/homeassistant/components/neato/config_flow.py +++ b/homeassistant/components/neato/config_flow.py @@ -8,7 +8,6 @@ from homeassistant import config_entries from homeassistant.const import CONF_TOKEN from homeassistant.helpers import config_entry_oauth2_flow -# pylint: disable=unused-import from .const import NEATO_DOMAIN diff --git a/homeassistant/components/noaa_tides/sensor.py b/homeassistant/components/noaa_tides/sensor.py index a0453e3acb1..0759c5093c8 100644 --- a/homeassistant/components/noaa_tides/sensor.py +++ b/homeassistant/components/noaa_tides/sensor.py @@ -2,7 +2,7 @@ from datetime import datetime, timedelta import logging -import noaa_coops as coops # pylint: disable=import-error +import noaa_coops as coops import requests import voluptuous as vol diff --git a/homeassistant/components/omnilogic/config_flow.py b/homeassistant/components/omnilogic/config_flow.py index 641ec5a8d94..f8dffaeda44 100644 --- a/homeassistant/components/omnilogic/config_flow.py +++ b/homeassistant/components/omnilogic/config_flow.py @@ -9,7 +9,7 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback from homeassistant.helpers import aiohttp_client -from .const import CONF_SCAN_INTERVAL, DOMAIN # pylint:disable=unused-import +from .const import CONF_SCAN_INTERVAL, DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/onewire/config_flow.py b/homeassistant/components/onewire/config_flow.py index 9ad4d5347f0..fbb1d5debef 100644 --- a/homeassistant/components/onewire/config_flow.py +++ b/homeassistant/components/onewire/config_flow.py @@ -5,7 +5,7 @@ from homeassistant.config_entries import CONN_CLASS_LOCAL_POLL, ConfigFlow from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE from homeassistant.helpers.typing import HomeAssistantType -from .const import ( # pylint: disable=unused-import +from .const import ( CONF_MOUNT_DIR, CONF_TYPE_OWFS, CONF_TYPE_OWSERVER, diff --git a/homeassistant/components/owntracks/config_flow.py b/homeassistant/components/owntracks/config_flow.py index f0838b510ec..2e588ff933a 100644 --- a/homeassistant/components/owntracks/config_flow.py +++ b/homeassistant/components/owntracks/config_flow.py @@ -4,7 +4,7 @@ import secrets from homeassistant import config_entries from homeassistant.const import CONF_WEBHOOK_ID -from .const import DOMAIN # noqa pylint: disable=unused-import +from .const import DOMAIN # pylint: disable=unused-import from .helper import supports_encryption CONF_SECRET = "secret" diff --git a/homeassistant/components/ozw/config_flow.py b/homeassistant/components/ozw/config_flow.py index 00917c0609c..2546a2e0aff 100644 --- a/homeassistant/components/ozw/config_flow.py +++ b/homeassistant/components/ozw/config_flow.py @@ -7,8 +7,7 @@ from homeassistant import config_entries from homeassistant.core import callback from homeassistant.data_entry_flow import AbortFlow -from .const import CONF_INTEGRATION_CREATED_ADDON, CONF_USE_ADDON -from .const import DOMAIN # pylint:disable=unused-import +from .const import CONF_INTEGRATION_CREATED_ADDON, CONF_USE_ADDON, DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py b/homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py index 61cedb8f063..bdec7714eef 100644 --- a/homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py +++ b/homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py @@ -1,5 +1,5 @@ """Support for binary sensor using RPi GPIO.""" -from pi4ioe5v9xxxx import pi4ioe5v9xxxx # pylint: disable=import-error +from pi4ioe5v9xxxx import pi4ioe5v9xxxx import voluptuous as vol from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity diff --git a/homeassistant/components/pi4ioe5v9xxxx/switch.py b/homeassistant/components/pi4ioe5v9xxxx/switch.py index 81de76c086c..85bde509070 100644 --- a/homeassistant/components/pi4ioe5v9xxxx/switch.py +++ b/homeassistant/components/pi4ioe5v9xxxx/switch.py @@ -1,5 +1,5 @@ """Allows to configure a switch using RPi GPIO.""" -from pi4ioe5v9xxxx import pi4ioe5v9xxxx # pylint: disable=import-error +from pi4ioe5v9xxxx import pi4ioe5v9xxxx import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index d611c09c43e..d1fa5684cf5 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -27,7 +27,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.network import get_url -from .const import ( # pylint: disable=unused-import +from .const import ( AUTH_CALLBACK_NAME, AUTH_CALLBACK_PATH, AUTOMATIC_SETUP_STRING, diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 8f9d4d1cc51..c8d383867cf 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -189,7 +189,7 @@ class PlexServer: _connect_with_url() except requests.exceptions.SSLError as error: while error and not isinstance(error, ssl.SSLCertVerificationError): - error = error.__context__ # pylint: disable=no-member + error = error.__context__ if isinstance(error, ssl.SSLCertVerificationError): domain = urlparse(self._url).netloc.split(":")[0] if domain.endswith("plex.direct") and error.args[0].startswith( diff --git a/homeassistant/components/ps4/__init__.py b/homeassistant/components/ps4/__init__.py index 390637c26a3..11d271be543 100644 --- a/homeassistant/components/ps4/__init__.py +++ b/homeassistant/components/ps4/__init__.py @@ -25,7 +25,7 @@ from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import location from homeassistant.util.json import load_json, save_json -from .config_flow import PlayStation4FlowHandler # noqa: pylint: disable=unused-import +from .config_flow import PlayStation4FlowHandler # noqa: F401 from .const import ATTR_MEDIA_IMAGE_URL, COMMANDS, DOMAIN, GAMES_FILE, PS4_DATA _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/rest/__init__.py b/homeassistant/components/rest/__init__.py index ebeddcfd7c7..233a12f44c8 100644 --- a/homeassistant/components/rest/__init__.py +++ b/homeassistant/components/rest/__init__.py @@ -35,7 +35,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import COORDINATOR, DOMAIN, PLATFORM_IDX, REST, REST_IDX from .data import RestData -from .schema import CONFIG_SCHEMA # noqa:F401 pylint: disable=unused-import +from .schema import CONFIG_SCHEMA # noqa: F401 _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/rpi_rf/switch.py b/homeassistant/components/rpi_rf/switch.py index 4ac7283b194..a374300a264 100644 --- a/homeassistant/components/rpi_rf/switch.py +++ b/homeassistant/components/rpi_rf/switch.py @@ -45,7 +45,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -# pylint: disable=no-member def setup_platform(hass, config, add_entities, discovery_info=None): """Find and return switches controlled by a generic RF device via GPIO.""" rpi_rf = importlib.import_module("rpi_rf") diff --git a/homeassistant/components/sharkiq/update_coordinator.py b/homeassistant/components/sharkiq/update_coordinator.py index eff18064dcc..8675058de86 100644 --- a/homeassistant/components/sharkiq/update_coordinator.py +++ b/homeassistant/components/sharkiq/update_coordinator.py @@ -99,7 +99,7 @@ class SharkIqUpdateCoordinator(DataUpdateCoordinator): _LOGGER.debug("Matching flow found") raise UpdateFailed(err) from err - except Exception as err: # pylint: disable=broad-except + except Exception as err: _LOGGER.exception("Unexpected error updating SharkIQ") raise UpdateFailed(err) from err diff --git a/homeassistant/components/sms/config_flow.py b/homeassistant/components/sms/config_flow.py index 01c1d182c93..da5db672959 100644 --- a/homeassistant/components/sms/config_flow.py +++ b/homeassistant/components/sms/config_flow.py @@ -1,7 +1,7 @@ """Config flow for SMS integration.""" import logging -import gammu # pylint: disable=import-error, no-member +import gammu # pylint: disable=import-error import voluptuous as vol from homeassistant import config_entries, core, exceptions diff --git a/homeassistant/components/sms/gateway.py b/homeassistant/components/sms/gateway.py index 9bdfbad0f55..e28b3947e37 100644 --- a/homeassistant/components/sms/gateway.py +++ b/homeassistant/components/sms/gateway.py @@ -1,10 +1,8 @@ """The sms gateway to interact with a GSM modem.""" import logging -import gammu # pylint: disable=import-error, no-member -from gammu.asyncworker import ( # pylint: disable=import-error, no-member - GammuAsyncWorker, -) +import gammu # pylint: disable=import-error +from gammu.asyncworker import GammuAsyncWorker # pylint: disable=import-error from homeassistant.core import callback @@ -165,6 +163,6 @@ async def create_sms_gateway(config, hass): gateway = Gateway(worker, hass) await gateway.init_async() return gateway - except gammu.GSMError as exc: # pylint: disable=no-member + except gammu.GSMError as exc: _LOGGER.error("Failed to initialize, error %s", exc) return None diff --git a/homeassistant/components/sms/notify.py b/homeassistant/components/sms/notify.py index f030409b6ca..04964c15878 100644 --- a/homeassistant/components/sms/notify.py +++ b/homeassistant/components/sms/notify.py @@ -1,7 +1,7 @@ """Support for SMS notification services.""" import logging -import gammu # pylint: disable=import-error, no-member +import gammu # pylint: disable=import-error import voluptuous as vol from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService @@ -51,8 +51,8 @@ class SMSNotificationService(BaseNotificationService): } try: # Encode messages - encoded = gammu.EncodeSMS(smsinfo) # pylint: disable=no-member - except gammu.GSMError as exc: # pylint: disable=no-member + encoded = gammu.EncodeSMS(smsinfo) + except gammu.GSMError as exc: _LOGGER.error("Encoding message %s failed: %s", message, exc) return @@ -64,5 +64,5 @@ class SMSNotificationService(BaseNotificationService): try: # Actually send the message await self.gateway.send_sms_async(encoded_message) - except gammu.GSMError as exc: # pylint: disable=no-member + except gammu.GSMError as exc: _LOGGER.error("Sending to %s failed: %s", self.number, exc) diff --git a/homeassistant/components/sms/sensor.py b/homeassistant/components/sms/sensor.py index eaad395eaa6..e775b4a0e05 100644 --- a/homeassistant/components/sms/sensor.py +++ b/homeassistant/components/sms/sensor.py @@ -1,7 +1,7 @@ """Support for SMS dongle sensor.""" import logging -import gammu # pylint: disable=import-error, no-member +import gammu # pylint: disable=import-error from homeassistant.const import DEVICE_CLASS_SIGNAL_STRENGTH, SIGNAL_STRENGTH_DECIBELS from homeassistant.helpers.entity import Entity @@ -71,7 +71,7 @@ class GSMSignalSensor(Entity): """Get the latest data from the modem.""" try: self._state = await self._gateway.get_signal_quality_async() - except gammu.GSMError as exc: # pylint: disable=no-member + except gammu.GSMError as exc: _LOGGER.error("Failed to read signal quality: %s", exc) @property diff --git a/homeassistant/components/somfy_mylink/config_flow.py b/homeassistant/components/somfy_mylink/config_flow.py index b6d647b9a3b..d1a1e19609a 100644 --- a/homeassistant/components/somfy_mylink/config_flow.py +++ b/homeassistant/components/somfy_mylink/config_flow.py @@ -19,9 +19,9 @@ from .const import ( CONF_TARGET_ID, CONF_TARGET_NAME, DEFAULT_PORT, + DOMAIN, MYLINK_STATUS, ) -from .const import DOMAIN # pylint:disable=unused-import _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/speedtestdotnet/config_flow.py b/homeassistant/components/speedtestdotnet/config_flow.py index 5ff405b59a8..84d63ebc33b 100644 --- a/homeassistant/components/speedtestdotnet/config_flow.py +++ b/homeassistant/components/speedtestdotnet/config_flow.py @@ -13,8 +13,8 @@ from .const import ( DEFAULT_NAME, DEFAULT_SCAN_INTERVAL, DEFAULT_SERVER, + DOMAIN, ) -from .const import DOMAIN # pylint: disable=unused-import class SpeedTestFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/switchbot/switch.py b/homeassistant/components/switchbot/switch.py index 3dd931abe49..89ba7b5be5e 100644 --- a/homeassistant/components/switchbot/switch.py +++ b/homeassistant/components/switchbot/switch.py @@ -1,7 +1,7 @@ """Support for Switchbot.""" from typing import Any, Dict -# pylint: disable=import-error, no-member +# pylint: disable=import-error import switchbot import voluptuous as vol diff --git a/homeassistant/components/switchmate/switch.py b/homeassistant/components/switchmate/switch.py index a052b9051a1..24b54537dc8 100644 --- a/homeassistant/components/switchmate/switch.py +++ b/homeassistant/components/switchmate/switch.py @@ -1,7 +1,7 @@ """Support for Switchmate.""" from datetime import timedelta -# pylint: disable=import-error, no-member +# pylint: disable=import-error import switchmate import voluptuous as vol diff --git a/homeassistant/components/syncthru/config_flow.py b/homeassistant/components/syncthru/config_flow.py index 83f044d8ebc..2c6eb65e4ce 100644 --- a/homeassistant/components/syncthru/config_flow.py +++ b/homeassistant/components/syncthru/config_flow.py @@ -62,7 +62,6 @@ class SyncThruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # Remove trailing " (ip)" if present for consistency with user driven config self.name = re.sub(r"\s+\([\d.]+\)\s*$", "", self.name) - # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = {CONF_NAME: self.name} return await self.async_step_confirm() diff --git a/homeassistant/components/tasmota/config_flow.py b/homeassistant/components/tasmota/config_flow.py index 5d39fa02438..320b4ff2448 100644 --- a/homeassistant/components/tasmota/config_flow.py +++ b/homeassistant/components/tasmota/config_flow.py @@ -4,11 +4,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components.mqtt import valid_subscribe_topic -from .const import ( # pylint:disable=unused-import - CONF_DISCOVERY_PREFIX, - DEFAULT_PREFIX, - DOMAIN, -) +from .const import CONF_DISCOVERY_PREFIX, DEFAULT_PREFIX, DOMAIN class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/tensorflow/image_processing.py b/homeassistant/components/tensorflow/image_processing.py index e387ae97afe..65240dc04dc 100644 --- a/homeassistant/components/tensorflow/image_processing.py +++ b/homeassistant/components/tensorflow/image_processing.py @@ -341,9 +341,8 @@ class TensorFlowImageProcessor(ImageProcessingEntity): start = time.perf_counter() try: - import cv2 # pylint: disable=import-error, import-outside-toplevel + import cv2 # pylint: disable=import-outside-toplevel - # pylint: disable=no-member img = cv2.imdecode(np.asarray(bytearray(image)), cv2.IMREAD_UNCHANGED) inp = img[:, :, [2, 1, 0]] # BGR->RGB inp_expanded = inp.reshape(1, inp.shape[0], inp.shape[1], 3) diff --git a/homeassistant/components/tuya/config_flow.py b/homeassistant/components/tuya/config_flow.py index b705c2c7c36..18820098a91 100644 --- a/homeassistant/components/tuya/config_flow.py +++ b/homeassistant/components/tuya/config_flow.py @@ -42,11 +42,11 @@ from .const import ( DEFAULT_DISCOVERY_INTERVAL, DEFAULT_QUERY_INTERVAL, DEFAULT_TUYA_MAX_COLTEMP, + DOMAIN, TUYA_DATA, TUYA_PLATFORMS, TUYA_TYPE_NOT_QUERY, ) -from .const import DOMAIN # pylint:disable=unused-import _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index 9d65bb4c5d4..282c477d20d 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -5,7 +5,7 @@ import logging import async_timeout from awesomeversion import AwesomeVersion -from distro import linux_distribution # pylint: disable=import-error +from distro import linux_distribution import voluptuous as vol from homeassistant.const import __version__ as current_version diff --git a/homeassistant/components/vera/config_flow.py b/homeassistant/components/vera/config_flow.py index 754d2eca542..e62b21c82ea 100644 --- a/homeassistant/components/vera/config_flow.py +++ b/homeassistant/components/vera/config_flow.py @@ -13,11 +13,7 @@ from homeassistant.const import CONF_EXCLUDE, CONF_LIGHTS, CONF_SOURCE from homeassistant.core import callback from homeassistant.helpers.entity_registry import EntityRegistry -from .const import ( # pylint: disable=unused-import - CONF_CONTROLLER, - CONF_LEGACY_UNIQUE_ID, - DOMAIN, -) +from .const import CONF_CONTROLLER, CONF_LEGACY_UNIQUE_ID, DOMAIN LIST_REGEX = re.compile("[^0-9]+") _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/websocket_api/__init__.py b/homeassistant/components/websocket_api/__init__.py index 2d591455eaf..43f5c807770 100644 --- a/homeassistant/components/websocket_api/__init__.py +++ b/homeassistant/components/websocket_api/__init__.py @@ -6,9 +6,9 @@ import voluptuous as vol from homeassistant.core import HomeAssistant, callback from homeassistant.loader import bind_hass -from . import commands, connection, const, decorators, http, messages # noqa -from .connection import ActiveConnection # noqa -from .const import ( # noqa +from . import commands, connection, const, decorators, http, messages # noqa: F401 +from .connection import ActiveConnection # noqa: F401 +from .const import ( # noqa: F401 ERR_HOME_ASSISTANT_ERROR, ERR_INVALID_FORMAT, ERR_NOT_FOUND, @@ -19,13 +19,13 @@ from .const import ( # noqa ERR_UNKNOWN_COMMAND, ERR_UNKNOWN_ERROR, ) -from .decorators import ( # noqa +from .decorators import ( # noqa: F401 async_response, require_admin, websocket_command, ws_require_user, ) -from .messages import ( # noqa +from .messages import ( # noqa: F401 BASE_COMMAND_MESSAGE_SCHEMA, error_message, event_message, diff --git a/homeassistant/components/websocket_api/const.py b/homeassistant/components/websocket_api/const.py index 5f2cfb2257d..7c3f18f856c 100644 --- a/homeassistant/components/websocket_api/const.py +++ b/homeassistant/components/websocket_api/const.py @@ -9,7 +9,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.json import JSONEncoder if TYPE_CHECKING: - from .connection import ActiveConnection # noqa + from .connection import ActiveConnection WebSocketCommandHandler = Callable[[HomeAssistant, "ActiveConnection", dict], None] diff --git a/homeassistant/components/xbox/media_source.py b/homeassistant/components/xbox/media_source.py index 750300e49ee..53346f3750d 100644 --- a/homeassistant/components/xbox/media_source.py +++ b/homeassistant/components/xbox/media_source.py @@ -2,8 +2,7 @@ from dataclasses import dataclass from typing import List, Tuple -# pylint: disable=no-name-in-module -from pydantic.error_wrappers import ValidationError +from pydantic.error_wrappers import ValidationError # pylint: disable=no-name-in-module from xbox.webapi.api.client import XboxLiveClient from xbox.webapi.api.provider.catalog.models import FieldsTemplate, Image from xbox.webapi.api.provider.gameclips.models import GameclipsResponse diff --git a/homeassistant/components/xiaomi_miio/device_tracker.py b/homeassistant/components/xiaomi_miio/device_tracker.py index ef527d0aa40..a6c1c7e5a28 100644 --- a/homeassistant/components/xiaomi_miio/device_tracker.py +++ b/homeassistant/components/xiaomi_miio/device_tracker.py @@ -1,7 +1,7 @@ """Support for Xiaomi Mi WiFi Repeater 2.""" import logging -from miio import DeviceException, WifiRepeater # pylint: disable=import-error +from miio import DeviceException, WifiRepeater import voluptuous as vol from homeassistant.components.device_tracker import ( diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index 055690a4264..c8e200516a0 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -4,7 +4,7 @@ from enum import Enum from functools import partial import logging -from miio import ( # pylint: disable=import-error +from miio import ( AirFresh, AirHumidifier, AirHumidifierMiot, @@ -12,24 +12,24 @@ from miio import ( # pylint: disable=import-error AirPurifierMiot, DeviceException, ) -from miio.airfresh import ( # pylint: disable=import-error, import-error +from miio.airfresh import ( LedBrightness as AirfreshLedBrightness, OperationMode as AirfreshOperationMode, ) -from miio.airhumidifier import ( # pylint: disable=import-error, import-error +from miio.airhumidifier import ( LedBrightness as AirhumidifierLedBrightness, OperationMode as AirhumidifierOperationMode, ) -from miio.airhumidifier_miot import ( # pylint: disable=import-error, import-error +from miio.airhumidifier_miot import ( LedBrightness as AirhumidifierMiotLedBrightness, OperationMode as AirhumidifierMiotOperationMode, PressedButton as AirhumidifierPressedButton, ) -from miio.airpurifier import ( # pylint: disable=import-error, import-error +from miio.airpurifier import ( LedBrightness as AirpurifierLedBrightness, OperationMode as AirpurifierOperationMode, ) -from miio.airpurifier_miot import ( # pylint: disable=import-error, import-error +from miio.airpurifier_miot import ( LedBrightness as AirpurifierMiotLedBrightness, OperationMode as AirpurifierMiotOperationMode, ) diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index 7f168cf0e3e..b03aa1b0d2a 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -6,8 +6,14 @@ from functools import partial import logging from math import ceil -from miio import Ceil, DeviceException, PhilipsBulb, PhilipsEyecare, PhilipsMoonlight -from miio import Device # pylint: disable=import-error +from miio import ( + Ceil, + Device, + DeviceException, + PhilipsBulb, + PhilipsEyecare, + PhilipsMoonlight, +) from miio.gateway import ( GATEWAY_MODEL_AC_V1, GATEWAY_MODEL_AC_V2, diff --git a/homeassistant/components/xiaomi_miio/remote.py b/homeassistant/components/xiaomi_miio/remote.py index c946533ab54..7d75e943d4d 100644 --- a/homeassistant/components/xiaomi_miio/remote.py +++ b/homeassistant/components/xiaomi_miio/remote.py @@ -4,7 +4,7 @@ from datetime import timedelta import logging import time -from miio import ChuangmiIr, DeviceException # pylint: disable=import-error +from miio import ChuangmiIr, DeviceException import voluptuous as vol from homeassistant.components.remote import ( diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index 821fe164ea9..52cd4fdca5e 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -2,8 +2,7 @@ from dataclasses import dataclass import logging -from miio import AirQualityMonitor # pylint: disable=import-error -from miio import DeviceException +from miio import AirQualityMonitor, DeviceException from miio.gateway import ( GATEWAY_MODEL_AC_V1, GATEWAY_MODEL_AC_V2, diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index 3cc95572e6c..04bb40ec27d 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -3,9 +3,8 @@ import asyncio from functools import partial import logging -from miio import AirConditioningCompanionV3 # pylint: disable=import-error -from miio import ChuangmiPlug, DeviceException, PowerStrip -from miio.powerstrip import PowerMode # pylint: disable=import-error +from miio import AirConditioningCompanionV3, ChuangmiPlug, DeviceException, PowerStrip +from miio.powerstrip import PowerMode import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py index 7bdbfca7bc9..bbb4cfd1b7f 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -2,7 +2,7 @@ from functools import partial import logging -from miio import DeviceException, Vacuum # pylint: disable=import-error +from miio import DeviceException, Vacuum import voluptuous as vol from homeassistant.components.vacuum import ( diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index d5f76fa5e23..351fc86a1e5 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -94,7 +94,7 @@ async def async_setup_entry(hass, config_entry): if config.get(CONF_ENABLE_QUIRKS, True): # needs to be done here so that the ZHA module is finished loading # before zhaquirks is imported - import zhaquirks # noqa: F401 pylint: disable=unused-import, import-outside-toplevel, import-error + import zhaquirks # noqa: F401 pylint: disable=unused-import, import-outside-toplevel zha_gateway = ZHAGateway(hass, config, config_entry) await zha_gateway.async_initialize() diff --git a/homeassistant/components/zha/core/channels/__init__.py b/homeassistant/components/zha/core/channels/__init__.py index 852d576c035..d3de5f7004d 100644 --- a/homeassistant/components/zha/core/channels/__init__.py +++ b/homeassistant/components/zha/core/channels/__init__.py @@ -10,7 +10,7 @@ from homeassistant.const import ATTR_DEVICE_ID from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send -from . import ( # noqa: F401 # pylint: disable=unused-import +from . import ( # noqa: F401 base, closures, general, diff --git a/homeassistant/components/zha/core/store.py b/homeassistant/components/zha/core/store.py index 051fcaa2925..2db803258bc 100644 --- a/homeassistant/components/zha/core/store.py +++ b/homeassistant/components/zha/core/store.py @@ -1,5 +1,4 @@ """Data storage helper for ZHA.""" -# pylint: disable=unused-import from collections import OrderedDict import datetime import time diff --git a/homeassistant/components/zone/config_flow.py b/homeassistant/components/zone/config_flow.py index bb34a83ad26..a778d71d2e5 100644 --- a/homeassistant/components/zone/config_flow.py +++ b/homeassistant/components/zone/config_flow.py @@ -6,7 +6,7 @@ migrated to the storage collection. """ from homeassistant import config_entries -from .const import DOMAIN # noqa # pylint:disable=unused-import +from .const import DOMAIN # pylint: disable=unused-import class ZoneConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 3acf361dd52..8e9889b5fac 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -396,7 +396,6 @@ async def async_setup_entry(hass, config_entry): Will automatically load components to support devices found on the network. """ - # pylint: disable=import-error from openzwave.group import ZWaveGroup from openzwave.network import ZWaveNetwork from openzwave.option import ZWaveOption @@ -1251,7 +1250,6 @@ class ZWaveDeviceEntity(ZWaveBaseEntity): def __init__(self, values, domain): """Initialize the z-Wave device.""" - # pylint: disable=import-error super().__init__() from openzwave.network import ZWaveNetwork from pydispatch import dispatcher diff --git a/homeassistant/components/zwave/node_entity.py b/homeassistant/components/zwave/node_entity.py index faaea30e0ee..05e5951f921 100644 --- a/homeassistant/components/zwave/node_entity.py +++ b/homeassistant/components/zwave/node_entity.py @@ -118,7 +118,6 @@ class ZWaveNodeEntity(ZWaveBaseEntity): def __init__(self, node, network): """Initialize node.""" - # pylint: disable=import-error super().__init__() from openzwave.network import ZWaveNetwork from pydispatch import dispatcher diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index f5f3d9e5c5b..804212d310a 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -380,7 +380,6 @@ DISCOVERY_SCHEMAS = [ @callback def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None, None]: """Run discovery on ZWave node and return matching (primary) values.""" - # pylint: disable=too-many-nested-blocks for value in node.values.values(): for schema in DISCOVERY_SCHEMAS: # check manufacturer_id diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index b54300faaa7..d45f2b94516 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -927,7 +927,6 @@ class ConfigFlow(data_entry_flow.FlowHandler): @property def unique_id(self) -> Optional[str]: """Return unique ID if available.""" - # pylint: disable=no-member if not self.context: return None @@ -976,7 +975,7 @@ class ConfigFlow(data_entry_flow.FlowHandler): Returns optionally existing config entry with same ID. """ if unique_id is None: - self.context["unique_id"] = None # pylint: disable=no-member + self.context["unique_id"] = None return None if raise_on_progress: @@ -984,7 +983,7 @@ class ConfigFlow(data_entry_flow.FlowHandler): if progress["context"].get("unique_id") == unique_id: raise data_entry_flow.AbortFlow("already_in_progress") - self.context["unique_id"] = unique_id # pylint: disable=no-member + self.context["unique_id"] = unique_id # Abort discoveries done using the default discovery unique id if unique_id != DEFAULT_DISCOVERY_UNIQUE_ID: diff --git a/homeassistant/core.py b/homeassistant/core.py index 3483dc96069..b7bb645be36 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -642,7 +642,6 @@ class Event: def __repr__(self) -> str: """Return the representation.""" - # pylint: disable=maybe-no-member if self.data: return f"" diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index e8235c9a23c..15ad417c6ad 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -266,11 +266,10 @@ class FlowHandler: # Set by flow manager cur_step: Optional[Dict[str, str]] = None - # Ignore types, pylint workaround: https://github.com/PyCQA/pylint/issues/3167 + # Ignore types: https://github.com/PyCQA/pylint/issues/3167 flow_id: str = None # type: ignore hass: HomeAssistant = None # type: ignore handler: str = None # type: ignore - # Pylint workaround: https://github.com/PyCQA/pylint/issues/3167 # Ensure the attribute has a subscriptable, but immutable, default value. context: Dict = MappingProxyType({}) # type: ignore diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index 84ba2cfa348..9a7b3da0f1d 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Generator, Optional, Sequence import attr if TYPE_CHECKING: - from .core import Context # noqa: F401 pylint: disable=unused-import + from .core import Context class HomeAssistantError(Exception): diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 2caf7fe46ab..d860a8a3390 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -62,7 +62,7 @@ class EntityPlatform: self.scan_interval = scan_interval self.entity_namespace = entity_namespace self.config_entry: Optional[config_entries.ConfigEntry] = None - self.entities: Dict[str, Entity] = {} # pylint: disable=used-before-assignment + self.entities: Dict[str, Entity] = {} self._tasks: List[asyncio.Future] = [] # Stop tracking tasks after setup is completed self._setup_complete = False diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index c86bd64d73e..f18dda71529 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -45,7 +45,7 @@ from homeassistant.util.yaml import load_yaml from .typing import UNDEFINED, HomeAssistantType, UndefinedType if TYPE_CHECKING: - from homeassistant.config_entries import ConfigEntry # noqa: F401 + from homeassistant.config_entries import ConfigEntry PATH_REGISTRY = "entity_registry.yaml" DATA_REGISTRY = "entity_registry" diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 932384493f3..85595998a54 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -62,7 +62,7 @@ from homeassistant.util.yaml import load_yaml from homeassistant.util.yaml.loader import JSON_TYPE if TYPE_CHECKING: - from homeassistant.helpers.entity import Entity # noqa + from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_platform import EntityPlatform diff --git a/homeassistant/helpers/sun.py b/homeassistant/helpers/sun.py index 2b82e19b8ce..ce0f0598318 100644 --- a/homeassistant/helpers/sun.py +++ b/homeassistant/helpers/sun.py @@ -12,7 +12,7 @@ from homeassistant.util import dt as dt_util from .typing import HomeAssistantType if TYPE_CHECKING: - import astral # pylint: disable=unused-import + import astral DATA_LOCATION_CACHE = "astral_location_cache" diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 7377120af40..8b3f6ec6e59 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -366,7 +366,7 @@ class Template: try: render_result = compiled.render(kwargs) - except Exception as err: # pylint: disable=broad-except + except Exception as err: raise TemplateError(err) from err render_result = render_result.strip() diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index 9a5fbdb180f..4a9162707fa 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -195,6 +195,8 @@ def color_name_to_rgb(color_name: str) -> Tuple[int, int, int]: # pylint: disable=invalid-name + + def color_RGB_to_xy( iR: int, iG: int, iB: int, Gamut: Optional[GamutType] = None ) -> Tuple[float, float]: @@ -205,7 +207,6 @@ def color_RGB_to_xy( # Taken from: # http://www.developers.meethue.com/documentation/color-conversions-rgb-xy # License: Code is given as is. Use at your own risk and discretion. -# pylint: disable=invalid-name def color_RGB_to_xy_brightness( iR: int, iG: int, iB: int, Gamut: Optional[GamutType] = None ) -> Tuple[float, float, int]: diff --git a/homeassistant/util/ssl.py b/homeassistant/util/ssl.py index 7b987d8eeb2..4f10809ff21 100644 --- a/homeassistant/util/ssl.py +++ b/homeassistant/util/ssl.py @@ -23,7 +23,7 @@ def server_context_modern() -> ssl.SSLContext: https://wiki.mozilla.org/Security/Server_Side_TLS Modern guidelines are followed. """ - context = ssl.SSLContext(ssl.PROTOCOL_TLS) # pylint: disable=no-member + context = ssl.SSLContext(ssl.PROTOCOL_TLS) context.options |= ( ssl.OP_NO_SSLv2 @@ -53,7 +53,7 @@ def server_context_intermediate() -> ssl.SSLContext: https://wiki.mozilla.org/Security/Server_Side_TLS Intermediate guidelines are followed. """ - context = ssl.SSLContext(ssl.PROTOCOL_TLS) # pylint: disable=no-member + context = ssl.SSLContext(ssl.PROTOCOL_TLS) context.options |= ( ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_CIPHER_SERVER_PREFERENCE diff --git a/setup.cfg b/setup.cfg index 69cf931185e..98a01278838 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,6 +30,7 @@ ignore = E203, D202, W504 +noqa-require-code = True [mypy] python_version = 3.8 diff --git a/tests/common.py b/tests/common.py index 0ae6f7ef5c7..df8f8fd2fea 100644 --- a/tests/common.py +++ b/tests/common.py @@ -18,7 +18,7 @@ from typing import Any, Awaitable, Collection, Optional from unittest.mock import AsyncMock, Mock, patch import uuid -from aiohttp.test_utils import unused_port as get_test_instance_port # noqa +from aiohttp.test_utils import unused_port as get_test_instance_port # noqa: F401 from homeassistant import auth, config_entries, core as ha, loader from homeassistant.auth import ( diff --git a/tests/components/abode/conftest.py b/tests/components/abode/conftest.py index cc18597c56e..21d64a644a9 100644 --- a/tests/components/abode/conftest.py +++ b/tests/components/abode/conftest.py @@ -3,7 +3,7 @@ import abodepy.helpers.constants as CONST import pytest from tests.common import load_fixture -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 @pytest.fixture(autouse=True) diff --git a/tests/components/alarm_control_panel/test_device_action.py b/tests/components/alarm_control_panel/test_device_action.py index 514e8fa81f2..2b50f83fd8d 100644 --- a/tests/components/alarm_control_panel/test_device_action.py +++ b/tests/components/alarm_control_panel/test_device_action.py @@ -23,7 +23,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/alarm_control_panel/test_device_condition.py b/tests/components/alarm_control_panel/test_device_condition.py index 33f717e1893..450393135af 100644 --- a/tests/components/alarm_control_panel/test_device_condition.py +++ b/tests/components/alarm_control_panel/test_device_condition.py @@ -22,7 +22,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/alarm_control_panel/test_device_trigger.py b/tests/components/alarm_control_panel/test_device_trigger.py index 56316026c9a..ca6260863f1 100644 --- a/tests/components/alarm_control_panel/test_device_trigger.py +++ b/tests/components/alarm_control_panel/test_device_trigger.py @@ -22,7 +22,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/arcam_fmj/test_device_trigger.py b/tests/components/arcam_fmj/test_device_trigger.py index 0cae565f7bb..ff3cf55ecd1 100644 --- a/tests/components/arcam_fmj/test_device_trigger.py +++ b/tests/components/arcam_fmj/test_device_trigger.py @@ -12,7 +12,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/automation/conftest.py b/tests/components/automation/conftest.py index a967e0af192..438948e8475 100644 --- a/tests/components/automation/conftest.py +++ b/tests/components/automation/conftest.py @@ -1,3 +1,3 @@ """Conftest for automation tests.""" -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 diff --git a/tests/components/axis/conftest.py b/tests/components/axis/conftest.py index 33ea820f9cf..b3964663767 100644 --- a/tests/components/axis/conftest.py +++ b/tests/components/axis/conftest.py @@ -1,2 +1,2 @@ """axis conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/binary_sensor/test_device_condition.py b/tests/components/binary_sensor/test_device_condition.py index f25b529d426..d8c9e1ca894 100644 --- a/tests/components/binary_sensor/test_device_condition.py +++ b/tests/components/binary_sensor/test_device_condition.py @@ -20,7 +20,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/binary_sensor/test_device_trigger.py b/tests/components/binary_sensor/test_device_trigger.py index fef109eb9d5..9b50d52b785 100644 --- a/tests/components/binary_sensor/test_device_trigger.py +++ b/tests/components/binary_sensor/test_device_trigger.py @@ -20,7 +20,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/blebox/conftest.py b/tests/components/blebox/conftest.py index 3b12c682f3f..c603c15c32b 100644 --- a/tests/components/blebox/conftest.py +++ b/tests/components/blebox/conftest.py @@ -10,7 +10,7 @@ from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 def patch_product_identify(path=None, **kwargs): diff --git a/tests/components/bond/conftest.py b/tests/components/bond/conftest.py index 7ab1805cb73..378c3340e29 100644 --- a/tests/components/bond/conftest.py +++ b/tests/components/bond/conftest.py @@ -1,2 +1,2 @@ """bond conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/climate/test_device_action.py b/tests/components/climate/test_device_action.py index 4084d37358e..dc956f0738c 100644 --- a/tests/components/climate/test_device_action.py +++ b/tests/components/climate/test_device_action.py @@ -15,7 +15,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/climate/test_device_condition.py b/tests/components/climate/test_device_condition.py index 8e6d5829c41..009d29dab39 100644 --- a/tests/components/climate/test_device_condition.py +++ b/tests/components/climate/test_device_condition.py @@ -15,7 +15,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/climate/test_device_trigger.py b/tests/components/climate/test_device_trigger.py index 69bb4626e49..017385362d1 100644 --- a/tests/components/climate/test_device_trigger.py +++ b/tests/components/climate/test_device_trigger.py @@ -16,7 +16,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index 00c89edeef0..541cd3068d2 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -5,7 +5,7 @@ from unittest.mock import patch from homeassistant.bootstrap import async_setup_component from homeassistant.components import config -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 async def test_get_device_config(hass, hass_client): diff --git a/tests/components/config/test_device_registry.py b/tests/components/config/test_device_registry.py index b2273d640de..a123a2edb35 100644 --- a/tests/components/config/test_device_registry.py +++ b/tests/components/config/test_device_registry.py @@ -4,7 +4,7 @@ import pytest from homeassistant.components.config import device_registry from tests.common import mock_device_registry -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/cover/test_device_action.py b/tests/components/cover/test_device_action.py index cad6074ff34..5cec3d901e1 100644 --- a/tests/components/cover/test_device_action.py +++ b/tests/components/cover/test_device_action.py @@ -16,7 +16,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/cover/test_device_condition.py b/tests/components/cover/test_device_condition.py index b3098ceeca9..04415661515 100644 --- a/tests/components/cover/test_device_condition.py +++ b/tests/components/cover/test_device_condition.py @@ -22,7 +22,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/cover/test_device_trigger.py b/tests/components/cover/test_device_trigger.py index e8bb3cdc8df..e0f6d6d5fb9 100644 --- a/tests/components/cover/test_device_trigger.py +++ b/tests/components/cover/test_device_trigger.py @@ -22,7 +22,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/deconz/conftest.py b/tests/components/deconz/conftest.py index 4c45d3c912b..5cf122213c4 100644 --- a/tests/components/deconz/conftest.py +++ b/tests/components/deconz/conftest.py @@ -1,2 +1,2 @@ """deconz conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/deconz/test_device_trigger.py b/tests/components/deconz/test_device_trigger.py index b9d538588cd..20fd79e9e8c 100644 --- a/tests/components/deconz/test_device_trigger.py +++ b/tests/components/deconz/test_device_trigger.py @@ -19,7 +19,7 @@ from homeassistant.const import ( from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration from tests.common import assert_lists_same, async_get_device_automations -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 SENSORS = { "1": { diff --git a/tests/components/default_config/test_init.py b/tests/components/default_config/test_init.py index 9830d944471..ec2d207a68b 100644 --- a/tests/components/default_config/test_init.py +++ b/tests/components/default_config/test_init.py @@ -5,7 +5,7 @@ import pytest from homeassistant.setup import async_setup_component -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture(autouse=True) diff --git a/tests/components/demo/conftest.py b/tests/components/demo/conftest.py index 666224fb737..13574c50182 100644 --- a/tests/components/demo/conftest.py +++ b/tests/components/demo/conftest.py @@ -1,2 +1,2 @@ """demo conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/device_automation/test_init.py b/tests/components/device_automation/test_init.py index 83ec146b53e..a2f042bcd73 100644 --- a/tests/components/device_automation/test_init.py +++ b/tests/components/device_automation/test_init.py @@ -13,7 +13,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/device_tracker/test_device_condition.py b/tests/components/device_tracker/test_device_condition.py index a187f21e954..2cd4aceeb07 100644 --- a/tests/components/device_tracker/test_device_condition.py +++ b/tests/components/device_tracker/test_device_condition.py @@ -15,7 +15,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/device_tracker/test_device_trigger.py b/tests/components/device_tracker/test_device_trigger.py index 2f0ec14ec4b..0ec1ee67d36 100644 --- a/tests/components/device_tracker/test_device_trigger.py +++ b/tests/components/device_tracker/test_device_trigger.py @@ -16,7 +16,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 AWAY_LATITUDE = 32.881011 AWAY_LONGITUDE = -117.234758 diff --git a/tests/components/dynalite/conftest.py b/tests/components/dynalite/conftest.py index 187e4f9cbaa..59f109e7e47 100644 --- a/tests/components/dynalite/conftest.py +++ b/tests/components/dynalite/conftest.py @@ -1,2 +1,2 @@ """dynalite conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/elgato/conftest.py b/tests/components/elgato/conftest.py index f2ac45155fb..fe86f26b535 100644 --- a/tests/components/elgato/conftest.py +++ b/tests/components/elgato/conftest.py @@ -1,2 +1,2 @@ """elgato conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/everlights/conftest.py b/tests/components/everlights/conftest.py index 31915934b31..5009251b102 100644 --- a/tests/components/everlights/conftest.py +++ b/tests/components/everlights/conftest.py @@ -1,2 +1,2 @@ """everlights conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/fan/test_device_action.py b/tests/components/fan/test_device_action.py index d3a9aedcf9c..491e6afab6a 100644 --- a/tests/components/fan/test_device_action.py +++ b/tests/components/fan/test_device_action.py @@ -14,7 +14,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/fan/test_device_condition.py b/tests/components/fan/test_device_condition.py index 6725587aeda..8f11d963ed3 100644 --- a/tests/components/fan/test_device_condition.py +++ b/tests/components/fan/test_device_condition.py @@ -15,7 +15,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/fan/test_device_trigger.py b/tests/components/fan/test_device_trigger.py index d96f0a828f3..59af7666a03 100644 --- a/tests/components/fan/test_device_trigger.py +++ b/tests/components/fan/test_device_trigger.py @@ -15,7 +15,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/geo_location/test_trigger.py b/tests/components/geo_location/test_trigger.py index ab984a2c309..c547cb61230 100644 --- a/tests/components/geo_location/test_trigger.py +++ b/tests/components/geo_location/test_trigger.py @@ -7,7 +7,7 @@ from homeassistant.core import Context from homeassistant.setup import async_setup_component from tests.common import async_mock_service, mock_component -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/google_assistant/test_helpers.py b/tests/components/google_assistant/test_helpers.py index abf2773d67e..d6094a771bd 100644 --- a/tests/components/google_assistant/test_helpers.py +++ b/tests/components/google_assistant/test_helpers.py @@ -5,7 +5,7 @@ from unittest.mock import Mock, call, patch import pytest from homeassistant.components.google_assistant import helpers -from homeassistant.components.google_assistant.const import ( # noqa: F401 +from homeassistant.components.google_assistant.const import ( EVENT_COMMAND_RECEIVED, NOT_EXPOSE_LOCAL, ) diff --git a/tests/components/group/conftest.py b/tests/components/group/conftest.py index 6fe34aca91c..e26e98598e6 100644 --- a/tests/components/group/conftest.py +++ b/tests/components/group/conftest.py @@ -1,2 +1,2 @@ """group conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/hassio/test_websocket_api.py b/tests/components/hassio/test_websocket_api.py index 18da5df13ea..dcf6b64d9e2 100644 --- a/tests/components/hassio/test_websocket_api.py +++ b/tests/components/hassio/test_websocket_api.py @@ -14,7 +14,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.setup import async_setup_component -from . import mock_all # noqa +from . import mock_all # noqa: F401 from tests.common import async_mock_signal diff --git a/tests/components/homeassistant/triggers/conftest.py b/tests/components/homeassistant/triggers/conftest.py index 5c983ba698e..77520a1bf68 100644 --- a/tests/components/homeassistant/triggers/conftest.py +++ b/tests/components/homeassistant/triggers/conftest.py @@ -1,3 +1,3 @@ """Conftest for HA triggers.""" -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 diff --git a/tests/components/homekit_controller/conftest.py b/tests/components/homekit_controller/conftest.py index 26adb25df21..4e095b1d2d9 100644 --- a/tests/components/homekit_controller/conftest.py +++ b/tests/components/homekit_controller/conftest.py @@ -8,7 +8,7 @@ import pytest import homeassistant.util.dt as dt_util -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 @pytest.fixture diff --git a/tests/components/homekit_controller/test_device_trigger.py b/tests/components/homekit_controller/test_device_trigger.py index 9de9a30f99f..ce771eac768 100644 --- a/tests/components/homekit_controller/test_device_trigger.py +++ b/tests/components/homekit_controller/test_device_trigger.py @@ -12,7 +12,7 @@ from tests.common import ( async_get_device_automations, async_mock_service, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 from tests.components.homekit_controller.common import setup_test_component diff --git a/tests/components/homematicip_cloud/conftest.py b/tests/components/homematicip_cloud/conftest.py index b05683d2361..aac7f60558c 100644 --- a/tests/components/homematicip_cloud/conftest.py +++ b/tests/components/homematicip_cloud/conftest.py @@ -25,7 +25,7 @@ from homeassistant.helpers.typing import ConfigType, HomeAssistantType from .helper import AUTH_TOKEN, HAPID, HAPPIN, HomeFactory from tests.common import MockConfigEntry -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 @pytest.fixture(name="mock_connection") diff --git a/tests/components/hue/conftest.py b/tests/components/hue/conftest.py index fc42babbb35..db45b9fcd4d 100644 --- a/tests/components/hue/conftest.py +++ b/tests/components/hue/conftest.py @@ -12,7 +12,7 @@ from homeassistant import config_entries from homeassistant.components import hue from homeassistant.components.hue import sensor_base as hue_sensor_base -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 @pytest.fixture(autouse=True) diff --git a/tests/components/hue/test_device_trigger.py b/tests/components/hue/test_device_trigger.py index 178cd7b09f1..5711c36da98 100644 --- a/tests/components/hue/test_device_trigger.py +++ b/tests/components/hue/test_device_trigger.py @@ -15,7 +15,7 @@ from tests.common import ( async_mock_service, mock_device_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 REMOTES_RESPONSE = {"7": HUE_TAP_REMOTE_1, "8": HUE_DIMMER_REMOTE_1} diff --git a/tests/components/humidifier/test_device_action.py b/tests/components/humidifier/test_device_action.py index 93b97408c39..1bf1c110ec6 100644 --- a/tests/components/humidifier/test_device_action.py +++ b/tests/components/humidifier/test_device_action.py @@ -16,7 +16,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/humidifier/test_device_condition.py b/tests/components/humidifier/test_device_condition.py index ad001d52ae0..d72d0e3b70e 100644 --- a/tests/components/humidifier/test_device_condition.py +++ b/tests/components/humidifier/test_device_condition.py @@ -17,7 +17,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/humidifier/test_device_trigger.py b/tests/components/humidifier/test_device_trigger.py index 7cd736b79f4..0b6154a84df 100644 --- a/tests/components/humidifier/test_device_trigger.py +++ b/tests/components/humidifier/test_device_trigger.py @@ -20,7 +20,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/hyperion/conftest.py b/tests/components/hyperion/conftest.py index 4eb59770fae..f971fa3c767 100644 --- a/tests/components/hyperion/conftest.py +++ b/tests/components/hyperion/conftest.py @@ -1,2 +1,2 @@ """hyperion conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/kodi/test_device_trigger.py b/tests/components/kodi/test_device_trigger.py index 0dd75b9c357..1edf7da6604 100644 --- a/tests/components/kodi/test_device_trigger.py +++ b/tests/components/kodi/test_device_trigger.py @@ -15,7 +15,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/light/test_device_action.py b/tests/components/light/test_device_action.py index 63670d9bfab..4760dfd1c53 100644 --- a/tests/components/light/test_device_action.py +++ b/tests/components/light/test_device_action.py @@ -21,7 +21,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/light/test_device_condition.py b/tests/components/light/test_device_condition.py index 2a877478b1e..d529c82bfa5 100644 --- a/tests/components/light/test_device_condition.py +++ b/tests/components/light/test_device_condition.py @@ -19,7 +19,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/light/test_device_trigger.py b/tests/components/light/test_device_trigger.py index fad39898467..1c9f6cf1454 100644 --- a/tests/components/light/test_device_trigger.py +++ b/tests/components/light/test_device_trigger.py @@ -19,7 +19,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/litejet/test_trigger.py b/tests/components/litejet/test_trigger.py index 216da9b54ef..50d7fb40715 100644 --- a/tests/components/litejet/test_trigger.py +++ b/tests/components/litejet/test_trigger.py @@ -13,7 +13,7 @@ import homeassistant.util.dt as dt_util from . import async_init_integration from tests.common import async_fire_time_changed, async_mock_service -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/lock/test_device_action.py b/tests/components/lock/test_device_action.py index 91cab9cdaf4..7d484ae96aa 100644 --- a/tests/components/lock/test_device_action.py +++ b/tests/components/lock/test_device_action.py @@ -15,7 +15,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/lock/test_device_condition.py b/tests/components/lock/test_device_condition.py index 949100daa55..b021ef23391 100644 --- a/tests/components/lock/test_device_condition.py +++ b/tests/components/lock/test_device_condition.py @@ -15,7 +15,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/lock/test_device_trigger.py b/tests/components/lock/test_device_trigger.py index 20674c483fd..272cf57bccd 100644 --- a/tests/components/lock/test_device_trigger.py +++ b/tests/components/lock/test_device_trigger.py @@ -15,7 +15,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/media_player/test_device_condition.py b/tests/components/media_player/test_device_condition.py index c7668d748af..63cdb1c55a7 100644 --- a/tests/components/media_player/test_device_condition.py +++ b/tests/components/media_player/test_device_condition.py @@ -21,7 +21,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/media_player/test_device_trigger.py b/tests/components/media_player/test_device_trigger.py index 93d9127f8b8..912edfc9c3c 100644 --- a/tests/components/media_player/test_device_trigger.py +++ b/tests/components/media_player/test_device_trigger.py @@ -21,7 +21,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/mochad/conftest.py b/tests/components/mochad/conftest.py index 9b095046d9c..bd543eae943 100644 --- a/tests/components/mochad/conftest.py +++ b/tests/components/mochad/conftest.py @@ -1,2 +1,2 @@ """mochad conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/mqtt/conftest.py b/tests/components/mqtt/conftest.py index 5f0acc3e5c2..6f70a4dbadb 100644 --- a/tests/components/mqtt/conftest.py +++ b/tests/components/mqtt/conftest.py @@ -1,2 +1,2 @@ """Test fixtures for mqtt component.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/mqtt/test_device_trigger.py b/tests/components/mqtt/test_device_trigger.py index 210dac19e0c..c1e012a90d6 100644 --- a/tests/components/mqtt/test_device_trigger.py +++ b/tests/components/mqtt/test_device_trigger.py @@ -16,7 +16,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/mqtt/test_trigger.py b/tests/components/mqtt/test_trigger.py index d0a86e08655..70ee5e9327c 100644 --- a/tests/components/mqtt/test_trigger.py +++ b/tests/components/mqtt/test_trigger.py @@ -8,7 +8,7 @@ from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, SERVICE_TURN_O from homeassistant.setup import async_setup_component from tests.common import async_fire_mqtt_message, async_mock_service, mock_component -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/number/test_device_action.py b/tests/components/number/test_device_action.py index 21c79a9f741..a981a331e7e 100644 --- a/tests/components/number/test_device_action.py +++ b/tests/components/number/test_device_action.py @@ -15,7 +15,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/ozw/conftest.py b/tests/components/ozw/conftest.py index a59388f118f..1df365054d4 100644 --- a/tests/components/ozw/conftest.py +++ b/tests/components/ozw/conftest.py @@ -9,7 +9,7 @@ from homeassistant.config_entries import ENTRY_STATE_LOADED from .common import MQTTMessage from tests.common import MockConfigEntry, load_fixture -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 @pytest.fixture(name="generic_data", scope="session") diff --git a/tests/components/philips_js/test_device_trigger.py b/tests/components/philips_js/test_device_trigger.py index ebda40f13e5..e0ee10c6abb 100644 --- a/tests/components/philips_js/test_device_trigger.py +++ b/tests/components/philips_js/test_device_trigger.py @@ -10,7 +10,7 @@ from tests.common import ( async_get_device_automations, async_mock_service, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/remote/test_device_action.py b/tests/components/remote/test_device_action.py index 17165639e25..7cd5a632982 100644 --- a/tests/components/remote/test_device_action.py +++ b/tests/components/remote/test_device_action.py @@ -16,7 +16,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/remote/test_device_condition.py b/tests/components/remote/test_device_condition.py index c6a2b3f0c52..12cf0e05493 100644 --- a/tests/components/remote/test_device_condition.py +++ b/tests/components/remote/test_device_condition.py @@ -19,7 +19,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/remote/test_device_trigger.py b/tests/components/remote/test_device_trigger.py index eccf96c04f6..616c356936c 100644 --- a/tests/components/remote/test_device_trigger.py +++ b/tests/components/remote/test_device_trigger.py @@ -19,7 +19,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/rflink/conftest.py b/tests/components/rflink/conftest.py index f33c8ab89db..dcaeb0a5e01 100644 --- a/tests/components/rflink/conftest.py +++ b/tests/components/rflink/conftest.py @@ -1,2 +1,2 @@ """rflink conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/rfxtrx/conftest.py b/tests/components/rfxtrx/conftest.py index ee695bee9dd..06e37545d25 100644 --- a/tests/components/rfxtrx/conftest.py +++ b/tests/components/rfxtrx/conftest.py @@ -9,7 +9,7 @@ from homeassistant.components.rfxtrx import DOMAIN from homeassistant.util.dt import utcnow from tests.common import MockConfigEntry, async_fire_time_changed -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 def create_rfx_test_cfg(device="abcd", automatic_add=False, devices=None): diff --git a/tests/components/ring/conftest.py b/tests/components/ring/conftest.py index f2e05189dea..cda662aab64 100644 --- a/tests/components/ring/conftest.py +++ b/tests/components/ring/conftest.py @@ -5,7 +5,7 @@ import pytest import requests_mock from tests.common import load_fixture -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 @pytest.fixture(name="requests_mock") diff --git a/tests/components/search/test_init.py b/tests/components/search/test_init.py index 5710fa04698..6f6a7793981 100644 --- a/tests/components/search/test_init.py +++ b/tests/components/search/test_init.py @@ -3,7 +3,7 @@ from homeassistant.components import search from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 async def test_search(hass): diff --git a/tests/components/sensor/test_device_condition.py b/tests/components/sensor/test_device_condition.py index 9a023d6f5ad..80c20cb8e2d 100644 --- a/tests/components/sensor/test_device_condition.py +++ b/tests/components/sensor/test_device_condition.py @@ -16,7 +16,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 from tests.testing_config.custom_components.test.sensor import DEVICE_CLASSES diff --git a/tests/components/sensor/test_device_trigger.py b/tests/components/sensor/test_device_trigger.py index d5755ac3288..eb38060f0dd 100644 --- a/tests/components/sensor/test_device_trigger.py +++ b/tests/components/sensor/test_device_trigger.py @@ -20,7 +20,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 from tests.testing_config.custom_components.test.sensor import DEVICE_CLASSES diff --git a/tests/components/smartthings/conftest.py b/tests/components/smartthings/conftest.py index b99309bea52..d145e7644a9 100644 --- a/tests/components/smartthings/conftest.py +++ b/tests/components/smartthings/conftest.py @@ -47,7 +47,7 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 COMPONENT_PREFIX = "homeassistant.components.smartthings." diff --git a/tests/components/sun/test_trigger.py b/tests/components/sun/test_trigger.py index a288150517d..fd01daac5b7 100644 --- a/tests/components/sun/test_trigger.py +++ b/tests/components/sun/test_trigger.py @@ -18,7 +18,7 @@ from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed, async_mock_service, mock_component -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 ORIG_TIME_ZONE = dt_util.DEFAULT_TIME_ZONE diff --git a/tests/components/switch/conftest.py b/tests/components/switch/conftest.py index d69757a9b1b..11f1563b723 100644 --- a/tests/components/switch/conftest.py +++ b/tests/components/switch/conftest.py @@ -1,2 +1,2 @@ """switch conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/switch/test_device_action.py b/tests/components/switch/test_device_action.py index 4da401a215c..2a98cd2fad4 100644 --- a/tests/components/switch/test_device_action.py +++ b/tests/components/switch/test_device_action.py @@ -16,7 +16,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/switch/test_device_condition.py b/tests/components/switch/test_device_condition.py index 67ba3e8e38e..9273610dee9 100644 --- a/tests/components/switch/test_device_condition.py +++ b/tests/components/switch/test_device_condition.py @@ -19,7 +19,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/switch/test_device_trigger.py b/tests/components/switch/test_device_trigger.py index 34817b687f8..d958dd21911 100644 --- a/tests/components/switch/test_device_trigger.py +++ b/tests/components/switch/test_device_trigger.py @@ -19,7 +19,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/tag/test_trigger.py b/tests/components/tag/test_trigger.py index 9a97d95e7d5..5563b5644a4 100644 --- a/tests/components/tag/test_trigger.py +++ b/tests/components/tag/test_trigger.py @@ -9,7 +9,7 @@ from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF from homeassistant.setup import async_setup_component from tests.common import async_mock_service -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/tasmota/conftest.py b/tests/components/tasmota/conftest.py index 3c530a93d1e..5c0b7d315f9 100644 --- a/tests/components/tasmota/conftest.py +++ b/tests/components/tasmota/conftest.py @@ -17,7 +17,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 @pytest.fixture diff --git a/tests/components/tasmota/test_device_trigger.py b/tests/components/tasmota/test_device_trigger.py index ec8744881c5..1fa1c629a33 100644 --- a/tests/components/tasmota/test_device_trigger.py +++ b/tests/components/tasmota/test_device_trigger.py @@ -18,7 +18,7 @@ from tests.common import ( async_fire_mqtt_message, async_get_device_automations, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 async def test_get_triggers_btn(hass, device_reg, entity_reg, mqtt_mock, setup_tasmota): diff --git a/tests/components/template/conftest.py b/tests/components/template/conftest.py index 8e3491b160a..0848200b35d 100644 --- a/tests/components/template/conftest.py +++ b/tests/components/template/conftest.py @@ -1,2 +1,2 @@ """template conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/template/test_trigger.py b/tests/components/template/test_trigger.py index 55311005201..b40eee7cab3 100644 --- a/tests/components/template/test_trigger.py +++ b/tests/components/template/test_trigger.py @@ -18,7 +18,7 @@ from tests.common import ( async_mock_service, mock_component, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/tplink/conftest.py b/tests/components/tplink/conftest.py index d1800513486..61b242c5d2e 100644 --- a/tests/components/tplink/conftest.py +++ b/tests/components/tplink/conftest.py @@ -1,2 +1,2 @@ """tplink conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/tradfri/conftest.py b/tests/components/tradfri/conftest.py index 93675a9e4d1..54a8625f23c 100644 --- a/tests/components/tradfri/conftest.py +++ b/tests/components/tradfri/conftest.py @@ -5,7 +5,7 @@ import pytest from . import MOCK_GATEWAY_ID -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 # pylint: disable=protected-access diff --git a/tests/components/vacuum/test_device_action.py b/tests/components/vacuum/test_device_action.py index 3edeaba2a41..aa5dc1786f7 100644 --- a/tests/components/vacuum/test_device_action.py +++ b/tests/components/vacuum/test_device_action.py @@ -14,7 +14,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/vacuum/test_device_condition.py b/tests/components/vacuum/test_device_condition.py index 3dc7a628741..84a25d183b8 100644 --- a/tests/components/vacuum/test_device_condition.py +++ b/tests/components/vacuum/test_device_condition.py @@ -19,7 +19,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/vacuum/test_device_trigger.py b/tests/components/vacuum/test_device_trigger.py index e3f615891e6..fd1918b8e69 100644 --- a/tests/components/vacuum/test_device_trigger.py +++ b/tests/components/vacuum/test_device_trigger.py @@ -14,7 +14,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/vera/conftest.py b/tests/components/vera/conftest.py index da027207748..e5d2dac1dbf 100644 --- a/tests/components/vera/conftest.py +++ b/tests/components/vera/conftest.py @@ -5,7 +5,7 @@ import pytest from .common import ComponentFactory -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 @pytest.fixture() diff --git a/tests/components/water_heater/test_device_action.py b/tests/components/water_heater/test_device_action.py index 06bd43ec654..060d9ead29f 100644 --- a/tests/components/water_heater/test_device_action.py +++ b/tests/components/water_heater/test_device_action.py @@ -14,7 +14,7 @@ from tests.common import ( mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/webhook/test_trigger.py b/tests/components/webhook/test_trigger.py index 2a22c330e14..4909c668dfe 100644 --- a/tests/components/webhook/test_trigger.py +++ b/tests/components/webhook/test_trigger.py @@ -6,7 +6,7 @@ import pytest from homeassistant.core import callback from homeassistant.setup import async_setup_component -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture(autouse=True) diff --git a/tests/components/wilight/conftest.py b/tests/components/wilight/conftest.py index a8fd13553dd..b20a7757e22 100644 --- a/tests/components/wilight/conftest.py +++ b/tests/components/wilight/conftest.py @@ -1,2 +1,2 @@ """wilight conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/wled/conftest.py b/tests/components/wled/conftest.py index 31b71a92f19..7b8eb9cd50c 100644 --- a/tests/components/wled/conftest.py +++ b/tests/components/wled/conftest.py @@ -1,2 +1,2 @@ """wled conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/yeelight/conftest.py b/tests/components/yeelight/conftest.py index 3e65a60f374..f418e90e848 100644 --- a/tests/components/yeelight/conftest.py +++ b/tests/components/yeelight/conftest.py @@ -1,2 +1,2 @@ """yeelight conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/zerproc/conftest.py b/tests/components/zerproc/conftest.py index b4c35bebc71..9d6bd9dea23 100644 --- a/tests/components/zerproc/conftest.py +++ b/tests/components/zerproc/conftest.py @@ -1,2 +1,2 @@ """zerproc conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/zha/conftest.py b/tests/components/zha/conftest.py index 57241b9bb74..b3ac4aff16e 100644 --- a/tests/components/zha/conftest.py +++ b/tests/components/zha/conftest.py @@ -16,7 +16,7 @@ from homeassistant.setup import async_setup_component from .common import FakeDevice, FakeEndpoint, get_zha_gateway from tests.common import MockConfigEntry -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 FIXTURE_GRP_ID = 0x1001 FIXTURE_GRP_NAME = "fixture group" diff --git a/tests/components/zha/test_device_action.py b/tests/components/zha/test_device_action.py index 316d475f17f..1160995e8d7 100644 --- a/tests/components/zha/test_device_action.py +++ b/tests/components/zha/test_device_action.py @@ -16,7 +16,7 @@ from homeassistant.helpers.device_registry import async_get_registry from homeassistant.setup import async_setup_component from tests.common import async_mock_service, mock_coro -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 SHORT_PRESS = "remote_button_short_press" COMMAND = "command" diff --git a/tests/components/zha/test_device_trigger.py b/tests/components/zha/test_device_trigger.py index 96ee5520e2a..ec947846801 100644 --- a/tests/components/zha/test_device_trigger.py +++ b/tests/components/zha/test_device_trigger.py @@ -19,7 +19,7 @@ from tests.common import ( async_get_device_automations, async_mock_service, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 ON = 1 OFF = 0 diff --git a/tests/components/zone/test_trigger.py b/tests/components/zone/test_trigger.py index d7f5857b466..52fbb55ba97 100644 --- a/tests/components/zone/test_trigger.py +++ b/tests/components/zone/test_trigger.py @@ -7,7 +7,7 @@ from homeassistant.core import Context from homeassistant.setup import async_setup_component from tests.common import async_mock_service, mock_component -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/zwave/conftest.py b/tests/components/zwave/conftest.py index 13da12c67ff..027d3a82ea2 100644 --- a/tests/components/zwave/conftest.py +++ b/tests/components/zwave/conftest.py @@ -5,7 +5,7 @@ import pytest from homeassistant.components.zwave import const -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 from tests.mock.zwave import MockNetwork, MockNode, MockOption, MockValue From ab53b49d3f29e3bd0950d3d9ce9a76cf8a554787 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Tue, 2 Mar 2021 06:52:00 -0500 Subject: [PATCH 116/831] Clean up constants (#46948) * Clean up constants * clean up humidifier constants * fix tests * fix prometheus tests Co-authored-by: Tobias Sauerwein --- .../components/google_assistant/trait.py | 7 ++++--- homeassistant/components/humidifier/const.py | 2 -- .../components/humidifier/device_action.py | 7 ++++--- .../components/humidifier/device_condition.py | 5 +++-- homeassistant/components/humidifier/intent.py | 3 +-- .../components/humidifier/reproduce_state.py | 16 +++++++-------- .../components/input_select/__init__.py | 2 +- homeassistant/components/ipp/const.py | 1 - homeassistant/components/ipp/sensor.py | 3 +-- homeassistant/components/melcloud/sensor.py | 4 ++-- .../components/mobile_app/__init__.py | 3 +-- .../components/mobile_app/config_flow.py | 3 ++- homeassistant/components/mobile_app/const.py | 1 - .../components/mobile_app/device_tracker.py | 8 ++++++-- .../components/mobile_app/helpers.py | 8 ++++++-- .../components/mobile_app/http_api.py | 3 +-- .../components/mobile_app/webhook.py | 2 +- homeassistant/components/netatmo/const.py | 1 - homeassistant/components/ombi/__init__.py | 2 +- homeassistant/components/ombi/const.py | 1 - .../components/prometheus/__init__.py | 2 +- homeassistant/components/pushsafer/notify.py | 3 +-- homeassistant/components/remote/__init__.py | 2 +- homeassistant/components/sesame/lock.py | 2 +- tests/components/demo/test_humidifier.py | 2 +- .../google_assistant/test_google_assistant.py | 2 +- .../components/google_assistant/test_trait.py | 3 ++- .../humidifier/test_device_condition.py | 20 +++++++------------ .../humidifier/test_device_trigger.py | 8 ++++---- tests/components/humidifier/test_intent.py | 2 +- .../humidifier/test_reproduce_state.py | 9 +++++++-- 31 files changed, 69 insertions(+), 68 deletions(-) diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 8b0bde09010..be7ceb98ad3 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -27,6 +27,7 @@ from homeassistant.const import ( ATTR_CODE, ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, + ATTR_MODE, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, CAST_APP_ID_HOMEASSISTANT, @@ -1424,8 +1425,8 @@ class ModesTrait(_Trait): elif self.state.domain == input_select.DOMAIN: mode_settings["option"] = self.state.state elif self.state.domain == humidifier.DOMAIN: - if humidifier.ATTR_MODE in attrs: - mode_settings["mode"] = attrs.get(humidifier.ATTR_MODE) + if ATTR_MODE in attrs: + mode_settings["mode"] = attrs.get(ATTR_MODE) elif self.state.domain == light.DOMAIN: if light.ATTR_EFFECT in attrs: mode_settings["effect"] = attrs.get(light.ATTR_EFFECT) @@ -1460,7 +1461,7 @@ class ModesTrait(_Trait): humidifier.DOMAIN, humidifier.SERVICE_SET_MODE, { - humidifier.ATTR_MODE: requested_mode, + ATTR_MODE: requested_mode, ATTR_ENTITY_ID: self.state.entity_id, }, blocking=True, diff --git a/homeassistant/components/humidifier/const.py b/homeassistant/components/humidifier/const.py index 7e70c51df28..c2508770187 100644 --- a/homeassistant/components/humidifier/const.py +++ b/homeassistant/components/humidifier/const.py @@ -1,6 +1,4 @@ """Provides the constants needed for component.""" -from homeassistant.const import ATTR_MODE # noqa: F401 pylint: disable=unused-import - MODE_NORMAL = "normal" MODE_ECO = "eco" MODE_AWAY = "away" diff --git a/homeassistant/components/humidifier/device_action.py b/homeassistant/components/humidifier/device_action.py index 6bccd375207..c702a7c2a2d 100644 --- a/homeassistant/components/humidifier/device_action.py +++ b/homeassistant/components/humidifier/device_action.py @@ -6,6 +6,7 @@ import voluptuous as vol from homeassistant.components.device_automation import toggle_entity from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_MODE, ATTR_SUPPORTED_FEATURES, CONF_DEVICE_ID, CONF_DOMAIN, @@ -30,7 +31,7 @@ SET_MODE_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( { vol.Required(CONF_TYPE): "set_mode", vol.Required(CONF_ENTITY_ID): cv.entity_domain(DOMAIN), - vol.Required(const.ATTR_MODE): cv.string, + vol.Required(ATTR_MODE): cv.string, } ) @@ -90,7 +91,7 @@ async def async_call_action_from_config( service_data[const.ATTR_HUMIDITY] = config[const.ATTR_HUMIDITY] elif config[CONF_TYPE] == "set_mode": service = const.SERVICE_SET_MODE - service_data[const.ATTR_MODE] = config[const.ATTR_MODE] + service_data[ATTR_MODE] = config[ATTR_MODE] else: return await toggle_entity.async_call_action_from_config( hass, config, variables, context, DOMAIN @@ -115,7 +116,7 @@ async def async_get_action_capabilities(hass, config): available_modes = state.attributes.get(const.ATTR_AVAILABLE_MODES, []) else: available_modes = [] - fields[vol.Required(const.ATTR_MODE)] = vol.In(available_modes) + fields[vol.Required(ATTR_MODE)] = vol.In(available_modes) else: return {} diff --git a/homeassistant/components/humidifier/device_condition.py b/homeassistant/components/humidifier/device_condition.py index 714a51ab016..86a049d838b 100644 --- a/homeassistant/components/humidifier/device_condition.py +++ b/homeassistant/components/humidifier/device_condition.py @@ -6,6 +6,7 @@ import voluptuous as vol from homeassistant.components.device_automation import toggle_entity from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_MODE, ATTR_SUPPORTED_FEATURES, CONF_CONDITION, CONF_DEVICE_ID, @@ -28,7 +29,7 @@ MODE_CONDITION = DEVICE_CONDITION_BASE_SCHEMA.extend( { vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Required(CONF_TYPE): "is_mode", - vol.Required(const.ATTR_MODE): str, + vol.Required(ATTR_MODE): str, } ) @@ -72,7 +73,7 @@ def async_condition_from_config( config = CONDITION_SCHEMA(config) if config[CONF_TYPE] == "is_mode": - attribute = const.ATTR_MODE + attribute = ATTR_MODE else: return toggle_entity.async_condition_from_config(config) diff --git a/homeassistant/components/humidifier/intent.py b/homeassistant/components/humidifier/intent.py index fafbb0a494a..d9ecafbc537 100644 --- a/homeassistant/components/humidifier/intent.py +++ b/homeassistant/components/humidifier/intent.py @@ -1,7 +1,7 @@ """Intents for the humidifier integration.""" import voluptuous as vol -from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF +from homeassistant.const import ATTR_ENTITY_ID, ATTR_MODE, STATE_OFF from homeassistant.core import HomeAssistant from homeassistant.helpers import intent import homeassistant.helpers.config_validation as cv @@ -9,7 +9,6 @@ import homeassistant.helpers.config_validation as cv from . import ( ATTR_AVAILABLE_MODES, ATTR_HUMIDITY, - ATTR_MODE, DOMAIN, SERVICE_SET_HUMIDITY, SERVICE_SET_MODE, diff --git a/homeassistant/components/humidifier/reproduce_state.py b/homeassistant/components/humidifier/reproduce_state.py index e9b1777d63f..f6fff75203e 100644 --- a/homeassistant/components/humidifier/reproduce_state.py +++ b/homeassistant/components/humidifier/reproduce_state.py @@ -3,17 +3,17 @@ import asyncio import logging from typing import Any, Dict, Iterable, Optional -from homeassistant.const import SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, STATE_ON +from homeassistant.const import ( + ATTR_MODE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, +) from homeassistant.core import Context, State from homeassistant.helpers.typing import HomeAssistantType -from .const import ( - ATTR_HUMIDITY, - ATTR_MODE, - DOMAIN, - SERVICE_SET_HUMIDITY, - SERVICE_SET_MODE, -) +from .const import ATTR_HUMIDITY, DOMAIN, SERVICE_SET_HUMIDITY, SERVICE_SET_MODE _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/input_select/__init__.py b/homeassistant/components/input_select/__init__.py index f6831dc3e88..5c10c33421a 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -8,6 +8,7 @@ import voluptuous as vol from homeassistant.const import ( ATTR_EDITABLE, + ATTR_OPTION, CONF_ICON, CONF_ID, CONF_NAME, @@ -29,7 +30,6 @@ DOMAIN = "input_select" CONF_INITIAL = "initial" CONF_OPTIONS = "options" -ATTR_OPTION = "option" ATTR_OPTIONS = "options" ATTR_CYCLE = "cycle" diff --git a/homeassistant/components/ipp/const.py b/homeassistant/components/ipp/const.py index a5345f4145e..d482f2d73e4 100644 --- a/homeassistant/components/ipp/const.py +++ b/homeassistant/components/ipp/const.py @@ -7,7 +7,6 @@ DOMAIN = "ipp" ATTR_COMMAND_SET = "command_set" ATTR_IDENTIFIERS = "identifiers" ATTR_INFO = "info" -ATTR_LOCATION = "location" ATTR_MANUFACTURER = "manufacturer" ATTR_MARKER_TYPE = "marker_type" ATTR_MARKER_LOW_LEVEL = "marker_low_level" diff --git a/homeassistant/components/ipp/sensor.py b/homeassistant/components/ipp/sensor.py index 6991e6d19ea..a278f6e8ef9 100644 --- a/homeassistant/components/ipp/sensor.py +++ b/homeassistant/components/ipp/sensor.py @@ -3,7 +3,7 @@ from datetime import timedelta from typing import Any, Callable, Dict, List, Optional from homeassistant.config_entries import ConfigEntry -from homeassistant.const import DEVICE_CLASS_TIMESTAMP, PERCENTAGE +from homeassistant.const import ATTR_LOCATION, DEVICE_CLASS_TIMESTAMP, PERCENTAGE from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util.dt import utcnow @@ -12,7 +12,6 @@ from . import IPPDataUpdateCoordinator, IPPEntity from .const import ( ATTR_COMMAND_SET, ATTR_INFO, - ATTR_LOCATION, ATTR_MARKER_HIGH_LEVEL, ATTR_MARKER_LOW_LEVEL, ATTR_MARKER_TYPE, diff --git a/homeassistant/components/melcloud/sensor.py b/homeassistant/components/melcloud/sensor.py index c96433f17df..bd85d1b13bc 100644 --- a/homeassistant/components/melcloud/sensor.py +++ b/homeassistant/components/melcloud/sensor.py @@ -3,6 +3,8 @@ from pymelcloud import DEVICE_TYPE_ATA, DEVICE_TYPE_ATW from pymelcloud.atw_device import Zone from homeassistant.const import ( + ATTR_DEVICE_CLASS, + ATTR_ICON, DEVICE_CLASS_TEMPERATURE, ENERGY_KILO_WATT_HOUR, TEMP_CELSIUS, @@ -13,9 +15,7 @@ from . import MelCloudDevice from .const import DOMAIN ATTR_MEASUREMENT_NAME = "measurement_name" -ATTR_ICON = "icon" ATTR_UNIT = "unit" -ATTR_DEVICE_CLASS = "device_class" ATTR_VALUE_FN = "value_fn" ATTR_ENABLED_FN = "enabled" diff --git a/homeassistant/components/mobile_app/__init__.py b/homeassistant/components/mobile_app/__init__.py index 54fa3398ee2..e1476270844 100644 --- a/homeassistant/components/mobile_app/__init__.py +++ b/homeassistant/components/mobile_app/__init__.py @@ -6,12 +6,11 @@ from homeassistant.components.webhook import ( async_register as webhook_register, async_unregister as webhook_unregister, ) -from homeassistant.const import CONF_WEBHOOK_ID +from homeassistant.const import ATTR_DEVICE_ID, CONF_WEBHOOK_ID from homeassistant.helpers import device_registry as dr, discovery from homeassistant.helpers.typing import ConfigType, HomeAssistantType from .const import ( - ATTR_DEVICE_ID, ATTR_DEVICE_NAME, ATTR_MANUFACTURER, ATTR_MODEL, diff --git a/homeassistant/components/mobile_app/config_flow.py b/homeassistant/components/mobile_app/config_flow.py index 80b6c8db5e1..752ef86d68d 100644 --- a/homeassistant/components/mobile_app/config_flow.py +++ b/homeassistant/components/mobile_app/config_flow.py @@ -3,9 +3,10 @@ import uuid from homeassistant import config_entries from homeassistant.components import person +from homeassistant.const import ATTR_DEVICE_ID from homeassistant.helpers import entity_registry as er -from .const import ATTR_APP_ID, ATTR_DEVICE_ID, ATTR_DEVICE_NAME, CONF_USER_ID, DOMAIN +from .const import ATTR_APP_ID, ATTR_DEVICE_NAME, CONF_USER_ID, DOMAIN @config_entries.HANDLERS.register(DOMAIN) diff --git a/homeassistant/components/mobile_app/const.py b/homeassistant/components/mobile_app/const.py index b603e117c4c..af828ce423e 100644 --- a/homeassistant/components/mobile_app/const.py +++ b/homeassistant/components/mobile_app/const.py @@ -20,7 +20,6 @@ ATTR_APP_ID = "app_id" ATTR_APP_NAME = "app_name" ATTR_APP_VERSION = "app_version" ATTR_CONFIG_ENTRY_ID = "entry_id" -ATTR_DEVICE_ID = "device_id" ATTR_DEVICE_NAME = "device_name" ATTR_MANUFACTURER = "manufacturer" ATTR_MODEL = "model" diff --git a/homeassistant/components/mobile_app/device_tracker.py b/homeassistant/components/mobile_app/device_tracker.py index d2e987066ef..f0cd30074fa 100644 --- a/homeassistant/components/mobile_app/device_tracker.py +++ b/homeassistant/components/mobile_app/device_tracker.py @@ -7,14 +7,18 @@ from homeassistant.components.device_tracker import ( ) from homeassistant.components.device_tracker.config_entry import TrackerEntity from homeassistant.components.device_tracker.const import SOURCE_TYPE_GPS -from homeassistant.const import ATTR_BATTERY_LEVEL, ATTR_LATITUDE, ATTR_LONGITUDE +from homeassistant.const import ( + ATTR_BATTERY_LEVEL, + ATTR_DEVICE_ID, + ATTR_LATITUDE, + ATTR_LONGITUDE, +) from homeassistant.core import callback from homeassistant.helpers.restore_state import RestoreEntity from .const import ( ATTR_ALTITUDE, ATTR_COURSE, - ATTR_DEVICE_ID, ATTR_DEVICE_NAME, ATTR_SPEED, ATTR_VERTICAL_ACCURACY, diff --git a/homeassistant/components/mobile_app/helpers.py b/homeassistant/components/mobile_app/helpers.py index a9079be4f04..fed322df464 100644 --- a/homeassistant/components/mobile_app/helpers.py +++ b/homeassistant/components/mobile_app/helpers.py @@ -7,7 +7,12 @@ from aiohttp.web import Response, json_response from nacl.encoding import Base64Encoder from nacl.secret import SecretBox -from homeassistant.const import CONTENT_TYPE_JSON, HTTP_BAD_REQUEST, HTTP_OK +from homeassistant.const import ( + ATTR_DEVICE_ID, + CONTENT_TYPE_JSON, + HTTP_BAD_REQUEST, + HTTP_OK, +) from homeassistant.core import Context from homeassistant.helpers.json import JSONEncoder from homeassistant.helpers.typing import HomeAssistantType @@ -17,7 +22,6 @@ from .const import ( ATTR_APP_ID, ATTR_APP_NAME, ATTR_APP_VERSION, - ATTR_DEVICE_ID, ATTR_DEVICE_NAME, ATTR_MANUFACTURER, ATTR_MODEL, diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py index a5a96b83bc6..4bd8d0cd76b 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -9,7 +9,7 @@ import voluptuous as vol from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.data_validator import RequestDataValidator -from homeassistant.const import CONF_WEBHOOK_ID, HTTP_CREATED +from homeassistant.const import ATTR_DEVICE_ID, CONF_WEBHOOK_ID, HTTP_CREATED from homeassistant.helpers import config_validation as cv from homeassistant.util import slugify @@ -18,7 +18,6 @@ from .const import ( ATTR_APP_ID, ATTR_APP_NAME, ATTR_APP_VERSION, - ATTR_DEVICE_ID, ATTR_DEVICE_NAME, ATTR_MANUFACTURER, ATTR_MODEL, diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 3044f2df212..a7b3f38a015 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -23,6 +23,7 @@ from homeassistant.components.frontend import MANIFEST_JSON from homeassistant.components.sensor import DEVICE_CLASSES as SENSOR_CLASSES from homeassistant.components.zone.const import DOMAIN as ZONE_DOMAIN from homeassistant.const import ( + ATTR_DEVICE_ID, ATTR_DOMAIN, ATTR_SERVICE, ATTR_SERVICE_DATA, @@ -49,7 +50,6 @@ from .const import ( ATTR_APP_VERSION, ATTR_CAMERA_ENTITY_ID, ATTR_COURSE, - ATTR_DEVICE_ID, ATTR_DEVICE_NAME, ATTR_EVENT_DATA, ATTR_EVENT_TYPE, diff --git a/homeassistant/components/netatmo/const.py b/homeassistant/components/netatmo/const.py index baee3e4035c..ab268b8703b 100644 --- a/homeassistant/components/netatmo/const.py +++ b/homeassistant/components/netatmo/const.py @@ -72,7 +72,6 @@ DEFAULT_DISCOVERY = True DEFAULT_WEBHOOKS = False ATTR_PSEUDO = "pseudo" -ATTR_NAME = "name" ATTR_EVENT_TYPE = "event_type" ATTR_HEATING_POWER_REQUEST = "heating_power_request" ATTR_HOME_ID = "home_id" diff --git a/homeassistant/components/ombi/__init__.py b/homeassistant/components/ombi/__init__.py index dcd8f264161..5db46658eb1 100644 --- a/homeassistant/components/ombi/__init__.py +++ b/homeassistant/components/ombi/__init__.py @@ -5,6 +5,7 @@ import pyombi import voluptuous as vol from homeassistant.const import ( + ATTR_NAME, CONF_API_KEY, CONF_HOST, CONF_PASSWORD, @@ -15,7 +16,6 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv from .const import ( - ATTR_NAME, ATTR_SEASON, CONF_URLBASE, DEFAULT_PORT, diff --git a/homeassistant/components/ombi/const.py b/homeassistant/components/ombi/const.py index 42b58e7f50d..784b46a99b7 100644 --- a/homeassistant/components/ombi/const.py +++ b/homeassistant/components/ombi/const.py @@ -1,5 +1,4 @@ """Support for Ombi.""" -ATTR_NAME = "name" ATTR_SEASON = "season" CONF_URLBASE = "urlbase" diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py index bd9a6e35276..bbab9af83e8 100644 --- a/homeassistant/components/prometheus/__init__.py +++ b/homeassistant/components/prometheus/__init__.py @@ -16,12 +16,12 @@ from homeassistant.components.http import HomeAssistantView from homeassistant.components.humidifier.const import ( ATTR_AVAILABLE_MODES, ATTR_HUMIDITY, - ATTR_MODE, ) from homeassistant.const import ( ATTR_BATTERY_LEVEL, ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, + ATTR_MODE, ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT, CONTENT_TYPE_TEXT_PLAIN, diff --git a/homeassistant/components/pushsafer/notify.py b/homeassistant/components/pushsafer/notify.py index 12735764b4b..bec85409010 100644 --- a/homeassistant/components/pushsafer/notify.py +++ b/homeassistant/components/pushsafer/notify.py @@ -15,7 +15,7 @@ from homeassistant.components.notify import ( PLATFORM_SCHEMA, BaseNotificationService, ) -from homeassistant.const import HTTP_OK +from homeassistant.const import ATTR_ICON, HTTP_OK import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -28,7 +28,6 @@ CONF_TIMEOUT = 15 # Top level attributes in 'data' ATTR_SOUND = "sound" ATTR_VIBRATION = "vibration" -ATTR_ICON = "icon" ATTR_ICONCOLOR = "iconcolor" ATTR_URL = "url" ATTR_URLTITLE = "urltitle" diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index 44a318988b2..b3b84669ed1 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -8,6 +8,7 @@ import voluptuous as vol from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( + ATTR_COMMAND, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, @@ -29,7 +30,6 @@ from homeassistant.loader import bind_hass _LOGGER = logging.getLogger(__name__) ATTR_ACTIVITY = "activity" -ATTR_COMMAND = "command" ATTR_COMMAND_TYPE = "command_type" ATTR_DEVICE = "device" ATTR_NUM_REPEATS = "num_repeats" diff --git a/homeassistant/components/sesame/lock.py b/homeassistant/components/sesame/lock.py index 0ad3d87a171..9c86c262235 100644 --- a/homeassistant/components/sesame/lock.py +++ b/homeassistant/components/sesame/lock.py @@ -7,6 +7,7 @@ import voluptuous as vol from homeassistant.components.lock import PLATFORM_SCHEMA, LockEntity from homeassistant.const import ( ATTR_BATTERY_LEVEL, + ATTR_DEVICE_ID, CONF_API_KEY, STATE_LOCKED, STATE_UNLOCKED, @@ -14,7 +15,6 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType -ATTR_DEVICE_ID = "device_id" ATTR_SERIAL_NO = "serial" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Required(CONF_API_KEY): cv.string}) diff --git a/tests/components/demo/test_humidifier.py b/tests/components/demo/test_humidifier.py index ba2bd60f8f2..cd400f98347 100644 --- a/tests/components/demo/test_humidifier.py +++ b/tests/components/demo/test_humidifier.py @@ -7,7 +7,6 @@ from homeassistant.components.humidifier.const import ( ATTR_HUMIDITY, ATTR_MAX_HUMIDITY, ATTR_MIN_HUMIDITY, - ATTR_MODE, DOMAIN, MODE_AWAY, MODE_ECO, @@ -16,6 +15,7 @@ from homeassistant.components.humidifier.const import ( ) from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_MODE, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, diff --git a/tests/components/google_assistant/test_google_assistant.py b/tests/components/google_assistant/test_google_assistant.py index cd268a5c2d9..bc9195264d9 100644 --- a/tests/components/google_assistant/test_google_assistant.py +++ b/tests/components/google_assistant/test_google_assistant.py @@ -473,4 +473,4 @@ async def test_execute_request(hass_fixture, assistant_client, auth_header): assert dehumidifier.attributes.get(humidifier.ATTR_HUMIDITY) == 45 hygrostat = hass_fixture.states.get("humidifier.hygrostat") - assert hygrostat.attributes.get(humidifier.ATTR_MODE) == "eco" + assert hygrostat.attributes.get(const.ATTR_MODE) == "eco" diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index ba189020513..43e9c30f91a 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -31,6 +31,7 @@ from homeassistant.const import ( ATTR_ASSUMED_STATE, ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, + ATTR_MODE, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, SERVICE_TURN_OFF, @@ -1778,7 +1779,7 @@ async def test_modes_humidifier(hass): humidifier.ATTR_MIN_HUMIDITY: 30, humidifier.ATTR_MAX_HUMIDITY: 99, humidifier.ATTR_HUMIDITY: 50, - humidifier.ATTR_MODE: humidifier.MODE_AUTO, + ATTR_MODE: humidifier.MODE_AUTO, }, ), BASIC_CONFIG, diff --git a/tests/components/humidifier/test_device_condition.py b/tests/components/humidifier/test_device_condition.py index d72d0e3b70e..8b356552233 100644 --- a/tests/components/humidifier/test_device_condition.py +++ b/tests/components/humidifier/test_device_condition.py @@ -4,7 +4,7 @@ import voluptuous_serialize import homeassistant.components.automation as automation from homeassistant.components.humidifier import DOMAIN, const, device_condition -from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.const import ATTR_MODE, STATE_OFF, STATE_ON from homeassistant.helpers import config_validation as cv, device_registry from homeassistant.setup import async_setup_component @@ -51,7 +51,7 @@ async def test_get_conditions(hass, device_reg, entity_reg): f"{DOMAIN}.test_5678", STATE_ON, { - const.ATTR_MODE: const.MODE_AWAY, + ATTR_MODE: const.MODE_AWAY, const.ATTR_AVAILABLE_MODES: [const.MODE_HOME, const.MODE_AWAY], }, ) @@ -98,7 +98,7 @@ async def test_get_conditions_toggle_only(hass, device_reg, entity_reg): f"{DOMAIN}.test_5678", STATE_ON, { - const.ATTR_MODE: const.MODE_AWAY, + ATTR_MODE: const.MODE_AWAY, const.ATTR_AVAILABLE_MODES: [const.MODE_HOME, const.MODE_AWAY], }, ) @@ -127,9 +127,7 @@ async def test_get_conditions_toggle_only(hass, device_reg, entity_reg): async def test_if_state(hass, calls): """Test for turn_on and turn_off conditions.""" - hass.states.async_set( - "humidifier.entity", STATE_ON, {const.ATTR_MODE: const.MODE_AWAY} - ) + hass.states.async_set("humidifier.entity", STATE_ON, {ATTR_MODE: const.MODE_AWAY}) assert await async_setup_component( hass, @@ -213,9 +211,7 @@ async def test_if_state(hass, calls): assert len(calls) == 2 assert calls[1].data["some"] == "is_off event - test_event2" - hass.states.async_set( - "humidifier.entity", STATE_ON, {const.ATTR_MODE: const.MODE_AWAY} - ) + hass.states.async_set("humidifier.entity", STATE_ON, {ATTR_MODE: const.MODE_AWAY}) hass.bus.async_fire("test_event3") await hass.async_block_till_done() @@ -223,9 +219,7 @@ async def test_if_state(hass, calls): assert len(calls) == 3 assert calls[2].data["some"] == "is_mode - event - test_event3" - hass.states.async_set( - "humidifier.entity", STATE_ON, {const.ATTR_MODE: const.MODE_HOME} - ) + hass.states.async_set("humidifier.entity", STATE_ON, {ATTR_MODE: const.MODE_HOME}) # Should not fire hass.bus.async_fire("test_event3") @@ -239,7 +233,7 @@ async def test_capabilities(hass): "humidifier.entity", STATE_ON, { - const.ATTR_MODE: const.MODE_AWAY, + ATTR_MODE: const.MODE_AWAY, const.ATTR_AVAILABLE_MODES: [const.MODE_HOME, const.MODE_AWAY], }, ) diff --git a/tests/components/humidifier/test_device_trigger.py b/tests/components/humidifier/test_device_trigger.py index 0b6154a84df..12918684df7 100644 --- a/tests/components/humidifier/test_device_trigger.py +++ b/tests/components/humidifier/test_device_trigger.py @@ -6,7 +6,7 @@ import voluptuous_serialize import homeassistant.components.automation as automation from homeassistant.components.humidifier import DOMAIN, const, device_trigger -from homeassistant.const import ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON +from homeassistant.const import ATTR_MODE, ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON from homeassistant.helpers import config_validation as cv, device_registry from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -56,7 +56,7 @@ async def test_get_triggers(hass, device_reg, entity_reg): STATE_ON, { const.ATTR_HUMIDITY: 23, - const.ATTR_MODE: "home", + ATTR_MODE: "home", const.ATTR_AVAILABLE_MODES: ["home", "away"], ATTR_SUPPORTED_FEATURES: 1, }, @@ -95,7 +95,7 @@ async def test_if_fires_on_state_change(hass, calls): STATE_ON, { const.ATTR_HUMIDITY: 23, - const.ATTR_MODE: "home", + ATTR_MODE: "home", const.ATTR_AVAILABLE_MODES: ["home", "away"], ATTR_SUPPORTED_FEATURES: 1, }, @@ -243,7 +243,7 @@ async def test_invalid_config(hass, calls): STATE_ON, { const.ATTR_HUMIDITY: 23, - const.ATTR_MODE: "home", + ATTR_MODE: "home", const.ATTR_AVAILABLE_MODES: ["home", "away"], ATTR_SUPPORTED_FEATURES: 1, }, diff --git a/tests/components/humidifier/test_intent.py b/tests/components/humidifier/test_intent.py index 18c5b632aa6..66ff62872f3 100644 --- a/tests/components/humidifier/test_intent.py +++ b/tests/components/humidifier/test_intent.py @@ -2,7 +2,6 @@ from homeassistant.components.humidifier import ( ATTR_AVAILABLE_MODES, ATTR_HUMIDITY, - ATTR_MODE, DOMAIN, SERVICE_SET_HUMIDITY, SERVICE_SET_MODE, @@ -10,6 +9,7 @@ from homeassistant.components.humidifier import ( ) from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_MODE, ATTR_SUPPORTED_FEATURES, SERVICE_TURN_ON, STATE_OFF, diff --git a/tests/components/humidifier/test_reproduce_state.py b/tests/components/humidifier/test_reproduce_state.py index 8c1f69353a0..15b797c66a8 100644 --- a/tests/components/humidifier/test_reproduce_state.py +++ b/tests/components/humidifier/test_reproduce_state.py @@ -4,7 +4,6 @@ import pytest from homeassistant.components.humidifier.const import ( ATTR_HUMIDITY, - ATTR_MODE, DOMAIN, MODE_AWAY, MODE_ECO, @@ -13,7 +12,13 @@ from homeassistant.components.humidifier.const import ( SERVICE_SET_MODE, ) from homeassistant.components.humidifier.reproduce_state import async_reproduce_states -from homeassistant.const import SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, STATE_ON +from homeassistant.const import ( + ATTR_MODE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, +) from homeassistant.core import Context, State from tests.common import async_mock_service From 6b9abfc2c647df8fab1326341c51e84f6418abf2 Mon Sep 17 00:00:00 2001 From: Quentame Date: Tue, 2 Mar 2021 13:37:33 +0100 Subject: [PATCH 117/831] Add init test to Freebox (#46998) * Add init test to Freebox * Review : more readable conftest * Expect 2 blank lines between defs * Review : Not I/O in the event loop * Fix test_setup test * remove useless const * Review : mock setup methods * Add service test * Add import test --- .coveragerc | 1 - homeassistant/components/freebox/__init__.py | 5 +- homeassistant/components/freebox/const.py | 1 + homeassistant/components/freebox/router.py | 23 +- tests/components/freebox/conftest.py | 32 +- tests/components/freebox/const.py | 376 +++++++++++++++++++ tests/components/freebox/test_config_flow.py | 92 +++-- tests/components/freebox/test_init.py | 119 ++++++ 8 files changed, 585 insertions(+), 64 deletions(-) create mode 100644 tests/components/freebox/const.py create mode 100644 tests/components/freebox/test_init.py diff --git a/.coveragerc b/.coveragerc index 281c8d5be83..36d4399466f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -314,7 +314,6 @@ omit = homeassistant/components/foscam/camera.py homeassistant/components/foursquare/* homeassistant/components/free_mobile/notify.py - homeassistant/components/freebox/__init__.py homeassistant/components/freebox/device_tracker.py homeassistant/components/freebox/router.py homeassistant/components/freebox/sensor.py diff --git a/homeassistant/components/freebox/__init__.py b/homeassistant/components/freebox/__init__.py index f36a2303b6d..c6c98e6c2df 100644 --- a/homeassistant/components/freebox/__init__.py +++ b/homeassistant/components/freebox/__init__.py @@ -9,7 +9,7 @@ from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP from homeassistant.helpers import config_validation as cv from homeassistant.helpers.typing import HomeAssistantType -from .const import DOMAIN, PLATFORMS +from .const import DOMAIN, PLATFORMS, SERVICE_REBOOT from .router import FreeboxRouter _LOGGER = logging.getLogger(__name__) @@ -55,7 +55,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): """Handle reboot service call.""" await router.reboot() - hass.services.async_register(DOMAIN, "reboot", async_reboot) + hass.services.async_register(DOMAIN, SERVICE_REBOOT, async_reboot) async def async_close_connection(event): """Close Freebox connection on HA Stop.""" @@ -79,5 +79,6 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): if unload_ok: router = hass.data[DOMAIN].pop(entry.unique_id) await router.close() + hass.services.async_remove(DOMAIN, SERVICE_REBOOT) return unload_ok diff --git a/homeassistant/components/freebox/const.py b/homeassistant/components/freebox/const.py index d0ac63fa9bb..e47211f0926 100644 --- a/homeassistant/components/freebox/const.py +++ b/homeassistant/components/freebox/const.py @@ -8,6 +8,7 @@ from homeassistant.const import ( ) DOMAIN = "freebox" +SERVICE_REBOOT = "reboot" APP_DESC = { "app_id": "hass", diff --git a/homeassistant/components/freebox/router.py b/homeassistant/components/freebox/router.py index 2511280f719..623c1fcc564 100644 --- a/homeassistant/components/freebox/router.py +++ b/homeassistant/components/freebox/router.py @@ -1,6 +1,7 @@ """Represent the Freebox router and its devices and sensors.""" from datetime import datetime, timedelta import logging +import os from pathlib import Path from typing import Any, Dict, List, Optional @@ -31,6 +32,18 @@ _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=30) +async def get_api(hass: HomeAssistantType, host: str) -> Freepybox: + """Get the Freebox API.""" + freebox_path = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY).path + + if not os.path.exists(freebox_path): + await hass.async_add_executor_job(os.makedirs, freebox_path) + + token_file = Path(f"{freebox_path}/{slugify(host)}.conf") + + return Freepybox(APP_DESC, token_file, API_VERSION) + + class FreeboxRouter: """Representation of a Freebox router.""" @@ -188,13 +201,3 @@ class FreeboxRouter: def wifi(self) -> Wifi: """Return the wifi.""" return self._api.wifi - - -async def get_api(hass: HomeAssistantType, host: str) -> Freepybox: - """Get the Freebox API.""" - freebox_path = Path(hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY).path) - freebox_path.mkdir(exist_ok=True) - - token_file = Path(f"{freebox_path}/{slugify(host)}.conf") - - return Freepybox(APP_DESC, token_file, API_VERSION) diff --git a/tests/components/freebox/conftest.py b/tests/components/freebox/conftest.py index e813469cbbf..3220552b6cf 100644 --- a/tests/components/freebox/conftest.py +++ b/tests/components/freebox/conftest.py @@ -1,11 +1,41 @@ """Test helpers for Freebox.""" -from unittest.mock import patch +from unittest.mock import AsyncMock, patch import pytest +from .const import ( + DATA_CALL_GET_CALLS_LOG, + DATA_CONNECTION_GET_STATUS, + DATA_LAN_GET_HOSTS_LIST, + DATA_STORAGE_GET_DISKS, + DATA_SYSTEM_GET_CONFIG, + WIFI_GET_GLOBAL_CONFIG, +) + @pytest.fixture(autouse=True) def mock_path(): """Mock path lib.""" with patch("homeassistant.components.freebox.router.Path"): yield + + +@pytest.fixture(name="router") +def mock_router(): + """Mock a successful connection.""" + with patch("homeassistant.components.freebox.router.Freepybox") as service_mock: + instance = service_mock.return_value + instance.open = AsyncMock() + instance.system.get_config = AsyncMock(return_value=DATA_SYSTEM_GET_CONFIG) + # sensor + instance.call.get_calls_log = AsyncMock(return_value=DATA_CALL_GET_CALLS_LOG) + instance.storage.get_disks = AsyncMock(return_value=DATA_STORAGE_GET_DISKS) + instance.connection.get_status = AsyncMock( + return_value=DATA_CONNECTION_GET_STATUS + ) + # switch + instance.wifi.get_global_config = AsyncMock(return_value=WIFI_GET_GLOBAL_CONFIG) + # device_tracker + instance.lan.get_hosts_list = AsyncMock(return_value=DATA_LAN_GET_HOSTS_LIST) + instance.close = AsyncMock() + yield service_mock diff --git a/tests/components/freebox/const.py b/tests/components/freebox/const.py new file mode 100644 index 00000000000..cc3d720d7ef --- /dev/null +++ b/tests/components/freebox/const.py @@ -0,0 +1,376 @@ +"""Test constants.""" + +MOCK_HOST = "myrouter.freeboxos.fr" +MOCK_PORT = 1234 + +# router +DATA_SYSTEM_GET_CONFIG = { + "mac": "68:A3:78:00:00:00", + "model_info": { + "has_ext_telephony": True, + "has_speakers_jack": True, + "wifi_type": "2d4_5g", + "pretty_name": "Freebox Server (r2)", + "customer_hdd_slots": 0, + "name": "fbxgw-r2/full", + "has_speakers": True, + "internal_hdd_size": 250, + "has_femtocell_exp": True, + "has_internal_hdd": True, + "has_dect": True, + }, + "fans": [{"id": "fan0_speed", "name": "Ventilateur 1", "value": 2130}], + "sensors": [ + {"id": "temp_hdd", "name": "Disque dur", "value": 40}, + {"id": "temp_sw", "name": "Température Switch", "value": 50}, + {"id": "temp_cpum", "name": "Température CPU M", "value": 60}, + {"id": "temp_cpub", "name": "Température CPU B", "value": 56}, + ], + "board_name": "fbxgw2r", + "disk_status": "active", + "uptime": "156 jours 19 heures 56 minutes 16 secondes", + "uptime_val": 13550176, + "user_main_storage": "Disque dur", + "box_authenticated": True, + "serial": "762601T190510709", + "firmware_version": "4.2.5", +} + +# sensors +DATA_CONNECTION_GET_STATUS = { + "type": "ethernet", + "rate_down": 198900, + "bytes_up": 12035728872949, + "ipv4_port_range": [0, 65535], + "rate_up": 1440000, + "bandwidth_up": 700000000, + "ipv6": "2a01:e35:ffff:ffff::1", + "bandwidth_down": 1000000000, + "media": "ftth", + "state": "up", + "bytes_down": 2355966141297, + "ipv4": "82.67.00.00", +} + +DATA_CALL_GET_CALLS_LOG = [ + { + "number": "0988290475", + "type": "missed", + "id": 94, + "duration": 15, + "datetime": 1613752718, + "contact_id": 0, + "line_id": 0, + "name": "0988290475", + "new": True, + }, + { + "number": "0367250217", + "type": "missed", + "id": 93, + "duration": 25, + "datetime": 1613662328, + "contact_id": 0, + "line_id": 0, + "name": "0367250217", + "new": True, + }, + { + "number": "0184726018", + "type": "missed", + "id": 92, + "duration": 25, + "datetime": 1613225098, + "contact_id": 0, + "line_id": 0, + "name": "0184726018", + "new": True, + }, +] + +DATA_STORAGE_GET_DISKS = [ + { + "idle_duration": 0, + "read_error_requests": 0, + "read_requests": 110, + "spinning": True, + # "table_type": "ms-dos", API returns without dash, but codespell isn't agree + "firmware": "SC1D", + "type": "internal", + "idle": False, + "connector": 0, + "id": 0, + "write_error_requests": 0, + "state": "enabled", + "write_requests": 2708929, + "total_bytes": 250050000000, + "model": "ST9250311CS", + "active_duration": 0, + "temp": 40, + "serial": "6VCQY907", + "partitions": [ + { + "fstype": "ext4", + "total_bytes": 244950000000, + "label": "Disque dur", + "id": 2, + "internal": True, + "fsck_result": "no_run_yet", + "state": "mounted", + "disk_id": 0, + "free_bytes": 227390000000, + "used_bytes": 5090000000, + "path": "L0Rpc3F1ZSBkdXI=", + } + ], + } +] + +# switch +WIFI_GET_GLOBAL_CONFIG = {"enabled": True, "mac_filter_state": "disabled"} + +# device_tracker +DATA_LAN_GET_HOSTS_LIST = [ + { + "l2ident": {"id": "8C:97:EA:00:00:00", "type": "mac_address"}, + "active": True, + "persistent": False, + "names": [ + {"name": "d633d0c8-958c-43cc-e807-d881b076924b", "source": "mdns"}, + {"name": "Freebox Player POP", "source": "mdns_srv"}, + ], + "vendor_name": "Freebox SAS", + "host_type": "smartphone", + "interface": "pub", + "id": "ether-8c:97:ea:00:00:00", + "last_time_reachable": 1614107652, + "primary_name_manual": False, + "l3connectivities": [ + { + "addr": "192.168.1.180", + "active": True, + "reachable": True, + "last_activity": 1614107614, + "af": "ipv4", + "last_time_reachable": 1614104242, + }, + { + "addr": "fe80::dcef:dbba:6604:31d1", + "active": True, + "reachable": True, + "last_activity": 1614107645, + "af": "ipv6", + "last_time_reachable": 1614107645, + }, + { + "addr": "2a01:e34:eda1:eb40:8102:4704:7ce0:2ace", + "active": False, + "reachable": False, + "last_activity": 1611574428, + "af": "ipv6", + "last_time_reachable": 1611574428, + }, + { + "addr": "2a01:e34:eda1:eb40:c8e5:c524:c96d:5f5e", + "active": False, + "reachable": False, + "last_activity": 1612475101, + "af": "ipv6", + "last_time_reachable": 1612475101, + }, + { + "addr": "2a01:e34:eda1:eb40:583a:49df:1df0:c2df", + "active": True, + "reachable": True, + "last_activity": 1614107652, + "af": "ipv6", + "last_time_reachable": 1614107652, + }, + { + "addr": "2a01:e34:eda1:eb40:147e:3569:86ab:6aaa", + "active": False, + "reachable": False, + "last_activity": 1612486752, + "af": "ipv6", + "last_time_reachable": 1612486752, + }, + ], + "default_name": "Freebox Player POP", + "model": "fbx8am", + "reachable": True, + "last_activity": 1614107652, + "primary_name": "Freebox Player POP", + }, + { + "l2ident": {"id": "DE:00:B0:00:00:00", "type": "mac_address"}, + "active": False, + "persistent": False, + "vendor_name": "", + "host_type": "workstation", + "interface": "pub", + "id": "ether-de:00:b0:00:00:00", + "last_time_reachable": 1607125599, + "primary_name_manual": False, + "default_name": "", + "l3connectivities": [ + { + "addr": "192.168.1.181", + "active": False, + "reachable": False, + "last_activity": 1607125599, + "af": "ipv4", + "last_time_reachable": 1607125599, + }, + { + "addr": "192.168.1.182", + "active": False, + "reachable": False, + "last_activity": 1605958758, + "af": "ipv4", + "last_time_reachable": 1605958758, + }, + { + "addr": "2a01:e34:eda1:eb40:dc00:b0ff:fedf:e30", + "active": False, + "reachable": False, + "last_activity": 1607125594, + "af": "ipv6", + "last_time_reachable": 1607125594, + }, + ], + "reachable": False, + "last_activity": 1607125599, + "primary_name": "", + }, + { + "l2ident": {"id": "DC:00:B0:00:00:00", "type": "mac_address"}, + "active": True, + "persistent": False, + "names": [ + {"name": "Repeteur-Wifi-Freebox", "source": "mdns"}, + {"name": "Repeteur Wifi Freebox", "source": "mdns_srv"}, + ], + "vendor_name": "", + "host_type": "freebox_wifi", + "interface": "pub", + "id": "ether-dc:00:b0:00:00:00", + "last_time_reachable": 1614107678, + "primary_name_manual": False, + "l3connectivities": [ + { + "addr": "192.168.1.145", + "active": True, + "reachable": True, + "last_activity": 1614107678, + "af": "ipv4", + "last_time_reachable": 1614107678, + }, + { + "addr": "fe80::de00:b0ff:fe52:6ef6", + "active": True, + "reachable": True, + "last_activity": 1614107608, + "af": "ipv6", + "last_time_reachable": 1614107603, + }, + { + "addr": "2a01:e34:eda1:eb40:de00:b0ff:fe52:6ef6", + "active": True, + "reachable": True, + "last_activity": 1614107618, + "af": "ipv6", + "last_time_reachable": 1614107618, + }, + ], + "default_name": "Repeteur Wifi Freebox", + "model": "fbxwmr", + "reachable": True, + "last_activity": 1614107678, + "primary_name": "Repeteur Wifi Freebox", + }, + { + "l2ident": {"id": "5E:65:55:00:00:00", "type": "mac_address"}, + "active": False, + "persistent": False, + "names": [ + {"name": "iPhoneofQuentin", "source": "dhcp"}, + {"name": "iPhone-of-Quentin", "source": "mdns"}, + ], + "vendor_name": "", + "host_type": "smartphone", + "interface": "pub", + "id": "ether-5e:65:55:00:00:00", + "last_time_reachable": 1612611982, + "primary_name_manual": False, + "default_name": "iPhonedeQuentin", + "l3connectivities": [ + { + "addr": "192.168.1.148", + "active": False, + "reachable": False, + "last_activity": 1612611973, + "af": "ipv4", + "last_time_reachable": 1612611973, + }, + { + "addr": "fe80::14ca:6c30:938b:e281", + "active": False, + "reachable": False, + "last_activity": 1609693223, + "af": "ipv6", + "last_time_reachable": 1609693223, + }, + { + "addr": "fe80::1c90:2b94:1ba2:bd8b", + "active": False, + "reachable": False, + "last_activity": 1610797303, + "af": "ipv6", + "last_time_reachable": 1610797303, + }, + { + "addr": "fe80::8c8:e58b:838e:6785", + "active": False, + "reachable": False, + "last_activity": 1612611951, + "af": "ipv6", + "last_time_reachable": 1612611946, + }, + { + "addr": "2a01:e34:eda1:eb40:f0e7:e198:3a69:58", + "active": False, + "reachable": False, + "last_activity": 1609693245, + "af": "ipv6", + "last_time_reachable": 1609693245, + }, + { + "addr": "2a01:e34:eda1:eb40:1dc4:c6f8:aa20:c83b", + "active": False, + "reachable": False, + "last_activity": 1610797176, + "af": "ipv6", + "last_time_reachable": 1610797176, + }, + { + "addr": "2a01:e34:eda1:eb40:6cf6:5811:1770:c662", + "active": False, + "reachable": False, + "last_activity": 1612611982, + "af": "ipv6", + "last_time_reachable": 1612611982, + }, + { + "addr": "2a01:e34:eda1:eb40:438:9b2c:4f8f:f48a", + "active": False, + "reachable": False, + "last_activity": 1612611946, + "af": "ipv6", + "last_time_reachable": 1612611946, + }, + ], + "reachable": False, + "last_activity": 1612611982, + "primary_name": "iPhoneofQuentin", + }, +] diff --git a/tests/components/freebox/test_config_flow.py b/tests/components/freebox/test_config_flow.py index 5f3aace9465..565387d3fb4 100644 --- a/tests/components/freebox/test_config_flow.py +++ b/tests/components/freebox/test_config_flow.py @@ -1,23 +1,22 @@ """Tests for the Freebox config flow.""" -from unittest.mock import AsyncMock, patch +from unittest.mock import Mock, patch from freebox_api.exceptions import ( AuthorizationError, HttpRequestError, InvalidTokenError, ) -import pytest from homeassistant import data_entry_flow from homeassistant.components.freebox.const import DOMAIN from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER, SOURCE_ZEROCONF from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.helpers.typing import HomeAssistantType + +from .const import MOCK_HOST, MOCK_PORT from tests.common import MockConfigEntry -HOST = "myrouter.freeboxos.fr" -PORT = 1234 - MOCK_ZEROCONF_DATA = { "host": "192.168.0.254", "port": 80, @@ -30,33 +29,15 @@ MOCK_ZEROCONF_DATA = { "api_base_url": "/api/", "uid": "b15ab20debb399f95001a9ca207d2777", "https_available": "1", - "https_port": f"{PORT}", + "https_port": f"{MOCK_PORT}", "box_model": "fbxgw-r2/full", "box_model_name": "Freebox Server (r2)", - "api_domain": HOST, + "api_domain": MOCK_HOST, }, } -@pytest.fixture(name="connect") -def mock_controller_connect(): - """Mock a successful connection.""" - with patch("homeassistant.components.freebox.router.Freepybox") as service_mock: - service_mock.return_value.open = AsyncMock() - service_mock.return_value.system.get_config = AsyncMock( - return_value={ - "mac": "abcd", - "model_info": {"pretty_name": "Pretty Model"}, - "firmware_version": "123", - } - ) - service_mock.return_value.lan.get_hosts_list = AsyncMock() - service_mock.return_value.connection.get_status = AsyncMock() - service_mock.return_value.close = AsyncMock() - yield service_mock - - -async def test_user(hass): +async def test_user(hass: HomeAssistantType): """Test user config.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} @@ -68,24 +49,24 @@ async def test_user(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "link" -async def test_import(hass): +async def test_import(hass: HomeAssistantType): """Test import step.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "link" -async def test_zeroconf(hass): +async def test_zeroconf(hass: HomeAssistantType): """Test zeroconf step.""" result = await hass.config_entries.flow.async_init( DOMAIN, @@ -96,53 +77,64 @@ async def test_zeroconf(hass): assert result["step_id"] == "link" -async def test_link(hass, connect): +async def test_link(hass: HomeAssistantType, router: Mock): """Test linking.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, - ) + with patch( + "homeassistant.components.freebox.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.freebox.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, + ) - result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["result"].unique_id == HOST - assert result["title"] == HOST - assert result["data"][CONF_HOST] == HOST - assert result["data"][CONF_PORT] == PORT + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["result"].unique_id == MOCK_HOST + assert result["title"] == MOCK_HOST + assert result["data"][CONF_HOST] == MOCK_HOST + assert result["data"][CONF_PORT] == MOCK_PORT + + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 -async def test_abort_if_already_setup(hass): +async def test_abort_if_already_setup(hass: HomeAssistantType): """Test we abort if component is already setup.""" MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: HOST, CONF_PORT: PORT}, unique_id=HOST + domain=DOMAIN, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, + unique_id=MOCK_HOST, ).add_to_hass(hass) - # Should fail, same HOST (import) + # Should fail, same MOCK_HOST (import) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_configured" - # Should fail, same HOST (flow) + # Should fail, same MOCK_HOST (flow) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_configured" -async def test_on_link_failed(hass): +async def test_on_link_failed(hass: HomeAssistantType): """Test when we have errors during linking the router.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, ) with patch( diff --git a/tests/components/freebox/test_init.py b/tests/components/freebox/test_init.py new file mode 100644 index 00000000000..aae5f911e10 --- /dev/null +++ b/tests/components/freebox/test_init.py @@ -0,0 +1,119 @@ +"""Tests for the Freebox config flow.""" +from unittest.mock import Mock, patch + +from homeassistant.components.device_tracker import DOMAIN as DT_DOMAIN +from homeassistant.components.freebox.const import DOMAIN as DOMAIN, SERVICE_REBOOT +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN +from homeassistant.config_entries import ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED +from homeassistant.const import CONF_HOST, CONF_PORT, STATE_UNAVAILABLE +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.setup import async_setup_component + +from .const import MOCK_HOST, MOCK_PORT + +from tests.common import MockConfigEntry + + +async def test_setup(hass: HomeAssistantType, router: Mock): + """Test setup of integration.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, + unique_id=MOCK_HOST, + ) + entry.add_to_hass(hass) + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + assert hass.config_entries.async_entries() == [entry] + + assert router.call_count == 1 + assert router().open.call_count == 1 + + assert hass.services.has_service(DOMAIN, SERVICE_REBOOT) + + with patch( + "homeassistant.components.freebox.router.FreeboxRouter.reboot" + ) as mock_service: + await hass.services.async_call( + DOMAIN, + SERVICE_REBOOT, + blocking=True, + ) + await hass.async_block_till_done() + mock_service.assert_called_once() + + +async def test_setup_import(hass: HomeAssistantType, router: Mock): + """Test setup of integration from import.""" + await async_setup_component(hass, "persistent_notification", {}) + + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, + unique_id=MOCK_HOST, + ) + entry.add_to_hass(hass) + assert await async_setup_component( + hass, DOMAIN, {DOMAIN: {CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}} + ) + await hass.async_block_till_done() + assert hass.config_entries.async_entries() == [entry] + + assert router.call_count == 1 + assert router().open.call_count == 1 + + assert hass.services.has_service(DOMAIN, SERVICE_REBOOT) + + +async def test_unload_remove(hass: HomeAssistantType, router: Mock): + """Test unload and remove of integration.""" + entity_id_dt = f"{DT_DOMAIN}.freebox_server_r2" + entity_id_sensor = f"{SENSOR_DOMAIN}.freebox_download_speed" + entity_id_switch = f"{SWITCH_DOMAIN}.freebox_wifi" + + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, + ) + entry.add_to_hass(hass) + + config_entries = hass.config_entries.async_entries(DOMAIN) + assert len(config_entries) == 1 + assert entry is config_entries[0] + + assert await async_setup_component(hass, DOMAIN, {}) is True + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_LOADED + state_dt = hass.states.get(entity_id_dt) + assert state_dt + state_sensor = hass.states.get(entity_id_sensor) + assert state_sensor + state_switch = hass.states.get(entity_id_switch) + assert state_switch + + await hass.config_entries.async_unload(entry.entry_id) + + assert entry.state == ENTRY_STATE_NOT_LOADED + state_dt = hass.states.get(entity_id_dt) + assert state_dt.state == STATE_UNAVAILABLE + state_sensor = hass.states.get(entity_id_sensor) + assert state_sensor.state == STATE_UNAVAILABLE + state_switch = hass.states.get(entity_id_switch) + assert state_switch.state == STATE_UNAVAILABLE + + assert router().close.call_count == 1 + assert not hass.services.has_service(DOMAIN, SERVICE_REBOOT) + + await hass.config_entries.async_remove(entry.entry_id) + await hass.async_block_till_done() + + assert router().close.call_count == 1 + assert entry.state == ENTRY_STATE_NOT_LOADED + state_dt = hass.states.get(entity_id_dt) + assert state_dt is None + state_sensor = hass.states.get(entity_id_sensor) + assert state_sensor is None + state_switch = hass.states.get(entity_id_switch) + assert state_switch is None From 027d125617af8c016c1cc9a3906b8868f7543154 Mon Sep 17 00:00:00 2001 From: Nick Adams <4012017+Nick-Adams-AU@users.noreply.github.com> Date: Tue, 2 Mar 2021 22:58:41 +1000 Subject: [PATCH 118/831] Add services for izone airflow min/max (#45727) * Create airflow_min and airflow_max services for the izone component * Bump pizone library requirement --- homeassistant/components/izone/climate.py | 62 ++++++++++++++++++++ homeassistant/components/izone/manifest.json | 2 +- homeassistant/components/izone/services.yaml | 18 ++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/izone/services.yaml diff --git a/homeassistant/components/izone/climate.py b/homeassistant/components/izone/climate.py index 776d3f120c9..5f00720ae9d 100644 --- a/homeassistant/components/izone/climate.py +++ b/homeassistant/components/izone/climate.py @@ -1,8 +1,10 @@ """Support for the iZone HVAC.""" + import logging from typing import List, Optional from pizone import Controller, Zone +import voluptuous as vol from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( @@ -24,12 +26,14 @@ from homeassistant.components.climate.const import ( ) from homeassistant.const import ( ATTR_TEMPERATURE, + CONF_ENTITY_ID, CONF_EXCLUDE, PRECISION_HALVES, PRECISION_TENTHS, TEMP_CELSIUS, ) from homeassistant.core import callback +from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.temperature import display_temp as show_temp from homeassistant.helpers.typing import ConfigType, HomeAssistantType @@ -54,6 +58,20 @@ _IZONE_FAN_TO_HA = { Controller.Fan.AUTO: FAN_AUTO, } +ATTR_AIRFLOW = "airflow" + +IZONE_SERVICE_AIRFLOW_MIN = "airflow_min" +IZONE_SERVICE_AIRFLOW_MAX = "airflow_max" + +IZONE_SERVICE_AIRFLOW_SCHEMA = vol.Schema( + { + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Required(ATTR_AIRFLOW): vol.All( + vol.Coerce(int), vol.Range(min=0, max=100), msg="invalid airflow" + ), + } +) + async def async_setup_entry( hass: HomeAssistantType, config: ConfigType, async_add_entities @@ -83,6 +101,18 @@ async def async_setup_entry( # connect to register any further components async_dispatcher_connect(hass, DISPATCH_CONTROLLER_DISCOVERED, init_controller) + platform = entity_platform.current_platform.get() + platform.async_register_entity_service( + IZONE_SERVICE_AIRFLOW_MIN, + IZONE_SERVICE_AIRFLOW_SCHEMA, + "async_set_airflow_min", + ) + platform.async_register_entity_service( + IZONE_SERVICE_AIRFLOW_MAX, + IZONE_SERVICE_AIRFLOW_SCHEMA, + "async_set_airflow_max", + ) + return True @@ -572,6 +602,38 @@ class ZoneDevice(ClimateEntity): """Return the maximum temperature.""" return self._controller.max_temp + @property + def airflow_min(self): + """Return the minimum air flow.""" + return self._zone.airflow_min + + @property + def airflow_max(self): + """Return the maximum air flow.""" + return self._zone.airflow_max + + @property + def device_state_attributes(self): + """Return the optional state attributes.""" + return { + "airflow_min": self._zone.airflow_min, + "airflow_max": self._zone.airflow_max, + } + + async def async_set_airflow_min(self, **kwargs): + """Set new airflow minimum.""" + await self._controller.wrap_and_catch( + self._zone.set_airflow_min(int(kwargs[ATTR_AIRFLOW])) + ) + self.async_write_ha_state() + + async def async_set_airflow_max(self, **kwargs): + """Set new airflow maximum.""" + await self._controller.wrap_and_catch( + self._zone.set_airflow_max(int(kwargs[ATTR_AIRFLOW])) + ) + self.async_write_ha_state() + async def async_set_temperature(self, **kwargs): """Set new target temperature.""" if self._zone.mode != Zone.Mode.AUTO: diff --git a/homeassistant/components/izone/manifest.json b/homeassistant/components/izone/manifest.json index 479ac496906..bed7654b7e8 100644 --- a/homeassistant/components/izone/manifest.json +++ b/homeassistant/components/izone/manifest.json @@ -2,7 +2,7 @@ "domain": "izone", "name": "iZone", "documentation": "https://www.home-assistant.io/integrations/izone", - "requirements": ["python-izone==1.1.3"], + "requirements": ["python-izone==1.1.4"], "codeowners": ["@Swamp-Ig"], "config_flow": true, "homekit": { diff --git a/homeassistant/components/izone/services.yaml b/homeassistant/components/izone/services.yaml new file mode 100644 index 00000000000..14aaa69349a --- /dev/null +++ b/homeassistant/components/izone/services.yaml @@ -0,0 +1,18 @@ +airflow_min: + description: Set the airflow minimum percent for a zone + fields: + entity_id: + description: iZone Zone entity + example: "climate.bed_1" + airflow: + description: Airflow percent in 5% increments + example: "95" +airflow_max: + description: Set the airflow maximum percent for a zone + fields: + entity_id: + description: iZone Zone entity + example: "climate.bed_1" + airflow: + description: Airflow percent in 5% increments + example: "95" diff --git a/requirements_all.txt b/requirements_all.txt index 75e660f0fb0..09c328b2842 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1784,7 +1784,7 @@ python-gitlab==1.6.0 python-hpilo==4.3 # homeassistant.components.izone -python-izone==1.1.3 +python-izone==1.1.4 # homeassistant.components.joaoapps_join python-join-api==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2037a55bd49..63eeddffe5a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -930,7 +930,7 @@ python-ecobee-api==0.2.10 python-forecastio==1.4.0 # homeassistant.components.izone -python-izone==1.1.3 +python-izone==1.1.4 # homeassistant.components.juicenet python-juicenet==1.0.1 From 959181a2e9bcca173dd838599287959a4b91888f Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 2 Mar 2021 14:28:31 +0100 Subject: [PATCH 119/831] Make MQTT number respect retain setting (#47270) --- homeassistant/components/mqtt/number.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/mqtt/number.py b/homeassistant/components/mqtt/number.py index 969eb254072..aa24f81eb69 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -28,6 +28,7 @@ from . import ( subscription, ) from .. import mqtt +from .const import CONF_RETAIN from .debug_info import log_messages from .mixins import ( MQTT_AVAILABILITY_SCHEMA, @@ -161,6 +162,7 @@ class MqttNumber(MqttEntity, NumberEntity, RestoreEntity): self._config[CONF_COMMAND_TOPIC], current_number, self._config[CONF_QOS], + self._config[CONF_RETAIN], ) @property From 2ebca88950ce5d2ca7dbd172f750914431cf36f5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 2 Mar 2021 06:13:45 -0800 Subject: [PATCH 120/831] Fix Alexa doorbells (#47257) --- .../components/alexa/state_report.py | 30 +++++++------------ tests/components/alexa/test_state_report.py | 16 ++++++++++ 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/alexa/state_report.py b/homeassistant/components/alexa/state_report.py index d66906810b2..c34dc34f0dd 100644 --- a/homeassistant/components/alexa/state_report.py +++ b/homeassistant/components/alexa/state_report.py @@ -73,10 +73,7 @@ async def async_enable_proactive_mode(hass, smart_home_config): if not should_report and interface.properties_proactively_reported(): should_report = True - if ( - interface.name() == "Alexa.DoorbellEventSource" - and new_state.state == STATE_ON - ): + if interface.name() == "Alexa.DoorbellEventSource": should_doorbell = True break @@ -84,27 +81,22 @@ async def async_enable_proactive_mode(hass, smart_home_config): return if should_doorbell: - should_report = False + if new_state.state == STATE_ON: + await async_send_doorbell_event_message( + hass, smart_home_config, alexa_changed_entity + ) + return - if should_report: - alexa_properties = list(alexa_changed_entity.serialize_properties()) - else: - alexa_properties = None + alexa_properties = list(alexa_changed_entity.serialize_properties()) if not checker.async_is_significant_change( new_state, extra_arg=alexa_properties ): return - if should_report: - await async_send_changereport_message( - hass, smart_home_config, alexa_changed_entity, alexa_properties - ) - - elif should_doorbell: - await async_send_doorbell_event_message( - hass, smart_home_config, alexa_changed_entity - ) + await async_send_changereport_message( + hass, smart_home_config, alexa_changed_entity, alexa_properties + ) return hass.helpers.event.async_track_state_change( MATCH_ALL, async_entity_state_listener @@ -246,7 +238,7 @@ async def async_send_delete_message(hass, config, entity_ids): async def async_send_doorbell_event_message(hass, config, alexa_entity): """Send a DoorbellPress event message for an Alexa entity. - https://developer.amazon.com/docs/smarthome/send-events-to-the-alexa-event-gateway.html + https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-doorbelleventsource.html """ token = await config.async_get_access_token() diff --git a/tests/components/alexa/test_state_report.py b/tests/components/alexa/test_state_report.py index a057eada531..2cbf8636d79 100644 --- a/tests/components/alexa/test_state_report.py +++ b/tests/components/alexa/test_state_report.py @@ -175,6 +175,22 @@ async def test_doorbell_event(hass, aioclient_mock): assert call_json["event"]["payload"]["cause"]["type"] == "PHYSICAL_INTERACTION" assert call_json["event"]["endpoint"]["endpointId"] == "binary_sensor#test_doorbell" + hass.states.async_set( + "binary_sensor.test_doorbell", + "off", + {"friendly_name": "Test Doorbell Sensor", "device_class": "occupancy"}, + ) + + hass.states.async_set( + "binary_sensor.test_doorbell", + "on", + {"friendly_name": "Test Doorbell Sensor", "device_class": "occupancy"}, + ) + + await hass.async_block_till_done() + + assert len(aioclient_mock.mock_calls) == 2 + async def test_proactive_mode_filter_states(hass, aioclient_mock): """Test all the cases that filter states.""" From 4904207f774a39a92f38eb68fa0a4fdb2dda9e4b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Mar 2021 16:58:02 +0100 Subject: [PATCH 121/831] Fix izone flake8 error (#47276) --- homeassistant/components/izone/climate.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/izone/climate.py b/homeassistant/components/izone/climate.py index 5f00720ae9d..ef054bcfb29 100644 --- a/homeassistant/components/izone/climate.py +++ b/homeassistant/components/izone/climate.py @@ -612,14 +612,6 @@ class ZoneDevice(ClimateEntity): """Return the maximum air flow.""" return self._zone.airflow_max - @property - def device_state_attributes(self): - """Return the optional state attributes.""" - return { - "airflow_min": self._zone.airflow_min, - "airflow_max": self._zone.airflow_max, - } - async def async_set_airflow_min(self, **kwargs): """Set new airflow minimum.""" await self._controller.wrap_and_catch( @@ -675,5 +667,7 @@ class ZoneDevice(ClimateEntity): def device_state_attributes(self): """Return the optional state attributes.""" return { + "airflow_max": self._zone.airflow_max, + "airflow_min": self._zone.airflow_min, "zone_index": self.zone_index, } From 227292569936408475a8d169e4e54f4d1beeb16f Mon Sep 17 00:00:00 2001 From: Rene Lehfeld <54720674+rlehfeld@users.noreply.github.com> Date: Tue, 2 Mar 2021 18:57:14 +0100 Subject: [PATCH 122/831] Add force_update to tasmota sensors (#47052) * Add force update also to non-binary sensors as e.g. POWER Measurement agerage cannot be calculated otherwise. This is the same behavior as set with the obsolete tasmota detection * add tests in binary_sensor and test_sensor for force_update flag * satisfy flake8 * next try for force_update test but this time on the entity object which is the correct level * once again satisfy flake8 * one more try for a test * fix typo * satisfy black --- homeassistant/components/tasmota/sensor.py | 5 +++++ tests/components/tasmota/test_binary_sensor.py | 6 ++++++ tests/components/tasmota/test_sensor.py | 6 ++++++ 3 files changed, 17 insertions(+) diff --git a/homeassistant/components/tasmota/sensor.py b/homeassistant/components/tasmota/sensor.py index 17a6e2a35c2..39577e9e558 100644 --- a/homeassistant/components/tasmota/sensor.py +++ b/homeassistant/components/tasmota/sensor.py @@ -192,6 +192,11 @@ class TasmotaSensor(TasmotaAvailability, TasmotaDiscoveryUpdate, Entity): return self._state.isoformat() return self._state + @property + def force_update(self): + """Force update.""" + return True + @property def unit_of_measurement(self): """Return the unit this state is expressed in.""" diff --git a/tests/components/tasmota/test_binary_sensor.py b/tests/components/tasmota/test_binary_sensor.py index 6d4263853dc..6b13dcc89ec 100644 --- a/tests/components/tasmota/test_binary_sensor.py +++ b/tests/components/tasmota/test_binary_sensor.py @@ -95,6 +95,12 @@ async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota): state = hass.states.get("binary_sensor.tasmota_binary_sensor_1") assert state.state == STATE_OFF + # Test force update flag + entity = hass.data["entity_components"]["binary_sensor"].get_entity( + "binary_sensor.tasmota_binary_sensor_1" + ) + assert entity.force_update + async def test_controlling_state_via_mqtt_switchname(hass, mqtt_mock, setup_tasmota): """Test state update via MQTT.""" diff --git a/tests/components/tasmota/test_sensor.py b/tests/components/tasmota/test_sensor.py index fe415c264ef..6e5160273d6 100644 --- a/tests/components/tasmota/test_sensor.py +++ b/tests/components/tasmota/test_sensor.py @@ -275,6 +275,12 @@ async def test_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): state = hass.states.get("sensor.tasmota_status") assert state.state == "20.0" + # Test force update flag + entity = hass.data["entity_components"]["sensor"].get_entity( + "sensor.tasmota_status" + ) + assert entity.force_update + @pytest.mark.parametrize("status_sensor_disabled", [False]) async def test_single_shot_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): From 8e80e428d045b9191275c389779f3b0252c841a5 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 2 Mar 2021 20:15:09 +0100 Subject: [PATCH 123/831] Update frontend to 20210302.0 (#47278) --- 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 e8f9ff2698d..e7d7723a510 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20210301.0" + "home-assistant-frontend==20210302.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index cb211fb1962..d4b18de88ee 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210301.0 +home-assistant-frontend==20210302.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 09c328b2842..89cdbc873c7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210301.0 +home-assistant-frontend==20210302.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 63eeddffe5a..48b018803a2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210301.0 +home-assistant-frontend==20210302.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 112b0391076d307ef8533045c3b5c4966d78e2cd Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Tue, 2 Mar 2021 12:19:04 -0700 Subject: [PATCH 124/831] Bump simplisafe-python to 9.6.9 (#47273) --- 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 6122428ea98..45deb938b59 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.6.8"], + "requirements": ["simplisafe-python==9.6.9"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index 89cdbc873c7..6fcc28924af 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2041,7 +2041,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==9.6.8 +simplisafe-python==9.6.9 # homeassistant.components.sisyphus sisyphus-control==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 48b018803a2..789486f0561 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1048,7 +1048,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==9.6.8 +simplisafe-python==9.6.9 # homeassistant.components.slack slackclient==2.5.0 From 7ef174fb5e3f568f2ea442e29960ddbfd83e2ed4 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Tue, 2 Mar 2021 15:12:30 -0500 Subject: [PATCH 125/831] Update ZHA dependencies (#47282) --- 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 d7bb0dbe5bc..7d367c3dc00 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zha", "requirements": [ - "bellows==0.21.0", + "bellows==0.22.0", "pyserial==3.5", "pyserial-asyncio==0.5", "zha-quirks==0.0.54", diff --git a/requirements_all.txt b/requirements_all.txt index 6fcc28924af..2a2e37dd3a8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -340,7 +340,7 @@ beautifulsoup4==4.9.3 # beewi_smartclim==0.0.10 # homeassistant.components.zha -bellows==0.21.0 +bellows==0.22.0 # homeassistant.components.bmw_connected_drive bimmer_connected==0.7.15 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 789486f0561..18a1dc33839 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -193,7 +193,7 @@ azure-eventhub==5.1.0 base36==0.1.1 # homeassistant.components.zha -bellows==0.21.0 +bellows==0.22.0 # homeassistant.components.bmw_connected_drive bimmer_connected==0.7.15 From b0c873dd34cd4cb24a366ff98e44e7dadda5d4d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Tue, 2 Mar 2021 22:16:11 +0200 Subject: [PATCH 126/831] Upgrade isort to 5.7.0 (#47279) https://pycqa.github.io/isort/CHANGELOG/#570-december-30th-2020 --- .pre-commit-config.yaml | 2 +- requirements_test_pre_commit.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8e2fb5cb3b3..4ab91245b9e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,7 +43,7 @@ repos: - --configfile=tests/bandit.yaml files: ^(homeassistant|script|tests)/.+\.py$ - repo: https://github.com/PyCQA/isort - rev: 5.5.3 + rev: 5.7.0 hooks: - id: isort - repo: https://github.com/pre-commit/pre-commit-hooks diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 953c7d75394..07f1efbc694 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -5,7 +5,7 @@ black==20.8b1 codespell==2.0.0 flake8-docstrings==1.5.0 flake8==3.8.4 -isort==5.5.3 +isort==5.7.0 pydocstyle==5.1.1 pyupgrade==2.7.2 yamllint==1.24.2 From ca54de095d297a75fce0c8529ac205fbe4641435 Mon Sep 17 00:00:00 2001 From: Quentame Date: Tue, 2 Mar 2021 21:23:02 +0100 Subject: [PATCH 127/831] Add disk sensor to Freebox (#46689) * Add disk sensor to Freebox * Add debug logging into sensors * Remove useless sensor[X] assignement in disk sensor --- homeassistant/components/freebox/const.py | 10 ++++ homeassistant/components/freebox/router.py | 27 ++++++--- homeassistant/components/freebox/sensor.py | 66 +++++++++++++++++++++- 3 files changed, 94 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/freebox/const.py b/homeassistant/components/freebox/const.py index e47211f0926..df251dcf954 100644 --- a/homeassistant/components/freebox/const.py +++ b/homeassistant/components/freebox/const.py @@ -4,6 +4,7 @@ import socket from homeassistant.const import ( DATA_RATE_KILOBYTES_PER_SECOND, DEVICE_CLASS_TEMPERATURE, + PERCENTAGE, TEMP_CELSIUS, ) @@ -56,6 +57,15 @@ CALL_SENSORS = { }, } +DISK_PARTITION_SENSORS = { + "partition_free_space": { + SENSOR_NAME: "free space", + SENSOR_UNIT: PERCENTAGE, + SENSOR_ICON: "mdi:harddisk", + SENSOR_DEVICE_CLASS: None, + }, +} + TEMPERATURE_SENSOR_TEMPLATE = { SENSOR_NAME: None, SENSOR_UNIT: TEMP_CELSIUS, diff --git a/homeassistant/components/freebox/router.py b/homeassistant/components/freebox/router.py index 623c1fcc564..a38992320eb 100644 --- a/homeassistant/components/freebox/router.py +++ b/homeassistant/components/freebox/router.py @@ -55,12 +55,13 @@ class FreeboxRouter: self._port = entry.data[CONF_PORT] self._api: Freepybox = None - self._name = None + self.name = None self.mac = None self._sw_v = None self._attrs = {} - self.devices: Dict[str, Any] = {} + self.devices: Dict[str, Dict[str, Any]] = {} + self.disks: Dict[int, Dict[str, Any]] = {} self.sensors_temperature: Dict[str, int] = {} self.sensors_connection: Dict[str, float] = {} self.call_list: List[Dict[str, Any]] = [] @@ -81,7 +82,7 @@ class FreeboxRouter: # System fbx_config = await self._api.system.get_config() self.mac = fbx_config["mac"] - self._name = fbx_config["model_info"]["pretty_name"] + self.name = fbx_config["model_info"]["pretty_name"] self._sw_v = fbx_config["firmware_version"] # Devices & sensors @@ -92,18 +93,18 @@ class FreeboxRouter: async def update_all(self, now: Optional[datetime] = None) -> None: """Update all Freebox platforms.""" + await self.update_device_trackers() await self.update_sensors() - await self.update_devices() - async def update_devices(self) -> None: + async def update_device_trackers(self) -> None: """Update Freebox devices.""" new_device = False - fbx_devices: Dict[str, Any] = await self._api.lan.get_hosts_list() + fbx_devices: [Dict[str, Any]] = await self._api.lan.get_hosts_list() # Adds the Freebox itself fbx_devices.append( { - "primary_name": self._name, + "primary_name": self.name, "l2ident": {"id": self.mac}, "vendor_name": "Freebox SAS", "host_type": "router", @@ -153,8 +154,18 @@ class FreeboxRouter: self.call_list = await self._api.call.get_calls_log() + await self._update_disks_sensors() + async_dispatcher_send(self.hass, self.signal_sensor_update) + async def _update_disks_sensors(self) -> None: + """Update Freebox disks.""" + # None at first request + fbx_disks: [Dict[str, Any]] = await self._api.storage.get_disks() or [] + + for fbx_disk in fbx_disks: + self.disks[fbx_disk["id"]] = fbx_disk + async def reboot(self) -> None: """Reboot the Freebox.""" await self._api.system.reboot() @@ -172,7 +183,7 @@ class FreeboxRouter: return { "connections": {(CONNECTION_NETWORK_MAC, self.mac)}, "identifiers": {(DOMAIN, self.mac)}, - "name": self._name, + "name": self.name, "manufacturer": "Freebox SAS", "sw_version": self._sw_v, } diff --git a/homeassistant/components/freebox/sensor.py b/homeassistant/components/freebox/sensor.py index aeeaba438ff..b8881ad7949 100644 --- a/homeassistant/components/freebox/sensor.py +++ b/homeassistant/components/freebox/sensor.py @@ -1,4 +1,5 @@ """Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" +import logging from typing import Dict from homeassistant.config_entries import ConfigEntry @@ -12,6 +13,7 @@ import homeassistant.util.dt as dt_util from .const import ( CALL_SENSORS, CONNECTION_SENSORS, + DISK_PARTITION_SENSORS, DOMAIN, SENSOR_DEVICE_CLASS, SENSOR_ICON, @@ -21,6 +23,8 @@ from .const import ( ) from .router import FreeboxRouter +_LOGGER = logging.getLogger(__name__) + async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, async_add_entities @@ -29,6 +33,12 @@ async def async_setup_entry( router = hass.data[DOMAIN][entry.unique_id] entities = [] + _LOGGER.debug( + "%s - %s - %s temperature sensors", + router.name, + router.mac, + len(router.sensors_temperature), + ) for sensor_name in router.sensors_temperature: entities.append( FreeboxSensor( @@ -46,6 +56,20 @@ async def async_setup_entry( for sensor_key in CALL_SENSORS: entities.append(FreeboxCallSensor(router, sensor_key, CALL_SENSORS[sensor_key])) + _LOGGER.debug("%s - %s - %s disk(s)", router.name, router.mac, len(router.disks)) + for disk in router.disks.values(): + for partition in disk["partitions"]: + for sensor_key in DISK_PARTITION_SENSORS: + entities.append( + FreeboxDiskSensor( + router, + disk, + partition, + sensor_key, + DISK_PARTITION_SENSORS[sensor_key], + ) + ) + async_add_entities(entities, True) @@ -139,8 +163,8 @@ class FreeboxCallSensor(FreeboxSensor): self, router: FreeboxRouter, sensor_type: str, sensor: Dict[str, any] ) -> None: """Initialize a Freebox call sensor.""" - self._call_list_for_type = [] super().__init__(router, sensor_type, sensor) + self._call_list_for_type = [] @callback def async_update_state(self) -> None: @@ -162,3 +186,43 @@ class FreeboxCallSensor(FreeboxSensor): dt_util.utc_from_timestamp(call["datetime"]).isoformat(): call["name"] for call in self._call_list_for_type } + + +class FreeboxDiskSensor(FreeboxSensor): + """Representation of a Freebox disk sensor.""" + + def __init__( + self, + router: FreeboxRouter, + disk: Dict[str, any], + partition: Dict[str, any], + sensor_type: str, + sensor: Dict[str, any], + ) -> None: + """Initialize a Freebox disk sensor.""" + super().__init__(router, sensor_type, sensor) + self._disk = disk + self._partition = partition + self._name = f"{partition['label']} {sensor[SENSOR_NAME]}" + self._unique_id = f"{self._router.mac} {sensor_type} {self._disk['id']} {self._partition['id']}" + + @property + def device_info(self) -> Dict[str, any]: + """Return the device information.""" + return { + "identifiers": {(DOMAIN, self._disk["id"])}, + "name": f"Disk {self._disk['id']}", + "model": self._disk["model"], + "sw_version": self._disk["firmware"], + "via_device": ( + DOMAIN, + self._router.mac, + ), + } + + @callback + def async_update_state(self) -> None: + """Update the Freebox disk sensor.""" + self._state = round( + self._partition["free_bytes"] * 100 / self._partition["total_bytes"], 2 + ) From 198ecb0945aa062971afc471fc860b9069a567da Mon Sep 17 00:00:00 2001 From: Quentame Date: Tue, 2 Mar 2021 21:43:59 +0100 Subject: [PATCH 128/831] Uniformize platform setup (#47101) * A platform is not a component * Fix dynalite * SUPPORTED_PLATFORMS --> PLATFORMS * In tests * In tests 2 * Fix SmartThings * Fix ZHA test * Fix Z-Wave * Revert Z-Wave * Use PLATFORMS const in ambient_station * Fix ihc comment --- homeassistant/components/abode/__init__.py | 6 +- .../components/accuweather/__init__.py | 8 +- homeassistant/components/acmeda/__init__.py | 8 +- homeassistant/components/adguard/__init__.py | 10 +- .../components/advantage_air/__init__.py | 8 +- homeassistant/components/aemet/__init__.py | 10 +- homeassistant/components/aemet/const.py | 2 +- homeassistant/components/airly/__init__.py | 8 +- homeassistant/components/airnow/__init__.py | 8 +- .../components/airvisual/__init__.py | 8 +- .../components/alarmdecoder/__init__.py | 8 +- .../components/ambient_station/__init__.py | 206 +++++++++--------- .../ambient_station/binary_sensor.py | 15 +- .../components/ambient_station/const.py | 3 - .../components/ambient_station/sensor.py | 11 +- homeassistant/components/apple_tv/__init__.py | 4 +- homeassistant/components/atag/__init__.py | 4 +- homeassistant/components/august/__init__.py | 10 +- homeassistant/components/august/const.py | 2 +- homeassistant/components/aurora/__init__.py | 8 +- homeassistant/components/blebox/__init__.py | 4 +- homeassistant/components/blink/__init__.py | 8 +- homeassistant/components/bloomsky/__init__.py | 8 +- .../bmw_connected_drive/__init__.py | 12 +- homeassistant/components/bond/__init__.py | 8 +- homeassistant/components/braviatv/__init__.py | 8 +- homeassistant/components/brother/__init__.py | 8 +- homeassistant/components/canary/__init__.py | 8 +- .../components/climacell/__init__.py | 8 +- homeassistant/components/control4/__init__.py | 8 +- .../components/coronavirus/__init__.py | 8 +- homeassistant/components/daikin/__init__.py | 10 +- .../components/danfoss_air/__init__.py | 4 +- homeassistant/components/deconz/const.py | 2 +- homeassistant/components/deconz/gateway.py | 10 +- homeassistant/components/demo/__init__.py | 8 +- homeassistant/components/dexcom/__init__.py | 8 +- homeassistant/components/directv/__init__.py | 8 +- homeassistant/components/doorbird/__init__.py | 8 +- homeassistant/components/dsmr/__init__.py | 4 +- homeassistant/components/dunehd/__init__.py | 8 +- homeassistant/components/dynalite/__init__.py | 9 +- homeassistant/components/dynalite/bridge.py | 11 +- homeassistant/components/dynalite/const.py | 2 +- homeassistant/components/dyson/__init__.py | 4 +- homeassistant/components/ecobee/__init__.py | 16 +- homeassistant/components/ecobee/const.py | 2 +- homeassistant/components/econet/__init__.py | 8 +- homeassistant/components/elkm1/__init__.py | 10 +- homeassistant/components/epson/__init__.py | 8 +- .../components/faa_delays/__init__.py | 8 +- homeassistant/components/fibaro/__init__.py | 16 +- .../components/fireservicerota/__init__.py | 6 +- homeassistant/components/flo/__init__.py | 8 +- homeassistant/components/flume/__init__.py | 8 +- .../components/flunearyou/__init__.py | 8 +- homeassistant/components/foscam/__init__.py | 8 +- homeassistant/components/fritzbox/__init__.py | 8 +- .../fritzbox_callmonitor/__init__.py | 8 +- .../components/garmin_connect/__init__.py | 8 +- homeassistant/components/goalzero/__init__.py | 4 +- homeassistant/components/guardian/__init__.py | 8 +- homeassistant/components/habitica/__init__.py | 8 +- homeassistant/components/harmony/__init__.py | 8 +- .../components/home_connect/__init__.py | 8 +- .../components/homematicip_cloud/const.py | 2 +- .../components/homematicip_cloud/hap.py | 10 +- .../hunterdouglas_powerview/__init__.py | 8 +- .../components/hvv_departures/__init__.py | 8 +- homeassistant/components/hyperion/__init__.py | 8 +- homeassistant/components/ihc/__init__.py | 32 +-- homeassistant/components/insteon/__init__.py | 6 +- homeassistant/components/insteon/const.py | 2 +- homeassistant/components/ipp/__init__.py | 8 +- homeassistant/components/iqvia/__init__.py | 8 +- homeassistant/components/isy994/__init__.py | 8 +- homeassistant/components/isy994/const.py | 2 +- homeassistant/components/isy994/helpers.py | 12 +- homeassistant/components/isy994/services.py | 4 +- homeassistant/components/juicenet/__init__.py | 8 +- homeassistant/components/kaiterra/__init__.py | 8 +- homeassistant/components/kaiterra/const.py | 2 +- .../components/keenetic_ndms2/__init__.py | 8 +- homeassistant/components/kmtronic/__init__.py | 8 +- homeassistant/components/kodi/__init__.py | 8 +- .../components/konnected/__init__.py | 8 +- homeassistant/components/kulersky/__init__.py | 8 +- homeassistant/components/litejet/__init__.py | 8 +- .../components/litterrobot/__init__.py | 8 +- .../components/logi_circle/__init__.py | 10 +- homeassistant/components/lutron/__init__.py | 8 +- .../components/lutron_caseta/__init__.py | 12 +- homeassistant/components/lyric/__init__.py | 8 +- homeassistant/components/mazda/__init__.py | 8 +- .../components/metoffice/__init__.py | 8 +- .../components/mobile_app/__init__.py | 4 +- .../components/monoprice/__init__.py | 8 +- .../components/motion_blinds/__init__.py | 10 +- .../components/motion_blinds/const.py | 2 +- homeassistant/components/mullvad/__init__.py | 8 +- homeassistant/components/myq/__init__.py | 8 +- .../components/mysensors/__init__.py | 6 +- homeassistant/components/mysensors/const.py | 2 +- homeassistant/components/n26/__init__.py | 8 +- homeassistant/components/neato/__init__.py | 4 +- homeassistant/components/nest/__init__.py | 8 +- .../components/nest/legacy/__init__.py | 6 +- homeassistant/components/netatmo/__init__.py | 8 +- homeassistant/components/nexia/__init__.py | 8 +- .../components/nextcloud/__init__.py | 6 +- .../components/nightscout/__init__.py | 8 +- .../components/nissan_leaf/__init__.py | 8 +- homeassistant/components/nuheat/__init__.py | 8 +- homeassistant/components/nuki/__init__.py | 4 +- homeassistant/components/nut/__init__.py | 8 +- homeassistant/components/nws/__init__.py | 8 +- homeassistant/components/nzbget/__init__.py | 8 +- .../components/omnilogic/__init__.py | 8 +- .../components/ondilo_ico/__init__.py | 8 +- homeassistant/components/onewire/__init__.py | 10 +- homeassistant/components/onewire/const.py | 2 +- homeassistant/components/onvif/__init__.py | 8 +- homeassistant/components/openuv/__init__.py | 8 +- .../components/openweathermap/__init__.py | 10 +- .../components/openweathermap/const.py | 2 +- homeassistant/components/ozw/__init__.py | 8 +- .../components/panasonic_viera/__init__.py | 8 +- .../components/philips_js/__init__.py | 8 +- homeassistant/components/plugwise/gateway.py | 8 +- .../components/plum_lightpad/__init__.py | 4 +- homeassistant/components/point/__init__.py | 18 +- .../components/poolsense/__init__.py | 8 +- .../components/powerwall/__init__.py | 8 +- .../components/progettihwsw/__init__.py | 8 +- .../components/proxmoxve/__init__.py | 4 +- homeassistant/components/rachio/__init__.py | 12 +- homeassistant/components/rainbird/__init__.py | 4 +- .../components/rainmachine/__init__.py | 8 +- .../components/recollect_waste/__init__.py | 8 +- homeassistant/components/rfxtrx/__init__.py | 10 +- homeassistant/components/ring/__init__.py | 8 +- homeassistant/components/risco/__init__.py | 8 +- .../rituals_perfume_genie/__init__.py | 8 +- homeassistant/components/roku/__init__.py | 8 +- homeassistant/components/roomba/__init__.py | 10 +- homeassistant/components/roomba/const.py | 2 +- .../components/ruckus_unleashed/__init__.py | 4 +- homeassistant/components/sense/__init__.py | 8 +- homeassistant/components/sharkiq/__init__.py | 10 +- homeassistant/components/sharkiq/const.py | 2 +- homeassistant/components/shelly/__init__.py | 8 +- .../components/simplisafe/__init__.py | 8 +- homeassistant/components/smappee/__init__.py | 10 +- homeassistant/components/smappee/const.py | 2 +- .../components/smart_meter_texas/__init__.py | 8 +- homeassistant/components/smarthab/__init__.py | 10 +- .../components/smartthings/__init__.py | 22 +- homeassistant/components/smartthings/const.py | 2 +- homeassistant/components/sms/__init__.py | 8 +- homeassistant/components/soma/__init__.py | 10 +- homeassistant/components/somfy/__init__.py | 10 +- .../components/somfy_mylink/__init__.py | 10 +- .../components/somfy_mylink/const.py | 2 +- homeassistant/components/sonarr/__init__.py | 8 +- homeassistant/components/spider/__init__.py | 8 +- .../components/streamlabswater/__init__.py | 8 +- homeassistant/components/subaru/__init__.py | 10 +- homeassistant/components/subaru/const.py | 2 +- homeassistant/components/tado/__init__.py | 10 +- homeassistant/components/tahoma/__init__.py | 10 +- homeassistant/components/tasmota/__init__.py | 12 +- .../components/tellduslive/__init__.py | 4 +- homeassistant/components/tesla/__init__.py | 12 +- homeassistant/components/tesla/const.py | 4 +- homeassistant/components/tibber/__init__.py | 8 +- homeassistant/components/tile/__init__.py | 8 +- homeassistant/components/toon/__init__.py | 10 +- .../components/totalconnect/__init__.py | 4 +- homeassistant/components/tradfri/__init__.py | 8 +- homeassistant/components/tuya/__init__.py | 4 +- homeassistant/components/unifi/controller.py | 6 +- homeassistant/components/upb/__init__.py | 10 +- homeassistant/components/velbus/__init__.py | 20 +- homeassistant/components/velux/__init__.py | 6 +- homeassistant/components/verisure/__init__.py | 22 +- homeassistant/components/vesync/__init__.py | 4 +- homeassistant/components/vicare/__init__.py | 4 +- homeassistant/components/volumio/__init__.py | 8 +- .../components/volvooncall/__init__.py | 6 +- homeassistant/components/wiffi/__init__.py | 8 +- homeassistant/components/wilight/__init__.py | 8 +- homeassistant/components/wled/__init__.py | 10 +- homeassistant/components/xbox/__init__.py | 8 +- .../components/xiaomi_aqara/__init__.py | 8 +- .../components/xiaomi_miio/__init__.py | 8 +- homeassistant/components/xs1/__init__.py | 12 +- homeassistant/components/yeelight/__init__.py | 8 +- homeassistant/components/zerproc/__init__.py | 8 +- homeassistant/components/zha/__init__.py | 14 +- homeassistant/components/zha/core/const.py | 2 +- .../components/zha/core/discovery.py | 4 +- homeassistant/components/zwave/__init__.py | 6 +- homeassistant/components/zwave_js/__init__.py | 8 +- .../config_flow/integration/__init__.py | 8 +- .../integration/__init__.py | 8 +- .../integration/__init__.py | 8 +- tests/components/abode/common.py | 2 +- tests/components/dynalite/test_init.py | 6 +- tests/components/dyson/conftest.py | 4 +- tests/components/dyson/test_init.py | 4 +- tests/components/dyson/test_sensor.py | 4 +- .../components/onewire/test_binary_sensor.py | 2 +- .../onewire/test_entity_owserver.py | 10 +- tests/components/onewire/test_sensor.py | 2 +- tests/components/onewire/test_switch.py | 2 +- tests/components/smartthings/test_init.py | 10 +- tests/components/unifi/test_controller.py | 7 +- tests/components/zha/test_discover.py | 2 +- 218 files changed, 924 insertions(+), 932 deletions(-) diff --git a/homeassistant/components/abode/__init__.py b/homeassistant/components/abode/__init__.py index 20c0624742c..47e03eea44c 100644 --- a/homeassistant/components/abode/__init__.py +++ b/homeassistant/components/abode/__init__.py @@ -66,7 +66,7 @@ CAPTURE_IMAGE_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids}) AUTOMATION_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids}) -ABODE_PLATFORMS = [ +PLATFORMS = [ "alarm_control_panel", "binary_sensor", "lock", @@ -138,7 +138,7 @@ async def async_setup_entry(hass, config_entry): hass.data[DOMAIN] = AbodeSystem(abode, polling) - for platform in ABODE_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(config_entry, platform) ) @@ -158,7 +158,7 @@ async def async_unload_entry(hass, config_entry): tasks = [] - for platform in ABODE_PLATFORMS: + for platform in PLATFORMS: tasks.append( hass.config_entries.async_forward_entry_unload(config_entry, platform) ) diff --git a/homeassistant/components/accuweather/__init__.py b/homeassistant/components/accuweather/__init__.py index 27dbae7a41f..b2ec7e0224b 100644 --- a/homeassistant/components/accuweather/__init__.py +++ b/homeassistant/components/accuweather/__init__.py @@ -57,9 +57,9 @@ async def async_setup_entry(hass, config_entry) -> bool: UNDO_UPDATE_LISTENER: undo_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -70,8 +70,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/acmeda/__init__.py b/homeassistant/components/acmeda/__init__.py index 3b4f135a6fd..ede6751a6fe 100644 --- a/homeassistant/components/acmeda/__init__.py +++ b/homeassistant/components/acmeda/__init__.py @@ -28,9 +28,9 @@ async def async_setup_entry( hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][config_entry.entry_id] = hub - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -45,8 +45,8 @@ async def async_unload_entry( unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/adguard/__init__.py b/homeassistant/components/adguard/__init__.py index 71dff2ab6ee..0a316784f8b 100644 --- a/homeassistant/components/adguard/__init__.py +++ b/homeassistant/components/adguard/__init__.py @@ -43,6 +43,8 @@ SERVICE_REFRESH_SCHEMA = vol.Schema( {vol.Optional(CONF_FORCE, default=False): cv.boolean} ) +PLATFORMS = ["sensor", "switch"] + async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: """Set up the AdGuard Home components.""" @@ -69,9 +71,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool except AdGuardHomeConnectionError as exception: raise ConfigEntryNotReady from exception - for component in "sensor", "switch": + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) async def add_url(call) -> None: @@ -123,8 +125,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigType) -> bool hass.services.async_remove(DOMAIN, SERVICE_DISABLE_URL) hass.services.async_remove(DOMAIN, SERVICE_REFRESH) - for component in "sensor", "switch": - await hass.config_entries.async_forward_entry_unload(entry, component) + for platform in PLATFORMS: + await hass.config_entries.async_forward_entry_unload(entry, platform) del hass.data[DOMAIN] diff --git a/homeassistant/components/advantage_air/__init__.py b/homeassistant/components/advantage_air/__init__.py index 7b270f1f335..d98e991364d 100644 --- a/homeassistant/components/advantage_air/__init__.py +++ b/homeassistant/components/advantage_air/__init__.py @@ -14,7 +14,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda from .const import ADVANTAGE_AIR_RETRY, DOMAIN ADVANTAGE_AIR_SYNC_INTERVAL = 15 -ADVANTAGE_AIR_PLATFORMS = ["climate", "cover", "binary_sensor", "sensor", "switch"] +PLATFORMS = ["climate", "cover", "binary_sensor", "sensor", "switch"] _LOGGER = logging.getLogger(__name__) @@ -67,7 +67,7 @@ async def async_setup_entry(hass, entry): "async_change": async_change, } - for platform in ADVANTAGE_AIR_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform) ) @@ -80,8 +80,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in ADVANTAGE_AIR_PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/aemet/__init__.py b/homeassistant/components/aemet/__init__.py index 58b1a3b10f0..54c93f43a25 100644 --- a/homeassistant/components/aemet/__init__.py +++ b/homeassistant/components/aemet/__init__.py @@ -8,7 +8,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.core import HomeAssistant -from .const import COMPONENTS, DOMAIN, ENTRY_NAME, ENTRY_WEATHER_COORDINATOR +from .const import DOMAIN, ENTRY_NAME, ENTRY_WEATHER_COORDINATOR, PLATFORMS from .weather_update_coordinator import WeatherUpdateCoordinator _LOGGER = logging.getLogger(__name__) @@ -37,9 +37,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): ENTRY_WEATHER_COORDINATOR: weather_coordinator, } - for component in COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -50,8 +50,8 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in COMPONENTS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/aemet/const.py b/homeassistant/components/aemet/const.py index 13b9d944bf0..390ccb86003 100644 --- a/homeassistant/components/aemet/const.py +++ b/homeassistant/components/aemet/const.py @@ -34,7 +34,7 @@ from homeassistant.const import ( ) ATTRIBUTION = "Powered by AEMET OpenData" -COMPONENTS = ["sensor", "weather"] +PLATFORMS = ["sensor", "weather"] DEFAULT_NAME = "AEMET" DOMAIN = "aemet" ENTRY_NAME = "name" diff --git a/homeassistant/components/airly/__init__.py b/homeassistant/components/airly/__init__.py index fd1defc64f6..ff7a87f92fc 100644 --- a/homeassistant/components/airly/__init__.py +++ b/homeassistant/components/airly/__init__.py @@ -79,9 +79,9 @@ async def async_setup_entry(hass, config_entry): hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][config_entry.entry_id] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -92,8 +92,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/airnow/__init__.py b/homeassistant/components/airnow/__init__.py index 5cbc87947f9..1732445c566 100644 --- a/homeassistant/components/airnow/__init__.py +++ b/homeassistant/components/airnow/__init__.py @@ -68,9 +68,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -81,8 +81,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index 3a88243b0b9..c04f56f6b09 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -268,9 +268,9 @@ async def async_setup_entry(hass, config_entry): hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -323,8 +323,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/alarmdecoder/__init__.py b/homeassistant/components/alarmdecoder/__init__.py index 8dd704f1333..34493b3b9d0 100644 --- a/homeassistant/components/alarmdecoder/__init__.py +++ b/homeassistant/components/alarmdecoder/__init__.py @@ -130,9 +130,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool await open_connection() - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -144,8 +144,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index 4a5558c5963..b4ac6992459 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -5,7 +5,11 @@ from aioambient import Client from aioambient.errors import WebsocketError import voluptuous as vol -from homeassistant.components.binary_sensor import DEVICE_CLASS_CONNECTIVITY +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + DOMAIN as BINARY_SENSOR, +) +from homeassistant.components.sensor import DOMAIN as SENSOR from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_LOCATION, @@ -39,10 +43,10 @@ from .const import ( DATA_CLIENT, DOMAIN, LOGGER, - TYPE_BINARY_SENSOR, - TYPE_SENSOR, ) +PLATFORMS = [BINARY_SENSOR, SENSOR] + DATA_CONFIG = "config" DEFAULT_SOCKET_MIN_RETRY = 15 @@ -141,109 +145,109 @@ TYPE_WINDSPDMPH_AVG2M = "windspdmph_avg2m" TYPE_WINDSPEEDMPH = "windspeedmph" TYPE_YEARLYRAININ = "yearlyrainin" SENSOR_TYPES = { - TYPE_24HOURRAININ: ("24 Hr Rain", "in", TYPE_SENSOR, None), - TYPE_BAROMABSIN: ("Abs Pressure", PRESSURE_INHG, TYPE_SENSOR, "pressure"), - TYPE_BAROMRELIN: ("Rel Pressure", PRESSURE_INHG, TYPE_SENSOR, "pressure"), - TYPE_BATT10: ("Battery 10", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATT1: ("Battery 1", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATT2: ("Battery 2", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATT3: ("Battery 3", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATT4: ("Battery 4", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATT5: ("Battery 5", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATT6: ("Battery 6", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATT7: ("Battery 7", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATT8: ("Battery 8", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATT9: ("Battery 9", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATTOUT: ("Battery", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_CO2: ("co2", CONCENTRATION_PARTS_PER_MILLION, TYPE_SENSOR, None), - TYPE_DAILYRAININ: ("Daily Rain", "in", TYPE_SENSOR, None), - TYPE_DEWPOINT: ("Dew Point", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_EVENTRAININ: ("Event Rain", "in", TYPE_SENSOR, None), - TYPE_FEELSLIKE: ("Feels Like", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_HOURLYRAININ: ("Hourly Rain Rate", "in/hr", TYPE_SENSOR, None), - TYPE_HUMIDITY10: ("Humidity 10", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY1: ("Humidity 1", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY2: ("Humidity 2", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY3: ("Humidity 3", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY4: ("Humidity 4", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY5: ("Humidity 5", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY6: ("Humidity 6", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY7: ("Humidity 7", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY8: ("Humidity 8", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY9: ("Humidity 9", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY: ("Humidity", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITYIN: ("Humidity In", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_LASTRAIN: ("Last Rain", None, TYPE_SENSOR, "timestamp"), - TYPE_MAXDAILYGUST: ("Max Gust", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None), - TYPE_MONTHLYRAININ: ("Monthly Rain", "in", TYPE_SENSOR, None), - TYPE_RELAY10: ("Relay 10", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_RELAY1: ("Relay 1", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_RELAY2: ("Relay 2", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_RELAY3: ("Relay 3", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_RELAY4: ("Relay 4", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_RELAY5: ("Relay 5", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_RELAY6: ("Relay 6", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_RELAY7: ("Relay 7", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_RELAY8: ("Relay 8", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_RELAY9: ("Relay 9", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_SOILHUM10: ("Soil Humidity 10", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILHUM1: ("Soil Humidity 1", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILHUM2: ("Soil Humidity 2", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILHUM3: ("Soil Humidity 3", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILHUM4: ("Soil Humidity 4", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILHUM5: ("Soil Humidity 5", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILHUM6: ("Soil Humidity 6", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILHUM7: ("Soil Humidity 7", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILHUM8: ("Soil Humidity 8", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILHUM9: ("Soil Humidity 9", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILTEMP10F: ("Soil Temp 10", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOILTEMP1F: ("Soil Temp 1", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOILTEMP2F: ("Soil Temp 2", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOILTEMP3F: ("Soil Temp 3", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOILTEMP4F: ("Soil Temp 4", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOILTEMP5F: ("Soil Temp 5", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOILTEMP6F: ("Soil Temp 6", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOILTEMP7F: ("Soil Temp 7", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOILTEMP8F: ("Soil Temp 8", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOILTEMP9F: ("Soil Temp 9", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), + TYPE_24HOURRAININ: ("24 Hr Rain", "in", SENSOR, None), + TYPE_BAROMABSIN: ("Abs Pressure", PRESSURE_INHG, SENSOR, "pressure"), + TYPE_BAROMRELIN: ("Rel Pressure", PRESSURE_INHG, SENSOR, "pressure"), + TYPE_BATT10: ("Battery 10", None, BINARY_SENSOR, "battery"), + TYPE_BATT1: ("Battery 1", None, BINARY_SENSOR, "battery"), + TYPE_BATT2: ("Battery 2", None, BINARY_SENSOR, "battery"), + TYPE_BATT3: ("Battery 3", None, BINARY_SENSOR, "battery"), + TYPE_BATT4: ("Battery 4", None, BINARY_SENSOR, "battery"), + TYPE_BATT5: ("Battery 5", None, BINARY_SENSOR, "battery"), + TYPE_BATT6: ("Battery 6", None, BINARY_SENSOR, "battery"), + TYPE_BATT7: ("Battery 7", None, BINARY_SENSOR, "battery"), + TYPE_BATT8: ("Battery 8", None, BINARY_SENSOR, "battery"), + TYPE_BATT9: ("Battery 9", None, BINARY_SENSOR, "battery"), + TYPE_BATTOUT: ("Battery", None, BINARY_SENSOR, "battery"), + TYPE_CO2: ("co2", CONCENTRATION_PARTS_PER_MILLION, SENSOR, None), + TYPE_DAILYRAININ: ("Daily Rain", "in", SENSOR, None), + TYPE_DEWPOINT: ("Dew Point", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_EVENTRAININ: ("Event Rain", "in", SENSOR, None), + TYPE_FEELSLIKE: ("Feels Like", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_HOURLYRAININ: ("Hourly Rain Rate", "in/hr", SENSOR, None), + TYPE_HUMIDITY10: ("Humidity 10", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY1: ("Humidity 1", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY2: ("Humidity 2", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY3: ("Humidity 3", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY4: ("Humidity 4", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY5: ("Humidity 5", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY6: ("Humidity 6", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY7: ("Humidity 7", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY8: ("Humidity 8", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY9: ("Humidity 9", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY: ("Humidity", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITYIN: ("Humidity In", PERCENTAGE, SENSOR, "humidity"), + TYPE_LASTRAIN: ("Last Rain", None, SENSOR, "timestamp"), + TYPE_MAXDAILYGUST: ("Max Gust", SPEED_MILES_PER_HOUR, SENSOR, None), + TYPE_MONTHLYRAININ: ("Monthly Rain", "in", SENSOR, None), + TYPE_RELAY10: ("Relay 10", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY1: ("Relay 1", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY2: ("Relay 2", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY3: ("Relay 3", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY4: ("Relay 4", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY5: ("Relay 5", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY6: ("Relay 6", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY7: ("Relay 7", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY8: ("Relay 8", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY9: ("Relay 9", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_SOILHUM10: ("Soil Humidity 10", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILHUM1: ("Soil Humidity 1", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILHUM2: ("Soil Humidity 2", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILHUM3: ("Soil Humidity 3", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILHUM4: ("Soil Humidity 4", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILHUM5: ("Soil Humidity 5", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILHUM6: ("Soil Humidity 6", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILHUM7: ("Soil Humidity 7", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILHUM8: ("Soil Humidity 8", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILHUM9: ("Soil Humidity 9", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILTEMP10F: ("Soil Temp 10", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_SOILTEMP1F: ("Soil Temp 1", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_SOILTEMP2F: ("Soil Temp 2", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_SOILTEMP3F: ("Soil Temp 3", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_SOILTEMP4F: ("Soil Temp 4", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_SOILTEMP5F: ("Soil Temp 5", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_SOILTEMP6F: ("Soil Temp 6", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_SOILTEMP7F: ("Soil Temp 7", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_SOILTEMP8F: ("Soil Temp 8", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_SOILTEMP9F: ("Soil Temp 9", TEMP_FAHRENHEIT, SENSOR, "temperature"), TYPE_SOLARRADIATION: ( "Solar Rad", IRRADIATION_WATTS_PER_SQUARE_METER, - TYPE_SENSOR, + SENSOR, None, ), - TYPE_SOLARRADIATION_LX: ("Solar Rad (lx)", LIGHT_LUX, TYPE_SENSOR, "illuminance"), - TYPE_TEMP10F: ("Temp 10", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMP1F: ("Temp 1", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMP2F: ("Temp 2", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMP3F: ("Temp 3", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMP4F: ("Temp 4", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMP5F: ("Temp 5", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMP6F: ("Temp 6", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMP7F: ("Temp 7", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMP8F: ("Temp 8", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMP9F: ("Temp 9", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMPF: ("Temp", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMPINF: ("Inside Temp", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TOTALRAININ: ("Lifetime Rain", "in", TYPE_SENSOR, None), - TYPE_UV: ("uv", "Index", TYPE_SENSOR, None), - TYPE_PM25: ("PM25", CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, TYPE_SENSOR, None), + TYPE_SOLARRADIATION_LX: ("Solar Rad (lx)", LIGHT_LUX, SENSOR, "illuminance"), + TYPE_TEMP10F: ("Temp 10", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMP1F: ("Temp 1", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMP2F: ("Temp 2", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMP3F: ("Temp 3", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMP4F: ("Temp 4", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMP5F: ("Temp 5", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMP6F: ("Temp 6", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMP7F: ("Temp 7", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMP8F: ("Temp 8", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMP9F: ("Temp 9", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMPF: ("Temp", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMPINF: ("Inside Temp", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TOTALRAININ: ("Lifetime Rain", "in", SENSOR, None), + TYPE_UV: ("uv", "Index", SENSOR, None), + TYPE_PM25: ("PM25", CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, SENSOR, None), TYPE_PM25_24H: ( "PM25 24h Avg", CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - TYPE_SENSOR, + SENSOR, None, ), - TYPE_WEEKLYRAININ: ("Weekly Rain", "in", TYPE_SENSOR, None), - TYPE_WINDDIR: ("Wind Dir", DEGREE, TYPE_SENSOR, None), - TYPE_WINDDIR_AVG10M: ("Wind Dir Avg 10m", DEGREE, TYPE_SENSOR, None), - TYPE_WINDDIR_AVG2M: ("Wind Dir Avg 2m", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None), - TYPE_WINDGUSTDIR: ("Gust Dir", DEGREE, TYPE_SENSOR, None), - TYPE_WINDGUSTMPH: ("Wind Gust", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None), - TYPE_WINDSPDMPH_AVG10M: ("Wind Avg 10m", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None), - TYPE_WINDSPDMPH_AVG2M: ("Wind Avg 2m", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None), - TYPE_WINDSPEEDMPH: ("Wind Speed", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None), - TYPE_YEARLYRAININ: ("Yearly Rain", "in", TYPE_SENSOR, None), + TYPE_WEEKLYRAININ: ("Weekly Rain", "in", SENSOR, None), + TYPE_WINDDIR: ("Wind Dir", DEGREE, SENSOR, None), + TYPE_WINDDIR_AVG10M: ("Wind Dir Avg 10m", DEGREE, SENSOR, None), + TYPE_WINDDIR_AVG2M: ("Wind Dir Avg 2m", SPEED_MILES_PER_HOUR, SENSOR, None), + TYPE_WINDGUSTDIR: ("Gust Dir", DEGREE, SENSOR, None), + TYPE_WINDGUSTMPH: ("Wind Gust", SPEED_MILES_PER_HOUR, SENSOR, None), + TYPE_WINDSPDMPH_AVG10M: ("Wind Avg 10m", SPEED_MILES_PER_HOUR, SENSOR, None), + TYPE_WINDSPDMPH_AVG2M: ("Wind Avg 2m", SPEED_MILES_PER_HOUR, SENSOR, None), + TYPE_WINDSPEEDMPH: ("Wind Speed", SPEED_MILES_PER_HOUR, SENSOR, None), + TYPE_YEARLYRAININ: ("Yearly Rain", "in", SENSOR, None), } CONFIG_SCHEMA = vol.Schema( @@ -260,7 +264,7 @@ CONFIG_SCHEMA = vol.Schema( async def async_setup(hass, config): - """Set up the Ambient PWS component.""" + """Set up the Ambient PWS integration.""" hass.data[DOMAIN] = {} hass.data[DOMAIN][DATA_CLIENT] = {} @@ -322,8 +326,8 @@ async def async_unload_entry(hass, config_entry): hass.async_create_task(ambient.ws_disconnect()) tasks = [ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in ("binary_sensor", "sensor") + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] await asyncio.gather(*tasks) @@ -431,10 +435,10 @@ class AmbientStation: # attempt forward setup of the config entry (because it will have # already been done): if not self._entry_setup_complete: - for component in ("binary_sensor", "sensor"): + for platform in PLATFORMS: self._hass.async_create_task( self._hass.config_entries.async_forward_entry_setup( - self._config_entry, component + self._config_entry, platform ) ) self._entry_setup_complete = True diff --git a/homeassistant/components/ambient_station/binary_sensor.py b/homeassistant/components/ambient_station/binary_sensor.py index 0a3a91e515c..8fdeb862e2e 100644 --- a/homeassistant/components/ambient_station/binary_sensor.py +++ b/homeassistant/components/ambient_station/binary_sensor.py @@ -1,5 +1,8 @@ """Support for Ambient Weather Station binary sensors.""" -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DOMAIN as BINARY_SENSOR, + BinarySensorEntity, +) from homeassistant.const import ATTR_NAME from homeassistant.core import callback @@ -18,13 +21,7 @@ from . import ( TYPE_BATTOUT, AmbientWeatherEntity, ) -from .const import ( - ATTR_LAST_DATA, - ATTR_MONITORED_CONDITIONS, - DATA_CLIENT, - DOMAIN, - TYPE_BINARY_SENSOR, -) +from .const import ATTR_LAST_DATA, ATTR_MONITORED_CONDITIONS, DATA_CLIENT, DOMAIN async def async_setup_entry(hass, entry, async_add_entities): @@ -35,7 +32,7 @@ async def async_setup_entry(hass, entry, async_add_entities): for mac_address, station in ambient.stations.items(): for condition in station[ATTR_MONITORED_CONDITIONS]: name, _, kind, device_class = SENSOR_TYPES[condition] - if kind == TYPE_BINARY_SENSOR: + if kind == BINARY_SENSOR: binary_sensor_list.append( AmbientWeatherBinarySensor( ambient, diff --git a/homeassistant/components/ambient_station/const.py b/homeassistant/components/ambient_station/const.py index e59f926eac3..87b5ff61877 100644 --- a/homeassistant/components/ambient_station/const.py +++ b/homeassistant/components/ambient_station/const.py @@ -10,6 +10,3 @@ ATTR_MONITORED_CONDITIONS = "monitored_conditions" CONF_APP_KEY = "app_key" DATA_CLIENT = "data_client" - -TYPE_BINARY_SENSOR = "binary_sensor" -TYPE_SENSOR = "sensor" diff --git a/homeassistant/components/ambient_station/sensor.py b/homeassistant/components/ambient_station/sensor.py index 540e2facd4d..184927a1ef5 100644 --- a/homeassistant/components/ambient_station/sensor.py +++ b/homeassistant/components/ambient_station/sensor.py @@ -1,4 +1,5 @@ """Support for Ambient Weather Station sensors.""" +from homeassistant.components.binary_sensor import DOMAIN as SENSOR from homeassistant.const import ATTR_NAME from homeassistant.core import callback @@ -8,13 +9,7 @@ from . import ( TYPE_SOLARRADIATION_LX, AmbientWeatherEntity, ) -from .const import ( - ATTR_LAST_DATA, - ATTR_MONITORED_CONDITIONS, - DATA_CLIENT, - DOMAIN, - TYPE_SENSOR, -) +from .const import ATTR_LAST_DATA, ATTR_MONITORED_CONDITIONS, DATA_CLIENT, DOMAIN async def async_setup_entry(hass, entry, async_add_entities): @@ -25,7 +20,7 @@ async def async_setup_entry(hass, entry, async_add_entities): for mac_address, station in ambient.stations.items(): for condition in station[ATTR_MONITORED_CONDITIONS]: name, unit, kind, device_class = SENSOR_TYPES[condition] - if kind == TYPE_SENSOR: + if kind == SENSOR: sensor_list.append( AmbientWeatherSensor( ambient, diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index b41a107d126..c735ebc57e7 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -62,8 +62,8 @@ async def async_setup_entry(hass, entry): """Set up platforms and initiate connection.""" await asyncio.gather( *[ - hass.config_entries.async_forward_entry_setup(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_setup(entry, platform) + for platform in PLATFORMS ] ) await manager.init() diff --git a/homeassistant/components/atag/__init__.py b/homeassistant/components/atag/__init__.py index 7489ada3341..c4cffe1c41a 100644 --- a/homeassistant/components/atag/__init__.py +++ b/homeassistant/components/atag/__init__.py @@ -78,8 +78,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index 6f16f7d5b31..23419f4df2d 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -21,7 +21,6 @@ import homeassistant.helpers.config_validation as cv from .activity import ActivityStream from .const import ( - AUGUST_COMPONENTS, CONF_ACCESS_TOKEN_CACHE_FILE, CONF_INSTALL_ID, CONF_LOGIN_METHOD, @@ -32,6 +31,7 @@ from .const import ( DOMAIN, LOGIN_METHODS, MIN_TIME_BETWEEN_DETAIL_UPDATES, + PLATFORMS, VERIFICATION_CODE_KEY, ) from .exceptions import CannotConnect, InvalidAuth, RequireValidation @@ -137,9 +137,9 @@ async def async_setup_august(hass, config_entry, august_gateway): await hass.data[DOMAIN][entry_id][DATA_AUGUST].async_setup() - for component in AUGUST_COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -209,8 +209,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in AUGUST_COMPONENTS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/august/const.py b/homeassistant/components/august/const.py index a32e187647a..57e0d5a7fb7 100644 --- a/homeassistant/components/august/const.py +++ b/homeassistant/components/august/const.py @@ -43,4 +43,4 @@ ACTIVITY_UPDATE_INTERVAL = timedelta(seconds=10) LOGIN_METHODS = ["phone", "email"] -AUGUST_COMPONENTS = ["camera", "binary_sensor", "lock", "sensor"] +PLATFORMS = ["camera", "binary_sensor", "lock", "sensor"] diff --git a/homeassistant/components/aurora/__init__.py b/homeassistant/components/aurora/__init__.py index a187288e2e4..044b118b030 100644 --- a/homeassistant/components/aurora/__init__.py +++ b/homeassistant/components/aurora/__init__.py @@ -79,9 +79,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): AURORA_API: api, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -92,8 +92,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/blebox/__init__.py b/homeassistant/components/blebox/__init__.py index 3d3c997596a..e0b5a3c4f5d 100644 --- a/homeassistant/components/blebox/__init__.py +++ b/homeassistant/components/blebox/__init__.py @@ -48,9 +48,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): domain_entry = domain.setdefault(entry.entry_id, {}) product = domain_entry.setdefault(PRODUCT, product) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True diff --git a/homeassistant/components/blink/__init__.py b/homeassistant/components/blink/__init__.py index 0809018522e..c9c03dc654d 100644 --- a/homeassistant/components/blink/__init__.py +++ b/homeassistant/components/blink/__init__.py @@ -86,9 +86,9 @@ async def async_setup_entry(hass, entry): if not hass.data[DOMAIN][entry.entry_id].available: raise ConfigEntryNotReady - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) def blink_refresh(event_time=None): @@ -133,8 +133,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/bloomsky/__init__.py b/homeassistant/components/bloomsky/__init__.py index cd993e0332a..76ed9cdd12a 100644 --- a/homeassistant/components/bloomsky/__init__.py +++ b/homeassistant/components/bloomsky/__init__.py @@ -18,7 +18,7 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -BLOOMSKY_TYPE = ["camera", "binary_sensor", "sensor"] +PLATFORMS = ["camera", "binary_sensor", "sensor"] DOMAIN = "bloomsky" @@ -32,7 +32,7 @@ CONFIG_SCHEMA = vol.Schema( def setup(hass, config): - """Set up the BloomSky component.""" + """Set up the BloomSky integration.""" api_key = config[DOMAIN][CONF_API_KEY] try: @@ -42,8 +42,8 @@ def setup(hass, config): hass.data[DOMAIN] = bloomsky - for component in BLOOMSKY_TYPE: - discovery.load_platform(hass, component, DOMAIN, {}, config) + for platform in PLATFORMS: + discovery.load_platform(hass, platform, DOMAIN, {}, config) return True diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index a8bebfbc617..3a653ec252f 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -59,7 +59,7 @@ DEFAULT_OPTIONS = { CONF_USE_LOCATION: False, } -BMW_PLATFORMS = ["binary_sensor", "device_tracker", "lock", "notify", "sensor"] +PLATFORMS = ["binary_sensor", "device_tracker", "lock", "notify", "sensor"] UPDATE_INTERVAL = 5 # in minutes SERVICE_UPDATE_STATE = "update_state" @@ -138,13 +138,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): await _async_update_all() - for platform in BMW_PLATFORMS: + for platform in PLATFORMS: if platform != NOTIFY_DOMAIN: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform) ) - # set up notify platform, no entry support for notify component yet, + # set up notify platform, no entry support for notify platform yet, # have to use discovery to load platform. hass.async_create_task( discovery.async_load_platform( @@ -164,9 +164,9 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in BMW_PLATFORMS - if component != NOTIFY_DOMAIN + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS + if platform != NOTIFY_DOMAIN ] ) ) diff --git a/homeassistant/components/bond/__init__.py b/homeassistant/components/bond/__init__.py index ae9cc2111a2..9ec0c662b8e 100644 --- a/homeassistant/components/bond/__init__.py +++ b/homeassistant/components/bond/__init__.py @@ -65,9 +65,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: _async_remove_old_device_identifiers(config_entry_id, device_registry, hub) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -78,8 +78,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/braviatv/__init__.py b/homeassistant/components/braviatv/__init__.py index 46fd8675358..95debd9170d 100644 --- a/homeassistant/components/braviatv/__init__.py +++ b/homeassistant/components/braviatv/__init__.py @@ -28,9 +28,9 @@ async def async_setup_entry(hass, config_entry): UNDO_UPDATE_LISTENER: undo_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -41,8 +41,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/brother/__init__.py b/homeassistant/components/brother/__init__.py index d7cf906a87c..9cddf0efdd5 100644 --- a/homeassistant/components/brother/__init__.py +++ b/homeassistant/components/brother/__init__.py @@ -46,9 +46,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id] = coordinator hass.data[DOMAIN][SNMP] = snmp_engine - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -59,8 +59,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/canary/__init__.py b/homeassistant/components/canary/__init__.py index 64f2d00735e..b3fcf6e8641 100644 --- a/homeassistant/components/canary/__init__.py +++ b/homeassistant/components/canary/__init__.py @@ -107,9 +107,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool DATA_UNDO_UPDATE_LISTENER: undo_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -120,8 +120,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/climacell/__init__.py b/homeassistant/components/climacell/__init__.py index d6bf0ec4e12..6f1ac730f2f 100644 --- a/homeassistant/components/climacell/__init__.py +++ b/homeassistant/components/climacell/__init__.py @@ -117,9 +117,9 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) hass.data[DOMAIN][config_entry.entry_id] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -132,8 +132,8 @@ async def async_unload_entry( unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/control4/__init__.py b/homeassistant/components/control4/__init__.py index a45cdc4006a..7f8ec09cdde 100644 --- a/homeassistant/components/control4/__init__.py +++ b/homeassistant/components/control4/__init__.py @@ -115,9 +115,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): entry_data[CONF_CONFIG_LISTENER] = entry.add_update_listener(update_listener) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -134,8 +134,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/coronavirus/__init__.py b/homeassistant/components/coronavirus/__init__.py index fa8efebe154..d05c4cef862 100644 --- a/homeassistant/components/coronavirus/__init__.py +++ b/homeassistant/components/coronavirus/__init__.py @@ -44,9 +44,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): if not entry.unique_id: hass.config_entries.async_update_entry(entry, unique_id=entry.data["country"]) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -57,8 +57,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/daikin/__init__.py b/homeassistant/components/daikin/__init__.py index 16fd2b2ff56..d0bc109c082 100644 --- a/homeassistant/components/daikin/__init__.py +++ b/homeassistant/components/daikin/__init__.py @@ -25,7 +25,7 @@ DOMAIN = "daikin" PARALLEL_UPDATES = 0 MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) -COMPONENT_TYPES = ["climate", "sensor", "switch"] +PLATFORMS = ["climate", "sensor", "switch"] CONFIG_SCHEMA = vol.Schema( vol.All( @@ -83,9 +83,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): if not daikin_api: return False hass.data.setdefault(DOMAIN, {}).update({entry.entry_id: daikin_api}) - for component in COMPONENT_TYPES: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -94,8 +94,8 @@ async def async_unload_entry(hass, config_entry): """Unload a config entry.""" await asyncio.wait( [ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in COMPONENT_TYPES + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) hass.data[DOMAIN].pop(config_entry.entry_id) diff --git a/homeassistant/components/danfoss_air/__init__.py b/homeassistant/components/danfoss_air/__init__.py index b1dbf890eb9..18780c10310 100644 --- a/homeassistant/components/danfoss_air/__init__.py +++ b/homeassistant/components/danfoss_air/__init__.py @@ -13,7 +13,7 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -DANFOSS_AIR_PLATFORMS = ["sensor", "binary_sensor", "switch"] +PLATFORMS = ["sensor", "binary_sensor", "switch"] DOMAIN = "danfoss_air" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) @@ -29,7 +29,7 @@ def setup(hass, config): hass.data[DOMAIN] = DanfossAir(conf[CONF_HOST]) - for platform in DANFOSS_AIR_PLATFORMS: + for platform in PLATFORMS: discovery.load_platform(hass, platform, DOMAIN, {}, config) return True diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py index 67effa4e81b..64667a9e616 100644 --- a/homeassistant/components/deconz/const.py +++ b/homeassistant/components/deconz/const.py @@ -28,7 +28,7 @@ CONF_ALLOW_DECONZ_GROUPS = "allow_deconz_groups" CONF_ALLOW_NEW_DEVICES = "allow_new_devices" CONF_MASTER_GATEWAY = "master" -SUPPORTED_PLATFORMS = [ +PLATFORMS = [ BINARY_SENSOR_DOMAIN, CLIMATE_DOMAIN, COVER_DOMAIN, diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py index a6cbb2acef9..2b38f6956be 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/gateway.py @@ -26,7 +26,7 @@ from .const import ( NEW_LIGHT, NEW_SCENE, NEW_SENSOR, - SUPPORTED_PLATFORMS, + PLATFORMS, ) from .deconz_event import async_setup_events, async_unload_events from .errors import AuthenticationRequired, CannotConnect @@ -184,10 +184,10 @@ class DeconzGateway: ) return False - for component in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: self.hass.async_create_task( self.hass.config_entries.async_forward_entry_setup( - self.config_entry, component + self.config_entry, platform ) ) @@ -259,9 +259,9 @@ class DeconzGateway: self.api.async_connection_status_callback = None self.api.close() - for component in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: await self.hass.config_entries.async_forward_entry_unload( - self.config_entry, component + self.config_entry, platform ) for unsub_dispatcher in self.listeners: diff --git a/homeassistant/components/demo/__init__.py b/homeassistant/components/demo/__init__.py index 09c3d27a1bc..b32537ae44e 100644 --- a/homeassistant/components/demo/__init__.py +++ b/homeassistant/components/demo/__init__.py @@ -50,9 +50,9 @@ async def async_setup(hass, config): ) # Set up demo platforms - for component in COMPONENTS_WITH_DEMO_PLATFORM: + for platform in COMPONENTS_WITH_DEMO_PLATFORM: hass.async_create_task( - hass.helpers.discovery.async_load_platform(component, DOMAIN, {}, config) + hass.helpers.discovery.async_load_platform(platform, DOMAIN, {}, config) ) config.setdefault(ha.DOMAIN, {}) @@ -146,9 +146,9 @@ async def async_setup(hass, config): async def async_setup_entry(hass, config_entry): """Set the config entry up.""" # Set up demo platforms with config entry - for component in COMPONENTS_WITH_CONFIG_ENTRY_DEMO_PLATFORM: + for platform in COMPONENTS_WITH_CONFIG_ENTRY_DEMO_PLATFORM: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True diff --git a/homeassistant/components/dexcom/__init__.py b/homeassistant/components/dexcom/__init__.py index c2eb9bd466d..5455c49232b 100644 --- a/homeassistant/components/dexcom/__init__.py +++ b/homeassistant/components/dexcom/__init__.py @@ -70,9 +70,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): await hass.data[DOMAIN][entry.entry_id][COORDINATOR].async_refresh() - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -83,8 +83,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/directv/__init__.py b/homeassistant/components/directv/__init__.py index 22a97b9e82e..34b8c5cd30c 100644 --- a/homeassistant/components/directv/__init__.py +++ b/homeassistant/components/directv/__init__.py @@ -45,9 +45,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN][entry.entry_id] = dtv - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -58,8 +58,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/doorbird/__init__.py b/homeassistant/components/doorbird/__init__.py index 22db3c76273..8376d75ccbb 100644 --- a/homeassistant/components/doorbird/__init__.py +++ b/homeassistant/components/doorbird/__init__.py @@ -168,9 +168,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): UNDO_UPDATE_LISTENER: undo_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -188,8 +188,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/dsmr/__init__.py b/homeassistant/components/dsmr/__init__.py index 50823bb1d29..cda3838bf78 100644 --- a/homeassistant/components/dsmr/__init__.py +++ b/homeassistant/components/dsmr/__init__.py @@ -44,8 +44,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/dunehd/__init__.py b/homeassistant/components/dunehd/__init__.py index a1fa456aa09..68a6f0d83de 100644 --- a/homeassistant/components/dunehd/__init__.py +++ b/homeassistant/components/dunehd/__init__.py @@ -24,9 +24,9 @@ async def async_setup_entry(hass, config_entry): hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][config_entry.entry_id] = player - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -37,8 +37,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/dynalite/__init__.py b/homeassistant/components/dynalite/__init__.py index e52ec5946b6..e19ab319896 100644 --- a/homeassistant/components/dynalite/__init__.py +++ b/homeassistant/components/dynalite/__init__.py @@ -47,8 +47,8 @@ from .const import ( DEFAULT_PORT, DEFAULT_TEMPLATES, DOMAIN, - ENTITY_PLATFORMS, LOGGER, + PLATFORMS, SERVICE_REQUEST_AREA_PRESET, SERVICE_REQUEST_CHANNEL_LEVEL, ) @@ -267,14 +267,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # need to do it before the listener hass.data[DOMAIN][entry.entry_id] = bridge entry.add_update_listener(async_entry_changed) + if not await bridge.async_setup(): LOGGER.error("Could not set up bridge for entry %s", entry.data) hass.data[DOMAIN][entry.entry_id] = None raise ConfigEntryNotReady - for platform in ENTITY_PLATFORMS: + + for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform) ) + return True @@ -284,7 +287,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN].pop(entry.entry_id) tasks = [ hass.config_entries.async_forward_entry_unload(entry, platform) - for platform in ENTITY_PLATFORMS + for platform in PLATFORMS ] results = await asyncio.gather(*tasks) return False not in results diff --git a/homeassistant/components/dynalite/bridge.py b/homeassistant/components/dynalite/bridge.py index 5bf21801b3d..e024ba11802 100644 --- a/homeassistant/components/dynalite/bridge.py +++ b/homeassistant/components/dynalite/bridge.py @@ -16,14 +16,7 @@ from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_send -from .const import ( - ATTR_AREA, - ATTR_HOST, - ATTR_PACKET, - ATTR_PRESET, - ENTITY_PLATFORMS, - LOGGER, -) +from .const import ATTR_AREA, ATTR_HOST, ATTR_PACKET, ATTR_PRESET, LOGGER, PLATFORMS from .convert_config import convert_config @@ -107,7 +100,7 @@ class DynaliteBridge: def add_devices_when_registered(self, devices: List[DynaliteBaseDevice]) -> None: """Add the devices to HA if the add devices callback was registered, otherwise queue until it is.""" - for platform in ENTITY_PLATFORMS: + for platform in PLATFORMS: platform_devices = [ device for device in devices if device.category == platform ] diff --git a/homeassistant/components/dynalite/const.py b/homeassistant/components/dynalite/const.py index 3f1e201f3fd..eda43305461 100644 --- a/homeassistant/components/dynalite/const.py +++ b/homeassistant/components/dynalite/const.py @@ -6,7 +6,7 @@ from homeassistant.const import CONF_ROOM LOGGER = logging.getLogger(__package__) DOMAIN = "dynalite" -ENTITY_PLATFORMS = ["light", "switch", "cover"] +PLATFORMS = ["light", "switch", "cover"] CONF_ACTIVE = "active" diff --git a/homeassistant/components/dyson/__init__.py b/homeassistant/components/dyson/__init__.py index b39af2a2fd1..d8023b42973 100644 --- a/homeassistant/components/dyson/__init__.py +++ b/homeassistant/components/dyson/__init__.py @@ -17,7 +17,7 @@ CONF_RETRY = "retry" DEFAULT_TIMEOUT = 5 DEFAULT_RETRY = 10 DYSON_DEVICES = "dyson_devices" -DYSON_PLATFORMS = ["sensor", "fan", "vacuum", "climate", "air_quality"] +PLATFORMS = ["sensor", "fan", "vacuum", "climate", "air_quality"] DOMAIN = "dyson" @@ -105,7 +105,7 @@ def setup(hass, config): # Start fan/sensors components if hass.data[DYSON_DEVICES]: _LOGGER.debug("Starting sensor/fan components") - for platform in DYSON_PLATFORMS: + for platform in PLATFORMS: discovery.load_platform(hass, platform, DOMAIN, {}, config) return True diff --git a/homeassistant/components/ecobee/__init__.py b/homeassistant/components/ecobee/__init__.py index 26bfbe5b3da..015ee1fbf6c 100644 --- a/homeassistant/components/ecobee/__init__.py +++ b/homeassistant/components/ecobee/__init__.py @@ -10,13 +10,7 @@ from homeassistant.const import CONF_API_KEY from homeassistant.helpers import config_validation as cv from homeassistant.util import Throttle -from .const import ( - _LOGGER, - CONF_REFRESH_TOKEN, - DATA_ECOBEE_CONFIG, - DOMAIN, - ECOBEE_PLATFORMS, -) +from .const import _LOGGER, CONF_REFRESH_TOKEN, DATA_ECOBEE_CONFIG, DOMAIN, PLATFORMS MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=180) @@ -32,7 +26,7 @@ async def async_setup(hass, config): But, an "ecobee:" entry in configuration.yaml will trigger an import flow if a config entry doesn't already exist. If ecobee.conf exists, the import flow will attempt to import it and create a config entry, to assist users - migrating from the old ecobee component. Otherwise, the user will have to + migrating from the old ecobee integration. Otherwise, the user will have to continue setting up the integration via the config flow. """ hass.data[DATA_ECOBEE_CONFIG] = config.get(DOMAIN, {}) @@ -66,9 +60,9 @@ async def async_setup_entry(hass, entry): hass.data[DOMAIN] = data - for component in ECOBEE_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -120,7 +114,7 @@ async def async_unload_entry(hass, config_entry): hass.data.pop(DOMAIN) tasks = [] - for platform in ECOBEE_PLATFORMS: + for platform in PLATFORMS: tasks.append( hass.config_entries.async_forward_entry_unload(config_entry, platform) ) diff --git a/homeassistant/components/ecobee/const.py b/homeassistant/components/ecobee/const.py index 5ec3a0fcf96..44abafe8380 100644 --- a/homeassistant/components/ecobee/const.py +++ b/homeassistant/components/ecobee/const.py @@ -37,7 +37,7 @@ ECOBEE_MODEL_TO_NAME = { "vulcanSmart": "ecobee4 Smart", } -ECOBEE_PLATFORMS = ["binary_sensor", "climate", "sensor", "weather"] +PLATFORMS = ["binary_sensor", "climate", "sensor", "weather"] MANUFACTURER = "ecobee" diff --git a/homeassistant/components/econet/__init__.py b/homeassistant/components/econet/__init__.py index dce4550eb1b..03ca9c76120 100644 --- a/homeassistant/components/econet/__init__.py +++ b/homeassistant/components/econet/__init__.py @@ -60,9 +60,9 @@ async def async_setup_entry(hass, config_entry): hass.data[DOMAIN][API_CLIENT][config_entry.entry_id] = api hass.data[DOMAIN][EQUIPMENT][config_entry.entry_id] = equipment - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) api.subscribe() @@ -92,8 +92,8 @@ async def async_setup_entry(hass, config_entry): async def async_unload_entry(hass, entry): """Unload a EcoNet config entry.""" tasks = [ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] await asyncio.gather(*tasks) diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index d50c5d65d90..3dddd670bb4 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -52,7 +52,7 @@ SYNC_TIMEOUT = 120 _LOGGER = logging.getLogger(__name__) -SUPPORTED_DOMAINS = [ +PLATFORMS = [ "alarm_control_panel", "climate", "light", @@ -262,9 +262,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): "keypads": {}, } - for component in SUPPORTED_DOMAINS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -289,8 +289,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in SUPPORTED_DOMAINS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/epson/__init__.py b/homeassistant/components/epson/__init__.py index c7278ed2cc9..51d464dacb5 100644 --- a/homeassistant/components/epson/__init__.py +++ b/homeassistant/components/epson/__init__.py @@ -48,9 +48,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): _LOGGER.warning("Cannot connect to projector %s", entry.data[CONF_HOST]) return False hass.data[DOMAIN][entry.entry_id] = projector - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -60,8 +60,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/faa_delays/__init__.py b/homeassistant/components/faa_delays/__init__.py index b9def765123..fd834da48d8 100644 --- a/homeassistant/components/faa_delays/__init__.py +++ b/homeassistant/components/faa_delays/__init__.py @@ -39,9 +39,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN][entry.entry_id] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -52,8 +52,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/fibaro/__init__.py b/homeassistant/components/fibaro/__init__.py index 51e31cf27ad..d2a81468e05 100644 --- a/homeassistant/components/fibaro/__init__.py +++ b/homeassistant/components/fibaro/__init__.py @@ -37,7 +37,7 @@ CONF_RESET_COLOR = "reset_color" DOMAIN = "fibaro" FIBARO_CONTROLLERS = "fibaro_controllers" FIBARO_DEVICES = "fibaro_devices" -FIBARO_COMPONENTS = [ +PLATFORMS = [ "binary_sensor", "climate", "cover", @@ -365,21 +365,21 @@ def setup(hass, base_config): controller.disable_state_handler() hass.data[FIBARO_DEVICES] = {} - for component in FIBARO_COMPONENTS: - hass.data[FIBARO_DEVICES][component] = [] + for platform in PLATFORMS: + hass.data[FIBARO_DEVICES][platform] = [] for gateway in gateways: controller = FibaroController(gateway) if controller.connect(): hass.data[FIBARO_CONTROLLERS][controller.hub_serial] = controller - for component in FIBARO_COMPONENTS: - hass.data[FIBARO_DEVICES][component].extend( - controller.fibaro_devices[component] + for platform in PLATFORMS: + hass.data[FIBARO_DEVICES][platform].extend( + controller.fibaro_devices[platform] ) if hass.data[FIBARO_CONTROLLERS]: - for component in FIBARO_COMPONENTS: - discovery.load_platform(hass, component, DOMAIN, {}, base_config) + for platform in PLATFORMS: + discovery.load_platform(hass, platform, DOMAIN, {}, base_config) for controller in hass.data[FIBARO_CONTROLLERS].values(): controller.enable_state_handler() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_fibaro) diff --git a/homeassistant/components/fireservicerota/__init__.py b/homeassistant/components/fireservicerota/__init__.py index bf5f3f6beea..513a572b2c2 100644 --- a/homeassistant/components/fireservicerota/__init__.py +++ b/homeassistant/components/fireservicerota/__init__.py @@ -26,7 +26,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) _LOGGER = logging.getLogger(__name__) -SUPPORTED_PLATFORMS = {SENSOR_DOMAIN, BINARYSENSOR_DOMAIN, SWITCH_DOMAIN} +PLATFORMS = [SENSOR_DOMAIN, BINARYSENSOR_DOMAIN, SWITCH_DOMAIN] async def async_setup(hass: HomeAssistant, config: dict) -> bool: @@ -64,7 +64,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: DATA_COORDINATOR: coordinator, } - for platform in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform) ) @@ -83,7 +83,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await asyncio.gather( *[ hass.config_entries.async_forward_entry_unload(entry, platform) - for platform in SUPPORTED_PLATFORMS + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/flo/__init__.py b/homeassistant/components/flo/__init__.py index b57cdd5f871..71f8a8bfe5c 100644 --- a/homeassistant/components/flo/__init__.py +++ b/homeassistant/components/flo/__init__.py @@ -49,9 +49,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): tasks = [device.async_refresh() for device in devices] await asyncio.gather(*tasks) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -62,8 +62,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/flume/__init__.py b/homeassistant/components/flume/__init__.py index b9a5fc17682..fb87588ac82 100644 --- a/homeassistant/components/flume/__init__.py +++ b/homeassistant/components/flume/__init__.py @@ -79,9 +79,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): FLUME_HTTP_SESSION: http_session, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -92,8 +92,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/flunearyou/__init__.py b/homeassistant/components/flunearyou/__init__.py index 46442f112b6..8e5e3762f32 100644 --- a/homeassistant/components/flunearyou/__init__.py +++ b/homeassistant/components/flunearyou/__init__.py @@ -67,9 +67,9 @@ async def async_setup_entry(hass, config_entry): await asyncio.gather(*data_init_tasks) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -80,8 +80,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/foscam/__init__.py b/homeassistant/components/foscam/__init__.py index 6a2c961544f..1b3ae5e7216 100644 --- a/homeassistant/components/foscam/__init__.py +++ b/homeassistant/components/foscam/__init__.py @@ -22,9 +22,9 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up foscam from a config entry.""" - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) hass.data[DOMAIN][entry.entry_id] = entry.data @@ -37,8 +37,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/fritzbox/__init__.py b/homeassistant/components/fritzbox/__init__.py index 3c01657da4e..34f56ddc6f9 100644 --- a/homeassistant/components/fritzbox/__init__.py +++ b/homeassistant/components/fritzbox/__init__.py @@ -93,9 +93,9 @@ async def async_setup_entry(hass, entry): hass.data.setdefault(DOMAIN, {CONF_CONNECTIONS: {}, CONF_DEVICES: set()}) hass.data[DOMAIN][CONF_CONNECTIONS][entry.entry_id] = fritz - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) def logout_fritzbox(event): @@ -115,8 +115,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/fritzbox_callmonitor/__init__.py b/homeassistant/components/fritzbox_callmonitor/__init__.py index 933dd797dfc..0527b7164d3 100644 --- a/homeassistant/components/fritzbox_callmonitor/__init__.py +++ b/homeassistant/components/fritzbox_callmonitor/__init__.py @@ -59,9 +59,9 @@ async def async_setup_entry(hass, config_entry): UNDO_UPDATE_LISTENER: undo_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -73,8 +73,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/garmin_connect/__init__.py b/homeassistant/components/garmin_connect/__init__.py index 896c243f386..c009124b024 100644 --- a/homeassistant/components/garmin_connect/__init__.py +++ b/homeassistant/components/garmin_connect/__init__.py @@ -57,9 +57,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): garmin_data = GarminConnectData(hass, garmin_client) hass.data[DOMAIN][entry.entry_id] = garmin_data - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -70,8 +70,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/goalzero/__init__.py b/homeassistant/components/goalzero/__init__.py index ff60a9ac043..e00b17ebae4 100644 --- a/homeassistant/components/goalzero/__init__.py +++ b/homeassistant/components/goalzero/__init__.py @@ -76,8 +76,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/guardian/__init__.py b/homeassistant/components/guardian/__init__.py index fdc82f14778..465e1d730af 100644 --- a/homeassistant/components/guardian/__init__.py +++ b/homeassistant/components/guardian/__init__.py @@ -104,9 +104,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ].async_add_listener(async_process_paired_sensor_uids) # Set up all of the Guardian entity platforms: - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -117,8 +117,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/habitica/__init__.py b/homeassistant/components/habitica/__init__.py index c70d374dfe9..159e760e223 100644 --- a/homeassistant/components/habitica/__init__.py +++ b/homeassistant/components/habitica/__init__.py @@ -148,9 +148,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b ) data[config_entry.entry_id] = api - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) if not hass.services.has_service(DOMAIN, SERVICE_API_CALL): @@ -166,8 +166,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/harmony/__init__.py b/homeassistant/components/harmony/__init__.py index 8445c7be937..c4056044ca0 100644 --- a/homeassistant/components/harmony/__init__.py +++ b/homeassistant/components/harmony/__init__.py @@ -48,9 +48,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): entry.add_update_listener(_update_listener) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -108,8 +108,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/home_connect/__init__.py b/homeassistant/components/home_connect/__init__.py index 301bd1976e6..baf4fd17f85 100644 --- a/homeassistant/components/home_connect/__init__.py +++ b/homeassistant/components/home_connect/__init__.py @@ -71,9 +71,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await update_all_devices(hass, entry) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -84,8 +84,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/homematicip_cloud/const.py b/homeassistant/components/homematicip_cloud/const.py index 4fb21febb40..31ccb8b9bc7 100644 --- a/homeassistant/components/homematicip_cloud/const.py +++ b/homeassistant/components/homematicip_cloud/const.py @@ -16,7 +16,7 @@ _LOGGER = logging.getLogger(".") DOMAIN = "homematicip_cloud" -COMPONENTS = [ +PLATFORMS = [ ALARM_CONTROL_PANEL_DOMAIN, BINARY_SENSOR_DOMAIN, CLIMATE_DOMAIN, diff --git a/homeassistant/components/homematicip_cloud/hap.py b/homeassistant/components/homematicip_cloud/hap.py index 151807391b9..5ad4efed1f6 100644 --- a/homeassistant/components/homematicip_cloud/hap.py +++ b/homeassistant/components/homematicip_cloud/hap.py @@ -13,7 +13,7 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.typing import HomeAssistantType -from .const import COMPONENTS, HMIPC_AUTHTOKEN, HMIPC_HAPID, HMIPC_NAME, HMIPC_PIN +from .const import HMIPC_AUTHTOKEN, HMIPC_HAPID, HMIPC_NAME, HMIPC_PIN, PLATFORMS from .errors import HmipcConnectionError _LOGGER = logging.getLogger(__name__) @@ -102,10 +102,10 @@ class HomematicipHAP: "Connected to HomematicIP with HAP %s", self.config_entry.unique_id ) - for component in COMPONENTS: + for platform in PLATFORMS: self.hass.async_create_task( self.hass.config_entries.async_forward_entry_setup( - self.config_entry, component + self.config_entry, platform ) ) return True @@ -215,9 +215,9 @@ class HomematicipHAP: self._retry_task.cancel() await self.home.disable_events() _LOGGER.info("Closed connection to HomematicIP cloud server") - for component in COMPONENTS: + for platform in PLATFORMS: await self.hass.config_entries.async_forward_entry_unload( - self.config_entry, component + self.config_entry, platform ) self.hmip_device_by_entity_id = {} return True diff --git a/homeassistant/components/hunterdouglas_powerview/__init__.py b/homeassistant/components/hunterdouglas_powerview/__init__.py index 7555146ba8e..2a5c5061cae 100644 --- a/homeassistant/components/hunterdouglas_powerview/__init__.py +++ b/homeassistant/components/hunterdouglas_powerview/__init__.py @@ -132,9 +132,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): DEVICE_INFO: device_info, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -180,8 +180,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/hvv_departures/__init__.py b/homeassistant/components/hvv_departures/__init__.py index e003b25ea85..c90e5cb6d9c 100644 --- a/homeassistant/components/hvv_departures/__init__.py +++ b/homeassistant/components/hvv_departures/__init__.py @@ -32,9 +32,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = hub - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -45,8 +45,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/hyperion/__init__.py b/homeassistant/components/hyperion/__init__.py index 8c001c2b467..e5e6cb4494a 100644 --- a/homeassistant/components/hyperion/__init__.py +++ b/homeassistant/components/hyperion/__init__.py @@ -281,8 +281,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b async def setup_then_listen() -> None: await asyncio.gather( *[ - hass.config_entries.async_forward_entry_setup(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_setup(config_entry, platform) + for platform in PLATFORMS ] ) assert hyperion_client @@ -310,8 +310,8 @@ async def async_unload_entry( unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/ihc/__init__.py b/homeassistant/components/ihc/__init__.py index c539156b759..959d86a7cc1 100644 --- a/homeassistant/components/ihc/__init__.py +++ b/homeassistant/components/ihc/__init__.py @@ -55,7 +55,7 @@ DOMAIN = "ihc" IHC_CONTROLLER = "controller" IHC_INFO = "info" -IHC_PLATFORMS = ("binary_sensor", "light", "sensor", "switch") +PLATFORMS = ("binary_sensor", "light", "sensor", "switch") def validate_name(config): @@ -219,7 +219,7 @@ PULSE_SCHEMA = vol.Schema( def setup(hass, config): - """Set up the IHC platform.""" + """Set up the IHC integration.""" conf = config.get(DOMAIN) for index, controller_conf in enumerate(conf): if not ihc_setup(hass, config, controller_conf, index): @@ -229,7 +229,7 @@ def setup(hass, config): def ihc_setup(hass, config, conf, controller_id): - """Set up the IHC component.""" + """Set up the IHC integration.""" url = conf[CONF_URL] username = conf[CONF_USERNAME] password = conf[CONF_PASSWORD] @@ -256,11 +256,11 @@ def ihc_setup(hass, config, conf, controller_id): def get_manual_configuration(hass, config, conf, ihc_controller, controller_id): """Get manual configuration for IHC devices.""" - for component in IHC_PLATFORMS: + for platform in PLATFORMS: discovery_info = {} - if component in conf: - component_setup = conf.get(component) - for sensor_cfg in component_setup: + if platform in conf: + platform_setup = conf.get(platform) + for sensor_cfg in platform_setup: name = sensor_cfg[CONF_NAME] device = { "ihc_id": sensor_cfg[CONF_ID], @@ -281,7 +281,7 @@ def get_manual_configuration(hass, config, conf, ihc_controller, controller_id): } discovery_info[name] = device if discovery_info: - discovery.load_platform(hass, component, DOMAIN, discovery_info, config) + discovery.load_platform(hass, platform, DOMAIN, discovery_info, config) def autosetup_ihc_products( @@ -304,21 +304,23 @@ def autosetup_ihc_products( except vol.Invalid as exception: _LOGGER.error("Invalid IHC auto setup data: %s", exception) return False + groups = project.findall(".//group") - for component in IHC_PLATFORMS: - component_setup = auto_setup_conf[component] - discovery_info = get_discovery_info(component_setup, groups, controller_id) + for platform in PLATFORMS: + platform_setup = auto_setup_conf[platform] + discovery_info = get_discovery_info(platform_setup, groups, controller_id) if discovery_info: - discovery.load_platform(hass, component, DOMAIN, discovery_info, config) + discovery.load_platform(hass, platform, DOMAIN, discovery_info, config) + return True -def get_discovery_info(component_setup, groups, controller_id): - """Get discovery info for specified IHC component.""" +def get_discovery_info(platform_setup, groups, controller_id): + """Get discovery info for specified IHC platform.""" discovery_data = {} for group in groups: groupname = group.attrib["name"] - for product_cfg in component_setup: + for product_cfg in platform_setup: products = group.findall(product_cfg[CONF_XPATH]) for product in products: nodes = product.findall(product_cfg[CONF_NODE]) diff --git a/homeassistant/components/insteon/__init__.py b/homeassistant/components/insteon/__init__.py index 0188671e07f..7fb2178def4 100644 --- a/homeassistant/components/insteon/__init__.py +++ b/homeassistant/components/insteon/__init__.py @@ -17,7 +17,7 @@ from .const import ( CONF_UNITCODE, CONF_X10, DOMAIN, - INSTEON_COMPONENTS, + INSTEON_PLATFORMS, ON_OFF_EVENTS, ) from .schemas import convert_yaml_to_config_flow @@ -138,9 +138,9 @@ async def async_setup_entry(hass, entry): ) device = devices.add_x10_device(housecode, unitcode, x10_type, steps) - for component in INSTEON_COMPONENTS: + for platform in INSTEON_PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) for address in devices: diff --git a/homeassistant/components/insteon/const.py b/homeassistant/components/insteon/const.py index 07717dade9b..a40a0b0d4b0 100644 --- a/homeassistant/components/insteon/const.py +++ b/homeassistant/components/insteon/const.py @@ -34,7 +34,7 @@ from pyinsteon.groups import ( DOMAIN = "insteon" -INSTEON_COMPONENTS = [ +INSTEON_PLATFORMS = [ "binary_sensor", "climate", "cover", diff --git a/homeassistant/components/ipp/__init__.py b/homeassistant/components/ipp/__init__.py index 7a18da03ddc..c36b0cb7959 100644 --- a/homeassistant/components/ipp/__init__.py +++ b/homeassistant/components/ipp/__init__.py @@ -66,9 +66,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if not coordinator.last_update_success: raise ConfigEntryNotReady - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -79,8 +79,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/iqvia/__init__.py b/homeassistant/components/iqvia/__init__.py index bf1725a036a..4dc066d7629 100644 --- a/homeassistant/components/iqvia/__init__.py +++ b/homeassistant/components/iqvia/__init__.py @@ -84,9 +84,9 @@ async def async_setup_entry(hass, entry): await asyncio.gather(*init_data_update_tasks) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -97,8 +97,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py index 9fd844521d7..d07fef97edd 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -31,7 +31,7 @@ from .const import ( ISY994_PROGRAMS, ISY994_VARIABLES, MANUFACTURER, - SUPPORTED_PLATFORMS, + PLATFORMS, SUPPORTED_PROGRAM_PLATFORMS, UNDO_UPDATE_LISTENER, ) @@ -111,7 +111,7 @@ async def async_setup_entry( hass_isy_data = hass.data[DOMAIN][entry.entry_id] hass_isy_data[ISY994_NODES] = {} - for platform in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: hass_isy_data[ISY994_NODES][platform] = [] hass_isy_data[ISY994_PROGRAMS] = {} @@ -176,7 +176,7 @@ async def async_setup_entry( await _async_get_or_create_isy_device_in_registry(hass, entry, isy) # Load platforms for the devices in the ISY controller that we support. - for platform in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform) ) @@ -248,7 +248,7 @@ async def async_unload_entry( await asyncio.gather( *[ hass.config_entries.async_forward_entry_unload(entry, platform) - for platform in SUPPORTED_PLATFORMS + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/isy994/const.py b/homeassistant/components/isy994/const.py index ef75a974377..9fdef92c84f 100644 --- a/homeassistant/components/isy994/const.py +++ b/homeassistant/components/isy994/const.py @@ -129,7 +129,7 @@ DEFAULT_VAR_SENSOR_STRING = "HA." KEY_ACTIONS = "actions" KEY_STATUS = "status" -SUPPORTED_PLATFORMS = [BINARY_SENSOR, SENSOR, LOCK, FAN, COVER, LIGHT, SWITCH, CLIMATE] +PLATFORMS = [BINARY_SENSOR, SENSOR, LOCK, FAN, COVER, LIGHT, SWITCH, CLIMATE] SUPPORTED_PROGRAM_PLATFORMS = [BINARY_SENSOR, LOCK, FAN, COVER, SWITCH] SUPPORTED_BIN_SENS_CLASSES = ["moisture", "opening", "motion", "climate"] diff --git a/homeassistant/components/isy994/helpers.py b/homeassistant/components/isy994/helpers.py index f9834586583..fecf18f789b 100644 --- a/homeassistant/components/isy994/helpers.py +++ b/homeassistant/components/isy994/helpers.py @@ -38,12 +38,12 @@ from .const import ( KEY_ACTIONS, KEY_STATUS, NODE_FILTERS, + PLATFORMS, SUBNODE_CLIMATE_COOL, SUBNODE_CLIMATE_HEAT, SUBNODE_EZIO2X4_SENSORS, SUBNODE_FANLINC_LIGHT, SUBNODE_IOLINC_RELAY, - SUPPORTED_PLATFORMS, SUPPORTED_PROGRAM_PLATFORMS, TYPE_CATEGORY_SENSOR_ACTUATORS, TYPE_EZIO2X4, @@ -69,7 +69,7 @@ def _check_for_node_def( node_def_id = node.node_def_id - platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform] + platforms = PLATFORMS if not single_platform else [single_platform] for platform in platforms: if node_def_id in NODE_FILTERS[platform][FILTER_NODE_DEF_ID]: hass_isy_data[ISY994_NODES][platform].append(node) @@ -94,7 +94,7 @@ def _check_for_insteon_type( return False device_type = node.type - platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform] + platforms = PLATFORMS if not single_platform else [single_platform] for platform in platforms: if any( device_type.startswith(t) @@ -159,7 +159,7 @@ def _check_for_zwave_cat( return False device_type = node.zwave_props.category - platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform] + platforms = PLATFORMS if not single_platform else [single_platform] for platform in platforms: if any( device_type.startswith(t) @@ -198,7 +198,7 @@ def _check_for_uom_id( return True return False - platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform] + platforms = PLATFORMS if not single_platform else [single_platform] for platform in platforms: if node_uom in NODE_FILTERS[platform][FILTER_UOM]: hass_isy_data[ISY994_NODES][platform].append(node) @@ -235,7 +235,7 @@ def _check_for_states_in_uom( return True return False - platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform] + platforms = PLATFORMS if not single_platform else [single_platform] for platform in platforms: if node_uom == set(NODE_FILTERS[platform][FILTER_STATES]): hass_isy_data[ISY994_NODES][platform].append(node) diff --git a/homeassistant/components/isy994/services.py b/homeassistant/components/isy994/services.py index 3e9e0ae1d29..b9c4bcdeef2 100644 --- a/homeassistant/components/isy994/services.py +++ b/homeassistant/components/isy994/services.py @@ -27,7 +27,7 @@ from .const import ( ISY994_NODES, ISY994_PROGRAMS, ISY994_VARIABLES, - SUPPORTED_PLATFORMS, + PLATFORMS, SUPPORTED_PROGRAM_PLATFORMS, ) @@ -279,7 +279,7 @@ def async_setup_services(hass: HomeAssistantType): hass_isy_data = hass.data[DOMAIN][config_entry_id] uuid = hass_isy_data[ISY994_ISY].configuration["uuid"] - for platform in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: for node in hass_isy_data[ISY994_NODES][platform]: if hasattr(node, "address"): current_unique_ids.append(f"{uuid}_{node.address}") diff --git a/homeassistant/components/juicenet/__init__.py b/homeassistant/components/juicenet/__init__.py index 080df7be6bf..42bce53f911 100644 --- a/homeassistant/components/juicenet/__init__.py +++ b/homeassistant/components/juicenet/__init__.py @@ -91,9 +91,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): await coordinator.async_refresh() - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -104,8 +104,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/kaiterra/__init__.py b/homeassistant/components/kaiterra/__init__.py index d043dc15eaf..eae14bd330e 100644 --- a/homeassistant/components/kaiterra/__init__.py +++ b/homeassistant/components/kaiterra/__init__.py @@ -25,7 +25,7 @@ from .const import ( DEFAULT_PREFERRED_UNIT, DEFAULT_SCAN_INTERVAL, DOMAIN, - KAITERRA_COMPONENTS, + PLATFORMS, ) KAITERRA_DEVICE_SCHEMA = vol.Schema( @@ -54,7 +54,7 @@ CONFIG_SCHEMA = vol.Schema({DOMAIN: KAITERRA_SCHEMA}, extra=vol.ALLOW_EXTRA) async def async_setup(hass, config): - """Set up the Kaiterra components.""" + """Set up the Kaiterra integration.""" conf = config[DOMAIN] scan_interval = conf[CONF_SCAN_INTERVAL] @@ -76,11 +76,11 @@ async def async_setup(hass, config): device.get(CONF_NAME) or device[CONF_TYPE], device[CONF_DEVICE_ID], ) - for component in KAITERRA_COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( async_load_platform( hass, - component, + platform, DOMAIN, {CONF_NAME: device_name, CONF_DEVICE_ID: device_id}, config, diff --git a/homeassistant/components/kaiterra/const.py b/homeassistant/components/kaiterra/const.py index 583cd60085d..f4cc5638c18 100644 --- a/homeassistant/components/kaiterra/const.py +++ b/homeassistant/components/kaiterra/const.py @@ -71,4 +71,4 @@ DEFAULT_AQI_STANDARD = "us" DEFAULT_PREFERRED_UNIT = [] DEFAULT_SCAN_INTERVAL = timedelta(seconds=30) -KAITERRA_COMPONENTS = ["sensor", "air_quality"] +PLATFORMS = ["sensor", "air_quality"] diff --git a/homeassistant/components/keenetic_ndms2/__init__.py b/homeassistant/components/keenetic_ndms2/__init__.py index 42d747b5238..d0217b2a4f5 100644 --- a/homeassistant/components/keenetic_ndms2/__init__.py +++ b/homeassistant/components/keenetic_ndms2/__init__.py @@ -44,9 +44,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b UNDO_UPDATE_LISTENER: undo_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -56,8 +56,8 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> """Unload a config entry.""" hass.data[DOMAIN][config_entry.entry_id][UNDO_UPDATE_LISTENER]() - for component in PLATFORMS: - await hass.config_entries.async_forward_entry_unload(config_entry, component) + for platform in PLATFORMS: + await hass.config_entries.async_forward_entry_unload(config_entry, platform) router: KeeneticRouter = hass.data[DOMAIN][config_entry.entry_id][ROUTER] diff --git a/homeassistant/components/kmtronic/__init__.py b/homeassistant/components/kmtronic/__init__.py index b55ab9e1c9c..1f012985447 100644 --- a/homeassistant/components/kmtronic/__init__.py +++ b/homeassistant/components/kmtronic/__init__.py @@ -80,9 +80,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): DATA_COORDINATOR: coordinator, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -93,8 +93,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/kodi/__init__.py b/homeassistant/components/kodi/__init__.py index 4dcb25b3ea9..ea867e8c407 100644 --- a/homeassistant/components/kodi/__init__.py +++ b/homeassistant/components/kodi/__init__.py @@ -72,9 +72,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): DATA_REMOVE_LISTENER: remove_stop_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -85,8 +85,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/konnected/__init__.py b/homeassistant/components/konnected/__init__.py index 348eaeda3ac..7ed4d5c1ac6 100644 --- a/homeassistant/components/konnected/__init__.py +++ b/homeassistant/components/konnected/__init__.py @@ -261,9 +261,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): # async_connect will handle retries until it establishes a connection await client.async_connect() - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) # config entry specific data to enable unload @@ -278,8 +278,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/kulersky/__init__.py b/homeassistant/components/kulersky/__init__.py index 9459d44805c..666239ad22b 100644 --- a/homeassistant/components/kulersky/__init__.py +++ b/homeassistant/components/kulersky/__init__.py @@ -16,9 +16,9 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Kuler Sky from a config entry.""" - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -29,8 +29,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/litejet/__init__.py b/homeassistant/components/litejet/__init__.py index 0c8f59c4127..f00853af524 100644 --- a/homeassistant/components/litejet/__init__.py +++ b/homeassistant/components/litejet/__init__.py @@ -59,9 +59,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN] = system - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -73,8 +73,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/litterrobot/__init__.py b/homeassistant/components/litterrobot/__init__.py index 19e76b9bb19..84e6822dc13 100644 --- a/homeassistant/components/litterrobot/__init__.py +++ b/homeassistant/components/litterrobot/__init__.py @@ -30,9 +30,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): except LitterRobotException as ex: raise ConfigEntryNotReady from ex - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -43,8 +43,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/logi_circle/__init__.py b/homeassistant/components/logi_circle/__init__.py index 056783ef6da..3364cd725c7 100644 --- a/homeassistant/components/logi_circle/__init__.py +++ b/homeassistant/components/logi_circle/__init__.py @@ -47,6 +47,8 @@ SERVICE_LIVESTREAM_RECORD = "livestream_record" ATTR_VALUE = "value" ATTR_DURATION = "duration" +PLATFORMS = ["camera", "sensor"] + SENSOR_SCHEMA = vol.Schema( { vol.Optional(CONF_MONITORED_CONDITIONS, default=list(LOGI_SENSORS)): vol.All( @@ -171,9 +173,9 @@ async def async_setup_entry(hass, entry): hass.data[DATA_LOGI] = logi_circle - for component in "camera", "sensor": + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) async def service_handler(service): @@ -219,8 +221,8 @@ async def async_setup_entry(hass, entry): async def async_unload_entry(hass, entry): """Unload a config entry.""" - for component in "camera", "sensor": - await hass.config_entries.async_forward_entry_unload(entry, component) + for platform in PLATFORMS: + await hass.config_entries.async_forward_entry_unload(entry, platform) logi_circle = hass.data.pop(DATA_LOGI) diff --git a/homeassistant/components/lutron/__init__.py b/homeassistant/components/lutron/__init__.py index 2eeebac4c96..b38968c36b8 100644 --- a/homeassistant/components/lutron/__init__.py +++ b/homeassistant/components/lutron/__init__.py @@ -12,6 +12,8 @@ from homeassistant.util import slugify DOMAIN = "lutron" +PLATFORMS = ["light", "cover", "switch", "scene", "binary_sensor"] + _LOGGER = logging.getLogger(__name__) LUTRON_BUTTONS = "lutron_buttons" @@ -37,7 +39,7 @@ CONFIG_SCHEMA = vol.Schema( def setup(hass, base_config): - """Set up the Lutron component.""" + """Set up the Lutron integration.""" hass.data[LUTRON_BUTTONS] = [] hass.data[LUTRON_CONTROLLER] = None hass.data[LUTRON_DEVICES] = { @@ -92,8 +94,8 @@ def setup(hass, base_config): (area.name, area.occupancy_group) ) - for component in ("light", "cover", "switch", "scene", "binary_sensor"): - discovery.load_platform(hass, component, DOMAIN, {}, base_config) + for platform in PLATFORMS: + discovery.load_platform(hass, platform, DOMAIN, {}, base_config) return True diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index 56cc7a78c96..f93833acfc5 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -62,7 +62,7 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) -LUTRON_CASETA_COMPONENTS = ["light", "switch", "cover", "scene", "fan", "binary_sensor"] +PLATFORMS = ["light", "switch", "cover", "scene", "fan", "binary_sensor"] async def async_setup(hass, base_config): @@ -125,7 +125,7 @@ async def async_setup_entry(hass, config_entry): bridge_device = devices[BRIDGE_DEVICE_ID] await _async_register_bridge_device(hass, config_entry.entry_id, bridge_device) # Store this bridge (keyed by entry_id) so it can be retrieved by the - # components we're setting up. + # platforms we're setting up. hass.data[DOMAIN][config_entry.entry_id] = { BRIDGE_LEAP: bridge, BRIDGE_DEVICE: bridge_device, @@ -139,9 +139,9 @@ async def async_setup_entry(hass, config_entry): # pico remotes to control other devices. await async_setup_lip(hass, config_entry, bridge.lip_devices) - for component in LUTRON_CASETA_COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -289,8 +289,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in LUTRON_CASETA_COMPONENTS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/lyric/__init__.py b/homeassistant/components/lyric/__init__.py index 12990d66ba9..8b23d6f85cf 100644 --- a/homeassistant/components/lyric/__init__.py +++ b/homeassistant/components/lyric/__init__.py @@ -113,9 +113,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if not coordinator.last_update_success: raise ConfigEntryNotReady - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -126,8 +126,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/mazda/__init__.py b/homeassistant/components/mazda/__init__.py index 14b33df66c0..1b1e6584a0e 100644 --- a/homeassistant/components/mazda/__init__.py +++ b/homeassistant/components/mazda/__init__.py @@ -121,9 +121,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): raise ConfigEntryNotReady # Setup components - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -134,8 +134,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/metoffice/__init__.py b/homeassistant/components/metoffice/__init__.py index 8a68646240a..87a5488fe01 100644 --- a/homeassistant/components/metoffice/__init__.py +++ b/homeassistant/components/metoffice/__init__.py @@ -61,9 +61,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): if metoffice_data.now is None: raise ConfigEntryNotReady() - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -74,8 +74,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/mobile_app/__init__.py b/homeassistant/components/mobile_app/__init__.py index e1476270844..4ba6a6f2086 100644 --- a/homeassistant/components/mobile_app/__init__.py +++ b/homeassistant/components/mobile_app/__init__.py @@ -104,8 +104,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/monoprice/__init__.py b/homeassistant/components/monoprice/__init__.py index 06883ddc8a8..adc0b05bab7 100644 --- a/homeassistant/components/monoprice/__init__.py +++ b/homeassistant/components/monoprice/__init__.py @@ -54,9 +54,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): FIRST_RUN: first_run, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -67,8 +67,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/motion_blinds/__init__.py b/homeassistant/components/motion_blinds/__init__.py index 5d02d5a14a8..c6226bed910 100644 --- a/homeassistant/components/motion_blinds/__init__.py +++ b/homeassistant/components/motion_blinds/__init__.py @@ -18,7 +18,7 @@ from .const import ( KEY_GATEWAY, KEY_MULTICAST_LISTENER, MANUFACTURER, - MOTION_PLATFORMS, + PLATFORMS, ) from .gateway import ConnectMotionGateway @@ -107,9 +107,9 @@ async def async_setup_entry( sw_version=motion_gateway.protocol, ) - for component in MOTION_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -122,8 +122,8 @@ async def async_unload_entry( unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in MOTION_PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/motion_blinds/const.py b/homeassistant/components/motion_blinds/const.py index 27f2310c7ce..4b1f0ee0527 100644 --- a/homeassistant/components/motion_blinds/const.py +++ b/homeassistant/components/motion_blinds/const.py @@ -3,7 +3,7 @@ DOMAIN = "motion_blinds" MANUFACTURER = "Motion Blinds, Coulisse B.V." DEFAULT_GATEWAY_NAME = "Motion Blinds Gateway" -MOTION_PLATFORMS = ["cover", "sensor"] +PLATFORMS = ["cover", "sensor"] KEY_GATEWAY = "gateway" KEY_COORDINATOR = "coordinator" diff --git a/homeassistant/components/mullvad/__init__.py b/homeassistant/components/mullvad/__init__.py index 20a7092d58a..b506be93100 100644 --- a/homeassistant/components/mullvad/__init__.py +++ b/homeassistant/components/mullvad/__init__.py @@ -43,9 +43,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: dict): hass.data[DOMAIN] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -56,8 +56,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/myq/__init__.py b/homeassistant/components/myq/__init__.py index 6b3a52ba7b0..b25751d7270 100644 --- a/homeassistant/components/myq/__init__.py +++ b/homeassistant/components/myq/__init__.py @@ -58,9 +58,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN][entry.entry_id] = {MYQ_GATEWAY: myq, MYQ_COORDINATOR: coordinator} - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -71,8 +71,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index 25b4d3106da..33ac4932889 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -30,7 +30,7 @@ from .const import ( DOMAIN, MYSENSORS_GATEWAYS, MYSENSORS_ON_UNLOAD, - SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT, + PLATFORMS_WITH_ENTRY_SUPPORT, DevId, GatewayId, SensorType, @@ -192,7 +192,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool await asyncio.gather( *[ hass.config_entries.async_forward_entry_setup(entry, platform) - for platform in SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT + for platform in PLATFORMS_WITH_ENTRY_SUPPORT ] ) await finish_setup(hass, entry, gateway) @@ -211,7 +211,7 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo await asyncio.gather( *[ hass.config_entries.async_forward_entry_unload(entry, platform) - for platform in SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT + for platform in PLATFORMS_WITH_ENTRY_SUPPORT ] ) ) diff --git a/homeassistant/components/mysensors/const.py b/homeassistant/components/mysensors/const.py index 66bee128d4d..700c2bb930a 100644 --- a/homeassistant/components/mysensors/const.py +++ b/homeassistant/components/mysensors/const.py @@ -158,7 +158,7 @@ for platform, platform_types in PLATFORM_TYPES.items(): for s_type_name in platform_types: TYPE_TO_PLATFORMS[s_type_name].append(platform) -SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT = set(PLATFORM_TYPES.keys()) - { +PLATFORMS_WITH_ENTRY_SUPPORT = set(PLATFORM_TYPES.keys()) - { "notify", "device_tracker", } diff --git a/homeassistant/components/n26/__init__.py b/homeassistant/components/n26/__init__.py index f8379cb310f..b1e83cd5311 100644 --- a/homeassistant/components/n26/__init__.py +++ b/homeassistant/components/n26/__init__.py @@ -36,7 +36,7 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) -N26_COMPONENTS = ["sensor", "switch"] +PLATFORMS = ["sensor", "switch"] def setup(hass, config): @@ -65,9 +65,9 @@ def setup(hass, config): hass.data[DOMAIN] = {} hass.data[DOMAIN][DATA] = api_data_list - # Load components for supported devices - for component in N26_COMPONENTS: - load_platform(hass, component, DOMAIN, {}, config) + # Load platforms for supported devices + for platform in PLATFORMS: + load_platform(hass, platform, DOMAIN, {}, config) return True diff --git a/homeassistant/components/neato/__init__.py b/homeassistant/components/neato/__init__.py index 1d9d3de4f89..bb0db8ebd85 100644 --- a/homeassistant/components/neato/__init__.py +++ b/homeassistant/components/neato/__init__.py @@ -103,9 +103,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool hass.data[NEATO_LOGIN] = hub - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index b0abd24012a..cd3f6ed9ed3 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -198,9 +198,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN].pop(DATA_NEST_UNAVAILABLE, None) hass.data[DOMAIN][DATA_SUBSCRIBER] = subscriber - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -217,8 +217,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/nest/legacy/__init__.py b/homeassistant/components/nest/legacy/__init__.py index 218b01fd71b..11bc1ab33ff 100644 --- a/homeassistant/components/nest/legacy/__init__.py +++ b/homeassistant/components/nest/legacy/__init__.py @@ -28,6 +28,8 @@ from .const import DATA_NEST, DATA_NEST_CONFIG, DOMAIN, SIGNAL_NEST_UPDATE _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) +PLATFORMS = ["climate", "camera", "sensor", "binary_sensor"] + # Configuration for the legacy nest API SERVICE_CANCEL_ETA = "cancel_eta" SERVICE_SET_ETA = "set_eta" @@ -131,9 +133,9 @@ async def async_setup_legacy_entry(hass, entry): if not await hass.async_add_executor_job(hass.data[DATA_NEST].initialize): return False - for component in "climate", "camera", "sensor", "binary_sensor": + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) def validate_structures(target_structures): diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py index 5827486429a..d76133c91ab 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -111,9 +111,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): await data_handler.async_setup() hass.data[DOMAIN][entry.entry_id][DATA_HANDLER] = data_handler - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) async def unregister_webhook(_): @@ -214,8 +214,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/nexia/__init__.py b/homeassistant/components/nexia/__init__.py index fc2ef7fef35..4dde2084400 100644 --- a/homeassistant/components/nexia/__init__.py +++ b/homeassistant/components/nexia/__init__.py @@ -80,9 +80,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): UPDATE_COORDINATOR: coordinator, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -93,8 +93,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/nextcloud/__init__.py b/homeassistant/components/nextcloud/__init__.py index 1a773040980..efa5b2e2f32 100644 --- a/homeassistant/components/nextcloud/__init__.py +++ b/homeassistant/components/nextcloud/__init__.py @@ -17,7 +17,7 @@ from homeassistant.helpers.event import track_time_interval _LOGGER = logging.getLogger(__name__) DOMAIN = "nextcloud" -NEXTCLOUD_COMPONENTS = ("sensor", "binary_sensor") +PLATFORMS = ("sensor", "binary_sensor") SCAN_INTERVAL = timedelta(seconds=60) # Validate user configuration @@ -116,8 +116,8 @@ def setup(hass, config): # Update sensors on time interval track_time_interval(hass, nextcloud_update, conf[CONF_SCAN_INTERVAL]) - for component in NEXTCLOUD_COMPONENTS: - discovery.load_platform(hass, component, DOMAIN, {}, config) + for platform in PLATFORMS: + discovery.load_platform(hass, platform, DOMAIN, {}, config) return True diff --git a/homeassistant/components/nightscout/__init__.py b/homeassistant/components/nightscout/__init__.py index 4c8ab756ddf..dfaaf28048e 100644 --- a/homeassistant/components/nightscout/__init__.py +++ b/homeassistant/components/nightscout/__init__.py @@ -48,9 +48,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): entry_type="service", ) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -61,8 +61,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/nissan_leaf/__init__.py b/homeassistant/components/nissan_leaf/__init__.py index 6417926702d..bace7f14556 100644 --- a/homeassistant/components/nissan_leaf/__init__.py +++ b/homeassistant/components/nissan_leaf/__init__.py @@ -81,7 +81,7 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) -LEAF_COMPONENTS = ["sensor", "switch", "binary_sensor"] +PLATFORMS = ["sensor", "switch", "binary_sensor"] SIGNAL_UPDATE_LEAF = "nissan_leaf_update" @@ -94,7 +94,7 @@ START_CHARGE_LEAF_SCHEMA = vol.Schema({vol.Required(ATTR_VIN): cv.string}) def setup(hass, config): - """Set up the Nissan Leaf component.""" + """Set up the Nissan Leaf integration.""" async def async_handle_update(service): """Handle service to update leaf data from Nissan servers.""" @@ -170,8 +170,8 @@ def setup(hass, config): data_store = LeafDataStore(hass, leaf, car_config) hass.data[DATA_LEAF][leaf.vin] = data_store - for component in LEAF_COMPONENTS: - load_platform(hass, component, DOMAIN, {}, car_config) + for platform in PLATFORMS: + load_platform(hass, platform, DOMAIN, {}, car_config) async_track_point_in_utc_time( hass, data_store.async_update_data, utcnow() + INITIAL_UPDATE diff --git a/homeassistant/components/nuheat/__init__.py b/homeassistant/components/nuheat/__init__.py index 2f51ae377a5..9fe4764e1af 100644 --- a/homeassistant/components/nuheat/__init__.py +++ b/homeassistant/components/nuheat/__init__.py @@ -80,9 +80,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN][entry.entry_id] = (thermostat, coordinator) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -93,8 +93,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/nuki/__init__.py b/homeassistant/components/nuki/__init__.py index 4af3e0d8ed4..6aa945a52bf 100644 --- a/homeassistant/components/nuki/__init__.py +++ b/homeassistant/components/nuki/__init__.py @@ -11,7 +11,7 @@ import homeassistant.helpers.config_validation as cv from .const import DEFAULT_PORT, DOMAIN -NUKI_PLATFORMS = ["lock"] +PLATFORMS = ["lock"] UPDATE_INTERVAL = timedelta(seconds=30) NUKI_SCHEMA = vol.Schema( @@ -29,7 +29,7 @@ async def async_setup(hass, config): """Set up the Nuki component.""" hass.data.setdefault(DOMAIN, {}) - for platform in NUKI_PLATFORMS: + for platform in PLATFORMS: confs = config.get(platform) if confs is None: continue diff --git a/homeassistant/components/nut/__init__.py b/homeassistant/components/nut/__init__.py index 5669b8a5c3b..3aba05ddd2b 100644 --- a/homeassistant/components/nut/__init__.py +++ b/homeassistant/components/nut/__init__.py @@ -101,9 +101,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): UNDO_UPDATE_LISTENER: undo_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -178,8 +178,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/nws/__init__.py b/homeassistant/components/nws/__init__.py index a0958be8d9e..83fca527d43 100644 --- a/homeassistant/components/nws/__init__.py +++ b/homeassistant/components/nws/__init__.py @@ -157,9 +157,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): await coordinator_forecast.async_refresh() await coordinator_forecast_hourly.async_refresh() - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -169,8 +169,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/nzbget/__init__.py b/homeassistant/components/nzbget/__init__.py index eba5eb58baf..57a741f0469 100644 --- a/homeassistant/components/nzbget/__init__.py +++ b/homeassistant/components/nzbget/__init__.py @@ -107,9 +107,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool DATA_UNDO_UPDATE_LISTENER: undo_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) _async_register_services(hass, coordinator) @@ -122,8 +122,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/omnilogic/__init__.py b/homeassistant/components/omnilogic/__init__.py index ff4dd93a0e1..7196cd28cd7 100644 --- a/homeassistant/components/omnilogic/__init__.py +++ b/homeassistant/components/omnilogic/__init__.py @@ -66,9 +66,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): OMNI_API: api, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -79,8 +79,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/ondilo_ico/__init__.py b/homeassistant/components/ondilo_ico/__init__.py index 69538c5e8b3..4dac83815ba 100644 --- a/homeassistant/components/ondilo_ico/__init__.py +++ b/homeassistant/components/ondilo_ico/__init__.py @@ -35,9 +35,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN][entry.entry_id] = api.OndiloClient(hass, entry, implementation) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -48,8 +48,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/onewire/__init__.py b/homeassistant/components/onewire/__init__.py index 6d64478aa72..779bc6dfd3a 100644 --- a/homeassistant/components/onewire/__init__.py +++ b/homeassistant/components/onewire/__init__.py @@ -5,7 +5,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.typing import HomeAssistantType -from .const import DOMAIN, SUPPORTED_PLATFORMS +from .const import DOMAIN, PLATFORMS from .onewirehub import CannotConnect, OneWireHub @@ -26,9 +26,9 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry): hass.data[DOMAIN][config_entry.unique_id] = onewirehub - for component in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -38,8 +38,8 @@ async def async_unload_entry(hass: HomeAssistantType, config_entry: ConfigEntry) unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in SUPPORTED_PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/onewire/const.py b/homeassistant/components/onewire/const.py index e68039078e9..54b18f7c905 100644 --- a/homeassistant/components/onewire/const.py +++ b/homeassistant/components/onewire/const.py @@ -61,7 +61,7 @@ SENSOR_TYPES = { SWITCH_TYPE_PIO: [None, None], } -SUPPORTED_PLATFORMS = [ +PLATFORMS = [ BINARY_SENSOR_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN, diff --git a/homeassistant/components/onvif/__init__.py b/homeassistant/components/onvif/__init__.py index b332b7a795a..0eb39064db7 100644 --- a/homeassistant/components/onvif/__init__.py +++ b/homeassistant/components/onvif/__init__.py @@ -88,9 +88,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): if device.capabilities.events: platforms += ["binary_sensor", "sensor"] - for component in platforms: + for platform in platforms: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, device.async_stop) @@ -111,8 +111,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): return all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in platforms + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in platforms ] ) ) diff --git a/homeassistant/components/openuv/__init__.py b/homeassistant/components/openuv/__init__.py index ce75365771d..387b7547514 100644 --- a/homeassistant/components/openuv/__init__.py +++ b/homeassistant/components/openuv/__init__.py @@ -69,9 +69,9 @@ async def async_setup_entry(hass, config_entry): LOGGER.error("Config entry failed: %s", err) raise ConfigEntryNotReady from err - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) @_verify_domain_control @@ -110,8 +110,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/openweathermap/__init__.py b/homeassistant/components/openweathermap/__init__.py index 4754c4b2eff..82b1545e359 100644 --- a/homeassistant/components/openweathermap/__init__.py +++ b/homeassistant/components/openweathermap/__init__.py @@ -17,7 +17,6 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from .const import ( - COMPONENTS, CONF_LANGUAGE, CONFIG_FLOW_VERSION, DOMAIN, @@ -25,6 +24,7 @@ from .const import ( ENTRY_WEATHER_COORDINATOR, FORECAST_MODE_FREE_DAILY, FORECAST_MODE_ONECALL_DAILY, + PLATFORMS, UPDATE_LISTENER, ) from .weather_update_coordinator import WeatherUpdateCoordinator @@ -65,9 +65,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): ENTRY_WEATHER_COORDINATOR: weather_coordinator, } - for component in COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) update_listener = config_entry.add_update_listener(async_update_options) @@ -108,8 +108,8 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in COMPONENTS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/openweathermap/const.py b/homeassistant/components/openweathermap/const.py index c70afa9cab0..7c8e8aeee57 100644 --- a/homeassistant/components/openweathermap/const.py +++ b/homeassistant/components/openweathermap/const.py @@ -62,7 +62,7 @@ SENSOR_NAME = "sensor_name" SENSOR_UNIT = "sensor_unit" SENSOR_DEVICE_CLASS = "sensor_device_class" UPDATE_LISTENER = "update_listener" -COMPONENTS = ["sensor", "weather"] +PLATFORMS = ["sensor", "weather"] FORECAST_MODE_HOURLY = "hourly" FORECAST_MODE_DAILY = "daily" diff --git a/homeassistant/components/ozw/__init__.py b/homeassistant/components/ozw/__init__.py index a75c05416dc..3f54c731e39 100644 --- a/homeassistant/components/ozw/__init__.py +++ b/homeassistant/components/ozw/__init__.py @@ -267,8 +267,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def start_platforms(): await asyncio.gather( *[ - hass.config_entries.async_forward_entry_setup(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_setup(entry, platform) + for platform in PLATFORMS ] ) if entry.data.get(CONF_USE_ADDON): @@ -310,8 +310,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/panasonic_viera/__init__.py b/homeassistant/components/panasonic_viera/__init__.py index 9902d39a56c..449515802b9 100644 --- a/homeassistant/components/panasonic_viera/__init__.py +++ b/homeassistant/components/panasonic_viera/__init__.py @@ -104,9 +104,9 @@ async def async_setup_entry(hass, config_entry): data={**config, ATTR_DEVICE_INFO: device_info}, ) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -117,8 +117,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index dd3e8d194e9..b3abaa28132 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -47,9 +47,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): await coordinator.async_refresh() hass.data[DOMAIN][entry.entry_id] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -60,8 +60,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index c14395319d4..280de59898b 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -139,9 +139,9 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: if single_master_thermostat is None: platforms = SENSOR_PLATFORMS - for component in platforms: + for platform in platforms: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -160,8 +160,8 @@ async def async_unload_entry_gw(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS_GATEWAY + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS_GATEWAY ] ) ) diff --git a/homeassistant/components/plum_lightpad/__init__.py b/homeassistant/components/plum_lightpad/__init__.py index 2a7ce4497bb..858f87e74f8 100644 --- a/homeassistant/components/plum_lightpad/__init__.py +++ b/homeassistant/components/plum_lightpad/__init__.py @@ -67,9 +67,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = plum - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) def cleanup(event): diff --git a/homeassistant/components/point/__init__.py b/homeassistant/components/point/__init__.py index 13dce13c0da..a92f4f3f14f 100644 --- a/homeassistant/components/point/__init__.py +++ b/homeassistant/components/point/__init__.py @@ -39,6 +39,8 @@ _LOGGER = logging.getLogger(__name__) DATA_CONFIG_ENTRY_LOCK = "point_config_entry_lock" CONFIG_ENTRY_IS_SETUP = "point_config_entry_is_setup" +PLATFORMS = ["binary_sensor", "sensor"] + CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( @@ -137,8 +139,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): session = hass.data[DOMAIN].pop(entry.entry_id) await session.remove_webhook() - for component in ("binary_sensor", "sensor"): - await hass.config_entries.async_forward_entry_unload(entry, component) + for platform in PLATFORMS: + await hass.config_entries.async_forward_entry_unload(entry, platform) if not hass.data[DOMAIN]: hass.data.pop(DOMAIN) @@ -186,18 +188,18 @@ class MinutPointClient: async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY) return - async def new_device(device_id, component): + async def new_device(device_id, platform): """Load new device.""" - config_entries_key = f"{component}.{DOMAIN}" + config_entries_key = f"{platform}.{DOMAIN}" async with self._hass.data[DATA_CONFIG_ENTRY_LOCK]: if config_entries_key not in self._hass.data[CONFIG_ENTRY_IS_SETUP]: await self._hass.config_entries.async_forward_entry_setup( - self._config_entry, component + self._config_entry, platform ) self._hass.data[CONFIG_ENTRY_IS_SETUP].add(config_entries_key) async_dispatcher_send( - self._hass, POINT_DISCOVERY_NEW.format(component, DOMAIN), device_id + self._hass, POINT_DISCOVERY_NEW.format(platform, DOMAIN), device_id ) self._is_available = True @@ -207,8 +209,8 @@ class MinutPointClient: self._known_homes.add(home_id) for device in self._client.devices: if device.device_id not in self._known_devices: - for component in ("sensor", "binary_sensor"): - await new_device(device.device_id, component) + for platform in PLATFORMS: + await new_device(device.device_id, platform) self._known_devices.add(device.device_id) async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY) diff --git a/homeassistant/components/poolsense/__init__.py b/homeassistant/components/poolsense/__init__.py index 315816f4e1c..3dc9ace1b8f 100644 --- a/homeassistant/components/poolsense/__init__.py +++ b/homeassistant/components/poolsense/__init__.py @@ -56,9 +56,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN][entry.entry_id] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -69,8 +69,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/powerwall/__init__.py b/homeassistant/components/powerwall/__init__.py index b392b713741..b529680da50 100644 --- a/homeassistant/components/powerwall/__init__.py +++ b/homeassistant/components/powerwall/__init__.py @@ -158,9 +158,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): await coordinator.async_refresh() - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -228,8 +228,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/progettihwsw/__init__.py b/homeassistant/components/progettihwsw/__init__.py index 02418c963d4..7597b2ff1a2 100644 --- a/homeassistant/components/progettihwsw/__init__.py +++ b/homeassistant/components/progettihwsw/__init__.py @@ -30,9 +30,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): # Check board validation again to load new values to API. await hass.data[DOMAIN][entry.entry_id].check_board() - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -43,8 +43,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/proxmoxve/__init__.py b/homeassistant/components/proxmoxve/__init__.py index 2f42ca8fe9e..663afc69b1d 100644 --- a/homeassistant/components/proxmoxve/__init__.py +++ b/homeassistant/components/proxmoxve/__init__.py @@ -187,10 +187,10 @@ async def async_setup(hass: HomeAssistant, config: dict): # Fetch initial data await coordinator.async_refresh() - for component in PLATFORMS: + for platform in PLATFORMS: await hass.async_create_task( hass.helpers.discovery.async_load_platform( - component, DOMAIN, {"config": config}, config + platform, DOMAIN, {"config": config}, config ) ) diff --git a/homeassistant/components/rachio/__init__.py b/homeassistant/components/rachio/__init__.py index 672ff272344..30015dcf8c1 100644 --- a/homeassistant/components/rachio/__init__.py +++ b/homeassistant/components/rachio/__init__.py @@ -21,7 +21,7 @@ from .webhooks import ( _LOGGER = logging.getLogger(__name__) -SUPPORTED_DOMAINS = ["switch", "binary_sensor"] +PLATFORMS = ["switch", "binary_sensor"] CONFIG_SCHEMA = cv.deprecated(DOMAIN) @@ -39,8 +39,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in SUPPORTED_DOMAINS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) @@ -99,13 +99,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): webhook_url, ) - # Enable component + # Enable platform hass.data[DOMAIN][entry.entry_id] = person async_register_webhook(hass, webhook_id, entry.entry_id) - for component in SUPPORTED_DOMAINS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True diff --git a/homeassistant/components/rainbird/__init__.py b/homeassistant/components/rainbird/__init__.py index 83c358c480b..d8334470b60 100644 --- a/homeassistant/components/rainbird/__init__.py +++ b/homeassistant/components/rainbird/__init__.py @@ -16,7 +16,7 @@ import homeassistant.helpers.config_validation as cv CONF_ZONES = "zones" -SUPPORTED_PLATFORMS = [switch.DOMAIN, sensor.DOMAIN, binary_sensor.DOMAIN] +PLATFORMS = [switch.DOMAIN, sensor.DOMAIN, binary_sensor.DOMAIN] _LOGGER = logging.getLogger(__name__) @@ -80,7 +80,7 @@ def _setup_controller(hass, controller_config, config): return False hass.data[DATA_RAINBIRD].append(controller) _LOGGER.debug("Rain Bird Controller %d set to: %s", position, server) - for platform in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: discovery.load_platform( hass, platform, diff --git a/homeassistant/components/rainmachine/__init__.py b/homeassistant/components/rainmachine/__init__.py index 98fbdbcf401..b4d8510cc77 100644 --- a/homeassistant/components/rainmachine/__init__.py +++ b/homeassistant/components/rainmachine/__init__.py @@ -155,9 +155,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await asyncio.gather(*controller_init_tasks) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) hass.data[DOMAIN][DATA_LISTENER] = entry.add_update_listener(async_reload_entry) @@ -170,8 +170,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/recollect_waste/__init__.py b/homeassistant/components/recollect_waste/__init__.py index 0600d73d8a1..0d6961831d8 100644 --- a/homeassistant/components/recollect_waste/__init__.py +++ b/homeassistant/components/recollect_waste/__init__.py @@ -61,9 +61,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN][DATA_COORDINATOR][entry.entry_id] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) hass.data[DOMAIN][DATA_LISTENER][entry.entry_id] = entry.add_update_listener( @@ -83,8 +83,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/rfxtrx/__init__.py b/homeassistant/components/rfxtrx/__init__.py index 5952cb62a71..c9e8c0dee75 100644 --- a/homeassistant/components/rfxtrx/__init__.py +++ b/homeassistant/components/rfxtrx/__init__.py @@ -151,7 +151,7 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) -DOMAINS = ["switch", "sensor", "light", "binary_sensor", "cover"] +PLATFORMS = ["switch", "sensor", "light", "binary_sensor", "cover"] async def async_setup(hass, config): @@ -202,9 +202,9 @@ async def async_setup_entry(hass, entry: config_entries.ConfigEntry): ) return False - for domain in DOMAINS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, domain) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -215,8 +215,8 @@ async def async_unload_entry(hass, entry: config_entries.ConfigEntry): if not all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in DOMAINS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ): diff --git a/homeassistant/components/ring/__init__.py b/homeassistant/components/ring/__init__.py index ed22575bccc..a4d005d3c44 100644 --- a/homeassistant/components/ring/__init__.py +++ b/homeassistant/components/ring/__init__.py @@ -99,9 +99,9 @@ async def async_setup_entry(hass, entry): ), } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) if hass.services.has_service(DOMAIN, "update"): @@ -126,8 +126,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/risco/__init__.py b/homeassistant/components/risco/__init__.py index 685fee43adf..f5ebc4e13ef 100644 --- a/homeassistant/components/risco/__init__.py +++ b/homeassistant/components/risco/__init__.py @@ -63,8 +63,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def start_platforms(): await asyncio.gather( *[ - hass.config_entries.async_forward_entry_setup(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_setup(entry, platform) + for platform in PLATFORMS ] ) await events_coordinator.async_refresh() @@ -79,8 +79,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/rituals_perfume_genie/__init__.py b/homeassistant/components/rituals_perfume_genie/__init__.py index ba11206d496..f2fd13a9ef4 100644 --- a/homeassistant/components/rituals_perfume_genie/__init__.py +++ b/homeassistant/components/rituals_perfume_genie/__init__.py @@ -37,9 +37,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data.setdefault(DOMAIN, {})[entry.entry_id] = account - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -50,8 +50,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/roku/__init__.py b/homeassistant/components/roku/__init__.py index a75fc813fb9..06e8d0ae848 100644 --- a/homeassistant/components/roku/__init__.py +++ b/homeassistant/components/roku/__init__.py @@ -54,9 +54,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool hass.data[DOMAIN][entry.entry_id] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -67,8 +67,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 658c230c3a7..6de775e1d99 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -8,7 +8,7 @@ from roombapy import Roomba, RoombaConnectionError from homeassistant import exceptions from homeassistant.const import CONF_DELAY, CONF_HOST, CONF_NAME, CONF_PASSWORD -from .const import BLID, COMPONENTS, CONF_BLID, CONF_CONTINUOUS, DOMAIN, ROOMBA_SESSION +from .const import BLID, CONF_BLID, CONF_CONTINUOUS, DOMAIN, PLATFORMS, ROOMBA_SESSION _LOGGER = logging.getLogger(__name__) @@ -51,9 +51,9 @@ async def async_setup_entry(hass, config_entry): BLID: config_entry.data[CONF_BLID], } - for component in COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) if not config_entry.update_listeners: @@ -105,8 +105,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in COMPONENTS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/roomba/const.py b/homeassistant/components/roomba/const.py index 2ffb34eb7c8..0509cd92116 100644 --- a/homeassistant/components/roomba/const.py +++ b/homeassistant/components/roomba/const.py @@ -1,6 +1,6 @@ """The roomba constants.""" DOMAIN = "roomba" -COMPONENTS = ["sensor", "binary_sensor", "vacuum"] +PLATFORMS = ["sensor", "binary_sensor", "vacuum"] CONF_CERT = "certificate" CONF_CONTINUOUS = "continuous" CONF_BLID = "blid" diff --git a/homeassistant/components/ruckus_unleashed/__init__.py b/homeassistant/components/ruckus_unleashed/__init__.py index ed38f5a2a3a..2bab1747f0f 100644 --- a/homeassistant/components/ruckus_unleashed/__init__.py +++ b/homeassistant/components/ruckus_unleashed/__init__.py @@ -84,8 +84,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/sense/__init__.py b/homeassistant/components/sense/__init__.py index 2d6c0c41e5b..1689d8c4834 100644 --- a/homeassistant/components/sense/__init__.py +++ b/homeassistant/components/sense/__init__.py @@ -139,9 +139,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): SENSE_DISCOVERED_DEVICES_DATA: sense_discovered_devices, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) async def async_sense_update(_): @@ -169,8 +169,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/sharkiq/__init__.py b/homeassistant/components/sharkiq/__init__.py index b7684f98855..e43338cd5b0 100644 --- a/homeassistant/components/sharkiq/__init__.py +++ b/homeassistant/components/sharkiq/__init__.py @@ -14,7 +14,7 @@ from sharkiqpy import ( from homeassistant import exceptions from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from .const import _LOGGER, API_TIMEOUT, COMPONENTS, DOMAIN +from .const import _LOGGER, API_TIMEOUT, DOMAIN, PLATFORMS from .update_coordinator import SharkIqUpdateCoordinator @@ -70,9 +70,9 @@ async def async_setup_entry(hass, config_entry): hass.data[DOMAIN][config_entry.entry_id] = coordinator - for component in COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -98,8 +98,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in COMPONENTS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/sharkiq/const.py b/homeassistant/components/sharkiq/const.py index e0feb306f77..8f4c56b65db 100644 --- a/homeassistant/components/sharkiq/const.py +++ b/homeassistant/components/sharkiq/const.py @@ -5,7 +5,7 @@ import logging _LOGGER = logging.getLogger(__package__) API_TIMEOUT = 20 -COMPONENTS = ["vacuum"] +PLATFORMS = ["vacuum"] DOMAIN = "sharkiq" SHARK = "Shark" UPDATE_INTERVAL = timedelta(seconds=30) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index ccb52127525..52dae7f164d 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -134,9 +134,9 @@ async def async_device_setup( ] = ShellyDeviceRestWrapper(hass, device) platforms = PLATFORMS - for component in platforms: + for platform in platforms: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) @@ -323,8 +323,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in platforms + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in platforms ] ) ) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 495ba29fefb..a6d95e1d9af 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -71,7 +71,7 @@ EVENT_SIMPLISAFE_NOTIFICATION = "SIMPLISAFE_NOTIFICATION" DEFAULT_SOCKET_MIN_RETRY = 15 -SUPPORTED_PLATFORMS = ( +PLATFORMS = ( "alarm_control_panel", "binary_sensor", "lock", @@ -219,7 +219,7 @@ async def async_setup_entry(hass, config_entry): ) await simplisafe.async_init() - for platform in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(config_entry, platform) ) @@ -327,8 +327,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in SUPPORTED_PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/smappee/__init__.py b/homeassistant/components/smappee/__init__.py index aed67c5c167..f803f38b8ea 100644 --- a/homeassistant/components/smappee/__init__.py +++ b/homeassistant/components/smappee/__init__.py @@ -21,7 +21,7 @@ from .const import ( CONF_SERIALNUMBER, DOMAIN, MIN_TIME_BETWEEN_UPDATES, - SMAPPEE_PLATFORMS, + PLATFORMS, TOKEN_URL, ) @@ -92,9 +92,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN][entry.entry_id] = SmappeeBase(hass, smappee) - for component in SMAPPEE_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -105,8 +105,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in SMAPPEE_PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/smappee/const.py b/homeassistant/components/smappee/const.py index 2c69f1ccb96..fc059509ced 100644 --- a/homeassistant/components/smappee/const.py +++ b/homeassistant/components/smappee/const.py @@ -12,7 +12,7 @@ CONF_TITLE = "title" ENV_CLOUD = "cloud" ENV_LOCAL = "local" -SMAPPEE_PLATFORMS = ["binary_sensor", "sensor", "switch"] +PLATFORMS = ["binary_sensor", "sensor", "switch"] SUPPORTED_LOCAL_DEVICES = ("Smappee1", "Smappee2") diff --git a/homeassistant/components/smart_meter_texas/__init__.py b/homeassistant/components/smart_meter_texas/__init__.py index 7b1c6cfa9b7..3180248fcd1 100644 --- a/homeassistant/components/smart_meter_texas/__init__.py +++ b/homeassistant/components/smart_meter_texas/__init__.py @@ -83,9 +83,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): asyncio.create_task(coordinator.async_refresh()) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -122,8 +122,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/smarthab/__init__.py b/homeassistant/components/smarthab/__init__.py index c826b5d8f4d..c259ef71aab 100644 --- a/homeassistant/components/smarthab/__init__.py +++ b/homeassistant/components/smarthab/__init__.py @@ -13,7 +13,7 @@ from homeassistant.helpers.typing import HomeAssistantType DOMAIN = "smarthab" DATA_HUB = "hub" -COMPONENTS = ["light", "cover"] +PLATFORMS = ["light", "cover"] _LOGGER = logging.getLogger(__name__) @@ -69,9 +69,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): # Pass hub object to child platforms hass.data[DOMAIN][entry.entry_id] = {DATA_HUB: hub} - for component in COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -83,8 +83,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): result = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in COMPONENTS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index d184a3ca6ce..77ef913c629 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -36,8 +36,8 @@ from .const import ( DATA_MANAGER, DOMAIN, EVENT_BUTTON, + PLATFORMS, SIGNAL_SMARTTHINGS_UPDATE, - SUPPORTED_PLATFORMS, TOKEN_REFRESH_INTERVAL, ) from .smartapp import ( @@ -184,9 +184,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): ) return False - for component in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -213,8 +213,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): broker.disconnect() tasks = [ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in SUPPORTED_PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] return all(await asyncio.gather(*tasks)) @@ -293,11 +293,13 @@ class DeviceBroker: for device in devices: capabilities = device.capabilities.copy() slots = {} - for platform_name in SUPPORTED_PLATFORMS: - platform = importlib.import_module(f".{platform_name}", self.__module__) - if not hasattr(platform, "get_capabilities"): + for platform in PLATFORMS: + platform_module = importlib.import_module( + f".{platform}", self.__module__ + ) + if not hasattr(platform_module, "get_capabilities"): continue - assigned = platform.get_capabilities(capabilities) + assigned = platform_module.get_capabilities(capabilities) if not assigned: continue # Draw-down capabilities and set slot assignment @@ -305,7 +307,7 @@ class DeviceBroker: if capability not in capabilities: continue capabilities.remove(capability) - slots[capability] = platform_name + slots[capability] = platform assignments[device.device_id] = slots return assignments diff --git a/homeassistant/components/smartthings/const.py b/homeassistant/components/smartthings/const.py index 03188411f07..a7aa9066dd2 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -31,7 +31,7 @@ STORAGE_VERSION = 1 # Ordered 'specific to least-specific platform' in order for capabilities # to be drawn-down and represented by the most appropriate platform. -SUPPORTED_PLATFORMS = [ +PLATFORMS = [ "climate", "fan", "light", diff --git a/homeassistant/components/sms/__init__.py b/homeassistant/components/sms/__init__.py index 8752dfd90da..c4fdb38ebaa 100644 --- a/homeassistant/components/sms/__init__.py +++ b/homeassistant/components/sms/__init__.py @@ -46,9 +46,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): if not gateway: return False hass.data[DOMAIN][SMS_GATEWAY] = gateway - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -59,8 +59,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/soma/__init__.py b/homeassistant/components/soma/__init__.py index bd5695cb7ec..3f15199c162 100644 --- a/homeassistant/components/soma/__init__.py +++ b/homeassistant/components/soma/__init__.py @@ -24,7 +24,7 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) -SOMA_COMPONENTS = ["cover", "sensor"] +PLATFORMS = ["cover", "sensor"] async def async_setup(hass, config): @@ -50,9 +50,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): devices = await hass.async_add_executor_job(hass.data[DOMAIN][API].list_devices) hass.data[DOMAIN][DEVICES] = devices["shades"] - for component in SOMA_COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -63,8 +63,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in SOMA_COMPONENTS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/somfy/__init__.py b/homeassistant/components/somfy/__init__.py index ac32b9d5379..0a83ac2fc52 100644 --- a/homeassistant/components/somfy/__init__.py +++ b/homeassistant/components/somfy/__init__.py @@ -48,7 +48,7 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) -SOMFY_COMPONENTS = ["climate", "cover", "sensor", "switch"] +PLATFORMS = ["climate", "cover", "sensor", "switch"] async def async_setup(hass, config): @@ -134,9 +134,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): model=hub.type, ) - for component in SOMFY_COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -147,8 +147,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): hass.data[DOMAIN].pop(API, None) await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in SOMFY_COMPONENTS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) return True diff --git a/homeassistant/components/somfy_mylink/__init__.py b/homeassistant/components/somfy_mylink/__init__.py index d371fd96310..a2ade387c01 100644 --- a/homeassistant/components/somfy_mylink/__init__.py +++ b/homeassistant/components/somfy_mylink/__init__.py @@ -23,7 +23,7 @@ from .const import ( DEFAULT_PORT, DOMAIN, MYLINK_STATUS, - SOMFY_MYLINK_COMPONENTS, + PLATFORMS, ) CONFIG_OPTIONS = (CONF_DEFAULT_REVERSE, CONF_ENTITY_CONFIG) @@ -121,9 +121,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): UNDO_UPDATE_LISTENER: undo_listener, } - for component in SOMFY_MYLINK_COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -182,8 +182,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in SOMFY_MYLINK_COMPONENTS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/somfy_mylink/const.py b/homeassistant/components/somfy_mylink/const.py index a7cbf864cd9..bf58ee1af92 100644 --- a/homeassistant/components/somfy_mylink/const.py +++ b/homeassistant/components/somfy_mylink/const.py @@ -14,6 +14,6 @@ DATA_SOMFY_MYLINK = "somfy_mylink_data" MYLINK_STATUS = "mylink_status" DOMAIN = "somfy_mylink" -SOMFY_MYLINK_COMPONENTS = ["cover"] +PLATFORMS = ["cover"] MANUFACTURER = "Somfy" diff --git a/homeassistant/components/sonarr/__init__.py b/homeassistant/components/sonarr/__init__.py index 3e2a5498c55..636653dad00 100644 --- a/homeassistant/components/sonarr/__init__.py +++ b/homeassistant/components/sonarr/__init__.py @@ -84,9 +84,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool DATA_UNDO_UPDATE_LISTENER: undo_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -97,8 +97,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/spider/__init__.py b/homeassistant/components/spider/__init__.py index b0c34ae5a08..d9ccdfd248a 100644 --- a/homeassistant/components/spider/__init__.py +++ b/homeassistant/components/spider/__init__.py @@ -66,9 +66,9 @@ async def async_setup_entry(hass, entry): hass.data[DOMAIN][entry.entry_id] = api - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -79,8 +79,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/streamlabswater/__init__.py b/homeassistant/components/streamlabswater/__init__.py index 336e92358ee..0a49e128c2f 100644 --- a/homeassistant/components/streamlabswater/__init__.py +++ b/homeassistant/components/streamlabswater/__init__.py @@ -17,7 +17,7 @@ SERVICE_SET_AWAY_MODE = "set_away_mode" AWAY_MODE_AWAY = "away" AWAY_MODE_HOME = "home" -STREAMLABSWATER_COMPONENTS = ["sensor", "binary_sensor"] +PLATFORMS = ["sensor", "binary_sensor"] CONF_LOCATION_ID = "location_id" @@ -39,7 +39,7 @@ SET_AWAY_MODE_SCHEMA = vol.Schema( def setup(hass, config): - """Set up the streamlabs water component.""" + """Set up the streamlabs water integration.""" conf = config[DOMAIN] api_key = conf.get(CONF_API_KEY) @@ -74,8 +74,8 @@ def setup(hass, config): "location_name": location_name, } - for component in STREAMLABSWATER_COMPONENTS: - discovery.load_platform(hass, component, DOMAIN, {}, config) + for platform in PLATFORMS: + discovery.load_platform(hass, platform, DOMAIN, {}, config) def set_away_mode(service): """Set the StreamLabsWater Away Mode.""" diff --git a/homeassistant/components/subaru/__init__.py b/homeassistant/components/subaru/__init__.py index 63bc644b50a..04f61111671 100644 --- a/homeassistant/components/subaru/__init__.py +++ b/homeassistant/components/subaru/__init__.py @@ -22,7 +22,7 @@ from .const import ( ENTRY_COORDINATOR, ENTRY_VEHICLES, FETCH_INTERVAL, - SUPPORTED_PLATFORMS, + PLATFORMS, UPDATE_INTERVAL, VEHICLE_API_GEN, VEHICLE_HAS_EV, @@ -94,9 +94,9 @@ async def async_setup_entry(hass, entry): ENTRY_VEHICLES: vehicle_info, } - for component in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -107,8 +107,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in SUPPORTED_PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/subaru/const.py b/homeassistant/components/subaru/const.py index 7349f9c32d6..fa6fe984e61 100644 --- a/homeassistant/components/subaru/const.py +++ b/homeassistant/components/subaru/const.py @@ -30,7 +30,7 @@ API_GEN_1 = "g1" API_GEN_2 = "g2" MANUFACTURER = "Subaru Corp." -SUPPORTED_PLATFORMS = [ +PLATFORMS = [ "sensor", ] diff --git a/homeassistant/components/tado/__init__.py b/homeassistant/components/tado/__init__.py index c7fb180e6d8..36af3bb0dba 100644 --- a/homeassistant/components/tado/__init__.py +++ b/homeassistant/components/tado/__init__.py @@ -31,7 +31,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -TADO_COMPONENTS = ["binary_sensor", "sensor", "climate", "water_heater"] +PLATFORMS = ["binary_sensor", "sensor", "climate", "water_heater"] MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=4) SCAN_INTERVAL = timedelta(minutes=5) @@ -92,9 +92,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): UPDATE_LISTENER: update_listener, } - for component in TADO_COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -118,8 +118,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in TADO_COMPONENTS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/tahoma/__init__.py b/homeassistant/components/tahoma/__init__.py index d75ccaec414..b322d454002 100644 --- a/homeassistant/components/tahoma/__init__.py +++ b/homeassistant/components/tahoma/__init__.py @@ -31,7 +31,7 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) -TAHOMA_COMPONENTS = ["binary_sensor", "cover", "lock", "scene", "sensor", "switch"] +PLATFORMS = ["binary_sensor", "cover", "lock", "scene", "sensor", "switch"] TAHOMA_TYPES = { "io:AwningValanceIOComponent": "cover", @@ -73,7 +73,7 @@ TAHOMA_TYPES = { def setup(hass, config): - """Activate Tahoma component.""" + """Set up Tahoma integration.""" conf = config[DOMAIN] username = conf.get(CONF_USERNAME) @@ -111,14 +111,14 @@ def setup(hass, config): for scene in scenes: hass.data[DOMAIN]["scenes"].append(scene) - for component in TAHOMA_COMPONENTS: - discovery.load_platform(hass, component, DOMAIN, {}, config) + for platform in PLATFORMS: + discovery.load_platform(hass, platform, DOMAIN, {}, config) return True def map_tahoma_device(tahoma_device): - """Map Tahoma device types to Home Assistant components.""" + """Map Tahoma device types to Home Assistant platforms.""" return TAHOMA_TYPES.get(tahoma_device.type) diff --git a/homeassistant/components/tasmota/__init__.py b/homeassistant/components/tasmota/__init__.py index c0ebae7695e..0b42725cdcf 100644 --- a/homeassistant/components/tasmota/__init__.py +++ b/homeassistant/components/tasmota/__init__.py @@ -92,8 +92,8 @@ async def async_setup_entry(hass, entry): await device_automation.async_setup_entry(hass, entry) await asyncio.gather( *[ - hass.config_entries.async_forward_entry_setup(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_setup(entry, platform) + for platform in PLATFORMS ] ) @@ -113,8 +113,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) @@ -128,8 +128,8 @@ async def async_unload_entry(hass, entry): for unsub in hass.data[DATA_UNSUB]: unsub() hass.data.pop(DATA_REMOVE_DISCOVER_COMPONENT.format("device_automation"))() - for component in PLATFORMS: - hass.data.pop(DATA_REMOVE_DISCOVER_COMPONENT.format(component))() + for platform in PLATFORMS: + hass.data.pop(DATA_REMOVE_DISCOVER_COMPONENT.format(platform))() # deattach device triggers device_registry = await hass.helpers.device_registry.async_get_registry() diff --git a/homeassistant/components/tellduslive/__init__.py b/homeassistant/components/tellduslive/__init__.py index 5d4721e60e6..70cc8848814 100644 --- a/homeassistant/components/tellduslive/__init__.py +++ b/homeassistant/components/tellduslive/__init__.py @@ -123,8 +123,8 @@ async def async_unload_entry(hass, config_entry): interval_tracker() await asyncio.wait( [ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in hass.data.pop(CONFIG_ENTRY_IS_SETUP) + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in hass.data.pop(CONFIG_ENTRY_IS_SETUP) ] ) del hass.data[DOMAIN] diff --git a/homeassistant/components/tesla/__init__.py b/homeassistant/components/tesla/__init__.py index b31f8ae6dd3..a2ed908948f 100644 --- a/homeassistant/components/tesla/__init__.py +++ b/homeassistant/components/tesla/__init__.py @@ -39,7 +39,7 @@ from .const import ( DOMAIN, ICONS, MIN_SCAN_INTERVAL, - TESLA_COMPONENTS, + PLATFORMS, ) _LOGGER = logging.getLogger(__name__) @@ -190,10 +190,10 @@ async def async_setup_entry(hass, config_entry): for device in all_devices: entry_data["devices"][device.hass_type].append(device) - for component in TESLA_COMPONENTS: - _LOGGER.debug("Loading %s", component) + for platform in PLATFORMS: + _LOGGER.debug("Loading %s", platform) hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -203,8 +203,8 @@ async def async_unload_entry(hass, config_entry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in TESLA_COMPONENTS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/tesla/const.py b/homeassistant/components/tesla/const.py index 2b8485c7616..94883e4a833 100644 --- a/homeassistant/components/tesla/const.py +++ b/homeassistant/components/tesla/const.py @@ -5,7 +5,8 @@ DATA_LISTENER = "listener" DEFAULT_SCAN_INTERVAL = 660 DEFAULT_WAKE_ON_START = False MIN_SCAN_INTERVAL = 60 -TESLA_COMPONENTS = [ + +PLATFORMS = [ "sensor", "lock", "climate", @@ -13,6 +14,7 @@ TESLA_COMPONENTS = [ "device_tracker", "switch", ] + ICONS = { "battery sensor": "mdi:battery", "range sensor": "mdi:gauge", diff --git a/homeassistant/components/tibber/__init__.py b/homeassistant/components/tibber/__init__.py index b4b29a84297..fd7fc389c75 100644 --- a/homeassistant/components/tibber/__init__.py +++ b/homeassistant/components/tibber/__init__.py @@ -73,9 +73,9 @@ async def async_setup_entry(hass, entry): _LOGGER.error("Failed to login. %s", exp) return False - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) # set up notify platform, no entry support for notify component yet, @@ -93,8 +93,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/tile/__init__.py b/homeassistant/components/tile/__init__.py index 205742017d3..48bf8177c63 100644 --- a/homeassistant/components/tile/__init__.py +++ b/homeassistant/components/tile/__init__.py @@ -74,9 +74,9 @@ async def async_setup_entry(hass, entry): await gather_with_concurrency(DEFAULT_INIT_TASK_LIMIT, *coordinator_init_tasks) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -87,8 +87,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/toon/__init__.py b/homeassistant/components/toon/__init__.py index d1753ad4d02..2a3322991e1 100644 --- a/homeassistant/components/toon/__init__.py +++ b/homeassistant/components/toon/__init__.py @@ -27,7 +27,7 @@ from .const import CONF_AGREEMENT_ID, CONF_MIGRATE, DEFAULT_SCAN_INTERVAL, DOMAI from .coordinator import ToonDataUpdateCoordinator from .oauth2 import register_oauth2_implementations -ENTITY_COMPONENTS = { +PLATFORMS = { BINARY_SENSOR_DOMAIN, CLIMATE_DOMAIN, SENSOR_DOMAIN, @@ -119,9 +119,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) # Spin up the platforms - for component in ENTITY_COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) # If Home Assistant is already in a running state, register the webhook @@ -146,8 +146,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *( - hass.config_entries.async_forward_entry_unload(entry, component) - for component in ENTITY_COMPONENTS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ) ) ) diff --git a/homeassistant/components/totalconnect/__init__.py b/homeassistant/components/totalconnect/__init__.py index 179d60b794a..8ef223c49a5 100644 --- a/homeassistant/components/totalconnect/__init__.py +++ b/homeassistant/components/totalconnect/__init__.py @@ -82,9 +82,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN][entry.entry_id] = client - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True diff --git a/homeassistant/components/tradfri/__init__.py b/homeassistant/components/tradfri/__init__.py index 4c984067ada..3323c54d9c2 100644 --- a/homeassistant/components/tradfri/__init__.py +++ b/homeassistant/components/tradfri/__init__.py @@ -148,9 +148,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): sw_version=gateway_info.firmware_version, ) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) async def async_keep_alive(now): @@ -174,8 +174,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/tuya/__init__.py b/homeassistant/components/tuya/__init__.py index 7f6ba6b26fd..1f16d131e39 100644 --- a/homeassistant/components/tuya/__init__.py +++ b/homeassistant/components/tuya/__init__.py @@ -240,9 +240,9 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): await asyncio.gather( *[ hass.config_entries.async_forward_entry_unload( - entry, component.split(".", 1)[0] + entry, platform.split(".", 1)[0] ) - for component in hass.data[DOMAIN][ENTRY_IS_SETUP] + for platform in hass.data[DOMAIN][ENTRY_IS_SETUP] ] ) ) diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 128f0107984..45268a341e1 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -73,7 +73,7 @@ from .errors import AuthenticationRequired, CannotConnect RETRY_TIMER = 15 CHECK_HEARTBEAT_INTERVAL = timedelta(seconds=1) -SUPPORTED_PLATFORMS = [TRACKER_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN] +PLATFORMS = [TRACKER_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN] CLIENT_CONNECTED = ( WIRED_CLIENT_CONNECTED, @@ -368,7 +368,7 @@ class UniFiController: self.wireless_clients = wireless_clients.get_data(self.config_entry) self.update_wireless_clients() - for platform in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: self.hass.async_create_task( self.hass.config_entries.async_forward_entry_setup( self.config_entry, platform @@ -465,7 +465,7 @@ class UniFiController: self.hass.config_entries.async_forward_entry_unload( self.config_entry, platform ) - for platform in SUPPORTED_PLATFORMS + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/upb/__init__.py b/homeassistant/components/upb/__init__.py index f2765ff317d..70f37451e6b 100644 --- a/homeassistant/components/upb/__init__.py +++ b/homeassistant/components/upb/__init__.py @@ -17,7 +17,7 @@ from .const import ( EVENT_UPB_SCENE_CHANGED, ) -UPB_PLATFORMS = ["light", "scene"] +PLATFORMS = ["light", "scene"] async def async_setup(hass: HomeAssistant, hass_config: ConfigType) -> bool: @@ -36,9 +36,9 @@ async def async_setup_entry(hass, config_entry): hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][config_entry.entry_id] = {"upb": upb} - for component in UPB_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) def _element_changed(element, changeset): @@ -71,8 +71,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in UPB_PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index a859567e219..a15b0a641ef 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -22,7 +22,7 @@ CONFIG_SCHEMA = vol.Schema( {DOMAIN: vol.Schema({vol.Required(CONF_PORT): cv.string})}, extra=vol.ALLOW_EXTRA ) -COMPONENT_TYPES = ["switch", "sensor", "binary_sensor", "cover", "climate", "light"] +PLATFORMS = ["switch", "sensor", "binary_sensor", "cover", "climate", "light"] async def async_setup(hass, config): @@ -51,19 +51,19 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): def callback(): modules = controller.get_modules() discovery_info = {"cntrl": controller} - for category in COMPONENT_TYPES: - discovery_info[category] = [] + for platform in PLATFORMS: + discovery_info[platform] = [] for module in modules: for channel in range(1, module.number_of_channels() + 1): - for category in COMPONENT_TYPES: - if category in module.get_categories(channel): - discovery_info[category].append( + for platform in PLATFORMS: + if platform in module.get_categories(channel): + discovery_info[platform].append( (module.get_module_address(), channel) ) hass.data[DOMAIN][entry.entry_id] = discovery_info - for category in COMPONENT_TYPES: - hass.add_job(hass.config_entries.async_forward_entry_setup(entry, category)) + for platform in PLATFORMS: + hass.add_job(hass.config_entries.async_forward_entry_setup(entry, platform)) try: controller = velbus.Controller(entry.data[CONF_PORT]) @@ -113,8 +113,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): """Remove the velbus connection.""" await asyncio.wait( [ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in COMPONENT_TYPES + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) hass.data[DOMAIN][entry.entry_id]["cntrl"].stop() diff --git a/homeassistant/components/velux/__init__.py b/homeassistant/components/velux/__init__.py index 90ed0a91b14..5c1d8bfd370 100644 --- a/homeassistant/components/velux/__init__.py +++ b/homeassistant/components/velux/__init__.py @@ -10,7 +10,7 @@ import homeassistant.helpers.config_validation as cv DOMAIN = "velux" DATA_VELUX = "data_velux" -SUPPORTED_DOMAINS = ["cover", "scene"] +PLATFORMS = ["cover", "scene"] _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema( @@ -34,9 +34,9 @@ async def async_setup(hass, config): _LOGGER.exception("Can't connect to velux interface: %s", ex) return False - for component in SUPPORTED_DOMAINS: + for platform in PLATFORMS: hass.async_create_task( - discovery.async_load_platform(hass, component, DOMAIN, {}, config) + discovery.async_load_platform(hass, platform, DOMAIN, {}, config) ) return True diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index 2348d42a0d3..ab061faccad 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -38,6 +38,15 @@ from .const import ( SERVICE_ENABLE_AUTOLOCK, ) +PLATFORMS = [ + "sensor", + "switch", + "alarm_control_panel", + "lock", + "camera", + "binary_sensor", +] + HUB = None CONFIG_SCHEMA = vol.Schema( @@ -70,7 +79,7 @@ DEVICE_SERIAL_SCHEMA = vol.Schema({vol.Required(ATTR_DEVICE_SERIAL): cv.string}) def setup(hass, config): - """Set up the Verisure component.""" + """Set up the Verisure integration.""" global HUB # pylint: disable=global-statement HUB = VerisureHub(config[DOMAIN]) HUB.update_overview = Throttle(config[DOMAIN][CONF_SCAN_INTERVAL])( @@ -81,15 +90,8 @@ def setup(hass, config): hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: HUB.logout()) HUB.update_overview() - for component in ( - "sensor", - "switch", - "alarm_control_panel", - "lock", - "camera", - "binary_sensor", - ): - discovery.load_platform(hass, component, DOMAIN, {}, config) + for platform in PLATFORMS: + discovery.load_platform(hass, platform, DOMAIN, {}, config) async def capture_smartcam(service): """Capture a new picture from a smartcam.""" diff --git a/homeassistant/components/vesync/__init__.py b/homeassistant/components/vesync/__init__.py index 24bd0f000df..686a71427c3 100644 --- a/homeassistant/components/vesync/__init__.py +++ b/homeassistant/components/vesync/__init__.py @@ -156,8 +156,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/vicare/__init__.py b/homeassistant/components/vicare/__init__.py index 940e076c813..3c8dfe948d0 100644 --- a/homeassistant/components/vicare/__init__.py +++ b/homeassistant/components/vicare/__init__.py @@ -19,7 +19,7 @@ from homeassistant.helpers.storage import STORAGE_DIR _LOGGER = logging.getLogger(__name__) -VICARE_PLATFORMS = ["climate", "sensor", "binary_sensor", "water_heater"] +PLATFORMS = ["climate", "sensor", "binary_sensor", "water_heater"] DOMAIN = "vicare" PYVICARE_ERROR = "error" @@ -91,7 +91,7 @@ def setup(hass, config): hass.data[DOMAIN][VICARE_NAME] = conf[CONF_NAME] hass.data[DOMAIN][VICARE_HEATING_TYPE] = heating_type - for platform in VICARE_PLATFORMS: + for platform in PLATFORMS: discovery.load_platform(hass, platform, DOMAIN, {}, config) return True diff --git a/homeassistant/components/volumio/__init__.py b/homeassistant/components/volumio/__init__.py index 8d171cab9d2..ec1fdec685a 100644 --- a/homeassistant/components/volumio/__init__.py +++ b/homeassistant/components/volumio/__init__.py @@ -35,9 +35,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): DATA_INFO: info, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -48,8 +48,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/volvooncall/__init__.py b/homeassistant/components/volvooncall/__init__.py index 792fcc25eff..743bb903c72 100644 --- a/homeassistant/components/volvooncall/__init__.py +++ b/homeassistant/components/volvooncall/__init__.py @@ -39,7 +39,7 @@ CONF_MUTABLE = "mutable" SIGNAL_STATE_UPDATED = f"{DOMAIN}.updated" -COMPONENTS = { +PLATFORMS = { "sensor": "sensor", "binary_sensor": "binary_sensor", "lock": "lock", @@ -146,7 +146,7 @@ async def async_setup(hass, config): for instrument in ( instrument for instrument in dashboard.instruments - if instrument.component in COMPONENTS and is_enabled(instrument.slug_attr) + if instrument.component in PLATFORMS and is_enabled(instrument.slug_attr) ): data.instruments.add(instrument) @@ -154,7 +154,7 @@ async def async_setup(hass, config): hass.async_create_task( discovery.async_load_platform( hass, - COMPONENTS[instrument.component], + PLATFORMS[instrument.component], DOMAIN, (vehicle.vin, instrument.component, instrument.attr), config, diff --git a/homeassistant/components/wiffi/__init__.py b/homeassistant/components/wiffi/__init__.py index 000b961bda9..d93bd54437b 100644 --- a/homeassistant/components/wiffi/__init__.py +++ b/homeassistant/components/wiffi/__init__.py @@ -59,9 +59,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): _LOGGER.error("Port %s already in use", config_entry.data[CONF_PORT]) raise ConfigEntryNotReady from exc - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -80,8 +80,8 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/wilight/__init__.py b/homeassistant/components/wilight/__init__.py index 67433772551..3e14ea20b0c 100644 --- a/homeassistant/components/wilight/__init__.py +++ b/homeassistant/components/wilight/__init__.py @@ -33,9 +33,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN][entry.entry_id] = parent # Set up all platforms for this device/entry. - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -47,8 +47,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): # Unload entities for this entry/device. await asyncio.gather( *( - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ) ) diff --git a/homeassistant/components/wled/__init__.py b/homeassistant/components/wled/__init__.py index 7cc91d32062..72fee5f078c 100644 --- a/homeassistant/components/wled/__init__.py +++ b/homeassistant/components/wled/__init__.py @@ -30,7 +30,7 @@ from .const import ( ) SCAN_INTERVAL = timedelta(seconds=5) -WLED_COMPONENTS = (LIGHT_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN) +PLATFORMS = (LIGHT_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN) _LOGGER = logging.getLogger(__name__) @@ -60,9 +60,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) # Set up all platforms for this device/entry. - for component in WLED_COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -75,8 +75,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *( - hass.config_entries.async_forward_entry_unload(entry, component) - for component in WLED_COMPONENTS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ) ) ) diff --git a/homeassistant/components/xbox/__init__.py b/homeassistant/components/xbox/__init__.py index 3e8d537799a..1d921f5fd18 100644 --- a/homeassistant/components/xbox/__init__.py +++ b/homeassistant/components/xbox/__init__.py @@ -101,9 +101,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): "coordinator": coordinator, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -114,8 +114,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/xiaomi_aqara/__init__.py b/homeassistant/components/xiaomi_aqara/__init__.py index f54c262abba..a33f697abdf 100644 --- a/homeassistant/components/xiaomi_aqara/__init__.py +++ b/homeassistant/components/xiaomi_aqara/__init__.py @@ -188,9 +188,9 @@ async def async_setup_entry( else: platforms = GATEWAY_PLATFORMS_NO_KEY - for component in platforms: + for platform in platforms: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -208,8 +208,8 @@ async def async_unload_entry( unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in platforms + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in platforms ] ) ) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index 42b491c9b55..a72298c7c44 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -106,9 +106,9 @@ async def async_setup_gateway_entry( KEY_COORDINATOR: coordinator, } - for component in GATEWAY_PLATFORMS: + for platform in GATEWAY_PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -133,9 +133,9 @@ async def async_setup_device_entry( if not platforms: return False - for component in platforms: + for platform in platforms: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True diff --git a/homeassistant/components/xs1/__init__.py b/homeassistant/components/xs1/__init__.py index 8651c33546c..9392b9be403 100644 --- a/homeassistant/components/xs1/__init__.py +++ b/homeassistant/components/xs1/__init__.py @@ -38,7 +38,7 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) -XS1_COMPONENTS = ["climate", "sensor", "switch"] +PLATFORMS = ["climate", "sensor", "switch"] # Lock used to limit the amount of concurrent update requests # as the XS1 Gateway can only handle a very @@ -47,7 +47,7 @@ UPDATE_LOCK = asyncio.Lock() def setup(hass, config): - """Set up XS1 Component.""" + """Set up XS1 integration.""" _LOGGER.debug("Initializing XS1") host = config[DOMAIN][CONF_HOST] @@ -78,10 +78,10 @@ def setup(hass, config): hass.data[DOMAIN][ACTUATORS] = actuators hass.data[DOMAIN][SENSORS] = sensors - _LOGGER.debug("Loading components for XS1 platform...") - # Load components for supported devices - for component in XS1_COMPONENTS: - discovery.load_platform(hass, component, DOMAIN, {}, config) + _LOGGER.debug("Loading platforms for XS1 integration...") + # Load platforms for supported devices + for platform in PLATFORMS: + discovery.load_platform(hass, platform, DOMAIN, {}, config) return True diff --git a/homeassistant/components/yeelight/__init__.py b/homeassistant/components/yeelight/__init__.py index f24847a2d54..a86af10fe1c 100644 --- a/homeassistant/components/yeelight/__init__.py +++ b/homeassistant/components/yeelight/__init__.py @@ -198,9 +198,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def _load_platforms(): - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) # Move options from data for imported entries @@ -246,8 +246,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/zerproc/__init__.py b/homeassistant/components/zerproc/__init__.py index 2c652f61c21..f3d2e9daebc 100644 --- a/homeassistant/components/zerproc/__init__.py +++ b/homeassistant/components/zerproc/__init__.py @@ -20,9 +20,9 @@ async def async_setup(hass, config): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Zerproc from a config entry.""" - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -33,8 +33,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): return all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index 351fc86a1e5..de5c5423fe1 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -16,7 +16,6 @@ from . import api from .core import ZHAGateway from .core.const import ( BAUD_RATES, - COMPONENTS, CONF_BAUDRATE, CONF_DATABASE, CONF_DEVICE_CONFIG, @@ -30,6 +29,7 @@ from .core.const import ( DATA_ZHA_GATEWAY, DATA_ZHA_PLATFORM_LOADED, DOMAIN, + PLATFORMS, SIGNAL_ADD_ENTITIES, RadioType, ) @@ -88,8 +88,8 @@ async def async_setup_entry(hass, config_entry): zha_data = hass.data.setdefault(DATA_ZHA, {}) config = zha_data.get(DATA_ZHA_CONFIG, {}) - for component in COMPONENTS: - zha_data.setdefault(component, []) + for platform in PLATFORMS: + zha_data.setdefault(platform, []) if config.get(CONF_ENABLE_QUIRKS, True): # needs to be done here so that the ZHA module is finished loading @@ -101,8 +101,8 @@ async def async_setup_entry(hass, config_entry): zha_data[DATA_ZHA_DISPATCHERS] = [] zha_data[DATA_ZHA_PLATFORM_LOADED] = [] - for component in COMPONENTS: - coro = hass.config_entries.async_forward_entry_setup(config_entry, component) + for platform in PLATFORMS: + coro = hass.config_entries.async_forward_entry_setup(config_entry, platform) zha_data[DATA_ZHA_PLATFORM_LOADED].append(hass.async_create_task(coro)) device_registry = await hass.helpers.device_registry.async_get_registry() @@ -138,8 +138,8 @@ async def async_unload_entry(hass, config_entry): for unsub_dispatcher in dispatchers: unsub_dispatcher() - for component in COMPONENTS: - await hass.config_entries.async_forward_entry_unload(config_entry, component) + for platform in PLATFORMS: + await hass.config_entries.async_forward_entry_unload(config_entry, platform) return True diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index ac4f53d2a8c..2454085d9a4 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -104,7 +104,7 @@ CLUSTER_COMMANDS_SERVER = "server_commands" CLUSTER_TYPE_IN = "in" CLUSTER_TYPE_OUT = "out" -COMPONENTS = ( +PLATFORMS = ( BINARY_SENSOR, CLIMATE, COVER, diff --git a/homeassistant/components/zha/core/discovery.py b/homeassistant/components/zha/core/discovery.py index e071a523321..ba970570ecb 100644 --- a/homeassistant/components/zha/core/discovery.py +++ b/homeassistant/components/zha/core/discovery.py @@ -75,7 +75,7 @@ class ProbeEndpoint: ep_device_type = channel_pool.endpoint.device_type component = zha_regs.DEVICE_CLASS[ep_profile_id].get(ep_device_type) - if component and component in zha_const.COMPONENTS: + if component and component in zha_const.PLATFORMS: channels = channel_pool.unclaimed_channels() entity_class, claimed = zha_regs.ZHA_ENTITIES.get_entity( component, channel_pool.manufacturer, channel_pool.model, channels @@ -122,7 +122,7 @@ class ProbeEndpoint: ep_channels: zha_typing.ChannelPoolType, ) -> None: """Probe specified cluster for specific component.""" - if component is None or component not in zha_const.COMPONENTS: + if component is None or component not in zha_const.PLATFORMS: return channel_list = [channel] unique_id = f"{ep_channels.unique_id}-{channel.cluster.cluster_id}" diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 8e9889b5fac..0ab1f786555 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -89,7 +89,7 @@ DEFAULT_CONF_INVERT_PERCENT = False DEFAULT_CONF_REFRESH_VALUE = False DEFAULT_CONF_REFRESH_DELAY = 5 -SUPPORTED_PLATFORMS = [ +PLATFORMS = [ "binary_sensor", "climate", "cover", @@ -1060,7 +1060,7 @@ async def async_setup_entry(hass, config_entry): hass.services.async_register(DOMAIN, const.SERVICE_START_NETWORK, start_zwave) - for entry_component in SUPPORTED_PLATFORMS: + for entry_component in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(config_entry, entry_component) ) @@ -1228,7 +1228,7 @@ class ZWaveDeviceEntityValues: return self._hass.data[DATA_DEVICES][device.unique_id] = device - if component in SUPPORTED_PLATFORMS: + if component in PLATFORMS: async_dispatcher_send(self._hass, f"zwave_new_{component}", device) else: await discovery.async_load_platform( diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 798fd9fda2c..4f4c8d6eab0 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -278,8 +278,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # wait until all required platforms are ready await asyncio.gather( *[ - hass.config_entries.async_forward_entry_setup(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_setup(entry, platform) + for platform in PLATFORMS ] ) @@ -388,8 +388,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/script/scaffold/templates/config_flow/integration/__init__.py b/script/scaffold/templates/config_flow/integration/__init__.py index c6df6e99979..334ac8dbbc9 100644 --- a/script/scaffold/templates/config_flow/integration/__init__.py +++ b/script/scaffold/templates/config_flow/integration/__init__.py @@ -21,9 +21,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): # TODO Store an API object for your platforms to access # hass.data[DOMAIN][entry.entry_id] = MyApi(...) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -34,8 +34,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/script/scaffold/templates/config_flow_discovery/integration/__init__.py b/script/scaffold/templates/config_flow_discovery/integration/__init__.py index c6df6e99979..334ac8dbbc9 100644 --- a/script/scaffold/templates/config_flow_discovery/integration/__init__.py +++ b/script/scaffold/templates/config_flow_discovery/integration/__init__.py @@ -21,9 +21,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): # TODO Store an API object for your platforms to access # hass.data[DOMAIN][entry.entry_id] = MyApi(...) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -34,8 +34,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/script/scaffold/templates/config_flow_oauth2/integration/__init__.py b/script/scaffold/templates/config_flow_oauth2/integration/__init__.py index c51061b57fe..4e290921047 100644 --- a/script/scaffold/templates/config_flow_oauth2/integration/__init__.py +++ b/script/scaffold/templates/config_flow_oauth2/integration/__init__.py @@ -72,9 +72,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): aiohttp_client.async_get_clientsession(hass), session ) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -85,8 +85,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/tests/components/abode/common.py b/tests/components/abode/common.py index aabc732daa2..c134552ccd4 100644 --- a/tests/components/abode/common.py +++ b/tests/components/abode/common.py @@ -16,7 +16,7 @@ async def setup_platform(hass, platform): ) mock_entry.add_to_hass(hass) - with patch("homeassistant.components.abode.ABODE_PLATFORMS", [platform]), patch( + with patch("homeassistant.components.abode.PLATFORMS", [platform]), patch( "abodepy.event_controller.sio" ), patch("abodepy.utils.save_cache"): assert await async_setup_component(hass, ABODE_DOMAIN, {}) diff --git a/tests/components/dynalite/test_init.py b/tests/components/dynalite/test_init.py index eab88fb18ca..34b66399a3e 100644 --- a/tests/components/dynalite/test_init.py +++ b/tests/components/dynalite/test_init.py @@ -277,9 +277,7 @@ async def test_unload_entry(hass): ) as mock_unload: assert await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() - assert mock_unload.call_count == len(dynalite.ENTITY_PLATFORMS) - expected_calls = [ - call(entry, platform) for platform in dynalite.ENTITY_PLATFORMS - ] + assert mock_unload.call_count == len(dynalite.PLATFORMS) + expected_calls = [call(entry, platform) for platform in dynalite.PLATFORMS] for cur_call in mock_unload.mock_calls: assert cur_call in expected_calls diff --git a/tests/components/dyson/conftest.py b/tests/components/dyson/conftest.py index 747f7a43986..300c80f3a73 100644 --- a/tests/components/dyson/conftest.py +++ b/tests/components/dyson/conftest.py @@ -26,8 +26,8 @@ async def device(hass: HomeAssistant, request) -> DysonDevice: device = get_device() with patch(f"{BASE_PATH}.DysonAccount.login", return_value=True), patch( f"{BASE_PATH}.DysonAccount.devices", return_value=[device] - ), patch(f"{BASE_PATH}.DYSON_PLATFORMS", [platform]): - # DYSON_PLATFORMS is patched so that only the platform being tested is set up + ), patch(f"{BASE_PATH}.PLATFORMS", [platform]): + # PLATFORMS is patched so that only the platform being tested is set up await async_setup_component( hass, DOMAIN, diff --git a/tests/components/dyson/test_init.py b/tests/components/dyson/test_init.py index 2535da4d166..714ac919c19 100644 --- a/tests/components/dyson/test_init.py +++ b/tests/components/dyson/test_init.py @@ -54,7 +54,7 @@ async def test_setup_manual(hass: HomeAssistant): with patch(f"{BASE_PATH}.DysonAccount.login", return_value=True) as login, patch( f"{BASE_PATH}.DysonAccount.devices", return_value=devices ) as devices_method, patch( - f"{BASE_PATH}.DYSON_PLATFORMS", ["fan", "vacuum"] + f"{BASE_PATH}.PLATFORMS", ["fan", "vacuum"] ): # Patch platforms to get rid of sensors assert await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() @@ -85,7 +85,7 @@ async def test_setup_autoconnect(hass: HomeAssistant): with patch(f"{BASE_PATH}.DysonAccount.login", return_value=True), patch( f"{BASE_PATH}.DysonAccount.devices", return_value=devices ), patch( - f"{BASE_PATH}.DYSON_PLATFORMS", ["fan"] + f"{BASE_PATH}.PLATFORMS", ["fan"] ): # Patch platforms to get rid of sensors assert await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() diff --git a/tests/components/dyson/test_sensor.py b/tests/components/dyson/test_sensor.py index a1f8e4bb37c..4541ef2190a 100644 --- a/tests/components/dyson/test_sensor.py +++ b/tests/components/dyson/test_sensor.py @@ -168,8 +168,8 @@ async def test_temperature( device = async_get_device(DysonPureCoolLink) with patch(f"{BASE_PATH}.DysonAccount.login", return_value=True), patch( f"{BASE_PATH}.DysonAccount.devices", return_value=[device] - ), patch(f"{BASE_PATH}.DYSON_PLATFORMS", [PLATFORM_DOMAIN]): - # DYSON_PLATFORMS is patched so that only the platform being tested is set up + ), patch(f"{BASE_PATH}.PLATFORMS", [PLATFORM_DOMAIN]): + # PLATFORMS is patched so that only the platform being tested is set up await async_setup_component( hass, DOMAIN, diff --git a/tests/components/onewire/test_binary_sensor.py b/tests/components/onewire/test_binary_sensor.py index aee1641c24f..dd44510e0ad 100644 --- a/tests/components/onewire/test_binary_sensor.py +++ b/tests/components/onewire/test_binary_sensor.py @@ -68,7 +68,7 @@ async def test_owserver_binary_sensor(owproxy, hass, device_id): item["default_disabled"] = False with patch( - "homeassistant.components.onewire.SUPPORTED_PLATFORMS", [BINARY_SENSOR_DOMAIN] + "homeassistant.components.onewire.PLATFORMS", [BINARY_SENSOR_DOMAIN] ), patch.dict( "homeassistant.components.onewire.binary_sensor.DEVICE_BINARY_SENSORS", patch_device_binary_sensors, diff --git a/tests/components/onewire/test_entity_owserver.py b/tests/components/onewire/test_entity_owserver.py index 42cbf77711c..a3a205795bf 100644 --- a/tests/components/onewire/test_entity_owserver.py +++ b/tests/components/onewire/test_entity_owserver.py @@ -5,11 +5,7 @@ from pyownet.protocol import Error as ProtocolError import pytest from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN -from homeassistant.components.onewire.const import ( - DOMAIN, - PRESSURE_CBAR, - SUPPORTED_PLATFORMS, -) +from homeassistant.components.onewire.const import DOMAIN, PLATFORMS, PRESSURE_CBAR from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import ( @@ -754,7 +750,7 @@ MOCK_DEVICE_SENSORS = { @pytest.mark.parametrize("device_id", MOCK_DEVICE_SENSORS.keys()) -@pytest.mark.parametrize("platform", SUPPORTED_PLATFORMS) +@pytest.mark.parametrize("platform", PLATFORMS) @patch("homeassistant.components.onewire.onewirehub.protocol.proxy") async def test_owserver_setup_valid_device(owproxy, hass, device_id, platform): """Test for 1-Wire device. @@ -782,7 +778,7 @@ async def test_owserver_setup_valid_device(owproxy, hass, device_id, platform): owproxy.return_value.dir.return_value = dir_return_value owproxy.return_value.read.side_effect = read_side_effect - with patch("homeassistant.components.onewire.SUPPORTED_PLATFORMS", [platform]): + with patch("homeassistant.components.onewire.PLATFORMS", [platform]): await setup_onewire_patched_owserver_integration(hass) await hass.async_block_till_done() diff --git a/tests/components/onewire/test_sensor.py b/tests/components/onewire/test_sensor.py index 9e91da01b21..44351cf9a63 100644 --- a/tests/components/onewire/test_sensor.py +++ b/tests/components/onewire/test_sensor.py @@ -134,7 +134,7 @@ async def test_sensors_on_owserver_coupler(owproxy, hass, device_id): owproxy.return_value.dir.side_effect = dir_side_effect owproxy.return_value.read.side_effect = read_side_effect - with patch("homeassistant.components.onewire.SUPPORTED_PLATFORMS", [SENSOR_DOMAIN]): + with patch("homeassistant.components.onewire.PLATFORMS", [SENSOR_DOMAIN]): await setup_onewire_patched_owserver_integration(hass) await hass.async_block_till_done() diff --git a/tests/components/onewire/test_switch.py b/tests/components/onewire/test_switch.py index 1c778d4e264..0d8c9918711 100644 --- a/tests/components/onewire/test_switch.py +++ b/tests/components/onewire/test_switch.py @@ -94,7 +94,7 @@ async def test_owserver_switch(owproxy, hass, device_id): item["default_disabled"] = False with patch( - "homeassistant.components.onewire.SUPPORTED_PLATFORMS", [SWITCH_DOMAIN] + "homeassistant.components.onewire.PLATFORMS", [SWITCH_DOMAIN] ), patch.dict( "homeassistant.components.onewire.switch.DEVICE_SWITCHES", patch_device_switches ): diff --git a/tests/components/smartthings/test_init.py b/tests/components/smartthings/test_init.py index 9024b72bb85..eed1d5d26b1 100644 --- a/tests/components/smartthings/test_init.py +++ b/tests/components/smartthings/test_init.py @@ -14,8 +14,8 @@ from homeassistant.components.smartthings.const import ( DATA_BROKERS, DOMAIN, EVENT_BUTTON, + PLATFORMS, SIGNAL_SMARTTHINGS_UPDATE, - SUPPORTED_PLATFORMS, ) from homeassistant.config import async_process_ha_core_config from homeassistant.const import HTTP_FORBIDDEN, HTTP_INTERNAL_SERVER_ERROR @@ -174,7 +174,7 @@ async def test_scenes_unauthorized_loads_platforms( assert await smartthings.async_setup_entry(hass, config_entry) # Assert platforms loaded await hass.async_block_till_done() - assert forward_mock.call_count == len(SUPPORTED_PLATFORMS) + assert forward_mock.call_count == len(PLATFORMS) async def test_config_entry_loads_platforms( @@ -206,7 +206,7 @@ async def test_config_entry_loads_platforms( assert await smartthings.async_setup_entry(hass, config_entry) # Assert platforms loaded await hass.async_block_till_done() - assert forward_mock.call_count == len(SUPPORTED_PLATFORMS) + assert forward_mock.call_count == len(PLATFORMS) async def test_config_entry_loads_unconnected_cloud( @@ -237,7 +237,7 @@ async def test_config_entry_loads_unconnected_cloud( with patch.object(hass.config_entries, "async_forward_entry_setup") as forward_mock: assert await smartthings.async_setup_entry(hass, config_entry) await hass.async_block_till_done() - assert forward_mock.call_count == len(SUPPORTED_PLATFORMS) + assert forward_mock.call_count == len(PLATFORMS) async def test_unload_entry(hass, config_entry): @@ -258,7 +258,7 @@ async def test_unload_entry(hass, config_entry): assert config_entry.entry_id not in hass.data[DOMAIN][DATA_BROKERS] # Assert platforms unloaded await hass.async_block_till_done() - assert forward_mock.call_count == len(SUPPORTED_PLATFORMS) + assert forward_mock.call_count == len(PLATFORMS) async def test_remove_entry(hass, config_entry, smartthings_mock): diff --git a/tests/components/unifi/test_controller.py b/tests/components/unifi/test_controller.py index 00865b4e910..6b40df5857f 100644 --- a/tests/components/unifi/test_controller.py +++ b/tests/components/unifi/test_controller.py @@ -22,10 +22,7 @@ from homeassistant.components.unifi.const import ( DOMAIN as UNIFI_DOMAIN, UNIFI_WIRELESS_CLIENTS, ) -from homeassistant.components.unifi.controller import ( - SUPPORTED_PLATFORMS, - get_controller, -) +from homeassistant.components.unifi.controller import PLATFORMS, get_controller from homeassistant.components.unifi.errors import AuthenticationRequired, CannotConnect from homeassistant.const import ( CONF_HOST, @@ -211,7 +208,7 @@ async def test_controller_setup(hass, aioclient_mock): controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] entry = controller.config_entry - assert len(forward_entry_setup.mock_calls) == len(SUPPORTED_PLATFORMS) + assert len(forward_entry_setup.mock_calls) == len(PLATFORMS) assert forward_entry_setup.mock_calls[0][1] == (entry, TRACKER_DOMAIN) assert forward_entry_setup.mock_calls[1][1] == (entry, SENSOR_DOMAIN) assert forward_entry_setup.mock_calls[2][1] == (entry, SWITCH_DOMAIN) diff --git a/tests/components/zha/test_discover.py b/tests/components/zha/test_discover.py index ac2ef085e14..d4195c681e7 100644 --- a/tests/components/zha/test_discover.py +++ b/tests/components/zha/test_discover.py @@ -96,7 +96,7 @@ async def test_devices( entity_ids = hass_disable_services.states.async_entity_ids() await hass_disable_services.async_block_till_done() zha_entity_ids = { - ent for ent in entity_ids if ent.split(".")[0] in zha_const.COMPONENTS + ent for ent in entity_ids if ent.split(".")[0] in zha_const.PLATFORMS } if cluster_identify: From 17444e2f2f835b647a3b8b48245e983e3a1c564c Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Tue, 2 Mar 2021 21:50:28 +0100 Subject: [PATCH 129/831] Limit log spam by ConfigEntryNotReady (#47201) --- homeassistant/config_entries.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index d45f2b94516..d3e8f66abc4 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -257,12 +257,19 @@ class ConfigEntry: self.state = ENTRY_STATE_SETUP_RETRY wait_time = 2 ** min(tries, 4) * 5 tries += 1 - _LOGGER.warning( - "Config entry '%s' for %s integration not ready yet. Retrying in %d seconds", - self.title, - self.domain, - wait_time, - ) + if tries == 1: + _LOGGER.warning( + "Config entry '%s' for %s integration not ready yet. Retrying in background", + self.title, + self.domain, + ) + else: + _LOGGER.debug( + "Config entry '%s' for %s integration not ready yet. Retrying in %d seconds", + self.title, + self.domain, + wait_time, + ) async def setup_again(now: Any) -> None: """Run setup again.""" From 2df644c6cce91cad8d9eabd842e4d4f7223e3280 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 2 Mar 2021 12:58:53 -0800 Subject: [PATCH 130/831] Clean up secret loading (#47034) --- homeassistant/bootstrap.py | 3 - .../components/lovelace/dashboard.py | 5 +- homeassistant/config.py | 19 +- homeassistant/helpers/check_config.py | 12 +- homeassistant/scripts/check_config.py | 31 +-- homeassistant/util/yaml/__init__.py | 4 +- homeassistant/util/yaml/loader.py | 179 ++++++++++-------- tests/util/yaml/test_init.py | 53 +++--- 8 files changed, 172 insertions(+), 134 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index fd2d580a879..6d334ac8953 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -28,7 +28,6 @@ from homeassistant.setup import ( from homeassistant.util.async_ import gather_with_concurrency from homeassistant.util.logging import async_activate_log_queue_handler from homeassistant.util.package import async_get_user_site, is_virtual_env -from homeassistant.util.yaml import clear_secret_cache if TYPE_CHECKING: from .runner import RuntimeConfig @@ -122,8 +121,6 @@ async def async_setup_hass( basic_setup_success = ( await async_from_config_dict(config_dict, hass) is not None ) - finally: - clear_secret_cache() if config_dict is None: safe_mode = True diff --git a/homeassistant/components/lovelace/dashboard.py b/homeassistant/components/lovelace/dashboard.py index 2d3196054e3..c6f4726724b 100644 --- a/homeassistant/components/lovelace/dashboard.py +++ b/homeassistant/components/lovelace/dashboard.py @@ -2,6 +2,7 @@ from abc import ABC, abstractmethod import logging import os +from pathlib import Path import time from typing import Optional, cast @@ -12,7 +13,7 @@ from homeassistant.const import CONF_FILENAME from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import collection, storage -from homeassistant.util.yaml import load_yaml +from homeassistant.util.yaml import Secrets, load_yaml from .const import ( CONF_ICON, @@ -201,7 +202,7 @@ class LovelaceYAML(LovelaceConfig): is_updated = self._cache is not None try: - config = load_yaml(self.path) + config = load_yaml(self.path, Secrets(Path(self.hass.config.config_dir))) except FileNotFoundError: raise ConfigNotFound from None diff --git a/homeassistant/config.py b/homeassistant/config.py index 90df365c349..cfc1390a37b 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -2,6 +2,7 @@ from collections import OrderedDict import logging import os +from pathlib import Path import re import shutil from types import ModuleType @@ -59,7 +60,7 @@ from homeassistant.requirements import ( ) from homeassistant.util.package import is_docker_env from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM -from homeassistant.util.yaml import SECRET_YAML, load_yaml +from homeassistant.util.yaml import SECRET_YAML, Secrets, load_yaml _LOGGER = logging.getLogger(__name__) @@ -318,23 +319,33 @@ async def async_hass_config_yaml(hass: HomeAssistant) -> Dict: This function allow a component inside the asyncio loop to reload its configuration by itself. Include package merge. """ + if hass.config.config_dir is None: + secrets = None + else: + secrets = Secrets(Path(hass.config.config_dir)) + # Not using async_add_executor_job because this is an internal method. config = await hass.loop.run_in_executor( - None, load_yaml_config_file, hass.config.path(YAML_CONFIG_FILE) + None, + load_yaml_config_file, + hass.config.path(YAML_CONFIG_FILE), + secrets, ) core_config = config.get(CONF_CORE, {}) await merge_packages_config(hass, config, core_config.get(CONF_PACKAGES, {})) return config -def load_yaml_config_file(config_path: str) -> Dict[Any, Any]: +def load_yaml_config_file( + config_path: str, secrets: Optional[Secrets] = None +) -> Dict[Any, Any]: """Parse a YAML configuration file. Raises FileNotFoundError or HomeAssistantError. This method needs to run in an executor. """ - conf_dict = load_yaml(config_path) + conf_dict = load_yaml(config_path, secrets) if not isinstance(conf_dict, dict): msg = ( diff --git a/homeassistant/helpers/check_config.py b/homeassistant/helpers/check_config.py index 7b7b53d3c0f..5dd7623ecc8 100644 --- a/homeassistant/helpers/check_config.py +++ b/homeassistant/helpers/check_config.py @@ -4,6 +4,7 @@ from __future__ import annotations from collections import OrderedDict import logging import os +from pathlib import Path from typing import List, NamedTuple, Optional import voluptuous as vol @@ -87,13 +88,18 @@ async def async_check_ha_config_file(hass: HomeAssistant) -> HomeAssistantConfig try: if not await hass.async_add_executor_job(os.path.isfile, config_path): return result.add_error("File configuration.yaml not found.") - config = await hass.async_add_executor_job(load_yaml_config_file, config_path) + + assert hass.config.config_dir is not None + + config = await hass.async_add_executor_job( + load_yaml_config_file, + config_path, + yaml_loader.Secrets(Path(hass.config.config_dir)), + ) except FileNotFoundError: return result.add_error(f"File not found: {config_path}") except HomeAssistantError as err: return result.add_error(f"Error loading {config_path}: {err}") - finally: - yaml_loader.clear_secret_cache() # Extract and validate core [homeassistant] config try: diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index f75594a546e..4fc8383782c 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -9,10 +9,11 @@ import os from typing import Any, Callable, Dict, List, Tuple from unittest.mock import patch -from homeassistant import bootstrap, core +from homeassistant import core from homeassistant.config import get_default_config_dir from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.check_config import async_check_ha_config_file +from homeassistant.util.yaml import Secrets import homeassistant.util.yaml.loader as yaml_loader # mypy: allow-untyped-calls, allow-untyped-defs @@ -26,7 +27,6 @@ MOCKS: Dict[str, Tuple[str, Callable]] = { "load*": ("homeassistant.config.load_yaml", yaml_loader.load_yaml), "secrets": ("homeassistant.util.yaml.loader.secret_yaml", yaml_loader.secret_yaml), } -SILENCE = ("homeassistant.scripts.check_config.yaml_loader.clear_secret_cache",) PATCHES: Dict[str, Any] = {} @@ -154,14 +154,14 @@ def check(config_dir, secrets=False): "secrets": OrderedDict(), # secret cache and secrets loaded "except": OrderedDict(), # exceptions raised (with config) #'components' is a HomeAssistantConfig # noqa: E265 - "secret_cache": None, + "secret_cache": {}, } # pylint: disable=possibly-unused-variable - def mock_load(filename): + def mock_load(filename, secrets=None): """Mock hass.util.load_yaml to save config file names.""" res["yaml_files"][filename] = True - return MOCKS["load"][1](filename) + return MOCKS["load"][1](filename, secrets) # pylint: disable=possibly-unused-variable def mock_secrets(ldr, node): @@ -173,10 +173,6 @@ def check(config_dir, secrets=False): res["secrets"][node.value] = val return val - # Patches to skip functions - for sil in SILENCE: - PATCHES[sil] = patch(sil) - # Patches with local mock functions for key, val in MOCKS.items(): if not secrets and key == "secrets": @@ -192,11 +188,19 @@ def check(config_dir, secrets=False): if secrets: # Ensure !secrets point to the patched function - yaml_loader.yaml.SafeLoader.add_constructor("!secret", yaml_loader.secret_yaml) + yaml_loader.SafeLineLoader.add_constructor("!secret", yaml_loader.secret_yaml) + + def secrets_proxy(*args): + secrets = Secrets(*args) + res["secret_cache"] = secrets._cache + return secrets try: - res["components"] = asyncio.run(async_check_config(config_dir)) - res["secret_cache"] = OrderedDict(yaml_loader.__SECRET_CACHE) + with patch.object(yaml_loader, "Secrets", secrets_proxy): + res["components"] = asyncio.run(async_check_config(config_dir)) + res["secret_cache"] = { + str(key): val for key, val in res["secret_cache"].items() + } for err in res["components"].errors: domain = err.domain or ERROR_STR res["except"].setdefault(domain, []).append(err.message) @@ -212,10 +216,9 @@ def check(config_dir, secrets=False): pat.stop() if secrets: # Ensure !secrets point to the original function - yaml_loader.yaml.SafeLoader.add_constructor( + yaml_loader.SafeLineLoader.add_constructor( "!secret", yaml_loader.secret_yaml ) - bootstrap.clear_secret_cache() return res diff --git a/homeassistant/util/yaml/__init__.py b/homeassistant/util/yaml/__init__.py index a152086ea82..b3f1b7ecd43 100644 --- a/homeassistant/util/yaml/__init__.py +++ b/homeassistant/util/yaml/__init__.py @@ -2,7 +2,7 @@ from .const import SECRET_YAML from .dumper import dump, save_yaml from .input import UndefinedSubstitution, extract_inputs, substitute -from .loader import clear_secret_cache, load_yaml, parse_yaml, secret_yaml +from .loader import Secrets, load_yaml, parse_yaml, secret_yaml from .objects import Input __all__ = [ @@ -10,7 +10,7 @@ __all__ = [ "Input", "dump", "save_yaml", - "clear_secret_cache", + "Secrets", "load_yaml", "secret_yaml", "parse_yaml", diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index 7d713c9f0c0..04e51a5a9c5 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -3,8 +3,8 @@ from collections import OrderedDict import fnmatch import logging import os -import sys -from typing import Dict, Iterator, List, TextIO, TypeVar, Union, overload +from pathlib import Path +from typing import Any, Dict, Iterator, List, Optional, TextIO, TypeVar, Union, overload import yaml @@ -19,20 +19,82 @@ JSON_TYPE = Union[List, Dict, str] # pylint: disable=invalid-name DICT_T = TypeVar("DICT_T", bound=Dict) # pylint: disable=invalid-name _LOGGER = logging.getLogger(__name__) -__SECRET_CACHE: Dict[str, JSON_TYPE] = {} -def clear_secret_cache() -> None: - """Clear the secret cache. +class Secrets: + """Store secrets while loading YAML.""" - Async friendly. - """ - __SECRET_CACHE.clear() + def __init__(self, config_dir: Path): + """Initialize secrets.""" + self.config_dir = config_dir + self._cache: Dict[Path, Dict[str, str]] = {} + + def get(self, requester_path: str, secret: str) -> str: + """Return the value of a secret.""" + current_path = Path(requester_path) + + secret_dir = current_path + while True: + secret_dir = secret_dir.parent + + try: + secret_dir.relative_to(self.config_dir) + except ValueError: + # We went above the config dir + break + + secrets = self._load_secret_yaml(secret_dir) + + if secret in secrets: + _LOGGER.debug( + "Secret %s retrieved from secrets.yaml in folder %s", + secret, + secret_dir, + ) + return secrets[secret] + + raise HomeAssistantError(f"Secret {secret} not defined") + + def _load_secret_yaml(self, secret_dir: Path) -> Dict[str, str]: + """Load the secrets yaml from path.""" + secret_path = secret_dir / SECRET_YAML + + if secret_path in self._cache: + return self._cache[secret_path] + + _LOGGER.debug("Loading %s", secret_path) + try: + secrets = load_yaml(str(secret_path)) + + if not isinstance(secrets, dict): + raise HomeAssistantError("Secrets is not a dictionary") + + if "logger" in secrets: + logger = str(secrets["logger"]).lower() + if logger == "debug": + _LOGGER.setLevel(logging.DEBUG) + else: + _LOGGER.error( + "secrets.yaml: 'logger: debug' expected, but 'logger: %s' found", + logger, + ) + del secrets["logger"] + except FileNotFoundError: + secrets = {} + + self._cache[secret_path] = secrets + + return secrets class SafeLineLoader(yaml.SafeLoader): """Loader class that keeps track of line numbers.""" + def __init__(self, stream: Any, secrets: Optional[Secrets] = None) -> None: + """Initialize a safe line loader.""" + super().__init__(stream) + self.secrets = secrets + def compose_node(self, parent: yaml.nodes.Node, index: int) -> yaml.nodes.Node: """Annotate a node with the first line it was seen.""" last_line: int = self.line @@ -41,22 +103,27 @@ class SafeLineLoader(yaml.SafeLoader): return node -def load_yaml(fname: str) -> JSON_TYPE: +def load_yaml(fname: str, secrets: Optional[Secrets] = None) -> JSON_TYPE: """Load a YAML file.""" try: with open(fname, encoding="utf-8") as conf_file: - return parse_yaml(conf_file) + return parse_yaml(conf_file, secrets) except UnicodeDecodeError as exc: _LOGGER.error("Unable to read file %s: %s", fname, exc) raise HomeAssistantError(exc) from exc -def parse_yaml(content: Union[str, TextIO]) -> JSON_TYPE: +def parse_yaml( + content: Union[str, TextIO], secrets: Optional[Secrets] = None +) -> JSON_TYPE: """Load a YAML file.""" try: # If configuration file is empty YAML returns None # We convert that to an empty dict - return yaml.load(content, Loader=SafeLineLoader) or OrderedDict() + return ( + yaml.load(content, Loader=lambda stream: SafeLineLoader(stream, secrets)) + or OrderedDict() + ) except yaml.YAMLError as exc: _LOGGER.error(str(exc)) raise HomeAssistantError(exc) from exc @@ -64,21 +131,21 @@ def parse_yaml(content: Union[str, TextIO]) -> JSON_TYPE: @overload def _add_reference( - obj: Union[list, NodeListClass], loader: yaml.SafeLoader, node: yaml.nodes.Node + obj: Union[list, NodeListClass], loader: SafeLineLoader, node: yaml.nodes.Node ) -> NodeListClass: ... @overload def _add_reference( - obj: Union[str, NodeStrClass], loader: yaml.SafeLoader, node: yaml.nodes.Node + obj: Union[str, NodeStrClass], loader: SafeLineLoader, node: yaml.nodes.Node ) -> NodeStrClass: ... @overload def _add_reference( - obj: DICT_T, loader: yaml.SafeLoader, node: yaml.nodes.Node + obj: DICT_T, loader: SafeLineLoader, node: yaml.nodes.Node ) -> DICT_T: ... @@ -103,7 +170,7 @@ def _include_yaml(loader: SafeLineLoader, node: yaml.nodes.Node) -> JSON_TYPE: """ fname = os.path.join(os.path.dirname(loader.name), node.value) try: - return _add_reference(load_yaml(fname), loader, node) + return _add_reference(load_yaml(fname, loader.secrets), loader, node) except FileNotFoundError as exc: raise HomeAssistantError( f"{node.start_mark}: Unable to read file {fname}." @@ -135,7 +202,7 @@ def _include_dir_named_yaml( filename = os.path.splitext(os.path.basename(fname))[0] if os.path.basename(fname) == SECRET_YAML: continue - mapping[filename] = load_yaml(fname) + mapping[filename] = load_yaml(fname, loader.secrets) return _add_reference(mapping, loader, node) @@ -148,7 +215,7 @@ def _include_dir_merge_named_yaml( for fname in _find_files(loc, "*.yaml"): if os.path.basename(fname) == SECRET_YAML: continue - loaded_yaml = load_yaml(fname) + loaded_yaml = load_yaml(fname, loader.secrets) if isinstance(loaded_yaml, dict): mapping.update(loaded_yaml) return _add_reference(mapping, loader, node) @@ -175,7 +242,7 @@ def _include_dir_merge_list_yaml( for fname in _find_files(loc, "*.yaml"): if os.path.basename(fname) == SECRET_YAML: continue - loaded_yaml = load_yaml(fname) + loaded_yaml = load_yaml(fname, loader.secrets) if isinstance(loaded_yaml, list): merged_list.extend(loaded_yaml) return _add_reference(merged_list, loader, node) @@ -232,75 +299,27 @@ def _env_var_yaml(loader: SafeLineLoader, node: yaml.nodes.Node) -> str: raise HomeAssistantError(node.value) -def _load_secret_yaml(secret_path: str) -> JSON_TYPE: - """Load the secrets yaml from path.""" - secret_path = os.path.join(secret_path, SECRET_YAML) - if secret_path in __SECRET_CACHE: - return __SECRET_CACHE[secret_path] - - _LOGGER.debug("Loading %s", secret_path) - try: - secrets = load_yaml(secret_path) - if not isinstance(secrets, dict): - raise HomeAssistantError("Secrets is not a dictionary") - if "logger" in secrets: - logger = str(secrets["logger"]).lower() - if logger == "debug": - _LOGGER.setLevel(logging.DEBUG) - else: - _LOGGER.error( - "secrets.yaml: 'logger: debug' expected, but 'logger: %s' found", - logger, - ) - del secrets["logger"] - except FileNotFoundError: - secrets = {} - __SECRET_CACHE[secret_path] = secrets - return secrets - - def secret_yaml(loader: SafeLineLoader, node: yaml.nodes.Node) -> JSON_TYPE: """Load secrets and embed it into the configuration YAML.""" - if os.path.basename(loader.name) == SECRET_YAML: - _LOGGER.error("secrets.yaml: attempt to load secret from within secrets file") - raise HomeAssistantError( - "secrets.yaml: attempt to load secret from within secrets file" - ) - secret_path = os.path.dirname(loader.name) - while True: - secrets = _load_secret_yaml(secret_path) + if loader.secrets is None: + raise HomeAssistantError("Secrets not supported in this YAML file") - if node.value in secrets: - _LOGGER.debug( - "Secret %s retrieved from secrets.yaml in folder %s", - node.value, - secret_path, - ) - return secrets[node.value] - - if secret_path == os.path.dirname(sys.path[0]): - break # sys.path[0] set to config/deps folder by bootstrap - - secret_path = os.path.dirname(secret_path) - if not os.path.exists(secret_path) or len(secret_path) < 5: - break # Somehow we got past the .homeassistant config folder - - raise HomeAssistantError(f"Secret {node.value} not defined") + return loader.secrets.get(loader.name, node.value) -yaml.SafeLoader.add_constructor("!include", _include_yaml) -yaml.SafeLoader.add_constructor( +SafeLineLoader.add_constructor("!include", _include_yaml) +SafeLineLoader.add_constructor( yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, _ordered_dict ) -yaml.SafeLoader.add_constructor( +SafeLineLoader.add_constructor( yaml.resolver.BaseResolver.DEFAULT_SEQUENCE_TAG, _construct_seq ) -yaml.SafeLoader.add_constructor("!env_var", _env_var_yaml) -yaml.SafeLoader.add_constructor("!secret", secret_yaml) -yaml.SafeLoader.add_constructor("!include_dir_list", _include_dir_list_yaml) -yaml.SafeLoader.add_constructor("!include_dir_merge_list", _include_dir_merge_list_yaml) -yaml.SafeLoader.add_constructor("!include_dir_named", _include_dir_named_yaml) -yaml.SafeLoader.add_constructor( +SafeLineLoader.add_constructor("!env_var", _env_var_yaml) +SafeLineLoader.add_constructor("!secret", secret_yaml) +SafeLineLoader.add_constructor("!include_dir_list", _include_dir_list_yaml) +SafeLineLoader.add_constructor("!include_dir_merge_list", _include_dir_merge_list_yaml) +SafeLineLoader.add_constructor("!include_dir_named", _include_dir_named_yaml) +SafeLineLoader.add_constructor( "!include_dir_merge_named", _include_dir_merge_named_yaml ) -yaml.SafeLoader.add_constructor("!input", Input.from_node) +SafeLineLoader.add_constructor("!input", Input.from_node) diff --git a/tests/util/yaml/test_init.py b/tests/util/yaml/test_init.py index b3a8ca4e486..daa0275b7aa 100644 --- a/tests/util/yaml/test_init.py +++ b/tests/util/yaml/test_init.py @@ -18,7 +18,7 @@ def test_simple_list(): """Test simple list.""" conf = "config:\n - simple\n - list" with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert doc["config"] == ["simple", "list"] @@ -26,7 +26,7 @@ def test_simple_dict(): """Test simple dict.""" conf = "key: value" with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert doc["key"] == "value" @@ -49,7 +49,7 @@ def test_environment_variable(): os.environ["PASSWORD"] = "secret_password" conf = "password: !env_var PASSWORD" with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert doc["password"] == "secret_password" del os.environ["PASSWORD"] @@ -58,7 +58,7 @@ def test_environment_variable_default(): """Test config file with default value for environment variable.""" conf = "password: !env_var PASSWORD secret_password" with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert doc["password"] == "secret_password" @@ -67,7 +67,7 @@ def test_invalid_environment_variable(): conf = "password: !env_var PASSWORD" with pytest.raises(HomeAssistantError): with io.StringIO(conf) as file: - yaml_loader.yaml.safe_load(file) + yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) def test_include_yaml(): @@ -75,13 +75,13 @@ def test_include_yaml(): with patch_yaml_files({"test.yaml": "value"}): conf = "key: !include test.yaml" with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert doc["key"] == "value" with patch_yaml_files({"test.yaml": None}): conf = "key: !include test.yaml" with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert doc["key"] == {} @@ -93,7 +93,7 @@ def test_include_dir_list(mock_walk): with patch_yaml_files({"/test/one.yaml": "one", "/test/two.yaml": "two"}): conf = "key: !include_dir_list /test" with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert doc["key"] == sorted(["one", "two"]) @@ -118,7 +118,7 @@ def test_include_dir_list_recursive(mock_walk): assert ( ".ignore" in mock_walk.return_value[0][1] ), "Expecting .ignore in here" - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert "tmp2" in mock_walk.return_value[0][1] assert ".ignore" not in mock_walk.return_value[0][1] assert sorted(doc["key"]) == sorted(["zero", "one", "two"]) @@ -135,7 +135,7 @@ def test_include_dir_named(mock_walk): conf = "key: !include_dir_named /test" correct = {"first": "one", "second": "two"} with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert doc["key"] == correct @@ -161,7 +161,7 @@ def test_include_dir_named_recursive(mock_walk): assert ( ".ignore" in mock_walk.return_value[0][1] ), "Expecting .ignore in here" - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert "tmp2" in mock_walk.return_value[0][1] assert ".ignore" not in mock_walk.return_value[0][1] assert doc["key"] == correct @@ -177,7 +177,7 @@ def test_include_dir_merge_list(mock_walk): ): conf = "key: !include_dir_merge_list /test" with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert sorted(doc["key"]) == sorted(["one", "two", "three"]) @@ -202,7 +202,7 @@ def test_include_dir_merge_list_recursive(mock_walk): assert ( ".ignore" in mock_walk.return_value[0][1] ), "Expecting .ignore in here" - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert "tmp2" in mock_walk.return_value[0][1] assert ".ignore" not in mock_walk.return_value[0][1] assert sorted(doc["key"]) == sorted(["one", "two", "three", "four"]) @@ -221,7 +221,7 @@ def test_include_dir_merge_named(mock_walk): with patch_yaml_files(files): conf = "key: !include_dir_merge_named /test" with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert doc["key"] == {"key1": "one", "key2": "two", "key3": "three"} @@ -246,7 +246,7 @@ def test_include_dir_merge_named_recursive(mock_walk): assert ( ".ignore" in mock_walk.return_value[0][1] ), "Expecting .ignore in here" - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert "tmp2" in mock_walk.return_value[0][1] assert ".ignore" not in mock_walk.return_value[0][1] assert doc["key"] == { @@ -278,11 +278,11 @@ def test_dump_unicode(): FILES = {} -def load_yaml(fname, string): +def load_yaml(fname, string, secrets=None): """Write a string to file and return the parsed yaml.""" FILES[fname] = string with patch_yaml_files(FILES): - return load_yaml_config_file(fname) + return load_yaml_config_file(fname, secrets) class TestSecrets(unittest.TestCase): @@ -293,7 +293,6 @@ class TestSecrets(unittest.TestCase): def setUp(self): """Create & load secrets file.""" config_dir = get_test_config_dir() - yaml.clear_secret_cache() self._yaml_path = os.path.join(config_dir, YAML_CONFIG_FILE) self._secret_path = os.path.join(config_dir, yaml.SECRET_YAML) self._sub_folder_path = os.path.join(config_dir, "subFolder") @@ -315,11 +314,11 @@ class TestSecrets(unittest.TestCase): " username: !secret comp1_un\n" " password: !secret comp1_pw\n" "", + yaml_loader.Secrets(config_dir), ) def tearDown(self): """Clean up secrets.""" - yaml.clear_secret_cache() FILES.clear() def test_secrets_from_yaml(self): @@ -341,6 +340,7 @@ class TestSecrets(unittest.TestCase): " username: !secret comp1_un\n" " password: !secret comp1_pw\n" "", + yaml_loader.Secrets(get_test_config_dir()), ) assert expected == self._yaml["http"] @@ -359,6 +359,7 @@ class TestSecrets(unittest.TestCase): " username: !secret comp1_un\n" " password: !secret comp1_pw\n" "", + yaml_loader.Secrets(get_test_config_dir()), ) assert expected == self._yaml["http"] @@ -380,9 +381,12 @@ class TestSecrets(unittest.TestCase): @patch("homeassistant.util.yaml.loader._LOGGER.error") def test_bad_logger_value(self, mock_error): """Ensure logger: debug was removed.""" - yaml.clear_secret_cache() load_yaml(self._secret_path, "logger: info\npw: abc") - load_yaml(self._yaml_path, "api_password: !secret pw") + load_yaml( + self._yaml_path, + "api_password: !secret pw", + yaml_loader.Secrets(get_test_config_dir()), + ) assert mock_error.call_count == 1, "Expected an error about logger: value" def test_secrets_are_not_dict(self): @@ -390,7 +394,6 @@ class TestSecrets(unittest.TestCase): FILES[ self._secret_path ] = "- http_pw: pwhttp\n comp1_un: un1\n comp1_pw: pw1\n" - yaml.clear_secret_cache() with pytest.raises(HomeAssistantError): load_yaml( self._yaml_path, @@ -424,10 +427,8 @@ def test_no_recursive_secrets(caplog): files = {YAML_CONFIG_FILE: "key: !secret a", yaml.SECRET_YAML: "a: 1\nb: !secret a"} with patch_yaml_files(files), pytest.raises(HomeAssistantError) as e: load_yaml_config_file(YAML_CONFIG_FILE) - assert e.value.args == ( - "secrets.yaml: attempt to load secret from within secrets file", - ) - assert "attempt to load secret from within secrets file" in caplog.text + + assert e.value.args == ("Secrets not supported in this YAML file",) def test_input_class(): From 1926941d8ea7f9e74322706d8ce757dc838ca8ad Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Mar 2021 22:02:41 +0100 Subject: [PATCH 131/831] Upgrade pillow to 8.1.1 (#47223) --- homeassistant/components/doods/manifest.json | 2 +- homeassistant/components/image/manifest.json | 2 +- homeassistant/components/proxy/manifest.json | 2 +- homeassistant/components/qrcode/manifest.json | 2 +- homeassistant/components/seven_segments/manifest.json | 2 +- homeassistant/components/sighthound/manifest.json | 2 +- homeassistant/components/tensorflow/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/doods/manifest.json b/homeassistant/components/doods/manifest.json index ecbcd8563a7..f5d425cb9ef 100644 --- a/homeassistant/components/doods/manifest.json +++ b/homeassistant/components/doods/manifest.json @@ -2,6 +2,6 @@ "domain": "doods", "name": "DOODS - Dedicated Open Object Detection Service", "documentation": "https://www.home-assistant.io/integrations/doods", - "requirements": ["pydoods==1.0.2", "pillow==8.1.0"], + "requirements": ["pydoods==1.0.2", "pillow==8.1.1"], "codeowners": [] } diff --git a/homeassistant/components/image/manifest.json b/homeassistant/components/image/manifest.json index 6978f09ab68..c8029c2e313 100644 --- a/homeassistant/components/image/manifest.json +++ b/homeassistant/components/image/manifest.json @@ -3,7 +3,7 @@ "name": "Image", "config_flow": false, "documentation": "https://www.home-assistant.io/integrations/image", - "requirements": ["pillow==8.1.0"], + "requirements": ["pillow==8.1.1"], "dependencies": ["http"], "codeowners": ["@home-assistant/core"], "quality_scale": "internal" diff --git a/homeassistant/components/proxy/manifest.json b/homeassistant/components/proxy/manifest.json index 65d8d21fc0c..c1a01004fe9 100644 --- a/homeassistant/components/proxy/manifest.json +++ b/homeassistant/components/proxy/manifest.json @@ -2,6 +2,6 @@ "domain": "proxy", "name": "Camera Proxy", "documentation": "https://www.home-assistant.io/integrations/proxy", - "requirements": ["pillow==8.1.0"], + "requirements": ["pillow==8.1.1"], "codeowners": [] } diff --git a/homeassistant/components/qrcode/manifest.json b/homeassistant/components/qrcode/manifest.json index b16eace14fd..5867d0d6b51 100644 --- a/homeassistant/components/qrcode/manifest.json +++ b/homeassistant/components/qrcode/manifest.json @@ -2,6 +2,6 @@ "domain": "qrcode", "name": "QR Code", "documentation": "https://www.home-assistant.io/integrations/qrcode", - "requirements": ["pillow==8.1.0", "pyzbar==0.1.7"], + "requirements": ["pillow==8.1.1", "pyzbar==0.1.7"], "codeowners": [] } diff --git a/homeassistant/components/seven_segments/manifest.json b/homeassistant/components/seven_segments/manifest.json index 01e0275feeb..4f9f6514531 100644 --- a/homeassistant/components/seven_segments/manifest.json +++ b/homeassistant/components/seven_segments/manifest.json @@ -2,6 +2,6 @@ "domain": "seven_segments", "name": "Seven Segments OCR", "documentation": "https://www.home-assistant.io/integrations/seven_segments", - "requirements": ["pillow==8.1.0"], + "requirements": ["pillow==8.1.1"], "codeowners": ["@fabaff"] } diff --git a/homeassistant/components/sighthound/manifest.json b/homeassistant/components/sighthound/manifest.json index 99902b8dd36..aa9519fd68b 100644 --- a/homeassistant/components/sighthound/manifest.json +++ b/homeassistant/components/sighthound/manifest.json @@ -2,6 +2,6 @@ "domain": "sighthound", "name": "Sighthound", "documentation": "https://www.home-assistant.io/integrations/sighthound", - "requirements": ["pillow==8.1.0", "simplehound==0.3"], + "requirements": ["pillow==8.1.1", "simplehound==0.3"], "codeowners": ["@robmarkcole"] } diff --git a/homeassistant/components/tensorflow/manifest.json b/homeassistant/components/tensorflow/manifest.json index f039a14d5b3..300c3ddd1db 100644 --- a/homeassistant/components/tensorflow/manifest.json +++ b/homeassistant/components/tensorflow/manifest.json @@ -7,7 +7,7 @@ "tf-models-official==2.3.0", "pycocotools==2.0.1", "numpy==1.19.2", - "pillow==8.1.0" + "pillow==8.1.1" ], "codeowners": [] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index d4b18de88ee..44e6195317d 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -20,7 +20,7 @@ httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 paho-mqtt==1.5.1 -pillow==8.1.0 +pillow==8.1.1 pip>=8.0.3,<20.3 python-slugify==4.0.1 pytz>=2021.1 diff --git a/requirements_all.txt b/requirements_all.txt index 2a2e37dd3a8..37c69382519 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1126,7 +1126,7 @@ pilight==0.1.1 # homeassistant.components.seven_segments # homeassistant.components.sighthound # homeassistant.components.tensorflow -pillow==8.1.0 +pillow==8.1.1 # homeassistant.components.dominos pizzapi==0.0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 18a1dc33839..9b5a7cd7651 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -575,7 +575,7 @@ pilight==0.1.1 # homeassistant.components.seven_segments # homeassistant.components.sighthound # homeassistant.components.tensorflow -pillow==8.1.0 +pillow==8.1.1 # homeassistant.components.plex plexapi==4.4.0 From d20659d2eee63e244791de8705183d6b5abfa749 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Tue, 2 Mar 2021 22:02:59 +0100 Subject: [PATCH 132/831] Fix issue when setting boost preset for a turned off Netatmo thermostat (#47275) --- homeassistant/components/netatmo/climate.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index 91026c40c2f..a53f7f9fb08 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -352,6 +352,9 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): def set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" + if self.hvac_mode == HVAC_MODE_OFF: + self.turn_on() + if self.target_temperature == 0: self._home_status.set_room_thermpoint( self._id, From 42af775f53ffffda58fd23a27254220c105a62d3 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 2 Mar 2021 16:10:30 -0500 Subject: [PATCH 133/831] Add raw values to zwave_js value notification event (#47258) * add value_raw to value notification event that always shows the untranslated state value * add property key and property to event params --- homeassistant/components/zwave_js/__init__.py | 8 +++++++- homeassistant/components/zwave_js/const.py | 3 +++ tests/components/zwave_js/test_events.py | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 4f4c8d6eab0..8272bfdac2c 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -30,10 +30,13 @@ from .const import ( ATTR_LABEL, ATTR_NODE_ID, ATTR_PARAMETERS, + ATTR_PROPERTY, + ATTR_PROPERTY_KEY, ATTR_PROPERTY_KEY_NAME, ATTR_PROPERTY_NAME, ATTR_TYPE, ATTR_VALUE, + ATTR_VALUE_RAW, CONF_INTEGRATION_CREATED_ADDON, DATA_CLIENT, DATA_UNSUBSCRIBE, @@ -213,7 +216,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: def async_on_value_notification(notification: ValueNotification) -> None: """Relay stateless value notification events from Z-Wave nodes to hass.""" device = dev_reg.async_get_device({get_device_id(client, notification.node)}) - value = notification.value + raw_value = value = notification.value if notification.metadata.states: value = notification.metadata.states.get(str(value), value) hass.bus.async_fire( @@ -228,9 +231,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ATTR_COMMAND_CLASS: notification.command_class, ATTR_COMMAND_CLASS_NAME: notification.command_class_name, ATTR_LABEL: notification.metadata.label, + ATTR_PROPERTY: notification.property_, ATTR_PROPERTY_NAME: notification.property_name, + ATTR_PROPERTY_KEY: notification.property_key, ATTR_PROPERTY_KEY_NAME: notification.property_key_name, ATTR_VALUE: value, + ATTR_VALUE_RAW: raw_value, }, ) diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index 19e6fc3db14..27be45c43a0 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -26,12 +26,15 @@ ATTR_HOME_ID = "home_id" ATTR_ENDPOINT = "endpoint" ATTR_LABEL = "label" ATTR_VALUE = "value" +ATTR_VALUE_RAW = "value_raw" ATTR_COMMAND_CLASS = "command_class" ATTR_COMMAND_CLASS_NAME = "command_class_name" ATTR_TYPE = "type" ATTR_DEVICE_ID = "device_id" ATTR_PROPERTY_NAME = "property_name" ATTR_PROPERTY_KEY_NAME = "property_key_name" +ATTR_PROPERTY = "property" +ATTR_PROPERTY_KEY = "property_key" ATTR_PARAMETERS = "parameters" # service constants diff --git a/tests/components/zwave_js/test_events.py b/tests/components/zwave_js/test_events.py index 2a347f6afea..e40782270a9 100644 --- a/tests/components/zwave_js/test_events.py +++ b/tests/components/zwave_js/test_events.py @@ -47,6 +47,7 @@ async def test_scenes(hass, hank_binary_switch, integration, client): assert events[0].data["command_class_name"] == "Basic" assert events[0].data["label"] == "Event value" assert events[0].data["value"] == 255 + assert events[0].data["value_raw"] == 255 # Publish fake Scene Activation value notification event = Event( @@ -82,6 +83,7 @@ async def test_scenes(hass, hank_binary_switch, integration, client): assert events[1].data["command_class_name"] == "Scene Activation" assert events[1].data["label"] == "Scene ID" assert events[1].data["value"] == 16 + assert events[1].data["value_raw"] == 16 # Publish fake Central Scene value notification event = Event( @@ -128,6 +130,7 @@ async def test_scenes(hass, hank_binary_switch, integration, client): assert events[2].data["command_class_name"] == "Central Scene" assert events[2].data["label"] == "Scene 001" assert events[2].data["value"] == "KeyPressed3x" + assert events[2].data["value_raw"] == 4 async def test_notifications(hass, hank_binary_switch, integration, client): From c327f3fc42744bdda053aac996dc2095b61b45d0 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 2 Mar 2021 16:25:09 -0500 Subject: [PATCH 134/831] Convert climacell forecast timestamp to isoformat so that UI shows the right times (#47286) --- homeassistant/components/climacell/weather.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/climacell/weather.py b/homeassistant/components/climacell/weather.py index da3282108a5..c77bbfbd50a 100644 --- a/homeassistant/components/climacell/weather.py +++ b/homeassistant/components/climacell/weather.py @@ -1,4 +1,5 @@ """Weather component that handles meteorological data for your location.""" +from datetime import datetime import logging from typing import Any, Callable, Dict, List, Optional @@ -80,7 +81,7 @@ def _translate_condition( def _forecast_dict( hass: HomeAssistantType, - time: str, + forecast_dt: datetime, use_datetime: bool, condition: str, precipitation: Optional[float], @@ -92,10 +93,7 @@ def _forecast_dict( ) -> Dict[str, Any]: """Return formatted Forecast dict from ClimaCell forecast data.""" if use_datetime: - translated_condition = _translate_condition( - condition, - is_up(hass, dt_util.as_utc(dt_util.parse_datetime(time))), - ) + translated_condition = _translate_condition(condition, is_up(hass, forecast_dt)) else: translated_condition = _translate_condition(condition, True) @@ -112,7 +110,7 @@ def _forecast_dict( wind_speed = distance_convert(wind_speed, LENGTH_MILES, LENGTH_KILOMETERS) data = { - ATTR_FORECAST_TIME: time, + ATTR_FORECAST_TIME: forecast_dt.isoformat(), ATTR_FORECAST_CONDITION: translated_condition, ATTR_FORECAST_PRECIPITATION: precipitation, ATTR_FORECAST_PRECIPITATION_PROBABILITY: precipitation_probability, @@ -246,7 +244,9 @@ class ClimaCellWeatherEntity(ClimaCellEntity, WeatherEntity): # Set default values (in cases where keys don't exist), None will be # returned. Override properties per forecast type as needed for forecast in self.coordinator.data[FORECASTS][self.forecast_type]: - timestamp = self._get_cc_value(forecast, CC_ATTR_TIMESTAMP) + forecast_dt = dt_util.parse_datetime( + self._get_cc_value(forecast, CC_ATTR_TIMESTAMP) + ) use_datetime = True condition = self._get_cc_value(forecast, CC_ATTR_CONDITION) precipitation = self._get_cc_value(forecast, CC_ATTR_PRECIPITATION) @@ -290,7 +290,7 @@ class ClimaCellWeatherEntity(ClimaCellEntity, WeatherEntity): forecasts.append( _forecast_dict( self.hass, - timestamp, + forecast_dt, use_datetime, condition, precipitation, From e443597b46d39d16ceccfd319b0574cad5723782 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 2 Mar 2021 17:09:50 -0500 Subject: [PATCH 135/831] Bump zwave-js-server-python to 0.20.1 (#47289) --- homeassistant/components/zwave_js/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index 9e57a3b72e2..c812515a179 100644 --- a/homeassistant/components/zwave_js/manifest.json +++ b/homeassistant/components/zwave_js/manifest.json @@ -3,7 +3,7 @@ "name": "Z-Wave JS", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zwave_js", - "requirements": ["zwave-js-server-python==0.20.0"], + "requirements": ["zwave-js-server-python==0.20.1"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index 37c69382519..b79401f72df 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2394,4 +2394,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.20.0 +zwave-js-server-python==0.20.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9b5a7cd7651..9e32b02bf39 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1231,4 +1231,4 @@ zigpy-znp==0.4.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.20.0 +zwave-js-server-python==0.20.1 From d3721bcf2618bcdf6e7516fceb57c88dcf90a825 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 2 Mar 2021 23:22:42 +0100 Subject: [PATCH 136/831] Add zwave_js add-on manager (#47251) Co-authored-by: Paulus Schoutsen --- homeassistant/components/hassio/__init__.py | 27 ++ homeassistant/components/zwave_js/__init__.py | 101 +++++-- homeassistant/components/zwave_js/addon.py | 246 ++++++++++++++++ .../components/zwave_js/config_flow.py | 55 ++-- homeassistant/components/zwave_js/const.py | 8 + .../components/zwave_js/strings.json | 1 - .../components/zwave_js/translations/en.json | 6 - tests/components/zwave_js/conftest.py | 118 ++++++++ tests/components/zwave_js/test_config_flow.py | 127 +++------ tests/components/zwave_js/test_init.py | 263 ++++++++++++++++-- 10 files changed, 797 insertions(+), 155 deletions(-) create mode 100644 homeassistant/components/zwave_js/addon.py diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 4e9a78e75a8..c09927fa7d2 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -183,6 +183,18 @@ async def async_uninstall_addon(hass: HomeAssistantType, slug: str) -> dict: return await hassio.send_command(command, timeout=60) +@bind_hass +@api_data +async def async_update_addon(hass: HomeAssistantType, slug: str) -> dict: + """Update add-on. + + The caller of the function should handle HassioAPIError. + """ + hassio = hass.data[DOMAIN] + command = f"/addons/{slug}/update" + return await hassio.send_command(command, timeout=None) + + @bind_hass @api_data async def async_start_addon(hass: HomeAssistantType, slug: str) -> dict: @@ -232,6 +244,21 @@ async def async_get_addon_discovery_info( return next((addon for addon in discovered_addons if addon["addon"] == slug), None) +@bind_hass +@api_data +async def async_create_snapshot( + hass: HomeAssistantType, payload: dict, partial: bool = False +) -> dict: + """Create a full or partial snapshot. + + The caller of the function should handle HassioAPIError. + """ + hassio = hass.data[DOMAIN] + snapshot_type = "partial" if partial else "full" + command = f"/snapshots/new/{snapshot_type}" + return await hassio.send_command(command, payload=payload, timeout=None) + + @callback @bind_hass def get_info(hass): diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 8272bfdac2c..0c5a55c25fb 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -1,16 +1,14 @@ """The Z-Wave JS integration.""" import asyncio -import logging from typing import Callable, List from async_timeout import timeout from zwave_js_server.client import Client as ZwaveClient -from zwave_js_server.exceptions import BaseZwaveJSServerError +from zwave_js_server.exceptions import BaseZwaveJSServerError, InvalidServerVersion from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.notification import Notification from zwave_js_server.model.value import ValueNotification -from homeassistant.components.hassio.handler import HassioAPIError from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_DOMAIN, CONF_URL, EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant, callback @@ -19,9 +17,9 @@ from homeassistant.helpers import device_registry, entity_registry from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.dispatcher import async_dispatcher_send +from .addon import AddonError, AddonManager, get_addon_manager from .api import async_register_api from .const import ( - ADDON_SLUG, ATTR_COMMAND_CLASS, ATTR_COMMAND_CLASS_NAME, ATTR_DEVICE_ID, @@ -38,10 +36,14 @@ from .const import ( ATTR_VALUE, ATTR_VALUE_RAW, CONF_INTEGRATION_CREATED_ADDON, + CONF_NETWORK_KEY, + CONF_USB_PATH, + CONF_USE_ADDON, DATA_CLIENT, DATA_UNSUBSCRIBE, DOMAIN, EVENT_DEVICE_ADDED_TO_REGISTRY, + LOGGER, PLATFORMS, ZWAVE_JS_EVENT, ) @@ -49,10 +51,11 @@ from .discovery import async_discover_values from .helpers import get_device_id, get_old_value_id, get_unique_id from .services import ZWaveServices -LOGGER = logging.getLogger(__package__) CONNECT_TIMEOUT = 10 DATA_CLIENT_LISTEN_TASK = "client_listen_task" DATA_START_PLATFORM_TASK = "start_platform_task" +DATA_CONNECT_FAILED_LOGGED = "connect_failed_logged" +DATA_INVALID_SERVER_VERSION_LOGGED = "invalid_server_version_logged" async def async_setup(hass: HomeAssistant, config: dict) -> bool: @@ -87,6 +90,10 @@ def register_node_in_dev_reg( async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Z-Wave JS from a config entry.""" + use_addon = entry.data.get(CONF_USE_ADDON) + if use_addon: + await async_ensure_addon_running(hass, entry) + client = ZwaveClient(entry.data[CONF_URL], async_get_clientsession(hass)) dev_reg = await device_registry.async_get_registry(hass) ent_reg = entity_registry.async_get(hass) @@ -257,21 +264,31 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: }, ) + entry_hass_data: dict = hass.data[DOMAIN].setdefault(entry.entry_id, {}) # connect and throw error if connection failed try: async with timeout(CONNECT_TIMEOUT): await client.connect() + except InvalidServerVersion as err: + if not entry_hass_data.get(DATA_INVALID_SERVER_VERSION_LOGGED): + LOGGER.error("Invalid server version: %s", err) + entry_hass_data[DATA_INVALID_SERVER_VERSION_LOGGED] = True + if use_addon: + async_ensure_addon_updated(hass) + raise ConfigEntryNotReady from err except (asyncio.TimeoutError, BaseZwaveJSServerError) as err: - LOGGER.error("Failed to connect: %s", err) + if not entry_hass_data.get(DATA_CONNECT_FAILED_LOGGED): + LOGGER.error("Failed to connect: %s", err) + entry_hass_data[DATA_CONNECT_FAILED_LOGGED] = True raise ConfigEntryNotReady from err else: LOGGER.info("Connected to Zwave JS Server") + entry_hass_data[DATA_CONNECT_FAILED_LOGGED] = False + entry_hass_data[DATA_INVALID_SERVER_VERSION_LOGGED] = False unsubscribe_callbacks: List[Callable] = [] - hass.data[DOMAIN][entry.entry_id] = { - DATA_CLIENT: client, - DATA_UNSUBSCRIBE: unsubscribe_callbacks, - } + entry_hass_data[DATA_CLIENT] = client + entry_hass_data[DATA_UNSUBSCRIBE] = unsubscribe_callbacks services = ZWaveServices(hass, ent_reg) services.async_register() @@ -298,7 +315,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: listen_task = asyncio.create_task( client_listen(hass, entry, client, driver_ready) ) - hass.data[DOMAIN][entry.entry_id][DATA_CLIENT_LISTEN_TASK] = listen_task + entry_hass_data[DATA_CLIENT_LISTEN_TASK] = listen_task unsubscribe_callbacks.append( hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, handle_ha_shutdown) ) @@ -340,7 +357,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) platform_task = hass.async_create_task(start_platforms()) - hass.data[DOMAIN][entry.entry_id][DATA_START_PLATFORM_TASK] = platform_task + entry_hass_data[DATA_START_PLATFORM_TASK] = platform_task return True @@ -416,6 +433,15 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: platform_task=info[DATA_START_PLATFORM_TASK], ) + if entry.data.get(CONF_USE_ADDON) and entry.disabled_by: + addon_manager: AddonManager = get_addon_manager(hass) + LOGGER.debug("Stopping Z-Wave JS add-on") + try: + await addon_manager.async_stop_addon() + except AddonError as err: + LOGGER.error("Failed to stop the Z-Wave JS add-on: %s", err) + return False + return True @@ -424,12 +450,51 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: if not entry.data.get(CONF_INTEGRATION_CREATED_ADDON): return + addon_manager: AddonManager = get_addon_manager(hass) try: - await hass.components.hassio.async_stop_addon(ADDON_SLUG) - except HassioAPIError as err: - LOGGER.error("Failed to stop the Z-Wave JS add-on: %s", err) + await addon_manager.async_stop_addon() + except AddonError as err: + LOGGER.error(err) return try: - await hass.components.hassio.async_uninstall_addon(ADDON_SLUG) - except HassioAPIError as err: - LOGGER.error("Failed to uninstall the Z-Wave JS add-on: %s", err) + await addon_manager.async_create_snapshot() + except AddonError as err: + LOGGER.error(err) + return + try: + await addon_manager.async_uninstall_addon() + except AddonError as err: + LOGGER.error(err) + + +async def async_ensure_addon_running(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Ensure that Z-Wave JS add-on is installed and running.""" + addon_manager: AddonManager = get_addon_manager(hass) + if addon_manager.task_in_progress(): + raise ConfigEntryNotReady + try: + addon_is_installed = await addon_manager.async_is_addon_installed() + addon_is_running = await addon_manager.async_is_addon_running() + except AddonError as err: + LOGGER.error("Failed to get the Z-Wave JS add-on info") + raise ConfigEntryNotReady from err + + usb_path: str = entry.data[CONF_USB_PATH] + network_key: str = entry.data[CONF_NETWORK_KEY] + + if not addon_is_installed: + addon_manager.async_schedule_install_addon(usb_path, network_key) + raise ConfigEntryNotReady + + if not addon_is_running: + addon_manager.async_schedule_setup_addon(usb_path, network_key) + raise ConfigEntryNotReady + + +@callback +def async_ensure_addon_updated(hass: HomeAssistant) -> None: + """Ensure that Z-Wave JS add-on is updated and running.""" + addon_manager: AddonManager = get_addon_manager(hass) + if addon_manager.task_in_progress(): + raise ConfigEntryNotReady + addon_manager.async_schedule_update_addon() diff --git a/homeassistant/components/zwave_js/addon.py b/homeassistant/components/zwave_js/addon.py new file mode 100644 index 00000000000..54169dcaf94 --- /dev/null +++ b/homeassistant/components/zwave_js/addon.py @@ -0,0 +1,246 @@ +"""Provide add-on management.""" +from __future__ import annotations + +import asyncio +from functools import partial +from typing import Any, Callable, Optional, TypeVar, cast + +from homeassistant.components.hassio import ( + async_create_snapshot, + async_get_addon_discovery_info, + async_get_addon_info, + async_install_addon, + async_set_addon_options, + async_start_addon, + async_stop_addon, + async_uninstall_addon, + async_update_addon, +) +from homeassistant.components.hassio.handler import HassioAPIError +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.singleton import singleton + +from .const import ADDON_SLUG, CONF_ADDON_DEVICE, CONF_ADDON_NETWORK_KEY, DOMAIN, LOGGER + +F = TypeVar("F", bound=Callable[..., Any]) # pylint: disable=invalid-name + +DATA_ADDON_MANAGER = f"{DOMAIN}_addon_manager" + + +@singleton(DATA_ADDON_MANAGER) +@callback +def get_addon_manager(hass: HomeAssistant) -> AddonManager: + """Get the add-on manager.""" + return AddonManager(hass) + + +def api_error(error_message: str) -> Callable[[F], F]: + """Handle HassioAPIError and raise a specific AddonError.""" + + def handle_hassio_api_error(func: F) -> F: + """Handle a HassioAPIError.""" + + async def wrapper(*args, **kwargs): # type: ignore + """Wrap an add-on manager method.""" + try: + return_value = await func(*args, **kwargs) + except HassioAPIError as err: + raise AddonError(error_message) from err + + return return_value + + return cast(F, wrapper) + + return handle_hassio_api_error + + +class AddonManager: + """Manage the add-on. + + Methods may raise AddonError. + Only one instance of this class may exist + to keep track of running add-on tasks. + """ + + def __init__(self, hass: HomeAssistant) -> None: + """Set up the add-on manager.""" + self._hass = hass + self._install_task: Optional[asyncio.Task] = None + self._update_task: Optional[asyncio.Task] = None + self._setup_task: Optional[asyncio.Task] = None + + def task_in_progress(self) -> bool: + """Return True if any of the add-on tasks are in progress.""" + return any( + task and not task.done() + for task in ( + self._install_task, + self._setup_task, + self._update_task, + ) + ) + + @api_error("Failed to get Z-Wave JS add-on discovery info") + async def async_get_addon_discovery_info(self) -> dict: + """Return add-on discovery info.""" + discovery_info = await async_get_addon_discovery_info(self._hass, ADDON_SLUG) + + if not discovery_info: + raise AddonError("Failed to get Z-Wave JS add-on discovery info") + + discovery_info_config: dict = discovery_info["config"] + return discovery_info_config + + @api_error("Failed to get the Z-Wave JS add-on info") + async def async_get_addon_info(self) -> dict: + """Return and cache Z-Wave JS add-on info.""" + addon_info: dict = await async_get_addon_info(self._hass, ADDON_SLUG) + return addon_info + + async def async_is_addon_running(self) -> bool: + """Return True if Z-Wave JS add-on is running.""" + addon_info = await self.async_get_addon_info() + return bool(addon_info["state"] == "started") + + async def async_is_addon_installed(self) -> bool: + """Return True if Z-Wave JS add-on is installed.""" + addon_info = await self.async_get_addon_info() + return addon_info["version"] is not None + + async def async_get_addon_options(self) -> dict: + """Get Z-Wave JS add-on options.""" + addon_info = await self.async_get_addon_info() + return cast(dict, addon_info["options"]) + + @api_error("Failed to set the Z-Wave JS add-on options") + async def async_set_addon_options(self, config: dict) -> None: + """Set Z-Wave JS add-on options.""" + options = {"options": config} + await async_set_addon_options(self._hass, ADDON_SLUG, options) + + @api_error("Failed to install the Z-Wave JS add-on") + async def async_install_addon(self) -> None: + """Install the Z-Wave JS add-on.""" + await async_install_addon(self._hass, ADDON_SLUG) + + @callback + def async_schedule_install_addon( + self, usb_path: str, network_key: str + ) -> asyncio.Task: + """Schedule a task that installs and sets up the Z-Wave JS add-on. + + Only schedule a new install task if the there's no running task. + """ + if not self._install_task or self._install_task.done(): + LOGGER.info("Z-Wave JS add-on is not installed. Installing add-on") + self._install_task = self._async_schedule_addon_operation( + self.async_install_addon, + partial(self.async_setup_addon, usb_path, network_key), + ) + return self._install_task + + @api_error("Failed to uninstall the Z-Wave JS add-on") + async def async_uninstall_addon(self) -> None: + """Uninstall the Z-Wave JS add-on.""" + await async_uninstall_addon(self._hass, ADDON_SLUG) + + @api_error("Failed to update the Z-Wave JS add-on") + async def async_update_addon(self) -> None: + """Update the Z-Wave JS add-on if needed.""" + addon_info = await self.async_get_addon_info() + addon_version = addon_info["version"] + update_available = addon_info["update_available"] + + if addon_version is None: + raise AddonError("Z-Wave JS add-on is not installed") + + if not update_available: + return + + await async_update_addon(self._hass, ADDON_SLUG) + + @callback + def async_schedule_update_addon(self) -> asyncio.Task: + """Schedule a task that updates and sets up the Z-Wave JS add-on. + + Only schedule a new update task if the there's no running task. + """ + if not self._update_task or self._update_task.done(): + LOGGER.info("Trying to update the Z-Wave JS add-on") + self._update_task = self._async_schedule_addon_operation( + self.async_create_snapshot, self.async_update_addon + ) + return self._update_task + + @api_error("Failed to start the Z-Wave JS add-on") + async def async_start_addon(self) -> None: + """Start the Z-Wave JS add-on.""" + await async_start_addon(self._hass, ADDON_SLUG) + + @api_error("Failed to stop the Z-Wave JS add-on") + async def async_stop_addon(self) -> None: + """Stop the Z-Wave JS add-on.""" + await async_stop_addon(self._hass, ADDON_SLUG) + + async def async_setup_addon(self, usb_path: str, network_key: str) -> None: + """Configure and start Z-Wave JS add-on.""" + addon_options = await self.async_get_addon_options() + + new_addon_options = { + CONF_ADDON_DEVICE: usb_path, + CONF_ADDON_NETWORK_KEY: network_key, + } + + if new_addon_options != addon_options: + await self.async_set_addon_options(new_addon_options) + + await self.async_start_addon() + + @callback + def async_schedule_setup_addon( + self, usb_path: str, network_key: str + ) -> asyncio.Task: + """Schedule a task that configures and starts the Z-Wave JS add-on. + + Only schedule a new setup task if the there's no running task. + """ + if not self._setup_task or self._setup_task.done(): + LOGGER.info("Z-Wave JS add-on is not running. Starting add-on") + self._setup_task = self._async_schedule_addon_operation( + partial(self.async_setup_addon, usb_path, network_key) + ) + return self._setup_task + + @api_error("Failed to create a snapshot of the Z-Wave JS add-on.") + async def async_create_snapshot(self) -> None: + """Create a partial snapshot of the Z-Wave JS add-on.""" + addon_info = await self.async_get_addon_info() + addon_version = addon_info["version"] + name = f"addon_{ADDON_SLUG}_{addon_version}" + + LOGGER.debug("Creating snapshot: %s", name) + await async_create_snapshot( + self._hass, + {"name": name, "addons": [ADDON_SLUG]}, + partial=True, + ) + + @callback + def _async_schedule_addon_operation(self, *funcs: Callable) -> asyncio.Task: + """Schedule an add-on task.""" + + async def addon_operation() -> None: + """Do the add-on operation and catch AddonError.""" + for func in funcs: + try: + await func() + except AddonError as err: + LOGGER.error(err) + break + + return self._hass.async_create_task(addon_operation()) + + +class AddonError(HomeAssistantError): + """Represent an error with Z-Wave JS add-on.""" diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index cc19fb85d3a..37923c574b4 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -9,33 +9,25 @@ import voluptuous as vol from zwave_js_server.version import VersionInfo, get_server_version from homeassistant import config_entries, exceptions -from homeassistant.components.hassio import ( - async_get_addon_discovery_info, - async_get_addon_info, - async_install_addon, - async_set_addon_options, - async_start_addon, - is_hassio, -) -from homeassistant.components.hassio.handler import HassioAPIError +from homeassistant.components.hassio import is_hassio from homeassistant.const import CONF_URL from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers.aiohttp_client import async_get_clientsession +from .addon import AddonError, AddonManager, get_addon_manager from .const import ( # pylint:disable=unused-import - ADDON_SLUG, + CONF_ADDON_DEVICE, + CONF_ADDON_NETWORK_KEY, CONF_INTEGRATION_CREATED_ADDON, + CONF_NETWORK_KEY, + CONF_USB_PATH, CONF_USE_ADDON, DOMAIN, ) _LOGGER = logging.getLogger(__name__) -CONF_ADDON_DEVICE = "device" -CONF_ADDON_NETWORK_KEY = "network_key" -CONF_NETWORK_KEY = "network_key" -CONF_USB_PATH = "usb_path" DEFAULT_URL = "ws://localhost:3000" TITLE = "Z-Wave JS" @@ -180,6 +172,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self, user_input: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Handle logic when on Supervisor host.""" + # Only one entry with Supervisor add-on support is allowed. + for entry in self.hass.config_entries.async_entries(DOMAIN): + if entry.data.get(CONF_USE_ADDON): + return await self.async_step_manual() + if user_input is None: return self.async_show_form( step_id="on_supervisor", data_schema=ON_SUPERVISOR_SCHEMA @@ -212,7 +209,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): try: await self.install_task - except HassioAPIError as err: + except AddonError as err: _LOGGER.error("Failed to install Z-Wave JS add-on: %s", err) return self.async_show_progress_done(next_step_id="install_failed") @@ -275,7 +272,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): try: await self.start_task - except (CannotConnect, HassioAPIError) as err: + except (CannotConnect, AddonError) as err: _LOGGER.error("Failed to start Z-Wave JS add-on: %s", err) return self.async_show_progress_done(next_step_id="start_failed") @@ -290,8 +287,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _async_start_addon(self) -> None: """Start the Z-Wave JS add-on.""" assert self.hass + addon_manager: AddonManager = get_addon_manager(self.hass) try: - await async_start_addon(self.hass, ADDON_SLUG) + await addon_manager.async_start_addon() # Sleep some seconds to let the add-on start properly before connecting. for _ in range(ADDON_SETUP_TIMEOUT_ROUNDS): await asyncio.sleep(ADDON_SETUP_TIMEOUT) @@ -345,9 +343,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _async_get_addon_info(self) -> dict: """Return and cache Z-Wave JS add-on info.""" + addon_manager: AddonManager = get_addon_manager(self.hass) try: - addon_info: dict = await async_get_addon_info(self.hass, ADDON_SLUG) - except HassioAPIError as err: + addon_info: dict = await addon_manager.async_get_addon_info() + except AddonError as err: _LOGGER.error("Failed to get Z-Wave JS add-on info: %s", err) raise AbortFlow("addon_info_failed") from err @@ -371,16 +370,18 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _async_set_addon_config(self, config: dict) -> None: """Set Z-Wave JS add-on config.""" options = {"options": config} + addon_manager: AddonManager = get_addon_manager(self.hass) try: - await async_set_addon_options(self.hass, ADDON_SLUG, options) - except HassioAPIError as err: + await addon_manager.async_set_addon_options(options) + except AddonError as err: _LOGGER.error("Failed to set Z-Wave JS add-on config: %s", err) raise AbortFlow("addon_set_config_failed") from err async def _async_install_addon(self) -> None: """Install the Z-Wave JS add-on.""" + addon_manager: AddonManager = get_addon_manager(self.hass) try: - await async_install_addon(self.hass, ADDON_SLUG) + await addon_manager.async_install_addon() finally: # Continue the flow after show progress when the task is done. self.hass.async_create_task( @@ -389,17 +390,13 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _async_get_addon_discovery_info(self) -> dict: """Return add-on discovery info.""" + addon_manager: AddonManager = get_addon_manager(self.hass) try: - discovery_info = await async_get_addon_discovery_info(self.hass, ADDON_SLUG) - except HassioAPIError as err: + discovery_info_config = await addon_manager.async_get_addon_discovery_info() + except AddonError as err: _LOGGER.error("Failed to get Z-Wave JS add-on discovery info: %s", err) raise AbortFlow("addon_get_discovery_info_failed") from err - if not discovery_info: - _LOGGER.error("Failed to get Z-Wave JS add-on discovery info") - raise AbortFlow("addon_missing_discovery_info") - - discovery_info_config: dict = discovery_info["config"] return discovery_info_config diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index 27be45c43a0..ffd6031349a 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -1,5 +1,11 @@ """Constants for the Z-Wave JS integration.""" +import logging + +CONF_ADDON_DEVICE = "device" +CONF_ADDON_NETWORK_KEY = "network_key" CONF_INTEGRATION_CREATED_ADDON = "integration_created_addon" +CONF_NETWORK_KEY = "network_key" +CONF_USB_PATH = "usb_path" CONF_USE_ADDON = "use_addon" DOMAIN = "zwave_js" PLATFORMS = [ @@ -19,6 +25,8 @@ DATA_UNSUBSCRIBE = "unsubs" EVENT_DEVICE_ADDED_TO_REGISTRY = f"{DOMAIN}_device_added_to_registry" +LOGGER = logging.getLogger(__package__) + # constants for events ZWAVE_JS_EVENT = f"{DOMAIN}_event" ATTR_NODE_ID = "node_id" diff --git a/homeassistant/components/zwave_js/strings.json b/homeassistant/components/zwave_js/strings.json index 5d3aa730a7c..eb13ad512e3 100644 --- a/homeassistant/components/zwave_js/strings.json +++ b/homeassistant/components/zwave_js/strings.json @@ -41,7 +41,6 @@ "addon_set_config_failed": "Failed to set Z-Wave JS configuration.", "addon_start_failed": "Failed to start the Z-Wave JS add-on.", "addon_get_discovery_info_failed": "Failed to get Z-Wave JS add-on discovery info.", - "addon_missing_discovery_info": "Missing Z-Wave JS add-on discovery info.", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "progress": { diff --git a/homeassistant/components/zwave_js/translations/en.json b/homeassistant/components/zwave_js/translations/en.json index 5be980d52cb..101942dc717 100644 --- a/homeassistant/components/zwave_js/translations/en.json +++ b/homeassistant/components/zwave_js/translations/en.json @@ -4,7 +4,6 @@ "addon_get_discovery_info_failed": "Failed to get Z-Wave JS add-on discovery info.", "addon_info_failed": "Failed to get Z-Wave JS add-on info.", "addon_install_failed": "Failed to install the Z-Wave JS add-on.", - "addon_missing_discovery_info": "Missing Z-Wave JS add-on discovery info.", "addon_set_config_failed": "Failed to set Z-Wave JS configuration.", "addon_start_failed": "Failed to start the Z-Wave JS add-on.", "already_configured": "Device is already configured", @@ -49,11 +48,6 @@ }, "start_addon": { "title": "The Z-Wave JS add-on is starting." - }, - "user": { - "data": { - "url": "URL" - } } } }, diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 72835fb17c1..50cacd97422 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -14,6 +14,124 @@ from homeassistant.helpers.device_registry import async_get as async_get_device_ from tests.common import MockConfigEntry, load_fixture +# Add-on fixtures + + +@pytest.fixture(name="addon_info_side_effect") +def addon_info_side_effect_fixture(): + """Return the add-on info side effect.""" + return None + + +@pytest.fixture(name="addon_info") +def mock_addon_info(addon_info_side_effect): + """Mock Supervisor add-on info.""" + with patch( + "homeassistant.components.zwave_js.addon.async_get_addon_info", + side_effect=addon_info_side_effect, + ) as addon_info: + addon_info.return_value = {} + yield addon_info + + +@pytest.fixture(name="addon_running") +def mock_addon_running(addon_info): + """Mock add-on already running.""" + addon_info.return_value["state"] = "started" + return addon_info + + +@pytest.fixture(name="addon_installed") +def mock_addon_installed(addon_info): + """Mock add-on already installed but not running.""" + addon_info.return_value["state"] = "stopped" + addon_info.return_value["version"] = "1.0" + return addon_info + + +@pytest.fixture(name="addon_options") +def mock_addon_options(addon_info): + """Mock add-on options.""" + addon_info.return_value["options"] = {} + return addon_info.return_value["options"] + + +@pytest.fixture(name="set_addon_options_side_effect") +def set_addon_options_side_effect_fixture(): + """Return the set add-on options side effect.""" + return None + + +@pytest.fixture(name="set_addon_options") +def mock_set_addon_options(set_addon_options_side_effect): + """Mock set add-on options.""" + with patch( + "homeassistant.components.zwave_js.addon.async_set_addon_options", + side_effect=set_addon_options_side_effect, + ) as set_options: + yield set_options + + +@pytest.fixture(name="install_addon") +def mock_install_addon(): + """Mock install add-on.""" + with patch( + "homeassistant.components.zwave_js.addon.async_install_addon" + ) as install_addon: + yield install_addon + + +@pytest.fixture(name="update_addon") +def mock_update_addon(): + """Mock update add-on.""" + with patch( + "homeassistant.components.zwave_js.addon.async_update_addon" + ) as update_addon: + yield update_addon + + +@pytest.fixture(name="start_addon_side_effect") +def start_addon_side_effect_fixture(): + """Return the set add-on options side effect.""" + return None + + +@pytest.fixture(name="start_addon") +def mock_start_addon(start_addon_side_effect): + """Mock start add-on.""" + with patch( + "homeassistant.components.zwave_js.addon.async_start_addon", + side_effect=start_addon_side_effect, + ) as start_addon: + yield start_addon + + +@pytest.fixture(name="stop_addon") +def stop_addon_fixture(): + """Mock stop add-on.""" + with patch( + "homeassistant.components.zwave_js.addon.async_stop_addon" + ) as stop_addon: + yield stop_addon + + +@pytest.fixture(name="uninstall_addon") +def uninstall_addon_fixture(): + """Mock uninstall add-on.""" + with patch( + "homeassistant.components.zwave_js.addon.async_uninstall_addon" + ) as uninstall_addon: + yield uninstall_addon + + +@pytest.fixture(name="create_shapshot") +def create_snapshot_fixture(): + """Mock create snapshot.""" + with patch( + "homeassistant.components.zwave_js.addon.async_create_snapshot" + ) as create_shapshot: + yield create_shapshot + @pytest.fixture(name="device_registry") async def device_registry_fixture(hass): diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index 08b0ffe3080..fc97f7420cf 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -44,93 +44,13 @@ def discovery_info_side_effect_fixture(): def mock_get_addon_discovery_info(discovery_info, discovery_info_side_effect): """Mock get add-on discovery info.""" with patch( - "homeassistant.components.zwave_js.config_flow.async_get_addon_discovery_info", + "homeassistant.components.zwave_js.addon.async_get_addon_discovery_info", side_effect=discovery_info_side_effect, return_value=discovery_info, ) as get_addon_discovery_info: yield get_addon_discovery_info -@pytest.fixture(name="addon_info_side_effect") -def addon_info_side_effect_fixture(): - """Return the add-on info side effect.""" - return None - - -@pytest.fixture(name="addon_info") -def mock_addon_info(addon_info_side_effect): - """Mock Supervisor add-on info.""" - with patch( - "homeassistant.components.zwave_js.config_flow.async_get_addon_info", - side_effect=addon_info_side_effect, - ) as addon_info: - addon_info.return_value = {} - yield addon_info - - -@pytest.fixture(name="addon_running") -def mock_addon_running(addon_info): - """Mock add-on already running.""" - addon_info.return_value["state"] = "started" - return addon_info - - -@pytest.fixture(name="addon_installed") -def mock_addon_installed(addon_info): - """Mock add-on already installed but not running.""" - addon_info.return_value["state"] = "stopped" - addon_info.return_value["version"] = "1.0" - return addon_info - - -@pytest.fixture(name="addon_options") -def mock_addon_options(addon_info): - """Mock add-on options.""" - addon_info.return_value["options"] = {} - return addon_info.return_value["options"] - - -@pytest.fixture(name="set_addon_options_side_effect") -def set_addon_options_side_effect_fixture(): - """Return the set add-on options side effect.""" - return None - - -@pytest.fixture(name="set_addon_options") -def mock_set_addon_options(set_addon_options_side_effect): - """Mock set add-on options.""" - with patch( - "homeassistant.components.zwave_js.config_flow.async_set_addon_options", - side_effect=set_addon_options_side_effect, - ) as set_options: - yield set_options - - -@pytest.fixture(name="install_addon") -def mock_install_addon(): - """Mock install add-on.""" - with patch( - "homeassistant.components.zwave_js.config_flow.async_install_addon" - ) as install_addon: - yield install_addon - - -@pytest.fixture(name="start_addon_side_effect") -def start_addon_side_effect_fixture(): - """Return the set add-on options side effect.""" - return None - - -@pytest.fixture(name="start_addon") -def mock_start_addon(start_addon_side_effect): - """Mock start add-on.""" - with patch( - "homeassistant.components.zwave_js.config_flow.async_start_addon", - side_effect=start_addon_side_effect, - ) as start_addon: - yield start_addon - - @pytest.fixture(name="server_version_side_effect") def server_version_side_effect_fixture(): """Return the server version side effect.""" @@ -587,6 +507,49 @@ async def test_not_addon(hass, supervisor): assert len(mock_setup_entry.mock_calls) == 1 +async def test_addon_already_configured(hass, supervisor): + """Test add-on already configured leads to manual step.""" + entry = MockConfigEntry( + domain=DOMAIN, data={"use_addon": True}, title=TITLE, unique_id=5678 + ) + entry.add_to_hass(hass) + + await setup.async_setup_component(hass, "persistent_notification", {}) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "manual" + + with patch( + "homeassistant.components.zwave_js.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.zwave_js.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "url": "ws://localhost:3000", + }, + ) + await hass.async_block_till_done() + + assert result["type"] == "create_entry" + assert result["title"] == TITLE + assert result["data"] == { + "url": "ws://localhost:3000", + "usb_path": None, + "network_key": None, + "use_addon": False, + "integration_created_addon": False, + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 2 + + @pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) async def test_addon_running( hass, @@ -654,7 +617,7 @@ async def test_addon_running( None, None, None, - "addon_missing_discovery_info", + "addon_get_discovery_info_failed", ), ( {"config": ADDON_DISCOVERY_INFO}, diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 2a2f249c361..6f60bbc0300 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -1,9 +1,9 @@ """Test the Z-Wave JS init module.""" from copy import deepcopy -from unittest.mock import patch +from unittest.mock import call, patch import pytest -from zwave_js_server.exceptions import BaseZwaveJSServerError +from zwave_js_server.exceptions import BaseZwaveJSServerError, InvalidServerVersion from zwave_js_server.model.node import Node from homeassistant.components.hassio.handler import HassioAPIError @@ -11,6 +11,7 @@ from homeassistant.components.zwave_js.const import DOMAIN from homeassistant.components.zwave_js.helpers import get_device_id from homeassistant.config_entries import ( CONN_CLASS_LOCAL_PUSH, + DISABLED_USER, ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED, ENTRY_STATE_SETUP_RETRY, @@ -34,22 +35,6 @@ def connect_timeout_fixture(): yield timeout -@pytest.fixture(name="stop_addon") -def stop_addon_fixture(): - """Mock stop add-on.""" - with patch("homeassistant.components.hassio.async_stop_addon") as stop_addon: - yield stop_addon - - -@pytest.fixture(name="uninstall_addon") -def uninstall_addon_fixture(): - """Mock uninstall add-on.""" - with patch( - "homeassistant.components.hassio.async_uninstall_addon" - ) as uninstall_addon: - yield uninstall_addon - - async def test_entry_setup_unload(hass, client, integration): """Test the integration set up and unload.""" entry = integration @@ -367,7 +352,203 @@ async def test_existing_node_not_ready(hass, client, multisensor_6, device_regis ) -async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog): +async def test_start_addon( + hass, addon_installed, install_addon, addon_options, set_addon_options, start_addon +): + """Test start the Z-Wave JS add-on during entry setup.""" + device = "/test" + network_key = "abc123" + addon_options = { + "device": device, + "network_key": network_key, + } + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave JS", + connection_class=CONN_CLASS_LOCAL_PUSH, + data={"use_addon": True, "usb_path": device, "network_key": network_key}, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_SETUP_RETRY + assert install_addon.call_count == 0 + assert set_addon_options.call_count == 1 + assert set_addon_options.call_args == call( + hass, "core_zwave_js", {"options": addon_options} + ) + assert start_addon.call_count == 1 + assert start_addon.call_args == call(hass, "core_zwave_js") + + +async def test_install_addon( + hass, addon_installed, install_addon, addon_options, set_addon_options, start_addon +): + """Test install and start the Z-Wave JS add-on during entry setup.""" + addon_installed.return_value["version"] = None + device = "/test" + network_key = "abc123" + addon_options = { + "device": device, + "network_key": network_key, + } + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave JS", + connection_class=CONN_CLASS_LOCAL_PUSH, + data={"use_addon": True, "usb_path": device, "network_key": network_key}, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_SETUP_RETRY + assert install_addon.call_count == 1 + assert install_addon.call_args == call(hass, "core_zwave_js") + assert set_addon_options.call_count == 1 + assert set_addon_options.call_args == call( + hass, "core_zwave_js", {"options": addon_options} + ) + assert start_addon.call_count == 1 + assert start_addon.call_args == call(hass, "core_zwave_js") + + +@pytest.mark.parametrize("addon_info_side_effect", [HassioAPIError("Boom")]) +async def test_addon_info_failure( + hass, + addon_installed, + install_addon, + addon_options, + set_addon_options, + start_addon, +): + """Test failure to get add-on info for Z-Wave JS add-on during entry setup.""" + device = "/test" + network_key = "abc123" + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave JS", + connection_class=CONN_CLASS_LOCAL_PUSH, + data={"use_addon": True, "usb_path": device, "network_key": network_key}, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_SETUP_RETRY + assert install_addon.call_count == 0 + assert start_addon.call_count == 0 + + +@pytest.mark.parametrize( + "addon_version, update_available, update_calls, update_addon_side_effect", + [ + ("1.0", True, 1, None), + ("1.0", False, 0, None), + ("1.0", True, 1, HassioAPIError("Boom")), + ], +) +async def test_update_addon( + hass, + client, + addon_info, + addon_installed, + addon_running, + create_shapshot, + update_addon, + addon_options, + addon_version, + update_available, + update_calls, + update_addon_side_effect, +): + """Test update the Z-Wave JS add-on during entry setup.""" + addon_info.return_value["version"] = addon_version + addon_info.return_value["update_available"] = update_available + update_addon.side_effect = update_addon_side_effect + client.connect.side_effect = InvalidServerVersion("Invalid version") + device = "/test" + network_key = "abc123" + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave JS", + connection_class=CONN_CLASS_LOCAL_PUSH, + data={ + "url": "ws://host1:3001", + "use_addon": True, + "usb_path": device, + "network_key": network_key, + }, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_SETUP_RETRY + assert create_shapshot.call_count == 1 + assert create_shapshot.call_args == call( + hass, + {"name": f"addon_core_zwave_js_{addon_version}", "addons": ["core_zwave_js"]}, + partial=True, + ) + assert update_addon.call_count == update_calls + + +@pytest.mark.parametrize( + "stop_addon_side_effect, entry_state", + [ + (None, ENTRY_STATE_NOT_LOADED), + (HassioAPIError("Boom"), ENTRY_STATE_LOADED), + ], +) +async def test_stop_addon( + hass, + client, + addon_installed, + addon_running, + addon_options, + stop_addon, + stop_addon_side_effect, + entry_state, +): + """Test stop the Z-Wave JS add-on on entry unload if entry is disabled.""" + stop_addon.side_effect = stop_addon_side_effect + device = "/test" + network_key = "abc123" + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave JS", + connection_class=CONN_CLASS_LOCAL_PUSH, + data={ + "url": "ws://host1:3001", + "use_addon": True, + "usb_path": device, + "network_key": network_key, + }, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_LOADED + + await hass.config_entries.async_set_disabled_by(entry.entry_id, DISABLED_USER) + await hass.async_block_till_done() + + assert entry.state == entry_state + assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_zwave_js") + + +async def test_remove_entry( + hass, addon_installed, stop_addon, create_shapshot, uninstall_addon, caplog +): """Test remove the config entry.""" # test successful remove without created add-on entry = MockConfigEntry( @@ -398,10 +579,19 @@ async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog): await hass.config_entries.async_remove(entry.entry_id) assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_zwave_js") + assert create_shapshot.call_count == 1 + assert create_shapshot.call_args == call( + hass, + {"name": "addon_core_zwave_js_1.0", "addons": ["core_zwave_js"]}, + partial=True, + ) assert uninstall_addon.call_count == 1 + assert uninstall_addon.call_args == call(hass, "core_zwave_js") assert entry.state == ENTRY_STATE_NOT_LOADED assert len(hass.config_entries.async_entries(DOMAIN)) == 0 stop_addon.reset_mock() + create_shapshot.reset_mock() uninstall_addon.reset_mock() # test add-on stop failure @@ -412,12 +602,39 @@ async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog): await hass.config_entries.async_remove(entry.entry_id) assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_zwave_js") + assert create_shapshot.call_count == 0 assert uninstall_addon.call_count == 0 assert entry.state == ENTRY_STATE_NOT_LOADED assert len(hass.config_entries.async_entries(DOMAIN)) == 0 assert "Failed to stop the Z-Wave JS add-on" in caplog.text stop_addon.side_effect = None stop_addon.reset_mock() + create_shapshot.reset_mock() + uninstall_addon.reset_mock() + + # test create snapshot failure + entry.add_to_hass(hass) + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + create_shapshot.side_effect = HassioAPIError() + + await hass.config_entries.async_remove(entry.entry_id) + + assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_zwave_js") + assert create_shapshot.call_count == 1 + assert create_shapshot.call_args == call( + hass, + {"name": "addon_core_zwave_js_1.0", "addons": ["core_zwave_js"]}, + partial=True, + ) + assert uninstall_addon.call_count == 0 + assert entry.state == ENTRY_STATE_NOT_LOADED + assert len(hass.config_entries.async_entries(DOMAIN)) == 0 + assert "Failed to create a snapshot of the Z-Wave JS add-on" in caplog.text + create_shapshot.side_effect = None + stop_addon.reset_mock() + create_shapshot.reset_mock() uninstall_addon.reset_mock() # test add-on uninstall failure @@ -428,7 +645,15 @@ async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog): await hass.config_entries.async_remove(entry.entry_id) assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_zwave_js") + assert create_shapshot.call_count == 1 + assert create_shapshot.call_args == call( + hass, + {"name": "addon_core_zwave_js_1.0", "addons": ["core_zwave_js"]}, + partial=True, + ) assert uninstall_addon.call_count == 1 + assert uninstall_addon.call_args == call(hass, "core_zwave_js") assert entry.state == ENTRY_STATE_NOT_LOADED assert len(hass.config_entries.async_entries(DOMAIN)) == 0 assert "Failed to uninstall the Z-Wave JS add-on" in caplog.text From ce8871ef598d0670fbdff6288d93425a4a4846ab Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Wed, 3 Mar 2021 00:50:40 +0100 Subject: [PATCH 137/831] KNX remove custom deprecation warnings (#47238) --- homeassistant/components/knx/__init__.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 697b4cfb524..e74bae7442c 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -77,6 +77,8 @@ SERVICE_KNX_READ = "read" CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.All( + # deprecated since 2021.3 + cv.deprecated(CONF_KNX_CONFIG), # deprecated since 2021.2 cv.deprecated(CONF_KNX_FIRE_EVENT), cv.deprecated("fire_event_filter", replacement_key=CONF_KNX_EVENT_FILTER), @@ -236,21 +238,6 @@ async def async_setup(hass, config): discovery.async_load_platform(hass, platform.value, DOMAIN, {}, config) ) - if not knx_module.xknx.devices: - _LOGGER.warning( - "No KNX devices are configured. Please read " - "https://www.home-assistant.io/blog/2020/09/17/release-115/#breaking-changes" - ) - - # deprecation warning since 2021.3 - if CONF_KNX_CONFIG in config[DOMAIN]: - _LOGGER.warning( - "The 'config_file' option is deprecated. Please replace it with Home Assistant config schema " - "directly in `configuration.yaml` (see https://www.home-assistant.io/integrations/knx/). \n" - "An online configuration converter tool for your `%s` is available at https://xknx.io/config-converter/", - config[DOMAIN][CONF_KNX_CONFIG], - ) - hass.services.async_register( DOMAIN, SERVICE_KNX_SEND, From 6019bcf9d140458dba3b7eb39d99e9e2d4f2354a Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 2 Mar 2021 19:55:10 -0500 Subject: [PATCH 138/831] Correct climacell device info (#47292) --- homeassistant/components/climacell/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/climacell/__init__.py b/homeassistant/components/climacell/__init__.py index 6f1ac730f2f..c3c3f702780 100644 --- a/homeassistant/components/climacell/__init__.py +++ b/homeassistant/components/climacell/__init__.py @@ -256,7 +256,8 @@ class ClimaCellEntity(CoordinatorEntity): """Return device registry information.""" return { "identifiers": {(DOMAIN, self._config_entry.data[CONF_API_KEY])}, - "name": self.name, + "name": "ClimaCell", "manufacturer": "ClimaCell", + "sw_version": "v3", "entry_type": "service", } From 32fe4fa37854cb38ba4f64227c5a751a668cb1cc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Mar 2021 17:57:36 -1000 Subject: [PATCH 139/831] Add activity properties to remote entity model (#47237) --- homeassistant/components/harmony/const.py | 2 -- homeassistant/components/harmony/remote.py | 20 ++++++++++++---- homeassistant/components/remote/__init__.py | 26 ++++++++++++++++++++- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/harmony/const.py b/homeassistant/components/harmony/const.py index ee4a454847e..d7b4d8248ed 100644 --- a/homeassistant/components/harmony/const.py +++ b/homeassistant/components/harmony/const.py @@ -6,9 +6,7 @@ PLATFORMS = ["remote", "switch"] UNIQUE_ID = "unique_id" ACTIVITY_POWER_OFF = "PowerOff" HARMONY_OPTIONS_UPDATE = "harmony_options_update" -ATTR_ACTIVITY_LIST = "activity_list" ATTR_DEVICES_LIST = "devices_list" ATTR_LAST_ACTIVITY = "last_activity" -ATTR_CURRENT_ACTIVITY = "current_activity" ATTR_ACTIVITY_STARTING = "activity_starting" PREVIOUS_ACTIVE_ACTIVITY = "Previous Active Activity" diff --git a/homeassistant/components/harmony/remote.py b/homeassistant/components/harmony/remote.py index 9b3d53c21fa..55af62e6d39 100644 --- a/homeassistant/components/harmony/remote.py +++ b/homeassistant/components/harmony/remote.py @@ -12,6 +12,7 @@ from homeassistant.components.remote import ( ATTR_HOLD_SECS, ATTR_NUM_REPEATS, DEFAULT_DELAY_SECS, + SUPPORT_ACTIVITY, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ENTITY_ID @@ -24,9 +25,7 @@ from homeassistant.helpers.restore_state import RestoreEntity from .connection_state import ConnectionStateMixin from .const import ( ACTIVITY_POWER_OFF, - ATTR_ACTIVITY_LIST, ATTR_ACTIVITY_STARTING, - ATTR_CURRENT_ACTIVITY, ATTR_DEVICES_LIST, ATTR_LAST_ACTIVITY, DOMAIN, @@ -100,6 +99,11 @@ class HarmonyRemote(ConnectionStateMixin, remote.RemoteEntity, RestoreEntity): self._last_activity = None self._config_path = out_path + @property + def supported_features(self): + """Supported features for the remote.""" + return SUPPORT_ACTIVITY + async def _async_update_options(self, data): """Change options when the options flow does.""" if ATTR_DELAY_SECS in data: @@ -178,13 +182,21 @@ class HarmonyRemote(ConnectionStateMixin, remote.RemoteEntity, RestoreEntity): """Return the fact that we should not be polled.""" return False + @property + def current_activity(self): + """Return the current activity.""" + return self._current_activity + + @property + def activity_list(self): + """Return the available activities.""" + return self._data.activity_names + @property def device_state_attributes(self): """Add platform specific attributes.""" return { ATTR_ACTIVITY_STARTING: self._activity_starting, - ATTR_CURRENT_ACTIVITY: self._current_activity, - ATTR_ACTIVITY_LIST: self._data.activity_names, ATTR_DEVICES_LIST: self._data.device_names, ATTR_LAST_ACTIVITY: self._last_activity, } diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index b3b84669ed1..720f7c5ff16 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -2,7 +2,7 @@ from datetime import timedelta import functools as ft import logging -from typing import Any, Iterable, cast +from typing import Any, Dict, Iterable, List, Optional, cast import voluptuous as vol @@ -30,6 +30,8 @@ from homeassistant.loader import bind_hass _LOGGER = logging.getLogger(__name__) ATTR_ACTIVITY = "activity" +ATTR_ACTIVITY_LIST = "activity_list" +ATTR_CURRENT_ACTIVITY = "current_activity" ATTR_COMMAND_TYPE = "command_type" ATTR_DEVICE = "device" ATTR_NUM_REPEATS = "num_repeats" @@ -56,6 +58,7 @@ DEFAULT_HOLD_SECS = 0 SUPPORT_LEARN_COMMAND = 1 SUPPORT_DELETE_COMMAND = 2 +SUPPORT_ACTIVITY = 4 REMOTE_SERVICE_ACTIVITY_SCHEMA = make_entity_service_schema( {vol.Optional(ATTR_ACTIVITY): cv.string} @@ -143,6 +146,27 @@ class RemoteEntity(ToggleEntity): """Flag supported features.""" return 0 + @property + def current_activity(self) -> Optional[str]: + """Active activity.""" + return None + + @property + def activity_list(self) -> Optional[List[str]]: + """List of available activities.""" + return None + + @property + def state_attributes(self) -> Optional[Dict[str, Any]]: + """Return optional state attributes.""" + if not self.supported_features & SUPPORT_ACTIVITY: + return None + + return { + ATTR_ACTIVITY_LIST: self.activity_list, + ATTR_CURRENT_ACTIVITY: self.current_activity, + } + def send_command(self, command: Iterable[str], **kwargs: Any) -> None: """Send commands to a device.""" raise NotImplementedError() From 9022b909459946bb7b880629c318c4b8529fe383 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Tue, 2 Mar 2021 22:04:17 -0800 Subject: [PATCH 140/831] bump python-smarttub to 0.0.19 (#47294) --- homeassistant/components/smarttub/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/smarttub/manifest.json b/homeassistant/components/smarttub/manifest.json index 292ce81b4fb..2425268e05c 100644 --- a/homeassistant/components/smarttub/manifest.json +++ b/homeassistant/components/smarttub/manifest.json @@ -6,7 +6,7 @@ "dependencies": [], "codeowners": ["@mdz"], "requirements": [ - "python-smarttub==0.0.17" + "python-smarttub==0.0.19" ], "quality_scale": "platinum" } diff --git a/requirements_all.txt b/requirements_all.txt index b79401f72df..67b52a29bed 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1820,7 +1820,7 @@ python-qbittorrent==0.4.2 python-ripple-api==0.0.3 # homeassistant.components.smarttub -python-smarttub==0.0.17 +python-smarttub==0.0.19 # homeassistant.components.sochain python-sochain-api==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9e32b02bf39..6d9307eb52b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -945,7 +945,7 @@ python-nest==4.1.0 python-openzwave-mqtt[mqtt-client]==1.4.0 # homeassistant.components.smarttub -python-smarttub==0.0.17 +python-smarttub==0.0.19 # homeassistant.components.songpal python-songpal==0.12 From 8f3c2573e26755c6113b2efd1cecf00f51076631 Mon Sep 17 00:00:00 2001 From: Milan Meulemans Date: Wed, 3 Mar 2021 10:12:26 +0100 Subject: [PATCH 141/831] Remove name from keenetic-ndms2 strings (#47113) --- homeassistant/components/keenetic_ndms2/strings.json | 1 - homeassistant/components/keenetic_ndms2/translations/en.json | 1 - 2 files changed, 2 deletions(-) diff --git a/homeassistant/components/keenetic_ndms2/strings.json b/homeassistant/components/keenetic_ndms2/strings.json index 15629ba0f2f..0dc1c9c302f 100644 --- a/homeassistant/components/keenetic_ndms2/strings.json +++ b/homeassistant/components/keenetic_ndms2/strings.json @@ -4,7 +4,6 @@ "user": { "title": "Set up Keenetic NDMS2 Router", "data": { - "name": "Name", "host": "[%key:common::config_flow::data::host%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]", diff --git a/homeassistant/components/keenetic_ndms2/translations/en.json b/homeassistant/components/keenetic_ndms2/translations/en.json index e95f2f740ef..5a946751ff4 100644 --- a/homeassistant/components/keenetic_ndms2/translations/en.json +++ b/homeassistant/components/keenetic_ndms2/translations/en.json @@ -10,7 +10,6 @@ "user": { "data": { "host": "Host", - "name": "Name", "password": "Password", "port": "Port", "username": "Username" From b147ba137731e6e2cc5ee4e5254b8123bf1ed08d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 3 Mar 2021 10:20:48 +0100 Subject: [PATCH 142/831] Correct gogogate2 battery sensor attributes (#47302) --- homeassistant/components/gogogate2/cover.py | 6 ++---- homeassistant/components/gogogate2/sensor.py | 7 +++---- tests/components/gogogate2/test_cover.py | 18 ++++++++++++++++++ tests/components/gogogate2/test_sensor.py | 20 ++++++++++++++++++++ 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index f2e05b10599..37c362893c8 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -122,8 +122,6 @@ class DeviceCover(GoGoGate2Entity, CoverEntity): await self._api.async_close_door(self._get_door().door_id) @property - def state_attributes(self): + def device_state_attributes(self): """Return the state attributes.""" - attrs = super().state_attributes - attrs["door_id"] = self._get_door().door_id - return attrs + return {"door_id": self._get_door().door_id} diff --git a/homeassistant/components/gogogate2/sensor.py b/homeassistant/components/gogogate2/sensor.py index 55aacd2fdbb..c4f6cff9dfa 100644 --- a/homeassistant/components/gogogate2/sensor.py +++ b/homeassistant/components/gogogate2/sensor.py @@ -50,10 +50,9 @@ class DoorSensor(GoGoGate2Entity): return door.voltage # This is a percentage, not an absolute voltage @property - def state_attributes(self): + def device_state_attributes(self): """Return the state attributes.""" - attrs = super().state_attributes or {} door = self._get_door() if door.sensorid is not None: - attrs["sensorid"] = door.door_id - return attrs + return {"door_id": door.door_id, "sensor_id": door.sensorid} + return None diff --git a/tests/components/gogogate2/test_cover.py b/tests/components/gogogate2/test_cover.py index 31810cb67d0..41b4368c640 100644 --- a/tests/components/gogogate2/test_cover.py +++ b/tests/components/gogogate2/test_cover.py @@ -21,6 +21,8 @@ from homeassistant.components.cover import ( DEVICE_CLASS_GARAGE, DEVICE_CLASS_GATE, DOMAIN as COVER_DOMAIN, + SUPPORT_CLOSE, + SUPPORT_OPEN, ) from homeassistant.components.gogogate2.const import ( DEVICE_TYPE_GOGOGATE2, @@ -319,6 +321,13 @@ async def test_open_close_update(gogogate2api_mock, hass: HomeAssistant) -> None wifi=Wifi(SSID="", linkquality="", signal=""), ) + expected_attributes = { + "device_class": "garage", + "door_id": 1, + "friendly_name": "Door1", + "supported_features": SUPPORT_CLOSE | SUPPORT_OPEN, + } + api = MagicMock(GogoGate2Api) api.async_activate.return_value = GogoGate2ActivateResponse(result=True) api.async_info.return_value = info_response(DoorStatus.OPENED) @@ -339,6 +348,7 @@ async def test_open_close_update(gogogate2api_mock, hass: HomeAssistant) -> None assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert hass.states.get("cover.door1").state == STATE_OPEN + assert dict(hass.states.get("cover.door1").attributes) == expected_attributes api.async_info.return_value = info_response(DoorStatus.CLOSED) await hass.services.async_call( @@ -376,6 +386,13 @@ async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None: """Test availability.""" closed_door_response = _mocked_ismartgate_closed_door_response() + expected_attributes = { + "device_class": "garage", + "door_id": 1, + "friendly_name": "Door1", + "supported_features": SUPPORT_CLOSE | SUPPORT_OPEN, + } + api = MagicMock(ISmartGateApi) api.async_info.return_value = closed_door_response ismartgateapi_mock.return_value = api @@ -416,6 +433,7 @@ async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None: async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) await hass.async_block_till_done() assert hass.states.get("cover.door1").state == STATE_CLOSED + assert dict(hass.states.get("cover.door1").attributes) == expected_attributes @patch("homeassistant.components.gogogate2.common.ISmartGateApi") diff --git a/tests/components/gogogate2/test_sensor.py b/tests/components/gogogate2/test_sensor.py index 0bd67dfc92a..f8eb9bf88b7 100644 --- a/tests/components/gogogate2/test_sensor.py +++ b/tests/components/gogogate2/test_sensor.py @@ -164,6 +164,13 @@ def _mocked_ismartgate_sensor_response(battery_level: int): async def test_sensor_update(gogogate2api_mock, hass: HomeAssistant) -> None: """Test data update.""" + expected_attributes = { + "device_class": "battery", + "door_id": 1, + "friendly_name": "Door1 battery", + "sensor_id": "ABCD", + } + api = MagicMock(GogoGate2Api) api.async_activate.return_value = GogoGate2ActivateResponse(result=True) api.async_info.return_value = _mocked_gogogate_sensor_response(25) @@ -192,6 +199,9 @@ async def test_sensor_update(gogogate2api_mock, hass: HomeAssistant) -> None: assert hass.states.get("cover.door2") assert hass.states.get("cover.door2") assert hass.states.get("sensor.door1_battery").state == "25" + assert ( + dict(hass.states.get("sensor.door1_battery").attributes) == expected_attributes + ) assert hass.states.get("sensor.door2_battery") is None assert hass.states.get("sensor.door2_battery") is None @@ -212,6 +222,13 @@ async def test_sensor_update(gogogate2api_mock, hass: HomeAssistant) -> None: @patch("homeassistant.components.gogogate2.common.ISmartGateApi") async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None: """Test availability.""" + expected_attributes = { + "device_class": "battery", + "door_id": 1, + "friendly_name": "Door1 battery", + "sensor_id": "ABCD", + } + sensor_response = _mocked_ismartgate_sensor_response(35) api = MagicMock(ISmartGateApi) api.async_info.return_value = sensor_response @@ -259,3 +276,6 @@ async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None: async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) await hass.async_block_till_done() assert hass.states.get("sensor.door1_battery").state == "35" + assert ( + dict(hass.states.get("sensor.door1_battery").attributes) == expected_attributes + ) From cc72cf0c0d0af3a2121193d34a8ad3dc18100193 Mon Sep 17 00:00:00 2001 From: Nick Adams <4012017+Nick-Adams-AU@users.noreply.github.com> Date: Wed, 3 Mar 2021 20:51:40 +1000 Subject: [PATCH 143/831] Update izone services.yaml and remove entity_id from schema. (#47305) Co-authored-by: Franck Nijhof --- homeassistant/components/izone/climate.py | 16 ++++----- homeassistant/components/izone/services.yaml | 38 +++++++++++++++----- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/izone/climate.py b/homeassistant/components/izone/climate.py index ef054bcfb29..77ce9cb2452 100644 --- a/homeassistant/components/izone/climate.py +++ b/homeassistant/components/izone/climate.py @@ -26,14 +26,13 @@ from homeassistant.components.climate.const import ( ) from homeassistant.const import ( ATTR_TEMPERATURE, - CONF_ENTITY_ID, CONF_EXCLUDE, PRECISION_HALVES, PRECISION_TENTHS, TEMP_CELSIUS, ) from homeassistant.core import callback -from homeassistant.helpers import config_validation as cv, entity_platform +from homeassistant.helpers import entity_platform from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.temperature import display_temp as show_temp from homeassistant.helpers.typing import ConfigType, HomeAssistantType @@ -63,14 +62,11 @@ ATTR_AIRFLOW = "airflow" IZONE_SERVICE_AIRFLOW_MIN = "airflow_min" IZONE_SERVICE_AIRFLOW_MAX = "airflow_max" -IZONE_SERVICE_AIRFLOW_SCHEMA = vol.Schema( - { - vol.Required(CONF_ENTITY_ID): cv.entity_id, - vol.Required(ATTR_AIRFLOW): vol.All( - vol.Coerce(int), vol.Range(min=0, max=100), msg="invalid airflow" - ), - } -) +IZONE_SERVICE_AIRFLOW_SCHEMA = { + vol.Required(ATTR_AIRFLOW): vol.All( + vol.Coerce(int), vol.Range(min=0, max=100), msg="invalid airflow" + ), +} async def async_setup_entry( diff --git a/homeassistant/components/izone/services.yaml b/homeassistant/components/izone/services.yaml index 14aaa69349a..d03ad66421a 100644 --- a/homeassistant/components/izone/services.yaml +++ b/homeassistant/components/izone/services.yaml @@ -1,18 +1,40 @@ airflow_min: + name: Set minimum airflow description: Set the airflow minimum percent for a zone + target: + entity: + integration: izone + domain: climate fields: - entity_id: - description: iZone Zone entity - example: "climate.bed_1" airflow: + name: Percent description: Airflow percent in 5% increments - example: "95" + required: true + example: 95 + selector: + number: + min: 0 + max: 100 + step: 5 + unit_of_measurement: "%" + mode: slider airflow_max: + name: Set maximum airflow description: Set the airflow maximum percent for a zone + target: + entity: + integration: izone + domain: climate fields: - entity_id: - description: iZone Zone entity - example: "climate.bed_1" airflow: + name: Percent description: Airflow percent in 5% increments - example: "95" + required: true + example: 95 + selector: + number: + min: 0 + max: 100 + step: 5 + unit_of_measurement: "%" + mode: slider From c192a44e870667c5f0a2ff9160b5133c1d6d2590 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 3 Mar 2021 14:35:58 +0100 Subject: [PATCH 144/831] Update frontend to 20210302.3 (#47310) --- 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 e7d7723a510..4d4127fc2f2 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20210302.0" + "home-assistant-frontend==20210302.3" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 44e6195317d..752a3755169 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210302.0 +home-assistant-frontend==20210302.3 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 67b52a29bed..c1f3d577251 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.0 +home-assistant-frontend==20210302.3 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6d9307eb52b..a27ec0f8051 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.0 +home-assistant-frontend==20210302.3 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 099c9c59cb40f7e1dfdd33ef60aaf679d3057e7c Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 3 Mar 2021 14:37:36 +0100 Subject: [PATCH 145/831] Fix Supervisor platform coordinator data lookup (#47308) --- homeassistant/components/hassio/binary_sensor.py | 2 +- homeassistant/components/hassio/sensor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/hassio/binary_sensor.py b/homeassistant/components/hassio/binary_sensor.py index c3daaa07f28..2208f3fb580 100644 --- a/homeassistant/components/hassio/binary_sensor.py +++ b/homeassistant/components/hassio/binary_sensor.py @@ -23,7 +23,7 @@ async def async_setup_entry( HassioAddonBinarySensor( coordinator, addon, ATTR_UPDATE_AVAILABLE, "Update Available" ) - for addon in coordinator.data.values() + for addon in coordinator.data["addons"].values() ] if coordinator.is_hass_os: entities.append( diff --git a/homeassistant/components/hassio/sensor.py b/homeassistant/components/hassio/sensor.py index 857f4831587..711a2a46300 100644 --- a/homeassistant/components/hassio/sensor.py +++ b/homeassistant/components/hassio/sensor.py @@ -24,7 +24,7 @@ async def async_setup_entry( (ATTR_VERSION, "Version"), (ATTR_VERSION_LATEST, "Newest Version"), ): - for addon in coordinator.data.values(): + for addon in coordinator.data["addons"].values(): entities.append( HassioAddonSensor(coordinator, addon, attribute_name, sensor_name) ) From 7626aa5c945556e6cd03483831a0e327731411c6 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Wed, 3 Mar 2021 18:51:58 +0100 Subject: [PATCH 146/831] Philips JS correct post review comments (#47247) --- .coveragerc | 1 + .../components/philips_js/__init__.py | 2 +- .../components/philips_js/config_flow.py | 29 +++++++++---------- .../components/philips_js/media_player.py | 12 ++++---- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/.coveragerc b/.coveragerc index 36d4399466f..5d502a7503a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -725,6 +725,7 @@ omit = homeassistant/components/pencom/switch.py homeassistant/components/philips_js/__init__.py homeassistant/components/philips_js/media_player.py + homeassistant/components/philips_js/remote.py homeassistant/components/pi_hole/sensor.py homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py homeassistant/components/pi4ioe5v9xxxx/switch.py diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index b3abaa28132..088e29e4e26 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -150,7 +150,7 @@ class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]): and self.api.on and self.api.notify_change_supported ): - self._notify_future = self.hass.loop.create_task(self._notify_task()) + self._notify_future = asyncio.create_task(self._notify_task()) @callback def async_remove_listener(self, update_callback: CALLBACK_TYPE) -> None: diff --git a/homeassistant/components/philips_js/config_flow.py b/homeassistant/components/philips_js/config_flow.py index 778bcba282b..39150be9ce8 100644 --- a/homeassistant/components/philips_js/config_flow.py +++ b/homeassistant/components/philips_js/config_flow.py @@ -1,6 +1,6 @@ """Config flow for Philips TV integration.""" import platform -from typing import Any, Dict, Optional, Tuple, TypedDict +from typing import Any, Dict, Optional, Tuple from haphilipsjs import ConnectionFailure, PairingFailure, PhilipsTV import voluptuous as vol @@ -23,12 +23,6 @@ from .const import ( # pylint:disable=unused-import ) -class FlowUserDict(TypedDict): - """Data for user step.""" - - host: str - - async def validate_input( hass: core.HomeAssistant, host: str, api_version: int ) -> Tuple[Dict, PhilipsTV]: @@ -50,11 +44,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL - _current = {} - _hub: PhilipsTV - _pair_state: Any + def __init__(self) -> None: + """Initialize flow.""" + super().__init__() + self._current = {} + self._hub: Optional[PhilipsTV] = None + self._pair_state: Any = None - async def async_step_import(self, conf: Dict[str, Any]): + async def async_step_import(self, conf: dict) -> dict: """Import a configuration from config.yaml.""" for entry in self._async_current_entries(): if entry.data[CONF_HOST] == conf[CONF_HOST]: @@ -75,7 +72,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): data=self._current, ) - async def async_step_pair(self, user_input: Optional[Dict] = None): + async def async_step_pair(self, user_input: Optional[dict] = None) -> dict: """Attempt to pair with device.""" assert self._hub @@ -96,7 +93,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): "native", ) except PairingFailure as exc: - LOGGER.debug(str(exc)) + LOGGER.debug(exc) return self.async_abort( reason="pairing_failure", description_placeholders={"error_id": exc.data.get("error_id")}, @@ -110,7 +107,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._pair_state, user_input[CONF_PIN] ) except PairingFailure as exc: - LOGGER.debug(str(exc)) + LOGGER.debug(exc) if exc.data.get("error_id") == "INVALID_PIN": errors[CONF_PIN] = "invalid_pin" return self.async_show_form( @@ -126,7 +123,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._current[CONF_PASSWORD] = password return await self._async_create_current() - async def async_step_user(self, user_input: Optional[FlowUserDict] = None): + async def async_step_user(self, user_input: Optional[dict] = None) -> dict: """Handle the initial step.""" errors = {} if user_input: @@ -136,7 +133,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self.hass, user_input[CONF_HOST], user_input[CONF_API_VERSION] ) except ConnectionFailure as exc: - LOGGER.error(str(exc)) + LOGGER.error(exc) errors["base"] = "cannot_connect" except Exception: # pylint: disable=broad-except LOGGER.exception("Unexpected exception") diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index 2b2714b20ce..38521989567 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -295,8 +295,8 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): def media_image_url(self): """Image url of current playing media.""" if self._media_content_id and self._media_content_type in ( - MEDIA_CLASS_APP, - MEDIA_CLASS_CHANNEL, + MEDIA_TYPE_APP, + MEDIA_TYPE_CHANNEL, ): return self.get_browse_image_url( self._media_content_type, self._media_content_id, media_image_id=None @@ -384,7 +384,7 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): media_class=MEDIA_CLASS_DIRECTORY, media_content_id="channels", media_content_type=MEDIA_TYPE_CHANNELS, - children_media_class=MEDIA_TYPE_CHANNEL, + children_media_class=MEDIA_CLASS_CHANNEL, can_play=False, can_expand=True, children=children, @@ -427,7 +427,7 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): media_class=MEDIA_CLASS_DIRECTORY, media_content_id=f"favorites/{list_id}", media_content_type=MEDIA_TYPE_CHANNELS, - children_media_class=MEDIA_TYPE_CHANNEL, + children_media_class=MEDIA_CLASS_CHANNEL, can_play=False, can_expand=True, children=children, @@ -458,7 +458,7 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): media_class=MEDIA_CLASS_DIRECTORY, media_content_id="applications", media_content_type=MEDIA_TYPE_APPS, - children_media_class=MEDIA_TYPE_APP, + children_media_class=MEDIA_CLASS_APP, can_play=False, can_expand=True, children=children, @@ -479,7 +479,7 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): media_class=MEDIA_CLASS_DIRECTORY, media_content_id="favorite_lists", media_content_type=MEDIA_TYPE_CHANNELS, - children_media_class=MEDIA_TYPE_CHANNEL, + children_media_class=MEDIA_CLASS_CHANNEL, can_play=False, can_expand=True, children=children, From 504e5b77ca966ff153fb68a314bf556b5b815d42 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 3 Mar 2021 19:12:37 +0100 Subject: [PATCH 147/831] Improve behaviour when disabling or enabling config entries (#47301) --- homeassistant/config_entries.py | 32 ++++++--- homeassistant/const.py | 1 - homeassistant/helpers/device_registry.py | 71 +++++++++---------- homeassistant/helpers/entity_registry.py | 87 +++++++++++------------- tests/helpers/test_entity_registry.py | 2 +- 5 files changed, 97 insertions(+), 96 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index d3e8f66abc4..f74acede507 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -11,10 +11,9 @@ import weakref import attr from homeassistant import data_entry_flow, loader -from homeassistant.const import EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError -from homeassistant.helpers import entity_registry +from homeassistant.helpers import device_registry, entity_registry from homeassistant.helpers.event import Event from homeassistant.helpers.typing import UNDEFINED, UndefinedType from homeassistant.setup import async_process_deps_reqs, async_setup_component @@ -807,12 +806,21 @@ class ConfigEntries: entry.disabled_by = disabled_by self._async_schedule_save() - # Unload the config entry, then fire an event + dev_reg = device_registry.async_get(self.hass) + ent_reg = entity_registry.async_get(self.hass) + + if not entry.disabled_by: + # The config entry will no longer be disabled, enable devices and entities + device_registry.async_config_entry_disabled_by_changed(dev_reg, entry) + entity_registry.async_config_entry_disabled_by_changed(ent_reg, entry) + + # Load or unload the config entry reload_result = await self.async_reload(entry_id) - self.hass.bus.async_fire( - EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, {"config_entry_id": entry_id} - ) + if entry.disabled_by: + # The config entry has been disabled, disable devices and entities + device_registry.async_config_entry_disabled_by_changed(dev_reg, entry) + entity_registry.async_config_entry_disabled_by_changed(ent_reg, entry) return reload_result @@ -1250,8 +1258,16 @@ class EntityRegistryDisabledHandler: @callback def _handle_entry_updated_filter(event: Event) -> bool: - """Handle entity registry entry update filter.""" - if event.data["action"] != "update" or "disabled_by" not in event.data["changes"]: + """Handle entity registry entry update filter. + + Only handle changes to "disabled_by". + If "disabled_by" was DISABLED_CONFIG_ENTRY, reload is not needed. + """ + if ( + event.data["action"] != "update" + or "disabled_by" not in event.data["changes"] + or event.data["changes"]["disabled_by"] == entity_registry.DISABLED_CONFIG_ENTRY + ): return False return True diff --git a/homeassistant/const.py b/homeassistant/const.py index 712f7ede0d3..1076b962f2a 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -202,7 +202,6 @@ CONF_ZONE = "zone" # #### EVENTS #### EVENT_CALL_SERVICE = "call_service" EVENT_COMPONENT_LOADED = "component_loaded" -EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED = "config_entry_disabled_by_updated" EVENT_CORE_CONFIG_UPDATE = "core_config_updated" EVENT_HOMEASSISTANT_CLOSE = "homeassistant_close" EVENT_HOMEASSISTANT_START = "homeassistant_start" diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 705f6cdd89a..d311538f27f 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -6,10 +6,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union, import attr -from homeassistant.const import ( - EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, - EVENT_HOMEASSISTANT_STARTED, -) +from homeassistant.const import EVENT_HOMEASSISTANT_STARTED from homeassistant.core import Event, callback from homeassistant.loader import bind_hass import homeassistant.util.uuid as uuid_util @@ -20,6 +17,8 @@ from .typing import UNDEFINED, HomeAssistantType, UndefinedType # mypy: disallow_any_generics if TYPE_CHECKING: + from homeassistant.config_entries import ConfigEntry + from . import entity_registry _LOGGER = logging.getLogger(__name__) @@ -143,10 +142,6 @@ class DeviceRegistry: self.hass = hass self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) self._clear_index() - self.hass.bus.async_listen( - EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, - self.async_config_entry_disabled_by_changed, - ) @callback def async_get(self, device_id: str) -> Optional[DeviceEntry]: @@ -618,38 +613,6 @@ class DeviceRegistry: if area_id == device.area_id: self._async_update_device(dev_id, area_id=None) - @callback - def async_config_entry_disabled_by_changed(self, event: Event) -> None: - """Handle a config entry being disabled or enabled. - - Disable devices in the registry that are associated to a config entry when - the config entry is disabled. - """ - config_entry = self.hass.config_entries.async_get_entry( - event.data["config_entry_id"] - ) - - # The config entry may be deleted already if the event handling is late - if not config_entry: - return - - if not config_entry.disabled_by: - devices = async_entries_for_config_entry( - self, event.data["config_entry_id"] - ) - for device in devices: - if device.disabled_by != DISABLED_CONFIG_ENTRY: - continue - self.async_update_device(device.id, disabled_by=None) - return - - devices = async_entries_for_config_entry(self, event.data["config_entry_id"]) - for device in devices: - if device.disabled: - # Entity already disabled, do not overwrite - continue - self.async_update_device(device.id, disabled_by=DISABLED_CONFIG_ENTRY) - @callback def async_get(hass: HomeAssistantType) -> DeviceRegistry: @@ -691,6 +654,34 @@ def async_entries_for_config_entry( ] +@callback +def async_config_entry_disabled_by_changed( + registry: DeviceRegistry, config_entry: "ConfigEntry" +) -> None: + """Handle a config entry being disabled or enabled. + + Disable devices in the registry that are associated with a config entry when + the config entry is disabled, enable devices in the registry that are associated + with a config entry when the config entry is enabled and the devices are marked + DISABLED_CONFIG_ENTRY. + """ + + devices = async_entries_for_config_entry(registry, config_entry.entry_id) + + if not config_entry.disabled_by: + for device in devices: + if device.disabled_by != DISABLED_CONFIG_ENTRY: + continue + registry.async_update_device(device.id, disabled_by=None) + return + + for device in devices: + if device.disabled: + # Device already disabled, do not overwrite + continue + registry.async_update_device(device.id, disabled_by=DISABLED_CONFIG_ENTRY) + + @callback def async_cleanup( hass: HomeAssistantType, diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index f18dda71529..36b010c82a0 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -31,7 +31,6 @@ from homeassistant.const import ( ATTR_RESTORED, ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT, - EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, EVENT_HOMEASSISTANT_START, STATE_UNAVAILABLE, ) @@ -158,10 +157,6 @@ class EntityRegistry: self.hass.bus.async_listen( EVENT_DEVICE_REGISTRY_UPDATED, self.async_device_modified ) - self.hass.bus.async_listen( - EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, - self.async_config_entry_disabled_by_changed, - ) @callback def async_get_device_class_lookup(self, domain_device_classes: set) -> dict: @@ -363,40 +358,6 @@ class EntityRegistry: for entity in entities: self.async_update_entity(entity.entity_id, disabled_by=DISABLED_DEVICE) - @callback - def async_config_entry_disabled_by_changed(self, event: Event) -> None: - """Handle a config entry being disabled or enabled. - - Disable entities in the registry that are associated to a config entry when - the config entry is disabled. - """ - config_entry = self.hass.config_entries.async_get_entry( - event.data["config_entry_id"] - ) - - # The config entry may be deleted already if the event handling is late - if not config_entry: - return - - if not config_entry.disabled_by: - entities = async_entries_for_config_entry( - self, event.data["config_entry_id"] - ) - for entity in entities: - if entity.disabled_by != DISABLED_CONFIG_ENTRY: - continue - self.async_update_entity(entity.entity_id, disabled_by=None) - return - - entities = async_entries_for_config_entry(self, event.data["config_entry_id"]) - for entity in entities: - if entity.disabled: - # Entity already disabled, do not overwrite - continue - self.async_update_entity( - entity.entity_id, disabled_by=DISABLED_CONFIG_ENTRY - ) - @callback def async_update_entity( self, @@ -443,7 +404,8 @@ class EntityRegistry: """Private facing update properties method.""" old = self.entities[entity_id] - changes = {} + new_values = {} # Dict with new key/value pairs + old_values = {} # Dict with old key/value pairs for attr_name, value in ( ("name", name), @@ -460,7 +422,8 @@ class EntityRegistry: ("original_icon", original_icon), ): if value is not UNDEFINED and value != getattr(old, attr_name): - changes[attr_name] = value + new_values[attr_name] = value + old_values[attr_name] = getattr(old, attr_name) if new_entity_id is not UNDEFINED and new_entity_id != old.entity_id: if self.async_is_registered(new_entity_id): @@ -473,7 +436,8 @@ class EntityRegistry: raise ValueError("New entity ID should be same domain") self.entities.pop(entity_id) - entity_id = changes["entity_id"] = new_entity_id + entity_id = new_values["entity_id"] = new_entity_id + old_values["entity_id"] = old.entity_id if new_unique_id is not UNDEFINED: conflict_entity_id = self.async_get_entity_id( @@ -484,18 +448,19 @@ class EntityRegistry: f"Unique id '{new_unique_id}' is already in use by " f"'{conflict_entity_id}'" ) - changes["unique_id"] = new_unique_id + new_values["unique_id"] = new_unique_id + old_values["unique_id"] = old.unique_id - if not changes: + if not new_values: return old self._remove_index(old) - new = attr.evolve(old, **changes) + new = attr.evolve(old, **new_values) self._register_entry(new) self.async_schedule_save() - data = {"action": "update", "entity_id": entity_id, "changes": list(changes)} + data = {"action": "update", "entity_id": entity_id, "changes": old_values} if old.entity_id != entity_id: data["old_entity_id"] = old.entity_id @@ -670,6 +635,36 @@ def async_entries_for_config_entry( ] +@callback +def async_config_entry_disabled_by_changed( + registry: EntityRegistry, config_entry: "ConfigEntry" +) -> None: + """Handle a config entry being disabled or enabled. + + Disable entities in the registry that are associated with a config entry when + the config entry is disabled, enable entities in the registry that are associated + with a config entry when the config entry is enabled and the entities are marked + DISABLED_CONFIG_ENTRY. + """ + + entities = async_entries_for_config_entry(registry, config_entry.entry_id) + + if not config_entry.disabled_by: + for entity in entities: + if entity.disabled_by != DISABLED_CONFIG_ENTRY: + continue + registry.async_update_entity(entity.entity_id, disabled_by=None) + return + + for entity in entities: + if entity.disabled: + # Entity already disabled, do not overwrite + continue + registry.async_update_entity( + entity.entity_id, disabled_by=DISABLED_CONFIG_ENTRY + ) + + async def _async_migrate(entities: Dict[str, Any]) -> Dict[str, List[Dict[str, Any]]]: """Migrate the YAML config file to storage helper format.""" return { diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 86cdab82238..0a1a27efef5 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -313,7 +313,7 @@ async def test_updating_config_entry_id(hass, registry, update_events): assert update_events[0]["entity_id"] == entry.entity_id assert update_events[1]["action"] == "update" assert update_events[1]["entity_id"] == entry.entity_id - assert update_events[1]["changes"] == ["config_entry_id"] + assert update_events[1]["changes"] == {"config_entry_id": "mock-id-1"} async def test_removing_config_entry_id(hass, registry, update_events): From e6a6b2a6802bb1ff625fc0ec759d1a061e15ab86 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 3 Mar 2021 10:13:04 -0800 Subject: [PATCH 148/831] Simplify switch light (#47317) --- homeassistant/components/switch/light.py | 50 +++++++++--------------- 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/switch/light.py b/homeassistant/components/switch/light.py index 5128a49d8b7..2650bd61bfb 100644 --- a/homeassistant/components/switch/light.py +++ b/homeassistant/components/switch/light.py @@ -12,7 +12,7 @@ from homeassistant.const import ( STATE_ON, STATE_UNAVAILABLE, ) -from homeassistant.core import CALLBACK_TYPE, callback +from homeassistant.core import State, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change_event @@ -37,7 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( async def async_setup_platform( hass: HomeAssistantType, config: ConfigType, - async_add_entities: Callable[[Sequence[Entity], bool], None], + async_add_entities: Callable[[Sequence[Entity]], None], discovery_info: Optional[DiscoveryInfoType] = None, ) -> None: """Initialize Light Switch platform.""" @@ -53,8 +53,7 @@ async def async_setup_platform( config[CONF_ENTITY_ID], unique_id, ) - ], - True, + ] ) @@ -66,9 +65,7 @@ class LightSwitch(LightEntity): self._name = name self._switch_entity_id = switch_entity_id self._unique_id = unique_id - self._is_on = False - self._available = False - self._async_unsub_state_changed: Optional[CALLBACK_TYPE] = None + self._switch_state: Optional[State] = None @property def name(self) -> str: @@ -78,12 +75,16 @@ class LightSwitch(LightEntity): @property def is_on(self) -> bool: """Return true if light switch is on.""" - return self._is_on + assert self._switch_state is not None + return self._switch_state.state == STATE_ON @property def available(self) -> bool: """Return true if light switch is on.""" - return self._available + return ( + self._switch_state is not None + and self._switch_state.state != STATE_UNAVAILABLE + ) @property def should_poll(self) -> bool: @@ -117,33 +118,20 @@ class LightSwitch(LightEntity): context=self._context, ) - async def async_update(self): - """Query the switch in this light switch and determine the state.""" - switch_state = self.hass.states.get(self._switch_entity_id) - - if switch_state is None: - self._available = False - return - - self._is_on = switch_state.state == STATE_ON - self._available = switch_state.state != STATE_UNAVAILABLE - async def async_added_to_hass(self) -> None: """Register callbacks.""" + assert self.hass is not None + self._switch_state = self.hass.states.get(self._switch_entity_id) @callback def async_state_changed_listener(*_: Any) -> None: """Handle child updates.""" - self.async_schedule_update_ha_state(True) + assert self.hass is not None + self._switch_state = self.hass.states.get(self._switch_entity_id) + self.async_write_ha_state() - assert self.hass is not None - self._async_unsub_state_changed = async_track_state_change_event( - self.hass, [self._switch_entity_id], async_state_changed_listener + self.async_on_remove( + async_track_state_change_event( + self.hass, [self._switch_entity_id], async_state_changed_listener + ) ) - - async def async_will_remove_from_hass(self): - """Handle removal from Home Assistant.""" - if self._async_unsub_state_changed is not None: - self._async_unsub_state_changed() - self._async_unsub_state_changed = None - self._available = False From 53e62a897bce6d0b8367c073c453a585d20fb59e Mon Sep 17 00:00:00 2001 From: tkdrob Date: Wed, 3 Mar 2021 18:20:35 -0500 Subject: [PATCH 149/831] Fix grammar in pi_hole logs (#47324) --- homeassistant/components/pi_hole/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/pi_hole/__init__.py b/homeassistant/components/pi_hole/__init__.py index 2d540d936e5..bc486a0c901 100644 --- a/homeassistant/components/pi_hole/__init__.py +++ b/homeassistant/components/pi_hole/__init__.py @@ -112,7 +112,7 @@ async def async_setup_entry(hass, entry): try: await api.get_data() except HoleError as err: - raise UpdateFailed(f"Failed to communicating with API: {err}") from err + raise UpdateFailed(f"Failed to communicate with API: {err}") from err coordinator = DataUpdateCoordinator( hass, From 6d478804e735e5260edc66cf0e8867de8a0522c1 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 3 Mar 2021 16:32:37 -0700 Subject: [PATCH 150/831] Add LZW36 device schema to zwave_js discovery (#47314) * Add LZW26 device schema to discovery Co-authored-by: @kpine * Update homeassistant/components/zwave_js/discovery.py Co-authored-by: kpine * Add tests * Fix test Co-authored-by: kpine --- .../components/zwave_js/discovery.py | 14 + tests/components/zwave_js/conftest.py | 14 + tests/components/zwave_js/test_discovery.py | 12 + .../zwave_js/inovelli_lzw36_state.json | 1956 +++++++++++++++++ 4 files changed, 1996 insertions(+) create mode 100644 tests/fixtures/zwave_js/inovelli_lzw36_state.json diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 804212d310a..f27325b87d2 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -115,6 +115,20 @@ DISCOVERY_SCHEMAS = [ product_type={0x0038}, primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, ), + # Inovelli LZW36 light / fan controller combo using switch multilevel CC + # The fan is endpoint 2, the light is endpoint 1. + ZWaveDiscoverySchema( + platform="fan", + manufacturer_id={0x031E}, + product_id={0x0001}, + product_type={0x000E}, + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.SWITCH_MULTILEVEL}, + endpoint={2}, + property={"currentValue"}, + type={"number"}, + ), + ), # Fibaro Shutter Fibaro FGS222 ZWaveDiscoverySchema( platform="cover", diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 50cacd97422..af87d9d49e1 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -282,6 +282,12 @@ def aeotec_radiator_thermostat_state_fixture(): return json.loads(load_fixture("zwave_js/aeotec_radiator_thermostat_state.json")) +@pytest.fixture(name="inovelli_lzw36_state", scope="session") +def inovelli_lzw36_state_fixture(): + """Load the Inovelli LZW36 node state fixture data.""" + return json.loads(load_fixture("zwave_js/inovelli_lzw36_state.json")) + + @pytest.fixture(name="client") def mock_client_fixture(controller_state, version_state): """Mock a client.""" @@ -512,3 +518,11 @@ def ge_12730_fixture(client, ge_12730_state): node = Node(client, copy.deepcopy(ge_12730_state)) client.driver.controller.nodes[node.node_id] = node return node + + +@pytest.fixture(name="inovelli_lzw36") +def inovelli_lzw36_fixture(client, inovelli_lzw36_state): + """Mock a Inovelli LZW36 fan controller node.""" + node = Node(client, copy.deepcopy(inovelli_lzw36_state)) + client.driver.controller.nodes[node.node_id] = node + return node diff --git a/tests/components/zwave_js/test_discovery.py b/tests/components/zwave_js/test_discovery.py index e28c8ae1563..810ccb8df33 100644 --- a/tests/components/zwave_js/test_discovery.py +++ b/tests/components/zwave_js/test_discovery.py @@ -23,3 +23,15 @@ async def test_ge_12730(hass, client, ge_12730, integration): state = hass.states.get("fan.in_wall_smart_fan_control") assert state + + +async def test_inovelli_lzw36(hass, client, inovelli_lzw36, integration): + """Test LZW36 Fan Controller multilevel switch endpoint 2 is discovered as a fan.""" + node = inovelli_lzw36 + assert node.device_class.specific.label == "Unused" + + state = hass.states.get("light.family_room_combo") + assert state.state == "off" + + state = hass.states.get("fan.family_room_combo_2") + assert state diff --git a/tests/fixtures/zwave_js/inovelli_lzw36_state.json b/tests/fixtures/zwave_js/inovelli_lzw36_state.json new file mode 100644 index 00000000000..bfa56891413 --- /dev/null +++ b/tests/fixtures/zwave_js/inovelli_lzw36_state.json @@ -0,0 +1,1956 @@ +{ + "nodeId": 19, + "index": 0, + "installerIcon": 7168, + "userIcon": 7168, + "status": 4, + "ready": true, + "deviceClass": { + "basic": { + "key": 4, + "label": "Routing Slave" + }, + "generic": { + "key": 17, + "label": "Multilevel Switch" + }, + "specific": { + "key": 0, + "label": "Unused" + }, + "mandatorySupportedCCs": [ + 32, + 38 + ], + "mandatoryControlledCCs": [] + }, + "isListening": true, + "isFrequentListening": false, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 4, + "isBeaming": true, + "manufacturerId": 798, + "productId": 1, + "productType": 14, + "firmwareVersion": "1.34", + "zwavePlusVersion": 2, + "nodeType": 0, + "roleType": 5, + "name": "family_room_combo", + "deviceConfig": { + "filename": "/opt/node_modules/@zwave-js/config/config/devices/0x031e/lzw36.json", + "manufacturerId": 798, + "manufacturer": "Inovelli", + "label": "LZW36", + "description": "Fan/Light Dimmer", + "devices": [ + { + "productType": "0x000e", + "productId": "0x0001" + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + }, + "associations": {}, + "paramInformation": { + "_map": {} + } + }, + "label": "LZW36", + "neighbors": [ + 1, + 13, + 14, + 15, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30 + ], + "endpointCountIsDynamic": false, + "endpointsHaveIdenticalCapabilities": true, + "individualEndpointCount": 2, + "aggregatedEndpointCount": 0, + "interviewAttempts": 1, + "interviewStage": 7, + "commandClasses": [ + { + "id": 38, + "name": "Multilevel Switch", + "version": 4, + "isSecure": false + }, + { + "id": 94, + "name": "Z-Wave Plus Info", + "version": 2, + "isSecure": false + }, + { + "id": 152, + "name": "Security", + "version": 1, + "isSecure": true + }, + { + "id": 108, + "name": "Supervision", + "version": 1, + "isSecure": false + }, + { + "id": 112, + "name": "Configuration", + "version": 4, + "isSecure": false + }, + { + "id": 133, + "name": "Association", + "version": 2, + "isSecure": false + }, + { + "id": 89, + "name": "Association Group Information", + "version": 3, + "isSecure": false + }, + { + "id": 142, + "name": "Multi Channel Association", + "version": 3, + "isSecure": false + }, + { + "id": 134, + "name": "Version", + "version": 3, + "isSecure": false + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 2, + "isSecure": false + }, + { + "id": 90, + "name": "Device Reset Locally", + "version": 1, + "isSecure": false + }, + { + "id": 117, + "name": "Protection", + "version": 2, + "isSecure": false + }, + { + "id": 122, + "name": "Firmware Update Meta Data", + "version": 5, + "isSecure": false + }, + { + "id": 91, + "name": "Central Scene", + "version": 3, + "isSecure": false + }, + { + "id": 135, + "name": "Indicator", + "version": 3, + "isSecure": false + }, + { + "id": 96, + "name": "Multi Channel", + "version": 4, + "isSecure": false + }, + { + "id": 50, + "name": "Meter", + "version": 3, + "isSecure": false + } + ], + "endpoints": [ + { + "nodeId": 19, + "index": 0, + "installerIcon": 7168, + "userIcon": 7168 + }, + { + "nodeId": 19, + "index": 1, + "installerIcon": 1536, + "userIcon": 1536 + }, + { + "nodeId": 19, + "index": 2, + "installerIcon": 1536, + "userIcon": 1536 + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 1, + "propertyName": "Light Dimming Speed", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 98, + "default": 4, + "format": 1, + "allowManualEntry": true, + "label": "Light Dimming Speed", + "description": "This changes the speed in which the attached light dims up or down. A setting of 0 should turn the light immediately on or off (almost like an on/off switch). Increasing the value should slow down the transition speed. Range:0-98 Default: 4", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 2, + "propertyName": "Light Dimming Speed (From Switch)", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 99, + "default": 99, + "format": 1, + "allowManualEntry": true, + "label": "Light Dimming Speed (From Switch)", + "description": "This changes the speed in which the attached light dims up or down when controlled from the physical switch. A setting of 0 should turn the light immediately on or off (almost like an on/off switch). Increasing the value should slow down the transition speed. A setting of 99 should keep this in sync with parameter 1. Range:0-99 Default: 99", + "isFromConfig": true + }, + "value": 99 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 3, + "propertyName": "Light Ramp Rate", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 99, + "default": 99, + "format": 1, + "allowManualEntry": true, + "label": "Light Ramp Rate", + "description": "This changes the speed in which the attached light turns on or off. For example, when a user sends the switch a basicSet(value: 0xFF) or basicSet(value: 0x00), this is the speed in which those actions take place. A setting of 0 should turn the light immediately on or off (almost like an on/off switch). Increasing the value should slow down the transition speed. A setting of 99 should keep this in sync with parameter 1. Range:0-99 Default: 99", + "isFromConfig": true + }, + "value": 99 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 4, + "propertyName": "Light Ramp Rate (From Switch)", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 99, + "default": 99, + "format": 1, + "allowManualEntry": true, + "label": "Light Ramp Rate (From Switch)", + "description": "This changes the speed in which the attached light turns on or off from the physical switch. For example, when a user presses the up or down button, this is the speed in which those actions take place. A setting of 0 should turn the light immediately on or off (almost like an on/off switch). Increasing the value should slow down the transition speed. A setting of 99 should keep this in sync with parameter 1. Range:0-99 Default: 99", + "isFromConfig": true + }, + "value": 99 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 5, + "propertyName": "Minimum Light Level", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 45, + "default": 1, + "format": 1, + "allowManualEntry": true, + "label": "Minimum Light Level", + "description": "The minimum level that the dimmer allows the bulb to be dimmed to. Useful when the user has an LED bulb that does not turn on or flickers at a lower level. Range:1-45 Default: 1", + "isFromConfig": true + }, + "value": 40 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 6, + "propertyName": "Maximum Light Level", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 55, + "max": 99, + "default": 99, + "format": 1, + "allowManualEntry": true, + "label": "Maximum Light Level", + "description": "The maximum level that the dimmer allows the bulb to be dimmed to. Useful when the user has an LED bulb that reaches its maximum level before the dimmer value of 99. Range:55-99 Default: 99", + "isFromConfig": true + }, + "value": 99 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 7, + "propertyName": "Minimum Fan Level", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 45, + "default": 1, + "format": 1, + "allowManualEntry": true, + "label": "Minimum Fan Level", + "description": "The minimum level that the dimmer allows the fan to be dimmed to. Useful when the user has a fan that does not turn at a lower level. Range:1-45 Default: 1", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 8, + "propertyName": "Maximum Fan Level", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 55, + "max": 99, + "default": 99, + "format": 1, + "allowManualEntry": true, + "label": "Maximum Fan Level", + "description": "The maximum level that the dimmer allows the fan to be dimmed to. Range:55-99 Default: 99", + "isFromConfig": true + }, + "value": 99 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 10, + "propertyName": "Auto Off Light Timer", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 0, + "max": 32767, + "default": 0, + "unit": "s", + "format": 1, + "allowManualEntry": true, + "label": "Auto Off Light Timer", + "description": "Automatically turns the light switch off after this many seconds. When the switch is turned on a timer is started that is the duration of this setting. When the timer expires, the switch is turned off. Range:0-32767 Default: 0", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 11, + "propertyName": "Auto Off Fan Timer", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 0, + "max": 32767, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Auto Off Fan Timer", + "description": "Automatically turns the fan switch off after this many seconds. When the switch is turned on a timer is started that is the duration of this setting. When the timer expires, the switch is turned off. Range:0-32767 Default: 0", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 12, + "propertyName": "Default Light Level (Local)", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 99, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Default Light Level (Local)", + "description": "Default level for the dimmer when it is powered on from the local switch. A setting of 0 means that the switch will return to the level that it was on before it was turned off. Range:1-99 Default: 0", + "isFromConfig": true + }, + "value": 70 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 13, + "propertyName": "Default Light Level (Z-Wave)", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 99, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Default Light Level (Z-Wave)", + "description": "Default level for the dimmer when it is powered on from a Z-Wave command. A setting of 0 means that the switch will return to the level that it was on before it was turned off. Range:1-99 Default: 0", + "isFromConfig": true + }, + "value": 85 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 14, + "propertyName": "Default Fan Level (Local)", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 99, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Last State", + "33": "Low", + "66": "Medium", + "99": "High" + }, + "label": "Default Fan Level (Local)", + "description": "Default level for the fan dimmer when it is powered on from the local switch. Default: Last State", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 15, + "propertyName": "Default Fan Level (Z-Wave)", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 99, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Last State", + "33": "Low", + "66": "Medium", + "99": "High" + }, + "label": "Default Fan Level (Z-Wave)", + "description": "Default level for the fan dimmer when it is powered on from the local switch. Default: Last State", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 16, + "propertyName": "Light State After Power Restored", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Light State After Power Restored", + "description": "The state the switch should return to once power is restored after power failure. 0 = off, 1-99 = level, 100=previous. Range:0-100 Default: 100", + "isFromConfig": true + }, + "value": 100 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 17, + "propertyName": "Fan State After Power Restored", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Fan State After Power Restored", + "description": "The state the switch should return to once power is restored after power failure. Default: Off", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 18, + "propertyName": "Light LED Indicator Color", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 0, + "max": 255, + "default": 170, + "format": 1, + "allowManualEntry": true, + "label": "Light LED Indicator Color", + "description": "This is the color of the Light LED strip represented as part of the HUE color wheel. Since the wheel has 360 values and this parameter only has 255, the following equation can be used to determine the color: value/255 * 360 = Hue color wheel value Range: 0 to 255 Default: 170", + "isFromConfig": true + }, + "value": 170 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 19, + "propertyName": "Light LED Strip Intensity", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 10, + "default": 5, + "format": 1, + "allowManualEntry": true, + "label": "Light LED Strip Intensity", + "description": "This is the intensity of the Light LED strip. Range: 0-10 Default: 5", + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 20, + "propertyName": "Fan LED Indicator Color", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 0, + "max": 255, + "default": 170, + "format": 1, + "allowManualEntry": true, + "label": "Fan LED Indicator Color", + "description": "This is the color of the Fan LED strip represented as part of the HUE color wheel. Since the wheel has 360 values and this parameter only has 255, the following equation can be used to determine the color: value/255 * 360 = Hue color wheel value Range: 0 to 255 Default: 170", + "isFromConfig": true + }, + "value": 170 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 21, + "propertyName": "Fan LED Strip Intensity", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 10, + "default": 5, + "format": 1, + "allowManualEntry": true, + "label": "Fan LED Strip Intensity", + "description": "This is the intensity of the Fan LED strip. Range: 0-10 Default: 5", + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 22, + "propertyName": "Light LED Strip Intensity (When OFF)", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 10, + "default": 1, + "format": 1, + "allowManualEntry": true, + "label": "Light LED Strip Intensity (When OFF)", + "description": "This is the intensity of the Light LED strip when the switch is off. This is useful for users to see the light switch location when the lights are off. Range: 0-10 Default: 1", + "isFromConfig": true + }, + "value": 4 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 23, + "propertyName": "Fan LED Strip Intensity (When OFF)", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 10, + "default": 1, + "format": 1, + "allowManualEntry": true, + "label": "Fan LED Strip Intensity (When OFF)", + "description": "This is the intensity of the Fan LED strip when the switch is off. This is useful for users to see the light switch location when the lights are off. Range: 0-10 Default: 1", + "isFromConfig": true + }, + "value": 4 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 26, + "propertyName": "Light LED Strip Timeout", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 10, + "default": 3, + "unit": "s", + "format": 1, + "allowManualEntry": true, + "label": "Light LED Strip Timeout", + "description": "When the LED strip is disabled (Light LED Strip Intensity is set to 0), this setting allows the LED strip to turn on temporarily while being adjusted. Range: 0-10 Default: 3 Disabled: 0", + "isFromConfig": true + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 27, + "propertyName": "Fan LED Strip Timeout", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 10, + "default": 3, + "unit": "s", + "format": 1, + "allowManualEntry": true, + "label": "Fan LED Strip Timeout", + "description": "When the LED strip is disabled (Fan LED Strip Intensity is set to 0), this setting allows the LED strip to turn on temporarily while being adjusted. Range: 0-10 Default: 3 Disabled: 0", + "isFromConfig": true + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 28, + "propertyName": "Active Power Reports", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 10, + "format": 1, + "allowManualEntry": true, + "label": "Active Power Reports", + "description": "The power level change that will result in a new power report being sent. The value is a percentage of the previous report. 0 = disabled. Range:0-100 Default: 10", + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyName": "Periodic Power & Energy Reports", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 0, + "max": 32767, + "default": 3600, + "format": 1, + "allowManualEntry": true, + "label": "Periodic Power & Energy Reports", + "description": "Time period between consecutive power & energy reports being sent (in seconds). The timer is reset after each report is sent. Range:0-32767 Default: 3600", + "isFromConfig": true + }, + "value": 3600 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 30, + "propertyName": "Energy Reports", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 10, + "format": 1, + "allowManualEntry": true, + "label": "Energy Reports", + "description": "The energy level change that will result in a new energy report being sent. The value is a percentage of the previous report. Range:0-100 Default: 10", + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 31, + "propertyName": "Local Protection", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 3, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "None", + "1": "light", + "2": "fan", + "3": "Both" + }, + "label": "Local Protection", + "description": "Enable local protection on these buttons. 0 = none, 1 = light, 2 = fan, 3 = both.", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 24, + "propertyKey": 2130706432, + "propertyName": "Light LED Strip Effect", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 5, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Off", + "1": "Solid", + "2": "Slow Blink", + "3": "Fast Blink", + "4": "Chase", + "5": "Pulse" + }, + "label": "Light LED Strip Effect", + "description": "Light LED Strip Effect. 0 = Off, 1 = Solid, 2 = Slow Blink, 3 = Fast Blink, 4 = Chase, 5 = Pulse Default: 0", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 24, + "propertyKey": 16711680, + "propertyName": "Light LED Strip Effect Duration", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 255, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Light LED Strip Effect Duration", + "description": "Light LED Strip Effect Duration. 1 to 60 = seconds, 61 to 120 minutes, 121 - 254 = hours, 255 = Indefinitely Default: 0", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 24, + "propertyKey": 65280, + "propertyName": "Light LED Strip Effect Intensity", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 9, + "default": 3, + "format": 1, + "allowManualEntry": true, + "label": "Light LED Strip Effect Intensity", + "description": "Light LED Strip Effect Intensity. 0 to 9. 0 = dim, 9 = bright Default: 3", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 24, + "propertyKey": 255, + "propertyName": "Light LED Strip Effect Color", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 255, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Light LED Strip Effect Color", + "description": "Light LED Strip Effect Color. Color - 0 - 255. Hue color wheel. value/255 * 360 = Hue color wheel value Range: 0-255 Default: 0", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 25, + "propertyKey": 2130706432, + "propertyName": "Fan LED Strip Effect", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 5, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Off", + "1": "Solid", + "2": "Slow Blink", + "3": "Fast Blink", + "4": "Chase", + "5": "Pulse" + }, + "label": "Fan LED Strip Effect", + "description": "Fan LED Strip Effect. 0 = Off, 1 = Solid, 2 = Slow Blink, 3 = Fast Blink, 4 = Chase, 5 = Pulse Default: 0", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 25, + "propertyKey": 16711680, + "propertyName": "Fan LED Strip Effect Duration", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 255, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Fan LED Strip Effect Duration", + "description": "Fan LED Strip Duration. 1 to 60 = seconds, 61 to 120 minutes, 121 - 254 = hours, 255 = Indefinitely Default: 0", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 25, + "propertyKey": 65280, + "propertyName": "Fan LED Strip Effect Intensity", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 9, + "default": 3, + "format": 1, + "allowManualEntry": true, + "label": "Fan LED Strip Effect Intensity", + "description": "Fan LED Strip Intensity 0 to 9. 0 = dim, 9 = bright Default: 3", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 25, + "propertyKey": 255, + "propertyName": "Fan LED Strip Effect Color", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 255, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Fan LED Strip Effect Color", + "description": "Fan LED Color 0 - 255. Hue color wheel. value/255 * 360 = Hue color wheel value Range: 0-255 Default: 0", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 51, + "propertyName": "Instant On", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 1, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Enabled (no delay)", + "1": "Disabled (700ms delay)" + }, + "label": "Instant On", + "description": "Enables instant on (ie: disables the 700ms button delay). Note, if you disable the delay, it will also disable scene control except for the following: Light on/off pressed, held, released, Fan on/off pressed, held, released & light up/down fan up/down pressed (firmware 1.36+).", + "isFromConfig": true + } + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "7.13" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": [ + "1.34" + ] + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version" + } + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "sdkVersion", + "propertyName": "sdkVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "7.13.4" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "applicationFrameworkAPIVersion", + "propertyName": "applicationFrameworkAPIVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "10.13.4" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "applicationFrameworkBuildNumber", + "propertyName": "applicationFrameworkBuildNumber", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 310 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hostInterfaceVersion", + "propertyName": "hostInterfaceVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "unused" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hostInterfaceBuildNumber", + "propertyName": "hostInterfaceBuildNumber", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "zWaveProtocolVersion", + "propertyName": "zWaveProtocolVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "7.13.4" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "zWaveProtocolBuildNumber", + "propertyName": "zWaveProtocolBuildNumber", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 310 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "applicationVersion", + "propertyName": "applicationVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "1.34.1" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "applicationBuildNumber", + "propertyName": "applicationBuildNumber", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 43707 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 798 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 14 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "local", + "propertyName": "local", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Local protection state", + "states": { + "0": "Unprotected", + "1": "ProtectedBySequence", + "2": "NoOperationPossible" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "rf", + "propertyName": "rf", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "RF protection state", + "states": { + "0": "Unprotected", + "1": "NoControl", + "2": "NoResponse" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "exclusiveControlNodeId", + "propertyName": "exclusiveControlNodeId", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + } + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "timeout", + "propertyName": "timeout", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + } + }, + { + "endpoint": 0, + "commandClass": 91, + "commandClassName": "Central Scene", + "property": "slowRefresh", + "propertyName": "slowRefresh", + "ccVersion": 3, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Send held down notifications at a slow rate", + "description": "When this is true, KeyHeldDown notifications are sent every 55s. When this is false, the notifications are sent every 200ms." + } + }, + { + "endpoint": 0, + "commandClass": 91, + "commandClassName": "Central Scene", + "property": "scene", + "propertyKey": "001", + "propertyName": "scene", + "propertyKeyName": "001", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Scene 001", + "states": { + "0": "KeyPressed", + "1": "KeyReleased", + "2": "KeyHeldDown", + "3": "KeyPressed2x", + "4": "KeyPressed3x", + "5": "KeyPressed4x", + "6": "KeyPressed5x" + } + } + }, + { + "endpoint": 0, + "commandClass": 91, + "commandClassName": "Central Scene", + "property": "scene", + "propertyKey": "002", + "propertyName": "scene", + "propertyKeyName": "002", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Scene 002", + "states": { + "0": "KeyPressed", + "1": "KeyReleased", + "2": "KeyHeldDown", + "3": "KeyPressed2x", + "4": "KeyPressed3x", + "5": "KeyPressed4x", + "6": "KeyPressed5x" + } + } + }, + { + "endpoint": 0, + "commandClass": 91, + "commandClassName": "Central Scene", + "property": "scene", + "propertyKey": "003", + "propertyName": "scene", + "propertyKeyName": "003", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Scene 003", + "states": { + "0": "KeyPressed" + } + } + }, + { + "endpoint": 0, + "commandClass": 91, + "commandClassName": "Central Scene", + "property": "scene", + "propertyKey": "004", + "propertyName": "scene", + "propertyKeyName": "004", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Scene 004", + "states": { + "0": "KeyPressed" + } + } + }, + { + "endpoint": 0, + "commandClass": 91, + "commandClassName": "Central Scene", + "property": "scene", + "propertyKey": "005", + "propertyName": "scene", + "propertyKeyName": "005", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Scene 005", + "states": { + "0": "KeyPressed" + } + } + }, + { + "endpoint": 0, + "commandClass": 91, + "commandClassName": "Central Scene", + "property": "scene", + "propertyKey": "006", + "propertyName": "scene", + "propertyKeyName": "006", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Scene 006", + "states": { + "0": "KeyPressed" + } + } + }, + { + "endpoint": 0, + "commandClass": 135, + "commandClassName": "Indicator", + "property": 80, + "propertyKey": 3, + "propertyName": "Node Identify", + "propertyKeyName": "On/Off Period: Duration", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Node Identify - On/Off Period: Duration", + "description": "Sets the duration of an on/off period in 1/10th seconds. Must be set together with \"On/Off Cycle Count\"", + "ccSpecific": { + "indicatorId": 80, + "propertyId": 3 + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 135, + "commandClassName": "Indicator", + "property": 80, + "propertyKey": 4, + "propertyName": "Node Identify", + "propertyKeyName": "On/Off Cycle Count", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Node Identify - On/Off Cycle Count", + "description": "Sets the number of on/off periods. 0xff means infinite. Must be set together with \"On/Off Period duration\"", + "ccSpecific": { + "indicatorId": 80, + "propertyId": 4 + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 135, + "commandClassName": "Indicator", + "property": 80, + "propertyKey": 5, + "propertyName": "Node Identify", + "propertyKeyName": "On/Off Period: On time", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Node Identify - On/Off Period: On time", + "description": "This property is used to set the length of the On time during an On/Off period. It allows asymetic On/Off periods. The value 0x00 MUST represent symmetric On/Off period (On time equal to Off time)", + "ccSpecific": { + "indicatorId": 80, + "propertyId": 5 + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "value", + "propertyKey": 65537, + "propertyName": "value", + "propertyKeyName": "Electric_kWh_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [kWh]", + "unit": "kWh", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 0 + } + }, + "value": 78.057 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "deltaTime", + "propertyKey": 65537, + "propertyName": "deltaTime", + "propertyKeyName": "Electric_kWh_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [kWh] (prev. time delta)", + "unit": "s", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 0 + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "value", + "propertyKey": 66049, + "propertyName": "value", + "propertyKeyName": "Electric_W_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [W]", + "unit": "W", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 2 + } + }, + "value": 0.4 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "deltaTime", + "propertyKey": 66049, + "propertyName": "deltaTime", + "propertyKeyName": "Electric_W_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [W] (prev. time delta)", + "unit": "s", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 2 + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "reset", + "propertyName": "reset", + "ccVersion": 3, + "metadata": { + "type": "boolean", + "readable": false, + "writeable": true, + "label": "Reset accumulated values" + } + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "previousValue", + "propertyKey": 65537, + "propertyName": "previousValue", + "propertyKeyName": "Electric_kWh_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [kWh] (prev. value)", + "unit": "kWh", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 0 + } + } + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "previousValue", + "propertyKey": 66049, + "propertyName": "previousValue", + "propertyKeyName": "Electric_W_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [W] (prev. value)", + "unit": "W", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 2 + } + } + }, + { + "endpoint": 1, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 99, + "label": "Target value" + }, + "value": 0 + }, + { + "endpoint": 1, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "duration", + "propertyName": "duration", + "ccVersion": 4, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Transition duration" + }, + "value": { + "value": 0, + "unit": "seconds" + } + }, + { + "endpoint": 1, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 99, + "label": "Current value" + }, + "value": 0 + }, + { + "endpoint": 1, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Up", + "propertyName": "Up", + "ccVersion": 4, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Up)", + "ccSpecific": { + "switchType": 2 + } + } + }, + { + "endpoint": 1, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Down", + "propertyName": "Down", + "ccVersion": 4, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Down)", + "ccSpecific": { + "switchType": 2 + } + } + }, + { + "endpoint": 2, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 99, + "label": "Target value" + }, + "value": 0 + }, + { + "endpoint": 2, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "duration", + "propertyName": "duration", + "ccVersion": 4, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Transition duration" + }, + "value": { + "value": 0, + "unit": "seconds" + } + }, + { + "endpoint": 2, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 99, + "label": "Current value" + }, + "value": 0 + }, + { + "endpoint": 2, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Up", + "propertyName": "Up", + "ccVersion": 4, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Up)", + "ccSpecific": { + "switchType": 2 + } + } + }, + { + "endpoint": 2, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Down", + "propertyName": "Down", + "ccVersion": 4, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Down)", + "ccSpecific": { + "switchType": 2 + } + } + } + ] + } \ No newline at end of file From 17401cbc29ae7ba07b4d4fa0ea7fad3acc1ae71f Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 4 Mar 2021 14:16:24 +0100 Subject: [PATCH 151/831] Initial automation tracing (#46755) * Initial prototype of automation tracing * Small fixes * Lint * Move trace helpers to its own file * Improve trace for state and numeric_state conditions * Tweaks + apply suggestions from code review * Index traces by automation_id, trace while script is running * Refactor condition tracing * Improve WS API to get traces for single automation * Add tests * Fix imports * Fix imports * Address review comments * Cap logging of loops * Remove unused ContextVar action_config --- .../components/automation/__init__.py | 261 +++++++++++--- homeassistant/components/automation/config.py | 18 + homeassistant/components/config/automation.py | 27 ++ homeassistant/components/script/__init__.py | 11 +- homeassistant/helpers/condition.py | 220 +++++++++++- homeassistant/helpers/script.py | 206 +++++++++-- homeassistant/helpers/trace.py | 78 +++++ tests/components/config/test_automation.py | 161 +++++++++ tests/helpers/test_condition.py | 330 ++++++++++++++++++ tests/helpers/test_script.py | 43 ++- 10 files changed, 1255 insertions(+), 100 deletions(-) create mode 100644 homeassistant/helpers/trace.py diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 1b1a927d147..acb28df05b0 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -1,4 +1,6 @@ """Allow to set up simple automation rules via the config file.""" +from collections import deque +from contextlib import contextmanager import logging from typing import Any, Awaitable, Callable, Dict, List, Optional, Set, Union, cast @@ -39,6 +41,11 @@ from homeassistant.exceptions import ( HomeAssistantError, ) from homeassistant.helpers import condition, extract_domain_configs, template +from homeassistant.helpers.condition import ( + condition_path, + condition_trace_clear, + condition_trace_get, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent @@ -50,17 +57,22 @@ from homeassistant.helpers.script import ( CONF_MAX, CONF_MAX_EXCEEDED, Script, + action_path, + action_trace_clear, + action_trace_get, ) from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.trigger import async_initialize_triggers from homeassistant.helpers.typing import TemplateVarsType from homeassistant.loader import bind_hass +from homeassistant.util import dt as dt_util from homeassistant.util.dt import parse_datetime +from .config import AutomationConfig, async_validate_config_item + # Not used except by packages to check config structure from .config import PLATFORM_SCHEMA # noqa: F401 -from .config import async_validate_config_item from .const import ( CONF_ACTION, CONF_INITIAL_STATE, @@ -90,6 +102,10 @@ ATTR_SOURCE = "source" ATTR_VARIABLES = "variables" SERVICE_TRIGGER = "trigger" +DATA_AUTOMATION_TRACE = "automation_trace" +STORED_TRACES = 5 # Stored traces per automation + +_LOGGER = logging.getLogger(__name__) AutomationActionType = Callable[[HomeAssistant, TemplateVarsType], Awaitable[None]] @@ -166,8 +182,9 @@ def devices_in_automation(hass: HomeAssistant, entity_id: str) -> List[str]: async def async_setup(hass, config): - """Set up the automation.""" + """Set up all automations.""" hass.data[DOMAIN] = component = EntityComponent(LOGGER, DOMAIN, hass) + hass.data.setdefault(DATA_AUTOMATION_TRACE, {}) # To register the automation blueprints async_get_blueprints(hass) @@ -176,7 +193,7 @@ async def async_setup(hass, config): await async_get_blueprints(hass).async_populate() async def trigger_service_handler(entity, service_call): - """Handle automation triggers.""" + """Handle forced automation trigger, e.g. from frontend.""" await entity.async_trigger( service_call.data[ATTR_VARIABLES], skip_condition=service_call.data[CONF_SKIP_CONDITION], @@ -215,6 +232,103 @@ async def async_setup(hass, config): return True +class AutomationTrace: + """Container for automation trace.""" + + def __init__(self, unique_id, config, trigger, context, action_trace): + """Container for automation trace.""" + self._action_trace = action_trace + self._condition_trace = None + self._config = config + self._context = context + self._error = None + self._state = "running" + self._timestamp_finish = None + self._timestamp_start = dt_util.utcnow() + self._trigger = trigger + self._unique_id = unique_id + self._variables = None + + def set_error(self, ex): + """Set error.""" + self._error = ex + + def set_variables(self, variables): + """Set variables.""" + self._variables = variables + + def set_condition_trace(self, condition_trace): + """Set condition trace.""" + self._condition_trace = condition_trace + + def finished(self): + """Set finish time.""" + self._timestamp_finish = dt_util.utcnow() + self._state = "stopped" + + def as_dict(self): + """Return dictionary version of this AutomationTrace.""" + + action_traces = {} + condition_traces = {} + for key, trace_list in self._action_trace.items(): + action_traces[key] = [item.as_dict() for item in trace_list] + + if self._condition_trace: + for key, trace_list in self._condition_trace.items(): + condition_traces[key] = [item.as_dict() for item in trace_list] + + result = { + "action_trace": action_traces, + "condition_trace": condition_traces, + "config": self._config, + "context": self._context, + "state": self._state, + "timestamp": { + "start": self._timestamp_start, + "finish": self._timestamp_finish, + }, + "trigger": self._trigger, + "unique_id": self._unique_id, + "variables": self._variables, + } + if self._error is not None: + result["error"] = str(self._error) + return result + + +@contextmanager +def trace_automation(hass, unique_id, config, trigger, context): + """Trace action execution of automation with automation_id.""" + action_trace_clear() + action_trace = action_trace_get() + automation_trace = AutomationTrace( + unique_id, config, trigger, context, action_trace + ) + + if unique_id: + if unique_id not in hass.data[DATA_AUTOMATION_TRACE]: + hass.data[DATA_AUTOMATION_TRACE][unique_id] = deque([], STORED_TRACES) + traces = hass.data[DATA_AUTOMATION_TRACE][unique_id] + traces.append(automation_trace) + + try: + yield automation_trace + except Exception as ex: # pylint: disable=broad-except + if unique_id: + automation_trace.set_error(ex) + raise ex + finally: + if unique_id: + automation_trace.finished() + _LOGGER.debug( + "Automation finished. Summary:\n\ttrigger: %s\n\tcondition: %s\n\taction: %s", + automation_trace._trigger, # pylint: disable=protected-access + automation_trace._condition_trace, # pylint: disable=protected-access + action_trace, + ) + + class AutomationEntity(ToggleEntity, RestoreEntity): """Entity to show status of entity.""" @@ -228,6 +342,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity): initial_state, variables, trigger_variables, + raw_config, ): """Initialize an automation entity.""" self._id = automation_id @@ -244,6 +359,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity): self._logger = LOGGER self._variables: ScriptVariables = variables self._trigger_variables: ScriptVariables = trigger_variables + self._raw_config = raw_config @property def name(self): @@ -374,52 +490,73 @@ class AutomationEntity(ToggleEntity, RestoreEntity): This method is a coroutine. """ - if self._variables: - try: - variables = self._variables.async_render(self.hass, run_variables) - except template.TemplateError as err: - self._logger.error("Error rendering variables: %s", err) + reason = "" + if "trigger" in run_variables and "description" in run_variables["trigger"]: + reason = f' by {run_variables["trigger"]["description"]}' + self._logger.debug("Automation triggered%s", reason) + + trigger = run_variables["trigger"] if "trigger" in run_variables else None + with trace_automation( + self.hass, self.unique_id, self._raw_config, trigger, context + ) as automation_trace: + if self._variables: + try: + variables = self._variables.async_render(self.hass, run_variables) + except template.TemplateError as err: + self._logger.error("Error rendering variables: %s", err) + automation_trace.set_error(err) + return + else: + variables = run_variables + automation_trace.set_variables(variables) + + if ( + not skip_condition + and self._cond_func is not None + and not self._cond_func(variables) + ): + self._logger.debug( + "Conditions not met, aborting automation. Condition summary: %s", + condition_trace_get(), + ) + automation_trace.set_condition_trace(condition_trace_get()) return - else: - variables = run_variables + automation_trace.set_condition_trace(condition_trace_get()) + condition_trace_clear() - if ( - not skip_condition - and self._cond_func is not None - and not self._cond_func(variables) - ): - return + # Create a new context referring to the old context. + parent_id = None if context is None else context.id + trigger_context = Context(parent_id=parent_id) - # Create a new context referring to the old context. - parent_id = None if context is None else context.id - trigger_context = Context(parent_id=parent_id) + self.async_set_context(trigger_context) + event_data = { + ATTR_NAME: self._name, + ATTR_ENTITY_ID: self.entity_id, + } + if "trigger" in variables and "description" in variables["trigger"]: + event_data[ATTR_SOURCE] = variables["trigger"]["description"] - self.async_set_context(trigger_context) - event_data = { - ATTR_NAME: self._name, - ATTR_ENTITY_ID: self.entity_id, - } - if "trigger" in variables and "description" in variables["trigger"]: - event_data[ATTR_SOURCE] = variables["trigger"]["description"] + @callback + def started_action(): + self.hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, event_data, context=trigger_context + ) - @callback - def started_action(): - self.hass.bus.async_fire( - EVENT_AUTOMATION_TRIGGERED, event_data, context=trigger_context - ) - - try: - await self.action_script.async_run( - variables, trigger_context, started_action - ) - except (vol.Invalid, HomeAssistantError) as err: - self._logger.error( - "Error while executing automation %s: %s", - self.entity_id, - err, - ) - except Exception: # pylint: disable=broad-except - self._logger.exception("While executing automation %s", self.entity_id) + try: + with action_path("action"): + await self.action_script.async_run( + variables, trigger_context, started_action + ) + except (vol.Invalid, HomeAssistantError) as err: + self._logger.error( + "Error while executing automation %s: %s", + self.entity_id, + err, + ) + automation_trace.set_error(err) + except Exception as err: # pylint: disable=broad-except + self._logger.exception("While executing automation %s", self.entity_id) + automation_trace.set_error(err) async def async_will_remove_from_hass(self): """Remove listeners when removing automation from Home Assistant.""" @@ -527,16 +664,16 @@ async def _async_process_config( ] for list_no, config_block in enumerate(conf): + raw_config = None if isinstance(config_block, blueprint.BlueprintInputs): # type: ignore blueprints_used = True blueprint_inputs = config_block try: + raw_config = blueprint_inputs.async_substitute() config_block = cast( Dict[str, Any], - await async_validate_config_item( - hass, blueprint_inputs.async_substitute() - ), + await async_validate_config_item(hass, raw_config), ) except vol.Invalid as err: LOGGER.error( @@ -546,6 +683,8 @@ async def _async_process_config( humanize_error(config_block, err), ) continue + else: + raw_config = cast(AutomationConfig, config_block).raw_config automation_id = config_block.get(CONF_ID) name = config_block.get(CONF_ALIAS) or f"{config_key} {list_no}" @@ -596,6 +735,7 @@ async def _async_process_config( initial_state, variables, config_block.get(CONF_TRIGGER_VARIABLES), + raw_config, ) entities.append(entity) @@ -623,8 +763,9 @@ async def _async_process_if(hass, name, config, p_config): errors = [] for index, check in enumerate(checks): try: - if not check(hass, variables): - return False + with condition_path(["condition", str(index)]): + if not check(hass, variables): + return False except ConditionError as ex: errors.append( ConditionErrorIndex( @@ -672,3 +813,25 @@ def _trigger_extract_entities(trigger_conf: dict) -> List[str]: return ["sun.sun"] return [] + + +@callback +def get_debug_traces_for_automation(hass, automation_id): + """Return a serializable list of debug traces for an automation.""" + traces = [] + + for trace in hass.data[DATA_AUTOMATION_TRACE].get(automation_id, []): + traces.append(trace.as_dict()) + + return traces + + +@callback +def get_debug_traces(hass): + """Return a serializable list of debug traces.""" + traces = {} + + for automation_id in hass.data[DATA_AUTOMATION_TRACE]: + traces[automation_id] = get_debug_traces_for_automation(hass, automation_id) + + return traces diff --git a/homeassistant/components/automation/config.py b/homeassistant/components/automation/config.py index 32ad92cb86e..5abff8fe974 100644 --- a/homeassistant/components/automation/config.py +++ b/homeassistant/components/automation/config.py @@ -79,8 +79,21 @@ async def async_validate_config_item(hass, config, full_config=None): return config +class AutomationConfig(dict): + """Dummy class to allow adding attributes.""" + + raw_config = None + + async def _try_async_validate_config_item(hass, config, full_config=None): """Validate config item.""" + raw_config = None + try: + raw_config = dict(config) + except ValueError: + # Invalid config + pass + try: config = await async_validate_config_item(hass, config, full_config) except ( @@ -92,6 +105,11 @@ async def _try_async_validate_config_item(hass, config, full_config=None): async_log_exception(ex, DOMAIN, full_config or config, hass) return None + if isinstance(config, blueprint.BlueprintInputs): + return config + + config = AutomationConfig(config) + config.raw_config = raw_config return config diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index 01e22297c0d..23baa0c8843 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -2,6 +2,13 @@ from collections import OrderedDict import uuid +import voluptuous as vol + +from homeassistant.components import websocket_api +from homeassistant.components.automation import ( + get_debug_traces, + get_debug_traces_for_automation, +) from homeassistant.components.automation.config import ( DOMAIN, PLATFORM_SCHEMA, @@ -17,6 +24,8 @@ from . import ACTION_DELETE, EditIdBasedConfigView async def async_setup(hass): """Set up the Automation config API.""" + websocket_api.async_register_command(hass, websocket_automation_trace) + async def hook(action, config_key): """post_write_hook for Config View that reloads automations.""" await hass.services.async_call(DOMAIN, SERVICE_RELOAD) @@ -80,3 +89,21 @@ class EditAutomationConfigView(EditIdBasedConfigView): updated_value.update(cur_value) updated_value.update(new_value) data[index] = updated_value + + +@websocket_api.websocket_command( + {vol.Required("type"): "automation/trace", vol.Optional("automation_id"): str} +) +@websocket_api.async_response +async def websocket_automation_trace(hass, connection, msg): + """Get automation traces.""" + automation_id = msg.get("automation_id") + + if not automation_id: + automation_traces = get_debug_traces(hass) + else: + automation_traces = { + automation_id: get_debug_traces_for_automation(hass, automation_id) + } + + connection.send_result(msg["id"], automation_traces) diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index 5de3cb8264f..429e97230ce 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -308,7 +308,11 @@ class ScriptEntity(ToggleEntity): self._changed.set() async def async_turn_on(self, **kwargs): - """Turn the script on.""" + """Run the script. + + Depending on the script's run mode, this may do nothing, restart the script or + fire an additional parallel run. + """ variables = kwargs.get("variables") context = kwargs.get("context") wait = kwargs.get("wait", True) @@ -331,7 +335,10 @@ class ScriptEntity(ToggleEntity): await self._changed.wait() async def async_turn_off(self, **kwargs): - """Turn script off.""" + """Stop running the script. + + If multiple runs are in progress, all will be stopped. + """ await self.script.async_stop() async def async_will_remove_from_hass(self): diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 40087650141..1abbf550bb1 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -1,12 +1,25 @@ """Offer reusable conditions.""" import asyncio from collections import deque +from contextlib import contextmanager +from contextvars import ContextVar from datetime import datetime, timedelta import functools as ft import logging import re import sys -from typing import Any, Callable, Container, List, Optional, Set, Union, cast +from typing import ( + Any, + Callable, + Container, + Dict, + Generator, + List, + Optional, + Set, + Union, + cast, +) from homeassistant.components import zone as zone_cmp from homeassistant.components.device_automation import ( @@ -51,6 +64,14 @@ from homeassistant.helpers.typing import ConfigType, TemplateVarsType from homeassistant.util.async_ import run_callback_threadsafe import homeassistant.util.dt as dt_util +from .trace import ( + TraceElement, + trace_append_element, + trace_stack_pop, + trace_stack_push, + trace_stack_top, +) + FROM_CONFIG_FORMAT = "{}_from_config" ASYNC_FROM_CONFIG_FORMAT = "async_{}_from_config" @@ -63,6 +84,126 @@ INPUT_ENTITY_ID = re.compile( ConditionCheckerType = Callable[[HomeAssistant, TemplateVarsType], bool] +# Context variables for tracing +# Trace of condition being evaluated +condition_trace = ContextVar("condition_trace", default=None) +# Stack of TraceElements +condition_trace_stack: ContextVar[Optional[List[TraceElement]]] = ContextVar( + "condition_trace_stack", default=None +) +# Current location in config tree +condition_path_stack: ContextVar[Optional[List[str]]] = ContextVar( + "condition_path_stack", default=None +) + + +def condition_trace_stack_push(node: TraceElement) -> None: + """Push a TraceElement to the top of the trace stack.""" + trace_stack_push(condition_trace_stack, node) + + +def condition_trace_stack_pop() -> None: + """Remove the top element from the trace stack.""" + trace_stack_pop(condition_trace_stack) + + +def condition_trace_stack_top() -> Optional[TraceElement]: + """Return the element at the top of the trace stack.""" + return cast(Optional[TraceElement], trace_stack_top(condition_trace_stack)) + + +def condition_path_push(suffix: Union[str, List[str]]) -> int: + """Go deeper in the config tree.""" + if isinstance(suffix, str): + suffix = [suffix] + for node in suffix: + trace_stack_push(condition_path_stack, node) + return len(suffix) + + +def condition_path_pop(count: int) -> None: + """Go n levels up in the config tree.""" + for _ in range(count): + trace_stack_pop(condition_path_stack) + + +def condition_path_get() -> str: + """Return a string representing the current location in the config tree.""" + path = condition_path_stack.get() + if not path: + return "" + return "/".join(path) + + +def condition_trace_get() -> Optional[Dict[str, TraceElement]]: + """Return the trace of the condition that was evaluated.""" + return condition_trace.get() + + +def condition_trace_clear() -> None: + """Clear the condition trace.""" + condition_trace.set(None) + condition_trace_stack.set(None) + condition_path_stack.set(None) + + +def condition_trace_append(variables: TemplateVarsType, path: str) -> TraceElement: + """Append a TraceElement to trace[path].""" + trace_element = TraceElement(variables) + trace_append_element(condition_trace, trace_element, path) + return trace_element + + +def condition_trace_set_result(result: bool, **kwargs: Any) -> None: + """Set the result of TraceElement at the top of the stack.""" + node = condition_trace_stack_top() + + # The condition function may be called directly, in which case tracing + # is not setup + if not node: + return + + node.set_result(result=result, **kwargs) + + +@contextmanager +def trace_condition(variables: TemplateVarsType) -> Generator: + """Trace condition evaluation.""" + trace_element = condition_trace_append(variables, condition_path_get()) + condition_trace_stack_push(trace_element) + try: + yield trace_element + except Exception as ex: # pylint: disable=broad-except + trace_element.set_error(ex) + raise ex + finally: + condition_trace_stack_pop() + + +@contextmanager +def condition_path(suffix: Union[str, List[str]]) -> Generator: + """Go deeper in the config tree.""" + count = condition_path_push(suffix) + try: + yield + finally: + condition_path_pop(count) + + +def trace_condition_function(condition: ConditionCheckerType) -> ConditionCheckerType: + """Wrap a condition function to enable basic tracing.""" + + @ft.wraps(condition) + def wrapper(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: + """Trace condition.""" + with trace_condition(variables): + result = condition(hass, variables) + condition_trace_set_result(result) + return result + + return wrapper + + async def async_from_config( hass: HomeAssistant, config: Union[ConfigType, Template], @@ -111,6 +252,7 @@ async def async_and_from_config( await async_from_config(hass, entry, False) for entry in config["conditions"] ] + @trace_condition_function def if_and_condition( hass: HomeAssistant, variables: TemplateVarsType = None ) -> bool: @@ -118,8 +260,9 @@ async def async_and_from_config( errors = [] for index, check in enumerate(checks): try: - if not check(hass, variables): - return False + with condition_path(["conditions", str(index)]): + if not check(hass, variables): + return False except ConditionError as ex: errors.append( ConditionErrorIndex("and", index=index, total=len(checks), error=ex) @@ -144,6 +287,7 @@ async def async_or_from_config( await async_from_config(hass, entry, False) for entry in config["conditions"] ] + @trace_condition_function def if_or_condition( hass: HomeAssistant, variables: TemplateVarsType = None ) -> bool: @@ -151,8 +295,9 @@ async def async_or_from_config( errors = [] for index, check in enumerate(checks): try: - if check(hass, variables): - return True + with condition_path(["conditions", str(index)]): + if check(hass, variables): + return True except ConditionError as ex: errors.append( ConditionErrorIndex("or", index=index, total=len(checks), error=ex) @@ -177,6 +322,7 @@ async def async_not_from_config( await async_from_config(hass, entry, False) for entry in config["conditions"] ] + @trace_condition_function def if_not_condition( hass: HomeAssistant, variables: TemplateVarsType = None ) -> bool: @@ -184,8 +330,9 @@ async def async_not_from_config( errors = [] for index, check in enumerate(checks): try: - if check(hass, variables): - return False + with condition_path(["conditions", str(index)]): + if check(hass, variables): + return False except ConditionError as ex: errors.append( ConditionErrorIndex("not", index=index, total=len(checks), error=ex) @@ -290,6 +437,11 @@ def async_numeric_state( ) try: if fvalue >= float(below_entity.state): + condition_trace_set_result( + False, + state=fvalue, + wanted_state_below=float(below_entity.state), + ) return False except (ValueError, TypeError) as ex: raise ConditionErrorMessage( @@ -297,6 +449,7 @@ def async_numeric_state( f"the 'below' entity {below} state '{below_entity.state}' cannot be processed as a number", ) from ex elif fvalue >= below: + condition_trace_set_result(False, state=fvalue, wanted_state_below=below) return False if above is not None: @@ -311,6 +464,11 @@ def async_numeric_state( ) try: if fvalue <= float(above_entity.state): + condition_trace_set_result( + False, + state=fvalue, + wanted_state_above=float(above_entity.state), + ) return False except (ValueError, TypeError) as ex: raise ConditionErrorMessage( @@ -318,8 +476,10 @@ def async_numeric_state( f"the 'above' entity {above} state '{above_entity.state}' cannot be processed as a number", ) from ex elif fvalue <= above: + condition_trace_set_result(False, state=fvalue, wanted_state_above=above) return False + condition_trace_set_result(True, state=fvalue) return True @@ -335,6 +495,7 @@ def async_numeric_state_from_config( above = config.get(CONF_ABOVE) value_template = config.get(CONF_VALUE_TEMPLATE) + @trace_condition_function def if_numeric_state( hass: HomeAssistant, variables: TemplateVarsType = None ) -> bool: @@ -345,10 +506,19 @@ def async_numeric_state_from_config( errors = [] for index, entity_id in enumerate(entity_ids): try: - if not async_numeric_state( - hass, entity_id, below, above, value_template, variables, attribute + with condition_path(["entity_id", str(index)]), trace_condition( + variables ): - return False + if not async_numeric_state( + hass, + entity_id, + below, + above, + value_template, + variables, + attribute, + ): + return False except ConditionError as ex: errors.append( ConditionErrorIndex( @@ -421,9 +591,13 @@ def state( break if for_period is None or not is_state: + condition_trace_set_result(is_state, state=value, wanted_state=state_value) return is_state - return dt_util.utcnow() - for_period > entity.last_changed + duration = dt_util.utcnow() - for_period + duration_ok = duration > entity.last_changed + condition_trace_set_result(duration_ok, state=value, duration=duration) + return duration_ok def state_from_config( @@ -440,13 +614,17 @@ def state_from_config( if not isinstance(req_states, list): req_states = [req_states] + @trace_condition_function def if_state(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: """Test if condition.""" errors = [] for index, entity_id in enumerate(entity_ids): try: - if not state(hass, entity_id, req_states, for_period, attribute): - return False + with condition_path(["entity_id", str(index)]), trace_condition( + variables + ): + if not state(hass, entity_id, req_states, for_period, attribute): + return False except ConditionError as ex: errors.append( ConditionErrorIndex( @@ -529,11 +707,12 @@ def sun_from_config( before_offset = config.get("before_offset") after_offset = config.get("after_offset") - def time_if(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: + @trace_condition_function + def sun_if(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: """Validate time based if-condition.""" return sun(hass, before, after, before_offset, after_offset) - return time_if + return sun_if def template( @@ -565,6 +744,7 @@ def async_template_from_config( config = cv.TEMPLATE_CONDITION_SCHEMA(config) value_template = cast(Template, config.get(CONF_VALUE_TEMPLATE)) + @trace_condition_function def template_if(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: """Validate template based if-condition.""" value_template.hass = hass @@ -645,6 +825,7 @@ def time_from_config( after = config.get(CONF_AFTER) weekday = config.get(CONF_WEEKDAY) + @trace_condition_function def time_if(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: """Validate time based if-condition.""" return time(hass, before, after, weekday) @@ -710,6 +891,7 @@ def zone_from_config( entity_ids = config.get(CONF_ENTITY_ID, []) zone_entity_ids = config.get(CONF_ZONE, []) + @trace_condition_function def if_in_zone(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: """Test if condition.""" errors = [] @@ -750,9 +932,11 @@ async def async_device_from_config( platform = await async_get_device_automation_platform( hass, config[CONF_DOMAIN], "condition" ) - return cast( - ConditionCheckerType, - platform.async_condition_from_config(config, config_validation), # type: ignore + return trace_condition_function( + cast( + ConditionCheckerType, + platform.async_condition_from_config(config, config_validation), # type: ignore + ) ) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index e4eb0d4a901..aaed10f7814 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -1,5 +1,7 @@ """Helpers to execute scripts.""" import asyncio +from contextlib import contextmanager +from contextvars import ContextVar from datetime import datetime, timedelta from functools import partial import itertools @@ -63,6 +65,12 @@ from homeassistant.core import ( callback, ) from homeassistant.helpers import condition, config_validation as cv, service, template +from homeassistant.helpers.condition import ( + condition_path, + condition_trace_clear, + condition_trace_get, + trace_condition_function, +) from homeassistant.helpers.event import async_call_later, async_track_template from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.trigger import ( @@ -73,6 +81,14 @@ from homeassistant.helpers.typing import ConfigType from homeassistant.util import slugify from homeassistant.util.dt import utcnow +from .trace import ( + TraceElement, + trace_append_element, + trace_stack_pop, + trace_stack_push, + trace_stack_top, +) + # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs SCRIPT_MODE_PARALLEL = "parallel" @@ -108,6 +124,115 @@ _TIMEOUT_MSG = "Timeout reached, abort script." _SHUTDOWN_MAX_WAIT = 60 +ACTION_TRACE_NODE_MAX_LEN = 20 # Max the length of a trace node for repeated actions + +action_trace = ContextVar("action_trace", default=None) +action_trace_stack = ContextVar("action_trace_stack", default=None) +action_path_stack = ContextVar("action_path_stack", default=None) + + +def action_trace_stack_push(node): + """Push a TraceElement to the top of the trace stack.""" + trace_stack_push(action_trace_stack, node) + + +def action_trace_stack_pop(): + """Remove the top element from the trace stack.""" + trace_stack_pop(action_trace_stack) + + +def action_trace_stack_top(): + """Return the element at the top of the trace stack.""" + return trace_stack_top(action_trace_stack) + + +def action_path_push(suffix): + """Go deeper in the config tree.""" + if isinstance(suffix, str): + suffix = [suffix] + for node in suffix: + trace_stack_push(action_path_stack, node) + return len(suffix) + + +def action_path_pop(count): + """Go n levels up in the config tree.""" + for _ in range(count): + trace_stack_pop(action_path_stack) + + +def action_path_get(): + """Return a string representing the current location in the config tree.""" + path = action_path_stack.get() + if not path: + return "" + return "/".join(path) + + +def action_trace_get(): + """Return the trace of the script that was executed.""" + return action_trace.get() + + +def action_trace_clear(): + """Clear the action trace.""" + action_trace.set({}) + action_trace_stack.set(None) + action_path_stack.set(None) + + +def action_trace_append(variables, path): + """Append a TraceElement to trace[path].""" + trace_element = TraceElement(variables) + trace_append_element(action_trace, trace_element, path, ACTION_TRACE_NODE_MAX_LEN) + return trace_element + + +def action_trace_set_result(**kwargs): + """Set the result of TraceElement at the top of the stack.""" + node = action_trace_stack_top() + node.set_result(**kwargs) + + +def action_trace_add_conditions(): + """Add the result of condition evaluation to the action trace.""" + condition_trace = condition_trace_get() + condition_trace_clear() + + if condition_trace is None: + return + + _action_path = action_path_get() + for cond_path, conditions in condition_trace.items(): + path = _action_path + "/" + cond_path if cond_path else _action_path + for cond in conditions: + trace_append_element(action_trace, cond, path) + + +@contextmanager +def trace_action(variables): + """Trace action execution.""" + trace_element = action_trace_append(variables, action_path_get()) + action_trace_stack_push(trace_element) + try: + yield trace_element + except Exception as ex: # pylint: disable=broad-except + trace_element.set_error(ex) + raise ex + finally: + action_trace_stack_pop() + + +@contextmanager +def action_path(suffix): + """Go deeper in the config tree.""" + count = action_path_push(suffix) + try: + yield + finally: + action_path_pop(count) + + def make_script_schema(schema, default_script_mode, extra=vol.PREVENT_EXTRA): """Make a schema for a component that uses the script helper.""" return vol.Schema( @@ -258,16 +383,16 @@ class _ScriptRun: self._finish() async def _async_step(self, log_exceptions): - try: - await getattr( - self, f"_async_{cv.determine_script_action(self._action)}_step" - )() - except Exception as ex: - if not isinstance(ex, (_StopScript, asyncio.CancelledError)) and ( - self._log_exceptions or log_exceptions - ): - self._log_exception(ex) - raise + with action_path(str(self._step)), trace_action(None): + try: + handler = f"_async_{cv.determine_script_action(self._action)}_step" + await getattr(self, handler)() + except Exception as ex: + if not isinstance(ex, (_StopScript, asyncio.CancelledError)) and ( + self._log_exceptions or log_exceptions + ): + self._log_exception(ex) + raise def _finish(self) -> None: self._script._runs.remove(self) # pylint: disable=protected-access @@ -514,15 +639,37 @@ class _ScriptRun: ) cond = await self._async_get_condition(self._action) try: - check = cond(self._hass, self._variables) + with condition_path("condition"): + check = cond(self._hass, self._variables) except exceptions.ConditionError as ex: _LOGGER.warning("Error in 'condition' evaluation:\n%s", ex) check = False self._log("Test condition %s: %s", self._script.last_action, check) + action_trace_set_result(result=check) + action_trace_add_conditions() if not check: raise _StopScript + def _test_conditions(self, conditions, name): + @trace_condition_function + def traced_test_conditions(hass, variables): + try: + with condition_path("conditions"): + for idx, cond in enumerate(conditions): + with condition_path(str(idx)): + if not cond(hass, variables): + return False + except exceptions.ConditionError as ex: + _LOGGER.warning("Error in '%s[%s]' evaluation: %s", name, idx, ex) + return None + + return True + + result = traced_test_conditions(self._hass, self._variables) + action_trace_add_conditions() + return result + async def _async_repeat_step(self): """Repeat a sequence.""" description = self._action.get(CONF_ALIAS, "sequence") @@ -541,7 +688,8 @@ class _ScriptRun: async def async_run_sequence(iteration, extra_msg=""): self._log("Repeating %s: Iteration %i%s", description, iteration, extra_msg) - await self._async_run_script(script) + with action_path(str(self._step)): + await self._async_run_script(script) if CONF_COUNT in repeat: count = repeat[CONF_COUNT] @@ -570,9 +718,9 @@ class _ScriptRun: for iteration in itertools.count(1): set_repeat_var(iteration) try: - if self._stop.is_set() or not all( - cond(self._hass, self._variables) for cond in conditions - ): + if self._stop.is_set(): + break + if not self._test_conditions(conditions, "while"): break except exceptions.ConditionError as ex: _LOGGER.warning("Error in 'while' evaluation:\n%s", ex) @@ -588,9 +736,9 @@ class _ScriptRun: set_repeat_var(iteration) await async_run_sequence(iteration) try: - if self._stop.is_set() or all( - cond(self._hass, self._variables) for cond in conditions - ): + if self._stop.is_set(): + break + if self._test_conditions(conditions, "until") in [True, None]: break except exceptions.ConditionError as ex: _LOGGER.warning("Error in 'until' evaluation:\n%s", ex) @@ -606,18 +754,20 @@ class _ScriptRun: # pylint: disable=protected-access choose_data = await self._script._async_get_choose_data(self._step) - for conditions, script in choose_data["choices"]: - try: - if all( - condition(self._hass, self._variables) for condition in conditions - ): - await self._async_run_script(script) - return - except exceptions.ConditionError as ex: - _LOGGER.warning("Error in 'choose' evaluation:\n%s", ex) + for idx, (conditions, script) in enumerate(choose_data["choices"]): + with action_path(str(idx)): + try: + if self._test_conditions(conditions, "choose"): + action_trace_set_result(choice=idx) + await self._async_run_script(script) + return + except exceptions.ConditionError as ex: + _LOGGER.warning("Error in 'choose' evaluation:\n%s", ex) if choose_data["default"]: - await self._async_run_script(choose_data["default"]) + action_trace_set_result(choice="default") + with action_path("default"): + await self._async_run_script(choose_data["default"]) async def _async_wait_for_trigger_step(self): """Wait for a trigger event.""" diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py new file mode 100644 index 00000000000..450faa0336f --- /dev/null +++ b/homeassistant/helpers/trace.py @@ -0,0 +1,78 @@ +"""Helpers for script and condition tracing.""" +from collections import deque +from contextvars import ContextVar +from typing import Any, Dict, Optional + +from homeassistant.helpers.typing import TemplateVarsType +import homeassistant.util.dt as dt_util + + +def trace_stack_push(trace_stack_var: ContextVar, node: Any) -> None: + """Push an element to the top of a trace stack.""" + trace_stack = trace_stack_var.get() + if trace_stack is None: + trace_stack = [] + trace_stack_var.set(trace_stack) + trace_stack.append(node) + + +def trace_stack_pop(trace_stack_var: ContextVar) -> None: + """Remove the top element from a trace stack.""" + trace_stack = trace_stack_var.get() + trace_stack.pop() + + +def trace_stack_top(trace_stack_var: ContextVar) -> Optional[Any]: + """Return the element at the top of a trace stack.""" + trace_stack = trace_stack_var.get() + return trace_stack[-1] if trace_stack else None + + +class TraceElement: + """Container for trace data.""" + + def __init__(self, variables: TemplateVarsType): + """Container for trace data.""" + self._error: Optional[Exception] = None + self._result: Optional[dict] = None + self._timestamp = dt_util.utcnow() + self._variables = variables + + def __repr__(self) -> str: + """Container for trace data.""" + return str(self.as_dict()) + + def set_error(self, ex: Exception) -> None: + """Set error.""" + self._error = ex + + def set_result(self, **kwargs: Any) -> None: + """Set result.""" + self._result = {**kwargs} + + def as_dict(self) -> Dict[str, Any]: + """Return dictionary version of this TraceElement.""" + result: Dict[str, Any] = {"timestamp": self._timestamp} + # Commented out because we get too many copies of the same data + # result["variables"] = self._variables + if self._error is not None: + result["error"] = str(self._error) + if self._result is not None: + result["result"] = self._result + return result + + +def trace_append_element( + trace_var: ContextVar, + trace_element: TraceElement, + path: str, + maxlen: Optional[int] = None, +) -> None: + """Append a TraceElement to trace[path].""" + trace = trace_var.get() + if trace is None: + trace_var.set({}) + trace = trace_var.get() + if path not in trace: + trace[path] = deque(maxlen=maxlen) + trace[path].append(trace_element) diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index 541cd3068d2..7e0cf9e8e4d 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -164,3 +164,164 @@ async def test_delete_automation(hass, hass_client): assert written[0][0]["id"] == "moon" assert len(ent_reg.entities) == 1 + + +async def test_get_automation_trace(hass, hass_ws_client): + """Test deleting an automation.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": {"service": "test.automation"}, + } + moon_config = { + "id": "moon", + "trigger": [ + {"platform": "event", "event_type": "test_event2"}, + {"platform": "event", "event_type": "test_event3"}, + ], + "condition": { + "condition": "template", + "value_template": "{{ trigger.event.event_type=='test_event2' }}", + }, + "action": {"event": "another_event"}, + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + moon_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + await client.send_json({"id": next_id(), "type": "automation/trace"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {} + + await client.send_json( + {"id": next_id(), "type": "automation/trace", "automation_id": "sun"} + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {"sun": []} + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + + # Get trace + await client.send_json({"id": next_id(), "type": "automation/trace"}) + response = await client.receive_json() + assert response["success"] + assert "moon" not in response["result"] + assert len(response["result"]["sun"]) == 1 + trace = response["result"]["sun"][0] + assert len(trace["action_trace"]) == 1 + assert len(trace["action_trace"]["action/0"]) == 1 + assert trace["action_trace"]["action/0"][0]["error"] + assert "result" not in trace["action_trace"]["action/0"][0] + assert trace["condition_trace"] == {} + assert trace["config"] == sun_config + assert trace["context"] + assert trace["error"] == "Unable to find service test.automation" + assert trace["state"] == "stopped" + assert trace["trigger"]["description"] == "event 'test_event'" + assert trace["unique_id"] == "sun" + assert trace["variables"] + + # Trigger "moon" automation, with passing condition + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # Get trace + await client.send_json( + {"id": next_id(), "type": "automation/trace", "automation_id": "moon"} + ) + response = await client.receive_json() + assert response["success"] + assert "sun" not in response["result"] + assert len(response["result"]["moon"]) == 1 + trace = response["result"]["moon"][0] + assert len(trace["action_trace"]) == 1 + assert len(trace["action_trace"]["action/0"]) == 1 + assert "error" not in trace["action_trace"]["action/0"][0] + assert "result" not in trace["action_trace"]["action/0"][0] + assert len(trace["condition_trace"]) == 1 + assert len(trace["condition_trace"]["condition/0"]) == 1 + assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} + assert trace["config"] == moon_config + assert trace["context"] + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["trigger"]["description"] == "event 'test_event2'" + assert trace["unique_id"] == "moon" + assert trace["variables"] + + # Trigger "moon" automation, with failing condition + hass.bus.async_fire("test_event3") + await hass.async_block_till_done() + + # Get trace + await client.send_json( + {"id": next_id(), "type": "automation/trace", "automation_id": "moon"} + ) + response = await client.receive_json() + assert response["success"] + assert "sun" not in response["result"] + assert len(response["result"]["moon"]) == 2 + trace = response["result"]["moon"][1] + assert len(trace["action_trace"]) == 0 + assert len(trace["condition_trace"]) == 1 + assert len(trace["condition_trace"]["condition/0"]) == 1 + assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": False} + assert trace["config"] == moon_config + assert trace["context"] + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["trigger"]["description"] == "event 'test_event3'" + assert trace["unique_id"] == "moon" + assert trace["variables"] + + # Trigger "moon" automation, with passing condition + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # Get trace + await client.send_json( + {"id": next_id(), "type": "automation/trace", "automation_id": "moon"} + ) + response = await client.receive_json() + assert response["success"] + assert "sun" not in response["result"] + assert len(response["result"]["moon"]) == 3 + trace = response["result"]["moon"][2] + assert len(trace["action_trace"]) == 1 + assert len(trace["action_trace"]["action/0"]) == 1 + assert "error" not in trace["action_trace"]["action/0"][0] + assert "result" not in trace["action_trace"]["action/0"][0] + assert len(trace["condition_trace"]) == 1 + assert len(trace["condition_trace"]["condition/0"]) == 1 + assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} + assert trace["config"] == moon_config + assert trace["context"] + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["trigger"]["description"] == "event 'test_event2'" + assert trace["unique_id"] == "moon" + assert trace["variables"] diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index 5074b6e70c4..b3e950131b0 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -11,6 +11,32 @@ from homeassistant.setup import async_setup_component from homeassistant.util import dt +def assert_element(trace_element, expected_element, path): + """Assert a trace element is as expected. + + Note: Unused variable path is passed to get helpful errors from pytest. + """ + for result_key, result in expected_element.get("result", {}).items(): + assert trace_element._result[result_key] == result + if "error_type" in expected_element: + assert isinstance(trace_element._error, expected_element["error_type"]) + else: + assert trace_element._error is None + + +def assert_condition_trace(expected): + """Assert a trace condition sequence is as expected.""" + condition_trace = condition.condition_trace_get() + condition.condition_trace_clear() + expected_trace_keys = list(expected.keys()) + assert list(condition_trace.keys()) == expected_trace_keys + for trace_key_index, key in enumerate(expected_trace_keys): + assert len(condition_trace[key]) == len(expected[key]) + for index, element in enumerate(expected[key]): + path = f"[{trace_key_index}][{index}]" + assert_element(condition_trace[key][index], element, path) + + async def test_invalid_condition(hass): """Test if invalid condition raises.""" with pytest.raises(HomeAssistantError): @@ -53,15 +79,112 @@ async def test_and_condition(hass): with pytest.raises(ConditionError): test(hass) + assert_condition_trace( + { + "": [{"error_type": ConditionError}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"error_type": ConditionError}], + "conditions/1/entity_id/0": [{"error_type": ConditionError}], + } + ) hass.states.async_set("sensor.temperature", 120) assert not test(hass) + assert_condition_trace( + { + "": [{"result": {"result": False}}], + "conditions/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [{"result": {"result": False}}], + } + ) hass.states.async_set("sensor.temperature", 105) assert not test(hass) + assert_condition_trace( + { + "": [{"result": {"result": False}}], + "conditions/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [{"result": {"result": False}}], + } + ) hass.states.async_set("sensor.temperature", 100) assert test(hass) + assert_condition_trace( + { + "": [{"result": {"result": True}}], + "conditions/0": [{"result": {"result": True}}], + "conditions/0/entity_id/0": [{"result": {"result": True}}], + "conditions/1": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True}}], + } + ) + + +async def test_and_condition_raises(hass): + """Test the 'and' condition.""" + test = await condition.async_from_config( + hass, + { + "alias": "And Condition", + "condition": "and", + "conditions": [ + { + "condition": "state", + "entity_id": "sensor.temperature", + "state": "100", + }, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature2", + "above": 110, + }, + ], + }, + ) + + # All subconditions raise, the AND-condition should raise + with pytest.raises(ConditionError): + test(hass) + assert_condition_trace( + { + "": [{"error_type": ConditionError}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"error_type": ConditionError}], + "conditions/1/entity_id/0": [{"error_type": ConditionError}], + } + ) + + # The first subconditions raises, the second returns True, the AND-condition + # should raise + hass.states.async_set("sensor.temperature2", 120) + with pytest.raises(ConditionError): + test(hass) + assert_condition_trace( + { + "": [{"error_type": ConditionError}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True}}], + } + ) + + # The first subconditions raises, the second returns False, the AND-condition + # should return False + hass.states.async_set("sensor.temperature2", 90) + assert not test(hass) + assert_condition_trace( + { + "": [{"result": {"result": False}}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [{"result": {"result": False}}], + } + ) async def test_and_condition_with_template(hass): @@ -119,15 +242,114 @@ async def test_or_condition(hass): with pytest.raises(ConditionError): test(hass) + assert_condition_trace( + { + "": [{"error_type": ConditionError}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"error_type": ConditionError}], + "conditions/1/entity_id/0": [{"error_type": ConditionError}], + } + ) hass.states.async_set("sensor.temperature", 120) assert not test(hass) + assert_condition_trace( + { + "": [{"result": {"result": False}}], + "conditions/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/1": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [{"result": {"result": False}}], + } + ) hass.states.async_set("sensor.temperature", 105) assert test(hass) + assert_condition_trace( + { + "": [{"result": {"result": True}}], + "conditions/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/1": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True}}], + } + ) hass.states.async_set("sensor.temperature", 100) assert test(hass) + assert_condition_trace( + { + "": [{"result": {"result": True}}], + "conditions/0": [{"result": {"result": True}}], + "conditions/0/entity_id/0": [{"result": {"result": True}}], + } + ) + + +async def test_or_condition_raises(hass): + """Test the 'or' condition.""" + test = await condition.async_from_config( + hass, + { + "alias": "Or Condition", + "condition": "or", + "conditions": [ + { + "condition": "state", + "entity_id": "sensor.temperature", + "state": "100", + }, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature2", + "above": 110, + }, + ], + }, + ) + + # All subconditions raise, the OR-condition should raise + with pytest.raises(ConditionError): + test(hass) + assert_condition_trace( + { + "": [{"error_type": ConditionError}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"error_type": ConditionError}], + "conditions/1/entity_id/0": [{"error_type": ConditionError}], + } + ) + + # The first subconditions raises, the second returns False, the OR-condition + # should raise + hass.states.async_set("sensor.temperature2", 100) + with pytest.raises(ConditionError): + test(hass) + assert_condition_trace( + { + "": [{"error_type": ConditionError}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [{"result": {"result": False}}], + } + ) + + # The first subconditions raises, the second returns True, the OR-condition + # should return True + hass.states.async_set("sensor.temperature2", 120) + assert test(hass) + assert_condition_trace( + { + "": [{"result": {"result": True}}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True}}], + } + ) async def test_or_condition_with_template(hass): @@ -181,18 +403,126 @@ async def test_not_condition(hass): with pytest.raises(ConditionError): test(hass) + assert_condition_trace( + { + "": [{"error_type": ConditionError}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"error_type": ConditionError}], + "conditions/1/entity_id/0": [{"error_type": ConditionError}], + } + ) hass.states.async_set("sensor.temperature", 101) assert test(hass) + assert_condition_trace( + { + "": [{"result": {"result": True}}], + "conditions/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/1": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [{"result": {"result": False}}], + } + ) hass.states.async_set("sensor.temperature", 50) assert test(hass) + assert_condition_trace( + { + "": [{"result": {"result": True}}], + "conditions/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/1": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [{"result": {"result": False}}], + } + ) hass.states.async_set("sensor.temperature", 49) assert not test(hass) + assert_condition_trace( + { + "": [{"result": {"result": False}}], + "conditions/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/1": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True}}], + } + ) hass.states.async_set("sensor.temperature", 100) assert not test(hass) + assert_condition_trace( + { + "": [{"result": {"result": False}}], + "conditions/0": [{"result": {"result": True}}], + "conditions/0/entity_id/0": [{"result": {"result": True}}], + } + ) + + +async def test_not_condition_raises(hass): + """Test the 'and' condition.""" + test = await condition.async_from_config( + hass, + { + "alias": "Not Condition", + "condition": "not", + "conditions": [ + { + "condition": "state", + "entity_id": "sensor.temperature", + "state": "100", + }, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature2", + "below": 50, + }, + ], + }, + ) + + # All subconditions raise, the NOT-condition should raise + with pytest.raises(ConditionError): + test(hass) + assert_condition_trace( + { + "": [{"error_type": ConditionError}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"error_type": ConditionError}], + "conditions/1/entity_id/0": [{"error_type": ConditionError}], + } + ) + + # The first subconditions raises, the second returns False, the NOT-condition + # should raise + hass.states.async_set("sensor.temperature2", 90) + with pytest.raises(ConditionError): + test(hass) + assert_condition_trace( + { + "": [{"error_type": ConditionError}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [{"result": {"result": False}}], + } + ) + + # The first subconditions raises, the second returns True, the NOT-condition + # should return False + hass.states.async_set("sensor.temperature2", 40) + assert not test(hass) + assert_condition_trace( + { + "": [{"result": {"result": False}}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True}}], + } + ) async def test_not_condition_with_template(hass): diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index d2946fcd494..04f922b685e 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -30,6 +30,32 @@ from tests.common import ( ENTITY_ID = "script.test" +def assert_element(trace_element, expected_element, path): + """Assert a trace element is as expected. + + Note: Unused variable 'path' is passed to get helpful errors from pytest. + """ + for result_key, result in expected_element.get("result", {}).items(): + assert trace_element._result[result_key] == result + if "error_type" in expected_element: + assert isinstance(trace_element._error, expected_element["error_type"]) + else: + assert trace_element._error is None + + +def assert_action_trace(expected): + """Assert a trace condition sequence is as expected.""" + action_trace = script.action_trace_get() + script.action_trace_clear() + expected_trace_keys = list(expected.keys()) + assert list(action_trace.keys()) == expected_trace_keys + for trace_key_index, key in enumerate(expected_trace_keys): + assert len(action_trace[key]) == len(expected[key]) + for index, element in enumerate(expected[key]): + path = f"[{trace_key_index}][{index}]" + assert_element(action_trace[key][index], element, path) + + def async_watch_for_action(script_obj, message): """Watch for message in last_action.""" flag = asyncio.Event() @@ -54,9 +80,14 @@ async def test_firing_event_basic(hass, caplog): sequence = cv.SCRIPT_SCHEMA( {"alias": alias, "event": event, "event_data": {"hello": "world"}} ) - script_obj = script.Script( - hass, sequence, "Test Name", "test_domain", running_description="test script" - ) + with script.trace_action(None): + script_obj = script.Script( + hass, + sequence, + "Test Name", + "test_domain", + running_description="test script", + ) await script_obj.async_run(context=context) await hass.async_block_till_done() @@ -67,6 +98,12 @@ async def test_firing_event_basic(hass, caplog): assert ".test_name:" in caplog.text assert "Test Name: Running test script" in caplog.text assert f"Executing step {alias}" in caplog.text + assert_action_trace( + { + "": [{}], + "0": [{}], + } + ) async def test_firing_event_template(hass): From 208a104e96290bcbc6760f62a4b4ba36c8cd5a86 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 4 Mar 2021 18:33:35 +0100 Subject: [PATCH 152/831] Fix secrets in files included via include_dir_list (#47350) --- homeassistant/util/yaml/loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index 04e51a5a9c5..b4699ed95d2 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -227,7 +227,7 @@ def _include_dir_list_yaml( """Load multiple files from directory as a list.""" loc = os.path.join(os.path.dirname(loader.name), node.value) return [ - load_yaml(f) + load_yaml(f, loader.secrets) for f in _find_files(loc, "*.yaml") if os.path.basename(f) != SECRET_YAML ] From b49a672fd5e0bbe7475864f55c30cebb97fe1b78 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Thu, 4 Mar 2021 21:47:24 +0100 Subject: [PATCH 153/831] Catch ConditionError in generic_thermostat climate (#47359) --- .../components/generic_thermostat/climate.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index 5fbdf499146..7062267de19 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -35,6 +35,7 @@ from homeassistant.const import ( STATE_UNKNOWN, ) from homeassistant.core import DOMAIN as HA_DOMAIN, CoreState, callback +from homeassistant.exceptions import ConditionError from homeassistant.helpers import condition import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import ( @@ -439,12 +440,16 @@ class GenericThermostat(ClimateEntity, RestoreEntity): current_state = STATE_ON else: current_state = HVAC_MODE_OFF - long_enough = condition.state( - self.hass, - self.heater_entity_id, - current_state, - self.min_cycle_duration, - ) + try: + long_enough = condition.state( + self.hass, + self.heater_entity_id, + current_state, + self.min_cycle_duration, + ) + except ConditionError: + long_enough = False + if not long_enough: return From b58f9ce33aacd4aec41f1d5f37dcd09fc98003b3 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 4 Mar 2021 21:53:09 +0100 Subject: [PATCH 154/831] Fix Xiaomi Miio setup of switch entity for lumi.acpartner.v3 (#47345) --- .../components/xiaomi_miio/__init__.py | 2 +- homeassistant/components/xiaomi_miio/const.py | 1 - homeassistant/components/xiaomi_miio/switch.py | 18 +++++++++++------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index a72298c7c44..139ac017f66 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -24,7 +24,7 @@ from .gateway import ConnectXiaomiGateway _LOGGER = logging.getLogger(__name__) -GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "light"] +GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "switch", "light"] SWITCH_PLATFORMS = ["switch"] FAN_PLATFORMS = ["fan"] VACUUM_PLATFORMS = ["vacuum"] diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index 5dc381b17fb..ed34192eae3 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -68,7 +68,6 @@ MODELS_SWITCH = [ "chuangmi.plug.v2", "chuangmi.plug.hmi205", "chuangmi.plug.hmi206", - "lumi.acpartner.v3", ] MODELS_FAN = MODELS_FAN_MIIO + MODELS_HUMIDIFIER_MIOT + MODELS_PURIFIER_MIOT MODELS_VACUUM = ["roborock.vacuum"] diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index 04bb40ec27d..b290cc6a956 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -21,6 +21,7 @@ import homeassistant.helpers.config_validation as cv from .const import ( CONF_DEVICE, CONF_FLOW_TYPE, + CONF_GATEWAY, CONF_MODEL, DOMAIN, SERVICE_SET_POWER_MODE, @@ -128,16 +129,19 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the switch from a config entry.""" entities = [] - if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: + host = config_entry.data[CONF_HOST] + token = config_entry.data[CONF_TOKEN] + name = config_entry.title + model = config_entry.data[CONF_MODEL] + unique_id = config_entry.unique_id + + if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE or ( + config_entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY + and model == "lumi.acpartner.v3" + ): if DATA_KEY not in hass.data: hass.data[DATA_KEY] = {} - host = config_entry.data[CONF_HOST] - token = config_entry.data[CONF_TOKEN] - name = config_entry.title - model = config_entry.data[CONF_MODEL] - unique_id = config_entry.unique_id - _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) if model in ["chuangmi.plug.v1", "chuangmi.plug.v3", "chuangmi.plug.hmi208"]: From 62d8e47c5184927f7eebe919cab524b2b68a1f42 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Mar 2021 11:02:50 -1000 Subject: [PATCH 155/831] Map silent as a preset mode for fan backcompat (#47396) The original change did not map silent as a preset mode because it was not clear if it was a speed or a preset. --- homeassistant/components/fan/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 25911eb2d06..7a50997d76d 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -77,6 +77,7 @@ _NOT_SPEED_INTERVAL = "interval" _NOT_SPEED_IDLE = "idle" _NOT_SPEED_FAVORITE = "favorite" _NOT_SPEED_SLEEP = "sleep" +_NOT_SPEED_SILENT = "silent" _NOT_SPEEDS_FILTER = { _NOT_SPEED_OFF, @@ -85,6 +86,7 @@ _NOT_SPEEDS_FILTER = { _NOT_SPEED_SMART, _NOT_SPEED_INTERVAL, _NOT_SPEED_IDLE, + _NOT_SPEED_SILENT, _NOT_SPEED_SLEEP, _NOT_SPEED_FAVORITE, } @@ -651,7 +653,7 @@ def speed_list_without_preset_modes(speed_list: List): output: ["1", "2", "3", "4", "5", "6", "7"] input: ["Auto", "Silent", "Favorite", "Idle", "Medium", "High", "Strong"] - output: ["Silent", "Medium", "High", "Strong"] + output: ["Medium", "High", "Strong"] """ return [speed for speed in speed_list if speed.lower() not in _NOT_SPEEDS_FILTER] @@ -673,7 +675,7 @@ def preset_modes_from_speed_list(speed_list: List): output: ["smart"] input: ["Auto", "Silent", "Favorite", "Idle", "Medium", "High", "Strong"] - output: ["Auto", "Favorite", "Idle"] + output: ["Auto", "Silent", "Favorite", "Idle"] """ return [ From f05f60c4c472dd7473e5a2af41424b146fc1d112 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Thu, 4 Mar 2021 13:07:42 -0800 Subject: [PATCH 156/831] Revert "Speed-up wemo discovery (#46821)" (#47392) This reverts commit 6e52b26c06098052d379065b00f570c2a44653e1. --- homeassistant/components/wemo/__init__.py | 48 ++++------------------- tests/components/wemo/test_init.py | 33 +++++----------- 2 files changed, 17 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/wemo/__init__.py b/homeassistant/components/wemo/__init__.py index df737f101ba..db380ae11ca 100644 --- a/homeassistant/components/wemo/__init__.py +++ b/homeassistant/components/wemo/__init__.py @@ -1,4 +1,5 @@ """Support for WeMo device discovery.""" +import asyncio import logging import pywemo @@ -15,14 +16,9 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_call_later -from homeassistant.util.async_ import gather_with_concurrency from .const import DOMAIN -# Max number of devices to initialize at once. This limit is in place to -# avoid tying up too many executor threads with WeMo device setup. -MAX_CONCURRENCY = 3 - # Mapping from Wemo model_name to domain. WEMO_MODEL_DISPATCH = { "Bridge": LIGHT_DOMAIN, @@ -118,12 +114,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): static_conf = config.get(CONF_STATIC, []) if static_conf: _LOGGER.debug("Adding statically configured WeMo devices...") - for device in await gather_with_concurrency( - MAX_CONCURRENCY, + for device in await asyncio.gather( *[ hass.async_add_executor_job(validate_static_config, host, port) for host, port in static_conf - ], + ] ): if device: wemo_dispatcher.async_add_unique_device(hass, device) @@ -192,44 +187,15 @@ class WemoDiscovery: self._wemo_dispatcher = wemo_dispatcher self._stop = None self._scan_delay = 0 - self._upnp_entries = set() - - async def async_add_from_upnp_entry(self, entry: pywemo.ssdp.UPNPEntry) -> None: - """Create a WeMoDevice from an UPNPEntry and add it to the dispatcher. - - Uses the self._upnp_entries set to avoid interrogating the same device - multiple times. - """ - if entry in self._upnp_entries: - return - try: - device = await self._hass.async_add_executor_job( - pywemo.discovery.device_from_uuid_and_location, - entry.udn, - entry.location, - ) - except pywemo.PyWeMoException as err: - _LOGGER.error("Unable to setup WeMo %r (%s)", entry, err) - else: - self._wemo_dispatcher.async_add_unique_device(self._hass, device) - self._upnp_entries.add(entry) async def async_discover_and_schedule(self, *_) -> None: """Periodically scan the network looking for WeMo devices.""" _LOGGER.debug("Scanning network for WeMo devices...") try: - # pywemo.ssdp.scan is a light-weight UDP UPnP scan for WeMo devices. - entries = await self._hass.async_add_executor_job(pywemo.ssdp.scan) - - # async_add_from_upnp_entry causes multiple HTTP requests to be sent - # to the WeMo device for the initial setup of the WeMoDevice - # instance. This may take some time to complete. The per-device - # setup work is done in parallel to speed up initial setup for the - # component. - await gather_with_concurrency( - MAX_CONCURRENCY, - *[self.async_add_from_upnp_entry(entry) for entry in entries], - ) + for device in await self._hass.async_add_executor_job( + pywemo.discover_devices + ): + self._wemo_dispatcher.async_add_unique_device(self._hass, device) finally: # Run discovery more frequently after hass has just started. self._scan_delay = min( diff --git a/tests/components/wemo/test_init.py b/tests/components/wemo/test_init.py index 7c2b43dfd8c..374222d8688 100644 --- a/tests/components/wemo/test_init.py +++ b/tests/components/wemo/test_init.py @@ -100,41 +100,28 @@ async def test_static_config_with_invalid_host(hass): async def test_discovery(hass, pywemo_registry): """Verify that discovery dispatches devices to the platform for setup.""" - def create_device(uuid, location): + def create_device(counter): """Create a unique mock Motion detector device for each counter value.""" device = create_autospec(pywemo.Motion, instance=True) - device.host = location - device.port = MOCK_PORT - device.name = f"{MOCK_NAME}_{uuid}" - device.serialnumber = f"{MOCK_SERIAL_NUMBER}_{uuid}" + device.host = f"{MOCK_HOST}_{counter}" + device.port = MOCK_PORT + counter + device.name = f"{MOCK_NAME}_{counter}" + device.serialnumber = f"{MOCK_SERIAL_NUMBER}_{counter}" device.model_name = "Motion" device.get_state.return_value = 0 # Default to Off return device - def create_upnp_entry(counter): - return pywemo.ssdp.UPNPEntry.from_response( - "\r\n".join( - [ - "", - f"LOCATION: http://192.168.1.100:{counter}/setup.xml", - f"USN: uuid:Socket-1_0-SERIAL{counter}::upnp:rootdevice", - "", - ] - ) - ) - - upnp_entries = [create_upnp_entry(0), create_upnp_entry(1)] + pywemo_devices = [create_device(0), create_device(1)] # Setup the component and start discovery. with patch( - "pywemo.discovery.device_from_uuid_and_location", side_effect=create_device - ), patch("pywemo.ssdp.scan", return_value=upnp_entries) as mock_scan: + "pywemo.discover_devices", return_value=pywemo_devices + ) as mock_discovery: assert await async_setup_component( hass, DOMAIN, {DOMAIN: {CONF_DISCOVERY: True}} ) await pywemo_registry.semaphore.acquire() # Returns after platform setup. - mock_scan.assert_called() - # Add two of the same entries to test deduplication. - upnp_entries.extend([create_upnp_entry(2), create_upnp_entry(2)]) + mock_discovery.assert_called() + pywemo_devices.append(create_device(2)) # Test that discovery runs periodically and the async_dispatcher_send code works. async_fire_time_changed( From 7a8b7224c8639b7e7f3fd22e2e596f15ac23c53d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 4 Mar 2021 22:09:08 +0100 Subject: [PATCH 157/831] Don't raise on known non-matching states in numeric state condition (#47378) --- homeassistant/helpers/condition.py | 25 ++++--- .../triggers/test_numeric_state.py | 61 +---------------- tests/helpers/test_condition.py | 68 +++++++++---------- 3 files changed, 48 insertions(+), 106 deletions(-) diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 1abbf550bb1..bc1ff21b9cc 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -412,10 +412,9 @@ def async_numeric_state( "numeric_state", f"template error: {ex}" ) from ex + # Known states that never match the numeric condition if value in (STATE_UNAVAILABLE, STATE_UNKNOWN): - raise ConditionErrorMessage( - "numeric_state", f"state of {entity_id} is unavailable" - ) + return False try: fvalue = float(value) @@ -428,13 +427,15 @@ def async_numeric_state( if below is not None: if isinstance(below, str): below_entity = hass.states.get(below) - if not below_entity or below_entity.state in ( + if not below_entity: + raise ConditionErrorMessage( + "numeric_state", f"unknown 'below' entity {below}" + ) + if below_entity.state in ( STATE_UNAVAILABLE, STATE_UNKNOWN, ): - raise ConditionErrorMessage( - "numeric_state", f"the 'below' entity {below} is unavailable" - ) + return False try: if fvalue >= float(below_entity.state): condition_trace_set_result( @@ -455,13 +456,15 @@ def async_numeric_state( if above is not None: if isinstance(above, str): above_entity = hass.states.get(above) - if not above_entity or above_entity.state in ( + if not above_entity: + raise ConditionErrorMessage( + "numeric_state", f"unknown 'above' entity {above}" + ) + if above_entity.state in ( STATE_UNAVAILABLE, STATE_UNKNOWN, ): - raise ConditionErrorMessage( - "numeric_state", f"the 'above' entity {above} is unavailable" - ) + return False try: if fvalue <= float(above_entity.state): condition_trace_set_result( diff --git a/tests/components/homeassistant/triggers/test_numeric_state.py b/tests/components/homeassistant/triggers/test_numeric_state.py index 831e20b78a1..9eb9ac79a94 100644 --- a/tests/components/homeassistant/triggers/test_numeric_state.py +++ b/tests/components/homeassistant/triggers/test_numeric_state.py @@ -10,12 +10,7 @@ import homeassistant.components.automation as automation from homeassistant.components.homeassistant.triggers import ( numeric_state as numeric_state_trigger, ) -from homeassistant.const import ( - ATTR_ENTITY_ID, - ENTITY_MATCH_ALL, - SERVICE_TURN_OFF, - STATE_UNAVAILABLE, -) +from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, SERVICE_TURN_OFF from homeassistant.core import Context from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -347,52 +342,6 @@ async def test_if_fires_on_entity_unavailable_at_startup(hass, calls): assert len(calls) == 0 -async def test_if_not_fires_on_entity_unavailable(hass, calls): - """Test the firing with entity changing to unavailable.""" - # set initial state - hass.states.async_set("test.entity", 9) - await hass.async_block_till_done() - - assert await async_setup_component( - hass, - automation.DOMAIN, - { - automation.DOMAIN: { - "trigger": { - "platform": "numeric_state", - "entity_id": "test.entity", - "above": 10, - }, - "action": {"service": "test.automation"}, - } - }, - ) - - # 11 is above 10 - hass.states.async_set("test.entity", 11) - await hass.async_block_till_done() - assert len(calls) == 1 - - # Going to unavailable and back should not fire - hass.states.async_set("test.entity", STATE_UNAVAILABLE) - await hass.async_block_till_done() - assert len(calls) == 1 - hass.states.async_set("test.entity", 11) - await hass.async_block_till_done() - assert len(calls) == 1 - - # Crossing threshold via unavailable should fire - hass.states.async_set("test.entity", 9) - await hass.async_block_till_done() - assert len(calls) == 1 - hass.states.async_set("test.entity", STATE_UNAVAILABLE) - await hass.async_block_till_done() - assert len(calls) == 1 - hass.states.async_set("test.entity", 11) - await hass.async_block_till_done() - assert len(calls) == 2 - - @pytest.mark.parametrize("above", (10, "input_number.value_10")) async def test_if_fires_on_entity_change_below_to_above(hass, calls, above): """Test the firing with changed entity.""" @@ -1522,7 +1471,7 @@ async def test_if_fires_on_change_with_for_template_3(hass, calls, above, below) assert len(calls) == 1 -async def test_if_not_fires_on_error_with_for_template(hass, caplog, calls): +async def test_if_not_fires_on_error_with_for_template(hass, calls): """Test for not firing on error with for template.""" hass.states.async_set("test.entity", 0) await hass.async_block_till_done() @@ -1547,17 +1496,11 @@ async def test_if_not_fires_on_error_with_for_template(hass, caplog, calls): await hass.async_block_till_done() assert len(calls) == 0 - caplog.clear() - caplog.set_level(logging.WARNING) - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=3)) hass.states.async_set("test.entity", "unavailable") await hass.async_block_till_done() assert len(calls) == 0 - assert len(caplog.record_tuples) == 1 - assert caplog.record_tuples[0][1] == logging.WARNING - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=3)) hass.states.async_set("test.entity", 101) await hass.async_block_till_done() diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index b3e950131b0..cfed8ebbcf6 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -1,5 +1,4 @@ """Test the condition helper.""" -from logging import WARNING from unittest.mock import patch import pytest @@ -693,27 +692,6 @@ async def test_time_using_input_datetime(hass): condition.time(hass, before="input_datetime.not_existing") -async def test_if_numeric_state_raises_on_unavailable(hass, caplog): - """Test numeric_state raises on unavailable/unknown state.""" - test = await condition.async_from_config( - hass, - {"condition": "numeric_state", "entity_id": "sensor.temperature", "below": 42}, - ) - - caplog.clear() - caplog.set_level(WARNING) - - hass.states.async_set("sensor.temperature", "unavailable") - with pytest.raises(ConditionError): - test(hass) - assert len(caplog.record_tuples) == 0 - - hass.states.async_set("sensor.temperature", "unknown") - with pytest.raises(ConditionError): - test(hass) - assert len(caplog.record_tuples) == 0 - - async def test_state_raises(hass): """Test that state raises ConditionError on errors.""" # No entity @@ -961,6 +939,26 @@ async def test_state_using_input_entities(hass): assert test(hass) +async def test_numeric_state_known_non_matching(hass): + """Test that numeric_state doesn't match on known non-matching states.""" + hass.states.async_set("sensor.temperature", "unavailable") + test = await condition.async_from_config( + hass, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature", + "above": 0, + }, + ) + + # Unavailable state + assert not test(hass) + + # Unknown state + hass.states.async_set("sensor.temperature", "unknown") + assert not test(hass) + + async def test_numeric_state_raises(hass): """Test that numeric_state raises ConditionError on errors.""" # Unknown entities @@ -1007,20 +1005,6 @@ async def test_numeric_state_raises(hass): hass.states.async_set("sensor.temperature", 50) test(hass) - # Unavailable state - with pytest.raises(ConditionError, match="state of .* is unavailable"): - test = await condition.async_from_config( - hass, - { - "condition": "numeric_state", - "entity_id": "sensor.temperature", - "above": 0, - }, - ) - - hass.states.async_set("sensor.temperature", "unavailable") - test(hass) - # Bad number with pytest.raises(ConditionError, match="cannot be processed as a number"): test = await condition.async_from_config( @@ -1182,6 +1166,12 @@ async def test_numeric_state_using_input_number(hass): hass.states.async_set("sensor.temperature", 100) assert not test(hass) + hass.states.async_set("input_number.high", "unknown") + assert not test(hass) + + hass.states.async_set("input_number.high", "unavailable") + assert not test(hass) + await hass.services.async_call( "input_number", "set_value", @@ -1193,6 +1183,12 @@ async def test_numeric_state_using_input_number(hass): ) assert test(hass) + hass.states.async_set("input_number.low", "unknown") + assert not test(hass) + + hass.states.async_set("input_number.low", "unavailable") + assert not test(hass) + with pytest.raises(ConditionError): condition.async_numeric_state( hass, entity="sensor.temperature", below="input_number.not_exist" From cea4808db80a5b0fdafea692926a3a0cf9a09b92 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 4 Mar 2021 22:09:51 +0100 Subject: [PATCH 158/831] Update frontend to 20210302.4 (#47383) --- 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 4d4127fc2f2..a5b4c6f10d5 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20210302.3" + "home-assistant-frontend==20210302.4" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 752a3755169..919b53f64ed 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210302.3 +home-assistant-frontend==20210302.4 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index c1f3d577251..c3914ec2c25 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.3 +home-assistant-frontend==20210302.4 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a27ec0f8051..06fb55c9f0b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.3 +home-assistant-frontend==20210302.4 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From d64fe6ea326314d7e8df6489afe664cbdb9a12fd Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 4 Mar 2021 22:11:07 +0100 Subject: [PATCH 159/831] Fix zwave_js manual reconfiguration of add-on managed entry (#47364) --- homeassistant/components/zwave_js/config_flow.py | 10 +++++++++- tests/components/zwave_js/test_config_flow.py | 16 ++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index 37923c574b4..ac466223fb6 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -117,7 +117,15 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): await self.async_set_unique_id( version_info.home_id, raise_on_progress=False ) - self._abort_if_unique_id_configured(user_input) + # Make sure we disable any add-on handling + # if the controller is reconfigured in a manual step. + self._abort_if_unique_id_configured( + updates={ + **user_input, + CONF_USE_ADDON: False, + CONF_INTEGRATION_CREATED_ADDON: False, + } + ) self.ws_address = user_input[CONF_URL] return self._async_create_entry_from_vars() diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index fc97f7420cf..c6b59e07412 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -184,7 +184,16 @@ async def test_manual_errors( async def test_manual_already_configured(hass): """Test that only one unique instance is allowed.""" - entry = MockConfigEntry(domain=DOMAIN, data={}, title=TITLE, unique_id=1234) + entry = MockConfigEntry( + domain=DOMAIN, + data={ + "url": "ws://localhost:3000", + "use_addon": True, + "integration_created_addon": True, + }, + title=TITLE, + unique_id=1234, + ) entry.add_to_hass(hass) await setup.async_setup_component(hass, "persistent_notification", {}) @@ -198,12 +207,15 @@ async def test_manual_already_configured(hass): result = await hass.config_entries.flow.async_configure( result["flow_id"], { - "url": "ws://localhost:3000", + "url": "ws://1.1.1.1:3001", }, ) assert result["type"] == "abort" assert result["reason"] == "already_configured" + assert entry.data["url"] == "ws://1.1.1.1:3001" + assert entry.data["use_addon"] is False + assert entry.data["integration_created_addon"] is False @pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) From 5ced7395f3e5e84ae1ac3da3ac935d22d4ce4791 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 4 Mar 2021 22:11:38 +0100 Subject: [PATCH 160/831] Fix access of missing zwave_js climate unit value (#47380) --- homeassistant/components/zwave_js/climate.py | 8 +- tests/components/zwave_js/common.py | 1 + tests/components/zwave_js/conftest.py | 14 + tests/components/zwave_js/test_climate.py | 17 ++ .../zwave_js/srt321_hrt4_zw_state.json | 262 ++++++++++++++++++ 5 files changed, 300 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/zwave_js/srt321_hrt4_zw_state.json diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 54966538aae..cc449b89e91 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -118,7 +118,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): super().__init__(config_entry, client, info) self._hvac_modes: Dict[str, Optional[int]] = {} self._hvac_presets: Dict[str, Optional[int]] = {} - self._unit_value: ZwaveValue = None + self._unit_value: Optional[ZwaveValue] = None self._current_mode = self.get_zwave_value( THERMOSTAT_MODE_PROPERTY, command_class=CommandClass.THERMOSTAT_MODE @@ -215,7 +215,11 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): @property def temperature_unit(self) -> str: """Return the unit of measurement used by the platform.""" - if "f" in self._unit_value.metadata.unit.lower(): + if ( + self._unit_value + and self._unit_value.metadata.unit + and "f" in self._unit_value.metadata.unit.lower() + ): return TEMP_FAHRENHEIT return TEMP_CELSIUS diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index a5ee628754e..ec54e139404 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -16,6 +16,7 @@ PROPERTY_DOOR_STATUS_BINARY_SENSOR = ( CLIMATE_RADIO_THERMOSTAT_ENTITY = "climate.z_wave_thermostat" CLIMATE_DANFOSS_LC13_ENTITY = "climate.living_connect_z_thermostat" CLIMATE_FLOOR_THERMOSTAT_ENTITY = "climate.floor_thermostat" +CLIMATE_MAIN_HEAT_ACTIONNER = "climate.main_heat_actionner" BULB_6_MULTI_COLOR_LIGHT_ENTITY = "light.bulb_6_multi_color" EATON_RF9640_ENTITY = "light.allloaddimmer" AEON_SMART_SWITCH_LIGHT_ENTITY = "light.smart_switch_6" diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index af87d9d49e1..b171fb38425 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -240,6 +240,12 @@ def nortek_thermostat_state_fixture(): return json.loads(load_fixture("zwave_js/nortek_thermostat_state.json")) +@pytest.fixture(name="srt321_hrt4_zw_state", scope="session") +def srt321_hrt4_zw_state_fixture(): + """Load the climate HRT4-ZW / SRT321 / SRT322 thermostat node state fixture data.""" + return json.loads(load_fixture("zwave_js/srt321_hrt4_zw_state.json")) + + @pytest.fixture(name="chain_actuator_zws12_state", scope="session") def window_cover_state_fixture(): """Load the window cover node state fixture data.""" @@ -423,6 +429,14 @@ def nortek_thermostat_fixture(client, nortek_thermostat_state): return node +@pytest.fixture(name="srt321_hrt4_zw") +def srt321_hrt4_zw_fixture(client, srt321_hrt4_zw_state): + """Mock a HRT4-ZW / SRT321 / SRT322 thermostat node.""" + node = Node(client, copy.deepcopy(srt321_hrt4_zw_state)) + client.driver.controller.nodes[node.node_id] = node + return node + + @pytest.fixture(name="aeotec_radiator_thermostat") def aeotec_radiator_thermostat_fixture(client, aeotec_radiator_thermostat_state): """Mock a Aeotec thermostat node.""" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index 44804825885..ea75f10328c 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -31,6 +31,7 @@ from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE from .common import ( CLIMATE_DANFOSS_LC13_ENTITY, CLIMATE_FLOOR_THERMOSTAT_ENTITY, + CLIMATE_MAIN_HEAT_ACTIONNER, CLIMATE_RADIO_THERMOSTAT_ENTITY, ) @@ -488,3 +489,19 @@ async def test_thermostat_heatit(hass, client, climate_heatit_z_trm3, integratio assert state.attributes[ATTR_TEMPERATURE] == 22.5 assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + + +async def test_thermostat_srt321_hrt4_zw(hass, client, srt321_hrt4_zw, integration): + """Test a climate entity from a HRT4-ZW / SRT321 thermostat device. + + This device currently has no setpoint values. + """ + state = hass.states.get(CLIMATE_MAIN_HEAT_ACTIONNER) + + assert state + assert state.state == HVAC_MODE_OFF + assert state.attributes[ATTR_HVAC_MODES] == [ + HVAC_MODE_OFF, + HVAC_MODE_HEAT, + ] + assert state.attributes[ATTR_CURRENT_TEMPERATURE] is None diff --git a/tests/fixtures/zwave_js/srt321_hrt4_zw_state.json b/tests/fixtures/zwave_js/srt321_hrt4_zw_state.json new file mode 100644 index 00000000000..a2fdaa99561 --- /dev/null +++ b/tests/fixtures/zwave_js/srt321_hrt4_zw_state.json @@ -0,0 +1,262 @@ +{ + "nodeId": 20, + "index": 0, + "status": 4, + "ready": true, + "deviceClass": { + "basic": { + "key": 4, + "label": "Routing Slave" + }, + "generic": { + "key": 8, + "label": "Thermostat" + }, + "specific": { + "key": 0, + "label": "Unused" + }, + "mandatorySupportedCCs": [], + "mandatoryControlledCCs": [] + }, + "isListening": true, + "isFrequentListening": false, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 3, + "isBeaming": true, + "manufacturerId": 89, + "productId": 1, + "productType": 3, + "firmwareVersion": "2.0", + "name": "main_heat_actionner", + "location": "kitchen", + "deviceConfig": { + "filename": "/opt/node_modules/@zwave-js/config/config/devices/0x0059/asr-zw.json", + "manufacturerId": 89, + "manufacturer": "Secure Meters (UK) Ltd.", + "label": "SRT322", + "description": "Thermostat Receiver", + "devices": [ + { + "productType": "0x0003", + "productId": "0x0001" + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + } + }, + "label": "SRT322", + "neighbors": [ + 1, + 5, + 10, + 12, + 13, + 14, + 15, + 18, + 21 + ], + "interviewAttempts": 1, + "interviewStage": 7, + "commandClasses": [ + { + "id": 37, + "name": "Binary Switch", + "version": 1, + "isSecure": false + }, + { + "id": 64, + "name": "Thermostat Mode", + "version": 1, + "isSecure": false + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 1, + "isSecure": false + }, + { + "id": 134, + "name": "Version", + "version": 1, + "isSecure": false + } + ], + "endpoints": [ + { + "nodeId": 20, + "index": 0 + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Current value" + }, + "value": false + }, + { + "endpoint": 0, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Target value" + }, + "value": false + }, + { + "endpoint": 0, + "commandClass": 64, + "commandClassName": "Thermostat Mode", + "property": "mode", + "propertyName": "mode", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 255, + "states": { + "0": "Off", + "1": "Heat" + }, + "label": "Thermostat mode" + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 64, + "commandClassName": "Thermostat Mode", + "property": "manufacturerData", + "propertyName": "manufacturerData", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + } + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 89 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 6 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "2.78" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": [ + "2.0" + ] + } + ] + } From 541e1663175ce2725b9e10698659388389ec7721 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Thu, 4 Mar 2021 22:12:04 +0100 Subject: [PATCH 161/831] Fix measurement unit (Closes: #47390) (#47398) --- homeassistant/components/xiaomi_miio/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index 52cd4fdca5e..d6b87000f40 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -21,7 +21,6 @@ from homeassistant.const import ( DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, - LIGHT_LUX, PERCENTAGE, PRESSURE_HPA, TEMP_CELSIUS, @@ -37,6 +36,7 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Xiaomi Miio Sensor" DATA_KEY = "sensor.xiaomi_miio" +UNIT_LUMEN = "lm" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -301,7 +301,7 @@ class XiaomiGatewayIlluminanceSensor(Entity): @property def unit_of_measurement(self): """Return the unit of measurement of this entity.""" - return LIGHT_LUX + return UNIT_LUMEN @property def device_class(self): From 972baa2ce4ac553ee01ff7704c1aa87bb91c6775 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 4 Mar 2021 16:15:27 -0500 Subject: [PATCH 162/831] Don't convert Climacell forecast temperatures to celsius because platform does it automatically (#47406) --- homeassistant/components/climacell/weather.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/homeassistant/components/climacell/weather.py b/homeassistant/components/climacell/weather.py index c77bbfbd50a..1f0fe76cff6 100644 --- a/homeassistant/components/climacell/weather.py +++ b/homeassistant/components/climacell/weather.py @@ -22,7 +22,6 @@ from homeassistant.const import ( LENGTH_MILES, PRESSURE_HPA, PRESSURE_INHG, - TEMP_CELSIUS, TEMP_FAHRENHEIT, ) from homeassistant.helpers.entity import Entity @@ -31,7 +30,6 @@ from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util from homeassistant.util.distance import convert as distance_convert from homeassistant.util.pressure import convert as pressure_convert -from homeassistant.util.temperature import convert as temp_convert from . import ClimaCellDataUpdateCoordinator, ClimaCellEntity from .const import ( @@ -102,10 +100,6 @@ def _forecast_dict( precipitation = ( distance_convert(precipitation / 12, LENGTH_FEET, LENGTH_METERS) * 1000 ) - if temp: - temp = temp_convert(temp, TEMP_FAHRENHEIT, TEMP_CELSIUS) - if temp_low: - temp_low = temp_convert(temp_low, TEMP_FAHRENHEIT, TEMP_CELSIUS) if wind_speed: wind_speed = distance_convert(wind_speed, LENGTH_MILES, LENGTH_KILOMETERS) From fa8ded5ad82039e9a17d9df1affd7da78345018b Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 4 Mar 2021 14:20:08 -0700 Subject: [PATCH 163/831] Fix AirVisual exception when config entry contains old integration type (#47405) --- .../components/airvisual/__init__.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index c04f56f6b09..e900bfa65de 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -39,6 +39,7 @@ from .const import ( DATA_COORDINATOR, DOMAIN, INTEGRATION_TYPE_GEOGRAPHY_COORDS, + INTEGRATION_TYPE_GEOGRAPHY_NAME, INTEGRATION_TYPE_NODE_PRO, LOGGER, ) @@ -142,12 +143,21 @@ def _standardize_geography_config_entry(hass, config_entry): if not config_entry.options: # If the config entry doesn't already have any options set, set defaults: entry_updates["options"] = {CONF_SHOW_ON_MAP: True} - if CONF_INTEGRATION_TYPE not in config_entry.data: - # If the config entry data doesn't contain the integration type, add it: - entry_updates["data"] = { - **config_entry.data, - CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY_COORDS, - } + if config_entry.data.get(CONF_INTEGRATION_TYPE) not in [ + INTEGRATION_TYPE_GEOGRAPHY_COORDS, + INTEGRATION_TYPE_GEOGRAPHY_NAME, + ]: + # If the config entry data doesn't contain an integration type that we know + # about, infer it from the data we have: + entry_updates["data"] = {**config_entry.data} + if CONF_CITY in config_entry.data: + entry_updates["data"][ + CONF_INTEGRATION_TYPE + ] = INTEGRATION_TYPE_GEOGRAPHY_NAME + else: + entry_updates["data"][ + CONF_INTEGRATION_TYPE + ] = INTEGRATION_TYPE_GEOGRAPHY_COORDS if not entry_updates: return From 74746125ce03e24acd541b9582086d6a8b6024a9 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 4 Mar 2021 16:21:56 -0500 Subject: [PATCH 164/831] Fix Climacell timezone issue with daily forecasts (#47402) --- homeassistant/components/climacell/weather.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/climacell/weather.py b/homeassistant/components/climacell/weather.py index 1f0fe76cff6..e5a24197d6b 100644 --- a/homeassistant/components/climacell/weather.py +++ b/homeassistant/components/climacell/weather.py @@ -254,6 +254,7 @@ class ClimaCellWeatherEntity(ClimaCellEntity, WeatherEntity): if self.forecast_type == DAILY: use_datetime = False + forecast_dt = dt_util.start_of_local_day(forecast_dt) precipitation = self._get_cc_value( forecast, CC_ATTR_PRECIPITATION_DAILY ) From 6f7179dce9e6c47b7fcabf9b53160e1ec55573f4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 4 Mar 2021 22:27:59 +0100 Subject: [PATCH 165/831] Fix older Roborock models (#47412) --- homeassistant/components/xiaomi_miio/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index ed34192eae3..da422986900 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -70,7 +70,7 @@ MODELS_SWITCH = [ "chuangmi.plug.hmi206", ] MODELS_FAN = MODELS_FAN_MIIO + MODELS_HUMIDIFIER_MIOT + MODELS_PURIFIER_MIOT -MODELS_VACUUM = ["roborock.vacuum"] +MODELS_VACUUM = ["roborock.vacuum", "rockrobo.vacuum"] MODELS_ALL_DEVICES = MODELS_SWITCH + MODELS_FAN + MODELS_VACUUM MODELS_ALL = MODELS_ALL_DEVICES + MODELS_GATEWAY From 7ed80d6c3903787c95410cf05365a9068b9f9e91 Mon Sep 17 00:00:00 2001 From: Petru Paler Date: Thu, 4 Mar 2021 21:29:19 +0000 Subject: [PATCH 166/831] Update Solax library to 0.2.6 (#47384) --- homeassistant/components/solax/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/solax/manifest.json b/homeassistant/components/solax/manifest.json index 232715ebe18..90bfd8e6184 100644 --- a/homeassistant/components/solax/manifest.json +++ b/homeassistant/components/solax/manifest.json @@ -2,6 +2,6 @@ "domain": "solax", "name": "SolaX Power", "documentation": "https://www.home-assistant.io/integrations/solax", - "requirements": ["solax==0.2.5"], + "requirements": ["solax==0.2.6"], "codeowners": ["@squishykid"] } diff --git a/requirements_all.txt b/requirements_all.txt index c3914ec2c25..465aaadd454 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2088,7 +2088,7 @@ solaredge-local==0.2.0 solaredge==0.0.2 # homeassistant.components.solax -solax==0.2.5 +solax==0.2.6 # homeassistant.components.honeywell somecomfort==0.5.2 From 682943511a41a5f7d3ca56bdc01bcaad36e70883 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 4 Mar 2021 23:14:24 +0100 Subject: [PATCH 167/831] Make zwave_js add-on manager more flexible (#47356) --- homeassistant/components/zwave_js/__init__.py | 10 ++- homeassistant/components/zwave_js/addon.py | 66 ++++++++++++---- .../components/zwave_js/config_flow.py | 17 ++-- tests/components/zwave_js/test_config_flow.py | 77 +++++++------------ 4 files changed, 95 insertions(+), 75 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 0c5a55c25fb..637ea0fe08a 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -483,11 +483,15 @@ async def async_ensure_addon_running(hass: HomeAssistant, entry: ConfigEntry) -> network_key: str = entry.data[CONF_NETWORK_KEY] if not addon_is_installed: - addon_manager.async_schedule_install_addon(usb_path, network_key) + addon_manager.async_schedule_install_setup_addon( + usb_path, network_key, catch_error=True + ) raise ConfigEntryNotReady if not addon_is_running: - addon_manager.async_schedule_setup_addon(usb_path, network_key) + addon_manager.async_schedule_setup_addon( + usb_path, network_key, catch_error=True + ) raise ConfigEntryNotReady @@ -497,4 +501,4 @@ def async_ensure_addon_updated(hass: HomeAssistant) -> None: addon_manager: AddonManager = get_addon_manager(hass) if addon_manager.task_in_progress(): raise ConfigEntryNotReady - addon_manager.async_schedule_update_addon() + addon_manager.async_schedule_update_addon(catch_error=True) diff --git a/homeassistant/components/zwave_js/addon.py b/homeassistant/components/zwave_js/addon.py index 54169dcaf94..b8d020cfb00 100644 --- a/homeassistant/components/zwave_js/addon.py +++ b/homeassistant/components/zwave_js/addon.py @@ -67,8 +67,8 @@ class AddonManager: """Set up the add-on manager.""" self._hass = hass self._install_task: Optional[asyncio.Task] = None + self._start_task: Optional[asyncio.Task] = None self._update_task: Optional[asyncio.Task] = None - self._setup_task: Optional[asyncio.Task] = None def task_in_progress(self) -> bool: """Return True if any of the add-on tasks are in progress.""" @@ -76,7 +76,7 @@ class AddonManager: task and not task.done() for task in ( self._install_task, - self._setup_task, + self._start_task, self._update_task, ) ) @@ -125,8 +125,21 @@ class AddonManager: await async_install_addon(self._hass, ADDON_SLUG) @callback - def async_schedule_install_addon( - self, usb_path: str, network_key: str + def async_schedule_install_addon(self, catch_error: bool = False) -> asyncio.Task: + """Schedule a task that installs the Z-Wave JS add-on. + + Only schedule a new install task if the there's no running task. + """ + if not self._install_task or self._install_task.done(): + LOGGER.info("Z-Wave JS add-on is not installed. Installing add-on") + self._install_task = self._async_schedule_addon_operation( + self.async_install_addon, catch_error=catch_error + ) + return self._install_task + + @callback + def async_schedule_install_setup_addon( + self, usb_path: str, network_key: str, catch_error: bool = False ) -> asyncio.Task: """Schedule a task that installs and sets up the Z-Wave JS add-on. @@ -136,7 +149,9 @@ class AddonManager: LOGGER.info("Z-Wave JS add-on is not installed. Installing add-on") self._install_task = self._async_schedule_addon_operation( self.async_install_addon, - partial(self.async_setup_addon, usb_path, network_key), + partial(self.async_configure_addon, usb_path, network_key), + self.async_start_addon, + catch_error=catch_error, ) return self._install_task @@ -161,7 +176,7 @@ class AddonManager: await async_update_addon(self._hass, ADDON_SLUG) @callback - def async_schedule_update_addon(self) -> asyncio.Task: + def async_schedule_update_addon(self, catch_error: bool = False) -> asyncio.Task: """Schedule a task that updates and sets up the Z-Wave JS add-on. Only schedule a new update task if the there's no running task. @@ -169,7 +184,9 @@ class AddonManager: if not self._update_task or self._update_task.done(): LOGGER.info("Trying to update the Z-Wave JS add-on") self._update_task = self._async_schedule_addon_operation( - self.async_create_snapshot, self.async_update_addon + self.async_create_snapshot, + self.async_update_addon, + catch_error=catch_error, ) return self._update_task @@ -178,12 +195,25 @@ class AddonManager: """Start the Z-Wave JS add-on.""" await async_start_addon(self._hass, ADDON_SLUG) + @callback + def async_schedule_start_addon(self, catch_error: bool = False) -> asyncio.Task: + """Schedule a task that starts the Z-Wave JS add-on. + + Only schedule a new start task if the there's no running task. + """ + if not self._start_task or self._start_task.done(): + LOGGER.info("Z-Wave JS add-on is not running. Starting add-on") + self._start_task = self._async_schedule_addon_operation( + self.async_start_addon, catch_error=catch_error + ) + return self._start_task + @api_error("Failed to stop the Z-Wave JS add-on") async def async_stop_addon(self) -> None: """Stop the Z-Wave JS add-on.""" await async_stop_addon(self._hass, ADDON_SLUG) - async def async_setup_addon(self, usb_path: str, network_key: str) -> None: + async def async_configure_addon(self, usb_path: str, network_key: str) -> None: """Configure and start Z-Wave JS add-on.""" addon_options = await self.async_get_addon_options() @@ -195,22 +225,22 @@ class AddonManager: if new_addon_options != addon_options: await self.async_set_addon_options(new_addon_options) - await self.async_start_addon() - @callback def async_schedule_setup_addon( - self, usb_path: str, network_key: str + self, usb_path: str, network_key: str, catch_error: bool = False ) -> asyncio.Task: """Schedule a task that configures and starts the Z-Wave JS add-on. Only schedule a new setup task if the there's no running task. """ - if not self._setup_task or self._setup_task.done(): + if not self._start_task or self._start_task.done(): LOGGER.info("Z-Wave JS add-on is not running. Starting add-on") - self._setup_task = self._async_schedule_addon_operation( - partial(self.async_setup_addon, usb_path, network_key) + self._start_task = self._async_schedule_addon_operation( + partial(self.async_configure_addon, usb_path, network_key), + self.async_start_addon, + catch_error=catch_error, ) - return self._setup_task + return self._start_task @api_error("Failed to create a snapshot of the Z-Wave JS add-on.") async def async_create_snapshot(self) -> None: @@ -227,7 +257,9 @@ class AddonManager: ) @callback - def _async_schedule_addon_operation(self, *funcs: Callable) -> asyncio.Task: + def _async_schedule_addon_operation( + self, *funcs: Callable, catch_error: bool = False + ) -> asyncio.Task: """Schedule an add-on task.""" async def addon_operation() -> None: @@ -236,6 +268,8 @@ class AddonManager: try: await func() except AddonError as err: + if not catch_error: + raise LOGGER.error(err) break diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index ac466223fb6..4929f7e7869 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -180,11 +180,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self, user_input: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Handle logic when on Supervisor host.""" - # Only one entry with Supervisor add-on support is allowed. - for entry in self.hass.config_entries.async_entries(DOMAIN): - if entry.data.get(CONF_USE_ADDON): - return await self.async_step_manual() - if user_input is None: return self.async_show_form( step_id="on_supervisor", data_schema=ON_SUPERVISOR_SCHEMA @@ -297,7 +292,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): assert self.hass addon_manager: AddonManager = get_addon_manager(self.hass) try: - await addon_manager.async_start_addon() + await addon_manager.async_schedule_start_addon() # Sleep some seconds to let the add-on start properly before connecting. for _ in range(ADDON_SETUP_TIMEOUT_ROUNDS): await asyncio.sleep(ADDON_SETUP_TIMEOUT) @@ -346,7 +341,13 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): version_info.home_id, raise_on_progress=False ) - self._abort_if_unique_id_configured() + self._abort_if_unique_id_configured( + updates={ + CONF_URL: self.ws_address, + CONF_USB_PATH: self.usb_path, + CONF_NETWORK_KEY: self.network_key, + } + ) return self._async_create_entry_from_vars() async def _async_get_addon_info(self) -> dict: @@ -389,7 +390,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Install the Z-Wave JS add-on.""" addon_manager: AddonManager = get_addon_manager(self.hass) try: - await addon_manager.async_install_addon() + await addon_manager.async_schedule_install_addon() finally: # Continue the flow after show progress when the task is done. self.hass.async_create_task( diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index c6b59e07412..7eea126e52e 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -519,49 +519,6 @@ async def test_not_addon(hass, supervisor): assert len(mock_setup_entry.mock_calls) == 1 -async def test_addon_already_configured(hass, supervisor): - """Test add-on already configured leads to manual step.""" - entry = MockConfigEntry( - domain=DOMAIN, data={"use_addon": True}, title=TITLE, unique_id=5678 - ) - entry.add_to_hass(hass) - - await setup.async_setup_component(hass, "persistent_notification", {}) - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == "form" - assert result["step_id"] == "manual" - - with patch( - "homeassistant.components.zwave_js.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.zwave_js.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "url": "ws://localhost:3000", - }, - ) - await hass.async_block_till_done() - - assert result["type"] == "create_entry" - assert result["title"] == TITLE - assert result["data"] == { - "url": "ws://localhost:3000", - "usb_path": None, - "network_key": None, - "use_addon": False, - "integration_created_addon": False, - } - assert len(mock_setup.mock_calls) == 1 - assert len(mock_setup_entry.mock_calls) == 2 - - @pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) async def test_addon_running( hass, @@ -673,9 +630,18 @@ async def test_addon_running_already_configured( hass, supervisor, addon_running, addon_options, get_addon_discovery_info ): """Test that only one unique instance is allowed when add-on is running.""" - addon_options["device"] = "/test" - addon_options["network_key"] = "abc123" - entry = MockConfigEntry(domain=DOMAIN, data={}, title=TITLE, unique_id=1234) + addon_options["device"] = "/test_new" + addon_options["network_key"] = "def456" + entry = MockConfigEntry( + domain=DOMAIN, + data={ + "url": "ws://localhost:3000", + "usb_path": "/test", + "network_key": "abc123", + }, + title=TITLE, + unique_id=1234, + ) entry.add_to_hass(hass) await setup.async_setup_component(hass, "persistent_notification", {}) @@ -692,6 +658,9 @@ async def test_addon_running_already_configured( assert result["type"] == "abort" assert result["reason"] == "already_configured" + assert entry.data["url"] == "ws://host1:3001" + assert entry.data["usb_path"] == "/test_new" + assert entry.data["network_key"] == "def456" @pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) @@ -897,7 +866,16 @@ async def test_addon_installed_already_configured( get_addon_discovery_info, ): """Test that only one unique instance is allowed when add-on is installed.""" - entry = MockConfigEntry(domain=DOMAIN, data={}, title=TITLE, unique_id=1234) + entry = MockConfigEntry( + domain=DOMAIN, + data={ + "url": "ws://localhost:3000", + "usb_path": "/test", + "network_key": "abc123", + }, + title=TITLE, + unique_id=1234, + ) entry.add_to_hass(hass) await setup.async_setup_component(hass, "persistent_notification", {}) @@ -916,7 +894,7 @@ async def test_addon_installed_already_configured( assert result["step_id"] == "configure_addon" result = await hass.config_entries.flow.async_configure( - result["flow_id"], {"usb_path": "/test", "network_key": "abc123"} + result["flow_id"], {"usb_path": "/test_new", "network_key": "def456"} ) assert result["type"] == "progress" @@ -927,6 +905,9 @@ async def test_addon_installed_already_configured( assert result["type"] == "abort" assert result["reason"] == "already_configured" + assert entry.data["url"] == "ws://host1:3001" + assert entry.data["usb_path"] == "/test_new" + assert entry.data["network_key"] == "def456" @pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) From c3bddc0fa6d724c09f277c4c829dbc482d736dd1 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Thu, 4 Mar 2021 23:35:39 +0100 Subject: [PATCH 168/831] Update browse_media.py (#47414) --- homeassistant/components/xbox/browse_media.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/xbox/browse_media.py b/homeassistant/components/xbox/browse_media.py index a91713931c2..2aebb07df1b 100644 --- a/homeassistant/components/xbox/browse_media.py +++ b/homeassistant/components/xbox/browse_media.py @@ -169,7 +169,7 @@ def item_payload(item: InstalledPackage, images: Dict[str, List[Image]]): ) -def _find_media_image(images=List[Image]) -> Optional[Image]: +def _find_media_image(images: List[Image]) -> Optional[Image]: purpose_order = ["Poster", "Tile", "Logo", "BoxArt"] for purpose in purpose_order: for image in images: From 35d5522e79d4ab5ed5ce0f02fe6cc732da309820 Mon Sep 17 00:00:00 2001 From: Cooper Dale Date: Fri, 5 Mar 2021 00:58:42 +0100 Subject: [PATCH 169/831] Fix typo in docs link for forked_daapd (#47413) corrected link to existing site --- homeassistant/components/forked_daapd/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/forked_daapd/manifest.json b/homeassistant/components/forked_daapd/manifest.json index 15f043dbbfe..b9f78875a2d 100644 --- a/homeassistant/components/forked_daapd/manifest.json +++ b/homeassistant/components/forked_daapd/manifest.json @@ -1,7 +1,7 @@ { "domain": "forked_daapd", "name": "forked-daapd", - "documentation": "https://www.home-assistant.io/integrations/forked-daapd", + "documentation": "https://www.home-assistant.io/integrations/forked_daapd", "codeowners": ["@uvjustin"], "requirements": ["pyforked-daapd==0.1.11", "pylibrespot-java==0.1.0"], "config_flow": true, From a1faba29f08eb98460b446cb74ed13edf7b417d9 Mon Sep 17 00:00:00 2001 From: Christophe Painchaud Date: Fri, 5 Mar 2021 01:09:54 +0100 Subject: [PATCH 170/831] Fix RFLink TCP KeepAlive error log (#47395) --- homeassistant/components/rflink/__init__.py | 41 +++++++++++---------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/rflink/__init__.py b/homeassistant/components/rflink/__init__.py index 3cff3beed3c..68783c3426a 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -203,25 +203,28 @@ async def async_setup(hass, config): # TCP port when host configured, otherwise serial port port = config[DOMAIN][CONF_PORT] - # TCP KEEPALIVE will be enabled if value > 0 - keepalive_idle_timer = config[DOMAIN][CONF_KEEPALIVE_IDLE] - if keepalive_idle_timer < 0: - _LOGGER.error( - "A bogus TCP Keepalive IDLE timer was provided (%d secs), " - "default value will be used. " - "Recommended values: 60-3600 (seconds)", - keepalive_idle_timer, - ) - keepalive_idle_timer = DEFAULT_TCP_KEEPALIVE_IDLE_TIMER - elif keepalive_idle_timer == 0: - keepalive_idle_timer = None - elif keepalive_idle_timer <= 30: - _LOGGER.warning( - "A very short TCP Keepalive IDLE timer was provided (%d secs), " - "and may produce unexpected disconnections from RFlink device." - " Recommended values: 60-3600 (seconds)", - keepalive_idle_timer, - ) + keepalive_idle_timer = None + # TCP KeepAlive only if this is TCP based connection (not serial) + if host is not None: + # TCP KEEPALIVE will be enabled if value > 0 + keepalive_idle_timer = config[DOMAIN][CONF_KEEPALIVE_IDLE] + if keepalive_idle_timer < 0: + _LOGGER.error( + "A bogus TCP Keepalive IDLE timer was provided (%d secs), " + "it will be disabled. " + "Recommended values: 60-3600 (seconds)", + keepalive_idle_timer, + ) + keepalive_idle_timer = None + elif keepalive_idle_timer == 0: + keepalive_idle_timer = None + elif keepalive_idle_timer <= 30: + _LOGGER.warning( + "A very short TCP Keepalive IDLE timer was provided (%d secs) " + "and may produce unexpected disconnections from RFlink device." + " Recommended values: 60-3600 (seconds)", + keepalive_idle_timer, + ) @callback def reconnect(exc=None): From ee69e93b46d91b57eb5466a087ae1ad78b5fb1c7 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 4 Mar 2021 19:15:50 -0500 Subject: [PATCH 171/831] Bump zwave-js-server-python to 0.21.0 (#47408) Co-authored-by: Tobias Sauerwein --- homeassistant/components/zwave_js/__init__.py | 74 +-------- homeassistant/components/zwave_js/climate.py | 8 - homeassistant/components/zwave_js/entity.py | 3 - homeassistant/components/zwave_js/helpers.py | 11 -- homeassistant/components/zwave_js/light.py | 8 - .../components/zwave_js/manifest.json | 2 +- homeassistant/components/zwave_js/migrate.py | 113 +++++++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zwave_js/test_api.py | 2 +- tests/components/zwave_js/test_climate.py | 6 +- tests/components/zwave_js/test_init.py | 148 +++++++++++++++++- .../zwave_js/climate_danfoss_lc_13_state.json | 90 +++++++++-- .../zwave_js/climate_heatit_z_trm3_state.json | 1 + 14 files changed, 352 insertions(+), 118 deletions(-) create mode 100644 homeassistant/components/zwave_js/migrate.py diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 637ea0fe08a..519771c1a49 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -48,7 +48,8 @@ from .const import ( ZWAVE_JS_EVENT, ) from .discovery import async_discover_values -from .helpers import get_device_id, get_old_value_id, get_unique_id +from .helpers import get_device_id +from .migrate import async_migrate_discovered_value from .services import ZWaveServices CONNECT_TIMEOUT = 10 @@ -98,31 +99,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: dev_reg = await device_registry.async_get_registry(hass) ent_reg = entity_registry.async_get(hass) - @callback - def migrate_entity(platform: str, old_unique_id: str, new_unique_id: str) -> None: - """Check if entity with old unique ID exists, and if so migrate it to new ID.""" - if entity_id := ent_reg.async_get_entity_id(platform, DOMAIN, old_unique_id): - LOGGER.debug( - "Migrating entity %s from old unique ID '%s' to new unique ID '%s'", - entity_id, - old_unique_id, - new_unique_id, - ) - try: - ent_reg.async_update_entity( - entity_id, - new_unique_id=new_unique_id, - ) - except ValueError: - LOGGER.debug( - ( - "Entity %s can't be migrated because the unique ID is taken. " - "Cleaning it up since it is likely no longer valid." - ), - entity_id, - ) - ent_reg.async_remove(entity_id) - @callback def async_on_node_ready(node: ZwaveNode) -> None: """Handle node ready event.""" @@ -136,49 +112,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: LOGGER.debug("Discovered entity: %s", disc_info) # This migration logic was added in 2021.3 to handle a breaking change to - # the value_id format. Some time in the future, this code block - # (as well as get_old_value_id helper and migrate_entity closure) can be - # removed. - value_ids = [ - # 2021.2.* format - get_old_value_id(disc_info.primary_value), - # 2021.3.0b0 format - disc_info.primary_value.value_id, - ] - - new_unique_id = get_unique_id( - client.driver.controller.home_id, - disc_info.primary_value.value_id, - ) - - for value_id in value_ids: - old_unique_id = get_unique_id( - client.driver.controller.home_id, - f"{disc_info.primary_value.node.node_id}.{value_id}", - ) - # Most entities have the same ID format, but notification binary sensors - # have a state key in their ID so we need to handle them differently - if ( - disc_info.platform == "binary_sensor" - and disc_info.platform_hint == "notification" - ): - for state_key in disc_info.primary_value.metadata.states: - # ignore idle key (0) - if state_key == "0": - continue - - migrate_entity( - disc_info.platform, - f"{old_unique_id}.{state_key}", - f"{new_unique_id}.{state_key}", - ) - - # Once we've iterated through all state keys, we can move on to the - # next item - continue - - migrate_entity(disc_info.platform, old_unique_id, new_unique_id) - + # the value_id format. Some time in the future, this call (as well as the + # helper functions) can be removed. + async_migrate_discovered_value(ent_reg, client, disc_info) async_dispatcher_send( hass, f"{DOMAIN}_{entry.entry_id}_add_{disc_info.platform}", disc_info ) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index cc449b89e91..325cf14b379 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -125,18 +125,10 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): ) self._setpoint_values: Dict[ThermostatSetpointType, ZwaveValue] = {} for enum in ThermostatSetpointType: - # Some devices don't include a property key so we need to check for value - # ID's, both with and without the property key self._setpoint_values[enum] = self.get_zwave_value( THERMOSTAT_SETPOINT_PROPERTY, command_class=CommandClass.THERMOSTAT_SETPOINT, value_property_key=enum.value.key, - value_property_key_name=enum.value.name, - add_to_watched_value_ids=True, - ) or self.get_zwave_value( - THERMOSTAT_SETPOINT_PROPERTY, - command_class=CommandClass.THERMOSTAT_SETPOINT, - value_property_key_name=enum.value.name, add_to_watched_value_ids=True, ) # Use the first found setpoint value to always determine the temperature unit diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index d0ed9eb5291..c061abc4d0d 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -169,7 +169,6 @@ class ZWaveBaseEntity(Entity): command_class: Optional[int] = None, endpoint: Optional[int] = None, value_property_key: Optional[int] = None, - value_property_key_name: Optional[str] = None, add_to_watched_value_ids: bool = True, check_all_endpoints: bool = False, ) -> Optional[ZwaveValue]: @@ -188,7 +187,6 @@ class ZWaveBaseEntity(Entity): value_property, endpoint=endpoint, property_key=value_property_key, - property_key_name=value_property_key_name, ) return_value = self.info.node.values.get(value_id) @@ -203,7 +201,6 @@ class ZWaveBaseEntity(Entity): value_property, endpoint=endpoint_.index, property_key=value_property_key, - property_key_name=value_property_key_name, ) return_value = self.info.node.values.get(value_id) if return_value: diff --git a/homeassistant/components/zwave_js/helpers.py b/homeassistant/components/zwave_js/helpers.py index 9582b7ee054..16baeb816c2 100644 --- a/homeassistant/components/zwave_js/helpers.py +++ b/homeassistant/components/zwave_js/helpers.py @@ -3,7 +3,6 @@ from typing import List, Tuple, cast from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.node import Node as ZwaveNode -from zwave_js_server.model.value import Value as ZwaveValue from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -13,16 +12,6 @@ from homeassistant.helpers.entity_registry import async_get as async_get_ent_reg from .const import DATA_CLIENT, DOMAIN -@callback -def get_old_value_id(value: ZwaveValue) -> str: - """Get old value ID so we can migrate entity unique ID.""" - command_class = value.command_class - endpoint = value.endpoint or "00" - property_ = value.property_ - property_key_name = value.property_key_name or "00" - return f"{value.node.node_id}-{command_class}-{endpoint}-{property_}-{property_key_name}" - - @callback def get_unique_id(home_id: str, value_id: str) -> str: """Get unique ID from home ID and value ID.""" diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index d9c31210bea..b501ecb58e7 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -228,7 +228,6 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): "targetColor", CommandClass.SWITCH_COLOR, value_property_key=None, - value_property_key_name=None, ) if combined_color_val and isinstance(combined_color_val.value, dict): colors_dict = {} @@ -252,7 +251,6 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): "targetColor", CommandClass.SWITCH_COLOR, value_property_key=property_key.key, - value_property_key_name=property_key.name, ) if target_zwave_value is None: # guard for unsupported color @@ -318,31 +316,26 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): "currentColor", CommandClass.SWITCH_COLOR, value_property_key=ColorComponent.RED.value.key, - value_property_key_name=ColorComponent.RED.value.name, ) green_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, value_property_key=ColorComponent.GREEN.value.key, - value_property_key_name=ColorComponent.GREEN.value.name, ) blue_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, value_property_key=ColorComponent.BLUE.value.key, - value_property_key_name=ColorComponent.BLUE.value.name, ) ww_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, value_property_key=ColorComponent.WARM_WHITE.value.key, - value_property_key_name=ColorComponent.WARM_WHITE.value.name, ) cw_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, value_property_key=ColorComponent.COLD_WHITE.value.key, - value_property_key_name=ColorComponent.COLD_WHITE.value.name, ) # prefer the (new) combined color property # https://github.com/zwave-js/node-zwave-js/pull/1782 @@ -350,7 +343,6 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): "currentColor", CommandClass.SWITCH_COLOR, value_property_key=None, - value_property_key_name=None, ) if combined_color_val and isinstance(combined_color_val.value, dict): multi_color = combined_color_val.value diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index c812515a179..2a6f036fa80 100644 --- a/homeassistant/components/zwave_js/manifest.json +++ b/homeassistant/components/zwave_js/manifest.json @@ -3,7 +3,7 @@ "name": "Z-Wave JS", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zwave_js", - "requirements": ["zwave-js-server-python==0.20.1"], + "requirements": ["zwave-js-server-python==0.21.0"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/homeassistant/components/zwave_js/migrate.py b/homeassistant/components/zwave_js/migrate.py new file mode 100644 index 00000000000..49c18073de5 --- /dev/null +++ b/homeassistant/components/zwave_js/migrate.py @@ -0,0 +1,113 @@ +"""Functions used to migrate unique IDs for Z-Wave JS entities.""" +import logging +from typing import List + +from zwave_js_server.client import Client as ZwaveClient +from zwave_js_server.model.value import Value as ZwaveValue + +from homeassistant.core import callback +from homeassistant.helpers.entity_registry import EntityRegistry + +from .const import DOMAIN +from .discovery import ZwaveDiscoveryInfo +from .helpers import get_unique_id + +_LOGGER = logging.getLogger(__name__) + + +@callback +def async_migrate_entity( + ent_reg: EntityRegistry, platform: str, old_unique_id: str, new_unique_id: str +) -> None: + """Check if entity with old unique ID exists, and if so migrate it to new ID.""" + if entity_id := ent_reg.async_get_entity_id(platform, DOMAIN, old_unique_id): + _LOGGER.debug( + "Migrating entity %s from old unique ID '%s' to new unique ID '%s'", + entity_id, + old_unique_id, + new_unique_id, + ) + try: + ent_reg.async_update_entity( + entity_id, + new_unique_id=new_unique_id, + ) + except ValueError: + _LOGGER.debug( + ( + "Entity %s can't be migrated because the unique ID is taken. " + "Cleaning it up since it is likely no longer valid." + ), + entity_id, + ) + ent_reg.async_remove(entity_id) + + +@callback +def async_migrate_discovered_value( + ent_reg: EntityRegistry, client: ZwaveClient, disc_info: ZwaveDiscoveryInfo +) -> None: + """Migrate unique ID for entity/entities tied to discovered value.""" + new_unique_id = get_unique_id( + client.driver.controller.home_id, + disc_info.primary_value.value_id, + ) + + # 2021.2.*, 2021.3.0b0, and 2021.3.0 formats + for value_id in get_old_value_ids(disc_info.primary_value): + old_unique_id = get_unique_id( + client.driver.controller.home_id, + value_id, + ) + # Most entities have the same ID format, but notification binary sensors + # have a state key in their ID so we need to handle them differently + if ( + disc_info.platform == "binary_sensor" + and disc_info.platform_hint == "notification" + ): + for state_key in disc_info.primary_value.metadata.states: + # ignore idle key (0) + if state_key == "0": + continue + + async_migrate_entity( + ent_reg, + disc_info.platform, + f"{old_unique_id}.{state_key}", + f"{new_unique_id}.{state_key}", + ) + + # Once we've iterated through all state keys, we can move on to the + # next item + continue + + async_migrate_entity(ent_reg, disc_info.platform, old_unique_id, new_unique_id) + + +@callback +def get_old_value_ids(value: ZwaveValue) -> List[str]: + """Get old value IDs so we can migrate entity unique ID.""" + value_ids = [] + + # Pre 2021.3.0 value ID + command_class = value.command_class + endpoint = value.endpoint or "00" + property_ = value.property_ + property_key_name = value.property_key_name or "00" + value_ids.append( + f"{value.node.node_id}.{value.node.node_id}-{command_class}-{endpoint}-" + f"{property_}-{property_key_name}" + ) + + endpoint = "00" if value.endpoint is None else value.endpoint + property_key = "00" if value.property_key is None else value.property_key + property_key_name = value.property_key_name or "00" + + value_id = ( + f"{value.node.node_id}-{command_class}-{endpoint}-" + f"{property_}-{property_key}-{property_key_name}" + ) + # 2021.3.0b0 and 2021.3.0 value IDs + value_ids.extend([f"{value.node.node_id}.{value_id}", value_id]) + + return value_ids diff --git a/requirements_all.txt b/requirements_all.txt index 465aaadd454..99854ac3d9f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2394,4 +2394,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.20.1 +zwave-js-server-python==0.21.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 06fb55c9f0b..4517457935b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1231,4 +1231,4 @@ zigpy-znp==0.4.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.20.1 +zwave-js-server-python==0.21.0 diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index 9760e1a06b7..78bac5518a0 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -71,7 +71,7 @@ async def test_websocket_api(hass, integration, multisensor_6, hass_ws_client): result = msg["result"] assert len(result) == 61 - key = "52-112-0-2-00-00" + key = "52-112-0-2" assert result[key]["property"] == 2 assert result[key]["metadata"]["type"] == "number" assert result[key]["configuration_value_type"] == "enumerated" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index ea75f10328c..fe3e0708acc 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -405,7 +405,7 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat assert state assert state.state == HVAC_MODE_HEAT - assert state.attributes[ATTR_TEMPERATURE] == 25 + assert state.attributes[ATTR_TEMPERATURE] == 14 assert state.attributes[ATTR_HVAC_MODES] == [HVAC_MODE_HEAT] assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE @@ -432,6 +432,7 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat "commandClassName": "Thermostat Setpoint", "property": "setpoint", "propertyName": "setpoint", + "propertyKey": 1, "propertyKeyName": "Heating", "ccVersion": 2, "metadata": { @@ -441,7 +442,7 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat "unit": "\u00b0C", "ccSpecific": {"setpointType": 1}, }, - "value": 25, + "value": 14, } assert args["value"] == 21.5 @@ -459,6 +460,7 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat "commandClass": 67, "endpoint": 0, "property": "setpoint", + "propertyKey": 1, "propertyKeyName": "Heating", "propertyName": "setpoint", "newValue": 23, diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 6f60bbc0300..cd2017f2c66 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -160,7 +160,7 @@ async def test_unique_id_migration_dupes( # Check that new RegistryEntry is using new unique ID format entity_entry = ent_reg.async_get(AIR_TEMPERATURE_SENSOR) - new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Air temperature-00-00" + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Air temperature" assert entity_entry.unique_id == new_unique_id assert ent_reg.async_get(f"{AIR_TEMPERATURE_SENSOR}_1") is None @@ -195,7 +195,7 @@ async def test_unique_id_migration_v1(hass, multisensor_6_state, client, integra # Check that new RegistryEntry is using new unique ID format entity_entry = ent_reg.async_get(AIR_TEMPERATURE_SENSOR) - new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Air temperature-00-00" + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Air temperature" assert entity_entry.unique_id == new_unique_id @@ -228,7 +228,147 @@ async def test_unique_id_migration_v2(hass, multisensor_6_state, client, integra # Check that new RegistryEntry is using new unique ID format entity_entry = ent_reg.async_get(ILLUMINANCE_SENSOR) - new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Illuminance-00-00" + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Illuminance" + assert entity_entry.unique_id == new_unique_id + + +async def test_unique_id_migration_v3(hass, multisensor_6_state, client, integration): + """Test unique ID is migrated from old format to new (version 3).""" + ent_reg = entity_registry.async_get(hass) + # Migrate version 2 + ILLUMINANCE_SENSOR = "sensor.multisensor_6_illuminance" + entity_name = ILLUMINANCE_SENSOR.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = f"{client.driver.controller.home_id}.52-49-0-Illuminance-00-00" + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == ILLUMINANCE_SENSOR + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, multisensor_6_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + await hass.async_block_till_done() + + # Check that new RegistryEntry is using new unique ID format + entity_entry = ent_reg.async_get(ILLUMINANCE_SENSOR) + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Illuminance" + assert entity_entry.unique_id == new_unique_id + + +async def test_unique_id_migration_property_key_v1( + hass, hank_binary_switch_state, client, integration +): + """Test unique ID with property key is migrated from old format to new (version 1).""" + ent_reg = entity_registry.async_get(hass) + + SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed" + entity_name = SENSOR_NAME.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = f"{client.driver.controller.home_id}.32.32-50-00-value-W_Consumed" + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == SENSOR_NAME + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, hank_binary_switch_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + await hass.async_block_till_done() + + # Check that new RegistryEntry is using new unique ID format + entity_entry = ent_reg.async_get(SENSOR_NAME) + new_unique_id = f"{client.driver.controller.home_id}.32-50-0-value-66049" + assert entity_entry.unique_id == new_unique_id + + +async def test_unique_id_migration_property_key_v2( + hass, hank_binary_switch_state, client, integration +): + """Test unique ID with property key is migrated from old format to new (version 2).""" + ent_reg = entity_registry.async_get(hass) + + SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed" + entity_name = SENSOR_NAME.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = ( + f"{client.driver.controller.home_id}.32.32-50-0-value-66049-W_Consumed" + ) + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == SENSOR_NAME + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, hank_binary_switch_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + await hass.async_block_till_done() + + # Check that new RegistryEntry is using new unique ID format + entity_entry = ent_reg.async_get(SENSOR_NAME) + new_unique_id = f"{client.driver.controller.home_id}.32-50-0-value-66049" + assert entity_entry.unique_id == new_unique_id + + +async def test_unique_id_migration_property_key_v3( + hass, hank_binary_switch_state, client, integration +): + """Test unique ID with property key is migrated from old format to new (version 3).""" + ent_reg = entity_registry.async_get(hass) + + SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed" + entity_name = SENSOR_NAME.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = f"{client.driver.controller.home_id}.32-50-0-value-66049-W_Consumed" + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == SENSOR_NAME + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, hank_binary_switch_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + await hass.async_block_till_done() + + # Check that new RegistryEntry is using new unique ID format + entity_entry = ent_reg.async_get(SENSOR_NAME) + new_unique_id = f"{client.driver.controller.home_id}.32-50-0-value-66049" assert entity_entry.unique_id == new_unique_id @@ -262,7 +402,7 @@ async def test_unique_id_migration_notification_binary_sensor( # Check that new RegistryEntry is using new unique ID format entity_entry = ent_reg.async_get(NOTIFICATION_MOTION_BINARY_SENSOR) - new_unique_id = f"{client.driver.controller.home_id}.52-113-0-Home Security-Motion sensor status-Motion sensor status.8" + new_unique_id = f"{client.driver.controller.home_id}.52-113-0-Home Security-Motion sensor status.8" assert entity_entry.unique_id == new_unique_id diff --git a/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json b/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json index 90410998597..8574674714f 100644 --- a/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json +++ b/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json @@ -4,11 +4,25 @@ "status": 1, "ready": true, "deviceClass": { - "basic": {"key": 4, "label":"Routing Slave"}, - "generic": {"key": 8, "label":"Thermostat"}, - "specific": {"key": 4, "label":"Setpoint Thermostat"}, - "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "basic": { + "key": 4, + "label": "Routing Slave" + }, + "generic": { + "key": 8, + "label": "Thermostat" + }, + "specific": { + "key": 4, + "label": "Setpoint Thermostat" + }, + "mandatorySupportedCCs": [ + 114, + 143, + 67, + 134 + ], + "mandatoryControlledCCs": [] }, "isListening": false, "isFrequentListening": false, @@ -22,6 +36,7 @@ "productType": 5, "firmwareVersion": "1.1", "deviceConfig": { + "filename": "/usr/src/app/node_modules/@zwave-js/config/config/devices/0x0002/lc-13.json", "manufacturerId": 2, "manufacturer": "Danfoss", "label": "LC-13", @@ -66,19 +81,76 @@ 14 ], "interviewAttempts": 1, + "interviewStage": 7, + "commandClasses": [ + { + "id": 67, + "name": "Thermostat Setpoint", + "version": 2, + "isSecure": false + }, + { + "id": 70, + "name": "Climate Control Schedule", + "version": 1, + "isSecure": false + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 1, + "isSecure": false + }, + { + "id": 117, + "name": "Protection", + "version": 2, + "isSecure": false + }, + { + "id": 128, + "name": "Battery", + "version": 1, + "isSecure": false + }, + { + "id": 129, + "name": "Clock", + "version": 1, + "isSecure": false + }, + { + "id": 132, + "name": "Wake Up", + "version": 1, + "isSecure": false + }, + { + "id": 134, + "name": "Version", + "version": 1, + "isSecure": false + }, + { + "id": 143, + "name": "Multi Command", + "version": 1, + "isSecure": false + } + ], "endpoints": [ { "nodeId": 5, "index": 0 } ], - "commandClasses": [], "values": [ { "endpoint": 0, "commandClass": 67, "commandClassName": "Thermostat Setpoint", "property": "setpoint", + "propertyKey": 1, "propertyName": "setpoint", "propertyKeyName": "Heating", "ccVersion": 2, @@ -91,7 +163,7 @@ "setpointType": 1 } }, - "value": 25 + "value": 14 }, { "endpoint": 0, @@ -262,7 +334,7 @@ "unit": "%", "label": "Battery level" }, - "value": 53 + "value": 49 }, { "endpoint": 0, @@ -361,4 +433,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json b/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json index 0dc040c6cb2..b26b69be9ad 100644 --- a/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json +++ b/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json @@ -837,6 +837,7 @@ "commandClassName": "Thermostat Setpoint", "property": "setpoint", "propertyName": "setpoint", + "propertyKey": 1, "propertyKeyName": "Heating", "ccVersion": 3, "metadata": { From 0350a6ed215fef56696053efa6a63acea468ff45 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 5 Mar 2021 01:38:33 +0100 Subject: [PATCH 172/831] Only create snapshot if add-on update will be done (#47424) --- homeassistant/components/zwave_js/addon.py | 2 +- tests/components/zwave_js/test_init.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/zwave_js/addon.py b/homeassistant/components/zwave_js/addon.py index b8d020cfb00..818e46a34aa 100644 --- a/homeassistant/components/zwave_js/addon.py +++ b/homeassistant/components/zwave_js/addon.py @@ -173,6 +173,7 @@ class AddonManager: if not update_available: return + await self.async_create_snapshot() await async_update_addon(self._hass, ADDON_SLUG) @callback @@ -184,7 +185,6 @@ class AddonManager: if not self._update_task or self._update_task.done(): LOGGER.info("Trying to update the Z-Wave JS add-on") self._update_task = self._async_schedule_addon_operation( - self.async_create_snapshot, self.async_update_addon, catch_error=catch_error, ) diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index cd2017f2c66..e56db58f3cc 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -585,11 +585,13 @@ async def test_addon_info_failure( @pytest.mark.parametrize( - "addon_version, update_available, update_calls, update_addon_side_effect", + "addon_version, update_available, update_calls, snapshot_calls, " + "update_addon_side_effect, create_shapshot_side_effect", [ - ("1.0", True, 1, None), - ("1.0", False, 0, None), - ("1.0", True, 1, HassioAPIError("Boom")), + ("1.0", True, 1, 1, None, None), + ("1.0", False, 0, 0, None, None), + ("1.0", True, 1, 1, HassioAPIError("Boom"), None), + ("1.0", True, 0, 1, None, HassioAPIError("Boom")), ], ) async def test_update_addon( @@ -604,11 +606,14 @@ async def test_update_addon( addon_version, update_available, update_calls, + snapshot_calls, update_addon_side_effect, + create_shapshot_side_effect, ): """Test update the Z-Wave JS add-on during entry setup.""" addon_info.return_value["version"] = addon_version addon_info.return_value["update_available"] = update_available + create_shapshot.side_effect = create_shapshot_side_effect update_addon.side_effect = update_addon_side_effect client.connect.side_effect = InvalidServerVersion("Invalid version") device = "/test" @@ -630,12 +635,7 @@ async def test_update_addon( await hass.async_block_till_done() assert entry.state == ENTRY_STATE_SETUP_RETRY - assert create_shapshot.call_count == 1 - assert create_shapshot.call_args == call( - hass, - {"name": f"addon_core_zwave_js_{addon_version}", "addons": ["core_zwave_js"]}, - partial=True, - ) + assert create_shapshot.call_count == snapshot_calls assert update_addon.call_count == update_calls From 6a4b755fafad5137fe29e3799b5f3f80429e80a5 Mon Sep 17 00:00:00 2001 From: Paul Dee <647633+systemcrash@users.noreply.github.com> Date: Fri, 5 Mar 2021 15:36:07 +0100 Subject: [PATCH 173/831] Spellcheck on Synology component (#47451) --- homeassistant/components/synology_dsm/const.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index 97f378c8e76..ba1aa393c85 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -128,21 +128,21 @@ UTILISATION_SENSORS = { ENTITY_ENABLE: True, }, f"{SynoCoreUtilization.API_KEY}:cpu_1min_load": { - ENTITY_NAME: "CPU Load Averarge (1 min)", + ENTITY_NAME: "CPU Load Average (1 min)", ENTITY_UNIT: ENTITY_UNIT_LOAD, ENTITY_ICON: "mdi:chip", ENTITY_CLASS: None, ENTITY_ENABLE: False, }, f"{SynoCoreUtilization.API_KEY}:cpu_5min_load": { - ENTITY_NAME: "CPU Load Averarge (5 min)", + ENTITY_NAME: "CPU Load Average (5 min)", ENTITY_UNIT: ENTITY_UNIT_LOAD, ENTITY_ICON: "mdi:chip", ENTITY_CLASS: None, ENTITY_ENABLE: True, }, f"{SynoCoreUtilization.API_KEY}:cpu_15min_load": { - ENTITY_NAME: "CPU Load Averarge (15 min)", + ENTITY_NAME: "CPU Load Average (15 min)", ENTITY_UNIT: ENTITY_UNIT_LOAD, ENTITY_ICON: "mdi:chip", ENTITY_CLASS: None, From 864380e77c277b58ab4a85f5d1d3c023b31cf4c7 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Fri, 5 Mar 2021 22:51:07 +0800 Subject: [PATCH 174/831] Add allenporter to stream codeowners (#47431) --- CODEOWNERS | 2 +- homeassistant/components/stream/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 5324c15db6a..07340065588 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -453,7 +453,7 @@ homeassistant/components/starline/* @anonym-tsk homeassistant/components/statistics/* @fabaff homeassistant/components/stiebel_eltron/* @fucm homeassistant/components/stookalert/* @fwestenberg -homeassistant/components/stream/* @hunterjm @uvjustin +homeassistant/components/stream/* @hunterjm @uvjustin @allenporter homeassistant/components/stt/* @pvizeli homeassistant/components/subaru/* @G-Two homeassistant/components/suez_water/* @ooii diff --git a/homeassistant/components/stream/manifest.json b/homeassistant/components/stream/manifest.json index 19b9e7b2e8a..400b50eae04 100644 --- a/homeassistant/components/stream/manifest.json +++ b/homeassistant/components/stream/manifest.json @@ -4,6 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/stream", "requirements": ["av==8.0.3"], "dependencies": ["http"], - "codeowners": ["@hunterjm", "@uvjustin"], + "codeowners": ["@hunterjm", "@uvjustin", "@allenporter"], "quality_scale": "internal" } From 79ebe930e31a91967928a5642c98e58b522109b0 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Fri, 5 Mar 2021 16:16:07 +0100 Subject: [PATCH 175/831] Limit log spam by ESPHome (#47456) --- homeassistant/components/esphome/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index 6ce411b5169..b9720dcdf80 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -256,13 +256,16 @@ async def _setup_auto_reconnect_logic( # really short reconnect interval. tries = min(tries, 10) # prevent OverflowError wait_time = int(round(min(1.8 ** tries, 60.0))) - _LOGGER.info("Trying to reconnect to %s in %s seconds", host, wait_time) + if tries == 1: + _LOGGER.info("Trying to reconnect to %s in the background", host) + _LOGGER.debug("Retrying %s in %d seconds", host, wait_time) await asyncio.sleep(wait_time) try: await cli.connect(on_stop=try_connect, login=True) except APIConnectionError as error: - _LOGGER.info( + logger = _LOGGER.info if tries == 0 else _LOGGER.debug + logger( "Can't connect to ESPHome API for %s (%s): %s", entry.unique_id, host, From cad5e675889341206da86f442f04ac9ee402f51f Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 5 Mar 2021 12:41:36 -0500 Subject: [PATCH 176/831] Bump zwave-js-server-python to 0.21.1 (#47464) --- homeassistant/components/zwave_js/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index 2a6f036fa80..de0f2cc0a6f 100644 --- a/homeassistant/components/zwave_js/manifest.json +++ b/homeassistant/components/zwave_js/manifest.json @@ -3,7 +3,7 @@ "name": "Z-Wave JS", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zwave_js", - "requirements": ["zwave-js-server-python==0.21.0"], + "requirements": ["zwave-js-server-python==0.21.1"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index 99854ac3d9f..cc2c08a39aa 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2394,4 +2394,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.21.0 +zwave-js-server-python==0.21.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4517457935b..607b7deb7e6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1231,4 +1231,4 @@ zigpy-znp==0.4.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.21.0 +zwave-js-server-python==0.21.1 From a6c5e79de21a1f97c64478d14aa3d8d53cda373e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 5 Mar 2021 18:42:08 +0100 Subject: [PATCH 177/831] Update frontend to 20210302.5 (#47462) --- 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 a5b4c6f10d5..8093c65d91a 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20210302.4" + "home-assistant-frontend==20210302.5" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 919b53f64ed..0586b956f39 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210302.4 +home-assistant-frontend==20210302.5 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index cc2c08a39aa..883d78815ba 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.4 +home-assistant-frontend==20210302.5 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 607b7deb7e6..a916bb02992 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.4 +home-assistant-frontend==20210302.5 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From f2a2dbb561b31cc559918b786b2ca04454750e89 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Fri, 5 Mar 2021 18:42:20 +0100 Subject: [PATCH 178/831] Bump version with fix for v1 (#47458) --- homeassistant/components/philips_js/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/philips_js/manifest.json b/homeassistant/components/philips_js/manifest.json index e1e1fa69b6b..9ed1cedbf05 100644 --- a/homeassistant/components/philips_js/manifest.json +++ b/homeassistant/components/philips_js/manifest.json @@ -3,7 +3,7 @@ "name": "Philips TV", "documentation": "https://www.home-assistant.io/integrations/philips_js", "requirements": [ - "ha-philipsjs==2.3.0" + "ha-philipsjs==2.3.1" ], "codeowners": [ "@elupus" diff --git a/requirements_all.txt b/requirements_all.txt index 883d78815ba..f52f9b7e353 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -718,7 +718,7 @@ guppy3==3.1.0 ha-ffmpeg==3.0.2 # homeassistant.components.philips_js -ha-philipsjs==2.3.0 +ha-philipsjs==2.3.1 # homeassistant.components.habitica habitipy==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a916bb02992..b60ce427b00 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -379,7 +379,7 @@ guppy3==3.1.0 ha-ffmpeg==3.0.2 # homeassistant.components.philips_js -ha-philipsjs==2.3.0 +ha-philipsjs==2.3.1 # homeassistant.components.habitica habitipy==0.2.0 From cc99fd5e32c6755fa9a1a4768ebc897cc6820c61 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 5 Mar 2021 18:43:26 +0100 Subject: [PATCH 179/831] Fix Hue scene overriding Hue default transition times (#47454) --- homeassistant/components/hue/bridge.py | 7 ++----- homeassistant/components/hue/const.py | 2 -- tests/components/hue/test_bridge.py | 1 + 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/hue/bridge.py b/homeassistant/components/hue/bridge.py index 201e9f3a546..dc9b56fcdfe 100644 --- a/homeassistant/components/hue/bridge.py +++ b/homeassistant/components/hue/bridge.py @@ -19,7 +19,6 @@ from .const import ( CONF_ALLOW_UNREACHABLE, DEFAULT_ALLOW_HUE_GROUPS, DEFAULT_ALLOW_UNREACHABLE, - DEFAULT_SCENE_TRANSITION, LOGGER, ) from .errors import AuthenticationRequired, CannotConnect @@ -34,9 +33,7 @@ SCENE_SCHEMA = vol.Schema( { vol.Required(ATTR_GROUP_NAME): cv.string, vol.Required(ATTR_SCENE_NAME): cv.string, - vol.Optional( - ATTR_TRANSITION, default=DEFAULT_SCENE_TRANSITION - ): cv.positive_int, + vol.Optional(ATTR_TRANSITION): cv.positive_int, } ) # How long should we sleep if the hub is busy @@ -209,7 +206,7 @@ class HueBridge: """Service to call directly into bridge to set scenes.""" group_name = call.data[ATTR_GROUP_NAME] scene_name = call.data[ATTR_SCENE_NAME] - transition = call.data.get(ATTR_TRANSITION, DEFAULT_SCENE_TRANSITION) + transition = call.data.get(ATTR_TRANSITION) group = next( (group for group in self.api.groups.values() if group.name == group_name), diff --git a/homeassistant/components/hue/const.py b/homeassistant/components/hue/const.py index b782ce70193..8d01617073b 100644 --- a/homeassistant/components/hue/const.py +++ b/homeassistant/components/hue/const.py @@ -14,8 +14,6 @@ DEFAULT_ALLOW_UNREACHABLE = False CONF_ALLOW_HUE_GROUPS = "allow_hue_groups" DEFAULT_ALLOW_HUE_GROUPS = False -DEFAULT_SCENE_TRANSITION = 4 - GROUP_TYPE_LIGHT_GROUP = "LightGroup" GROUP_TYPE_ROOM = "Room" GROUP_TYPE_LUMINAIRE = "Luminaire" diff --git a/tests/components/hue/test_bridge.py b/tests/components/hue/test_bridge.py index 29bc2acf03a..093f6356b09 100644 --- a/tests/components/hue/test_bridge.py +++ b/tests/components/hue/test_bridge.py @@ -189,6 +189,7 @@ async def test_hue_activate_scene(hass, mock_api): assert len(mock_api.mock_requests) == 3 assert mock_api.mock_requests[2]["json"]["scene"] == "scene_1" + assert "transitiontime" not in mock_api.mock_requests[2]["json"] assert mock_api.mock_requests[2]["path"] == "groups/group_1/action" From 3baeed3684cd053b40c513380cf2f99ba2603cdb Mon Sep 17 00:00:00 2001 From: tkdrob Date: Fri, 5 Mar 2021 13:08:04 -0500 Subject: [PATCH 180/831] Clean up constants (#47323) --- .../components/automation/__init__.py | 2 +- homeassistant/components/script/__init__.py | 2 +- .../components/seventeentrack/sensor.py | 2 +- .../components/shopping_list/__init__.py | 3 +- homeassistant/components/slack/notify.py | 3 +- homeassistant/components/spaceapi/__init__.py | 2 +- homeassistant/components/upb/__init__.py | 3 +- homeassistant/components/upb/const.py | 1 - homeassistant/components/upcloud/__init__.py | 1 - homeassistant/components/vilfo/const.py | 9 ++++-- homeassistant/components/vilfo/sensor.py | 2 +- homeassistant/components/webostv/__init__.py | 28 +++++++++---------- homeassistant/components/webostv/const.py | 1 - .../components/xiaomi_aqara/__init__.py | 2 +- homeassistant/components/xiaomi_miio/fan.py | 2 +- .../components/xiaomi_miio/sensor.py | 2 +- .../components/xiaomi_miio/switch.py | 2 +- homeassistant/components/zha/api.py | 3 +- .../components/zha/core/channels/base.py | 2 +- homeassistant/components/zha/core/const.py | 2 -- homeassistant/components/zha/core/device.py | 3 +- homeassistant/components/zha/entity.py | 2 +- homeassistant/components/zwave/__init__.py | 9 +++--- homeassistant/components/zwave/const.py | 1 - homeassistant/components/zwave_js/__init__.py | 8 ++++-- homeassistant/components/zwave_js/const.py | 1 - homeassistant/helpers/script.py | 1 - tests/components/webostv/test_media_player.py | 2 +- tests/components/zha/test_api.py | 2 +- tests/components/zwave/test_init.py | 12 ++++---- 30 files changed, 55 insertions(+), 60 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index acb28df05b0..f12a7398f7e 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -10,6 +10,7 @@ from voluptuous.humanize import humanize_error from homeassistant.components import blueprint from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_MODE, ATTR_NAME, CONF_ALIAS, CONF_CONDITION, @@ -53,7 +54,6 @@ from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.script import ( ATTR_CUR, ATTR_MAX, - ATTR_MODE, CONF_MAX, CONF_MAX_EXCEEDED, Script, diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index 429e97230ce..e408be47f65 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -7,6 +7,7 @@ import voluptuous as vol from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_MODE, ATTR_NAME, CONF_ALIAS, CONF_ICON, @@ -27,7 +28,6 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.script import ( ATTR_CUR, ATTR_MAX, - ATTR_MODE, CONF_MAX, CONF_MAX_EXCEEDED, SCRIPT_MODE_SINGLE, diff --git a/homeassistant/components/seventeentrack/sensor.py b/homeassistant/components/seventeentrack/sensor.py index fa94ca4e384..07cfe9ca66f 100644 --- a/homeassistant/components/seventeentrack/sensor.py +++ b/homeassistant/components/seventeentrack/sensor.py @@ -9,6 +9,7 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( ATTR_ATTRIBUTION, + ATTR_FRIENDLY_NAME, ATTR_LOCATION, CONF_PASSWORD, CONF_SCAN_INTERVAL, @@ -22,7 +23,6 @@ from homeassistant.util import Throttle, slugify _LOGGER = logging.getLogger(__name__) ATTR_DESTINATION_COUNTRY = "destination_country" -ATTR_FRIENDLY_NAME = "friendly_name" ATTR_INFO_TEXT = "info_text" ATTR_ORIGIN_COUNTRY = "origin_country" ATTR_PACKAGES = "packages" diff --git a/homeassistant/components/shopping_list/__init__.py b/homeassistant/components/shopping_list/__init__.py index e438bf3b8f4..841865cd759 100644 --- a/homeassistant/components/shopping_list/__init__.py +++ b/homeassistant/components/shopping_list/__init__.py @@ -7,14 +7,13 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components import http, websocket_api from homeassistant.components.http.data_validator import RequestDataValidator -from homeassistant.const import HTTP_BAD_REQUEST, HTTP_NOT_FOUND +from homeassistant.const import ATTR_NAME, HTTP_BAD_REQUEST, HTTP_NOT_FOUND from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.util.json import load_json, save_json from .const import DOMAIN -ATTR_NAME = "name" ATTR_COMPLETE = "complete" _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/slack/notify.py b/homeassistant/components/slack/notify.py index fb7b949e3ed..f1e293773bd 100644 --- a/homeassistant/components/slack/notify.py +++ b/homeassistant/components/slack/notify.py @@ -20,7 +20,7 @@ from homeassistant.components.notify import ( PLATFORM_SCHEMA, BaseNotificationService, ) -from homeassistant.const import CONF_API_KEY, CONF_ICON, CONF_USERNAME +from homeassistant.const import ATTR_ICON, CONF_API_KEY, CONF_ICON, CONF_USERNAME from homeassistant.core import callback from homeassistant.helpers import aiohttp_client, config_validation as cv import homeassistant.helpers.template as template @@ -35,7 +35,6 @@ _LOGGER = logging.getLogger(__name__) ATTR_BLOCKS = "blocks" ATTR_BLOCKS_TEMPLATE = "blocks_template" ATTR_FILE = "file" -ATTR_ICON = "icon" ATTR_PASSWORD = "password" ATTR_PATH = "path" ATTR_URL = "url" diff --git a/homeassistant/components/spaceapi/__init__.py b/homeassistant/components/spaceapi/__init__.py index 571e0ab62f3..f6def03ec6a 100644 --- a/homeassistant/components/spaceapi/__init__.py +++ b/homeassistant/components/spaceapi/__init__.py @@ -6,6 +6,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_ICON, ATTR_LOCATION, + ATTR_NAME, ATTR_STATE, ATTR_UNIT_OF_MEASUREMENT, CONF_ADDRESS, @@ -35,7 +36,6 @@ ATTR_CONTACT = "contact" ATTR_ISSUE_REPORT_CHANNELS = "issue_report_channels" ATTR_LASTCHANGE = "lastchange" ATTR_LOGO = "logo" -ATTR_NAME = "name" ATTR_OPEN = "open" ATTR_SENSORS = "sensors" ATTR_SPACE = "space" diff --git a/homeassistant/components/upb/__init__.py b/homeassistant/components/upb/__init__.py index 70f37451e6b..bf23be16af2 100644 --- a/homeassistant/components/upb/__init__.py +++ b/homeassistant/components/upb/__init__.py @@ -3,7 +3,7 @@ import asyncio import upb_lib -from homeassistant.const import CONF_FILE_PATH, CONF_HOST +from homeassistant.const import ATTR_COMMAND, CONF_FILE_PATH, CONF_HOST from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType @@ -11,7 +11,6 @@ from homeassistant.helpers.typing import ConfigType from .const import ( ATTR_ADDRESS, ATTR_BRIGHTNESS_PCT, - ATTR_COMMAND, ATTR_RATE, DOMAIN, EVENT_UPB_SCENE_CHANGED, diff --git a/homeassistant/components/upb/const.py b/homeassistant/components/upb/const.py index 75d754087e4..8a2c435a70f 100644 --- a/homeassistant/components/upb/const.py +++ b/homeassistant/components/upb/const.py @@ -10,7 +10,6 @@ ATTR_ADDRESS = "address" ATTR_BLINK_RATE = "blink_rate" ATTR_BRIGHTNESS = "brightness" ATTR_BRIGHTNESS_PCT = "brightness_pct" -ATTR_COMMAND = "command" ATTR_RATE = "rate" CONF_NETWORK = "network" EVENT_UPB_SCENE_CHANGED = "upb.scene_changed" diff --git a/homeassistant/components/upcloud/__init__.py b/homeassistant/components/upcloud/__init__.py index 5acf9e364bc..bb96ef0f1d6 100644 --- a/homeassistant/components/upcloud/__init__.py +++ b/homeassistant/components/upcloud/__init__.py @@ -40,7 +40,6 @@ _LOGGER = logging.getLogger(__name__) ATTR_CORE_NUMBER = "core_number" ATTR_HOSTNAME = "hostname" ATTR_MEMORY_AMOUNT = "memory_amount" -ATTR_STATE = "state" ATTR_TITLE = "title" ATTR_UUID = "uuid" ATTR_ZONE = "zone" diff --git a/homeassistant/components/vilfo/const.py b/homeassistant/components/vilfo/const.py index 74eb813bcc5..d47e738a858 100644 --- a/homeassistant/components/vilfo/const.py +++ b/homeassistant/components/vilfo/const.py @@ -1,13 +1,16 @@ """Constants for the Vilfo Router integration.""" -from homeassistant.const import DEVICE_CLASS_TIMESTAMP, PERCENTAGE +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + ATTR_ICON, + DEVICE_CLASS_TIMESTAMP, + PERCENTAGE, +) DOMAIN = "vilfo" ATTR_API_DATA_FIELD = "api_data_field" ATTR_API_DATA_FIELD_LOAD = "load" ATTR_API_DATA_FIELD_BOOT_TIME = "boot_time" -ATTR_DEVICE_CLASS = "device_class" -ATTR_ICON = "icon" ATTR_LABEL = "label" ATTR_LOAD = "load" ATTR_UNIT = "unit" diff --git a/homeassistant/components/vilfo/sensor.py b/homeassistant/components/vilfo/sensor.py index e2909647c2d..80a14354913 100644 --- a/homeassistant/components/vilfo/sensor.py +++ b/homeassistant/components/vilfo/sensor.py @@ -1,10 +1,10 @@ """Support for Vilfo Router sensors.""" +from homeassistant.const import ATTR_ICON from homeassistant.helpers.entity import Entity from .const import ( ATTR_API_DATA_FIELD, ATTR_DEVICE_CLASS, - ATTR_ICON, ATTR_LABEL, ATTR_UNIT, DOMAIN, diff --git a/homeassistant/components/webostv/__init__.py b/homeassistant/components/webostv/__init__.py index ebac158c452..3a117955bee 100644 --- a/homeassistant/components/webostv/__init__.py +++ b/homeassistant/components/webostv/__init__.py @@ -6,20 +6,8 @@ from aiopylgtv import PyLGTVCmdException, PyLGTVPairException, WebOsClient import voluptuous as vol from websockets.exceptions import ConnectionClosed -from homeassistant.components.webostv.const import ( - ATTR_BUTTON, - ATTR_COMMAND, - ATTR_PAYLOAD, - CONF_ON_ACTION, - CONF_SOURCES, - DEFAULT_NAME, - DOMAIN, - SERVICE_BUTTON, - SERVICE_COMMAND, - SERVICE_SELECT_SOUND_OUTPUT, - WEBOSTV_CONFIG_FILE, -) from homeassistant.const import ( + ATTR_COMMAND, ATTR_ENTITY_ID, CONF_CUSTOMIZE, CONF_HOST, @@ -30,7 +18,19 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send -from .const import ATTR_SOUND_OUTPUT +from .const import ( + ATTR_BUTTON, + ATTR_PAYLOAD, + ATTR_SOUND_OUTPUT, + CONF_ON_ACTION, + CONF_SOURCES, + DEFAULT_NAME, + DOMAIN, + SERVICE_BUTTON, + SERVICE_COMMAND, + SERVICE_SELECT_SOUND_OUTPUT, + WEBOSTV_CONFIG_FILE, +) CUSTOMIZE_SCHEMA = vol.Schema( {vol.Optional(CONF_SOURCES, default=[]): vol.All(cv.ensure_list, [cv.string])} diff --git a/homeassistant/components/webostv/const.py b/homeassistant/components/webostv/const.py index bea485a7d68..9091491a29d 100644 --- a/homeassistant/components/webostv/const.py +++ b/homeassistant/components/webostv/const.py @@ -4,7 +4,6 @@ DOMAIN = "webostv" DEFAULT_NAME = "LG webOS Smart TV" ATTR_BUTTON = "button" -ATTR_COMMAND = "command" ATTR_PAYLOAD = "payload" ATTR_SOUND_OUTPUT = "sound_output" diff --git a/homeassistant/components/xiaomi_aqara/__init__.py b/homeassistant/components/xiaomi_aqara/__init__.py index a33f697abdf..53a9427d30a 100644 --- a/homeassistant/components/xiaomi_aqara/__init__.py +++ b/homeassistant/components/xiaomi_aqara/__init__.py @@ -9,6 +9,7 @@ from xiaomi_gateway import XiaomiGateway, XiaomiGatewayDiscovery from homeassistant import config_entries, core from homeassistant.const import ( ATTR_BATTERY_LEVEL, + ATTR_DEVICE_ID, ATTR_VOLTAGE, CONF_HOST, CONF_MAC, @@ -42,7 +43,6 @@ GATEWAY_PLATFORMS_NO_KEY = ["binary_sensor", "sensor"] ATTR_GW_MAC = "gw_mac" ATTR_RINGTONE_ID = "ringtone_id" ATTR_RINGTONE_VOL = "ringtone_vol" -ATTR_DEVICE_ID = "device_id" TIME_TILL_UNAVAILABLE = timedelta(minutes=150) diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index c8e200516a0..addb1a3cbca 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -47,6 +47,7 @@ from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_MODE, + ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, CONF_TOKEN, @@ -112,7 +113,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ATTR_MODEL = "model" # Air Purifier -ATTR_TEMPERATURE = "temperature" ATTR_HUMIDITY = "humidity" ATTR_AIR_QUALITY_INDEX = "aqi" ATTR_FILTER_HOURS_USED = "filter_hours_used" diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index d6b87000f40..a7cfdd788a5 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -14,6 +14,7 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( + ATTR_BATTERY_LEVEL, CONF_HOST, CONF_NAME, CONF_TOKEN, @@ -48,7 +49,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ATTR_POWER = "power" ATTR_CHARGING = "charging" -ATTR_BATTERY_LEVEL = "battery_level" ATTR_DISPLAY_CLOCK = "display_clock" ATTR_NIGHT_MODE = "night_mode" ATTR_NIGHT_TIME_BEGIN = "night_time_begin" diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index b290cc6a956..0a35e8e0a35 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -12,6 +12,7 @@ from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_MODE, + ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, CONF_TOKEN, @@ -63,7 +64,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) ATTR_POWER = "power" -ATTR_TEMPERATURE = "temperature" ATTR_LOAD_POWER = "load_power" ATTR_MODEL = "model" ATTR_POWER_MODE = "power_mode" diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index 2bd712ff681..45f7d540052 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -11,6 +11,7 @@ from zigpy.types.named import EUI64 import zigpy.zdo.types as zdo_types from homeassistant.components import websocket_api +from homeassistant.const import ATTR_COMMAND, ATTR_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -20,14 +21,12 @@ from .core.const import ( ATTR_ATTRIBUTE, ATTR_CLUSTER_ID, ATTR_CLUSTER_TYPE, - ATTR_COMMAND, ATTR_COMMAND_TYPE, ATTR_ENDPOINT_ID, ATTR_IEEE, ATTR_LEVEL, ATTR_MANUFACTURER, ATTR_MEMBERS, - ATTR_NAME, ATTR_VALUE, ATTR_WARNING_DEVICE_DURATION, ATTR_WARNING_DEVICE_MODE, diff --git a/homeassistant/components/zha/core/channels/base.py b/homeassistant/components/zha/core/channels/base.py index 2dbd1629487..14774c09550 100644 --- a/homeassistant/components/zha/core/channels/base.py +++ b/homeassistant/components/zha/core/channels/base.py @@ -8,6 +8,7 @@ from typing import Any, Union import zigpy.exceptions +from homeassistant.const import ATTR_COMMAND from homeassistant.core import callback from .. import typing as zha_typing @@ -16,7 +17,6 @@ from ..const import ( ATTR_ATTRIBUTE_ID, ATTR_ATTRIBUTE_NAME, ATTR_CLUSTER_ID, - ATTR_COMMAND, ATTR_UNIQUE_ID, ATTR_VALUE, CHANNEL_ZDO, diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 2454085d9a4..672fafdd98f 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -31,7 +31,6 @@ ATTR_ATTRIBUTE_NAME = "attribute_name" ATTR_AVAILABLE = "available" ATTR_CLUSTER_ID = "cluster_id" ATTR_CLUSTER_TYPE = "cluster_type" -ATTR_COMMAND = "command" ATTR_COMMAND_TYPE = "command_type" ATTR_DEVICE_IEEE = "device_ieee" ATTR_DEVICE_TYPE = "device_type" @@ -47,7 +46,6 @@ ATTR_MANUFACTURER = "manufacturer" ATTR_MANUFACTURER_CODE = "manufacturer_code" ATTR_MEMBERS = "members" ATTR_MODEL = "model" -ATTR_NAME = "name" ATTR_NEIGHBORS = "neighbors" ATTR_NODE_DESCRIPTOR = "node_descriptor" ATTR_NWK = "nwk" diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index cd3b1bd93ce..4f93a7a95d6 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -14,6 +14,7 @@ import zigpy.quirks from zigpy.zcl.clusters.general import Groups import zigpy.zdo.types as zdo_types +from homeassistant.const import ATTR_COMMAND, ATTR_NAME from homeassistant.core import callback from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, @@ -28,7 +29,6 @@ from .const import ( ATTR_ATTRIBUTE, ATTR_AVAILABLE, ATTR_CLUSTER_ID, - ATTR_COMMAND, ATTR_COMMAND_TYPE, ATTR_DEVICE_TYPE, ATTR_ENDPOINT_ID, @@ -40,7 +40,6 @@ from .const import ( ATTR_MANUFACTURER, ATTR_MANUFACTURER_CODE, ATTR_MODEL, - ATTR_NAME, ATTR_NEIGHBORS, ATTR_NODE_DESCRIPTOR, ATTR_NWK, diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index db30e9e178c..3c8f0c59cb1 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -5,6 +5,7 @@ import functools import logging from typing import Any, Awaitable, Dict, List, Optional +from homeassistant.const import ATTR_NAME from homeassistant.core import CALLBACK_TYPE, Event, callback from homeassistant.helpers import entity from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE @@ -18,7 +19,6 @@ from homeassistant.helpers.restore_state import RestoreEntity from .core.const import ( ATTR_MANUFACTURER, ATTR_MODEL, - ATTR_NAME, DATA_ZHA, DATA_ZHA_BRIDGE_ID, DOMAIN, diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 0ab1f786555..649f17ff08f 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -11,6 +11,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_NAME, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, ) @@ -103,7 +104,7 @@ PLATFORMS = [ RENAME_NODE_SCHEMA = vol.Schema( { vol.Required(const.ATTR_NODE_ID): vol.Coerce(int), - vol.Required(const.ATTR_NAME): cv.string, + vol.Required(ATTR_NAME): cv.string, vol.Optional(const.ATTR_UPDATE_IDS, default=False): cv.boolean, } ) @@ -112,7 +113,7 @@ RENAME_VALUE_SCHEMA = vol.Schema( { vol.Required(const.ATTR_NODE_ID): vol.Coerce(int), vol.Required(const.ATTR_VALUE_ID): vol.Coerce(int), - vol.Required(const.ATTR_NAME): cv.string, + vol.Required(ATTR_NAME): cv.string, vol.Optional(const.ATTR_UPDATE_IDS, default=False): cv.boolean, } ) @@ -661,7 +662,7 @@ async def async_setup_entry(hass, config_entry): """Rename a node.""" node_id = service.data.get(const.ATTR_NODE_ID) node = network.nodes[node_id] # pylint: disable=unsubscriptable-object - name = service.data.get(const.ATTR_NAME) + name = service.data.get(ATTR_NAME) node.name = name _LOGGER.info("Renamed Z-Wave node %d to %s", node_id, name) update_ids = service.data.get(const.ATTR_UPDATE_IDS) @@ -682,7 +683,7 @@ async def async_setup_entry(hass, config_entry): value_id = service.data.get(const.ATTR_VALUE_ID) node = network.nodes[node_id] # pylint: disable=unsubscriptable-object value = node.values[value_id] - name = service.data.get(const.ATTR_NAME) + name = service.data.get(ATTR_NAME) value.label = name _LOGGER.info( "Renamed Z-Wave value (Node %d Value %d) to %s", node_id, value_id, name diff --git a/homeassistant/components/zwave/const.py b/homeassistant/components/zwave/const.py index 83fb43fd3fb..d11d308c490 100644 --- a/homeassistant/components/zwave/const.py +++ b/homeassistant/components/zwave/const.py @@ -8,7 +8,6 @@ ATTR_INSTANCE = "instance" ATTR_GROUP = "group" ATTR_VALUE_ID = "value_id" ATTR_MESSAGES = "messages" -ATTR_NAME = "name" ATTR_RETURN_ROUTES = "return_routes" ATTR_SCENE_ID = "scene_id" ATTR_SCENE_DATA = "scene_data" diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 519771c1a49..2d4db960886 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -10,7 +10,12 @@ from zwave_js_server.model.notification import Notification from zwave_js_server.model.value import ValueNotification from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_DOMAIN, CONF_URL, EVENT_HOMEASSISTANT_STOP +from homeassistant.const import ( + ATTR_DEVICE_ID, + ATTR_DOMAIN, + CONF_URL, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry, entity_registry @@ -22,7 +27,6 @@ from .api import async_register_api from .const import ( ATTR_COMMAND_CLASS, ATTR_COMMAND_CLASS_NAME, - ATTR_DEVICE_ID, ATTR_ENDPOINT, ATTR_HOME_ID, ATTR_LABEL, diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index ffd6031349a..49beb06283e 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -38,7 +38,6 @@ ATTR_VALUE_RAW = "value_raw" ATTR_COMMAND_CLASS = "command_class" ATTR_COMMAND_CLASS_NAME = "command_class_name" ATTR_TYPE = "type" -ATTR_DEVICE_ID = "device_id" ATTR_PROPERTY_NAME = "property_name" ATTR_PROPERTY_KEY_NAME = "property_key_name" ATTR_PROPERTY = "property" diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index aaed10f7814..3925535d06b 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -112,7 +112,6 @@ DEFAULT_MAX_EXCEEDED = "WARNING" ATTR_CUR = "current" ATTR_MAX = "max" -ATTR_MODE = "mode" DATA_SCRIPTS = "helpers.script" diff --git a/tests/components/webostv/test_media_player.py b/tests/components/webostv/test_media_player.py index 70bc8274684..3015fcccf9f 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -12,13 +12,13 @@ from homeassistant.components.media_player.const import ( ) from homeassistant.components.webostv.const import ( ATTR_BUTTON, - ATTR_COMMAND, ATTR_PAYLOAD, DOMAIN, SERVICE_BUTTON, SERVICE_COMMAND, ) from homeassistant.const import ( + ATTR_COMMAND, ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, diff --git a/tests/components/zha/test_api.py b/tests/components/zha/test_api.py index 363aa12db6e..8694b59ecfb 100644 --- a/tests/components/zha/test_api.py +++ b/tests/components/zha/test_api.py @@ -28,7 +28,6 @@ from homeassistant.components.zha.core.const import ( ATTR_IEEE, ATTR_MANUFACTURER, ATTR_MODEL, - ATTR_NAME, ATTR_NEIGHBORS, ATTR_QUIRK_APPLIED, CLUSTER_TYPE_IN, @@ -38,6 +37,7 @@ from homeassistant.components.zha.core.const import ( GROUP_IDS, GROUP_NAME, ) +from homeassistant.const import ATTR_NAME from homeassistant.core import Context from .conftest import FIXTURE_GRP_ID, FIXTURE_GRP_NAME diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index d70c3d631d5..d64e191e1f3 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -17,7 +17,7 @@ from homeassistant.components.zwave import ( const, ) from homeassistant.components.zwave.binary_sensor import get_device -from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg from homeassistant.helpers.entity_registry import async_get_registry @@ -506,7 +506,7 @@ async def test_value_entities(hass, mock_openzwave): await hass.services.async_call( "zwave", "rename_node", - {const.ATTR_NODE_ID: node.node_id, const.ATTR_NAME: "Demo Node"}, + {const.ATTR_NODE_ID: node.node_id, ATTR_NAME: "Demo Node"}, ) await hass.async_block_till_done() @@ -537,7 +537,7 @@ async def test_value_entities(hass, mock_openzwave): { const.ATTR_NODE_ID: node.node_id, const.ATTR_UPDATE_IDS: True, - const.ATTR_NAME: "New Node", + ATTR_NAME: "New Node", }, ) await hass.async_block_till_done() @@ -568,7 +568,7 @@ async def test_value_entities(hass, mock_openzwave): const.ATTR_NODE_ID: node.node_id, const.ATTR_VALUE_ID: value.object_id, const.ATTR_UPDATE_IDS: True, - const.ATTR_NAME: "New Label", + ATTR_NAME: "New Label", }, ) await hass.async_block_till_done() @@ -1360,7 +1360,7 @@ async def test_rename_node(hass, mock_openzwave, zwave_setup_ready): await hass.services.async_call( "zwave", "rename_node", - {const.ATTR_NODE_ID: 11, const.ATTR_NAME: "test_name"}, + {const.ATTR_NODE_ID: 11, ATTR_NAME: "test_name"}, ) await hass.async_block_till_done() @@ -1383,7 +1383,7 @@ async def test_rename_value(hass, mock_openzwave, zwave_setup_ready): { const.ATTR_NODE_ID: 11, const.ATTR_VALUE_ID: 123456, - const.ATTR_NAME: "New Label", + ATTR_NAME: "New Label", }, ) await hass.async_block_till_done() From a547d0fea2825a7f6a9943ade9bda09e72bb1b9b Mon Sep 17 00:00:00 2001 From: Emily Mills Date: Fri, 5 Mar 2021 13:14:03 -0600 Subject: [PATCH 181/831] Prevent Zerproc leaving open unnecessary connections (#47401) * Zerproc: Prevent leaving open unnecessary connections * Fix config entry unloading --- homeassistant/components/zerproc/__init__.py | 6 ++++ homeassistant/components/zerproc/light.py | 33 ++++------------- .../components/zerproc/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zerproc/test_light.py | 35 +++---------------- 6 files changed, 20 insertions(+), 60 deletions(-) diff --git a/homeassistant/components/zerproc/__init__.py b/homeassistant/components/zerproc/__init__.py index f3d2e9daebc..d6faaf7a981 100644 --- a/homeassistant/components/zerproc/__init__.py +++ b/homeassistant/components/zerproc/__init__.py @@ -20,6 +20,11 @@ async def async_setup(hass, config): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Zerproc from a config entry.""" + if DOMAIN not in hass.data: + hass.data[DOMAIN] = {} + if "addresses" not in hass.data[DOMAIN]: + hass.data[DOMAIN]["addresses"] = set() + for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform) @@ -30,6 +35,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" + hass.data.pop(DOMAIN, None) return all( await asyncio.gather( *[ diff --git a/homeassistant/components/zerproc/light.py b/homeassistant/components/zerproc/light.py index 89f60faf84e..bf9f917f230 100644 --- a/homeassistant/components/zerproc/light.py +++ b/homeassistant/components/zerproc/light.py @@ -1,5 +1,4 @@ """Zerproc light platform.""" -import asyncio from datetime import timedelta import logging from typing import Callable, List, Optional @@ -30,16 +29,6 @@ SUPPORT_ZERPROC = SUPPORT_BRIGHTNESS | SUPPORT_COLOR DISCOVERY_INTERVAL = timedelta(seconds=60) -async def connect_light(light: pyzerproc.Light) -> Optional[pyzerproc.Light]: - """Return the given light if it connects successfully.""" - try: - await light.connect() - except pyzerproc.ZerprocException: - _LOGGER.debug("Unable to connect to '%s'", light.address, exc_info=True) - return None - return light - - async def discover_entities(hass: HomeAssistant) -> List[Entity]: """Attempt to discover new lights.""" lights = await pyzerproc.discover() @@ -50,14 +39,9 @@ async def discover_entities(hass: HomeAssistant) -> List[Entity]: ] entities = [] - connected_lights = filter( - None, await asyncio.gather(*(connect_light(light) for light in new_lights)) - ) - for light in connected_lights: - # Double-check the light hasn't been added in the meantime - if light.address not in hass.data[DOMAIN]["addresses"]: - hass.data[DOMAIN]["addresses"].add(light.address) - entities.append(ZerprocLight(light)) + for light in new_lights: + hass.data[DOMAIN]["addresses"].add(light.address) + entities.append(ZerprocLight(light)) return entities @@ -68,11 +52,6 @@ async def async_setup_entry( async_add_entities: Callable[[List[Entity], bool], None], ) -> None: """Set up Zerproc light devices.""" - if DOMAIN not in hass.data: - hass.data[DOMAIN] = {} - if "addresses" not in hass.data[DOMAIN]: - hass.data[DOMAIN]["addresses"] = set() - warned = False async def discover(*args): @@ -120,7 +99,7 @@ class ZerprocLight(LightEntity): await self._light.disconnect() except pyzerproc.ZerprocException: _LOGGER.debug( - "Exception disconnected from %s", self.entity_id, exc_info=True + "Exception disconnecting from %s", self._light.address, exc_info=True ) @property @@ -198,11 +177,11 @@ class ZerprocLight(LightEntity): state = await self._light.get_state() except pyzerproc.ZerprocException: if self._available: - _LOGGER.warning("Unable to connect to %s", self.entity_id) + _LOGGER.warning("Unable to connect to %s", self._light.address) self._available = False return if self._available is False: - _LOGGER.info("Reconnected to %s", self.entity_id) + _LOGGER.info("Reconnected to %s", self._light.address) self._available = True self._is_on = state.is_on hsv = color_util.color_RGB_to_hsv(*state.color) diff --git a/homeassistant/components/zerproc/manifest.json b/homeassistant/components/zerproc/manifest.json index 54b70d78673..d2d00987ab7 100644 --- a/homeassistant/components/zerproc/manifest.json +++ b/homeassistant/components/zerproc/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zerproc", "requirements": [ - "pyzerproc==0.4.7" + "pyzerproc==0.4.8" ], "codeowners": [ "@emlove" diff --git a/requirements_all.txt b/requirements_all.txt index f52f9b7e353..9b00a2b7267 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1920,7 +1920,7 @@ pyxeoma==1.4.1 pyzbar==0.1.7 # homeassistant.components.zerproc -pyzerproc==0.4.7 +pyzerproc==0.4.8 # homeassistant.components.qnap qnapstats==0.3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b60ce427b00..02bc7304084 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -993,7 +993,7 @@ pywemo==0.6.3 pywilight==0.0.68 # homeassistant.components.zerproc -pyzerproc==0.4.7 +pyzerproc==0.4.8 # homeassistant.components.rachio rachiopy==1.0.3 diff --git a/tests/components/zerproc/test_light.py b/tests/components/zerproc/test_light.py index 1f0c7652bfd..1dc608e18df 100644 --- a/tests/components/zerproc/test_light.py +++ b/tests/components/zerproc/test_light.py @@ -118,6 +118,8 @@ async def test_init(hass, mock_entry): assert mock_light_1.disconnect.called assert mock_light_2.disconnect.called + assert hass.data[DOMAIN]["addresses"] == {"AA:BB:CC:DD:EE:FF", "11:22:33:44:55:66"} + async def test_discovery_exception(hass, mock_entry): """Test platform setup.""" @@ -136,42 +138,15 @@ async def test_discovery_exception(hass, mock_entry): assert len(hass.data[DOMAIN]["addresses"]) == 0 -async def test_connect_exception(hass, mock_entry): - """Test platform setup.""" - await setup.async_setup_component(hass, "persistent_notification", {}) - - mock_entry.add_to_hass(hass) - - mock_light_1 = MagicMock(spec=pyzerproc.Light) - mock_light_1.address = "AA:BB:CC:DD:EE:FF" - mock_light_1.name = "LEDBlue-CCDDEEFF" - mock_light_1.is_connected.return_value = False - - mock_light_2 = MagicMock(spec=pyzerproc.Light) - mock_light_2.address = "11:22:33:44:55:66" - mock_light_2.name = "LEDBlue-33445566" - mock_light_2.is_connected.return_value = False - - with patch( - "homeassistant.components.zerproc.light.pyzerproc.discover", - return_value=[mock_light_1, mock_light_2], - ), patch.object( - mock_light_1, "connect", side_effect=pyzerproc.ZerprocException("TEST") - ): - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() - - # The exception connecting to light 1 should be captured, but light 2 - # should still be added - assert len(hass.data[DOMAIN]["addresses"]) == 1 - - async def test_remove_entry(hass, mock_light, mock_entry): """Test platform setup.""" + assert hass.data[DOMAIN]["addresses"] == {"AA:BB:CC:DD:EE:FF"} + with patch.object(mock_light, "disconnect") as mock_disconnect: await hass.config_entries.async_remove(mock_entry.entry_id) assert mock_disconnect.called + assert DOMAIN not in hass.data async def test_remove_entry_exceptions_caught(hass, mock_light, mock_entry): From a2ee7d598bb02dcf9f4efb9ce4521b8da7b6607e Mon Sep 17 00:00:00 2001 From: functionpointer Date: Fri, 5 Mar 2021 20:21:24 +0100 Subject: [PATCH 182/831] Use conn_made callback in MySensors (#47463) --- homeassistant/components/mysensors/const.py | 1 - homeassistant/components/mysensors/gateway.py | 40 ++++++++----------- homeassistant/components/mysensors/handler.py | 23 +---------- 3 files changed, 17 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/mysensors/const.py b/homeassistant/components/mysensors/const.py index 700c2bb930a..1b8fa5e24e8 100644 --- a/homeassistant/components/mysensors/const.py +++ b/homeassistant/components/mysensors/const.py @@ -29,7 +29,6 @@ CONF_GATEWAY_TYPE_ALL: List[str] = [ DOMAIN: str = "mysensors" -MYSENSORS_GATEWAY_READY: str = "mysensors_gateway_ready_{}" MYSENSORS_GATEWAY_START_TASK: str = "mysensors_gateway_start_task_{}" MYSENSORS_GATEWAYS: str = "mysensors_gateways" PLATFORM: str = "platform" diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index 4267ba5cbb3..b6797cafb37 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -26,7 +26,6 @@ from .const import ( CONF_TOPIC_OUT_PREFIX, CONF_VERSION, DOMAIN, - MYSENSORS_GATEWAY_READY, MYSENSORS_GATEWAY_START_TASK, MYSENSORS_GATEWAYS, GatewayId, @@ -36,7 +35,7 @@ from .helpers import discover_mysensors_platform, validate_child, validate_node _LOGGER = logging.getLogger(__name__) -GATEWAY_READY_TIMEOUT = 15.0 +GATEWAY_READY_TIMEOUT = 20.0 MQTT_COMPONENT = "mqtt" @@ -64,24 +63,16 @@ async def try_connect(hass: HomeAssistantType, user_input: Dict[str, str]) -> bo if user_input[CONF_DEVICE] == MQTT_COMPONENT: return True # dont validate mqtt. mqtt gateways dont send ready messages :( try: - gateway_ready = asyncio.Future() + gateway_ready = asyncio.Event() - def gateway_ready_callback(msg): - msg_type = msg.gateway.const.MessageType(msg.type) - _LOGGER.debug("Received MySensors msg type %s: %s", msg_type.name, msg) - if msg_type.name != "internal": - return - internal = msg.gateway.const.Internal(msg.sub_type) - if internal.name != "I_GATEWAY_READY": - return - _LOGGER.debug("Received gateway ready") - gateway_ready.set_result(True) + def on_conn_made(_: BaseAsyncGateway) -> None: + gateway_ready.set() gateway: Optional[BaseAsyncGateway] = await _get_gateway( hass, device=user_input[CONF_DEVICE], version=user_input[CONF_VERSION], - event_callback=gateway_ready_callback, + event_callback=lambda _: None, persistence_file=None, baud_rate=user_input.get(CONF_BAUD_RATE), tcp_port=user_input.get(CONF_TCP_PORT), @@ -92,12 +83,13 @@ async def try_connect(hass: HomeAssistantType, user_input: Dict[str, str]) -> bo ) if gateway is None: return False + gateway.on_conn_made = on_conn_made connect_task = None try: connect_task = asyncio.create_task(gateway.start()) - with async_timeout.timeout(20): - await gateway_ready + with async_timeout.timeout(GATEWAY_READY_TIMEOUT): + await gateway_ready.wait() return True except asyncio.TimeoutError: _LOGGER.info("Try gateway connect failed with timeout") @@ -280,6 +272,12 @@ async def _gw_start( hass: HomeAssistantType, entry: ConfigEntry, gateway: BaseAsyncGateway ): """Start the gateway.""" + gateway_ready = asyncio.Event() + + def gateway_connected(_: BaseAsyncGateway): + gateway_ready.set() + + gateway.on_conn_made = gateway_connected # Don't use hass.async_create_task to avoid holding up setup indefinitely. hass.data[DOMAIN][ MYSENSORS_GATEWAY_START_TASK.format(entry.entry_id) @@ -294,21 +292,15 @@ async def _gw_start( if entry.data[CONF_DEVICE] == MQTT_COMPONENT: # Gatways connected via mqtt doesn't send gateway ready message. return - gateway_ready = asyncio.Future() - gateway_ready_key = MYSENSORS_GATEWAY_READY.format(entry.entry_id) - hass.data[DOMAIN][gateway_ready_key] = gateway_ready - try: with async_timeout.timeout(GATEWAY_READY_TIMEOUT): - await gateway_ready + await gateway_ready.wait() except asyncio.TimeoutError: _LOGGER.warning( - "Gateway %s not ready after %s secs so continuing with setup", + "Gateway %s not connected after %s secs so continuing with setup", entry.data[CONF_DEVICE], GATEWAY_READY_TIMEOUT, ) - finally: - hass.data[DOMAIN].pop(gateway_ready_key, None) def _gw_callback_factory( diff --git a/homeassistant/components/mysensors/handler.py b/homeassistant/components/mysensors/handler.py index 10165a171e0..a47c9174b23 100644 --- a/homeassistant/components/mysensors/handler.py +++ b/homeassistant/components/mysensors/handler.py @@ -8,14 +8,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import decorator -from .const import ( - CHILD_CALLBACK, - DOMAIN, - MYSENSORS_GATEWAY_READY, - NODE_CALLBACK, - DevId, - GatewayId, -) +from .const import CHILD_CALLBACK, NODE_CALLBACK, DevId, GatewayId from .device import get_mysensors_devices from .helpers import discover_mysensors_platform, validate_set_msg @@ -75,20 +68,6 @@ async def handle_sketch_version( _handle_node_update(hass, gateway_id, msg) -@HANDLERS.register("I_GATEWAY_READY") -async def handle_gateway_ready( - hass: HomeAssistantType, gateway_id: GatewayId, msg: Message -) -> None: - """Handle an internal gateway ready message. - - Set asyncio future result if gateway is ready. - """ - gateway_ready = hass.data[DOMAIN].get(MYSENSORS_GATEWAY_READY.format(gateway_id)) - if gateway_ready is None or gateway_ready.cancelled(): - return - gateway_ready.set_result(True) - - @callback def _handle_child_update( hass: HomeAssistantType, gateway_id: GatewayId, validated: Dict[str, List[DevId]] From ab0a5bccabf786118ec16d105d8a92c3a53cf95d Mon Sep 17 00:00:00 2001 From: mvn23 Date: Fri, 5 Mar 2021 20:22:40 +0100 Subject: [PATCH 183/831] Update pyotgw to 1.1b1 (#47446) --- homeassistant/components/opentherm_gw/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/opentherm_gw/manifest.json b/homeassistant/components/opentherm_gw/manifest.json index 066cee61c05..baa02dc3f46 100644 --- a/homeassistant/components/opentherm_gw/manifest.json +++ b/homeassistant/components/opentherm_gw/manifest.json @@ -2,7 +2,7 @@ "domain": "opentherm_gw", "name": "OpenTherm Gateway", "documentation": "https://www.home-assistant.io/integrations/opentherm_gw", - "requirements": ["pyotgw==1.0b1"], + "requirements": ["pyotgw==1.1b1"], "codeowners": ["@mvn23"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 9b00a2b7267..64071253d2f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1600,7 +1600,7 @@ pyoppleio==1.0.5 pyota==2.0.5 # homeassistant.components.opentherm_gw -pyotgw==1.0b1 +pyotgw==1.1b1 # homeassistant.auth.mfa_modules.notify # homeassistant.auth.mfa_modules.totp diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 02bc7304084..9d50753b944 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -848,7 +848,7 @@ pyopenuv==1.0.9 pyopnsense==0.2.0 # homeassistant.components.opentherm_gw -pyotgw==1.0b1 +pyotgw==1.1b1 # homeassistant.auth.mfa_modules.notify # homeassistant.auth.mfa_modules.totp From 61be29117d38962e2f16fce2b35906840f8760b1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 5 Mar 2021 20:51:49 +0100 Subject: [PATCH 184/831] Deprecate HomeKit auto start (#47470) --- homeassistant/components/homekit/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 0dfe7336bcf..e6d3a8abff4 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -131,6 +131,7 @@ def _has_all_unique_names_and_ports(bridges): BRIDGE_SCHEMA = vol.All( cv.deprecated(CONF_ZEROCONF_DEFAULT_INTERFACE), cv.deprecated(CONF_SAFE_MODE), + cv.deprecated(CONF_AUTO_START), vol.Schema( { vol.Optional(CONF_HOMEKIT_MODE, default=DEFAULT_HOMEKIT_MODE): vol.In( From 6debf52e9b9c6311045bf7d31f4a5a50b3f28fdf Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 5 Mar 2021 14:57:06 -0500 Subject: [PATCH 185/831] Update zwave_js.refresh_value service description (#47469) --- homeassistant/components/zwave_js/services.yaml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/services.yaml b/homeassistant/components/zwave_js/services.yaml index 8e6d907fc96..7277f540d76 100644 --- a/homeassistant/components/zwave_js/services.yaml +++ b/homeassistant/components/zwave_js/services.yaml @@ -70,10 +70,15 @@ set_config_parameter: refresh_value: name: Refresh value(s) of a Z-Wave entity description: Force update value(s) for a Z-Wave entity - target: - entity: - integration: zwave_js fields: + entity_id: + name: Entity + description: Entity whose value(s) should be refreshed + required: true + example: sensor.family_room_motion + selector: + entity: + integration: zwave_js refresh_all_values: name: Refresh all values? description: Whether to refresh all values (true) or just the primary value (false) From 7c08592b5af11132c4d75664262041f2946ffecf Mon Sep 17 00:00:00 2001 From: Emily Mills Date: Fri, 5 Mar 2021 14:24:55 -0600 Subject: [PATCH 186/831] Convert kulersky to use new async backend (#47403) --- .../components/kulersky/config_flow.py | 4 +- homeassistant/components/kulersky/light.py | 103 +++---- .../components/kulersky/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/kulersky/test_config_flow.py | 18 +- tests/components/kulersky/test_light.py | 258 ++++++------------ 7 files changed, 130 insertions(+), 259 deletions(-) diff --git a/homeassistant/components/kulersky/config_flow.py b/homeassistant/components/kulersky/config_flow.py index 04f7719b8e6..2a11a3c2e17 100644 --- a/homeassistant/components/kulersky/config_flow.py +++ b/homeassistant/components/kulersky/config_flow.py @@ -15,9 +15,7 @@ async def _async_has_devices(hass) -> bool: """Return if there are devices that can be discovered.""" # Check if there are any devices that can be discovered in the network. try: - devices = await hass.async_add_executor_job( - pykulersky.discover_bluetooth_devices - ) + devices = await pykulersky.discover() except pykulersky.PykulerskyException as exc: _LOGGER.error("Unable to discover nearby Kuler Sky devices: %s", exc) return False diff --git a/homeassistant/components/kulersky/light.py b/homeassistant/components/kulersky/light.py index 71dd4a158ca..9098975d500 100644 --- a/homeassistant/components/kulersky/light.py +++ b/homeassistant/components/kulersky/light.py @@ -1,5 +1,4 @@ """Kuler Sky light platform.""" -import asyncio from datetime import timedelta import logging from typing import Callable, List @@ -17,6 +16,7 @@ from homeassistant.components.light import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import HomeAssistantType @@ -30,14 +30,6 @@ SUPPORT_KULERSKY = SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_WHITE_VALUE DISCOVERY_INTERVAL = timedelta(seconds=60) -PARALLEL_UPDATES = 0 - - -def check_light(light: pykulersky.Light): - """Attempt to connect to this light and read the color.""" - light.connect() - light.get_color() - async def async_setup_entry( hass: HomeAssistantType, @@ -47,45 +39,26 @@ async def async_setup_entry( """Set up Kuler sky light devices.""" if DOMAIN not in hass.data: hass.data[DOMAIN] = {} - if "devices" not in hass.data[DOMAIN]: - hass.data[DOMAIN]["devices"] = set() - if "discovery" not in hass.data[DOMAIN]: - hass.data[DOMAIN]["discovery"] = asyncio.Lock() + if "addresses" not in hass.data[DOMAIN]: + hass.data[DOMAIN]["addresses"] = set() async def discover(*args): """Attempt to discover new lights.""" - # Since discovery needs to connect to all discovered bluetooth devices, and - # only rules out devices after a timeout, it can potentially take a long - # time. If there's already a discovery running, just skip this poll. - if hass.data[DOMAIN]["discovery"].locked(): - return + lights = await pykulersky.discover() - async with hass.data[DOMAIN]["discovery"]: - bluetooth_devices = await hass.async_add_executor_job( - pykulersky.discover_bluetooth_devices - ) + # Filter out already discovered lights + new_lights = [ + light + for light in lights + if light.address not in hass.data[DOMAIN]["addresses"] + ] - # Filter out already connected lights - new_devices = [ - device - for device in bluetooth_devices - if device["address"] not in hass.data[DOMAIN]["devices"] - ] + new_entities = [] + for light in new_lights: + hass.data[DOMAIN]["addresses"].add(light.address) + new_entities.append(KulerskyLight(light)) - for device in new_devices: - light = pykulersky.Light(device["address"], device["name"]) - try: - # If the connection fails, either this is not a Kuler Sky - # light, or it's bluetooth connection is currently locked - # by another device. If the vendor's app is connected to - # the light when home assistant tries to connect, this - # connection will fail. - await hass.async_add_executor_job(check_light, light) - except pykulersky.PykulerskyException: - continue - # The light has successfully connected - hass.data[DOMAIN]["devices"].add(device["address"]) - async_add_entities([KulerskyLight(light)], update_before_add=True) + async_add_entities(new_entities, update_before_add=True) # Start initial discovery hass.async_create_task(discover()) @@ -94,6 +67,11 @@ async def async_setup_entry( async_track_time_interval(hass, discover, DISCOVERY_INTERVAL) +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Cleanup the Kuler sky integration.""" + hass.data.pop(DOMAIN, None) + + class KulerskyLight(LightEntity): """Representation of an Kuler Sky Light.""" @@ -103,21 +81,24 @@ class KulerskyLight(LightEntity): self._hs_color = None self._brightness = None self._white_value = None - self._available = True + self._available = None async def async_added_to_hass(self) -> None: """Run when entity about to be added to hass.""" self.async_on_remove( - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.disconnect) + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, self.async_will_remove_from_hass + ) ) - async def async_will_remove_from_hass(self) -> None: + async def async_will_remove_from_hass(self, *args) -> None: """Run when entity will be removed from hass.""" - await self.hass.async_add_executor_job(self.disconnect) - - def disconnect(self, *args) -> None: - """Disconnect the underlying device.""" - self._light.disconnect() + try: + await self._light.disconnect() + except pykulersky.PykulerskyException: + _LOGGER.debug( + "Exception disconnected from %s", self._light.address, exc_info=True + ) @property def name(self): @@ -168,7 +149,7 @@ class KulerskyLight(LightEntity): """Return True if entity is available.""" return self._available - def turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs): """Instruct the light to turn on.""" default_hs = (0, 0) if self._hs_color is None else self._hs_color hue_sat = kwargs.get(ATTR_HS_COLOR, default_hs) @@ -187,28 +168,28 @@ class KulerskyLight(LightEntity): rgb = color_util.color_hsv_to_RGB(*hue_sat, brightness / 255 * 100) - self._light.set_color(*rgb, white_value) + await self._light.set_color(*rgb, white_value) - def turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs): """Instruct the light to turn off.""" - self._light.set_color(0, 0, 0, 0) + await self._light.set_color(0, 0, 0, 0) - def update(self): + async def async_update(self): """Fetch new state data for this light.""" try: - if not self._light.connected: - self._light.connect() + if not self._available: + await self._light.connect() # pylint: disable=invalid-name - r, g, b, w = self._light.get_color() + r, g, b, w = await self._light.get_color() except pykulersky.PykulerskyException as exc: if self._available: _LOGGER.warning("Unable to connect to %s: %s", self._light.address, exc) self._available = False return - if not self._available: - _LOGGER.info("Reconnected to %s", self.entity_id) - self._available = True + if self._available is False: + _LOGGER.info("Reconnected to %s", self._light.address) + self._available = True hsv = color_util.color_RGB_to_hsv(r, g, b) self._hs_color = hsv[:2] self._brightness = int(round((hsv[2] / 100) * 255)) diff --git a/homeassistant/components/kulersky/manifest.json b/homeassistant/components/kulersky/manifest.json index 4f445e4fc18..b690d94e8d4 100644 --- a/homeassistant/components/kulersky/manifest.json +++ b/homeassistant/components/kulersky/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/kulersky", "requirements": [ - "pykulersky==0.4.0" + "pykulersky==0.5.2" ], "codeowners": [ "@emlove" diff --git a/requirements_all.txt b/requirements_all.txt index 64071253d2f..5253f58a9ee 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1477,7 +1477,7 @@ pykmtronic==0.0.3 pykodi==0.2.1 # homeassistant.components.kulersky -pykulersky==0.4.0 +pykulersky==0.5.2 # homeassistant.components.kwb pykwb==0.0.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9d50753b944..45c06066e20 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -779,7 +779,7 @@ pykmtronic==0.0.3 pykodi==0.2.1 # homeassistant.components.kulersky -pykulersky==0.4.0 +pykulersky==0.5.2 # homeassistant.components.lastfm pylast==4.1.0 diff --git a/tests/components/kulersky/test_config_flow.py b/tests/components/kulersky/test_config_flow.py index 24f3f9a010e..c6933a01d3a 100644 --- a/tests/components/kulersky/test_config_flow.py +++ b/tests/components/kulersky/test_config_flow.py @@ -1,5 +1,5 @@ """Test the Kuler Sky config flow.""" -from unittest.mock import patch +from unittest.mock import MagicMock, patch import pykulersky @@ -16,14 +16,12 @@ async def test_flow_success(hass): assert result["type"] == "form" assert result["errors"] is None + light = MagicMock(spec=pykulersky.Light) + light.address = "AA:BB:CC:11:22:33" + light.name = "Bedroom" with patch( - "homeassistant.components.kulersky.config_flow.pykulersky.discover_bluetooth_devices", - return_value=[ - { - "address": "AA:BB:CC:11:22:33", - "name": "Bedroom", - } - ], + "homeassistant.components.kulersky.config_flow.pykulersky.discover", + return_value=[light], ), patch( "homeassistant.components.kulersky.async_setup", return_value=True ) as mock_setup, patch( @@ -54,7 +52,7 @@ async def test_flow_no_devices_found(hass): assert result["errors"] is None with patch( - "homeassistant.components.kulersky.config_flow.pykulersky.discover_bluetooth_devices", + "homeassistant.components.kulersky.config_flow.pykulersky.discover", return_value=[], ), patch( "homeassistant.components.kulersky.async_setup", return_value=True @@ -84,7 +82,7 @@ async def test_flow_exceptions_caught(hass): assert result["errors"] is None with patch( - "homeassistant.components.kulersky.config_flow.pykulersky.discover_bluetooth_devices", + "homeassistant.components.kulersky.config_flow.pykulersky.discover", side_effect=pykulersky.PykulerskyException("TEST"), ), patch( "homeassistant.components.kulersky.async_setup", return_value=True diff --git a/tests/components/kulersky/test_light.py b/tests/components/kulersky/test_light.py index fd5db92908b..9dd13fad18b 100644 --- a/tests/components/kulersky/test_light.py +++ b/tests/components/kulersky/test_light.py @@ -1,5 +1,4 @@ """Test the Kuler Sky lights.""" -import asyncio from unittest.mock import MagicMock, patch import pykulersky @@ -45,28 +44,17 @@ async def mock_light(hass, mock_entry): light = MagicMock(spec=pykulersky.Light) light.address = "AA:BB:CC:11:22:33" light.name = "Bedroom" - light.connected = False + light.connect.return_value = True + light.get_color.return_value = (0, 0, 0, 0) with patch( - "homeassistant.components.kulersky.light.pykulersky.discover_bluetooth_devices", - return_value=[ - { - "address": "AA:BB:CC:11:22:33", - "name": "Bedroom", - } - ], + "homeassistant.components.kulersky.light.pykulersky.discover", + return_value=[light], ): - with patch( - "homeassistant.components.kulersky.light.pykulersky.Light", - return_value=light, - ), patch.object(light, "connect") as mock_connect, patch.object( - light, "get_color", return_value=(0, 0, 0, 0) - ): - mock_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + mock_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() - assert mock_connect.called - light.connected = True + assert light.connect.called yield light @@ -82,113 +70,34 @@ async def test_init(hass, mock_light): | SUPPORT_WHITE_VALUE, } - with patch.object(hass.loop, "stop"), patch.object( - mock_light, "disconnect" - ) as mock_disconnect: + with patch.object(hass.loop, "stop"): await hass.async_stop() await hass.async_block_till_done() - assert mock_disconnect.called - - -async def test_discovery_lock(hass, mock_entry): - """Test discovery lock.""" - await setup.async_setup_component(hass, "persistent_notification", {}) - - discovery_finished = None - first_discovery_started = asyncio.Event() - - async def mock_discovery(*args): - """Block to simulate multiple discovery calls while one still running.""" - nonlocal discovery_finished - if discovery_finished: - first_discovery_started.set() - await discovery_finished.wait() - return [] - - with patch( - "homeassistant.components.kulersky.light.pykulersky.discover_bluetooth_devices", - return_value=[], - ), patch( - "homeassistant.components.kulersky.light.async_track_time_interval", - ) as mock_track_time_interval: - mock_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() - - with patch.object( - hass, "async_add_executor_job", side_effect=mock_discovery - ) as mock_run_discovery: - discovery_coroutine = mock_track_time_interval.call_args[0][1] - - discovery_finished = asyncio.Event() - - # Schedule multiple discoveries - hass.async_create_task(discovery_coroutine()) - hass.async_create_task(discovery_coroutine()) - hass.async_create_task(discovery_coroutine()) - - # Wait until the first discovery call is blocked - await first_discovery_started.wait() - - # Unblock the first discovery - discovery_finished.set() - - # Flush the remaining jobs - await hass.async_block_till_done() - - # The discovery method should only have been called once - mock_run_discovery.assert_called_once() - - -async def test_discovery_connection_error(hass, mock_entry): - """Test that invalid devices are skipped.""" - await setup.async_setup_component(hass, "persistent_notification", {}) - - light = MagicMock(spec=pykulersky.Light) - light.address = "AA:BB:CC:11:22:33" - light.name = "Bedroom" - light.connected = False - with patch( - "homeassistant.components.kulersky.light.pykulersky.discover_bluetooth_devices", - return_value=[ - { - "address": "AA:BB:CC:11:22:33", - "name": "Bedroom", - } - ], - ): - with patch( - "homeassistant.components.kulersky.light.pykulersky.Light" - ) as mockdevice, patch.object( - light, "connect", side_effect=pykulersky.PykulerskyException - ): - mockdevice.return_value = light - mock_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() - - # Assert entity was not added - state = hass.states.get("light.bedroom") - assert state is None + assert mock_light.disconnect.called async def test_remove_entry(hass, mock_light, mock_entry): """Test platform setup.""" - with patch.object(mock_light, "disconnect") as mock_disconnect: - await hass.config_entries.async_remove(mock_entry.entry_id) + await hass.config_entries.async_remove(mock_entry.entry_id) - assert mock_disconnect.called + assert mock_light.disconnect.called + + +async def test_remove_entry_exceptions_caught(hass, mock_light, mock_entry): + """Assert that disconnect exceptions are caught.""" + mock_light.disconnect.side_effect = pykulersky.PykulerskyException("Mock error") + await hass.config_entries.async_remove(mock_entry.entry_id) + + assert mock_light.disconnect.called async def test_update_exception(hass, mock_light): """Test platform setup.""" await setup.async_setup_component(hass, "persistent_notification", {}) - with patch.object( - mock_light, "get_color", side_effect=pykulersky.PykulerskyException - ): - await hass.helpers.entity_component.async_update_entity("light.bedroom") + mock_light.get_color.side_effect = pykulersky.PykulerskyException + await hass.helpers.entity_component.async_update_entity("light.bedroom") state = hass.states.get("light.bedroom") assert state is not None assert state.state == STATE_UNAVAILABLE @@ -196,69 +105,59 @@ async def test_update_exception(hass, mock_light): async def test_light_turn_on(hass, mock_light): """Test KulerSkyLight turn_on.""" - with patch.object(mock_light, "set_color") as mock_set_color, patch.object( - mock_light, "get_color", return_value=(255, 255, 255, 255) - ): - await hass.services.async_call( - "light", - "turn_on", - {ATTR_ENTITY_ID: "light.bedroom"}, - blocking=True, - ) - await hass.async_block_till_done() - mock_set_color.assert_called_with(255, 255, 255, 255) + mock_light.get_color.return_value = (255, 255, 255, 255) + await hass.services.async_call( + "light", + "turn_on", + {ATTR_ENTITY_ID: "light.bedroom"}, + blocking=True, + ) + await hass.async_block_till_done() + mock_light.set_color.assert_called_with(255, 255, 255, 255) - with patch.object(mock_light, "set_color") as mock_set_color, patch.object( - mock_light, "get_color", return_value=(50, 50, 50, 255) - ): - await hass.services.async_call( - "light", - "turn_on", - {ATTR_ENTITY_ID: "light.bedroom", ATTR_BRIGHTNESS: 50}, - blocking=True, - ) - await hass.async_block_till_done() - mock_set_color.assert_called_with(50, 50, 50, 255) + mock_light.get_color.return_value = (50, 50, 50, 255) + await hass.services.async_call( + "light", + "turn_on", + {ATTR_ENTITY_ID: "light.bedroom", ATTR_BRIGHTNESS: 50}, + blocking=True, + ) + await hass.async_block_till_done() + mock_light.set_color.assert_called_with(50, 50, 50, 255) - with patch.object(mock_light, "set_color") as mock_set_color, patch.object( - mock_light, "get_color", return_value=(50, 45, 25, 255) - ): - await hass.services.async_call( - "light", - "turn_on", - {ATTR_ENTITY_ID: "light.bedroom", ATTR_HS_COLOR: (50, 50)}, - blocking=True, - ) - await hass.async_block_till_done() + mock_light.get_color.return_value = (50, 45, 25, 255) + await hass.services.async_call( + "light", + "turn_on", + {ATTR_ENTITY_ID: "light.bedroom", ATTR_HS_COLOR: (50, 50)}, + blocking=True, + ) + await hass.async_block_till_done() - mock_set_color.assert_called_with(50, 45, 25, 255) + mock_light.set_color.assert_called_with(50, 45, 25, 255) - with patch.object(mock_light, "set_color") as mock_set_color, patch.object( - mock_light, "get_color", return_value=(220, 201, 110, 180) - ): - await hass.services.async_call( - "light", - "turn_on", - {ATTR_ENTITY_ID: "light.bedroom", ATTR_WHITE_VALUE: 180}, - blocking=True, - ) - await hass.async_block_till_done() - mock_set_color.assert_called_with(50, 45, 25, 180) + mock_light.get_color.return_value = (220, 201, 110, 180) + await hass.services.async_call( + "light", + "turn_on", + {ATTR_ENTITY_ID: "light.bedroom", ATTR_WHITE_VALUE: 180}, + blocking=True, + ) + await hass.async_block_till_done() + mock_light.set_color.assert_called_with(50, 45, 25, 180) async def test_light_turn_off(hass, mock_light): """Test KulerSkyLight turn_on.""" - with patch.object(mock_light, "set_color") as mock_set_color, patch.object( - mock_light, "get_color", return_value=(0, 0, 0, 0) - ): - await hass.services.async_call( - "light", - "turn_off", - {ATTR_ENTITY_ID: "light.bedroom"}, - blocking=True, - ) - await hass.async_block_till_done() - mock_set_color.assert_called_with(0, 0, 0, 0) + mock_light.get_color.return_value = (0, 0, 0, 0) + await hass.services.async_call( + "light", + "turn_off", + {ATTR_ENTITY_ID: "light.bedroom"}, + blocking=True, + ) + await hass.async_block_till_done() + mock_light.set_color.assert_called_with(0, 0, 0, 0) async def test_light_update(hass, mock_light): @@ -275,12 +174,10 @@ async def test_light_update(hass, mock_light): } # Test an exception during discovery - with patch.object( - mock_light, "get_color", side_effect=pykulersky.PykulerskyException("TEST") - ): - utcnow = utcnow + SCAN_INTERVAL - async_fire_time_changed(hass, utcnow) - await hass.async_block_till_done() + mock_light.get_color.side_effect = pykulersky.PykulerskyException("TEST") + utcnow = utcnow + SCAN_INTERVAL + async_fire_time_changed(hass, utcnow) + await hass.async_block_till_done() state = hass.states.get("light.bedroom") assert state.state == STATE_UNAVAILABLE @@ -291,14 +188,11 @@ async def test_light_update(hass, mock_light): | SUPPORT_WHITE_VALUE, } - with patch.object( - mock_light, - "get_color", - return_value=(80, 160, 200, 240), - ): - utcnow = utcnow + SCAN_INTERVAL - async_fire_time_changed(hass, utcnow) - await hass.async_block_till_done() + mock_light.get_color.side_effect = None + mock_light.get_color.return_value = (80, 160, 200, 240) + utcnow = utcnow + SCAN_INTERVAL + async_fire_time_changed(hass, utcnow) + await hass.async_block_till_done() state = hass.states.get("light.bedroom") assert state.state == STATE_ON From 793929f2ea12f2c04b88bc9be9f82c7a09f55b4a Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 5 Mar 2021 21:28:41 +0100 Subject: [PATCH 187/831] Increase test coverage of UniFi integration (#46347) * Increase coverage of init * Increase coverage of config_flow * Improve coverage of controller * Minor improvement to switch test * Fix review comment * Mock websocket class * Replace the rest of the old websocket event tests * Improve websocket fixture for cleaner tests * Fix typing * Improve connection state signalling based on Martins feedback * Improve tests of reconnection_mechanisms based on Martins review comments * Fix unload entry * Fix isort issue after rebase * Fix martins comment on not using caplog * Fix wireless clients test * Fix martins comments on wireless clients test --- homeassistant/components/unifi/config_flow.py | 5 +- homeassistant/components/unifi/controller.py | 3 +- tests/components/unifi/conftest.py | 21 ++ tests/components/unifi/test_config_flow.py | 32 +++ tests/components/unifi/test_controller.py | 171 +++++++++++-- tests/components/unifi/test_device_tracker.py | 232 ++++++++++++------ tests/components/unifi/test_init.py | 83 +++++-- tests/components/unifi/test_sensor.py | 28 ++- tests/components/unifi/test_switch.py | 104 ++++---- 9 files changed, 499 insertions(+), 180 deletions(-) diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index 5a0a4969f09..6d8c37e8b04 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -123,8 +123,9 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN): return await self.async_step_site() - host = self.config.get(CONF_HOST) - if not host and await async_discover_unifi(self.hass): + if not (host := self.config.get(CONF_HOST, "")) and await async_discover_unifi( + self.hass + ): host = "unifi" data = self.reauth_schema or { diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 45268a341e1..7c95058e8e4 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -415,9 +415,8 @@ class UniFiController: If config entry is updated due to reauth flow the entry might already have been reset and thus is not available. """ - if config_entry.entry_id not in hass.data[UNIFI_DOMAIN]: + if not (controller := hass.data[UNIFI_DOMAIN].get(config_entry.entry_id)): return - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] controller.load_config_entry_options() async_dispatcher_send(hass, controller.signal_options_update) diff --git a/tests/components/unifi/conftest.py b/tests/components/unifi/conftest.py index b0491a9fa2a..83dc99fdaf8 100644 --- a/tests/components/unifi/conftest.py +++ b/tests/components/unifi/conftest.py @@ -1,9 +1,30 @@ """Fixtures for UniFi methods.""" +from typing import Optional from unittest.mock import patch +from aiounifi.websocket import SIGNAL_CONNECTION_STATE, SIGNAL_DATA import pytest +@pytest.fixture(autouse=True) +def mock_unifi_websocket(): + """No real websocket allowed.""" + with patch("aiounifi.controller.WSClient") as mock: + + def make_websocket_call(data: Optional[dict] = None, state: str = ""): + """Generate a websocket call.""" + if data: + mock.return_value.data = data + mock.call_args[1]["callback"](SIGNAL_DATA) + elif state: + mock.return_value.state = state + mock.call_args[1]["callback"](SIGNAL_CONNECTION_STATE) + else: + raise NotImplementedError + + yield make_websocket_call + + @pytest.fixture(autouse=True) def mock_discovery(): """No real network traffic allowed.""" diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index a28f5f5f7c5..106c1852414 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -1,9 +1,12 @@ """Test UniFi config flow.""" + +import socket from unittest.mock import patch import aiounifi from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.unifi.config_flow import async_discover_unifi from homeassistant.components.unifi.const import ( CONF_ALLOW_BANDWIDTH_SENSORS, CONF_ALLOW_UPTIME_SENSORS, @@ -151,6 +154,23 @@ async def test_flow_works(hass, aioclient_mock, mock_discovery): } +async def test_flow_works_negative_discovery(hass, aioclient_mock, mock_discovery): + """Test config flow with a negative outcome of async_discovery_unifi.""" + result = await hass.config_entries.flow.async_init( + UNIFI_DOMAIN, context={"source": "user"} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["data_schema"]({CONF_USERNAME: "", CONF_PASSWORD: ""}) == { + CONF_HOST: "", + CONF_USERNAME: "", + CONF_PASSWORD: "", + CONF_PORT: 443, + CONF_VERIFY_SSL: False, + } + + async def test_flow_multiple_sites(hass, aioclient_mock): """Test config flow works when finding multiple sites.""" result = await hass.config_entries.flow.async_init( @@ -617,3 +637,15 @@ async def test_form_ssdp_gets_form_with_ignored_entry(hass): "host": "1.2.3.4", "site": "default", } + + +async def test_discover_unifi_positive(hass): + """Verify positive run of UniFi discovery.""" + with patch("socket.gethostbyname", return_value=True): + assert await async_discover_unifi(hass) + + +async def test_discover_unifi_negative(hass): + """Verify negative run of UniFi discovery.""" + with patch("socket.gethostbyname", side_effect=socket.gaierror): + assert await async_discover_unifi(hass) is None diff --git a/tests/components/unifi/test_controller.py b/tests/components/unifi/test_controller.py index 6b40df5857f..50d464d23c0 100644 --- a/tests/components/unifi/test_controller.py +++ b/tests/components/unifi/test_controller.py @@ -1,10 +1,12 @@ """Test UniFi Controller.""" +import asyncio from copy import deepcopy from datetime import timedelta -from unittest.mock import patch +from unittest.mock import Mock, patch import aiounifi +from aiounifi.websocket import STATE_DISCONNECTED, STATE_RUNNING import pytest from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN @@ -13,6 +15,8 @@ from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.unifi.const import ( CONF_CONTROLLER, CONF_SITE_ID, + CONF_TRACK_CLIENTS, + CONF_TRACK_DEVICES, DEFAULT_ALLOW_BANDWIDTH_SENSORS, DEFAULT_ALLOW_UPTIME_SENSORS, DEFAULT_DETECTION_TIME, @@ -22,7 +26,11 @@ from homeassistant.components.unifi.const import ( DOMAIN as UNIFI_DOMAIN, UNIFI_WIRELESS_CLIENTS, ) -from homeassistant.components.unifi.controller import PLATFORMS, get_controller +from homeassistant.components.unifi.controller import ( + PLATFORMS, + RETRY_TIMER, + get_controller, +) from homeassistant.components.unifi.errors import AuthenticationRequired, CannotConnect from homeassistant.const import ( CONF_HOST, @@ -32,10 +40,13 @@ from homeassistant.const import ( CONF_VERIFY_SSL, CONTENT_TYPE_JSON, ) +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.setup import async_setup_component +import homeassistant.util.dt as dt_util -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_fire_time_changed +DEFAULT_CONFIG_ENTRY_ID = 1 DEFAULT_HOST = "1.2.3.4" DEFAULT_SITE = "site_id" @@ -154,6 +165,7 @@ async def setup_unifi_integration( wlans_response=None, known_wireless_clients=None, controllers=None, + unique_id="1", ): """Create the UniFi controller.""" assert await async_setup_component(hass, UNIFI_DOMAIN, {}) @@ -162,8 +174,8 @@ async def setup_unifi_integration( domain=UNIFI_DOMAIN, data=deepcopy(config), options=deepcopy(options), - entry_id=1, - unique_id="1", + unique_id=unique_id, + entry_id=DEFAULT_CONFIG_ENTRY_ID, version=1, ) config_entry.add_to_hass(hass) @@ -188,8 +200,7 @@ async def setup_unifi_integration( wlans_response=wlans_response, ) - with patch.object(aiounifi.websocket.WSClient, "start", return_value=True): - await hass.config_entries.async_setup(config_entry.entry_id) + await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() if config_entry.entry_id not in hass.data[UNIFI_DOMAIN]: @@ -276,6 +287,27 @@ async def test_controller_unknown_error(hass): assert hass.data[UNIFI_DOMAIN] == {} +async def test_config_entry_updated(hass, aioclient_mock): + """Calling reset when the entry has been setup.""" + config_entry = await setup_unifi_integration(hass, aioclient_mock) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + + event_call = Mock() + unsub = async_dispatcher_connect(hass, controller.signal_options_update, event_call) + + hass.config_entries.async_update_entry( + config_entry, options={CONF_TRACK_CLIENTS: False, CONF_TRACK_DEVICES: False} + ) + await hass.async_block_till_done() + + assert config_entry.options[CONF_TRACK_CLIENTS] is False + assert config_entry.options[CONF_TRACK_DEVICES] is False + + event_call.assert_called_once() + + unsub() + + async def test_reset_after_successful_setup(hass, aioclient_mock): """Calling reset when the entry has been setup.""" config_entry = await setup_unifi_integration(hass, aioclient_mock) @@ -290,33 +322,126 @@ async def test_reset_after_successful_setup(hass, aioclient_mock): assert len(controller.listeners) == 0 -async def test_wireless_client_event_calls_update_wireless_devices( - hass, aioclient_mock -): - """Call update_wireless_devices method when receiving wireless client event.""" +async def test_reset_fails(hass, aioclient_mock): + """Calling reset when the entry has been setup can return false.""" config_entry = await setup_unifi_integration(hass, aioclient_mock) controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + with patch( + "homeassistant.config_entries.ConfigEntries.async_forward_entry_unload", + return_value=False, + ): + result = await controller.async_reset() + await hass.async_block_till_done() + + assert result is False + + +async def test_connection_state_signalling(hass, aioclient_mock, mock_unifi_websocket): + """Verify connection statesignalling and connection state are working.""" + client = { + "hostname": "client", + "ip": "10.0.0.1", + "is_wired": True, + "last_seen": dt_util.as_timestamp(dt_util.utcnow()), + "mac": "00:00:00:00:00:01", + } + await setup_unifi_integration(hass, aioclient_mock, clients_response=[client]) + + # Controller is connected + assert hass.states.get("device_tracker.client").state == "home" + + mock_unifi_websocket(state=STATE_DISCONNECTED) + await hass.async_block_till_done() + + # Controller is disconnected + assert hass.states.get("device_tracker.client").state == "unavailable" + + mock_unifi_websocket(state=STATE_RUNNING) + await hass.async_block_till_done() + + # Controller is once again connected + assert hass.states.get("device_tracker.client").state == "home" + + +async def test_wireless_client_event_calls_update_wireless_devices( + hass, aioclient_mock, mock_unifi_websocket +): + """Call update_wireless_devices method when receiving wireless client event.""" + await setup_unifi_integration(hass, aioclient_mock) + with patch( "homeassistant.components.unifi.controller.UniFiController.update_wireless_clients", return_value=None, ) as wireless_clients_mock: - controller.api.websocket._data = { - "meta": {"rc": "ok", "message": "events"}, - "data": [ - { - "datetime": "2020-01-20T19:37:04Z", - "key": aiounifi.events.WIRELESS_CLIENT_CONNECTED, - "msg": "User[11:22:33:44:55:66] has connected to WLAN", - "time": 1579549024893, - } - ], - } - controller.api.session_handler("data") + mock_unifi_websocket( + data={ + "meta": {"rc": "ok", "message": "events"}, + "data": [ + { + "datetime": "2020-01-20T19:37:04Z", + "key": aiounifi.events.WIRELESS_CLIENT_CONNECTED, + "msg": "User[11:22:33:44:55:66] has connected to WLAN", + "time": 1579549024893, + } + ], + }, + ) assert wireless_clients_mock.assert_called_once +async def test_reconnect_mechanism(hass, aioclient_mock, mock_unifi_websocket): + """Verify reconnect prints only on first reconnection try.""" + await setup_unifi_integration(hass, aioclient_mock) + + aioclient_mock.clear_requests() + aioclient_mock.post(f"https://{DEFAULT_HOST}:1234/api/login", status=502) + + mock_unifi_websocket(state=STATE_DISCONNECTED) + await hass.async_block_till_done() + + assert aioclient_mock.call_count == 0 + + new_time = dt_util.utcnow() + timedelta(seconds=RETRY_TIMER) + async_fire_time_changed(hass, new_time) + await hass.async_block_till_done() + + assert aioclient_mock.call_count == 1 + + new_time = dt_util.utcnow() + timedelta(seconds=RETRY_TIMER) + async_fire_time_changed(hass, new_time) + await hass.async_block_till_done() + + assert aioclient_mock.call_count == 2 + + +@pytest.mark.parametrize( + "exception", + [ + asyncio.TimeoutError, + aiounifi.BadGateway, + aiounifi.ServiceUnavailable, + aiounifi.AiounifiException, + ], +) +async def test_reconnect_mechanism_exceptions( + hass, aioclient_mock, mock_unifi_websocket, exception +): + """Verify async_reconnect calls expected methods.""" + await setup_unifi_integration(hass, aioclient_mock) + + with patch("aiounifi.Controller.login", side_effect=exception), patch( + "homeassistant.components.unifi.controller.UniFiController.reconnect" + ) as mock_reconnect: + mock_unifi_websocket(state=STATE_DISCONNECTED) + await hass.async_block_till_done() + + new_time = dt_util.utcnow() + timedelta(seconds=RETRY_TIMER) + async_fire_time_changed(hass, new_time) + mock_reconnect.assert_called_once() + + async def test_get_controller(hass): """Successful call.""" with patch("aiounifi.Controller.check_unifi_os", return_value=True), patch( diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index e8081a831c2..51dbd735e10 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -8,9 +8,8 @@ from aiounifi.controller import ( MESSAGE_CLIENT_REMOVED, MESSAGE_DEVICE, MESSAGE_EVENT, - SIGNAL_CONNECTION_STATE, ) -from aiounifi.websocket import SIGNAL_DATA, STATE_DISCONNECTED, STATE_RUNNING +from aiounifi.websocket import STATE_DISCONNECTED, STATE_RUNNING from homeassistant import config_entries from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN @@ -157,7 +156,7 @@ async def test_no_clients(hass, aioclient_mock): assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 0 -async def test_tracked_wireless_clients(hass, aioclient_mock): +async def test_tracked_wireless_clients(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some clients.""" config_entry = await setup_unifi_integration( hass, aioclient_mock, clients_response=[CLIENT_1] @@ -171,11 +170,12 @@ async def test_tracked_wireless_clients(hass, aioclient_mock): # State change signalling works without events client_1_copy = copy(CLIENT_1) - controller.api.websocket._data = { - "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_copy], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_copy], + } + ) await hass.async_block_till_done() client_1 = hass.states.get("device_tracker.client_1") @@ -186,11 +186,13 @@ async def test_tracked_wireless_clients(hass, aioclient_mock): assert client_1.attributes["host_name"] == "client_1" # State change signalling works with events - controller.api.websocket._data = { - "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_CLIENT_1_WIRELESS_DISCONNECTED], - } - controller.api.session_handler(SIGNAL_DATA) + + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_EVENT}, + "data": [EVENT_CLIENT_1_WIRELESS_DISCONNECTED], + } + ) await hass.async_block_till_done() client_1 = hass.states.get("device_tracker.client_1") @@ -204,30 +206,30 @@ async def test_tracked_wireless_clients(hass, aioclient_mock): client_1 = hass.states.get("device_tracker.client_1") assert client_1.state == "not_home" - controller.api.websocket._data = { - "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_CLIENT_1_WIRELESS_CONNECTED], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_EVENT}, + "data": [EVENT_CLIENT_1_WIRELESS_CONNECTED], + } + ) await hass.async_block_till_done() client_1 = hass.states.get("device_tracker.client_1") assert client_1.state == "home" -async def test_tracked_clients(hass, aioclient_mock): +async def test_tracked_clients(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some clients.""" client_4_copy = copy(CLIENT_4) client_4_copy["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) - config_entry = await setup_unifi_integration( + await setup_unifi_integration( hass, aioclient_mock, options={CONF_SSID_FILTER: ["ssid"]}, clients_response=[CLIENT_1, CLIENT_2, CLIENT_3, CLIENT_5, client_4_copy], known_wireless_clients=(CLIENT_4["mac"],), ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 4 client_1 = hass.states.get("device_tracker.client_1") @@ -254,22 +256,26 @@ async def test_tracked_clients(hass, aioclient_mock): # State change signalling works client_1_copy = copy(CLIENT_1) - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_1_copy]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_copy], + } + ) await hass.async_block_till_done() client_1 = hass.states.get("device_tracker.client_1") assert client_1.state == "home" -async def test_tracked_devices(hass, aioclient_mock): +async def test_tracked_devices(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some devices.""" - config_entry = await setup_unifi_integration( + await setup_unifi_integration( hass, aioclient_mock, devices_response=[DEVICE_1, DEVICE_2], ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 device_1 = hass.states.get("device_tracker.device_1") @@ -283,12 +289,20 @@ async def test_tracked_devices(hass, aioclient_mock): # State change signalling work device_1_copy = copy(DEVICE_1) device_1_copy["next_interval"] = 20 - event = {"meta": {"message": MESSAGE_DEVICE}, "data": [device_1_copy]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_DEVICE}, + "data": [device_1_copy], + } + ) device_2_copy = copy(DEVICE_2) device_2_copy["next_interval"] = 50 - event = {"meta": {"message": MESSAGE_DEVICE}, "data": [device_2_copy]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_DEVICE}, + "data": [device_2_copy], + } + ) await hass.async_block_till_done() device_1 = hass.states.get("device_tracker.device_1") @@ -309,8 +323,12 @@ async def test_tracked_devices(hass, aioclient_mock): # Disabled device is unavailable device_1_copy = copy(DEVICE_1) device_1_copy["disabled"] = True - event = {"meta": {"message": MESSAGE_DEVICE}, "data": [device_1_copy]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_DEVICE}, + "data": [device_1_copy], + } + ) await hass.async_block_till_done() device_1 = hass.states.get("device_tracker.device_1") @@ -319,10 +337,18 @@ async def test_tracked_devices(hass, aioclient_mock): # Update device registry when device is upgraded device_2_copy = copy(DEVICE_2) device_2_copy["version"] = EVENT_DEVICE_2_UPGRADED["version_to"] - message = {"meta": {"message": MESSAGE_DEVICE}, "data": [device_2_copy]} - controller.api.message_handler(message) - event = {"meta": {"message": MESSAGE_EVENT}, "data": [EVENT_DEVICE_2_UPGRADED]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_DEVICE}, + "data": [device_2_copy], + } + ) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_EVENT}, + "data": [EVENT_DEVICE_2_UPGRADED], + } + ) await hass.async_block_till_done() # Verify device registry has been updated @@ -333,12 +359,12 @@ async def test_tracked_devices(hass, aioclient_mock): assert device.sw_version == EVENT_DEVICE_2_UPGRADED["version_to"] -async def test_remove_clients(hass, aioclient_mock): +async def test_remove_clients(hass, aioclient_mock, mock_unifi_websocket): """Test the remove_items function with some clients.""" - config_entry = await setup_unifi_integration( + await setup_unifi_integration( hass, aioclient_mock, clients_response=[CLIENT_1, CLIENT_2] ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 client_1 = hass.states.get("device_tracker.client_1") @@ -347,11 +373,12 @@ async def test_remove_clients(hass, aioclient_mock): wired_client = hass.states.get("device_tracker.wired_client") assert wired_client is not None - controller.api.websocket._data = { - "meta": {"message": MESSAGE_CLIENT_REMOVED}, - "data": [CLIENT_1], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT_REMOVED}, + "data": [CLIENT_1], + } + ) await hass.async_block_till_done() await hass.async_block_till_done() @@ -364,15 +391,15 @@ async def test_remove_clients(hass, aioclient_mock): assert wired_client is not None -async def test_controller_state_change(hass, aioclient_mock): +async def test_controller_state_change(hass, aioclient_mock, mock_unifi_websocket): """Verify entities state reflect on controller becoming unavailable.""" - config_entry = await setup_unifi_integration( + await setup_unifi_integration( hass, aioclient_mock, clients_response=[CLIENT_1], devices_response=[DEVICE_1], ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 client_1 = hass.states.get("device_tracker.client_1") @@ -382,9 +409,7 @@ async def test_controller_state_change(hass, aioclient_mock): assert device_1.state == "home" # Controller unavailable - controller.async_unifi_signalling_callback( - SIGNAL_CONNECTION_STATE, STATE_DISCONNECTED - ) + mock_unifi_websocket(state=STATE_DISCONNECTED) await hass.async_block_till_done() client_1 = hass.states.get("device_tracker.client_1") @@ -394,7 +419,7 @@ async def test_controller_state_change(hass, aioclient_mock): assert device_1.state == STATE_UNAVAILABLE # Controller available - controller.async_unifi_signalling_callback(SIGNAL_CONNECTION_STATE, STATE_RUNNING) + mock_unifi_websocket(state=STATE_RUNNING) await hass.async_block_till_done() client_1 = hass.states.get("device_tracker.client_1") @@ -554,7 +579,7 @@ async def test_option_track_devices(hass, aioclient_mock): assert device_1 is not None -async def test_option_ssid_filter(hass, aioclient_mock): +async def test_option_ssid_filter(hass, aioclient_mock, mock_unifi_websocket): """Test the SSID filter works. Client 1 will travel from a supported SSID to an unsupported ssid. @@ -593,13 +618,21 @@ async def test_option_ssid_filter(hass, aioclient_mock): # Roams to SSID outside of filter client_1_copy = copy(CLIENT_1) client_1_copy["essid"] = "other_ssid" - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_1_copy]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_copy], + } + ) # Data update while SSID filter is in effect shouldn't create the client client_3_copy = copy(CLIENT_3) client_3_copy["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_3_copy]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_3_copy], + } + ) await hass.async_block_till_done() # SSID filter marks client as away @@ -616,10 +649,19 @@ async def test_option_ssid_filter(hass, aioclient_mock): options={CONF_SSID_FILTER: []}, ) await hass.async_block_till_done() - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_1_copy]} - controller.api.message_handler(event) - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_3_copy]} - controller.api.message_handler(event) + + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_copy], + } + ) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_3_copy], + } + ) await hass.async_block_till_done() client_1 = hass.states.get("device_tracker.client_1") @@ -636,16 +678,24 @@ async def test_option_ssid_filter(hass, aioclient_mock): client_1 = hass.states.get("device_tracker.client_1") assert client_1.state == "not_home" - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_3_copy]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_3_copy], + } + ) await hass.async_block_till_done() # Client won't go away until after next update client_3 = hass.states.get("device_tracker.client_3") assert client_3.state == "home" # Trigger update to get client marked as away - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [CLIENT_3]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_3_copy], + } + ) await hass.async_block_till_done() new_time = ( @@ -659,7 +709,9 @@ async def test_option_ssid_filter(hass, aioclient_mock): assert client_3.state == "not_home" -async def test_wireless_client_go_wired_issue(hass, aioclient_mock): +async def test_wireless_client_go_wired_issue( + hass, aioclient_mock, mock_unifi_websocket +): """Test the solution to catch wireless device go wired UniFi issue. UniFi has a known issue that when a wireless device goes away it sometimes gets marked as wired. @@ -681,8 +733,12 @@ async def test_wireless_client_go_wired_issue(hass, aioclient_mock): # Trigger wired bug client_1_client["is_wired"] = True - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_1_client]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_client], + } + ) await hass.async_block_till_done() # Wired bug fix keeps client marked as wireless @@ -702,8 +758,12 @@ async def test_wireless_client_go_wired_issue(hass, aioclient_mock): assert client_1.attributes["is_wired"] is False # Try to mark client as connected - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_1_client]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_client], + } + ) await hass.async_block_till_done() # Make sure it don't go online again until wired bug disappears @@ -713,8 +773,12 @@ async def test_wireless_client_go_wired_issue(hass, aioclient_mock): # Make client wireless client_1_client["is_wired"] = False - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_1_client]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_client], + } + ) await hass.async_block_till_done() # Client is no longer affected by wired bug and can be marked online @@ -723,7 +787,7 @@ async def test_wireless_client_go_wired_issue(hass, aioclient_mock): assert client_1.attributes["is_wired"] is False -async def test_option_ignore_wired_bug(hass, aioclient_mock): +async def test_option_ignore_wired_bug(hass, aioclient_mock, mock_unifi_websocket): """Test option to ignore wired bug.""" client_1_client = copy(CLIENT_1) client_1_client["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) @@ -745,8 +809,12 @@ async def test_option_ignore_wired_bug(hass, aioclient_mock): # Trigger wired bug client_1_client["is_wired"] = True - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_1_client]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_client], + } + ) await hass.async_block_till_done() # Wired bug in effect @@ -766,8 +834,12 @@ async def test_option_ignore_wired_bug(hass, aioclient_mock): assert client_1.attributes["is_wired"] is True # Mark client as connected again - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_1_client]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_client], + } + ) await hass.async_block_till_done() # Ignoring wired bug allows client to go home again even while affected @@ -777,8 +849,12 @@ async def test_option_ignore_wired_bug(hass, aioclient_mock): # Make client wireless client_1_client["is_wired"] = False - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_1_client]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_client], + } + ) await hass.async_block_till_done() # Client is wireless and still connected diff --git a/tests/components/unifi/test_init.py b/tests/components/unifi/test_init.py index 6d8b894fc34..591165dabf2 100644 --- a/tests/components/unifi/test_init.py +++ b/tests/components/unifi/test_init.py @@ -1,14 +1,20 @@ """Test UniFi setup process.""" -from unittest.mock import AsyncMock, Mock, patch +from unittest.mock import AsyncMock, patch from homeassistant.components import unifi from homeassistant.components.unifi import async_flatten_entry_data from homeassistant.components.unifi.const import CONF_CONTROLLER, DOMAIN as UNIFI_DOMAIN +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.setup import async_setup_component -from .test_controller import CONTROLLER_DATA, ENTRY_CONFIG, setup_unifi_integration +from .test_controller import ( + CONTROLLER_DATA, + DEFAULT_CONFIG_ENTRY_ID, + ENTRY_CONFIG, + setup_unifi_integration, +) -from tests.common import MockConfigEntry, mock_coro +from tests.common import MockConfigEntry async def test_setup_with_no_config(hass): @@ -19,7 +25,7 @@ async def test_setup_with_no_config(hass): async def test_successful_config_entry(hass, aioclient_mock): """Test that configured options for a host are loaded via config entry.""" - await setup_unifi_integration(hass, aioclient_mock) + await setup_unifi_integration(hass, aioclient_mock, unique_id=None) assert hass.data[UNIFI_DOMAIN] @@ -32,29 +38,28 @@ async def test_controller_fail_setup(hass): assert hass.data[UNIFI_DOMAIN] == {} -async def test_controller_no_mac(hass): +async def test_controller_mac(hass): """Test that configured options for a host are loaded via config entry.""" entry = MockConfigEntry( - domain=UNIFI_DOMAIN, - data=ENTRY_CONFIG, - unique_id="1", - version=1, + domain=UNIFI_DOMAIN, data=ENTRY_CONFIG, unique_id="1", entry_id=1 ) entry.add_to_hass(hass) - mock_registry = Mock() - with patch( - "homeassistant.components.unifi.UniFiController" - ) as mock_controller, patch( - "homeassistant.helpers.device_registry.async_get_registry", - return_value=mock_coro(mock_registry), - ): + + with patch("homeassistant.components.unifi.UniFiController") as mock_controller: mock_controller.return_value.async_setup = AsyncMock(return_value=True) - mock_controller.return_value.mac = None + mock_controller.return_value.mac = "mac1" assert await unifi.async_setup_entry(hass, entry) is True assert len(mock_controller.mock_calls) == 2 - assert len(mock_registry.mock_calls) == 0 + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get_or_create( + config_entry_id=entry.entry_id, connections={(CONNECTION_NETWORK_MAC, "mac1")} + ) + assert device.manufacturer == "Ubiquiti Networks" + assert device.model == "UniFi Controller" + assert device.name == "UniFi Controller" + assert device.sw_version is None async def test_flatten_entry_data(hass): @@ -73,5 +78,45 @@ async def test_unload_entry(hass, aioclient_mock): config_entry = await setup_unifi_integration(hass, aioclient_mock) assert hass.data[UNIFI_DOMAIN] - assert await unifi.async_unload_entry(hass, config_entry) + assert await hass.config_entries.async_unload(config_entry.entry_id) assert not hass.data[UNIFI_DOMAIN] + + +async def test_wireless_clients(hass, hass_storage, aioclient_mock): + """Verify wireless clients class.""" + hass_storage[unifi.STORAGE_KEY] = { + "version": unifi.STORAGE_VERSION, + "data": { + DEFAULT_CONFIG_ENTRY_ID: { + "wireless_devices": ["00:00:00:00:00:00", "00:00:00:00:00:01"] + } + }, + } + + client_1 = { + "hostname": "client_1", + "ip": "10.0.0.1", + "is_wired": False, + "mac": "00:00:00:00:00:01", + } + client_2 = { + "hostname": "client_2", + "ip": "10.0.0.2", + "is_wired": False, + "mac": "00:00:00:00:00:02", + } + config_entry = await setup_unifi_integration( + hass, aioclient_mock, clients_response=[client_1, client_2] + ) + + for mac in [ + "00:00:00:00:00:00", + "00:00:00:00:00:01", + "00:00:00:00:00:02", + ]: + assert ( + mac + in hass_storage[unifi.STORAGE_KEY]["data"][config_entry.entry_id][ + "wireless_devices" + ] + ) diff --git a/tests/components/unifi/test_sensor.py b/tests/components/unifi/test_sensor.py index c668bf3789f..db1794e0878 100644 --- a/tests/components/unifi/test_sensor.py +++ b/tests/components/unifi/test_sensor.py @@ -2,7 +2,6 @@ from copy import deepcopy from aiounifi.controller import MESSAGE_CLIENT, MESSAGE_CLIENT_REMOVED -from aiounifi.websocket import SIGNAL_DATA from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN @@ -63,7 +62,7 @@ async def test_no_clients(hass, aioclient_mock): assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 0 -async def test_sensors(hass, aioclient_mock): +async def test_sensors(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some clients.""" config_entry = await setup_unifi_integration( hass, @@ -104,8 +103,12 @@ async def test_sensors(hass, aioclient_mock): clients[1]["tx_bytes"] = 6789000000 clients[1]["uptime"] = 1600180860 - event = {"meta": {"message": MESSAGE_CLIENT}, "data": clients} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": clients, + } + ) await hass.async_block_till_done() wireless_client_rx = hass.states.get("sensor.wireless_client_name_rx") @@ -178,9 +181,9 @@ async def test_sensors(hass, aioclient_mock): assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 6 -async def test_remove_sensors(hass, aioclient_mock): +async def test_remove_sensors(hass, aioclient_mock, mock_unifi_websocket): """Test the remove_items function with some clients.""" - config_entry = await setup_unifi_integration( + await setup_unifi_integration( hass, aioclient_mock, options={ @@ -189,7 +192,7 @@ async def test_remove_sensors(hass, aioclient_mock): }, clients_response=CLIENTS, ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 6 assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 @@ -209,11 +212,12 @@ async def test_remove_sensors(hass, aioclient_mock): wireless_client_uptime = hass.states.get("sensor.wireless_client_name_uptime") assert wireless_client_uptime is not None - controller.api.websocket._data = { - "meta": {"message": MESSAGE_CLIENT_REMOVED}, - "data": [CLIENTS[0]], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT_REMOVED}, + "data": [CLIENTS[0]], + } + ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 3 diff --git a/tests/components/unifi/test_switch.py b/tests/components/unifi/test_switch.py index e5a3a7eccc4..a0dc8e984e1 100644 --- a/tests/components/unifi/test_switch.py +++ b/tests/components/unifi/test_switch.py @@ -2,7 +2,6 @@ from copy import deepcopy from aiounifi.controller import MESSAGE_CLIENT_REMOVED, MESSAGE_EVENT -from aiounifi.websocket import SIGNAL_DATA from homeassistant import config_entries from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN @@ -17,6 +16,7 @@ from homeassistant.components.unifi.const import ( ) from homeassistant.components.unifi.switch import POE_SWITCH from homeassistant.helpers import entity_registry +from homeassistant.helpers.dispatcher import async_dispatcher_send from .test_controller import ( CONTROLLER_HOST, @@ -370,6 +370,7 @@ async def test_switches(hass, aioclient_mock): dpi_switch = hass.states.get("switch.block_media_streaming") assert dpi_switch is not None assert dpi_switch.state == "on" + assert dpi_switch.attributes["icon"] == "mdi:network" # Block and unblock client @@ -419,17 +420,22 @@ async def test_switches(hass, aioclient_mock): assert aioclient_mock.call_count == 14 assert aioclient_mock.mock_calls[13][2] == {"enabled": True} + # Make sure no duplicates arise on generic signal update + async_dispatcher_send(hass, controller.signal_update) + await hass.async_block_till_done() + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 4 -async def test_remove_switches(hass, aioclient_mock): + +async def test_remove_switches(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some clients.""" - config_entry = await setup_unifi_integration( + await setup_unifi_integration( hass, aioclient_mock, options={CONF_BLOCK_CLIENT: [UNBLOCKED["mac"]]}, clients_response=[CLIENT_1, UNBLOCKED], devices_response=[DEVICE_1], ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 poe_switch = hass.states.get("switch.poe_client_1") @@ -438,11 +444,12 @@ async def test_remove_switches(hass, aioclient_mock): block_switch = hass.states.get("switch.block_client_2") assert block_switch is not None - controller.api.websocket._data = { - "meta": {"message": MESSAGE_CLIENT_REMOVED}, - "data": [CLIENT_1, UNBLOCKED], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT_REMOVED}, + "data": [CLIENT_1, UNBLOCKED], + } + ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 @@ -454,7 +461,7 @@ async def test_remove_switches(hass, aioclient_mock): assert block_switch is None -async def test_block_switches(hass, aioclient_mock): +async def test_block_switches(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some clients.""" config_entry = await setup_unifi_integration( hass, @@ -479,11 +486,12 @@ async def test_block_switches(hass, aioclient_mock): assert unblocked is not None assert unblocked.state == "on" - controller.api.websocket._data = { - "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_BLOCKED_CLIENT_UNBLOCKED], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_EVENT}, + "data": [EVENT_BLOCKED_CLIENT_UNBLOCKED], + } + ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 @@ -491,11 +499,12 @@ async def test_block_switches(hass, aioclient_mock): assert blocked is not None assert blocked.state == "on" - controller.api.websocket._data = { - "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_BLOCKED_CLIENT_BLOCKED], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_EVENT}, + "data": [EVENT_BLOCKED_CLIENT_BLOCKED], + } + ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 @@ -526,9 +535,11 @@ async def test_block_switches(hass, aioclient_mock): } -async def test_new_client_discovered_on_block_control(hass, aioclient_mock): +async def test_new_client_discovered_on_block_control( + hass, aioclient_mock, mock_unifi_websocket +): """Test if 2nd update has a new client.""" - config_entry = await setup_unifi_integration( + await setup_unifi_integration( hass, aioclient_mock, options={ @@ -538,27 +549,28 @@ async def test_new_client_discovered_on_block_control(hass, aioclient_mock): CONF_DPI_RESTRICTIONS: False, }, ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 blocked = hass.states.get("switch.block_client_1") assert blocked is None - controller.api.websocket._data = { - "meta": {"message": "sta:sync"}, - "data": [BLOCKED], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": "sta:sync"}, + "data": [BLOCKED], + } + ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 - controller.api.websocket._data = { - "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_BLOCKED_CLIENT_CONNECTED], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_EVENT}, + "data": [EVENT_BLOCKED_CLIENT_CONNECTED], + } + ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 @@ -634,7 +646,9 @@ async def test_option_remove_switches(hass, aioclient_mock): assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 -async def test_new_client_discovered_on_poe_control(hass, aioclient_mock): +async def test_new_client_discovered_on_poe_control( + hass, aioclient_mock, mock_unifi_websocket +): """Test if 2nd update has a new client.""" config_entry = await setup_unifi_integration( hass, @@ -647,20 +661,22 @@ async def test_new_client_discovered_on_poe_control(hass, aioclient_mock): assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 - controller.api.websocket._data = { - "meta": {"message": "sta:sync"}, - "data": [CLIENT_2], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": "sta:sync"}, + "data": [CLIENT_2], + } + ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 - controller.api.websocket._data = { - "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_CLIENT_2_CONNECTED], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_EVENT}, + "data": [EVENT_CLIENT_2_CONNECTED], + } + ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 From b3c33fc1be3b41239fec5c607e9fee59e602195d Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Fri, 5 Mar 2021 21:41:55 +0100 Subject: [PATCH 188/831] Fix issue at Netatmo startup (#47452) --- homeassistant/components/netatmo/camera.py | 48 ++++++++++--------- homeassistant/components/netatmo/climate.py | 8 +++- .../components/netatmo/data_handler.py | 6 ++- homeassistant/components/netatmo/light.py | 9 ++-- homeassistant/components/netatmo/sensor.py | 18 ++++++- 5 files changed, 56 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index 6e55b884d4d..5163c9582b0 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -7,6 +7,7 @@ import voluptuous as vol from homeassistant.components.camera import SUPPORT_STREAM, Camera from homeassistant.core import callback +from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -49,15 +50,17 @@ async def async_setup_entry(hass, entry, async_add_entities): data_handler = hass.data[DOMAIN][entry.entry_id][DATA_HANDLER] + await data_handler.register_data_class( + CAMERA_DATA_CLASS_NAME, CAMERA_DATA_CLASS_NAME, None + ) + + if CAMERA_DATA_CLASS_NAME not in data_handler.data: + raise PlatformNotReady + async def get_entities(): """Retrieve Netatmo entities.""" - await data_handler.register_data_class( - CAMERA_DATA_CLASS_NAME, CAMERA_DATA_CLASS_NAME, None - ) - data = data_handler.data - - if not data.get(CAMERA_DATA_CLASS_NAME): + if not data_handler.data.get(CAMERA_DATA_CLASS_NAME): return [] data_class = data_handler.data[CAMERA_DATA_CLASS_NAME] @@ -94,24 +97,25 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(await get_entities(), True) + await data_handler.unregister_data_class(CAMERA_DATA_CLASS_NAME, None) + platform = entity_platform.current_platform.get() - if data_handler.data[CAMERA_DATA_CLASS_NAME] is not None: - platform.async_register_entity_service( - SERVICE_SET_PERSONS_HOME, - {vol.Required(ATTR_PERSONS): vol.All(cv.ensure_list, [cv.string])}, - "_service_set_persons_home", - ) - platform.async_register_entity_service( - SERVICE_SET_PERSON_AWAY, - {vol.Optional(ATTR_PERSON): cv.string}, - "_service_set_person_away", - ) - platform.async_register_entity_service( - SERVICE_SET_CAMERA_LIGHT, - {vol.Required(ATTR_CAMERA_LIGHT_MODE): vol.In(CAMERA_LIGHT_MODES)}, - "_service_set_camera_light", - ) + platform.async_register_entity_service( + SERVICE_SET_PERSONS_HOME, + {vol.Required(ATTR_PERSONS): vol.All(cv.ensure_list, [cv.string])}, + "_service_set_persons_home", + ) + platform.async_register_entity_service( + SERVICE_SET_PERSON_AWAY, + {vol.Optional(ATTR_PERSON): cv.string}, + "_service_set_person_away", + ) + platform.async_register_entity_service( + SERVICE_SET_CAMERA_LIGHT, + {vol.Required(ATTR_CAMERA_LIGHT_MODE): vol.In(CAMERA_LIGHT_MODES)}, + "_service_set_camera_light", + ) class NetatmoCamera(NetatmoBase, Camera): diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index a53f7f9fb08..d12ee9263db 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -25,6 +25,7 @@ from homeassistant.const import ( TEMP_CELSIUS, ) from homeassistant.core import callback +from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.device_registry import async_get_registry from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -81,6 +82,7 @@ NETATMO_MAP_PRESET = { STATE_NETATMO_AWAY: PRESET_AWAY, STATE_NETATMO_OFF: STATE_NETATMO_OFF, STATE_NETATMO_MANUAL: STATE_NETATMO_MANUAL, + STATE_NETATMO_HOME: PRESET_SCHEDULE, } HVAC_MAP_NETATMO = { @@ -111,8 +113,8 @@ async def async_setup_entry(hass, entry, async_add_entities): ) home_data = data_handler.data.get(HOMEDATA_DATA_CLASS_NAME) - if not home_data: - return + if HOMEDATA_DATA_CLASS_NAME not in data_handler.data: + raise PlatformNotReady async def get_entities(): """Retrieve Netatmo entities.""" @@ -151,6 +153,8 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(await get_entities(), True) + await data_handler.unregister_data_class(HOMEDATA_DATA_CLASS_NAME, None) + platform = entity_platform.current_platform.get() if home_data is not None: diff --git a/homeassistant/components/netatmo/data_handler.py b/homeassistant/components/netatmo/data_handler.py index 9bc4b197f1b..be0120bd1a0 100644 --- a/homeassistant/components/netatmo/data_handler.py +++ b/homeassistant/components/netatmo/data_handler.py @@ -129,7 +129,11 @@ class NetatmoDataHandler: if update_callback: update_callback() - except (pyatmo.NoDevice, pyatmo.ApiError) as err: + except pyatmo.NoDevice as err: + _LOGGER.debug(err) + self.data[data_class_entry] = None + + except pyatmo.ApiError as err: _LOGGER.debug(err) async def register_data_class( diff --git a/homeassistant/components/netatmo/light.py b/homeassistant/components/netatmo/light.py index dc8bf3f1fc8..eed12f048c8 100644 --- a/homeassistant/components/netatmo/light.py +++ b/homeassistant/components/netatmo/light.py @@ -31,18 +31,15 @@ async def async_setup_entry(hass, entry, async_add_entities): data_handler = hass.data[DOMAIN][entry.entry_id][DATA_HANDLER] + if CAMERA_DATA_CLASS_NAME not in data_handler.data: + raise PlatformNotReady + async def get_entities(): """Retrieve Netatmo entities.""" - await data_handler.register_data_class( - CAMERA_DATA_CLASS_NAME, CAMERA_DATA_CLASS_NAME, None - ) entities = [] all_cameras = [] - if CAMERA_DATA_CLASS_NAME not in data_handler.data: - raise PlatformNotReady - try: for home in data_handler.data[CAMERA_DATA_CLASS_NAME].cameras.values(): for camera in home.values(): diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index f8484444818..9176b670bea 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -20,6 +20,7 @@ from homeassistant.const import ( TEMP_CELSIUS, ) from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.device_registry import async_entries_for_config_entry from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, @@ -129,14 +130,25 @@ async def async_setup_entry(hass, entry, async_add_entities): """Set up the Netatmo weather and homecoach platform.""" data_handler = hass.data[DOMAIN][entry.entry_id][DATA_HANDLER] + await data_handler.register_data_class( + WEATHERSTATION_DATA_CLASS_NAME, WEATHERSTATION_DATA_CLASS_NAME, None + ) + await data_handler.register_data_class( + HOMECOACH_DATA_CLASS_NAME, HOMECOACH_DATA_CLASS_NAME, None + ) + async def find_entities(data_class_name): """Find all entities.""" - await data_handler.register_data_class(data_class_name, data_class_name, None) + if data_class_name not in data_handler.data: + raise PlatformNotReady all_module_infos = {} data = data_handler.data - if not data.get(data_class_name): + if data_class_name not in data: + return [] + + if data[data_class_name] is None: return [] data_class = data[data_class_name] @@ -174,6 +186,8 @@ async def async_setup_entry(hass, entry, async_add_entities): NetatmoSensor(data_handler, data_class_name, module, condition) ) + await data_handler.unregister_data_class(data_class_name, None) + return entities for data_class_name in [ From 02e723f2066e0fddcd07698b32dedc395a97c75b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 5 Mar 2021 21:48:02 +0100 Subject: [PATCH 189/831] Typing tweak to the Elgato integration (#47471) --- .../components/elgato/config_flow.py | 20 +++++++++---------- homeassistant/components/elgato/light.py | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/elgato/config_flow.py b/homeassistant/components/elgato/config_flow.py index e9138afd86c..7c818d6f3fa 100644 --- a/homeassistant/components/elgato/config_flow.py +++ b/homeassistant/components/elgato/config_flow.py @@ -1,7 +1,7 @@ """Config flow to configure the Elgato Key Light integration.""" from __future__ import annotations -from typing import Any, Dict +from typing import Any from elgato import Elgato, ElgatoError import voluptuous as vol @@ -25,8 +25,8 @@ class ElgatoFlowHandler(ConfigFlow, domain=DOMAIN): serial_number: str async def async_step_user( - self, user_input: Dict[str, Any] | None = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" if user_input is None: return self._async_show_setup_form() @@ -42,8 +42,8 @@ class ElgatoFlowHandler(ConfigFlow, domain=DOMAIN): return self._async_create_entry() async def async_step_zeroconf( - self, discovery_info: Dict[str, Any] - ) -> Dict[str, Any]: + self, discovery_info: dict[str, Any] + ) -> dict[str, Any]: """Handle zeroconf discovery.""" self.host = discovery_info[CONF_HOST] self.port = discovery_info[CONF_PORT] @@ -59,15 +59,15 @@ class ElgatoFlowHandler(ConfigFlow, domain=DOMAIN): ) async def async_step_zeroconf_confirm( - self, _: Dict[str, Any] | None = None - ) -> Dict[str, Any]: + self, _: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a flow initiated by zeroconf.""" return self._async_create_entry() @callback def _async_show_setup_form( - self, errors: Dict[str, str] | None = None - ) -> Dict[str, Any]: + self, errors: dict[str, str] | None = None + ) -> dict[str, Any]: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -81,7 +81,7 @@ class ElgatoFlowHandler(ConfigFlow, domain=DOMAIN): ) @callback - def _async_create_entry(self) -> Dict[str, Any]: + def _async_create_entry(self) -> dict[str, Any]: return self.async_create_entry( title=self.serial_number, data={ diff --git a/homeassistant/components/elgato/light.py b/homeassistant/components/elgato/light.py index 0648a4817bc..3005560e2ea 100644 --- a/homeassistant/components/elgato/light.py +++ b/homeassistant/components/elgato/light.py @@ -3,7 +3,7 @@ from __future__ import annotations from datetime import timedelta import logging -from typing import Any, Callable, Dict, List +from typing import Any, Callable from elgato import Elgato, ElgatoError, Info, State @@ -38,7 +38,7 @@ SCAN_INTERVAL = timedelta(seconds=10) async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Elgato Key Light based on a config entry.""" elgato: Elgato = hass.data[DOMAIN][entry.entry_id][DATA_ELGATO_CLIENT] @@ -116,7 +116,7 @@ class ElgatoLight(LightEntity): async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the light.""" - data: Dict[str, bool | int] = {ATTR_ON: True} + data: dict[str, bool | int] = {ATTR_ON: True} if ATTR_ON in kwargs: data[ATTR_ON] = kwargs[ATTR_ON] @@ -149,7 +149,7 @@ class ElgatoLight(LightEntity): self._temperature = state.temperature @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this Elgato Key Light.""" return { ATTR_IDENTIFIERS: {(DOMAIN, self._info.serial_number)}, From 50d3aae418cfb06f205eb7cac087a0be621bb6a4 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 5 Mar 2021 22:09:05 +0100 Subject: [PATCH 190/831] Improve restoring UniFi POE entity state (#47148) * Improve restoring data and better handling when the restore data is empty Improve readability of some logic related to POE clients * There is no need to check clients_all in Switch platform * Add better tests when restoring state * Port except handling shouldn't be needed anymore * Walrusify get_last_state --- homeassistant/components/unifi/controller.py | 24 ++- homeassistant/components/unifi/switch.py | 73 ++++---- tests/components/unifi/test_switch.py | 171 +++++++++++++++++-- 3 files changed, 200 insertions(+), 68 deletions(-) diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 7c95058e8e4..9096620f0ed 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -40,6 +40,7 @@ from homeassistant.core import callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.entity_registry import async_entries_for_config_entry from homeassistant.helpers.event import async_track_time_interval import homeassistant.util.dt as dt_util @@ -338,21 +339,18 @@ class UniFiController: self._site_role = description[0]["site_role"] - # Restore clients that is not a part of active clients list. + # Restore clients that are not a part of active clients list. entity_registry = await self.hass.helpers.entity_registry.async_get_registry() - for entity in entity_registry.entities.values(): - if ( - entity.config_entry_id != self.config_entry.entry_id - or "-" not in entity.unique_id - ): + for entry in async_entries_for_config_entry( + entity_registry, self.config_entry.entry_id + ): + if entry.domain == TRACKER_DOMAIN: + mac = entry.unique_id.split("-", 1)[0] + elif entry.domain == SWITCH_DOMAIN: + mac = entry.unique_id.split("-", 1)[1] + else: continue - mac = "" - if entity.domain == TRACKER_DOMAIN: - mac = entity.unique_id.split("-", 1)[0] - elif entity.domain == SWITCH_DOMAIN: - mac = entity.unique_id.split("-", 1)[1] - if mac in self.api.clients or mac not in self.api.clients_all: continue @@ -360,7 +358,7 @@ class UniFiController: self.api.clients.process_raw([client.raw]) LOGGER.debug( "Restore disconnected client %s (%s)", - entity.entity_id, + entry.entity_id, client.mac, ) diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index e596e0b1e2a..6c8b6ea35cd 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -18,6 +18,7 @@ from aiounifi.events import ( from homeassistant.components.switch import DOMAIN, SwitchEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_registry import async_entries_for_config_entry from homeassistant.helpers.restore_state import RestoreEntity from .const import ATTR_MANUFACTURER, DOMAIN as UNIFI_DOMAIN @@ -50,19 +51,18 @@ async def async_setup_entry(hass, config_entry, async_add_entities): return # Store previously known POE control entities in case their POE are turned off. - previously_known_poe_clients = [] + known_poe_clients = [] entity_registry = await hass.helpers.entity_registry.async_get_registry() - for entity in entity_registry.entities.values(): + for entry in async_entries_for_config_entry(entity_registry, config_entry.entry_id): - if ( - entity.config_entry_id != config_entry.entry_id - or not entity.unique_id.startswith(POE_SWITCH) - ): + if not entry.unique_id.startswith(POE_SWITCH): continue - mac = entity.unique_id.replace(f"{POE_SWITCH}-", "") - if mac in controller.api.clients or mac in controller.api.clients_all: - previously_known_poe_clients.append(entity.unique_id) + mac = entry.unique_id.replace(f"{POE_SWITCH}-", "") + if mac not in controller.api.clients: + continue + + known_poe_clients.append(mac) for mac in controller.option_block_clients: if mac not in controller.api.clients and mac in controller.api.clients_all: @@ -80,9 +80,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): add_block_entities(controller, async_add_entities, clients) if controller.option_poe_clients: - add_poe_entities( - controller, async_add_entities, clients, previously_known_poe_clients - ) + add_poe_entities(controller, async_add_entities, clients, known_poe_clients) if controller.option_dpi_restrictions: add_dpi_entities(controller, async_add_entities, dpi_groups) @@ -91,7 +89,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): controller.listeners.append(async_dispatcher_connect(hass, signal, items_added)) items_added() - previously_known_poe_clients.clear() + known_poe_clients.clear() @callback @@ -111,9 +109,7 @@ def add_block_entities(controller, async_add_entities, clients): @callback -def add_poe_entities( - controller, async_add_entities, clients, previously_known_poe_clients -): +def add_poe_entities(controller, async_add_entities, clients, known_poe_clients): """Add new switch entities from the controller.""" switches = [] @@ -123,10 +119,13 @@ def add_poe_entities( if mac in controller.entities[DOMAIN][POE_SWITCH]: continue - poe_client_id = f"{POE_SWITCH}-{mac}" client = controller.api.clients[mac] - if poe_client_id not in previously_known_poe_clients and ( + # Try to identify new clients powered by POE. + # Known POE clients have been created in previous HASS sessions. + # If port_poe is None the port does not support POE + # If poe_enable is False we can't know if a POE client is available for control. + if mac not in known_poe_clients and ( mac in controller.wireless_clients or client.sw_mac not in devices or not devices[client.sw_mac].ports[client.sw_port].port_poe @@ -139,7 +138,7 @@ def add_poe_entities( multi_clients_on_port = False for client2 in controller.api.clients.values(): - if poe_client_id in previously_known_poe_clients: + if mac in known_poe_clients: break if ( @@ -196,18 +195,19 @@ class UniFiPOEClientSwitch(UniFiClient, SwitchEntity, RestoreEntity): """Call when entity about to be added to Home Assistant.""" await super().async_added_to_hass() - state = await self.async_get_last_state() - if state is None: + if self.poe_mode: # POE is enabled and client in a known state return - if self.poe_mode is None: - self.poe_mode = state.attributes["poe_mode"] + if (state := await self.async_get_last_state()) is None: + return + + self.poe_mode = state.attributes.get("poe_mode") if not self.client.sw_mac: - self.client.raw["sw_mac"] = state.attributes["switch"] + self.client.raw["sw_mac"] = state.attributes.get("switch") if not self.client.sw_port: - self.client.raw["sw_port"] = state.attributes["port"] + self.client.raw["sw_port"] = state.attributes.get("port") @property def is_on(self): @@ -218,16 +218,15 @@ class UniFiPOEClientSwitch(UniFiClient, SwitchEntity, RestoreEntity): def available(self): """Return if switch is available. - Poe_mode None means its poe state is unknown. + Poe_mode None means its POE state is unknown. Sw_mac unavailable means restored client. """ return ( - self.poe_mode is None - or self.client.sw_mac - and ( - self.controller.available - and self.client.sw_mac in self.controller.api.devices - ) + self.poe_mode is not None + and self.controller.available + and self.client.sw_port + and self.client.sw_mac + and self.client.sw_mac in self.controller.api.devices ) async def async_turn_on(self, **kwargs): @@ -257,15 +256,7 @@ class UniFiPOEClientSwitch(UniFiClient, SwitchEntity, RestoreEntity): @property def port(self): """Shortcut to the switch port that client is connected to.""" - try: - return self.device.ports[self.client.sw_port] - except (AttributeError, KeyError, TypeError): - _LOGGER.warning( - "Entity %s reports faulty device %s or port %s", - self.entity_id, - self.client.sw_mac, - self.client.sw_port, - ) + return self.device.ports[self.client.sw_port] async def options_updated(self) -> None: """Config entry options are updated, remove entity if option is disabled.""" diff --git a/tests/components/unifi/test_switch.py b/tests/components/unifi/test_switch.py index a0dc8e984e1..44120a18ee3 100644 --- a/tests/components/unifi/test_switch.py +++ b/tests/components/unifi/test_switch.py @@ -1,9 +1,10 @@ """UniFi switch platform tests.""" from copy import deepcopy +from unittest.mock import patch from aiounifi.controller import MESSAGE_CLIENT_REMOVED, MESSAGE_EVENT -from homeassistant import config_entries +from homeassistant import config_entries, core from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.unifi.const import ( @@ -726,8 +727,66 @@ async def test_ignore_multiple_poe_clients_on_same_port(hass, aioclient_mock): assert switch_2 is None -async def test_restoring_client(hass, aioclient_mock): - """Test the update_items function with some clients.""" +async def test_restore_client_succeed(hass, aioclient_mock): + """Test that RestoreEntity works as expected.""" + POE_DEVICE = { + "device_id": "12345", + "ip": "1.0.1.1", + "mac": "00:00:00:00:01:01", + "last_seen": 1562600145, + "model": "US16P150", + "name": "POE Switch", + "port_overrides": [ + { + "poe_mode": "off", + "port_idx": 1, + "portconf_id": "5f3edd2aba4cc806a19f2db2", + } + ], + "port_table": [ + { + "media": "GE", + "name": "Port 1", + "op_mode": "switch", + "poe_caps": 7, + "poe_class": "Unknown", + "poe_current": "0.00", + "poe_enable": False, + "poe_good": False, + "poe_mode": "off", + "poe_power": "0.00", + "poe_voltage": "0.00", + "port_idx": 1, + "port_poe": True, + "portconf_id": "5f3edd2aba4cc806a19f2db2", + "up": False, + }, + ], + "state": 1, + "type": "usw", + "version": "4.0.42.10433", + } + POE_CLIENT = { + "hostname": "poe_client", + "ip": "1.0.0.1", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + "name": "POE Client", + "oui": "Producer", + } + + fake_state = core.State( + "switch.poe_client", + "off", + { + "power": "0.00", + "switch": POE_DEVICE["mac"], + "port": 1, + "poe_mode": "auto", + }, + ) + config_entry = config_entries.ConfigEntry( version=1, domain=UNIFI_DOMAIN, @@ -744,15 +803,100 @@ async def test_restoring_client(hass, aioclient_mock): registry.async_get_or_create( SWITCH_DOMAIN, UNIFI_DOMAIN, - f'{POE_SWITCH}-{CLIENT_1["mac"]}', - suggested_object_id=CLIENT_1["hostname"], + f'{POE_SWITCH}-{POE_CLIENT["mac"]}', + suggested_object_id=POE_CLIENT["hostname"], config_entry=config_entry, ) + + with patch( + "homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state", + return_value=fake_state, + ): + await setup_unifi_integration( + hass, + aioclient_mock, + options={ + CONF_TRACK_CLIENTS: False, + CONF_TRACK_DEVICES: False, + }, + clients_response=[], + devices_response=[POE_DEVICE], + clients_all_response=[POE_CLIENT], + ) + + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 + + poe_client = hass.states.get("switch.poe_client") + assert poe_client.state == "off" + + +async def test_restore_client_no_old_state(hass, aioclient_mock): + """Test that RestoreEntity without old state makes entity unavailable.""" + POE_DEVICE = { + "device_id": "12345", + "ip": "1.0.1.1", + "mac": "00:00:00:00:01:01", + "last_seen": 1562600145, + "model": "US16P150", + "name": "POE Switch", + "port_overrides": [ + { + "poe_mode": "off", + "port_idx": 1, + "portconf_id": "5f3edd2aba4cc806a19f2db2", + } + ], + "port_table": [ + { + "media": "GE", + "name": "Port 1", + "op_mode": "switch", + "poe_caps": 7, + "poe_class": "Unknown", + "poe_current": "0.00", + "poe_enable": False, + "poe_good": False, + "poe_mode": "off", + "poe_power": "0.00", + "poe_voltage": "0.00", + "port_idx": 1, + "port_poe": True, + "portconf_id": "5f3edd2aba4cc806a19f2db2", + "up": False, + }, + ], + "state": 1, + "type": "usw", + "version": "4.0.42.10433", + } + POE_CLIENT = { + "hostname": "poe_client", + "ip": "1.0.0.1", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + "name": "POE Client", + "oui": "Producer", + } + + config_entry = config_entries.ConfigEntry( + version=1, + domain=UNIFI_DOMAIN, + title="Mock Title", + data=ENTRY_CONFIG, + source="test", + connection_class=config_entries.CONN_CLASS_LOCAL_POLL, + system_options={}, + options={}, + entry_id=1, + ) + + registry = await entity_registry.async_get_registry(hass) registry.async_get_or_create( SWITCH_DOMAIN, UNIFI_DOMAIN, - f'{POE_SWITCH}-{CLIENT_2["mac"]}', - suggested_object_id=CLIENT_2["hostname"], + f'{POE_SWITCH}-{POE_CLIENT["mac"]}', + suggested_object_id=POE_CLIENT["hostname"], config_entry=config_entry, ) @@ -760,16 +904,15 @@ async def test_restoring_client(hass, aioclient_mock): hass, aioclient_mock, options={ - CONF_BLOCK_CLIENT: ["random mac"], CONF_TRACK_CLIENTS: False, CONF_TRACK_DEVICES: False, }, - clients_response=[CLIENT_2], - devices_response=[DEVICE_1], - clients_all_response=[CLIENT_1], + clients_response=[], + devices_response=[POE_DEVICE], + clients_all_response=[POE_CLIENT], ) - assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 - device_1 = hass.states.get("switch.client_1") - assert device_1 is not None + poe_client = hass.states.get("switch.poe_client") + assert poe_client.state == "unavailable" # self.poe_mode is None From a12b98e30e6f6021bedfb0b674f6fc137d7e6cca Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Fri, 5 Mar 2021 17:01:54 -0500 Subject: [PATCH 191/831] Update ZHA dependencies (#47479) --- 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 7d367c3dc00..df3c0fcfda9 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -10,7 +10,7 @@ "zha-quirks==0.0.54", "zigpy-cc==0.5.2", "zigpy-deconz==0.11.1", - "zigpy==0.32.0", + "zigpy==0.33.0", "zigpy-xbee==0.13.0", "zigpy-zigate==0.7.3", "zigpy-znp==0.4.0" diff --git a/requirements_all.txt b/requirements_all.txt index 5253f58a9ee..b7d76130393 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2388,7 +2388,7 @@ zigpy-zigate==0.7.3 zigpy-znp==0.4.0 # homeassistant.components.zha -zigpy==0.32.0 +zigpy==0.33.0 # homeassistant.components.zoneminder zm-py==0.5.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 45c06066e20..e8242230052 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1228,7 +1228,7 @@ zigpy-zigate==0.7.3 zigpy-znp==0.4.0 # homeassistant.components.zha -zigpy==0.32.0 +zigpy==0.33.0 # homeassistant.components.zwave_js zwave-js-server-python==0.21.1 From 292f4262aab5bfcca0859b5d3ecbfa4b9dfce59a Mon Sep 17 00:00:00 2001 From: ollo69 <60491700+ollo69@users.noreply.github.com> Date: Fri, 5 Mar 2021 23:40:04 +0100 Subject: [PATCH 192/831] Move AsusWrt sensors update logic in router module (#46606) --- homeassistant/components/asuswrt/__init__.py | 2 +- .../components/asuswrt/config_flow.py | 3 +- homeassistant/components/asuswrt/const.py | 8 +- homeassistant/components/asuswrt/router.py | 151 ++++++++ homeassistant/components/asuswrt/sensor.py | 339 +++++++----------- tests/components/asuswrt/test_sensor.py | 95 ++--- 6 files changed, 346 insertions(+), 252 deletions(-) diff --git a/homeassistant/components/asuswrt/__init__.py b/homeassistant/components/asuswrt/__init__.py index 28e8fe76684..25a78f6a523 100644 --- a/homeassistant/components/asuswrt/__init__.py +++ b/homeassistant/components/asuswrt/__init__.py @@ -31,7 +31,6 @@ from .const import ( MODE_ROUTER, PROTOCOL_SSH, PROTOCOL_TELNET, - SENSOR_TYPES, ) from .router import AsusWrtRouter @@ -39,6 +38,7 @@ PLATFORMS = ["device_tracker", "sensor"] CONF_PUB_KEY = "pub_key" SECRET_GROUP = "Password or SSH Key" +SENSOR_TYPES = ["devices", "upload_speed", "download_speed", "download", "upload"] CONFIG_SCHEMA = vol.Schema( vol.All( diff --git a/homeassistant/components/asuswrt/config_flow.py b/homeassistant/components/asuswrt/config_flow.py index 303b3cc3822..b312d9b8186 100644 --- a/homeassistant/components/asuswrt/config_flow.py +++ b/homeassistant/components/asuswrt/config_flow.py @@ -21,7 +21,6 @@ from homeassistant.const import ( from homeassistant.core import callback from homeassistant.helpers import config_validation as cv -# pylint:disable=unused-import from .const import ( CONF_DNSMASQ, CONF_INTERFACE, @@ -32,12 +31,12 @@ from .const import ( DEFAULT_INTERFACE, DEFAULT_SSH_PORT, DEFAULT_TRACK_UNKNOWN, - DOMAIN, MODE_AP, MODE_ROUTER, PROTOCOL_SSH, PROTOCOL_TELNET, ) +from .const import DOMAIN # pylint:disable=unused-import from .router import get_api RESULT_CONN_ERROR = "cannot_connect" diff --git a/homeassistant/components/asuswrt/const.py b/homeassistant/components/asuswrt/const.py index 40752e81a08..a8977a77ea8 100644 --- a/homeassistant/components/asuswrt/const.py +++ b/homeassistant/components/asuswrt/const.py @@ -20,5 +20,9 @@ MODE_ROUTER = "router" PROTOCOL_SSH = "ssh" PROTOCOL_TELNET = "telnet" -# Sensor -SENSOR_TYPES = ["devices", "upload_speed", "download_speed", "download", "upload"] +# Sensors +SENSOR_CONNECTED_DEVICE = "sensor_connected_device" +SENSOR_RX_BYTES = "sensor_rx_bytes" +SENSOR_TX_BYTES = "sensor_tx_bytes" +SENSOR_RX_RATES = "sensor_rx_rates" +SENSOR_TX_RATES = "sensor_tx_rates" diff --git a/homeassistant/components/asuswrt/router.py b/homeassistant/components/asuswrt/router.py index 11545919b43..c94135423d8 100644 --- a/homeassistant/components/asuswrt/router.py +++ b/homeassistant/components/asuswrt/router.py @@ -24,6 +24,7 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.util import dt as dt_util from .const import ( @@ -37,14 +38,97 @@ from .const import ( DEFAULT_TRACK_UNKNOWN, DOMAIN, PROTOCOL_TELNET, + SENSOR_CONNECTED_DEVICE, + SENSOR_RX_BYTES, + SENSOR_RX_RATES, + SENSOR_TX_BYTES, + SENSOR_TX_RATES, ) CONF_REQ_RELOAD = [CONF_DNSMASQ, CONF_INTERFACE, CONF_REQUIRE_IP] + +KEY_COORDINATOR = "coordinator" +KEY_SENSORS = "sensors" + SCAN_INTERVAL = timedelta(seconds=30) +SENSORS_TYPE_BYTES = "sensors_bytes" +SENSORS_TYPE_COUNT = "sensors_count" +SENSORS_TYPE_RATES = "sensors_rates" + _LOGGER = logging.getLogger(__name__) +class AsusWrtSensorDataHandler: + """Data handler for AsusWrt sensor.""" + + def __init__(self, hass, api): + """Initialize a AsusWrt sensor data handler.""" + self._hass = hass + self._api = api + self._connected_devices = 0 + + async def _get_connected_devices(self): + """Return number of connected devices.""" + return {SENSOR_CONNECTED_DEVICE: self._connected_devices} + + async def _get_bytes(self): + """Fetch byte information from the router.""" + ret_dict: Dict[str, Any] = {} + try: + datas = await self._api.async_get_bytes_total() + except OSError as exc: + raise UpdateFailed from exc + + ret_dict[SENSOR_RX_BYTES] = datas[0] + ret_dict[SENSOR_TX_BYTES] = datas[1] + + return ret_dict + + async def _get_rates(self): + """Fetch rates information from the router.""" + ret_dict: Dict[str, Any] = {} + try: + rates = await self._api.async_get_current_transfer_rates() + except OSError as exc: + raise UpdateFailed from exc + + ret_dict[SENSOR_RX_RATES] = rates[0] + ret_dict[SENSOR_TX_RATES] = rates[1] + + return ret_dict + + def update_device_count(self, conn_devices: int): + """Update connected devices attribute.""" + if self._connected_devices == conn_devices: + return False + self._connected_devices = conn_devices + return True + + async def get_coordinator(self, sensor_type: str, should_poll=True): + """Get the coordinator for a specific sensor type.""" + if sensor_type == SENSORS_TYPE_COUNT: + method = self._get_connected_devices + elif sensor_type == SENSORS_TYPE_BYTES: + method = self._get_bytes + elif sensor_type == SENSORS_TYPE_RATES: + method = self._get_rates + else: + raise RuntimeError(f"Invalid sensor type: {sensor_type}") + + coordinator = DataUpdateCoordinator( + self._hass, + _LOGGER, + name=sensor_type, + update_method=method, + # Polling interval. Will only be polled if there are subscribers. + update_interval=SCAN_INTERVAL if should_poll else None, + ) + await coordinator.async_refresh() + + return coordinator + + class AsusWrtDevInfo: """Representation of a AsusWrt device info.""" @@ -111,8 +195,12 @@ class AsusWrtRouter: self._host = entry.data[CONF_HOST] self._devices: Dict[str, Any] = {} + self._connected_devices = 0 self._connect_error = False + self._sensors_data_handler: AsusWrtSensorDataHandler = None + self._sensors_coordinator: Dict[str, Any] = {} + self._on_close = [] self._options = { @@ -150,6 +238,9 @@ class AsusWrtRouter: # Update devices await self.update_devices() + # Init Sensors + await self.init_sensors_coordinator() + self.async_on_close( async_track_time_interval(self.hass, self.update_all, SCAN_INTERVAL) ) @@ -201,6 +292,51 @@ class AsusWrtRouter: if new_device: async_dispatcher_send(self.hass, self.signal_device_new) + self._connected_devices = len(wrt_devices) + await self._update_unpolled_sensors() + + async def init_sensors_coordinator(self) -> None: + """Init AsusWrt sensors coordinators.""" + if self._sensors_data_handler: + return + + self._sensors_data_handler = AsusWrtSensorDataHandler(self.hass, self._api) + self._sensors_data_handler.update_device_count(self._connected_devices) + + conn_dev_coordinator = await self._sensors_data_handler.get_coordinator( + SENSORS_TYPE_COUNT, False + ) + self._sensors_coordinator[SENSORS_TYPE_COUNT] = { + KEY_COORDINATOR: conn_dev_coordinator, + KEY_SENSORS: [SENSOR_CONNECTED_DEVICE], + } + + bytes_coordinator = await self._sensors_data_handler.get_coordinator( + SENSORS_TYPE_BYTES + ) + self._sensors_coordinator[SENSORS_TYPE_BYTES] = { + KEY_COORDINATOR: bytes_coordinator, + KEY_SENSORS: [SENSOR_RX_BYTES, SENSOR_TX_BYTES], + } + + rates_coordinator = await self._sensors_data_handler.get_coordinator( + SENSORS_TYPE_RATES + ) + self._sensors_coordinator[SENSORS_TYPE_RATES] = { + KEY_COORDINATOR: rates_coordinator, + KEY_SENSORS: [SENSOR_RX_RATES, SENSOR_TX_RATES], + } + + async def _update_unpolled_sensors(self) -> None: + """Request refresh for AsusWrt unpolled sensors.""" + if not self._sensors_data_handler: + return + + if SENSORS_TYPE_COUNT in self._sensors_coordinator: + coordinator = self._sensors_coordinator[SENSORS_TYPE_COUNT][KEY_COORDINATOR] + if self._sensors_data_handler.update_device_count(self._connected_devices): + await coordinator.async_refresh() + async def close(self) -> None: """Close the connection.""" if self._api is not None: @@ -230,6 +366,16 @@ class AsusWrtRouter: self._options.update(new_options) return req_reload + @property + def device_info(self) -> Dict[str, Any]: + """Return the device information.""" + return { + "identifiers": {(DOMAIN, "AsusWRT")}, + "name": self._host, + "model": "Asus Router", + "manufacturer": "Asus", + } + @property def signal_device_new(self) -> str: """Event specific per AsusWrt entry to signal new device.""" @@ -250,6 +396,11 @@ class AsusWrtRouter: """Return devices.""" return self._devices + @property + def sensors_coordinator(self) -> Dict[str, Any]: + """Return sensors coordinators.""" + return self._sensors_coordinator + @property def api(self) -> AsusWrt: """Return router API.""" diff --git a/homeassistant/components/asuswrt/sensor.py b/homeassistant/components/asuswrt/sensor.py index 2a39d339f06..2751e161ea9 100644 --- a/homeassistant/components/asuswrt/sensor.py +++ b/homeassistant/components/asuswrt/sensor.py @@ -1,236 +1,169 @@ """Asuswrt status sensors.""" -from datetime import timedelta -import enum import logging -from typing import Any, Dict, List, Optional - -from aioasuswrt.asuswrt import AsusWrt +from numbers import Number +from typing import Dict from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_NAME, DATA_GIGABYTES, DATA_RATE_MEGABITS_PER_SECOND +from homeassistant.const import DATA_GIGABYTES, DATA_RATE_MEGABITS_PER_SECOND from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, ) -from .const import DATA_ASUSWRT, DOMAIN, SENSOR_TYPES +from .const import ( + DATA_ASUSWRT, + DOMAIN, + SENSOR_CONNECTED_DEVICE, + SENSOR_RX_BYTES, + SENSOR_RX_RATES, + SENSOR_TX_BYTES, + SENSOR_TX_RATES, +) +from .router import KEY_COORDINATOR, KEY_SENSORS, AsusWrtRouter -UPLOAD_ICON = "mdi:upload-network" -DOWNLOAD_ICON = "mdi:download-network" +DEFAULT_PREFIX = "Asuswrt" + +SENSOR_DEVICE_CLASS = "device_class" +SENSOR_ICON = "icon" +SENSOR_NAME = "name" +SENSOR_UNIT = "unit" +SENSOR_FACTOR = "factor" +SENSOR_DEFAULT_ENABLED = "default_enabled" + +UNIT_DEVICES = "Devices" + +CONNECTION_SENSORS = { + SENSOR_CONNECTED_DEVICE: { + SENSOR_NAME: "Devices Connected", + SENSOR_UNIT: UNIT_DEVICES, + SENSOR_FACTOR: 0, + SENSOR_ICON: "mdi:router-network", + SENSOR_DEVICE_CLASS: None, + SENSOR_DEFAULT_ENABLED: True, + }, + SENSOR_RX_RATES: { + SENSOR_NAME: "Download Speed", + SENSOR_UNIT: DATA_RATE_MEGABITS_PER_SECOND, + SENSOR_FACTOR: 125000, + SENSOR_ICON: "mdi:download-network", + SENSOR_DEVICE_CLASS: None, + }, + SENSOR_TX_RATES: { + SENSOR_NAME: "Upload Speed", + SENSOR_UNIT: DATA_RATE_MEGABITS_PER_SECOND, + SENSOR_FACTOR: 125000, + SENSOR_ICON: "mdi:upload-network", + SENSOR_DEVICE_CLASS: None, + }, + SENSOR_RX_BYTES: { + SENSOR_NAME: "Download", + SENSOR_UNIT: DATA_GIGABYTES, + SENSOR_FACTOR: 1000000000, + SENSOR_ICON: "mdi:download", + SENSOR_DEVICE_CLASS: None, + }, + SENSOR_TX_BYTES: { + SENSOR_NAME: "Upload", + SENSOR_UNIT: DATA_GIGABYTES, + SENSOR_FACTOR: 1000000000, + SENSOR_ICON: "mdi:upload", + SENSOR_DEVICE_CLASS: None, + }, +} _LOGGER = logging.getLogger(__name__) -@enum.unique -class _SensorTypes(enum.Enum): - DEVICES = "devices" - UPLOAD = "upload" - DOWNLOAD = "download" - DOWNLOAD_SPEED = "download_speed" - UPLOAD_SPEED = "upload_speed" - - @property - def unit_of_measurement(self) -> Optional[str]: - """Return a string with the unit of the sensortype.""" - if self in (_SensorTypes.UPLOAD, _SensorTypes.DOWNLOAD): - return DATA_GIGABYTES - if self in (_SensorTypes.UPLOAD_SPEED, _SensorTypes.DOWNLOAD_SPEED): - return DATA_RATE_MEGABITS_PER_SECOND - if self == _SensorTypes.DEVICES: - return "devices" - return None - - @property - def icon(self) -> Optional[str]: - """Return the expected icon for the sensortype.""" - if self in (_SensorTypes.UPLOAD, _SensorTypes.UPLOAD_SPEED): - return UPLOAD_ICON - if self in (_SensorTypes.DOWNLOAD, _SensorTypes.DOWNLOAD_SPEED): - return DOWNLOAD_ICON - return None - - @property - def sensor_name(self) -> Optional[str]: - """Return the name of the sensor.""" - if self is _SensorTypes.DEVICES: - return "Asuswrt Devices Connected" - if self is _SensorTypes.UPLOAD: - return "Asuswrt Upload" - if self is _SensorTypes.DOWNLOAD: - return "Asuswrt Download" - if self is _SensorTypes.UPLOAD_SPEED: - return "Asuswrt Upload Speed" - if self is _SensorTypes.DOWNLOAD_SPEED: - return "Asuswrt Download Speed" - return None - - @property - def is_speed(self) -> bool: - """Return True if the type is an upload/download speed.""" - return self in (_SensorTypes.UPLOAD_SPEED, _SensorTypes.DOWNLOAD_SPEED) - - @property - def is_size(self) -> bool: - """Return True if the type is the total upload/download size.""" - return self in (_SensorTypes.UPLOAD, _SensorTypes.DOWNLOAD) - - -class _SensorInfo: - """Class handling sensor information.""" - - def __init__(self, sensor_type: _SensorTypes): - """Initialize the handler class.""" - self.type = sensor_type - self.enabled = False - - async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, async_add_entities ) -> None: - """Set up the asuswrt sensors.""" + """Set up the sensors.""" + router: AsusWrtRouter = hass.data[DOMAIN][entry.entry_id][DATA_ASUSWRT] + entities = [] - router = hass.data[DOMAIN][entry.entry_id][DATA_ASUSWRT] - api: AsusWrt = router.api - device_name = entry.data.get(CONF_NAME, "AsusWRT") + for sensor_data in router.sensors_coordinator.values(): + coordinator = sensor_data[KEY_COORDINATOR] + sensors = sensor_data[KEY_SENSORS] + for sensor_key in sensors: + if sensor_key in CONNECTION_SENSORS: + entities.append( + AsusWrtSensor( + coordinator, router, sensor_key, CONNECTION_SENSORS[sensor_key] + ) + ) - # Let's discover the valid sensor types. - sensors = [_SensorInfo(_SensorTypes(x)) for x in SENSOR_TYPES] - - data_handler = AsuswrtDataHandler(sensors, api) - coordinator = DataUpdateCoordinator( - hass, - _LOGGER, - name="sensor", - update_method=data_handler.update_data, - # Polling interval. Will only be polled if there are subscribers. - update_interval=timedelta(seconds=30), - ) - - await coordinator.async_refresh() - async_add_entities( - [AsuswrtSensor(coordinator, data_handler, device_name, x.type) for x in sensors] - ) + async_add_entities(entities, True) -class AsuswrtDataHandler: - """Class handling the API updates.""" - - def __init__(self, sensors: List[_SensorInfo], api: AsusWrt): - """Initialize the handler class.""" - self._api = api - self._sensors = sensors - self._connected = True - - def enable_sensor(self, sensor_type: _SensorTypes): - """Enable a specific sensor type.""" - for index, sensor in enumerate(self._sensors): - if sensor.type == sensor_type: - self._sensors[index].enabled = True - return - - def disable_sensor(self, sensor_type: _SensorTypes): - """Disable a specific sensor type.""" - for index, sensor in enumerate(self._sensors): - if sensor.type == sensor_type: - self._sensors[index].enabled = False - return - - async def update_data(self) -> Dict[_SensorTypes, Any]: - """Fetch the relevant data from the router.""" - ret_dict: Dict[_SensorTypes, Any] = {} - try: - if _SensorTypes.DEVICES in [x.type for x in self._sensors if x.enabled]: - # Let's check the nr of devices. - devices = await self._api.async_get_connected_devices() - ret_dict[_SensorTypes.DEVICES] = len(devices) - - if any(x.type.is_speed for x in self._sensors if x.enabled): - # Let's check the upload and download speed - speed = await self._api.async_get_current_transfer_rates() - ret_dict[_SensorTypes.DOWNLOAD_SPEED] = round(speed[0] / 125000, 2) - ret_dict[_SensorTypes.UPLOAD_SPEED] = round(speed[1] / 125000, 2) - - if any(x.type.is_size for x in self._sensors if x.enabled): - rates = await self._api.async_get_bytes_total() - ret_dict[_SensorTypes.DOWNLOAD] = round(rates[0] / 1000000000, 1) - ret_dict[_SensorTypes.UPLOAD] = round(rates[1] / 1000000000, 1) - - if not self._connected: - # Log a successful reconnect - self._connected = True - _LOGGER.warning("Successfully reconnected to ASUS router") - - except OSError as err: - if self._connected: - # Log the first time connection was lost - _LOGGER.warning("Lost connection to router error due to: '%s'", err) - self._connected = False - - return ret_dict - - -class AsuswrtSensor(CoordinatorEntity): - """The asuswrt specific sensor class.""" +class AsusWrtSensor(CoordinatorEntity): + """Representation of a AsusWrt sensor.""" def __init__( self, coordinator: DataUpdateCoordinator, - data_handler: AsuswrtDataHandler, - device_name: str, - sensor_type: _SensorTypes, - ): - """Initialize the sensor class.""" + router: AsusWrtRouter, + sensor_type: str, + sensor: Dict[str, any], + ) -> None: + """Initialize a AsusWrt sensor.""" super().__init__(coordinator) - self._handler = data_handler - self._device_name = device_name - self._type = sensor_type - - @property - def state(self): - """Return the state of the sensor.""" - return self.coordinator.data.get(self._type) - - @property - def name(self) -> str: - """Return the name of the sensor.""" - return self._type.sensor_name - - @property - def icon(self) -> Optional[str]: - """Return the icon to use in the frontend.""" - return self._type.icon - - @property - def unit_of_measurement(self) -> Optional[str]: - """Return the unit.""" - return self._type.unit_of_measurement - - @property - def unique_id(self) -> str: - """Return the unique_id of the sensor.""" - return f"{DOMAIN} {self._type.sensor_name}" - - @property - def device_info(self) -> Dict[str, any]: - """Return the device information.""" - return { - "identifiers": {(DOMAIN, "AsusWRT")}, - "name": self._device_name, - "model": "Asus Router", - "manufacturer": "Asus", - } + self._router = router + self._sensor_type = sensor_type + self._name = f"{DEFAULT_PREFIX} {sensor[SENSOR_NAME]}" + self._unique_id = f"{DOMAIN} {self._name}" + self._unit = sensor[SENSOR_UNIT] + self._factor = sensor[SENSOR_FACTOR] + self._icon = sensor[SENSOR_ICON] + self._device_class = sensor[SENSOR_DEVICE_CLASS] + self._default_enabled = sensor.get(SENSOR_DEFAULT_ENABLED, False) @property def entity_registry_enabled_default(self) -> bool: """Return if the entity should be enabled when first added to the entity registry.""" - return False + return self._default_enabled - async def async_added_to_hass(self) -> None: - """When entity is added to hass.""" - self._handler.enable_sensor(self._type) - await super().async_added_to_hass() + @property + def state(self) -> str: + """Return current state.""" + state = self.coordinator.data.get(self._sensor_type) + if state is None: + return None + if self._factor and isinstance(state, Number): + return round(state / self._factor, 2) + return state - async def async_will_remove_from_hass(self): - """Call when entity is removed from hass.""" - self._handler.disable_sensor(self._type) + @property + def unique_id(self) -> str: + """Return a unique ID.""" + return self._unique_id + + @property + def name(self) -> str: + """Return the name.""" + return self._name + + @property + def unit_of_measurement(self) -> str: + """Return the unit.""" + return self._unit + + @property + def icon(self) -> str: + """Return the icon.""" + return self._icon + + @property + def device_class(self) -> str: + """Return the device_class.""" + return self._device_class + + @property + def device_state_attributes(self) -> Dict[str, any]: + """Return the attributes.""" + return {"hostname": self._router.host} + + @property + def device_info(self) -> Dict[str, any]: + """Return the device information.""" + return self._router.device_info diff --git a/tests/components/asuswrt/test_sensor.py b/tests/components/asuswrt/test_sensor.py index 994111370fd..334f06d85a6 100644 --- a/tests/components/asuswrt/test_sensor.py +++ b/tests/components/asuswrt/test_sensor.py @@ -7,7 +7,7 @@ import pytest from homeassistant.components import device_tracker, sensor from homeassistant.components.asuswrt.const import DOMAIN -from homeassistant.components.asuswrt.sensor import _SensorTypes +from homeassistant.components.asuswrt.sensor import DEFAULT_PREFIX from homeassistant.components.device_tracker.const import CONF_CONSIDER_HOME from homeassistant.const import ( CONF_HOST, @@ -66,49 +66,56 @@ async def test_sensors(hass, connect): """Test creating an AsusWRT sensor.""" entity_reg = await hass.helpers.entity_registry.async_get_registry() - # Pre-enable the status sensor - entity_reg.async_get_or_create( - sensor.DOMAIN, - DOMAIN, - f"{DOMAIN} {_SensorTypes(_SensorTypes.DEVICES).sensor_name}", - suggested_object_id="asuswrt_connected_devices", - disabled_by=None, - ) - entity_reg.async_get_or_create( - sensor.DOMAIN, - DOMAIN, - f"{DOMAIN} {_SensorTypes(_SensorTypes.DOWNLOAD_SPEED).sensor_name}", - suggested_object_id="asuswrt_download_speed", - disabled_by=None, - ) - entity_reg.async_get_or_create( - sensor.DOMAIN, - DOMAIN, - f"{DOMAIN} {_SensorTypes(_SensorTypes.DOWNLOAD).sensor_name}", - suggested_object_id="asuswrt_download", - disabled_by=None, - ) - entity_reg.async_get_or_create( - sensor.DOMAIN, - DOMAIN, - f"{DOMAIN} {_SensorTypes(_SensorTypes.UPLOAD_SPEED).sensor_name}", - suggested_object_id="asuswrt_upload_speed", - disabled_by=None, - ) - entity_reg.async_get_or_create( - sensor.DOMAIN, - DOMAIN, - f"{DOMAIN} {_SensorTypes(_SensorTypes.UPLOAD).sensor_name}", - suggested_object_id="asuswrt_upload", - disabled_by=None, - ) - # init config entry config_entry = MockConfigEntry( domain=DOMAIN, data=CONFIG_DATA, options={CONF_CONSIDER_HOME: 60}, ) + + # init variable + unique_id = DOMAIN + name_prefix = DEFAULT_PREFIX + obj_prefix = name_prefix.lower() + sensor_prefix = f"{sensor.DOMAIN}.{obj_prefix}" + + # Pre-enable the status sensor + entity_reg.async_get_or_create( + sensor.DOMAIN, + DOMAIN, + f"{unique_id} {name_prefix} Devices Connected", + suggested_object_id=f"{obj_prefix}_devices_connected", + disabled_by=None, + ) + entity_reg.async_get_or_create( + sensor.DOMAIN, + DOMAIN, + f"{unique_id} {name_prefix} Download Speed", + suggested_object_id=f"{obj_prefix}_download_speed", + disabled_by=None, + ) + entity_reg.async_get_or_create( + sensor.DOMAIN, + DOMAIN, + f"{unique_id} {name_prefix} Download", + suggested_object_id=f"{obj_prefix}_download", + disabled_by=None, + ) + entity_reg.async_get_or_create( + sensor.DOMAIN, + DOMAIN, + f"{unique_id} {name_prefix} Upload Speed", + suggested_object_id=f"{obj_prefix}_upload_speed", + disabled_by=None, + ) + entity_reg.async_get_or_create( + sensor.DOMAIN, + DOMAIN, + f"{unique_id} {name_prefix} Upload", + suggested_object_id=f"{obj_prefix}_upload", + disabled_by=None, + ) + config_entry.add_to_hass(hass) # initial devices setup @@ -119,11 +126,11 @@ async def test_sensors(hass, connect): assert hass.states.get(f"{device_tracker.DOMAIN}.test").state == STATE_HOME assert hass.states.get(f"{device_tracker.DOMAIN}.testtwo").state == STATE_HOME - assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_connected_devices").state == "2" - assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_download_speed").state == "160.0" - assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_download").state == "60.0" - assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_upload_speed").state == "80.0" - assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_upload").state == "50.0" + assert hass.states.get(f"{sensor_prefix}_download_speed").state == "160.0" + assert hass.states.get(f"{sensor_prefix}_download").state == "60.0" + assert hass.states.get(f"{sensor_prefix}_upload_speed").state == "80.0" + assert hass.states.get(f"{sensor_prefix}_upload").state == "50.0" + assert hass.states.get(f"{sensor_prefix}_devices_connected").state == "2" # add one device and remove another MOCK_DEVICES.pop("a1:b1:c1:d1:e1:f1") @@ -137,7 +144,7 @@ async def test_sensors(hass, connect): assert hass.states.get(f"{device_tracker.DOMAIN}.test").state == STATE_HOME assert hass.states.get(f"{device_tracker.DOMAIN}.testtwo").state == STATE_HOME assert hass.states.get(f"{device_tracker.DOMAIN}.testthree").state == STATE_HOME - assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_connected_devices").state == "2" + assert hass.states.get(f"{sensor_prefix}_devices_connected").state == "2" hass.config_entries.async_update_entry( config_entry, options={CONF_CONSIDER_HOME: 0} From 2472dad1faaf7680fe4c9debb2a6fa17e38da732 Mon Sep 17 00:00:00 2001 From: Phil Bruckner Date: Fri, 5 Mar 2021 17:05:36 -0600 Subject: [PATCH 193/831] Bump amcrest package version to 1.7.1 (#47483) --- homeassistant/components/amcrest/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/amcrest/manifest.json b/homeassistant/components/amcrest/manifest.json index 0b6fbbdc09a..0b7a59edb79 100644 --- a/homeassistant/components/amcrest/manifest.json +++ b/homeassistant/components/amcrest/manifest.json @@ -2,7 +2,7 @@ "domain": "amcrest", "name": "Amcrest", "documentation": "https://www.home-assistant.io/integrations/amcrest", - "requirements": ["amcrest==1.7.0"], + "requirements": ["amcrest==1.7.1"], "dependencies": ["ffmpeg"], "codeowners": ["@pnbruckner"] } diff --git a/requirements_all.txt b/requirements_all.txt index b7d76130393..446b30b223b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -245,7 +245,7 @@ alpha_vantage==2.3.1 ambiclimate==0.2.1 # homeassistant.components.amcrest -amcrest==1.7.0 +amcrest==1.7.1 # homeassistant.components.androidtv androidtv[async]==0.0.57 From 8f31b09b55f4c87d6a274ce7340f26c944934f99 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 6 Mar 2021 00:33:26 +0100 Subject: [PATCH 194/831] Complete typing on TwenteMilieu integration (#47480) --- .../components/twentemilieu/__init__.py | 23 ++++++++-------- .../components/twentemilieu/config_flow.py | 26 +++++++++++-------- .../components/twentemilieu/sensor.py | 16 +++++++----- .../twentemilieu/test_config_flow.py | 20 ++++++++++---- 4 files changed, 51 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/twentemilieu/__init__.py b/homeassistant/components/twentemilieu/__init__.py index 06c2cb27f35..f53e4463146 100644 --- a/homeassistant/components/twentemilieu/__init__.py +++ b/homeassistant/components/twentemilieu/__init__.py @@ -1,7 +1,8 @@ """Support for Twente Milieu.""" +from __future__ import annotations + import asyncio from datetime import timedelta -from typing import Optional from twentemilieu import TwenteMilieu import voluptuous as vol @@ -15,11 +16,12 @@ from homeassistant.components.twentemilieu.const import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ID +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_track_time_interval -from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.helpers.typing import ConfigType SCAN_INTERVAL = timedelta(seconds=3600) @@ -27,9 +29,7 @@ SERVICE_UPDATE = "update" SERVICE_SCHEMA = vol.Schema({vol.Optional(CONF_ID): cv.string}) -async def _update_twentemilieu( - hass: HomeAssistantType, unique_id: Optional[str] -) -> None: +async def _update_twentemilieu(hass: HomeAssistant, unique_id: str | None) -> None: """Update Twente Milieu.""" if unique_id is not None: twentemilieu = hass.data[DOMAIN].get(unique_id) @@ -37,16 +37,15 @@ async def _update_twentemilieu( await twentemilieu.update() async_dispatcher_send(hass, DATA_UPDATE, unique_id) else: - tasks = [] - for twentemilieu in hass.data[DOMAIN].values(): - tasks.append(twentemilieu.update()) - await asyncio.wait(tasks) + await asyncio.wait( + [twentemilieu.update() for twentemilieu in hass.data[DOMAIN].values()] + ) for uid in hass.data[DOMAIN]: async_dispatcher_send(hass, DATA_UPDATE, uid) -async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Twente Milieu components.""" async def update(call) -> None: @@ -59,7 +58,7 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: return True -async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Twente Milieu from a config entry.""" session = async_get_clientsession(hass) twentemilieu = TwenteMilieu( @@ -85,7 +84,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool return True -async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload Twente Milieu config entry.""" await hass.config_entries.async_forward_entry_unload(entry, "sensor") diff --git a/homeassistant/components/twentemilieu/config_flow.py b/homeassistant/components/twentemilieu/config_flow.py index 76c9f33b3e9..769d24e334a 100644 --- a/homeassistant/components/twentemilieu/config_flow.py +++ b/homeassistant/components/twentemilieu/config_flow.py @@ -1,4 +1,8 @@ """Config flow to configure the Twente Milieu integration.""" +from __future__ import annotations + +from typing import Any + from twentemilieu import ( TwenteMilieu, TwenteMilieuAddressError, @@ -7,25 +11,23 @@ from twentemilieu import ( import voluptuous as vol from homeassistant import config_entries -from homeassistant.components.twentemilieu.const import ( - CONF_HOUSE_LETTER, - CONF_HOUSE_NUMBER, - CONF_POST_CODE, - DOMAIN, -) from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_ID from homeassistant.helpers.aiohttp_client import async_get_clientsession +from .const import CONF_HOUSE_LETTER, CONF_HOUSE_NUMBER, CONF_POST_CODE +from .const import DOMAIN # pylint: disable=unused-import -@config_entries.HANDLERS.register(DOMAIN) -class TwenteMilieuFlowHandler(ConfigFlow): + +class TwenteMilieuFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Twente Milieu config flow.""" VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL - async def _show_setup_form(self, errors=None): + async def _show_setup_form( + self, errors: dict[str, str] | None = None + ) -> dict[str, Any]: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -39,7 +41,9 @@ class TwenteMilieuFlowHandler(ConfigFlow): errors=errors or {}, ) - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" if user_input is None: return await self._show_setup_form(user_input) @@ -70,7 +74,7 @@ class TwenteMilieuFlowHandler(ConfigFlow): return self.async_abort(reason="already_configured") return self.async_create_entry( - title=unique_id, + title=str(unique_id), data={ CONF_ID: unique_id, CONF_POST_CODE: user_input[CONF_POST_CODE], diff --git a/homeassistant/components/twentemilieu/sensor.py b/homeassistant/components/twentemilieu/sensor.py index 92194e12172..34da746cf06 100644 --- a/homeassistant/components/twentemilieu/sensor.py +++ b/homeassistant/components/twentemilieu/sensor.py @@ -1,5 +1,7 @@ """Support for Twente Milieu sensors.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any, Callable from twentemilieu import ( WASTE_TYPE_NON_RECYCLABLE, @@ -10,20 +12,22 @@ from twentemilieu import ( TwenteMilieuConnectionError, ) -from homeassistant.components.twentemilieu.const import DATA_UPDATE, DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ID -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity -from homeassistant.helpers.typing import HomeAssistantType + +from .const import DATA_UPDATE, DOMAIN PARALLEL_UPDATES = 1 async def async_setup_entry( - hass: HomeAssistantType, entry: ConfigEntry, async_add_entities + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Twente Milieu sensor based on a config entry.""" twentemilieu = hass.data[DOMAIN][entry.data[CONF_ID]] @@ -142,7 +146,7 @@ class TwenteMilieuSensor(Entity): self._state = next_pickup.date().isoformat() @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about Twente Milieu.""" return { "identifiers": {(DOMAIN, self._unique_id)}, diff --git a/tests/components/twentemilieu/test_config_flow.py b/tests/components/twentemilieu/test_config_flow.py index ec3edf73f29..27fd86d0868 100644 --- a/tests/components/twentemilieu/test_config_flow.py +++ b/tests/components/twentemilieu/test_config_flow.py @@ -10,8 +10,10 @@ from homeassistant.components.twentemilieu.const import ( DOMAIN, ) from homeassistant.const import CONF_ID, CONTENT_TYPE_JSON +from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry +from tests.test_util.aiohttp import AiohttpClientMocker FIXTURE_USER_INPUT = { CONF_ID: "12345", @@ -21,7 +23,7 @@ FIXTURE_USER_INPUT = { } -async def test_show_set_form(hass): +async def test_show_set_form(hass: HomeAssistant) -> None: """Test that the setup form is served.""" flow = config_flow.TwenteMilieuFlowHandler() flow.hass = hass @@ -31,7 +33,9 @@ async def test_show_set_form(hass): assert result["step_id"] == "user" -async def test_connection_error(hass, aioclient_mock): +async def test_connection_error( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test we show user form on Twente Milieu connection error.""" aioclient_mock.post( "https://twentemilieuapi.ximmio.com/api/FetchAdress", exc=aiohttp.ClientError @@ -46,7 +50,9 @@ async def test_connection_error(hass, aioclient_mock): assert result["errors"] == {"base": "cannot_connect"} -async def test_invalid_address(hass, aioclient_mock): +async def test_invalid_address( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test we show user form on Twente Milieu invalid address error.""" aioclient_mock.post( "https://twentemilieuapi.ximmio.com/api/FetchAdress", @@ -63,7 +69,9 @@ async def test_invalid_address(hass, aioclient_mock): assert result["errors"] == {"base": "invalid_address"} -async def test_address_already_set_up(hass, aioclient_mock): +async def test_address_already_set_up( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test we abort if address has already been set up.""" MockConfigEntry(domain=DOMAIN, data=FIXTURE_USER_INPUT, title="12345").add_to_hass( hass @@ -83,7 +91,9 @@ async def test_address_already_set_up(hass, aioclient_mock): assert result["reason"] == "already_configured" -async def test_full_flow_implementation(hass, aioclient_mock): +async def test_full_flow_implementation( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test registering an integration and finishing flow works.""" aioclient_mock.post( "https://twentemilieuapi.ximmio.com/api/FetchAdress", From 4c181bbfe5f4f62dbd467c589c010ebecd844bd4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 5 Mar 2021 15:34:18 -0800 Subject: [PATCH 195/831] Raise error instead of crashing when template passed to call service target (#47467) --- .../components/websocket_api/commands.py | 16 +++++--- .../components/websocket_api/test_commands.py | 40 +++++++++++-------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index ddd7548cd68..53531cf9ba9 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -13,10 +13,9 @@ from homeassistant.exceptions import ( TemplateError, Unauthorized, ) -from homeassistant.helpers import config_validation as cv, entity +from homeassistant.helpers import config_validation as cv, entity, template from homeassistant.helpers.event import TrackTemplate, async_track_template_result from homeassistant.helpers.service import async_get_all_descriptions -from homeassistant.helpers.template import Template from homeassistant.loader import IntegrationNotFound, async_get_integration from . import const, decorators, messages @@ -132,6 +131,11 @@ async def handle_call_service(hass, connection, msg): if msg["domain"] == HASS_DOMAIN and msg["service"] in ["restart", "stop"]: blocking = False + # We do not support templates. + target = msg.get("target") + if template.is_complex(target): + raise vol.Invalid("Templates are not supported here") + try: context = connection.context(msg) await hass.services.async_call( @@ -140,7 +144,7 @@ async def handle_call_service(hass, connection, msg): msg.get("service_data"), blocking, context, - target=msg.get("target"), + target=target, ) connection.send_message( messages.result_message(msg["id"], {"context": context}) @@ -256,14 +260,14 @@ def handle_ping(hass, connection, msg): async def handle_render_template(hass, connection, msg): """Handle render_template command.""" template_str = msg["template"] - template = Template(template_str, hass) + template_obj = template.Template(template_str, hass) variables = msg.get("variables") timeout = msg.get("timeout") info = None if timeout: try: - timed_out = await template.async_render_will_timeout(timeout) + timed_out = await template_obj.async_render_will_timeout(timeout) except TemplateError as ex: connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex)) return @@ -294,7 +298,7 @@ async def handle_render_template(hass, connection, msg): try: info = async_track_template_result( hass, - [TrackTemplate(template, variables)], + [TrackTemplate(template_obj, variables)], _template_listener, raise_on_template_error=True, ) diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index 1f7abc42c4e..f596db63c5e 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -21,13 +21,7 @@ from tests.common import MockEntity, MockEntityPlatform, async_mock_service async def test_call_service(hass, websocket_client): """Test call service command.""" - calls = [] - - @callback - def service_call(call): - calls.append(call) - - hass.services.async_register("domain_test", "test_service", service_call) + calls = async_mock_service(hass, "domain_test", "test_service") await websocket_client.send_json( { @@ -54,13 +48,7 @@ async def test_call_service(hass, websocket_client): async def test_call_service_target(hass, websocket_client): """Test call service command with target.""" - calls = [] - - @callback - def service_call(call): - calls.append(call) - - hass.services.async_register("domain_test", "test_service", service_call) + calls = async_mock_service(hass, "domain_test", "test_service") await websocket_client.send_json( { @@ -93,6 +81,28 @@ async def test_call_service_target(hass, websocket_client): } +async def test_call_service_target_template(hass, websocket_client): + """Test call service command with target does not allow template.""" + await websocket_client.send_json( + { + "id": 5, + "type": "call_service", + "domain": "domain_test", + "service": "test_service", + "service_data": {"hello": "world"}, + "target": { + "entity_id": "{{ 1 }}", + }, + } + ) + + msg = await websocket_client.receive_json() + assert msg["id"] == 5 + assert msg["type"] == const.TYPE_RESULT + assert not msg["success"] + assert msg["error"]["code"] == const.ERR_INVALID_FORMAT + + async def test_call_service_not_found(hass, websocket_client): """Test call service command.""" await websocket_client.send_json( @@ -232,7 +242,6 @@ async def test_call_service_error(hass, websocket_client): ) msg = await websocket_client.receive_json() - print(msg) assert msg["id"] == 5 assert msg["type"] == const.TYPE_RESULT assert msg["success"] is False @@ -249,7 +258,6 @@ async def test_call_service_error(hass, websocket_client): ) msg = await websocket_client.receive_json() - print(msg) assert msg["id"] == 6 assert msg["type"] == const.TYPE_RESULT assert msg["success"] is False From 10dae253e5c10b0f2feecf899d6d0fae426b1043 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 6 Mar 2021 00:37:56 +0100 Subject: [PATCH 196/831] Complete typing on Verisure integration (#47482) --- homeassistant/components/verisure/__init__.py | 31 +++++++------ .../verisure/alarm_control_panel.py | 41 +++++++++++------ .../components/verisure/binary_sensor.py | 33 ++++++++----- homeassistant/components/verisure/camera.py | 26 ++++++++--- homeassistant/components/verisure/lock.py | 34 +++++++++----- homeassistant/components/verisure/sensor.py | 46 +++++++++++-------- homeassistant/components/verisure/switch.py | 38 +++++++++------ 7 files changed, 159 insertions(+), 90 deletions(-) diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index ab061faccad..ccb479814ab 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -1,5 +1,8 @@ """Support for Verisure devices.""" +from __future__ import annotations + from datetime import timedelta +from typing import Any, Literal from jsonpath import jsonpath import verisure @@ -12,8 +15,10 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, HTTP_SERVICE_UNAVAILABLE, ) +from homeassistant.core import HomeAssistant from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import ConfigType from homeassistant.util import Throttle from .const import ( @@ -78,7 +83,7 @@ CONFIG_SCHEMA = vol.Schema( DEVICE_SERIAL_SCHEMA = vol.Schema({vol.Required(ATTR_DEVICE_SERIAL): cv.string}) -def setup(hass, config): +def setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Verisure integration.""" global HUB # pylint: disable=global-statement HUB = VerisureHub(config[DOMAIN]) @@ -137,7 +142,7 @@ def setup(hass, config): class VerisureHub: """A Verisure hub wrapper class.""" - def __init__(self, domain_config): + def __init__(self, domain_config: ConfigType): """Initialize the Verisure hub.""" self.overview = {} self.imageseries = {} @@ -150,7 +155,7 @@ class VerisureHub: self.giid = domain_config.get(CONF_GIID) - def login(self): + def login(self) -> bool: """Login to Verisure.""" try: self.session.login() @@ -161,7 +166,7 @@ class VerisureHub: return self.set_giid() return True - def logout(self): + def logout(self) -> bool: """Logout from Verisure.""" try: self.session.logout() @@ -170,7 +175,7 @@ class VerisureHub: return False return True - def set_giid(self): + def set_giid(self) -> bool: """Set installation GIID.""" try: self.session.set_giid(self.giid) @@ -179,7 +184,7 @@ class VerisureHub: return False return True - def update_overview(self): + def update_overview(self) -> None: """Update the overview.""" try: self.overview = self.session.get_overview() @@ -192,34 +197,34 @@ class VerisureHub: raise @Throttle(timedelta(seconds=60)) - def update_smartcam_imageseries(self): + def update_smartcam_imageseries(self) -> None: """Update the image series.""" self.imageseries = self.session.get_camera_imageseries() @Throttle(timedelta(seconds=30)) - def smartcam_capture(self, device_id): + def smartcam_capture(self, device_id: str) -> None: """Capture a new image from a smartcam.""" self.session.capture_image(device_id) - def disable_autolock(self, device_id): + def disable_autolock(self, device_id: str) -> None: """Disable autolock.""" self.session.set_lock_config(device_id, auto_lock_enabled=False) - def enable_autolock(self, device_id): + def enable_autolock(self, device_id: str) -> None: """Enable autolock.""" self.session.set_lock_config(device_id, auto_lock_enabled=True) - def get(self, jpath, *args): + def get(self, jpath: str, *args) -> list[Any] | Literal[False]: """Get values from the overview that matches the jsonpath.""" res = jsonpath(self.overview, jpath % args) return res or [] - def get_first(self, jpath, *args): + def get_first(self, jpath: str, *args) -> Any | None: """Get first value from the overview that matches the jsonpath.""" res = self.get(jpath, *args) return res[0] if res else None - def get_image_info(self, jpath, *args): + def get_image_info(self, jpath: str, *args) -> list[Any] | Literal[False]: """Get values from the imageseries that matches the jsonpath.""" res = jsonpath(self.imageseries, jpath % args) return res or [] diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index fff58433a9c..c791bfc38dc 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -1,7 +1,13 @@ """Support for Verisure alarm control panels.""" -from time import sleep +from __future__ import annotations -import homeassistant.components.alarm_control_panel as alarm +from time import sleep +from typing import Any, Callable + +from homeassistant.components.alarm_control_panel import ( + FORMAT_NUMBER, + AlarmControlPanelEntity, +) from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, SUPPORT_ALARM_ARM_HOME, @@ -11,12 +17,19 @@ from homeassistant.const import ( STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, ) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from . import HUB as hub from .const import CONF_ALARM, CONF_CODE_DIGITS, CONF_GIID, LOGGER -def setup_platform(hass, config, add_entities, discovery_info=None): +def setup_platform( + hass: HomeAssistant, + config: dict[str, Any], + add_entities: Callable[[list[Entity], bool], None], + discovery_info: dict[str, Any] | None = None, +) -> None: """Set up the Verisure platform.""" alarms = [] if int(hub.config.get(CONF_ALARM, 1)): @@ -25,7 +38,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(alarms) -def set_arm_state(state, code=None): +def set_arm_state(state: str, code: str | None = None) -> None: """Send set arm state command.""" transaction_id = hub.session.set_arm_state(code, state)[ "armStateChangeTransactionId" @@ -38,7 +51,7 @@ def set_arm_state(state, code=None): hub.update_overview() -class VerisureAlarm(alarm.AlarmControlPanelEntity): +class VerisureAlarm(AlarmControlPanelEntity): """Representation of a Verisure alarm status.""" def __init__(self): @@ -48,7 +61,7 @@ class VerisureAlarm(alarm.AlarmControlPanelEntity): self._changed_by = None @property - def name(self): + def name(self) -> str: """Return the name of the device.""" giid = hub.config.get(CONF_GIID) if giid is not None: @@ -61,7 +74,7 @@ class VerisureAlarm(alarm.AlarmControlPanelEntity): return "{} alarm".format(hub.session.installations[0]["alias"]) @property - def state(self): + def state(self) -> str | None: """Return the state of the device.""" return self._state @@ -71,16 +84,16 @@ class VerisureAlarm(alarm.AlarmControlPanelEntity): return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY @property - def code_format(self): + def code_format(self) -> str: """Return one or more digits/characters.""" - return alarm.FORMAT_NUMBER + return FORMAT_NUMBER @property - def changed_by(self): + def changed_by(self) -> str | None: """Return the last change triggered by.""" return self._changed_by - def update(self): + def update(self) -> None: """Update alarm status.""" hub.update_overview() status = hub.get_first("$.armState.statusType") @@ -94,14 +107,14 @@ class VerisureAlarm(alarm.AlarmControlPanelEntity): LOGGER.error("Unknown alarm state %s", status) self._changed_by = hub.get_first("$.armState.name") - def alarm_disarm(self, code=None): + def alarm_disarm(self, code: str | None = None) -> None: """Send disarm command.""" set_arm_state("DISARMED", code) - def alarm_arm_home(self, code=None): + def alarm_arm_home(self, code: str | None = None) -> None: """Send arm home command.""" set_arm_state("ARMED_HOME", code) - def alarm_arm_away(self, code=None): + def alarm_arm_away(self, code: str | None = None) -> None: """Send arm away command.""" set_arm_state("ARMED_AWAY", code) diff --git a/homeassistant/components/verisure/binary_sensor.py b/homeassistant/components/verisure/binary_sensor.py index 5a7f4386ece..e30f008dba9 100644 --- a/homeassistant/components/verisure/binary_sensor.py +++ b/homeassistant/components/verisure/binary_sensor.py @@ -1,13 +1,24 @@ """Support for Verisure binary sensors.""" +from __future__ import annotations + +from typing import Any, Callable + from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, BinarySensorEntity, ) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from . import CONF_DOOR_WINDOW, HUB as hub -def setup_platform(hass, config, add_entities, discovery_info=None): +def setup_platform( + hass: HomeAssistant, + config: dict[str, Any], + add_entities: Callable[[list[Entity], bool], None], + discovery_info: dict[str, Any] | None = None, +) -> None: """Set up the Verisure binary sensors.""" sensors = [] hub.update_overview() @@ -29,12 +40,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class VerisureDoorWindowSensor(BinarySensorEntity): """Representation of a Verisure door window sensor.""" - def __init__(self, device_label): + def __init__(self, device_label: str): """Initialize the Verisure door window sensor.""" self._device_label = device_label @property - def name(self): + def name(self) -> str: """Return the name of the binary sensor.""" return hub.get_first( "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].area", @@ -42,7 +53,7 @@ class VerisureDoorWindowSensor(BinarySensorEntity): ) @property - def is_on(self): + def is_on(self) -> bool: """Return the state of the sensor.""" return ( hub.get_first( @@ -53,7 +64,7 @@ class VerisureDoorWindowSensor(BinarySensorEntity): ) @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return ( hub.get_first( @@ -64,7 +75,7 @@ class VerisureDoorWindowSensor(BinarySensorEntity): ) # pylint: disable=no-self-use - def update(self): + def update(self) -> None: """Update the state of the sensor.""" hub.update_overview() @@ -73,26 +84,26 @@ class VerisureEthernetStatus(BinarySensorEntity): """Representation of a Verisure VBOX internet status.""" @property - def name(self): + def name(self) -> str: """Return the name of the binary sensor.""" return "Verisure Ethernet status" @property - def is_on(self): + def is_on(self) -> bool: """Return the state of the sensor.""" return hub.get_first("$.ethernetConnectedNow") @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return hub.get_first("$.ethernetConnectedNow") is not None # pylint: disable=no-self-use - def update(self): + def update(self) -> None: """Update the state of the sensor.""" hub.update_overview() @property - def device_class(self): + def device_class(self) -> str: """Return the class of this device, from component DEVICE_CLASSES.""" return DEVICE_CLASS_CONNECTIVITY diff --git a/homeassistant/components/verisure/camera.py b/homeassistant/components/verisure/camera.py index a69e1fb95d8..ad6840c0614 100644 --- a/homeassistant/components/verisure/camera.py +++ b/homeassistant/components/verisure/camera.py @@ -1,22 +1,34 @@ """Support for Verisure cameras.""" +from __future__ import annotations + import errno import os +from typing import Any, Callable, Literal from homeassistant.components.camera import Camera from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from . import HUB as hub from .const import CONF_SMARTCAM, LOGGER -def setup_platform(hass, config, add_entities, discovery_info=None): +def setup_platform( + hass: HomeAssistant, + config: dict[str, Any], + add_entities: Callable[[list[Entity], bool], None], + discovery_info: dict[str, Any] | None = None, +) -> None | Literal[False]: """Set up the Verisure Camera.""" if not int(hub.config.get(CONF_SMARTCAM, 1)): return False + directory_path = hass.config.config_dir if not os.access(directory_path, os.R_OK): LOGGER.error("file path %s is not readable", directory_path) return False + hub.update_overview() smartcams = [ VerisureSmartcam(hass, device_label, directory_path) @@ -29,7 +41,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class VerisureSmartcam(Camera): """Representation of a Verisure camera.""" - def __init__(self, hass, device_label, directory_path): + def __init__(self, hass: HomeAssistant, device_label: str, directory_path: str): """Initialize Verisure File Camera component.""" super().__init__() @@ -39,7 +51,7 @@ class VerisureSmartcam(Camera): self._image_id = None hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.delete_image) - def camera_image(self): + def camera_image(self) -> bytes | None: """Return image response.""" self.check_imagelist() if not self._image: @@ -49,7 +61,7 @@ class VerisureSmartcam(Camera): with open(self._image, "rb") as file: return file.read() - def check_imagelist(self): + def check_imagelist(self) -> None: """Check the contents of the image list.""" hub.update_smartcam_imageseries() image_ids = hub.get_image_info( @@ -67,12 +79,12 @@ class VerisureSmartcam(Camera): ) hub.session.download_image(self._device_label, new_image_id, new_image_path) LOGGER.debug("Old image_id=%s", self._image_id) - self.delete_image(self) + self.delete_image() self._image_id = new_image_id self._image = new_image_path - def delete_image(self, event): + def delete_image(self) -> None: """Delete an old image.""" remove_image = os.path.join( self._directory_path, "{}{}".format(self._image_id, ".jpg") @@ -85,7 +97,7 @@ class VerisureSmartcam(Camera): raise @property - def name(self): + def name(self) -> str: """Return the name of this camera.""" return hub.get_first( "$.customerImageCameras[?(@.deviceLabel=='%s')].area", self._device_label diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index 228c8c6c176..b2e1cfb3db0 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -1,14 +1,24 @@ """Support for Verisure locks.""" +from __future__ import annotations + from time import monotonic, sleep +from typing import Any, Callable from homeassistant.components.lock import LockEntity from homeassistant.const import ATTR_CODE, STATE_LOCKED, STATE_UNLOCKED +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from . import HUB as hub from .const import CONF_CODE_DIGITS, CONF_DEFAULT_LOCK_CODE, CONF_LOCKS, LOGGER -def setup_platform(hass, config, add_entities, discovery_info=None): +def setup_platform( + hass: HomeAssistant, + config: dict[str, Any], + add_entities: Callable[[list[Entity], bool], None], + discovery_info: dict[str, Any] | None = None, +) -> None: """Set up the Verisure lock platform.""" locks = [] if int(hub.config.get(CONF_LOCKS, 1)): @@ -26,7 +36,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class VerisureDoorlock(LockEntity): """Representation of a Verisure doorlock.""" - def __init__(self, device_label): + def __init__(self, device_label: str): """Initialize the Verisure lock.""" self._device_label = device_label self._state = None @@ -36,19 +46,19 @@ class VerisureDoorlock(LockEntity): self._default_lock_code = hub.config.get(CONF_DEFAULT_LOCK_CODE) @property - def name(self): + def name(self) -> str: """Return the name of the lock.""" return hub.get_first( "$.doorLockStatusList[?(@.deviceLabel=='%s')].area", self._device_label ) @property - def state(self): + def state(self) -> str | None: """Return the state of the lock.""" return self._state @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return ( hub.get_first( @@ -58,16 +68,16 @@ class VerisureDoorlock(LockEntity): ) @property - def changed_by(self): + def changed_by(self) -> str | None: """Last change triggered by.""" return self._changed_by @property - def code_format(self): + def code_format(self) -> str: """Return the required six digit code.""" return "^\\d{%s}$" % self._digits - def update(self): + def update(self) -> None: """Update lock status.""" if monotonic() - self._change_timestamp < 10: return @@ -88,11 +98,11 @@ class VerisureDoorlock(LockEntity): ) @property - def is_locked(self): + def is_locked(self) -> bool: """Return true if lock is locked.""" return self._state == STATE_LOCKED - def unlock(self, **kwargs): + def unlock(self, **kwargs) -> None: """Send unlock command.""" if self._state is None: return @@ -104,7 +114,7 @@ class VerisureDoorlock(LockEntity): self.set_lock_state(code, STATE_UNLOCKED) - def lock(self, **kwargs): + def lock(self, **kwargs) -> None: """Send lock command.""" if self._state == STATE_LOCKED: return @@ -116,7 +126,7 @@ class VerisureDoorlock(LockEntity): self.set_lock_state(code, STATE_LOCKED) - def set_lock_state(self, code, state): + def set_lock_state(self, code: str, state: str) -> None: """Send set lock state command.""" lock_state = "lock" if state == STATE_LOCKED else "unlock" transaction_id = hub.session.set_lock_state( diff --git a/homeassistant/components/verisure/sensor.py b/homeassistant/components/verisure/sensor.py index ac7c8f40e8d..6582dfc409a 100644 --- a/homeassistant/components/verisure/sensor.py +++ b/homeassistant/components/verisure/sensor.py @@ -1,12 +1,22 @@ """Support for Verisure sensors.""" +from __future__ import annotations + +from typing import Any, Callable + from homeassistant.const import PERCENTAGE, TEMP_CELSIUS +from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from . import HUB as hub from .const import CONF_HYDROMETERS, CONF_MOUSE, CONF_THERMOMETERS -def setup_platform(hass, config, add_entities, discovery_info=None): +def setup_platform( + hass: HomeAssistant, + config: dict[str, Any], + add_entities: Callable[[list[Entity], bool], None], + discovery_info: dict[str, Any] | None = None, +) -> None: """Set up the Verisure platform.""" sensors = [] hub.update_overview() @@ -47,12 +57,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class VerisureThermometer(Entity): """Representation of a Verisure thermometer.""" - def __init__(self, device_label): + def __init__(self, device_label: str): """Initialize the sensor.""" self._device_label = device_label @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return ( hub.get_first( @@ -62,14 +72,14 @@ class VerisureThermometer(Entity): ) @property - def state(self): + def state(self) -> str | None: """Return the state of the device.""" return hub.get_first( "$.climateValues[?(@.deviceLabel=='%s')].temperature", self._device_label ) @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return ( hub.get_first( @@ -80,12 +90,12 @@ class VerisureThermometer(Entity): ) @property - def unit_of_measurement(self): + def unit_of_measurement(self) -> str: """Return the unit of measurement of this entity.""" return TEMP_CELSIUS # pylint: disable=no-self-use - def update(self): + def update(self) -> None: """Update the sensor.""" hub.update_overview() @@ -93,12 +103,12 @@ class VerisureThermometer(Entity): class VerisureHygrometer(Entity): """Representation of a Verisure hygrometer.""" - def __init__(self, device_label): + def __init__(self, device_label: str): """Initialize the sensor.""" self._device_label = device_label @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return ( hub.get_first( @@ -108,14 +118,14 @@ class VerisureHygrometer(Entity): ) @property - def state(self): + def state(self) -> str | None: """Return the state of the device.""" return hub.get_first( "$.climateValues[?(@.deviceLabel=='%s')].humidity", self._device_label ) @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return ( hub.get_first( @@ -125,12 +135,12 @@ class VerisureHygrometer(Entity): ) @property - def unit_of_measurement(self): + def unit_of_measurement(self) -> str: """Return the unit of measurement of this entity.""" return PERCENTAGE # pylint: disable=no-self-use - def update(self): + def update(self) -> None: """Update the sensor.""" hub.update_overview() @@ -143,7 +153,7 @@ class VerisureMouseDetection(Entity): self._device_label = device_label @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return ( hub.get_first( @@ -153,14 +163,14 @@ class VerisureMouseDetection(Entity): ) @property - def state(self): + def state(self) -> str | None: """Return the state of the device.""" return hub.get_first( "$.eventCounts[?(@.deviceLabel=='%s')].detections", self._device_label ) @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return ( hub.get_first("$.eventCounts[?(@.deviceLabel=='%s')]", self._device_label) @@ -168,11 +178,11 @@ class VerisureMouseDetection(Entity): ) @property - def unit_of_measurement(self): + def unit_of_measurement(self) -> str: """Return the unit of measurement of this entity.""" return "Mice" # pylint: disable=no-self-use - def update(self): + def update(self) -> None: """Update the sensor.""" hub.update_overview() diff --git a/homeassistant/components/verisure/switch.py b/homeassistant/components/verisure/switch.py index 4615e0a2a49..e5e19bd6d13 100644 --- a/homeassistant/components/verisure/switch.py +++ b/homeassistant/components/verisure/switch.py @@ -1,45 +1,53 @@ """Support for Verisure Smartplugs.""" +from __future__ import annotations + from time import monotonic +from typing import Any, Callable, Literal from homeassistant.components.switch import SwitchEntity +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from . import CONF_SMARTPLUGS, HUB as hub -def setup_platform(hass, config, add_entities, discovery_info=None): +def setup_platform( + hass: HomeAssistant, + config: dict[str, Any], + add_entities: Callable[[list[Entity], bool], None], + discovery_info: dict[str, Any] | None = None, +) -> None | Literal[False]: """Set up the Verisure switch platform.""" if not int(hub.config.get(CONF_SMARTPLUGS, 1)): return False hub.update_overview() - switches = [] - switches.extend( - [ - VerisureSmartplug(device_label) - for device_label in hub.get("$.smartPlugs[*].deviceLabel") - ] - ) + switches = [ + VerisureSmartplug(device_label) + for device_label in hub.get("$.smartPlugs[*].deviceLabel") + ] + add_entities(switches) class VerisureSmartplug(SwitchEntity): """Representation of a Verisure smartplug.""" - def __init__(self, device_id): + def __init__(self, device_id: str): """Initialize the Verisure device.""" self._device_label = device_id self._change_timestamp = 0 self._state = False @property - def name(self): + def name(self) -> str: """Return the name or location of the smartplug.""" return hub.get_first( "$.smartPlugs[?(@.deviceLabel == '%s')].area", self._device_label ) @property - def is_on(self): + def is_on(self) -> bool: """Return true if on.""" if monotonic() - self._change_timestamp < 10: return self._state @@ -53,26 +61,26 @@ class VerisureSmartplug(SwitchEntity): return self._state @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return ( hub.get_first("$.smartPlugs[?(@.deviceLabel == '%s')]", self._device_label) is not None ) - def turn_on(self, **kwargs): + def turn_on(self, **kwargs) -> None: """Set smartplug status on.""" hub.session.set_smartplug_state(self._device_label, True) self._state = True self._change_timestamp = monotonic() - def turn_off(self, **kwargs): + def turn_off(self, **kwargs) -> None: """Set smartplug status off.""" hub.session.set_smartplug_state(self._device_label, False) self._state = False self._change_timestamp = monotonic() # pylint: disable=no-self-use - def update(self): + def update(self) -> None: """Get the latest date of the smartplug.""" hub.update_overview() From c7718f2b3b5fb18c0828589e44a21bf874da9215 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Sat, 6 Mar 2021 10:21:00 +0100 Subject: [PATCH 197/831] Fix Sonos polling mode (#47498) --- homeassistant/components/sonos/media_player.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index e6ee45e7a57..1a9e9ef58df 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -646,9 +646,12 @@ class SonosEntity(MediaPlayerEntity): update_position = new_status != self._status self._status = new_status - track_uri = variables["current_track_uri"] if variables else None - - music_source = self.soco.music_source_from_uri(track_uri) + if variables: + track_uri = variables["current_track_uri"] + music_source = self.soco.music_source_from_uri(track_uri) + else: + # This causes a network round-trip so we avoid it when possible + music_source = self.soco.music_source if music_source == MUSIC_SRC_TV: self.update_media_linein(SOURCE_TV) From 4cade4b7363199062f42c86ece24ac61fe68fa07 Mon Sep 17 00:00:00 2001 From: FidgetyRat Date: Sat, 6 Mar 2021 04:26:04 -0500 Subject: [PATCH 198/831] Add OPENING & CLOSING state to MySensors cover (#47285) * Added OPENING & CLOSING State Support Added support for OPENING and CLOSING states using a combination of the required V_ variables. Simplified the determination of the cover's state by use of a new enumeration and single method allowing the state to be used by all three HomeAssistant query methods. * Fixes for HomeAssistant Style Corrections to style to allow flake8, isort, and black to pass. * Peer Review Changes Added @unique to the main enumeration. Removed unnecessary parens from door state logic. Reordered CLOSING and CLOSED in the enumeration. --- homeassistant/components/mysensors/cover.py | 52 ++++++++++++++++++-- homeassistant/components/mysensors/device.py | 3 ++ 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/mysensors/cover.py b/homeassistant/components/mysensors/cover.py index 782ab88c488..0e3478a57bf 100644 --- a/homeassistant/components/mysensors/cover.py +++ b/homeassistant/components/mysensors/cover.py @@ -1,4 +1,5 @@ """Support for MySensors covers.""" +from enum import Enum, unique import logging from typing import Callable @@ -14,6 +15,16 @@ from homeassistant.helpers.typing import HomeAssistantType _LOGGER = logging.getLogger(__name__) +@unique +class CoverState(Enum): + """An enumeration of the standard cover states.""" + + OPEN = 0 + OPENING = 1 + CLOSING = 2 + CLOSED = 3 + + async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable ): @@ -43,13 +54,44 @@ async def async_setup_entry( class MySensorsCover(mysensors.device.MySensorsEntity, CoverEntity): """Representation of the value of a MySensors Cover child node.""" + def get_cover_state(self): + """Return a CoverState enum representing the state of the cover.""" + set_req = self.gateway.const.SetReq + v_up = self._values.get(set_req.V_UP) == STATE_ON + v_down = self._values.get(set_req.V_DOWN) == STATE_ON + v_stop = self._values.get(set_req.V_STOP) == STATE_ON + + # If a V_DIMMER or V_PERCENTAGE is available, that is the amount + # the cover is open. Otherwise, use 0 or 100 based on the V_LIGHT + # or V_STATUS. + amount = 100 + if set_req.V_DIMMER in self._values: + amount = self._values.get(set_req.V_DIMMER) + else: + amount = 100 if self._values.get(set_req.V_LIGHT) == STATE_ON else 0 + + if v_up and not v_down and not v_stop: + return CoverState.OPENING + if not v_up and v_down and not v_stop: + return CoverState.CLOSING + if not v_up and not v_down and v_stop and amount == 0: + return CoverState.CLOSED + return CoverState.OPEN + @property def is_closed(self): - """Return True if cover is closed.""" - set_req = self.gateway.const.SetReq - if set_req.V_DIMMER in self._values: - return self._values.get(set_req.V_DIMMER) == 0 - return self._values.get(set_req.V_LIGHT) == STATE_OFF + """Return True if the cover is closed.""" + return self.get_cover_state() == CoverState.CLOSED + + @property + def is_closing(self): + """Return True if the cover is closing.""" + return self.get_cover_state() == CoverState.CLOSING + + @property + def is_opening(self): + """Return True if the cover is opening.""" + return self.get_cover_state() == CoverState.OPENING @property def current_cover_position(self): diff --git a/homeassistant/components/mysensors/device.py b/homeassistant/components/mysensors/device.py index 68414867345..25b892d70b3 100644 --- a/homeassistant/components/mysensors/device.py +++ b/homeassistant/components/mysensors/device.py @@ -162,6 +162,9 @@ class MySensorsDevice: set_req.V_LIGHT, set_req.V_LOCK_STATUS, set_req.V_TRIPPED, + set_req.V_UP, + set_req.V_DOWN, + set_req.V_STOP, ): self._values[value_type] = STATE_ON if int(value) == 1 else STATE_OFF elif value_type == set_req.V_DIMMER: From 022184176aa10cc45a44fcc5a0d0982a4f12c0d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 6 Mar 2021 13:36:20 +0200 Subject: [PATCH 199/831] Upgrade upcloud-api to 1.0.1 (#47501) https://github.com/UpCloudLtd/upcloud-python-api/releases/tag/0.4.6 https://github.com/UpCloudLtd/upcloud-python-api/releases/tag/v1.0.0 https://github.com/UpCloudLtd/upcloud-python-api/releases/tag/v1.0.1 --- homeassistant/components/upcloud/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/upcloud/manifest.json b/homeassistant/components/upcloud/manifest.json index 3a85f847c9d..f161e273bc3 100644 --- a/homeassistant/components/upcloud/manifest.json +++ b/homeassistant/components/upcloud/manifest.json @@ -3,6 +3,6 @@ "name": "UpCloud", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/upcloud", - "requirements": ["upcloud-api==0.4.5"], + "requirements": ["upcloud-api==1.0.1"], "codeowners": ["@scop"] } diff --git a/requirements_all.txt b/requirements_all.txt index 446b30b223b..46c25915af9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2251,7 +2251,7 @@ unifiled==0.11 upb_lib==0.4.12 # homeassistant.components.upcloud -upcloud-api==0.4.5 +upcloud-api==1.0.1 # homeassistant.components.huawei_lte # homeassistant.components.syncthru diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e8242230052..4404d099050 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1154,7 +1154,7 @@ twinkly-client==0.0.2 upb_lib==0.4.12 # homeassistant.components.upcloud -upcloud-api==0.4.5 +upcloud-api==1.0.1 # homeassistant.components.huawei_lte # homeassistant.components.syncthru From 2f9d03d115db15103ecf66110ed7305bae069358 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 6 Mar 2021 12:57:21 +0100 Subject: [PATCH 200/831] Merge action and condition traces (#47373) * Merge action and condition traces * Update __init__.py * Add typing to AutomationTrace * Make trace_get prepare a new trace by default * Correct typing of trace_cv * Fix tests --- .../components/automation/__init__.py | 101 ++++++++------ homeassistant/helpers/condition.py | 114 ++-------------- homeassistant/helpers/script.py | 128 +++--------------- homeassistant/helpers/trace.py | 120 ++++++++++++---- tests/helpers/test_condition.py | 6 +- tests/helpers/test_script.py | 6 +- 6 files changed, 191 insertions(+), 284 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index f12a7398f7e..df7102effde 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -1,8 +1,20 @@ """Allow to set up simple automation rules via the config file.""" from collections import deque from contextlib import contextmanager +import datetime as dt import logging -from typing import Any, Awaitable, Callable, Dict, List, Optional, Set, Union, cast +from typing import ( + Any, + Awaitable, + Callable, + Deque, + Dict, + List, + Optional, + Set, + Union, + cast, +) import voluptuous as vol from voluptuous.humanize import humanize_error @@ -42,11 +54,6 @@ from homeassistant.exceptions import ( HomeAssistantError, ) from homeassistant.helpers import condition, extract_domain_configs, template -from homeassistant.helpers.condition import ( - condition_path, - condition_trace_clear, - condition_trace_get, -) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent @@ -57,12 +64,10 @@ from homeassistant.helpers.script import ( CONF_MAX, CONF_MAX_EXCEEDED, Script, - action_path, - action_trace_clear, - action_trace_get, ) from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.service import async_register_admin_service +from homeassistant.helpers.trace import TraceElement, trace_get, trace_path from homeassistant.helpers.trigger import async_initialize_triggers from homeassistant.helpers.typing import TemplateVarsType from homeassistant.loader import bind_hass @@ -235,44 +240,55 @@ async def async_setup(hass, config): class AutomationTrace: """Container for automation trace.""" - def __init__(self, unique_id, config, trigger, context, action_trace): + def __init__( + self, + unique_id: Optional[str], + config: Dict[str, Any], + trigger: Dict[str, Any], + context: Context, + ): """Container for automation trace.""" - self._action_trace = action_trace - self._condition_trace = None - self._config = config - self._context = context - self._error = None - self._state = "running" - self._timestamp_finish = None - self._timestamp_start = dt_util.utcnow() - self._trigger = trigger - self._unique_id = unique_id - self._variables = None + self._action_trace: Optional[Dict[str, Deque[TraceElement]]] = None + self._condition_trace: Optional[Dict[str, Deque[TraceElement]]] = None + self._config: Dict[str, Any] = config + self._context: Context = context + self._error: Optional[Exception] = None + self._state: str = "running" + self._timestamp_finish: Optional[dt.datetime] = None + self._timestamp_start: dt.datetime = dt_util.utcnow() + self._trigger: Dict[str, Any] = trigger + self._unique_id: Optional[str] = unique_id + self._variables: Optional[Dict[str, Any]] = None - def set_error(self, ex): + def set_action_trace(self, trace: Dict[str, Deque[TraceElement]]) -> None: + """Set action trace.""" + self._action_trace = trace + + def set_condition_trace(self, trace: Dict[str, Deque[TraceElement]]) -> None: + """Set condition trace.""" + self._condition_trace = trace + + def set_error(self, ex: Exception) -> None: """Set error.""" self._error = ex - def set_variables(self, variables): + def set_variables(self, variables: Dict[str, Any]) -> None: """Set variables.""" self._variables = variables - def set_condition_trace(self, condition_trace): - """Set condition trace.""" - self._condition_trace = condition_trace - - def finished(self): + def finished(self) -> None: """Set finish time.""" self._timestamp_finish = dt_util.utcnow() self._state = "stopped" - def as_dict(self): + def as_dict(self) -> Dict[str, Any]: """Return dictionary version of this AutomationTrace.""" action_traces = {} condition_traces = {} - for key, trace_list in self._action_trace.items(): - action_traces[key] = [item.as_dict() for item in trace_list] + if self._action_trace: + for key, trace_list in self._action_trace.items(): + action_traces[key] = [item.as_dict() for item in trace_list] if self._condition_trace: for key, trace_list in self._condition_trace.items(): @@ -300,11 +316,7 @@ class AutomationTrace: @contextmanager def trace_automation(hass, unique_id, config, trigger, context): """Trace action execution of automation with automation_id.""" - action_trace_clear() - action_trace = action_trace_get() - automation_trace = AutomationTrace( - unique_id, config, trigger, context, action_trace - ) + automation_trace = AutomationTrace(unique_id, config, trigger, context) if unique_id: if unique_id not in hass.data[DATA_AUTOMATION_TRACE]: @@ -325,7 +337,7 @@ def trace_automation(hass, unique_id, config, trigger, context): "Automation finished. Summary:\n\ttrigger: %s\n\tcondition: %s\n\taction: %s", automation_trace._trigger, # pylint: disable=protected-access automation_trace._condition_trace, # pylint: disable=protected-access - action_trace, + automation_trace._action_trace, # pylint: disable=protected-access ) @@ -510,6 +522,9 @@ class AutomationEntity(ToggleEntity, RestoreEntity): variables = run_variables automation_trace.set_variables(variables) + # Prepare tracing the evaluation of the automation's conditions + automation_trace.set_condition_trace(trace_get()) + if ( not skip_condition and self._cond_func is not None @@ -517,12 +532,12 @@ class AutomationEntity(ToggleEntity, RestoreEntity): ): self._logger.debug( "Conditions not met, aborting automation. Condition summary: %s", - condition_trace_get(), + trace_get(clear=False), ) - automation_trace.set_condition_trace(condition_trace_get()) return - automation_trace.set_condition_trace(condition_trace_get()) - condition_trace_clear() + + # Prepare tracing the execution of the automation's actions + automation_trace.set_action_trace(trace_get()) # Create a new context referring to the old context. parent_id = None if context is None else context.id @@ -543,7 +558,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity): ) try: - with action_path("action"): + with trace_path("action"): await self.action_script.async_run( variables, trigger_context, started_action ) @@ -763,7 +778,7 @@ async def _async_process_if(hass, name, config, p_config): errors = [] for index, check in enumerate(checks): try: - with condition_path(["condition", str(index)]): + with trace_path(["condition", str(index)]): if not check(hass, variables): return False except ConditionError as ex: diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index bc1ff21b9cc..71bbaa5f0f4 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -2,24 +2,12 @@ import asyncio from collections import deque from contextlib import contextmanager -from contextvars import ContextVar from datetime import datetime, timedelta import functools as ft import logging import re import sys -from typing import ( - Any, - Callable, - Container, - Dict, - Generator, - List, - Optional, - Set, - Union, - cast, -) +from typing import Any, Callable, Container, Generator, List, Optional, Set, Union, cast from homeassistant.components import zone as zone_cmp from homeassistant.components.device_automation import ( @@ -67,6 +55,9 @@ import homeassistant.util.dt as dt_util from .trace import ( TraceElement, trace_append_element, + trace_path, + trace_path_get, + trace_stack_cv, trace_stack_pop, trace_stack_push, trace_stack_top, @@ -84,79 +75,16 @@ INPUT_ENTITY_ID = re.compile( ConditionCheckerType = Callable[[HomeAssistant, TemplateVarsType], bool] -# Context variables for tracing -# Trace of condition being evaluated -condition_trace = ContextVar("condition_trace", default=None) -# Stack of TraceElements -condition_trace_stack: ContextVar[Optional[List[TraceElement]]] = ContextVar( - "condition_trace_stack", default=None -) -# Current location in config tree -condition_path_stack: ContextVar[Optional[List[str]]] = ContextVar( - "condition_path_stack", default=None -) - - -def condition_trace_stack_push(node: TraceElement) -> None: - """Push a TraceElement to the top of the trace stack.""" - trace_stack_push(condition_trace_stack, node) - - -def condition_trace_stack_pop() -> None: - """Remove the top element from the trace stack.""" - trace_stack_pop(condition_trace_stack) - - -def condition_trace_stack_top() -> Optional[TraceElement]: - """Return the element at the top of the trace stack.""" - return cast(Optional[TraceElement], trace_stack_top(condition_trace_stack)) - - -def condition_path_push(suffix: Union[str, List[str]]) -> int: - """Go deeper in the config tree.""" - if isinstance(suffix, str): - suffix = [suffix] - for node in suffix: - trace_stack_push(condition_path_stack, node) - return len(suffix) - - -def condition_path_pop(count: int) -> None: - """Go n levels up in the config tree.""" - for _ in range(count): - trace_stack_pop(condition_path_stack) - - -def condition_path_get() -> str: - """Return a string representing the current location in the config tree.""" - path = condition_path_stack.get() - if not path: - return "" - return "/".join(path) - - -def condition_trace_get() -> Optional[Dict[str, TraceElement]]: - """Return the trace of the condition that was evaluated.""" - return condition_trace.get() - - -def condition_trace_clear() -> None: - """Clear the condition trace.""" - condition_trace.set(None) - condition_trace_stack.set(None) - condition_path_stack.set(None) - - def condition_trace_append(variables: TemplateVarsType, path: str) -> TraceElement: """Append a TraceElement to trace[path].""" trace_element = TraceElement(variables) - trace_append_element(condition_trace, trace_element, path) + trace_append_element(trace_element, path) return trace_element def condition_trace_set_result(result: bool, **kwargs: Any) -> None: """Set the result of TraceElement at the top of the stack.""" - node = condition_trace_stack_top() + node = trace_stack_top(trace_stack_cv) # The condition function may be called directly, in which case tracing # is not setup @@ -169,25 +97,15 @@ def condition_trace_set_result(result: bool, **kwargs: Any) -> None: @contextmanager def trace_condition(variables: TemplateVarsType) -> Generator: """Trace condition evaluation.""" - trace_element = condition_trace_append(variables, condition_path_get()) - condition_trace_stack_push(trace_element) + trace_element = condition_trace_append(variables, trace_path_get()) + trace_stack_push(trace_stack_cv, trace_element) try: yield trace_element except Exception as ex: # pylint: disable=broad-except trace_element.set_error(ex) raise ex finally: - condition_trace_stack_pop() - - -@contextmanager -def condition_path(suffix: Union[str, List[str]]) -> Generator: - """Go deeper in the config tree.""" - count = condition_path_push(suffix) - try: - yield - finally: - condition_path_pop(count) + trace_stack_pop(trace_stack_cv) def trace_condition_function(condition: ConditionCheckerType) -> ConditionCheckerType: @@ -260,7 +178,7 @@ async def async_and_from_config( errors = [] for index, check in enumerate(checks): try: - with condition_path(["conditions", str(index)]): + with trace_path(["conditions", str(index)]): if not check(hass, variables): return False except ConditionError as ex: @@ -295,7 +213,7 @@ async def async_or_from_config( errors = [] for index, check in enumerate(checks): try: - with condition_path(["conditions", str(index)]): + with trace_path(["conditions", str(index)]): if check(hass, variables): return True except ConditionError as ex: @@ -330,7 +248,7 @@ async def async_not_from_config( errors = [] for index, check in enumerate(checks): try: - with condition_path(["conditions", str(index)]): + with trace_path(["conditions", str(index)]): if check(hass, variables): return False except ConditionError as ex: @@ -509,9 +427,7 @@ def async_numeric_state_from_config( errors = [] for index, entity_id in enumerate(entity_ids): try: - with condition_path(["entity_id", str(index)]), trace_condition( - variables - ): + with trace_path(["entity_id", str(index)]), trace_condition(variables): if not async_numeric_state( hass, entity_id, @@ -623,9 +539,7 @@ def state_from_config( errors = [] for index, entity_id in enumerate(entity_ids): try: - with condition_path(["entity_id", str(index)]), trace_condition( - variables - ): + with trace_path(["entity_id", str(index)]), trace_condition(variables): if not state(hass, entity_id, req_states, for_period, attribute): return False except ConditionError as ex: diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 3925535d06b..8ee9b12bc1b 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -1,7 +1,6 @@ """Helpers to execute scripts.""" import asyncio from contextlib import contextmanager -from contextvars import ContextVar from datetime import datetime, timedelta from functools import partial import itertools @@ -65,12 +64,7 @@ from homeassistant.core import ( callback, ) from homeassistant.helpers import condition, config_validation as cv, service, template -from homeassistant.helpers.condition import ( - condition_path, - condition_trace_clear, - condition_trace_get, - trace_condition_function, -) +from homeassistant.helpers.condition import trace_condition_function from homeassistant.helpers.event import async_call_later, async_track_template from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.trigger import ( @@ -84,9 +78,12 @@ from homeassistant.util.dt import utcnow from .trace import ( TraceElement, trace_append_element, + trace_path, + trace_path_get, + trace_set_result, + trace_stack_cv, trace_stack_pop, trace_stack_push, - trace_stack_top, ) # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs @@ -125,111 +122,26 @@ _SHUTDOWN_MAX_WAIT = 60 ACTION_TRACE_NODE_MAX_LEN = 20 # Max the length of a trace node for repeated actions -action_trace = ContextVar("action_trace", default=None) -action_trace_stack = ContextVar("action_trace_stack", default=None) -action_path_stack = ContextVar("action_path_stack", default=None) - - -def action_trace_stack_push(node): - """Push a TraceElement to the top of the trace stack.""" - trace_stack_push(action_trace_stack, node) - - -def action_trace_stack_pop(): - """Remove the top element from the trace stack.""" - trace_stack_pop(action_trace_stack) - - -def action_trace_stack_top(): - """Return the element at the top of the trace stack.""" - return trace_stack_top(action_trace_stack) - - -def action_path_push(suffix): - """Go deeper in the config tree.""" - if isinstance(suffix, str): - suffix = [suffix] - for node in suffix: - trace_stack_push(action_path_stack, node) - return len(suffix) - - -def action_path_pop(count): - """Go n levels up in the config tree.""" - for _ in range(count): - trace_stack_pop(action_path_stack) - - -def action_path_get(): - """Return a string representing the current location in the config tree.""" - path = action_path_stack.get() - if not path: - return "" - return "/".join(path) - - -def action_trace_get(): - """Return the trace of the script that was executed.""" - return action_trace.get() - - -def action_trace_clear(): - """Clear the action trace.""" - action_trace.set({}) - action_trace_stack.set(None) - action_path_stack.set(None) - def action_trace_append(variables, path): """Append a TraceElement to trace[path].""" trace_element = TraceElement(variables) - trace_append_element(action_trace, trace_element, path, ACTION_TRACE_NODE_MAX_LEN) + trace_append_element(trace_element, path, ACTION_TRACE_NODE_MAX_LEN) return trace_element -def action_trace_set_result(**kwargs): - """Set the result of TraceElement at the top of the stack.""" - node = action_trace_stack_top() - node.set_result(**kwargs) - - -def action_trace_add_conditions(): - """Add the result of condition evaluation to the action trace.""" - condition_trace = condition_trace_get() - condition_trace_clear() - - if condition_trace is None: - return - - _action_path = action_path_get() - for cond_path, conditions in condition_trace.items(): - path = _action_path + "/" + cond_path if cond_path else _action_path - for cond in conditions: - trace_append_element(action_trace, cond, path) - - @contextmanager def trace_action(variables): """Trace action execution.""" - trace_element = action_trace_append(variables, action_path_get()) - action_trace_stack_push(trace_element) + trace_element = action_trace_append(variables, trace_path_get()) + trace_stack_push(trace_stack_cv, trace_element) try: yield trace_element except Exception as ex: # pylint: disable=broad-except trace_element.set_error(ex) raise ex finally: - action_trace_stack_pop() - - -@contextmanager -def action_path(suffix): - """Go deeper in the config tree.""" - count = action_path_push(suffix) - try: - yield - finally: - action_path_pop(count) + trace_stack_pop(trace_stack_cv) def make_script_schema(schema, default_script_mode, extra=vol.PREVENT_EXTRA): @@ -382,7 +294,7 @@ class _ScriptRun: self._finish() async def _async_step(self, log_exceptions): - with action_path(str(self._step)), trace_action(None): + with trace_path(str(self._step)), trace_action(None): try: handler = f"_async_{cv.determine_script_action(self._action)}_step" await getattr(self, handler)() @@ -638,15 +550,14 @@ class _ScriptRun: ) cond = await self._async_get_condition(self._action) try: - with condition_path("condition"): + with trace_path("condition"): check = cond(self._hass, self._variables) except exceptions.ConditionError as ex: _LOGGER.warning("Error in 'condition' evaluation:\n%s", ex) check = False self._log("Test condition %s: %s", self._script.last_action, check) - action_trace_set_result(result=check) - action_trace_add_conditions() + trace_set_result(result=check) if not check: raise _StopScript @@ -654,9 +565,9 @@ class _ScriptRun: @trace_condition_function def traced_test_conditions(hass, variables): try: - with condition_path("conditions"): + with trace_path("conditions"): for idx, cond in enumerate(conditions): - with condition_path(str(idx)): + with trace_path(str(idx)): if not cond(hass, variables): return False except exceptions.ConditionError as ex: @@ -666,7 +577,6 @@ class _ScriptRun: return True result = traced_test_conditions(self._hass, self._variables) - action_trace_add_conditions() return result async def _async_repeat_step(self): @@ -687,7 +597,7 @@ class _ScriptRun: async def async_run_sequence(iteration, extra_msg=""): self._log("Repeating %s: Iteration %i%s", description, iteration, extra_msg) - with action_path(str(self._step)): + with trace_path(str(self._step)): await self._async_run_script(script) if CONF_COUNT in repeat: @@ -754,18 +664,18 @@ class _ScriptRun: choose_data = await self._script._async_get_choose_data(self._step) for idx, (conditions, script) in enumerate(choose_data["choices"]): - with action_path(str(idx)): + with trace_path(str(idx)): try: if self._test_conditions(conditions, "choose"): - action_trace_set_result(choice=idx) + trace_set_result(choice=idx) await self._async_run_script(script) return except exceptions.ConditionError as ex: _LOGGER.warning("Error in 'choose' evaluation:\n%s", ex) if choose_data["default"]: - action_trace_set_result(choice="default") - with action_path("default"): + trace_set_result(choice="default") + with trace_path("default"): await self._async_run_script(choose_data["default"]) async def _async_wait_for_trigger_step(self): diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py index 450faa0336f..8c5c181ea24 100644 --- a/homeassistant/helpers/trace.py +++ b/homeassistant/helpers/trace.py @@ -1,33 +1,13 @@ """Helpers for script and condition tracing.""" from collections import deque +from contextlib import contextmanager from contextvars import ContextVar -from typing import Any, Dict, Optional +from typing import Any, Deque, Dict, Generator, List, Optional, Union, cast from homeassistant.helpers.typing import TemplateVarsType import homeassistant.util.dt as dt_util -def trace_stack_push(trace_stack_var: ContextVar, node: Any) -> None: - """Push an element to the top of a trace stack.""" - trace_stack = trace_stack_var.get() - if trace_stack is None: - trace_stack = [] - trace_stack_var.set(trace_stack) - trace_stack.append(node) - - -def trace_stack_pop(trace_stack_var: ContextVar) -> None: - """Remove the top element from a trace stack.""" - trace_stack = trace_stack_var.get() - trace_stack.pop() - - -def trace_stack_top(trace_stack_var: ContextVar) -> Optional[Any]: - """Return the element at the top of a trace stack.""" - trace_stack = trace_stack_var.get() - return trace_stack[-1] if trace_stack else None - - class TraceElement: """Container for trace data.""" @@ -62,17 +42,105 @@ class TraceElement: return result +# Context variables for tracing +# Current trace +trace_cv: ContextVar[Optional[Dict[str, Deque[TraceElement]]]] = ContextVar( + "trace_cv", default=None +) +# Stack of TraceElements +trace_stack_cv: ContextVar[Optional[List[TraceElement]]] = ContextVar( + "trace_stack_cv", default=None +) +# Current location in config tree +trace_path_stack_cv: ContextVar[Optional[List[str]]] = ContextVar( + "trace_path_stack_cv", default=None +) + + +def trace_stack_push(trace_stack_var: ContextVar, node: Any) -> None: + """Push an element to the top of a trace stack.""" + trace_stack = trace_stack_var.get() + if trace_stack is None: + trace_stack = [] + trace_stack_var.set(trace_stack) + trace_stack.append(node) + + +def trace_stack_pop(trace_stack_var: ContextVar) -> None: + """Remove the top element from a trace stack.""" + trace_stack = trace_stack_var.get() + trace_stack.pop() + + +def trace_stack_top(trace_stack_var: ContextVar) -> Optional[Any]: + """Return the element at the top of a trace stack.""" + trace_stack = trace_stack_var.get() + return trace_stack[-1] if trace_stack else None + + +def trace_path_push(suffix: Union[str, List[str]]) -> int: + """Go deeper in the config tree.""" + if isinstance(suffix, str): + suffix = [suffix] + for node in suffix: + trace_stack_push(trace_path_stack_cv, node) + return len(suffix) + + +def trace_path_pop(count: int) -> None: + """Go n levels up in the config tree.""" + for _ in range(count): + trace_stack_pop(trace_path_stack_cv) + + +def trace_path_get() -> str: + """Return a string representing the current location in the config tree.""" + path = trace_path_stack_cv.get() + if not path: + return "" + return "/".join(path) + + def trace_append_element( - trace_var: ContextVar, trace_element: TraceElement, path: str, maxlen: Optional[int] = None, ) -> None: """Append a TraceElement to trace[path].""" - trace = trace_var.get() + trace = trace_cv.get() if trace is None: - trace_var.set({}) - trace = trace_var.get() + trace = {} + trace_cv.set(trace) if path not in trace: trace[path] = deque(maxlen=maxlen) trace[path].append(trace_element) + + +def trace_get(clear: bool = True) -> Optional[Dict[str, Deque[TraceElement]]]: + """Return the current trace.""" + if clear: + trace_clear() + return trace_cv.get() + + +def trace_clear() -> None: + """Clear the trace.""" + trace_cv.set({}) + trace_stack_cv.set(None) + trace_path_stack_cv.set(None) + + +def trace_set_result(**kwargs: Any) -> None: + """Set the result of TraceElement at the top of the stack.""" + node = cast(TraceElement, trace_stack_top(trace_stack_cv)) + node.set_result(**kwargs) + + +@contextmanager +def trace_path(suffix: Union[str, List[str]]) -> Generator: + """Go deeper in the config tree.""" + count = trace_path_push(suffix) + try: + yield + finally: + trace_path_pop(count) diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index cfed8ebbcf6..c1bf727bc0f 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -4,7 +4,7 @@ from unittest.mock import patch import pytest from homeassistant.exceptions import ConditionError, HomeAssistantError -from homeassistant.helpers import condition +from homeassistant.helpers import condition, trace from homeassistant.helpers.template import Template from homeassistant.setup import async_setup_component from homeassistant.util import dt @@ -25,8 +25,8 @@ def assert_element(trace_element, expected_element, path): def assert_condition_trace(expected): """Assert a trace condition sequence is as expected.""" - condition_trace = condition.condition_trace_get() - condition.condition_trace_clear() + condition_trace = trace.trace_get(clear=False) + trace.trace_clear() expected_trace_keys = list(expected.keys()) assert list(condition_trace.keys()) == expected_trace_keys for trace_key_index, key in enumerate(expected_trace_keys): diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 04f922b685e..027254ee03e 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -17,7 +17,7 @@ from homeassistant import exceptions import homeassistant.components.scene as scene from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON from homeassistant.core import Context, CoreState, callback -from homeassistant.helpers import config_validation as cv, script +from homeassistant.helpers import config_validation as cv, script, trace from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -45,8 +45,8 @@ def assert_element(trace_element, expected_element, path): def assert_action_trace(expected): """Assert a trace condition sequence is as expected.""" - action_trace = script.action_trace_get() - script.action_trace_clear() + action_trace = trace.trace_get(clear=False) + trace.trace_clear() expected_trace_keys = list(expected.keys()) assert list(action_trace.keys()) == expected_trace_keys for trace_key_index, key in enumerate(expected_trace_keys): From 14f85d87314bb2bee4226962873f0c9de8034242 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Sat, 6 Mar 2021 23:40:49 +0800 Subject: [PATCH 201/831] Disable audio stream when ADTS AAC detected (#47441) * Disable audio stream when ADTS AAC detected * Use context manager for memoryview * Fix tests * Add test * Fix tests * Change FakePacket bytearray size to 3 --- homeassistant/components/stream/worker.py | 10 ++++++ tests/components/stream/test_worker.py | 37 ++++++++++++++++++++--- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 773170449e1..8d1df37d039 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -208,6 +208,16 @@ def stream_worker(source, options, segment_buffer, quit_event): missing_dts += 1 continue if packet.stream == audio_stream: + # detect ADTS AAC and disable audio + if audio_stream.codec.name == "aac" and packet.size > 2: + with memoryview(packet) as packet_view: + if packet_view[0] == 0xFF and packet_view[1] & 0xF0 == 0xF0: + _LOGGER.warning( + "ADTS AAC detected - disabling audio stream" + ) + container_packets = container.demux(video_stream) + audio_stream = None + continue found_audio = True elif ( segment_start_pts is None diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py index 2c202a290ce..bef5d366a8f 100644 --- a/tests/components/stream/test_worker.py +++ b/tests/components/stream/test_worker.py @@ -57,6 +57,11 @@ class FakePyAvStream: self.time_base = fractions.Fraction(1, rate) self.profile = "ignored-profile" + class FakeCodec: + name = "aac" + + self.codec = FakeCodec() + VIDEO_STREAM = FakePyAvStream(VIDEO_STREAM_FORMAT, VIDEO_FRAME_RATE) AUDIO_STREAM = FakePyAvStream(AUDIO_STREAM_FORMAT, AUDIO_SAMPLE_RATE) @@ -87,13 +92,18 @@ class PacketSequence: raise StopIteration self.packet += 1 - class FakePacket: + class FakePacket(bytearray): + # Be a bytearray so that memoryview works + def __init__(self): + super().__init__(3) + time_base = fractions.Fraction(1, VIDEO_FRAME_RATE) dts = self.packet * PACKET_DURATION / time_base pts = self.packet * PACKET_DURATION / time_base duration = PACKET_DURATION / time_base stream = VIDEO_STREAM is_keyframe = True + size = 3 return FakePacket() @@ -107,8 +117,8 @@ class FakePyAvContainer: self.packets = PacketSequence(0) class FakePyAvStreams: - video = video_stream - audio = audio_stream + video = [video_stream] if video_stream else [] + audio = [audio_stream] if audio_stream else [] self.streams = FakePyAvStreams() @@ -171,8 +181,8 @@ class MockPyAv: def __init__(self, video=True, audio=False): """Initialize the MockPyAv.""" - video_stream = [VIDEO_STREAM] if video else [] - audio_stream = [AUDIO_STREAM] if audio else [] + video_stream = VIDEO_STREAM if video else None + audio_stream = AUDIO_STREAM if audio else None self.container = FakePyAvContainer( video_stream=video_stream, audio_stream=audio_stream ) @@ -413,6 +423,23 @@ async def test_audio_packets_not_found(hass): assert len(decoded_stream.audio_packets) == 0 +async def test_adts_aac_audio(hass): + """Set up an ADTS AAC audio stream and disable audio.""" + py_av = MockPyAv(audio=True) + + num_packets = PACKETS_TO_WAIT_FOR_AUDIO + 1 + packets = list(PacketSequence(num_packets)) + packets[1].stream = AUDIO_STREAM + packets[1].dts = packets[0].dts / VIDEO_FRAME_RATE * AUDIO_SAMPLE_RATE + packets[1].pts = packets[0].pts / VIDEO_FRAME_RATE * AUDIO_SAMPLE_RATE + # The following is packet data is a sign of ADTS AAC + packets[1][0] = 255 + packets[1][1] = 241 + + decoded_stream = await async_decode_stream(hass, iter(packets), py_av=py_av) + assert len(decoded_stream.audio_packets) == 0 + + async def test_audio_is_first_packet(hass): """Set up an audio stream and audio packet is the first packet in the stream.""" py_av = MockPyAv(audio=True) From e9052233a626a4c54e4efca9bdd3ce2845f8c3f9 Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Sat, 6 Mar 2021 09:28:33 -0700 Subject: [PATCH 202/831] Adjust litterrobot tests and code to match guidelines (#47060) * Use SwitchEntity instead of ToggleEntity and adjust test patches as recommended * Move async_create_entry out of try block in config_flow * Patch pypi package instead of HA code * Bump pylitterbot to 2021.2.6, fix tests, and implement other code review suggestions * Bump pylitterbot to 2021.2.8, remove sleep mode start/end time from vacuum, adjust and add sensors for sleep mode start/end time * Move icon helper back to Litter-Robot component and isoformat times on time sensors --- .../components/litterrobot/config_flow.py | 8 +- homeassistant/components/litterrobot/hub.py | 21 ++-- .../components/litterrobot/manifest.json | 2 +- .../components/litterrobot/sensor.py | 95 +++++++++++++------ .../components/litterrobot/switch.py | 6 +- .../components/litterrobot/vacuum.py | 37 ++------ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/litterrobot/conftest.py | 72 ++++++++------ .../litterrobot/test_config_flow.py | 33 +++++-- tests/components/litterrobot/test_init.py | 38 +++++++- tests/components/litterrobot/test_sensor.py | 57 +++++++++-- tests/components/litterrobot/test_switch.py | 14 +-- tests/components/litterrobot/test_vacuum.py | 32 +++++-- 14 files changed, 277 insertions(+), 142 deletions(-) diff --git a/homeassistant/components/litterrobot/config_flow.py b/homeassistant/components/litterrobot/config_flow.py index d6c92d8dad6..36fc2064abb 100644 --- a/homeassistant/components/litterrobot/config_flow.py +++ b/homeassistant/components/litterrobot/config_flow.py @@ -35,9 +35,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): hub = LitterRobotHub(self.hass, user_input) try: await hub.login() - return self.async_create_entry( - title=user_input[CONF_USERNAME], data=user_input - ) except LitterRobotLoginException: errors["base"] = "invalid_auth" except LitterRobotException: @@ -46,6 +43,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" + if not errors: + return self.async_create_entry( + title=user_input[CONF_USERNAME], data=user_input + ) + return self.async_show_form( step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors ) diff --git a/homeassistant/components/litterrobot/hub.py b/homeassistant/components/litterrobot/hub.py index 0d0559140c7..943ef5bfe37 100644 --- a/homeassistant/components/litterrobot/hub.py +++ b/homeassistant/components/litterrobot/hub.py @@ -4,7 +4,7 @@ import logging from types import MethodType from typing import Any, Optional -from pylitterbot import Account, Robot +import pylitterbot from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException from homeassistant.const import CONF_PASSWORD, CONF_USERNAME @@ -49,7 +49,7 @@ class LitterRobotHub: async def login(self, load_robots: bool = False): """Login to Litter-Robot.""" self.logged_in = False - self.account = Account() + self.account = pylitterbot.Account() try: await self.account.connect( username=self._data[CONF_USERNAME], @@ -69,11 +69,11 @@ class LitterRobotHub: class LitterRobotEntity(CoordinatorEntity): """Generic Litter-Robot entity representing common data and methods.""" - def __init__(self, robot: Robot, entity_type: str, hub: LitterRobotHub): + def __init__(self, robot: pylitterbot.Robot, entity_type: str, hub: LitterRobotHub): """Pass coordinator to CoordinatorEntity.""" super().__init__(hub.coordinator) self.robot = robot - self.entity_type = entity_type if entity_type else "" + self.entity_type = entity_type self.hub = hub @property @@ -89,22 +89,21 @@ class LitterRobotEntity(CoordinatorEntity): @property def device_info(self): """Return the device information for a Litter-Robot.""" - model = "Litter-Robot 3 Connect" - if not self.robot.serial.startswith("LR3C"): - model = "Other Litter-Robot Connected Device" return { "identifiers": {(DOMAIN, self.robot.serial)}, "name": self.robot.name, "manufacturer": "Litter-Robot", - "model": model, + "model": self.robot.model, } async def perform_action_and_refresh(self, action: MethodType, *args: Any): """Perform an action and initiates a refresh of the robot data after a few seconds.""" + + async def async_call_later_callback(*_) -> None: + await self.hub.coordinator.async_request_refresh() + await action(*args) - async_call_later( - self.hass, REFRESH_WAIT_TIME, self.hub.coordinator.async_request_refresh - ) + async_call_later(self.hass, REFRESH_WAIT_TIME, async_call_later_callback) @staticmethod def parse_time_at_default_timezone(time_str: str) -> Optional[time]: diff --git a/homeassistant/components/litterrobot/manifest.json b/homeassistant/components/litterrobot/manifest.json index 1c6ac7274bf..8fa7ab8dcb5 100644 --- a/homeassistant/components/litterrobot/manifest.json +++ b/homeassistant/components/litterrobot/manifest.json @@ -3,6 +3,6 @@ "name": "Litter-Robot", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/litterrobot", - "requirements": ["pylitterbot==2021.2.5"], + "requirements": ["pylitterbot==2021.2.8"], "codeowners": ["@natekspencer"] } diff --git a/homeassistant/components/litterrobot/sensor.py b/homeassistant/components/litterrobot/sensor.py index 2843660bcee..8900c6c54ca 100644 --- a/homeassistant/components/litterrobot/sensor.py +++ b/homeassistant/components/litterrobot/sensor.py @@ -1,32 +1,44 @@ """Support for Litter-Robot sensors.""" -from homeassistant.const import PERCENTAGE +from typing import Optional + +from pylitterbot.robot import Robot + +from homeassistant.const import DEVICE_CLASS_TIMESTAMP, PERCENTAGE from homeassistant.helpers.entity import Entity from .const import DOMAIN -from .hub import LitterRobotEntity - -WASTE_DRAWER = "Waste Drawer" +from .hub import LitterRobotEntity, LitterRobotHub -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up Litter-Robot sensors using config entry.""" - hub = hass.data[DOMAIN][config_entry.entry_id] - - entities = [] - for robot in hub.account.robots: - entities.append(LitterRobotSensor(robot, WASTE_DRAWER, hub)) - - if entities: - async_add_entities(entities, True) +def icon_for_gauge_level(gauge_level: Optional[int] = None, offset: int = 0) -> str: + """Return a gauge icon valid identifier.""" + if gauge_level is None or gauge_level <= 0 + offset: + return "mdi:gauge-empty" + if gauge_level > 70 + offset: + return "mdi:gauge-full" + if gauge_level > 30 + offset: + return "mdi:gauge" + return "mdi:gauge-low" -class LitterRobotSensor(LitterRobotEntity, Entity): - """Litter-Robot sensors.""" +class LitterRobotPropertySensor(LitterRobotEntity, Entity): + """Litter-Robot property sensors.""" + + def __init__( + self, robot: Robot, entity_type: str, hub: LitterRobotHub, sensor_attribute: str + ): + """Pass coordinator to CoordinatorEntity.""" + super().__init__(robot, entity_type, hub) + self.sensor_attribute = sensor_attribute @property def state(self): """Return the state.""" - return self.robot.waste_drawer_gauge + return getattr(self.robot, self.sensor_attribute) + + +class LitterRobotWasteSensor(LitterRobotPropertySensor, Entity): + """Litter-Robot sensors.""" @property def unit_of_measurement(self): @@ -36,19 +48,40 @@ class LitterRobotSensor(LitterRobotEntity, Entity): @property def icon(self): """Return the icon to use in the frontend, if any.""" - if self.robot.waste_drawer_gauge <= 10: - return "mdi:gauge-empty" - if self.robot.waste_drawer_gauge < 50: - return "mdi:gauge-low" - if self.robot.waste_drawer_gauge <= 90: - return "mdi:gauge" - return "mdi:gauge-full" + return icon_for_gauge_level(self.state, 10) + + +class LitterRobotSleepTimeSensor(LitterRobotPropertySensor, Entity): + """Litter-Robot sleep time sensors.""" @property - def device_state_attributes(self): - """Return device specific state attributes.""" - return { - "cycle_count": self.robot.cycle_count, - "cycle_capacity": self.robot.cycle_capacity, - "cycles_after_drawer_full": self.robot.cycles_after_drawer_full, - } + def state(self): + """Return the state.""" + if self.robot.sleep_mode_active: + return super().state.isoformat() + return None + + @property + def device_class(self): + """Return the device class, if any.""" + return DEVICE_CLASS_TIMESTAMP + + +ROBOT_SENSORS = [ + (LitterRobotWasteSensor, "Waste Drawer", "waste_drawer_gauge"), + (LitterRobotSleepTimeSensor, "Sleep Mode Start Time", "sleep_mode_start_time"), + (LitterRobotSleepTimeSensor, "Sleep Mode End Time", "sleep_mode_end_time"), +] + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Litter-Robot sensors using config entry.""" + hub = hass.data[DOMAIN][config_entry.entry_id] + + entities = [] + for robot in hub.account.robots: + for (sensor_class, entity_type, sensor_attribute) in ROBOT_SENSORS: + entities.append(sensor_class(robot, entity_type, hub, sensor_attribute)) + + if entities: + async_add_entities(entities, True) diff --git a/homeassistant/components/litterrobot/switch.py b/homeassistant/components/litterrobot/switch.py index b94b29a35e1..9164cc35e90 100644 --- a/homeassistant/components/litterrobot/switch.py +++ b/homeassistant/components/litterrobot/switch.py @@ -1,11 +1,11 @@ """Support for Litter-Robot switches.""" -from homeassistant.helpers.entity import ToggleEntity +from homeassistant.components.switch import SwitchEntity from .const import DOMAIN from .hub import LitterRobotEntity -class LitterRobotNightLightModeSwitch(LitterRobotEntity, ToggleEntity): +class LitterRobotNightLightModeSwitch(LitterRobotEntity, SwitchEntity): """Litter-Robot Night Light Mode Switch.""" @property @@ -27,7 +27,7 @@ class LitterRobotNightLightModeSwitch(LitterRobotEntity, ToggleEntity): await self.perform_action_and_refresh(self.robot.set_night_light, False) -class LitterRobotPanelLockoutSwitch(LitterRobotEntity, ToggleEntity): +class LitterRobotPanelLockoutSwitch(LitterRobotEntity, SwitchEntity): """Litter-Robot Panel Lockout Switch.""" @property diff --git a/homeassistant/components/litterrobot/vacuum.py b/homeassistant/components/litterrobot/vacuum.py index 6ee92993869..4fe76d446f4 100644 --- a/homeassistant/components/litterrobot/vacuum.py +++ b/homeassistant/components/litterrobot/vacuum.py @@ -14,7 +14,6 @@ from homeassistant.components.vacuum import ( VacuumEntity, ) from homeassistant.const import STATE_OFF -import homeassistant.util.dt as dt_util from .const import DOMAIN from .hub import LitterRobotEntity @@ -54,27 +53,22 @@ class LitterRobotCleaner(LitterRobotEntity, VacuumEntity): def state(self): """Return the state of the cleaner.""" switcher = { - Robot.UnitStatus.CCP: STATE_CLEANING, - Robot.UnitStatus.EC: STATE_CLEANING, - Robot.UnitStatus.CCC: STATE_DOCKED, - Robot.UnitStatus.CST: STATE_DOCKED, - Robot.UnitStatus.DF1: STATE_DOCKED, - Robot.UnitStatus.DF2: STATE_DOCKED, - Robot.UnitStatus.RDY: STATE_DOCKED, + Robot.UnitStatus.CLEAN_CYCLE: STATE_CLEANING, + Robot.UnitStatus.EMPTY_CYCLE: STATE_CLEANING, + Robot.UnitStatus.CLEAN_CYCLE_COMPLETE: STATE_DOCKED, + Robot.UnitStatus.CAT_SENSOR_TIMING: STATE_DOCKED, + Robot.UnitStatus.DRAWER_FULL_1: STATE_DOCKED, + Robot.UnitStatus.DRAWER_FULL_2: STATE_DOCKED, + Robot.UnitStatus.READY: STATE_DOCKED, Robot.UnitStatus.OFF: STATE_OFF, } return switcher.get(self.robot.unit_status, STATE_ERROR) - @property - def error(self): - """Return the error associated with the current state, if any.""" - return self.robot.unit_status.value - @property def status(self): """Return the status of the cleaner.""" - return f"{self.robot.unit_status.value}{' (Sleeping)' if self.robot.is_sleeping else ''}" + return f"{self.robot.unit_status.label}{' (Sleeping)' if self.robot.is_sleeping else ''}" async def async_turn_on(self, **kwargs): """Turn the cleaner on, starting a clean cycle.""" @@ -119,22 +113,11 @@ class LitterRobotCleaner(LitterRobotEntity, VacuumEntity): @property def device_state_attributes(self): """Return device specific state attributes.""" - [sleep_mode_start_time, sleep_mode_end_time] = [None, None] - - if self.robot.sleep_mode_active: - sleep_mode_start_time = dt_util.as_local( - self.robot.sleep_mode_start_time - ).strftime("%H:%M:00") - sleep_mode_end_time = dt_util.as_local( - self.robot.sleep_mode_end_time - ).strftime("%H:%M:00") - return { "clean_cycle_wait_time_minutes": self.robot.clean_cycle_wait_time_minutes, "is_sleeping": self.robot.is_sleeping, - "sleep_mode_start_time": sleep_mode_start_time, - "sleep_mode_end_time": sleep_mode_end_time, + "sleep_mode_active": self.robot.sleep_mode_active, "power_status": self.robot.power_status, - "unit_status_code": self.robot.unit_status.name, + "unit_status_code": self.robot.unit_status.value, "last_seen": self.robot.last_seen, } diff --git a/requirements_all.txt b/requirements_all.txt index 46c25915af9..2e0e0a16ad9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1501,7 +1501,7 @@ pylibrespot-java==0.1.0 pylitejet==0.3.0 # homeassistant.components.litterrobot -pylitterbot==2021.2.5 +pylitterbot==2021.2.8 # homeassistant.components.loopenergy pyloopenergy==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4404d099050..c5c96aefd78 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -791,7 +791,7 @@ pylibrespot-java==0.1.0 pylitejet==0.3.0 # homeassistant.components.litterrobot -pylitterbot==2021.2.5 +pylitterbot==2021.2.8 # homeassistant.components.lutron_caseta pylutron-caseta==0.9.0 diff --git a/tests/components/litterrobot/conftest.py b/tests/components/litterrobot/conftest.py index dae183b4cf6..aadf7d810aa 100644 --- a/tests/components/litterrobot/conftest.py +++ b/tests/components/litterrobot/conftest.py @@ -1,45 +1,59 @@ """Configure pytest for Litter-Robot tests.""" +from typing import Optional from unittest.mock import AsyncMock, MagicMock, patch +import pylitterbot from pylitterbot import Robot import pytest from homeassistant.components import litterrobot -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .common import CONFIG, ROBOT_DATA from tests.common import MockConfigEntry -def create_mock_robot(hass): +def create_mock_robot(unit_status_code: Optional[str] = None): """Create a mock Litter-Robot device.""" - robot = Robot(data=ROBOT_DATA) - robot.start_cleaning = AsyncMock() - robot.set_power_status = AsyncMock() - robot.reset_waste_drawer = AsyncMock() - robot.set_sleep_mode = AsyncMock() - robot.set_night_light = AsyncMock() - robot.set_panel_lockout = AsyncMock() - return robot + if not ( + unit_status_code + and Robot.UnitStatus(unit_status_code) != Robot.UnitStatus.UNKNOWN + ): + unit_status_code = ROBOT_DATA["unitStatus"] + + with patch.dict(ROBOT_DATA, {"unitStatus": unit_status_code}): + robot = Robot(data=ROBOT_DATA) + robot.start_cleaning = AsyncMock() + robot.set_power_status = AsyncMock() + robot.reset_waste_drawer = AsyncMock() + robot.set_sleep_mode = AsyncMock() + robot.set_night_light = AsyncMock() + robot.set_panel_lockout = AsyncMock() + return robot -@pytest.fixture() -def mock_hub(hass): - """Mock a Litter-Robot hub.""" - hub = MagicMock( - hass=hass, - account=MagicMock(), - logged_in=True, - coordinator=MagicMock(spec=DataUpdateCoordinator), - spec=litterrobot.LitterRobotHub, - ) - hub.coordinator.last_update_success = True - hub.account.robots = [create_mock_robot(hass)] - return hub +def create_mock_account(unit_status_code: Optional[str] = None): + """Create a mock Litter-Robot account.""" + account = MagicMock(spec=pylitterbot.Account) + account.connect = AsyncMock() + account.refresh_robots = AsyncMock() + account.robots = [create_mock_robot(unit_status_code)] + return account -async def setup_hub(hass, mock_hub, platform_domain): +@pytest.fixture +def mock_account(): + """Mock a Litter-Robot account.""" + return create_mock_account() + + +@pytest.fixture +def mock_account_with_error(): + """Mock a Litter-Robot account with error.""" + return create_mock_account("BR") + + +async def setup_integration(hass, mock_account, platform_domain=None): """Load a Litter-Robot platform with the provided hub.""" entry = MockConfigEntry( domain=litterrobot.DOMAIN, @@ -47,9 +61,11 @@ async def setup_hub(hass, mock_hub, platform_domain): ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.litterrobot.LitterRobotHub", - return_value=mock_hub, + with patch("pylitterbot.Account", return_value=mock_account), patch( + "homeassistant.components.litterrobot.PLATFORMS", + [platform_domain] if platform_domain else [], ): - await hass.config_entries.async_forward_entry_setup(entry, platform_domain) + await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() + + return entry diff --git a/tests/components/litterrobot/test_config_flow.py b/tests/components/litterrobot/test_config_flow.py index fd88595d37e..5068ecf721b 100644 --- a/tests/components/litterrobot/test_config_flow.py +++ b/tests/components/litterrobot/test_config_flow.py @@ -4,11 +4,14 @@ from unittest.mock import patch from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException from homeassistant import config_entries, setup +from homeassistant.components import litterrobot from .common import CONF_USERNAME, CONFIG, DOMAIN +from tests.common import MockConfigEntry -async def test_form(hass): + +async def test_form(hass, mock_account): """Test we get the form.""" await setup.async_setup_component(hass, "persistent_notification", {}) result = await hass.config_entries.flow.async_init( @@ -17,10 +20,7 @@ async def test_form(hass): assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.litterrobot.config_flow.LitterRobotHub.login", - return_value=True, - ), patch( + with patch("pylitterbot.Account", return_value=mock_account), patch( "homeassistant.components.litterrobot.async_setup", return_value=True ) as mock_setup, patch( "homeassistant.components.litterrobot.async_setup_entry", @@ -38,6 +38,23 @@ async def test_form(hass): assert len(mock_setup_entry.mock_calls) == 1 +async def test_already_configured(hass): + """Test we handle already configured.""" + MockConfigEntry( + domain=litterrobot.DOMAIN, + data=CONFIG[litterrobot.DOMAIN], + ).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + data=CONFIG[litterrobot.DOMAIN], + ) + + assert result["type"] == "abort" + assert result["reason"] == "already_configured" + + async def test_form_invalid_auth(hass): """Test we handle invalid auth.""" result = await hass.config_entries.flow.async_init( @@ -45,7 +62,7 @@ async def test_form_invalid_auth(hass): ) with patch( - "homeassistant.components.litterrobot.config_flow.LitterRobotHub.login", + "pylitterbot.Account.connect", side_effect=LitterRobotLoginException, ): result2 = await hass.config_entries.flow.async_configure( @@ -63,7 +80,7 @@ async def test_form_cannot_connect(hass): ) with patch( - "homeassistant.components.litterrobot.config_flow.LitterRobotHub.login", + "pylitterbot.Account.connect", side_effect=LitterRobotException, ): result2 = await hass.config_entries.flow.async_configure( @@ -81,7 +98,7 @@ async def test_form_unknown_error(hass): ) with patch( - "homeassistant.components.litterrobot.config_flow.LitterRobotHub.login", + "pylitterbot.Account.connect", side_effect=Exception, ): result2 = await hass.config_entries.flow.async_configure( diff --git a/tests/components/litterrobot/test_init.py b/tests/components/litterrobot/test_init.py index 1d0ed075cc7..7cd36f33883 100644 --- a/tests/components/litterrobot/test_init.py +++ b/tests/components/litterrobot/test_init.py @@ -1,20 +1,48 @@ """Test Litter-Robot setup process.""" +from unittest.mock import patch + +from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException +import pytest + from homeassistant.components import litterrobot -from homeassistant.setup import async_setup_component +from homeassistant.config_entries import ( + ENTRY_STATE_SETUP_ERROR, + ENTRY_STATE_SETUP_RETRY, +) from .common import CONFIG +from .conftest import setup_integration from tests.common import MockConfigEntry -async def test_unload_entry(hass): +async def test_unload_entry(hass, mock_account): """Test being able to unload an entry.""" + entry = await setup_integration(hass, mock_account) + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + assert hass.data[litterrobot.DOMAIN] == {} + + +@pytest.mark.parametrize( + "side_effect,expected_state", + ( + (LitterRobotLoginException, ENTRY_STATE_SETUP_ERROR), + (LitterRobotException, ENTRY_STATE_SETUP_RETRY), + ), +) +async def test_entry_not_setup(hass, side_effect, expected_state): + """Test being able to handle config entry not setup.""" entry = MockConfigEntry( domain=litterrobot.DOMAIN, data=CONFIG[litterrobot.DOMAIN], ) entry.add_to_hass(hass) - assert await async_setup_component(hass, litterrobot.DOMAIN, {}) is True - assert await litterrobot.async_unload_entry(hass, entry) - assert hass.data[litterrobot.DOMAIN] == {} + with patch( + "pylitterbot.Account.connect", + side_effect=side_effect, + ): + await hass.config_entries.async_setup(entry.entry_id) + assert entry.state == expected_state diff --git a/tests/components/litterrobot/test_sensor.py b/tests/components/litterrobot/test_sensor.py index 2421489e237..7f1570c553e 100644 --- a/tests/components/litterrobot/test_sensor.py +++ b/tests/components/litterrobot/test_sensor.py @@ -1,20 +1,57 @@ """Test the Litter-Robot sensor entity.""" +from unittest.mock import Mock + +from homeassistant.components.litterrobot.sensor import LitterRobotSleepTimeSensor from homeassistant.components.sensor import DOMAIN as PLATFORM_DOMAIN -from homeassistant.const import PERCENTAGE +from homeassistant.const import DEVICE_CLASS_TIMESTAMP, PERCENTAGE -from .conftest import setup_hub +from .conftest import create_mock_robot, setup_integration -ENTITY_ID = "sensor.test_waste_drawer" +WASTE_DRAWER_ENTITY_ID = "sensor.test_waste_drawer" -async def test_sensor(hass, mock_hub): - """Tests the sensor entity was set up.""" - await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) +async def test_waste_drawer_sensor(hass, mock_account): + """Tests the waste drawer sensor entity was set up.""" + await setup_integration(hass, mock_account, PLATFORM_DOMAIN) - sensor = hass.states.get(ENTITY_ID) + sensor = hass.states.get(WASTE_DRAWER_ENTITY_ID) assert sensor assert sensor.state == "50" - assert sensor.attributes["cycle_count"] == 15 - assert sensor.attributes["cycle_capacity"] == 30 - assert sensor.attributes["cycles_after_drawer_full"] == 0 assert sensor.attributes["unit_of_measurement"] == PERCENTAGE + + +async def test_sleep_time_sensor_with_none_state(hass): + """Tests the sleep mode start time sensor where sleep mode is inactive.""" + robot = create_mock_robot() + robot.sleep_mode_active = False + sensor = LitterRobotSleepTimeSensor( + robot, "Sleep Mode Start Time", Mock(), "sleep_mode_start_time" + ) + + assert sensor + assert sensor.state is None + assert sensor.device_class == DEVICE_CLASS_TIMESTAMP + + +async def test_gauge_icon(): + """Test icon generator for gauge sensor.""" + from homeassistant.components.litterrobot.sensor import icon_for_gauge_level + + GAUGE_EMPTY = "mdi:gauge-empty" + GAUGE_LOW = "mdi:gauge-low" + GAUGE = "mdi:gauge" + GAUGE_FULL = "mdi:gauge-full" + + assert icon_for_gauge_level(None) == GAUGE_EMPTY + assert icon_for_gauge_level(0) == GAUGE_EMPTY + assert icon_for_gauge_level(5) == GAUGE_LOW + assert icon_for_gauge_level(40) == GAUGE + assert icon_for_gauge_level(80) == GAUGE_FULL + assert icon_for_gauge_level(100) == GAUGE_FULL + + assert icon_for_gauge_level(None, 10) == GAUGE_EMPTY + assert icon_for_gauge_level(0, 10) == GAUGE_EMPTY + assert icon_for_gauge_level(5, 10) == GAUGE_EMPTY + assert icon_for_gauge_level(40, 10) == GAUGE_LOW + assert icon_for_gauge_level(80, 10) == GAUGE + assert icon_for_gauge_level(100, 10) == GAUGE_FULL diff --git a/tests/components/litterrobot/test_switch.py b/tests/components/litterrobot/test_switch.py index c7f85db7412..69154bef8f5 100644 --- a/tests/components/litterrobot/test_switch.py +++ b/tests/components/litterrobot/test_switch.py @@ -12,7 +12,7 @@ from homeassistant.components.switch import ( from homeassistant.const import ATTR_ENTITY_ID, STATE_ON from homeassistant.util.dt import utcnow -from .conftest import setup_hub +from .conftest import setup_integration from tests.common import async_fire_time_changed @@ -20,9 +20,9 @@ NIGHT_LIGHT_MODE_ENTITY_ID = "switch.test_night_light_mode" PANEL_LOCKOUT_ENTITY_ID = "switch.test_panel_lockout" -async def test_switch(hass, mock_hub): +async def test_switch(hass, mock_account): """Tests the switch entity was set up.""" - await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) + await setup_integration(hass, mock_account, PLATFORM_DOMAIN) switch = hass.states.get(NIGHT_LIGHT_MODE_ENTITY_ID) assert switch @@ -36,9 +36,9 @@ async def test_switch(hass, mock_hub): (PANEL_LOCKOUT_ENTITY_ID, "set_panel_lockout"), ], ) -async def test_on_off_commands(hass, mock_hub, entity_id, robot_command): +async def test_on_off_commands(hass, mock_account, entity_id, robot_command): """Test sending commands to the switch.""" - await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) + await setup_integration(hass, mock_account, PLATFORM_DOMAIN) switch = hass.states.get(entity_id) assert switch @@ -48,12 +48,14 @@ async def test_on_off_commands(hass, mock_hub, entity_id, robot_command): count = 0 for service in [SERVICE_TURN_ON, SERVICE_TURN_OFF]: count += 1 + await hass.services.async_call( PLATFORM_DOMAIN, service, data, blocking=True, ) + future = utcnow() + timedelta(seconds=REFRESH_WAIT_TIME) async_fire_time_changed(hass, future) - assert getattr(mock_hub.account.robots[0], robot_command).call_count == count + assert getattr(mock_account.robots[0], robot_command).call_count == count diff --git a/tests/components/litterrobot/test_vacuum.py b/tests/components/litterrobot/test_vacuum.py index 03e63b472b6..2db2ef21546 100644 --- a/tests/components/litterrobot/test_vacuum.py +++ b/tests/components/litterrobot/test_vacuum.py @@ -12,20 +12,21 @@ from homeassistant.components.vacuum import ( SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_DOCKED, + STATE_ERROR, ) from homeassistant.const import ATTR_COMMAND, ATTR_ENTITY_ID from homeassistant.util.dt import utcnow -from .conftest import setup_hub +from .conftest import setup_integration from tests.common import async_fire_time_changed ENTITY_ID = "vacuum.test_litter_box" -async def test_vacuum(hass, mock_hub): +async def test_vacuum(hass, mock_account): """Tests the vacuum entity was set up.""" - await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) + await setup_integration(hass, mock_account, PLATFORM_DOMAIN) vacuum = hass.states.get(ENTITY_ID) assert vacuum @@ -33,6 +34,15 @@ async def test_vacuum(hass, mock_hub): assert vacuum.attributes["is_sleeping"] is False +async def test_vacuum_with_error(hass, mock_account_with_error): + """Tests a vacuum entity with an error.""" + await setup_integration(hass, mock_account_with_error, PLATFORM_DOMAIN) + + vacuum = hass.states.get(ENTITY_ID) + assert vacuum + assert vacuum.state == STATE_ERROR + + @pytest.mark.parametrize( "service,command,extra", [ @@ -52,14 +62,22 @@ async def test_vacuum(hass, mock_hub): ATTR_PARAMS: {"enabled": True, "sleep_time": "22:30"}, }, ), + ( + SERVICE_SEND_COMMAND, + "set_sleep_mode", + { + ATTR_COMMAND: "set_sleep_mode", + ATTR_PARAMS: {"enabled": True, "sleep_time": None}, + }, + ), ], ) -async def test_commands(hass, mock_hub, service, command, extra): +async def test_commands(hass, mock_account, service, command, extra): """Test sending commands to the vacuum.""" - await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) + await setup_integration(hass, mock_account, PLATFORM_DOMAIN) vacuum = hass.states.get(ENTITY_ID) - assert vacuum is not None + assert vacuum assert vacuum.state == STATE_DOCKED data = {ATTR_ENTITY_ID: ENTITY_ID} @@ -74,4 +92,4 @@ async def test_commands(hass, mock_hub, service, command, extra): ) future = utcnow() + timedelta(seconds=REFRESH_WAIT_TIME) async_fire_time_changed(hass, future) - getattr(mock_hub.account.robots[0], command).assert_called_once() + getattr(mock_account.robots[0], command).assert_called_once() From 1600207f5cd73ecd70bf965f1787a7f5aae6b81d Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 6 Mar 2021 18:33:55 +0100 Subject: [PATCH 203/831] Fix mysensors notify platform (#47517) --- .../components/mysensors/__init__.py | 47 ++++++++++++++----- .../components/mysensors/device_tracker.py | 3 ++ homeassistant/components/mysensors/helpers.py | 4 +- homeassistant/components/mysensors/notify.py | 3 ++ 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index 33ac4932889..f2478da5b57 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -1,5 +1,6 @@ """Connect to a MySensors gateway via pymysensors API.""" import asyncio +from functools import partial import logging from typing import Callable, Dict, List, Optional, Tuple, Type, Union @@ -8,10 +9,13 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components.mqtt import valid_publish_topic, valid_subscribe_topic +from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_OPTIMISTIC -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import async_load_platform +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import ConfigType, HomeAssistantType from .const import ( @@ -28,6 +32,7 @@ from .const import ( CONF_TOPIC_OUT_PREFIX, CONF_VERSION, DOMAIN, + MYSENSORS_DISCOVERY, MYSENSORS_GATEWAYS, MYSENSORS_ON_UNLOAD, PLATFORMS_WITH_ENTRY_SUPPORT, @@ -43,6 +48,8 @@ _LOGGER = logging.getLogger(__name__) CONF_DEBUG = "debug" CONF_NODE_NAME = "name" +DATA_HASS_CONFIG = "hass_config" + DEFAULT_BAUD_RATE = 115200 DEFAULT_TCP_PORT = 5003 DEFAULT_VERSION = "1.4" @@ -134,6 +141,8 @@ CONFIG_SCHEMA = vol.Schema( async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: """Set up the MySensors component.""" + hass.data[DOMAIN] = {DATA_HASS_CONFIG: config} + if DOMAIN not in config or bool(hass.config_entries.async_entries(DOMAIN)): return True @@ -181,14 +190,31 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool _LOGGER.error("Gateway setup failed for %s", entry.data) return False - if DOMAIN not in hass.data: - hass.data[DOMAIN] = {} - if MYSENSORS_GATEWAYS not in hass.data[DOMAIN]: hass.data[DOMAIN][MYSENSORS_GATEWAYS] = {} hass.data[DOMAIN][MYSENSORS_GATEWAYS][entry.entry_id] = gateway - async def finish(): + # Connect notify discovery as that integration doesn't support entry forwarding. + + load_notify_platform = partial( + async_load_platform, + hass, + NOTIFY_DOMAIN, + DOMAIN, + hass_config=hass.data[DOMAIN][DATA_HASS_CONFIG], + ) + + await on_unload( + hass, + entry.entry_id, + async_dispatcher_connect( + hass, + MYSENSORS_DISCOVERY.format(entry.entry_id, NOTIFY_DOMAIN), + load_notify_platform, + ), + ) + + async def finish() -> None: await asyncio.gather( *[ hass.config_entries.async_forward_entry_setup(entry, platform) @@ -248,14 +274,14 @@ async def on_unload( @callback def setup_mysensors_platform( - hass, + hass: HomeAssistant, domain: str, # hass platform name - discovery_info: Optional[Dict[str, List[DevId]]], + discovery_info: Dict[str, List[DevId]], device_class: Union[Type[MySensorsDevice], Dict[SensorType, Type[MySensorsEntity]]], device_args: Optional[ Tuple ] = None, # extra arguments that will be given to the entity constructor - async_add_entities: Callable = None, + async_add_entities: Optional[Callable] = None, ) -> Optional[List[MySensorsDevice]]: """Set up a MySensors platform. @@ -264,11 +290,6 @@ def setup_mysensors_platform( The function is also given a class. A new instance of the class is created for every device id, and the device id is given to the constructor of the class """ - # Only act if called via MySensors by discovery event. - # Otherwise gateway is not set up. - if not discovery_info: - _LOGGER.debug("Skipping setup due to no discovery info") - return None if device_args is None: device_args = () new_devices: List[MySensorsDevice] = [] diff --git a/homeassistant/components/mysensors/device_tracker.py b/homeassistant/components/mysensors/device_tracker.py index b395a48f28b..d1f89e4fe04 100644 --- a/homeassistant/components/mysensors/device_tracker.py +++ b/homeassistant/components/mysensors/device_tracker.py @@ -12,6 +12,9 @@ async def async_setup_scanner( hass: HomeAssistantType, config, async_see, discovery_info=None ): """Set up the MySensors device scanner.""" + if not discovery_info: + return False + new_devices = mysensors.setup_mysensors_platform( hass, DOMAIN, diff --git a/homeassistant/components/mysensors/helpers.py b/homeassistant/components/mysensors/helpers.py index d06bf0dee2f..4452dd0575b 100644 --- a/homeassistant/components/mysensors/helpers.py +++ b/homeassistant/components/mysensors/helpers.py @@ -9,7 +9,7 @@ from mysensors.sensor import ChildSensor import voluptuous as vol from homeassistant.const import CONF_NAME -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.util.decorator import Registry @@ -33,7 +33,7 @@ SCHEMAS = Registry() @callback def discover_mysensors_platform( - hass, gateway_id: GatewayId, platform: str, new_devices: List[DevId] + hass: HomeAssistant, gateway_id: GatewayId, platform: str, new_devices: List[DevId] ) -> None: """Discover a MySensors platform.""" _LOGGER.debug("Discovering platform %s with devIds: %s", platform, new_devices) diff --git a/homeassistant/components/mysensors/notify.py b/homeassistant/components/mysensors/notify.py index 99e731762df..50fca55ab39 100644 --- a/homeassistant/components/mysensors/notify.py +++ b/homeassistant/components/mysensors/notify.py @@ -5,6 +5,9 @@ from homeassistant.components.notify import ATTR_TARGET, DOMAIN, BaseNotificatio async def async_get_service(hass, config, discovery_info=None): """Get the MySensors notification service.""" + if not discovery_info: + return None + new_devices = mysensors.setup_mysensors_platform( hass, DOMAIN, discovery_info, MySensorsNotificationDevice ) From d944bbbc5256aade0db0d594aaa91632fe2ab0d8 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 6 Mar 2021 23:06:50 +0100 Subject: [PATCH 204/831] Bump pymysensors to 0.21.0 (#47530) --- homeassistant/components/mysensors/manifest.json | 13 +++---------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/mysensors/manifest.json b/homeassistant/components/mysensors/manifest.json index 8371f2930c2..c7d439dedc4 100644 --- a/homeassistant/components/mysensors/manifest.json +++ b/homeassistant/components/mysensors/manifest.json @@ -2,15 +2,8 @@ "domain": "mysensors", "name": "MySensors", "documentation": "https://www.home-assistant.io/integrations/mysensors", - "requirements": [ - "pymysensors==0.20.1" - ], - "after_dependencies": [ - "mqtt" - ], - "codeowners": [ - "@MartinHjelmare", - "@functionpointer" - ], + "requirements": ["pymysensors==0.21.0"], + "after_dependencies": ["mqtt"], + "codeowners": ["@MartinHjelmare", "@functionpointer"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 2e0e0a16ad9..c31d6ee7aa6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1552,7 +1552,7 @@ pymusiccast==0.1.6 pymyq==3.0.4 # homeassistant.components.mysensors -pymysensors==0.20.1 +pymysensors==0.21.0 # homeassistant.components.nanoleaf pynanoleaf==0.0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c5c96aefd78..496ed7ec4a8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -824,7 +824,7 @@ pymonoprice==0.3 pymyq==3.0.4 # homeassistant.components.mysensors -pymysensors==0.20.1 +pymysensors==0.21.0 # homeassistant.components.nuki pynuki==1.3.8 From b01a6367cc631c22b6a71994df00006bd5ef301e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 6 Mar 2021 23:19:03 +0100 Subject: [PATCH 205/831] Complete typing on AdGuard Home integration (#47477) --- homeassistant/components/adguard/__init__.py | 8 ++-- .../components/adguard/config_flow.py | 28 ++++++++---- homeassistant/components/adguard/sensor.py | 43 ++++++++++--------- homeassistant/components/adguard/switch.py | 42 ++++++++++-------- tests/components/adguard/test_config_flow.py | 30 +++++++++---- 5 files changed, 94 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/adguard/__init__.py b/homeassistant/components/adguard/__init__.py index 0a316784f8b..6ad7d9579a8 100644 --- a/homeassistant/components/adguard/__init__.py +++ b/homeassistant/components/adguard/__init__.py @@ -1,6 +1,8 @@ """Support for AdGuard Home.""" +from __future__ import annotations + import logging -from typing import Any, Dict +from typing import Any from adguardhome import AdGuardHome, AdGuardHomeConnectionError, AdGuardHomeError import voluptuous as vol @@ -117,7 +119,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool return True -async def async_unload_entry(hass: HomeAssistantType, entry: ConfigType) -> bool: +async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: """Unload AdGuard Home config entry.""" hass.services.async_remove(DOMAIN, SERVICE_ADD_URL) hass.services.async_remove(DOMAIN, SERVICE_REMOVE_URL) @@ -191,7 +193,7 @@ class AdGuardHomeDeviceEntity(AdGuardHomeEntity): """Defines a AdGuard Home device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this AdGuard Home instance.""" return { "identifiers": { diff --git a/homeassistant/components/adguard/config_flow.py b/homeassistant/components/adguard/config_flow.py index d728eed3003..308670272b5 100644 --- a/homeassistant/components/adguard/config_flow.py +++ b/homeassistant/components/adguard/config_flow.py @@ -1,9 +1,12 @@ """Config flow to configure the AdGuard Home integration.""" +from __future__ import annotations + +from typing import Any + from adguardhome import AdGuardHome, AdGuardHomeConnectionError import voluptuous as vol from homeassistant import config_entries -from homeassistant.components.adguard.const import DOMAIN from homeassistant.config_entries import ConfigFlow from homeassistant.const import ( CONF_HOST, @@ -15,9 +18,10 @@ from homeassistant.const import ( ) from homeassistant.helpers.aiohttp_client import async_get_clientsession +from .const import DOMAIN # pylint: disable=unused-import -@config_entries.HANDLERS.register(DOMAIN) -class AdGuardHomeFlowHandler(ConfigFlow): + +class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a AdGuard Home config flow.""" VERSION = 1 @@ -25,7 +29,9 @@ class AdGuardHomeFlowHandler(ConfigFlow): _hassio_discovery = None - async def _show_setup_form(self, errors=None): + async def _show_setup_form( + self, errors: dict[str, str] | None = None + ) -> dict[str, Any]: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -42,7 +48,9 @@ class AdGuardHomeFlowHandler(ConfigFlow): errors=errors or {}, ) - async def _show_hassio_form(self, errors=None): + async def _show_hassio_form( + self, errors: dict[str, str] | None = None + ) -> dict[str, Any]: """Show the Hass.io confirmation form to the user.""" return self.async_show_form( step_id="hassio_confirm", @@ -51,7 +59,9 @@ class AdGuardHomeFlowHandler(ConfigFlow): errors=errors or {}, ) - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -91,7 +101,7 @@ class AdGuardHomeFlowHandler(ConfigFlow): }, ) - async def async_step_hassio(self, discovery_info): + async def async_step_hassio(self, discovery_info: dict[str, Any]) -> dict[str, Any]: """Prepare configuration for a Hass.io AdGuard Home add-on. This flow is triggered by the discovery component. @@ -129,7 +139,9 @@ class AdGuardHomeFlowHandler(ConfigFlow): return self.async_abort(reason="existing_instance_updated") - async def async_step_hassio_confirm(self, user_input=None): + async def async_step_hassio_confirm( + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Confirm Hass.io discovery.""" if user_input is None: return await self._show_hassio_form() diff --git a/homeassistant/components/adguard/sensor.py b/homeassistant/components/adguard/sensor.py index 05e23ba8b80..edd9fe22ba9 100644 --- a/homeassistant/components/adguard/sensor.py +++ b/homeassistant/components/adguard/sensor.py @@ -1,25 +1,28 @@ """Support for AdGuard Home sensors.""" +from __future__ import annotations + from datetime import timedelta +from typing import Callable -from adguardhome import AdGuardHomeConnectionError +from adguardhome import AdGuardHome, AdGuardHomeConnectionError -from homeassistant.components.adguard import AdGuardHomeDeviceEntity -from homeassistant.components.adguard.const import ( - DATA_ADGUARD_CLIENT, - DATA_ADGUARD_VERION, - DOMAIN, -) from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE, TIME_MILLISECONDS +from homeassistant.core import HomeAssistant from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.entity import Entity + +from . import AdGuardHomeDeviceEntity +from .const import DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERION, DOMAIN SCAN_INTERVAL = timedelta(seconds=300) PARALLEL_UPDATES = 4 async def async_setup_entry( - hass: HomeAssistantType, entry: ConfigEntry, async_add_entities + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up AdGuard Home sensor based on a config entry.""" adguard = hass.data[DOMAIN][DATA_ADGUARD_CLIENT] @@ -50,7 +53,7 @@ class AdGuardHomeSensor(AdGuardHomeDeviceEntity): def __init__( self, - adguard, + adguard: AdGuardHome, name: str, icon: str, measurement: str, @@ -78,12 +81,12 @@ class AdGuardHomeSensor(AdGuardHomeDeviceEntity): ) @property - def state(self): + def state(self) -> str | None: """Return the state of the sensor.""" return self._state @property - def unit_of_measurement(self) -> str: + def unit_of_measurement(self) -> str | None: """Return the unit this state is expressed in.""" return self._unit_of_measurement @@ -91,7 +94,7 @@ class AdGuardHomeSensor(AdGuardHomeDeviceEntity): class AdGuardHomeDNSQueriesSensor(AdGuardHomeSensor): """Defines a AdGuard Home DNS Queries sensor.""" - def __init__(self, adguard): + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home sensor.""" super().__init__( adguard, "AdGuard DNS Queries", "mdi:magnify", "dns_queries", "queries" @@ -105,7 +108,7 @@ class AdGuardHomeDNSQueriesSensor(AdGuardHomeSensor): class AdGuardHomeBlockedFilteringSensor(AdGuardHomeSensor): """Defines a AdGuard Home blocked by filtering sensor.""" - def __init__(self, adguard): + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home sensor.""" super().__init__( adguard, @@ -124,7 +127,7 @@ class AdGuardHomeBlockedFilteringSensor(AdGuardHomeSensor): class AdGuardHomePercentageBlockedSensor(AdGuardHomeSensor): """Defines a AdGuard Home blocked percentage sensor.""" - def __init__(self, adguard): + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home sensor.""" super().__init__( adguard, @@ -143,7 +146,7 @@ class AdGuardHomePercentageBlockedSensor(AdGuardHomeSensor): class AdGuardHomeReplacedParentalSensor(AdGuardHomeSensor): """Defines a AdGuard Home replaced by parental control sensor.""" - def __init__(self, adguard): + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home sensor.""" super().__init__( adguard, @@ -161,7 +164,7 @@ class AdGuardHomeReplacedParentalSensor(AdGuardHomeSensor): class AdGuardHomeReplacedSafeBrowsingSensor(AdGuardHomeSensor): """Defines a AdGuard Home replaced by safe browsing sensor.""" - def __init__(self, adguard): + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home sensor.""" super().__init__( adguard, @@ -179,7 +182,7 @@ class AdGuardHomeReplacedSafeBrowsingSensor(AdGuardHomeSensor): class AdGuardHomeReplacedSafeSearchSensor(AdGuardHomeSensor): """Defines a AdGuard Home replaced by safe search sensor.""" - def __init__(self, adguard): + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home sensor.""" super().__init__( adguard, @@ -197,7 +200,7 @@ class AdGuardHomeReplacedSafeSearchSensor(AdGuardHomeSensor): class AdGuardHomeAverageProcessingTimeSensor(AdGuardHomeSensor): """Defines a AdGuard Home average processing time sensor.""" - def __init__(self, adguard): + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home sensor.""" super().__init__( adguard, @@ -216,7 +219,7 @@ class AdGuardHomeAverageProcessingTimeSensor(AdGuardHomeSensor): class AdGuardHomeRulesCountSensor(AdGuardHomeSensor): """Defines a AdGuard Home rules count sensor.""" - def __init__(self, adguard): + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home sensor.""" super().__init__( adguard, diff --git a/homeassistant/components/adguard/switch.py b/homeassistant/components/adguard/switch.py index 44aab11573d..0b127a280cf 100644 --- a/homeassistant/components/adguard/switch.py +++ b/homeassistant/components/adguard/switch.py @@ -1,19 +1,20 @@ """Support for AdGuard Home switches.""" +from __future__ import annotations + from datetime import timedelta import logging +from typing import Callable -from adguardhome import AdGuardHomeConnectionError, AdGuardHomeError +from adguardhome import AdGuardHome, AdGuardHomeConnectionError, AdGuardHomeError -from homeassistant.components.adguard import AdGuardHomeDeviceEntity -from homeassistant.components.adguard.const import ( - DATA_ADGUARD_CLIENT, - DATA_ADGUARD_VERION, - DOMAIN, -) from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.entity import Entity + +from . import AdGuardHomeDeviceEntity +from .const import DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERION, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -22,7 +23,9 @@ PARALLEL_UPDATES = 1 async def async_setup_entry( - hass: HomeAssistantType, entry: ConfigEntry, async_add_entities + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up AdGuard Home switch based on a config entry.""" adguard = hass.data[DOMAIN][DATA_ADGUARD_CLIENT] @@ -49,8 +52,13 @@ class AdGuardHomeSwitch(AdGuardHomeDeviceEntity, SwitchEntity): """Defines a AdGuard Home switch.""" def __init__( - self, adguard, name: str, icon: str, key: str, enabled_default: bool = True - ): + self, + adguard: AdGuardHome, + name: str, + icon: str, + key: str, + enabled_default: bool = True, + ) -> None: """Initialize AdGuard Home switch.""" self._state = False self._key = key @@ -96,7 +104,7 @@ class AdGuardHomeSwitch(AdGuardHomeDeviceEntity, SwitchEntity): class AdGuardHomeProtectionSwitch(AdGuardHomeSwitch): """Defines a AdGuard Home protection switch.""" - def __init__(self, adguard) -> None: + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home switch.""" super().__init__( adguard, "AdGuard Protection", "mdi:shield-check", "protection" @@ -118,7 +126,7 @@ class AdGuardHomeProtectionSwitch(AdGuardHomeSwitch): class AdGuardHomeParentalSwitch(AdGuardHomeSwitch): """Defines a AdGuard Home parental control switch.""" - def __init__(self, adguard) -> None: + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home switch.""" super().__init__( adguard, "AdGuard Parental Control", "mdi:shield-check", "parental" @@ -140,7 +148,7 @@ class AdGuardHomeParentalSwitch(AdGuardHomeSwitch): class AdGuardHomeSafeSearchSwitch(AdGuardHomeSwitch): """Defines a AdGuard Home safe search switch.""" - def __init__(self, adguard) -> None: + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home switch.""" super().__init__( adguard, "AdGuard Safe Search", "mdi:shield-check", "safesearch" @@ -162,7 +170,7 @@ class AdGuardHomeSafeSearchSwitch(AdGuardHomeSwitch): class AdGuardHomeSafeBrowsingSwitch(AdGuardHomeSwitch): """Defines a AdGuard Home safe search switch.""" - def __init__(self, adguard) -> None: + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home switch.""" super().__init__( adguard, "AdGuard Safe Browsing", "mdi:shield-check", "safebrowsing" @@ -184,7 +192,7 @@ class AdGuardHomeSafeBrowsingSwitch(AdGuardHomeSwitch): class AdGuardHomeFilteringSwitch(AdGuardHomeSwitch): """Defines a AdGuard Home filtering switch.""" - def __init__(self, adguard) -> None: + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home switch.""" super().__init__(adguard, "AdGuard Filtering", "mdi:shield-check", "filtering") @@ -204,7 +212,7 @@ class AdGuardHomeFilteringSwitch(AdGuardHomeSwitch): class AdGuardHomeQueryLogSwitch(AdGuardHomeSwitch): """Defines a AdGuard Home query log switch.""" - def __init__(self, adguard) -> None: + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home switch.""" super().__init__( adguard, diff --git a/tests/components/adguard/test_config_flow.py b/tests/components/adguard/test_config_flow.py index 06fe235741f..94760cade9f 100644 --- a/tests/components/adguard/test_config_flow.py +++ b/tests/components/adguard/test_config_flow.py @@ -16,8 +16,10 @@ from homeassistant.const import ( CONF_VERIFY_SSL, CONTENT_TYPE_JSON, ) +from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry +from tests.test_util.aiohttp import AiohttpClientMocker FIXTURE_USER_INPUT = { CONF_HOST: "127.0.0.1", @@ -29,7 +31,7 @@ FIXTURE_USER_INPUT = { } -async def test_show_authenticate_form(hass): +async def test_show_authenticate_form(hass: HomeAssistant) -> None: """Test that the setup form is served.""" flow = config_flow.AdGuardHomeFlowHandler() flow.hass = hass @@ -39,7 +41,9 @@ async def test_show_authenticate_form(hass): assert result["step_id"] == "user" -async def test_connection_error(hass, aioclient_mock): +async def test_connection_error( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test we show user form on AdGuard Home connection error.""" aioclient_mock.get( f"{'https' if FIXTURE_USER_INPUT[CONF_SSL] else 'http'}" @@ -57,7 +61,9 @@ async def test_connection_error(hass, aioclient_mock): assert result["errors"] == {"base": "cannot_connect"} -async def test_full_flow_implementation(hass, aioclient_mock): +async def test_full_flow_implementation( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test registering an integration and finishing flow works.""" aioclient_mock.get( f"{'https' if FIXTURE_USER_INPUT[CONF_SSL] else 'http'}" @@ -84,7 +90,7 @@ async def test_full_flow_implementation(hass, aioclient_mock): assert result["data"][CONF_VERIFY_SSL] == FIXTURE_USER_INPUT[CONF_VERIFY_SSL] -async def test_integration_already_exists(hass): +async def test_integration_already_exists(hass: HomeAssistant) -> None: """Test we only allow a single config flow.""" MockConfigEntry(domain=DOMAIN).add_to_hass(hass) @@ -95,7 +101,7 @@ async def test_integration_already_exists(hass): assert result["reason"] == "single_instance_allowed" -async def test_hassio_single_instance(hass): +async def test_hassio_single_instance(hass: HomeAssistant) -> None: """Test we only allow a single config flow.""" MockConfigEntry( domain="adguard", data={"host": "mock-adguard", "port": "3000"} @@ -110,7 +116,7 @@ async def test_hassio_single_instance(hass): assert result["reason"] == "single_instance_allowed" -async def test_hassio_update_instance_not_running(hass): +async def test_hassio_update_instance_not_running(hass: HomeAssistant) -> None: """Test we only allow a single config flow.""" entry = MockConfigEntry( domain="adguard", data={"host": "mock-adguard", "port": "3000"} @@ -131,7 +137,9 @@ async def test_hassio_update_instance_not_running(hass): assert result["reason"] == "existing_instance_updated" -async def test_hassio_update_instance_running(hass, aioclient_mock): +async def test_hassio_update_instance_running( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test we only allow a single config flow.""" aioclient_mock.get( "http://mock-adguard-updated:3000/control/status", @@ -192,7 +200,9 @@ async def test_hassio_update_instance_running(hass, aioclient_mock): assert entry.data["host"] == "mock-adguard-updated" -async def test_hassio_confirm(hass, aioclient_mock): +async def test_hassio_confirm( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test we can finish a config flow.""" aioclient_mock.get( "http://mock-adguard:3000/control/status", @@ -220,7 +230,9 @@ async def test_hassio_confirm(hass, aioclient_mock): assert result["data"][CONF_VERIFY_SSL] -async def test_hassio_connection_error(hass, aioclient_mock): +async def test_hassio_connection_error( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test we show Hass.io confirm form on AdGuard Home connection error.""" aioclient_mock.get( "http://mock-adguard:3000/control/status", exc=aiohttp.ClientError From f542b360d5a82e52afc84c106775b07d86064017 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 6 Mar 2021 23:41:43 +0100 Subject: [PATCH 206/831] Fix mysensors device tracker (#47536) --- .../components/mysensors/__init__.py | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index f2478da5b57..bcab1ed86b4 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -8,6 +8,7 @@ from mysensors import BaseAsyncGateway import voluptuous as vol from homeassistant import config_entries +from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER_DOMAIN from homeassistant.components.mqtt import valid_publish_topic, valid_subscribe_topic from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN from homeassistant.config_entries import ConfigEntry @@ -195,24 +196,27 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool hass.data[DOMAIN][MYSENSORS_GATEWAYS][entry.entry_id] = gateway # Connect notify discovery as that integration doesn't support entry forwarding. + # Allow loading device tracker platform via discovery + # until refactor to config entry is done. - load_notify_platform = partial( - async_load_platform, - hass, - NOTIFY_DOMAIN, - DOMAIN, - hass_config=hass.data[DOMAIN][DATA_HASS_CONFIG], - ) - - await on_unload( - hass, - entry.entry_id, - async_dispatcher_connect( + for platform in (DEVICE_TRACKER_DOMAIN, NOTIFY_DOMAIN): + load_discovery_platform = partial( + async_load_platform, hass, - MYSENSORS_DISCOVERY.format(entry.entry_id, NOTIFY_DOMAIN), - load_notify_platform, - ), - ) + platform, + DOMAIN, + hass_config=hass.data[DOMAIN][DATA_HASS_CONFIG], + ) + + await on_unload( + hass, + entry.entry_id, + async_dispatcher_connect( + hass, + MYSENSORS_DISCOVERY.format(entry.entry_id, platform), + load_discovery_platform, + ), + ) async def finish() -> None: await asyncio.gather( From 9101ed27327bfac96323b44665326d1cff575b80 Mon Sep 17 00:00:00 2001 From: N1c093 <59510296+N1c093@users.noreply.github.com> Date: Sun, 7 Mar 2021 00:48:22 +0100 Subject: [PATCH 207/831] Add precipitation probability forecast to owm (#47284) * Add precipitation probability forecast to owm * Update weather_update_coordinator.py Reformat the code based on black --- homeassistant/components/openweathermap/const.py | 3 +++ .../components/openweathermap/weather_update_coordinator.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/homeassistant/components/openweathermap/const.py b/homeassistant/components/openweathermap/const.py index 7c8e8aeee57..5b0165b2bee 100644 --- a/homeassistant/components/openweathermap/const.py +++ b/homeassistant/components/openweathermap/const.py @@ -16,6 +16,7 @@ from homeassistant.components.weather import ( ATTR_CONDITION_WINDY_VARIANT, ATTR_FORECAST_CONDITION, ATTR_FORECAST_PRECIPITATION, + ATTR_FORECAST_PRECIPITATION_PROBABILITY, ATTR_FORECAST_PRESSURE, ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW, @@ -93,6 +94,7 @@ MONITORED_CONDITIONS = [ FORECAST_MONITORED_CONDITIONS = [ ATTR_FORECAST_CONDITION, ATTR_FORECAST_PRECIPITATION, + ATTR_FORECAST_PRECIPITATION_PROBABILITY, ATTR_FORECAST_PRESSURE, ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW, @@ -212,6 +214,7 @@ WEATHER_SENSOR_TYPES = { FORECAST_SENSOR_TYPES = { ATTR_FORECAST_CONDITION: {SENSOR_NAME: "Condition"}, ATTR_FORECAST_PRECIPITATION: {SENSOR_NAME: "Precipitation"}, + ATTR_FORECAST_PRECIPITATION_PROBABILITY: {SENSOR_NAME: "Precipitation probability"}, ATTR_FORECAST_PRESSURE: {SENSOR_NAME: "Pressure"}, ATTR_FORECAST_TEMP: { SENSOR_NAME: "Temperature", diff --git a/homeassistant/components/openweathermap/weather_update_coordinator.py b/homeassistant/components/openweathermap/weather_update_coordinator.py index 93db4ca26d8..201029c3979 100644 --- a/homeassistant/components/openweathermap/weather_update_coordinator.py +++ b/homeassistant/components/openweathermap/weather_update_coordinator.py @@ -10,6 +10,7 @@ from homeassistant.components.weather import ( ATTR_CONDITION_SUNNY, ATTR_FORECAST_CONDITION, ATTR_FORECAST_PRECIPITATION, + ATTR_FORECAST_PRECIPITATION_PROBABILITY, ATTR_FORECAST_PRESSURE, ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW, @@ -143,6 +144,9 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): ATTR_FORECAST_PRECIPITATION: self._calc_precipitation( entry.rain, entry.snow ), + ATTR_FORECAST_PRECIPITATION_PROBABILITY: ( + entry.precipitation_probability * 100 + ), ATTR_FORECAST_PRESSURE: entry.pressure.get("press"), ATTR_FORECAST_WIND_SPEED: entry.wind().get("speed"), ATTR_FORECAST_WIND_BEARING: entry.wind().get("deg"), From 79b5ca9415a9977f6f030d055149f57645e57d3e Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sat, 6 Mar 2021 18:52:43 -0500 Subject: [PATCH 208/831] Add device classes for CO and CO2 measurements (#47487) --- homeassistant/components/sensor/__init__.py | 4 ++++ homeassistant/components/sensor/device_condition.py | 8 ++++++++ homeassistant/components/sensor/device_trigger.py | 8 ++++++++ homeassistant/components/sensor/strings.json | 4 ++++ homeassistant/const.py | 2 ++ tests/components/sensor/test_device_trigger.py | 2 +- tests/testing_config/custom_components/test/sensor.py | 9 ++++++++- 7 files changed, 35 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 46462019749..187cb0c410d 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -7,6 +7,8 @@ import voluptuous as vol from homeassistant.const import ( DEVICE_CLASS_BATTERY, + DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, DEVICE_CLASS_HUMIDITY, @@ -36,6 +38,8 @@ ENTITY_ID_FORMAT = DOMAIN + ".{}" SCAN_INTERVAL = timedelta(seconds=30) DEVICE_CLASSES = [ DEVICE_CLASS_BATTERY, # % of battery that is left + DEVICE_CLASS_CO, # ppm (parts per million) Carbon Monoxide gas concentration + DEVICE_CLASS_CO2, # ppm (parts per million) Carbon Dioxide gas concentration DEVICE_CLASS_CURRENT, # current (A) DEVICE_CLASS_ENERGY, # energy (kWh, Wh) DEVICE_CLASS_HUMIDITY, # % of humidity in the air diff --git a/homeassistant/components/sensor/device_condition.py b/homeassistant/components/sensor/device_condition.py index a9d44f2f860..e2efac7b141 100644 --- a/homeassistant/components/sensor/device_condition.py +++ b/homeassistant/components/sensor/device_condition.py @@ -14,6 +14,8 @@ from homeassistant.const import ( CONF_ENTITY_ID, CONF_TYPE, DEVICE_CLASS_BATTERY, + DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, DEVICE_CLASS_HUMIDITY, @@ -41,6 +43,8 @@ from . import DOMAIN DEVICE_CLASS_NONE = "none" CONF_IS_BATTERY_LEVEL = "is_battery_level" +CONF_IS_CO = "is_carbon_monoxide" +CONF_IS_CO2 = "is_carbon_dioxide" CONF_IS_CURRENT = "is_current" CONF_IS_ENERGY = "is_energy" CONF_IS_HUMIDITY = "is_humidity" @@ -56,6 +60,8 @@ CONF_IS_VALUE = "is_value" ENTITY_CONDITIONS = { DEVICE_CLASS_BATTERY: [{CONF_TYPE: CONF_IS_BATTERY_LEVEL}], + DEVICE_CLASS_CO: [{CONF_TYPE: CONF_IS_CO}], + DEVICE_CLASS_CO2: [{CONF_TYPE: CONF_IS_CO2}], DEVICE_CLASS_CURRENT: [{CONF_TYPE: CONF_IS_CURRENT}], DEVICE_CLASS_ENERGY: [{CONF_TYPE: CONF_IS_ENERGY}], DEVICE_CLASS_HUMIDITY: [{CONF_TYPE: CONF_IS_HUMIDITY}], @@ -77,6 +83,8 @@ CONDITION_SCHEMA = vol.All( vol.Required(CONF_TYPE): vol.In( [ CONF_IS_BATTERY_LEVEL, + CONF_IS_CO, + CONF_IS_CO2, CONF_IS_CURRENT, CONF_IS_ENERGY, CONF_IS_HUMIDITY, diff --git a/homeassistant/components/sensor/device_trigger.py b/homeassistant/components/sensor/device_trigger.py index 86dda53cd2b..9586261a191 100644 --- a/homeassistant/components/sensor/device_trigger.py +++ b/homeassistant/components/sensor/device_trigger.py @@ -17,6 +17,8 @@ from homeassistant.const import ( CONF_FOR, CONF_TYPE, DEVICE_CLASS_BATTERY, + DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, DEVICE_CLASS_HUMIDITY, @@ -39,6 +41,8 @@ from . import DOMAIN DEVICE_CLASS_NONE = "none" CONF_BATTERY_LEVEL = "battery_level" +CONF_CO = "carbon_monoxide" +CONF_CO2 = "carbon_dioxide" CONF_CURRENT = "current" CONF_ENERGY = "energy" CONF_HUMIDITY = "humidity" @@ -54,6 +58,8 @@ CONF_VALUE = "value" ENTITY_TRIGGERS = { DEVICE_CLASS_BATTERY: [{CONF_TYPE: CONF_BATTERY_LEVEL}], + DEVICE_CLASS_CO: [{CONF_TYPE: CONF_CO}], + DEVICE_CLASS_CO2: [{CONF_TYPE: CONF_CO2}], DEVICE_CLASS_CURRENT: [{CONF_TYPE: CONF_CURRENT}], DEVICE_CLASS_ENERGY: [{CONF_TYPE: CONF_ENERGY}], DEVICE_CLASS_HUMIDITY: [{CONF_TYPE: CONF_HUMIDITY}], @@ -76,6 +82,8 @@ TRIGGER_SCHEMA = vol.All( vol.Required(CONF_TYPE): vol.In( [ CONF_BATTERY_LEVEL, + CONF_CO, + CONF_CO2, CONF_CURRENT, CONF_ENERGY, CONF_HUMIDITY, diff --git a/homeassistant/components/sensor/strings.json b/homeassistant/components/sensor/strings.json index 76ea9efabc3..4298a367c2c 100644 --- a/homeassistant/components/sensor/strings.json +++ b/homeassistant/components/sensor/strings.json @@ -3,6 +3,8 @@ "device_automation": { "condition_type": { "is_battery_level": "Current {entity_name} battery level", + "is_carbon_monoxide": "Current {entity_name} carbon monoxide concentration level", + "is_carbon_dioxide": "Current {entity_name} carbon dioxide concentration level", "is_humidity": "Current {entity_name} humidity", "is_illuminance": "Current {entity_name} illuminance", "is_power": "Current {entity_name} power", @@ -18,6 +20,8 @@ }, "trigger_type": { "battery_level": "{entity_name} battery level changes", + "carbon_monoxide": "{entity_name} carbon monoxide concentration changes", + "carbon_dioxide": "{entity_name} carbon dioxide concentration changes", "humidity": "{entity_name} humidity changes", "illuminance": "{entity_name} illuminance changes", "power": "{entity_name} power changes", diff --git a/homeassistant/const.py b/homeassistant/const.py index 1076b962f2a..b601135279f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -219,6 +219,8 @@ EVENT_TIME_CHANGED = "time_changed" # #### DEVICE CLASSES #### DEVICE_CLASS_BATTERY = "battery" +DEVICE_CLASS_CO = "carbon_monoxide" +DEVICE_CLASS_CO2 = "carbon_dioxide" DEVICE_CLASS_HUMIDITY = "humidity" DEVICE_CLASS_ILLUMINANCE = "illuminance" DEVICE_CLASS_SIGNAL_STRENGTH = "signal_strength" diff --git a/tests/components/sensor/test_device_trigger.py b/tests/components/sensor/test_device_trigger.py index eb38060f0dd..ed1da9f86dd 100644 --- a/tests/components/sensor/test_device_trigger.py +++ b/tests/components/sensor/test_device_trigger.py @@ -77,7 +77,7 @@ async def test_get_triggers(hass, device_reg, entity_reg): if device_class != "none" ] triggers = await async_get_device_automations(hass, "trigger", device_entry.id) - assert len(triggers) == 12 + assert len(triggers) == 14 assert triggers == expected_triggers diff --git a/tests/testing_config/custom_components/test/sensor.py b/tests/testing_config/custom_components/test/sensor.py index d467a93fd62..de6f179daa7 100644 --- a/tests/testing_config/custom_components/test/sensor.py +++ b/tests/testing_config/custom_components/test/sensor.py @@ -4,7 +4,12 @@ Provide a mock sensor platform. Call init before using it in your tests to ensure clean test data. """ import homeassistant.components.sensor as sensor -from homeassistant.const import PERCENTAGE, PRESSURE_HPA, SIGNAL_STRENGTH_DECIBELS +from homeassistant.const import ( + CONCENTRATION_PARTS_PER_MILLION, + PERCENTAGE, + PRESSURE_HPA, + SIGNAL_STRENGTH_DECIBELS, +) from tests.common import MockEntity @@ -13,6 +18,8 @@ DEVICE_CLASSES.append("none") UNITS_OF_MEASUREMENT = { sensor.DEVICE_CLASS_BATTERY: PERCENTAGE, # % of battery that is left + sensor.DEVICE_CLASS_CO: CONCENTRATION_PARTS_PER_MILLION, # ppm of CO concentration + sensor.DEVICE_CLASS_CO2: CONCENTRATION_PARTS_PER_MILLION, # ppm of CO2 concentration sensor.DEVICE_CLASS_HUMIDITY: PERCENTAGE, # % of humidity in the air sensor.DEVICE_CLASS_ILLUMINANCE: "lm", # current light level (lx/lm) sensor.DEVICE_CLASS_SIGNAL_STRENGTH: SIGNAL_STRENGTH_DECIBELS, # signal strength (dB/dBm) From 2e89f152ba13ffd768f54181721a45c590801cad Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 6 Mar 2021 14:30:57 -1000 Subject: [PATCH 209/831] Bump HAP-python to 3.4.0 (#47476) * Bump HAP-python to 3.3.3 * bump * fix mocking --- homeassistant/components/homekit/__init__.py | 2 +- .../components/homekit/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/homekit/test_homekit.py | 26 +++++++++---------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index e6d3a8abff4..23492b12ccc 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -616,7 +616,7 @@ class HomeKit: self._async_register_bridge(dev_reg) await self._async_start(bridged_states) _LOGGER.debug("Driver start for %s", self._name) - self.hass.add_job(self.driver.start_service) + await self.driver.async_start() self.status = STATUS_RUNNING @callback diff --git a/homeassistant/components/homekit/manifest.json b/homeassistant/components/homekit/manifest.json index ac3fb0251e2..d7ec3297fa4 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit", "documentation": "https://www.home-assistant.io/integrations/homekit", "requirements": [ - "HAP-python==3.3.2", + "HAP-python==3.4.0", "fnvhash==0.1.0", "PyQRCode==1.2.1", "base36==0.1.1", diff --git a/requirements_all.txt b/requirements_all.txt index c31d6ee7aa6..f27e3835467 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -17,7 +17,7 @@ Adafruit-SHT31==1.0.2 # Adafruit_BBIO==1.1.1 # homeassistant.components.homekit -HAP-python==3.3.2 +HAP-python==3.4.0 # homeassistant.components.mastodon Mastodon.py==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 496ed7ec4a8..d06ac8bfd5b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ AEMET-OpenData==0.1.8 # homeassistant.components.homekit -HAP-python==3.3.2 +HAP-python==3.4.0 # homeassistant.components.flick_electric PyFlick==0.0.2 diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 9ce3e96f06f..4d2fbfe951d 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -493,7 +493,7 @@ async def test_homekit_start(hass, hk_driver, device_reg): ) as mock_setup_msg, patch( "pyhap.accessory_driver.AccessoryDriver.add_accessory" ) as hk_driver_add_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() @@ -528,7 +528,7 @@ async def test_homekit_start(hass, hk_driver, device_reg): ) as mock_setup_msg, patch( "pyhap.accessory_driver.AccessoryDriver.add_accessory" ) as hk_driver_add_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() @@ -567,7 +567,7 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroc ) as mock_setup_msg, patch( "pyhap.accessory_driver.AccessoryDriver.add_accessory", ) as hk_driver_add_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() @@ -630,7 +630,7 @@ async def test_homekit_reset_accessories(hass, mock_zeroconf): ), patch("pyhap.accessory.Bridge.add_accessory") as mock_add_accessory, patch( "pyhap.accessory_driver.AccessoryDriver.config_changed" ) as hk_driver_config_changed, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): await async_init_entry(hass, entry) @@ -674,7 +674,7 @@ async def test_homekit_too_many_accessories(hass, hk_driver, caplog, mock_zeroco hass.states.async_set("light.demo2", "on") hass.states.async_set("light.demo3", "on") - with patch("pyhap.accessory_driver.AccessoryDriver.start_service"), patch( + with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( "pyhap.accessory_driver.AccessoryDriver.add_accessory" ), patch(f"{PATH_HOMEKIT}.show_setup_message"), patch( f"{PATH_HOMEKIT}.HomeBridge", _mock_bridge @@ -738,7 +738,7 @@ async def test_homekit_finds_linked_batteries( with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): await homekit.async_start() await hass.async_block_till_done() @@ -810,7 +810,7 @@ async def test_homekit_async_get_integration_fails( with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): await homekit.async_start() await hass.async_block_till_done() @@ -895,7 +895,7 @@ async def test_homekit_uses_system_zeroconf(hass, hk_driver, mock_zeroconf): assert await async_setup_component(hass, "zeroconf", {"zeroconf": {}}) system_zc = await zeroconf.async_get_instance(hass) - with patch("pyhap.accessory_driver.AccessoryDriver.start_service"), patch( + with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( f"{PATH_HOMEKIT}.HomeKit.async_stop" ): entry.add_to_hass(hass) @@ -963,7 +963,7 @@ async def test_homekit_ignored_missing_devices( with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): await homekit.async_start() await hass.async_block_till_done() @@ -1025,7 +1025,7 @@ async def test_homekit_finds_linked_motion_sensors( with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): await homekit.async_start() await hass.async_block_till_done() @@ -1090,7 +1090,7 @@ async def test_homekit_finds_linked_humidity_sensors( with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): await homekit.async_start() await hass.async_block_till_done() @@ -1153,7 +1153,7 @@ async def test_reload(hass, mock_zeroconf): ), patch( f"{PATH_HOMEKIT}.get_accessory" ), patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): mock_homekit2.return_value = homekit = Mock() type(homekit).async_start = AsyncMock() @@ -1205,7 +1205,7 @@ async def test_homekit_start_in_accessory_mode(hass, hk_driver, device_reg): with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch( "pyhap.accessory_driver.AccessoryDriver.add_accessory" ), patch(f"{PATH_HOMEKIT}.show_setup_message") as mock_setup_msg, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() From 0d07dae3bc206f3bc59e027b3635550dc36f0e05 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 6 Mar 2021 21:41:56 -1000 Subject: [PATCH 210/831] Change default homekit ports to 21063 and 21064 (#47491) We previously used a value in the linux default ephemerial port range which meant that if something else happened to use that port HomeKit would not start up. We now use a value below 32768 to ensure that the port is not randomly unavailable --- homeassistant/components/homekit/const.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index 67312903b50..840e9ebe607 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -74,8 +74,8 @@ DEFAULT_LOW_BATTERY_THRESHOLD = 20 DEFAULT_MAX_FPS = 30 DEFAULT_MAX_HEIGHT = 1080 DEFAULT_MAX_WIDTH = 1920 -DEFAULT_PORT = 51827 -DEFAULT_CONFIG_FLOW_PORT = 51828 +DEFAULT_PORT = 21063 +DEFAULT_CONFIG_FLOW_PORT = 21064 DEFAULT_SAFE_MODE = False DEFAULT_VIDEO_CODEC = VIDEO_CODEC_LIBX264 DEFAULT_VIDEO_MAP = "0:v:0" From c8c394ef916373da73088cd7f2d345112f1293c7 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Sun, 7 Mar 2021 11:36:28 +0100 Subject: [PATCH 211/831] Increase ESPHome log level on first connection failure (#47547) --- homeassistant/components/esphome/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index b9720dcdf80..dee0813007c 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -264,8 +264,9 @@ async def _setup_auto_reconnect_logic( try: await cli.connect(on_stop=try_connect, login=True) except APIConnectionError as error: - logger = _LOGGER.info if tries == 0 else _LOGGER.debug - logger( + level = logging.WARNING if tries == 0 else logging.DEBUG + _LOGGER.log( + level, "Can't connect to ESPHome API for %s (%s): %s", entry.unique_id, host, From 48f1a55a28e53014a1d54a241fcc951730a8fc10 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sun, 7 Mar 2021 13:20:04 +0100 Subject: [PATCH 212/831] Improve common structure in UniFi device tracker tests (#47526) --- tests/components/unifi/test_device_tracker.py | 1028 +++++++++-------- 1 file changed, 573 insertions(+), 455 deletions(-) diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 51dbd735e10..33dda33be24 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -1,5 +1,5 @@ """The tests for the UniFi device tracker platform.""" -from copy import copy + from datetime import timedelta from unittest.mock import patch @@ -22,7 +22,7 @@ from homeassistant.components.unifi.const import ( CONF_TRACK_WIRED_CLIENTS, DOMAIN as UNIFI_DOMAIN, ) -from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_UNAVAILABLE from homeassistant.helpers import entity_registry import homeassistant.util.dt as dt_util @@ -30,126 +30,8 @@ from .test_controller import ENTRY_CONFIG, setup_unifi_integration from tests.common import async_fire_time_changed -CLIENT_1 = { - "ap_mac": "00:00:00:00:02:01", - "essid": "ssid", - "hostname": "client_1", - "ip": "10.0.0.1", - "is_wired": False, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:01", -} -CLIENT_2 = { - "hostname": "client_2", - "ip": "10.0.0.2", - "is_wired": True, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:02", - "name": "Wired Client", -} -CLIENT_3 = { - "essid": "ssid2", - "hostname": "client_3", - "ip": "10.0.0.3", - "is_wired": False, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:03", -} -CLIENT_4 = { - "essid": "ssid", - "hostname": "client_4", - "ip": "10.0.0.4", - "is_wired": True, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:04", -} -CLIENT_5 = { - "essid": "ssid", - "hostname": "client_5", - "ip": "10.0.0.5", - "is_wired": True, - "last_seen": None, - "mac": "00:00:00:00:00:05", -} -DEVICE_1 = { - "board_rev": 3, - "device_id": "mock-id", - "has_fan": True, - "fan_level": 0, - "ip": "10.0.1.1", - "last_seen": 1562600145, - "mac": "00:00:00:00:01:01", - "model": "US16P150", - "name": "device_1", - "next_interval": 20, - "overheating": True, - "state": 1, - "type": "usw", - "upgradable": True, - "version": "4.0.42.10433", -} -DEVICE_2 = { - "board_rev": 3, - "device_id": "mock-id", - "has_fan": True, - "ip": "10.0.1.2", - "mac": "00:00:00:00:01:02", - "model": "US16P150", - "name": "device_2", - "next_interval": 20, - "state": 0, - "type": "usw", - "version": "4.0.42.10433", -} - -EVENT_CLIENT_1_WIRELESS_CONNECTED = { - "user": CLIENT_1["mac"], - "ssid": CLIENT_1["essid"], - "ap": CLIENT_1["ap_mac"], - "radio": "na", - "channel": "44", - "hostname": CLIENT_1["hostname"], - "key": "EVT_WU_Connected", - "subsystem": "wlan", - "site_id": "name", - "time": 1587753456179, - "datetime": "2020-04-24T18:37:36Z", - "msg": f'User{[CLIENT_1["mac"]]} has connected to AP[{CLIENT_1["ap_mac"]}] with SSID "{CLIENT_1["essid"]}" on "channel 44(na)"', - "_id": "5ea331fa30c49e00f90ddc1a", -} - -EVENT_CLIENT_1_WIRELESS_DISCONNECTED = { - "user": CLIENT_1["mac"], - "ssid": CLIENT_1["essid"], - "hostname": CLIENT_1["hostname"], - "ap": CLIENT_1["ap_mac"], - "duration": 467, - "bytes": 459039, - "key": "EVT_WU_Disconnected", - "subsystem": "wlan", - "site_id": "name", - "time": 1587752927000, - "datetime": "2020-04-24T18:28:47Z", - "msg": f'User{[CLIENT_1["mac"]]} disconnected from "{CLIENT_1["essid"]}" (7m 47s connected, 448.28K bytes, last AP[{CLIENT_1["ap_mac"]}])', - "_id": "5ea32ff730c49e00f90dca1a", -} - -EVENT_DEVICE_2_UPGRADED = { - "_id": "5eae7fe02ab79c00f9d38960", - "datetime": "2020-05-09T20:06:37Z", - "key": "EVT_SW_Upgraded", - "msg": f'Switch[{DEVICE_2["mac"]}] was upgraded from "{DEVICE_2["version"]}" to "4.3.13.11253"', - "subsystem": "lan", - "sw": DEVICE_2["mac"], - "sw_name": DEVICE_2["name"], - "time": 1589054797635, - "version_from": {DEVICE_2["version"]}, - "version_to": "4.3.13.11253", -} - - -async def test_no_clients(hass, aioclient_mock): +async def test_no_entities(hass, aioclient_mock): """Test the update_clients function when no clients are found.""" await setup_unifi_integration(hass, aioclient_mock) @@ -157,196 +39,297 @@ async def test_no_clients(hass, aioclient_mock): async def test_tracked_wireless_clients(hass, aioclient_mock, mock_unifi_websocket): - """Test the update_items function with some clients.""" + """Verify tracking of wireless clients.""" + client = { + "ap_mac": "00:00:00:00:02:01", + "essid": "ssid", + "hostname": "client", + "ip": "10.0.0.1", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } config_entry = await setup_unifi_integration( - hass, aioclient_mock, clients_response=[CLIENT_1] + hass, aioclient_mock, clients_response=[client] ) controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - assert client_1.state == "not_home" + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME # State change signalling works without events - client_1_copy = copy(CLIENT_1) + mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_copy], + "data": [client], } ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - assert client_1.attributes["ip"] == "10.0.0.1" - assert client_1.attributes["mac"] == "00:00:00:00:00:01" - assert client_1.attributes["hostname"] == "client_1" - assert client_1.attributes["host_name"] == "client_1" + client_state = hass.states.get("device_tracker.client") + assert client_state.state == "home" + assert client_state.attributes["ip"] == "10.0.0.1" + assert client_state.attributes["mac"] == "00:00:00:00:00:01" + assert client_state.attributes["hostname"] == "client" + assert client_state.attributes["host_name"] == "client" # State change signalling works with events + # Disconnected event + + event = { + "user": client["mac"], + "ssid": client["essid"], + "hostname": client["hostname"], + "ap": client["ap_mac"], + "duration": 467, + "bytes": 459039, + "key": "EVT_WU_Disconnected", + "subsystem": "wlan", + "site_id": "name", + "time": 1587752927000, + "datetime": "2020-04-24T18:28:47Z", + "msg": f'User{[client["mac"]]} disconnected from "{client["essid"]}" (7m 47s connected, 448.28K bytes, last AP[{client["ap_mac"]}])', + "_id": "5ea32ff730c49e00f90dca1a", + } mock_unifi_websocket( data={ "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_CLIENT_1_WIRELESS_DISCONNECTED], + "data": [event], } ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" + assert hass.states.get("device_tracker.client").state == STATE_HOME + + # Change time to mark client as away new_time = dt_util.utcnow() + controller.option_detection_time with patch("homeassistant.util.dt.utcnow", return_value=new_time): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME + # Connected event + + event = { + "user": client["mac"], + "ssid": client["essid"], + "ap": client["ap_mac"], + "radio": "na", + "channel": "44", + "hostname": client["hostname"], + "key": "EVT_WU_Connected", + "subsystem": "wlan", + "site_id": "name", + "time": 1587753456179, + "datetime": "2020-04-24T18:37:36Z", + "msg": f'User{[client["mac"]]} has connected to AP[{client["ap_mac"]}] with SSID "{client["essid"]}" on "channel 44(na)"', + "_id": "5ea331fa30c49e00f90ddc1a", + } mock_unifi_websocket( data={ "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_CLIENT_1_WIRELESS_CONNECTED], + "data": [event], } ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" + assert hass.states.get("device_tracker.client").state == STATE_HOME async def test_tracked_clients(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some clients.""" - client_4_copy = copy(CLIENT_4) - client_4_copy["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) + client_1 = { + "ap_mac": "00:00:00:00:02:01", + "essid": "ssid", + "hostname": "client_1", + "ip": "10.0.0.1", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + client_2 = { + "ip": "10.0.0.2", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + "name": "Client 2", + } + client_3 = { + "essid": "ssid2", + "hostname": "client_3", + "ip": "10.0.0.3", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:03", + } + client_4 = { + "essid": "ssid", + "hostname": "client_4", + "ip": "10.0.0.4", + "is_wired": True, + "last_seen": dt_util.as_timestamp(dt_util.utcnow()), + "mac": "00:00:00:00:00:04", + } + client_5 = { + "essid": "ssid", + "hostname": "client_5", + "ip": "10.0.0.5", + "is_wired": True, + "last_seen": None, + "mac": "00:00:00:00:00:05", + } await setup_unifi_integration( hass, aioclient_mock, options={CONF_SSID_FILTER: ["ssid"]}, - clients_response=[CLIENT_1, CLIENT_2, CLIENT_3, CLIENT_5, client_4_copy], - known_wireless_clients=(CLIENT_4["mac"],), + clients_response=[client_1, client_2, client_3, client_4, client_5], + known_wireless_clients=(client_4["mac"],), ) + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 4 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - assert client_1.state == "not_home" - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - assert client_2.state == "not_home" + assert hass.states.get("device_tracker.client_1").state == STATE_NOT_HOME + assert hass.states.get("device_tracker.client_2").state == STATE_NOT_HOME # Client on SSID not in SSID filter - client_3 = hass.states.get("device_tracker.client_3") - assert not client_3 + assert not hass.states.get("device_tracker.client_3") # Wireless client with wired bug, if bug active on restart mark device away - client_4 = hass.states.get("device_tracker.client_4") - assert client_4 is not None - assert client_4.state == "not_home" + assert hass.states.get("device_tracker.client_4").state == STATE_NOT_HOME # A client that has never been seen should be marked away. - client_5 = hass.states.get("device_tracker.client_5") - assert client_5 is not None - assert client_5.state == "not_home" + assert hass.states.get("device_tracker.client_5").state == STATE_NOT_HOME # State change signalling works - client_1_copy = copy(CLIENT_1) + mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_copy], + "data": [client_1], } ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" + assert hass.states.get("device_tracker.client_1").state == STATE_HOME async def test_tracked_devices(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some devices.""" + device_1 = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device 1", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + device_2 = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "ip": "10.0.1.2", + "mac": "00:00:00:00:01:02", + "model": "US16P150", + "name": "Device 2", + "next_interval": 20, + "state": 0, + "type": "usw", + "version": "4.0.42.10433", + } await setup_unifi_integration( hass, aioclient_mock, - devices_response=[DEVICE_1, DEVICE_2], + devices_response=[device_1, device_2], ) assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 - assert device_1.state == "home" - - device_2 = hass.states.get("device_tracker.device_2") - assert device_2 - assert device_2.state == "not_home" + assert hass.states.get("device_tracker.device_1").state == STATE_HOME + assert hass.states.get("device_tracker.device_2").state == STATE_NOT_HOME # State change signalling work - device_1_copy = copy(DEVICE_1) - device_1_copy["next_interval"] = 20 + + device_1["next_interval"] = 20 mock_unifi_websocket( data={ "meta": {"message": MESSAGE_DEVICE}, - "data": [device_1_copy], + "data": [device_1], } ) - device_2_copy = copy(DEVICE_2) - device_2_copy["next_interval"] = 50 + device_2["next_interval"] = 50 mock_unifi_websocket( data={ "meta": {"message": MESSAGE_DEVICE}, - "data": [device_2_copy], + "data": [device_2], } ) await hass.async_block_till_done() - device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == "home" - device_2 = hass.states.get("device_tracker.device_2") - assert device_2.state == "home" + assert hass.states.get("device_tracker.device_1").state == STATE_HOME + assert hass.states.get("device_tracker.device_2").state == STATE_HOME + + # Change of time can mark device not_home outside of expected reporting interval new_time = dt_util.utcnow() + timedelta(seconds=90) with patch("homeassistant.util.dt.utcnow", return_value=new_time): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() - device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == "not_home" - device_2 = hass.states.get("device_tracker.device_2") - assert device_2.state == "home" + assert hass.states.get("device_tracker.device_1").state == STATE_NOT_HOME + assert hass.states.get("device_tracker.device_2").state == STATE_HOME # Disabled device is unavailable - device_1_copy = copy(DEVICE_1) - device_1_copy["disabled"] = True + + device_1["disabled"] = True mock_unifi_websocket( data={ "meta": {"message": MESSAGE_DEVICE}, - "data": [device_1_copy], + "data": [device_1], } ) await hass.async_block_till_done() - device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == STATE_UNAVAILABLE + assert hass.states.get("device_tracker.device_1").state == STATE_UNAVAILABLE + assert hass.states.get("device_tracker.device_2").state == STATE_HOME # Update device registry when device is upgraded - device_2_copy = copy(DEVICE_2) - device_2_copy["version"] = EVENT_DEVICE_2_UPGRADED["version_to"] + + event = { + "_id": "5eae7fe02ab79c00f9d38960", + "datetime": "2020-05-09T20:06:37Z", + "key": "EVT_SW_Upgraded", + "msg": f'Switch[{device_2["mac"]}] was upgraded from "{device_2["version"]}" to "4.3.13.11253"', + "subsystem": "lan", + "sw": device_2["mac"], + "sw_name": device_2["name"], + "time": 1589054797635, + "version_from": {device_2["version"]}, + "version_to": "4.3.13.11253", + } + + device_2["version"] = event["version_to"] mock_unifi_websocket( data={ "meta": {"message": MESSAGE_DEVICE}, - "data": [device_2_copy], + "data": [device_2], } ) mock_unifi_websocket( data={ "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_DEVICE_2_UPGRADED], + "data": [event], } ) await hass.async_block_till_done() @@ -356,97 +339,145 @@ async def test_tracked_devices(hass, aioclient_mock, mock_unifi_websocket): entry = entity_registry.async_get("device_tracker.device_2") device_registry = await hass.helpers.device_registry.async_get_registry() device = device_registry.async_get(entry.device_id) - assert device.sw_version == EVENT_DEVICE_2_UPGRADED["version_to"] + assert device.sw_version == event["version_to"] async def test_remove_clients(hass, aioclient_mock, mock_unifi_websocket): """Test the remove_items function with some clients.""" + client_1 = { + "essid": "ssid", + "hostname": "client_1", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + client_2 = { + "hostname": "client_2", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + } await setup_unifi_integration( - hass, aioclient_mock, clients_response=[CLIENT_1, CLIENT_2] + hass, aioclient_mock, clients_response=[client_1, client_2] ) assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 + assert hass.states.get("device_tracker.client_1") + assert hass.states.get("device_tracker.client_2") - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - wired_client = hass.states.get("device_tracker.wired_client") - assert wired_client is not None + # Remove client mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT_REMOVED}, - "data": [CLIENT_1], + "data": [client_1], } ) await hass.async_block_till_done() await hass.async_block_till_done() assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is None - - wired_client = hass.states.get("device_tracker.wired_client") - assert wired_client is not None + assert not hass.states.get("device_tracker.client_1") + assert hass.states.get("device_tracker.client_2") async def test_controller_state_change(hass, aioclient_mock, mock_unifi_websocket): """Verify entities state reflect on controller becoming unavailable.""" + client = { + "essid": "ssid", + "hostname": "client", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + device = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + await setup_unifi_integration( hass, aioclient_mock, - clients_response=[CLIENT_1], - devices_response=[DEVICE_1], + clients_response=[client], + devices_response=[device], ) assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == "home" + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME + assert hass.states.get("device_tracker.device").state == STATE_HOME # Controller unavailable mock_unifi_websocket(state=STATE_DISCONNECTED) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == STATE_UNAVAILABLE - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == STATE_UNAVAILABLE + assert hass.states.get("device_tracker.client").state == STATE_UNAVAILABLE + assert hass.states.get("device_tracker.device").state == STATE_UNAVAILABLE # Controller available mock_unifi_websocket(state=STATE_RUNNING) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == "home" + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME + assert hass.states.get("device_tracker.device").state == STATE_HOME async def test_option_track_clients(hass, aioclient_mock): """Test the tracking of clients can be turned off.""" + wireless_client = { + "essid": "ssid", + "hostname": "wireless_client", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + wired_client = { + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + "name": "Wired Client", + } + device = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, - clients_response=[CLIENT_1, CLIENT_2], - devices_response=[DEVICE_1], + clients_response=[wireless_client, wired_client], + devices_response=[device], ) + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 3 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.wireless_client") + assert hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -454,14 +485,9 @@ async def test_option_track_clients(hass, aioclient_mock): ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert not hass.states.get("device_tracker.wireless_client") + assert not hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -469,34 +495,55 @@ async def test_option_track_clients(hass, aioclient_mock): ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.wireless_client") + assert hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") async def test_option_track_wired_clients(hass, aioclient_mock): """Test the tracking of wired clients can be turned off.""" + wireless_client = { + "essid": "ssid", + "hostname": "wireless_client", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + wired_client = { + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + "name": "Wired Client", + } + device = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, - clients_response=[CLIENT_1, CLIENT_2], - devices_response=[DEVICE_1], + clients_response=[wireless_client, wired_client], + devices_response=[device], ) + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 3 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.wireless_client") + assert hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -504,14 +551,9 @@ async def test_option_track_wired_clients(hass, aioclient_mock): ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.wireless_client") + assert not hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -519,34 +561,44 @@ async def test_option_track_wired_clients(hass, aioclient_mock): ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.wireless_client") + assert hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") async def test_option_track_devices(hass, aioclient_mock): """Test the tracking of devices can be turned off.""" + client = { + "hostname": "client", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + device = { + "board_rev": 3, + "device_id": "mock-id", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, - clients_response=[CLIENT_1, CLIENT_2], - devices_response=[DEVICE_1], + clients_response=[client], + devices_response=[device], ) - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 3 - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 + assert hass.states.get("device_tracker.client") + assert hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -554,14 +606,8 @@ async def test_option_track_devices(hass, aioclient_mock): ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is None + assert hass.states.get("device_tracker.client") + assert not hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -569,36 +615,40 @@ async def test_option_track_devices(hass, aioclient_mock): ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.client") + assert hass.states.get("device_tracker.device") async def test_option_ssid_filter(hass, aioclient_mock, mock_unifi_websocket): """Test the SSID filter works. - Client 1 will travel from a supported SSID to an unsupported ssid. - Client 3 will be removed on change of options since it is in an unsupported SSID. + Client will travel from a supported SSID to an unsupported ssid. + Client on SSID2 will be removed on change of options. """ - client_1_copy = copy(CLIENT_1) - client_1_copy["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) + client = { + "essid": "ssid", + "hostname": "client", + "is_wired": False, + "last_seen": dt_util.as_timestamp(dt_util.utcnow()), + "mac": "00:00:00:00:00:01", + } + client_on_ssid2 = { + "essid": "ssid2", + "hostname": "client_on_ssid2", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + } config_entry = await setup_unifi_integration( - hass, aioclient_mock, clients_response=[client_1_copy, CLIENT_3] + hass, aioclient_mock, clients_response=[client, client_on_ssid2] ) controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - - client_3 = hass.states.get("device_tracker.client_3") - assert client_3 + assert hass.states.get("device_tracker.client").state == STATE_HOME + assert hass.states.get("device_tracker.client_on_ssid2").state == STATE_NOT_HOME # Setting SSID filter will remove clients outside of filter hass.config_entries.async_update_entry( @@ -608,40 +658,34 @@ async def test_option_ssid_filter(hass, aioclient_mock, mock_unifi_websocket): await hass.async_block_till_done() # Not affected by SSID filter - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" + assert hass.states.get("device_tracker.client").state == STATE_HOME # Removed due to SSID filter - client_3 = hass.states.get("device_tracker.client_3") - assert not client_3 + assert not hass.states.get("device_tracker.client_on_ssid2") # Roams to SSID outside of filter - client_1_copy = copy(CLIENT_1) - client_1_copy["essid"] = "other_ssid" + client["essid"] = "other_ssid" mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_copy], + "data": [client], } ) # Data update while SSID filter is in effect shouldn't create the client - client_3_copy = copy(CLIENT_3) - client_3_copy["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) + client_on_ssid2["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_3_copy], + "data": [client_on_ssid2], } ) await hass.async_block_till_done() # SSID filter marks client as away - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME # SSID still outside of filter - client_3 = hass.states.get("device_tracker.client_3") - assert not client_3 + assert not hass.states.get("device_tracker.client_on_ssid2") # Remove SSID filter hass.config_entries.async_update_entry( @@ -653,47 +697,45 @@ async def test_option_ssid_filter(hass, aioclient_mock, mock_unifi_websocket): mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_copy], + "data": [client], } ) mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_3_copy], + "data": [client_on_ssid2], } ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" + assert hass.states.get("device_tracker.client").state == STATE_HOME + assert hass.states.get("device_tracker.client_on_ssid2").state == STATE_HOME - client_3 = hass.states.get("device_tracker.client_3") - assert client_3.state == "home" + # Time pass to mark client as away new_time = dt_util.utcnow() + controller.option_detection_time with patch("homeassistant.util.dt.utcnow", return_value=new_time): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_3_copy], + "data": [client_on_ssid2], } ) await hass.async_block_till_done() + # Client won't go away until after next update - client_3 = hass.states.get("device_tracker.client_3") - assert client_3.state == "home" + assert hass.states.get("device_tracker.client_on_ssid2").state == STATE_HOME # Trigger update to get client marked as away mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_3_copy], + "data": [client_on_ssid2], } ) await hass.async_block_till_done() @@ -705,8 +747,7 @@ async def test_option_ssid_filter(hass, aioclient_mock, mock_unifi_websocket): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() - client_3 = hass.states.get("device_tracker.client_3") - assert client_3.state == "not_home" + assert hass.states.get("device_tracker.client_on_ssid2").state == STATE_NOT_HOME async def test_wireless_client_go_wired_issue( @@ -716,35 +757,41 @@ async def test_wireless_client_go_wired_issue( UniFi has a known issue that when a wireless device goes away it sometimes gets marked as wired. """ - client_1_client = copy(CLIENT_1) - client_1_client["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) + client = { + "essid": "ssid", + "hostname": "client", + "ip": "10.0.0.1", + "is_wired": False, + "last_seen": dt_util.as_timestamp(dt_util.utcnow()), + "mac": "00:00:00:00:00:01", + } config_entry = await setup_unifi_integration( - hass, aioclient_mock, clients_response=[client_1_client] + hass, aioclient_mock, clients_response=[client] ) controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 # Client is wireless - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is False # Trigger wired bug - client_1_client["is_wired"] = True + client["is_wired"] = True mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_client], + "data": [client], } ) await hass.async_block_till_done() # Wired bug fix keeps client marked as wireless - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is False # Pass time new_time = dt_util.utcnow() + controller.option_detection_time @@ -753,74 +800,80 @@ async def test_wireless_client_go_wired_issue( await hass.async_block_till_done() # Marked as home according to the timer - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_NOT_HOME + assert client_state.attributes["is_wired"] is False # Try to mark client as connected mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_client], + "data": [client], } ) await hass.async_block_till_done() # Make sure it don't go online again until wired bug disappears - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_NOT_HOME + assert client_state.attributes["is_wired"] is False # Make client wireless - client_1_client["is_wired"] = False + client["is_wired"] = False mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_client], + "data": [client], } ) await hass.async_block_till_done() # Client is no longer affected by wired bug and can be marked online - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is False async def test_option_ignore_wired_bug(hass, aioclient_mock, mock_unifi_websocket): """Test option to ignore wired bug.""" - client_1_client = copy(CLIENT_1) - client_1_client["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) + client = { + "ap_mac": "00:00:00:00:02:01", + "essid": "ssid", + "hostname": "client", + "ip": "10.0.0.1", + "is_wired": False, + "last_seen": dt_util.as_timestamp(dt_util.utcnow()), + "mac": "00:00:00:00:00:01", + } config_entry = await setup_unifi_integration( hass, aioclient_mock, options={CONF_IGNORE_WIRED_BUG: True}, - clients_response=[client_1_client], + clients_response=[client], ) controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 # Client is wireless - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is False # Trigger wired bug - client_1_client["is_wired"] = True + client["is_wired"] = True mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_client], + "data": [client], } ) await hass.async_block_till_done() # Wired bug in effect - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is True + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is True # pass time new_time = dt_util.utcnow() + controller.option_detection_time @@ -829,42 +882,61 @@ async def test_option_ignore_wired_bug(hass, aioclient_mock, mock_unifi_websocke await hass.async_block_till_done() # Timer marks client as away - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" - assert client_1.attributes["is_wired"] is True + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_NOT_HOME + assert client_state.attributes["is_wired"] is True # Mark client as connected again mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_client], + "data": [client], } ) await hass.async_block_till_done() # Ignoring wired bug allows client to go home again even while affected - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is True + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is True # Make client wireless - client_1_client["is_wired"] = False + client["is_wired"] = False mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_client], + "data": [client], } ) await hass.async_block_till_done() # Client is wireless and still connected - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is False async def test_restoring_client(hass, aioclient_mock): - """Test the update_items function with some clients.""" + """Verify clients are restored from clients_all if they ever was registered to entity registry.""" + client = { + "hostname": "client", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + restored = { + "hostname": "restored", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + } + not_restored = { + "hostname": "not_restored", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:03", + } + config_entry = config_entries.ConfigEntry( version=1, domain=UNIFI_DOMAIN, @@ -881,15 +953,8 @@ async def test_restoring_client(hass, aioclient_mock): registry.async_get_or_create( TRACKER_DOMAIN, UNIFI_DOMAIN, - f'{CLIENT_1["mac"]}-site_id', - suggested_object_id=CLIENT_1["hostname"], - config_entry=config_entry, - ) - registry.async_get_or_create( - TRACKER_DOMAIN, - UNIFI_DOMAIN, - f'{CLIENT_2["mac"]}-site_id', - suggested_object_id=CLIENT_2["hostname"], + f'{restored["mac"]}-site_id', + suggested_object_id=restored["hostname"], config_entry=config_entry, ) @@ -897,31 +962,63 @@ async def test_restoring_client(hass, aioclient_mock): hass, aioclient_mock, options={CONF_BLOCK_CLIENT: True}, - clients_response=[CLIENT_2], - clients_all_response=[CLIENT_1], + clients_response=[client], + clients_all_response=[restored, not_restored], ) - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - device_1 = hass.states.get("device_tracker.client_1") - assert device_1 is not None + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 + assert hass.states.get("device_tracker.client") + assert hass.states.get("device_tracker.restored") + assert not hass.states.get("device_tracker.not_restored") async def test_dont_track_clients(hass, aioclient_mock): """Test don't track clients config works.""" + wireless_client = { + "essid": "ssid", + "hostname": "Wireless client", + "ip": "10.0.0.1", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + wired_client = { + "hostname": "Wired client", + "ip": "10.0.0.2", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + } + device = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, options={CONF_TRACK_CLIENTS: False}, - clients_response=[CLIENT_1], - devices_response=[DEVICE_1], + clients_response=[wireless_client, wired_client], + devices_response=[device], ) + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert not hass.states.get("device_tracker.wireless_client") + assert not hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -929,31 +1026,49 @@ async def test_dont_track_clients(hass, aioclient_mock): ) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 3 + assert hass.states.get("device_tracker.wireless_client") + assert hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") async def test_dont_track_devices(hass, aioclient_mock): """Test don't track devices config works.""" + client = { + "hostname": "client", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + device = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, options={CONF_TRACK_DEVICES: False}, - clients_response=[CLIENT_1], - devices_response=[DEVICE_1], + clients_response=[client], + devices_response=[device], ) + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is None + assert hass.states.get("device_tracker.client") + assert not hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -962,29 +1077,36 @@ async def test_dont_track_devices(hass, aioclient_mock): await hass.async_block_till_done() assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.client") + assert hass.states.get("device_tracker.device") async def test_dont_track_wired_clients(hass, aioclient_mock): """Test don't track wired clients config works.""" + wireless_client = { + "essid": "ssid", + "hostname": "Wireless Client", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + wired_client = { + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + "name": "Wired Client", + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, options={CONF_TRACK_WIRED_CLIENTS: False}, - clients_response=[CLIENT_1, CLIENT_2], + clients_response=[wireless_client, wired_client], ) + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is None + assert hass.states.get("device_tracker.wireless_client") + assert not hass.states.get("device_tracker.wired_client") hass.config_entries.async_update_entry( config_entry, @@ -993,9 +1115,5 @@ async def test_dont_track_wired_clients(hass, aioclient_mock): await hass.async_block_till_done() assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None + assert hass.states.get("device_tracker.wireless_client") + assert hass.states.get("device_tracker.wired_client") From 4018d0a1523e547117cfb1f5c17a57bd5a50d722 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Sun, 7 Mar 2021 13:53:48 +0100 Subject: [PATCH 213/831] Correctly close lacrosse on homeassistant stop (#47555) Since lacrosse.close() takes no arguments, but was directly added as a listener to EVENT_HOMEASSISTANT_STOP, the following occured on shutdown: Traceback (most recent call last): File "/usr/lib/python/concurrent/futures/thread.py", line 57, in run result = self.fn(*self.args, **self.kwargs) TypeError: close() takes 1 positional argument but 2 were given --- homeassistant/components/lacrosse/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/lacrosse/sensor.py b/homeassistant/components/lacrosse/sensor.py index 2c7f5d294a9..f65c792ddb0 100644 --- a/homeassistant/components/lacrosse/sensor.py +++ b/homeassistant/components/lacrosse/sensor.py @@ -78,7 +78,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.warning("Unable to open serial port: %s", exc) return False - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lacrosse.close) + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: lacrosse.close()) if CONF_JEELINK_LED in config: lacrosse.led_mode_state(config.get(CONF_JEELINK_LED)) From 07fd1b3b4375227a019c1048d6b62de6560b3bc8 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 7 Mar 2021 08:14:15 -0500 Subject: [PATCH 214/831] Clean up Lutron Caseta (#47534) --- homeassistant/components/lutron_caseta/__init__.py | 3 --- homeassistant/components/lutron_caseta/binary_sensor.py | 1 - homeassistant/components/lutron_caseta/config_flow.py | 2 -- homeassistant/components/lutron_caseta/const.py | 1 - homeassistant/components/lutron_caseta/cover.py | 1 - homeassistant/components/lutron_caseta/fan.py | 1 - homeassistant/components/lutron_caseta/light.py | 1 - homeassistant/components/lutron_caseta/scene.py | 1 - homeassistant/components/lutron_caseta/switch.py | 1 - 9 files changed, 12 deletions(-) diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index f93833acfc5..07851db1e23 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -67,7 +67,6 @@ PLATFORMS = ["light", "switch", "cover", "scene", "fan", "binary_sensor"] async def async_setup(hass, base_config): """Set up the Lutron component.""" - hass.data.setdefault(DOMAIN, {}) if DOMAIN in base_config: @@ -92,7 +91,6 @@ async def async_setup(hass, base_config): async def async_setup_entry(hass, config_entry): """Set up a bridge from a config entry.""" - host = config_entry.data[CONF_HOST] keyfile = hass.config.path(config_entry.data[CONF_KEYFILE]) certfile = hass.config.path(config_entry.data[CONF_CERTFILE]) @@ -280,7 +278,6 @@ def _async_subscribe_pico_remote_events(hass, lip, button_devices_by_id): async def async_unload_entry(hass, config_entry): """Unload the bridge bridge from a config entry.""" - data = hass.data[DOMAIN][config_entry.entry_id] data[BRIDGE_LEAP].close() if data[BRIDGE_LIP]: diff --git a/homeassistant/components/lutron_caseta/binary_sensor.py b/homeassistant/components/lutron_caseta/binary_sensor.py index 97053eba08c..b58afd22a90 100644 --- a/homeassistant/components/lutron_caseta/binary_sensor.py +++ b/homeassistant/components/lutron_caseta/binary_sensor.py @@ -16,7 +16,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): Adds occupancy groups from the Caseta bridge associated with the config_entry as binary_sensor entities. """ - entities = [] data = hass.data[CASETA_DOMAIN][config_entry.entry_id] bridge = data[BRIDGE_LEAP] diff --git a/homeassistant/components/lutron_caseta/config_flow.py b/homeassistant/components/lutron_caseta/config_flow.py index 6cd30a78f0c..98b30b886f3 100644 --- a/homeassistant/components/lutron_caseta/config_flow.py +++ b/homeassistant/components/lutron_caseta/config_flow.py @@ -171,7 +171,6 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): This flow is triggered by `async_setup`. """ - host = import_info[CONF_HOST] # Store the imported config for other steps in this flow to access. self.data[CONF_HOST] = host @@ -213,7 +212,6 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_validate_connectable_bridge_config(self): """Check if we can connect to the bridge with the current config.""" - bridge = None try: diff --git a/homeassistant/components/lutron_caseta/const.py b/homeassistant/components/lutron_caseta/const.py index fcc647f00ba..40a5d2b01fd 100644 --- a/homeassistant/components/lutron_caseta/const.py +++ b/homeassistant/components/lutron_caseta/const.py @@ -31,7 +31,6 @@ ATTR_ACTION = "action" ACTION_PRESS = "press" ACTION_RELEASE = "release" -CONF_TYPE = "type" CONF_SUBTYPE = "subtype" BRIDGE_TIMEOUT = 35 diff --git a/homeassistant/components/lutron_caseta/cover.py b/homeassistant/components/lutron_caseta/cover.py index b3924ba31c8..31f7e9b55bd 100644 --- a/homeassistant/components/lutron_caseta/cover.py +++ b/homeassistant/components/lutron_caseta/cover.py @@ -24,7 +24,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): Adds shades from the Caseta bridge associated with the config_entry as cover entities. """ - entities = [] data = hass.data[CASETA_DOMAIN][config_entry.entry_id] bridge = data[BRIDGE_LEAP] diff --git a/homeassistant/components/lutron_caseta/fan.py b/homeassistant/components/lutron_caseta/fan.py index 55799315ba0..de2b2a2ae8c 100644 --- a/homeassistant/components/lutron_caseta/fan.py +++ b/homeassistant/components/lutron_caseta/fan.py @@ -25,7 +25,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): Adds fan controllers from the Caseta bridge associated with the config_entry as fan entities. """ - entities = [] data = hass.data[CASETA_DOMAIN][config_entry.entry_id] bridge = data[BRIDGE_LEAP] diff --git a/homeassistant/components/lutron_caseta/light.py b/homeassistant/components/lutron_caseta/light.py index ec200118082..016dd925b23 100644 --- a/homeassistant/components/lutron_caseta/light.py +++ b/homeassistant/components/lutron_caseta/light.py @@ -33,7 +33,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): Adds dimmers from the Caseta bridge associated with the config_entry as light entities. """ - entities = [] data = hass.data[CASETA_DOMAIN][config_entry.entry_id] bridge = data[BRIDGE_LEAP] diff --git a/homeassistant/components/lutron_caseta/scene.py b/homeassistant/components/lutron_caseta/scene.py index d70048db8cd..43e0429d151 100644 --- a/homeassistant/components/lutron_caseta/scene.py +++ b/homeassistant/components/lutron_caseta/scene.py @@ -12,7 +12,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): Adds scenes from the Caseta bridge associated with the config_entry as scene entities. """ - entities = [] data = hass.data[CASETA_DOMAIN][config_entry.entry_id] bridge = data[BRIDGE_LEAP] diff --git a/homeassistant/components/lutron_caseta/switch.py b/homeassistant/components/lutron_caseta/switch.py index 1e5b4ab6fe5..c6aea447055 100644 --- a/homeassistant/components/lutron_caseta/switch.py +++ b/homeassistant/components/lutron_caseta/switch.py @@ -15,7 +15,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): Adds switches from the Caseta bridge associated with the config_entry as switch entities. """ - entities = [] data = hass.data[CASETA_DOMAIN][config_entry.entry_id] bridge = data[BRIDGE_LEAP] From 13d4d7039eb114bafb06f5ccbbec662c62641847 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 7 Mar 2021 08:15:43 -0500 Subject: [PATCH 215/831] Clean up kmtronic (#47537) --- homeassistant/components/kmtronic/__init__.py | 15 +++------------ homeassistant/components/kmtronic/config_flow.py | 7 +++---- homeassistant/components/kmtronic/const.py | 5 ----- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/kmtronic/__init__.py b/homeassistant/components/kmtronic/__init__.py index 1f012985447..0ac4ea8cb59 100644 --- a/homeassistant/components/kmtronic/__init__.py +++ b/homeassistant/components/kmtronic/__init__.py @@ -10,20 +10,12 @@ from pykmtronic.hub import KMTronicHubAPI import voluptuous as vol from homeassistant.config_entries import ConfigEntry, ConfigEntryNotReady +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.helpers import aiohttp_client from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import ( - CONF_HOSTNAME, - CONF_PASSWORD, - CONF_USERNAME, - DATA_COORDINATOR, - DATA_HOST, - DATA_HUB, - DOMAIN, - MANUFACTURER, -) +from .const import DATA_COORDINATOR, DATA_HOST, DATA_HUB, DOMAIN, MANUFACTURER CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) @@ -41,11 +33,10 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up kmtronic from a config entry.""" - session = aiohttp_client.async_get_clientsession(hass) auth = Auth( session, - f"http://{entry.data[CONF_HOSTNAME]}", + f"http://{entry.data[CONF_HOST]}", entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD], ) diff --git a/homeassistant/components/kmtronic/config_flow.py b/homeassistant/components/kmtronic/config_flow.py index 376bb64c34c..841c541dd4e 100644 --- a/homeassistant/components/kmtronic/config_flow.py +++ b/homeassistant/components/kmtronic/config_flow.py @@ -7,23 +7,22 @@ from pykmtronic.hub import KMTronicHubAPI import voluptuous as vol from homeassistant import config_entries, core, exceptions +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import aiohttp_client -from .const import CONF_HOSTNAME, CONF_PASSWORD, CONF_USERNAME from .const import DOMAIN # pylint:disable=unused-import _LOGGER = logging.getLogger(__name__) -DATA_SCHEMA = vol.Schema({CONF_HOSTNAME: str, CONF_USERNAME: str, CONF_PASSWORD: str}) +DATA_SCHEMA = vol.Schema({CONF_HOST: str, CONF_USERNAME: str, CONF_PASSWORD: str}) async def validate_input(hass: core.HomeAssistant, data): """Validate the user input allows us to connect.""" - session = aiohttp_client.async_get_clientsession(hass) auth = Auth( session, - f"http://{data[CONF_HOSTNAME]}", + f"http://{data[CONF_HOST]}", data[CONF_USERNAME], data[CONF_PASSWORD], ) diff --git a/homeassistant/components/kmtronic/const.py b/homeassistant/components/kmtronic/const.py index 58553217799..8ca37a0b797 100644 --- a/homeassistant/components/kmtronic/const.py +++ b/homeassistant/components/kmtronic/const.py @@ -2,10 +2,6 @@ DOMAIN = "kmtronic" -CONF_HOSTNAME = "host" -CONF_USERNAME = "username" -CONF_PASSWORD = "password" - DATA_HUB = "hub" DATA_HOST = "host" DATA_COORDINATOR = "coordinator" @@ -13,4 +9,3 @@ DATA_COORDINATOR = "coordinator" MANUFACTURER = "KMtronic" ATTR_MANUFACTURER = "manufacturer" ATTR_IDENTIFIERS = "identifiers" -ATTR_NAME = "name" From d85d1a65a7a00670ce18fdff7483930e440334cf Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 7 Mar 2021 14:20:21 +0100 Subject: [PATCH 216/831] Fix mysensors unload clean up (#47541) --- .../components/mysensors/__init__.py | 21 +++--------------- homeassistant/components/mysensors/gateway.py | 18 +++++++++++---- homeassistant/components/mysensors/helpers.py | 22 ++++++++++++++++++- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index bcab1ed86b4..7dd118099f7 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -38,11 +38,11 @@ from .const import ( MYSENSORS_ON_UNLOAD, PLATFORMS_WITH_ENTRY_SUPPORT, DevId, - GatewayId, SensorType, ) from .device import MySensorsDevice, MySensorsEntity, get_mysensors_devices from .gateway import finish_setup, get_mysensors_gateway, gw_stop, setup_gateway +from .helpers import on_unload _LOGGER = logging.getLogger(__name__) @@ -253,29 +253,14 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo for fnct in hass.data[DOMAIN][key]: fnct() + hass.data[DOMAIN].pop(key) + del hass.data[DOMAIN][MYSENSORS_GATEWAYS][entry.entry_id] await gw_stop(hass, entry, gateway) return True -async def on_unload( - hass: HomeAssistantType, entry: Union[ConfigEntry, GatewayId], fnct: Callable -) -> None: - """Register a callback to be called when entry is unloaded. - - This function is used by platforms to cleanup after themselves - """ - if isinstance(entry, GatewayId): - uniqueid = entry - else: - uniqueid = entry.entry_id - key = MYSENSORS_ON_UNLOAD.format(uniqueid) - if key not in hass.data[DOMAIN]: - hass.data[DOMAIN][key] = [] - hass.data[DOMAIN][key].append(fnct) - - @callback def setup_mysensors_platform( hass: HomeAssistant, diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index b6797cafb37..a7f3a053d3f 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -31,7 +31,12 @@ from .const import ( GatewayId, ) from .handler import HANDLERS -from .helpers import discover_mysensors_platform, validate_child, validate_node +from .helpers import ( + discover_mysensors_platform, + on_unload, + validate_child, + validate_node, +) _LOGGER = logging.getLogger(__name__) @@ -260,8 +265,8 @@ async def _discover_persistent_devices( async def gw_stop(hass, entry: ConfigEntry, gateway: BaseAsyncGateway): """Stop the gateway.""" - connect_task = hass.data[DOMAIN].get( - MYSENSORS_GATEWAY_START_TASK.format(entry.entry_id) + connect_task = hass.data[DOMAIN].pop( + MYSENSORS_GATEWAY_START_TASK.format(entry.entry_id), None ) if connect_task is not None and not connect_task.done(): connect_task.cancel() @@ -288,7 +293,12 @@ async def _gw_start( async def stop_this_gw(_: Event): await gw_stop(hass, entry, gateway) - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_this_gw) + await on_unload( + hass, + entry.entry_id, + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_this_gw), + ) + if entry.data[CONF_DEVICE] == MQTT_COMPONENT: # Gatways connected via mqtt doesn't send gateway ready message. return diff --git a/homeassistant/components/mysensors/helpers.py b/homeassistant/components/mysensors/helpers.py index 4452dd0575b..0b8dc361158 100644 --- a/homeassistant/components/mysensors/helpers.py +++ b/homeassistant/components/mysensors/helpers.py @@ -2,16 +2,18 @@ from collections import defaultdict from enum import IntEnum import logging -from typing import DefaultDict, Dict, List, Optional, Set +from typing import Callable, DefaultDict, Dict, List, Optional, Set, Union from mysensors import BaseAsyncGateway, Message from mysensors.sensor import ChildSensor import voluptuous as vol +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util.decorator import Registry from .const import ( @@ -20,6 +22,7 @@ from .const import ( DOMAIN, FLAT_PLATFORM_TYPES, MYSENSORS_DISCOVERY, + MYSENSORS_ON_UNLOAD, TYPE_TO_PLATFORMS, DevId, GatewayId, @@ -31,6 +34,23 @@ _LOGGER = logging.getLogger(__name__) SCHEMAS = Registry() +async def on_unload( + hass: HomeAssistantType, entry: Union[ConfigEntry, GatewayId], fnct: Callable +) -> None: + """Register a callback to be called when entry is unloaded. + + This function is used by platforms to cleanup after themselves. + """ + if isinstance(entry, GatewayId): + uniqueid = entry + else: + uniqueid = entry.entry_id + key = MYSENSORS_ON_UNLOAD.format(uniqueid) + if key not in hass.data[DOMAIN]: + hass.data[DOMAIN][key] = [] + hass.data[DOMAIN][key].append(fnct) + + @callback def discover_mysensors_platform( hass: HomeAssistant, gateway_id: GatewayId, platform: str, new_devices: List[DevId] From d3bd2378ba07c5df89052d5e7307fb775e63edcb Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 7 Mar 2021 15:07:02 +0000 Subject: [PATCH 217/831] Correct weather entities forecast time (#47565) --- homeassistant/components/aemet/weather_update_coordinator.py | 4 ++-- .../components/openweathermap/weather_update_coordinator.py | 4 +++- tests/components/aemet/test_sensor.py | 4 +++- tests/components/aemet/test_weather.py | 5 +++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/aemet/weather_update_coordinator.py b/homeassistant/components/aemet/weather_update_coordinator.py index 6a06b1dd391..619429c9a5b 100644 --- a/homeassistant/components/aemet/weather_update_coordinator.py +++ b/homeassistant/components/aemet/weather_update_coordinator.py @@ -393,7 +393,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): ), ATTR_FORECAST_TEMP: self._get_temperature_day(day), ATTR_FORECAST_TEMP_LOW: self._get_temperature_low_day(day), - ATTR_FORECAST_TIME: dt_util.as_utc(date), + ATTR_FORECAST_TIME: dt_util.as_utc(date).isoformat(), ATTR_FORECAST_WIND_SPEED: self._get_wind_speed_day(day), ATTR_FORECAST_WIND_BEARING: self._get_wind_bearing_day(day), } @@ -412,7 +412,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): day, hour ), ATTR_FORECAST_TEMP: self._get_temperature(day, hour), - ATTR_FORECAST_TIME: dt_util.as_utc(forecast_dt), + ATTR_FORECAST_TIME: dt_util.as_utc(forecast_dt).isoformat(), ATTR_FORECAST_WIND_SPEED: self._get_wind_speed(day, hour), ATTR_FORECAST_WIND_BEARING: self._get_wind_bearing(day, hour), } diff --git a/homeassistant/components/openweathermap/weather_update_coordinator.py b/homeassistant/components/openweathermap/weather_update_coordinator.py index 201029c3979..b4d9b7a9c80 100644 --- a/homeassistant/components/openweathermap/weather_update_coordinator.py +++ b/homeassistant/components/openweathermap/weather_update_coordinator.py @@ -140,7 +140,9 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): def _convert_forecast(self, entry): forecast = { - ATTR_FORECAST_TIME: dt.utc_from_timestamp(entry.reference_time("unix")), + ATTR_FORECAST_TIME: dt.utc_from_timestamp( + entry.reference_time("unix") + ).isoformat(), ATTR_FORECAST_PRECIPITATION: self._calc_precipitation( entry.rain, entry.snow ), diff --git a/tests/components/aemet/test_sensor.py b/tests/components/aemet/test_sensor.py index 05f2d8d0b50..b265b996709 100644 --- a/tests/components/aemet/test_sensor.py +++ b/tests/components/aemet/test_sensor.py @@ -37,7 +37,9 @@ async def test_aemet_forecast_create_sensors(hass): assert state.state == "-4" state = hass.states.get("sensor.aemet_daily_forecast_time") - assert state.state == "2021-01-10 00:00:00+00:00" + assert ( + state.state == dt_util.parse_datetime("2021-01-10 00:00:00+00:00").isoformat() + ) state = hass.states.get("sensor.aemet_daily_forecast_wind_bearing") assert state.state == "45.0" diff --git a/tests/components/aemet/test_weather.py b/tests/components/aemet/test_weather.py index eef6107d543..43acf4c1c87 100644 --- a/tests/components/aemet/test_weather.py +++ b/tests/components/aemet/test_weather.py @@ -51,8 +51,9 @@ async def test_aemet_weather(hass): assert forecast.get(ATTR_FORECAST_PRECIPITATION_PROBABILITY) == 30 assert forecast.get(ATTR_FORECAST_TEMP) == 4 assert forecast.get(ATTR_FORECAST_TEMP_LOW) == -4 - assert forecast.get(ATTR_FORECAST_TIME) == dt_util.parse_datetime( - "2021-01-10 00:00:00+00:00" + assert ( + forecast.get(ATTR_FORECAST_TIME) + == dt_util.parse_datetime("2021-01-10 00:00:00+00:00").isoformat() ) assert forecast.get(ATTR_FORECAST_WIND_BEARING) == 45.0 assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 20 From 7050c71524fa0891b4feace0b363f8e9b30c1e6c Mon Sep 17 00:00:00 2001 From: Austin Mroczek Date: Sun, 7 Mar 2021 09:46:14 -0800 Subject: [PATCH 218/831] Round miles in myChevy sensors (#46879) --- homeassistant/components/mychevy/sensor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/mychevy/sensor.py b/homeassistant/components/mychevy/sensor.py index 41311845185..df8b136741a 100644 --- a/homeassistant/components/mychevy/sensor.py +++ b/homeassistant/components/mychevy/sensor.py @@ -160,6 +160,8 @@ class EVSensor(Entity): """Update state.""" if self._car is not None: self._state = getattr(self._car, self._attr, None) + if self._unit_of_measurement == "miles": + self._state = round(self._state) for attr in self._extra_attrs: self._state_attributes[attr] = getattr(self._car, attr) self.async_write_ha_state() From a066f8482866d69a38f69dd99cc20682565d9f4b Mon Sep 17 00:00:00 2001 From: Alex <7845120+newAM@users.noreply.github.com> Date: Sun, 7 Mar 2021 09:49:13 -0800 Subject: [PATCH 219/831] Remove @newAM from hdmi_cec codeowners (#47542) --- CODEOWNERS | 1 - homeassistant/components/hdmi_cec/manifest.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 07340065588..3f0b3d89e05 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -186,7 +186,6 @@ homeassistant/components/guardian/* @bachya homeassistant/components/habitica/* @ASMfreaK @leikoilja homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco @mkeesey homeassistant/components/hassio/* @home-assistant/supervisor -homeassistant/components/hdmi_cec/* @newAM homeassistant/components/heatmiser/* @andylockran homeassistant/components/heos/* @andrewsayre homeassistant/components/here_travel_time/* @eifinger diff --git a/homeassistant/components/hdmi_cec/manifest.json b/homeassistant/components/hdmi_cec/manifest.json index 4c307582281..90ed7cc3359 100644 --- a/homeassistant/components/hdmi_cec/manifest.json +++ b/homeassistant/components/hdmi_cec/manifest.json @@ -3,5 +3,5 @@ "name": "HDMI-CEC", "documentation": "https://www.home-assistant.io/integrations/hdmi_cec", "requirements": ["pyCEC==0.4.14"], - "codeowners": ["@newAM"] + "codeowners": [] } From 683425876f6fa8ca509cd2ebc8090284027a53ad Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 8 Mar 2021 06:45:15 +0100 Subject: [PATCH 220/831] Update frontend to 20210302.6 (#47592) --- 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 8093c65d91a..694be0382f7 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20210302.5" + "home-assistant-frontend==20210302.6" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 0586b956f39..7642e14bda8 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210302.5 +home-assistant-frontend==20210302.6 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index f27e3835467..285f8ed5133 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.5 +home-assistant-frontend==20210302.6 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d06ac8bfd5b..7804e3c3926 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.5 +home-assistant-frontend==20210302.6 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 1d387e12ccd83a681ab29209d329ae437acd67ea Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 8 Mar 2021 03:08:17 -0500 Subject: [PATCH 221/831] Add fallback zwave_js entity name using node ID (#47582) * add fallback zwave_js entity name using node ID * add new fixture and test for name that was failing --- homeassistant/components/zwave_js/entity.py | 6 +- tests/components/zwave_js/conftest.py | 14 + tests/components/zwave_js/test_init.py | 6 + .../zwave_js/null_name_check_state.json | 414 ++++++++++++++++++ 4 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/zwave_js/null_name_check_state.json diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index c061abc4d0d..7620323d940 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -105,7 +105,11 @@ class ZWaveBaseEntity(Entity): """Generate entity name.""" if additional_info is None: additional_info = [] - name: str = self.info.node.name or self.info.node.device_config.description + name: str = ( + self.info.node.name + or self.info.node.device_config.description + or f"Node {self.info.node.node_id}" + ) if include_value_name: value_name = ( alternate_value_name diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index b171fb38425..c9f7d35eb13 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -294,6 +294,12 @@ def inovelli_lzw36_state_fixture(): return json.loads(load_fixture("zwave_js/inovelli_lzw36_state.json")) +@pytest.fixture(name="null_name_check_state", scope="session") +def null_name_check_state_fixture(): + """Load the null name check node state fixture data.""" + return json.loads(load_fixture("zwave_js/null_name_check_state.json")) + + @pytest.fixture(name="client") def mock_client_fixture(controller_state, version_state): """Mock a client.""" @@ -490,6 +496,14 @@ def in_wall_smart_fan_control_fixture(client, in_wall_smart_fan_control_state): return node +@pytest.fixture(name="null_name_check") +def null_name_check_fixture(client, null_name_check_state): + """Mock a node with no name.""" + node = Node(client, copy.deepcopy(null_name_check_state)) + client.driver.controller.nodes[node.node_id] = node + return node + + @pytest.fixture(name="multiple_devices") def multiple_devices_fixture( client, climate_radio_thermostat_ct100_plus_state, lock_schlage_be469_state diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index e56db58f3cc..00574bd2d2f 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -459,6 +459,12 @@ async def test_existing_node_ready( ) +async def test_null_name(hass, client, null_name_check, integration): + """Test that node without a name gets a generic node name.""" + node = null_name_check + assert hass.states.get(f"switch.node_{node.node_id}") + + async def test_existing_node_not_ready(hass, client, multisensor_6, device_registry): """Test we handle a non ready node that exists during integration setup.""" node = multisensor_6 diff --git a/tests/fixtures/zwave_js/null_name_check_state.json b/tests/fixtures/zwave_js/null_name_check_state.json new file mode 100644 index 00000000000..fe63eaee207 --- /dev/null +++ b/tests/fixtures/zwave_js/null_name_check_state.json @@ -0,0 +1,414 @@ +{ + "nodeId": 10, + "index": 0, + "installerIcon": 3328, + "userIcon": 3328, + "status": 4, + "ready": true, + "isListening": true, + "isFrequentListening": false, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 4, + "isBeaming": true, + "manufacturerId": 277, + "productId": 1, + "productType": 272, + "firmwareVersion": "2.17", + "zwavePlusVersion": 1, + "nodeType": 0, + "roleType": 1, + "neighbors": [], + "endpointCountIsDynamic": false, + "endpointsHaveIdenticalCapabilities": false, + "individualEndpointCount": 4, + "aggregatedEndpointCount": 0, + "interviewAttempts": 1, + "interviewStage": 7, + "endpoints": [ + { + "nodeId": 10, + "index": 0, + "installerIcon": 3328, + "userIcon": 3328 + }, + { + "nodeId": 10, + "index": 1 + }, + { + "nodeId": 10, + "index": 2 + }, + { + "nodeId": 10, + "index": 3 + }, + { + "nodeId": 10, + "index": 4 + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Air temperature", + "propertyName": "Air temperature", + "ccVersion": 7, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "\u00b0C", + "label": "Air temperature", + "ccSpecific": { + "sensorType": 1, + "scale": 0 + } + }, + "value": 2.9 + }, + { + "endpoint": 0, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Humidity", + "propertyName": "Humidity", + "ccVersion": 7, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "%", + "label": "Humidity", + "ccSpecific": { + "sensorType": 5, + "scale": 0 + } + }, + "value": 8 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 277 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 272 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "4.38" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": ["2.17"] + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version" + } + }, + { + "endpoint": 1, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Current value" + }, + "value": false + }, + { + "endpoint": 1, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Target value" + }, + "value": false + }, + { + "endpoint": 2, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Current value" + }, + "value": false + }, + { + "endpoint": 2, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Target value" + }, + "value": false + }, + { + "endpoint": 3, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Current value" + }, + "value": false + }, + { + "endpoint": 3, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Target value" + } + }, + { + "endpoint": 4, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Current value" + }, + "value": true + }, + { + "endpoint": 4, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Target value" + }, + "value": true + } + ], + "deviceClass": { + "basic": { + "key": 4, + "label": "Routing Slave" + }, + "generic": { + "key": 33, + "label": "Multilevel Sensor" + }, + "specific": { + "key": 1, + "label": "Routing Multilevel Sensor" + }, + "mandatorySupportedCCs": [32, 49], + "mandatoryControlledCCs": [] + }, + "commandClasses": [ + { + "id": 37, + "name": "Binary Switch", + "version": 1, + "isSecure": false + }, + { + "id": 49, + "name": "Multilevel Sensor", + "version": 7, + "isSecure": false + }, + { + "id": 89, + "name": "Association Group Information", + "version": 1, + "isSecure": false + }, + { + "id": 90, + "name": "Device Reset Locally", + "version": 1, + "isSecure": false + }, + { + "id": 94, + "name": "Z-Wave Plus Info", + "version": 2, + "isSecure": false + }, + { + "id": 96, + "name": "Multi Channel", + "version": 4, + "isSecure": false + }, + { + "id": 112, + "name": "Configuration", + "version": 1, + "isSecure": false + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 2, + "isSecure": false + }, + { + "id": 122, + "name": "Firmware Update Meta Data", + "version": 3, + "isSecure": false + }, + { + "id": 133, + "name": "Association", + "version": 2, + "isSecure": false + }, + { + "id": 134, + "name": "Version", + "version": 2, + "isSecure": false + }, + { + "id": 142, + "name": "Multi Channel Association", + "version": 3, + "isSecure": false + } + ] +} From 629700c97ce6d3c281fdeda682cc0ceed3bb238d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Mar 2021 10:37:42 +0100 Subject: [PATCH 222/831] Bump actions/stale from v3.0.17 to v3.0.18 (#47612) Bumps [actions/stale](https://github.com/actions/stale) from v3.0.17 to v3.0.18. - [Release notes](https://github.com/actions/stale/releases) - [Commits](https://github.com/actions/stale/compare/v3.0.17...3b3c3f03cd4d8e2b61e179ef744a0d20efbe90b4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index a280d0ee89a..3ff0f47cedc 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -16,7 +16,7 @@ jobs: # - No PRs marked as no-stale # - No issues marked as no-stale or help-wanted - name: 90 days stale issues & PRs policy - uses: actions/stale@v3.0.17 + uses: actions/stale@v3.0.18 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 90 @@ -53,7 +53,7 @@ jobs: # - No PRs marked as no-stale or new-integrations # - No issues (-1) - name: 30 days stale PRs policy - uses: actions/stale@v3.0.17 + uses: actions/stale@v3.0.18 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 30 @@ -78,7 +78,7 @@ jobs: # - No Issues marked as no-stale or help-wanted # - No PRs (-1) - name: Needs more information stale issues policy - uses: actions/stale@v3.0.17 + uses: actions/stale@v3.0.18 with: repo-token: ${{ secrets.GITHUB_TOKEN }} only-labels: "needs-more-information" From 597d8eaa4ccb2694d69a60cd4891198926985d19 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Mon, 8 Mar 2021 05:15:08 -0600 Subject: [PATCH 223/831] Update rokuecp to 0.8.1 (#47589) --- homeassistant/components/roku/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roku/manifest.json b/homeassistant/components/roku/manifest.json index 10cef13cda0..981a9b08077 100644 --- a/homeassistant/components/roku/manifest.json +++ b/homeassistant/components/roku/manifest.json @@ -2,7 +2,7 @@ "domain": "roku", "name": "Roku", "documentation": "https://www.home-assistant.io/integrations/roku", - "requirements": ["rokuecp==0.8.0"], + "requirements": ["rokuecp==0.8.1"], "homekit": { "models": [ "3810X", diff --git a/requirements_all.txt b/requirements_all.txt index 285f8ed5133..c31341addb1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1968,7 +1968,7 @@ rjpl==0.3.6 rocketchat-API==0.6.1 # homeassistant.components.roku -rokuecp==0.8.0 +rokuecp==0.8.1 # homeassistant.components.roomba roombapy==1.6.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7804e3c3926..19bb8af0718 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1011,7 +1011,7 @@ rflink==0.0.58 ring_doorbell==0.6.2 # homeassistant.components.roku -rokuecp==0.8.0 +rokuecp==0.8.1 # homeassistant.components.roomba roombapy==1.6.2 From 457db1d0c366a5c00f3132889c034abe7a7087d7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 8 Mar 2021 12:57:27 +0100 Subject: [PATCH 224/831] Upgrade elgato to 2.0.1 (#47616) --- homeassistant/components/elgato/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/elgato/manifest.json b/homeassistant/components/elgato/manifest.json index 1da98a41211..9a166b86b8e 100644 --- a/homeassistant/components/elgato/manifest.json +++ b/homeassistant/components/elgato/manifest.json @@ -3,7 +3,7 @@ "name": "Elgato Key Light", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/elgato", - "requirements": ["elgato==1.0.0"], + "requirements": ["elgato==2.0.1"], "zeroconf": ["_elg._tcp.local."], "codeowners": ["@frenck"], "quality_scale": "platinum" diff --git a/requirements_all.txt b/requirements_all.txt index c31341addb1..7714669102a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -524,7 +524,7 @@ ecoaliface==0.4.0 eebrightbox==0.0.4 # homeassistant.components.elgato -elgato==1.0.0 +elgato==2.0.1 # homeassistant.components.eliqonline eliqonline==1.2.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 19bb8af0718..248dade5e63 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -279,7 +279,7 @@ dynalite_devices==0.1.46 eebrightbox==0.0.4 # homeassistant.components.elgato -elgato==1.0.0 +elgato==2.0.1 # homeassistant.components.elkm1 elkm1-lib==0.8.10 From d37fb7d88d7ef3f48ca2e5f181ea16f2cdea0156 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 8 Mar 2021 13:03:39 +0100 Subject: [PATCH 225/831] Upgrade pre-commit to 2.11.0 (#47618) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index c906543f54c..79203fba763 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -9,7 +9,7 @@ coverage==5.5 jsonpickle==1.4.1 mock-open==1.4.0 mypy==0.812 -pre-commit==2.10.1 +pre-commit==2.11.0 pylint==2.7.2 astroid==2.5.1 pipdeptree==1.0.0 From ad86eb4be3ece16a08eedb4487e86334c17ac942 Mon Sep 17 00:00:00 2001 From: Adam Ernst Date: Mon, 8 Mar 2021 06:36:03 -0600 Subject: [PATCH 226/831] Add support for Flo "pucks" (#47074) So far the Flo integration only supports shutoff valves. Add support for Flo leak detector pucks, which measure temperature and humidity in addition to providing leak alerts. --- homeassistant/components/flo/binary_sensor.py | 34 +++- homeassistant/components/flo/device.py | 19 ++- homeassistant/components/flo/sensor.py | 95 +++++++++-- homeassistant/components/flo/switch.py | 6 +- tests/components/flo/conftest.py | 8 +- tests/components/flo/test_binary_sensor.py | 19 ++- tests/components/flo/test_device.py | 76 +++++---- tests/components/flo/test_init.py | 2 +- tests/components/flo/test_sensor.py | 13 +- tests/components/flo/test_services.py | 12 +- tests/components/flo/test_switch.py | 2 +- .../flo/device_info_response_detector.json | 161 ++++++++++++++++++ .../flo/location_info_base_response.json | 4 + .../user_info_expand_locations_response.json | 4 + 14 files changed, 389 insertions(+), 66 deletions(-) create mode 100644 tests/fixtures/flo/device_info_response_detector.json diff --git a/homeassistant/components/flo/binary_sensor.py b/homeassistant/components/flo/binary_sensor.py index a8bac498674..3ab1152f83e 100644 --- a/homeassistant/components/flo/binary_sensor.py +++ b/homeassistant/components/flo/binary_sensor.py @@ -17,7 +17,21 @@ async def async_setup_entry(hass, config_entry, async_add_entities): devices: List[FloDeviceDataUpdateCoordinator] = hass.data[FLO_DOMAIN][ config_entry.entry_id ]["devices"] - entities = [FloPendingAlertsBinarySensor(device) for device in devices] + entities = [] + for device in devices: + if device.device_type == "puck_oem": + # Flo "pucks" (leak detectors) *do* support pending alerts. + # However these pending alerts mix unrelated issues like + # low-battery alerts, humidity alerts, & temperature alerts + # in addition to the critical "water detected" alert. + # + # Since there are non-binary sensors for battery, humidity, + # and temperature, the binary sensor should only cover + # water detection. If this sensor trips, you really have + # a problem - vs. battery/temp/humidity which are warnings. + entities.append(FloWaterDetectedBinarySensor(device)) + else: + entities.append(FloPendingAlertsBinarySensor(device)) async_add_entities(entities) @@ -48,3 +62,21 @@ class FloPendingAlertsBinarySensor(FloEntity, BinarySensorEntity): def device_class(self): """Return the device class for the binary sensor.""" return DEVICE_CLASS_PROBLEM + + +class FloWaterDetectedBinarySensor(FloEntity, BinarySensorEntity): + """Binary sensor that reports if water is detected (for leak detectors).""" + + def __init__(self, device): + """Initialize the pending alerts binary sensor.""" + super().__init__("water_detected", "Water Detected", device) + + @property + def is_on(self): + """Return true if the Flo device is detecting water.""" + return self._device.water_detected + + @property + def device_class(self): + """Return the device class for the binary sensor.""" + return DEVICE_CLASS_PROBLEM diff --git a/homeassistant/components/flo/device.py b/homeassistant/components/flo/device.py index af36034026d..49e0e991376 100644 --- a/homeassistant/components/flo/device.py +++ b/homeassistant/components/flo/device.py @@ -58,7 +58,9 @@ class FloDeviceDataUpdateCoordinator(DataUpdateCoordinator): @property def device_name(self) -> str: """Return device name.""" - return f"{self.manufacturer} {self.model}" + return self._device_information.get( + "nickname", f"{self.manufacturer} {self.model}" + ) @property def manufacturer(self) -> str: @@ -120,6 +122,11 @@ class FloDeviceDataUpdateCoordinator(DataUpdateCoordinator): """Return the current temperature in degrees F.""" return self._device_information["telemetry"]["current"]["tempF"] + @property + def humidity(self) -> float: + """Return the current humidity in percent (0-100).""" + return self._device_information["telemetry"]["current"]["humidity"] + @property def consumption_today(self) -> float: """Return the current consumption for today in gallons.""" @@ -159,6 +166,11 @@ class FloDeviceDataUpdateCoordinator(DataUpdateCoordinator): or self.pending_warning_alerts_count ) + @property + def water_detected(self) -> bool: + """Return whether water is detected, for leak detectors.""" + return self._device_information["fwProperties"]["telemetry_water"] + @property def last_known_valve_state(self) -> str: """Return the last known valve state for the device.""" @@ -169,6 +181,11 @@ class FloDeviceDataUpdateCoordinator(DataUpdateCoordinator): """Return the target valve state for the device.""" return self._device_information["valve"]["target"] + @property + def battery_level(self) -> float: + """Return the battery level for battery-powered device, e.g. leak detectors.""" + return self._device_information["battery"]["level"] + async def async_set_mode_home(self): """Set the Flo location to home mode.""" await self.api_client.location.set_mode_home(self._flo_location_id) diff --git a/homeassistant/components/flo/sensor.py b/homeassistant/components/flo/sensor.py index 2feeb3702a6..d88f2b950fc 100644 --- a/homeassistant/components/flo/sensor.py +++ b/homeassistant/components/flo/sensor.py @@ -3,13 +3,15 @@ from typing import List, Optional from homeassistant.const import ( + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, + PERCENTAGE, PRESSURE_PSI, - TEMP_CELSIUS, + TEMP_FAHRENHEIT, VOLUME_GALLONS, ) -from homeassistant.util.temperature import fahrenheit_to_celsius from .const import DOMAIN as FLO_DOMAIN from .device import FloDeviceDataUpdateCoordinator @@ -20,8 +22,11 @@ GAUGE_ICON = "mdi:gauge" NAME_DAILY_USAGE = "Today's Water Usage" NAME_CURRENT_SYSTEM_MODE = "Current System Mode" NAME_FLOW_RATE = "Water Flow Rate" -NAME_TEMPERATURE = "Water Temperature" +NAME_WATER_TEMPERATURE = "Water Temperature" +NAME_AIR_TEMPERATURE = "Temperature" NAME_WATER_PRESSURE = "Water Pressure" +NAME_HUMIDITY = "Humidity" +NAME_BATTERY = "Battery" async def async_setup_entry(hass, config_entry, async_add_entities): @@ -30,11 +35,25 @@ async def async_setup_entry(hass, config_entry, async_add_entities): config_entry.entry_id ]["devices"] entities = [] - entities.extend([FloDailyUsageSensor(device) for device in devices]) - entities.extend([FloSystemModeSensor(device) for device in devices]) - entities.extend([FloCurrentFlowRateSensor(device) for device in devices]) - entities.extend([FloTemperatureSensor(device) for device in devices]) - entities.extend([FloPressureSensor(device) for device in devices]) + for device in devices: + if device.device_type == "puck_oem": + entities.extend( + [ + FloTemperatureSensor(NAME_AIR_TEMPERATURE, device), + FloHumiditySensor(device), + FloBatterySensor(device), + ] + ) + else: + entities.extend( + [ + FloDailyUsageSensor(device), + FloSystemModeSensor(device), + FloCurrentFlowRateSensor(device), + FloTemperatureSensor(NAME_WATER_TEMPERATURE, device), + FloPressureSensor(device), + ] + ) async_add_entities(entities) @@ -109,9 +128,9 @@ class FloCurrentFlowRateSensor(FloEntity): class FloTemperatureSensor(FloEntity): """Monitors the temperature.""" - def __init__(self, device): + def __init__(self, name, device): """Initialize the temperature sensor.""" - super().__init__("temperature", NAME_TEMPERATURE, device) + super().__init__("temperature", name, device) self._state: float = None @property @@ -119,12 +138,12 @@ class FloTemperatureSensor(FloEntity): """Return the current temperature.""" if self._device.temperature is None: return None - return round(fahrenheit_to_celsius(self._device.temperature), 1) + return round(self._device.temperature, 1) @property def unit_of_measurement(self) -> str: - """Return gallons as the unit measurement for water.""" - return TEMP_CELSIUS + """Return fahrenheit as the unit measurement for temperature.""" + return TEMP_FAHRENHEIT @property def device_class(self) -> Optional[str]: @@ -132,6 +151,32 @@ class FloTemperatureSensor(FloEntity): return DEVICE_CLASS_TEMPERATURE +class FloHumiditySensor(FloEntity): + """Monitors the humidity.""" + + def __init__(self, device): + """Initialize the humidity sensor.""" + super().__init__("humidity", NAME_HUMIDITY, device) + self._state: float = None + + @property + def state(self) -> Optional[float]: + """Return the current humidity.""" + if self._device.humidity is None: + return None + return round(self._device.humidity, 1) + + @property + def unit_of_measurement(self) -> str: + """Return percent as the unit measurement for humidity.""" + return PERCENTAGE + + @property + def device_class(self) -> Optional[str]: + """Return the device class for this sensor.""" + return DEVICE_CLASS_HUMIDITY + + class FloPressureSensor(FloEntity): """Monitors the water pressure.""" @@ -156,3 +201,27 @@ class FloPressureSensor(FloEntity): def device_class(self) -> Optional[str]: """Return the device class for this sensor.""" return DEVICE_CLASS_PRESSURE + + +class FloBatterySensor(FloEntity): + """Monitors the battery level for battery-powered leak detectors.""" + + def __init__(self, device): + """Initialize the battery sensor.""" + super().__init__("battery", NAME_BATTERY, device) + self._state: float = None + + @property + def state(self) -> Optional[float]: + """Return the current battery level.""" + return self._device.battery_level + + @property + def unit_of_measurement(self) -> str: + """Return percentage as the unit measurement for battery.""" + return PERCENTAGE + + @property + def device_class(self) -> Optional[str]: + """Return the device class for this sensor.""" + return DEVICE_CLASS_BATTERY diff --git a/homeassistant/components/flo/switch.py b/homeassistant/components/flo/switch.py index 91f3fdf54e4..895212c56a0 100644 --- a/homeassistant/components/flo/switch.py +++ b/homeassistant/components/flo/switch.py @@ -26,7 +26,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities): devices: List[FloDeviceDataUpdateCoordinator] = hass.data[FLO_DOMAIN][ config_entry.entry_id ]["devices"] - async_add_entities([FloSwitch(device) for device in devices]) + entities = [] + for device in devices: + if device.device_type != "puck_oem": + entities.append(FloSwitch(device)) + async_add_entities(entities) platform = entity_platform.current_platform.get() diff --git a/tests/components/flo/conftest.py b/tests/components/flo/conftest.py index 907feb85569..d4ba80e6406 100644 --- a/tests/components/flo/conftest.py +++ b/tests/components/flo/conftest.py @@ -43,13 +43,19 @@ def aioclient_mock_fixture(aioclient_mock): headers={"Content-Type": CONTENT_TYPE_JSON}, status=200, ) - # Mocks the device for flo. + # Mocks the devices for flo. aioclient_mock.get( "https://api-gw.meetflo.com/api/v2/devices/98765", text=load_fixture("flo/device_info_response.json"), status=200, headers={"Content-Type": CONTENT_TYPE_JSON}, ) + aioclient_mock.get( + "https://api-gw.meetflo.com/api/v2/devices/32839", + text=load_fixture("flo/device_info_response_detector.json"), + status=200, + headers={"Content-Type": CONTENT_TYPE_JSON}, + ) # Mocks the water consumption for flo. aioclient_mock.get( "https://api-gw.meetflo.com/api/v2/water/consumption", diff --git a/tests/components/flo/test_binary_sensor.py b/tests/components/flo/test_binary_sensor.py index 64b8f787a85..b6a8abf727c 100644 --- a/tests/components/flo/test_binary_sensor.py +++ b/tests/components/flo/test_binary_sensor.py @@ -4,6 +4,7 @@ from homeassistant.const import ( ATTR_FRIENDLY_NAME, CONF_PASSWORD, CONF_USERNAME, + STATE_OFF, STATE_ON, ) from homeassistant.setup import async_setup_component @@ -19,12 +20,14 @@ async def test_binary_sensors(hass, config_entry, aioclient_mock_fixture): ) await hass.async_block_till_done() - assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 1 + assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 2 - # we should have 6 entities for the device - state = hass.states.get("binary_sensor.pending_system_alerts") - assert state.state == STATE_ON - assert state.attributes.get("info") == 0 - assert state.attributes.get("warning") == 2 - assert state.attributes.get("critical") == 0 - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Pending System Alerts" + valve_state = hass.states.get("binary_sensor.pending_system_alerts") + assert valve_state.state == STATE_ON + assert valve_state.attributes.get("info") == 0 + assert valve_state.attributes.get("warning") == 2 + assert valve_state.attributes.get("critical") == 0 + assert valve_state.attributes.get(ATTR_FRIENDLY_NAME) == "Pending System Alerts" + + detector_state = hass.states.get("binary_sensor.water_detected") + assert detector_state.state == STATE_OFF diff --git a/tests/components/flo/test_device.py b/tests/components/flo/test_device.py index 63e81a16fb4..5ac31a2bff9 100644 --- a/tests/components/flo/test_device.py +++ b/tests/components/flo/test_device.py @@ -13,46 +13,64 @@ from tests.common import async_fire_time_changed async def test_device(hass, config_entry, aioclient_mock_fixture, aioclient_mock): - """Test Flo by Moen device.""" + """Test Flo by Moen devices.""" config_entry.add_to_hass(hass) assert await async_setup_component( hass, FLO_DOMAIN, {CONF_USERNAME: TEST_USER_ID, CONF_PASSWORD: TEST_PASSWORD} ) await hass.async_block_till_done() - assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 1 + assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 2 - device: FloDeviceDataUpdateCoordinator = hass.data[FLO_DOMAIN][ + valve: FloDeviceDataUpdateCoordinator = hass.data[FLO_DOMAIN][ config_entry.entry_id ]["devices"][0] - assert device.api_client is not None - assert device.available - assert device.consumption_today == 3.674 - assert device.current_flow_rate == 0 - assert device.current_psi == 54.20000076293945 - assert device.current_system_mode == "home" - assert device.target_system_mode == "home" - assert device.firmware_version == "6.1.1" - assert device.device_type == "flo_device_v2" - assert device.id == "98765" - assert device.last_heard_from_time == "2020-07-24T12:45:00Z" - assert device.location_id == "mmnnoopp" - assert device.hass is not None - assert device.temperature == 70 - assert device.mac_address == "111111111111" - assert device.model == "flo_device_075_v2" - assert device.manufacturer == "Flo by Moen" - assert device.device_name == "Flo by Moen flo_device_075_v2" - assert device.rssi == -47 - assert device.pending_info_alerts_count == 0 - assert device.pending_critical_alerts_count == 0 - assert device.pending_warning_alerts_count == 2 - assert device.has_alerts is True - assert device.last_known_valve_state == "open" - assert device.target_valve_state == "open" + assert valve.api_client is not None + assert valve.available + assert valve.consumption_today == 3.674 + assert valve.current_flow_rate == 0 + assert valve.current_psi == 54.20000076293945 + assert valve.current_system_mode == "home" + assert valve.target_system_mode == "home" + assert valve.firmware_version == "6.1.1" + assert valve.device_type == "flo_device_v2" + assert valve.id == "98765" + assert valve.last_heard_from_time == "2020-07-24T12:45:00Z" + assert valve.location_id == "mmnnoopp" + assert valve.hass is not None + assert valve.temperature == 70 + assert valve.mac_address == "111111111111" + assert valve.model == "flo_device_075_v2" + assert valve.manufacturer == "Flo by Moen" + assert valve.device_name == "Smart Water Shutoff" + assert valve.rssi == -47 + assert valve.pending_info_alerts_count == 0 + assert valve.pending_critical_alerts_count == 0 + assert valve.pending_warning_alerts_count == 2 + assert valve.has_alerts is True + assert valve.last_known_valve_state == "open" + assert valve.target_valve_state == "open" + + detector: FloDeviceDataUpdateCoordinator = hass.data[FLO_DOMAIN][ + config_entry.entry_id + ]["devices"][1] + assert detector.api_client is not None + assert detector.available + assert detector.device_type == "puck_oem" + assert detector.id == "32839" + assert detector.last_heard_from_time == "2021-03-07T14:05:00Z" + assert detector.location_id == "mmnnoopp" + assert detector.hass is not None + assert detector.temperature == 61 + assert detector.humidity == 43 + assert detector.water_detected is False + assert detector.mac_address == "1a2b3c4d5e6f" + assert detector.model == "puck_v1" + assert detector.manufacturer == "Flo by Moen" + assert detector.device_name == "Kitchen Sink" call_count = aioclient_mock.call_count async_fire_time_changed(hass, dt.utcnow() + timedelta(seconds=90)) await hass.async_block_till_done() - assert aioclient_mock.call_count == call_count + 2 + assert aioclient_mock.call_count == call_count + 4 diff --git a/tests/components/flo/test_init.py b/tests/components/flo/test_init.py index 9061477da47..13f16f06cbf 100644 --- a/tests/components/flo/test_init.py +++ b/tests/components/flo/test_init.py @@ -13,6 +13,6 @@ async def test_setup_entry(hass, config_entry, aioclient_mock_fixture): hass, FLO_DOMAIN, {CONF_USERNAME: TEST_USER_ID, CONF_PASSWORD: TEST_PASSWORD} ) await hass.async_block_till_done() - assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 1 + assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 2 assert await hass.config_entries.async_unload(config_entry.entry_id) diff --git a/tests/components/flo/test_sensor.py b/tests/components/flo/test_sensor.py index 309dfc11266..f1572dae02c 100644 --- a/tests/components/flo/test_sensor.py +++ b/tests/components/flo/test_sensor.py @@ -14,14 +14,19 @@ async def test_sensors(hass, config_entry, aioclient_mock_fixture): ) await hass.async_block_till_done() - assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 1 + assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 2 - # we should have 5 entities for the device + # we should have 5 entities for the valve assert hass.states.get("sensor.current_system_mode").state == "home" assert hass.states.get("sensor.today_s_water_usage").state == "3.7" assert hass.states.get("sensor.water_flow_rate").state == "0" assert hass.states.get("sensor.water_pressure").state == "54.2" - assert hass.states.get("sensor.water_temperature").state == "21.1" + assert hass.states.get("sensor.water_temperature").state == "21" + + # and 3 entities for the detector + assert hass.states.get("sensor.temperature").state == "16" + assert hass.states.get("sensor.humidity").state == "43" + assert hass.states.get("sensor.battery").state == "100" async def test_manual_update_entity( @@ -34,7 +39,7 @@ async def test_manual_update_entity( ) await hass.async_block_till_done() - assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 1 + assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 2 await async_setup_component(hass, "homeassistant", {}) diff --git a/tests/components/flo/test_services.py b/tests/components/flo/test_services.py index 270279e4a9d..4941a118e48 100644 --- a/tests/components/flo/test_services.py +++ b/tests/components/flo/test_services.py @@ -25,8 +25,8 @@ async def test_services(hass, config_entry, aioclient_mock_fixture, aioclient_mo ) await hass.async_block_till_done() - assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 1 - assert aioclient_mock.call_count == 4 + assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 2 + assert aioclient_mock.call_count == 6 await hass.services.async_call( FLO_DOMAIN, @@ -35,7 +35,7 @@ async def test_services(hass, config_entry, aioclient_mock_fixture, aioclient_mo blocking=True, ) await hass.async_block_till_done() - assert aioclient_mock.call_count == 5 + assert aioclient_mock.call_count == 7 await hass.services.async_call( FLO_DOMAIN, @@ -44,7 +44,7 @@ async def test_services(hass, config_entry, aioclient_mock_fixture, aioclient_mo blocking=True, ) await hass.async_block_till_done() - assert aioclient_mock.call_count == 6 + assert aioclient_mock.call_count == 8 await hass.services.async_call( FLO_DOMAIN, @@ -53,7 +53,7 @@ async def test_services(hass, config_entry, aioclient_mock_fixture, aioclient_mo blocking=True, ) await hass.async_block_till_done() - assert aioclient_mock.call_count == 7 + assert aioclient_mock.call_count == 9 await hass.services.async_call( FLO_DOMAIN, @@ -66,4 +66,4 @@ async def test_services(hass, config_entry, aioclient_mock_fixture, aioclient_mo blocking=True, ) await hass.async_block_till_done() - assert aioclient_mock.call_count == 8 + assert aioclient_mock.call_count == 10 diff --git a/tests/components/flo/test_switch.py b/tests/components/flo/test_switch.py index 25a64433a29..cc6ab7e3a7e 100644 --- a/tests/components/flo/test_switch.py +++ b/tests/components/flo/test_switch.py @@ -15,7 +15,7 @@ async def test_valve_switches(hass, config_entry, aioclient_mock_fixture): ) await hass.async_block_till_done() - assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 1 + assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 2 entity_id = "switch.shutoff_valve" assert hass.states.get(entity_id).state == STATE_ON diff --git a/tests/fixtures/flo/device_info_response_detector.json b/tests/fixtures/flo/device_info_response_detector.json new file mode 100644 index 00000000000..aac24ab5e68 --- /dev/null +++ b/tests/fixtures/flo/device_info_response_detector.json @@ -0,0 +1,161 @@ +{ + "actionRules": [], + "battery": { + "level": 100, + "updated": "2021-03-01T12:05:00Z" + }, + "connectivity": { + "ssid": "SOMESSID" + }, + "deviceModel": "puck_v1", + "deviceType": "puck_oem", + "fwProperties": { + "alert_battery_active": false, + "alert_humidity_high_active": false, + "alert_humidity_high_count": 0, + "alert_humidity_low_active": false, + "alert_humidity_low_count": 1, + "alert_state": "inactive", + "alert_temperature_high_active": false, + "alert_temperature_high_count": 0, + "alert_temperature_low_active": false, + "alert_temperature_low_count": 0, + "alert_water_active": false, + "alert_water_count": 0, + "ap_mode_count": 1, + "beep_pattern": "off", + "button_click_count": 1, + "date": "2021-03-07T14:00:05.054Z", + "deep_sleep_count": 8229, + "device_boot_count": 25, + "device_boot_reason": "wakeup_timer", + "device_count": 8230, + "device_failed_count": 36, + "device_id": "1a2b3c4d5e6f", + "device_time_total": 405336, + "device_time_up": 1502, + "device_uuid": "32839", + "device_wakeup_count": 8254, + "flosense_shut_off_level": 2, + "fw_name": "1.1.15", + "fw_version": 10115, + "led_pattern": "led_blue_solid", + "limit_ota_battery_min": 30, + "pairing_state": "configured", + "reason": "heartbeat", + "serial_number": "111111111112", + "telemetry_battery_percent": 100, + "telemetry_battery_voltage": 2.9896278381347656, + "telemetry_count": 8224, + "telemetry_failed_count": 27, + "telemetry_humidity": 43.21965408325195, + "telemetry_rssi": 100, + "telemetry_temperature": 61.43144607543945, + "telemetry_water": false, + "timer_alarm_active": 10, + "timer_heartbeat_battery_low": 3600, + "timer_heartbeat_battery_ok": 1740, + "timer_heartbeat_last": 1740, + "timer_heartbeat_not_configured": 10, + "timer_heartbeat_retry_attempts": 3, + "timer_heartbeat_retry_delay": 600, + "timer_water_debounce": 2000, + "timer_wifi_ap_timeout": 600000, + "wifi_ap_ssid": "FloDetector-a123", + "wifi_sta_enc": "wpa2-psk", + "wifi_sta_failed_count": 21, + "wifi_sta_mac": "50:01:01:01:01:44", + "wifi_sta_ssid": "SOMESSID" + }, + "fwVersion": "1.1.15", + "hardwareThresholds": { + "battery": { + "maxValue": 100, + "minValue": 0, + "okMax": 100, + "okMin": 20 + }, + "batteryEnabled": true, + "humidity": { + "maxValue": 100, + "minValue": 0, + "okMax": 85, + "okMin": 15 + }, + "humidityEnabled": true, + "tempC": { + "maxValue": 60, + "minValue": -17.77777777777778, + "okMax": 37.77777777777778, + "okMin": 0 + }, + "tempEnabled": true, + "tempF": { + "maxValue": 140, + "minValue": 0, + "okMax": 100, + "okMin": 32 + } + }, + "id": "32839", + "installStatus": { + "isInstalled": false + }, + "isConnected": true, + "isPaired": true, + "lastHeardFromTime": "2021-03-07T14:05:00Z", + "location": { + "id": "mmnnoopp" + }, + "macAddress": "1a2b3c4d5e6f", + "nickname": "Kitchen Sink", + "notifications": { + "pending": { + "alarmCount": [], + "critical": { + "count": 0, + "devices": { + "absolute": 0, + "count": 0 + } + }, + "criticalCount": 0, + "info": { + "count": 0, + "devices": { + "absolute": 0, + "count": 0 + } + }, + "infoCount": 0, + "warning": { + "count": 0, + "devices": { + "absolute": 0, + "count": 0 + } + }, + "warningCount": 0 + } + }, + "puckConfig": { + "configuredAt": "2020-09-01T18:15:12.216Z", + "isConfigured": true + }, + "serialNumber": "111111111112", + "shutoff": { + "scheduledAt": "1970-01-01T00:00:00.000Z" + }, + "systemMode": { + "isLocked": false, + "shouldInherit": true + }, + "telemetry": { + "current": { + "humidity": 43, + "tempF": 61, + "updated": "2021-03-07T14:05:00Z" + } + }, + "valve": {} +} diff --git a/tests/fixtures/flo/location_info_base_response.json b/tests/fixtures/flo/location_info_base_response.json index f6840a0742b..a5a25da2d6c 100644 --- a/tests/fixtures/flo/location_info_base_response.json +++ b/tests/fixtures/flo/location_info_base_response.json @@ -9,6 +9,10 @@ { "id": "98765", "macAddress": "123456abcdef" + }, + { + "id": "32839", + "macAddress": "1a2b3c4d5e6f" } ], "userRoles": [ diff --git a/tests/fixtures/flo/user_info_expand_locations_response.json b/tests/fixtures/flo/user_info_expand_locations_response.json index 829596b6849..18643e049ba 100644 --- a/tests/fixtures/flo/user_info_expand_locations_response.json +++ b/tests/fixtures/flo/user_info_expand_locations_response.json @@ -19,6 +19,10 @@ { "id": "98765", "macAddress": "606405c11e10" + }, + { + "id": "32839", + "macAddress": "1a2b3c4d5e6f" } ], "userRoles": [ From f3c71a69f05adc415c309369b6a283ecc2b33712 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Mon, 8 Mar 2021 07:37:33 -0500 Subject: [PATCH 227/831] Allow 10mV precision for ZHA battery sensor entities (#47520) --- homeassistant/components/zha/sensor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index b02b3a549be..78bc8fcbe4e 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -186,7 +186,9 @@ class Battery(Sensor): state_attrs["battery_quantity"] = battery_quantity battery_voltage = self._channel.cluster.get("battery_voltage") if battery_voltage is not None: - state_attrs["battery_voltage"] = round(battery_voltage / 10, 1) + v_10mv = round(battery_voltage / 10, 2) + v_100mv = round(battery_voltage / 10, 1) + state_attrs["battery_voltage"] = v_100mv if v_100mv == v_10mv else v_10mv return state_attrs From cf507b51cb2190d053b03574181442bba242f950 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 8 Mar 2021 13:51:26 +0100 Subject: [PATCH 228/831] Add feels like temperature sensor to OpenWeatherMap (#47559) --- homeassistant/components/openweathermap/const.py | 7 +++++++ .../openweathermap/weather_update_coordinator.py | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/homeassistant/components/openweathermap/const.py b/homeassistant/components/openweathermap/const.py index 5b0165b2bee..8457ceb65e9 100644 --- a/homeassistant/components/openweathermap/const.py +++ b/homeassistant/components/openweathermap/const.py @@ -49,6 +49,7 @@ ATTR_API_PRECIPITATION = "precipitation" ATTR_API_DATETIME = "datetime" ATTR_API_WEATHER = "weather" ATTR_API_TEMPERATURE = "temperature" +ATTR_API_FEELS_LIKE_TEMPERATURE = "feels_like_temperature" ATTR_API_WIND_SPEED = "wind_speed" ATTR_API_WIND_BEARING = "wind_bearing" ATTR_API_HUMIDITY = "humidity" @@ -81,6 +82,7 @@ DEFAULT_FORECAST_MODE = FORECAST_MODE_ONECALL_DAILY MONITORED_CONDITIONS = [ ATTR_API_WEATHER, ATTR_API_TEMPERATURE, + ATTR_API_FEELS_LIKE_TEMPERATURE, ATTR_API_WIND_SPEED, ATTR_API_WIND_BEARING, ATTR_API_HUMIDITY, @@ -190,6 +192,11 @@ WEATHER_SENSOR_TYPES = { SENSOR_UNIT: TEMP_CELSIUS, SENSOR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, }, + ATTR_API_FEELS_LIKE_TEMPERATURE: { + SENSOR_NAME: "Feels like temperature", + SENSOR_UNIT: TEMP_CELSIUS, + SENSOR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, + }, ATTR_API_WIND_SPEED: { SENSOR_NAME: "Wind speed", SENSOR_UNIT: SPEED_METERS_PER_SECOND, diff --git a/homeassistant/components/openweathermap/weather_update_coordinator.py b/homeassistant/components/openweathermap/weather_update_coordinator.py index b4d9b7a9c80..07b8c507c87 100644 --- a/homeassistant/components/openweathermap/weather_update_coordinator.py +++ b/homeassistant/components/openweathermap/weather_update_coordinator.py @@ -25,6 +25,7 @@ from homeassistant.util import dt from .const import ( ATTR_API_CLOUDS, ATTR_API_CONDITION, + ATTR_API_FEELS_LIKE_TEMPERATURE, ATTR_API_FORECAST, ATTR_API_HUMIDITY, ATTR_API_PRESSURE, @@ -115,6 +116,9 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): return { ATTR_API_TEMPERATURE: current_weather.temperature("celsius").get("temp"), + ATTR_API_FEELS_LIKE_TEMPERATURE: current_weather.temperature("celsius").get( + "feels_like" + ), ATTR_API_PRESSURE: current_weather.pressure.get("press"), ATTR_API_HUMIDITY: current_weather.humidity, ATTR_API_WIND_BEARING: current_weather.wind().get("deg"), From 9774ada4aa847f8942b84171b51a8ad633ac0254 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Mon, 8 Mar 2021 05:05:39 -0800 Subject: [PATCH 229/831] Code cleanup for SmartTub integration (#47584) --- homeassistant/components/smarttub/const.py | 4 ++++ homeassistant/components/smarttub/controller.py | 15 +++++++++++---- homeassistant/components/smarttub/light.py | 3 ++- homeassistant/components/smarttub/sensor.py | 2 +- homeassistant/components/smarttub/switch.py | 4 ++-- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/smarttub/const.py b/homeassistant/components/smarttub/const.py index 0b97926cc43..082d46cb26a 100644 --- a/homeassistant/components/smarttub/const.py +++ b/homeassistant/components/smarttub/const.py @@ -20,3 +20,7 @@ DEFAULT_MAX_TEMP = 40 DEFAULT_LIGHT_EFFECT = "purple" # default to 50% brightness DEFAULT_LIGHT_BRIGHTNESS = 128 + +ATTR_STATUS = "status" +ATTR_PUMPS = "pumps" +ATTR_LIGHTS = "lights" diff --git a/homeassistant/components/smarttub/controller.py b/homeassistant/components/smarttub/controller.py index bf8de2f4e2e..e5246446665 100644 --- a/homeassistant/components/smarttub/controller.py +++ b/homeassistant/components/smarttub/controller.py @@ -15,7 +15,14 @@ from homeassistant.helpers import device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import DOMAIN, POLLING_TIMEOUT, SCAN_INTERVAL +from .const import ( + ATTR_LIGHTS, + ATTR_PUMPS, + ATTR_STATUS, + DOMAIN, + POLLING_TIMEOUT, + SCAN_INTERVAL, +) from .helpers import get_spa_name _LOGGER = logging.getLogger(__name__) @@ -92,9 +99,9 @@ class SmartTubController: spa.get_lights(), ) return { - "status": status, - "pumps": {pump.id: pump for pump in pumps}, - "lights": {light.zone: light for light in lights}, + ATTR_STATUS: status, + ATTR_PUMPS: {pump.id: pump for pump in pumps}, + ATTR_LIGHTS: {light.zone: light for light in lights}, } async def async_register_devices(self, entry): diff --git a/homeassistant/components/smarttub/light.py b/homeassistant/components/smarttub/light.py index 1baf7e527eb..57acf583415 100644 --- a/homeassistant/components/smarttub/light.py +++ b/homeassistant/components/smarttub/light.py @@ -13,6 +13,7 @@ from homeassistant.components.light import ( ) from .const import ( + ATTR_LIGHTS, DEFAULT_LIGHT_BRIGHTNESS, DEFAULT_LIGHT_EFFECT, DOMAIN, @@ -32,7 +33,7 @@ async def async_setup_entry(hass, entry, async_add_entities): entities = [ SmartTubLight(controller.coordinator, light) for spa in controller.spas - for light in await spa.get_lights() + for light in controller.coordinator.data[spa.id][ATTR_LIGHTS].values() ] async_add_entities(entities) diff --git a/homeassistant/components/smarttub/sensor.py b/homeassistant/components/smarttub/sensor.py index 44f09989a99..563c16b3ff1 100644 --- a/homeassistant/components/smarttub/sensor.py +++ b/homeassistant/components/smarttub/sensor.py @@ -61,7 +61,7 @@ class SmartTubPrimaryFiltrationCycle(SmartTubSensor): def __init__(self, coordinator, spa): """Initialize the entity.""" super().__init__( - coordinator, spa, "primary filtration cycle", "primary_filtration" + coordinator, spa, "Primary Filtration Cycle", "primary_filtration" ) @property diff --git a/homeassistant/components/smarttub/switch.py b/homeassistant/components/smarttub/switch.py index 736611e5411..d2891932546 100644 --- a/homeassistant/components/smarttub/switch.py +++ b/homeassistant/components/smarttub/switch.py @@ -6,7 +6,7 @@ from smarttub import SpaPump from homeassistant.components.switch import SwitchEntity -from .const import API_TIMEOUT, DOMAIN, SMARTTUB_CONTROLLER +from .const import API_TIMEOUT, ATTR_PUMPS, DOMAIN, SMARTTUB_CONTROLLER from .entity import SmartTubEntity from .helpers import get_spa_name @@ -21,7 +21,7 @@ async def async_setup_entry(hass, entry, async_add_entities): entities = [ SmartTubPump(controller.coordinator, pump) for spa in controller.spas - for pump in await spa.get_pumps() + for pump in controller.coordinator.data[spa.id][ATTR_PUMPS].values() ] async_add_entities(entities) From 197687399d4a67f4dd52177b5985ab8dfb1ab219 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 8 Mar 2021 14:26:52 +0100 Subject: [PATCH 230/831] Upgrade pillow to 8.1.2 (#47619) --- homeassistant/components/doods/manifest.json | 2 +- homeassistant/components/image/manifest.json | 2 +- homeassistant/components/proxy/manifest.json | 2 +- homeassistant/components/qrcode/manifest.json | 2 +- homeassistant/components/seven_segments/manifest.json | 2 +- homeassistant/components/sighthound/manifest.json | 2 +- homeassistant/components/tensorflow/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/doods/manifest.json b/homeassistant/components/doods/manifest.json index f5d425cb9ef..6f6fcb0d6b3 100644 --- a/homeassistant/components/doods/manifest.json +++ b/homeassistant/components/doods/manifest.json @@ -2,6 +2,6 @@ "domain": "doods", "name": "DOODS - Dedicated Open Object Detection Service", "documentation": "https://www.home-assistant.io/integrations/doods", - "requirements": ["pydoods==1.0.2", "pillow==8.1.1"], + "requirements": ["pydoods==1.0.2", "pillow==8.1.2"], "codeowners": [] } diff --git a/homeassistant/components/image/manifest.json b/homeassistant/components/image/manifest.json index c8029c2e313..741fb8511a6 100644 --- a/homeassistant/components/image/manifest.json +++ b/homeassistant/components/image/manifest.json @@ -3,7 +3,7 @@ "name": "Image", "config_flow": false, "documentation": "https://www.home-assistant.io/integrations/image", - "requirements": ["pillow==8.1.1"], + "requirements": ["pillow==8.1.2"], "dependencies": ["http"], "codeowners": ["@home-assistant/core"], "quality_scale": "internal" diff --git a/homeassistant/components/proxy/manifest.json b/homeassistant/components/proxy/manifest.json index c1a01004fe9..86f3d23d308 100644 --- a/homeassistant/components/proxy/manifest.json +++ b/homeassistant/components/proxy/manifest.json @@ -2,6 +2,6 @@ "domain": "proxy", "name": "Camera Proxy", "documentation": "https://www.home-assistant.io/integrations/proxy", - "requirements": ["pillow==8.1.1"], + "requirements": ["pillow==8.1.2"], "codeowners": [] } diff --git a/homeassistant/components/qrcode/manifest.json b/homeassistant/components/qrcode/manifest.json index 5867d0d6b51..bd574af0297 100644 --- a/homeassistant/components/qrcode/manifest.json +++ b/homeassistant/components/qrcode/manifest.json @@ -2,6 +2,6 @@ "domain": "qrcode", "name": "QR Code", "documentation": "https://www.home-assistant.io/integrations/qrcode", - "requirements": ["pillow==8.1.1", "pyzbar==0.1.7"], + "requirements": ["pillow==8.1.2", "pyzbar==0.1.7"], "codeowners": [] } diff --git a/homeassistant/components/seven_segments/manifest.json b/homeassistant/components/seven_segments/manifest.json index 4f9f6514531..13f3cf22506 100644 --- a/homeassistant/components/seven_segments/manifest.json +++ b/homeassistant/components/seven_segments/manifest.json @@ -2,6 +2,6 @@ "domain": "seven_segments", "name": "Seven Segments OCR", "documentation": "https://www.home-assistant.io/integrations/seven_segments", - "requirements": ["pillow==8.1.1"], + "requirements": ["pillow==8.1.2"], "codeowners": ["@fabaff"] } diff --git a/homeassistant/components/sighthound/manifest.json b/homeassistant/components/sighthound/manifest.json index aa9519fd68b..0cab5b45b84 100644 --- a/homeassistant/components/sighthound/manifest.json +++ b/homeassistant/components/sighthound/manifest.json @@ -2,6 +2,6 @@ "domain": "sighthound", "name": "Sighthound", "documentation": "https://www.home-assistant.io/integrations/sighthound", - "requirements": ["pillow==8.1.1", "simplehound==0.3"], + "requirements": ["pillow==8.1.2", "simplehound==0.3"], "codeowners": ["@robmarkcole"] } diff --git a/homeassistant/components/tensorflow/manifest.json b/homeassistant/components/tensorflow/manifest.json index 300c3ddd1db..49ee22176a7 100644 --- a/homeassistant/components/tensorflow/manifest.json +++ b/homeassistant/components/tensorflow/manifest.json @@ -7,7 +7,7 @@ "tf-models-official==2.3.0", "pycocotools==2.0.1", "numpy==1.19.2", - "pillow==8.1.1" + "pillow==8.1.2" ], "codeowners": [] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 7642e14bda8..46dc2d02781 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -20,7 +20,7 @@ httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 paho-mqtt==1.5.1 -pillow==8.1.1 +pillow==8.1.2 pip>=8.0.3,<20.3 python-slugify==4.0.1 pytz>=2021.1 diff --git a/requirements_all.txt b/requirements_all.txt index 7714669102a..0028e6e343a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1126,7 +1126,7 @@ pilight==0.1.1 # homeassistant.components.seven_segments # homeassistant.components.sighthound # homeassistant.components.tensorflow -pillow==8.1.1 +pillow==8.1.2 # homeassistant.components.dominos pizzapi==0.0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 248dade5e63..6ccb6898393 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -575,7 +575,7 @@ pilight==0.1.1 # homeassistant.components.seven_segments # homeassistant.components.sighthound # homeassistant.components.tensorflow -pillow==8.1.1 +pillow==8.1.2 # homeassistant.components.plex plexapi==4.4.0 From 24db0ff956a29d7883f235c925025e587ca0684f Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Mon, 8 Mar 2021 16:59:54 +0200 Subject: [PATCH 231/831] Fix Shelly logbook exception when missing COAP (#47620) --- homeassistant/components/shelly/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index 0058374cfe7..27152997ef7 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -177,9 +177,9 @@ def get_device_wrapper(hass: HomeAssistant, device_id: str): return None for config_entry in hass.data[DOMAIN][DATA_CONFIG_ENTRY]: - wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry][COAP] + wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry].get(COAP) - if wrapper.device_id == device_id: + if wrapper and wrapper.device_id == device_id: return wrapper return None From ee2658f9e6b6bd3333eabfaf2d8294c0facffb18 Mon Sep 17 00:00:00 2001 From: B-Hartley Date: Mon, 8 Mar 2021 16:22:13 +0000 Subject: [PATCH 232/831] Add (some) of ZCL concentration clusters to ZHA component (#47590) * Update registries.py Add concentration clusters recently added to zigpy * Update measurement.py Add concentration clusters recently added to ZigPy * Update sensor.py Add concentration clusters recently added to ZigPy * Update sensor.py remove unnecessary tabs * Update measurement.py remove unnecessary tabs * Update sensor.py Just adding CO and CO2 for now. * Update registries.py Just adding CO2 and CO for now. * Update measurement.py Just adding CO2 and CO for now * Update sensor.py import const CONCENTRATION_PARTS_PER_MILLION * Update registries.py removed trailing whitespace * Update sensor.py added extra blank lines and removed trailing whitespace * Update measurement.py added extra blank lines and removed trailing whitespace * Update sensor.py add device classes for CO and CO2 --- .../zha/core/channels/measurement.py | 28 +++++++++++++++++++ .../components/zha/core/registries.py | 2 ++ homeassistant/components/zha/sensor.py | 25 +++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/homeassistant/components/zha/core/channels/measurement.py b/homeassistant/components/zha/core/channels/measurement.py index 64db1aa82ac..78ff12a9bf3 100644 --- a/homeassistant/components/zha/core/channels/measurement.py +++ b/homeassistant/components/zha/core/channels/measurement.py @@ -74,3 +74,31 @@ class TemperatureMeasurement(ZigbeeChannel): "config": (REPORT_CONFIG_MIN_INT, REPORT_CONFIG_MAX_INT, 50), } ] + + +@registries.ZIGBEE_CHANNEL_REGISTRY.register( + measurement.CarbonMonoxideConcentration.cluster_id +) +class CarbonMonoxideConcentration(ZigbeeChannel): + """Carbon Monoxide measurement channel.""" + + REPORT_CONFIG = [ + { + "attr": "measured_value", + "config": (REPORT_CONFIG_MIN_INT, REPORT_CONFIG_MAX_INT, 0.000001), + } + ] + + +@registries.ZIGBEE_CHANNEL_REGISTRY.register( + measurement.CarbonDioxideConcentration.cluster_id +) +class CarbonDioxideConcentration(ZigbeeChannel): + """Carbon Dioxide measurement channel.""" + + REPORT_CONFIG = [ + { + "attr": "measured_value", + "config": (REPORT_CONFIG_MIN_INT, REPORT_CONFIG_MAX_INT, 0.000001), + } + ] diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py index 4dcccc98c05..66a9a70e752 100644 --- a/homeassistant/components/zha/core/registries.py +++ b/homeassistant/components/zha/core/registries.py @@ -68,6 +68,8 @@ SINGLE_INPUT_CLUSTER_DEVICE_CLASS = { zcl.clusters.general.PowerConfiguration.cluster_id: SENSOR, zcl.clusters.homeautomation.ElectricalMeasurement.cluster_id: SENSOR, zcl.clusters.hvac.Fan.cluster_id: FAN, + zcl.clusters.measurement.CarbonDioxideConcentration.cluster_id: SENSOR, + zcl.clusters.measurement.CarbonMonoxideConcentration.cluster_id: SENSOR, zcl.clusters.measurement.IlluminanceMeasurement.cluster_id: SENSOR, zcl.clusters.measurement.OccupancySensing.cluster_id: BINARY_SENSOR, zcl.clusters.measurement.PressureMeasurement.cluster_id: SENSOR, diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 78bc8fcbe4e..84d2dfb06bb 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -5,6 +5,8 @@ from typing import Any, Callable, Dict, List, Optional, Union from homeassistant.components.sensor import ( DEVICE_CLASS_BATTERY, + DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, @@ -14,6 +16,7 @@ from homeassistant.components.sensor import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( + CONCENTRATION_PARTS_PER_MILLION, LIGHT_LUX, PERCENTAGE, POWER_WATT, @@ -279,3 +282,25 @@ class Temperature(Sensor): _device_class = DEVICE_CLASS_TEMPERATURE _divisor = 100 _unit = TEMP_CELSIUS + + +@STRICT_MATCH(channel_names="carbon_dioxide_concentration") +class CarbonDioxideConcentration(Sensor): + """Carbon Dioxide Concentration sensor.""" + + SENSOR_ATTR = "measured_value" + _device_class = DEVICE_CLASS_CO2 + _decimals = 0 + _multiplier = 1e6 + _unit = CONCENTRATION_PARTS_PER_MILLION + + +@STRICT_MATCH(channel_names="carbon_monoxide_concentration") +class CarbonMonoxideConcentration(Sensor): + """Carbon Monoxide Concentration sensor.""" + + SENSOR_ATTR = "measured_value" + _device_class = DEVICE_CLASS_CO + _decimals = 0 + _multiplier = 1e6 + _unit = CONCENTRATION_PARTS_PER_MILLION From 8fe51b8ea7e5912a70f3650bd0fb794681e67524 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 8 Mar 2021 18:04:41 +0100 Subject: [PATCH 233/831] Store automation traces indexed by run_id (#47509) * Store traces indexed by run_id * Format * Add test * Add test * Clarify comment --- .../components/automation/__init__.py | 38 +++++++-- tests/components/config/test_automation.py | 80 ++++++++++++++++++- tests/helpers/test_script.py | 14 +++- 3 files changed, 122 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index df7102effde..9963c942f08 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -1,7 +1,8 @@ """Allow to set up simple automation rules via the config file.""" -from collections import deque +from collections import OrderedDict from contextlib import contextmanager import datetime as dt +from itertools import count import logging from typing import ( Any, @@ -240,6 +241,8 @@ async def async_setup(hass, config): class AutomationTrace: """Container for automation trace.""" + _runids = count(0) + def __init__( self, unique_id: Optional[str], @@ -254,6 +257,7 @@ class AutomationTrace: self._context: Context = context self._error: Optional[Exception] = None self._state: str = "running" + self.runid: str = str(next(self._runids)) self._timestamp_finish: Optional[dt.datetime] = None self._timestamp_start: dt.datetime = dt_util.utcnow() self._trigger: Dict[str, Any] = trigger @@ -300,6 +304,7 @@ class AutomationTrace: "config": self._config, "context": self._context, "state": self._state, + "run_id": self.runid, "timestamp": { "start": self._timestamp_start, "finish": self._timestamp_finish, @@ -313,16 +318,37 @@ class AutomationTrace: return result +class LimitedSizeDict(OrderedDict): + """OrderedDict limited in size.""" + + def __init__(self, *args, **kwds): + """Initialize OrderedDict limited in size.""" + self.size_limit = kwds.pop("size_limit", None) + OrderedDict.__init__(self, *args, **kwds) + self._check_size_limit() + + def __setitem__(self, key, value): + """Set item and check dict size.""" + OrderedDict.__setitem__(self, key, value) + self._check_size_limit() + + def _check_size_limit(self): + """Check dict size and evict items in FIFO order if needed.""" + if self.size_limit is not None: + while len(self) > self.size_limit: + self.popitem(last=False) + + @contextmanager def trace_automation(hass, unique_id, config, trigger, context): """Trace action execution of automation with automation_id.""" automation_trace = AutomationTrace(unique_id, config, trigger, context) if unique_id: - if unique_id not in hass.data[DATA_AUTOMATION_TRACE]: - hass.data[DATA_AUTOMATION_TRACE][unique_id] = deque([], STORED_TRACES) - traces = hass.data[DATA_AUTOMATION_TRACE][unique_id] - traces.append(automation_trace) + automation_traces = hass.data[DATA_AUTOMATION_TRACE] + if unique_id not in automation_traces: + automation_traces[unique_id] = LimitedSizeDict(size_limit=STORED_TRACES) + automation_traces[unique_id][automation_trace.runid] = automation_trace try: yield automation_trace @@ -835,7 +861,7 @@ def get_debug_traces_for_automation(hass, automation_id): """Return a serializable list of debug traces for an automation.""" traces = [] - for trace in hass.data[DATA_AUTOMATION_TRACE].get(automation_id, []): + for trace in hass.data[DATA_AUTOMATION_TRACE].get(automation_id, {}).values(): traces.append(trace.as_dict()) return traces diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index 7e0cf9e8e4d..cb192befade 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -3,7 +3,7 @@ import json from unittest.mock import patch from homeassistant.bootstrap import async_setup_component -from homeassistant.components import config +from homeassistant.components import automation, config from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @@ -325,3 +325,81 @@ async def test_get_automation_trace(hass, hass_ws_client): assert trace["trigger"]["description"] == "event 'test_event2'" assert trace["unique_id"] == "moon" assert trace["variables"] + + +async def test_automation_trace_overflow(hass, hass_ws_client): + """Test the number of stored traces per automation is limited.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": {"event": "some_event"}, + } + moon_config = { + "id": "moon", + "trigger": {"platform": "event", "event_type": "test_event2"}, + "action": {"event": "another_event"}, + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + moon_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + await client.send_json({"id": next_id(), "type": "automation/trace"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {} + + await client.send_json( + {"id": next_id(), "type": "automation/trace", "automation_id": "sun"} + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {"sun": []} + + # Trigger "sun" and "moon" automation once + hass.bus.async_fire("test_event") + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # Get traces + await client.send_json({"id": next_id(), "type": "automation/trace"}) + response = await client.receive_json() + assert response["success"] + assert len(response["result"]["moon"]) == 1 + moon_run_id = response["result"]["moon"][0]["run_id"] + assert len(response["result"]["sun"]) == 1 + + # Trigger "moon" automation enough times to overflow the number of stored traces + for _ in range(automation.STORED_TRACES): + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + await client.send_json({"id": next_id(), "type": "automation/trace"}) + response = await client.receive_json() + assert response["success"] + assert len(response["result"]["moon"]) == automation.STORED_TRACES + assert len(response["result"]["sun"]) == 1 + assert int(response["result"]["moon"][0]["run_id"]) == int(moon_run_id) + 1 + assert ( + int(response["result"]["moon"][-1]["run_id"]) + == int(moon_run_id) + automation.STORED_TRACES + ) diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 027254ee03e..18c769fee73 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -1192,11 +1192,11 @@ async def test_condition_all_cached(hass): assert len(script_obj._config_cache) == 2 -async def test_repeat_count(hass, caplog): +@pytest.mark.parametrize("count", [3, script.ACTION_TRACE_NODE_MAX_LEN * 2]) +async def test_repeat_count(hass, caplog, count): """Test repeat action w/ count option.""" event = "test_event" events = async_capture_events(hass, event) - count = 3 alias = "condition step" sequence = cv.SCRIPT_SCHEMA( @@ -1215,7 +1215,8 @@ async def test_repeat_count(hass, caplog): }, } ) - script_obj = script.Script(hass, sequence, "Test Name", "test_domain") + with script.trace_action(None): + script_obj = script.Script(hass, sequence, "Test Name", "test_domain") await script_obj.async_run(context=Context()) await hass.async_block_till_done() @@ -1226,6 +1227,13 @@ async def test_repeat_count(hass, caplog): assert event.data.get("index") == index + 1 assert event.data.get("last") == (index == count - 1) assert caplog.text.count(f"Repeating {alias}") == count + assert_action_trace( + { + "": [{}], + "0": [{}], + "0/0/0": [{}] * min(count, script.ACTION_TRACE_NODE_MAX_LEN), + } + ) @pytest.mark.parametrize("condition", ["while", "until"]) From 53952b96623e255d06b8f1674b81eff329fe680d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 07:34:34 -1000 Subject: [PATCH 234/831] Ensure template fan value_template always determines on state (#47598) --- homeassistant/components/template/fan.py | 3 --- tests/components/template/test_fan.py | 8 ++++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index 18a7d8262e0..51dce0f8d56 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -523,7 +523,6 @@ class TemplateFan(TemplateEntity, FanEntity): speed = str(speed) if speed in self._speed_list: - self._state = STATE_OFF if speed == SPEED_OFF else STATE_ON self._speed = speed self._percentage = self.speed_to_percentage(speed) self._preset_mode = speed if speed in self.preset_modes else None @@ -552,7 +551,6 @@ class TemplateFan(TemplateEntity, FanEntity): return if 0 <= percentage <= 100: - self._state = STATE_OFF if percentage == 0 else STATE_ON self._percentage = percentage if self._speed_list: self._speed = self.percentage_to_speed(percentage) @@ -569,7 +567,6 @@ class TemplateFan(TemplateEntity, FanEntity): preset_mode = str(preset_mode) if preset_mode in self.preset_modes: - self._state = STATE_ON self._speed = preset_mode self._percentage = None self._preset_mode = preset_mode diff --git a/tests/components/template/test_fan.py b/tests/components/template/test_fan.py index 2b9059017c6..34dccd7d172 100644 --- a/tests/components/template/test_fan.py +++ b/tests/components/template/test_fan.py @@ -246,6 +246,10 @@ async def test_templates_with_entities(hass, calls): await hass.async_block_till_done() _verify(hass, STATE_ON, None, 0, True, DIRECTION_FORWARD, None) + hass.states.async_set(_STATE_INPUT_BOOLEAN, False) + await hass.async_block_till_done() + _verify(hass, STATE_OFF, None, 0, True, DIRECTION_FORWARD, None) + async def test_templates_with_entities_and_invalid_percentage(hass, calls): """Test templates with values from other entities.""" @@ -274,7 +278,7 @@ async def test_templates_with_entities_and_invalid_percentage(hass, calls): await hass.async_start() await hass.async_block_till_done() - _verify(hass, STATE_OFF, SPEED_OFF, 0, None, None, None) + _verify(hass, STATE_ON, SPEED_OFF, 0, None, None, None) hass.states.async_set("sensor.percentage", "33") await hass.async_block_till_done() @@ -299,7 +303,7 @@ async def test_templates_with_entities_and_invalid_percentage(hass, calls): hass.states.async_set("sensor.percentage", "0") await hass.async_block_till_done() - _verify(hass, STATE_OFF, SPEED_OFF, 0, None, None, None) + _verify(hass, STATE_ON, SPEED_OFF, 0, None, None, None) async def test_templates_with_entities_and_preset_modes(hass, calls): From 8018097c544fe4e56d71fa56ff23029b9464910c Mon Sep 17 00:00:00 2001 From: Czapla <56671347+Antoni-Czaplicki@users.noreply.github.com> Date: Mon, 8 Mar 2021 18:37:37 +0100 Subject: [PATCH 235/831] Add title key to allow mobile app title translation to other languages (#46593) --- homeassistant/components/mobile_app/strings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/mobile_app/strings.json b/homeassistant/components/mobile_app/strings.json index b18f3e7265c..9e388ebc76c 100644 --- a/homeassistant/components/mobile_app/strings.json +++ b/homeassistant/components/mobile_app/strings.json @@ -1,4 +1,5 @@ { + "title": "Mobile App", "config": { "step": { "confirm": { From 9d14ff810522c893bcf9533ff30f48a8cd3019c2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 07:39:57 -1000 Subject: [PATCH 236/831] Add suggested_area support to Apple TV (#47015) --- homeassistant/components/apple_tv/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index c735ebc57e7..75c4d6ccc8a 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -348,10 +348,16 @@ class AppleTVManager: "name": self.config_entry.data[CONF_NAME], } + area = attrs["name"] + name_trailer = f" {DEFAULT_NAME}" + if area.endswith(name_trailer): + area = area[: -len(name_trailer)] + attrs["suggested_area"] = area + if self.atv: dev_info = self.atv.device_info - attrs["model"] = "Apple TV " + dev_info.model.name.replace("Gen", "") + attrs["model"] = DEFAULT_NAME + " " + dev_info.model.name.replace("Gen", "") attrs["sw_version"] = dev_info.version if dev_info.mac: From 65776ef9808a5fa8f041610322c4011b702361f8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 07:40:39 -1000 Subject: [PATCH 237/831] Remove self as code owner for mylink (#46242) Sadly these devices turn out to be so unreliable that I gave up on them and replaced them with bond (bondhome.io) devices which have been rock solid --- CODEOWNERS | 1 - homeassistant/components/somfy_mylink/manifest.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 3f0b3d89e05..b4498aecc27 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -436,7 +436,6 @@ homeassistant/components/solarlog/* @Ernst79 homeassistant/components/solax/* @squishykid homeassistant/components/soma/* @ratsept homeassistant/components/somfy/* @tetienne -homeassistant/components/somfy_mylink/* @bdraco homeassistant/components/sonarr/* @ctalkington homeassistant/components/songpal/* @rytilahti @shenxn homeassistant/components/sonos/* @cgtobi diff --git a/homeassistant/components/somfy_mylink/manifest.json b/homeassistant/components/somfy_mylink/manifest.json index a7be33583d2..a71661f57f4 100644 --- a/homeassistant/components/somfy_mylink/manifest.json +++ b/homeassistant/components/somfy_mylink/manifest.json @@ -5,7 +5,7 @@ "requirements": [ "somfy-mylink-synergy==1.0.6" ], - "codeowners": ["@bdraco"], + "codeowners": [], "config_flow": true, "dhcp": [{ "hostname":"somfy_*", "macaddress":"B8B7F1*" From f9e33a4a0d69db4b19ad7b280f2403d27be22157 Mon Sep 17 00:00:00 2001 From: Tony Roman Date: Mon, 8 Mar 2021 13:26:08 -0500 Subject: [PATCH 238/831] Allow running and restarting with both ozw and zwave active (#47566) Co-authored-by: Martin Hjelmare --- homeassistant/components/ozw/manifest.json | 3 +-- script/hassfest/dependencies.py | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/ozw/manifest.json b/homeassistant/components/ozw/manifest.json index 984e3f9c51a..a1409fd79a8 100644 --- a/homeassistant/components/ozw/manifest.json +++ b/homeassistant/components/ozw/manifest.json @@ -7,8 +7,7 @@ "python-openzwave-mqtt[mqtt-client]==1.4.0" ], "after_dependencies": [ - "mqtt", - "zwave" + "mqtt" ], "codeowners": [ "@cgarwood", diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py index 6283b2d8665..b13d7929042 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -147,6 +147,8 @@ IGNORE_VIOLATIONS = { # Demo ("demo", "manual"), ("demo", "openalpr_local"), + # Migration wizard from zwave to ozw. + "ozw", # This should become a helper method that integrations can submit data to ("websocket_api", "lovelace"), ("websocket_api", "shopping_list"), From 32476a3fed939e476c59a0625eaa901c18363906 Mon Sep 17 00:00:00 2001 From: ollo69 <60491700+ollo69@users.noreply.github.com> Date: Mon, 8 Mar 2021 19:34:12 +0100 Subject: [PATCH 239/831] Fix AsusWRT wrong api call (#47522) --- CODEOWNERS | 2 +- homeassistant/components/asuswrt/config_flow.py | 2 +- homeassistant/components/asuswrt/manifest.json | 2 +- homeassistant/components/asuswrt/router.py | 2 +- tests/components/asuswrt/test_config_flow.py | 4 ++-- tests/components/asuswrt/test_sensor.py | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index b4498aecc27..bece933d9c8 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -46,7 +46,7 @@ homeassistant/components/arcam_fmj/* @elupus homeassistant/components/arduino/* @fabaff homeassistant/components/arest/* @fabaff homeassistant/components/arris_tg2492lg/* @vanbalken -homeassistant/components/asuswrt/* @kennedyshead +homeassistant/components/asuswrt/* @kennedyshead @ollo69 homeassistant/components/atag/* @MatsNL homeassistant/components/aten_pe/* @mtdcr homeassistant/components/atome/* @baqs diff --git a/homeassistant/components/asuswrt/config_flow.py b/homeassistant/components/asuswrt/config_flow.py index b312d9b8186..94b5fd0f691 100644 --- a/homeassistant/components/asuswrt/config_flow.py +++ b/homeassistant/components/asuswrt/config_flow.py @@ -127,7 +127,7 @@ class AsusWrtFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): conf_protocol = user_input[CONF_PROTOCOL] if conf_protocol == PROTOCOL_TELNET: - await api.connection.disconnect() + api.connection.disconnect() return RESULT_SUCCESS async def async_step_user(self, user_input=None): diff --git a/homeassistant/components/asuswrt/manifest.json b/homeassistant/components/asuswrt/manifest.json index 744a05b9728..ab739f1c7ec 100644 --- a/homeassistant/components/asuswrt/manifest.json +++ b/homeassistant/components/asuswrt/manifest.json @@ -4,5 +4,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/asuswrt", "requirements": ["aioasuswrt==1.3.1"], - "codeowners": ["@kennedyshead"] + "codeowners": ["@kennedyshead", "@ollo69"] } diff --git a/homeassistant/components/asuswrt/router.py b/homeassistant/components/asuswrt/router.py index c94135423d8..4c92ee2ef67 100644 --- a/homeassistant/components/asuswrt/router.py +++ b/homeassistant/components/asuswrt/router.py @@ -341,7 +341,7 @@ class AsusWrtRouter: """Close the connection.""" if self._api is not None: if self._protocol == PROTOCOL_TELNET: - await self._api.connection.disconnect() + self._api.connection.disconnect() self._api = None for func in self._on_close: diff --git a/tests/components/asuswrt/test_config_flow.py b/tests/components/asuswrt/test_config_flow.py index 7faec5d336c..a6e24b09462 100644 --- a/tests/components/asuswrt/test_config_flow.py +++ b/tests/components/asuswrt/test_config_flow.py @@ -1,6 +1,6 @@ """Tests for the AsusWrt config flow.""" from socket import gaierror -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock, Mock, patch import pytest @@ -46,7 +46,7 @@ def mock_controller_connect(): with patch("homeassistant.components.asuswrt.router.AsusWrt") as service_mock: service_mock.return_value.connection.async_connect = AsyncMock() service_mock.return_value.is_connected = True - service_mock.return_value.connection.disconnect = AsyncMock() + service_mock.return_value.connection.disconnect = Mock() yield service_mock diff --git a/tests/components/asuswrt/test_sensor.py b/tests/components/asuswrt/test_sensor.py index 334f06d85a6..4a8a1f27653 100644 --- a/tests/components/asuswrt/test_sensor.py +++ b/tests/components/asuswrt/test_sensor.py @@ -1,6 +1,6 @@ """Tests for the AsusWrt sensor.""" from datetime import timedelta -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock, Mock, patch from aioasuswrt.asuswrt import Device import pytest @@ -49,7 +49,7 @@ def mock_controller_connect(): with patch("homeassistant.components.asuswrt.router.AsusWrt") as service_mock: service_mock.return_value.connection.async_connect = AsyncMock() service_mock.return_value.is_connected = True - service_mock.return_value.connection.disconnect = AsyncMock() + service_mock.return_value.connection.disconnect = Mock() service_mock.return_value.async_get_connected_devices = AsyncMock( return_value=MOCK_DEVICES ) From b315fcab1160e8daa272a8b75719b1382ca68083 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 08:43:22 -1000 Subject: [PATCH 240/831] Fix turn on without speed in homekit controller (#47597) --- .../components/homekit_controller/fan.py | 9 +- .../components/homekit_controller/conftest.py | 7 ++ .../components/homekit_controller/test_fan.py | 97 +++++++++++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homekit_controller/fan.py b/homeassistant/components/homekit_controller/fan.py index e2cdf7b3cfd..591050f5fd9 100644 --- a/homeassistant/components/homekit_controller/fan.py +++ b/homeassistant/components/homekit_controller/fan.py @@ -80,6 +80,13 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): return features + @property + def speed_count(self): + """Speed count for the fan.""" + return round( + 100 / max(1, self.service[CharacteristicsTypes.ROTATION_SPEED].minStep or 0) + ) + async def async_set_direction(self, direction): """Set the direction of the fan.""" await self.async_put_characteristics( @@ -110,7 +117,7 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): if not self.is_on: characteristics[self.on_characteristic] = True - if self.supported_features & SUPPORT_SET_SPEED: + if percentage is not None and self.supported_features & SUPPORT_SET_SPEED: characteristics[CharacteristicsTypes.ROTATION_SPEED] = percentage if characteristics: diff --git a/tests/components/homekit_controller/conftest.py b/tests/components/homekit_controller/conftest.py index 4e095b1d2d9..3cde3912709 100644 --- a/tests/components/homekit_controller/conftest.py +++ b/tests/components/homekit_controller/conftest.py @@ -11,6 +11,13 @@ import homeassistant.util.dt as dt_util from tests.components.light.conftest import mock_light_profiles # noqa: F401 +@pytest.fixture(autouse=True) +def mock_zeroconf(): + """Mock zeroconf.""" + with mock.patch("homeassistant.components.zeroconf.HaZeroconf") as mock_zc: + yield mock_zc.return_value + + @pytest.fixture def utcnow(request): """Freeze time at a known point.""" diff --git a/tests/components/homekit_controller/test_fan.py b/tests/components/homekit_controller/test_fan.py index b8d42b21643..d66ce81d534 100644 --- a/tests/components/homekit_controller/test_fan.py +++ b/tests/components/homekit_controller/test_fan.py @@ -50,6 +50,38 @@ def create_fanv2_service(accessory): swing_mode.value = 0 +def create_fanv2_service_with_min_step(accessory): + """Define fan v2 characteristics as per HAP spec.""" + service = accessory.add_service(ServicesTypes.FAN_V2) + + cur_state = service.add_char(CharacteristicsTypes.ACTIVE) + cur_state.value = 0 + + direction = service.add_char(CharacteristicsTypes.ROTATION_DIRECTION) + direction.value = 0 + + speed = service.add_char(CharacteristicsTypes.ROTATION_SPEED) + speed.value = 0 + speed.minStep = 25 + + swing_mode = service.add_char(CharacteristicsTypes.SWING_MODE) + swing_mode.value = 0 + + +def create_fanv2_service_without_rotation_speed(accessory): + """Define fan v2 characteristics as per HAP spec.""" + service = accessory.add_service(ServicesTypes.FAN_V2) + + cur_state = service.add_char(CharacteristicsTypes.ACTIVE) + cur_state.value = 0 + + direction = service.add_char(CharacteristicsTypes.ROTATION_DIRECTION) + direction.value = 0 + + swing_mode = service.add_char(CharacteristicsTypes.SWING_MODE) + swing_mode.value = 0 + + async def test_fan_read_state(hass, utcnow): """Test that we can read the state of a HomeKit fan accessory.""" helper = await setup_test_component(hass, create_fan_service) @@ -95,6 +127,29 @@ async def test_turn_on(hass, utcnow): assert helper.characteristics[V1_ROTATION_SPEED].value == 33.0 +async def test_turn_on_off_without_rotation_speed(hass, utcnow): + """Test that we can turn a fan on.""" + helper = await setup_test_component( + hass, create_fanv2_service_without_rotation_speed + ) + + await hass.services.async_call( + "fan", + "turn_on", + {"entity_id": "fan.testdevice"}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 1 + + await hass.services.async_call( + "fan", + "turn_off", + {"entity_id": "fan.testdevice"}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 0 + + async def test_turn_off(hass, utcnow): """Test that we can turn a fan off.""" helper = await setup_test_component(hass, create_fan_service) @@ -181,6 +236,7 @@ async def test_speed_read(hass, utcnow): state = await helper.poll_and_get_state() assert state.attributes["speed"] == "high" assert state.attributes["percentage"] == 100 + assert state.attributes["percentage_step"] == 1.0 helper.characteristics[V1_ROTATION_SPEED].value = 50 state = await helper.poll_and_get_state() @@ -277,6 +333,24 @@ async def test_v2_turn_on(hass, utcnow): assert helper.characteristics[V2_ACTIVE].value == 1 assert helper.characteristics[V2_ROTATION_SPEED].value == 33.0 + await hass.services.async_call( + "fan", + "turn_off", + {"entity_id": "fan.testdevice"}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 0 + assert helper.characteristics[V2_ROTATION_SPEED].value == 33.0 + + await hass.services.async_call( + "fan", + "turn_on", + {"entity_id": "fan.testdevice"}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 1 + assert helper.characteristics[V2_ROTATION_SPEED].value == 33.0 + async def test_v2_turn_off(hass, utcnow): """Test that we can turn a fan off.""" @@ -355,6 +429,29 @@ async def test_v2_set_percentage(hass, utcnow): assert helper.characteristics[V2_ACTIVE].value == 0 +async def test_v2_set_percentage_with_min_step(hass, utcnow): + """Test that we set fan speed by percentage.""" + helper = await setup_test_component(hass, create_fanv2_service_with_min_step) + + helper.characteristics[V2_ACTIVE].value = 1 + + await hass.services.async_call( + "fan", + "set_percentage", + {"entity_id": "fan.testdevice", "percentage": 66}, + blocking=True, + ) + assert helper.characteristics[V2_ROTATION_SPEED].value == 75 + + await hass.services.async_call( + "fan", + "set_percentage", + {"entity_id": "fan.testdevice", "percentage": 0}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 0 + + async def test_v2_speed_read(hass, utcnow): """Test that we can read a fans oscillation.""" helper = await setup_test_component(hass, create_fanv2_service) From 573c40cb1121420d7ba6e41238a0575a70a6d48c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 08:44:28 -1000 Subject: [PATCH 241/831] Ensure bond devices recover when wifi disconnects and reconnects (#47591) --- homeassistant/components/bond/entity.py | 2 +- tests/components/bond/test_entity.py | 169 ++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 tests/components/bond/test_entity.py diff --git a/homeassistant/components/bond/entity.py b/homeassistant/components/bond/entity.py index 6c56b47a9dc..cd120d79d56 100644 --- a/homeassistant/components/bond/entity.py +++ b/homeassistant/components/bond/entity.py @@ -102,7 +102,7 @@ class BondEntity(Entity): async def _async_update_if_bpup_not_alive(self, *_: Any) -> None: """Fetch via the API if BPUP is not alive.""" - if self._bpup_subs.alive and self._initialized: + if self._bpup_subs.alive and self._initialized and self._available: return assert self._update_lock is not None diff --git a/tests/components/bond/test_entity.py b/tests/components/bond/test_entity.py new file mode 100644 index 00000000000..e0a3f156ff5 --- /dev/null +++ b/tests/components/bond/test_entity.py @@ -0,0 +1,169 @@ +"""Tests for the Bond entities.""" +import asyncio +from datetime import timedelta +from unittest.mock import patch + +from bond_api import BPUPSubscriptions, DeviceType + +from homeassistant import core +from homeassistant.components import fan +from homeassistant.components.fan import DOMAIN as FAN_DOMAIN +from homeassistant.const import STATE_ON, STATE_UNAVAILABLE +from homeassistant.util import utcnow + +from .common import patch_bond_device_state, setup_platform + +from tests.common import async_fire_time_changed + + +def ceiling_fan(name: str): + """Create a ceiling fan with given name.""" + return { + "name": name, + "type": DeviceType.CEILING_FAN, + "actions": ["SetSpeed", "SetDirection"], + } + + +async def test_bpup_goes_offline_and_recovers_same_entity(hass: core.HomeAssistant): + """Test that push updates fail and we fallback to polling and then bpup recovers. + + The BPUP recovery is triggered by an update for the entity and + we do not fallback to polling because state is in sync. + """ + bpup_subs = BPUPSubscriptions() + with patch( + "homeassistant.components.bond.BPUPSubscriptions", + return_value=bpup_subs, + ): + await setup_platform( + hass, FAN_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id" + ) + + bpup_subs.notify( + { + "s": 200, + "t": "bond/test-device-id/update", + "b": {"power": 1, "speed": 3, "direction": 0}, + } + ) + await hass.async_block_till_done() + assert hass.states.get("fan.name_1").attributes[fan.ATTR_PERCENTAGE] == 100 + + bpup_subs.notify( + { + "s": 200, + "t": "bond/test-device-id/update", + "b": {"power": 1, "speed": 1, "direction": 0}, + } + ) + await hass.async_block_till_done() + assert hass.states.get("fan.name_1").attributes[fan.ATTR_PERCENTAGE] == 33 + + bpup_subs.last_message_time = 0 + with patch_bond_device_state(side_effect=asyncio.TimeoutError): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=230)) + await hass.async_block_till_done() + + assert hass.states.get("fan.name_1").state == STATE_UNAVAILABLE + + # Ensure we do not poll to get the state + # since bpup has recovered and we know we + # are back in sync + with patch_bond_device_state(side_effect=Exception): + bpup_subs.notify( + { + "s": 200, + "t": "bond/test-device-id/update", + "b": {"power": 1, "speed": 2, "direction": 0}, + } + ) + await hass.async_block_till_done() + + state = hass.states.get("fan.name_1") + assert state.state == STATE_ON + assert state.attributes[fan.ATTR_PERCENTAGE] == 66 + + +async def test_bpup_goes_offline_and_recovers_different_entity( + hass: core.HomeAssistant, +): + """Test that push updates fail and we fallback to polling and then bpup recovers. + + The BPUP recovery is triggered by an update for a different entity which + forces a poll since we need to re-get the state. + """ + bpup_subs = BPUPSubscriptions() + with patch( + "homeassistant.components.bond.BPUPSubscriptions", + return_value=bpup_subs, + ): + await setup_platform( + hass, FAN_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id" + ) + + bpup_subs.notify( + { + "s": 200, + "t": "bond/test-device-id/update", + "b": {"power": 1, "speed": 3, "direction": 0}, + } + ) + await hass.async_block_till_done() + assert hass.states.get("fan.name_1").attributes[fan.ATTR_PERCENTAGE] == 100 + + bpup_subs.notify( + { + "s": 200, + "t": "bond/test-device-id/update", + "b": {"power": 1, "speed": 1, "direction": 0}, + } + ) + await hass.async_block_till_done() + assert hass.states.get("fan.name_1").attributes[fan.ATTR_PERCENTAGE] == 33 + + bpup_subs.last_message_time = 0 + with patch_bond_device_state(side_effect=asyncio.TimeoutError): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=230)) + await hass.async_block_till_done() + + assert hass.states.get("fan.name_1").state == STATE_UNAVAILABLE + + bpup_subs.notify( + { + "s": 200, + "t": "bond/not-this-device-id/update", + "b": {"power": 1, "speed": 2, "direction": 0}, + } + ) + await hass.async_block_till_done() + assert hass.states.get("fan.name_1").state == STATE_UNAVAILABLE + + with patch_bond_device_state(return_value={"power": 1, "speed": 1}): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=430)) + await hass.async_block_till_done() + + state = hass.states.get("fan.name_1") + assert state.state == STATE_ON + assert state.attributes[fan.ATTR_PERCENTAGE] == 33 + + +async def test_polling_fails_and_recovers(hass: core.HomeAssistant): + """Test that polling fails and we recover.""" + await setup_platform( + hass, FAN_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id" + ) + + with patch_bond_device_state(side_effect=asyncio.TimeoutError): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=230)) + await hass.async_block_till_done() + + assert hass.states.get("fan.name_1").state == STATE_UNAVAILABLE + + with patch_bond_device_state(return_value={"power": 1, "speed": 1}): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=230)) + await hass.async_block_till_done() + + state = hass.states.get("fan.name_1") + assert state.state == STATE_ON + assert state.attributes[fan.ATTR_PERCENTAGE] == 33 From 67effbc8c40cce4fd8560173141f2fb1d4cdf21c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Mar 2021 10:54:51 -0800 Subject: [PATCH 242/831] Config flow to allow marking itself as confirm only (#47607) --- homeassistant/config_entries.py | 7 +++++++ homeassistant/helpers/config_entry_flow.py | 1 + tests/helpers/test_config_entry_flow.py | 9 +++++++++ 3 files changed, 17 insertions(+) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index f74acede507..c105be42ba3 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1012,6 +1012,13 @@ class ConfigFlow(data_entry_flow.FlowHandler): return None + @callback + def _set_confirm_only( + self, + ) -> None: + """Mark the config flow as only needing user confirmation to finish flow.""" + self.context["confirm_only"] = True + @callback def _async_current_entries(self, include_ignore: bool = False) -> List[ConfigEntry]: """Return current entries. diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index 889981537c6..69248186b53 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -42,6 +42,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow): ) -> Dict[str, Any]: """Confirm setup.""" if user_input is None: + self._set_confirm_only() return self.async_show_form(step_id="confirm") if self.source == config_entries.SOURCE_USER: diff --git a/tests/helpers/test_config_entry_flow.py b/tests/helpers/test_config_entry_flow.py index 874fd5df29a..b3233556957 100644 --- a/tests/helpers/test_config_entry_flow.py +++ b/tests/helpers/test_config_entry_flow.py @@ -78,6 +78,15 @@ async def test_user_has_confirmation(hass, discovery_flow_conf): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "confirm" + progress = hass.config_entries.flow.async_progress() + assert len(progress) == 1 + assert progress[0]["flow_id"] == result["flow_id"] + assert progress[0]["context"] == { + "confirm_only": True, + "source": config_entries.SOURCE_USER, + "unique_id": "test", + } + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY From d9bf63103fde44ddd38fb6b9a510d82609802b36 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Tue, 9 Mar 2021 03:23:57 +0800 Subject: [PATCH 243/831] Fix I-frame interval in stream test video (#47638) --- tests/components/stream/common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/components/stream/common.py b/tests/components/stream/common.py index 5ec4f4217ce..4c6841d03db 100644 --- a/tests/components/stream/common.py +++ b/tests/components/stream/common.py @@ -40,6 +40,7 @@ def generate_h264_video(container_format="mp4", audio_codec=None): stream.width = 480 stream.height = 320 stream.pix_fmt = "yuv420p" + stream.options.update({"g": str(fps), "keyint_min": str(fps)}) a_packet = None last_a_dts = -1 From a243adc5518c7c286c2e53a0b684a3ac505a9ec8 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 8 Mar 2021 20:30:52 +0100 Subject: [PATCH 244/831] Add WS command to get a summary of automation traces (#47557) * Add WS command to get a summary of automation traces * Update tests * Correct rebase mistake, update tests --- .../components/automation/__init__.py | 46 +++++- homeassistant/components/config/automation.py | 16 +- tests/components/config/test_automation.py | 141 +++++++++++++++--- 3 files changed, 178 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 9963c942f08..8b0fa1687ee 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -303,8 +303,8 @@ class AutomationTrace: "condition_trace": condition_traces, "config": self._config, "context": self._context, - "state": self._state, "run_id": self.runid, + "state": self._state, "timestamp": { "start": self._timestamp_start, "finish": self._timestamp_finish, @@ -317,6 +317,37 @@ class AutomationTrace: result["error"] = str(self._error) return result + def as_short_dict(self) -> Dict[str, Any]: + """Return a brief dictionary version of this AutomationTrace.""" + + last_action = None + last_condition = None + + if self._action_trace: + last_action = list(self._action_trace.keys())[-1] + if self._condition_trace: + last_condition = list(self._condition_trace.keys())[-1] + + result = { + "last_action": last_action, + "last_condition": last_condition, + "run_id": self.runid, + "state": self._state, + "timestamp": { + "start": self._timestamp_start, + "finish": self._timestamp_finish, + }, + "trigger": self._trigger.get("description"), + "unique_id": self._unique_id, + } + if self._error is not None: + result["error"] = str(self._error) + if last_action is not None: + result["last_action"] = last_action + result["last_condition"] = last_condition + + return result + class LimitedSizeDict(OrderedDict): """OrderedDict limited in size.""" @@ -857,22 +888,27 @@ def _trigger_extract_entities(trigger_conf: dict) -> List[str]: @callback -def get_debug_traces_for_automation(hass, automation_id): +def get_debug_traces_for_automation(hass, automation_id, summary=False): """Return a serializable list of debug traces for an automation.""" traces = [] for trace in hass.data[DATA_AUTOMATION_TRACE].get(automation_id, {}).values(): - traces.append(trace.as_dict()) + if summary: + traces.append(trace.as_short_dict()) + else: + traces.append(trace.as_dict()) return traces @callback -def get_debug_traces(hass): +def get_debug_traces(hass, summary=False): """Return a serializable list of debug traces.""" traces = {} for automation_id in hass.data[DATA_AUTOMATION_TRACE]: - traces[automation_id] = get_debug_traces_for_automation(hass, automation_id) + traces[automation_id] = get_debug_traces_for_automation( + hass, automation_id, summary + ) return traces diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index 23baa0c8843..708ad55aaeb 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -24,7 +24,8 @@ from . import ACTION_DELETE, EditIdBasedConfigView async def async_setup(hass): """Set up the Automation config API.""" - websocket_api.async_register_command(hass, websocket_automation_trace) + websocket_api.async_register_command(hass, websocket_automation_trace_get) + websocket_api.async_register_command(hass, websocket_automation_trace_list) async def hook(action, config_key): """post_write_hook for Config View that reloads automations.""" @@ -92,10 +93,10 @@ class EditAutomationConfigView(EditIdBasedConfigView): @websocket_api.websocket_command( - {vol.Required("type"): "automation/trace", vol.Optional("automation_id"): str} + {vol.Required("type"): "automation/trace/get", vol.Optional("automation_id"): str} ) @websocket_api.async_response -async def websocket_automation_trace(hass, connection, msg): +async def websocket_automation_trace_get(hass, connection, msg): """Get automation traces.""" automation_id = msg.get("automation_id") @@ -107,3 +108,12 @@ async def websocket_automation_trace(hass, connection, msg): } connection.send_result(msg["id"], automation_traces) + + +@websocket_api.websocket_command({vol.Required("type"): "automation/trace/list"}) +@websocket_api.async_response +async def websocket_automation_trace_list(hass, connection, msg): + """Summarize automation traces.""" + automation_traces = get_debug_traces(hass, summary=True) + + connection.send_result(msg["id"], automation_traces) diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index cb192befade..aaf97e22ccd 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -167,7 +167,7 @@ async def test_delete_automation(hass, hass_client): async def test_get_automation_trace(hass, hass_ws_client): - """Test deleting an automation.""" + """Test tracing an automation.""" id = 1 def next_id(): @@ -209,13 +209,13 @@ async def test_get_automation_trace(hass, hass_ws_client): client = await hass_ws_client() - await client.send_json({"id": next_id(), "type": "automation/trace"}) + await client.send_json({"id": next_id(), "type": "automation/trace/get"}) response = await client.receive_json() assert response["success"] assert response["result"] == {} await client.send_json( - {"id": next_id(), "type": "automation/trace", "automation_id": "sun"} + {"id": next_id(), "type": "automation/trace/get", "automation_id": "sun"} ) response = await client.receive_json() assert response["success"] @@ -226,7 +226,7 @@ async def test_get_automation_trace(hass, hass_ws_client): await hass.async_block_till_done() # Get trace - await client.send_json({"id": next_id(), "type": "automation/trace"}) + await client.send_json({"id": next_id(), "type": "automation/trace/get"}) response = await client.receive_json() assert response["success"] assert "moon" not in response["result"] @@ -251,7 +251,7 @@ async def test_get_automation_trace(hass, hass_ws_client): # Get trace await client.send_json( - {"id": next_id(), "type": "automation/trace", "automation_id": "moon"} + {"id": next_id(), "type": "automation/trace/get", "automation_id": "moon"} ) response = await client.receive_json() assert response["success"] @@ -279,7 +279,7 @@ async def test_get_automation_trace(hass, hass_ws_client): # Get trace await client.send_json( - {"id": next_id(), "type": "automation/trace", "automation_id": "moon"} + {"id": next_id(), "type": "automation/trace/get", "automation_id": "moon"} ) response = await client.receive_json() assert response["success"] @@ -304,7 +304,7 @@ async def test_get_automation_trace(hass, hass_ws_client): # Get trace await client.send_json( - {"id": next_id(), "type": "automation/trace", "automation_id": "moon"} + {"id": next_id(), "type": "automation/trace/get", "automation_id": "moon"} ) response = await client.receive_json() assert response["success"] @@ -363,25 +363,18 @@ async def test_automation_trace_overflow(hass, hass_ws_client): client = await hass_ws_client() - await client.send_json({"id": next_id(), "type": "automation/trace"}) + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] assert response["result"] == {} - await client.send_json( - {"id": next_id(), "type": "automation/trace", "automation_id": "sun"} - ) - response = await client.receive_json() - assert response["success"] - assert response["result"] == {"sun": []} - # Trigger "sun" and "moon" automation once hass.bus.async_fire("test_event") hass.bus.async_fire("test_event2") await hass.async_block_till_done() # Get traces - await client.send_json({"id": next_id(), "type": "automation/trace"}) + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] assert len(response["result"]["moon"]) == 1 @@ -393,7 +386,7 @@ async def test_automation_trace_overflow(hass, hass_ws_client): hass.bus.async_fire("test_event2") await hass.async_block_till_done() - await client.send_json({"id": next_id(), "type": "automation/trace"}) + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] assert len(response["result"]["moon"]) == automation.STORED_TRACES @@ -403,3 +396,117 @@ async def test_automation_trace_overflow(hass, hass_ws_client): int(response["result"]["moon"][-1]["run_id"]) == int(moon_run_id) + automation.STORED_TRACES ) + + +async def test_list_automation_traces(hass, hass_ws_client): + """Test listing automation traces.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": {"service": "test.automation"}, + } + moon_config = { + "id": "moon", + "trigger": [ + {"platform": "event", "event_type": "test_event2"}, + {"platform": "event", "event_type": "test_event3"}, + ], + "condition": { + "condition": "template", + "value_template": "{{ trigger.event.event_type=='test_event2' }}", + }, + "action": {"event": "another_event"}, + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + moon_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {} + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + + # Get trace + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + assert "moon" not in response["result"] + assert len(response["result"]["sun"]) == 1 + + # Trigger "moon" automation, with passing condition + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # Trigger "moon" automation, with failing condition + hass.bus.async_fire("test_event3") + await hass.async_block_till_done() + + # Trigger "moon" automation, with passing condition + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # Get trace + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + assert len(response["result"]["moon"]) == 3 + assert len(response["result"]["sun"]) == 1 + trace = response["result"]["sun"][0] + assert trace["last_action"] == "action/0" + assert trace["last_condition"] is None + assert trace["error"] == "Unable to find service test.automation" + assert trace["state"] == "stopped" + assert trace["timestamp"] + assert trace["trigger"] == "event 'test_event'" + assert trace["unique_id"] == "sun" + + trace = response["result"]["moon"][0] + assert trace["last_action"] == "action/0" + assert trace["last_condition"] == "condition/0" + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["timestamp"] + assert trace["trigger"] == "event 'test_event2'" + assert trace["unique_id"] == "moon" + + trace = response["result"]["moon"][1] + assert trace["last_action"] is None + assert trace["last_condition"] == "condition/0" + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["timestamp"] + assert trace["trigger"] == "event 'test_event3'" + assert trace["unique_id"] == "moon" + + trace = response["result"]["moon"][2] + assert trace["last_action"] == "action/0" + assert trace["last_condition"] == "condition/0" + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["timestamp"] + assert trace["trigger"] == "event 'test_event2'" + assert trace["unique_id"] == "moon" From 215ab5fd40eefbccd5677d7c8a247e3114da946c Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Mon, 8 Mar 2021 22:21:45 +0200 Subject: [PATCH 245/831] Add type hints to LightEntity (#47024) --- homeassistant/components/group/light.py | 8 ++++---- homeassistant/components/light/__init__.py | 18 +++++++++--------- homeassistant/components/zwave_js/light.py | 2 +- homeassistant/util/color.py | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index 00b7321076f..5720948bc01 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -88,8 +88,8 @@ class LightGroup(GroupEntity, light.LightEntity): self._brightness: Optional[int] = None self._hs_color: Optional[Tuple[float, float]] = None self._color_temp: Optional[int] = None - self._min_mireds: Optional[int] = 154 - self._max_mireds: Optional[int] = 500 + self._min_mireds: int = 154 + self._max_mireds: int = 500 self._white_value: Optional[int] = None self._effect_list: Optional[List[str]] = None self._effect: Optional[str] = None @@ -152,12 +152,12 @@ class LightGroup(GroupEntity, light.LightEntity): return self._color_temp @property - def min_mireds(self) -> Optional[int]: + def min_mireds(self) -> int: """Return the coldest color_temp that this light group supports.""" return self._min_mireds @property - def max_mireds(self) -> Optional[int]: + def max_mireds(self) -> int: """Return the warmest color_temp that this light group supports.""" return self._max_mireds diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 55476c754f2..4156736f74d 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -407,46 +407,46 @@ class LightEntity(ToggleEntity): """Representation of a light.""" @property - def brightness(self): + def brightness(self) -> Optional[int]: """Return the brightness of this light between 0..255.""" return None @property - def hs_color(self): + def hs_color(self) -> Optional[Tuple[float, float]]: """Return the hue and saturation color value [float, float].""" return None @property - def color_temp(self): + def color_temp(self) -> Optional[int]: """Return the CT color value in mireds.""" return None @property - def min_mireds(self): + def min_mireds(self) -> int: """Return the coldest color_temp that this light supports.""" # Default to the Philips Hue value that HA has always assumed # https://developers.meethue.com/documentation/core-concepts return 153 @property - def max_mireds(self): + def max_mireds(self) -> int: """Return the warmest color_temp that this light supports.""" # Default to the Philips Hue value that HA has always assumed # https://developers.meethue.com/documentation/core-concepts return 500 @property - def white_value(self): + def white_value(self) -> Optional[int]: """Return the white value of this light between 0..255.""" return None @property - def effect_list(self): + def effect_list(self) -> Optional[List[str]]: """Return the list of supported effects.""" return None @property - def effect(self): + def effect(self) -> Optional[str]: """Return the current effect.""" return None @@ -495,7 +495,7 @@ class LightEntity(ToggleEntity): return {key: val for key, val in data.items() if val is not None} @property - def supported_features(self): + def supported_features(self) -> int: """Flag supported features.""" return 0 diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index b501ecb58e7..d66821c9ba3 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -151,7 +151,7 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): return self._max_mireds @property - def supported_features(self) -> Optional[int]: + def supported_features(self) -> int: """Flag supported features.""" return self._supported_features diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index 4a9162707fa..36bf47aaf96 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -508,12 +508,12 @@ def _get_blue(temperature: float) -> float: return _bound(blue) -def color_temperature_mired_to_kelvin(mired_temperature: float) -> float: +def color_temperature_mired_to_kelvin(mired_temperature: float) -> int: """Convert absolute mired shift to degrees kelvin.""" return math.floor(1000000 / mired_temperature) -def color_temperature_kelvin_to_mired(kelvin_temperature: float) -> float: +def color_temperature_kelvin_to_mired(kelvin_temperature: float) -> int: """Convert degrees kelvin to mired shift.""" return math.floor(1000000 / kelvin_temperature) From 665e2c34730d4c4c78bd742446542b41d6f99838 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Mon, 8 Mar 2021 21:53:44 +0100 Subject: [PATCH 246/831] Ensure bond light follows proper typing (#47641) --- homeassistant/components/bond/light.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/bond/light.py b/homeassistant/components/bond/light.py index d5f7cb29207..d2b06012ed3 100644 --- a/homeassistant/components/bond/light.py +++ b/homeassistant/components/bond/light.py @@ -95,7 +95,7 @@ class BondBaseLight(BondEntity, LightEntity): return self._light == 1 @property - def supported_features(self) -> Optional[int]: + def supported_features(self) -> int: """Flag supported features.""" return 0 @@ -119,7 +119,7 @@ class BondLight(BondBaseLight, BondEntity, LightEntity): self._brightness = state.get("brightness") @property - def supported_features(self) -> Optional[int]: + def supported_features(self) -> int: """Flag supported features.""" if self._device.supports_set_brightness(): return SUPPORT_BRIGHTNESS @@ -203,7 +203,7 @@ class BondFireplace(BondEntity, LightEntity): self._flame = state.get("flame") @property - def supported_features(self) -> Optional[int]: + def supported_features(self) -> int: """Flag brightness as supported feature to represent flame level.""" return SUPPORT_BRIGHTNESS From ea4f3e31d58f8473b7af6bfd85d51196ac66e451 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 8 Mar 2021 22:48:36 +0100 Subject: [PATCH 247/831] Include changed variables in automation trace (#47549) * Include changed variables in automation trace * Deduplicate some code * Tweak * Apply suggestions from code review Co-authored-by: Paulus Schoutsen * Fix format Co-authored-by: Paulus Schoutsen --- homeassistant/helpers/script.py | 4 ++-- homeassistant/helpers/trace.py | 19 ++++++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 8ee9b12bc1b..b3fcffd4944 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -120,7 +120,7 @@ _TIMEOUT_MSG = "Timeout reached, abort script." _SHUTDOWN_MAX_WAIT = 60 -ACTION_TRACE_NODE_MAX_LEN = 20 # Max the length of a trace node for repeated actions +ACTION_TRACE_NODE_MAX_LEN = 20 # Max length of a trace node for repeated actions def action_trace_append(variables, path): @@ -294,7 +294,7 @@ class _ScriptRun: self._finish() async def _async_step(self, log_exceptions): - with trace_path(str(self._step)), trace_action(None): + with trace_path(str(self._step)), trace_action(self._variables): try: handler = f"_async_{cv.determine_script_action(self._action)}_step" await getattr(self, handler)() diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py index 8c5c181ea24..0c1969a8ac6 100644 --- a/homeassistant/helpers/trace.py +++ b/homeassistant/helpers/trace.py @@ -16,7 +16,17 @@ class TraceElement: self._error: Optional[Exception] = None self._result: Optional[dict] = None self._timestamp = dt_util.utcnow() - self._variables = variables + + if variables is None: + variables = {} + last_variables = variables_cv.get() or {} + variables_cv.set(dict(variables)) + changed_variables = { + key: value + for key, value in variables.items() + if key not in last_variables or last_variables[key] != value + } + self._variables = changed_variables def __repr__(self) -> str: """Container for trace data.""" @@ -33,8 +43,8 @@ class TraceElement: def as_dict(self) -> Dict[str, Any]: """Return dictionary version of this TraceElement.""" result: Dict[str, Any] = {"timestamp": self._timestamp} - # Commented out because we get too many copies of the same data - # result["variables"] = self._variables + if self._variables: + result["changed_variables"] = self._variables if self._error is not None: result["error"] = str(self._error) if self._result is not None: @@ -55,6 +65,8 @@ trace_stack_cv: ContextVar[Optional[List[TraceElement]]] = ContextVar( trace_path_stack_cv: ContextVar[Optional[List[str]]] = ContextVar( "trace_path_stack_cv", default=None ) +# Copy of last variables +variables_cv: ContextVar[Optional[Any]] = ContextVar("variables_cv", default=None) def trace_stack_push(trace_stack_var: ContextVar, node: Any) -> None: @@ -128,6 +140,7 @@ def trace_clear() -> None: trace_cv.set({}) trace_stack_cv.set(None) trace_path_stack_cv.set(None) + variables_cv.set(None) def trace_set_result(**kwargs: Any) -> None: From 520c4a8ee34fc050cbf6f49731a93c4521921577 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 8 Mar 2021 22:49:54 +0100 Subject: [PATCH 248/831] Update attrs to 20.3.0 (#47642) --- 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 46dc2d02781..d9d8618f78e 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -5,7 +5,7 @@ aiohttp_cors==0.7.0 astral==1.10.1 async-upnp-client==0.14.13 async_timeout==3.0.1 -attrs==19.3.0 +attrs==20.3.0 awesomeversion==21.2.3 bcrypt==3.1.7 certifi>=2020.12.5 diff --git a/requirements.txt b/requirements.txt index 0a5b754dbfc..6f6640764a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ aiohttp==3.7.4 astral==1.10.1 async_timeout==3.0.1 -attrs==19.3.0 +attrs==20.3.0 awesomeversion==21.2.3 bcrypt==3.1.7 certifi>=2020.12.5 diff --git a/setup.py b/setup.py index ce7d6b6883d..04a334cfd21 100755 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ REQUIRES = [ "aiohttp==3.7.4", "astral==1.10.1", "async_timeout==3.0.1", - "attrs==19.3.0", + "attrs==20.3.0", "awesomeversion==21.2.3", "bcrypt==3.1.7", "certifi>=2020.12.5", From ee257234689c682fa674528cc0c23c0bebda320b Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 8 Mar 2021 21:56:24 +0000 Subject: [PATCH 249/831] Add option to reverse switch behaviour in KMTronic (#47532) --- homeassistant/components/kmtronic/__init__.py | 13 +++- .../components/kmtronic/config_flow.py | 41 ++++++++++- homeassistant/components/kmtronic/const.py | 5 +- .../components/kmtronic/strings.json | 9 +++ homeassistant/components/kmtronic/switch.py | 32 ++++----- .../components/kmtronic/translations/en.json | 12 +++- tests/components/kmtronic/test_config_flow.py | 70 +++++++++++-------- tests/components/kmtronic/test_init.py | 62 ++++++++++++++++ tests/components/kmtronic/test_switch.py | 67 +++++++++++++----- 9 files changed, 240 insertions(+), 71 deletions(-) create mode 100644 tests/components/kmtronic/test_init.py diff --git a/homeassistant/components/kmtronic/__init__.py b/homeassistant/components/kmtronic/__init__.py index 0ac4ea8cb59..2903eb926e5 100644 --- a/homeassistant/components/kmtronic/__init__.py +++ b/homeassistant/components/kmtronic/__init__.py @@ -15,7 +15,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import aiohttp_client from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import DATA_COORDINATOR, DATA_HOST, DATA_HUB, DOMAIN, MANUFACTURER +from .const import DATA_COORDINATOR, DATA_HUB, DOMAIN, MANUFACTURER, UPDATE_LISTENER CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) @@ -67,7 +67,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN][entry.entry_id] = { DATA_HUB: hub, - DATA_HOST: entry.data[DATA_HOST], DATA_COORDINATOR: coordinator, } @@ -76,9 +75,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.config_entries.async_forward_entry_setup(entry, platform) ) + update_listener = entry.add_update_listener(async_update_options) + hass.data[DOMAIN][entry.entry_id][UPDATE_LISTENER] = update_listener + return True +async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry) -> None: + """Update options.""" + await hass.config_entries.async_reload(config_entry.entry_id) + + async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" unload_ok = all( @@ -90,6 +97,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): ) ) if unload_ok: + update_listener = hass.data[DOMAIN][entry.entry_id][UPDATE_LISTENER] + update_listener() hass.data[DOMAIN].pop(entry.entry_id) return unload_ok diff --git a/homeassistant/components/kmtronic/config_flow.py b/homeassistant/components/kmtronic/config_flow.py index 841c541dd4e..736e6a46c11 100644 --- a/homeassistant/components/kmtronic/config_flow.py +++ b/homeassistant/components/kmtronic/config_flow.py @@ -8,13 +8,21 @@ import voluptuous as vol from homeassistant import config_entries, core, exceptions from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import callback from homeassistant.helpers import aiohttp_client +from .const import CONF_REVERSE from .const import DOMAIN # pylint:disable=unused-import _LOGGER = logging.getLogger(__name__) -DATA_SCHEMA = vol.Schema({CONF_HOST: str, CONF_USERNAME: str, CONF_PASSWORD: str}) +DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_HOST): str, + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + } +) async def validate_input(hass: core.HomeAssistant, data): @@ -44,6 +52,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return KMTronicOptionsFlow(config_entry) + async def async_step_user(self, user_input=None): """Handle the initial step.""" errors = {} @@ -71,3 +85,28 @@ class CannotConnect(exceptions.HomeAssistantError): class InvalidAuth(exceptions.HomeAssistantError): """Error to indicate there is invalid auth.""" + + +class KMTronicOptionsFlow(config_entries.OptionsFlow): + """Handle options.""" + + def __init__(self, config_entry): + """Initialize options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Manage the options.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Optional( + CONF_REVERSE, + default=self.config_entry.options.get(CONF_REVERSE), + ): bool, + } + ), + ) diff --git a/homeassistant/components/kmtronic/const.py b/homeassistant/components/kmtronic/const.py index 8ca37a0b797..8b34d423724 100644 --- a/homeassistant/components/kmtronic/const.py +++ b/homeassistant/components/kmtronic/const.py @@ -2,10 +2,13 @@ DOMAIN = "kmtronic" +CONF_REVERSE = "reverse" + DATA_HUB = "hub" -DATA_HOST = "host" DATA_COORDINATOR = "coordinator" MANUFACTURER = "KMtronic" ATTR_MANUFACTURER = "manufacturer" ATTR_IDENTIFIERS = "identifiers" + +UPDATE_LISTENER = "update_listener" diff --git a/homeassistant/components/kmtronic/strings.json b/homeassistant/components/kmtronic/strings.json index 7becb830d91..2aaa0d2f8dd 100644 --- a/homeassistant/components/kmtronic/strings.json +++ b/homeassistant/components/kmtronic/strings.json @@ -17,5 +17,14 @@ "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "Reverse switch logic (use NC)" + } + } + } } } diff --git a/homeassistant/components/kmtronic/switch.py b/homeassistant/components/kmtronic/switch.py index 5970ec20cb8..d37cd54ce1a 100644 --- a/homeassistant/components/kmtronic/switch.py +++ b/homeassistant/components/kmtronic/switch.py @@ -3,19 +3,19 @@ from homeassistant.components.switch import SwitchEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DATA_COORDINATOR, DATA_HOST, DATA_HUB, DOMAIN +from .const import CONF_REVERSE, DATA_COORDINATOR, DATA_HUB, DOMAIN async def async_setup_entry(hass, entry, async_add_entities): """Config entry example.""" coordinator = hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR] hub = hass.data[DOMAIN][entry.entry_id][DATA_HUB] - host = hass.data[DOMAIN][entry.entry_id][DATA_HOST] + reverse = entry.options.get(CONF_REVERSE, False) await hub.async_get_relays() async_add_entities( [ - KMtronicSwitch(coordinator, host, relay, entry.unique_id) + KMtronicSwitch(coordinator, relay, reverse, entry.entry_id) for relay in hub.relays ] ) @@ -24,17 +24,12 @@ async def async_setup_entry(hass, entry, async_add_entities): class KMtronicSwitch(CoordinatorEntity, SwitchEntity): """KMtronic Switch Entity.""" - def __init__(self, coordinator, host, relay, config_entry_id): + def __init__(self, coordinator, relay, reverse, config_entry_id): """Pass coordinator to CoordinatorEntity.""" super().__init__(coordinator) - self._host = host self._relay = relay self._config_entry_id = config_entry_id - - @property - def available(self) -> bool: - """Return whether the entity is available.""" - return self.coordinator.last_update_success + self._reverse = reverse @property def name(self) -> str: @@ -46,22 +41,25 @@ class KMtronicSwitch(CoordinatorEntity, SwitchEntity): """Return the unique ID of the entity.""" return f"{self._config_entry_id}_relay{self._relay.id}" - @property - def entity_registry_enabled_default(self) -> bool: - """Return if the entity should be enabled when first added to the entity registry.""" - return True - @property def is_on(self): """Return entity state.""" + if self._reverse: + return not self._relay.is_on return self._relay.is_on async def async_turn_on(self, **kwargs) -> None: """Turn the switch on.""" - await self._relay.turn_on() + if self._reverse: + await self._relay.turn_off() + else: + await self._relay.turn_on() self.async_write_ha_state() async def async_turn_off(self, **kwargs) -> None: """Turn the switch off.""" - await self._relay.turn_off() + if self._reverse: + await self._relay.turn_on() + else: + await self._relay.turn_off() self.async_write_ha_state() diff --git a/homeassistant/components/kmtronic/translations/en.json b/homeassistant/components/kmtronic/translations/en.json index f15fe84c3ed..0a1bde9fb19 100644 --- a/homeassistant/components/kmtronic/translations/en.json +++ b/homeassistant/components/kmtronic/translations/en.json @@ -13,7 +13,17 @@ "data": { "host": "Host", "password": "Password", - "username": "Username" + "username": "Username", + "reverse": "Reverse switch logic (use NC)" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "Reverse switch logic (use NC)" } } } diff --git a/tests/components/kmtronic/test_config_flow.py b/tests/components/kmtronic/test_config_flow.py index ebbbf626451..b5ebdc79c8b 100644 --- a/tests/components/kmtronic/test_config_flow.py +++ b/tests/components/kmtronic/test_config_flow.py @@ -3,9 +3,9 @@ from unittest.mock import Mock, patch from aiohttp import ClientConnectorError, ClientResponseError -from homeassistant import config_entries, setup -from homeassistant.components.kmtronic.const import DOMAIN -from homeassistant.config_entries import ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.kmtronic.const import CONF_REVERSE, DOMAIN +from homeassistant.config_entries import ENTRY_STATE_LOADED from tests.common import MockConfigEntry @@ -49,6 +49,43 @@ async def test_form(hass): assert len(mock_setup_entry.mock_calls) == 1 +async def test_form_options(hass, aioclient_mock): + """Test that the options form.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={ + "host": "1.1.1.1", + "username": "admin", + "password": "admin", + }, + ) + config_entry.add_to_hass(hass) + + aioclient_mock.get( + "http://1.1.1.1/status.xml", + text="00", + ) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state == ENTRY_STATE_LOADED + + result = await hass.config_entries.options.async_init(config_entry.entry_id) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={CONF_REVERSE: True} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert config_entry.options == {CONF_REVERSE: True} + + await hass.async_block_till_done() + + assert config_entry.state == "loaded" + + async def test_form_invalid_auth(hass): """Test we handle invalid auth.""" result = await hass.config_entries.flow.async_init( @@ -116,30 +153,3 @@ async def test_form_unknown_error(hass): assert result2["type"] == "form" assert result2["errors"] == {"base": "unknown"} - - -async def test_unload_config_entry(hass, aioclient_mock): - """Test entry unloading.""" - - config_entry = MockConfigEntry( - domain=DOMAIN, - data={"host": "1.1.1.1", "username": "admin", "password": "admin"}, - ) - config_entry.add_to_hass(hass) - - aioclient_mock.get( - "http://1.1.1.1/status.xml", - text="00", - ) - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - config_entries = hass.config_entries.async_entries(DOMAIN) - assert len(config_entries) == 1 - assert config_entries[0] is config_entry - assert config_entry.state == ENTRY_STATE_LOADED - - await hass.config_entries.async_unload(config_entry.entry_id) - await hass.async_block_till_done() - - assert config_entry.state == ENTRY_STATE_NOT_LOADED diff --git a/tests/components/kmtronic/test_init.py b/tests/components/kmtronic/test_init.py new file mode 100644 index 00000000000..1b9cf7cb407 --- /dev/null +++ b/tests/components/kmtronic/test_init.py @@ -0,0 +1,62 @@ +"""The tests for the KMtronic component.""" +import asyncio + +from homeassistant.components.kmtronic.const import DOMAIN +from homeassistant.config_entries import ( + ENTRY_STATE_LOADED, + ENTRY_STATE_NOT_LOADED, + ENTRY_STATE_SETUP_RETRY, +) + +from tests.common import MockConfigEntry + + +async def test_unload_config_entry(hass, aioclient_mock): + """Test entry unloading.""" + + config_entry = MockConfigEntry( + domain=DOMAIN, + data={ + "host": "1.1.1.1", + "username": "admin", + "password": "admin", + }, + ) + config_entry.add_to_hass(hass) + + aioclient_mock.get( + "http://1.1.1.1/status.xml", + text="00", + ) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state == ENTRY_STATE_LOADED + + await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state == ENTRY_STATE_NOT_LOADED + + +async def test_config_entry_not_ready(hass, aioclient_mock): + """Tests configuration entry not ready.""" + + aioclient_mock.get( + "http://1.1.1.1/status.xml", + exc=asyncio.TimeoutError(), + ) + + config_entry = MockConfigEntry( + domain=DOMAIN, + data={ + "host": "1.1.1.1", + "username": "foo", + "password": "bar", + }, + ) + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state == ENTRY_STATE_SETUP_RETRY diff --git a/tests/components/kmtronic/test_switch.py b/tests/components/kmtronic/test_switch.py index 5eec3537176..df8ecda2c2e 100644 --- a/tests/components/kmtronic/test_switch.py +++ b/tests/components/kmtronic/test_switch.py @@ -3,7 +3,6 @@ import asyncio from datetime import timedelta from homeassistant.components.kmtronic.const import DOMAIN -from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY from homeassistant.const import STATE_UNAVAILABLE from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util @@ -88,24 +87,6 @@ async def test_update(hass, aioclient_mock): assert state.state == "on" -async def test_config_entry_not_ready(hass, aioclient_mock): - """Tests configuration entry not ready.""" - - aioclient_mock.get( - "http://1.1.1.1/status.xml", - exc=asyncio.TimeoutError(), - ) - - config_entry = MockConfigEntry( - domain=DOMAIN, data={"host": "1.1.1.1", "username": "foo", "password": "bar"} - ) - config_entry.add_to_hass(hass) - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - assert config_entry.state == ENTRY_STATE_SETUP_RETRY - - async def test_failed_update(hass, aioclient_mock): """Tests coordinator update fails.""" now = dt_util.utcnow() @@ -148,3 +129,51 @@ async def test_failed_update(hass, aioclient_mock): await hass.async_block_till_done() state = hass.states.get("switch.relay1") assert state.state == STATE_UNAVAILABLE + + +async def test_relay_on_off_reversed(hass, aioclient_mock): + """Tests the relay turns on correctly when configured as reverse.""" + + aioclient_mock.get( + "http://1.1.1.1/status.xml", + text="00", + ) + + MockConfigEntry( + domain=DOMAIN, + data={"host": "1.1.1.1", "username": "foo", "password": "bar"}, + options={"reverse": True}, + ).add_to_hass(hass) + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + + # Mocks the response for turning a relay1 off + aioclient_mock.get( + "http://1.1.1.1/FF0101", + text="", + ) + + state = hass.states.get("switch.relay1") + assert state.state == "on" + + await hass.services.async_call( + "switch", "turn_off", {"entity_id": "switch.relay1"}, blocking=True + ) + + await hass.async_block_till_done() + state = hass.states.get("switch.relay1") + assert state.state == "off" + + # Mocks the response for turning a relay1 off + aioclient_mock.get( + "http://1.1.1.1/FF0100", + text="", + ) + + await hass.services.async_call( + "switch", "turn_on", {"entity_id": "switch.relay1"}, blocking=True + ) + + await hass.async_block_till_done() + state = hass.states.get("switch.relay1") + assert state.state == "on" From fbf8b68488817b8a5ad80a78f5a5d372072744a8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 8 Mar 2021 23:13:18 +0100 Subject: [PATCH 250/831] Upgrade sentry-sdk to 1.0.0 (#47626) --- homeassistant/components/sentry/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sentry/manifest.json b/homeassistant/components/sentry/manifest.json index d0592493f15..04735d98687 100644 --- a/homeassistant/components/sentry/manifest.json +++ b/homeassistant/components/sentry/manifest.json @@ -3,6 +3,6 @@ "name": "Sentry", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sentry", - "requirements": ["sentry-sdk==0.20.3"], + "requirements": ["sentry-sdk==1.0.0"], "codeowners": ["@dcramer", "@frenck"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0028e6e343a..e16a101ce80 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2023,7 +2023,7 @@ sense-hat==2.2.0 sense_energy==0.9.0 # homeassistant.components.sentry -sentry-sdk==0.20.3 +sentry-sdk==1.0.0 # homeassistant.components.sharkiq sharkiqpy==0.1.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6ccb6898393..94f1bbba464 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1039,7 +1039,7 @@ scapy==2.4.4 sense_energy==0.9.0 # homeassistant.components.sentry -sentry-sdk==0.20.3 +sentry-sdk==1.0.0 # homeassistant.components.sharkiq sharkiqpy==0.1.8 From efe415f2252e8d53400f87647bb1274f15265502 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 8 Mar 2021 23:18:55 +0100 Subject: [PATCH 251/831] Upgrade aiohttp to 3.7.4.post0 (#47627) --- 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 d9d8618f78e..bcbe614ca20 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -1,6 +1,6 @@ PyJWT==1.7.1 PyNaCl==1.3.0 -aiohttp==3.7.4 +aiohttp==3.7.4.post0 aiohttp_cors==0.7.0 astral==1.10.1 async-upnp-client==0.14.13 diff --git a/requirements.txt b/requirements.txt index 6f6640764a6..dafb5686e4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -c homeassistant/package_constraints.txt # Home Assistant Core -aiohttp==3.7.4 +aiohttp==3.7.4.post0 astral==1.10.1 async_timeout==3.0.1 attrs==20.3.0 diff --git a/setup.py b/setup.py index 04a334cfd21..d3a8a5aea70 100755 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ PROJECT_URLS = { PACKAGES = find_packages(exclude=["tests", "tests.*"]) REQUIRES = [ - "aiohttp==3.7.4", + "aiohttp==3.7.4.post0", "astral==1.10.1", "async_timeout==3.0.1", "attrs==20.3.0", From 6af754a7d3c2e69b6fc3cf4a78cc8fb4b8a5bf1e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 12:19:05 -1000 Subject: [PATCH 252/831] Fix turning off scene in homekit (#47604) --- homeassistant/components/homekit/type_switches.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit/type_switches.py b/homeassistant/components/homekit/type_switches.py index 1ce6c364896..8ea19897420 100644 --- a/homeassistant/components/homekit/type_switches.py +++ b/homeassistant/components/homekit/type_switches.py @@ -27,7 +27,7 @@ from homeassistant.const import ( STATE_ON, ) from homeassistant.core import callback, split_entity_id -from homeassistant.helpers.event import call_later +from homeassistant.helpers.event import async_call_later from .accessories import TYPES, HomeAccessory from .const import ( @@ -134,7 +134,7 @@ class Switch(HomeAccessory): self.async_call_service(self._domain, service, params) if self.activate_only: - call_later(self.hass, 1, self.reset_switch) + async_call_later(self.hass, 1, self.reset_switch) @callback def async_update_state(self, new_state): From d9ffb65898e9f9796d82f02aabb370dae014955c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 12:20:21 -1000 Subject: [PATCH 253/831] Fix insteon fan speeds (#47603) --- homeassistant/components/insteon/fan.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/insteon/fan.py b/homeassistant/components/insteon/fan.py index cef19f57a9f..596c254dc65 100644 --- a/homeassistant/components/insteon/fan.py +++ b/homeassistant/components/insteon/fan.py @@ -1,8 +1,6 @@ """Support for INSTEON fans via PowerLinc Modem.""" import math -from pyinsteon.constants import FanSpeed - from homeassistant.components.fan import ( DOMAIN as FAN_DOMAIN, SUPPORT_SET_SPEED, @@ -19,7 +17,7 @@ from .const import SIGNAL_ADD_ENTITIES from .insteon_entity import InsteonEntity from .utils import async_add_insteon_entities -SPEED_RANGE = (1, FanSpeed.HIGH) # off is not included +SPEED_RANGE = (0x00, 0xFF) # off is not included async def async_setup_entry(hass, config_entry, async_add_entities): @@ -52,6 +50,11 @@ class InsteonFanEntity(InsteonEntity, FanEntity): """Flag supported features.""" return SUPPORT_SET_SPEED + @property + def speed_count(self) -> int: + """Flag supported features.""" + return 3 + async def async_turn_on( self, speed: str = None, @@ -60,9 +63,7 @@ class InsteonFanEntity(InsteonEntity, FanEntity): **kwargs, ) -> None: """Turn on the fan.""" - if percentage is None: - percentage = 50 - await self.async_set_percentage(percentage) + await self.async_set_percentage(percentage or 67) async def async_turn_off(self, **kwargs) -> None: """Turn off the fan.""" @@ -71,7 +72,7 @@ class InsteonFanEntity(InsteonEntity, FanEntity): async def async_set_percentage(self, percentage: int) -> None: """Set the speed percentage of the fan.""" if percentage == 0: - await self._insteon_device.async_fan_off() - else: - on_level = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) - await self._insteon_device.async_fan_on(on_level=on_level) + await self.async_turn_off() + return + on_level = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) + await self._insteon_device.async_on(group=2, on_level=on_level) From 797ee81fc9b8f4d0ef6314df6f45b98b5507f3c4 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 8 Mar 2021 18:11:54 -0500 Subject: [PATCH 254/831] Update zwave_js supported features list to be static (#47623) --- homeassistant/components/zwave_js/climate.py | 22 +++++++++-------- tests/components/zwave_js/test_climate.py | 26 +++++++++++++++++++- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 325cf14b379..26e9e730283 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -162,6 +162,15 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): add_to_watched_value_ids=True, ) self._set_modes_and_presets() + self._supported_features = SUPPORT_PRESET_MODE + # If any setpoint value exists, we can assume temperature + # can be set + if any(self._setpoint_values.values()): + self._supported_features |= SUPPORT_TARGET_TEMPERATURE + if HVAC_MODE_HEAT_COOL in self.hvac_modes: + self._supported_features |= SUPPORT_TARGET_TEMPERATURE_RANGE + if self._fan_mode: + self._supported_features |= SUPPORT_FAN_MODE def _setpoint_value(self, setpoint_type: ThermostatSetpointType) -> ZwaveValue: """Optionally return a ZwaveValue for a setpoint.""" @@ -259,7 +268,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): return None try: temp = self._setpoint_value(self._current_mode_setpoint_enums[0]) - except ValueError: + except (IndexError, ValueError): return None return temp.value if temp else None @@ -271,7 +280,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): return None try: temp = self._setpoint_value(self._current_mode_setpoint_enums[1]) - except ValueError: + except (IndexError, ValueError): return None return temp.value if temp else None @@ -335,14 +344,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): @property def supported_features(self) -> int: """Return the list of supported features.""" - support = SUPPORT_PRESET_MODE - if len(self._current_mode_setpoint_enums) == 1: - support |= SUPPORT_TARGET_TEMPERATURE - if len(self._current_mode_setpoint_enums) > 1: - support |= SUPPORT_TARGET_TEMPERATURE_RANGE - if self._fan_mode: - support |= SUPPORT_FAN_MODE - return support + return self._supported_features async def async_set_fan_mode(self, fan_mode: str) -> None: """Set new target fan mode.""" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index fe3e0708acc..a31aad19603 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -24,9 +24,17 @@ from homeassistant.components.climate.const import ( SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, SERVICE_SET_TEMPERATURE, + SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_RANGE, ) from homeassistant.components.zwave_js.climate import ATTR_FAN_STATE -from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + ATTR_TEMPERATURE, +) from .common import ( CLIMATE_DANFOSS_LC13_ENTITY, @@ -58,6 +66,13 @@ async def test_thermostat_v2( assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE assert state.attributes[ATTR_FAN_MODE] == "Auto low" assert state.attributes[ATTR_FAN_STATE] == "Idle / off" + assert ( + state.attributes[ATTR_SUPPORTED_FEATURES] + == SUPPORT_PRESET_MODE + | SUPPORT_TARGET_TEMPERATURE + | SUPPORT_TARGET_TEMPERATURE_RANGE + | SUPPORT_FAN_MODE + ) # Test setting preset mode await hass.services.async_call( @@ -408,6 +423,10 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat assert state.attributes[ATTR_TEMPERATURE] == 14 assert state.attributes[ATTR_HVAC_MODES] == [HVAC_MODE_HEAT] assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + assert ( + state.attributes[ATTR_SUPPORTED_FEATURES] + == SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE + ) client.async_send_command_no_wait.reset_mock() @@ -491,6 +510,10 @@ async def test_thermostat_heatit(hass, client, climate_heatit_z_trm3, integratio assert state.attributes[ATTR_TEMPERATURE] == 22.5 assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + assert ( + state.attributes[ATTR_SUPPORTED_FEATURES] + == SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE + ) async def test_thermostat_srt321_hrt4_zw(hass, client, srt321_hrt4_zw, integration): @@ -507,3 +530,4 @@ async def test_thermostat_srt321_hrt4_zw(hass, client, srt321_hrt4_zw, integrati HVAC_MODE_HEAT, ] assert state.attributes[ATTR_CURRENT_TEMPERATURE] is None + assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_PRESET_MODE From 10eca5b98684508d19abac54f19ca1325ba21c33 Mon Sep 17 00:00:00 2001 From: unaiur Date: Tue, 9 Mar 2021 00:14:24 +0100 Subject: [PATCH 255/831] Fix maxcube thermostat transition from off to heat mode (#47643) Transition from HVAC_MODE_OFF to HVAC_MODE_HEAT are not executed because target temperature is kept at OFF_TEMPERATURE, turning it into a no-op. This change ensures that we increase the target temperature to at least the minimum temperature when transitioning to HVAC_MODE_HEAT mode. --- homeassistant/components/maxcube/climate.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/maxcube/climate.py b/homeassistant/components/maxcube/climate.py index c17cc988c1d..5db4cc1e7bd 100644 --- a/homeassistant/components/maxcube/climate.py +++ b/homeassistant/components/maxcube/climate.py @@ -107,7 +107,9 @@ class MaxCubeClimate(ClimateEntity): device = self._cubehandle.cube.device_by_rf(self._rf_address) if device.min_temperature is None: return MIN_TEMPERATURE - return device.min_temperature + # OFF_TEMPERATURE (always off) a is valid temperature to maxcube but not to Home Assistant. + # We use HVAC_MODE_OFF instead to represent a turned off thermostat. + return max(device.min_temperature, MIN_TEMPERATURE) @property def max_temp(self): @@ -155,7 +157,9 @@ class MaxCubeClimate(ClimateEntity): if hvac_mode == HVAC_MODE_OFF: temp = OFF_TEMPERATURE - elif hvac_mode != HVAC_MODE_HEAT: + elif hvac_mode == HVAC_MODE_HEAT: + temp = max(temp, self.min_temp) + else: # Reset the temperature to a sane value. # Ideally, we should send 0 and the device will set its # temperature according to the schedule. However, current From 1dd35ff059ecd9cfa73591c104f43bfbc6451fad Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 13:15:22 -1000 Subject: [PATCH 256/831] Catch dhcp setup permission errors sooner (#47639) This solves an unexpected thread exception on macs when running as a user intead of root --- homeassistant/components/dhcp/__init__.py | 18 ++++++++++-------- tests/components/dhcp/test_init.py | 10 ++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index d33c6159888..304eea24fd4 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -207,8 +207,11 @@ class DHCPWatcher(WatcherBase): async def async_start(self): """Start watching for dhcp packets.""" + # disable scapy promiscuous mode as we do not need it + conf.sniff_promisc = 0 + try: - _verify_l2socket_creation_permission() + await self.hass.async_add_executor_job(_verify_l2socket_setup, FILTER) except (Scapy_Exception, OSError) as ex: if os.geteuid() == 0: _LOGGER.error("Cannot watch for dhcp packets: %s", ex) @@ -219,7 +222,7 @@ class DHCPWatcher(WatcherBase): return try: - await _async_verify_working_pcap(self.hass, FILTER) + await self.hass.async_add_executor_job(_verify_working_pcap, FILTER) except (Scapy_Exception, ImportError) as ex: _LOGGER.error( "Cannot watch for dhcp packets without a functional packet filter: %s", @@ -233,6 +236,7 @@ class DHCPWatcher(WatcherBase): prn=self.handle_dhcp_packet, store=0, ) + self._sniffer.start() def handle_dhcp_packet(self, packet): @@ -283,7 +287,7 @@ def _format_mac(mac_address): return format_mac(mac_address).replace(":", "") -def _verify_l2socket_creation_permission(): +def _verify_l2socket_setup(cap_filter): """Create a socket using the scapy configured l2socket. Try to create the socket @@ -292,15 +296,13 @@ def _verify_l2socket_creation_permission(): thread so we will not be able to capture any permission or bind errors. """ - # disable scapy promiscuous mode as we do not need it - conf.sniff_promisc = 0 - conf.L2socket() + conf.L2socket(filter=cap_filter) -async def _async_verify_working_pcap(hass, cap_filter): +def _verify_working_pcap(cap_filter): """Verify we can create a packet filter. If we cannot create a filter we will be listening for all traffic which is too intensive. """ - await hass.async_add_executor_job(compile_filter, cap_filter) + compile_filter(cap_filter) diff --git a/tests/components/dhcp/test_init.py b/tests/components/dhcp/test_init.py index fc24c8201e2..f5cc5f1728d 100644 --- a/tests/components/dhcp/test_init.py +++ b/tests/components/dhcp/test_init.py @@ -281,7 +281,7 @@ async def test_setup_and_stop(hass): await hass.async_block_till_done() with patch("homeassistant.components.dhcp.AsyncSniffer.start") as start_call, patch( - "homeassistant.components.dhcp._verify_l2socket_creation_permission", + "homeassistant.components.dhcp._verify_l2socket_setup", ), patch( "homeassistant.components.dhcp.compile_filter", ): @@ -307,7 +307,7 @@ async def test_setup_fails_as_root(hass, caplog): wait_event = threading.Event() with patch("os.geteuid", return_value=0), patch( - "homeassistant.components.dhcp._verify_l2socket_creation_permission", + "homeassistant.components.dhcp._verify_l2socket_setup", side_effect=Scapy_Exception, ): hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -330,7 +330,7 @@ async def test_setup_fails_non_root(hass, caplog): await hass.async_block_till_done() with patch("os.geteuid", return_value=10), patch( - "homeassistant.components.dhcp._verify_l2socket_creation_permission", + "homeassistant.components.dhcp._verify_l2socket_setup", side_effect=Scapy_Exception, ): hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -351,9 +351,7 @@ async def test_setup_fails_with_broken_libpcap(hass, caplog): ) await hass.async_block_till_done() - with patch( - "homeassistant.components.dhcp._verify_l2socket_creation_permission", - ), patch( + with patch("homeassistant.components.dhcp._verify_l2socket_setup",), patch( "homeassistant.components.dhcp.compile_filter", side_effect=ImportError, ) as compile_filter, patch( From 5d7b53603fdd3d6776c801bb9d5f7b22507b1146 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 13:44:55 -1000 Subject: [PATCH 257/831] Harmony: set confirm only (#47617) --- homeassistant/components/harmony/config_flow.py | 1 + tests/components/harmony/test_config_flow.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/homeassistant/components/harmony/config_flow.py b/homeassistant/components/harmony/config_flow.py index 899edeb8a91..a91c1f3b5ca 100644 --- a/homeassistant/components/harmony/config_flow.py +++ b/homeassistant/components/harmony/config_flow.py @@ -119,6 +119,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self.harmony_config, {} ) + self._set_confirm_only() return self.async_show_form( step_id="link", errors=errors, diff --git a/tests/components/harmony/test_config_flow.py b/tests/components/harmony/test_config_flow.py index 2a7f80d5c2f..7f651890868 100644 --- a/tests/components/harmony/test_config_flow.py +++ b/tests/components/harmony/test_config_flow.py @@ -74,6 +74,10 @@ async def test_form_ssdp(hass): "host": "Harmony Hub", "name": "192.168.1.12", } + progress = hass.config_entries.flow.async_progress() + assert len(progress) == 1 + assert progress[0]["flow_id"] == result["flow_id"] + assert progress[0]["context"]["confirm_only"] is True with patch( "homeassistant.components.harmony.util.HarmonyAPI", From 8e58c3aa7bc8d2c2b09b6bd329daa1c092d52d3c Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Tue, 9 Mar 2021 01:12:52 +0100 Subject: [PATCH 258/831] Add error message to options flow if connection fails for nut integration (#46972) --- homeassistant/components/nut/config_flow.py | 24 +++++++++++++++---- homeassistant/components/nut/strings.json | 4 ++++ .../components/nut/translations/en.json | 4 ++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/nut/config_flow.py b/homeassistant/components/nut/config_flow.py index 7407958cdc0..b6f56192d80 100644 --- a/homeassistant/components/nut/config_flow.py +++ b/homeassistant/components/nut/config_flow.py @@ -6,6 +6,7 @@ import voluptuous as vol from homeassistant import config_entries, core, exceptions from homeassistant.const import ( CONF_ALIAS, + CONF_BASE, CONF_HOST, CONF_PASSWORD, CONF_PORT, @@ -211,10 +212,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): try: info = await validate_input(self.hass, config) except CannotConnect: - errors["base"] = "cannot_connect" + errors[CONF_BASE] = "cannot_connect" except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" + errors[CONF_BASE] = "unknown" return info, errors @staticmethod @@ -241,7 +242,17 @@ class OptionsFlowHandler(config_entries.OptionsFlow): CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL ) - info = await validate_input(self.hass, self.config_entry.data) + errors = {} + try: + info = await validate_input(self.hass, self.config_entry.data) + except CannotConnect: + errors[CONF_BASE] = "cannot_connect" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors[CONF_BASE] = "unknown" + + if errors: + return self.async_show_form(step_id="abort", errors=errors) base_schema = _resource_schema_base(info["available_resources"], resources) base_schema[ @@ -249,10 +260,13 @@ class OptionsFlowHandler(config_entries.OptionsFlow): ] = cv.positive_int return self.async_show_form( - step_id="init", - data_schema=vol.Schema(base_schema), + step_id="init", data_schema=vol.Schema(base_schema), errors=errors ) + async def async_step_abort(self, user_input=None): + """Abort options flow.""" + return self.async_create_entry(title="", data=self.config_entry.options) + class CannotConnect(exceptions.HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/nut/strings.json b/homeassistant/components/nut/strings.json index 1b71280b6a9..97e637fdcb3 100644 --- a/homeassistant/components/nut/strings.json +++ b/homeassistant/components/nut/strings.json @@ -41,6 +41,10 @@ "scan_interval": "Scan Interval (seconds)" } } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" } } } diff --git a/homeassistant/components/nut/translations/en.json b/homeassistant/components/nut/translations/en.json index 2e5db79d81c..3d57189f7a5 100644 --- a/homeassistant/components/nut/translations/en.json +++ b/homeassistant/components/nut/translations/en.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "Failed to connect", + "unknown": "Unexpected error" + }, "step": { "init": { "data": { From 34b9e6f6fce982ecb2f9bf4b95d1318ed7142809 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 9 Mar 2021 04:13:43 -0800 Subject: [PATCH 259/831] Shelly: set confirm only (#47608) --- .../components/shelly/config_flow.py | 54 ++++------- tests/components/shelly/test_config_flow.py | 95 +++---------------- 2 files changed, 31 insertions(+), 118 deletions(-) diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index dfb078ee9c7..0ad95d67833 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -160,52 +160,34 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._abort_if_unique_id_configured({CONF_HOST: zeroconf_info["host"]}) self.host = zeroconf_info["host"] - if not info["auth"] and info.get("sleep_mode", False): - try: - self.device_info = await validate_input(self.hass, self.host, {}) - except HTTP_CONNECT_ERRORS: - return self.async_abort(reason="cannot_connect") - self.context["title_placeholders"] = { "name": zeroconf_info.get("name", "").split(".")[0] } + + if info["auth"]: + return await self.async_step_credentials() + + try: + self.device_info = await validate_input(self.hass, self.host, {}) + except HTTP_CONNECT_ERRORS: + return self.async_abort(reason="cannot_connect") + return await self.async_step_confirm_discovery() async def async_step_confirm_discovery(self, user_input=None): """Handle discovery confirm.""" errors = {} if user_input is not None: - if self.info["auth"]: - return await self.async_step_credentials() + return self.async_create_entry( + title=self.device_info["title"] or self.device_info["hostname"], + data={ + "host": self.host, + "sleep_period": self.device_info["sleep_period"], + "model": self.device_info["model"], + }, + ) - if self.device_info: - return self.async_create_entry( - title=self.device_info["title"] or self.device_info["hostname"], - data={ - "host": self.host, - "sleep_period": self.device_info["sleep_period"], - "model": self.device_info["model"], - }, - ) - - try: - device_info = await validate_input(self.hass, self.host, {}) - except HTTP_CONNECT_ERRORS: - errors["base"] = "cannot_connect" - except aioshelly.AuthRequired: - return await self.async_step_credentials() - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" - else: - return self.async_create_entry( - title=device_info["title"] or device_info["hostname"], - data={ - "host": self.host, - "sleep_period": device_info["sleep_period"], - "model": device_info["model"], - }, - ) + self._set_confirm_only() return self.async_show_form( step_id="confirm_discovery", diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index 9dfbc19255b..463c9111a60 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -338,6 +338,13 @@ async def test_zeroconf(hass): with patch( "aioshelly.get_info", return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, + ), patch( + "aioshelly.Device.create", + new=AsyncMock( + return_value=Mock( + settings=MOCK_SETTINGS, + ) + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -352,14 +359,8 @@ async def test_zeroconf(hass): if flow["flow_id"] == result["flow_id"] ) assert context["title_placeholders"]["name"] == "shelly1pm-12345" + assert context["confirm_only"] is True with patch( - "aioshelly.Device.create", - new=AsyncMock( - return_value=Mock( - settings=MOCK_SETTINGS, - ) - ), - ), patch( "homeassistant.components.shelly.async_setup", return_value=True ) as mock_setup, patch( "homeassistant.components.shelly.async_setup_entry", @@ -479,69 +480,6 @@ async def test_zeroconf_sleeping_device_error(hass, error): assert result["reason"] == "cannot_connect" -@pytest.mark.parametrize( - "error", [(asyncio.TimeoutError, "cannot_connect"), (ValueError, "unknown")] -) -async def test_zeroconf_confirm_error(hass, error): - """Test we get the form.""" - exc, base_error = error - await setup.async_setup_component(hass, "persistent_notification", {}) - - with patch( - "aioshelly.get_info", - return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - data=DISCOVERY_INFO, - context={"source": config_entries.SOURCE_ZEROCONF}, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {} - - with patch( - "aioshelly.Device.create", - new=AsyncMock(side_effect=exc), - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {}, - ) - - assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result2["errors"] == {"base": base_error} - - -async def test_zeroconf_confirm_auth_error(hass): - """Test we get credentials form after an auth error when confirming discovery.""" - await setup.async_setup_component(hass, "persistent_notification", {}) - - with patch( - "aioshelly.get_info", - return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - data=DISCOVERY_INFO, - context={"source": config_entries.SOURCE_ZEROCONF}, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {} - - with patch( - "aioshelly.Device.create", - new=AsyncMock(side_effect=aioshelly.AuthRequired), - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {}, - ) - - assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result2["step_id"] == "credentials" - assert result2["errors"] == {} - - async def test_zeroconf_already_configured(hass): """Test we get the form.""" await setup.async_setup_component(hass, "persistent_notification", {}) @@ -607,13 +545,6 @@ async def test_zeroconf_require_auth(hass): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {} - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {}, - ) - assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result2["errors"] == {} - with patch( "aioshelly.Device.create", new=AsyncMock( @@ -627,15 +558,15 @@ async def test_zeroconf_require_auth(hass): "homeassistant.components.shelly.async_setup_entry", return_value=True, ) as mock_setup_entry: - result3 = await hass.config_entries.flow.async_configure( - result2["flow_id"], + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {"username": "test username", "password": "test password"}, ) await hass.async_block_till_done() - assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result3["title"] == "Test name" - assert result3["data"] == { + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "Test name" + assert result2["data"] == { "host": "1.1.1.1", "model": "SHSW-1", "sleep_period": 0, From 19f67335ec94093ee2bd2c6f83e9d974662cd406 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Tue, 9 Mar 2021 14:45:58 +0200 Subject: [PATCH 260/831] Revert Shelly temperature sensor name change (#47664) --- homeassistant/components/shelly/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index 472f3be4dae..10237629223 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -133,7 +133,7 @@ SENSORS = { available=lambda block: block.sensorOp == "normal", ), ("sensor", "extTemp"): BlockAttributeDescription( - name="External Temperature", + name="Temperature", unit=temperature_unit, value=lambda value: round(value, 1), device_class=sensor.DEVICE_CLASS_TEMPERATURE, From 3a054c3be7a61a253831bc5d53ca8014f5762edb Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 9 Mar 2021 13:58:43 +0100 Subject: [PATCH 261/831] Replace Entity.device_state_attributes with Entity.extra_state_attributes (#47304) --- .../components/huawei_lte/binary_sensor.py | 4 +--- homeassistant/helpers/entity.py | 22 +++++++++++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/huawei_lte/binary_sensor.py b/homeassistant/components/huawei_lte/binary_sensor.py index 9a5f148d138..ea9d68bc843 100644 --- a/homeassistant/components/huawei_lte/binary_sensor.py +++ b/homeassistant/components/huawei_lte/binary_sensor.py @@ -144,10 +144,8 @@ class HuaweiLteMobileConnectionBinarySensor(HuaweiLteBaseBinarySensor): @property def device_state_attributes(self) -> Optional[Dict[str, Any]]: """Get additional attributes related to connection status.""" - attributes = super().device_state_attributes + attributes = {} if self._raw_state in CONNECTION_STATE_ATTRIBUTES: - if attributes is None: - attributes = {} attributes["additional_state"] = CONNECTION_STATE_ATTRIBUTES[ self._raw_state ] diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 7d0e38ab119..7afe1b2ad25 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -160,14 +160,23 @@ class Entity(ABC): def state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes. - Implemented by component base class. Convention for attribute names - is lowercase snake_case. + Implemented by component base class, should not be extended by integrations. + Convention for attribute names is lowercase snake_case. """ return None @property def device_state_attributes(self) -> Optional[Dict[str, Any]]: - """Return device specific state attributes. + """Return entity specific state attributes. + + This method is deprecated, platform classes should implement + extra_state_attributes instead. + """ + return None + + @property + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + """Return entity specific state attributes. Implemented by platform classes. Convention for attribute names is lowercase snake_case. @@ -319,7 +328,12 @@ class Entity(ABC): sstate = self.state state = STATE_UNKNOWN if sstate is None else str(sstate) attr.update(self.state_attributes or {}) - attr.update(self.device_state_attributes or {}) + extra_state_attributes = self.extra_state_attributes + # Backwards compatibility for "device_state_attributes" deprecated in 2021.4 + # Add warning in 2021.6, remove in 2021.10 + if extra_state_attributes is None: + extra_state_attributes = self.device_state_attributes + attr.update(extra_state_attributes or {}) unit_of_measurement = self.unit_of_measurement if unit_of_measurement is not None: From 78b21b1ad1835d26bac8cb0955c5e41031e013fb Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 9 Mar 2021 14:24:34 +0100 Subject: [PATCH 262/831] Update tests a-b to use async_get() instead of async_get_registry() (#47651) --- .../components/abode/test_alarm_control_panel.py | 3 ++- tests/components/abode/test_binary_sensor.py | 3 ++- tests/components/abode/test_camera.py | 3 ++- tests/components/abode/test_cover.py | 3 ++- tests/components/abode/test_light.py | 3 ++- tests/components/abode/test_lock.py | 3 ++- tests/components/abode/test_sensor.py | 3 ++- tests/components/abode/test_switch.py | 3 ++- tests/components/accuweather/test_sensor.py | 9 +++++---- tests/components/accuweather/test_weather.py | 5 +++-- .../advantage_air/test_binary_sensor.py | 3 ++- tests/components/advantage_air/test_climate.py | 3 ++- tests/components/advantage_air/test_cover.py | 3 ++- tests/components/advantage_air/test_sensor.py | 3 ++- tests/components/advantage_air/test_switch.py | 4 ++-- tests/components/airly/test_air_quality.py | 3 ++- tests/components/airly/test_sensor.py | 3 ++- tests/components/asuswrt/test_sensor.py | 3 ++- tests/components/atag/test_climate.py | 4 ++-- tests/components/atag/test_sensors.py | 4 ++-- tests/components/atag/test_water_heater.py | 4 ++-- tests/components/august/test_binary_sensor.py | 4 ++-- tests/components/august/test_lock.py | 6 +++--- tests/components/august/test_sensor.py | 16 ++++++++-------- tests/components/awair/test_sensor.py | 14 +++++++------- tests/components/axis/test_init.py | 4 ++-- tests/components/blebox/conftest.py | 3 ++- tests/components/blebox/test_air_quality.py | 4 ++-- tests/components/blebox/test_climate.py | 4 ++-- tests/components/blebox/test_cover.py | 8 ++++---- tests/components/blebox/test_light.py | 8 ++++---- tests/components/blebox/test_sensor.py | 4 ++-- tests/components/blebox/test_switch.py | 8 ++++---- tests/components/bond/test_cover.py | 3 ++- tests/components/bond/test_fan.py | 3 ++- tests/components/bond/test_init.py | 8 ++++---- tests/components/bond/test_light.py | 13 +++++++------ tests/components/bond/test_switch.py | 3 ++- tests/components/brother/test_sensor.py | 5 +++-- tests/test_config_entries.py | 3 ++- 40 files changed, 110 insertions(+), 86 deletions(-) diff --git a/tests/components/abode/test_alarm_control_panel.py b/tests/components/abode/test_alarm_control_panel.py index 63ae20441f5..55ff22b9ee3 100644 --- a/tests/components/abode/test_alarm_control_panel.py +++ b/tests/components/abode/test_alarm_control_panel.py @@ -16,6 +16,7 @@ from homeassistant.const import ( STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, ) +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -25,7 +26,7 @@ DEVICE_ID = "alarm_control_panel.abode_alarm" async def test_entity_registry(hass): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, ALARM_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get(DEVICE_ID) # Abode alarm device unique_id is the MAC address diff --git a/tests/components/abode/test_binary_sensor.py b/tests/components/abode/test_binary_sensor.py index a826191ccf3..e4aa08c7f5f 100644 --- a/tests/components/abode/test_binary_sensor.py +++ b/tests/components/abode/test_binary_sensor.py @@ -11,6 +11,7 @@ from homeassistant.const import ( ATTR_FRIENDLY_NAME, STATE_OFF, ) +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -18,7 +19,7 @@ from .common import setup_platform async def test_entity_registry(hass): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, BINARY_SENSOR_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("binary_sensor.front_door") assert entry.unique_id == "2834013428b6035fba7d4054aa7b25a3" diff --git a/tests/components/abode/test_camera.py b/tests/components/abode/test_camera.py index 06540955464..7dc943a0a74 100644 --- a/tests/components/abode/test_camera.py +++ b/tests/components/abode/test_camera.py @@ -4,6 +4,7 @@ from unittest.mock import patch from homeassistant.components.abode.const import DOMAIN as ABODE_DOMAIN from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN from homeassistant.const import ATTR_ENTITY_ID, STATE_IDLE +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -11,7 +12,7 @@ from .common import setup_platform async def test_entity_registry(hass): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, CAMERA_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("camera.test_cam") assert entry.unique_id == "d0a3a1c316891ceb00c20118aae2a133" diff --git a/tests/components/abode/test_cover.py b/tests/components/abode/test_cover.py index bb1b8fceffb..edd40a86707 100644 --- a/tests/components/abode/test_cover.py +++ b/tests/components/abode/test_cover.py @@ -10,6 +10,7 @@ from homeassistant.const import ( SERVICE_OPEN_COVER, STATE_CLOSED, ) +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -19,7 +20,7 @@ DEVICE_ID = "cover.garage_door" async def test_entity_registry(hass): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, COVER_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get(DEVICE_ID) assert entry.unique_id == "61cbz3b542d2o33ed2fz02721bda3324" diff --git a/tests/components/abode/test_light.py b/tests/components/abode/test_light.py index f0eee4b209b..b5160aece2a 100644 --- a/tests/components/abode/test_light.py +++ b/tests/components/abode/test_light.py @@ -16,6 +16,7 @@ from homeassistant.const import ( SERVICE_TURN_ON, STATE_ON, ) +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -25,7 +26,7 @@ DEVICE_ID = "light.living_room_lamp" async def test_entity_registry(hass): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, LIGHT_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get(DEVICE_ID) assert entry.unique_id == "741385f4388b2637df4c6b398fe50581" diff --git a/tests/components/abode/test_lock.py b/tests/components/abode/test_lock.py index 45e17861d33..c688b6f02bc 100644 --- a/tests/components/abode/test_lock.py +++ b/tests/components/abode/test_lock.py @@ -10,6 +10,7 @@ from homeassistant.const import ( SERVICE_UNLOCK, STATE_LOCKED, ) +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -19,7 +20,7 @@ DEVICE_ID = "lock.test_lock" async def test_entity_registry(hass): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, LOCK_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get(DEVICE_ID) assert entry.unique_id == "51cab3b545d2o34ed7fz02731bda5324" diff --git a/tests/components/abode/test_sensor.py b/tests/components/abode/test_sensor.py index d99fac50dde..5e3195430ab 100644 --- a/tests/components/abode/test_sensor.py +++ b/tests/components/abode/test_sensor.py @@ -9,6 +9,7 @@ from homeassistant.const import ( PERCENTAGE, TEMP_CELSIUS, ) +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -16,7 +17,7 @@ from .common import setup_platform async def test_entity_registry(hass): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, SENSOR_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("sensor.environment_sensor_humidity") assert entry.unique_id == "13545b21f4bdcd33d9abd461f8443e65-humidity" diff --git a/tests/components/abode/test_switch.py b/tests/components/abode/test_switch.py index 3ec9648d87d..829c5e8ae37 100644 --- a/tests/components/abode/test_switch.py +++ b/tests/components/abode/test_switch.py @@ -13,6 +13,7 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, ) +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -25,7 +26,7 @@ DEVICE_UID = "0012a4d3614cb7e2b8c9abea31d2fb2a" async def test_entity_registry(hass): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, SWITCH_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get(AUTOMATION_ID) assert entry.unique_id == AUTOMATION_UID diff --git a/tests/components/accuweather/test_sensor.py b/tests/components/accuweather/test_sensor.py index 361422883d4..bafad72ec0b 100644 --- a/tests/components/accuweather/test_sensor.py +++ b/tests/components/accuweather/test_sensor.py @@ -22,6 +22,7 @@ from homeassistant.const import ( TIME_HOURS, UV_INDEX, ) +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -32,7 +33,7 @@ from tests.components.accuweather import init_integration async def test_sensor_without_forecast(hass): """Test states of the sensor without forecast.""" await init_integration(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("sensor.home_cloud_ceiling") assert state @@ -94,7 +95,7 @@ async def test_sensor_without_forecast(hass): async def test_sensor_with_forecast(hass): """Test states of the sensor with forecast.""" await init_integration(hass, forecast=True) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("sensor.home_hours_of_sun_0d") assert state @@ -166,7 +167,7 @@ async def test_sensor_with_forecast(hass): async def test_sensor_disabled(hass): """Test sensor disabled by default.""" await init_integration(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.home_apparent_temperature") assert entry @@ -185,7 +186,7 @@ async def test_sensor_disabled(hass): async def test_sensor_enabled_without_forecast(hass): """Test enabling an advanced sensor.""" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) registry.async_get_or_create( SENSOR_DOMAIN, diff --git a/tests/components/accuweather/test_weather.py b/tests/components/accuweather/test_weather.py index 0c1559ef0d6..8190d96e634 100644 --- a/tests/components/accuweather/test_weather.py +++ b/tests/components/accuweather/test_weather.py @@ -23,6 +23,7 @@ from homeassistant.components.weather import ( ATTR_WEATHER_WIND_SPEED, ) from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ENTITY_ID, STATE_UNAVAILABLE +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -33,7 +34,7 @@ from tests.components.accuweather import init_integration async def test_weather_without_forecast(hass): """Test states of the weather without forecast.""" await init_integration(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("weather.home") assert state @@ -56,7 +57,7 @@ async def test_weather_without_forecast(hass): async def test_weather_with_forecast(hass): """Test states of the weather with forecast.""" await init_integration(hass, forecast=True) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("weather.home") assert state diff --git a/tests/components/advantage_air/test_binary_sensor.py b/tests/components/advantage_air/test_binary_sensor.py index d0b1a90aaad..dee4b9fd99a 100644 --- a/tests/components/advantage_air/test_binary_sensor.py +++ b/tests/components/advantage_air/test_binary_sensor.py @@ -1,6 +1,7 @@ """Test the Advantage Air Binary Sensor Platform.""" from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.helpers import entity_registry as er from tests.components.advantage_air import ( TEST_SET_RESPONSE, @@ -24,7 +25,7 @@ async def test_binary_sensor_async_setup_entry(hass, aioclient_mock): ) await add_mock_config(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert len(aioclient_mock.mock_calls) == 1 diff --git a/tests/components/advantage_air/test_climate.py b/tests/components/advantage_air/test_climate.py index 7eb1729a5db..ea0cf025462 100644 --- a/tests/components/advantage_air/test_climate.py +++ b/tests/components/advantage_air/test_climate.py @@ -22,6 +22,7 @@ from homeassistant.components.climate.const import ( SERVICE_SET_TEMPERATURE, ) from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE +from homeassistant.helpers import entity_registry as er from tests.components.advantage_air import ( TEST_SET_RESPONSE, @@ -45,7 +46,7 @@ async def test_climate_async_setup_entry(hass, aioclient_mock): ) await add_mock_config(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert len(aioclient_mock.mock_calls) == 1 diff --git a/tests/components/advantage_air/test_cover.py b/tests/components/advantage_air/test_cover.py index 18fd4f05b5a..29f0d288fdb 100644 --- a/tests/components/advantage_air/test_cover.py +++ b/tests/components/advantage_air/test_cover.py @@ -15,6 +15,7 @@ from homeassistant.components.cover import ( SERVICE_SET_COVER_POSITION, ) from homeassistant.const import ATTR_ENTITY_ID, STATE_OPEN +from homeassistant.helpers import entity_registry as er from tests.components.advantage_air import ( TEST_SET_RESPONSE, @@ -39,7 +40,7 @@ async def test_cover_async_setup_entry(hass, aioclient_mock): await add_mock_config(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert len(aioclient_mock.mock_calls) == 1 diff --git a/tests/components/advantage_air/test_sensor.py b/tests/components/advantage_air/test_sensor.py index e420ab978fd..684b965d94f 100644 --- a/tests/components/advantage_air/test_sensor.py +++ b/tests/components/advantage_air/test_sensor.py @@ -8,6 +8,7 @@ from homeassistant.components.advantage_air.sensor import ( ADVANTAGE_AIR_SET_COUNTDOWN_VALUE, ) from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.helpers import entity_registry as er from tests.components.advantage_air import ( TEST_SET_RESPONSE, @@ -31,7 +32,7 @@ async def test_sensor_platform(hass, aioclient_mock): ) await add_mock_config(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert len(aioclient_mock.mock_calls) == 1 diff --git a/tests/components/advantage_air/test_switch.py b/tests/components/advantage_air/test_switch.py index f45477adc70..1a78025df70 100644 --- a/tests/components/advantage_air/test_switch.py +++ b/tests/components/advantage_air/test_switch.py @@ -1,5 +1,4 @@ """Test the Advantage Air Switch Platform.""" - from json import loads from homeassistant.components.advantage_air.const import ( @@ -12,6 +11,7 @@ from homeassistant.components.switch import ( SERVICE_TURN_ON, ) from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF +from homeassistant.helpers import entity_registry as er from tests.components.advantage_air import ( TEST_SET_RESPONSE, @@ -36,7 +36,7 @@ async def test_cover_async_setup_entry(hass, aioclient_mock): await add_mock_config(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert len(aioclient_mock.mock_calls) == 1 diff --git a/tests/components/airly/test_air_quality.py b/tests/components/airly/test_air_quality.py index 24a98cbf155..de059e84aa4 100644 --- a/tests/components/airly/test_air_quality.py +++ b/tests/components/airly/test_air_quality.py @@ -23,6 +23,7 @@ from homeassistant.const import ( HTTP_INTERNAL_SERVER_ERROR, STATE_UNAVAILABLE, ) +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -35,7 +36,7 @@ from tests.components.airly import init_integration async def test_air_quality(hass, aioclient_mock): """Test states of the air_quality.""" await init_integration(hass, aioclient_mock) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("air_quality.home") assert state diff --git a/tests/components/airly/test_sensor.py b/tests/components/airly/test_sensor.py index abc53294bbc..925f3acb6d2 100644 --- a/tests/components/airly/test_sensor.py +++ b/tests/components/airly/test_sensor.py @@ -17,6 +17,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, TEMP_CELSIUS, ) +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -29,7 +30,7 @@ from tests.components.airly import init_integration async def test_sensor(hass, aioclient_mock): """Test states of the sensor.""" await init_integration(hass, aioclient_mock) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("sensor.home_humidity") assert state diff --git a/tests/components/asuswrt/test_sensor.py b/tests/components/asuswrt/test_sensor.py index 4a8a1f27653..e69af0cd322 100644 --- a/tests/components/asuswrt/test_sensor.py +++ b/tests/components/asuswrt/test_sensor.py @@ -19,6 +19,7 @@ from homeassistant.const import ( STATE_HOME, STATE_NOT_HOME, ) +from homeassistant.helpers import entity_registry as er from homeassistant.util.dt import utcnow from tests.common import MockConfigEntry, async_fire_time_changed @@ -64,7 +65,7 @@ def mock_controller_connect(): async def test_sensors(hass, connect): """Test creating an AsusWRT sensor.""" - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) # init config entry config_entry = MockConfigEntry( diff --git a/tests/components/atag/test_climate.py b/tests/components/atag/test_climate.py index 3d511821baf..e263ade7c75 100644 --- a/tests/components/atag/test_climate.py +++ b/tests/components/atag/test_climate.py @@ -1,5 +1,4 @@ """Tests for the Atag climate platform.""" - from unittest.mock import PropertyMock, patch from homeassistant.components.atag import CLIMATE, DOMAIN @@ -19,6 +18,7 @@ from homeassistant.components.homeassistant import ( ) from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_UNKNOWN from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from tests.components.atag import UID, init_integration @@ -33,7 +33,7 @@ async def test_climate( """Test the creation and values of Atag climate device.""" with patch("pyatag.entities.Climate.status"): entry = await init_integration(hass, aioclient_mock) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert registry.async_is_registered(CLIMATE_ID) entry = registry.async_get(CLIMATE_ID) diff --git a/tests/components/atag/test_sensors.py b/tests/components/atag/test_sensors.py index e7bf4df44e9..aeefcb2789b 100644 --- a/tests/components/atag/test_sensors.py +++ b/tests/components/atag/test_sensors.py @@ -1,7 +1,7 @@ """Tests for the Atag sensor platform.""" - from homeassistant.components.atag.sensor import SENSORS from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from tests.components.atag import UID, init_integration from tests.test_util.aiohttp import AiohttpClientMocker @@ -12,7 +12,7 @@ async def test_sensors( ) -> None: """Test the creation of ATAG sensors.""" entry = await init_integration(hass, aioclient_mock) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) for item in SENSORS: sensor_id = "_".join(f"sensor.{item}".lower().split()) diff --git a/tests/components/atag/test_water_heater.py b/tests/components/atag/test_water_heater.py index 5eb219fa3bc..4c78302224d 100644 --- a/tests/components/atag/test_water_heater.py +++ b/tests/components/atag/test_water_heater.py @@ -1,11 +1,11 @@ """Tests for the Atag water heater platform.""" - from unittest.mock import patch from homeassistant.components.atag import DOMAIN, WATER_HEATER from homeassistant.components.water_heater import SERVICE_SET_TEMPERATURE from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from tests.components.atag import UID, init_integration from tests.test_util.aiohttp import AiohttpClientMocker @@ -19,7 +19,7 @@ async def test_water_heater( """Test the creation of Atag water heater.""" with patch("pyatag.entities.DHW.status"): entry = await init_integration(hass, aioclient_mock) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert registry.async_is_registered(WATER_HEATER_ID) entry = registry.async_get(WATER_HEATER_ID) diff --git a/tests/components/august/test_binary_sensor.py b/tests/components/august/test_binary_sensor.py index 763f9f9528f..0e337813f52 100644 --- a/tests/components/august/test_binary_sensor.py +++ b/tests/components/august/test_binary_sensor.py @@ -1,5 +1,4 @@ """The binary_sensor tests for the august platform.""" - from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, @@ -9,6 +8,7 @@ from homeassistant.const import ( STATE_ON, STATE_UNAVAILABLE, ) +from homeassistant.helpers import device_registry as dr from tests.components.august.mocks import ( _create_august_with_devices, @@ -119,7 +119,7 @@ async def test_doorbell_device_registry(hass): doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.offline.json") await _create_august_with_devices(hass, [doorbell_one]) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) reg_device = device_registry.async_get_device(identifiers={("august", "tmt100")}) assert reg_device.model == "hydra1" diff --git a/tests/components/august/test_lock.py b/tests/components/august/test_lock.py index d013da30ff6..dadd6de2d4f 100644 --- a/tests/components/august/test_lock.py +++ b/tests/components/august/test_lock.py @@ -1,5 +1,4 @@ """The lock tests for the august platform.""" - from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, @@ -9,6 +8,7 @@ from homeassistant.const import ( STATE_UNKNOWN, STATE_UNLOCKED, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.components.august.mocks import ( _create_august_with_devices, @@ -23,7 +23,7 @@ async def test_lock_device_registry(hass): lock_one = await _mock_doorsense_enabled_august_lock_detail(hass) await _create_august_with_devices(hass, [lock_one]) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) reg_device = device_registry.async_get_device( identifiers={("august", "online_with_doorsense")} @@ -90,7 +90,7 @@ async def test_one_lock_operation(hass): assert lock_online_with_doorsense_name.state == STATE_LOCKED # No activity means it will be unavailable until the activity feed has data - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) lock_operator_sensor = entity_registry.async_get( "sensor.online_with_doorsense_name_operator" ) diff --git a/tests/components/august/test_sensor.py b/tests/components/august/test_sensor.py index fb7ddfde979..254f88ef4e9 100644 --- a/tests/components/august/test_sensor.py +++ b/tests/components/august/test_sensor.py @@ -1,6 +1,6 @@ """The sensor tests for the august platform.""" - from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, PERCENTAGE, STATE_UNKNOWN +from homeassistant.helpers import entity_registry as er from tests.components.august.mocks import ( _create_august_with_devices, @@ -29,7 +29,7 @@ async def test_create_doorbell_offline(hass): """Test creation of a doorbell that is offline.""" doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.offline.json") await _create_august_with_devices(hass, [doorbell_one]) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) sensor_tmt100_name_battery = hass.states.get("sensor.tmt100_name_battery") assert sensor_tmt100_name_battery.state == "81" @@ -55,7 +55,7 @@ async def test_create_lock_with_linked_keypad(hass): """Test creation of a lock with a linked keypad that both have a battery.""" lock_one = await _mock_lock_from_fixture(hass, "get_lock.doorsense_init.json") await _create_august_with_devices(hass, [lock_one]) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) sensor_a6697750d607098bae8d6baa11ef8063_name_battery = hass.states.get( "sensor.a6697750d607098bae8d6baa11ef8063_name_battery" @@ -85,7 +85,7 @@ async def test_create_lock_with_low_battery_linked_keypad(hass): """Test creation of a lock with a linked keypad that both have a battery.""" lock_one = await _mock_lock_from_fixture(hass, "get_lock.low_keypad_battery.json") await _create_august_with_devices(hass, [lock_one]) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) sensor_a6697750d607098bae8d6baa11ef8063_name_battery = hass.states.get( "sensor.a6697750d607098bae8d6baa11ef8063_name_battery" @@ -133,7 +133,7 @@ async def test_lock_operator_bluetooth(hass): ) await _create_august_with_devices(hass, [lock_one], activities=activities) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) lock_operator_sensor = entity_registry.async_get( "sensor.online_with_doorsense_name_operator" ) @@ -177,7 +177,7 @@ async def test_lock_operator_keypad(hass): ) await _create_august_with_devices(hass, [lock_one], activities=activities) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) lock_operator_sensor = entity_registry.async_get( "sensor.online_with_doorsense_name_operator" ) @@ -219,7 +219,7 @@ async def test_lock_operator_remote(hass): activities = await _mock_activities_from_fixture(hass, "get_activity.lock.json") await _create_august_with_devices(hass, [lock_one], activities=activities) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) lock_operator_sensor = entity_registry.async_get( "sensor.online_with_doorsense_name_operator" ) @@ -263,7 +263,7 @@ async def test_lock_operator_autorelock(hass): ) await _create_august_with_devices(hass, [lock_one], activities=activities) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) lock_operator_sensor = entity_registry.async_get( "sensor.online_with_doorsense_name_operator" ) diff --git a/tests/components/awair/test_sensor.py b/tests/components/awair/test_sensor.py index 0fcbab99a3a..b37e8dbf5d2 100644 --- a/tests/components/awair/test_sensor.py +++ b/tests/components/awair/test_sensor.py @@ -1,5 +1,4 @@ """Tests for the Awair sensor platform.""" - from unittest.mock import patch from homeassistant.components.awair.const import ( @@ -27,6 +26,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, TEMP_CELSIUS, ) +from homeassistant.helpers import entity_registry as er from .const import ( AWAIR_UUID, @@ -74,7 +74,7 @@ async def test_awair_gen1_sensors(hass): fixtures = [USER_FIXTURE, DEVICES_FIXTURE, GEN1_DATA_FIXTURE] await setup_awair(hass, fixtures) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert_expected_properties( hass, @@ -170,7 +170,7 @@ async def test_awair_gen2_sensors(hass): fixtures = [USER_FIXTURE, DEVICES_FIXTURE, GEN2_DATA_FIXTURE] await setup_awair(hass, fixtures) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert_expected_properties( hass, @@ -204,7 +204,7 @@ async def test_awair_mint_sensors(hass): fixtures = [USER_FIXTURE, DEVICES_FIXTURE, MINT_DATA_FIXTURE] await setup_awair(hass, fixtures) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert_expected_properties( hass, @@ -246,7 +246,7 @@ async def test_awair_glow_sensors(hass): fixtures = [USER_FIXTURE, DEVICES_FIXTURE, GLOW_DATA_FIXTURE] await setup_awair(hass, fixtures) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert_expected_properties( hass, @@ -266,7 +266,7 @@ async def test_awair_omni_sensors(hass): fixtures = [USER_FIXTURE, DEVICES_FIXTURE, OMNI_DATA_FIXTURE] await setup_awair(hass, fixtures) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert_expected_properties( hass, @@ -319,7 +319,7 @@ async def test_awair_unavailable(hass): fixtures = [USER_FIXTURE, DEVICES_FIXTURE, GEN1_DATA_FIXTURE] await setup_awair(hass, fixtures) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert_expected_properties( hass, diff --git a/tests/components/axis/test_init.py b/tests/components/axis/test_init.py index 36a603ea7b3..5ca9fb1eb8d 100644 --- a/tests/components/axis/test_init.py +++ b/tests/components/axis/test_init.py @@ -13,7 +13,7 @@ from homeassistant.const import ( CONF_PORT, CONF_USERNAME, ) -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.device_registry import format_mac from homeassistant.setup import async_setup_component @@ -83,7 +83,7 @@ async def test_migrate_entry(hass): assert not entry.unique_id # Create entity entry to migrate to new unique ID - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( BINARY_SENSOR_DOMAIN, AXIS_DOMAIN, diff --git a/tests/components/blebox/conftest.py b/tests/components/blebox/conftest.py index c603c15c32b..a63a0090c3a 100644 --- a/tests/components/blebox/conftest.py +++ b/tests/components/blebox/conftest.py @@ -7,6 +7,7 @@ import pytest from homeassistant.components.blebox.const import DOMAIN from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry @@ -84,7 +85,7 @@ async def async_setup_entities(hass, config, entity_ids): assert await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) return [entity_registry.async_get(entity_id) for entity_id in entity_ids] diff --git a/tests/components/blebox/test_air_quality.py b/tests/components/blebox/test_air_quality.py index 4f1f6dff671..8b5bc67d4bc 100644 --- a/tests/components/blebox/test_air_quality.py +++ b/tests/components/blebox/test_air_quality.py @@ -1,5 +1,4 @@ """Blebox air_quality tests.""" - import logging from unittest.mock import AsyncMock, PropertyMock @@ -8,6 +7,7 @@ import pytest from homeassistant.components.air_quality import ATTR_PM_0_1, ATTR_PM_2_5, ATTR_PM_10 from homeassistant.const import ATTR_ICON, STATE_UNKNOWN +from homeassistant.helpers import device_registry as dr from .conftest import async_setup_entity, mock_feature @@ -49,7 +49,7 @@ async def test_init(airsensor, hass, config): assert state.state == STATE_UNKNOWN - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My air sensor" diff --git a/tests/components/blebox/test_climate.py b/tests/components/blebox/test_climate.py index baaa5a5009e..0b27846e654 100644 --- a/tests/components/blebox/test_climate.py +++ b/tests/components/blebox/test_climate.py @@ -1,5 +1,4 @@ """BleBox climate entities tests.""" - import logging from unittest.mock import AsyncMock, PropertyMock @@ -28,6 +27,7 @@ from homeassistant.const import ( ATTR_TEMPERATURE, STATE_UNKNOWN, ) +from homeassistant.helpers import device_registry as dr from .conftest import async_setup_entity, mock_feature @@ -79,7 +79,7 @@ async def test_init(saunabox, hass, config): assert state.state == STATE_UNKNOWN - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My sauna" diff --git a/tests/components/blebox/test_cover.py b/tests/components/blebox/test_cover.py index a5d3a8f705b..8355411a0bb 100644 --- a/tests/components/blebox/test_cover.py +++ b/tests/components/blebox/test_cover.py @@ -1,5 +1,4 @@ """BleBox cover entities tests.""" - import logging from unittest.mock import AsyncMock, PropertyMock @@ -30,6 +29,7 @@ from homeassistant.const import ( SERVICE_STOP_COVER, STATE_UNKNOWN, ) +from homeassistant.helpers import device_registry as dr from .conftest import async_setup_entity, mock_feature @@ -117,7 +117,7 @@ async def test_init_gatecontroller(gatecontroller, hass, config): assert ATTR_CURRENT_POSITION not in state.attributes assert state.state == STATE_UNKNOWN - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My gate controller" @@ -147,7 +147,7 @@ async def test_init_shutterbox(shutterbox, hass, config): assert ATTR_CURRENT_POSITION not in state.attributes assert state.state == STATE_UNKNOWN - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My shutter" @@ -179,7 +179,7 @@ async def test_init_gatebox(gatebox, hass, config): assert ATTR_CURRENT_POSITION not in state.attributes assert state.state == STATE_UNKNOWN - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My gatebox" diff --git a/tests/components/blebox/test_light.py b/tests/components/blebox/test_light.py index 5d9e5709e4d..6c8c26fe938 100644 --- a/tests/components/blebox/test_light.py +++ b/tests/components/blebox/test_light.py @@ -1,5 +1,4 @@ """BleBox light entities tests.""" - import logging from unittest.mock import AsyncMock, PropertyMock @@ -21,6 +20,7 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, ) +from homeassistant.helpers import device_registry as dr from homeassistant.util import color from .conftest import async_setup_entity, mock_feature @@ -65,7 +65,7 @@ async def test_dimmer_init(dimmer, hass, config): assert state.attributes[ATTR_BRIGHTNESS] == 65 assert state.state == STATE_ON - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My dimmer" @@ -236,7 +236,7 @@ async def test_wlightbox_s_init(wlightbox_s, hass, config): assert ATTR_BRIGHTNESS not in state.attributes assert state.state == STATE_OFF - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My wLightBoxS" @@ -339,7 +339,7 @@ async def test_wlightbox_init(wlightbox, hass, config): assert ATTR_BRIGHTNESS not in state.attributes assert state.state == STATE_OFF - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My wLightBox" diff --git a/tests/components/blebox/test_sensor.py b/tests/components/blebox/test_sensor.py index aeb726cc726..2281c4ea68c 100644 --- a/tests/components/blebox/test_sensor.py +++ b/tests/components/blebox/test_sensor.py @@ -1,5 +1,4 @@ """Blebox sensors tests.""" - import logging from unittest.mock import AsyncMock, PropertyMock @@ -13,6 +12,7 @@ from homeassistant.const import ( STATE_UNKNOWN, TEMP_CELSIUS, ) +from homeassistant.helpers import device_registry as dr from .conftest import async_setup_entity, mock_feature @@ -49,7 +49,7 @@ async def test_init(tempsensor, hass, config): assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == TEMP_CELSIUS assert state.state == STATE_UNKNOWN - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My temperature sensor" diff --git a/tests/components/blebox/test_switch.py b/tests/components/blebox/test_switch.py index e2bc1240510..e67c0479cb3 100644 --- a/tests/components/blebox/test_switch.py +++ b/tests/components/blebox/test_switch.py @@ -1,5 +1,4 @@ """Blebox switch tests.""" - import logging from unittest.mock import AsyncMock, PropertyMock @@ -14,6 +13,7 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, ) +from homeassistant.helpers import device_registry as dr from .conftest import ( async_setup_entities, @@ -58,7 +58,7 @@ async def test_switchbox_init(switchbox, hass, config): assert state.state == STATE_OFF - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My switch box" @@ -204,7 +204,7 @@ async def test_switchbox_d_init(switchbox_d, hass, config): assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_SWITCH assert state.state == STATE_OFF # NOTE: should instead be STATE_UNKNOWN? - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My relays" @@ -221,7 +221,7 @@ async def test_switchbox_d_init(switchbox_d, hass, config): assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_SWITCH assert state.state == STATE_OFF # NOTE: should instead be STATE_UNKNOWN? - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My relays" diff --git a/tests/components/bond/test_cover.py b/tests/components/bond/test_cover.py index dbb8ee0f3b7..f516d84d50a 100644 --- a/tests/components/bond/test_cover.py +++ b/tests/components/bond/test_cover.py @@ -11,6 +11,7 @@ from homeassistant.const import ( SERVICE_OPEN_COVER, SERVICE_STOP_COVER, ) +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.util import utcnow @@ -39,7 +40,7 @@ async def test_entity_registry(hass: core.HomeAssistant): bond_device_id="test-device-id", ) - registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + registry: EntityRegistry = er.async_get(hass) entity = registry.entities["cover.name_1"] assert entity.unique_id == "test-hub-id_test-device-id" diff --git a/tests/components/bond/test_fan.py b/tests/components/bond/test_fan.py index 49a6e4a5b68..ca3bc9ac7e7 100644 --- a/tests/components/bond/test_fan.py +++ b/tests/components/bond/test_fan.py @@ -18,6 +18,7 @@ from homeassistant.components.fan import ( SPEED_OFF, ) from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.util import utcnow @@ -71,7 +72,7 @@ async def test_entity_registry(hass: core.HomeAssistant): bond_device_id="test-device-id", ) - registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + registry: EntityRegistry = er.async_get(hass) entity = registry.entities["fan.name_1"] assert entity.unique_id == "test-hub-id_test-device-id" diff --git a/tests/components/bond/test_init.py b/tests/components/bond/test_init.py index 7346acc5276..0bba04b4d97 100644 --- a/tests/components/bond/test_init.py +++ b/tests/components/bond/test_init.py @@ -79,7 +79,7 @@ async def test_async_setup_entry_sets_up_hub_and_supported_domains(hass: HomeAss assert config_entry.unique_id == "test-bond-id" # verify hub device is registered correctly - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) hub = device_registry.async_get_device(identifiers={(DOMAIN, "test-bond-id")}) assert hub.name == "bond-name" assert hub.manufacturer == "Olibra" @@ -127,7 +127,7 @@ async def test_old_identifiers_are_removed(hass: HomeAssistant): old_identifers = (DOMAIN, "device_id") new_identifiers = (DOMAIN, "test-bond-id", "device_id") - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, identifiers={old_identifers}, @@ -204,7 +204,7 @@ async def test_smart_by_bond_device_suggested_area(hass: HomeAssistant): assert config_entry.state == ENTRY_STATE_LOADED assert config_entry.unique_id == "test-bond-id" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get_device(identifiers={(DOMAIN, "test-bond-id")}) assert device is not None assert device.suggested_area == "Den" @@ -250,7 +250,7 @@ async def test_bridge_device_suggested_area(hass: HomeAssistant): assert config_entry.state == ENTRY_STATE_LOADED assert config_entry.unique_id == "test-bond-id" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get_device(identifiers={(DOMAIN, "test-bond-id")}) assert device is not None assert device.suggested_area == "Office" diff --git a/tests/components/bond/test_light.py b/tests/components/bond/test_light.py index 59d051fbe86..e59efcd7bcf 100644 --- a/tests/components/bond/test_light.py +++ b/tests/components/bond/test_light.py @@ -16,6 +16,7 @@ from homeassistant.const import ( SERVICE_TURN_OFF, SERVICE_TURN_ON, ) +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.util import utcnow @@ -107,7 +108,7 @@ async def test_fan_entity_registry(hass: core.HomeAssistant): bond_device_id="test-device-id", ) - registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + registry: EntityRegistry = er.async_get(hass) entity = registry.entities["light.fan_name"] assert entity.unique_id == "test-hub-id_test-device-id" @@ -122,7 +123,7 @@ async def test_fan_up_light_entity_registry(hass: core.HomeAssistant): bond_device_id="test-device-id", ) - registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + registry: EntityRegistry = er.async_get(hass) entity = registry.entities["light.fan_name_up_light"] assert entity.unique_id == "test-hub-id_test-device-id_up_light" @@ -137,7 +138,7 @@ async def test_fan_down_light_entity_registry(hass: core.HomeAssistant): bond_device_id="test-device-id", ) - registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + registry: EntityRegistry = er.async_get(hass) entity = registry.entities["light.fan_name_down_light"] assert entity.unique_id == "test-hub-id_test-device-id_down_light" @@ -152,7 +153,7 @@ async def test_fireplace_entity_registry(hass: core.HomeAssistant): bond_device_id="test-device-id", ) - registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + registry: EntityRegistry = er.async_get(hass) entity = registry.entities["light.fireplace_name"] assert entity.unique_id == "test-hub-id_test-device-id" @@ -167,7 +168,7 @@ async def test_fireplace_with_light_entity_registry(hass: core.HomeAssistant): bond_device_id="test-device-id", ) - registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + registry: EntityRegistry = er.async_get(hass) entity_flame = registry.entities["light.fireplace_name"] assert entity_flame.unique_id == "test-hub-id_test-device-id" entity_light = registry.entities["light.fireplace_name_light"] @@ -184,7 +185,7 @@ async def test_light_entity_registry(hass: core.HomeAssistant): bond_device_id="test-device-id", ) - registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + registry: EntityRegistry = er.async_get(hass) entity = registry.entities["light.light_name"] assert entity.unique_id == "test-hub-id_test-device-id" diff --git a/tests/components/bond/test_switch.py b/tests/components/bond/test_switch.py index f2568f5fb99..94a9179d3a7 100644 --- a/tests/components/bond/test_switch.py +++ b/tests/components/bond/test_switch.py @@ -6,6 +6,7 @@ from bond_api import Action, DeviceType from homeassistant import core from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.util import utcnow @@ -34,7 +35,7 @@ async def test_entity_registry(hass: core.HomeAssistant): bond_device_id="test-device-id", ) - registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + registry: EntityRegistry = er.async_get(hass) entity = registry.entities["switch.name_1"] assert entity.unique_id == "test-hub-id_test-device-id" diff --git a/tests/components/brother/test_sensor.py b/tests/components/brother/test_sensor.py index b386b0753b7..ab48721dec5 100644 --- a/tests/components/brother/test_sensor.py +++ b/tests/components/brother/test_sensor.py @@ -14,6 +14,7 @@ from homeassistant.const import ( PERCENTAGE, STATE_UNAVAILABLE, ) +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.dt import UTC, utcnow @@ -28,7 +29,7 @@ async def test_sensors(hass): """Test states of the sensors.""" entry = await init_integration(hass, skip_setup=True) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) # Pre-create registry entries for disabled by default sensors registry.async_get_or_create( @@ -241,7 +242,7 @@ async def test_disabled_by_default_sensors(hass): """Test the disabled by default Brother sensors.""" await init_integration(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("sensor.hl_l2340dw_uptime") assert state is None diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 8a479a802e4..9abb68e97c9 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -8,6 +8,7 @@ import pytest from homeassistant import config_entries, data_entry_flow, loader from homeassistant.core import callback from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util import dt @@ -299,7 +300,7 @@ async def test_remove_entry(hass, manager): assert len(hass.states.async_all()) == 1 # Check entity got added to entity registry - ent_reg = await hass.helpers.entity_registry.async_get_registry() + ent_reg = er.async_get(hass) assert len(ent_reg.entities) == 1 entity_entry = list(ent_reg.entities.values())[0] assert entity_entry.config_entry_id == entry.entry_id From b3fecb1c958a9571c6ddf453656477fa95d8881f Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 9 Mar 2021 14:25:03 +0100 Subject: [PATCH 263/831] Update tests t-z to use async_get() instead of async_get_registry() (#47655) --- tests/components/tasmota/test_common.py | 13 ++--- tests/components/tasmota/test_sensor.py | 9 ++-- tests/components/timer/test_init.py | 10 ++-- tests/components/traccar/test_init.py | 5 +- tests/components/tradfri/test_init.py | 9 ++-- tests/components/twinkly/test_twinkly.py | 6 +-- tests/components/unifi/test_device_tracker.py | 8 +-- tests/components/unifi/test_switch.py | 6 +-- tests/components/vera/test_init.py | 7 +-- tests/components/wemo/conftest.py | 3 +- tests/components/wemo/test_init.py | 9 ++-- tests/components/wilight/test_cover.py | 3 +- tests/components/wilight/test_fan.py | 3 +- tests/components/wilight/test_light.py | 3 +- .../components/withings/test_binary_sensor.py | 5 +- tests/components/withings/test_sensor.py | 9 ++-- tests/components/wled/test_light.py | 3 +- tests/components/wled/test_sensor.py | 5 +- tests/components/wled/test_switch.py | 3 +- tests/components/yeelight/test_init.py | 34 ++++++------ tests/components/yeelight/test_light.py | 4 +- tests/components/zha/test_device.py | 4 +- tests/components/zha/test_device_action.py | 6 +-- tests/components/zha/test_device_trigger.py | 12 ++--- tests/components/zha/test_discover.py | 2 +- tests/components/zone/test_init.py | 10 ++-- tests/components/zwave/test_init.py | 7 ++- tests/components/zwave_js/test_api.py | 4 +- .../components/zwave_js/test_binary_sensor.py | 3 +- tests/components/zwave_js/test_init.py | 42 ++++++--------- tests/components/zwave_js/test_sensor.py | 9 ++-- tests/helpers/test_device_registry.py | 5 +- tests/helpers/test_entity_platform.py | 28 +++++----- tests/helpers/test_entity_registry.py | 52 +++++++++---------- 34 files changed, 171 insertions(+), 170 deletions(-) diff --git a/tests/components/tasmota/test_common.py b/tests/components/tasmota/test_common.py index 973ecd3c890..74e8d2a5e59 100644 --- a/tests/components/tasmota/test_common.py +++ b/tests/components/tasmota/test_common.py @@ -20,6 +20,7 @@ from hatasmota.utils import ( from homeassistant.components.tasmota.const import DEFAULT_PREFIX from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.common import async_fire_mqtt_message @@ -363,8 +364,8 @@ async def help_test_discovery_removal( name="Test", ): """Test removal of discovered entity.""" - device_reg = await hass.helpers.device_registry.async_get_registry() - entity_reg = await hass.helpers.entity_registry.async_get_registry() + device_reg = dr.async_get(hass) + entity_reg = er.async_get(hass) data1 = json.dumps(config1) data2 = json.dumps(config2) @@ -470,8 +471,8 @@ async def help_test_discovery_device_remove( hass, mqtt_mock, domain, unique_id, config, sensor_config=None ): """Test domain entity is removed when device is removed.""" - device_reg = await hass.helpers.device_registry.async_get_registry() - entity_reg = await hass.helpers.entity_registry.async_get_registry() + device_reg = dr.async_get(hass) + entity_reg = er.async_get(hass) config = copy.deepcopy(config) @@ -502,7 +503,7 @@ async def help_test_entity_id_update_subscriptions( hass, mqtt_mock, domain, config, topics=None, sensor_config=None, entity_id="test" ): """Test MQTT subscriptions are managed when entity_id is updated.""" - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) config = copy.deepcopy(config) data = json.dumps(config) @@ -548,7 +549,7 @@ async def help_test_entity_id_update_discovery_update( hass, mqtt_mock, domain, config, sensor_config=None, entity_id="test" ): """Test MQTT discovery update after entity_id is updated.""" - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) config = copy.deepcopy(config) data = json.dumps(config) diff --git a/tests/components/tasmota/test_sensor.py b/tests/components/tasmota/test_sensor.py index 6e5160273d6..c08b1b531de 100644 --- a/tests/components/tasmota/test_sensor.py +++ b/tests/components/tasmota/test_sensor.py @@ -17,6 +17,7 @@ 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.helpers import entity_registry as er from homeassistant.util import dt from .test_common import ( @@ -226,7 +227,7 @@ async def test_indexed_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): @pytest.mark.parametrize("status_sensor_disabled", [False]) async def test_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): """Test state update via MQTT.""" - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) # Pre-enable the status sensor entity_reg.async_get_or_create( @@ -285,7 +286,7 @@ async def test_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): @pytest.mark.parametrize("status_sensor_disabled", [False]) async def test_single_shot_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): """Test state update via MQTT.""" - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) # Pre-enable the status sensor entity_reg.async_get_or_create( @@ -369,7 +370,7 @@ async def test_restart_time_status_sensor_state_via_mqtt( hass, mqtt_mock, setup_tasmota ): """Test state update via MQTT.""" - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) # Pre-enable the status sensor entity_reg.async_get_or_create( @@ -524,7 +525,7 @@ async def test_indexed_sensor_attributes(hass, mqtt_mock, setup_tasmota): @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() + entity_reg = er.async_get(hass) config = copy.deepcopy(DEFAULT_CONFIG) mac = config["mac"] diff --git a/tests/components/timer/test_init.py b/tests/components/timer/test_init.py index 74c3eceeea2..21f48ed6147 100644 --- a/tests/components/timer/test_init.py +++ b/tests/components/timer/test_init.py @@ -39,7 +39,7 @@ from homeassistant.const import ( ) from homeassistant.core import Context, CoreState from homeassistant.exceptions import Unauthorized -from homeassistant.helpers import config_validation as cv, entity_registry +from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -248,7 +248,7 @@ async def test_no_initial_state_and_no_restore_state(hass): async def test_config_reload(hass, hass_admin_user, hass_read_only_user): """Test reload service.""" count_start = len(hass.states.async_entity_ids()) - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) _LOGGER.debug("ENTITIES @ start: %s", hass.states.async_entity_ids()) @@ -498,7 +498,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): timer_id = "from_storage" timer_entity_id = f"{DOMAIN}.{DOMAIN}_{timer_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(timer_entity_id) assert state is not None @@ -525,7 +525,7 @@ async def test_update(hass, hass_ws_client, storage_setup): timer_id = "from_storage" timer_entity_id = f"{DOMAIN}.{DOMAIN}_{timer_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(timer_entity_id) assert state.attributes[ATTR_FRIENDLY_NAME] == "timer from storage" @@ -554,7 +554,7 @@ async def test_ws_create(hass, hass_ws_client, storage_setup): timer_id = "new_timer" timer_entity_id = f"{DOMAIN}.{timer_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(timer_entity_id) assert state is None diff --git a/tests/components/traccar/test_init.py b/tests/components/traccar/test_init.py index c7e031b2ca6..0e741751b8a 100644 --- a/tests/components/traccar/test_init.py +++ b/tests/components/traccar/test_init.py @@ -14,6 +14,7 @@ from homeassistant.const import ( STATE_HOME, STATE_NOT_HOME, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import DATA_DISPATCHER from homeassistant.setup import async_setup_component @@ -136,10 +137,10 @@ async def test_enter_and_exit(hass, client, webhook_id): ).state assert STATE_NOT_HOME == state_name - dev_reg = await hass.helpers.device_registry.async_get_registry() + dev_reg = dr.async_get(hass) assert len(dev_reg.devices) == 1 - ent_reg = await hass.helpers.entity_registry.async_get_registry() + ent_reg = er.async_get(hass) assert len(ent_reg.entities) == 1 diff --git a/tests/components/tradfri/test_init.py b/tests/components/tradfri/test_init.py index 0983b5aa22f..da9ae9da146 100644 --- a/tests/components/tradfri/test_init.py +++ b/tests/components/tradfri/test_init.py @@ -2,10 +2,7 @@ from unittest.mock import patch from homeassistant.components import tradfri -from homeassistant.helpers.device_registry import ( - async_entries_for_config_entry, - async_get_registry as async_get_device_registry, -) +from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry @@ -99,8 +96,8 @@ async def test_entry_setup_unload(hass, api_factory, gateway_id): await hass.async_block_till_done() assert setup.call_count == len(tradfri.PLATFORMS) - dev_reg = await async_get_device_registry(hass) - dev_entries = async_entries_for_config_entry(dev_reg, entry.entry_id) + dev_reg = dr.async_get(hass) + dev_entries = dr.async_entries_for_config_entry(dev_reg, entry.entry_id) assert dev_entries dev_entry = dev_entries[0] diff --git a/tests/components/twinkly/test_twinkly.py b/tests/components/twinkly/test_twinkly.py index c8158354195..d4afe02c11b 100644 --- a/tests/components/twinkly/test_twinkly.py +++ b/tests/components/twinkly/test_twinkly.py @@ -1,5 +1,4 @@ """Tests for the integration of a twinly device.""" - from typing import Tuple from unittest.mock import patch @@ -12,6 +11,7 @@ from homeassistant.components.twinkly.const import ( ) from homeassistant.components.twinkly.light import TwinklyLight from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.entity_registry import RegistryEntry @@ -211,8 +211,8 @@ async def _create_entries( assert await hass.config_entries.async_setup(client.id) await hass.async_block_till_done() - device_registry = await hass.helpers.device_registry.async_get_registry() - entity_registry = await hass.helpers.entity_registry.async_get_registry() + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) entity_id = entity_registry.async_get_entity_id("light", TWINKLY_DOMAIN, client.id) entity = entity_registry.async_get(entity_id) diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 33dda33be24..2018ae39b64 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -23,7 +23,7 @@ from homeassistant.components.unifi.const import ( DOMAIN as UNIFI_DOMAIN, ) from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_UNAVAILABLE -from homeassistant.helpers import entity_registry +from homeassistant.helpers import device_registry as dr, entity_registry as er import homeassistant.util.dt as dt_util from .test_controller import ENTRY_CONFIG, setup_unifi_integration @@ -335,9 +335,9 @@ async def test_tracked_devices(hass, aioclient_mock, mock_unifi_websocket): await hass.async_block_till_done() # Verify device registry has been updated - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("device_tracker.device_2") - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.sw_version == event["version_to"] @@ -949,7 +949,7 @@ async def test_restoring_client(hass, aioclient_mock): entry_id=1, ) - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( TRACKER_DOMAIN, UNIFI_DOMAIN, diff --git a/tests/components/unifi/test_switch.py b/tests/components/unifi/test_switch.py index 44120a18ee3..b8f6e2da553 100644 --- a/tests/components/unifi/test_switch.py +++ b/tests/components/unifi/test_switch.py @@ -16,7 +16,7 @@ from homeassistant.components.unifi.const import ( DOMAIN as UNIFI_DOMAIN, ) from homeassistant.components.unifi.switch import POE_SWITCH -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from .test_controller import ( @@ -799,7 +799,7 @@ async def test_restore_client_succeed(hass, aioclient_mock): entry_id=1, ) - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( SWITCH_DOMAIN, UNIFI_DOMAIN, @@ -891,7 +891,7 @@ async def test_restore_client_no_old_state(hass, aioclient_mock): entry_id=1, ) - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( SWITCH_DOMAIN, UNIFI_DOMAIN, diff --git a/tests/components/vera/test_init.py b/tests/components/vera/test_init.py index 33b6843d7e5..85735d0320e 100644 --- a/tests/components/vera/test_init.py +++ b/tests/components/vera/test_init.py @@ -13,6 +13,7 @@ from homeassistant.components.vera import ( ) from homeassistant.config_entries import ENTRY_STATE_NOT_LOADED from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from .common import ComponentFactory, ConfigSource, new_simple_controller_config @@ -40,7 +41,7 @@ async def test_init( ), ) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry1 = entity_registry.async_get(entity1_id) assert entry1 assert entry1.unique_id == "vera_first_serial_1" @@ -67,7 +68,7 @@ async def test_init_from_file( ), ) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry1 = entity_registry.async_get(entity1_id) assert entry1 assert entry1.unique_id == "vera_first_serial_1" @@ -117,7 +118,7 @@ async def test_multiple_controllers_with_legacy_one( ), ) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry1 = entity_registry.async_get(entity1_id) assert entry1 diff --git a/tests/components/wemo/conftest.py b/tests/components/wemo/conftest.py index 0e0a69216b2..69b4b84dcd3 100644 --- a/tests/components/wemo/conftest.py +++ b/tests/components/wemo/conftest.py @@ -7,6 +7,7 @@ import pywemo from homeassistant.components.wemo import CONF_DISCOVERY, CONF_STATIC from homeassistant.components.wemo.const import DOMAIN +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component MOCK_HOST = "127.0.0.1" @@ -72,7 +73,7 @@ async def async_wemo_entity_fixture(hass, pywemo_device): ) await hass.async_block_till_done() - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entity_entries = list(entity_registry.entities.values()) assert len(entity_entries) == 1 diff --git a/tests/components/wemo/test_init.py b/tests/components/wemo/test_init.py index 374222d8688..1164af7cf95 100644 --- a/tests/components/wemo/test_init.py +++ b/tests/components/wemo/test_init.py @@ -6,6 +6,7 @@ import pywemo from homeassistant.components.wemo import CONF_DISCOVERY, CONF_STATIC, WemoDiscovery from homeassistant.components.wemo.const import DOMAIN +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util import dt @@ -41,7 +42,7 @@ async def test_static_duplicate_static_entry(hass, pywemo_device): }, ) await hass.async_block_till_done() - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) entity_entries = list(entity_reg.entities.values()) assert len(entity_entries) == 1 @@ -59,7 +60,7 @@ async def test_static_config_with_port(hass, pywemo_device): }, ) await hass.async_block_till_done() - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) entity_entries = list(entity_reg.entities.values()) assert len(entity_entries) == 1 @@ -77,7 +78,7 @@ async def test_static_config_without_port(hass, pywemo_device): }, ) await hass.async_block_till_done() - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) entity_entries = list(entity_reg.entities.values()) assert len(entity_entries) == 1 @@ -132,7 +133,7 @@ async def test_discovery(hass, pywemo_registry): await hass.async_block_till_done() # Verify that the expected number of devices were setup. - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) entity_entries = list(entity_reg.entities.values()) assert len(entity_entries) == 3 diff --git a/tests/components/wilight/test_cover.py b/tests/components/wilight/test_cover.py index 85f62c9d120..8b058d95836 100644 --- a/tests/components/wilight/test_cover.py +++ b/tests/components/wilight/test_cover.py @@ -20,6 +20,7 @@ from homeassistant.const import ( STATE_OPEN, STATE_OPENING, ) +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.typing import HomeAssistantType from . import ( @@ -64,7 +65,7 @@ async def test_loading_cover( assert entry assert entry.unique_id == WILIGHT_ID - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # First segment of the strip state = hass.states.get("cover.wl000000000099_1") diff --git a/tests/components/wilight/test_fan.py b/tests/components/wilight/test_fan.py index 1247b622ae7..0ad7789c52c 100644 --- a/tests/components/wilight/test_fan.py +++ b/tests/components/wilight/test_fan.py @@ -20,6 +20,7 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, ) +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.typing import HomeAssistantType from . import ( @@ -64,7 +65,7 @@ async def test_loading_light_fan( assert entry assert entry.unique_id == WILIGHT_ID - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # First segment of the strip state = hass.states.get("fan.wl000000000099_2") diff --git a/tests/components/wilight/test_light.py b/tests/components/wilight/test_light.py index b7250df546d..9abe17ce9e5 100644 --- a/tests/components/wilight/test_light.py +++ b/tests/components/wilight/test_light.py @@ -16,6 +16,7 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, ) +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.typing import HomeAssistantType from tests.components.wilight import ( @@ -140,7 +141,7 @@ async def test_loading_light( assert entry assert entry.unique_id == WILIGHT_ID - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # First segment of the strip state = hass.states.get("light.wl000000000099_1") diff --git a/tests/components/withings/test_binary_sensor.py b/tests/components/withings/test_binary_sensor.py index 3477671ea79..22c6b6de862 100644 --- a/tests/components/withings/test_binary_sensor.py +++ b/tests/components/withings/test_binary_sensor.py @@ -8,6 +8,7 @@ from homeassistant.components.withings.common import ( from homeassistant.components.withings.const import Measurement from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_registry import EntityRegistry from .common import ComponentFactory, new_profile_config @@ -21,9 +22,7 @@ async def test_binary_sensor( person0 = new_profile_config("person0", 0) person1 = new_profile_config("person1", 1) - entity_registry: EntityRegistry = ( - await hass.helpers.entity_registry.async_get_registry() - ) + entity_registry: EntityRegistry = er.async_get(hass) await component_factory.configure_component(profile_configs=(person0, person1)) assert not await async_get_entity_id(hass, in_bed_attribute, person0.user_id) diff --git a/tests/components/withings/test_sensor.py b/tests/components/withings/test_sensor.py index 16b83a585aa..8b51f62514d 100644 --- a/tests/components/withings/test_sensor.py +++ b/tests/components/withings/test_sensor.py @@ -27,6 +27,7 @@ from homeassistant.components.withings.common import ( ) from homeassistant.components.withings.const import Measurement from homeassistant.core import HomeAssistant, State +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_registry import EntityRegistry from .common import ComponentFactory, new_profile_config @@ -304,9 +305,7 @@ async def test_sensor_default_enabled_entities( hass: HomeAssistant, component_factory: ComponentFactory ) -> None: """Test entities enabled by default.""" - entity_registry: EntityRegistry = ( - await hass.helpers.entity_registry.async_get_registry() - ) + entity_registry: EntityRegistry = er.async_get(hass) await component_factory.configure_component(profile_configs=(PERSON0,)) @@ -347,9 +346,7 @@ async def test_all_entities( hass: HomeAssistant, component_factory: ComponentFactory ) -> None: """Test all entities.""" - entity_registry: EntityRegistry = ( - await hass.helpers.entity_registry.async_get_registry() - ) + entity_registry: EntityRegistry = er.async_get(hass) with patch( "homeassistant.components.withings.sensor.BaseWithingsSensor.entity_registry_enabled_default" diff --git a/tests/components/wled/test_light.py b/tests/components/wled/test_light.py index eb9124ab906..0077cea0202 100644 --- a/tests/components/wled/test_light.py +++ b/tests/components/wled/test_light.py @@ -36,6 +36,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed, load_fixture @@ -49,7 +50,7 @@ async def test_rgb_light_state( """Test the creation and values of the WLED lights.""" await init_integration(hass, aioclient_mock) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # First segment of the strip state = hass.states.get("light.wled_rgb_light_segment_0") diff --git a/tests/components/wled/test_sensor.py b/tests/components/wled/test_sensor.py index 11e14bd79d9..9cebf2cda32 100644 --- a/tests/components/wled/test_sensor.py +++ b/tests/components/wled/test_sensor.py @@ -23,6 +23,7 @@ from homeassistant.const import ( SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from homeassistant.util import dt as dt_util from tests.components.wled import init_integration @@ -35,7 +36,7 @@ async def test_sensors( """Test the creation and values of the WLED sensors.""" entry = await init_integration(hass, aioclient_mock, skip_setup=True) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) # Pre-create registry entries for disabled by default sensors registry.async_get_or_create( @@ -185,7 +186,7 @@ async def test_disabled_by_default_sensors( ) -> None: """Test the disabled by default WLED sensors.""" await init_integration(hass, aioclient_mock) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get(entity_id) assert state is None diff --git a/tests/components/wled/test_switch.py b/tests/components/wled/test_switch.py index c6e30ef903e..ddeeee41ac8 100644 --- a/tests/components/wled/test_switch.py +++ b/tests/components/wled/test_switch.py @@ -20,6 +20,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from tests.components.wled import init_integration from tests.test_util.aiohttp import AiohttpClientMocker @@ -31,7 +32,7 @@ async def test_switch_state( """Test the creation and values of the WLED switches.""" await init_integration(hass, aioclient_mock) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) state = hass.states.get("switch.wled_rgb_light_nightlight") assert state diff --git a/tests/components/yeelight/test_init.py b/tests/components/yeelight/test_init.py index 05a0bd0d8d4..b6a59809d30 100644 --- a/tests/components/yeelight/test_init.py +++ b/tests/components/yeelight/test_init.py @@ -13,7 +13,7 @@ from homeassistant.components.yeelight import ( ) from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_NAME, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from . import ( @@ -106,11 +106,14 @@ async def test_unique_ids_device(hass: HomeAssistant): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - er = await entity_registry.async_get_registry(hass) - assert er.async_get(ENTITY_BINARY_SENSOR).unique_id == f"{ID}-nightlight_sensor" - assert er.async_get(ENTITY_LIGHT).unique_id == ID - assert er.async_get(ENTITY_NIGHTLIGHT).unique_id == f"{ID}-nightlight" - assert er.async_get(ENTITY_AMBILIGHT).unique_id == f"{ID}-ambilight" + entity_registry = er.async_get(hass) + assert ( + entity_registry.async_get(ENTITY_BINARY_SENSOR).unique_id + == f"{ID}-nightlight_sensor" + ) + assert entity_registry.async_get(ENTITY_LIGHT).unique_id == ID + assert entity_registry.async_get(ENTITY_NIGHTLIGHT).unique_id == f"{ID}-nightlight" + assert entity_registry.async_get(ENTITY_AMBILIGHT).unique_id == f"{ID}-ambilight" async def test_unique_ids_entry(hass: HomeAssistant): @@ -131,18 +134,19 @@ async def test_unique_ids_entry(hass: HomeAssistant): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - er = await entity_registry.async_get_registry(hass) + entity_registry = er.async_get(hass) assert ( - er.async_get(ENTITY_BINARY_SENSOR).unique_id + entity_registry.async_get(ENTITY_BINARY_SENSOR).unique_id == f"{config_entry.entry_id}-nightlight_sensor" ) - assert er.async_get(ENTITY_LIGHT).unique_id == config_entry.entry_id + assert entity_registry.async_get(ENTITY_LIGHT).unique_id == config_entry.entry_id assert ( - er.async_get(ENTITY_NIGHTLIGHT).unique_id + entity_registry.async_get(ENTITY_NIGHTLIGHT).unique_id == f"{config_entry.entry_id}-nightlight" ) assert ( - er.async_get(ENTITY_AMBILIGHT).unique_id == f"{config_entry.entry_id}-ambilight" + entity_registry.async_get(ENTITY_AMBILIGHT).unique_id + == f"{config_entry.entry_id}-ambilight" ) @@ -170,8 +174,8 @@ async def test_bulb_off_while_adding_in_ha(hass: HomeAssistant): binary_sensor_entity_id = ENTITY_BINARY_SENSOR_TEMPLATE.format( IP_ADDRESS.replace(".", "_") ) - er = await entity_registry.async_get_registry(hass) - assert er.async_get(binary_sensor_entity_id) is None + entity_registry = er.async_get(hass) + assert entity_registry.async_get(binary_sensor_entity_id) is None type(mocked_bulb).get_capabilities = MagicMock(CAPABILITIES) type(mocked_bulb).get_properties = MagicMock(None) @@ -179,5 +183,5 @@ async def test_bulb_off_while_adding_in_ha(hass: HomeAssistant): hass.data[DOMAIN][DATA_CONFIG_ENTRIES][config_entry.entry_id][DATA_DEVICE].update() await hass.async_block_till_done() - er = await entity_registry.async_get_registry(hass) - assert er.async_get(binary_sensor_entity_id) is not None + entity_registry = er.async_get(hass) + assert entity_registry.async_get(binary_sensor_entity_id) is not None diff --git a/tests/components/yeelight/test_light.py b/tests/components/yeelight/test_light.py index 0b7415140a3..90a8d1f9e6e 100644 --- a/tests/components/yeelight/test_light.py +++ b/tests/components/yeelight/test_light.py @@ -85,7 +85,7 @@ from homeassistant.components.yeelight.light import ( ) from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.color import ( color_hs_to_RGB, @@ -376,7 +376,7 @@ async def test_device_types(hass: HomeAssistant): await hass.config_entries.async_unload(config_entry.entry_id) await config_entry.async_remove(hass) - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_clear_config_entry(config_entry.entry_id) # nightlight diff --git a/tests/components/zha/test_device.py b/tests/components/zha/test_device.py index 1ce75045d38..d3ab3c3ada2 100644 --- a/tests/components/zha/test_device.py +++ b/tests/components/zha/test_device.py @@ -10,7 +10,7 @@ import zigpy.zcl.clusters.general as general import homeassistant.components.zha.core.device as zha_core_device from homeassistant.const import STATE_OFF, STATE_UNAVAILABLE -import homeassistant.helpers.device_registry as ha_dev_reg +import homeassistant.helpers.device_registry as dr import homeassistant.util.dt as dt_util from .common import async_enable_traffic, make_zcl_header @@ -227,7 +227,7 @@ async def test_ota_sw_version(hass, ota_zha_device): """Test device entry gets sw_version updated via OTA channel.""" ota_ch = ota_zha_device.channels.pools[0].client_channels["1:0x0019"] - dev_registry = await ha_dev_reg.async_get_registry(hass) + dev_registry = dr.async_get(hass) entry = dev_registry.async_get(ota_zha_device.device_id) assert entry.sw_version is None diff --git a/tests/components/zha/test_device_action.py b/tests/components/zha/test_device_action.py index 1160995e8d7..4a777fcebb6 100644 --- a/tests/components/zha/test_device_action.py +++ b/tests/components/zha/test_device_action.py @@ -12,7 +12,7 @@ from homeassistant.components.device_automation import ( _async_get_device_automations as async_get_device_automations, ) from homeassistant.components.zha import DOMAIN -from homeassistant.helpers.device_registry import async_get_registry +from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component from tests.common import async_mock_service, mock_coro @@ -49,7 +49,7 @@ async def test_get_actions(hass, device_ias): ieee_address = str(device_ias[0].ieee) - ha_device_registry = await async_get_registry(hass) + ha_device_registry = dr.async_get(hass) reg_device = ha_device_registry.async_get_device({(DOMAIN, ieee_address)}) actions = await async_get_device_automations(hass, "action", reg_device.id) @@ -72,7 +72,7 @@ async def test_action(hass, device_ias): ieee_address = str(zha_device.ieee) - ha_device_registry = await async_get_registry(hass) + ha_device_registry = dr.async_get(hass) reg_device = ha_device_registry.async_get_device({(DOMAIN, ieee_address)}) with patch( diff --git a/tests/components/zha/test_device_trigger.py b/tests/components/zha/test_device_trigger.py index ec947846801..b2f964e2695 100644 --- a/tests/components/zha/test_device_trigger.py +++ b/tests/components/zha/test_device_trigger.py @@ -8,7 +8,7 @@ import zigpy.zcl.clusters.general as general import homeassistant.components.automation as automation import homeassistant.components.zha.core.device as zha_core_device -from homeassistant.helpers.device_registry import async_get_registry +from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -86,7 +86,7 @@ async def test_triggers(hass, mock_devices): ieee_address = str(zha_device.ieee) - ha_device_registry = await async_get_registry(hass) + ha_device_registry = dr.async_get(hass) reg_device = ha_device_registry.async_get_device({("zha", ieee_address)}) triggers = await async_get_device_automations(hass, "trigger", reg_device.id) @@ -144,7 +144,7 @@ async def test_no_triggers(hass, mock_devices): _, zha_device = mock_devices ieee_address = str(zha_device.ieee) - ha_device_registry = await async_get_registry(hass) + ha_device_registry = dr.async_get(hass) reg_device = ha_device_registry.async_get_device({("zha", ieee_address)}) triggers = await async_get_device_automations(hass, "trigger", reg_device.id) @@ -173,7 +173,7 @@ async def test_if_fires_on_event(hass, mock_devices, calls): } ieee_address = str(zha_device.ieee) - ha_device_registry = await async_get_registry(hass) + ha_device_registry = dr.async_get(hass) reg_device = ha_device_registry.async_get_device({("zha", ieee_address)}) assert await async_setup_component( @@ -282,7 +282,7 @@ async def test_exception_no_triggers(hass, mock_devices, calls, caplog): _, zha_device = mock_devices ieee_address = str(zha_device.ieee) - ha_device_registry = await async_get_registry(hass) + ha_device_registry = dr.async_get(hass) reg_device = ha_device_registry.async_get_device({("zha", ieee_address)}) await async_setup_component( @@ -324,7 +324,7 @@ async def test_exception_bad_trigger(hass, mock_devices, calls, caplog): } ieee_address = str(zha_device.ieee) - ha_device_registry = await async_get_registry(hass) + ha_device_registry = dr.async_get(hass) reg_device = ha_device_registry.async_get_device({("zha", ieee_address)}) await async_setup_component( diff --git a/tests/components/zha/test_discover.py b/tests/components/zha/test_discover.py index d4195c681e7..c84c22e3251 100644 --- a/tests/components/zha/test_discover.py +++ b/tests/components/zha/test_discover.py @@ -67,7 +67,7 @@ async def test_devices( zha_device_joined_restored, ): """Test device discovery.""" - entity_registry = await homeassistant.helpers.entity_registry.async_get_registry( + entity_registry = homeassistant.helpers.entity_registry.async_get( hass_disable_services ) diff --git a/tests/components/zone/test_init.py b/tests/components/zone/test_init.py index 07fd83cbe77..38846050643 100644 --- a/tests/components/zone/test_init.py +++ b/tests/components/zone/test_init.py @@ -15,7 +15,7 @@ from homeassistant.const import ( ) from homeassistant.core import Context from homeassistant.exceptions import Unauthorized -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from tests.common import MockConfigEntry @@ -244,7 +244,7 @@ async def test_core_config_update(hass): async def test_reload(hass, hass_admin_user, hass_read_only_user): """Test reload service.""" count_start = len(hass.states.async_entity_ids()) - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) assert await setup.async_setup_component( hass, @@ -365,7 +365,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is not None @@ -401,7 +401,7 @@ async def test_update(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state.attributes["latitude"] == 1 @@ -435,7 +435,7 @@ async def test_ws_create(hass, hass_ws_client, storage_setup): input_id = "new_input" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is None diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index d64e191e1f3..878513da579 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -18,8 +18,7 @@ from homeassistant.components.zwave import ( ) from homeassistant.components.zwave.binary_sensor import get_device from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME -from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg -from homeassistant.helpers.entity_registry import async_get_registry +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.common import async_fire_time_changed, mock_registry from tests.mock.zwave import MockEntityValues, MockNetwork, MockNode, MockValue @@ -472,8 +471,8 @@ async def test_value_entities(hass, mock_openzwave): assert hass.states.get("binary_sensor.mock_node_mock_value").state == "off" assert hass.states.get("binary_sensor.mock_node_mock_value_b").state == "off" - ent_reg = await async_get_registry(hass) - dev_reg = await get_dev_reg(hass) + ent_reg = er.async_get(hass) + dev_reg = dr.async_get(hass) entry = ent_reg.async_get("zwave.mock_node") assert entry is not None diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index 78bac5518a0..aa085836b65 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -23,7 +23,7 @@ from homeassistant.components.zwave_js.api import ( VALUE, ) from homeassistant.components.zwave_js.const import DOMAIN -from homeassistant.helpers.device_registry import async_get_registry +from homeassistant.helpers import device_registry as dr async def test_websocket_api(hass, integration, multisensor_6, hass_ws_client): @@ -197,7 +197,7 @@ async def test_remove_node( # Add mock node to controller client.driver.controller.nodes[67] = nortek_thermostat - dev_reg = await async_get_registry(hass) + dev_reg = dr.async_get(hass) # Create device registry entry for mock node device = dev_reg.async_get_or_create( diff --git a/tests/components/zwave_js/test_binary_sensor.py b/tests/components/zwave_js/test_binary_sensor.py index e8361d8b03e..ddfed9727e6 100644 --- a/tests/components/zwave_js/test_binary_sensor.py +++ b/tests/components/zwave_js/test_binary_sensor.py @@ -3,6 +3,7 @@ from zwave_js_server.event import Event from homeassistant.components.binary_sensor import DEVICE_CLASS_MOTION from homeassistant.const import DEVICE_CLASS_BATTERY, STATE_OFF, STATE_ON +from homeassistant.helpers import entity_registry as er from .common import ( DISABLED_LEGACY_BINARY_SENSOR, @@ -61,7 +62,7 @@ async def test_disabled_legacy_sensor(hass, multisensor_6, integration): """Test disabled legacy boolean binary sensor.""" # this node has Notification CC implemented so legacy binary sensor should be disabled - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entity_id = DISABLED_LEGACY_BINARY_SENSOR state = hass.states.get(entity_id) assert state is None diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 00574bd2d2f..3e7f79b9cec 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -17,7 +17,7 @@ from homeassistant.config_entries import ( ENTRY_STATE_SETUP_RETRY, ) from homeassistant.const import STATE_UNAVAILABLE -from homeassistant.helpers import device_registry, entity_registry +from homeassistant.helpers import device_registry as dr, entity_registry as er from .common import ( AIR_TEMPERATURE_SENSOR, @@ -117,7 +117,7 @@ async def test_unique_id_migration_dupes( hass, multisensor_6_state, client, integration ): """Test we remove an entity when .""" - ent_reg = entity_registry.async_get(hass) + ent_reg = er.async_get(hass) entity_name = AIR_TEMPERATURE_SENSOR.split(".")[1] @@ -168,7 +168,7 @@ async def test_unique_id_migration_dupes( async def test_unique_id_migration_v1(hass, multisensor_6_state, client, integration): """Test unique ID is migrated from old format to new (version 1).""" - ent_reg = entity_registry.async_get(hass) + ent_reg = er.async_get(hass) # Migrate version 1 entity_name = AIR_TEMPERATURE_SENSOR.split(".")[1] @@ -201,7 +201,7 @@ async def test_unique_id_migration_v1(hass, multisensor_6_state, client, integra async def test_unique_id_migration_v2(hass, multisensor_6_state, client, integration): """Test unique ID is migrated from old format to new (version 2).""" - ent_reg = entity_registry.async_get(hass) + ent_reg = er.async_get(hass) # Migrate version 2 ILLUMINANCE_SENSOR = "sensor.multisensor_6_illuminance" entity_name = ILLUMINANCE_SENSOR.split(".")[1] @@ -234,7 +234,7 @@ async def test_unique_id_migration_v2(hass, multisensor_6_state, client, integra async def test_unique_id_migration_v3(hass, multisensor_6_state, client, integration): """Test unique ID is migrated from old format to new (version 3).""" - ent_reg = entity_registry.async_get(hass) + ent_reg = er.async_get(hass) # Migrate version 2 ILLUMINANCE_SENSOR = "sensor.multisensor_6_illuminance" entity_name = ILLUMINANCE_SENSOR.split(".")[1] @@ -269,7 +269,7 @@ async def test_unique_id_migration_property_key_v1( hass, hank_binary_switch_state, client, integration ): """Test unique ID with property key is migrated from old format to new (version 1).""" - ent_reg = entity_registry.async_get(hass) + ent_reg = er.async_get(hass) SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed" entity_name = SENSOR_NAME.split(".")[1] @@ -304,7 +304,7 @@ async def test_unique_id_migration_property_key_v2( hass, hank_binary_switch_state, client, integration ): """Test unique ID with property key is migrated from old format to new (version 2).""" - ent_reg = entity_registry.async_get(hass) + ent_reg = er.async_get(hass) SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed" entity_name = SENSOR_NAME.split(".")[1] @@ -341,7 +341,7 @@ async def test_unique_id_migration_property_key_v3( hass, hank_binary_switch_state, client, integration ): """Test unique ID with property key is migrated from old format to new (version 3).""" - ent_reg = entity_registry.async_get(hass) + ent_reg = er.async_get(hass) SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed" entity_name = SENSOR_NAME.split(".")[1] @@ -376,7 +376,7 @@ async def test_unique_id_migration_notification_binary_sensor( hass, multisensor_6_state, client, integration ): """Test unique ID is migrated from old format to new for a notification binary sensor.""" - ent_reg = entity_registry.async_get(hass) + ent_reg = er.async_get(hass) entity_name = NOTIFICATION_MOTION_BINARY_SENSOR.split(".")[1] @@ -813,17 +813,13 @@ async def test_removed_device(hass, client, multiple_devices, integration): assert len(client.driver.controller.nodes) == 2 # Make sure there are the same number of devices - dev_reg = await device_registry.async_get_registry(hass) - device_entries = device_registry.async_entries_for_config_entry( - dev_reg, integration.entry_id - ) + dev_reg = dr.async_get(hass) + device_entries = dr.async_entries_for_config_entry(dev_reg, integration.entry_id) assert len(device_entries) == 2 # Check how many entities there are - ent_reg = await entity_registry.async_get_registry(hass) - entity_entries = entity_registry.async_entries_for_config_entry( - ent_reg, integration.entry_id - ) + ent_reg = er.async_get(hass) + entity_entries = er.async_entries_for_config_entry(ent_reg, integration.entry_id) assert len(entity_entries) == 24 # Remove a node and reload the entry @@ -833,21 +829,17 @@ async def test_removed_device(hass, client, multiple_devices, integration): # Assert that the node and all of it's entities were removed from the device and # entity registry - device_entries = device_registry.async_entries_for_config_entry( - dev_reg, integration.entry_id - ) + device_entries = dr.async_entries_for_config_entry(dev_reg, integration.entry_id) assert len(device_entries) == 1 - entity_entries = entity_registry.async_entries_for_config_entry( - ent_reg, integration.entry_id - ) + entity_entries = er.async_entries_for_config_entry(ent_reg, integration.entry_id) assert len(entity_entries) == 15 assert dev_reg.async_get_device({get_device_id(client, old_node)}) is None async def test_suggested_area(hass, client, eaton_rf9640_dimmer): """Test that suggested area works.""" - dev_reg = device_registry.async_get(hass) - ent_reg = entity_registry.async_get(hass) + dev_reg = dr.async_get(hass) + ent_reg = er.async_get(hass) entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"}) entry.add_to_hass(hass) diff --git a/tests/components/zwave_js/test_sensor.py b/tests/components/zwave_js/test_sensor.py index bd6fb9f2569..398d831e692 100644 --- a/tests/components/zwave_js/test_sensor.py +++ b/tests/components/zwave_js/test_sensor.py @@ -7,10 +7,7 @@ from homeassistant.const import ( POWER_WATT, TEMP_CELSIUS, ) -from homeassistant.helpers.entity_registry import ( - DISABLED_INTEGRATION, - async_get_registry, -) +from homeassistant.helpers import entity_registry as er from .common import ( AIR_TEMPERATURE_SENSOR, @@ -49,12 +46,12 @@ async def test_energy_sensors(hass, hank_binary_switch, integration): async def test_disabled_notification_sensor(hass, multisensor_6, integration): """Test sensor is created from Notification CC and is disabled.""" - ent_reg = await async_get_registry(hass) + ent_reg = er.async_get(hass) entity_entry = ent_reg.async_get(NOTIFICATION_MOTION_SENSOR) assert entity_entry assert entity_entry.disabled - assert entity_entry.disabled_by == DISABLED_INTEGRATION + assert entity_entry.disabled_by == er.DISABLED_INTEGRATION # Test enabling entity updated_entry = ent_reg.async_update_entity( diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index 965ebcd3e23..1d1b1a3cca3 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -797,7 +797,7 @@ async def test_cleanup_device_registry(hass, registry): identifiers={("something", "d4")}, config_entry_id="non_existing" ) - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = entity_registry.async_get(hass) ent_reg.async_get_or_create("light", "hue", "e1", device_id=d1.id) ent_reg.async_get_or_create("light", "hue", "e2", device_id=d1.id) ent_reg.async_get_or_create("light", "hue", "e3", device_id=d3.id) @@ -829,7 +829,7 @@ async def test_cleanup_device_registry_removes_expired_orphaned_devices(hass, re assert len(registry.devices) == 0 assert len(registry.deleted_devices) == 3 - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = entity_registry.async_get(hass) device_registry.async_cleanup(hass, registry, ent_reg) assert len(registry.devices) == 0 @@ -847,7 +847,6 @@ async def test_cleanup_device_registry_removes_expired_orphaned_devices(hass, re async def test_cleanup_startup(hass): """Test we run a cleanup on startup.""" hass.state = CoreState.not_running - await device_registry.async_get_registry(hass) with patch( "homeassistant.helpers.device_registry.Debouncer.async_call" diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index ab3e04843f9..3f26535de18 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -9,7 +9,11 @@ import pytest from homeassistant.const import PERCENTAGE from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError, PlatformNotReady -from homeassistant.helpers import entity_platform, entity_registry +from homeassistant.helpers import ( + device_registry as dr, + entity_platform, + entity_registry as er, +) from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.entity_component import ( DEFAULT_SCAN_INTERVAL, @@ -467,7 +471,7 @@ async def test_overriding_name_from_registry(hass): mock_registry( hass, { - "test_domain.world": entity_registry.RegistryEntry( + "test_domain.world": er.RegistryEntry( entity_id="test_domain.world", unique_id="1234", # Using component.async_add_entities is equal to platform "domain" @@ -499,12 +503,12 @@ async def test_registry_respect_entity_disabled(hass): mock_registry( hass, { - "test_domain.world": entity_registry.RegistryEntry( + "test_domain.world": er.RegistryEntry( entity_id="test_domain.world", unique_id="1234", # Using component.async_add_entities is equal to platform "domain" platform="test_platform", - disabled_by=entity_registry.DISABLED_USER, + disabled_by=er.DISABLED_USER, ) }, ) @@ -520,7 +524,7 @@ async def test_entity_registry_updates_name(hass): registry = mock_registry( hass, { - "test_domain.world": entity_registry.RegistryEntry( + "test_domain.world": er.RegistryEntry( entity_id="test_domain.world", unique_id="1234", # Using component.async_add_entities is equal to platform "domain" @@ -624,7 +628,7 @@ async def test_entity_registry_updates_entity_id(hass): registry = mock_registry( hass, { - "test_domain.world": entity_registry.RegistryEntry( + "test_domain.world": er.RegistryEntry( entity_id="test_domain.world", unique_id="1234", # Using component.async_add_entities is equal to platform "domain" @@ -656,14 +660,14 @@ async def test_entity_registry_updates_invalid_entity_id(hass): registry = mock_registry( hass, { - "test_domain.world": entity_registry.RegistryEntry( + "test_domain.world": er.RegistryEntry( entity_id="test_domain.world", unique_id="1234", # Using component.async_add_entities is equal to platform "domain" platform="test_platform", name="Some name", ), - "test_domain.existing": entity_registry.RegistryEntry( + "test_domain.existing": er.RegistryEntry( entity_id="test_domain.existing", unique_id="5678", platform="test_platform", @@ -703,7 +707,7 @@ async def test_entity_registry_updates_invalid_entity_id(hass): async def test_device_info_called(hass): """Test device info is forwarded correctly.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) via = registry.async_get_or_create( config_entry_id="123", connections=set(), @@ -763,7 +767,7 @@ async def test_device_info_called(hass): async def test_device_info_not_overrides(hass): """Test device info is forwarded correctly.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) device = registry.async_get_or_create( config_entry_id="bla", connections={("mac", "abcd")}, @@ -823,7 +827,7 @@ async def test_entity_disabled_by_integration(hass): assert entity_disabled.hass is None assert entity_disabled.platform is None - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry_default = registry.async_get_or_create(DOMAIN, DOMAIN, "default") assert entry_default.disabled_by is None @@ -845,7 +849,7 @@ async def test_entity_info_added_to_entity_registry(hass): await component.async_add_entities([entity_default]) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry_default = registry.async_get_or_create(DOMAIN, DOMAIN, "default") print(entry_default) diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 0a1a27efef5..d671aacebb3 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -5,7 +5,7 @@ import pytest from homeassistant.const import EVENT_HOMEASSISTANT_START, STATE_UNAVAILABLE from homeassistant.core import CoreState, callback, valid_entity_id -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from tests.common import ( MockConfigEntry, @@ -32,7 +32,7 @@ def update_events(hass): def async_capture(event): events.append(event.data) - hass.bus.async_listen(entity_registry.EVENT_ENTITY_REGISTRY_UPDATED, async_capture) + hass.bus.async_listen(er.EVENT_ENTITY_REGISTRY_UPDATED, async_capture) return events @@ -74,7 +74,7 @@ def test_get_or_create_updates_data(registry): capabilities={"max": 100}, supported_features=5, device_class="mock-device-class", - disabled_by=entity_registry.DISABLED_HASS, + disabled_by=er.DISABLED_HASS, unit_of_measurement="initial-unit_of_measurement", original_name="initial-original_name", original_icon="initial-original_icon", @@ -85,7 +85,7 @@ def test_get_or_create_updates_data(registry): assert orig_entry.capabilities == {"max": 100} assert orig_entry.supported_features == 5 assert orig_entry.device_class == "mock-device-class" - assert orig_entry.disabled_by == entity_registry.DISABLED_HASS + assert orig_entry.disabled_by == er.DISABLED_HASS assert orig_entry.unit_of_measurement == "initial-unit_of_measurement" assert orig_entry.original_name == "initial-original_name" assert orig_entry.original_icon == "initial-original_icon" @@ -101,7 +101,7 @@ def test_get_or_create_updates_data(registry): capabilities={"new-max": 100}, supported_features=10, device_class="new-mock-device-class", - disabled_by=entity_registry.DISABLED_USER, + disabled_by=er.DISABLED_USER, unit_of_measurement="updated-unit_of_measurement", original_name="updated-original_name", original_icon="updated-original_icon", @@ -116,7 +116,7 @@ def test_get_or_create_updates_data(registry): assert new_entry.original_name == "updated-original_name" assert new_entry.original_icon == "updated-original_icon" # Should not be updated - assert new_entry.disabled_by == entity_registry.DISABLED_HASS + assert new_entry.disabled_by == er.DISABLED_HASS def test_get_or_create_suggested_object_id_conflict_register(registry): @@ -162,7 +162,7 @@ async def test_loading_saving_data(hass, registry): capabilities={"max": 100}, supported_features=5, device_class="mock-device-class", - disabled_by=entity_registry.DISABLED_HASS, + disabled_by=er.DISABLED_HASS, original_name="Original Name", original_icon="hass:original-icon", ) @@ -173,7 +173,7 @@ async def test_loading_saving_data(hass, registry): assert len(registry.entities) == 2 # Now load written data in new registry - registry2 = entity_registry.EntityRegistry(hass) + registry2 = er.EntityRegistry(hass) await flush_store(registry._store) await registry2.async_load() @@ -187,7 +187,7 @@ async def test_loading_saving_data(hass, registry): assert new_entry2.device_id == "mock-dev-id" assert new_entry2.area_id == "mock-area-id" - assert new_entry2.disabled_by == entity_registry.DISABLED_HASS + assert new_entry2.disabled_by == er.DISABLED_HASS assert new_entry2.capabilities == {"max": 100} assert new_entry2.supported_features == 5 assert new_entry2.device_class == "mock-device-class" @@ -220,8 +220,8 @@ def test_is_registered(registry): @pytest.mark.parametrize("load_registries", [False]) async def test_loading_extra_values(hass, hass_storage): """Test we load extra data from the registry.""" - hass_storage[entity_registry.STORAGE_KEY] = { - "version": entity_registry.STORAGE_VERSION, + hass_storage[er.STORAGE_KEY] = { + "version": er.STORAGE_VERSION, "data": { "entities": [ { @@ -257,8 +257,8 @@ async def test_loading_extra_values(hass, hass_storage): }, } - await entity_registry.async_load(hass) - registry = entity_registry.async_get(hass) + await er.async_load(hass) + registry = er.async_get(hass) assert len(registry.entities) == 4 @@ -279,9 +279,9 @@ async def test_loading_extra_values(hass, hass_storage): "test", "super_platform", "disabled-user" ) assert entry_disabled_hass.disabled - assert entry_disabled_hass.disabled_by == entity_registry.DISABLED_HASS + assert entry_disabled_hass.disabled_by == er.DISABLED_HASS assert entry_disabled_user.disabled - assert entry_disabled_user.disabled_by == entity_registry.DISABLED_USER + assert entry_disabled_user.disabled_by == er.DISABLED_USER def test_async_get_entity_id(registry): @@ -367,8 +367,8 @@ async def test_migration(hass): with patch("os.path.isfile", return_value=True), patch("os.remove"), patch( "homeassistant.helpers.entity_registry.load_yaml", return_value=old_conf ): - await entity_registry.async_load(hass) - registry = entity_registry.async_get(hass) + await er.async_load(hass) + registry = er.async_get(hass) assert registry.async_is_registered("light.kitchen") entry = registry.async_get_or_create( @@ -384,8 +384,8 @@ async def test_migration(hass): async def test_loading_invalid_entity_id(hass, hass_storage): """Test we autofix invalid entity IDs.""" - hass_storage[entity_registry.STORAGE_KEY] = { - "version": entity_registry.STORAGE_VERSION, + hass_storage[er.STORAGE_KEY] = { + "version": er.STORAGE_VERSION, "data": { "entities": [ { @@ -408,7 +408,7 @@ async def test_loading_invalid_entity_id(hass, hass_storage): }, } - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) entity_invalid_middle = registry.async_get_or_create( "test", "super_platform", "id-invalid-middle" @@ -479,7 +479,7 @@ async def test_update_entity(registry): for attr_name, new_value in ( ("name", "new name"), ("icon", "new icon"), - ("disabled_by", entity_registry.DISABLED_USER), + ("disabled_by", er.DISABLED_USER), ): changes = {attr_name: new_value} updated_entry = registry.async_update_entity(entry.entity_id, **changes) @@ -531,7 +531,7 @@ async def test_restore_states(hass): """Test restoring states.""" hass.state = CoreState.not_running - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( "light", @@ -545,7 +545,7 @@ async def test_restore_states(hass): "hue", "5678", suggested_object_id="disabled", - disabled_by=entity_registry.DISABLED_HASS, + disabled_by=er.DISABLED_HASS, ) registry.async_get_or_create( "light", @@ -597,7 +597,7 @@ async def test_async_get_device_class_lookup(hass): """Test registry device class lookup.""" hass.state = CoreState.not_running - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) ent_reg.async_get_or_create( "binary_sensor", @@ -888,10 +888,10 @@ async def test_disabled_entities_excluded_from_entity_list(hass, registry): disabled_by="user", ) - entries = entity_registry.async_entries_for_device(registry, device_entry.id) + entries = er.async_entries_for_device(registry, device_entry.id) assert entries == [entry1] - entries = entity_registry.async_entries_for_device( + entries = er.async_entries_for_device( registry, device_entry.id, include_disabled_entities=True ) assert entries == [entry1, entry2] From ba2978c6933f13329ab4c59a7b19b2c9fd313482 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 9 Mar 2021 14:28:32 +0100 Subject: [PATCH 264/831] Update tests p-s to use async_get() instead of async_get_registry() (#47654) --- tests/components/person/test_init.py | 6 ++-- tests/components/powerwall/test_sensor.py | 4 +-- .../pvpc_hourly_pricing/test_config_flow.py | 4 +-- tests/components/rfxtrx/test_config_flow.py | 36 ++++++++----------- tests/components/rfxtrx/test_init.py | 7 ++-- tests/components/ring/test_light.py | 3 +- tests/components/ring/test_switch.py | 3 +- .../risco/test_alarm_control_panel.py | 13 +++---- tests/components/risco/test_binary_sensor.py | 9 ++--- tests/components/risco/test_sensor.py | 7 ++-- tests/components/roku/test_media_player.py | 7 ++-- tests/components/roku/test_remote.py | 3 +- .../ruckus_unleashed/test_device_tracker.py | 6 ++-- .../components/ruckus_unleashed/test_init.py | 3 +- tests/components/search/test_init.py | 15 +++++--- tests/components/sharkiq/test_vacuum.py | 5 +-- .../smartthings/test_binary_sensor.py | 5 +-- tests/components/smartthings/test_climate.py | 5 +-- tests/components/smartthings/test_cover.py | 5 +-- tests/components/smartthings/test_fan.py | 5 +-- tests/components/smartthings/test_light.py | 5 +-- tests/components/smartthings/test_lock.py | 5 +-- tests/components/smartthings/test_scene.py | 3 +- tests/components/smartthings/test_sensor.py | 5 +-- tests/components/smartthings/test_switch.py | 5 +-- tests/components/sonarr/test_sensor.py | 5 +-- tests/components/songpal/test_media_player.py | 4 +-- tests/components/sonos/test_media_player.py | 3 +- .../surepetcare/test_binary_sensor.py | 3 +- 29 files changed, 103 insertions(+), 86 deletions(-) diff --git a/tests/components/person/test_init.py b/tests/components/person/test_init.py index 86ec71c1452..4e6793c07bb 100644 --- a/tests/components/person/test_init.py +++ b/tests/components/person/test_init.py @@ -22,7 +22,7 @@ from homeassistant.const import ( STATE_UNKNOWN, ) from homeassistant.core import Context, CoreState, State -from homeassistant.helpers import collection, entity_registry +from homeassistant.helpers import collection, entity_registry as er from homeassistant.setup import async_setup_component from tests.common import mock_component, mock_restore_cache @@ -589,7 +589,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): assert resp["success"] assert len(hass.states.async_entity_ids("person")) == 0 - ent_reg = await hass.helpers.entity_registry.async_get_registry() + ent_reg = er.async_get(hass) assert not ent_reg.async_is_registered("person.tracked_person") @@ -681,7 +681,7 @@ async def test_update_person_when_user_removed( async def test_removing_device_tracker(hass, storage_setup): """Test we automatically remove removed device trackers.""" storage_collection = hass.data[DOMAIN][1] - reg = await entity_registry.async_get_registry(hass) + reg = er.async_get(hass) entry = reg.async_get_or_create( "device_tracker", "mobile_app", "bla", suggested_object_id="pixel" ) diff --git a/tests/components/powerwall/test_sensor.py b/tests/components/powerwall/test_sensor.py index eff4631c7e5..32c7da9c78e 100644 --- a/tests/components/powerwall/test_sensor.py +++ b/tests/components/powerwall/test_sensor.py @@ -1,9 +1,9 @@ """The sensor tests for the powerwall platform.""" - from unittest.mock import patch from homeassistant.components.powerwall.const import DOMAIN from homeassistant.const import CONF_IP_ADDRESS, PERCENTAGE +from homeassistant.helpers import device_registry as dr from .mocks import _mock_powerwall_with_fixtures @@ -26,7 +26,7 @@ async def test_sensors(hass): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) reg_device = device_registry.async_get_device( identifiers={("powerwall", "TG0123456789AB_TG9876543210BA")}, ) diff --git a/tests/components/pvpc_hourly_pricing/test_config_flow.py b/tests/components/pvpc_hourly_pricing/test_config_flow.py index ad321181ec4..038b106f6c8 100644 --- a/tests/components/pvpc_hourly_pricing/test_config_flow.py +++ b/tests/components/pvpc_hourly_pricing/test_config_flow.py @@ -7,7 +7,7 @@ from pytz import timezone from homeassistant import data_entry_flow from homeassistant.components.pvpc_hourly_pricing import ATTR_TARIFF, DOMAIN from homeassistant.const import CONF_NAME -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from .conftest import check_valid_state @@ -60,7 +60,7 @@ async def test_config_flow( assert pvpc_aioclient_mock.call_count == 1 # Check removal - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry_entity = registry.async_get("sensor.test") assert await hass.config_entries.async_remove(registry_entity.config_entry_id) diff --git a/tests/components/rfxtrx/test_config_flow.py b/tests/components/rfxtrx/test_config_flow.py index e39c766bfd2..1c16507f960 100644 --- a/tests/components/rfxtrx/test_config_flow.py +++ b/tests/components/rfxtrx/test_config_flow.py @@ -6,13 +6,7 @@ import serial.tools.list_ports from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.rfxtrx import DOMAIN, config_flow -from homeassistant.helpers.device_registry import ( - async_entries_for_config_entry, - async_get_registry as async_get_device_registry, -) -from homeassistant.helpers.entity_registry import ( - async_get_registry as async_get_entity_registry, -) +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.common import MockConfigEntry @@ -610,8 +604,8 @@ async def test_options_add_remove_device(hass): assert state.state == "off" assert state.attributes.get("friendly_name") == "AC 213c7f2:48" - device_registry = await async_get_device_registry(hass) - device_entries = async_entries_for_config_entry(device_registry, entry.entry_id) + device_registry = dr.async_get(hass) + device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) assert device_entries[0].id @@ -704,8 +698,8 @@ async def test_options_replace_sensor_device(hass): ) assert state - device_registry = await async_get_device_registry(hass) - device_entries = async_entries_for_config_entry(device_registry, entry.entry_id) + device_registry = dr.async_get(hass) + device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) old_device = next( ( @@ -751,7 +745,7 @@ async def test_options_replace_sensor_device(hass): await hass.async_block_till_done() - entity_registry = await async_get_entity_registry(hass) + entity_registry = er.async_get(hass) entry = entity_registry.async_get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_rssi_numeric" @@ -843,8 +837,8 @@ async def test_options_replace_control_device(hass): state = hass.states.get("switch.ac_1118cdea_2") assert state - device_registry = await async_get_device_registry(hass) - device_entries = async_entries_for_config_entry(device_registry, entry.entry_id) + device_registry = dr.async_get(hass) + device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) old_device = next( ( @@ -890,7 +884,7 @@ async def test_options_replace_control_device(hass): await hass.async_block_till_done() - entity_registry = await async_get_entity_registry(hass) + entity_registry = er.async_get(hass) entry = entity_registry.async_get("binary_sensor.ac_118cdea_2") assert entry @@ -941,8 +935,8 @@ async def test_options_remove_multiple_devices(hass): state = hass.states.get("binary_sensor.ac_1118cdea_2") assert state - device_registry = await async_get_device_registry(hass) - device_entries = async_entries_for_config_entry(device_registry, entry.entry_id) + device_registry = dr.async_get(hass) + device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) assert len(device_entries) == 3 @@ -1061,8 +1055,8 @@ async def test_options_add_and_configure_device(hass): assert state.state == "off" assert state.attributes.get("friendly_name") == "PT2262 22670e" - device_registry = await async_get_device_registry(hass) - device_entries = async_entries_for_config_entry(device_registry, entry.entry_id) + device_registry = dr.async_get(hass) + device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) assert device_entries[0].id @@ -1151,8 +1145,8 @@ async def test_options_configure_rfy_cover_device(hass): assert entry.data["devices"]["071a000001020301"]["venetian_blind_mode"] == "EU" - device_registry = await async_get_device_registry(hass) - device_entries = async_entries_for_config_entry(device_registry, entry.entry_id) + device_registry = dr.async_get(hass) + device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) assert device_entries[0].id diff --git a/tests/components/rfxtrx/test_init.py b/tests/components/rfxtrx/test_init.py index 9112082a956..b3829e2b5cc 100644 --- a/tests/components/rfxtrx/test_init.py +++ b/tests/components/rfxtrx/test_init.py @@ -5,10 +5,7 @@ from unittest.mock import call from homeassistant.components.rfxtrx import DOMAIN from homeassistant.components.rfxtrx.const import EVENT_RFXTRX_EVENT from homeassistant.core import callback -from homeassistant.helpers.device_registry import ( - DeviceRegistry, - async_get_registry as async_get_device_registry, -) +from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry @@ -79,7 +76,7 @@ async def test_fire_event(hass, rfxtrx): await hass.async_block_till_done() await hass.async_start() - device_registry: DeviceRegistry = await async_get_device_registry(hass) + device_registry: dr.DeviceRegistry = dr.async_get(hass) calls = [] diff --git a/tests/components/ring/test_light.py b/tests/components/ring/test_light.py index 5a2687e4cf9..603c0bf3e84 100644 --- a/tests/components/ring/test_light.py +++ b/tests/components/ring/test_light.py @@ -1,5 +1,6 @@ """The tests for the Ring light platform.""" from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -9,7 +10,7 @@ from tests.common import load_fixture async def test_entity_registry(hass, requests_mock): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, LIGHT_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("light.front_light") assert entry.unique_id == 765432 diff --git a/tests/components/ring/test_switch.py b/tests/components/ring/test_switch.py index 6979fafc01d..ab81ed5c69a 100644 --- a/tests/components/ring/test_switch.py +++ b/tests/components/ring/test_switch.py @@ -1,5 +1,6 @@ """The tests for the Ring switch platform.""" from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -9,7 +10,7 @@ from tests.common import load_fixture async def test_entity_registry(hass, requests_mock): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, SWITCH_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("switch.front_siren") assert entry.unique_id == "765432-siren" diff --git a/tests/components/risco/test_alarm_control_panel.py b/tests/components/risco/test_alarm_control_panel.py index a5a16379fe9..23c4de4c7aa 100644 --- a/tests/components/risco/test_alarm_control_panel.py +++ b/tests/components/risco/test_alarm_control_panel.py @@ -27,6 +27,7 @@ from homeassistant.const import ( STATE_ALARM_TRIGGERED, STATE_UNKNOWN, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.entity_component import async_update_entity from .util import TEST_CONFIG, TEST_SITE_UUID, setup_risco @@ -114,7 +115,7 @@ async def test_cannot_connect(hass): config_entry.add_to_hass(hass) await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert not registry.async_is_registered(FIRST_ENTITY_ID) assert not registry.async_is_registered(SECOND_ENTITY_ID) @@ -130,14 +131,14 @@ async def test_unauthorized(hass): config_entry.add_to_hass(hass) await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert not registry.async_is_registered(FIRST_ENTITY_ID) assert not registry.async_is_registered(SECOND_ENTITY_ID) async def test_setup(hass, two_part_alarm): """Test entity setup.""" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert not registry.async_is_registered(FIRST_ENTITY_ID) assert not registry.async_is_registered(SECOND_ENTITY_ID) @@ -147,7 +148,7 @@ async def test_setup(hass, two_part_alarm): assert registry.async_is_registered(FIRST_ENTITY_ID) assert registry.async_is_registered(SECOND_ENTITY_ID) - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) device = registry.async_get_device({(DOMAIN, TEST_SITE_UUID + "_0")}) assert device is not None assert device.manufacturer == "Risco" @@ -251,7 +252,7 @@ async def test_sets_custom_mapping(hass, two_part_alarm): """Test settings the various modes when mapping some states.""" await setup_risco(hass, [], CUSTOM_MAPPING_OPTIONS) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entity = registry.async_get(FIRST_ENTITY_ID) assert entity.supported_features == EXPECTED_FEATURES @@ -277,7 +278,7 @@ async def test_sets_full_custom_mapping(hass, two_part_alarm): """Test settings the various modes when mapping all states.""" await setup_risco(hass, [], FULL_CUSTOM_MAPPING) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entity = registry.async_get(FIRST_ENTITY_ID) assert ( entity.supported_features == EXPECTED_FEATURES | SUPPORT_ALARM_ARM_CUSTOM_BYPASS diff --git a/tests/components/risco/test_binary_sensor.py b/tests/components/risco/test_binary_sensor.py index 7533512e3ef..7f68db7939d 100644 --- a/tests/components/risco/test_binary_sensor.py +++ b/tests/components/risco/test_binary_sensor.py @@ -4,6 +4,7 @@ from unittest.mock import PropertyMock, patch from homeassistant.components.risco import CannotConnectError, UnauthorizedError from homeassistant.components.risco.const import DOMAIN from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.entity_component import async_update_entity from .util import TEST_CONFIG, TEST_SITE_UUID, setup_risco @@ -26,7 +27,7 @@ async def test_cannot_connect(hass): config_entry.add_to_hass(hass) await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert not registry.async_is_registered(FIRST_ENTITY_ID) assert not registry.async_is_registered(SECOND_ENTITY_ID) @@ -42,14 +43,14 @@ async def test_unauthorized(hass): config_entry.add_to_hass(hass) await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert not registry.async_is_registered(FIRST_ENTITY_ID) assert not registry.async_is_registered(SECOND_ENTITY_ID) async def test_setup(hass, two_zone_alarm): # noqa: F811 """Test entity setup.""" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert not registry.async_is_registered(FIRST_ENTITY_ID) assert not registry.async_is_registered(SECOND_ENTITY_ID) @@ -59,7 +60,7 @@ async def test_setup(hass, two_zone_alarm): # noqa: F811 assert registry.async_is_registered(FIRST_ENTITY_ID) assert registry.async_is_registered(SECOND_ENTITY_ID) - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) device = registry.async_get_device({(DOMAIN, TEST_SITE_UUID + "_zone_0")}) assert device is not None assert device.manufacturer == "Risco" diff --git a/tests/components/risco/test_sensor.py b/tests/components/risco/test_sensor.py index 3d449f10e46..eb7ed990bd9 100644 --- a/tests/components/risco/test_sensor.py +++ b/tests/components/risco/test_sensor.py @@ -8,6 +8,7 @@ from homeassistant.components.risco import ( UnauthorizedError, ) from homeassistant.components.risco.const import DOMAIN +from homeassistant.helpers import entity_registry as er from homeassistant.util import dt from .util import TEST_CONFIG, TEST_SITE_UUID, setup_risco @@ -120,7 +121,7 @@ async def test_cannot_connect(hass): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) for id in ENTITY_IDS.values(): assert not registry.async_is_registered(id) @@ -137,7 +138,7 @@ async def test_unauthorized(hass): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) for id in ENTITY_IDS.values(): assert not registry.async_is_registered(id) @@ -167,7 +168,7 @@ def _check_state(hass, category, entity_id): async def test_setup(hass, two_zone_alarm): # noqa: F811 """Test entity setup.""" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) for id in ENTITY_IDS.values(): assert not registry.async_is_registered(id) diff --git a/tests/components/roku/test_media_player.py b/tests/components/roku/test_media_player.py index 09124ecdf37..dca336be076 100644 --- a/tests/components/roku/test_media_player.py +++ b/tests/components/roku/test_media_player.py @@ -61,6 +61,7 @@ from homeassistant.const import ( STATE_STANDBY, STATE_UNAVAILABLE, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util @@ -85,7 +86,7 @@ async def test_setup( """Test setup with basic config.""" await setup_integration(hass, aioclient_mock) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) main = entity_registry.async_get(MAIN_ENTITY_ID) assert hass.states.get(MAIN_ENTITY_ID) @@ -117,7 +118,7 @@ async def test_tv_setup( unique_id=TV_SERIAL, ) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) tv = entity_registry.async_get(TV_ENTITY_ID) assert hass.states.get(TV_ENTITY_ID) @@ -321,7 +322,7 @@ async def test_tv_device_registry( unique_id=TV_SERIAL, ) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) reg_device = device_registry.async_get_device(identifiers={(DOMAIN, TV_SERIAL)}) assert reg_device.model == TV_MODEL diff --git a/tests/components/roku/test_remote.py b/tests/components/roku/test_remote.py index 4122e0af1d1..363c7134bad 100644 --- a/tests/components/roku/test_remote.py +++ b/tests/components/roku/test_remote.py @@ -7,6 +7,7 @@ from homeassistant.components.remote import ( SERVICE_SEND_COMMAND, ) from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.typing import HomeAssistantType from tests.components.roku import UPNP_SERIAL, setup_integration @@ -31,7 +32,7 @@ async def test_unique_id( """Test unique id.""" await setup_integration(hass, aioclient_mock) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) main = entity_registry.async_get(MAIN_ENTITY_ID) assert main.unique_id == UPNP_SERIAL diff --git a/tests/components/ruckus_unleashed/test_device_tracker.py b/tests/components/ruckus_unleashed/test_device_tracker.py index 37bae441abf..92382007273 100644 --- a/tests/components/ruckus_unleashed/test_device_tracker.py +++ b/tests/components/ruckus_unleashed/test_device_tracker.py @@ -5,7 +5,7 @@ from unittest.mock import patch from homeassistant.components.ruckus_unleashed import API_MAC, DOMAIN from homeassistant.components.ruckus_unleashed.const import API_AP, API_ID, API_NAME from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_UNAVAILABLE -from homeassistant.helpers import entity_registry +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.util import utcnow @@ -80,7 +80,7 @@ async def test_restoring_clients(hass): entry = mock_config_entry() entry.add_to_hass(hass) - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( "device_tracker", DOMAIN, @@ -120,7 +120,7 @@ async def test_client_device_setup(hass): router_info = DEFAULT_AP_INFO[API_AP][API_ID]["1"] - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) client_device = device_registry.async_get_device( identifiers={}, connections={(CONNECTION_NETWORK_MAC, TEST_CLIENT[API_MAC])}, diff --git a/tests/components/ruckus_unleashed/test_init.py b/tests/components/ruckus_unleashed/test_init.py index 7b379a51011..0340f72891a 100644 --- a/tests/components/ruckus_unleashed/test_init.py +++ b/tests/components/ruckus_unleashed/test_init.py @@ -19,6 +19,7 @@ from homeassistant.config_entries import ( ENTRY_STATE_NOT_LOADED, ENTRY_STATE_SETUP_RETRY, ) +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from tests.components.ruckus_unleashed import ( @@ -64,7 +65,7 @@ async def test_router_device_setup(hass): device_info = DEFAULT_AP_INFO[API_AP][API_ID]["1"] - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get_device( identifiers={(CONNECTION_NETWORK_MAC, device_info[API_MAC])}, connections={(CONNECTION_NETWORK_MAC, device_info[API_MAC])}, diff --git a/tests/components/search/test_init.py b/tests/components/search/test_init.py index 6f6a7793981..57d2c365e71 100644 --- a/tests/components/search/test_init.py +++ b/tests/components/search/test_init.py @@ -1,5 +1,10 @@ """Tests for Search integration.""" from homeassistant.components import search +from homeassistant.helpers import ( + area_registry as ar, + device_registry as dr, + entity_registry as er, +) from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry @@ -8,9 +13,9 @@ from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: async def test_search(hass): """Test that search works.""" - area_reg = await hass.helpers.area_registry.async_get_registry() - device_reg = await hass.helpers.device_registry.async_get_registry() - entity_reg = await hass.helpers.entity_registry.async_get_registry() + area_reg = ar.async_get(hass) + device_reg = dr.async_get(hass) + entity_reg = er.async_get(hass) living_room_area = area_reg.async_create("Living Room") @@ -275,8 +280,8 @@ async def test_ws_api(hass, hass_ws_client): """Test WS API.""" assert await async_setup_component(hass, "search", {}) - area_reg = await hass.helpers.area_registry.async_get_registry() - device_reg = await hass.helpers.device_registry.async_get_registry() + area_reg = ar.async_get(hass) + device_reg = dr.async_get(hass) kitchen_area = area_reg.async_create("Kitchen") diff --git a/tests/components/sharkiq/test_vacuum.py b/tests/components/sharkiq/test_vacuum.py index edce5d75b2c..fb35d9d4cd2 100644 --- a/tests/components/sharkiq/test_vacuum.py +++ b/tests/components/sharkiq/test_vacuum.py @@ -46,6 +46,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component from .const import ( @@ -128,7 +129,7 @@ async def setup_integration(hass): async def test_simple_properties(hass: HomeAssistant): """Test that simple properties work as intended.""" state = hass.states.get(VAC_ENTITY_ID) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entity = registry.async_get(VAC_ENTITY_ID) assert entity @@ -199,7 +200,7 @@ async def test_device_properties( hass: HomeAssistant, device_property: str, target_value: str ): """Test device properties.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) device = registry.async_get_device({(DOMAIN, "AC000Wxxxxxxxxx")}) assert getattr(device, device_property) == target_value diff --git a/tests/components/smartthings/test_binary_sensor.py b/tests/components/smartthings/test_binary_sensor.py index e10d63a2e07..7e8ab7d2c9b 100644 --- a/tests/components/smartthings/test_binary_sensor.py +++ b/tests/components/smartthings/test_binary_sensor.py @@ -13,6 +13,7 @@ from homeassistant.components.binary_sensor import ( from homeassistant.components.smartthings import binary_sensor from homeassistant.components.smartthings.const import DOMAIN, SIGNAL_SMARTTHINGS_UPDATE from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_UNAVAILABLE +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -49,8 +50,8 @@ async def test_entity_and_device_attributes(hass, device_factory): device = device_factory( "Motion Sensor 1", [Capability.motion_sensor], {Attribute.motion: "inactive"} ) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - device_registry = await hass.helpers.device_registry.async_get_registry() + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) # Act await setup_platform(hass, BINARY_SENSOR_DOMAIN, devices=[device]) # Assert diff --git a/tests/components/smartthings/test_climate.py b/tests/components/smartthings/test_climate.py index 11f7695a775..dc8f2acc9fa 100644 --- a/tests/components/smartthings/test_climate.py +++ b/tests/components/smartthings/test_climate.py @@ -44,6 +44,7 @@ from homeassistant.const import ( SERVICE_TURN_ON, STATE_UNKNOWN, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from .conftest import setup_platform @@ -569,8 +570,8 @@ async def test_set_turn_on(hass, air_conditioner): async def test_entity_and_device_attributes(hass, thermostat): """Test the attributes of the entries are correct.""" await setup_platform(hass, CLIMATE_DOMAIN, devices=[thermostat]) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - device_registry = await hass.helpers.device_registry.async_get_registry() + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) entry = entity_registry.async_get("climate.thermostat") assert entry diff --git a/tests/components/smartthings/test_cover.py b/tests/components/smartthings/test_cover.py index 178c905208e..44c2b2f9285 100644 --- a/tests/components/smartthings/test_cover.py +++ b/tests/components/smartthings/test_cover.py @@ -20,6 +20,7 @@ from homeassistant.components.cover import ( ) from homeassistant.components.smartthings.const import DOMAIN, SIGNAL_SMARTTHINGS_UPDATE from homeassistant.const import ATTR_BATTERY_LEVEL, ATTR_ENTITY_ID, STATE_UNAVAILABLE +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -31,8 +32,8 @@ async def test_entity_and_device_attributes(hass, device_factory): device = device_factory( "Garage", [Capability.garage_door_control], {Attribute.door: "open"} ) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - device_registry = await hass.helpers.device_registry.async_get_registry() + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) # Act await setup_platform(hass, COVER_DOMAIN, devices=[device]) # Assert diff --git a/tests/components/smartthings/test_fan.py b/tests/components/smartthings/test_fan.py index 1f837d58bf8..6cdfa5b8917 100644 --- a/tests/components/smartthings/test_fan.py +++ b/tests/components/smartthings/test_fan.py @@ -22,6 +22,7 @@ from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, STATE_UNAVAILABLE, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -59,8 +60,8 @@ async def test_entity_and_device_attributes(hass, device_factory): ) # Act await setup_platform(hass, FAN_DOMAIN, devices=[device]) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - device_registry = await hass.helpers.device_registry.async_get_registry() + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) # Assert entry = entity_registry.async_get("fan.fan_1") assert entry diff --git a/tests/components/smartthings/test_light.py b/tests/components/smartthings/test_light.py index f6d7d8dd9f4..c9dbb094161 100644 --- a/tests/components/smartthings/test_light.py +++ b/tests/components/smartthings/test_light.py @@ -24,6 +24,7 @@ from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, STATE_UNAVAILABLE, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -110,8 +111,8 @@ async def test_entity_and_device_attributes(hass, device_factory): """Test the attributes of the entity are correct.""" # Arrange device = device_factory("Light 1", [Capability.switch, Capability.switch_level]) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - device_registry = await hass.helpers.device_registry.async_get_registry() + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) # Act await setup_platform(hass, LIGHT_DOMAIN, devices=[device]) # Assert diff --git a/tests/components/smartthings/test_lock.py b/tests/components/smartthings/test_lock.py index 185eae22ccf..1168108656e 100644 --- a/tests/components/smartthings/test_lock.py +++ b/tests/components/smartthings/test_lock.py @@ -10,6 +10,7 @@ from pysmartthings.device import Status from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.components.smartthings.const import DOMAIN, SIGNAL_SMARTTHINGS_UPDATE from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -19,8 +20,8 @@ async def test_entity_and_device_attributes(hass, device_factory): """Test the attributes of the entity are correct.""" # Arrange device = device_factory("Lock_1", [Capability.lock], {Attribute.lock: "unlocked"}) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - device_registry = await hass.helpers.device_registry.async_get_registry() + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) # Act await setup_platform(hass, LOCK_DOMAIN, devices=[device]) # Assert diff --git a/tests/components/smartthings/test_scene.py b/tests/components/smartthings/test_scene.py index 6ab4bc08080..647389eeb42 100644 --- a/tests/components/smartthings/test_scene.py +++ b/tests/components/smartthings/test_scene.py @@ -6,6 +6,7 @@ real HTTP calls are not initiated during testing. """ from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON, STATE_UNAVAILABLE +from homeassistant.helpers import entity_registry as er from .conftest import setup_platform @@ -13,7 +14,7 @@ from .conftest import setup_platform async def test_entity_and_device_attributes(hass, scene): """Test the attributes of the entity are correct.""" # Arrange - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Act await setup_platform(hass, SCENE_DOMAIN, scenes=[scene]) # Assert diff --git a/tests/components/smartthings/test_sensor.py b/tests/components/smartthings/test_sensor.py index 53f4b2c7244..0f148b8931f 100644 --- a/tests/components/smartthings/test_sensor.py +++ b/tests/components/smartthings/test_sensor.py @@ -16,6 +16,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -78,8 +79,8 @@ async def test_entity_and_device_attributes(hass, device_factory): """Test the attributes of the entity are correct.""" # Arrange device = device_factory("Sensor 1", [Capability.battery], {Attribute.battery: 100}) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - device_registry = await hass.helpers.device_registry.async_get_registry() + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) # Act await setup_platform(hass, SENSOR_DOMAIN, devices=[device]) # Assert diff --git a/tests/components/smartthings/test_switch.py b/tests/components/smartthings/test_switch.py index 27ed5050bee..21d508bcbc2 100644 --- a/tests/components/smartthings/test_switch.py +++ b/tests/components/smartthings/test_switch.py @@ -13,6 +13,7 @@ from homeassistant.components.switch import ( DOMAIN as SWITCH_DOMAIN, ) from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -22,8 +23,8 @@ async def test_entity_and_device_attributes(hass, device_factory): """Test the attributes of the entity are correct.""" # Arrange device = device_factory("Switch_1", [Capability.switch], {Attribute.switch: "on"}) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - device_registry = await hass.helpers.device_registry.async_get_registry() + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) # Act await setup_platform(hass, SWITCH_DOMAIN, devices=[device]) # Assert diff --git a/tests/components/sonarr/test_sensor.py b/tests/components/sonarr/test_sensor.py index 0b306dc8240..3a11688a56f 100644 --- a/tests/components/sonarr/test_sensor.py +++ b/tests/components/sonarr/test_sensor.py @@ -12,6 +12,7 @@ from homeassistant.const import ( DATA_GIGABYTES, STATE_UNAVAILABLE, ) +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util @@ -27,7 +28,7 @@ async def test_sensors( ) -> None: """Test the creation and values of the sensors.""" entry = await setup_integration(hass, aioclient_mock, skip_entry_setup=True) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) # Pre-create registry entries for disabled by default sensors sensors = { @@ -107,7 +108,7 @@ async def test_disabled_by_default_sensors( ) -> None: """Test the disabled by default sensors.""" await setup_integration(hass, aioclient_mock) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) print(registry.entities) state = hass.states.get(entity_id) diff --git a/tests/components/songpal/test_media_player.py b/tests/components/songpal/test_media_player.py index aff79ef62ff..0fd5644e794 100644 --- a/tests/components/songpal/test_media_player.py +++ b/tests/components/songpal/test_media_player.py @@ -113,7 +113,7 @@ async def test_state(hass): assert attributes["source"] == "title2" assert attributes["supported_features"] == SUPPORT_SONGPAL - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) device = device_registry.async_get_device(identifiers={(songpal.DOMAIN, MAC)}) assert device.connections == {(dr.CONNECTION_NETWORK_MAC, MAC)} assert device.manufacturer == "Sony Corporation" @@ -121,7 +121,7 @@ async def test_state(hass): assert device.sw_version == SW_VERSION assert device.model == MODEL - entity_registry = await er.async_get_registry(hass) + entity_registry = er.async_get(hass) entity = entity_registry.async_get(ENTITY_ID) assert entity.unique_id == MAC diff --git a/tests/components/sonos/test_media_player.py b/tests/components/sonos/test_media_player.py index ba9ba1c6db6..466a0df5905 100644 --- a/tests/components/sonos/test_media_player.py +++ b/tests/components/sonos/test_media_player.py @@ -4,6 +4,7 @@ import pytest from homeassistant.components.sonos import DOMAIN, media_player from homeassistant.core import Context from homeassistant.exceptions import Unauthorized +from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component @@ -48,7 +49,7 @@ async def test_device_registry(hass, config_entry, config, soco): """Test sonos device registered in the device registry.""" await setup_platform(hass, config_entry, config) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) reg_device = device_registry.async_get_device( identifiers={("sonos", "RINCON_test")} ) diff --git a/tests/components/surepetcare/test_binary_sensor.py b/tests/components/surepetcare/test_binary_sensor.py index ad723f707f5..67755dbd645 100644 --- a/tests/components/surepetcare/test_binary_sensor.py +++ b/tests/components/surepetcare/test_binary_sensor.py @@ -2,6 +2,7 @@ from surepy import MESTART_RESOURCE from homeassistant.components.surepetcare.const import DOMAIN +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from . import MOCK_API_DATA, MOCK_CONFIG, _patch_sensor_setup @@ -24,7 +25,7 @@ async def test_binary_sensors(hass, surepetcare) -> None: assert await async_setup_component(hass, DOMAIN, MOCK_CONFIG) await hass.async_block_till_done() - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) state_entity_ids = hass.states.async_entity_ids() for entity_id, unique_id in EXPECTED_ENTITY_IDS.items(): From 87e7cebd36ce6be6d79d3ff35b60d1ebf33edcd1 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 9 Mar 2021 14:31:17 +0100 Subject: [PATCH 265/831] Update tests c-h to use registry async_get (#47652) --- tests/components/cast/test_media_player.py | 21 ++++++++++--------- tests/components/config/test_automation.py | 3 ++- tests/components/config/test_scene.py | 3 ++- tests/components/coronavirus/test_init.py | 8 +++---- tests/components/counter/test_init.py | 8 +++---- tests/components/deconz/test_binary_sensor.py | 4 ++-- tests/components/deconz/test_init.py | 6 +++--- tests/components/deconz/test_services.py | 6 +++--- tests/components/directv/test_media_player.py | 3 ++- tests/components/directv/test_remote.py | 3 ++- tests/components/dsmr/test_sensor.py | 5 +++-- tests/components/dynalite/common.py | 4 ++-- tests/components/dyson/test_climate.py | 6 +++--- tests/components/dyson/test_fan.py | 10 ++++----- tests/components/dyson/test_sensor.py | 6 +++--- tests/components/dyson/test_vacuum.py | 6 +++--- tests/components/elgato/test_light.py | 3 ++- tests/components/gdacs/test_geo_location.py | 4 ++-- .../generic_thermostat/test_climate.py | 3 ++- tests/components/geofency/test_init.py | 5 +++-- .../geonetnz_quakes/test_geo_location.py | 4 ++-- tests/components/gios/test_air_quality.py | 5 +++-- tests/components/gpslogger/test_init.py | 5 +++-- tests/components/harmony/test_init.py | 12 +++++------ tests/components/heos/test_media_player.py | 5 +++-- tests/components/homekit/test_type_covers.py | 6 +++--- tests/components/homekit/test_type_fans.py | 4 ++-- tests/components/homekit/test_type_lights.py | 4 ++-- .../homekit/test_type_media_players.py | 4 ++-- tests/components/homekit/test_type_sensors.py | 4 ++-- .../homekit/test_type_thermostats.py | 6 +++--- .../specific_devices/test_anker_eufycam.py | 5 +++-- .../specific_devices/test_aqara_gateway.py | 5 +++-- .../specific_devices/test_aqara_switch.py | 4 +++- .../specific_devices/test_ecobee3.py | 11 +++++----- .../specific_devices/test_ecobee_occupancy.py | 6 ++++-- .../test_homeassistant_bridge.py | 5 +++-- .../specific_devices/test_hue_bridge.py | 6 ++++-- .../specific_devices/test_koogeek_ls1.py | 5 +++-- .../specific_devices/test_koogeek_p1eu.py | 6 ++++-- .../specific_devices/test_lennox_e30.py | 5 +++-- .../specific_devices/test_lg_tv.py | 5 +++-- .../test_rainmachine_pro_8.py | 6 ++++-- .../test_simpleconnect_fan.py | 5 +++-- .../specific_devices/test_velux_gateway.py | 5 +++-- .../homekit_controller/test_device_trigger.py | 17 ++++++++------- .../homematicip_cloud/test_device.py | 16 +++++++------- tests/components/hue/test_light.py | 11 +++------- tests/components/hyperion/test_light.py | 4 ++-- 49 files changed, 165 insertions(+), 138 deletions(-) diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 27f817b6771..a3d5676a878 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -28,6 +28,7 @@ from homeassistant.components.media_player.const import ( from homeassistant.config import async_process_ha_core_config from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.exceptions import PlatformNotReady +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import HomeAssistantType from homeassistant.setup import async_setup_component @@ -489,7 +490,7 @@ async def test_discover_dynamic_group(hass, dial_mock, pycast_mock, caplog): zconf_1 = get_fake_zconf(host="host_1", port=23456) zconf_2 = get_fake_zconf(host="host_2", port=34567) - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) # Fake dynamic group info tmp1 = MagicMock() @@ -613,7 +614,7 @@ async def test_entity_availability(hass: HomeAssistantType): async def test_entity_cast_status(hass: HomeAssistantType): """Test handling of cast status.""" entity_id = "media_player.speaker" - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) info = get_fake_chromecast_info() full_info = attr.evolve( @@ -682,7 +683,7 @@ async def test_entity_cast_status(hass: HomeAssistantType): async def test_entity_play_media(hass: HomeAssistantType): """Test playing media.""" entity_id = "media_player.speaker" - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) info = get_fake_chromecast_info() full_info = attr.evolve( @@ -711,7 +712,7 @@ async def test_entity_play_media(hass: HomeAssistantType): async def test_entity_play_media_cast(hass: HomeAssistantType, quick_play_mock): """Test playing media with cast special features.""" entity_id = "media_player.speaker" - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) info = get_fake_chromecast_info() full_info = attr.evolve( @@ -744,7 +745,7 @@ async def test_entity_play_media_cast(hass: HomeAssistantType, quick_play_mock): async def test_entity_play_media_cast_invalid(hass, caplog, quick_play_mock): """Test playing media.""" entity_id = "media_player.speaker" - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) info = get_fake_chromecast_info() full_info = attr.evolve( @@ -817,7 +818,7 @@ async def test_entity_play_media_sign_URL(hass: HomeAssistantType): async def test_entity_media_content_type(hass: HomeAssistantType): """Test various content types.""" entity_id = "media_player.speaker" - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) info = get_fake_chromecast_info() full_info = attr.evolve( @@ -871,7 +872,7 @@ async def test_entity_media_content_type(hass: HomeAssistantType): async def test_entity_control(hass: HomeAssistantType): """Test various device and media controls.""" entity_id = "media_player.speaker" - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) info = get_fake_chromecast_info() full_info = attr.evolve( @@ -980,7 +981,7 @@ async def test_entity_control(hass: HomeAssistantType): async def test_entity_media_states(hass: HomeAssistantType): """Test various entity media states.""" entity_id = "media_player.speaker" - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) info = get_fake_chromecast_info() full_info = attr.evolve( @@ -1039,7 +1040,7 @@ async def test_entity_media_states(hass: HomeAssistantType): async def test_group_media_states(hass, mz_mock): """Test media states are read from group if entity has no state.""" entity_id = "media_player.speaker" - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) info = get_fake_chromecast_info() full_info = attr.evolve( @@ -1092,7 +1093,7 @@ async def test_group_media_states(hass, mz_mock): async def test_group_media_control(hass, mz_mock): """Test media controls are handled by group if entity has no state.""" entity_id = "media_player.speaker" - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) info = get_fake_chromecast_info() full_info = attr.evolve( diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index aaf97e22ccd..d52295c75f7 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -4,6 +4,7 @@ from unittest.mock import patch from homeassistant.bootstrap import async_setup_component from homeassistant.components import automation, config +from homeassistant.helpers import entity_registry as er from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @@ -110,7 +111,7 @@ async def test_bad_formatted_automations(hass, hass_client): async def test_delete_automation(hass, hass_client): """Test deleting an automation.""" - ent_reg = await hass.helpers.entity_registry.async_get_registry() + ent_reg = er.async_get(hass) assert await async_setup_component( hass, diff --git a/tests/components/config/test_scene.py b/tests/components/config/test_scene.py index bdb2a2e3f10..8e4276cc9fd 100644 --- a/tests/components/config/test_scene.py +++ b/tests/components/config/test_scene.py @@ -4,6 +4,7 @@ from unittest.mock import patch from homeassistant.bootstrap import async_setup_component from homeassistant.components import config +from homeassistant.helpers import entity_registry as er from homeassistant.util.yaml import dump @@ -114,7 +115,7 @@ async def test_bad_formatted_scene(hass, hass_client): async def test_delete_scene(hass, hass_client): """Test deleting a scene.""" - ent_reg = await hass.helpers.entity_registry.async_get_registry() + ent_reg = er.async_get(hass) assert await async_setup_component( hass, diff --git a/tests/components/coronavirus/test_init.py b/tests/components/coronavirus/test_init.py index 483fabab9f9..cc49bf7d4b6 100644 --- a/tests/components/coronavirus/test_init.py +++ b/tests/components/coronavirus/test_init.py @@ -1,6 +1,6 @@ """Test init of Coronavirus integration.""" from homeassistant.components.coronavirus.const import DOMAIN, OPTION_WORLDWIDE -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry, mock_registry @@ -17,13 +17,13 @@ async def test_migration(hass): mock_registry( hass, { - "sensor.netherlands_confirmed": entity_registry.RegistryEntry( + "sensor.netherlands_confirmed": er.RegistryEntry( entity_id="sensor.netherlands_confirmed", unique_id="34-confirmed", platform="coronavirus", config_entry_id=nl_entry.entry_id, ), - "sensor.worldwide_confirmed": entity_registry.RegistryEntry( + "sensor.worldwide_confirmed": er.RegistryEntry( entity_id="sensor.worldwide_confirmed", unique_id="__worldwide-confirmed", platform="coronavirus", @@ -34,7 +34,7 @@ async def test_migration(hass): assert await async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) sensor_nl = ent_reg.async_get("sensor.netherlands_confirmed") assert sensor_nl.unique_id == "Netherlands-confirmed" diff --git a/tests/components/counter/test_init.py b/tests/components/counter/test_init.py index d6a41af6deb..7e5859497c5 100644 --- a/tests/components/counter/test_init.py +++ b/tests/components/counter/test_init.py @@ -21,7 +21,7 @@ from homeassistant.components.counter import ( ) from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_ICON, ATTR_NAME from homeassistant.core import Context, CoreState, State -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from tests.common import mock_restore_cache @@ -569,7 +569,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is not None @@ -606,7 +606,7 @@ async def test_update_min_max(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is not None @@ -683,7 +683,7 @@ async def test_create(hass, hass_ws_client, storage_setup): counter_id = "new_counter" input_entity_id = f"{DOMAIN}.{counter_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is None diff --git a/tests/components/deconz/test_binary_sensor.py b/tests/components/deconz/test_binary_sensor.py index 70d3db4149b..39809ecc231 100644 --- a/tests/components/deconz/test_binary_sensor.py +++ b/tests/components/deconz/test_binary_sensor.py @@ -1,5 +1,4 @@ """deCONZ binary sensor platform tests.""" - from copy import deepcopy from homeassistant.components.binary_sensor import ( @@ -15,6 +14,7 @@ from homeassistant.components.deconz.const import ( from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.deconz.services import SERVICE_DEVICE_REFRESH from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_registry import async_entries_for_config_entry from .test_gateway import ( @@ -191,7 +191,7 @@ async def test_add_new_binary_sensor_ignored(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 assert not hass.states.get("binary_sensor.presence_sensor") - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) assert ( len(async_entries_for_config_entry(entity_registry, config_entry.entry_id)) == 0 ) diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index ed7655bf620..46b563ca007 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -16,7 +16,7 @@ from homeassistant.components.deconz.const import ( ) from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration @@ -133,7 +133,7 @@ async def test_update_group_unique_id(hass): }, ) - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) # Create entity entry to migrate to new unique ID registry.async_get_or_create( LIGHT_DOMAIN, @@ -172,7 +172,7 @@ async def test_update_group_unique_id_no_legacy_group_id(hass): data={}, ) - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) # Create entity entry to migrate to new unique ID registry.async_get_or_create( LIGHT_DOMAIN, diff --git a/tests/components/deconz/test_services.py b/tests/components/deconz/test_services.py index 41eefa95785..35279572113 100644 --- a/tests/components/deconz/test_services.py +++ b/tests/components/deconz/test_services.py @@ -1,5 +1,4 @@ """deCONZ service tests.""" - from copy import deepcopy from unittest.mock import Mock, patch @@ -23,6 +22,7 @@ from homeassistant.components.deconz.services import ( async_unload_services, ) from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.entity_registry import async_entries_for_config_entry from .test_gateway import ( @@ -245,7 +245,7 @@ async def test_remove_orphaned_entries_service(hass, aioclient_mock): data = {CONF_BRIDGE_ID: BRIDGEID} - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, identifiers={("mac", "123")} ) @@ -261,7 +261,7 @@ async def test_remove_orphaned_entries_service(hass, aioclient_mock): == 5 # Host, gateway, light, switch and orphan ) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entity_registry.async_get_or_create( SENSOR_DOMAIN, DECONZ_DOMAIN, diff --git a/tests/components/directv/test_media_player.py b/tests/components/directv/test_media_player.py index 506cf62a44d..aeaa245161d 100644 --- a/tests/components/directv/test_media_player.py +++ b/tests/components/directv/test_media_player.py @@ -53,6 +53,7 @@ from homeassistant.const import ( STATE_PLAYING, STATE_UNAVAILABLE, ) +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util @@ -167,7 +168,7 @@ async def test_unique_id( """Test unique id.""" await setup_integration(hass, aioclient_mock) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) main = entity_registry.async_get(MAIN_ENTITY_ID) assert main.device_class == DEVICE_CLASS_RECEIVER diff --git a/tests/components/directv/test_remote.py b/tests/components/directv/test_remote.py index 33521958747..92bcd6af014 100644 --- a/tests/components/directv/test_remote.py +++ b/tests/components/directv/test_remote.py @@ -7,6 +7,7 @@ from homeassistant.components.remote import ( SERVICE_SEND_COMMAND, ) from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.typing import HomeAssistantType from tests.components.directv import setup_integration @@ -36,7 +37,7 @@ async def test_unique_id( """Test unique id.""" await setup_integration(hass, aioclient_mock) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) main = entity_registry.async_get(MAIN_ENTITY_ID) assert main.unique_id == "028877455858" diff --git a/tests/components/dsmr/test_sensor.py b/tests/components/dsmr/test_sensor.py index 31c4f2be8db..9aaa558e92e 100644 --- a/tests/components/dsmr/test_sensor.py +++ b/tests/components/dsmr/test_sensor.py @@ -19,6 +19,7 @@ from homeassistant.const import ( VOLUME_CUBIC_METERS, VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR, ) +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry, patch @@ -107,7 +108,7 @@ async def test_default_setup(hass, dsmr_connection_fixture): await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.power_consumption") assert entry @@ -167,7 +168,7 @@ async def test_setup_only_energy(hass, dsmr_connection_fixture): await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.power_consumption") assert entry diff --git a/tests/components/dynalite/common.py b/tests/components/dynalite/common.py index 072e222c194..446cdc74c0b 100644 --- a/tests/components/dynalite/common.py +++ b/tests/components/dynalite/common.py @@ -3,7 +3,7 @@ from unittest.mock import AsyncMock, Mock, call, patch from homeassistant.components import dynalite from homeassistant.const import ATTR_SERVICE -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from tests.common import MockConfigEntry @@ -23,7 +23,7 @@ def create_mock_device(platform, spec): async def get_entry_id_from_hass(hass): """Get the config entry id from hass.""" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) assert ent_reg conf_entries = hass.config_entries.async_entries(dynalite.DOMAIN) assert len(conf_entries) == 1 diff --git a/tests/components/dyson/test_climate.py b/tests/components/dyson/test_climate.py index 0e389000c29..90d91092dca 100644 --- a/tests/components/dyson/test_climate.py +++ b/tests/components/dyson/test_climate.py @@ -60,7 +60,7 @@ from homeassistant.const import ( ATTR_TEMPERATURE, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from .common import ( ENTITY_NAME, @@ -99,8 +99,8 @@ def async_get_device(spec: Type[DysonDevice]) -> DysonDevice: ) async def test_state_common(hass: HomeAssistant, device: DysonDevice) -> None: """Test common state and attributes of two types of climate entities.""" - er = await entity_registry.async_get_registry(hass) - assert er.async_get(ENTITY_ID).unique_id == SERIAL + entity_registry = er.async_get(hass) + assert entity_registry.async_get(ENTITY_ID).unique_id == SERIAL state = hass.states.get(ENTITY_ID) assert state.name == NAME diff --git a/tests/components/dyson/test_fan.py b/tests/components/dyson/test_fan.py index dacde12c569..eb83f319c1f 100644 --- a/tests/components/dyson/test_fan.py +++ b/tests/components/dyson/test_fan.py @@ -52,7 +52,7 @@ from homeassistant.const import ( STATE_ON, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from .common import ( ENTITY_NAME, @@ -79,8 +79,8 @@ async def test_state_purecoollink( hass: HomeAssistant, device: DysonPureCoolLink ) -> None: """Test the state of a PureCoolLink fan.""" - er = await entity_registry.async_get_registry(hass) - assert er.async_get(ENTITY_ID).unique_id == SERIAL + entity_registry = er.async_get(hass) + assert entity_registry.async_get(ENTITY_ID).unique_id == SERIAL state = hass.states.get(ENTITY_ID) assert state.state == STATE_ON @@ -128,8 +128,8 @@ async def test_state_purecoollink( @pytest.mark.parametrize("device", [DysonPureCool], indirect=True) async def test_state_purecool(hass: HomeAssistant, device: DysonPureCool) -> None: """Test the state of a PureCool fan.""" - er = await entity_registry.async_get_registry(hass) - assert er.async_get(ENTITY_ID).unique_id == SERIAL + entity_registry = er.async_get(hass) + assert entity_registry.async_get(ENTITY_ID).unique_id == SERIAL state = hass.states.get(ENTITY_ID) assert state.state == STATE_ON diff --git a/tests/components/dyson/test_sensor.py b/tests/components/dyson/test_sensor.py index 4541ef2190a..a27aeeb99e1 100644 --- a/tests/components/dyson/test_sensor.py +++ b/tests/components/dyson/test_sensor.py @@ -16,7 +16,7 @@ from homeassistant.const import ( TEMP_FAHRENHEIT, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM, UnitSystem from .common import ( @@ -120,12 +120,12 @@ async def test_sensors( # Make sure no other sensors are set up assert len(hass.states.async_all()) == len(sensors) - er = await entity_registry.async_get_registry(hass) + entity_registry = er.async_get(hass) for sensor in sensors: entity_id = _async_get_entity_id(sensor) # Test unique id - assert er.async_get(entity_id).unique_id == f"{SERIAL}-{sensor}" + assert entity_registry.async_get(entity_id).unique_id == f"{SERIAL}-{sensor}" # Test state state = hass.states.get(entity_id) diff --git a/tests/components/dyson/test_vacuum.py b/tests/components/dyson/test_vacuum.py index 03a3b076b02..b77dee3270f 100644 --- a/tests/components/dyson/test_vacuum.py +++ b/tests/components/dyson/test_vacuum.py @@ -24,7 +24,7 @@ from homeassistant.const import ( STATE_ON, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from .common import ( ENTITY_NAME, @@ -45,8 +45,8 @@ def async_get_device(state=Dyson360EyeMode.FULL_CLEAN_RUNNING) -> Dyson360Eye: async def test_state(hass: HomeAssistant, device: Dyson360Eye) -> None: """Test the state of the vacuum.""" - er = await entity_registry.async_get_registry(hass) - assert er.async_get(ENTITY_ID).unique_id == SERIAL + entity_registry = er.async_get(hass) + assert entity_registry.async_get(ENTITY_ID).unique_id == SERIAL state = hass.states.get(ENTITY_ID) assert state.name == NAME diff --git a/tests/components/elgato/test_light.py b/tests/components/elgato/test_light.py index aed569c18fe..48e6bd24625 100644 --- a/tests/components/elgato/test_light.py +++ b/tests/components/elgato/test_light.py @@ -16,6 +16,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from tests.common import mock_coro from tests.components.elgato import init_integration @@ -28,7 +29,7 @@ async def test_light_state( """Test the creation and values of the Elgato Key Lights.""" await init_integration(hass, aioclient_mock) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # First segment of the strip state = hass.states.get("light.frenck") diff --git a/tests/components/gdacs/test_geo_location.py b/tests/components/gdacs/test_geo_location.py index 7dc23eaa5d8..d93db73aa79 100644 --- a/tests/components/gdacs/test_geo_location.py +++ b/tests/components/gdacs/test_geo_location.py @@ -29,7 +29,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_START, LENGTH_KILOMETERS, ) -from homeassistant.helpers.entity_registry import async_get_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from homeassistant.util.unit_system import IMPERIAL_SYSTEM @@ -99,7 +99,7 @@ async def test_setup(hass, legacy_patchable_time): all_states = hass.states.async_all() # 3 geolocation and 1 sensor entities assert len(all_states) == 4 - entity_registry = await async_get_registry(hass) + entity_registry = er.async_get(hass) assert len(entity_registry.entities) == 4 state = hass.states.get("geo_location.drought_name_1") diff --git a/tests/components/generic_thermostat/test_climate.py b/tests/components/generic_thermostat/test_climate.py index e6cdf962d24..b83828c86c7 100644 --- a/tests/components/generic_thermostat/test_climate.py +++ b/tests/components/generic_thermostat/test_climate.py @@ -35,6 +35,7 @@ from homeassistant.const import ( ) import homeassistant.core as ha from homeassistant.core import DOMAIN as HASS_DOMAIN, CoreState, State, callback +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.unit_system import METRIC_SYSTEM @@ -179,7 +180,7 @@ async def test_unique_id(hass, setup_comp_1): ) await hass.async_block_till_done() - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get(ENTITY) assert entry diff --git a/tests/components/geofency/test_init.py b/tests/components/geofency/test_init.py index b87b201a144..92a2f5b19f2 100644 --- a/tests/components/geofency/test_init.py +++ b/tests/components/geofency/test_init.py @@ -16,6 +16,7 @@ from homeassistant.const import ( STATE_HOME, STATE_NOT_HOME, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util import slugify @@ -223,10 +224,10 @@ async def test_gps_enter_and_exit_home(hass, geofency_client, webhook_id): ] assert NOT_HOME_LONGITUDE == current_longitude - dev_reg = await hass.helpers.device_registry.async_get_registry() + dev_reg = dr.async_get(hass) assert len(dev_reg.devices) == 1 - ent_reg = await hass.helpers.entity_registry.async_get_registry() + ent_reg = er.async_get(hass) assert len(ent_reg.entities) == 1 diff --git a/tests/components/geonetnz_quakes/test_geo_location.py b/tests/components/geonetnz_quakes/test_geo_location.py index b0e54e89929..9c700c2b38e 100644 --- a/tests/components/geonetnz_quakes/test_geo_location.py +++ b/tests/components/geonetnz_quakes/test_geo_location.py @@ -25,7 +25,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_START, LENGTH_KILOMETERS, ) -from homeassistant.helpers.entity_registry import async_get_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from homeassistant.util.unit_system import IMPERIAL_SYSTEM @@ -75,7 +75,7 @@ async def test_setup(hass): all_states = hass.states.async_all() # 3 geolocation and 1 sensor entities assert len(all_states) == 4 - entity_registry = await async_get_registry(hass) + entity_registry = er.async_get(hass) assert len(entity_registry.entities) == 4 state = hass.states.get("geo_location.title_1") diff --git a/tests/components/gios/test_air_quality.py b/tests/components/gios/test_air_quality.py index 21a1abf637a..873e1e089a3 100644 --- a/tests/components/gios/test_air_quality.py +++ b/tests/components/gios/test_air_quality.py @@ -23,6 +23,7 @@ from homeassistant.const import ( CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, STATE_UNAVAILABLE, ) +from homeassistant.helpers import entity_registry as er from homeassistant.util.dt import utcnow from tests.common import async_fire_time_changed, load_fixture @@ -32,7 +33,7 @@ from tests.components.gios import init_integration async def test_air_quality(hass): """Test states of the air_quality.""" await init_integration(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("air_quality.home") assert state @@ -60,7 +61,7 @@ async def test_air_quality(hass): async def test_air_quality_with_incomplete_data(hass): """Test states of the air_quality with incomplete data from measuring station.""" await init_integration(hass, incomplete_data=True) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("air_quality.home") assert state diff --git a/tests/components/gpslogger/test_init.py b/tests/components/gpslogger/test_init.py index d30f57f0f33..8a4880c4878 100644 --- a/tests/components/gpslogger/test_init.py +++ b/tests/components/gpslogger/test_init.py @@ -14,6 +14,7 @@ from homeassistant.const import ( STATE_HOME, STATE_NOT_HOME, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import DATA_DISPATCHER from homeassistant.setup import async_setup_component @@ -135,10 +136,10 @@ async def test_enter_and_exit(hass, gpslogger_client, webhook_id): state_name = hass.states.get(f"{DEVICE_TRACKER_DOMAIN}.{data['device']}").state assert STATE_NOT_HOME == state_name - dev_reg = await hass.helpers.device_registry.async_get_registry() + dev_reg = dr.async_get(hass) assert len(dev_reg.devices) == 1 - ent_reg = await hass.helpers.entity_registry.async_get_registry() + ent_reg = er.async_get(hass) assert len(ent_reg.entities) == 1 diff --git a/tests/components/harmony/test_init.py b/tests/components/harmony/test_init.py index c63727f8738..29a1ff26b82 100644 --- a/tests/components/harmony/test_init.py +++ b/tests/components/harmony/test_init.py @@ -1,7 +1,7 @@ """Test init of Logitch Harmony Hub integration.""" from homeassistant.components.harmony.const import DOMAIN from homeassistant.const import CONF_HOST, CONF_NAME -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from .const import ( @@ -28,28 +28,28 @@ async def test_unique_id_migration(mock_hc, hass, mock_write_config): hass, { # old format - ENTITY_WATCH_TV: entity_registry.RegistryEntry( + ENTITY_WATCH_TV: er.RegistryEntry( entity_id=ENTITY_WATCH_TV, unique_id="123443-Watch TV", platform="harmony", config_entry_id=entry.entry_id, ), # old format, activity name with - - ENTITY_NILE_TV: entity_registry.RegistryEntry( + ENTITY_NILE_TV: er.RegistryEntry( entity_id=ENTITY_NILE_TV, unique_id="123443-Nile-TV", platform="harmony", config_entry_id=entry.entry_id, ), # new format - ENTITY_PLAY_MUSIC: entity_registry.RegistryEntry( + ENTITY_PLAY_MUSIC: er.RegistryEntry( entity_id=ENTITY_PLAY_MUSIC, unique_id=f"activity_{PLAY_MUSIC_ACTIVITY_ID}", platform="harmony", config_entry_id=entry.entry_id, ), # old entity which no longer has a matching activity on the hub. skipped. - "switch.some_other_activity": entity_registry.RegistryEntry( + "switch.some_other_activity": er.RegistryEntry( entity_id="switch.some_other_activity", unique_id="123443-Some Other Activity", platform="harmony", @@ -60,7 +60,7 @@ async def test_unique_id_migration(mock_hc, hass, mock_write_config): assert await async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) switch_tv = ent_reg.async_get(ENTITY_WATCH_TV) assert switch_tv.unique_id == f"activity_{WATCH_TV_ACTIVITY_ID}" diff --git a/tests/components/heos/test_media_player.py b/tests/components/heos/test_media_player.py index 4d979f8e556..8b87acfd9fd 100644 --- a/tests/components/heos/test_media_player.py +++ b/tests/components/heos/test_media_player.py @@ -53,6 +53,7 @@ from homeassistant.const import ( STATE_PLAYING, STATE_UNAVAILABLE, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component @@ -237,8 +238,8 @@ async def test_updates_from_players_changed_new_ids( ): """Test player updates from changes to available players.""" await setup_platform(hass, config_entry, config) - device_registry = await hass.helpers.device_registry.async_get_registry() - entity_registry = await hass.helpers.entity_registry.async_get_registry() + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) player = controller.players[1] event = asyncio.Event() diff --git a/tests/components/homekit/test_type_covers.py b/tests/components/homekit/test_type_covers.py index 1c0de6c3af2..8514801687c 100644 --- a/tests/components/homekit/test_type_covers.py +++ b/tests/components/homekit/test_type_covers.py @@ -40,7 +40,7 @@ from homeassistant.const import ( STATE_UNKNOWN, ) from homeassistant.core import CoreState -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from tests.common import async_mock_service @@ -446,7 +446,7 @@ async def test_windowcovering_basic_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( "cover", @@ -484,7 +484,7 @@ async def test_windowcovering_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( "cover", diff --git a/tests/components/homekit/test_type_fans.py b/tests/components/homekit/test_type_fans.py index ba660f2f12d..15e2366a883 100644 --- a/tests/components/homekit/test_type_fans.py +++ b/tests/components/homekit/test_type_fans.py @@ -28,7 +28,7 @@ from homeassistant.const import ( STATE_UNKNOWN, ) from homeassistant.core import CoreState -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from tests.common import async_mock_service @@ -526,7 +526,7 @@ async def test_fan_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( "fan", diff --git a/tests/components/homekit/test_type_lights.py b/tests/components/homekit/test_type_lights.py index 0ab3ef8e45d..0c81de2efe7 100644 --- a/tests/components/homekit/test_type_lights.py +++ b/tests/components/homekit/test_type_lights.py @@ -24,7 +24,7 @@ from homeassistant.const import ( STATE_UNKNOWN, ) from homeassistant.core import CoreState -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from tests.common import async_mock_service @@ -354,7 +354,7 @@ async def test_light_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create("light", "hue", "1234", suggested_object_id="simple") registry.async_get_or_create( diff --git a/tests/components/homekit/test_type_media_players.py b/tests/components/homekit/test_type_media_players.py index 0b9d25ce3ec..b95903d3e3f 100644 --- a/tests/components/homekit/test_type_media_players.py +++ b/tests/components/homekit/test_type_media_players.py @@ -37,7 +37,7 @@ from homeassistant.const import ( STATE_STANDBY, ) from homeassistant.core import CoreState -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from tests.common import async_mock_service @@ -421,7 +421,7 @@ async def test_tv_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( "media_player", diff --git a/tests/components/homekit/test_type_sensors.py b/tests/components/homekit/test_type_sensors.py index fe2ae7566d5..c7c06dcc90a 100644 --- a/tests/components/homekit/test_type_sensors.py +++ b/tests/components/homekit/test_type_sensors.py @@ -30,7 +30,7 @@ from homeassistant.const import ( TEMP_FAHRENHEIT, ) from homeassistant.core import CoreState -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er async def test_temperature(hass, hk_driver): @@ -326,7 +326,7 @@ async def test_sensor_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( "sensor", diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py index 7d3d0d14c2f..d9c2a6bf0ed 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -61,7 +61,7 @@ from homeassistant.const import ( TEMP_FAHRENHEIT, ) from homeassistant.core import CoreState -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from tests.common import async_mock_service @@ -889,7 +889,7 @@ async def test_thermostat_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( "climate", "generic", "1234", suggested_object_id="simple" @@ -1705,7 +1705,7 @@ async def test_water_heater_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( "water_heater", "generic", "1234", suggested_object_id="simple" diff --git a/tests/components/homekit_controller/specific_devices/test_anker_eufycam.py b/tests/components/homekit_controller/specific_devices/test_anker_eufycam.py index 45e0466ceea..15ac6d2d3ab 100644 --- a/tests/components/homekit_controller/specific_devices/test_anker_eufycam.py +++ b/tests/components/homekit_controller/specific_devices/test_anker_eufycam.py @@ -1,4 +1,5 @@ """Test against characteristics captured from a eufycam.""" +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.components.homekit_controller.common import ( Helper, @@ -12,7 +13,7 @@ async def test_eufycam_setup(hass): accessories = await setup_accessories_from_file(hass, "anker_eufycam.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Check that the camera is correctly found and set up camera_id = "camera.eufycam2_0000" @@ -32,7 +33,7 @@ async def test_eufycam_setup(hass): assert camera_state.state == "idle" assert camera_state.attributes["supported_features"] == 0 - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(camera.device_id) assert device.manufacturer == "Anker" diff --git a/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py b/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py index 8bee6e0591d..b4437a7a9b5 100644 --- a/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py +++ b/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py @@ -5,6 +5,7 @@ https://github.com/home-assistant/core/issues/20957 """ from homeassistant.components.light import SUPPORT_BRIGHTNESS, SUPPORT_COLOR +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.components.homekit_controller.common import ( Helper, @@ -18,7 +19,7 @@ async def test_aqara_gateway_setup(hass): accessories = await setup_accessories_from_file(hass, "aqara_gateway.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Check that the light is correctly found and set up alarm_id = "alarm_control_panel.aqara_hub_1563" @@ -48,7 +49,7 @@ async def test_aqara_gateway_setup(hass): SUPPORT_BRIGHTNESS | SUPPORT_COLOR ) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) # All the entities are services of the same accessory # So it looks at the protocol like a single physical device diff --git a/tests/components/homekit_controller/specific_devices/test_aqara_switch.py b/tests/components/homekit_controller/specific_devices/test_aqara_switch.py index a9744fb7bfc..6ed0d861193 100644 --- a/tests/components/homekit_controller/specific_devices/test_aqara_switch.py +++ b/tests/components/homekit_controller/specific_devices/test_aqara_switch.py @@ -7,6 +7,8 @@ service-label-index despite not being linked to a service-label. https://github.com/home-assistant/core/pull/39090 """ +from homeassistant.helpers import entity_registry as er + from tests.common import assert_lists_same, async_get_device_automations from tests.components.homekit_controller.common import ( setup_accessories_from_file, @@ -19,7 +21,7 @@ async def test_aqara_switch_setup(hass): accessories = await setup_accessories_from_file(hass, "aqara_switch.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) battery_id = "sensor.programmable_switch_battery" battery = entity_registry.async_get(battery_id) diff --git a/tests/components/homekit_controller/specific_devices/test_ecobee3.py b/tests/components/homekit_controller/specific_devices/test_ecobee3.py index d05e36ed0eb..ae050f67324 100644 --- a/tests/components/homekit_controller/specific_devices/test_ecobee3.py +++ b/tests/components/homekit_controller/specific_devices/test_ecobee3.py @@ -15,6 +15,7 @@ from homeassistant.components.climate.const import ( SUPPORT_TARGET_TEMPERATURE_RANGE, ) from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.components.homekit_controller.common import ( Helper, @@ -30,7 +31,7 @@ async def test_ecobee3_setup(hass): accessories = await setup_accessories_from_file(hass, "ecobee3.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) climate = entity_registry.async_get("climate.homew") assert climate.unique_id == "homekit-123456789012-16" @@ -73,7 +74,7 @@ async def test_ecobee3_setup(hass): occ3 = entity_registry.async_get("binary_sensor.basement") assert occ3.unique_id == "homekit-AB3C-56" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) climate_device = device_registry.async_get(climate.device_id) assert climate_device.manufacturer == "ecobee Inc." @@ -112,7 +113,7 @@ async def test_ecobee3_setup_from_cache(hass, hass_storage): await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) climate = entity_registry.async_get("climate.homew") assert climate.unique_id == "homekit-123456789012-16" @@ -131,7 +132,7 @@ async def test_ecobee3_setup_connection_failure(hass): """Test that Ecbobee can be correctly setup from its cached entity map.""" accessories = await setup_accessories_from_file(hass, "ecobee3.json") - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Test that the connection fails during initial setup. # No entities should be created. @@ -170,7 +171,7 @@ async def test_ecobee3_setup_connection_failure(hass): async def test_ecobee3_add_sensors_at_runtime(hass): """Test that new sensors are automatically added.""" - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Set up a base Ecobee 3 with no additional sensors. # There shouldn't be any entities but climate visible. diff --git a/tests/components/homekit_controller/specific_devices/test_ecobee_occupancy.py b/tests/components/homekit_controller/specific_devices/test_ecobee_occupancy.py index 6823cdc16ea..63f5d22e04b 100644 --- a/tests/components/homekit_controller/specific_devices/test_ecobee_occupancy.py +++ b/tests/components/homekit_controller/specific_devices/test_ecobee_occupancy.py @@ -4,6 +4,8 @@ Regression tests for Ecobee occupancy. https://github.com/home-assistant/core/issues/31827 """ +from homeassistant.helpers import device_registry as dr, entity_registry as er + from tests.components.homekit_controller.common import ( Helper, setup_accessories_from_file, @@ -16,7 +18,7 @@ async def test_ecobee_occupancy_setup(hass): accessories = await setup_accessories_from_file(hass, "ecobee_occupancy.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) sensor = entity_registry.async_get("binary_sensor.master_fan") assert sensor.unique_id == "homekit-111111111111-56" @@ -27,7 +29,7 @@ async def test_ecobee_occupancy_setup(hass): sensor_state = await sensor_helper.poll_and_get_state() assert sensor_state.attributes["friendly_name"] == "Master Fan" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(sensor.device_id) assert device.manufacturer == "ecobee Inc." diff --git a/tests/components/homekit_controller/specific_devices/test_homeassistant_bridge.py b/tests/components/homekit_controller/specific_devices/test_homeassistant_bridge.py index fbdf00698f7..1cbfa23b64c 100644 --- a/tests/components/homekit_controller/specific_devices/test_homeassistant_bridge.py +++ b/tests/components/homekit_controller/specific_devices/test_homeassistant_bridge.py @@ -5,6 +5,7 @@ from homeassistant.components.fan import ( SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.components.homekit_controller.common import ( Helper, @@ -20,7 +21,7 @@ async def test_homeassistant_bridge_fan_setup(hass): ) config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Check that the fan is correctly found and set up fan_id = "fan.living_room_fan" @@ -42,7 +43,7 @@ async def test_homeassistant_bridge_fan_setup(hass): SUPPORT_DIRECTION | SUPPORT_SET_SPEED | SUPPORT_OSCILLATE ) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(fan.device_id) assert device.manufacturer == "Home Assistant" diff --git a/tests/components/homekit_controller/specific_devices/test_hue_bridge.py b/tests/components/homekit_controller/specific_devices/test_hue_bridge.py index 168ae85b228..0452407bfb8 100644 --- a/tests/components/homekit_controller/specific_devices/test_hue_bridge.py +++ b/tests/components/homekit_controller/specific_devices/test_hue_bridge.py @@ -1,5 +1,7 @@ """Tests for handling accessories on a Hue bridge via HomeKit.""" +from homeassistant.helpers import device_registry as dr, entity_registry as er + from tests.common import assert_lists_same, async_get_device_automations from tests.components.homekit_controller.common import ( Helper, @@ -13,7 +15,7 @@ async def test_hue_bridge_setup(hass): accessories = await setup_accessories_from_file(hass, "hue_bridge.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Check that the battery is correctly found and set up battery_id = "sensor.hue_dimmer_switch_battery" @@ -28,7 +30,7 @@ async def test_hue_bridge_setup(hass): assert battery_state.attributes["icon"] == "mdi:battery" assert battery_state.state == "100" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(battery.device_id) assert device.manufacturer == "Philips" diff --git a/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py b/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py index 4682d4b2bcc..505ff2aacc7 100644 --- a/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py +++ b/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py @@ -8,6 +8,7 @@ from aiohomekit.testing import FakePairing import pytest from homeassistant.components.light import SUPPORT_BRIGHTNESS, SUPPORT_COLOR +from homeassistant.helpers import device_registry as dr, entity_registry as er import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed @@ -25,7 +26,7 @@ async def test_koogeek_ls1_setup(hass): accessories = await setup_accessories_from_file(hass, "koogeek_ls1.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Assert that the entity is correctly added to the entity registry entry = entity_registry.async_get("light.koogeek_ls1_20833f") @@ -44,7 +45,7 @@ async def test_koogeek_ls1_setup(hass): SUPPORT_BRIGHTNESS | SUPPORT_COLOR ) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.manufacturer == "Koogeek" diff --git a/tests/components/homekit_controller/specific_devices/test_koogeek_p1eu.py b/tests/components/homekit_controller/specific_devices/test_koogeek_p1eu.py index f97821ef111..db72aad7541 100644 --- a/tests/components/homekit_controller/specific_devices/test_koogeek_p1eu.py +++ b/tests/components/homekit_controller/specific_devices/test_koogeek_p1eu.py @@ -1,5 +1,7 @@ """Make sure that existing Koogeek P1EU support isn't broken.""" +from homeassistant.helpers import device_registry as dr, entity_registry as er + from tests.components.homekit_controller.common import ( Helper, setup_accessories_from_file, @@ -12,8 +14,8 @@ async def test_koogeek_p1eu_setup(hass): accessories = await setup_accessories_from_file(hass, "koogeek_p1eu.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - device_registry = await hass.helpers.device_registry.async_get_registry() + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) # Check that the switch entity is handled correctly diff --git a/tests/components/homekit_controller/specific_devices/test_lennox_e30.py b/tests/components/homekit_controller/specific_devices/test_lennox_e30.py index a49effdb75d..cdab08039e1 100644 --- a/tests/components/homekit_controller/specific_devices/test_lennox_e30.py +++ b/tests/components/homekit_controller/specific_devices/test_lennox_e30.py @@ -8,6 +8,7 @@ from homeassistant.components.climate.const import ( SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.components.homekit_controller.common import ( Helper, @@ -21,7 +22,7 @@ async def test_lennox_e30_setup(hass): accessories = await setup_accessories_from_file(hass, "lennox_e30.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) climate = entity_registry.async_get("climate.lennox") assert climate.unique_id == "homekit-XXXXXXXX-100" @@ -35,7 +36,7 @@ async def test_lennox_e30_setup(hass): SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_RANGE ) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(climate.device_id) assert device.manufacturer == "Lennox" diff --git a/tests/components/homekit_controller/specific_devices/test_lg_tv.py b/tests/components/homekit_controller/specific_devices/test_lg_tv.py index ebc50fda8bc..7f7ada4ac1f 100644 --- a/tests/components/homekit_controller/specific_devices/test_lg_tv.py +++ b/tests/components/homekit_controller/specific_devices/test_lg_tv.py @@ -5,6 +5,7 @@ from homeassistant.components.media_player.const import ( SUPPORT_PLAY, SUPPORT_SELECT_SOURCE, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.common import async_get_device_automations from tests.components.homekit_controller.common import ( @@ -19,7 +20,7 @@ async def test_lg_tv(hass): accessories = await setup_accessories_from_file(hass, "lg_tv.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Assert that the entity is correctly added to the entity registry entry = entity_registry.async_get("media_player.lg_webos_tv_af80") @@ -54,7 +55,7 @@ async def test_lg_tv(hass): # CURRENT_MEDIA_STATE. Therefore "ok" is the best we can say. assert state.state == "ok" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.manufacturer == "LG Electronics" diff --git a/tests/components/homekit_controller/specific_devices/test_rainmachine_pro_8.py b/tests/components/homekit_controller/specific_devices/test_rainmachine_pro_8.py index fd95ef98c09..81e31918c91 100644 --- a/tests/components/homekit_controller/specific_devices/test_rainmachine_pro_8.py +++ b/tests/components/homekit_controller/specific_devices/test_rainmachine_pro_8.py @@ -4,6 +4,8 @@ Make sure that existing RainMachine support isn't broken. https://github.com/home-assistant/core/issues/31745 """ +from homeassistant.helpers import device_registry as dr, entity_registry as er + from tests.components.homekit_controller.common import ( Helper, setup_accessories_from_file, @@ -16,7 +18,7 @@ async def test_rainmachine_pro_8_setup(hass): accessories = await setup_accessories_from_file(hass, "rainmachine-pro-8.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Assert that the entity is correctly added to the entity registry entry = entity_registry.async_get("switch.rainmachine_00ce4a") @@ -30,7 +32,7 @@ async def test_rainmachine_pro_8_setup(hass): # Assert that the friendly name is detected correctly assert state.attributes["friendly_name"] == "RainMachine-00ce4a" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.manufacturer == "Green Electronics LLC" diff --git a/tests/components/homekit_controller/specific_devices/test_simpleconnect_fan.py b/tests/components/homekit_controller/specific_devices/test_simpleconnect_fan.py index b30dd1e9c89..a2195320293 100644 --- a/tests/components/homekit_controller/specific_devices/test_simpleconnect_fan.py +++ b/tests/components/homekit_controller/specific_devices/test_simpleconnect_fan.py @@ -5,6 +5,7 @@ https://github.com/home-assistant/core/issues/26180 """ from homeassistant.components.fan import SUPPORT_DIRECTION, SUPPORT_SET_SPEED +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.components.homekit_controller.common import ( Helper, @@ -18,7 +19,7 @@ async def test_simpleconnect_fan_setup(hass): accessories = await setup_accessories_from_file(hass, "simpleconnect_fan.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Check that the fan is correctly found and set up fan_id = "fan.simpleconnect_fan_06f674" @@ -40,7 +41,7 @@ async def test_simpleconnect_fan_setup(hass): SUPPORT_DIRECTION | SUPPORT_SET_SPEED ) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(fan.device_id) assert device.manufacturer == "Hunter Fan" diff --git a/tests/components/homekit_controller/specific_devices/test_velux_gateway.py b/tests/components/homekit_controller/specific_devices/test_velux_gateway.py index 033b4aa7b4d..b93afdfbfa4 100644 --- a/tests/components/homekit_controller/specific_devices/test_velux_gateway.py +++ b/tests/components/homekit_controller/specific_devices/test_velux_gateway.py @@ -9,6 +9,7 @@ from homeassistant.components.cover import ( SUPPORT_OPEN, SUPPORT_SET_POSITION, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.components.homekit_controller.common import ( Helper, @@ -22,7 +23,7 @@ async def test_simpleconnect_cover_setup(hass): accessories = await setup_accessories_from_file(hass, "velux_gateway.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Check that the cover is correctly found and set up cover_id = "cover.velux_window" @@ -64,7 +65,7 @@ async def test_simpleconnect_cover_setup(hass): # The cover and sensor are different devices (accessories) attached to the same bridge assert cover.device_id != sensor.device_id - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(cover.device_id) assert device.manufacturer == "VELUX" diff --git a/tests/components/homekit_controller/test_device_trigger.py b/tests/components/homekit_controller/test_device_trigger.py index ce771eac768..cbb8da36bc2 100644 --- a/tests/components/homekit_controller/test_device_trigger.py +++ b/tests/components/homekit_controller/test_device_trigger.py @@ -5,6 +5,7 @@ import pytest import homeassistant.components.automation as automation from homeassistant.components.homekit_controller.const import DOMAIN +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component from tests.common import ( @@ -82,10 +83,10 @@ async def test_enumerate_remote(hass, utcnow): """Test that remote is correctly enumerated.""" await setup_test_component(hass, create_remote) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("sensor.testdevice_battery") - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) expected = [ @@ -118,10 +119,10 @@ async def test_enumerate_button(hass, utcnow): """Test that a button is correctly enumerated.""" await setup_test_component(hass, create_button) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("sensor.testdevice_battery") - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) expected = [ @@ -153,10 +154,10 @@ async def test_enumerate_doorbell(hass, utcnow): """Test that a button is correctly enumerated.""" await setup_test_component(hass, create_doorbell) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("sensor.testdevice_battery") - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) expected = [ @@ -188,10 +189,10 @@ async def test_handle_events(hass, utcnow, calls): """Test that events are handled.""" helper = await setup_test_component(hass, create_remote) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("sensor.testdevice_battery") - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert await async_setup_component( diff --git a/tests/components/homematicip_cloud/test_device.py b/tests/components/homematicip_cloud/test_device.py index 264d93c4145..6f1d3071714 100644 --- a/tests/components/homematicip_cloud/test_device.py +++ b/tests/components/homematicip_cloud/test_device.py @@ -41,8 +41,8 @@ async def test_hmip_remove_device(hass, default_mock_hap_factory): assert ha_state.state == STATE_ON assert hmip_device - device_registry = await dr.async_get_registry(hass) - entity_registry = await er.async_get_registry(hass) + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) pre_device_count = len(device_registry.devices) pre_entity_count = len(entity_registry.entities) @@ -73,8 +73,8 @@ async def test_hmip_add_device(hass, default_mock_hap_factory, hmip_config_entry assert ha_state.state == STATE_ON assert hmip_device - device_registry = await dr.async_get_registry(hass) - entity_registry = await er.async_get_registry(hass) + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) pre_device_count = len(device_registry.devices) pre_entity_count = len(entity_registry.entities) @@ -119,8 +119,8 @@ async def test_hmip_remove_group(hass, default_mock_hap_factory): assert ha_state.state == STATE_ON assert hmip_device - device_registry = await dr.async_get_registry(hass) - entity_registry = await er.async_get_registry(hass) + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) pre_device_count = len(device_registry.devices) pre_entity_count = len(entity_registry.entities) @@ -257,12 +257,12 @@ async def test_hmip_multi_area_device(hass, default_mock_hap_factory): assert ha_state # get the entity - entity_registry = await er.async_get_registry(hass) + entity_registry = er.async_get(hass) entity = entity_registry.async_get(ha_state.entity_id) assert entity # get the device - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) device = device_registry.async_get(entity.device_id) assert device.name == "Wired Eingangsmodul – 32-fach" diff --git a/tests/components/hue/test_light.py b/tests/components/hue/test_light.py index 39b9a5a23fc..df873536ce2 100644 --- a/tests/components/hue/test_light.py +++ b/tests/components/hue/test_light.py @@ -7,12 +7,7 @@ import aiohue from homeassistant import config_entries from homeassistant.components import hue from homeassistant.components.hue import light as hue_light -from homeassistant.helpers.device_registry import ( - async_get_registry as async_get_device_registry, -) -from homeassistant.helpers.entity_registry import ( - async_get_registry as async_get_entity_registry, -) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.util import color HUE_LIGHT_NS = "homeassistant.components.light.hue." @@ -937,8 +932,8 @@ async def test_group_features(hass, mock_bridge): group_3 = hass.states.get("light.dining_room") assert group_3.attributes["supported_features"] == extended_color_feature - entity_registry = await async_get_entity_registry(hass) - device_registry = await async_get_device_registry(hass) + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) entry = entity_registry.async_get("light.hue_lamp_1") device_entry = device_registry.async_get(entry.device_id) diff --git a/tests/components/hyperion/test_light.py b/tests/components/hyperion/test_light.py index fe4279ed731..94f41aa4fd0 100644 --- a/tests/components/hyperion/test_light.py +++ b/tests/components/hyperion/test_light.py @@ -26,7 +26,7 @@ from homeassistant.const import ( SERVICE_TURN_OFF, SERVICE_TURN_ON, ) -from homeassistant.helpers.entity_registry import async_get_registry +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.typing import HomeAssistantType import homeassistant.util.color as color_util @@ -109,7 +109,7 @@ async def test_setup_config_entry_not_ready_load_state_fail( async def test_setup_config_entry_dynamic_instances(hass: HomeAssistantType) -> None: """Test dynamic changes in the instance configuration.""" - registry = await async_get_registry(hass) + registry = er.async_get(hass) config_entry = add_test_config_entry(hass) From 84226da40486e0991096342b78dd35312550b882 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 9 Mar 2021 14:32:08 +0100 Subject: [PATCH 266/831] Update tests i-o to use async_get() instead of async_get_registry() (#47653) --- tests/components/input_boolean/test_init.py | 6 ++--- tests/components/input_datetime/test_init.py | 10 +++---- tests/components/input_number/test_init.py | 10 +++---- tests/components/input_select/test_init.py | 10 +++---- tests/components/input_text/test_init.py | 8 +++--- tests/components/ipma/test_config_flow.py | 8 +++--- tests/components/ipp/test_sensor.py | 7 ++--- .../jewish_calendar/test_binary_sensor.py | 3 ++- .../components/jewish_calendar/test_sensor.py | 3 ++- tests/components/litejet/__init__.py | 3 ++- tests/components/litejet/test_scene.py | 3 ++- tests/components/mazda/test_sensor.py | 7 ++--- tests/components/met/test_weather.py | 5 ++-- .../mikrotik/test_device_tracker.py | 4 +-- .../mobile_app/test_binary_sensor.py | 4 +-- tests/components/mobile_app/test_init.py | 5 ++-- tests/components/mobile_app/test_sensor.py | 4 +-- .../components/monoprice/test_media_player.py | 7 ++--- tests/components/mqtt/test_common.py | 21 ++++++++------- tests/components/mqtt/test_device_trigger.py | 9 ++++--- tests/components/mqtt/test_init.py | 18 ++++++------- tests/components/mqtt/test_sensor.py | 3 ++- tests/components/mqtt/test_tag.py | 8 +++--- tests/components/nest/camera_sdm_test.py | 5 ++-- tests/components/nest/sensor_sdm_test.py | 10 ++++--- tests/components/nest/test_device_trigger.py | 11 ++++---- tests/components/nest/test_events.py | 13 ++++----- tests/components/nut/test_sensor.py | 17 ++++++------ tests/components/nws/test_weather.py | 7 ++--- tests/components/nzbget/test_sensor.py | 3 ++- tests/components/nzbget/test_switch.py | 3 ++- tests/components/onboarding/test_views.py | 3 ++- tests/components/opentherm_gw/test_init.py | 3 ++- tests/components/ozw/test_binary_sensor.py | 5 ++-- tests/components/ozw/test_migration.py | 27 +++++++------------ tests/components/ozw/test_sensor.py | 7 ++--- 36 files changed, 150 insertions(+), 130 deletions(-) diff --git a/tests/components/input_boolean/test_init.py b/tests/components/input_boolean/test_init.py index c5d4d40e0d5..6534006dd81 100644 --- a/tests/components/input_boolean/test_init.py +++ b/tests/components/input_boolean/test_init.py @@ -20,7 +20,7 @@ from homeassistant.const import ( STATE_ON, ) from homeassistant.core import Context, CoreState, State -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from tests.common import mock_component, mock_restore_cache @@ -192,7 +192,7 @@ async def test_input_boolean_context(hass, hass_admin_user): async def test_reload(hass, hass_admin_user): """Test reload service.""" count_start = len(hass.states.async_entity_ids()) - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) _LOGGER.debug("ENTITIES @ start: %s", hass.states.async_entity_ids()) @@ -313,7 +313,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is not None diff --git a/tests/components/input_datetime/test_init.py b/tests/components/input_datetime/test_init.py index 8d9ddf9546d..39497e1164c 100644 --- a/tests/components/input_datetime/test_init.py +++ b/tests/components/input_datetime/test_init.py @@ -28,7 +28,7 @@ from homeassistant.components.input_datetime import ( from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_NAME from homeassistant.core import Context, CoreState, State from homeassistant.exceptions import Unauthorized -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util @@ -415,7 +415,7 @@ async def test_input_datetime_context(hass, hass_admin_user): async def test_reload(hass, hass_admin_user, hass_read_only_user): """Test reload service.""" count_start = len(hass.states.async_entity_ids()) - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) assert await async_setup_component( hass, @@ -544,7 +544,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.datetime_from_storage" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is not None @@ -570,7 +570,7 @@ async def test_update(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.datetime_from_storage" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state.attributes[ATTR_FRIENDLY_NAME] == "datetime from storage" @@ -602,7 +602,7 @@ async def test_ws_create(hass, hass_ws_client, storage_setup): input_id = "new_datetime" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is None diff --git a/tests/components/input_number/test_init.py b/tests/components/input_number/test_init.py index 28b9d27d23f..78fa54b03be 100644 --- a/tests/components/input_number/test_init.py +++ b/tests/components/input_number/test_init.py @@ -21,7 +21,7 @@ from homeassistant.const import ( ) from homeassistant.core import Context, CoreState, State from homeassistant.exceptions import Unauthorized -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from tests.common import mock_restore_cache @@ -300,7 +300,7 @@ async def test_input_number_context(hass, hass_admin_user): async def test_reload(hass, hass_admin_user, hass_read_only_user): """Test reload service.""" count_start = len(hass.states.async_entity_ids()) - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) assert await async_setup_component( hass, @@ -442,7 +442,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is not None @@ -478,7 +478,7 @@ async def test_update_min_max(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is not None @@ -518,7 +518,7 @@ async def test_ws_create(hass, hass_ws_client, storage_setup): input_id = "new_input" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is None diff --git a/tests/components/input_select/test_init.py b/tests/components/input_select/test_init.py index 38a3c3ba7a2..70663f71e7a 100644 --- a/tests/components/input_select/test_init.py +++ b/tests/components/input_select/test_init.py @@ -26,7 +26,7 @@ from homeassistant.const import ( ) from homeassistant.core import Context, State from homeassistant.exceptions import Unauthorized -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.loader import bind_hass from homeassistant.setup import async_setup_component @@ -425,7 +425,7 @@ async def test_input_select_context(hass, hass_admin_user): async def test_reload(hass, hass_admin_user, hass_read_only_user): """Test reload service.""" count_start = len(hass.states.async_entity_ids()) - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) assert await async_setup_component( hass, @@ -559,7 +559,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is not None @@ -592,7 +592,7 @@ async def test_update(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state.attributes[ATTR_OPTIONS] == ["yaml update 1", "yaml update 2"] @@ -633,7 +633,7 @@ async def test_ws_create(hass, hass_ws_client, storage_setup): input_id = "new_input" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is None diff --git a/tests/components/input_text/test_init.py b/tests/components/input_text/test_init.py index cc226dc1d87..531606d67ed 100644 --- a/tests/components/input_text/test_init.py +++ b/tests/components/input_text/test_init.py @@ -25,7 +25,7 @@ from homeassistant.const import ( ) from homeassistant.core import Context, CoreState, State from homeassistant.exceptions import Unauthorized -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.loader import bind_hass from homeassistant.setup import async_setup_component @@ -393,7 +393,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is not None @@ -419,7 +419,7 @@ async def test_update(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state.attributes[ATTR_FRIENDLY_NAME] == "from storage" @@ -457,7 +457,7 @@ async def test_ws_create(hass, hass_ws_client, storage_setup): input_id = "new_input" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is None diff --git a/tests/components/ipma/test_config_flow.py b/tests/components/ipma/test_config_flow.py index 493bcfe28cf..8c96b9a01d8 100644 --- a/tests/components/ipma/test_config_flow.py +++ b/tests/components/ipma/test_config_flow.py @@ -4,7 +4,7 @@ from unittest.mock import Mock, patch from homeassistant.components.ipma import DOMAIN, config_flow from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_MODE -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from .test_weather import MockLocation @@ -143,13 +143,13 @@ async def test_config_entry_migration(hass): mock_registry( hass, { - "weather.hometown": entity_registry.RegistryEntry( + "weather.hometown": er.RegistryEntry( entity_id="weather.hometown", unique_id="0, 0", platform="ipma", config_entry_id=ipma_entry.entry_id, ), - "weather.hometown_2": entity_registry.RegistryEntry( + "weather.hometown_2": er.RegistryEntry( entity_id="weather.hometown_2", unique_id="0, 0, hourly", platform="ipma", @@ -165,7 +165,7 @@ async def test_config_entry_migration(hass): assert await async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) weather_home = ent_reg.async_get("weather.hometown") assert weather_home.unique_id == "0, 0, daily" diff --git a/tests/components/ipp/test_sensor.py b/tests/components/ipp/test_sensor.py index 69143faec64..9366b290fef 100644 --- a/tests/components/ipp/test_sensor.py +++ b/tests/components/ipp/test_sensor.py @@ -6,6 +6,7 @@ from homeassistant.components.ipp.const import DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ATTR_ICON, ATTR_UNIT_OF_MEASUREMENT, PERCENTAGE from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from homeassistant.util import dt as dt_util from tests.components.ipp import init_integration, mock_connection @@ -19,7 +20,7 @@ async def test_sensors( mock_connection(aioclient_mock) entry = await init_integration(hass, aioclient_mock, skip_setup=True) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) # Pre-create registry entries for disabled by default sensors registry.async_get_or_create( @@ -86,7 +87,7 @@ async def test_disabled_by_default_sensors( ) -> None: """Test the disabled by default IPP sensors.""" await init_integration(hass, aioclient_mock) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("sensor.epson_xp_6000_series_uptime") assert state is None @@ -102,7 +103,7 @@ async def test_missing_entry_unique_id( ) -> None: """Test the unique_id of IPP sensor when printer is missing identifiers.""" entry = await init_integration(hass, aioclient_mock, uuid=None, unique_id=None) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entity = registry.async_get("sensor.epson_xp_6000_series") assert entity diff --git a/tests/components/jewish_calendar/test_binary_sensor.py b/tests/components/jewish_calendar/test_binary_sensor.py index 8986c2e6f53..ca31381f164 100644 --- a/tests/components/jewish_calendar/test_binary_sensor.py +++ b/tests/components/jewish_calendar/test_binary_sensor.py @@ -5,6 +5,7 @@ import pytest from homeassistant.components import jewish_calendar from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -84,7 +85,7 @@ async def test_issur_melacha_sensor( hass.config.latitude = latitude hass.config.longitude = longitude - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) with alter_time(test_time): assert await async_setup_component( diff --git a/tests/components/jewish_calendar/test_sensor.py b/tests/components/jewish_calendar/test_sensor.py index 1c771c71a3a..a5c99c850b8 100644 --- a/tests/components/jewish_calendar/test_sensor.py +++ b/tests/components/jewish_calendar/test_sensor.py @@ -4,6 +4,7 @@ from datetime import datetime as dt, timedelta import pytest from homeassistant.components import jewish_calendar +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -511,7 +512,7 @@ async def test_shabbat_times_sensor( hass.config.latitude = latitude hass.config.longitude = longitude - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) with alter_time(test_time): assert await async_setup_component( diff --git a/tests/components/litejet/__init__.py b/tests/components/litejet/__init__.py index 13e2b547cd8..e0304c21617 100644 --- a/tests/components/litejet/__init__.py +++ b/tests/components/litejet/__init__.py @@ -2,6 +2,7 @@ from homeassistant.components import scene, switch from homeassistant.components.litejet import DOMAIN from homeassistant.const import CONF_PORT +from homeassistant.helpers import entity_registry as er from tests.common import MockConfigEntry @@ -11,7 +12,7 @@ async def async_init_integration( ) -> MockConfigEntry: """Set up the LiteJet integration in Home Assistant.""" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry_data = {CONF_PORT: "/dev/mock"} diff --git a/tests/components/litejet/test_scene.py b/tests/components/litejet/test_scene.py index 5df26f8c680..c76c8738f86 100644 --- a/tests/components/litejet/test_scene.py +++ b/tests/components/litejet/test_scene.py @@ -1,6 +1,7 @@ """The tests for the litejet component.""" from homeassistant.components import scene from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON +from homeassistant.helpers import entity_registry as er from . import async_init_integration @@ -14,7 +15,7 @@ async def test_disabled_by_default(hass, mock_litejet): """Test the scene is disabled by default.""" await async_init_integration(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get(ENTITY_SCENE) assert state is None diff --git a/tests/components/mazda/test_sensor.py b/tests/components/mazda/test_sensor.py index 1cb9f7ac4b7..d5f25bce2f3 100644 --- a/tests/components/mazda/test_sensor.py +++ b/tests/components/mazda/test_sensor.py @@ -10,6 +10,7 @@ from homeassistant.const import ( PERCENTAGE, PRESSURE_PSI, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.util.unit_system import IMPERIAL_SYSTEM from tests.components.mazda import init_integration @@ -19,7 +20,7 @@ async def test_device_nickname(hass): """Test creation of the device when vehicle has a nickname.""" await init_integration(hass, use_nickname=True) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) reg_device = device_registry.async_get_device( identifiers={(DOMAIN, "JM000000000000000")}, ) @@ -33,7 +34,7 @@ async def test_device_no_nickname(hass): """Test creation of the device when vehicle has no nickname.""" await init_integration(hass, use_nickname=False) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) reg_device = device_registry.async_get_device( identifiers={(DOMAIN, "JM000000000000000")}, ) @@ -47,7 +48,7 @@ async def test_sensors(hass): """Test creation of the sensors.""" await init_integration(hass) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Fuel Remaining Percentage state = hass.states.get("sensor.my_mazda3_fuel_remaining_percentage") diff --git a/tests/components/met/test_weather.py b/tests/components/met/test_weather.py index 24a81be3896..89c1dc62612 100644 --- a/tests/components/met/test_weather.py +++ b/tests/components/met/test_weather.py @@ -2,6 +2,7 @@ from homeassistant.components.met import DOMAIN from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN +from homeassistant.helpers import entity_registry as er async def test_tracking_home(hass, mock_weather): @@ -12,7 +13,7 @@ async def test_tracking_home(hass, mock_weather): assert len(mock_weather.mock_calls) == 4 # Test the hourly sensor is disabled by default - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("weather.test_home_hourly") assert state is None @@ -38,7 +39,7 @@ async def test_not_tracking_home(hass, mock_weather): """Test when we not track home.""" # Pre-create registry entry for disabled by default hourly weather - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) registry.async_get_or_create( WEATHER_DOMAIN, DOMAIN, diff --git a/tests/components/mikrotik/test_device_tracker.py b/tests/components/mikrotik/test_device_tracker.py index d4151be0add..fcd29c18682 100644 --- a/tests/components/mikrotik/test_device_tracker.py +++ b/tests/components/mikrotik/test_device_tracker.py @@ -3,7 +3,7 @@ from datetime import timedelta from homeassistant.components import mikrotik import homeassistant.components.device_tracker as device_tracker -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -101,7 +101,7 @@ async def test_restoring_devices(hass): ) config_entry.add_to_hass(hass) - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( device_tracker.DOMAIN, mikrotik.DOMAIN, diff --git a/tests/components/mobile_app/test_binary_sensor.py b/tests/components/mobile_app/test_binary_sensor.py index 5ada948a5d6..7965bf472cb 100644 --- a/tests/components/mobile_app/test_binary_sensor.py +++ b/tests/components/mobile_app/test_binary_sensor.py @@ -1,6 +1,6 @@ """Entity tests for mobile_app.""" from homeassistant.const import STATE_OFF -from homeassistant.helpers import device_registry +from homeassistant.helpers import device_registry as dr async def test_sensor(hass, create_registrations, webhook_client): @@ -70,7 +70,7 @@ async def test_sensor(hass, create_registrations, webhook_client): assert updated_entity.state == "off" assert "foo" not in updated_entity.attributes - dev_reg = await device_registry.async_get_registry(hass) + dev_reg = dr.async_get(hass) assert len(dev_reg.devices) == len(create_registrations) # Reload to verify state is restored diff --git a/tests/components/mobile_app/test_init.py b/tests/components/mobile_app/test_init.py index fe956796a96..abd4b0a7218 100644 --- a/tests/components/mobile_app/test_init.py +++ b/tests/components/mobile_app/test_init.py @@ -1,5 +1,6 @@ """Tests for the mobile app integration.""" from homeassistant.components.mobile_app.const import DATA_DELETED_IDS, DOMAIN +from homeassistant.helpers import device_registry as dr, entity_registry as er from .const import CALL_SERVICE @@ -30,8 +31,8 @@ async def test_remove_entry(hass, create_registrations): await hass.config_entries.async_remove(config_entry.entry_id) assert config_entry.data["webhook_id"] in hass.data[DOMAIN][DATA_DELETED_IDS] - dev_reg = await hass.helpers.device_registry.async_get_registry() + dev_reg = dr.async_get(hass) assert len(dev_reg.devices) == 0 - ent_reg = await hass.helpers.entity_registry.async_get_registry() + ent_reg = er.async_get(hass) assert len(ent_reg.entities) == 0 diff --git a/tests/components/mobile_app/test_sensor.py b/tests/components/mobile_app/test_sensor.py index 0ba1cf3096d..ed638301bd6 100644 --- a/tests/components/mobile_app/test_sensor.py +++ b/tests/components/mobile_app/test_sensor.py @@ -1,6 +1,6 @@ """Entity tests for mobile_app.""" from homeassistant.const import PERCENTAGE, STATE_UNKNOWN -from homeassistant.helpers import device_registry +from homeassistant.helpers import device_registry as dr async def test_sensor(hass, create_registrations, webhook_client): @@ -68,7 +68,7 @@ async def test_sensor(hass, create_registrations, webhook_client): assert updated_entity.state == "123" assert "foo" not in updated_entity.attributes - dev_reg = await device_registry.async_get_registry(hass) + dev_reg = dr.async_get(hass) assert len(dev_reg.devices) == len(create_registrations) # Reload to verify state is restored diff --git a/tests/components/monoprice/test_media_player.py b/tests/components/monoprice/test_media_player.py index d7b505b5279..9d3bbd40a46 100644 --- a/tests/components/monoprice/test_media_player.py +++ b/tests/components/monoprice/test_media_player.py @@ -33,6 +33,7 @@ from homeassistant.const import ( SERVICE_VOLUME_SET, SERVICE_VOLUME_UP, ) +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_component import async_update_entity from tests.common import MockConfigEntry @@ -497,7 +498,7 @@ async def test_first_run_with_available_zones(hass): monoprice = MockMonoprice() await _setup_monoprice(hass, monoprice) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get(ZONE_7_ID) assert not entry.disabled @@ -510,7 +511,7 @@ async def test_first_run_with_failing_zones(hass): with patch.object(MockMonoprice, "zone_status", side_effect=SerialException): await _setup_monoprice(hass, monoprice) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get(ZONE_1_ID) assert not entry.disabled @@ -527,7 +528,7 @@ async def test_not_first_run_with_failing_zone(hass): with patch.object(MockMonoprice, "zone_status", side_effect=SerialException): await _setup_monoprice_not_first_run(hass, monoprice) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get(ZONE_1_ID) assert not entry.disabled diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index c8cd80372c6..f1243918e4e 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -8,6 +8,7 @@ from homeassistant.components import mqtt from homeassistant.components.mqtt import debug_info from homeassistant.components.mqtt.const import MQTT_DISCONNECTED from homeassistant.const import ATTR_ASSUMED_STATE, STATE_UNAVAILABLE +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.setup import async_setup_component @@ -725,7 +726,7 @@ async def help_test_entity_device_info_with_identifier(hass, mqtt_mock, domain, config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID) config["unique_id"] = "veryunique" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data) @@ -750,7 +751,7 @@ async def help_test_entity_device_info_with_connection(hass, mqtt_mock, domain, config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_MAC) config["unique_id"] = "veryunique" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data) @@ -772,8 +773,8 @@ async def help_test_entity_device_info_remove(hass, mqtt_mock, domain, config): config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID) config["unique_id"] = "veryunique" - dev_registry = await hass.helpers.device_registry.async_get_registry() - ent_registry = await hass.helpers.entity_registry.async_get_registry() + dev_registry = dr.async_get(hass) + ent_registry = er.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data) @@ -801,7 +802,7 @@ async def help_test_entity_device_info_update(hass, mqtt_mock, domain, config): config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID) config["unique_id"] = "veryunique" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data) @@ -913,7 +914,7 @@ async def help_test_entity_debug_info(hass, mqtt_mock, domain, config): config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID) config["unique_id"] = "veryunique" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data) @@ -946,7 +947,7 @@ async def help_test_entity_debug_info_max_messages(hass, mqtt_mock, domain, conf config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID) config["unique_id"] = "veryunique" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data) @@ -1008,7 +1009,7 @@ async def help_test_entity_debug_info_message( if payload is None: payload = "ON" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data) @@ -1054,7 +1055,7 @@ async def help_test_entity_debug_info_remove(hass, mqtt_mock, domain, config): config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID) config["unique_id"] = "veryunique" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data) @@ -1097,7 +1098,7 @@ async def help_test_entity_debug_info_update_entity_id(hass, mqtt_mock, domain, config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID) config["unique_id"] = "veryunique" - dev_registry = await hass.helpers.device_registry.async_get_registry() + dev_registry = dr.async_get(hass) ent_registry = mock_registry(hass, {}) data = json.dumps(config) diff --git a/tests/components/mqtt/test_device_trigger.py b/tests/components/mqtt/test_device_trigger.py index c1e012a90d6..62ffade11f4 100644 --- a/tests/components/mqtt/test_device_trigger.py +++ b/tests/components/mqtt/test_device_trigger.py @@ -6,6 +6,7 @@ import pytest import homeassistant.components.automation as automation from homeassistant.components.mqtt import DOMAIN, debug_info from homeassistant.components.mqtt.device_trigger import async_attach_trigger +from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component from tests.common import ( @@ -836,7 +837,7 @@ async def test_attach_remove_late2(hass, device_reg, mqtt_mock): async def test_entity_device_info_with_connection(hass, mqtt_mock): """Test MQTT device registry integration.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps( { @@ -867,7 +868,7 @@ async def test_entity_device_info_with_connection(hass, mqtt_mock): async def test_entity_device_info_with_identifier(hass, mqtt_mock): """Test MQTT device registry integration.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps( { @@ -898,7 +899,7 @@ async def test_entity_device_info_with_identifier(hass, mqtt_mock): async def test_entity_device_info_update(hass, mqtt_mock): """Test device registry update.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) config = { "automation_type": "trigger", @@ -1159,7 +1160,7 @@ async def test_trigger_debug_info(hass, mqtt_mock): This is a test helper for MQTT debug_info. """ - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) config = { "platform": "mqtt", diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 2907a0e4cfc..15401a6d02f 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -19,7 +19,7 @@ from homeassistant.const import ( TEMP_CELSIUS, ) from homeassistant.core import callback -from homeassistant.helpers import device_registry +from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -1058,7 +1058,7 @@ async def test_mqtt_ws_remove_non_mqtt_device( device_entry = device_reg.async_get_or_create( config_entry_id=config_entry.entry_id, - connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) assert device_entry is not None @@ -1157,7 +1157,7 @@ async def test_debug_info_multiple_devices(hass, mqtt_mock): }, ] - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) for d in devices: data = json.dumps(d["config"]) @@ -1236,7 +1236,7 @@ async def test_debug_info_multiple_entities_triggers(hass, mqtt_mock): }, ] - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) for c in config: data = json.dumps(c["config"]) @@ -1284,7 +1284,7 @@ async def test_debug_info_non_mqtt(hass, device_reg, entity_reg): config_entry.add_to_hass(hass) device_entry = device_reg.async_get_or_create( config_entry_id=config_entry.entry_id, - connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) for device_class in DEVICE_CLASSES: entity_reg.async_get_or_create( @@ -1311,7 +1311,7 @@ async def test_debug_info_wildcard(hass, mqtt_mock): "unique_id": "veryunique", } - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) @@ -1357,7 +1357,7 @@ async def test_debug_info_filter_same(hass, mqtt_mock): "unique_id": "veryunique", } - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) @@ -1416,7 +1416,7 @@ async def test_debug_info_same_topic(hass, mqtt_mock): "unique_id": "veryunique", } - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) @@ -1467,7 +1467,7 @@ async def test_debug_info_qos_retain(hass, mqtt_mock): "unique_id": "veryunique", } - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index a2c2605d6ab..4e3634ebfa8 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -9,6 +9,7 @@ import pytest import homeassistant.components.sensor as sensor from homeassistant.const import EVENT_STATE_CHANGED, STATE_UNAVAILABLE import homeassistant.core as ha +from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -574,7 +575,7 @@ async def test_entity_id_update_discovery_update(hass, mqtt_mock): async def test_entity_device_info_with_hub(hass, mqtt_mock): """Test MQTT sensor device registry integration.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) hub = registry.async_get_or_create( config_entry_id="123", connections=set(), diff --git a/tests/components/mqtt/test_tag.py b/tests/components/mqtt/test_tag.py index 67964a36e1a..98dcfa050e5 100644 --- a/tests/components/mqtt/test_tag.py +++ b/tests/components/mqtt/test_tag.py @@ -5,6 +5,8 @@ from unittest.mock import ANY, patch import pytest +from homeassistant.helpers import device_registry as dr + from tests.common import ( async_fire_mqtt_message, async_get_device_automations, @@ -378,7 +380,7 @@ async def test_not_fires_on_mqtt_message_after_remove_from_registry( async def test_entity_device_info_with_connection(hass, mqtt_mock): """Test MQTT device registry integration.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps( { @@ -406,7 +408,7 @@ async def test_entity_device_info_with_connection(hass, mqtt_mock): async def test_entity_device_info_with_identifier(hass, mqtt_mock): """Test MQTT device registry integration.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps( { @@ -434,7 +436,7 @@ async def test_entity_device_info_with_identifier(hass, mqtt_mock): async def test_entity_device_info_update(hass, mqtt_mock): """Test device registry update.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) config = { "topic": "test-topic", diff --git a/tests/components/nest/camera_sdm_test.py b/tests/components/nest/camera_sdm_test.py index 956d6036aed..57747ad9f59 100644 --- a/tests/components/nest/camera_sdm_test.py +++ b/tests/components/nest/camera_sdm_test.py @@ -16,6 +16,7 @@ import pytest from homeassistant.components import camera from homeassistant.components.camera import STATE_IDLE from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -168,13 +169,13 @@ async def test_camera_device(hass): assert camera is not None assert camera.state == STATE_IDLE - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("camera.my_camera") assert entry.unique_id == "some-device-id-camera" assert entry.original_name == "My Camera" assert entry.domain == "camera" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My Camera" assert device.model == "Camera" diff --git a/tests/components/nest/sensor_sdm_test.py b/tests/components/nest/sensor_sdm_test.py index b8b2912b124..dfdfd58d546 100644 --- a/tests/components/nest/sensor_sdm_test.py +++ b/tests/components/nest/sensor_sdm_test.py @@ -8,6 +8,8 @@ pubsub subscriber. from google_nest_sdm.device import Device from google_nest_sdm.event import EventMessage +from homeassistant.helpers import device_registry as dr, entity_registry as er + from .common import async_setup_sdm_platform PLATFORM = "sensor" @@ -52,13 +54,13 @@ 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() + registry = er.async_get(hass) 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_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My Sensor" assert device.model == "Thermostat" @@ -197,13 +199,13 @@ async def test_device_with_unknown_type(hass): assert temperature is not None assert temperature.state == "25.1" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) 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_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My Sensor" assert device.model is None diff --git a/tests/components/nest/test_device_trigger.py b/tests/components/nest/test_device_trigger.py index 5e3b9bde442..c9dc9992410 100644 --- a/tests/components/nest/test_device_trigger.py +++ b/tests/components/nest/test_device_trigger.py @@ -9,6 +9,7 @@ from homeassistant.components.device_automation.exceptions import ( ) from homeassistant.components.nest import DOMAIN from homeassistant.components.nest.events import NEST_EVENT +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -101,7 +102,7 @@ async def test_get_triggers(hass): ) await async_setup_camera(hass, {DEVICE_ID: camera}) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device_entry = device_registry.async_get_device({("nest", DEVICE_ID)}) expected_triggers = [ @@ -140,7 +141,7 @@ async def test_multiple_devices(hass): ) await async_setup_camera(hass, {"device-id-1": camera1, "device-id-2": camera2}) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry1 = registry.async_get("camera.camera_1") assert entry1.unique_id == "device-id-1-camera" entry2 = registry.async_get("camera.camera_2") @@ -176,7 +177,7 @@ async def test_triggers_for_invalid_device_id(hass): ) await async_setup_camera(hass, {DEVICE_ID: camera}) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device_entry = device_registry.async_get_device({("nest", DEVICE_ID)}) assert device_entry is not None @@ -198,7 +199,7 @@ async def test_no_triggers(hass): camera = make_camera(device_id=DEVICE_ID, traits={}) await async_setup_camera(hass, {DEVICE_ID: camera}) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("camera.my_camera") assert entry.unique_id == "some-device-id-camera" @@ -288,7 +289,7 @@ async def test_subscriber_automation(hass, calls): ) subscriber = await async_setup_camera(hass, {DEVICE_ID: camera}) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device_entry = device_registry.async_get_device({("nest", DEVICE_ID)}) assert await setup_automation(hass, device_entry.id, "camera_motion") diff --git a/tests/components/nest/test_events.py b/tests/components/nest/test_events.py index 692507d6ff9..df459a35f71 100644 --- a/tests/components/nest/test_events.py +++ b/tests/components/nest/test_events.py @@ -7,6 +7,7 @@ pubsub subscriber. from google_nest_sdm.device import Device from google_nest_sdm.event import EventMessage +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.util.dt import utcnow from .common import async_setup_sdm_platform @@ -91,14 +92,14 @@ async def test_doorbell_chime_event(hass): create_device_traits("sdm.devices.traits.DoorbellChime"), ) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("camera.front") assert entry is not None assert entry.unique_id == "some-device-id-camera" assert entry.original_name == "Front" assert entry.domain == "camera" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "Front" assert device.model == "Doorbell" @@ -127,7 +128,7 @@ async def test_camera_motion_event(hass): "sdm.devices.types.CAMERA", create_device_traits("sdm.devices.traits.CameraMotion"), ) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("camera.front") assert entry is not None @@ -154,7 +155,7 @@ async def test_camera_sound_event(hass): "sdm.devices.types.CAMERA", create_device_traits("sdm.devices.traits.CameraSound"), ) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("camera.front") assert entry is not None @@ -181,7 +182,7 @@ async def test_camera_person_event(hass): "sdm.devices.types.DOORBELL", create_device_traits("sdm.devices.traits.CameraEventImage"), ) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("camera.front") assert entry is not None @@ -208,7 +209,7 @@ async def test_camera_multiple_event(hass): "sdm.devices.types.DOORBELL", create_device_traits("sdm.devices.traits.CameraEventImage"), ) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("camera.front") assert entry is not None diff --git a/tests/components/nut/test_sensor.py b/tests/components/nut/test_sensor.py index 4950d467d6a..0d8fec71d51 100644 --- a/tests/components/nut/test_sensor.py +++ b/tests/components/nut/test_sensor.py @@ -1,6 +1,7 @@ """The sensor tests for the nut platform.""" from homeassistant.const import PERCENTAGE +from homeassistant.helpers import entity_registry as er from .util import async_init_integration @@ -9,7 +10,7 @@ async def test_pr3000rt2u(hass): """Test creation of PR3000RT2U sensors.""" await async_init_integration(hass, "PR3000RT2U", ["battery.charge"]) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.ups1_battery_charge") assert entry assert entry.unique_id == "CPS_PR3000RT2U_PYVJO2000034_battery.charge" @@ -35,7 +36,7 @@ async def test_cp1350c(hass): config_entry = await async_init_integration(hass, "CP1350C", ["battery.charge"]) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.ups1_battery_charge") assert entry assert entry.unique_id == f"{config_entry.entry_id}_battery.charge" @@ -60,7 +61,7 @@ async def test_5e850i(hass): """Test creation of 5E850I sensors.""" config_entry = await async_init_integration(hass, "5E850I", ["battery.charge"]) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.ups1_battery_charge") assert entry assert entry.unique_id == f"{config_entry.entry_id}_battery.charge" @@ -85,7 +86,7 @@ async def test_5e650i(hass): """Test creation of 5E650I sensors.""" config_entry = await async_init_integration(hass, "5E650I", ["battery.charge"]) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.ups1_battery_charge") assert entry assert entry.unique_id == f"{config_entry.entry_id}_battery.charge" @@ -110,7 +111,7 @@ async def test_backupsses600m1(hass): """Test creation of BACKUPSES600M1 sensors.""" await async_init_integration(hass, "BACKUPSES600M1", ["battery.charge"]) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.ups1_battery_charge") assert entry assert ( @@ -140,7 +141,7 @@ async def test_cp1500pfclcd(hass): config_entry = await async_init_integration( hass, "CP1500PFCLCD", ["battery.charge"] ) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.ups1_battery_charge") assert entry assert entry.unique_id == f"{config_entry.entry_id}_battery.charge" @@ -165,7 +166,7 @@ async def test_dl650elcd(hass): """Test creation of DL650ELCD sensors.""" config_entry = await async_init_integration(hass, "DL650ELCD", ["battery.charge"]) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.ups1_battery_charge") assert entry assert entry.unique_id == f"{config_entry.entry_id}_battery.charge" @@ -190,7 +191,7 @@ async def test_blazer_usb(hass): """Test creation of blazer_usb sensors.""" config_entry = await async_init_integration(hass, "blazer_usb", ["battery.charge"]) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.ups1_battery_charge") assert entry assert entry.unique_id == f"{config_entry.entry_id}_battery.charge" diff --git a/tests/components/nws/test_weather.py b/tests/components/nws/test_weather.py index c7cb5b81c2a..1679e489ab8 100644 --- a/tests/components/nws/test_weather.py +++ b/tests/components/nws/test_weather.py @@ -12,6 +12,7 @@ from homeassistant.components.weather import ( DOMAIN as WEATHER_DOMAIN, ) from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM @@ -40,7 +41,7 @@ async def test_imperial_metric( ): """Test with imperial and metric units.""" # enable the hourly entity - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) registry.async_get_or_create( WEATHER_DOMAIN, nws.DOMAIN, @@ -284,7 +285,7 @@ async def test_error_forecast_hourly(hass, mock_simple_nws): instance.update_forecast_hourly.side_effect = aiohttp.ClientError # enable the hourly entity - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) registry.async_get_or_create( WEATHER_DOMAIN, nws.DOMAIN, @@ -330,7 +331,7 @@ async def test_forecast_hourly_disable_enable(hass, mock_simple_nws): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get_or_create( WEATHER_DOMAIN, nws.DOMAIN, diff --git a/tests/components/nzbget/test_sensor.py b/tests/components/nzbget/test_sensor.py index f5954bc7ee0..de5e2382a63 100644 --- a/tests/components/nzbget/test_sensor.py +++ b/tests/components/nzbget/test_sensor.py @@ -8,6 +8,7 @@ from homeassistant.const import ( DATA_RATE_MEGABYTES_PER_SECOND, DEVICE_CLASS_TIMESTAMP, ) +from homeassistant.helpers import entity_registry as er from homeassistant.util import dt as dt_util from . import init_integration @@ -19,7 +20,7 @@ async def test_sensors(hass, nzbget_api) -> None: with patch("homeassistant.components.nzbget.sensor.utcnow", return_value=now): entry = await init_integration(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) uptime = now - timedelta(seconds=600) diff --git a/tests/components/nzbget/test_switch.py b/tests/components/nzbget/test_switch.py index c12fe8ca526..debfd9a1be8 100644 --- a/tests/components/nzbget/test_switch.py +++ b/tests/components/nzbget/test_switch.py @@ -7,6 +7,7 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, ) +from homeassistant.helpers import entity_registry as er from . import init_integration @@ -18,7 +19,7 @@ async def test_download_switch(hass, nzbget_api) -> None: entry = await init_integration(hass) assert entry - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entity_id = "switch.nzbgettest_download" entity_entry = registry.async_get(entity_id) assert entity_entry diff --git a/tests/components/onboarding/test_views.py b/tests/components/onboarding/test_views.py index fe956b2ac0a..558cab7ee99 100644 --- a/tests/components/onboarding/test_views.py +++ b/tests/components/onboarding/test_views.py @@ -8,6 +8,7 @@ import pytest from homeassistant.components import onboarding from homeassistant.components.onboarding import const, views from homeassistant.const import HTTP_FORBIDDEN +from homeassistant.helpers import area_registry as ar from homeassistant.setup import async_setup_component from . import mock_storage @@ -181,7 +182,7 @@ async def test_onboarding_user(hass, hass_storage, aiohttp_client): ) # Validate created areas - area_registry = await hass.helpers.area_registry.async_get_registry() + area_registry = ar.async_get(hass) assert len(area_registry.areas) == 3 assert sorted([area.name for area in area_registry.async_list_areas()]) == [ "Bedroom", diff --git a/tests/components/opentherm_gw/test_init.py b/tests/components/opentherm_gw/test_init.py index b28f869e1e6..554f58fd81b 100644 --- a/tests/components/opentherm_gw/test_init.py +++ b/tests/components/opentherm_gw/test_init.py @@ -6,6 +6,7 @@ from pyotgw.vars import OTGW, OTGW_ABOUT from homeassistant import setup from homeassistant.components.opentherm_gw.const import DOMAIN from homeassistant.const import CONF_DEVICE, CONF_ID, CONF_NAME +from homeassistant.helpers import device_registry as dr from tests.common import MockConfigEntry, mock_device_registry @@ -38,7 +39,7 @@ async def test_device_registry_insert(hass): await hass.async_block_till_done() - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) gw_dev = device_registry.async_get_device(identifiers={(DOMAIN, MOCK_GATEWAY_ID)}) assert gw_dev.sw_version == VERSION_OLD diff --git a/tests/components/ozw/test_binary_sensor.py b/tests/components/ozw/test_binary_sensor.py index 62b23be0cca..95b150b5791 100644 --- a/tests/components/ozw/test_binary_sensor.py +++ b/tests/components/ozw/test_binary_sensor.py @@ -5,6 +5,7 @@ from homeassistant.components.binary_sensor import ( ) from homeassistant.components.ozw.const import DOMAIN from homeassistant.const import ATTR_DEVICE_CLASS +from homeassistant.helpers import entity_registry as er from .common import setup_ozw @@ -14,7 +15,7 @@ async def test_binary_sensor(hass, generic_data, binary_sensor_msg): receive_msg = await setup_ozw(hass, fixture=generic_data) # Test Legacy sensor (disabled by default) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entity_id = "binary_sensor.trisensor_sensor" state = hass.states.get(entity_id) assert state is None @@ -46,7 +47,7 @@ async def test_binary_sensor(hass, generic_data, binary_sensor_msg): async def test_sensor_enabled(hass, generic_data, binary_sensor_alt_msg): """Test enabling a legacy binary_sensor.""" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get_or_create( BINARY_SENSOR_DOMAIN, diff --git a/tests/components/ozw/test_migration.py b/tests/components/ozw/test_migration.py index d83a39f2b15..076974bc48f 100644 --- a/tests/components/ozw/test_migration.py +++ b/tests/components/ozw/test_migration.py @@ -4,14 +4,7 @@ from unittest.mock import patch import pytest from homeassistant.components.ozw.websocket_api import ID, TYPE -from homeassistant.helpers.device_registry import ( - DeviceEntry, - async_get_registry as async_get_device_registry, -) -from homeassistant.helpers.entity_registry import ( - RegistryEntry, - async_get_registry as async_get_entity_registry, -) +from homeassistant.helpers import device_registry as dr, entity_registry as er from .common import setup_ozw @@ -41,35 +34,35 @@ ZWAVE_POWER_ICON = "mdi:zwave-test-power" @pytest.fixture(name="zwave_migration_data") def zwave_migration_data_fixture(hass): """Return mock zwave migration data.""" - zwave_source_node_device = DeviceEntry( + zwave_source_node_device = dr.DeviceEntry( id=ZWAVE_SOURCE_NODE_DEVICE_ID, name_by_user=ZWAVE_SOURCE_NODE_DEVICE_NAME, area_id=ZWAVE_SOURCE_NODE_DEVICE_AREA, ) - zwave_source_node_entry = RegistryEntry( + zwave_source_node_entry = er.RegistryEntry( entity_id=ZWAVE_SOURCE_ENTITY, unique_id=ZWAVE_SOURCE_NODE_UNIQUE_ID, platform="zwave", name="Z-Wave Source Node", ) - zwave_battery_device = DeviceEntry( + zwave_battery_device = dr.DeviceEntry( id=ZWAVE_BATTERY_DEVICE_ID, name_by_user=ZWAVE_BATTERY_DEVICE_NAME, area_id=ZWAVE_BATTERY_DEVICE_AREA, ) - zwave_battery_entry = RegistryEntry( + zwave_battery_entry = er.RegistryEntry( entity_id=ZWAVE_BATTERY_ENTITY, unique_id=ZWAVE_BATTERY_UNIQUE_ID, platform="zwave", name=ZWAVE_BATTERY_NAME, icon=ZWAVE_BATTERY_ICON, ) - zwave_power_device = DeviceEntry( + zwave_power_device = dr.DeviceEntry( id=ZWAVE_POWER_DEVICE_ID, name_by_user=ZWAVE_POWER_DEVICE_NAME, area_id=ZWAVE_POWER_DEVICE_AREA, ) - zwave_power_entry = RegistryEntry( + zwave_power_entry = er.RegistryEntry( entity_id=ZWAVE_POWER_ENTITY, unique_id=ZWAVE_POWER_UNIQUE_ID, platform="zwave", @@ -169,8 +162,8 @@ async def test_migrate_zwave(hass, migration_data, hass_ws_client, zwave_integra assert result["migration_entity_map"] == migration_entity_map assert result["migrated"] is True - dev_reg = await async_get_device_registry(hass) - ent_reg = await async_get_entity_registry(hass) + dev_reg = dr.async_get(hass) + ent_reg = er.async_get(hass) # check the device registry migration @@ -252,7 +245,7 @@ async def test_migrate_zwave_dry_run( assert result["migration_entity_map"] == migration_entity_map assert result["migrated"] is False - ent_reg = await async_get_entity_registry(hass) + ent_reg = er.async_get(hass) # no real migration should have been done assert ent_reg.async_is_registered("sensor.water_sensor_6_battery_level") diff --git a/tests/components/ozw/test_sensor.py b/tests/components/ozw/test_sensor.py index 91de895648e..500bd81aa0b 100644 --- a/tests/components/ozw/test_sensor.py +++ b/tests/components/ozw/test_sensor.py @@ -7,6 +7,7 @@ from homeassistant.components.sensor import ( DOMAIN as SENSOR_DOMAIN, ) from homeassistant.const import ATTR_DEVICE_CLASS +from homeassistant.helpers import entity_registry as er from .common import setup_ozw @@ -34,7 +35,7 @@ async def test_sensor(hass, generic_data): assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_POWER # Test ZWaveListSensor disabled by default - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entity_id = "sensor.water_sensor_6_instance_1_water" state = hass.states.get(entity_id) assert state is None @@ -55,7 +56,7 @@ async def test_sensor(hass, generic_data): async def test_sensor_enabled(hass, generic_data, sensor_msg): """Test enabling an advanced sensor.""" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get_or_create( SENSOR_DOMAIN, @@ -79,7 +80,7 @@ async def test_sensor_enabled(hass, generic_data, sensor_msg): async def test_string_sensor(hass, string_sensor_data): """Test so the returned type is a string sensor.""" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get_or_create( SENSOR_DOMAIN, From 0c61cb555c34bc55aea0260fe0359e8ed82a9207 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Tue, 9 Mar 2021 14:57:44 +0100 Subject: [PATCH 267/831] Add TYPE_CHECKING to coverage excludes (#47668) --- .coveragerc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.coveragerc b/.coveragerc index 5d502a7503a..2347ee1902e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1167,3 +1167,6 @@ exclude_lines = # Don't complain if tests don't hit defensive assertion code: raise AssertionError raise NotImplementedError + + # TYPE_CHECKING block is never executed during pytest run + if TYPE_CHECKING: From fd1add8f159761c57aa679f0c1a4258d65ec7112 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 9 Mar 2021 15:49:41 +0100 Subject: [PATCH 268/831] Rename AutomationTrace.runid to AutomationTrace.run_id (#47669) --- homeassistant/components/automation/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 8b0fa1687ee..b9acfc0dde9 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -241,7 +241,7 @@ async def async_setup(hass, config): class AutomationTrace: """Container for automation trace.""" - _runids = count(0) + _run_ids = count(0) def __init__( self, @@ -257,7 +257,7 @@ class AutomationTrace: self._context: Context = context self._error: Optional[Exception] = None self._state: str = "running" - self.runid: str = str(next(self._runids)) + self.run_id: str = str(next(self._run_ids)) self._timestamp_finish: Optional[dt.datetime] = None self._timestamp_start: dt.datetime = dt_util.utcnow() self._trigger: Dict[str, Any] = trigger @@ -303,7 +303,7 @@ class AutomationTrace: "condition_trace": condition_traces, "config": self._config, "context": self._context, - "run_id": self.runid, + "run_id": self.run_id, "state": self._state, "timestamp": { "start": self._timestamp_start, @@ -331,7 +331,7 @@ class AutomationTrace: result = { "last_action": last_action, "last_condition": last_condition, - "run_id": self.runid, + "run_id": self.run_id, "state": self._state, "timestamp": { "start": self._timestamp_start, @@ -379,7 +379,7 @@ def trace_automation(hass, unique_id, config, trigger, context): automation_traces = hass.data[DATA_AUTOMATION_TRACE] if unique_id not in automation_traces: automation_traces[unique_id] = LimitedSizeDict(size_limit=STORED_TRACES) - automation_traces[unique_id][automation_trace.runid] = automation_trace + automation_traces[unique_id][automation_trace.run_id] = automation_trace try: yield automation_trace From ed679b263b89a76f6f567a4efc1fc06e9b72a68c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 9 Mar 2021 10:52:53 -0800 Subject: [PATCH 269/831] Improve logging elgato (#47681) --- homeassistant/components/elgato/__init__.py | 3 ++ homeassistant/components/elgato/light.py | 41 +++++++++------------ 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/elgato/__init__.py b/homeassistant/components/elgato/__init__.py index d6c849a4c5a..4f7731c122d 100644 --- a/homeassistant/components/elgato/__init__.py +++ b/homeassistant/components/elgato/__init__.py @@ -1,4 +1,6 @@ """Support for Elgato Key Lights.""" +import logging + from elgato import Elgato, ElgatoConnectionError from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN @@ -30,6 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: await elgato.info() except ElgatoConnectionError as exception: + logging.getLogger(__name__).debug("Unable to connect: %s", exception) raise ConfigEntryNotReady from exception hass.data.setdefault(DOMAIN, {}) diff --git a/homeassistant/components/elgato/light.py b/homeassistant/components/elgato/light.py index 3005560e2ea..ae3d8274281 100644 --- a/homeassistant/components/elgato/light.py +++ b/homeassistant/components/elgato/light.py @@ -55,25 +55,20 @@ class ElgatoLight(LightEntity): info: Info, ): """Initialize Elgato Key Light.""" - self._brightness: int | None = None self._info: Info = info - self._state: bool | None = None - self._temperature: int | None = None - self._available = True + self._state: State | None = None self.elgato = elgato @property def name(self) -> str: """Return the name of the entity.""" # Return the product name, if display name is not set - if not self._info.display_name: - return self._info.product_name - return self._info.display_name + return self._info.display_name or self._info.product_name @property def available(self) -> bool: """Return True if entity is available.""" - return self._available + return self._state is not None @property def unique_id(self) -> str: @@ -83,12 +78,14 @@ class ElgatoLight(LightEntity): @property def brightness(self) -> int | None: """Return the brightness of this light between 1..255.""" - return self._brightness + assert self._state is not None + return round((self._state.brightness * 255) / 100) @property def color_temp(self) -> int | None: """Return the CT color value in mireds.""" - return self._temperature + assert self._state is not None + return self._state.temperature @property def min_mireds(self) -> int: @@ -108,7 +105,8 @@ class ElgatoLight(LightEntity): @property def is_on(self) -> bool: """Return the state of the light.""" - return bool(self._state) + assert self._state is not None + return self._state.on async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the light.""" @@ -131,22 +129,19 @@ class ElgatoLight(LightEntity): await self.elgato.light(**data) except ElgatoError: _LOGGER.error("An error occurred while updating the Elgato Key Light") - self._available = False + self._state = None async def async_update(self) -> None: """Update Elgato entity.""" + restoring = self._state is None try: - state: State = await self.elgato.state() - except ElgatoError: - if self._available: - _LOGGER.error("An error occurred while updating the Elgato Key Light") - self._available = False - return - - self._available = True - self._brightness = round((state.brightness * 255) / 100) - self._state = state.on - self._temperature = state.temperature + self._state: State = await self.elgato.state() + if restoring: + _LOGGER.info("Connection restored") + except ElgatoError as err: + meth = _LOGGER.error if self._state else _LOGGER.debug + meth("An error occurred while updating the Elgato Key Light: %s", err) + self._state = None @property def device_info(self) -> dict[str, Any]: From 46e593485e516b7b48d847c061040b6503925b80 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 9 Mar 2021 12:14:00 -0800 Subject: [PATCH 270/831] Handle zeroconf updated events (#47683) --- homeassistant/components/zeroconf/__init__.py | 2 +- tests/components/zeroconf/test_init.py | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 2ef7db3a1b4..fb2d097d063 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -242,7 +242,7 @@ async def _async_start_zeroconf_browser(hass, zeroconf): nonlocal zeroconf_types nonlocal homekit_models - if state_change != ServiceStateChange.Added: + if state_change == ServiceStateChange.Removed: return try: diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index cc34a511573..7bd670fb4c9 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -3,6 +3,7 @@ from unittest.mock import patch from zeroconf import ( BadTypeInNameException, + Error as ZeroconfError, InterfaceChoice, IPVersion, ServiceInfo, @@ -495,3 +496,35 @@ async def test_get_instance(hass, mock_zeroconf): hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) await hass.async_block_till_done() assert len(mock_zeroconf.ha_close.mock_calls) == 1 + + +async def test_removed_ignored(hass, mock_zeroconf): + """Test we remove it when a zeroconf entry is removed.""" + mock_zeroconf.get_service_info.side_effect = ZeroconfError + + def service_update_mock(zeroconf, services, handlers): + """Call service update handler.""" + handlers[0]( + zeroconf, "_service.added", "name._service.added", ServiceStateChange.Added + ) + handlers[0]( + zeroconf, + "_service.updated", + "name._service.updated", + ServiceStateChange.Updated, + ) + handlers[0]( + zeroconf, + "_service.removed", + "name._service.removed", + ServiceStateChange.Removed, + ) + + with patch.object(zeroconf, "HaServiceBrowser", side_effect=service_update_mock): + assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + assert len(mock_zeroconf.get_service_info.mock_calls) == 2 + assert mock_zeroconf.get_service_info.mock_calls[0][1][0] == "_service.added" + assert mock_zeroconf.get_service_info.mock_calls[1][1][0] == "_service.updated" From a060acc2b1b00dacebeb6d447196b22b416ad16c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 9 Mar 2021 11:16:19 -1000 Subject: [PATCH 271/831] Fix recorder with MSSQL (#46678) Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- .coveragerc | 1 + homeassistant/components/recorder/const.py | 3 + homeassistant/components/recorder/models.py | 2 +- homeassistant/components/recorder/purge.py | 162 ++++++++++++-------- homeassistant/components/recorder/repack.py | 35 +++++ setup.cfg | 2 +- tests/components/recorder/test_purge.py | 99 ++++++------ 7 files changed, 196 insertions(+), 108 deletions(-) create mode 100644 homeassistant/components/recorder/repack.py diff --git a/.coveragerc b/.coveragerc index 2347ee1902e..db940ed642b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -785,6 +785,7 @@ omit = homeassistant/components/raspyrfm/* homeassistant/components/recollect_waste/__init__.py homeassistant/components/recollect_waste/sensor.py + homeassistant/components/recorder/repack.py homeassistant/components/recswitch/switch.py homeassistant/components/reddit/* homeassistant/components/rejseplanen/sensor.py diff --git a/homeassistant/components/recorder/const.py b/homeassistant/components/recorder/const.py index a2b5ffc6f2a..026628a32df 100644 --- a/homeassistant/components/recorder/const.py +++ b/homeassistant/components/recorder/const.py @@ -5,3 +5,6 @@ SQLITE_URL_PREFIX = "sqlite://" DOMAIN = "recorder" CONF_DB_INTEGRITY_CHECK = "db_integrity_check" + +# The maximum number of rows (events) we purge in one delete statement +MAX_ROWS_TO_PURGE = 1000 diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index 9481e954bde..69e2115ce34 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -117,7 +117,7 @@ class States(Base): # type: ignore last_updated = Column(DateTime(timezone=True), default=dt_util.utcnow, index=True) created = Column(DateTime(timezone=True), default=dt_util.utcnow) old_state_id = Column( - Integer, ForeignKey("states.state_id", ondelete="SET NULL"), index=True + Integer, ForeignKey("states.state_id", ondelete="NO ACTION"), index=True ) event = relationship("Events", uselist=False) old_state = relationship("States", remote_side=[state_id]) diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index 43e84785f7d..ac10dadc227 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -1,88 +1,51 @@ """Purge old data helper.""" -from datetime import timedelta +from __future__ import annotations + +from datetime import datetime, timedelta import logging import time +from typing import TYPE_CHECKING from sqlalchemy.exc import OperationalError, SQLAlchemyError +from sqlalchemy.orm.session import Session import homeassistant.util.dt as dt_util +from .const import MAX_ROWS_TO_PURGE from .models import Events, RecorderRuns, States -from .util import execute, session_scope +from .repack import repack_database +from .util import session_scope + +if TYPE_CHECKING: + from . import Recorder _LOGGER = logging.getLogger(__name__) -def purge_old_data(instance, purge_days: int, repack: bool) -> bool: +def purge_old_data(instance: Recorder, purge_days: int, repack: bool) -> bool: """Purge events and states older than purge_days ago. Cleans up an timeframe of an hour, based on the oldest record. """ purge_before = dt_util.utcnow() - timedelta(days=purge_days) _LOGGER.debug("Purging states and events before target %s", purge_before) - try: - with session_scope(session=instance.get_session()) as session: - # Purge a max of 1 hour, based on the oldest states or events record - batch_purge_before = purge_before - - query = session.query(States).order_by(States.last_updated.asc()).limit(1) - states = execute(query, to_native=True, validate_entity_ids=False) - if states: - batch_purge_before = min( - batch_purge_before, - states[0].last_updated + timedelta(hours=1), - ) - - query = session.query(Events).order_by(Events.time_fired.asc()).limit(1) - events = execute(query, to_native=True) - if events: - batch_purge_before = min( - batch_purge_before, - events[0].time_fired + timedelta(hours=1), - ) - - _LOGGER.debug("Purging states and events before %s", batch_purge_before) - - deleted_rows = ( - session.query(States) - .filter(States.last_updated < batch_purge_before) - .delete(synchronize_session=False) - ) - _LOGGER.debug("Deleted %s states", deleted_rows) - - deleted_rows = ( - session.query(Events) - .filter(Events.time_fired < batch_purge_before) - .delete(synchronize_session=False) - ) - _LOGGER.debug("Deleted %s events", deleted_rows) - - # If states or events purging isn't processing the purge_before yet, - # return false, as we are not done yet. - if batch_purge_before != purge_before: + with session_scope(session=instance.get_session()) as session: # type: ignore + # Purge a max of MAX_ROWS_TO_PURGE, based on the oldest states or events record + event_ids = _select_event_ids_to_purge(session, purge_before) + state_ids = _select_state_ids_to_purge(session, purge_before, event_ids) + if state_ids: + _disconnect_states_about_to_be_purged(session, state_ids) + _purge_state_ids(session, state_ids) + if event_ids: + _purge_event_ids(session, event_ids) + # If states or events purging isn't processing the purge_before yet, + # return false, as we are not done yet. _LOGGER.debug("Purging hasn't fully completed yet") return False - - # Recorder runs is small, no need to batch run it - deleted_rows = ( - session.query(RecorderRuns) - .filter(RecorderRuns.start < purge_before) - .filter(RecorderRuns.run_id != instance.run_info.run_id) - .delete(synchronize_session=False) - ) - _LOGGER.debug("Deleted %s recorder_runs", deleted_rows) - + _purge_old_recorder_runs(instance, session, purge_before) if repack: - # Execute sqlite or postgresql vacuum command to free up space on disk - if instance.engine.driver in ("pysqlite", "postgresql"): - _LOGGER.debug("Vacuuming SQL DB to free space") - instance.engine.execute("VACUUM") - # Optimize mysql / mariadb tables to free up space on disk - elif instance.engine.driver in ("mysqldb", "pymysql"): - _LOGGER.debug("Optimizing SQL DB to free space") - instance.engine.execute("OPTIMIZE TABLE states, events, recorder_runs") - + repack_database(instance) except OperationalError as err: # Retry when one of the following MySQL errors occurred: # 1205: Lock wait timeout exceeded; try restarting transaction @@ -101,3 +64,78 @@ def purge_old_data(instance, purge_days: int, repack: bool) -> bool: except SQLAlchemyError as err: _LOGGER.warning("Error purging history: %s", err) return True + + +def _select_event_ids_to_purge(session: Session, purge_before: datetime) -> list: + """Return a list of event ids to purge.""" + events = ( + session.query(Events.event_id) + .filter(Events.time_fired < purge_before) + .limit(MAX_ROWS_TO_PURGE) + .all() + ) + _LOGGER.debug("Selected %s event ids to remove", len(events)) + return [event.event_id for event in events] + + +def _select_state_ids_to_purge( + session: Session, purge_before: datetime, event_ids: list +) -> list: + """Return a list of state ids to purge.""" + if not event_ids: + return [] + states = ( + session.query(States.state_id) + .filter(States.last_updated < purge_before) + .filter(States.event_id.in_(event_ids)) + .all() + ) + _LOGGER.debug("Selected %s state ids to remove", len(states)) + return [state.state_id for state in states] + + +def _disconnect_states_about_to_be_purged(session: Session, state_ids: list) -> None: + # Update old_state_id to NULL before deleting to ensure + # the delete does not fail due to a foreign key constraint + # since some databases (MSSQL) cannot do the ON DELETE SET NULL + # for us. + disconnected_rows = ( + session.query(States) + .filter(States.old_state_id.in_(state_ids)) + .update({"old_state_id": None}, synchronize_session=False) + ) + _LOGGER.debug("Updated %s states to remove old_state_id", disconnected_rows) + + +def _purge_state_ids(session: Session, state_ids: list) -> None: + """Delete by state id.""" + deleted_rows = ( + session.query(States) + .filter(States.state_id.in_(state_ids)) + .delete(synchronize_session=False) + ) + _LOGGER.debug("Deleted %s states", deleted_rows) + + +def _purge_event_ids(session: Session, event_ids: list) -> None: + """Delete by event id.""" + deleted_rows = ( + session.query(Events) + .filter(Events.event_id.in_(event_ids)) + .delete(synchronize_session=False) + ) + _LOGGER.debug("Deleted %s events", deleted_rows) + + +def _purge_old_recorder_runs( + instance: Recorder, session: Session, purge_before: datetime +) -> None: + """Purge all old recorder runs.""" + # Recorder runs is small, no need to batch run it + deleted_rows = ( + session.query(RecorderRuns) + .filter(RecorderRuns.start < purge_before) + .filter(RecorderRuns.run_id != instance.run_info.run_id) + .delete(synchronize_session=False) + ) + _LOGGER.debug("Deleted %s recorder_runs", deleted_rows) diff --git a/homeassistant/components/recorder/repack.py b/homeassistant/components/recorder/repack.py new file mode 100644 index 00000000000..68d7d5954c9 --- /dev/null +++ b/homeassistant/components/recorder/repack.py @@ -0,0 +1,35 @@ +"""Purge repack helper.""" +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from . import Recorder + +_LOGGER = logging.getLogger(__name__) + + +def repack_database(instance: Recorder) -> None: + """Repack based on engine type.""" + + # Execute sqlite command to free up space on disk + if instance.engine.dialect.name == "sqlite": + _LOGGER.debug("Vacuuming SQL DB to free space") + instance.engine.execute("VACUUM") + return + + # Execute postgresql vacuum command to free up space on disk + if instance.engine.dialect.name == "postgresql": + _LOGGER.debug("Vacuuming SQL DB to free space") + with instance.engine.connect().execution_options( + isolation_level="AUTOCOMMIT" + ) as conn: + conn.execute("VACUUM") + return + + # Optimize mysql / mariadb tables to free up space on disk + if instance.engine.dialect.name == "mysql": + _LOGGER.debug("Optimizing SQL DB to free space") + instance.engine.execute("OPTIMIZE TABLE states, events, recorder_runs") + return diff --git a/setup.cfg b/setup.cfg index 98a01278838..dbdb61b5fcf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -43,7 +43,7 @@ warn_redundant_casts = true warn_unused_configs = true -[mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.bond.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*,tests.components.hyperion.*] +[mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.bond.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.recorder.purge,homeassistant.components.recorder.repack,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*,tests.components.hyperion.*] strict = true ignore_errors = false warn_unreachable = true diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index 791bd84b11b..aaf53000865 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -1,7 +1,6 @@ """Test data purging.""" -from datetime import datetime, timedelta +from datetime import timedelta import json -from unittest.mock import patch from homeassistant.components import recorder from homeassistant.components.recorder.const import DATA_INSTANCE @@ -22,16 +21,21 @@ def test_purge_old_states(hass, hass_recorder): with session_scope(hass=hass) as session: states = session.query(States) assert states.count() == 6 + assert states[0].old_state_id is None + assert states[-1].old_state_id == states[-2].state_id + + events = session.query(Events).filter(Events.event_type == "state_changed") + assert events.count() == 6 # run purge_old_data() - finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False) - assert not finished - assert states.count() == 4 - finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False) assert not finished assert states.count() == 2 + states_after_purge = session.query(States) + assert states_after_purge[1].old_state_id == states_after_purge[0].state_id + assert states_after_purge[0].old_state_id is None + finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False) assert finished assert states.count() == 2 @@ -47,10 +51,6 @@ def test_purge_old_events(hass, hass_recorder): assert events.count() == 6 # run purge_old_data() - finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False) - assert not finished - assert events.count() == 4 - finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False) assert not finished assert events.count() == 2 @@ -72,12 +72,15 @@ def test_purge_old_recorder_runs(hass, hass_recorder): assert recorder_runs.count() == 7 # run purge_old_data() + finished = purge_old_data(hass.data[DATA_INSTANCE], 0, repack=False) + assert not finished + finished = purge_old_data(hass.data[DATA_INSTANCE], 0, repack=False) assert finished assert recorder_runs.count() == 1 -def test_purge_method(hass, hass_recorder): +def test_purge_method(hass, hass_recorder, caplog): """Test purge method.""" hass = hass_recorder() service_data = {"keep_days": 4} @@ -131,23 +134,19 @@ def test_purge_method(hass, hass_recorder): assert not ("EVENT_TEST_PURGE" in (event.event_type for event in events.all())) # run purge method - correct service data, with repack - with patch("homeassistant.components.recorder.purge._LOGGER") as mock_logger: - service_data["repack"] = True - hass.services.call("recorder", "purge", service_data=service_data) - hass.block_till_done() - hass.data[DATA_INSTANCE].block_till_done() - wait_recording_done(hass) - assert ( - mock_logger.debug.mock_calls[5][1][0] - == "Vacuuming SQL DB to free space" - ) + service_data["repack"] = True + hass.services.call("recorder", "purge", service_data=service_data) + hass.block_till_done() + hass.data[DATA_INSTANCE].block_till_done() + wait_recording_done(hass) + assert "Vacuuming SQL DB to free space" in caplog.text def _add_test_states(hass): """Add multiple states to the db for testing.""" - now = datetime.now() - five_days_ago = now - timedelta(days=5) - eleven_days_ago = now - timedelta(days=11) + utcnow = dt_util.utcnow() + five_days_ago = utcnow - timedelta(days=5) + eleven_days_ago = utcnow - timedelta(days=11) attributes = {"test_attr": 5, "test_attr_10": "nice"} hass.block_till_done() @@ -155,6 +154,7 @@ def _add_test_states(hass): wait_recording_done(hass) with recorder.session_scope(hass=hass) as session: + old_state_id = None for event_id in range(6): if event_id < 2: timestamp = eleven_days_ago @@ -163,28 +163,39 @@ def _add_test_states(hass): timestamp = five_days_ago state = "purgeme" else: - timestamp = now + timestamp = utcnow state = "dontpurgeme" - session.add( - States( - entity_id="test.recorder2", - domain="sensor", - state=state, - attributes=json.dumps(attributes), - last_changed=timestamp, - last_updated=timestamp, - created=timestamp, - event_id=event_id + 1000, - ) + event = Events( + event_type="state_changed", + event_data="{}", + origin="LOCAL", + created=timestamp, + time_fired=timestamp, ) + session.add(event) + session.flush() + state = States( + entity_id="test.recorder2", + domain="sensor", + state=state, + attributes=json.dumps(attributes), + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + event_id=event.event_id, + old_state_id=old_state_id, + ) + session.add(state) + session.flush() + old_state_id = state.state_id def _add_test_events(hass): """Add a few events for testing.""" - now = datetime.now() - five_days_ago = now - timedelta(days=5) - eleven_days_ago = now - timedelta(days=11) + utcnow = dt_util.utcnow() + five_days_ago = utcnow - timedelta(days=5) + eleven_days_ago = utcnow - timedelta(days=11) event_data = {"test_attr": 5, "test_attr_10": "nice"} hass.block_till_done() @@ -200,7 +211,7 @@ def _add_test_events(hass): timestamp = five_days_ago event_type = "EVENT_TEST_PURGE" else: - timestamp = now + timestamp = utcnow event_type = "EVENT_TEST" session.add( @@ -216,9 +227,9 @@ def _add_test_events(hass): def _add_test_recorder_runs(hass): """Add a few recorder_runs for testing.""" - now = datetime.now() - five_days_ago = now - timedelta(days=5) - eleven_days_ago = now - timedelta(days=11) + utcnow = dt_util.utcnow() + five_days_ago = utcnow - timedelta(days=5) + eleven_days_ago = utcnow - timedelta(days=11) hass.block_till_done() hass.data[DATA_INSTANCE].block_till_done() @@ -231,7 +242,7 @@ def _add_test_recorder_runs(hass): elif rec_id < 4: timestamp = five_days_ago else: - timestamp = now + timestamp = utcnow session.add( RecorderRuns( From 7840db0598318061ea19e015c1df9e633f0cc34c Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Tue, 9 Mar 2021 16:26:54 -0800 Subject: [PATCH 272/831] Restore switch.turn_on and switch.turn_off functionality for SmartTub pumps (#47586) Revert "Remove turn_on and turn_off from SmartTub pump switches (#47184)" This reverts commit bab66a5cb968bcc7c6ef7787c9dc645885569429. --- homeassistant/components/smarttub/switch.py | 14 ++++++++++++++ tests/components/smarttub/test_switch.py | 21 +++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/homeassistant/components/smarttub/switch.py b/homeassistant/components/smarttub/switch.py index d2891932546..26239df9dff 100644 --- a/homeassistant/components/smarttub/switch.py +++ b/homeassistant/components/smarttub/switch.py @@ -61,6 +61,20 @@ class SmartTubPump(SmartTubEntity, SwitchEntity): """Return True if the pump is on.""" return self.pump.state != SpaPump.PumpState.OFF + async def async_turn_on(self, **kwargs) -> None: + """Turn the pump on.""" + + # the API only supports toggling + if not self.is_on: + await self.async_toggle() + + async def async_turn_off(self, **kwargs) -> None: + """Turn the pump off.""" + + # the API only supports toggling + if self.is_on: + await self.async_toggle() + async def async_toggle(self, **kwargs) -> None: """Toggle the pump on or off.""" async with async_timeout.timeout(API_TIMEOUT): diff --git a/tests/components/smarttub/test_switch.py b/tests/components/smarttub/test_switch.py index 89e0ec03b23..81b53604065 100644 --- a/tests/components/smarttub/test_switch.py +++ b/tests/components/smarttub/test_switch.py @@ -2,6 +2,8 @@ import pytest +from homeassistant.const import STATE_OFF, STATE_ON + @pytest.mark.parametrize( "pump_id,entity_suffix,pump_state", @@ -28,3 +30,22 @@ async def test_pumps(spa, setup_entry, hass, pump_id, pump_state, entity_suffix) blocking=True, ) pump.toggle.assert_called() + + if state.state == STATE_OFF: + await hass.services.async_call( + "switch", + "turn_on", + {"entity_id": entity_id}, + blocking=True, + ) + pump.toggle.assert_called() + else: + assert state.state == STATE_ON + + await hass.services.async_call( + "switch", + "turn_off", + {"entity_id": entity_id}, + blocking=True, + ) + pump.toggle.assert_called() From 62e49e545b942b93733eda9e30f1e336314603fd Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 10 Mar 2021 01:53:00 +0100 Subject: [PATCH 273/831] Add confirm only for Elgato (#47684) --- homeassistant/components/elgato/config_flow.py | 1 + tests/components/elgato/test_config_flow.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/homeassistant/components/elgato/config_flow.py b/homeassistant/components/elgato/config_flow.py index 7c818d6f3fa..79960cc95ff 100644 --- a/homeassistant/components/elgato/config_flow.py +++ b/homeassistant/components/elgato/config_flow.py @@ -53,6 +53,7 @@ class ElgatoFlowHandler(ConfigFlow, domain=DOMAIN): except ElgatoError: return self.async_abort(reason="cannot_connect") + self._set_confirm_only() return self.async_show_form( step_id="zeroconf_confirm", description_placeholders={"serial_number": self.serial_number}, diff --git a/tests/components/elgato/test_config_flow.py b/tests/components/elgato/test_config_flow.py index 0f3ff032722..49c85c5f2a2 100644 --- a/tests/components/elgato/test_config_flow.py +++ b/tests/components/elgato/test_config_flow.py @@ -82,6 +82,11 @@ async def test_full_zeroconf_flow_implementation( assert result["step_id"] == "zeroconf_confirm" assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + progress = hass.config_entries.flow.async_progress() + assert len(progress) == 1 + assert progress[0]["flow_id"] == result["flow_id"] + assert progress[0]["context"]["confirm_only"] is True + result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} ) From bf64421be92663ba807c3151980b36f1e4dd8ede Mon Sep 17 00:00:00 2001 From: Sam Steele Date: Tue, 9 Mar 2021 23:52:32 -0500 Subject: [PATCH 274/831] Use the local timezone when parsing Todoist due dates (#45994) --- homeassistant/components/todoist/calendar.py | 27 ++++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/todoist/calendar.py b/homeassistant/components/todoist/calendar.py index 1188831c26d..b9ab4fe6dbb 100644 --- a/homeassistant/components/todoist/calendar.py +++ b/homeassistant/components/todoist/calendar.py @@ -226,15 +226,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -def _parse_due_date(data: dict) -> datetime: +def _parse_due_date(data: dict, gmt_string) -> datetime: """Parse the due date dict into a datetime object.""" # Add time information to date only strings. if len(data["date"]) == 10: data["date"] += "T00:00:00" - # If there is no timezone provided, use UTC. - if data["timezone"] is None: - data["date"] += "Z" - return dt.parse_datetime(data["date"]) + if dt.parse_datetime(data["date"]).tzinfo is None: + data["date"] += gmt_string + return dt.as_utc(dt.parse_datetime(data["date"])) class TodoistProjectDevice(CalendarEventDevice): @@ -407,7 +406,9 @@ class TodoistProjectData: # Generally speaking, that means right now. task[START] = dt.utcnow() if data[DUE] is not None: - task[END] = _parse_due_date(data[DUE]) + task[END] = _parse_due_date( + data[DUE], self._api.state["user"]["tz_info"]["gmt_string"] + ) if self._due_date_days is not None and ( task[END] > dt.utcnow() + self._due_date_days @@ -529,9 +530,19 @@ class TodoistProjectData: for task in project_task_data: if task["due"] is None: continue - due_date = _parse_due_date(task["due"]) + due_date = _parse_due_date( + task["due"], self._api.state["user"]["tz_info"]["gmt_string"] + ) + midnight = dt.as_utc( + dt.parse_datetime( + due_date.strftime("%Y-%m-%d") + + "T00:00:00" + + self._api.state["user"]["tz_info"]["gmt_string"] + ) + ) + if start_date < due_date < end_date: - if due_date.hour == 0 and due_date.minute == 0: + if due_date == midnight: # If the due date has no time data, return just the date so that it # will render correctly as an all day event on a calendar. due_date_value = due_date.strftime("%Y-%m-%d") From 704000c0495bdd7ba73c1c312346ab894d047ddc Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 10 Mar 2021 06:23:11 +0100 Subject: [PATCH 275/831] Add support for breakpoints in scripts (#47632) --- .../components/automation/__init__.py | 8 +- homeassistant/components/config/automation.py | 195 +++++++- homeassistant/helpers/script.py | 159 ++++++- homeassistant/helpers/trace.py | 16 +- tests/components/config/test_automation.py | 424 ++++++++++++++++++ tests/helpers/test_script.py | 191 +++++++- 6 files changed, 961 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index b9acfc0dde9..6410954191d 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -68,7 +68,12 @@ from homeassistant.helpers.script import ( ) from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.service import async_register_admin_service -from homeassistant.helpers.trace import TraceElement, trace_get, trace_path +from homeassistant.helpers.trace import ( + TraceElement, + trace_get, + trace_id_set, + trace_path, +) from homeassistant.helpers.trigger import async_initialize_triggers from homeassistant.helpers.typing import TemplateVarsType from homeassistant.loader import bind_hass @@ -374,6 +379,7 @@ class LimitedSizeDict(OrderedDict): def trace_automation(hass, unique_id, config, trigger, context): """Trace action execution of automation with automation_id.""" automation_trace = AutomationTrace(unique_id, config, trigger, context) + trace_id_set((unique_id, automation_trace.runid)) if unique_id: automation_traces = hass.data[DATA_AUTOMATION_TRACE] diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index 708ad55aaeb..b5aa1bf7af5 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -16,7 +16,25 @@ from homeassistant.components.automation.config import ( ) from homeassistant.config import AUTOMATION_CONFIG_PATH from homeassistant.const import CONF_ID, SERVICE_RELOAD +from homeassistant.core import callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, entity_registry +from homeassistant.helpers.dispatcher import ( + DATA_DISPATCHER, + async_dispatcher_connect, + async_dispatcher_send, +) +from homeassistant.helpers.script import ( + SCRIPT_BREAKPOINT_HIT, + SCRIPT_DEBUG_CONTINUE_ALL, + breakpoint_clear, + breakpoint_clear_all, + breakpoint_list, + breakpoint_set, + debug_continue, + debug_step, + debug_stop, +) from . import ACTION_DELETE, EditIdBasedConfigView @@ -26,6 +44,13 @@ async def async_setup(hass): websocket_api.async_register_command(hass, websocket_automation_trace_get) websocket_api.async_register_command(hass, websocket_automation_trace_list) + websocket_api.async_register_command(hass, websocket_automation_breakpoint_clear) + websocket_api.async_register_command(hass, websocket_automation_breakpoint_list) + websocket_api.async_register_command(hass, websocket_automation_breakpoint_set) + websocket_api.async_register_command(hass, websocket_automation_debug_continue) + websocket_api.async_register_command(hass, websocket_automation_debug_step) + websocket_api.async_register_command(hass, websocket_automation_debug_stop) + websocket_api.async_register_command(hass, websocket_subscribe_breakpoint_events) async def hook(action, config_key): """post_write_hook for Config View that reloads automations.""" @@ -92,11 +117,12 @@ class EditAutomationConfigView(EditIdBasedConfigView): data[index] = updated_value +@callback +@websocket_api.require_admin @websocket_api.websocket_command( {vol.Required("type"): "automation/trace/get", vol.Optional("automation_id"): str} ) -@websocket_api.async_response -async def websocket_automation_trace_get(hass, connection, msg): +def websocket_automation_trace_get(hass, connection, msg): """Get automation traces.""" automation_id = msg.get("automation_id") @@ -110,10 +136,171 @@ async def websocket_automation_trace_get(hass, connection, msg): connection.send_result(msg["id"], automation_traces) +@callback +@websocket_api.require_admin @websocket_api.websocket_command({vol.Required("type"): "automation/trace/list"}) -@websocket_api.async_response -async def websocket_automation_trace_list(hass, connection, msg): +def websocket_automation_trace_list(hass, connection, msg): """Summarize automation traces.""" automation_traces = get_debug_traces(hass, summary=True) connection.send_result(msg["id"], automation_traces) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/breakpoint/set", + vol.Required("automation_id"): str, + vol.Required("node"): str, + vol.Optional("run_id"): str, + } +) +def websocket_automation_breakpoint_set(hass, connection, msg): + """Set breakpoint.""" + automation_id = msg["automation_id"] + node = msg["node"] + run_id = msg.get("run_id") + + if ( + SCRIPT_BREAKPOINT_HIT not in hass.data.get(DATA_DISPATCHER, {}) + or not hass.data[DATA_DISPATCHER][SCRIPT_BREAKPOINT_HIT] + ): + raise HomeAssistantError("No breakpoint subscription") + + result = breakpoint_set(hass, automation_id, run_id, node) + connection.send_result(msg["id"], result) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/breakpoint/clear", + vol.Required("automation_id"): str, + vol.Required("node"): str, + vol.Optional("run_id"): str, + } +) +def websocket_automation_breakpoint_clear(hass, connection, msg): + """Clear breakpoint.""" + automation_id = msg["automation_id"] + node = msg["node"] + run_id = msg.get("run_id") + + result = breakpoint_clear(hass, automation_id, run_id, node) + + connection.send_result(msg["id"], result) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + {vol.Required("type"): "automation/debug/breakpoint/list"} +) +def websocket_automation_breakpoint_list(hass, connection, msg): + """List breakpoints.""" + breakpoints = breakpoint_list(hass) + for _breakpoint in breakpoints: + _breakpoint["automation_id"] = _breakpoint.pop("unique_id") + + connection.send_result(msg["id"], breakpoints) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + {vol.Required("type"): "automation/debug/breakpoint/subscribe"} +) +def websocket_subscribe_breakpoint_events(hass, connection, msg): + """Subscribe to breakpoint events.""" + + @callback + def breakpoint_hit(automation_id, run_id, node): + """Forward events to websocket.""" + connection.send_message( + websocket_api.event_message( + msg["id"], + { + "automation_id": automation_id, + "run_id": run_id, + "node": node, + }, + ) + ) + + remove_signal = async_dispatcher_connect( + hass, SCRIPT_BREAKPOINT_HIT, breakpoint_hit + ) + + @callback + def unsub(): + """Unsubscribe from breakpoint events.""" + remove_signal() + if ( + SCRIPT_BREAKPOINT_HIT not in hass.data.get(DATA_DISPATCHER, {}) + or not hass.data[DATA_DISPATCHER][SCRIPT_BREAKPOINT_HIT] + ): + breakpoint_clear_all(hass) + async_dispatcher_send(hass, SCRIPT_DEBUG_CONTINUE_ALL) + + connection.subscriptions[msg["id"]] = unsub + + connection.send_message(websocket_api.result_message(msg["id"])) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/continue", + vol.Required("automation_id"): str, + vol.Required("run_id"): str, + } +) +def websocket_automation_debug_continue(hass, connection, msg): + """Resume execution of halted automation.""" + automation_id = msg["automation_id"] + run_id = msg["run_id"] + + result = debug_continue(hass, automation_id, run_id) + + connection.send_result(msg["id"], result) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/step", + vol.Required("automation_id"): str, + vol.Required("run_id"): str, + } +) +def websocket_automation_debug_step(hass, connection, msg): + """Single step a halted automation.""" + automation_id = msg["automation_id"] + run_id = msg["run_id"] + + result = debug_step(hass, automation_id, run_id) + + connection.send_result(msg["id"], result) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/stop", + vol.Required("automation_id"): str, + vol.Required("run_id"): str, + } +) +def websocket_automation_debug_stop(hass, connection, msg): + """Stop a halted automation.""" + automation_id = msg["automation_id"] + run_id = msg["run_id"] + + result = debug_stop(hass, automation_id, run_id) + + connection.send_result(msg["id"], result) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index b3fcffd4944..257fd6d9715 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -1,6 +1,6 @@ """Helpers to execute scripts.""" import asyncio -from contextlib import contextmanager +from contextlib import asynccontextmanager from datetime import datetime, timedelta from functools import partial import itertools @@ -65,6 +65,10 @@ from homeassistant.core import ( ) from homeassistant.helpers import condition, config_validation as cv, service, template from homeassistant.helpers.condition import trace_condition_function +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.helpers.event import async_call_later, async_track_template from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.trigger import ( @@ -78,6 +82,7 @@ from homeassistant.util.dt import utcnow from .trace import ( TraceElement, trace_append_element, + trace_id_get, trace_path, trace_path_get, trace_set_result, @@ -111,6 +116,9 @@ ATTR_CUR = "current" ATTR_MAX = "max" DATA_SCRIPTS = "helpers.script" +DATA_SCRIPT_BREAKPOINTS = "helpers.script_breakpoints" +RUN_ID_ANY = "*" +NODE_ANY = "*" _LOGGER = logging.getLogger(__name__) @@ -122,6 +130,10 @@ _SHUTDOWN_MAX_WAIT = 60 ACTION_TRACE_NODE_MAX_LEN = 20 # Max length of a trace node for repeated actions +SCRIPT_BREAKPOINT_HIT = "script_breakpoint_hit" +SCRIPT_DEBUG_CONTINUE_STOP = "script_debug_continue_stop_{}_{}" +SCRIPT_DEBUG_CONTINUE_ALL = "script_debug_continue_all" + def action_trace_append(variables, path): """Append a TraceElement to trace[path].""" @@ -130,11 +142,57 @@ def action_trace_append(variables, path): return trace_element -@contextmanager -def trace_action(variables): +@asynccontextmanager +async def trace_action(hass, script_run, stop, variables): """Trace action execution.""" - trace_element = action_trace_append(variables, trace_path_get()) + path = trace_path_get() + trace_element = action_trace_append(variables, path) trace_stack_push(trace_stack_cv, trace_element) + + trace_id = trace_id_get() + if trace_id: + unique_id = trace_id[0] + run_id = trace_id[1] + breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS] + if unique_id in breakpoints and ( + ( + run_id in breakpoints[unique_id] + and ( + path in breakpoints[unique_id][run_id] + or NODE_ANY in breakpoints[unique_id][run_id] + ) + ) + or ( + RUN_ID_ANY in breakpoints[unique_id] + and ( + path in breakpoints[unique_id][RUN_ID_ANY] + or NODE_ANY in breakpoints[unique_id][RUN_ID_ANY] + ) + ) + ): + async_dispatcher_send(hass, SCRIPT_BREAKPOINT_HIT, unique_id, run_id, path) + + done = asyncio.Event() + + @callback + def async_continue_stop(command=None): + if command == "stop": + stop.set() + done.set() + + signal = SCRIPT_DEBUG_CONTINUE_STOP.format(unique_id, run_id) + remove_signal1 = async_dispatcher_connect(hass, signal, async_continue_stop) + remove_signal2 = async_dispatcher_connect( + hass, SCRIPT_DEBUG_CONTINUE_ALL, async_continue_stop + ) + + tasks = [hass.async_create_task(flag.wait()) for flag in (stop, done)] + await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) + for task in tasks: + task.cancel() + remove_signal1() + remove_signal2() + try: yield trace_element except Exception as ex: # pylint: disable=broad-except @@ -294,16 +352,19 @@ class _ScriptRun: self._finish() async def _async_step(self, log_exceptions): - with trace_path(str(self._step)), trace_action(self._variables): - try: - handler = f"_async_{cv.determine_script_action(self._action)}_step" - await getattr(self, handler)() - except Exception as ex: - if not isinstance(ex, (_StopScript, asyncio.CancelledError)) and ( - self._log_exceptions or log_exceptions - ): - self._log_exception(ex) - raise + with trace_path(str(self._step)): + async with trace_action(self._hass, self, self._stop, self._variables): + if self._stop.is_set(): + return + try: + handler = f"_async_{cv.determine_script_action(self._action)}_step" + await getattr(self, handler)() + except Exception as ex: + if not isinstance(ex, (_StopScript, asyncio.CancelledError)) and ( + self._log_exceptions or log_exceptions + ): + self._log_exception(ex) + raise def _finish(self) -> None: self._script._runs.remove(self) # pylint: disable=protected-access @@ -876,6 +937,8 @@ class Script: all_scripts.append( {"instance": self, "started_before_shutdown": not hass.is_stopping} ) + if DATA_SCRIPT_BREAKPOINTS not in hass.data: + hass.data[DATA_SCRIPT_BREAKPOINTS] = {} self._hass = hass self.sequence = sequence @@ -1213,3 +1276,71 @@ class Script: self._logger.exception(msg, *args, **kwargs) else: self._logger.log(level, msg, *args, **kwargs) + + +@callback +def breakpoint_clear(hass, unique_id, run_id, node): + """Clear a breakpoint.""" + run_id = run_id or RUN_ID_ANY + breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS] + if unique_id not in breakpoints or run_id not in breakpoints[unique_id]: + return + breakpoints[unique_id][run_id].discard(node) + + +@callback +def breakpoint_clear_all(hass): + """Clear all breakpoints.""" + hass.data[DATA_SCRIPT_BREAKPOINTS] = {} + + +@callback +def breakpoint_set(hass, unique_id, run_id, node): + """Set a breakpoint.""" + run_id = run_id or RUN_ID_ANY + breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS] + if unique_id not in breakpoints: + breakpoints[unique_id] = {} + if run_id not in breakpoints[unique_id]: + breakpoints[unique_id][run_id] = set() + breakpoints[unique_id][run_id].add(node) + + +@callback +def breakpoint_list(hass): + """List breakpoints.""" + breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS] + + return [ + {"unique_id": unique_id, "run_id": run_id, "node": node} + for unique_id in breakpoints + for run_id in breakpoints[unique_id] + for node in breakpoints[unique_id][run_id] + ] + + +@callback +def debug_continue(hass, unique_id, run_id): + """Continue execution of a halted script.""" + # Clear any wildcard breakpoint + breakpoint_clear(hass, unique_id, run_id, NODE_ANY) + + signal = SCRIPT_DEBUG_CONTINUE_STOP.format(unique_id, run_id) + async_dispatcher_send(hass, signal, "continue") + + +@callback +def debug_step(hass, unique_id, run_id): + """Single step a halted script.""" + # Set a wildcard breakpoint + breakpoint_set(hass, unique_id, run_id, NODE_ANY) + + signal = SCRIPT_DEBUG_CONTINUE_STOP.format(unique_id, run_id) + async_dispatcher_send(hass, signal, "continue") + + +@callback +def debug_stop(hass, unique_id, run_id): + """Stop execution of a running or halted script.""" + signal = SCRIPT_DEBUG_CONTINUE_STOP.format(unique_id, run_id) + async_dispatcher_send(hass, signal, "stop") diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py index 0c1969a8ac6..e0c67a1de54 100644 --- a/homeassistant/helpers/trace.py +++ b/homeassistant/helpers/trace.py @@ -2,7 +2,7 @@ from collections import deque from contextlib import contextmanager from contextvars import ContextVar -from typing import Any, Deque, Dict, Generator, List, Optional, Union, cast +from typing import Any, Deque, Dict, Generator, List, Optional, Tuple, Union, cast from homeassistant.helpers.typing import TemplateVarsType import homeassistant.util.dt as dt_util @@ -67,6 +67,20 @@ trace_path_stack_cv: ContextVar[Optional[List[str]]] = ContextVar( ) # Copy of last variables variables_cv: ContextVar[Optional[Any]] = ContextVar("variables_cv", default=None) +# Automation ID + Run ID +trace_id_cv: ContextVar[Optional[Tuple[str, str]]] = ContextVar( + "trace_id_cv", default=None +) + + +def trace_id_set(trace_id: Tuple[str, str]) -> None: + """Set id of the current trace.""" + trace_id_cv.set(trace_id) + + +def trace_id_get() -> Optional[Tuple[str, str]]: + """Get id if the current trace.""" + return trace_id_cv.get() def trace_stack_push(trace_stack_var: ContextVar, node: Any) -> None: diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index d52295c75f7..2880287be94 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -6,6 +6,7 @@ from homeassistant.bootstrap import async_setup_component from homeassistant.components import automation, config from homeassistant.helpers import entity_registry as er +from tests.common import assert_lists_same from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @@ -511,3 +512,426 @@ async def test_list_automation_traces(hass, hass_ws_client): assert trace["timestamp"] assert trace["trigger"] == "event 'test_event2'" assert trace["unique_id"] == "moon" + + +async def test_automation_breakpoints(hass, hass_ws_client): + """Test automation breakpoints.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + async def assert_last_action(automation_id, expected_action, expected_state): + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + trace = response["result"][automation_id][-1] + assert trace["last_action"] == expected_action + assert trace["state"] == expected_state + return trace["run_id"] + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": [ + {"event": "event0"}, + {"event": "event1"}, + {"event": "event2"}, + {"event": "event3"}, + {"event": "event4"}, + {"event": "event5"}, + {"event": "event6"}, + {"event": "event7"}, + {"event": "event8"}, + ], + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "1", + } + ) + response = await client.receive_json() + assert not response["success"] + + await client.send_json( + {"id": next_id(), "type": "automation/debug/breakpoint/list"} + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == [] + + subscription_id = next_id() + await client.send_json( + {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/1", + } + ) + response = await client.receive_json() + assert response["success"] + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/5", + } + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + {"id": next_id(), "type": "automation/debug/breakpoint/list"} + ) + response = await client.receive_json() + assert response["success"] + assert_lists_same( + response["result"], + [ + {"node": "action/1", "run_id": "*", "automation_id": "sun"}, + {"node": "action/5", "run_id": "*", "automation_id": "sun"}, + ], + ) + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/1", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/1", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/step", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/2", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/2", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/continue", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/5", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/5", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/stop", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + await hass.async_block_till_done() + await assert_last_action("sun", "action/5", "stopped") + + +async def test_automation_breakpoints_2(hass, hass_ws_client): + """Test execution resumes and breakpoints are removed after subscription removed.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + async def assert_last_action(automation_id, expected_action, expected_state): + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + trace = response["result"][automation_id][-1] + assert trace["last_action"] == expected_action + assert trace["state"] == expected_state + return trace["run_id"] + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": [ + {"event": "event0"}, + {"event": "event1"}, + {"event": "event2"}, + {"event": "event3"}, + {"event": "event4"}, + {"event": "event5"}, + {"event": "event6"}, + {"event": "event7"}, + {"event": "event8"}, + ], + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + subscription_id = next_id() + await client.send_json( + {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/1", + } + ) + response = await client.receive_json() + assert response["success"] + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/1", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/1", + "run_id": run_id, + } + + # Unsubscribe - execution should resume + await client.send_json( + {"id": next_id(), "type": "unsubscribe_events", "subscription": subscription_id} + ) + response = await client.receive_json() + assert response["success"] + await hass.async_block_till_done() + await assert_last_action("sun", "action/8", "stopped") + + # Should not be possible to set breakpoints + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "1", + } + ) + response = await client.receive_json() + assert not response["success"] + + # Trigger "sun" automation, should finish without stopping on breakpoints + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + + new_run_id = await assert_last_action("sun", "action/8", "stopped") + assert new_run_id != run_id + + +async def test_automation_breakpoints_3(hass, hass_ws_client): + """Test breakpoints can be cleared.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + async def assert_last_action(automation_id, expected_action, expected_state): + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + trace = response["result"][automation_id][-1] + assert trace["last_action"] == expected_action + assert trace["state"] == expected_state + return trace["run_id"] + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": [ + {"event": "event0"}, + {"event": "event1"}, + {"event": "event2"}, + {"event": "event3"}, + {"event": "event4"}, + {"event": "event5"}, + {"event": "event6"}, + {"event": "event7"}, + {"event": "event8"}, + ], + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + subscription_id = next_id() + await client.send_json( + {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/1", + } + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/5", + } + ) + response = await client.receive_json() + assert response["success"] + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/1", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/1", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/continue", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/5", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/5", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/stop", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + await hass.async_block_till_done() + await assert_last_action("sun", "action/5", "stopped") + + # Clear 1st breakpoint + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/clear", + "automation_id": "sun", + "node": "action/1", + } + ) + response = await client.receive_json() + assert response["success"] + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/5", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/5", + "run_id": run_id, + } diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 18c769fee73..7cb4b627a94 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -18,6 +18,7 @@ import homeassistant.components.scene as scene from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON from homeassistant.core import Context, CoreState, callback from homeassistant.helpers import config_validation as cv, script, trace +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -80,14 +81,17 @@ async def test_firing_event_basic(hass, caplog): sequence = cv.SCRIPT_SCHEMA( {"alias": alias, "event": event, "event_data": {"hello": "world"}} ) - with script.trace_action(None): - script_obj = script.Script( - hass, - sequence, - "Test Name", - "test_domain", - running_description="test script", - ) + + # Prepare tracing + trace.trace_get() + + script_obj = script.Script( + hass, + sequence, + "Test Name", + "test_domain", + running_description="test script", + ) await script_obj.async_run(context=context) await hass.async_block_till_done() @@ -100,7 +104,6 @@ async def test_firing_event_basic(hass, caplog): assert f"Executing step {alias}" in caplog.text assert_action_trace( { - "": [{}], "0": [{}], } ) @@ -1215,8 +1218,11 @@ async def test_repeat_count(hass, caplog, count): }, } ) - with script.trace_action(None): - script_obj = script.Script(hass, sequence, "Test Name", "test_domain") + + # Prepare tracing + trace.trace_get() + + script_obj = script.Script(hass, sequence, "Test Name", "test_domain") await script_obj.async_run(context=Context()) await hass.async_block_till_done() @@ -1229,7 +1235,6 @@ async def test_repeat_count(hass, caplog, count): assert caplog.text.count(f"Repeating {alias}") == count assert_action_trace( { - "": [{}], "0": [{}], "0/0/0": [{}] * min(count, script.ACTION_TRACE_NODE_MAX_LEN), } @@ -2348,3 +2353,165 @@ async def test_embedded_wait_for_trigger_in_automation(hass): await hass.async_block_till_done() assert len(mock_calls) == 1 + + +async def test_breakpoints_1(hass): + """Test setting a breakpoint halts execution, and execution can be resumed.""" + event = "test_event" + events = async_capture_events(hass, event) + sequence = cv.SCRIPT_SCHEMA( + [ + {"event": event, "event_data": {"value": 0}}, # Node "0" + {"event": event, "event_data": {"value": 1}}, # Node "1" + {"event": event, "event_data": {"value": 2}}, # Node "2" + {"event": event, "event_data": {"value": 3}}, # Node "3" + {"event": event, "event_data": {"value": 4}}, # Node "4" + {"event": event, "event_data": {"value": 5}}, # Node "5" + {"event": event, "event_data": {"value": 6}}, # Node "6" + {"event": event, "event_data": {"value": 7}}, # Node "7" + ] + ) + logger = logging.getLogger("TEST") + script_obj = script.Script( + hass, + sequence, + "Test Name", + "test_domain", + script_mode="queued", + max_runs=2, + logger=logger, + ) + trace.trace_id_set(("script_1", "1")) + script.breakpoint_set(hass, "script_1", script.RUN_ID_ANY, "1") + script.breakpoint_set(hass, "script_1", script.RUN_ID_ANY, "5") + + breakpoint_hit_event = asyncio.Event() + + @callback + def breakpoint_hit(*_): + breakpoint_hit_event.set() + + async_dispatcher_connect(hass, script.SCRIPT_BREAKPOINT_HIT, breakpoint_hit) + + watch_messages = [] + + @callback + def check_action(): + for message, flag in watch_messages: + if script_obj.last_action and message in script_obj.last_action: + flag.set() + + script_obj.change_listener = check_action + + assert not script_obj.is_running + assert script_obj.runs == 0 + + # Start script, should stop on breakpoint at node "1" + hass.async_create_task(script_obj.async_run(context=Context())) + await breakpoint_hit_event.wait() + assert script_obj.is_running + assert script_obj.runs == 1 + assert len(events) == 1 + assert events[-1].data["value"] == 0 + + # Single step script, should stop at node "2" + breakpoint_hit_event.clear() + script.debug_step(hass, "script_1", "1") + await breakpoint_hit_event.wait() + assert script_obj.is_running + assert script_obj.runs == 1 + assert len(events) == 2 + assert events[-1].data["value"] == 1 + + # Single step script, should stop at node "3" + breakpoint_hit_event.clear() + script.debug_step(hass, "script_1", "1") + await breakpoint_hit_event.wait() + assert script_obj.is_running + assert script_obj.runs == 1 + assert len(events) == 3 + assert events[-1].data["value"] == 2 + + # Resume script, should stop on breakpoint at node "5" + breakpoint_hit_event.clear() + script.debug_continue(hass, "script_1", "1") + await breakpoint_hit_event.wait() + assert script_obj.is_running + assert script_obj.runs == 1 + assert len(events) == 5 + assert events[-1].data["value"] == 4 + + # Resume script, should run until completion + script.debug_continue(hass, "script_1", "1") + await hass.async_block_till_done() + assert not script_obj.is_running + assert script_obj.runs == 0 + assert len(events) == 8 + assert events[-1].data["value"] == 7 + + +async def test_breakpoints_2(hass): + """Test setting a breakpoint halts execution, and execution can be aborted.""" + event = "test_event" + events = async_capture_events(hass, event) + sequence = cv.SCRIPT_SCHEMA( + [ + {"event": event, "event_data": {"value": 0}}, # Node "0" + {"event": event, "event_data": {"value": 1}}, # Node "1" + {"event": event, "event_data": {"value": 2}}, # Node "2" + {"event": event, "event_data": {"value": 3}}, # Node "3" + {"event": event, "event_data": {"value": 4}}, # Node "4" + {"event": event, "event_data": {"value": 5}}, # Node "5" + {"event": event, "event_data": {"value": 6}}, # Node "6" + {"event": event, "event_data": {"value": 7}}, # Node "7" + ] + ) + logger = logging.getLogger("TEST") + script_obj = script.Script( + hass, + sequence, + "Test Name", + "test_domain", + script_mode="queued", + max_runs=2, + logger=logger, + ) + trace.trace_id_set(("script_1", "1")) + script.breakpoint_set(hass, "script_1", script.RUN_ID_ANY, "1") + script.breakpoint_set(hass, "script_1", script.RUN_ID_ANY, "5") + + breakpoint_hit_event = asyncio.Event() + + @callback + def breakpoint_hit(*_): + breakpoint_hit_event.set() + + async_dispatcher_connect(hass, script.SCRIPT_BREAKPOINT_HIT, breakpoint_hit) + + watch_messages = [] + + @callback + def check_action(): + for message, flag in watch_messages: + if script_obj.last_action and message in script_obj.last_action: + flag.set() + + script_obj.change_listener = check_action + + assert not script_obj.is_running + assert script_obj.runs == 0 + + # Start script, should stop on breakpoint at node "1" + hass.async_create_task(script_obj.async_run(context=Context())) + await breakpoint_hit_event.wait() + assert script_obj.is_running + assert script_obj.runs == 1 + assert len(events) == 1 + assert events[-1].data["value"] == 0 + + # Abort script + script.debug_stop(hass, "script_1", "1") + await hass.async_block_till_done() + assert not script_obj.is_running + assert script_obj.runs == 0 + assert len(events) == 1 From a50e9812cb8f952c3bd36a960b4ac286c79e4b24 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 9 Mar 2021 22:40:17 -0800 Subject: [PATCH 276/831] Fix automations with traces. (#47705) --- homeassistant/components/automation/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 6410954191d..7e1352afc2f 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -379,7 +379,7 @@ class LimitedSizeDict(OrderedDict): def trace_automation(hass, unique_id, config, trigger, context): """Trace action execution of automation with automation_id.""" automation_trace = AutomationTrace(unique_id, config, trigger, context) - trace_id_set((unique_id, automation_trace.runid)) + trace_id_set((unique_id, automation_trace.run_id)) if unique_id: automation_traces = hass.data[DATA_AUTOMATION_TRACE] From 5c70dd8ab4f531947f6a4796ad4216a984875e5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Mar 2021 09:40:03 +0100 Subject: [PATCH 277/831] Bump codecov/codecov-action from v1.2.1 to v1.2.2 (#47707) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from v1.2.1 to v1.2.2. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v1.2.1...1f8f3abcccf7960749744fd13547965f0e7d1bdd) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b7599340506..f395a4b3c42 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -739,4 +739,4 @@ jobs: coverage report --fail-under=94 coverage xml - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1.2.1 + uses: codecov/codecov-action@v1.2.2 From 5bc0e9a50f06253df69e74d8eab05debc69ce415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Wed, 10 Mar 2021 10:25:04 +0100 Subject: [PATCH 278/831] Fix aemet temperatures with a value of 0 (#47680) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * aemet: catch TypeError exceptions format_float() and format_int() should also catch possible TypeError exceptions. Signed-off-by: Álvaro Fernández Rojas * aemet: correctly parse temperatures with a value of 0 Right now, when a temperature with a value of 0 is provided by the API, the if condition isn't satisfied, return None instead of 0. Signed-off-by: Álvaro Fernández Rojas * aemet: group format int/float exceptions Signed-off-by: Álvaro Fernández Rojas --- .../aemet/weather_update_coordinator.py | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/aemet/weather_update_coordinator.py b/homeassistant/components/aemet/weather_update_coordinator.py index 619429c9a5b..1a70baa6765 100644 --- a/homeassistant/components/aemet/weather_update_coordinator.py +++ b/homeassistant/components/aemet/weather_update_coordinator.py @@ -98,7 +98,7 @@ def format_float(value) -> float: """Try converting string to float.""" try: return float(value) - except ValueError: + except (TypeError, ValueError): return None @@ -106,7 +106,7 @@ def format_int(value) -> int: """Try converting string to int.""" try: return int(value) - except ValueError: + except (TypeError, ValueError): return None @@ -535,9 +535,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): def _get_temperature(day_data, hour): """Get temperature (hour) from weather data.""" val = get_forecast_hour_value(day_data[AEMET_ATTR_TEMPERATURE], hour) - if val: - return format_int(val) - return None + return format_int(val) @staticmethod def _get_temperature_day(day_data): @@ -545,9 +543,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): val = get_forecast_day_value( day_data[AEMET_ATTR_TEMPERATURE], key=AEMET_ATTR_MAX ) - if val: - return format_int(val) - return None + return format_int(val) @staticmethod def _get_temperature_low_day(day_data): @@ -555,17 +551,13 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): val = get_forecast_day_value( day_data[AEMET_ATTR_TEMPERATURE], key=AEMET_ATTR_MIN ) - if val: - return format_int(val) - return None + return format_int(val) @staticmethod def _get_temperature_feeling(day_data, hour): """Get temperature from weather data.""" val = get_forecast_hour_value(day_data[AEMET_ATTR_TEMPERATURE_FEELING], hour) - if val: - return format_int(val) - return None + return format_int(val) def _get_town_id(self): """Get town ID from weather data.""" From 461e766a93556b47996ab35ae85cd8123cbcfa7d Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Wed, 10 Mar 2021 12:52:55 +0100 Subject: [PATCH 279/831] Add device class CO2 to various integrations (#47676) * Add device class CO2 to Fibaro * Add device class CO2 to Awair * Add device class CO2 to Tasmota * Add device class CO2 to Netatmo * Add device class CO2 to Ambient Station * Update Tasmota tests * Remove icon --- homeassistant/components/ambient_station/__init__.py | 3 ++- homeassistant/components/awair/const.py | 3 ++- homeassistant/components/fibaro/sensor.py | 9 ++++++++- homeassistant/components/netatmo/sensor.py | 3 ++- homeassistant/components/tasmota/sensor.py | 3 ++- tests/components/tasmota/test_sensor.py | 8 ++++---- 6 files changed, 20 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index b4ac6992459..bee5ab33db7 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -18,6 +18,7 @@ from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, CONF_API_KEY, DEGREE, + DEVICE_CLASS_CO2, EVENT_HOMEASSISTANT_STOP, IRRADIATION_WATTS_PER_SQUARE_METER, LIGHT_LUX, @@ -159,7 +160,7 @@ SENSOR_TYPES = { TYPE_BATT8: ("Battery 8", None, BINARY_SENSOR, "battery"), TYPE_BATT9: ("Battery 9", None, BINARY_SENSOR, "battery"), TYPE_BATTOUT: ("Battery", None, BINARY_SENSOR, "battery"), - TYPE_CO2: ("co2", CONCENTRATION_PARTS_PER_MILLION, SENSOR, None), + TYPE_CO2: ("co2", CONCENTRATION_PARTS_PER_MILLION, SENSOR, DEVICE_CLASS_CO2), TYPE_DAILYRAININ: ("Daily Rain", "in", SENSOR, None), TYPE_DEWPOINT: ("Dew Point", TEMP_FAHRENHEIT, SENSOR, "temperature"), TYPE_EVENTRAININ: ("Event Rain", "in", SENSOR, None), diff --git a/homeassistant/components/awair/const.py b/homeassistant/components/awair/const.py index 44490b8401f..2853ef9dd6c 100644 --- a/homeassistant/components/awair/const.py +++ b/homeassistant/components/awair/const.py @@ -12,6 +12,7 @@ from homeassistant.const import ( CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONCENTRATION_PARTS_PER_BILLION, CONCENTRATION_PARTS_PER_MILLION, + DEVICE_CLASS_CO2, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, @@ -104,7 +105,7 @@ SENSOR_TYPES = { ATTR_UNIQUE_ID: "PM10", # matches legacy format }, API_CO2: { - ATTR_DEVICE_CLASS: None, + ATTR_DEVICE_CLASS: DEVICE_CLASS_CO2, ATTR_ICON: "mdi:cloud", ATTR_UNIT: CONCENTRATION_PARTS_PER_MILLION, ATTR_LABEL: "Carbon dioxide", diff --git a/homeassistant/components/fibaro/sensor.py b/homeassistant/components/fibaro/sensor.py index 4e9af8803f2..40a843bc7bb 100644 --- a/homeassistant/components/fibaro/sensor.py +++ b/homeassistant/components/fibaro/sensor.py @@ -2,6 +2,7 @@ from homeassistant.components.sensor import DOMAIN from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, + DEVICE_CLASS_CO2, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, @@ -27,7 +28,13 @@ SENSOR_TYPES = { "mdi:fire", None, ], - "CO2": ["CO2", CONCENTRATION_PARTS_PER_MILLION, "mdi:cloud", None], + "CO2": [ + "CO2", + CONCENTRATION_PARTS_PER_MILLION, + None, + None, + DEVICE_CLASS_CO2, + ], "com.fibaro.humiditySensor": [ "Humidity", PERCENTAGE, diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index 9176b670bea..efda94d6399 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -8,6 +8,7 @@ from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, DEGREE, DEVICE_CLASS_BATTERY, + DEVICE_CLASS_CO2, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_SIGNAL_STRENGTH, @@ -52,7 +53,7 @@ SUPPORTED_PUBLIC_SENSOR_TYPES = [ SENSOR_TYPES = { "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], + "co2": ["CO2", CONCENTRATION_PARTS_PER_MILLION, None, DEVICE_CLASS_CO2, 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], diff --git a/homeassistant/components/tasmota/sensor.py b/homeassistant/components/tasmota/sensor.py index 39577e9e558..0387e835522 100644 --- a/homeassistant/components/tasmota/sensor.py +++ b/homeassistant/components/tasmota/sensor.py @@ -9,6 +9,7 @@ from homeassistant.const import ( CONCENTRATION_PARTS_PER_BILLION, CONCENTRATION_PARTS_PER_MILLION, DEVICE_CLASS_BATTERY, + DEVICE_CLASS_CO2, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, @@ -52,7 +53,7 @@ SENSOR_DEVICE_CLASS_ICON_MAP = { hc.SENSOR_APPARENT_POWERUSAGE: {DEVICE_CLASS: DEVICE_CLASS_POWER}, hc.SENSOR_BATTERY: {DEVICE_CLASS: DEVICE_CLASS_BATTERY}, hc.SENSOR_CCT: {ICON: "mdi:temperature-kelvin"}, - hc.SENSOR_CO2: {ICON: "mdi:molecule-co2"}, + hc.SENSOR_CO2: {DEVICE_CLASS: DEVICE_CLASS_CO2}, hc.SENSOR_COLOR_BLUE: {ICON: "mdi:palette"}, hc.SENSOR_COLOR_GREEN: {ICON: "mdi:palette"}, hc.SENSOR_COLOR_RED: {ICON: "mdi:palette"}, diff --git a/tests/components/tasmota/test_sensor.py b/tests/components/tasmota/test_sensor.py index c08b1b531de..2b7c388ca2f 100644 --- a/tests/components/tasmota/test_sensor.py +++ b/tests/components/tasmota/test_sensor.py @@ -446,9 +446,9 @@ async def test_attributes(hass, mqtt_mock, setup_tasmota): assert state.attributes.get("unit_of_measurement") == "°C" state = hass.states.get("sensor.tasmota_beer_CarbonDioxide") - assert state.attributes.get("device_class") is None + assert state.attributes.get("device_class") == "carbon_dioxide" assert state.attributes.get("friendly_name") == "Tasmota Beer CarbonDioxide" - assert state.attributes.get("icon") == "mdi:molecule-co2" + assert state.attributes.get("icon") is None assert state.attributes.get("unit_of_measurement") == "ppm" @@ -516,9 +516,9 @@ async def test_indexed_sensor_attributes(hass, mqtt_mock, setup_tasmota): assert state.attributes.get("unit_of_measurement") == "°C" state = hass.states.get("sensor.tasmota_dummy2_carbondioxide_1") - assert state.attributes.get("device_class") is None + assert state.attributes.get("device_class") == "carbon_dioxide" assert state.attributes.get("friendly_name") == "Tasmota Dummy2 CarbonDioxide 1" - assert state.attributes.get("icon") == "mdi:molecule-co2" + assert state.attributes.get("icon") is None assert state.attributes.get("unit_of_measurement") == "ppm" From d53f1e98acac70b217c41a7f4e9005f0508983bc Mon Sep 17 00:00:00 2001 From: David Bonnes Date: Wed, 10 Mar 2021 17:58:04 +0000 Subject: [PATCH 280/831] bump client library (#47722) --- homeassistant/components/evohome/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/evohome/manifest.json b/homeassistant/components/evohome/manifest.json index 8bcecca551b..e707387ce4f 100644 --- a/homeassistant/components/evohome/manifest.json +++ b/homeassistant/components/evohome/manifest.json @@ -2,6 +2,6 @@ "domain": "evohome", "name": "Honeywell Total Connect Comfort (Europe)", "documentation": "https://www.home-assistant.io/integrations/evohome", - "requirements": ["evohome-async==0.3.5.post1"], + "requirements": ["evohome-async==0.3.8"], "codeowners": ["@zxdavb"] } diff --git a/requirements_all.txt b/requirements_all.txt index e16a101ce80..2a4242bef65 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -569,7 +569,7 @@ eternalegypt==0.0.12 # evdev==1.1.2 # homeassistant.components.evohome -evohome-async==0.3.5.post1 +evohome-async==0.3.8 # homeassistant.components.faa_delays faadelays==0.0.6 From 2103335323acd5575e050a3840f5a1e9ea01ee37 Mon Sep 17 00:00:00 2001 From: David Bonnes Date: Wed, 10 Mar 2021 17:58:37 +0000 Subject: [PATCH 281/831] Bump incomfort client to 0.4.4 (#47718) * bump incomfort client * bump client to 0.4.4 * restore launch.json --- homeassistant/components/incomfort/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/incomfort/manifest.json b/homeassistant/components/incomfort/manifest.json index 80b6952c383..891cbb20be4 100644 --- a/homeassistant/components/incomfort/manifest.json +++ b/homeassistant/components/incomfort/manifest.json @@ -2,6 +2,6 @@ "domain": "incomfort", "name": "Intergas InComfort/Intouch Lan2RF gateway", "documentation": "https://www.home-assistant.io/integrations/incomfort", - "requirements": ["incomfort-client==0.4.0"], + "requirements": ["incomfort-client==0.4.4"], "codeowners": ["@zxdavb"] } diff --git a/requirements_all.txt b/requirements_all.txt index 2a4242bef65..7bc5592a626 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -817,7 +817,7 @@ iglo==1.2.7 ihcsdk==2.7.0 # homeassistant.components.incomfort -incomfort-client==0.4.0 +incomfort-client==0.4.4 # homeassistant.components.influxdb influxdb-client==1.14.0 From 7c8851264fbaa3574e58095a47b39f73720cab7f Mon Sep 17 00:00:00 2001 From: CurrentThread <62957822+CurrentThread@users.noreply.github.com> Date: Wed, 10 Mar 2021 19:12:58 +0100 Subject: [PATCH 282/831] Use LONGTEXT column instead of TEXT for MySQL/MariaDB and migrate existing databases (#47026) --- .../components/recorder/migration.py | 42 +++++++++++++++++++ homeassistant/components/recorder/models.py | 7 ++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index aeb62cc111d..e730b1af239 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -204,6 +204,44 @@ def _add_columns(engine, table_name, columns_def): ) +def _modify_columns(engine, table_name, columns_def): + """Modify columns in a table.""" + _LOGGER.warning( + "Modifying columns %s in table %s. Note: this can take several " + "minutes on large databases and slow computers. Please " + "be patient!", + ", ".join(column.split(" ")[0] for column in columns_def), + table_name, + ) + columns_def = [f"MODIFY {col_def}" for col_def in columns_def] + + try: + engine.execute( + text( + "ALTER TABLE {table} {columns_def}".format( + table=table_name, columns_def=", ".join(columns_def) + ) + ) + ) + return + except (InternalError, OperationalError): + _LOGGER.info("Unable to use quick column modify. Modifying 1 by 1") + + for column_def in columns_def: + try: + engine.execute( + text( + "ALTER TABLE {table} {column_def}".format( + table=table_name, column_def=column_def + ) + ) + ) + except (InternalError, OperationalError): + _LOGGER.exception( + "Could not modify column %s in table %s", column_def, table_name + ) + + def _update_states_table_with_foreign_key_options(engine): """Add the options to foreign key constraints.""" inspector = reflection.Inspector.from_engine(engine) @@ -321,6 +359,10 @@ def _apply_update(engine, new_version, old_version): elif new_version == 11: _create_index(engine, "states", "ix_states_old_state_id") _update_states_table_with_foreign_key_options(engine) + elif new_version == 12: + if engine.dialect.name == "mysql": + _modify_columns(engine, "events", ["event_data LONGTEXT"]) + _modify_columns(engine, "states", ["attributes LONGTEXT"]) else: raise ValueError(f"No schema migration defined for version {new_version}") diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index 69e2115ce34..551abeac15a 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -13,6 +13,7 @@ from sqlalchemy import ( Text, distinct, ) +from sqlalchemy.dialects import mysql from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship from sqlalchemy.orm.session import Session @@ -25,7 +26,7 @@ import homeassistant.util.dt as dt_util # pylint: disable=invalid-name Base = declarative_base() -SCHEMA_VERSION = 11 +SCHEMA_VERSION = 12 _LOGGER = logging.getLogger(__name__) @@ -49,7 +50,7 @@ class Events(Base): # type: ignore __tablename__ = TABLE_EVENTS event_id = Column(Integer, primary_key=True) event_type = Column(String(32)) - event_data = Column(Text) + event_data = Column(Text().with_variant(mysql.LONGTEXT, "mysql")) origin = Column(String(32)) time_fired = Column(DateTime(timezone=True), index=True) created = Column(DateTime(timezone=True), default=dt_util.utcnow) @@ -109,7 +110,7 @@ class States(Base): # type: ignore domain = Column(String(64)) entity_id = Column(String(255)) state = Column(String(255)) - attributes = Column(Text) + attributes = Column(Text().with_variant(mysql.LONGTEXT, "mysql")) event_id = Column( Integer, ForeignKey("events.event_id", ondelete="CASCADE"), index=True ) From 78c974d5278a5fb975ced94a6a37ec609c21b99e Mon Sep 17 00:00:00 2001 From: Mike Keesey Date: Wed, 10 Mar 2021 11:19:04 -0700 Subject: [PATCH 283/831] Refactor Harmony tests to better follow Home Assistant conventions (#47141) --- tests/components/harmony/conftest.py | 27 +++- .../harmony/test_connection_changes.py | 67 --------- .../{test_commands.py => test_remote.py} | 131 +++++++++++++++++- ...est_activity_changes.py => test_switch.py} | 122 +++++++--------- 4 files changed, 199 insertions(+), 148 deletions(-) delete mode 100644 tests/components/harmony/test_connection_changes.py rename tests/components/harmony/{test_commands.py => test_remote.py} (62%) rename tests/components/harmony/{test_activity_changes.py => test_switch.py} (64%) diff --git a/tests/components/harmony/conftest.py b/tests/components/harmony/conftest.py index 29e897916b9..5072eb0deb9 100644 --- a/tests/components/harmony/conftest.py +++ b/tests/components/harmony/conftest.py @@ -37,10 +37,10 @@ IDS_TO_DEVICES = { class FakeHarmonyClient: """FakeHarmonyClient to mock away network calls.""" - def __init__( + def initialize( self, ip_address: str = "", callbacks: ClientCallbackType = MagicMock() ): - """Initialize FakeHarmonyClient class.""" + """Initialize FakeHarmonyClient class to capture callbacks.""" self._activity_name = "Watch TV" self.close = AsyncMock() self.send_commands = AsyncMock() @@ -49,6 +49,8 @@ class FakeHarmonyClient: self._callbacks = callbacks self.fw_version = "123.456" + return self + async def connect(self): """Connect and call the appropriate callbacks.""" self._callbacks.connect(None) @@ -130,13 +132,28 @@ class FakeHarmonyClient: ) return config + def mock_reconnection(self): + """Simulate reconnection to the hub.""" + self._callbacks.connect(None) + + def mock_disconnection(self): + """Simulate disconnection to the hub.""" + self._callbacks.disconnect(None) + @pytest.fixture() -def mock_hc(): - """Create a mock HarmonyClient.""" +def harmony_client(): + """Create the FakeHarmonyClient instance.""" + return FakeHarmonyClient() + + +@pytest.fixture() +def mock_hc(harmony_client): + """Patch the real HarmonyClient with initialization side effect.""" + with patch( "homeassistant.components.harmony.data.HarmonyClient", - side_effect=FakeHarmonyClient, + side_effect=harmony_client.initialize, ) as fake: yield fake diff --git a/tests/components/harmony/test_connection_changes.py b/tests/components/harmony/test_connection_changes.py deleted file mode 100644 index 15d46298855..00000000000 --- a/tests/components/harmony/test_connection_changes.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Test the Logitech Harmony Hub entities with connection state changes.""" - -from datetime import timedelta - -from homeassistant.components.harmony.const import DOMAIN -from homeassistant.const import ( - CONF_HOST, - CONF_NAME, - STATE_OFF, - STATE_ON, - STATE_UNAVAILABLE, -) -from homeassistant.util import utcnow - -from .const import ENTITY_PLAY_MUSIC, ENTITY_REMOTE, ENTITY_WATCH_TV, HUB_NAME - -from tests.common import MockConfigEntry, async_fire_time_changed - - -async def test_connection_state_changes(mock_hc, hass, mock_write_config): - """Ensure connection changes are reflected in the switch states.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - data = hass.data[DOMAIN][entry.entry_id] - - # mocks start with current activity == Watch TV - assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) - assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) - assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) - - data._disconnected() - await hass.async_block_till_done() - - # Entities do not immediately show as unavailable - assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) - assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) - assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) - - future_time = utcnow() + timedelta(seconds=10) - async_fire_time_changed(hass, future_time) - await hass.async_block_till_done() - assert hass.states.is_state(ENTITY_REMOTE, STATE_UNAVAILABLE) - assert hass.states.is_state(ENTITY_WATCH_TV, STATE_UNAVAILABLE) - assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_UNAVAILABLE) - - data._connected() - await hass.async_block_till_done() - - assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) - assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) - assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) - - data._disconnected() - data._connected() - future_time = utcnow() + timedelta(seconds=10) - async_fire_time_changed(hass, future_time) - - await hass.async_block_till_done() - assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) - assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) - assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) diff --git a/tests/components/harmony/test_commands.py b/tests/components/harmony/test_remote.py similarity index 62% rename from tests/components/harmony/test_commands.py rename to tests/components/harmony/test_remote.py index 62056a08e1d..8c4d67e1117 100644 --- a/tests/components/harmony/test_commands.py +++ b/tests/components/harmony/test_remote.py @@ -1,4 +1,6 @@ -"""Test sending commands to the Harmony Hub remote.""" +"""Test the Logitech Harmony Hub remote.""" + +from datetime import timedelta from aioharmony.const import SendCommandDevice @@ -9,6 +11,7 @@ from homeassistant.components.harmony.const import ( ) from homeassistant.components.harmony.remote import ATTR_CHANNEL, ATTR_DELAY_SECS from homeassistant.components.remote import ( + ATTR_ACTIVITY, ATTR_COMMAND, ATTR_DEVICE, ATTR_NUM_REPEATS, @@ -16,18 +19,136 @@ from homeassistant.components.remote import ( DEFAULT_HOLD_SECS, DOMAIN as REMOTE_DOMAIN, SERVICE_SEND_COMMAND, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, ) -from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME +from homeassistant.const import ( + ATTR_ENTITY_ID, + CONF_HOST, + CONF_NAME, + STATE_OFF, + STATE_ON, + STATE_UNAVAILABLE, +) +from homeassistant.util import utcnow -from .conftest import TV_DEVICE_ID, TV_DEVICE_NAME -from .const import ENTITY_REMOTE, HUB_NAME +from .conftest import ACTIVITIES_TO_IDS, TV_DEVICE_ID, TV_DEVICE_NAME +from .const import ENTITY_PLAY_MUSIC, ENTITY_REMOTE, ENTITY_WATCH_TV, HUB_NAME -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_fire_time_changed PLAY_COMMAND = "Play" STOP_COMMAND = "Stop" +async def test_connection_state_changes( + harmony_client, mock_hc, hass, mock_write_config +): + """Ensure connection changes are reflected in the remote state.""" + entry = MockConfigEntry( + domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} + ) + + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + # mocks start with current activity == Watch TV + assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) + + harmony_client.mock_disconnection() + await hass.async_block_till_done() + + # Entities do not immediately show as unavailable + assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) + + future_time = utcnow() + timedelta(seconds=10) + async_fire_time_changed(hass, future_time) + await hass.async_block_till_done() + assert hass.states.is_state(ENTITY_REMOTE, STATE_UNAVAILABLE) + + harmony_client.mock_reconnection() + await hass.async_block_till_done() + + assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) + + harmony_client.mock_disconnection() + harmony_client.mock_reconnection() + future_time = utcnow() + timedelta(seconds=10) + async_fire_time_changed(hass, future_time) + + await hass.async_block_till_done() + assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) + + +async def test_remote_toggles(mock_hc, hass, mock_write_config): + """Ensure calls to the remote also updates the switches.""" + entry = MockConfigEntry( + domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} + ) + + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + # mocks start with current activity == Watch TV + assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) + assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) + assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) + + # turn off remote + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: ENTITY_REMOTE}, + blocking=True, + ) + await hass.async_block_till_done() + + assert hass.states.is_state(ENTITY_REMOTE, STATE_OFF) + assert hass.states.is_state(ENTITY_WATCH_TV, STATE_OFF) + assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) + + # turn on remote, restoring the last activity + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: ENTITY_REMOTE}, + blocking=True, + ) + await hass.async_block_till_done() + + assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) + assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) + assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) + + # send new activity command, with activity name + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: ENTITY_REMOTE, ATTR_ACTIVITY: "Play Music"}, + blocking=True, + ) + await hass.async_block_till_done() + + assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) + assert hass.states.is_state(ENTITY_WATCH_TV, STATE_OFF) + assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_ON) + + # send new activity command, with activity id + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: ENTITY_REMOTE, ATTR_ACTIVITY: ACTIVITIES_TO_IDS["Watch TV"]}, + blocking=True, + ) + await hass.async_block_till_done() + + assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) + assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) + assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) + + async def test_async_send_command(mock_hc, hass, mock_write_config): """Ensure calls to send remote commands properly propagate to devices.""" entry = MockConfigEntry( diff --git a/tests/components/harmony/test_activity_changes.py b/tests/components/harmony/test_switch.py similarity index 64% rename from tests/components/harmony/test_activity_changes.py rename to tests/components/harmony/test_switch.py index dbbc6beef5b..1940c54e112 100644 --- a/tests/components/harmony/test_activity_changes.py +++ b/tests/components/harmony/test_switch.py @@ -1,6 +1,8 @@ """Test the Logitech Harmony Hub activity switches.""" + +from datetime import timedelta + from homeassistant.components.harmony.const import DOMAIN -from homeassistant.components.remote import ATTR_ACTIVITY, DOMAIN as REMOTE_DOMAIN from homeassistant.components.switch import ( DOMAIN as SWITCH_DOMAIN, SERVICE_TURN_OFF, @@ -12,12 +14,58 @@ from homeassistant.const import ( CONF_NAME, STATE_OFF, STATE_ON, + STATE_UNAVAILABLE, ) +from homeassistant.util import utcnow -from .conftest import ACTIVITIES_TO_IDS from .const import ENTITY_PLAY_MUSIC, ENTITY_REMOTE, ENTITY_WATCH_TV, HUB_NAME -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_fire_time_changed + + +async def test_connection_state_changes( + harmony_client, mock_hc, hass, mock_write_config +): + """Ensure connection changes are reflected in the switch states.""" + entry = MockConfigEntry( + domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} + ) + + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + # mocks start with current activity == Watch TV + assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) + assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) + + harmony_client.mock_disconnection() + await hass.async_block_till_done() + + # Entities do not immediately show as unavailable + assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) + assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) + + future_time = utcnow() + timedelta(seconds=10) + async_fire_time_changed(hass, future_time) + await hass.async_block_till_done() + assert hass.states.is_state(ENTITY_WATCH_TV, STATE_UNAVAILABLE) + assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_UNAVAILABLE) + + harmony_client.mock_reconnection() + await hass.async_block_till_done() + + assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) + assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) + + harmony_client.mock_disconnection() + harmony_client.mock_reconnection() + future_time = utcnow() + timedelta(seconds=10) + async_fire_time_changed(hass, future_time) + + await hass.async_block_till_done() + assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) + assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) async def test_switch_toggles(mock_hc, hass, mock_write_config): @@ -54,74 +102,6 @@ async def test_switch_toggles(mock_hc, hass, mock_write_config): assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) -async def test_remote_toggles(mock_hc, hass, mock_write_config): - """Ensure calls to the remote also updates the switches.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - # mocks start with current activity == Watch TV - assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) - assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) - assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) - - # turn off remote - await hass.services.async_call( - REMOTE_DOMAIN, - SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: ENTITY_REMOTE}, - blocking=True, - ) - await hass.async_block_till_done() - - assert hass.states.is_state(ENTITY_REMOTE, STATE_OFF) - assert hass.states.is_state(ENTITY_WATCH_TV, STATE_OFF) - assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) - - # turn on remote, restoring the last activity - await hass.services.async_call( - REMOTE_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: ENTITY_REMOTE}, - blocking=True, - ) - await hass.async_block_till_done() - - assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) - assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) - assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) - - # send new activity command, with activity name - await hass.services.async_call( - REMOTE_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: ENTITY_REMOTE, ATTR_ACTIVITY: "Play Music"}, - blocking=True, - ) - await hass.async_block_till_done() - - assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) - assert hass.states.is_state(ENTITY_WATCH_TV, STATE_OFF) - assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_ON) - - # send new activity command, with activity id - await hass.services.async_call( - REMOTE_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: ENTITY_REMOTE, ATTR_ACTIVITY: ACTIVITIES_TO_IDS["Watch TV"]}, - blocking=True, - ) - await hass.async_block_till_done() - - assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) - assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) - assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) - - async def _toggle_switch_and_wait(hass, service_name, entity): await hass.services.async_call( SWITCH_DOMAIN, From 3ad4c26f982deb7d4fe7bf1855bcc7e2defc546b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 10 Mar 2021 10:21:51 -0800 Subject: [PATCH 284/831] Allow SSDP discovery modern Hue hubs (#47725) --- homeassistant/components/hue/config_flow.py | 7 +++++-- tests/components/hue/test_config_flow.py | 17 +++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index 580b69251c2..2c3a90318b7 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -25,7 +25,7 @@ from .const import ( # pylint: disable=unused-import ) from .errors import AuthenticationRequired, CannotConnect -HUE_MANUFACTURERURL = "http://www.philips.com" +HUE_MANUFACTURERURL = ("http://www.philips.com", "http://www.philips-hue.com") HUE_IGNORED_BRIDGE_NAMES = ["Home Assistant Bridge", "Espalexa"] HUE_MANUAL_BRIDGE_ID = "manual" @@ -179,7 +179,10 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): host is already configured and delegate to the import step if not. """ # Filter out non-Hue bridges #1 - if discovery_info.get(ssdp.ATTR_UPNP_MANUFACTURER_URL) != HUE_MANUFACTURERURL: + if ( + discovery_info.get(ssdp.ATTR_UPNP_MANUFACTURER_URL) + not in HUE_MANUFACTURERURL + ): return self.async_abort(reason="not_hue_bridge") # Filter out non-Hue bridges #2 diff --git a/tests/components/hue/test_config_flow.py b/tests/components/hue/test_config_flow.py index 57f4bd7fbca..9ba34f23bf4 100644 --- a/tests/components/hue/test_config_flow.py +++ b/tests/components/hue/test_config_flow.py @@ -375,14 +375,15 @@ async def test_flow_link_unknown_host(hass): assert result["reason"] == "cannot_connect" -async def test_bridge_ssdp(hass): +@pytest.mark.parametrize("mf_url", config_flow.HUE_MANUFACTURERURL) +async def test_bridge_ssdp(hass, mf_url): """Test a bridge being discovered.""" result = await hass.config_entries.flow.async_init( const.DOMAIN, context={"source": "ssdp"}, data={ ssdp.ATTR_SSDP_LOCATION: "http://0.0.0.0/", - ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_UPNP_MANUFACTURER_URL: mf_url, ssdp.ATTR_UPNP_SERIAL: "1234", }, ) @@ -411,7 +412,7 @@ async def test_bridge_ssdp_emulated_hue(hass): data={ ssdp.ATTR_SSDP_LOCATION: "http://0.0.0.0/", ssdp.ATTR_UPNP_FRIENDLY_NAME: "Home Assistant Bridge", - ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0], ssdp.ATTR_UPNP_SERIAL: "1234", }, ) @@ -426,7 +427,7 @@ async def test_bridge_ssdp_missing_location(hass): const.DOMAIN, context={"source": "ssdp"}, data={ - ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0], ssdp.ATTR_UPNP_SERIAL: "1234", }, ) @@ -442,7 +443,7 @@ async def test_bridge_ssdp_missing_serial(hass): context={"source": "ssdp"}, data={ ssdp.ATTR_SSDP_LOCATION: "http://0.0.0.0/", - ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0], }, ) @@ -458,7 +459,7 @@ async def test_bridge_ssdp_espalexa(hass): data={ ssdp.ATTR_SSDP_LOCATION: "http://0.0.0.0/", ssdp.ATTR_UPNP_FRIENDLY_NAME: "Espalexa (0.0.0.0)", - ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0], ssdp.ATTR_UPNP_SERIAL: "1234", }, ) @@ -478,7 +479,7 @@ async def test_bridge_ssdp_already_configured(hass): context={"source": "ssdp"}, data={ ssdp.ATTR_SSDP_LOCATION: "http://0.0.0.0/", - ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0], ssdp.ATTR_UPNP_SERIAL: "1234", }, ) @@ -617,7 +618,7 @@ async def test_ssdp_discovery_update_configuration(hass): context={"source": "ssdp"}, data={ ssdp.ATTR_SSDP_LOCATION: "http://1.1.1.1/", - ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0], ssdp.ATTR_UPNP_SERIAL: "aabbccddeeff", }, ) From 10535018cc6575106f207bb0cbc4426abe16cfdc Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 10 Mar 2021 20:20:51 +0100 Subject: [PATCH 285/831] Improve HomeKit discovered Hue config flow (#47729) --- homeassistant/components/hue/config_flow.py | 11 +++++++++++ tests/components/hue/test_config_flow.py | 9 ++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index 2c3a90318b7..95e4a1ad7f2 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -210,6 +210,17 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self.bridge = bridge return await self.async_step_link() + async def async_step_homekit(self, discovery_info): + """Handle a discovered Hue bridge on HomeKit. + + The bridge ID communicated over HomeKit differs, so we cannot use that + as the unique identifier. Therefore, this method uses discovery without + a unique ID. + """ + self.bridge = self._async_get_bridge(discovery_info[CONF_HOST]) + await self._async_handle_discovery_without_unique_id() + return await self.async_step_link() + async def async_step_import(self, import_info): """Import a new bridge as a config entry. diff --git a/tests/components/hue/test_config_flow.py b/tests/components/hue/test_config_flow.py index 9ba34f23bf4..12a360cdf94 100644 --- a/tests/components/hue/test_config_flow.py +++ b/tests/components/hue/test_config_flow.py @@ -571,7 +571,14 @@ async def test_bridge_homekit(hass, aioclient_mock): ) assert result["type"] == "form" - assert result["step_id"] == "init" + assert result["step_id"] == "link" + + flow = next( + flow + for flow in hass.config_entries.flow.async_progress() + if flow["flow_id"] == result["flow_id"] + ) + assert flow["context"]["unique_id"] == config_entries.DEFAULT_DISCOVERY_UNIQUE_ID async def test_bridge_import_already_configured(hass): From ff09643b33461876f754a1b6ee75c8a102e2598d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Wed, 10 Mar 2021 21:31:37 +0100 Subject: [PATCH 286/831] Add Tado weather support (#44807) --- homeassistant/components/tado/__init__.py | 11 +- homeassistant/components/tado/const.py | 16 +++ homeassistant/components/tado/entity.py | 22 +++- homeassistant/components/tado/sensor.py | 125 +++++++++++++++++++++- tests/components/tado/test_sensor.py | 15 +++ tests/components/tado/util.py | 5 + tests/fixtures/tado/weather.json | 22 ++++ 7 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 tests/fixtures/tado/weather.json diff --git a/homeassistant/components/tado/__init__.py b/homeassistant/components/tado/__init__.py index 36af3bb0dba..094465d38aa 100644 --- a/homeassistant/components/tado/__init__.py +++ b/homeassistant/components/tado/__init__.py @@ -144,11 +144,13 @@ class TadoConnector: self._fallback = fallback self.home_id = None + self.home_name = None self.tado = None self.zones = None self.devices = None self.data = { "device": {}, + "weather": {}, "zone": {}, } @@ -164,7 +166,9 @@ class TadoConnector: # Load zones and devices self.zones = self.tado.getZones() self.devices = self.tado.getDevices() - self.home_id = self.tado.getMe()["homes"][0]["id"] + tado_home = self.tado.getMe()["homes"][0] + self.home_id = tado_home["id"] + self.home_name = tado_home["name"] @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): @@ -173,6 +177,11 @@ class TadoConnector: self.update_sensor("device", device["shortSerialNo"]) for zone in self.zones: self.update_sensor("zone", zone["id"]) + self.data["weather"] = self.tado.getWeather() + dispatcher_send( + self.hass, + SIGNAL_TADO_UPDATE_RECEIVED.format(self.home_id, "weather", "data"), + ) def update_sensor(self, sensor_type, sensor): """Update the internal data from Tado.""" diff --git a/homeassistant/components/tado/const.py b/homeassistant/components/tado/const.py index 6e009df7ca2..2c86fa2d642 100644 --- a/homeassistant/components/tado/const.py +++ b/homeassistant/components/tado/const.py @@ -48,6 +48,21 @@ CONF_FALLBACK = "fallback" DATA = "data" UPDATE_TRACK = "update_track" +# Weather +CONDITIONS_MAP = { + "clear-night": {"NIGHT_CLEAR"}, + "cloudy": {"CLOUDY", "CLOUDY_MOSTLY", "NIGHT_CLOUDY"}, + "fog": {"FOGGY"}, + "hail": {"HAIL", "RAIN_HAIL"}, + "lightning": {"THUNDERSTORM"}, + "partlycloudy": {"CLOUDY_PARTLY"}, + "rainy": {"DRIZZLE", "RAIN", "SCATTERED_RAIN"}, + "snowy": {"FREEZING", "SCATTERED_SNOW", "SNOW"}, + "snowy-rainy": {"RAIN_SNOW", "SCATTERED_RAIN_SNOW"}, + "sunny": {"SUN"}, + "windy": {"WIND"}, +} + # Types TYPE_AIR_CONDITIONING = "AIR_CONDITIONING" TYPE_HEATING = "HEATING" @@ -149,6 +164,7 @@ UNIQUE_ID = "unique_id" DEFAULT_NAME = "Tado" +TADO_HOME = "Home" TADO_ZONE = "Zone" UPDATE_LISTENER = "update_listener" diff --git a/homeassistant/components/tado/entity.py b/homeassistant/components/tado/entity.py index 34473a45c98..270d6f1e911 100644 --- a/homeassistant/components/tado/entity.py +++ b/homeassistant/components/tado/entity.py @@ -1,7 +1,7 @@ """Base class for Tado entity.""" from homeassistant.helpers.entity import Entity -from .const import DEFAULT_NAME, DOMAIN, TADO_ZONE +from .const import DEFAULT_NAME, DOMAIN, TADO_HOME, TADO_ZONE class TadoDeviceEntity(Entity): @@ -32,6 +32,26 @@ class TadoDeviceEntity(Entity): return False +class TadoHomeEntity(Entity): + """Base implementation for Tado home.""" + + def __init__(self, tado): + """Initialize a Tado home.""" + super().__init__() + self.home_name = tado.home_name + self.home_id = tado.home_id + + @property + def device_info(self): + """Return the device_info of the device.""" + return { + "identifiers": {(DOMAIN, self.home_id)}, + "name": self.home_name, + "manufacturer": DEFAULT_NAME, + "model": TADO_HOME, + } + + class TadoZoneEntity(Entity): """Base implementation for Tado zone.""" diff --git a/homeassistant/components/tado/sensor.py b/homeassistant/components/tado/sensor.py index 6613de82bff..8d38d9eab96 100644 --- a/homeassistant/components/tado/sensor.py +++ b/homeassistant/components/tado/sensor.py @@ -13,6 +13,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity from .const import ( + CONDITIONS_MAP, DATA, DOMAIN, SIGNAL_TADO_UPDATE_RECEIVED, @@ -20,10 +21,16 @@ from .const import ( TYPE_HEATING, TYPE_HOT_WATER, ) -from .entity import TadoZoneEntity +from .entity import TadoHomeEntity, TadoZoneEntity _LOGGER = logging.getLogger(__name__) +HOME_SENSORS = { + "outdoor temperature", + "solar percentage", + "weather condition", +} + ZONE_SENSORS = { TYPE_HEATING: [ "temperature", @@ -41,6 +48,14 @@ ZONE_SENSORS = { } +def format_condition(condition: str) -> str: + """Return condition from dict CONDITIONS_MAP.""" + for key, value in CONDITIONS_MAP.items(): + if condition in value: + return key + return condition + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities ): @@ -50,6 +65,9 @@ async def async_setup_entry( zones = tado.zones entities = [] + # Create home sensors + entities.extend([TadoHomeSensor(tado, variable) for variable in HOME_SENSORS]) + # Create zone sensors for zone in zones: zone_type = zone["type"] @@ -68,6 +86,111 @@ async def async_setup_entry( async_add_entities(entities, True) +class TadoHomeSensor(TadoHomeEntity, Entity): + """Representation of a Tado Sensor.""" + + def __init__(self, tado, home_variable): + """Initialize of the Tado Sensor.""" + super().__init__(tado) + self._tado = tado + + self.home_variable = home_variable + + self._unique_id = f"{home_variable} {tado.home_id}" + + self._state = None + self._state_attributes = None + self._tado_weather_data = self._tado.data["weather"] + + async def async_added_to_hass(self): + """Register for sensor updates.""" + + self.async_on_remove( + async_dispatcher_connect( + self.hass, + SIGNAL_TADO_UPDATE_RECEIVED.format( + self._tado.home_id, "weather", "data" + ), + self._async_update_callback, + ) + ) + self._async_update_home_data() + + @property + def unique_id(self): + """Return the unique id.""" + return self._unique_id + + @property + def name(self): + """Return the name of the sensor.""" + return f"{self._tado.home_name} {self.home_variable}" + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return self._state_attributes + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + if self.home_variable == "temperature": + return TEMP_CELSIUS + if self.home_variable == "solar percentage": + return PERCENTAGE + if self.home_variable == "weather condition": + return None + + @property + def device_class(self): + """Return the device class.""" + if self.home_variable == "outdoor temperature": + return DEVICE_CLASS_TEMPERATURE + return None + + @callback + def _async_update_callback(self): + """Update and write state.""" + self._async_update_home_data() + self.async_write_ha_state() + + @callback + def _async_update_home_data(self): + """Handle update callbacks.""" + try: + self._tado_weather_data = self._tado.data["weather"] + except KeyError: + return + + if self.home_variable == "outdoor temperature": + self._state = self.hass.config.units.temperature( + self._tado_weather_data["outsideTemperature"]["celsius"], + TEMP_CELSIUS, + ) + self._state_attributes = { + "time": self._tado_weather_data["outsideTemperature"]["timestamp"], + } + + elif self.home_variable == "solar percentage": + self._state = self._tado_weather_data["solarIntensity"]["percentage"] + self._state_attributes = { + "time": self._tado_weather_data["solarIntensity"]["timestamp"], + } + + elif self.home_variable == "weather condition": + self._state = format_condition( + self._tado_weather_data["weatherState"]["value"] + ) + self._state_attributes = { + "time": self._tado_weather_data["weatherState"]["timestamp"] + } + + class TadoZoneSensor(TadoZoneEntity, Entity): """Representation of a tado Sensor.""" diff --git a/tests/components/tado/test_sensor.py b/tests/components/tado/test_sensor.py index 2fac88bc22e..bb926ff1ae2 100644 --- a/tests/components/tado/test_sensor.py +++ b/tests/components/tado/test_sensor.py @@ -21,6 +21,21 @@ async def test_air_con_create_sensors(hass): assert state.state == "60.9" +async def test_home_create_sensors(hass): + """Test creation of home sensors.""" + + await async_init_integration(hass) + + state = hass.states.get("sensor.home_name_outdoor_temperature") + assert state.state == "7.46" + + state = hass.states.get("sensor.home_name_solar_percentage") + assert state.state == "2.1" + + state = hass.states.get("sensor.home_name_weather_condition") + assert state.state == "fog" + + async def test_heater_create_sensors(hass): """Test creation of heater sensors.""" diff --git a/tests/components/tado/util.py b/tests/components/tado/util.py index c5bf8cf28a4..ce1dd92942d 100644 --- a/tests/components/tado/util.py +++ b/tests/components/tado/util.py @@ -18,6 +18,7 @@ async def async_init_integration( token_fixture = "tado/token.json" devices_fixture = "tado/devices.json" me_fixture = "tado/me.json" + weather_fixture = "tado/weather.json" zones_fixture = "tado/zones.json" # WR1 Device @@ -55,6 +56,10 @@ async def async_init_integration( "https://my.tado.com/api/v2/me", text=load_fixture(me_fixture), ) + m.get( + "https://my.tado.com/api/v2/homes/1/weather", + text=load_fixture(weather_fixture), + ) m.get( "https://my.tado.com/api/v2/homes/1/devices", text=load_fixture(devices_fixture), diff --git a/tests/fixtures/tado/weather.json b/tests/fixtures/tado/weather.json new file mode 100644 index 00000000000..72379f05512 --- /dev/null +++ b/tests/fixtures/tado/weather.json @@ -0,0 +1,22 @@ +{ + "outsideTemperature": { + "celsius": 7.46, + "fahrenheit": 45.43, + "precision": { + "celsius": 0.01, + "fahrenheit": 0.01 + }, + "timestamp": "2020-12-22T08:13:13.652Z", + "type": "TEMPERATURE" + }, + "solarIntensity": { + "percentage": 2.1, + "timestamp": "2020-12-22T08:13:13.652Z", + "type": "PERCENTAGE" + }, + "weatherState": { + "timestamp": "2020-12-22T08:13:13.652Z", + "type": "WEATHER_STATE", + "value": "FOGGY" + } +} From 54a9b69ecb0313c783c4a0877bc6055b7ae4ad7d Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Wed, 10 Mar 2021 21:32:22 +0100 Subject: [PATCH 287/831] Update xknx to 0.17.2 (#47732) --- homeassistant/components/knx/__init__.py | 2 +- homeassistant/components/knx/manifest.json | 2 +- requirements_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index e74bae7442c..99198fbaa99 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -77,7 +77,7 @@ SERVICE_KNX_READ = "read" CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.All( - # deprecated since 2021.3 + # deprecated since 2021.4 cv.deprecated(CONF_KNX_CONFIG), # deprecated since 2021.2 cv.deprecated(CONF_KNX_FIRE_EVENT), diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 7ca7657d0ff..f8ed51f364a 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -2,7 +2,7 @@ "domain": "knx", "name": "KNX", "documentation": "https://www.home-assistant.io/integrations/knx", - "requirements": ["xknx==0.17.1"], + "requirements": ["xknx==0.17.2"], "codeowners": ["@Julius2342", "@farmio", "@marvin-w"], "quality_scale": "silver" } diff --git a/requirements_all.txt b/requirements_all.txt index 7bc5592a626..5170f3b2574 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2330,7 +2330,7 @@ xbox-webapi==2.0.8 xboxapi==2.0.1 # homeassistant.components.knx -xknx==0.17.1 +xknx==0.17.2 # homeassistant.components.bluesound # homeassistant.components.rest From 15da1c478517f0d9eeae31b12544c836a1a2e275 Mon Sep 17 00:00:00 2001 From: hung2kgithub <73251414+hung2kgithub@users.noreply.github.com> Date: Thu, 11 Mar 2021 04:48:06 +0800 Subject: [PATCH 288/831] Add missing clear-night weather condition (#47666) --- homeassistant/components/template/weather.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/template/weather.py b/homeassistant/components/template/weather.py index 560bd5639ba..0db94520afe 100644 --- a/homeassistant/components/template/weather.py +++ b/homeassistant/components/template/weather.py @@ -2,6 +2,7 @@ import voluptuous as vol from homeassistant.components.weather import ( + ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_CLOUDY, ATTR_CONDITION_EXCEPTIONAL, ATTR_CONDITION_FOG, @@ -29,6 +30,7 @@ from .const import DOMAIN, PLATFORMS from .template_entity import TemplateEntity CONDITION_CLASSES = { + ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_CLOUDY, ATTR_CONDITION_FOG, ATTR_CONDITION_HAIL, From a9a9e1f199e936f4528a2c96e7cb09afe27516d1 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 10 Mar 2021 23:42:13 +0100 Subject: [PATCH 289/831] Tweak automation tracing (#47721) --- .../components/automation/__init__.py | 224 +---- homeassistant/components/automation/trace.py | 206 +++++ .../components/automation/websocket_api.py | 229 +++++ homeassistant/components/config/automation.py | 224 ----- homeassistant/helpers/script.py | 2 +- .../automation/test_websocket_api.py | 803 ++++++++++++++++++ tests/components/config/test_automation.py | 772 +---------------- 7 files changed, 1248 insertions(+), 1212 deletions(-) create mode 100644 homeassistant/components/automation/trace.py create mode 100644 homeassistant/components/automation/websocket_api.py create mode 100644 tests/components/automation/test_websocket_api.py diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 7e1352afc2f..50b9fc43bf5 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -1,21 +1,6 @@ """Allow to set up simple automation rules via the config file.""" -from collections import OrderedDict -from contextlib import contextmanager -import datetime as dt -from itertools import count import logging -from typing import ( - Any, - Awaitable, - Callable, - Deque, - Dict, - List, - Optional, - Set, - Union, - cast, -) +from typing import Any, Awaitable, Callable, Dict, List, Optional, Set, Union, cast import voluptuous as vol from voluptuous.humanize import humanize_error @@ -68,18 +53,13 @@ from homeassistant.helpers.script import ( ) from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.service import async_register_admin_service -from homeassistant.helpers.trace import ( - TraceElement, - trace_get, - trace_id_set, - trace_path, -) +from homeassistant.helpers.trace import trace_get, trace_path from homeassistant.helpers.trigger import async_initialize_triggers from homeassistant.helpers.typing import TemplateVarsType from homeassistant.loader import bind_hass -from homeassistant.util import dt as dt_util from homeassistant.util.dt import parse_datetime +from . import websocket_api from .config import AutomationConfig, async_validate_config_item # Not used except by packages to check config structure @@ -94,6 +74,7 @@ from .const import ( LOGGER, ) from .helpers import async_get_blueprints +from .trace import DATA_AUTOMATION_TRACE, trace_automation # mypy: allow-untyped-calls, allow-untyped-defs # mypy: no-check-untyped-defs, no-warn-return-any @@ -113,9 +94,6 @@ ATTR_SOURCE = "source" ATTR_VARIABLES = "variables" SERVICE_TRIGGER = "trigger" -DATA_AUTOMATION_TRACE = "automation_trace" -STORED_TRACES = 5 # Stored traces per automation - _LOGGER = logging.getLogger(__name__) AutomationActionType = Callable[[HomeAssistant, TemplateVarsType], Awaitable[None]] @@ -194,9 +172,12 @@ def devices_in_automation(hass: HomeAssistant, entity_id: str) -> List[str]: async def async_setup(hass, config): """Set up all automations.""" + # Local import to avoid circular import hass.data[DOMAIN] = component = EntityComponent(LOGGER, DOMAIN, hass) hass.data.setdefault(DATA_AUTOMATION_TRACE, {}) + websocket_api.async_setup(hass) + # To register the automation blueprints async_get_blueprints(hass) @@ -243,167 +224,6 @@ async def async_setup(hass, config): return True -class AutomationTrace: - """Container for automation trace.""" - - _run_ids = count(0) - - def __init__( - self, - unique_id: Optional[str], - config: Dict[str, Any], - trigger: Dict[str, Any], - context: Context, - ): - """Container for automation trace.""" - self._action_trace: Optional[Dict[str, Deque[TraceElement]]] = None - self._condition_trace: Optional[Dict[str, Deque[TraceElement]]] = None - self._config: Dict[str, Any] = config - self._context: Context = context - self._error: Optional[Exception] = None - self._state: str = "running" - self.run_id: str = str(next(self._run_ids)) - self._timestamp_finish: Optional[dt.datetime] = None - self._timestamp_start: dt.datetime = dt_util.utcnow() - self._trigger: Dict[str, Any] = trigger - self._unique_id: Optional[str] = unique_id - self._variables: Optional[Dict[str, Any]] = None - - def set_action_trace(self, trace: Dict[str, Deque[TraceElement]]) -> None: - """Set action trace.""" - self._action_trace = trace - - def set_condition_trace(self, trace: Dict[str, Deque[TraceElement]]) -> None: - """Set condition trace.""" - self._condition_trace = trace - - def set_error(self, ex: Exception) -> None: - """Set error.""" - self._error = ex - - def set_variables(self, variables: Dict[str, Any]) -> None: - """Set variables.""" - self._variables = variables - - def finished(self) -> None: - """Set finish time.""" - self._timestamp_finish = dt_util.utcnow() - self._state = "stopped" - - def as_dict(self) -> Dict[str, Any]: - """Return dictionary version of this AutomationTrace.""" - - action_traces = {} - condition_traces = {} - if self._action_trace: - for key, trace_list in self._action_trace.items(): - action_traces[key] = [item.as_dict() for item in trace_list] - - if self._condition_trace: - for key, trace_list in self._condition_trace.items(): - condition_traces[key] = [item.as_dict() for item in trace_list] - - result = { - "action_trace": action_traces, - "condition_trace": condition_traces, - "config": self._config, - "context": self._context, - "run_id": self.run_id, - "state": self._state, - "timestamp": { - "start": self._timestamp_start, - "finish": self._timestamp_finish, - }, - "trigger": self._trigger, - "unique_id": self._unique_id, - "variables": self._variables, - } - if self._error is not None: - result["error"] = str(self._error) - return result - - def as_short_dict(self) -> Dict[str, Any]: - """Return a brief dictionary version of this AutomationTrace.""" - - last_action = None - last_condition = None - - if self._action_trace: - last_action = list(self._action_trace.keys())[-1] - if self._condition_trace: - last_condition = list(self._condition_trace.keys())[-1] - - result = { - "last_action": last_action, - "last_condition": last_condition, - "run_id": self.run_id, - "state": self._state, - "timestamp": { - "start": self._timestamp_start, - "finish": self._timestamp_finish, - }, - "trigger": self._trigger.get("description"), - "unique_id": self._unique_id, - } - if self._error is not None: - result["error"] = str(self._error) - if last_action is not None: - result["last_action"] = last_action - result["last_condition"] = last_condition - - return result - - -class LimitedSizeDict(OrderedDict): - """OrderedDict limited in size.""" - - def __init__(self, *args, **kwds): - """Initialize OrderedDict limited in size.""" - self.size_limit = kwds.pop("size_limit", None) - OrderedDict.__init__(self, *args, **kwds) - self._check_size_limit() - - def __setitem__(self, key, value): - """Set item and check dict size.""" - OrderedDict.__setitem__(self, key, value) - self._check_size_limit() - - def _check_size_limit(self): - """Check dict size and evict items in FIFO order if needed.""" - if self.size_limit is not None: - while len(self) > self.size_limit: - self.popitem(last=False) - - -@contextmanager -def trace_automation(hass, unique_id, config, trigger, context): - """Trace action execution of automation with automation_id.""" - automation_trace = AutomationTrace(unique_id, config, trigger, context) - trace_id_set((unique_id, automation_trace.run_id)) - - if unique_id: - automation_traces = hass.data[DATA_AUTOMATION_TRACE] - if unique_id not in automation_traces: - automation_traces[unique_id] = LimitedSizeDict(size_limit=STORED_TRACES) - automation_traces[unique_id][automation_trace.run_id] = automation_trace - - try: - yield automation_trace - except Exception as ex: # pylint: disable=broad-except - if unique_id: - automation_trace.set_error(ex) - raise ex - finally: - if unique_id: - automation_trace.finished() - _LOGGER.debug( - "Automation finished. Summary:\n\ttrigger: %s\n\tcondition: %s\n\taction: %s", - automation_trace._trigger, # pylint: disable=protected-access - automation_trace._condition_trace, # pylint: disable=protected-access - automation_trace._action_trace, # pylint: disable=protected-access - ) - - class AutomationEntity(ToggleEntity, RestoreEntity): """Entity to show status of entity.""" @@ -570,9 +390,8 @@ class AutomationEntity(ToggleEntity, RestoreEntity): reason = f' by {run_variables["trigger"]["description"]}' self._logger.debug("Automation triggered%s", reason) - trigger = run_variables["trigger"] if "trigger" in run_variables else None with trace_automation( - self.hass, self.unique_id, self._raw_config, trigger, context + self.hass, self.unique_id, self._raw_config, context ) as automation_trace: if self._variables: try: @@ -891,30 +710,3 @@ def _trigger_extract_entities(trigger_conf: dict) -> List[str]: return ["sun.sun"] return [] - - -@callback -def get_debug_traces_for_automation(hass, automation_id, summary=False): - """Return a serializable list of debug traces for an automation.""" - traces = [] - - for trace in hass.data[DATA_AUTOMATION_TRACE].get(automation_id, {}).values(): - if summary: - traces.append(trace.as_short_dict()) - else: - traces.append(trace.as_dict()) - - return traces - - -@callback -def get_debug_traces(hass, summary=False): - """Return a serializable list of debug traces.""" - traces = {} - - for automation_id in hass.data[DATA_AUTOMATION_TRACE]: - traces[automation_id] = get_debug_traces_for_automation( - hass, automation_id, summary - ) - - return traces diff --git a/homeassistant/components/automation/trace.py b/homeassistant/components/automation/trace.py new file mode 100644 index 00000000000..ebd981f2541 --- /dev/null +++ b/homeassistant/components/automation/trace.py @@ -0,0 +1,206 @@ +"""Trace support for automation.""" +from collections import OrderedDict +from contextlib import contextmanager +import datetime as dt +from itertools import count +import logging +from typing import Any, Awaitable, Callable, Deque, Dict, Optional + +from homeassistant.core import Context, HomeAssistant, callback +from homeassistant.helpers.trace import TraceElement, trace_id_set +from homeassistant.helpers.typing import TemplateVarsType +from homeassistant.util import dt as dt_util + +DATA_AUTOMATION_TRACE = "automation_trace" +STORED_TRACES = 5 # Stored traces per automation + +_LOGGER = logging.getLogger(__name__) +AutomationActionType = Callable[[HomeAssistant, TemplateVarsType], Awaitable[None]] + +# mypy: allow-untyped-calls, allow-untyped-defs +# mypy: no-check-untyped-defs, no-warn-return-any + + +class AutomationTrace: + """Container for automation trace.""" + + _run_ids = count(0) + + def __init__( + self, + unique_id: Optional[str], + config: Dict[str, Any], + context: Context, + ): + """Container for automation trace.""" + self._action_trace: Optional[Dict[str, Deque[TraceElement]]] = None + self._condition_trace: Optional[Dict[str, Deque[TraceElement]]] = None + self._config: Dict[str, Any] = config + self._context: Context = context + self._error: Optional[Exception] = None + self._state: str = "running" + self.run_id: str = str(next(self._run_ids)) + self._timestamp_finish: Optional[dt.datetime] = None + self._timestamp_start: dt.datetime = dt_util.utcnow() + self._unique_id: Optional[str] = unique_id + self._variables: Optional[Dict[str, Any]] = None + + def set_action_trace(self, trace: Dict[str, Deque[TraceElement]]) -> None: + """Set action trace.""" + self._action_trace = trace + + def set_condition_trace(self, trace: Dict[str, Deque[TraceElement]]) -> None: + """Set condition trace.""" + self._condition_trace = trace + + def set_error(self, ex: Exception) -> None: + """Set error.""" + self._error = ex + + def set_variables(self, variables: Dict[str, Any]) -> None: + """Set variables.""" + self._variables = variables + + def finished(self) -> None: + """Set finish time.""" + self._timestamp_finish = dt_util.utcnow() + self._state = "stopped" + + def as_dict(self) -> Dict[str, Any]: + """Return dictionary version of this AutomationTrace.""" + + result = self.as_short_dict() + + action_traces = {} + condition_traces = {} + if self._action_trace: + for key, trace_list in self._action_trace.items(): + action_traces[key] = [item.as_dict() for item in trace_list] + + if self._condition_trace: + for key, trace_list in self._condition_trace.items(): + condition_traces[key] = [item.as_dict() for item in trace_list] + + result.update( + { + "action_trace": action_traces, + "condition_trace": condition_traces, + "config": self._config, + "context": self._context, + "variables": self._variables, + } + ) + if self._error is not None: + result["error"] = str(self._error) + return result + + def as_short_dict(self) -> Dict[str, Any]: + """Return a brief dictionary version of this AutomationTrace.""" + + last_action = None + last_condition = None + trigger = None + + if self._action_trace: + last_action = list(self._action_trace)[-1] + if self._condition_trace: + last_condition = list(self._condition_trace)[-1] + if self._variables: + trigger = self._variables.get("trigger", {}).get("description") + + result = { + "last_action": last_action, + "last_condition": last_condition, + "run_id": self.run_id, + "state": self._state, + "timestamp": { + "start": self._timestamp_start, + "finish": self._timestamp_finish, + }, + "trigger": trigger, + "unique_id": self._unique_id, + } + if self._error is not None: + result["error"] = str(self._error) + if last_action is not None: + result["last_action"] = last_action + result["last_condition"] = last_condition + + return result + + +class LimitedSizeDict(OrderedDict): + """OrderedDict limited in size.""" + + def __init__(self, *args, **kwds): + """Initialize OrderedDict limited in size.""" + self.size_limit = kwds.pop("size_limit", None) + OrderedDict.__init__(self, *args, **kwds) + self._check_size_limit() + + def __setitem__(self, key, value): + """Set item and check dict size.""" + OrderedDict.__setitem__(self, key, value) + self._check_size_limit() + + def _check_size_limit(self): + """Check dict size and evict items in FIFO order if needed.""" + if self.size_limit is not None: + while len(self) > self.size_limit: + self.popitem(last=False) + + +@contextmanager +def trace_automation(hass, unique_id, config, context): + """Trace action execution of automation with automation_id.""" + automation_trace = AutomationTrace(unique_id, config, context) + trace_id_set((unique_id, automation_trace.run_id)) + + if unique_id: + automation_traces = hass.data[DATA_AUTOMATION_TRACE] + if unique_id not in automation_traces: + automation_traces[unique_id] = LimitedSizeDict(size_limit=STORED_TRACES) + automation_traces[unique_id][automation_trace.run_id] = automation_trace + + try: + yield automation_trace + except Exception as ex: # pylint: disable=broad-except + if unique_id: + automation_trace.set_error(ex) + raise ex + finally: + if unique_id: + automation_trace.finished() + + +@callback +def get_debug_trace(hass, automation_id, run_id): + """Return a serializable debug trace.""" + return hass.data[DATA_AUTOMATION_TRACE][automation_id][run_id] + + +@callback +def get_debug_traces_for_automation(hass, automation_id, summary=False): + """Return a serializable list of debug traces for an automation.""" + traces = [] + + for trace in hass.data[DATA_AUTOMATION_TRACE].get(automation_id, {}).values(): + if summary: + traces.append(trace.as_short_dict()) + else: + traces.append(trace.as_dict()) + + return traces + + +@callback +def get_debug_traces(hass, summary=False): + """Return a serializable list of debug traces.""" + traces = {} + + for automation_id in hass.data[DATA_AUTOMATION_TRACE]: + traces[automation_id] = get_debug_traces_for_automation( + hass, automation_id, summary + ) + + return traces diff --git a/homeassistant/components/automation/websocket_api.py b/homeassistant/components/automation/websocket_api.py new file mode 100644 index 00000000000..aaebbef7f83 --- /dev/null +++ b/homeassistant/components/automation/websocket_api.py @@ -0,0 +1,229 @@ +"""Websocket API for automation.""" +import voluptuous as vol + +from homeassistant.components import websocket_api +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.dispatcher import ( + DATA_DISPATCHER, + async_dispatcher_connect, + async_dispatcher_send, +) +from homeassistant.helpers.script import ( + SCRIPT_BREAKPOINT_HIT, + SCRIPT_DEBUG_CONTINUE_ALL, + breakpoint_clear, + breakpoint_clear_all, + breakpoint_list, + breakpoint_set, + debug_continue, + debug_step, + debug_stop, +) + +from .trace import get_debug_trace, get_debug_traces + +# mypy: allow-untyped-calls, allow-untyped-defs + + +@callback +def async_setup(hass: HomeAssistant) -> None: + """Set up the websocket API.""" + websocket_api.async_register_command(hass, websocket_automation_trace_get) + websocket_api.async_register_command(hass, websocket_automation_trace_list) + websocket_api.async_register_command(hass, websocket_automation_breakpoint_clear) + websocket_api.async_register_command(hass, websocket_automation_breakpoint_list) + websocket_api.async_register_command(hass, websocket_automation_breakpoint_set) + websocket_api.async_register_command(hass, websocket_automation_debug_continue) + websocket_api.async_register_command(hass, websocket_automation_debug_step) + websocket_api.async_register_command(hass, websocket_automation_debug_stop) + websocket_api.async_register_command(hass, websocket_subscribe_breakpoint_events) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/trace/get", + vol.Required("automation_id"): str, + vol.Required("run_id"): str, + } +) +def websocket_automation_trace_get(hass, connection, msg): + """Get automation traces.""" + automation_id = msg["automation_id"] + run_id = msg["run_id"] + + trace = get_debug_trace(hass, automation_id, run_id) + + connection.send_result(msg["id"], trace) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command({vol.Required("type"): "automation/trace/list"}) +def websocket_automation_trace_list(hass, connection, msg): + """Summarize automation traces.""" + automation_traces = get_debug_traces(hass, summary=True) + + connection.send_result(msg["id"], automation_traces) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/breakpoint/set", + vol.Required("automation_id"): str, + vol.Required("node"): str, + vol.Optional("run_id"): str, + } +) +def websocket_automation_breakpoint_set(hass, connection, msg): + """Set breakpoint.""" + automation_id = msg["automation_id"] + node = msg["node"] + run_id = msg.get("run_id") + + if ( + SCRIPT_BREAKPOINT_HIT not in hass.data.get(DATA_DISPATCHER, {}) + or not hass.data[DATA_DISPATCHER][SCRIPT_BREAKPOINT_HIT] + ): + raise HomeAssistantError("No breakpoint subscription") + + result = breakpoint_set(hass, automation_id, run_id, node) + connection.send_result(msg["id"], result) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/breakpoint/clear", + vol.Required("automation_id"): str, + vol.Required("node"): str, + vol.Optional("run_id"): str, + } +) +def websocket_automation_breakpoint_clear(hass, connection, msg): + """Clear breakpoint.""" + automation_id = msg["automation_id"] + node = msg["node"] + run_id = msg.get("run_id") + + result = breakpoint_clear(hass, automation_id, run_id, node) + + connection.send_result(msg["id"], result) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + {vol.Required("type"): "automation/debug/breakpoint/list"} +) +def websocket_automation_breakpoint_list(hass, connection, msg): + """List breakpoints.""" + breakpoints = breakpoint_list(hass) + for _breakpoint in breakpoints: + _breakpoint["automation_id"] = _breakpoint.pop("unique_id") + + connection.send_result(msg["id"], breakpoints) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + {vol.Required("type"): "automation/debug/breakpoint/subscribe"} +) +def websocket_subscribe_breakpoint_events(hass, connection, msg): + """Subscribe to breakpoint events.""" + + @callback + def breakpoint_hit(automation_id, run_id, node): + """Forward events to websocket.""" + connection.send_message( + websocket_api.event_message( + msg["id"], + { + "automation_id": automation_id, + "run_id": run_id, + "node": node, + }, + ) + ) + + remove_signal = async_dispatcher_connect( + hass, SCRIPT_BREAKPOINT_HIT, breakpoint_hit + ) + + @callback + def unsub(): + """Unsubscribe from breakpoint events.""" + remove_signal() + if ( + SCRIPT_BREAKPOINT_HIT not in hass.data.get(DATA_DISPATCHER, {}) + or not hass.data[DATA_DISPATCHER][SCRIPT_BREAKPOINT_HIT] + ): + breakpoint_clear_all(hass) + async_dispatcher_send(hass, SCRIPT_DEBUG_CONTINUE_ALL) + + connection.subscriptions[msg["id"]] = unsub + + connection.send_message(websocket_api.result_message(msg["id"])) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/continue", + vol.Required("automation_id"): str, + vol.Required("run_id"): str, + } +) +def websocket_automation_debug_continue(hass, connection, msg): + """Resume execution of halted automation.""" + automation_id = msg["automation_id"] + run_id = msg["run_id"] + + result = debug_continue(hass, automation_id, run_id) + + connection.send_result(msg["id"], result) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/step", + vol.Required("automation_id"): str, + vol.Required("run_id"): str, + } +) +def websocket_automation_debug_step(hass, connection, msg): + """Single step a halted automation.""" + automation_id = msg["automation_id"] + run_id = msg["run_id"] + + result = debug_step(hass, automation_id, run_id) + + connection.send_result(msg["id"], result) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/stop", + vol.Required("automation_id"): str, + vol.Required("run_id"): str, + } +) +def websocket_automation_debug_stop(hass, connection, msg): + """Stop a halted automation.""" + automation_id = msg["automation_id"] + run_id = msg["run_id"] + + result = debug_stop(hass, automation_id, run_id) + + connection.send_result(msg["id"], result) diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index b5aa1bf7af5..01e22297c0d 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -2,13 +2,6 @@ from collections import OrderedDict import uuid -import voluptuous as vol - -from homeassistant.components import websocket_api -from homeassistant.components.automation import ( - get_debug_traces, - get_debug_traces_for_automation, -) from homeassistant.components.automation.config import ( DOMAIN, PLATFORM_SCHEMA, @@ -16,25 +9,7 @@ from homeassistant.components.automation.config import ( ) from homeassistant.config import AUTOMATION_CONFIG_PATH from homeassistant.const import CONF_ID, SERVICE_RELOAD -from homeassistant.core import callback -from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, entity_registry -from homeassistant.helpers.dispatcher import ( - DATA_DISPATCHER, - async_dispatcher_connect, - async_dispatcher_send, -) -from homeassistant.helpers.script import ( - SCRIPT_BREAKPOINT_HIT, - SCRIPT_DEBUG_CONTINUE_ALL, - breakpoint_clear, - breakpoint_clear_all, - breakpoint_list, - breakpoint_set, - debug_continue, - debug_step, - debug_stop, -) from . import ACTION_DELETE, EditIdBasedConfigView @@ -42,16 +17,6 @@ from . import ACTION_DELETE, EditIdBasedConfigView async def async_setup(hass): """Set up the Automation config API.""" - websocket_api.async_register_command(hass, websocket_automation_trace_get) - websocket_api.async_register_command(hass, websocket_automation_trace_list) - websocket_api.async_register_command(hass, websocket_automation_breakpoint_clear) - websocket_api.async_register_command(hass, websocket_automation_breakpoint_list) - websocket_api.async_register_command(hass, websocket_automation_breakpoint_set) - websocket_api.async_register_command(hass, websocket_automation_debug_continue) - websocket_api.async_register_command(hass, websocket_automation_debug_step) - websocket_api.async_register_command(hass, websocket_automation_debug_stop) - websocket_api.async_register_command(hass, websocket_subscribe_breakpoint_events) - async def hook(action, config_key): """post_write_hook for Config View that reloads automations.""" await hass.services.async_call(DOMAIN, SERVICE_RELOAD) @@ -115,192 +80,3 @@ class EditAutomationConfigView(EditIdBasedConfigView): updated_value.update(cur_value) updated_value.update(new_value) data[index] = updated_value - - -@callback -@websocket_api.require_admin -@websocket_api.websocket_command( - {vol.Required("type"): "automation/trace/get", vol.Optional("automation_id"): str} -) -def websocket_automation_trace_get(hass, connection, msg): - """Get automation traces.""" - automation_id = msg.get("automation_id") - - if not automation_id: - automation_traces = get_debug_traces(hass) - else: - automation_traces = { - automation_id: get_debug_traces_for_automation(hass, automation_id) - } - - connection.send_result(msg["id"], automation_traces) - - -@callback -@websocket_api.require_admin -@websocket_api.websocket_command({vol.Required("type"): "automation/trace/list"}) -def websocket_automation_trace_list(hass, connection, msg): - """Summarize automation traces.""" - automation_traces = get_debug_traces(hass, summary=True) - - connection.send_result(msg["id"], automation_traces) - - -@callback -@websocket_api.require_admin -@websocket_api.websocket_command( - { - vol.Required("type"): "automation/debug/breakpoint/set", - vol.Required("automation_id"): str, - vol.Required("node"): str, - vol.Optional("run_id"): str, - } -) -def websocket_automation_breakpoint_set(hass, connection, msg): - """Set breakpoint.""" - automation_id = msg["automation_id"] - node = msg["node"] - run_id = msg.get("run_id") - - if ( - SCRIPT_BREAKPOINT_HIT not in hass.data.get(DATA_DISPATCHER, {}) - or not hass.data[DATA_DISPATCHER][SCRIPT_BREAKPOINT_HIT] - ): - raise HomeAssistantError("No breakpoint subscription") - - result = breakpoint_set(hass, automation_id, run_id, node) - connection.send_result(msg["id"], result) - - -@callback -@websocket_api.require_admin -@websocket_api.websocket_command( - { - vol.Required("type"): "automation/debug/breakpoint/clear", - vol.Required("automation_id"): str, - vol.Required("node"): str, - vol.Optional("run_id"): str, - } -) -def websocket_automation_breakpoint_clear(hass, connection, msg): - """Clear breakpoint.""" - automation_id = msg["automation_id"] - node = msg["node"] - run_id = msg.get("run_id") - - result = breakpoint_clear(hass, automation_id, run_id, node) - - connection.send_result(msg["id"], result) - - -@callback -@websocket_api.require_admin -@websocket_api.websocket_command( - {vol.Required("type"): "automation/debug/breakpoint/list"} -) -def websocket_automation_breakpoint_list(hass, connection, msg): - """List breakpoints.""" - breakpoints = breakpoint_list(hass) - for _breakpoint in breakpoints: - _breakpoint["automation_id"] = _breakpoint.pop("unique_id") - - connection.send_result(msg["id"], breakpoints) - - -@callback -@websocket_api.require_admin -@websocket_api.websocket_command( - {vol.Required("type"): "automation/debug/breakpoint/subscribe"} -) -def websocket_subscribe_breakpoint_events(hass, connection, msg): - """Subscribe to breakpoint events.""" - - @callback - def breakpoint_hit(automation_id, run_id, node): - """Forward events to websocket.""" - connection.send_message( - websocket_api.event_message( - msg["id"], - { - "automation_id": automation_id, - "run_id": run_id, - "node": node, - }, - ) - ) - - remove_signal = async_dispatcher_connect( - hass, SCRIPT_BREAKPOINT_HIT, breakpoint_hit - ) - - @callback - def unsub(): - """Unsubscribe from breakpoint events.""" - remove_signal() - if ( - SCRIPT_BREAKPOINT_HIT not in hass.data.get(DATA_DISPATCHER, {}) - or not hass.data[DATA_DISPATCHER][SCRIPT_BREAKPOINT_HIT] - ): - breakpoint_clear_all(hass) - async_dispatcher_send(hass, SCRIPT_DEBUG_CONTINUE_ALL) - - connection.subscriptions[msg["id"]] = unsub - - connection.send_message(websocket_api.result_message(msg["id"])) - - -@callback -@websocket_api.require_admin -@websocket_api.websocket_command( - { - vol.Required("type"): "automation/debug/continue", - vol.Required("automation_id"): str, - vol.Required("run_id"): str, - } -) -def websocket_automation_debug_continue(hass, connection, msg): - """Resume execution of halted automation.""" - automation_id = msg["automation_id"] - run_id = msg["run_id"] - - result = debug_continue(hass, automation_id, run_id) - - connection.send_result(msg["id"], result) - - -@callback -@websocket_api.require_admin -@websocket_api.websocket_command( - { - vol.Required("type"): "automation/debug/step", - vol.Required("automation_id"): str, - vol.Required("run_id"): str, - } -) -def websocket_automation_debug_step(hass, connection, msg): - """Single step a halted automation.""" - automation_id = msg["automation_id"] - run_id = msg["run_id"] - - result = debug_step(hass, automation_id, run_id) - - connection.send_result(msg["id"], result) - - -@callback -@websocket_api.require_admin -@websocket_api.websocket_command( - { - vol.Required("type"): "automation/debug/stop", - vol.Required("automation_id"): str, - vol.Required("run_id"): str, - } -) -def websocket_automation_debug_stop(hass, connection, msg): - """Stop a halted automation.""" - automation_id = msg["automation_id"] - run_id = msg["run_id"] - - result = debug_stop(hass, automation_id, run_id) - - connection.send_result(msg["id"], result) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 257fd6d9715..a2df055331d 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -360,7 +360,7 @@ class _ScriptRun: handler = f"_async_{cv.determine_script_action(self._action)}_step" await getattr(self, handler)() except Exception as ex: - if not isinstance(ex, (_StopScript, asyncio.CancelledError)) and ( + if not isinstance(ex, _StopScript) and ( self._log_exceptions or log_exceptions ): self._log_exception(ex) diff --git a/tests/components/automation/test_websocket_api.py b/tests/components/automation/test_websocket_api.py new file mode 100644 index 00000000000..106f687f4ee --- /dev/null +++ b/tests/components/automation/test_websocket_api.py @@ -0,0 +1,803 @@ +"""Test Automation config panel.""" +from unittest.mock import patch + +from homeassistant.bootstrap import async_setup_component +from homeassistant.components import automation, config + +from tests.common import assert_lists_same +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 + + +async def test_get_automation_trace(hass, hass_ws_client): + """Test tracing an automation.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": {"service": "test.automation"}, + } + moon_config = { + "id": "moon", + "trigger": [ + {"platform": "event", "event_type": "test_event2"}, + {"platform": "event", "event_type": "test_event3"}, + ], + "condition": { + "condition": "template", + "value_template": "{{ trigger.event.event_type=='test_event2' }}", + }, + "action": {"event": "another_event"}, + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + moon_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + + # List traces + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + run_id = response["result"]["sun"][-1]["run_id"] + + # Get trace + await client.send_json( + { + "id": next_id(), + "type": "automation/trace/get", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + trace = response["result"] + assert len(trace["action_trace"]) == 1 + assert len(trace["action_trace"]["action/0"]) == 1 + assert trace["action_trace"]["action/0"][0]["error"] + assert "result" not in trace["action_trace"]["action/0"][0] + assert trace["condition_trace"] == {} + assert trace["config"] == sun_config + assert trace["context"] + assert trace["error"] == "Unable to find service test.automation" + assert trace["state"] == "stopped" + assert trace["trigger"] == "event 'test_event'" + assert trace["unique_id"] == "sun" + assert trace["variables"] + + # Trigger "moon" automation, with passing condition + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # List traces + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + run_id = response["result"]["moon"][-1]["run_id"] + + # Get trace + await client.send_json( + { + "id": next_id(), + "type": "automation/trace/get", + "automation_id": "moon", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + trace = response["result"] + assert len(trace["action_trace"]) == 1 + assert len(trace["action_trace"]["action/0"]) == 1 + assert "error" not in trace["action_trace"]["action/0"][0] + assert "result" not in trace["action_trace"]["action/0"][0] + assert len(trace["condition_trace"]) == 1 + assert len(trace["condition_trace"]["condition/0"]) == 1 + assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} + assert trace["config"] == moon_config + assert trace["context"] + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["trigger"] == "event 'test_event2'" + assert trace["unique_id"] == "moon" + assert trace["variables"] + + # Trigger "moon" automation, with failing condition + hass.bus.async_fire("test_event3") + await hass.async_block_till_done() + + # List traces + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + run_id = response["result"]["moon"][-1]["run_id"] + + # Get trace + await client.send_json( + { + "id": next_id(), + "type": "automation/trace/get", + "automation_id": "moon", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + trace = response["result"] + assert len(trace["action_trace"]) == 0 + assert len(trace["condition_trace"]) == 1 + assert len(trace["condition_trace"]["condition/0"]) == 1 + assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": False} + assert trace["config"] == moon_config + assert trace["context"] + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["trigger"] == "event 'test_event3'" + assert trace["unique_id"] == "moon" + assert trace["variables"] + + # Trigger "moon" automation, with passing condition + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # List traces + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + run_id = response["result"]["moon"][-1]["run_id"] + + # Get trace + await client.send_json( + { + "id": next_id(), + "type": "automation/trace/get", + "automation_id": "moon", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + trace = response["result"] + assert len(trace["action_trace"]) == 1 + assert len(trace["action_trace"]["action/0"]) == 1 + assert "error" not in trace["action_trace"]["action/0"][0] + assert "result" not in trace["action_trace"]["action/0"][0] + assert len(trace["condition_trace"]) == 1 + assert len(trace["condition_trace"]["condition/0"]) == 1 + assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} + assert trace["config"] == moon_config + assert trace["context"] + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["trigger"] == "event 'test_event2'" + assert trace["unique_id"] == "moon" + assert trace["variables"] + + +async def test_automation_trace_overflow(hass, hass_ws_client): + """Test the number of stored traces per automation is limited.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": {"event": "some_event"}, + } + moon_config = { + "id": "moon", + "trigger": {"platform": "event", "event_type": "test_event2"}, + "action": {"event": "another_event"}, + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + moon_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {} + + # Trigger "sun" and "moon" automation once + hass.bus.async_fire("test_event") + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # List traces + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + assert len(response["result"]["moon"]) == 1 + moon_run_id = response["result"]["moon"][0]["run_id"] + assert len(response["result"]["sun"]) == 1 + + # Trigger "moon" automation enough times to overflow the number of stored traces + for _ in range(automation.trace.STORED_TRACES): + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + assert len(response["result"]["moon"]) == automation.trace.STORED_TRACES + assert len(response["result"]["sun"]) == 1 + assert int(response["result"]["moon"][0]["run_id"]) == int(moon_run_id) + 1 + assert ( + int(response["result"]["moon"][-1]["run_id"]) + == int(moon_run_id) + automation.trace.STORED_TRACES + ) + + +async def test_list_automation_traces(hass, hass_ws_client): + """Test listing automation traces.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": {"service": "test.automation"}, + } + moon_config = { + "id": "moon", + "trigger": [ + {"platform": "event", "event_type": "test_event2"}, + {"platform": "event", "event_type": "test_event3"}, + ], + "condition": { + "condition": "template", + "value_template": "{{ trigger.event.event_type=='test_event2' }}", + }, + "action": {"event": "another_event"}, + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + moon_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {} + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + + # Get trace + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + assert "moon" not in response["result"] + assert len(response["result"]["sun"]) == 1 + + # Trigger "moon" automation, with passing condition + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # Trigger "moon" automation, with failing condition + hass.bus.async_fire("test_event3") + await hass.async_block_till_done() + + # Trigger "moon" automation, with passing condition + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # Get trace + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + assert len(response["result"]["moon"]) == 3 + assert len(response["result"]["sun"]) == 1 + trace = response["result"]["sun"][0] + assert trace["last_action"] == "action/0" + assert trace["last_condition"] is None + assert trace["error"] == "Unable to find service test.automation" + assert trace["state"] == "stopped" + assert trace["timestamp"] + assert trace["trigger"] == "event 'test_event'" + assert trace["unique_id"] == "sun" + + trace = response["result"]["moon"][0] + assert trace["last_action"] == "action/0" + assert trace["last_condition"] == "condition/0" + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["timestamp"] + assert trace["trigger"] == "event 'test_event2'" + assert trace["unique_id"] == "moon" + + trace = response["result"]["moon"][1] + assert trace["last_action"] is None + assert trace["last_condition"] == "condition/0" + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["timestamp"] + assert trace["trigger"] == "event 'test_event3'" + assert trace["unique_id"] == "moon" + + trace = response["result"]["moon"][2] + assert trace["last_action"] == "action/0" + assert trace["last_condition"] == "condition/0" + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["timestamp"] + assert trace["trigger"] == "event 'test_event2'" + assert trace["unique_id"] == "moon" + + +async def test_automation_breakpoints(hass, hass_ws_client): + """Test automation breakpoints.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + async def assert_last_action(automation_id, expected_action, expected_state): + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + trace = response["result"][automation_id][-1] + assert trace["last_action"] == expected_action + assert trace["state"] == expected_state + return trace["run_id"] + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": [ + {"event": "event0"}, + {"event": "event1"}, + {"event": "event2"}, + {"event": "event3"}, + {"event": "event4"}, + {"event": "event5"}, + {"event": "event6"}, + {"event": "event7"}, + {"event": "event8"}, + ], + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "1", + } + ) + response = await client.receive_json() + assert not response["success"] + + await client.send_json( + {"id": next_id(), "type": "automation/debug/breakpoint/list"} + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == [] + + subscription_id = next_id() + await client.send_json( + {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/1", + } + ) + response = await client.receive_json() + assert response["success"] + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/5", + } + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + {"id": next_id(), "type": "automation/debug/breakpoint/list"} + ) + response = await client.receive_json() + assert response["success"] + assert_lists_same( + response["result"], + [ + {"node": "action/1", "run_id": "*", "automation_id": "sun"}, + {"node": "action/5", "run_id": "*", "automation_id": "sun"}, + ], + ) + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/1", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/1", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/step", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/2", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/2", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/continue", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/5", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/5", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/stop", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + await hass.async_block_till_done() + await assert_last_action("sun", "action/5", "stopped") + + +async def test_automation_breakpoints_2(hass, hass_ws_client): + """Test execution resumes and breakpoints are removed after subscription removed.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + async def assert_last_action(automation_id, expected_action, expected_state): + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + trace = response["result"][automation_id][-1] + assert trace["last_action"] == expected_action + assert trace["state"] == expected_state + return trace["run_id"] + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": [ + {"event": "event0"}, + {"event": "event1"}, + {"event": "event2"}, + {"event": "event3"}, + {"event": "event4"}, + {"event": "event5"}, + {"event": "event6"}, + {"event": "event7"}, + {"event": "event8"}, + ], + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + subscription_id = next_id() + await client.send_json( + {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/1", + } + ) + response = await client.receive_json() + assert response["success"] + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/1", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/1", + "run_id": run_id, + } + + # Unsubscribe - execution should resume + await client.send_json( + {"id": next_id(), "type": "unsubscribe_events", "subscription": subscription_id} + ) + response = await client.receive_json() + assert response["success"] + await hass.async_block_till_done() + await assert_last_action("sun", "action/8", "stopped") + + # Should not be possible to set breakpoints + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "1", + } + ) + response = await client.receive_json() + assert not response["success"] + + # Trigger "sun" automation, should finish without stopping on breakpoints + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + + new_run_id = await assert_last_action("sun", "action/8", "stopped") + assert new_run_id != run_id + + +async def test_automation_breakpoints_3(hass, hass_ws_client): + """Test breakpoints can be cleared.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + async def assert_last_action(automation_id, expected_action, expected_state): + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + trace = response["result"][automation_id][-1] + assert trace["last_action"] == expected_action + assert trace["state"] == expected_state + return trace["run_id"] + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": [ + {"event": "event0"}, + {"event": "event1"}, + {"event": "event2"}, + {"event": "event3"}, + {"event": "event4"}, + {"event": "event5"}, + {"event": "event6"}, + {"event": "event7"}, + {"event": "event8"}, + ], + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + subscription_id = next_id() + await client.send_json( + {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/1", + } + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/5", + } + ) + response = await client.receive_json() + assert response["success"] + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/1", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/1", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/continue", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/5", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/5", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/stop", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + await hass.async_block_till_done() + await assert_last_action("sun", "action/5", "stopped") + + # Clear 1st breakpoint + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/clear", + "automation_id": "sun", + "node": "action/1", + } + ) + response = await client.receive_json() + assert response["success"] + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/5", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/5", + "run_id": run_id, + } diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index 2880287be94..6aeb71a7fd0 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -3,10 +3,9 @@ import json from unittest.mock import patch from homeassistant.bootstrap import async_setup_component -from homeassistant.components import automation, config +from homeassistant.components import config from homeassistant.helpers import entity_registry as er -from tests.common import assert_lists_same from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @@ -166,772 +165,3 @@ async def test_delete_automation(hass, hass_client): assert written[0][0]["id"] == "moon" assert len(ent_reg.entities) == 1 - - -async def test_get_automation_trace(hass, hass_ws_client): - """Test tracing an automation.""" - id = 1 - - def next_id(): - nonlocal id - id += 1 - return id - - sun_config = { - "id": "sun", - "trigger": {"platform": "event", "event_type": "test_event"}, - "action": {"service": "test.automation"}, - } - moon_config = { - "id": "moon", - "trigger": [ - {"platform": "event", "event_type": "test_event2"}, - {"platform": "event", "event_type": "test_event3"}, - ], - "condition": { - "condition": "template", - "value_template": "{{ trigger.event.event_type=='test_event2' }}", - }, - "action": {"event": "another_event"}, - } - - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - moon_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) - - client = await hass_ws_client() - - await client.send_json({"id": next_id(), "type": "automation/trace/get"}) - response = await client.receive_json() - assert response["success"] - assert response["result"] == {} - - await client.send_json( - {"id": next_id(), "type": "automation/trace/get", "automation_id": "sun"} - ) - response = await client.receive_json() - assert response["success"] - assert response["result"] == {"sun": []} - - # Trigger "sun" automation - hass.bus.async_fire("test_event") - await hass.async_block_till_done() - - # Get trace - await client.send_json({"id": next_id(), "type": "automation/trace/get"}) - response = await client.receive_json() - assert response["success"] - assert "moon" not in response["result"] - assert len(response["result"]["sun"]) == 1 - trace = response["result"]["sun"][0] - assert len(trace["action_trace"]) == 1 - assert len(trace["action_trace"]["action/0"]) == 1 - assert trace["action_trace"]["action/0"][0]["error"] - assert "result" not in trace["action_trace"]["action/0"][0] - assert trace["condition_trace"] == {} - assert trace["config"] == sun_config - assert trace["context"] - assert trace["error"] == "Unable to find service test.automation" - assert trace["state"] == "stopped" - assert trace["trigger"]["description"] == "event 'test_event'" - assert trace["unique_id"] == "sun" - assert trace["variables"] - - # Trigger "moon" automation, with passing condition - hass.bus.async_fire("test_event2") - await hass.async_block_till_done() - - # Get trace - await client.send_json( - {"id": next_id(), "type": "automation/trace/get", "automation_id": "moon"} - ) - response = await client.receive_json() - assert response["success"] - assert "sun" not in response["result"] - assert len(response["result"]["moon"]) == 1 - trace = response["result"]["moon"][0] - assert len(trace["action_trace"]) == 1 - assert len(trace["action_trace"]["action/0"]) == 1 - assert "error" not in trace["action_trace"]["action/0"][0] - assert "result" not in trace["action_trace"]["action/0"][0] - assert len(trace["condition_trace"]) == 1 - assert len(trace["condition_trace"]["condition/0"]) == 1 - assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} - assert trace["config"] == moon_config - assert trace["context"] - assert "error" not in trace - assert trace["state"] == "stopped" - assert trace["trigger"]["description"] == "event 'test_event2'" - assert trace["unique_id"] == "moon" - assert trace["variables"] - - # Trigger "moon" automation, with failing condition - hass.bus.async_fire("test_event3") - await hass.async_block_till_done() - - # Get trace - await client.send_json( - {"id": next_id(), "type": "automation/trace/get", "automation_id": "moon"} - ) - response = await client.receive_json() - assert response["success"] - assert "sun" not in response["result"] - assert len(response["result"]["moon"]) == 2 - trace = response["result"]["moon"][1] - assert len(trace["action_trace"]) == 0 - assert len(trace["condition_trace"]) == 1 - assert len(trace["condition_trace"]["condition/0"]) == 1 - assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": False} - assert trace["config"] == moon_config - assert trace["context"] - assert "error" not in trace - assert trace["state"] == "stopped" - assert trace["trigger"]["description"] == "event 'test_event3'" - assert trace["unique_id"] == "moon" - assert trace["variables"] - - # Trigger "moon" automation, with passing condition - hass.bus.async_fire("test_event2") - await hass.async_block_till_done() - - # Get trace - await client.send_json( - {"id": next_id(), "type": "automation/trace/get", "automation_id": "moon"} - ) - response = await client.receive_json() - assert response["success"] - assert "sun" not in response["result"] - assert len(response["result"]["moon"]) == 3 - trace = response["result"]["moon"][2] - assert len(trace["action_trace"]) == 1 - assert len(trace["action_trace"]["action/0"]) == 1 - assert "error" not in trace["action_trace"]["action/0"][0] - assert "result" not in trace["action_trace"]["action/0"][0] - assert len(trace["condition_trace"]) == 1 - assert len(trace["condition_trace"]["condition/0"]) == 1 - assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} - assert trace["config"] == moon_config - assert trace["context"] - assert "error" not in trace - assert trace["state"] == "stopped" - assert trace["trigger"]["description"] == "event 'test_event2'" - assert trace["unique_id"] == "moon" - assert trace["variables"] - - -async def test_automation_trace_overflow(hass, hass_ws_client): - """Test the number of stored traces per automation is limited.""" - id = 1 - - def next_id(): - nonlocal id - id += 1 - return id - - sun_config = { - "id": "sun", - "trigger": {"platform": "event", "event_type": "test_event"}, - "action": {"event": "some_event"}, - } - moon_config = { - "id": "moon", - "trigger": {"platform": "event", "event_type": "test_event2"}, - "action": {"event": "another_event"}, - } - - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - moon_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) - - client = await hass_ws_client() - - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) - response = await client.receive_json() - assert response["success"] - assert response["result"] == {} - - # Trigger "sun" and "moon" automation once - hass.bus.async_fire("test_event") - hass.bus.async_fire("test_event2") - await hass.async_block_till_done() - - # Get traces - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) - response = await client.receive_json() - assert response["success"] - assert len(response["result"]["moon"]) == 1 - moon_run_id = response["result"]["moon"][0]["run_id"] - assert len(response["result"]["sun"]) == 1 - - # Trigger "moon" automation enough times to overflow the number of stored traces - for _ in range(automation.STORED_TRACES): - hass.bus.async_fire("test_event2") - await hass.async_block_till_done() - - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) - response = await client.receive_json() - assert response["success"] - assert len(response["result"]["moon"]) == automation.STORED_TRACES - assert len(response["result"]["sun"]) == 1 - assert int(response["result"]["moon"][0]["run_id"]) == int(moon_run_id) + 1 - assert ( - int(response["result"]["moon"][-1]["run_id"]) - == int(moon_run_id) + automation.STORED_TRACES - ) - - -async def test_list_automation_traces(hass, hass_ws_client): - """Test listing automation traces.""" - id = 1 - - def next_id(): - nonlocal id - id += 1 - return id - - sun_config = { - "id": "sun", - "trigger": {"platform": "event", "event_type": "test_event"}, - "action": {"service": "test.automation"}, - } - moon_config = { - "id": "moon", - "trigger": [ - {"platform": "event", "event_type": "test_event2"}, - {"platform": "event", "event_type": "test_event3"}, - ], - "condition": { - "condition": "template", - "value_template": "{{ trigger.event.event_type=='test_event2' }}", - }, - "action": {"event": "another_event"}, - } - - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - moon_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) - - client = await hass_ws_client() - - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) - response = await client.receive_json() - assert response["success"] - assert response["result"] == {} - - # Trigger "sun" automation - hass.bus.async_fire("test_event") - await hass.async_block_till_done() - - # Get trace - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) - response = await client.receive_json() - assert response["success"] - assert "moon" not in response["result"] - assert len(response["result"]["sun"]) == 1 - - # Trigger "moon" automation, with passing condition - hass.bus.async_fire("test_event2") - await hass.async_block_till_done() - - # Trigger "moon" automation, with failing condition - hass.bus.async_fire("test_event3") - await hass.async_block_till_done() - - # Trigger "moon" automation, with passing condition - hass.bus.async_fire("test_event2") - await hass.async_block_till_done() - - # Get trace - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) - response = await client.receive_json() - assert response["success"] - assert len(response["result"]["moon"]) == 3 - assert len(response["result"]["sun"]) == 1 - trace = response["result"]["sun"][0] - assert trace["last_action"] == "action/0" - assert trace["last_condition"] is None - assert trace["error"] == "Unable to find service test.automation" - assert trace["state"] == "stopped" - assert trace["timestamp"] - assert trace["trigger"] == "event 'test_event'" - assert trace["unique_id"] == "sun" - - trace = response["result"]["moon"][0] - assert trace["last_action"] == "action/0" - assert trace["last_condition"] == "condition/0" - assert "error" not in trace - assert trace["state"] == "stopped" - assert trace["timestamp"] - assert trace["trigger"] == "event 'test_event2'" - assert trace["unique_id"] == "moon" - - trace = response["result"]["moon"][1] - assert trace["last_action"] is None - assert trace["last_condition"] == "condition/0" - assert "error" not in trace - assert trace["state"] == "stopped" - assert trace["timestamp"] - assert trace["trigger"] == "event 'test_event3'" - assert trace["unique_id"] == "moon" - - trace = response["result"]["moon"][2] - assert trace["last_action"] == "action/0" - assert trace["last_condition"] == "condition/0" - assert "error" not in trace - assert trace["state"] == "stopped" - assert trace["timestamp"] - assert trace["trigger"] == "event 'test_event2'" - assert trace["unique_id"] == "moon" - - -async def test_automation_breakpoints(hass, hass_ws_client): - """Test automation breakpoints.""" - id = 1 - - def next_id(): - nonlocal id - id += 1 - return id - - async def assert_last_action(automation_id, expected_action, expected_state): - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) - response = await client.receive_json() - assert response["success"] - trace = response["result"][automation_id][-1] - assert trace["last_action"] == expected_action - assert trace["state"] == expected_state - return trace["run_id"] - - sun_config = { - "id": "sun", - "trigger": {"platform": "event", "event_type": "test_event"}, - "action": [ - {"event": "event0"}, - {"event": "event1"}, - {"event": "event2"}, - {"event": "event3"}, - {"event": "event4"}, - {"event": "event5"}, - {"event": "event6"}, - {"event": "event7"}, - {"event": "event8"}, - ], - } - - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) - - client = await hass_ws_client() - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", - "node": "1", - } - ) - response = await client.receive_json() - assert not response["success"] - - await client.send_json( - {"id": next_id(), "type": "automation/debug/breakpoint/list"} - ) - response = await client.receive_json() - assert response["success"] - assert response["result"] == [] - - subscription_id = next_id() - await client.send_json( - {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} - ) - response = await client.receive_json() - assert response["success"] - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", - "node": "action/1", - } - ) - response = await client.receive_json() - assert response["success"] - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", - "node": "action/5", - } - ) - response = await client.receive_json() - assert response["success"] - - await client.send_json( - {"id": next_id(), "type": "automation/debug/breakpoint/list"} - ) - response = await client.receive_json() - assert response["success"] - assert_lists_same( - response["result"], - [ - {"node": "action/1", "run_id": "*", "automation_id": "sun"}, - {"node": "action/5", "run_id": "*", "automation_id": "sun"}, - ], - ) - - # Trigger "sun" automation - hass.bus.async_fire("test_event") - - response = await client.receive_json() - run_id = await assert_last_action("sun", "action/1", "running") - assert response["event"] == { - "automation_id": "sun", - "node": "action/1", - "run_id": run_id, - } - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/step", - "automation_id": "sun", - "run_id": run_id, - } - ) - response = await client.receive_json() - assert response["success"] - - response = await client.receive_json() - run_id = await assert_last_action("sun", "action/2", "running") - assert response["event"] == { - "automation_id": "sun", - "node": "action/2", - "run_id": run_id, - } - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/continue", - "automation_id": "sun", - "run_id": run_id, - } - ) - response = await client.receive_json() - assert response["success"] - - response = await client.receive_json() - run_id = await assert_last_action("sun", "action/5", "running") - assert response["event"] == { - "automation_id": "sun", - "node": "action/5", - "run_id": run_id, - } - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/stop", - "automation_id": "sun", - "run_id": run_id, - } - ) - response = await client.receive_json() - assert response["success"] - await hass.async_block_till_done() - await assert_last_action("sun", "action/5", "stopped") - - -async def test_automation_breakpoints_2(hass, hass_ws_client): - """Test execution resumes and breakpoints are removed after subscription removed.""" - id = 1 - - def next_id(): - nonlocal id - id += 1 - return id - - async def assert_last_action(automation_id, expected_action, expected_state): - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) - response = await client.receive_json() - assert response["success"] - trace = response["result"][automation_id][-1] - assert trace["last_action"] == expected_action - assert trace["state"] == expected_state - return trace["run_id"] - - sun_config = { - "id": "sun", - "trigger": {"platform": "event", "event_type": "test_event"}, - "action": [ - {"event": "event0"}, - {"event": "event1"}, - {"event": "event2"}, - {"event": "event3"}, - {"event": "event4"}, - {"event": "event5"}, - {"event": "event6"}, - {"event": "event7"}, - {"event": "event8"}, - ], - } - - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) - - client = await hass_ws_client() - - subscription_id = next_id() - await client.send_json( - {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} - ) - response = await client.receive_json() - assert response["success"] - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", - "node": "action/1", - } - ) - response = await client.receive_json() - assert response["success"] - - # Trigger "sun" automation - hass.bus.async_fire("test_event") - - response = await client.receive_json() - run_id = await assert_last_action("sun", "action/1", "running") - assert response["event"] == { - "automation_id": "sun", - "node": "action/1", - "run_id": run_id, - } - - # Unsubscribe - execution should resume - await client.send_json( - {"id": next_id(), "type": "unsubscribe_events", "subscription": subscription_id} - ) - response = await client.receive_json() - assert response["success"] - await hass.async_block_till_done() - await assert_last_action("sun", "action/8", "stopped") - - # Should not be possible to set breakpoints - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", - "node": "1", - } - ) - response = await client.receive_json() - assert not response["success"] - - # Trigger "sun" automation, should finish without stopping on breakpoints - hass.bus.async_fire("test_event") - await hass.async_block_till_done() - - new_run_id = await assert_last_action("sun", "action/8", "stopped") - assert new_run_id != run_id - - -async def test_automation_breakpoints_3(hass, hass_ws_client): - """Test breakpoints can be cleared.""" - id = 1 - - def next_id(): - nonlocal id - id += 1 - return id - - async def assert_last_action(automation_id, expected_action, expected_state): - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) - response = await client.receive_json() - assert response["success"] - trace = response["result"][automation_id][-1] - assert trace["last_action"] == expected_action - assert trace["state"] == expected_state - return trace["run_id"] - - sun_config = { - "id": "sun", - "trigger": {"platform": "event", "event_type": "test_event"}, - "action": [ - {"event": "event0"}, - {"event": "event1"}, - {"event": "event2"}, - {"event": "event3"}, - {"event": "event4"}, - {"event": "event5"}, - {"event": "event6"}, - {"event": "event7"}, - {"event": "event8"}, - ], - } - - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) - - client = await hass_ws_client() - - subscription_id = next_id() - await client.send_json( - {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} - ) - response = await client.receive_json() - assert response["success"] - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", - "node": "action/1", - } - ) - response = await client.receive_json() - assert response["success"] - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", - "node": "action/5", - } - ) - response = await client.receive_json() - assert response["success"] - - # Trigger "sun" automation - hass.bus.async_fire("test_event") - - response = await client.receive_json() - run_id = await assert_last_action("sun", "action/1", "running") - assert response["event"] == { - "automation_id": "sun", - "node": "action/1", - "run_id": run_id, - } - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/continue", - "automation_id": "sun", - "run_id": run_id, - } - ) - response = await client.receive_json() - assert response["success"] - - response = await client.receive_json() - run_id = await assert_last_action("sun", "action/5", "running") - assert response["event"] == { - "automation_id": "sun", - "node": "action/5", - "run_id": run_id, - } - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/stop", - "automation_id": "sun", - "run_id": run_id, - } - ) - response = await client.receive_json() - assert response["success"] - await hass.async_block_till_done() - await assert_last_action("sun", "action/5", "stopped") - - # Clear 1st breakpoint - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/breakpoint/clear", - "automation_id": "sun", - "node": "action/1", - } - ) - response = await client.receive_json() - assert response["success"] - - # Trigger "sun" automation - hass.bus.async_fire("test_event") - - response = await client.receive_json() - run_id = await assert_last_action("sun", "action/5", "running") - assert response["event"] == { - "automation_id": "sun", - "node": "action/5", - "run_id": run_id, - } From 00bd591238ca714988c559a8fc4580ab38d99ba5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 10 Mar 2021 16:08:12 -0800 Subject: [PATCH 290/831] Verify get_zones webhook works (#47741) --- tests/components/mobile_app/test_webhook.py | 42 +++++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/tests/components/mobile_app/test_webhook.py b/tests/components/mobile_app/test_webhook.py index a7dc675b7b7..d5cb72fa850 100644 --- a/tests/components/mobile_app/test_webhook.py +++ b/tests/components/mobile_app/test_webhook.py @@ -9,7 +9,6 @@ from homeassistant.components.zone import DOMAIN as ZONE_DOMAIN from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError -from homeassistant.setup import async_setup_component from .const import CALL_SERVICE, FIRE_EVENT, REGISTER_CLEARTEXT, RENDER_TEMPLATE, UPDATE @@ -152,11 +151,29 @@ async def test_webhook_update_registration(webhook_client, authed_api_client): async def test_webhook_handle_get_zones(hass, create_registrations, webhook_client): """Test that we can get zones properly.""" - await async_setup_component( - hass, - ZONE_DOMAIN, - {ZONE_DOMAIN: {}}, - ) + # Zone is already loaded as part of the fixture, + # so we just trigger a reload. + with patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value={ + ZONE_DOMAIN: [ + { + "name": "School", + "latitude": 32.8773367, + "longitude": -117.2494053, + "radius": 250, + "icon": "mdi:school", + }, + { + "name": "Work", + "latitude": 33.8773367, + "longitude": -118.2494053, + }, + ] + }, + ): + await hass.services.async_call(ZONE_DOMAIN, "reload", blocking=True) resp = await webhook_client.post( "/api/webhook/{}".format(create_registrations[1]["webhook_id"]), @@ -166,10 +183,21 @@ async def test_webhook_handle_get_zones(hass, create_registrations, webhook_clie assert resp.status == 200 json = await resp.json() - assert len(json) == 1 + assert len(json) == 3 zones = sorted(json, key=lambda entry: entry["entity_id"]) assert zones[0]["entity_id"] == "zone.home" + assert zones[1]["entity_id"] == "zone.school" + assert zones[1]["attributes"]["icon"] == "mdi:school" + assert zones[1]["attributes"]["latitude"] == 32.8773367 + assert zones[1]["attributes"]["longitude"] == -117.2494053 + assert zones[1]["attributes"]["radius"] == 250 + + assert zones[2]["entity_id"] == "zone.work" + assert "icon" not in zones[2]["attributes"] + assert zones[2]["attributes"]["latitude"] == 33.8773367 + assert zones[2]["attributes"]["longitude"] == -118.2494053 + async def test_webhook_handle_get_config(hass, create_registrations, webhook_client): """Test that we can get config properly.""" From 9a686d148e5d98e559fccccb9ba6fcd28adaf1e5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Mar 2021 21:12:02 -1000 Subject: [PATCH 291/831] Ensure startup can proceed when there is package metadata cruft (#47706) If a package fails to install or partially installed importlib version can return None. We now try pkg_resources first, then try importlib, and handle the case where version unexpectedly returns None --- homeassistant/util/package.py | 12 ++++++++- tests/util/test_package.py | 47 ++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py index 5391d92ed89..34628b4ca4d 100644 --- a/homeassistant/util/package.py +++ b/homeassistant/util/package.py @@ -34,6 +34,9 @@ def is_installed(package: str) -> bool: Returns False when the package is not installed or doesn't meet req. """ try: + pkg_resources.get_distribution(package) + return True + except (pkg_resources.ResolutionError, pkg_resources.ExtractionError): req = pkg_resources.Requirement.parse(package) except ValueError: # This is a zip file. We no longer use this in Home Assistant, @@ -41,7 +44,14 @@ def is_installed(package: str) -> bool: req = pkg_resources.Requirement.parse(urlparse(package).fragment) try: - return version(req.project_name) in req + installed_version = version(req.project_name) + # This will happen when an install failed or + # was aborted while in progress see + # https://github.com/home-assistant/core/issues/47699 + if installed_version is None: + _LOGGER.error("Installed version for %s resolved to None", req.project_name) # type: ignore + return False + return installed_version in req except PackageNotFoundError: return False diff --git a/tests/util/test_package.py b/tests/util/test_package.py index 0c251662444..494fe5fa11f 100644 --- a/tests/util/test_package.py +++ b/tests/util/test_package.py @@ -239,10 +239,55 @@ async def test_async_get_user_site(mock_env_copy): def test_check_package_global(): """Test for an installed package.""" - installed_package = list(pkg_resources.working_set)[0].project_name + first_package = list(pkg_resources.working_set)[0] + installed_package = first_package.project_name + installed_version = first_package.version + assert package.is_installed(installed_package) + assert package.is_installed(f"{installed_package}=={installed_version}") + assert package.is_installed(f"{installed_package}>={installed_version}") + assert package.is_installed(f"{installed_package}<={installed_version}") + assert not package.is_installed(f"{installed_package}<{installed_version}") + + +def test_check_package_version_does_not_match(): + """Test for version mismatch.""" + installed_package = list(pkg_resources.working_set)[0].project_name + assert not package.is_installed(f"{installed_package}==999.999.999") + assert not package.is_installed(f"{installed_package}>=999.999.999") def test_check_package_zip(): """Test for an installed zip package.""" assert not package.is_installed(TEST_ZIP_REQ) + + +def test_get_distribution_falls_back_to_version(): + """Test for get_distribution failing and fallback to version.""" + first_package = list(pkg_resources.working_set)[0] + installed_package = first_package.project_name + installed_version = first_package.version + + with patch( + "homeassistant.util.package.pkg_resources.get_distribution", + side_effect=pkg_resources.ExtractionError, + ): + assert package.is_installed(installed_package) + assert package.is_installed(f"{installed_package}=={installed_version}") + assert package.is_installed(f"{installed_package}>={installed_version}") + assert package.is_installed(f"{installed_package}<={installed_version}") + assert not package.is_installed(f"{installed_package}<{installed_version}") + + +def test_check_package_previous_failed_install(): + """Test for when a previously install package failed and left cruft behind.""" + first_package = list(pkg_resources.working_set)[0] + installed_package = first_package.project_name + installed_version = first_package.version + + with patch( + "homeassistant.util.package.pkg_resources.get_distribution", + side_effect=pkg_resources.ExtractionError, + ), patch("homeassistant.util.package.version", return_value=None): + assert not package.is_installed(installed_package) + assert not package.is_installed(f"{installed_package}=={installed_version}") From 7e615cb7fdde6de265384d2635072c07c4b66505 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Thu, 11 Mar 2021 10:16:52 +0100 Subject: [PATCH 292/831] Fixed string typos in Lutron and Roomba (#47745) --- homeassistant/components/lutron_caseta/strings.json | 2 +- homeassistant/components/roomba/strings.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lutron_caseta/strings.json b/homeassistant/components/lutron_caseta/strings.json index 604a3c24ab2..9464523fcce 100644 --- a/homeassistant/components/lutron_caseta/strings.json +++ b/homeassistant/components/lutron_caseta/strings.json @@ -7,7 +7,7 @@ "description": "Couldn’t setup bridge (host: {host}) imported from configuration.yaml." }, "user": { - "title": "Automaticlly connect to the bridge", + "title": "Automatically connect to the bridge", "description": "Enter the IP address of the device.", "data": { "host": "[%key:common::config_flow::data::host%]" diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json index 48e130df4f5..59039a8d276 100644 --- a/homeassistant/components/roomba/strings.json +++ b/homeassistant/components/roomba/strings.json @@ -3,7 +3,7 @@ "flow_title": "iRobot {name} ({host})", "step": { "init": { - "title": "Automaticlly connect to the device", + "title": "Automatically connect to the device", "description": "Select a Roomba or Braava.", "data": { "host": "[%key:common::config_flow::data::host%]" From b9c2f80cab187d8e0be14935996aa04ec51c160a Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 11 Mar 2021 11:46:32 +0100 Subject: [PATCH 293/831] Fix light brightness_step on multiple entities (#47746) * Fix light brightness_step on multiple entities * Fix comment Co-authored-by: Martin Hjelmare --- homeassistant/components/light/__init__.py | 2 +- tests/components/light/test_init.py | 47 ++++++++++++++-------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 4156736f74d..693eb1a573a 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -207,7 +207,7 @@ async def async_setup(hass, config): If brightness is set to 0, this service will turn the light off. """ - params = call.data["params"] + params = dict(call.data["params"]) # Only process params once we processed brightness step if params and ( diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index c36fa623e37..10f475a580d 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -706,36 +706,51 @@ async def test_light_turn_on_auth(hass, hass_admin_user): async def test_light_brightness_step(hass): """Test that light context works.""" platform = getattr(hass.components, "test.light") - platform.init() - entity = platform.ENTITIES[0] - entity.supported_features = light.SUPPORT_BRIGHTNESS - entity.brightness = 100 + platform.init(empty=True) + platform.ENTITIES.append(platform.MockLight("Test_0", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_1", STATE_ON)) + entity0 = platform.ENTITIES[0] + entity0.supported_features = light.SUPPORT_BRIGHTNESS + entity0.brightness = 100 + entity1 = platform.ENTITIES[1] + entity1.supported_features = light.SUPPORT_BRIGHTNESS + entity1.brightness = 50 assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() - state = hass.states.get(entity.entity_id) + state = hass.states.get(entity0.entity_id) assert state is not None assert state.attributes["brightness"] == 100 + state = hass.states.get(entity1.entity_id) + assert state is not None + assert state.attributes["brightness"] == 50 await hass.services.async_call( "light", "turn_on", - {"entity_id": entity.entity_id, "brightness_step": -10}, + {"entity_id": [entity0.entity_id, entity1.entity_id], "brightness_step": -10}, blocking=True, ) - _, data = entity.last_call("turn_on") - assert data["brightness"] == 90, data + _, data = entity0.last_call("turn_on") + assert data["brightness"] == 90 # 100 - 10 + _, data = entity1.last_call("turn_on") + assert data["brightness"] == 40 # 50 - 10 await hass.services.async_call( "light", "turn_on", - {"entity_id": entity.entity_id, "brightness_step_pct": 10}, + { + "entity_id": [entity0.entity_id, entity1.entity_id], + "brightness_step_pct": 10, + }, blocking=True, ) - _, data = entity.last_call("turn_on") - assert data["brightness"] == 126, data + _, data = entity0.last_call("turn_on") + assert data["brightness"] == 126 # 100 + (255 * 0.10) + _, data = entity1.last_call("turn_on") + assert data["brightness"] == 76 # 50 + (255 * 0.10) async def test_light_brightness_pct_conversion(hass): @@ -760,7 +775,7 @@ async def test_light_brightness_pct_conversion(hass): ) _, data = entity.last_call("turn_on") - assert data["brightness"] == 3, data + assert data["brightness"] == 3 await hass.services.async_call( "light", @@ -770,7 +785,7 @@ async def test_light_brightness_pct_conversion(hass): ) _, data = entity.last_call("turn_on") - assert data["brightness"] == 5, data + assert data["brightness"] == 5 await hass.services.async_call( "light", @@ -780,7 +795,7 @@ async def test_light_brightness_pct_conversion(hass): ) _, data = entity.last_call("turn_on") - assert data["brightness"] == 128, data + assert data["brightness"] == 128 await hass.services.async_call( "light", @@ -790,7 +805,7 @@ async def test_light_brightness_pct_conversion(hass): ) _, data = entity.last_call("turn_on") - assert data["brightness"] == 252, data + assert data["brightness"] == 252 await hass.services.async_call( "light", @@ -800,7 +815,7 @@ async def test_light_brightness_pct_conversion(hass): ) _, data = entity.last_call("turn_on") - assert data["brightness"] == 255, data + assert data["brightness"] == 255 def test_deprecated_base_class(caplog): From 724574d336598034ca51c1dc107e4394e778a6cb Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 11 Mar 2021 11:48:48 +0100 Subject: [PATCH 294/831] Add Xiaomi Miio sensor config flow (#46964) * add config flow * fix styling * Add air_quality platform * fix imports * fix black * Update homeassistant/components/xiaomi_miio/sensor.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/xiaomi_miio/air_quality.py Co-authored-by: Martin Hjelmare * process revieuw feedback * remove unused import * fix formatting Co-authored-by: Martin Hjelmare --- .../components/xiaomi_miio/__init__.py | 5 + .../components/xiaomi_miio/air_quality.py | 95 +++++++++---------- homeassistant/components/xiaomi_miio/const.py | 17 ++-- .../components/xiaomi_miio/sensor.py | 83 +++++++--------- 4 files changed, 94 insertions(+), 106 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index 139ac017f66..5b67d21b481 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -16,6 +16,7 @@ from .const import ( CONF_MODEL, DOMAIN, KEY_COORDINATOR, + MODELS_AIR_MONITOR, MODELS_FAN, MODELS_SWITCH, MODELS_VACUUM, @@ -28,6 +29,7 @@ GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "switch", "light"] SWITCH_PLATFORMS = ["switch"] FAN_PLATFORMS = ["fan"] VACUUM_PLATFORMS = ["vacuum"] +AIR_MONITOR_PLATFORMS = ["air_quality", "sensor"] async def async_setup(hass: core.HomeAssistant, config: dict): @@ -129,6 +131,9 @@ async def async_setup_device_entry( for vacuum_model in MODELS_VACUUM: if model.startswith(vacuum_model): platforms = VACUUM_PLATFORMS + for air_monitor_model in MODELS_AIR_MONITOR: + if model.startswith(air_monitor_model): + platforms = AIR_MONITOR_PLATFORMS if not platforms: return False diff --git a/homeassistant/components/xiaomi_miio/air_quality.py b/homeassistant/components/xiaomi_miio/air_quality.py index 1e1e1b58632..b278a60bd48 100644 --- a/homeassistant/components/xiaomi_miio/air_quality.py +++ b/homeassistant/components/xiaomi_miio/air_quality.py @@ -1,19 +1,24 @@ """Support for Xiaomi Mi Air Quality Monitor (PM2.5).""" import logging -from miio import AirQualityMonitor, Device, DeviceException +from miio import AirQualityMonitor, DeviceException import voluptuous as vol from homeassistant.components.air_quality import PLATFORM_SCHEMA, AirQualityEntity +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN -from homeassistant.exceptions import NoEntitySpecifiedError, PlatformNotReady import homeassistant.helpers.config_validation as cv from .const import ( + CONF_DEVICE, + CONF_FLOW_TYPE, + CONF_MODEL, + DOMAIN, MODEL_AIRQUALITYMONITOR_B1, MODEL_AIRQUALITYMONITOR_S1, MODEL_AIRQUALITYMONITOR_V1, ) +from .device import XiaomiMiioEntity _LOGGER = logging.getLogger(__name__) @@ -41,52 +46,54 @@ PROP_TO_ATTR = { async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the sensor from config.""" - - host = config[CONF_HOST] - token = config[CONF_TOKEN] - name = config[CONF_NAME] - - _LOGGER.info("Initializing with host %s (token %s...)", host, token[:5]) - - miio_device = Device(host, token) - - try: - device_info = await hass.async_add_executor_job(miio_device.info) - except DeviceException as ex: - raise PlatformNotReady from ex - - model = device_info.model - unique_id = f"{model}-{device_info.mac_address}" - _LOGGER.debug( - "%s %s %s detected", - model, - device_info.firmware_version, - device_info.hardware_version, + """Import Miio configuration from YAML.""" + _LOGGER.warning( + "Loading Xiaomi Miio Air Quality via platform setup is deprecated. " + "Please remove it from your configuration" + ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) ) - device = AirQualityMonitor(host, token, model=model) - if model == MODEL_AIRQUALITYMONITOR_S1: - entity = AirMonitorS1(name, device, unique_id) - elif model == MODEL_AIRQUALITYMONITOR_B1: - entity = AirMonitorB1(name, device, unique_id) - elif model == MODEL_AIRQUALITYMONITOR_V1: - entity = AirMonitorV1(name, device, unique_id) - else: - raise NoEntitySpecifiedError(f"Not support for entity {unique_id}") +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Xiaomi Air Quality from a config entry.""" + entities = [] - async_add_entities([entity], update_before_add=True) + if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: + host = config_entry.data[CONF_HOST] + token = config_entry.data[CONF_TOKEN] + name = config_entry.title + model = config_entry.data[CONF_MODEL] + unique_id = config_entry.unique_id + + _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) + + device = AirQualityMonitor(host, token, model=model) + + if model == MODEL_AIRQUALITYMONITOR_S1: + entities.append(AirMonitorS1(name, device, config_entry, unique_id)) + elif model == MODEL_AIRQUALITYMONITOR_B1: + entities.append(AirMonitorB1(name, device, config_entry, unique_id)) + elif model == MODEL_AIRQUALITYMONITOR_V1: + entities.append(AirMonitorV1(name, device, config_entry, unique_id)) + else: + _LOGGER.warning("AirQualityMonitor model '%s' is not yet supported", model) + + async_add_entities(entities, update_before_add=True) -class AirMonitorB1(AirQualityEntity): +class AirMonitorB1(XiaomiMiioEntity, AirQualityEntity): """Air Quality class for Xiaomi cgllc.airmonitor.b1 device.""" - def __init__(self, name, device, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the entity.""" - self._name = name - self._device = device - self._unique_id = unique_id + super().__init__(name, device, entry, unique_id) + self._icon = "mdi:cloud" self._available = None self._air_quality_index = None @@ -112,11 +119,6 @@ class AirMonitorB1(AirQualityEntity): self._available = False _LOGGER.error("Got exception while fetching the state: %s", ex) - @property - def name(self): - """Return the name of this entity, if any.""" - return self._name - @property def icon(self): """Return the icon to use for device if any.""" @@ -127,11 +129,6 @@ class AirMonitorB1(AirQualityEntity): """Return true when state is known.""" return self._available - @property - def unique_id(self): - """Return the unique ID.""" - return self._unique_id - @property def air_quality_index(self): """Return the Air Quality Index (AQI).""" diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index da422986900..6bf2e2f69d1 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -55,6 +55,11 @@ MODELS_FAN_MIIO = [ MODEL_AIRFRESH_VA2, ] +# AirQuality Models +MODEL_AIRQUALITYMONITOR_V1 = "zhimi.airmonitor.v1" +MODEL_AIRQUALITYMONITOR_B1 = "cgllc.airmonitor.b1" +MODEL_AIRQUALITYMONITOR_S1 = "cgllc.airmonitor.s1" + # Model lists MODELS_GATEWAY = ["lumi.gateway", "lumi.acpartner"] MODELS_SWITCH = [ @@ -71,8 +76,13 @@ MODELS_SWITCH = [ ] MODELS_FAN = MODELS_FAN_MIIO + MODELS_HUMIDIFIER_MIOT + MODELS_PURIFIER_MIOT MODELS_VACUUM = ["roborock.vacuum", "rockrobo.vacuum"] +MODELS_AIR_MONITOR = [ + MODEL_AIRQUALITYMONITOR_V1, + MODEL_AIRQUALITYMONITOR_B1, + MODEL_AIRQUALITYMONITOR_S1, +] -MODELS_ALL_DEVICES = MODELS_SWITCH + MODELS_FAN + MODELS_VACUUM +MODELS_ALL_DEVICES = MODELS_SWITCH + MODELS_VACUUM + MODELS_AIR_MONITOR + MODELS_FAN MODELS_ALL = MODELS_ALL_DEVICES + MODELS_GATEWAY # Fan Services @@ -126,8 +136,3 @@ SERVICE_STOP_REMOTE_CONTROL = "vacuum_remote_control_stop" SERVICE_CLEAN_SEGMENT = "vacuum_clean_segment" SERVICE_CLEAN_ZONE = "vacuum_clean_zone" SERVICE_GOTO = "vacuum_goto" - -# AirQuality Model -MODEL_AIRQUALITYMONITOR_V1 = "zhimi.airmonitor.v1" -MODEL_AIRQUALITYMONITOR_B1 = "cgllc.airmonitor.b1" -MODEL_AIRQUALITYMONITOR_S1 = "cgllc.airmonitor.s1" diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index a7cfdd788a5..6d43b835f1c 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -13,6 +13,7 @@ from miio.gateway import ( import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_BATTERY_LEVEL, CONF_HOST, @@ -26,17 +27,16 @@ from homeassistant.const import ( PRESSURE_HPA, TEMP_CELSIUS, ) -from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from .const import CONF_FLOW_TYPE, CONF_GATEWAY, DOMAIN, KEY_COORDINATOR +from .const import CONF_DEVICE, CONF_FLOW_TYPE, CONF_GATEWAY, DOMAIN, KEY_COORDINATOR +from .device import XiaomiMiioEntity from .gateway import XiaomiGatewayDevice _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Xiaomi Miio Sensor" -DATA_KEY = "sensor.xiaomi_miio" UNIT_LUMEN = "lm" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @@ -54,7 +54,6 @@ ATTR_NIGHT_MODE = "night_mode" ATTR_NIGHT_TIME_BEGIN = "night_time_begin" ATTR_NIGHT_TIME_END = "night_time_end" ATTR_SENSOR_STATE = "sensor_state" -ATTR_MODEL = "model" SUCCESS = ["ok"] @@ -81,6 +80,21 @@ GATEWAY_SENSOR_TYPES = { } +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Import Miio configuration from YAML.""" + _LOGGER.warning( + "Loading Xiaomi Miio Sensor via platform setup is deprecated. " + "Please remove it from your configuration" + ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) + ) + + async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Xiaomi sensor from a config entry.""" entities = [] @@ -114,48 +128,26 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ] ) + if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: + host = config_entry.data[CONF_HOST] + token = config_entry.data[CONF_TOKEN] + name = config_entry.title + unique_id = config_entry.unique_id + + _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) + + device = AirQualityMonitor(host, token) + entities.append(XiaomiAirQualityMonitor(name, device, config_entry, unique_id)) + async_add_entities(entities, update_before_add=True) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the sensor from config.""" - if DATA_KEY not in hass.data: - hass.data[DATA_KEY] = {} - - host = config[CONF_HOST] - token = config[CONF_TOKEN] - name = config[CONF_NAME] - - _LOGGER.info("Initializing with host %s (token %s...)", host, token[:5]) - - try: - air_quality_monitor = AirQualityMonitor(host, token) - device_info = await hass.async_add_executor_job(air_quality_monitor.info) - model = device_info.model - unique_id = f"{model}-{device_info.mac_address}" - _LOGGER.info( - "%s %s %s detected", - model, - device_info.firmware_version, - device_info.hardware_version, - ) - device = XiaomiAirQualityMonitor(name, air_quality_monitor, model, unique_id) - except DeviceException as ex: - raise PlatformNotReady from ex - - hass.data[DATA_KEY][host] = device - async_add_entities([device], update_before_add=True) - - -class XiaomiAirQualityMonitor(Entity): +class XiaomiAirQualityMonitor(XiaomiMiioEntity): """Representation of a Xiaomi Air Quality Monitor.""" - def __init__(self, name, device, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the entity.""" - self._name = name - self._device = device - self._model = model - self._unique_id = unique_id + super().__init__(name, device, entry, unique_id) self._icon = "mdi:cloud" self._unit_of_measurement = "AQI" @@ -170,19 +162,8 @@ class XiaomiAirQualityMonitor(Entity): ATTR_NIGHT_TIME_BEGIN: None, ATTR_NIGHT_TIME_END: None, ATTR_SENSOR_STATE: None, - ATTR_MODEL: self._model, } - @property - def unique_id(self): - """Return an unique ID.""" - return self._unique_id - - @property - def name(self): - """Return the name of this entity, if any.""" - return self._name - @property def unit_of_measurement(self): """Return the unit of measurement of this entity, if any.""" From 9e487eb260271c11bb67e9ffaa584941da17b739 Mon Sep 17 00:00:00 2001 From: Kristian Heljas Date: Thu, 11 Mar 2021 14:42:13 +0200 Subject: [PATCH 295/831] Hoist mqtt name property and add icon support to MqttEntity (#47165) * hoist common MqttEntity properties * remove default name for MqttEntity Default naming is sensible enough * disable overriding common MqttEntity schema * merge common MqttEntity schemas into MQTT_ENTITY_COMMON_SCHEMA --- .../components/mqtt/alarm_control_panel.py | 66 +++---- .../components/mqtt/binary_sensor.py | 40 ++-- homeassistant/components/mqtt/camera.py | 36 +--- homeassistant/components/mqtt/climate.py | 178 ++++++++---------- homeassistant/components/mqtt/cover.py | 22 +-- .../mqtt/device_tracker/schema_discovery.py | 46 +---- homeassistant/components/mqtt/fan.py | 80 +++----- .../components/mqtt/light/schema_basic.py | 21 +-- .../components/mqtt/light/schema_json.py | 21 +-- .../components/mqtt/light/schema_template.py | 21 +-- homeassistant/components/mqtt/lock.py | 53 ++---- homeassistant/components/mqtt/mixins.py | 23 ++- homeassistant/components/mqtt/number.py | 49 +---- homeassistant/components/mqtt/sensor.py | 43 +---- homeassistant/components/mqtt/switch.py | 50 ++--- .../components/mqtt/vacuum/schema_legacy.py | 25 +-- .../components/mqtt/vacuum/schema_state.py | 26 +-- 17 files changed, 235 insertions(+), 565 deletions(-) diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index 2f2ba06d6d7..0f10e91e41c 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -14,9 +14,7 @@ from homeassistant.components.alarm_control_panel.const import ( ) from homeassistant.const import ( CONF_CODE, - CONF_DEVICE, CONF_NAME, - CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_CUSTOM_BYPASS, @@ -44,13 +42,7 @@ from . import ( ) from .. import mqtt from .debug_info import log_messages -from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, - async_setup_entry_helper, -) +from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper _LOGGER = logging.getLogger(__name__) @@ -70,34 +62,28 @@ DEFAULT_ARM_HOME = "ARM_HOME" DEFAULT_ARM_CUSTOM_BYPASS = "ARM_CUSTOM_BYPASS" DEFAULT_DISARM = "DISARM" DEFAULT_NAME = "MQTT Alarm" -PLATFORM_SCHEMA = ( - mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_CODE): cv.string, - vol.Optional(CONF_CODE_ARM_REQUIRED, default=True): cv.boolean, - vol.Optional(CONF_CODE_DISARM_REQUIRED, default=True): cv.boolean, - vol.Optional( - CONF_COMMAND_TEMPLATE, default=DEFAULT_COMMAND_TEMPLATE - ): cv.template, - vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string, - vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string, - vol.Optional(CONF_PAYLOAD_ARM_NIGHT, default=DEFAULT_ARM_NIGHT): cv.string, - vol.Optional( - CONF_PAYLOAD_ARM_CUSTOM_BYPASS, default=DEFAULT_ARM_CUSTOM_BYPASS - ): cv.string, - vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string, - vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean, - vol.Required(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_UNIQUE_ID): cv.string, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_CODE): cv.string, + vol.Optional(CONF_CODE_ARM_REQUIRED, default=True): cv.boolean, + vol.Optional(CONF_CODE_DISARM_REQUIRED, default=True): cv.boolean, + vol.Optional( + CONF_COMMAND_TEMPLATE, default=DEFAULT_COMMAND_TEMPLATE + ): cv.template, + vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string, + vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string, + vol.Optional(CONF_PAYLOAD_ARM_NIGHT, default=DEFAULT_ARM_NIGHT): cv.string, + vol.Optional( + CONF_PAYLOAD_ARM_CUSTOM_BYPASS, default=DEFAULT_ARM_CUSTOM_BYPASS + ): cv.string, + vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string, + vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean, + vol.Required(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_platform( @@ -138,7 +124,6 @@ class MqttAlarm(MqttEntity, alarm.AlarmControlPanelEntity): return PLATFORM_SCHEMA def _setup_from_config(self, config): - self._config = config value_template = self._config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = self.hass @@ -186,11 +171,6 @@ class MqttAlarm(MqttEntity, alarm.AlarmControlPanelEntity): }, ) - @property - def name(self): - """Return the name of the device.""" - return self._config[CONF_NAME] - @property def state(self): """Return the state of the device.""" diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index a1f13e7873e..fbd5e7535c5 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -11,13 +11,11 @@ from homeassistant.components.binary_sensor import ( BinarySensorEntity, ) from homeassistant.const import ( - CONF_DEVICE, CONF_DEVICE_CLASS, CONF_FORCE_UPDATE, CONF_NAME, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, - CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, ) from homeassistant.core import callback @@ -32,9 +30,7 @@ from . import CONF_QOS, CONF_STATE_TOPIC, DOMAIN, PLATFORMS, subscription from .. import mqtt from .debug_info import log_messages from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, + MQTT_ENTITY_COMMON_SCHEMA, MqttAvailability, MqttEntity, async_setup_entry_helper, @@ -49,23 +45,17 @@ DEFAULT_PAYLOAD_ON = "ON" DEFAULT_FORCE_UPDATE = False CONF_EXPIRE_AFTER = "expire_after" -PLATFORM_SCHEMA = ( - mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int, - vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_OFF_DELAY): cv.positive_int, - vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, - vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, - vol.Optional(CONF_UNIQUE_ID): cv.string, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int, + vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_OFF_DELAY): cv.positive_int, + vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, + vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_platform( @@ -113,7 +103,6 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity): return PLATFORM_SCHEMA def _setup_from_config(self, config): - self._config = config value_template = self._config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = self.hass @@ -218,11 +207,6 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity): self.async_write_ha_state() - @property - def name(self): - """Return the name of the binary sensor.""" - return self._config[CONF_NAME] - @property def is_on(self): """Return true if the binary sensor is on.""" diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py index cc58741a923..0a1a35b2ddd 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -5,7 +5,7 @@ import voluptuous as vol from homeassistant.components import camera from homeassistant.components.camera import Camera -from homeassistant.const import CONF_DEVICE, CONF_NAME, CONF_UNIQUE_ID +from homeassistant.const import CONF_NAME from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.reload import async_setup_reload_service @@ -14,29 +14,17 @@ from homeassistant.helpers.typing import ConfigType, HomeAssistantType from . import CONF_QOS, DOMAIN, PLATFORMS, subscription from .. import mqtt from .debug_info import log_messages -from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, - async_setup_entry_helper, -) +from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper CONF_TOPIC = "topic" DEFAULT_NAME = "MQTT Camera" -PLATFORM_SCHEMA = ( - mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_UNIQUE_ID): cv.string, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic, + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_platform( @@ -77,9 +65,6 @@ class MqttCamera(MqttEntity, Camera): """Return the config schema.""" return PLATFORM_SCHEMA - def _setup_from_config(self, config): - self._config = config - async def _subscribe_topics(self): """(Re)Subscribe to topics.""" @@ -105,8 +90,3 @@ class MqttCamera(MqttEntity, Camera): async def async_camera_image(self): """Return image response.""" return self._last_image - - @property - def name(self): - """Return the name of this camera.""" - return self._config[CONF_NAME] diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index ede39103791..8ab7a9ca3cf 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -36,12 +36,10 @@ from homeassistant.components.climate.const import ( ) from homeassistant.const import ( ATTR_TEMPERATURE, - CONF_DEVICE, CONF_NAME, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_TEMPERATURE_UNIT, - CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, PRECISION_HALVES, PRECISION_TENTHS, @@ -63,13 +61,7 @@ from . import ( ) from .. import mqtt from .debug_info import log_messages -from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, - async_setup_entry_helper, -) +from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper _LOGGER = logging.getLogger(__name__) @@ -178,90 +170,84 @@ TOPIC_KEYS = ( ) SCHEMA_BASE = CLIMATE_PLATFORM_SCHEMA.extend(MQTT_BASE_PLATFORM_SCHEMA.schema) -PLATFORM_SCHEMA = ( - SCHEMA_BASE.extend( - { - vol.Optional(CONF_AUX_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_AUX_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_AUX_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_AWAY_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_AWAY_MODE_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_AWAY_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_CURRENT_TEMP_TEMPLATE): cv.template, - vol.Optional(CONF_CURRENT_TEMP_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_FAN_MODE_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_FAN_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional( - CONF_FAN_MODE_LIST, - default=[FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH], - ): cv.ensure_list, - vol.Optional(CONF_FAN_MODE_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_FAN_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_HOLD_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_HOLD_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_HOLD_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_HOLD_LIST, default=list): cv.ensure_list, - vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional( - CONF_MODE_LIST, - default=[ - HVAC_MODE_AUTO, - HVAC_MODE_OFF, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - ], - ): cv.ensure_list, - vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string, - vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string, - vol.Optional(CONF_POWER_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_POWER_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_POWER_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_PRECISION): vol.In( - [PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE] - ), - vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean, - vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean, - vol.Optional(CONF_ACTION_TEMPLATE): cv.template, - vol.Optional(CONF_ACTION_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_SWING_MODE_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_SWING_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional( - CONF_SWING_MODE_LIST, default=[STATE_ON, HVAC_MODE_OFF] - ): cv.ensure_list, - vol.Optional(CONF_SWING_MODE_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_SWING_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_TEMP_INITIAL, default=21): cv.positive_int, - vol.Optional(CONF_TEMP_MIN, default=DEFAULT_MIN_TEMP): vol.Coerce(float), - vol.Optional(CONF_TEMP_MAX, default=DEFAULT_MAX_TEMP): vol.Coerce(float), - vol.Optional(CONF_TEMP_STEP, default=1.0): vol.Coerce(float), - vol.Optional(CONF_TEMP_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_TEMP_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_TEMP_HIGH_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_TEMP_HIGH_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_TEMP_HIGH_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_TEMP_HIGH_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_TEMP_LOW_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_TEMP_LOW_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_TEMP_LOW_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_TEMP_LOW_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_TEMP_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_TEMP_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_TEMPERATURE_UNIT): cv.temperature_unit, - vol.Optional(CONF_UNIQUE_ID): cv.string, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA = SCHEMA_BASE.extend( + { + vol.Optional(CONF_AUX_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_AUX_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_AUX_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_AWAY_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_AWAY_MODE_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_AWAY_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_CURRENT_TEMP_TEMPLATE): cv.template, + vol.Optional(CONF_CURRENT_TEMP_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_FAN_MODE_COMMAND_TEMPLATE): cv.template, + vol.Optional(CONF_FAN_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional( + CONF_FAN_MODE_LIST, + default=[FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH], + ): cv.ensure_list, + vol.Optional(CONF_FAN_MODE_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_FAN_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_HOLD_COMMAND_TEMPLATE): cv.template, + vol.Optional(CONF_HOLD_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_HOLD_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_HOLD_LIST, default=list): cv.ensure_list, + vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template, + vol.Optional(CONF_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional( + CONF_MODE_LIST, + default=[ + HVAC_MODE_AUTO, + HVAC_MODE_OFF, + HVAC_MODE_COOL, + HVAC_MODE_HEAT, + HVAC_MODE_DRY, + HVAC_MODE_FAN_ONLY, + ], + ): cv.ensure_list, + vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string, + vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string, + vol.Optional(CONF_POWER_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_POWER_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_POWER_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_PRECISION): vol.In( + [PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE] + ), + vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean, + vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean, + vol.Optional(CONF_ACTION_TEMPLATE): cv.template, + vol.Optional(CONF_ACTION_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_SWING_MODE_COMMAND_TEMPLATE): cv.template, + vol.Optional(CONF_SWING_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional( + CONF_SWING_MODE_LIST, default=[STATE_ON, HVAC_MODE_OFF] + ): cv.ensure_list, + vol.Optional(CONF_SWING_MODE_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_SWING_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_TEMP_INITIAL, default=21): cv.positive_int, + vol.Optional(CONF_TEMP_MIN, default=DEFAULT_MIN_TEMP): vol.Coerce(float), + vol.Optional(CONF_TEMP_MAX, default=DEFAULT_MAX_TEMP): vol.Coerce(float), + vol.Optional(CONF_TEMP_STEP, default=1.0): vol.Coerce(float), + vol.Optional(CONF_TEMP_COMMAND_TEMPLATE): cv.template, + vol.Optional(CONF_TEMP_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_TEMP_HIGH_COMMAND_TEMPLATE): cv.template, + vol.Optional(CONF_TEMP_HIGH_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_TEMP_HIGH_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_TEMP_HIGH_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_TEMP_LOW_COMMAND_TEMPLATE): cv.template, + vol.Optional(CONF_TEMP_LOW_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_TEMP_LOW_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_TEMP_LOW_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_TEMP_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_TEMP_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_TEMPERATURE_UNIT): cv.temperature_unit, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_platform( @@ -321,7 +307,6 @@ class MqttClimate(MqttEntity, ClimateEntity): def _setup_from_config(self, config): """(Re)Setup the entity.""" - self._config = config self._topic = {key: config.get(key) for key in TOPIC_KEYS} # set to None in non-optimistic mode @@ -563,11 +548,6 @@ class MqttClimate(MqttEntity, ClimateEntity): self.hass, self._sub_state, topics ) - @property - def name(self): - """Return the name of the climate device.""" - return self._config[CONF_NAME] - @property def temperature_unit(self): """Return the unit of measurement.""" diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 7b7f983b1e4..5c7b0c10cf6 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -20,11 +20,9 @@ from homeassistant.components.cover import ( CoverEntity, ) from homeassistant.const import ( - CONF_DEVICE, CONF_DEVICE_CLASS, CONF_NAME, CONF_OPTIMISTIC, - CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, STATE_CLOSED, STATE_CLOSING, @@ -48,13 +46,7 @@ from . import ( ) from .. import mqtt from .debug_info import log_messages -from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, - async_setup_entry_helper, -) +from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper _LOGGER = logging.getLogger(__name__) @@ -147,7 +139,6 @@ PLATFORM_SCHEMA = vol.All( mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( { vol.Optional(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_GET_POSITION_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, @@ -183,14 +174,11 @@ PLATFORM_SCHEMA = vol.All( ): cv.boolean, vol.Optional(CONF_TILT_STATUS_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_TILT_STATUS_TEMPLATE): cv.template, - vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_GET_POSITION_TEMPLATE): cv.template, vol.Optional(CONF_TILT_COMMAND_TEMPLATE): cv.template, } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema), + ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema), validate_options, ) @@ -238,7 +226,6 @@ class MqttCover(MqttEntity, CoverEntity): return PLATFORM_SCHEMA def _setup_from_config(self, config): - self._config = config self._optimistic = config[CONF_OPTIMISTIC] or ( config.get(CONF_STATE_TOPIC) is None and config.get(CONF_GET_POSITION_TOPIC) is None @@ -399,11 +386,6 @@ class MqttCover(MqttEntity, CoverEntity): """Return true if we do optimistic updates.""" return self._optimistic - @property - def name(self): - """Return the name of the cover.""" - return self._config[CONF_NAME] - @property def is_closed(self): """Return true if the cover is closed or None if the status is unknown.""" diff --git a/homeassistant/components/mqtt/device_tracker/schema_discovery.py b/homeassistant/components/mqtt/device_tracker/schema_discovery.py index bd5d9a1e60e..a1279f512a5 100644 --- a/homeassistant/components/mqtt/device_tracker/schema_discovery.py +++ b/homeassistant/components/mqtt/device_tracker/schema_discovery.py @@ -10,10 +10,7 @@ from homeassistant.const import ( ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, - CONF_DEVICE, - CONF_ICON, CONF_NAME, - CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, STATE_HOME, STATE_NOT_HOME, @@ -25,33 +22,20 @@ from .. import subscription from ... import mqtt from ..const import CONF_QOS, CONF_STATE_TOPIC from ..debug_info import log_messages -from ..mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, - async_setup_entry_helper, -) +from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper CONF_PAYLOAD_HOME = "payload_home" CONF_PAYLOAD_NOT_HOME = "payload_not_home" CONF_SOURCE_TYPE = "source_type" -PLATFORM_SCHEMA_DISCOVERY = ( - mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_ICON): cv.icon, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_PAYLOAD_HOME, default=STATE_HOME): cv.string, - vol.Optional(CONF_PAYLOAD_NOT_HOME, default=STATE_NOT_HOME): cv.string, - vol.Optional(CONF_SOURCE_TYPE): vol.In(SOURCE_TYPES), - vol.Optional(CONF_UNIQUE_ID): cv.string, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA_DISCOVERY = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_PAYLOAD_HOME, default=STATE_HOME): cv.string, + vol.Optional(CONF_PAYLOAD_NOT_HOME, default=STATE_NOT_HOME): cv.string, + vol.Optional(CONF_SOURCE_TYPE): vol.In(SOURCE_TYPES), + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_entry_from_discovery(hass, config_entry, async_add_entities): @@ -87,8 +71,6 @@ class MqttDeviceTracker(MqttEntity, TrackerEntity): def _setup_from_config(self, config): """(Re)Setup the entity.""" - self._config = config - value_template = self._config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = self.hass @@ -125,11 +107,6 @@ class MqttDeviceTracker(MqttEntity, TrackerEntity): }, ) - @property - def icon(self): - """Return the icon of the device.""" - return self._config.get(CONF_ICON) - @property def latitude(self): """Return latitude if provided in device_state_attributes or None.""" @@ -165,11 +142,6 @@ class MqttDeviceTracker(MqttEntity, TrackerEntity): """Return a location name for the current location of the device.""" return self._location_name - @property - def name(self): - """Return the name of the device tracker.""" - return self._config.get(CONF_NAME) - @property def source_type(self): """Return the source type, eg gps or router, of the device.""" diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index 24d652062ae..c0663370805 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -15,13 +15,11 @@ from homeassistant.components.fan import ( FanEntity, ) from homeassistant.const import ( - CONF_DEVICE, CONF_NAME, CONF_OPTIMISTIC, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_STATE, - CONF_UNIQUE_ID, ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv @@ -39,13 +37,7 @@ from . import ( ) from .. import mqtt from .debug_info import log_messages -from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, - async_setup_entry_helper, -) +from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper CONF_STATE_VALUE_TEMPLATE = "state_value_template" CONF_SPEED_STATE_TOPIC = "speed_state_topic" @@ -72,41 +64,35 @@ OSCILLATE_OFF_PAYLOAD = "oscillate_off" OSCILLATION = "oscillation" -PLATFORM_SCHEMA = ( - mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, - vol.Optional(CONF_OSCILLATION_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_OSCILLATION_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_OSCILLATION_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_PAYLOAD_HIGH_SPEED, default=SPEED_HIGH): cv.string, - vol.Optional(CONF_PAYLOAD_LOW_SPEED, default=SPEED_LOW): cv.string, - vol.Optional(CONF_PAYLOAD_MEDIUM_SPEED, default=SPEED_MEDIUM): cv.string, - vol.Optional(CONF_PAYLOAD_OFF_SPEED, default=SPEED_OFF): cv.string, - vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, - vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, - vol.Optional( - CONF_PAYLOAD_OSCILLATION_OFF, default=OSCILLATE_OFF_PAYLOAD - ): cv.string, - vol.Optional( - CONF_PAYLOAD_OSCILLATION_ON, default=OSCILLATE_ON_PAYLOAD - ): cv.string, - vol.Optional(CONF_SPEED_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional( - CONF_SPEED_LIST, - default=[SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH], - ): cv.ensure_list, - vol.Optional(CONF_SPEED_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_SPEED_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_UNIQUE_ID): cv.string, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, + vol.Optional(CONF_OSCILLATION_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_OSCILLATION_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_OSCILLATION_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_PAYLOAD_HIGH_SPEED, default=SPEED_HIGH): cv.string, + vol.Optional(CONF_PAYLOAD_LOW_SPEED, default=SPEED_LOW): cv.string, + vol.Optional(CONF_PAYLOAD_MEDIUM_SPEED, default=SPEED_MEDIUM): cv.string, + vol.Optional(CONF_PAYLOAD_OFF_SPEED, default=SPEED_OFF): cv.string, + vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, + vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, + vol.Optional( + CONF_PAYLOAD_OSCILLATION_OFF, default=OSCILLATE_OFF_PAYLOAD + ): cv.string, + vol.Optional( + CONF_PAYLOAD_OSCILLATION_ON, default=OSCILLATE_ON_PAYLOAD + ): cv.string, + vol.Optional(CONF_SPEED_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional( + CONF_SPEED_LIST, + default=[SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH], + ): cv.ensure_list, + vol.Optional(CONF_SPEED_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_SPEED_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template, + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_platform( @@ -158,7 +144,6 @@ class MqttFan(MqttEntity, FanEntity): def _setup_from_config(self, config): """(Re)Setup the entity.""" - self._config = config self._topic = { key: config.get(key) for key in ( @@ -288,11 +273,6 @@ class MqttFan(MqttEntity, FanEntity): """Return true if device is on.""" return self._state - @property - def name(self) -> str: - """Get entity name.""" - return self._config[CONF_NAME] - @property def speed_list(self) -> list: """Get the list of available speeds.""" diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index 04f01ea0d3a..e2443ec1df6 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -17,12 +17,10 @@ from homeassistant.components.light import ( LightEntity, ) from homeassistant.const import ( - CONF_DEVICE, CONF_NAME, CONF_OPTIMISTIC, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, - CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, STATE_ON, ) @@ -34,12 +32,7 @@ import homeassistant.util.color as color_util from .. import CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, subscription from ... import mqtt from ..debug_info import log_messages -from ..mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, -) +from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity from .schema import MQTT_LIGHT_SCHEMA_SCHEMA _LOGGER = logging.getLogger(__name__) @@ -110,7 +103,6 @@ PLATFORM_SCHEMA_BASIC = ( vol.Optional(CONF_COLOR_TEMP_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_COLOR_TEMP_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_COLOR_TEMP_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_EFFECT_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_EFFECT_STATE_TOPIC): mqtt.valid_subscribe_topic, @@ -132,7 +124,6 @@ PLATFORM_SCHEMA_BASIC = ( vol.Optional(CONF_RGB_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_RGB_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_WHITE_VALUE_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional( CONF_WHITE_VALUE_SCALE, default=DEFAULT_WHITE_VALUE_SCALE @@ -144,8 +135,7 @@ PLATFORM_SCHEMA_BASIC = ( vol.Optional(CONF_XY_VALUE_TEMPLATE): cv.template, } ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) + .extend(MQTT_ENTITY_COMMON_SCHEMA.schema) .extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema) ) @@ -194,8 +184,6 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): def _setup_from_config(self, config): """(Re)Setup the entity.""" - self._config = config - topic = { key: config.get(key) for key in ( @@ -540,11 +528,6 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): return white_value return None - @property - def name(self): - """Return the name of the device if any.""" - return self._config[CONF_NAME] - @property def is_on(self): """Return true if device is on.""" diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 8ec5c29db62..99c48aa1c8f 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -26,13 +26,11 @@ from homeassistant.components.light import ( from homeassistant.const import ( CONF_BRIGHTNESS, CONF_COLOR_TEMP, - CONF_DEVICE, CONF_EFFECT, CONF_HS, CONF_NAME, CONF_OPTIMISTIC, CONF_RGB, - CONF_UNIQUE_ID, CONF_WHITE_VALUE, CONF_XY, STATE_ON, @@ -46,12 +44,7 @@ import homeassistant.util.color as color_util from .. import CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, subscription from ... import mqtt from ..debug_info import log_messages -from ..mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, -) +from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity from .schema import MQTT_LIGHT_SCHEMA_SCHEMA from .schema_basic import CONF_BRIGHTNESS_SCALE @@ -89,7 +82,6 @@ PLATFORM_SCHEMA_JSON = ( CONF_BRIGHTNESS_SCALE, default=DEFAULT_BRIGHTNESS_SCALE ): vol.All(vol.Coerce(int), vol.Range(min=1)), vol.Optional(CONF_COLOR_TEMP, default=DEFAULT_COLOR_TEMP): cv.boolean, - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_EFFECT, default=DEFAULT_EFFECT): cv.boolean, vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]), vol.Optional( @@ -109,13 +101,11 @@ PLATFORM_SCHEMA_JSON = ( vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean, vol.Optional(CONF_RGB, default=DEFAULT_RGB): cv.boolean, vol.Optional(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_WHITE_VALUE, default=DEFAULT_WHITE_VALUE): cv.boolean, vol.Optional(CONF_XY, default=DEFAULT_XY): cv.boolean, } ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) + .extend(MQTT_ENTITY_COMMON_SCHEMA.schema) .extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema) ) @@ -153,8 +143,6 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): def _setup_from_config(self, config): """(Re)Setup the entity.""" - self._config = config - self._topic = { key: config.get(key) for key in (CONF_STATE_TOPIC, CONF_COMMAND_TOPIC) } @@ -338,11 +326,6 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): """Return the white property.""" return self._white_value - @property - def name(self): - """Return the name of the device if any.""" - return self._config[CONF_NAME] - @property def is_on(self): """Return true if device is on.""" diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index 665e1a30d99..118746f2229 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -21,11 +21,9 @@ from homeassistant.components.light import ( LightEntity, ) from homeassistant.const import ( - CONF_DEVICE, CONF_NAME, CONF_OPTIMISTIC, CONF_STATE_TEMPLATE, - CONF_UNIQUE_ID, STATE_OFF, STATE_ON, ) @@ -37,12 +35,7 @@ import homeassistant.util.color as color_util from .. import CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, subscription from ... import mqtt from ..debug_info import log_messages -from ..mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, -) +from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity from .schema import MQTT_LIGHT_SCHEMA_SCHEMA _LOGGER = logging.getLogger(__name__) @@ -73,7 +66,6 @@ PLATFORM_SCHEMA_TEMPLATE = ( vol.Optional(CONF_COLOR_TEMP_TEMPLATE): cv.template, vol.Required(CONF_COMMAND_OFF_TEMPLATE): cv.template, vol.Required(CONF_COMMAND_ON_TEMPLATE): cv.template, - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_EFFECT_TEMPLATE): cv.template, vol.Optional(CONF_GREEN_TEMPLATE): cv.template, @@ -83,12 +75,10 @@ PLATFORM_SCHEMA_TEMPLATE = ( vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_RED_TEMPLATE): cv.template, vol.Optional(CONF_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_WHITE_VALUE_TEMPLATE): cv.template, } ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) + .extend(MQTT_ENTITY_COMMON_SCHEMA.schema) .extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema) ) @@ -127,8 +117,6 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): def _setup_from_config(self, config): """(Re)Setup the entity.""" - self._config = config - self._topics = { key: config.get(key) for key in (CONF_STATE_TOPIC, CONF_COMMAND_TOPIC) } @@ -299,11 +287,6 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): """Return the white property.""" return self._white_value - @property - def name(self): - """Return the name of the entity.""" - return self._config[CONF_NAME] - @property def is_on(self): """Return True if entity is on.""" diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index e66b93f51c0..cdfa5101548 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -5,13 +5,7 @@ import voluptuous as vol from homeassistant.components import lock from homeassistant.components.lock import LockEntity -from homeassistant.const import ( - CONF_DEVICE, - CONF_NAME, - CONF_OPTIMISTIC, - CONF_UNIQUE_ID, - CONF_VALUE_TEMPLATE, -) +from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.reload import async_setup_reload_service @@ -28,13 +22,7 @@ from . import ( ) from .. import mqtt from .debug_info import log_messages -from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, - async_setup_entry_helper, -) +from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper CONF_PAYLOAD_LOCK = "payload_lock" CONF_PAYLOAD_UNLOCK = "payload_unlock" @@ -49,26 +37,16 @@ DEFAULT_PAYLOAD_UNLOCK = "UNLOCK" DEFAULT_STATE_LOCKED = "LOCKED" DEFAULT_STATE_UNLOCKED = "UNLOCKED" -PLATFORM_SCHEMA = ( - mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, - vol.Optional(CONF_PAYLOAD_LOCK, default=DEFAULT_PAYLOAD_LOCK): cv.string, - vol.Optional( - CONF_PAYLOAD_UNLOCK, default=DEFAULT_PAYLOAD_UNLOCK - ): cv.string, - vol.Optional(CONF_STATE_LOCKED, default=DEFAULT_STATE_LOCKED): cv.string, - vol.Optional( - CONF_STATE_UNLOCKED, default=DEFAULT_STATE_UNLOCKED - ): cv.string, - vol.Optional(CONF_UNIQUE_ID): cv.string, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, + vol.Optional(CONF_PAYLOAD_LOCK, default=DEFAULT_PAYLOAD_LOCK): cv.string, + vol.Optional(CONF_PAYLOAD_UNLOCK, default=DEFAULT_PAYLOAD_UNLOCK): cv.string, + vol.Optional(CONF_STATE_LOCKED, default=DEFAULT_STATE_LOCKED): cv.string, + vol.Optional(CONF_STATE_UNLOCKED, default=DEFAULT_STATE_UNLOCKED): cv.string, + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_platform( @@ -111,8 +89,6 @@ class MqttLock(MqttEntity, LockEntity): def _setup_from_config(self, config): """(Re)Setup the entity.""" - self._config = config - self._optimistic = config[CONF_OPTIMISTIC] value_template = self._config.get(CONF_VALUE_TEMPLATE) @@ -153,11 +129,6 @@ class MqttLock(MqttEntity, LockEntity): }, ) - @property - def name(self): - """Return the name of the lock.""" - return self._config[CONF_NAME] - @property def is_locked(self): """Return true if lock is locked.""" diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 8d9c9533ed3..8bcb5e5ca97 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -6,7 +6,7 @@ from typing import Optional import voluptuous as vol -from homeassistant.const import CONF_DEVICE, CONF_NAME, CONF_UNIQUE_ID +from homeassistant.const import CONF_DEVICE, CONF_ICON, CONF_NAME, CONF_UNIQUE_ID from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import ( @@ -134,10 +134,13 @@ MQTT_ENTITY_DEVICE_INFO_SCHEMA = vol.All( validate_device_has_at_least_one_identifier, ) -MQTT_JSON_ATTRS_SCHEMA = vol.Schema( +MQTT_ENTITY_COMMON_SCHEMA = MQTT_AVAILABILITY_SCHEMA.extend( { + vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, + vol.Optional(CONF_ICON): cv.icon, vol.Optional(CONF_JSON_ATTRS_TOPIC): valid_subscribe_topic, vol.Optional(CONF_JSON_ATTRS_TEMPLATE): cv.template, + vol.Optional(CONF_UNIQUE_ID): cv.string, } ) @@ -527,11 +530,12 @@ class MqttEntity( def __init__(self, hass, config, config_entry, discovery_data): """Init the MQTT Entity.""" self.hass = hass + self._config = config self._unique_id = config.get(CONF_UNIQUE_ID) self._sub_state = None # Load config - self._setup_from_config(config) + self._setup_from_config(self._config) # Initialize mixin classes MqttAttributes.__init__(self, config) @@ -547,7 +551,8 @@ class MqttEntity( async def discovery_update(self, discovery_payload): """Handle updated discovery message.""" config = self.config_schema()(discovery_payload) - self._setup_from_config(config) + self._config = config + self._setup_from_config(self._config) await self.attributes_discovery_update(config) await self.availability_discovery_update(config) await self.device_info_discovery_update(config) @@ -575,6 +580,16 @@ class MqttEntity( async def _subscribe_topics(self): """(Re)Subscribe to topics.""" + @property + def icon(self): + """Return icon of the entity if any.""" + return self._config.get(CONF_ICON) + + @property + def name(self): + """Return the name of the device if any.""" + return self._config.get(CONF_NAME) + @property def should_poll(self): """No polling needed.""" diff --git a/homeassistant/components/mqtt/number.py b/homeassistant/components/mqtt/number.py index aa24f81eb69..e7839f8e483 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -6,13 +6,7 @@ import voluptuous as vol from homeassistant.components import number from homeassistant.components.number import NumberEntity -from homeassistant.const import ( - CONF_DEVICE, - CONF_ICON, - CONF_NAME, - CONF_OPTIMISTIC, - CONF_UNIQUE_ID, -) +from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.reload import async_setup_reload_service @@ -30,32 +24,19 @@ from . import ( from .. import mqtt from .const import CONF_RETAIN from .debug_info import log_messages -from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, - async_setup_entry_helper, -) +from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "MQTT Number" DEFAULT_OPTIMISTIC = False -PLATFORM_SCHEMA = ( - mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_ICON): cv.icon, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, - vol.Optional(CONF_UNIQUE_ID): cv.string, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_platform( @@ -90,7 +71,6 @@ class MqttNumber(MqttEntity, NumberEntity, RestoreEntity): self._current_number = None self._optimistic = config.get(CONF_OPTIMISTIC) - self._unique_id = config.get(CONF_UNIQUE_ID) NumberEntity.__init__(self) MqttEntity.__init__(self, None, config, config_entry, discovery_data) @@ -100,9 +80,6 @@ class MqttNumber(MqttEntity, NumberEntity, RestoreEntity): """Return the config schema.""" return PLATFORM_SCHEMA - def _setup_from_config(self, config): - self._config = config - async def _subscribe_topics(self): """(Re)Subscribe to topics.""" @@ -165,17 +142,7 @@ class MqttNumber(MqttEntity, NumberEntity, RestoreEntity): self._config[CONF_RETAIN], ) - @property - def name(self): - """Return the name of this number.""" - return self._config[CONF_NAME] - @property def assumed_state(self): """Return true if we do optimistic updates.""" return self._optimistic - - @property - def icon(self): - """Return the icon.""" - return self._config.get(CONF_ICON) diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index cb263febe13..d32f4c42383 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -8,12 +8,9 @@ import voluptuous as vol from homeassistant.components import sensor from homeassistant.components.sensor import DEVICE_CLASSES_SCHEMA from homeassistant.const import ( - CONF_DEVICE, CONF_DEVICE_CLASS, CONF_FORCE_UPDATE, - CONF_ICON, CONF_NAME, - CONF_UNIQUE_ID, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, ) @@ -29,9 +26,7 @@ from . import CONF_QOS, CONF_STATE_TOPIC, DOMAIN, PLATFORMS, subscription from .. import mqtt from .debug_info import log_messages from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, + MQTT_ENTITY_COMMON_SCHEMA, MqttAvailability, MqttEntity, async_setup_entry_helper, @@ -41,22 +36,15 @@ CONF_EXPIRE_AFTER = "expire_after" DEFAULT_NAME = "MQTT Sensor" DEFAULT_FORCE_UPDATE = False -PLATFORM_SCHEMA = ( - mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int, - vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, - vol.Optional(CONF_ICON): cv.icon, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_UNIQUE_ID): cv.string, - vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int, + vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_platform( @@ -105,7 +93,6 @@ class MqttSensor(MqttEntity, Entity): def _setup_from_config(self, config): """(Re)Setup the entity.""" - self._config = config template = self._config.get(CONF_VALUE_TEMPLATE) if template is not None: template.hass = self.hass @@ -163,11 +150,6 @@ class MqttSensor(MqttEntity, Entity): self._expired = True self.async_write_ha_state() - @property - def name(self): - """Return the name of the sensor.""" - return self._config[CONF_NAME] - @property def unit_of_measurement(self): """Return the unit this state is expressed in.""" @@ -183,11 +165,6 @@ class MqttSensor(MqttEntity, Entity): """Return the state of the entity.""" return self._state - @property - def icon(self): - """Return the icon.""" - return self._config.get(CONF_ICON) - @property def device_class(self) -> Optional[str]: """Return the device class of the sensor.""" diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index 939d6bb98b1..2b272b0f9be 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -6,13 +6,10 @@ import voluptuous as vol from homeassistant.components import switch from homeassistant.components.switch import SwitchEntity from homeassistant.const import ( - CONF_DEVICE, - CONF_ICON, CONF_NAME, CONF_OPTIMISTIC, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, - CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, STATE_ON, ) @@ -33,13 +30,7 @@ from . import ( ) from .. import mqtt from .debug_info import log_messages -from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, - async_setup_entry_helper, -) +from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper DEFAULT_NAME = "MQTT Switch" DEFAULT_PAYLOAD_ON = "ON" @@ -48,23 +39,16 @@ DEFAULT_OPTIMISTIC = False CONF_STATE_ON = "state_on" CONF_STATE_OFF = "state_off" -PLATFORM_SCHEMA = ( - mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_ICON): cv.icon, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, - vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, - vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, - vol.Optional(CONF_STATE_OFF): cv.string, - vol.Optional(CONF_STATE_ON): cv.string, - vol.Optional(CONF_UNIQUE_ID): cv.string, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, + vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, + vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, + vol.Optional(CONF_STATE_OFF): cv.string, + vol.Optional(CONF_STATE_ON): cv.string, + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_platform( @@ -110,8 +94,6 @@ class MqttSwitch(MqttEntity, SwitchEntity, RestoreEntity): def _setup_from_config(self, config): """(Re)Setup the entity.""" - self._config = config - state_on = config.get(CONF_STATE_ON) self._state_on = state_on if state_on else config[CONF_PAYLOAD_ON] @@ -163,11 +145,6 @@ class MqttSwitch(MqttEntity, SwitchEntity, RestoreEntity): if last_state: self._state = last_state.state == STATE_ON - @property - def name(self): - """Return the name of the switch.""" - return self._config[CONF_NAME] - @property def is_on(self): """Return true if device is on.""" @@ -178,11 +155,6 @@ class MqttSwitch(MqttEntity, SwitchEntity, RestoreEntity): """Return true if we do optimistic updates.""" return self._optimistic - @property - def icon(self): - """Return the icon.""" - return self._config.get(CONF_ICON) - async def async_turn_on(self, **kwargs): """Turn the device on. diff --git a/homeassistant/components/mqtt/vacuum/schema_legacy.py b/homeassistant/components/mqtt/vacuum/schema_legacy.py index ca91b8948d4..f0f00a72bb4 100644 --- a/homeassistant/components/mqtt/vacuum/schema_legacy.py +++ b/homeassistant/components/mqtt/vacuum/schema_legacy.py @@ -17,12 +17,7 @@ from homeassistant.components.vacuum import ( SUPPORT_TURN_ON, VacuumEntity, ) -from homeassistant.const import ( - ATTR_SUPPORTED_FEATURES, - CONF_DEVICE, - CONF_NAME, - CONF_UNIQUE_ID, -) +from homeassistant.const import ATTR_SUPPORTED_FEATURES, CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.icon import icon_for_battery_level @@ -30,12 +25,7 @@ from homeassistant.helpers.icon import icon_for_battery_level from .. import subscription from ... import mqtt from ..debug_info import log_messages -from ..mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, -) +from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services SERVICE_TO_STRING = { @@ -117,7 +107,6 @@ PLATFORM_SCHEMA_LEGACY = ( vol.Inclusive(CONF_CHARGING_TOPIC, "charging"): mqtt.valid_publish_topic, vol.Inclusive(CONF_CLEANING_TEMPLATE, "cleaning"): cv.template, vol.Inclusive(CONF_CLEANING_TOPIC, "cleaning"): mqtt.valid_publish_topic, - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Inclusive(CONF_DOCKED_TEMPLATE, "docked"): cv.template, vol.Inclusive(CONF_DOCKED_TOPIC, "docked"): mqtt.valid_publish_topic, vol.Inclusive(CONF_ERROR_TEMPLATE, "error"): cv.template, @@ -152,13 +141,11 @@ PLATFORM_SCHEMA_LEGACY = ( vol.Optional( CONF_SUPPORTED_FEATURES, default=DEFAULT_SERVICE_STRINGS ): vol.All(cv.ensure_list, [vol.In(STRING_TO_SERVICE.keys())]), - vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(mqtt.CONF_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(mqtt.CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, } ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) + .extend(MQTT_ENTITY_COMMON_SCHEMA.schema) .extend(MQTT_VACUUM_SCHEMA.schema) ) @@ -192,7 +179,6 @@ class MqttVacuum(MqttEntity, VacuumEntity): return PLATFORM_SCHEMA_LEGACY def _setup_from_config(self, config): - self._name = config[CONF_NAME] supported_feature_strings = config[CONF_SUPPORTED_FEATURES] self._supported_features = strings_to_services( supported_feature_strings, STRING_TO_SERVICE @@ -338,11 +324,6 @@ class MqttVacuum(MqttEntity, VacuumEntity): }, ) - @property - def name(self): - """Return the name of the vacuum.""" - return self._name - @property def is_on(self): """Return true if vacuum is on.""" diff --git a/homeassistant/components/mqtt/vacuum/schema_state.py b/homeassistant/components/mqtt/vacuum/schema_state.py index 3e43736ab2e..37a12d33df6 100644 --- a/homeassistant/components/mqtt/vacuum/schema_state.py +++ b/homeassistant/components/mqtt/vacuum/schema_state.py @@ -22,24 +22,14 @@ from homeassistant.components.vacuum import ( SUPPORT_STOP, StateVacuumEntity, ) -from homeassistant.const import ( - ATTR_SUPPORTED_FEATURES, - CONF_DEVICE, - CONF_NAME, - CONF_UNIQUE_ID, -) +from homeassistant.const import ATTR_SUPPORTED_FEATURES, CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from .. import CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, subscription from ... import mqtt from ..debug_info import log_messages -from ..mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, -) +from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services SERVICE_TO_STRING = { @@ -113,7 +103,6 @@ DEFAULT_PAYLOAD_PAUSE = "pause" PLATFORM_SCHEMA_STATE = ( mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( { - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_FAN_SPEED_LIST, default=[]): vol.All( cv.ensure_list, [cv.string] ), @@ -136,13 +125,11 @@ PLATFORM_SCHEMA_STATE = ( vol.Optional( CONF_SUPPORTED_FEATURES, default=DEFAULT_SERVICE_STRINGS ): vol.All(cv.ensure_list, [vol.In(STRING_TO_SERVICE.keys())]), - vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, } ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) + .extend(MQTT_ENTITY_COMMON_SCHEMA.schema) .extend(MQTT_VACUUM_SCHEMA.schema) ) @@ -171,8 +158,6 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): return PLATFORM_SCHEMA_STATE def _setup_from_config(self, config): - self._config = config - self._name = config[CONF_NAME] supported_feature_strings = config[CONF_SUPPORTED_FEATURES] self._supported_features = strings_to_services( supported_feature_strings, STRING_TO_SERVICE @@ -219,11 +204,6 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): self.hass, self._sub_state, topics ) - @property - def name(self): - """Return the name of the vacuum.""" - return self._name - @property def state(self): """Return state of vacuum.""" From b162c45e0ad4c376c12bb5a132940460d618035c Mon Sep 17 00:00:00 2001 From: mtl010957 Date: Thu, 11 Mar 2021 07:49:10 -0500 Subject: [PATCH 296/831] Cover Tilt Position Bugfix (#47682) * Report tilt position properly when inverting using tilt_max < tilt_min * Add warning per review comment * Add test for inverted tilt position configuration * Separate non-numeric and out of range warnings per review comment * Fix out of range message and add tests for not numeric and out of range messages --- homeassistant/components/mqtt/cover.py | 15 +++- tests/components/mqtt/test_cover.py | 106 +++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 5c7b0c10cf6..7f810501e1a 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -267,15 +267,26 @@ class MqttCover(MqttEntity, CoverEntity): payload ) - if payload.isnumeric() and ( + if not payload.isnumeric(): + _LOGGER.warning("Payload '%s' is not numeric", payload) + elif ( self._config[CONF_TILT_MIN] <= int(payload) <= self._config[CONF_TILT_MAX] + or self._config[CONF_TILT_MAX] + <= int(payload) + <= self._config[CONF_TILT_MIN] ): - level = self.find_percentage_in_range(float(payload)) self._tilt_value = level self.async_write_ha_state() + else: + _LOGGER.warning( + "Payload '%s' is out of range, must be between '%s' and '%s' inclusive", + payload, + self._config[CONF_TILT_MIN], + self._config[CONF_TILT_MAX], + ) @callback @log_messages(self.hass, self.entity_id) diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index 44144642f40..d6899d5149a 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -1315,6 +1315,112 @@ async def test_tilt_via_topic_altered_range(hass, mqtt_mock): assert current_cover_tilt_position == 50 +async def test_tilt_status_out_of_range_warning(hass, caplog, mqtt_mock): + """Test tilt status via MQTT tilt out of range warning message.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "qos": 0, + "payload_open": "OPEN", + "payload_close": "CLOSE", + "payload_stop": "STOP", + "tilt_command_topic": "tilt-command-topic", + "tilt_status_topic": "tilt-status-topic", + "tilt_min": 0, + "tilt_max": 50, + } + }, + ) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "tilt-status-topic", "60") + + assert ( + "Payload '60' is out of range, must be between '0' and '50' inclusive" + ) in caplog.text + + +async def test_tilt_status_not_numeric_warning(hass, caplog, mqtt_mock): + """Test tilt status via MQTT tilt not numeric warning message.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "qos": 0, + "payload_open": "OPEN", + "payload_close": "CLOSE", + "payload_stop": "STOP", + "tilt_command_topic": "tilt-command-topic", + "tilt_status_topic": "tilt-status-topic", + "tilt_min": 0, + "tilt_max": 50, + } + }, + ) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "tilt-status-topic", "abc") + + assert ("Payload 'abc' is not numeric") in caplog.text + + +async def test_tilt_via_topic_altered_range_inverted(hass, mqtt_mock): + """Test tilt status via MQTT with altered tilt range and inverted tilt position.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "qos": 0, + "payload_open": "OPEN", + "payload_close": "CLOSE", + "payload_stop": "STOP", + "tilt_command_topic": "tilt-command-topic", + "tilt_status_topic": "tilt-status-topic", + "tilt_min": 50, + "tilt_max": 0, + } + }, + ) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "tilt-status-topic", "0") + + current_cover_tilt_position = hass.states.get("cover.test").attributes[ + ATTR_CURRENT_TILT_POSITION + ] + assert current_cover_tilt_position == 100 + + async_fire_mqtt_message(hass, "tilt-status-topic", "50") + + current_cover_tilt_position = hass.states.get("cover.test").attributes[ + ATTR_CURRENT_TILT_POSITION + ] + assert current_cover_tilt_position == 0 + + async_fire_mqtt_message(hass, "tilt-status-topic", "25") + + current_cover_tilt_position = hass.states.get("cover.test").attributes[ + ATTR_CURRENT_TILT_POSITION + ] + assert current_cover_tilt_position == 50 + + async def test_tilt_via_topic_template_altered_range(hass, mqtt_mock): """Test tilt status via MQTT and template with altered tilt range.""" assert await async_setup_component( From cf4954feadda7252b8e758efb17f00bc0ecb3c55 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 11 Mar 2021 14:09:21 +0100 Subject: [PATCH 297/831] Add Xiaomi Miio light config flow (#47161) Co-authored-by: Martin Hjelmare --- .../components/xiaomi_miio/__init__.py | 4 + homeassistant/components/xiaomi_miio/const.py | 25 +- homeassistant/components/xiaomi_miio/light.py | 377 ++++++++---------- 3 files changed, 198 insertions(+), 208 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index 5b67d21b481..069520ada7d 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -18,6 +18,7 @@ from .const import ( KEY_COORDINATOR, MODELS_AIR_MONITOR, MODELS_FAN, + MODELS_LIGHT, MODELS_SWITCH, MODELS_VACUUM, ) @@ -28,6 +29,7 @@ _LOGGER = logging.getLogger(__name__) GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "switch", "light"] SWITCH_PLATFORMS = ["switch"] FAN_PLATFORMS = ["fan"] +LIGHT_PLATFORMS = ["light"] VACUUM_PLATFORMS = ["vacuum"] AIR_MONITOR_PLATFORMS = ["air_quality", "sensor"] @@ -128,6 +130,8 @@ async def async_setup_device_entry( platforms = SWITCH_PLATFORMS elif model in MODELS_FAN: platforms = FAN_PLATFORMS + elif model in MODELS_LIGHT: + platforms = LIGHT_PLATFORMS for vacuum_model in MODELS_VACUUM: if model.startswith(vacuum_model): platforms = VACUUM_PLATFORMS diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index 6bf2e2f69d1..977e390f26b 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -9,7 +9,7 @@ CONF_MAC = "mac" KEY_COORDINATOR = "coordinator" -# Fam Models +# Fan Models MODEL_AIRPURIFIER_V1 = "zhimi.airpurifier.v1" MODEL_AIRPURIFIER_V2 = "zhimi.airpurifier.v2" MODEL_AIRPURIFIER_V3 = "zhimi.airpurifier.v3" @@ -60,6 +60,18 @@ MODEL_AIRQUALITYMONITOR_V1 = "zhimi.airmonitor.v1" MODEL_AIRQUALITYMONITOR_B1 = "cgllc.airmonitor.b1" MODEL_AIRQUALITYMONITOR_S1 = "cgllc.airmonitor.s1" +# Light Models +MODELS_LIGHT_EYECARE = ["philips.light.sread1"] +MODELS_LIGHT_CEILING = ["philips.light.ceiling", "philips.light.zyceiling"] +MODELS_LIGHT_MOON = ["philips.light.moonlight"] +MODELS_LIGHT_BULB = [ + "philips.light.bulb", + "philips.light.candle", + "philips.light.candle2", + "philips.light.downlight", +] +MODELS_LIGHT_MONO = ["philips.light.mono1"] + # Model lists MODELS_GATEWAY = ["lumi.gateway", "lumi.acpartner"] MODELS_SWITCH = [ @@ -75,6 +87,13 @@ MODELS_SWITCH = [ "chuangmi.plug.hmi206", ] MODELS_FAN = MODELS_FAN_MIIO + MODELS_HUMIDIFIER_MIOT + MODELS_PURIFIER_MIOT +MODELS_LIGHT = ( + MODELS_LIGHT_EYECARE + + MODELS_LIGHT_CEILING + + MODELS_LIGHT_MOON + + MODELS_LIGHT_BULB + + MODELS_LIGHT_MONO +) MODELS_VACUUM = ["roborock.vacuum", "rockrobo.vacuum"] MODELS_AIR_MONITOR = [ MODEL_AIRQUALITYMONITOR_V1, @@ -82,7 +101,9 @@ MODELS_AIR_MONITOR = [ MODEL_AIRQUALITYMONITOR_S1, ] -MODELS_ALL_DEVICES = MODELS_SWITCH + MODELS_VACUUM + MODELS_AIR_MONITOR + MODELS_FAN +MODELS_ALL_DEVICES = ( + MODELS_SWITCH + MODELS_VACUUM + MODELS_AIR_MONITOR + MODELS_FAN + MODELS_LIGHT +) MODELS_ALL = MODELS_ALL_DEVICES + MODELS_GATEWAY # Fan Services diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index b03aa1b0d2a..c0590fbb332 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -6,14 +6,7 @@ from functools import partial import logging from math import ceil -from miio import ( - Ceil, - Device, - DeviceException, - PhilipsBulb, - PhilipsEyecare, - PhilipsMoonlight, -) +from miio import Ceil, DeviceException, PhilipsBulb, PhilipsEyecare, PhilipsMoonlight from miio.gateway import ( GATEWAY_MODEL_AC_V1, GATEWAY_MODEL_AC_V2, @@ -32,15 +25,23 @@ from homeassistant.components.light import ( SUPPORT_COLOR_TEMP, LightEntity, ) +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_TOKEN -from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.util import color, dt from .const import ( + CONF_DEVICE, CONF_FLOW_TYPE, CONF_GATEWAY, + CONF_MODEL, DOMAIN, + MODELS_LIGHT, + MODELS_LIGHT_BULB, + MODELS_LIGHT_CEILING, + MODELS_LIGHT_EYECARE, + MODELS_LIGHT_MONO, + MODELS_LIGHT_MOON, SERVICE_EYECARE_MODE_OFF, SERVICE_EYECARE_MODE_ON, SERVICE_NIGHT_LIGHT_MODE_OFF, @@ -50,32 +51,19 @@ from .const import ( SERVICE_SET_DELAYED_TURN_OFF, SERVICE_SET_SCENE, ) +from .device import XiaomiMiioEntity _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Xiaomi Philips Light" DATA_KEY = "light.xiaomi_miio" -CONF_MODEL = "model" - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, vol.Required(CONF_TOKEN): vol.All(cv.string, vol.Length(min=32, max=32)), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_MODEL): vol.In( - [ - "philips.light.sread1", - "philips.light.ceiling", - "philips.light.zyceiling", - "philips.light.moonlight", - "philips.light.bulb", - "philips.light.candle", - "philips.light.candle2", - "philips.light.mono1", - "philips.light.downlight", - ] - ), + vol.Optional(CONF_MODEL): vol.In(MODELS_LIGHT), } ) @@ -87,7 +75,6 @@ DELAYED_TURN_OFF_MAX_DEVIATION_SECONDS = 4 DELAYED_TURN_OFF_MAX_DEVIATION_MINUTES = 1 SUCCESS = ["ok"] -ATTR_MODEL = "model" ATTR_SCENE = "scene" ATTR_DELAYED_TURN_OFF = "delayed_turn_off" ATTR_TIME_PERIOD = "time_period" @@ -100,8 +87,8 @@ ATTR_EYECARE_MODE = "eyecare_mode" ATTR_SLEEP_ASSISTANT = "sleep_assistant" ATTR_SLEEP_OFF_TIME = "sleep_off_time" ATTR_TOTAL_ASSISTANT_SLEEP_TIME = "total_assistant_sleep_time" -ATTR_BRAND_SLEEP = "brand_sleep" -ATTR_BRAND = "brand" +ATTR_BAND_SLEEP = "band_sleep" +ATTR_BAND = "band" XIAOMI_MIIO_SERVICE_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids}) @@ -131,6 +118,21 @@ SERVICE_TO_METHOD = { } +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Import Miio configuration from YAML.""" + _LOGGER.warning( + "Loading Xiaomi Miio Light via platform setup is deprecated. " + "Please remove it from your configuration" + ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) + ) + + async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Xiaomi light from a config entry.""" entities = [] @@ -147,147 +149,110 @@ async def async_setup_entry(hass, config_entry, async_add_entities): XiaomiGatewayLight(gateway, config_entry.title, config_entry.unique_id) ) + if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: + if DATA_KEY not in hass.data: + hass.data[DATA_KEY] = {} + + host = config_entry.data[CONF_HOST] + token = config_entry.data[CONF_TOKEN] + name = config_entry.title + model = config_entry.data[CONF_MODEL] + unique_id = config_entry.unique_id + + _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) + + if model in MODELS_LIGHT_EYECARE: + light = PhilipsEyecare(host, token) + entity = XiaomiPhilipsEyecareLamp(name, light, config_entry, unique_id) + entities.append(entity) + hass.data[DATA_KEY][host] = entity + + entities.append( + XiaomiPhilipsEyecareLampAmbientLight( + name, light, config_entry, unique_id + ) + ) + # The ambient light doesn't expose additional services. + # A hass.data[DATA_KEY] entry isn't needed. + elif model in MODELS_LIGHT_CEILING: + light = Ceil(host, token) + entity = XiaomiPhilipsCeilingLamp(name, light, config_entry, unique_id) + entities.append(entity) + hass.data[DATA_KEY][host] = entity + elif model in MODELS_LIGHT_MOON: + light = PhilipsMoonlight(host, token) + entity = XiaomiPhilipsMoonlightLamp(name, light, config_entry, unique_id) + entities.append(entity) + hass.data[DATA_KEY][host] = entity + elif model in MODELS_LIGHT_BULB: + light = PhilipsBulb(host, token) + entity = XiaomiPhilipsBulb(name, light, config_entry, unique_id) + entities.append(entity) + hass.data[DATA_KEY][host] = entity + elif model in MODELS_LIGHT_MONO: + light = PhilipsBulb(host, token) + entity = XiaomiPhilipsGenericLight(name, light, config_entry, unique_id) + entities.append(entity) + hass.data[DATA_KEY][host] = entity + else: + _LOGGER.error( + "Unsupported device found! Please create an issue at " + "https://github.com/syssi/philipslight/issues " + "and provide the following data: %s", + model, + ) + return + + async def async_service_handler(service): + """Map services to methods on Xiaomi Philips Lights.""" + method = SERVICE_TO_METHOD.get(service.service) + params = { + key: value + for key, value in service.data.items() + if key != ATTR_ENTITY_ID + } + entity_ids = service.data.get(ATTR_ENTITY_ID) + if entity_ids: + target_devices = [ + dev + for dev in hass.data[DATA_KEY].values() + if dev.entity_id in entity_ids + ] + else: + target_devices = hass.data[DATA_KEY].values() + + update_tasks = [] + for target_device in target_devices: + if not hasattr(target_device, method["method"]): + continue + await getattr(target_device, method["method"])(**params) + update_tasks.append(target_device.async_update_ha_state(True)) + + if update_tasks: + await asyncio.wait(update_tasks) + + for xiaomi_miio_service in SERVICE_TO_METHOD: + schema = SERVICE_TO_METHOD[xiaomi_miio_service].get( + "schema", XIAOMI_MIIO_SERVICE_SCHEMA + ) + hass.services.async_register( + DOMAIN, xiaomi_miio_service, async_service_handler, schema=schema + ) + async_add_entities(entities, update_before_add=True) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the light from config.""" - if DATA_KEY not in hass.data: - hass.data[DATA_KEY] = {} - - host = config[CONF_HOST] - token = config[CONF_TOKEN] - name = config[CONF_NAME] - model = config.get(CONF_MODEL) - - _LOGGER.info("Initializing with host %s (token %s...)", host, token[:5]) - - devices = [] - unique_id = None - - if model is None: - try: - miio_device = Device(host, token) - device_info = await hass.async_add_executor_job(miio_device.info) - model = device_info.model - unique_id = f"{model}-{device_info.mac_address}" - _LOGGER.info( - "%s %s %s detected", - model, - device_info.firmware_version, - device_info.hardware_version, - ) - except DeviceException as ex: - raise PlatformNotReady from ex - - if model == "philips.light.sread1": - light = PhilipsEyecare(host, token) - primary_device = XiaomiPhilipsEyecareLamp(name, light, model, unique_id) - devices.append(primary_device) - hass.data[DATA_KEY][host] = primary_device - - secondary_device = XiaomiPhilipsEyecareLampAmbientLight( - name, light, model, unique_id - ) - devices.append(secondary_device) - # The ambient light doesn't expose additional services. - # A hass.data[DATA_KEY] entry isn't needed. - elif model in ["philips.light.ceiling", "philips.light.zyceiling"]: - light = Ceil(host, token) - device = XiaomiPhilipsCeilingLamp(name, light, model, unique_id) - devices.append(device) - hass.data[DATA_KEY][host] = device - elif model == "philips.light.moonlight": - light = PhilipsMoonlight(host, token) - device = XiaomiPhilipsMoonlightLamp(name, light, model, unique_id) - devices.append(device) - hass.data[DATA_KEY][host] = device - elif model in [ - "philips.light.bulb", - "philips.light.candle", - "philips.light.candle2", - "philips.light.downlight", - ]: - light = PhilipsBulb(host, token) - device = XiaomiPhilipsBulb(name, light, model, unique_id) - devices.append(device) - hass.data[DATA_KEY][host] = device - elif model == "philips.light.mono1": - light = PhilipsBulb(host, token) - device = XiaomiPhilipsGenericLight(name, light, model, unique_id) - devices.append(device) - hass.data[DATA_KEY][host] = device - else: - _LOGGER.error( - "Unsupported device found! Please create an issue at " - "https://github.com/syssi/philipslight/issues " - "and provide the following data: %s", - model, - ) - return False - - async_add_entities(devices, update_before_add=True) - - async def async_service_handler(service): - """Map services to methods on Xiaomi Philips Lights.""" - method = SERVICE_TO_METHOD.get(service.service) - params = { - key: value for key, value in service.data.items() if key != ATTR_ENTITY_ID - } - entity_ids = service.data.get(ATTR_ENTITY_ID) - if entity_ids: - target_devices = [ - dev - for dev in hass.data[DATA_KEY].values() - if dev.entity_id in entity_ids - ] - else: - target_devices = hass.data[DATA_KEY].values() - - update_tasks = [] - for target_device in target_devices: - if not hasattr(target_device, method["method"]): - continue - await getattr(target_device, method["method"])(**params) - update_tasks.append(target_device.async_update_ha_state(True)) - - if update_tasks: - await asyncio.wait(update_tasks) - - for xiaomi_miio_service in SERVICE_TO_METHOD: - schema = SERVICE_TO_METHOD[xiaomi_miio_service].get( - "schema", XIAOMI_MIIO_SERVICE_SCHEMA - ) - hass.services.async_register( - DOMAIN, xiaomi_miio_service, async_service_handler, schema=schema - ) - - -class XiaomiPhilipsAbstractLight(LightEntity): +class XiaomiPhilipsAbstractLight(XiaomiMiioEntity, LightEntity): """Representation of a Abstract Xiaomi Philips Light.""" - def __init__(self, name, light, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the light device.""" - self._name = name - self._light = light - self._model = model - self._unique_id = unique_id + super().__init__(name, device, entry, unique_id) self._brightness = None - self._available = False self._state = None - self._state_attrs = {ATTR_MODEL: self._model} - - @property - def unique_id(self): - """Return an unique ID.""" - return self._unique_id - - @property - def name(self): - """Return the name of the device if any.""" - return self._name + self._state_attrs = {} @property def available(self): @@ -341,23 +306,23 @@ class XiaomiPhilipsAbstractLight(LightEntity): result = await self._try_command( "Setting brightness failed: %s", - self._light.set_brightness, + self._device.set_brightness, percent_brightness, ) if result: self._brightness = brightness else: - await self._try_command("Turning the light on failed.", self._light.on) + await self._try_command("Turning the light on failed.", self._device.on) async def async_turn_off(self, **kwargs): """Turn the light off.""" - await self._try_command("Turning the light off failed.", self._light.off) + await self._try_command("Turning the light off failed.", self._device.off) async def async_update(self): """Fetch state from the device.""" try: - state = await self.hass.async_add_executor_job(self._light.status) + state = await self.hass.async_add_executor_job(self._device.status) except DeviceException as ex: if self._available: self._available = False @@ -374,16 +339,16 @@ class XiaomiPhilipsAbstractLight(LightEntity): class XiaomiPhilipsGenericLight(XiaomiPhilipsAbstractLight): """Representation of a Generic Xiaomi Philips Light.""" - def __init__(self, name, light, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the light device.""" - super().__init__(name, light, model, unique_id) + super().__init__(name, device, entry, unique_id) self._state_attrs.update({ATTR_SCENE: None, ATTR_DELAYED_TURN_OFF: None}) async def async_update(self): """Fetch state from the device.""" try: - state = await self.hass.async_add_executor_job(self._light.status) + state = await self.hass.async_add_executor_job(self._device.status) except DeviceException as ex: if self._available: self._available = False @@ -409,14 +374,14 @@ class XiaomiPhilipsGenericLight(XiaomiPhilipsAbstractLight): async def async_set_scene(self, scene: int = 1): """Set the fixed scene.""" await self._try_command( - "Setting a fixed scene failed.", self._light.set_scene, scene + "Setting a fixed scene failed.", self._device.set_scene, scene ) async def async_set_delayed_turn_off(self, time_period: timedelta): """Set delayed turn off.""" await self._try_command( "Setting the turn off delay failed.", - self._light.delay_off, + self._device.delay_off, time_period.total_seconds(), ) @@ -445,9 +410,9 @@ class XiaomiPhilipsGenericLight(XiaomiPhilipsAbstractLight): class XiaomiPhilipsBulb(XiaomiPhilipsGenericLight): """Representation of a Xiaomi Philips Bulb.""" - def __init__(self, name, light, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the light device.""" - super().__init__(name, light, model, unique_id) + super().__init__(name, device, entry, unique_id) self._color_temp = None @@ -495,7 +460,7 @@ class XiaomiPhilipsBulb(XiaomiPhilipsGenericLight): result = await self._try_command( "Setting brightness and color temperature failed: %s bri, %s cct", - self._light.set_brightness_and_color_temperature, + self._device.set_brightness_and_color_temperature, percent_brightness, percent_color_temp, ) @@ -513,7 +478,7 @@ class XiaomiPhilipsBulb(XiaomiPhilipsGenericLight): result = await self._try_command( "Setting color temperature failed: %s cct", - self._light.set_color_temperature, + self._device.set_color_temperature, percent_color_temp, ) @@ -528,7 +493,7 @@ class XiaomiPhilipsBulb(XiaomiPhilipsGenericLight): result = await self._try_command( "Setting brightness failed: %s", - self._light.set_brightness, + self._device.set_brightness, percent_brightness, ) @@ -536,12 +501,12 @@ class XiaomiPhilipsBulb(XiaomiPhilipsGenericLight): self._brightness = brightness else: - await self._try_command("Turning the light on failed.", self._light.on) + await self._try_command("Turning the light on failed.", self._device.on) async def async_update(self): """Fetch state from the device.""" try: - state = await self.hass.async_add_executor_job(self._light.status) + state = await self.hass.async_add_executor_job(self._device.status) except DeviceException as ex: if self._available: self._available = False @@ -579,9 +544,9 @@ class XiaomiPhilipsBulb(XiaomiPhilipsGenericLight): class XiaomiPhilipsCeilingLamp(XiaomiPhilipsBulb): """Representation of a Xiaomi Philips Ceiling Lamp.""" - def __init__(self, name, light, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the light device.""" - super().__init__(name, light, model, unique_id) + super().__init__(name, device, entry, unique_id) self._state_attrs.update( {ATTR_NIGHT_LIGHT_MODE: None, ATTR_AUTOMATIC_COLOR_TEMPERATURE: None} @@ -600,7 +565,7 @@ class XiaomiPhilipsCeilingLamp(XiaomiPhilipsBulb): async def async_update(self): """Fetch state from the device.""" try: - state = await self.hass.async_add_executor_job(self._light.status) + state = await self.hass.async_add_executor_job(self._device.status) except DeviceException as ex: if self._available: self._available = False @@ -635,9 +600,9 @@ class XiaomiPhilipsCeilingLamp(XiaomiPhilipsBulb): class XiaomiPhilipsEyecareLamp(XiaomiPhilipsGenericLight): """Representation of a Xiaomi Philips Eyecare Lamp 2.""" - def __init__(self, name, light, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the light device.""" - super().__init__(name, light, model, unique_id) + super().__init__(name, device, entry, unique_id) self._state_attrs.update( {ATTR_REMINDER: None, ATTR_NIGHT_LIGHT_MODE: None, ATTR_EYECARE_MODE: None} @@ -646,7 +611,7 @@ class XiaomiPhilipsEyecareLamp(XiaomiPhilipsGenericLight): async def async_update(self): """Fetch state from the device.""" try: - state = await self.hass.async_add_executor_job(self._light.status) + state = await self.hass.async_add_executor_job(self._device.status) except DeviceException as ex: if self._available: self._available = False @@ -679,46 +644,46 @@ class XiaomiPhilipsEyecareLamp(XiaomiPhilipsGenericLight): """Set delayed turn off.""" await self._try_command( "Setting the turn off delay failed.", - self._light.delay_off, + self._device.delay_off, round(time_period.total_seconds() / 60), ) async def async_reminder_on(self): """Enable the eye fatigue notification.""" await self._try_command( - "Turning on the reminder failed.", self._light.reminder_on + "Turning on the reminder failed.", self._device.reminder_on ) async def async_reminder_off(self): """Disable the eye fatigue notification.""" await self._try_command( - "Turning off the reminder failed.", self._light.reminder_off + "Turning off the reminder failed.", self._device.reminder_off ) async def async_night_light_mode_on(self): """Turn the smart night light mode on.""" await self._try_command( "Turning on the smart night light mode failed.", - self._light.smart_night_light_on, + self._device.smart_night_light_on, ) async def async_night_light_mode_off(self): """Turn the smart night light mode off.""" await self._try_command( "Turning off the smart night light mode failed.", - self._light.smart_night_light_off, + self._device.smart_night_light_off, ) async def async_eyecare_mode_on(self): """Turn the eyecare mode on.""" await self._try_command( - "Turning on the eyecare mode failed.", self._light.eyecare_on + "Turning on the eyecare mode failed.", self._device.eyecare_on ) async def async_eyecare_mode_off(self): """Turn the eyecare mode off.""" await self._try_command( - "Turning off the eyecare mode failed.", self._light.eyecare_off + "Turning off the eyecare mode failed.", self._device.eyecare_off ) @staticmethod @@ -748,12 +713,12 @@ class XiaomiPhilipsEyecareLamp(XiaomiPhilipsGenericLight): class XiaomiPhilipsEyecareLampAmbientLight(XiaomiPhilipsAbstractLight): """Representation of a Xiaomi Philips Eyecare Lamp Ambient Light.""" - def __init__(self, name, light, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the light device.""" name = f"{name} Ambient Light" if unique_id is not None: unique_id = f"{unique_id}-ambient" - super().__init__(name, light, model, unique_id) + super().__init__(name, device, entry, unique_id) async def async_turn_on(self, **kwargs): """Turn the light on.""" @@ -769,7 +734,7 @@ class XiaomiPhilipsEyecareLampAmbientLight(XiaomiPhilipsAbstractLight): result = await self._try_command( "Setting brightness of the ambient failed: %s", - self._light.set_ambient_brightness, + self._device.set_ambient_brightness, percent_brightness, ) @@ -777,19 +742,19 @@ class XiaomiPhilipsEyecareLampAmbientLight(XiaomiPhilipsAbstractLight): self._brightness = brightness else: await self._try_command( - "Turning the ambient light on failed.", self._light.ambient_on + "Turning the ambient light on failed.", self._device.ambient_on ) async def async_turn_off(self, **kwargs): """Turn the light off.""" await self._try_command( - "Turning the ambient light off failed.", self._light.ambient_off + "Turning the ambient light off failed.", self._device.ambient_off ) async def async_update(self): """Fetch state from the device.""" try: - state = await self.hass.async_add_executor_job(self._light.status) + state = await self.hass.async_add_executor_job(self._device.status) except DeviceException as ex: if self._available: self._available = False @@ -806,9 +771,9 @@ class XiaomiPhilipsEyecareLampAmbientLight(XiaomiPhilipsAbstractLight): class XiaomiPhilipsMoonlightLamp(XiaomiPhilipsBulb): """Representation of a Xiaomi Philips Zhirui Bedside Lamp.""" - def __init__(self, name, light, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the light device.""" - super().__init__(name, light, model, unique_id) + super().__init__(name, device, entry, unique_id) self._hs_color = None self._state_attrs.pop(ATTR_DELAYED_TURN_OFF) @@ -817,8 +782,8 @@ class XiaomiPhilipsMoonlightLamp(XiaomiPhilipsBulb): ATTR_SLEEP_ASSISTANT: None, ATTR_SLEEP_OFF_TIME: None, ATTR_TOTAL_ASSISTANT_SLEEP_TIME: None, - ATTR_BRAND_SLEEP: None, - ATTR_BRAND: None, + ATTR_BAND_SLEEP: None, + ATTR_BAND: None, } ) @@ -868,7 +833,7 @@ class XiaomiPhilipsMoonlightLamp(XiaomiPhilipsBulb): result = await self._try_command( "Setting brightness and color failed: %s bri, %s color", - self._light.set_brightness_and_rgb, + self._device.set_brightness_and_rgb, percent_brightness, rgb, ) @@ -889,7 +854,7 @@ class XiaomiPhilipsMoonlightLamp(XiaomiPhilipsBulb): result = await self._try_command( "Setting brightness and color temperature failed: %s bri, %s cct", - self._light.set_brightness_and_color_temperature, + self._device.set_brightness_and_color_temperature, percent_brightness, percent_color_temp, ) @@ -902,7 +867,7 @@ class XiaomiPhilipsMoonlightLamp(XiaomiPhilipsBulb): _LOGGER.debug("Setting color: %s", rgb) result = await self._try_command( - "Setting color failed: %s", self._light.set_rgb, rgb + "Setting color failed: %s", self._device.set_rgb, rgb ) if result: @@ -917,7 +882,7 @@ class XiaomiPhilipsMoonlightLamp(XiaomiPhilipsBulb): result = await self._try_command( "Setting color temperature failed: %s cct", - self._light.set_color_temperature, + self._device.set_color_temperature, percent_color_temp, ) @@ -932,7 +897,7 @@ class XiaomiPhilipsMoonlightLamp(XiaomiPhilipsBulb): result = await self._try_command( "Setting brightness failed: %s", - self._light.set_brightness, + self._device.set_brightness, percent_brightness, ) @@ -940,12 +905,12 @@ class XiaomiPhilipsMoonlightLamp(XiaomiPhilipsBulb): self._brightness = brightness else: - await self._try_command("Turning the light on failed.", self._light.on) + await self._try_command("Turning the light on failed.", self._device.on) async def async_update(self): """Fetch state from the device.""" try: - state = await self.hass.async_add_executor_job(self._light.status) + state = await self.hass.async_add_executor_job(self._device.status) except DeviceException as ex: if self._available: self._available = False @@ -968,8 +933,8 @@ class XiaomiPhilipsMoonlightLamp(XiaomiPhilipsBulb): ATTR_SLEEP_ASSISTANT: state.sleep_assistant, ATTR_SLEEP_OFF_TIME: state.sleep_off_time, ATTR_TOTAL_ASSISTANT_SLEEP_TIME: state.total_assistant_sleep_time, - ATTR_BRAND_SLEEP: state.brand_sleep, - ATTR_BRAND: state.brand, + ATTR_BAND_SLEEP: state.brand_sleep, + ATTR_BAND: state.brand, } ) From f92b75cbb210d68d1fb1d17514fe94a0d8737652 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 11 Mar 2021 07:22:35 -0700 Subject: [PATCH 298/831] Write SimpliSafe alarm control panel state after arming/disarming (#47649) * Write SimpliSafe alarm control panel state after arming/disarming * Include locks --- homeassistant/components/simplisafe/alarm_control_panel.py | 3 +++ homeassistant/components/simplisafe/lock.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/homeassistant/components/simplisafe/alarm_control_panel.py b/homeassistant/components/simplisafe/alarm_control_panel.py index 7634f1cce86..8f394890ad4 100644 --- a/homeassistant/components/simplisafe/alarm_control_panel.py +++ b/homeassistant/components/simplisafe/alarm_control_panel.py @@ -163,6 +163,7 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): return self._state = STATE_ALARM_DISARMED + self.async_write_ha_state() async def async_alarm_arm_home(self, code=None): """Send arm home command.""" @@ -178,6 +179,7 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): return self._state = STATE_ALARM_ARMED_HOME + self.async_write_ha_state() async def async_alarm_arm_away(self, code=None): """Send arm away command.""" @@ -193,6 +195,7 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): return self._state = STATE_ALARM_ARMING + self.async_write_ha_state() @callback def async_update_from_rest_api(self): diff --git a/homeassistant/components/simplisafe/lock.py b/homeassistant/components/simplisafe/lock.py index 08ffb82d24f..a4d823efe38 100644 --- a/homeassistant/components/simplisafe/lock.py +++ b/homeassistant/components/simplisafe/lock.py @@ -55,6 +55,9 @@ class SimpliSafeLock(SimpliSafeEntity, LockEntity): LOGGER.error('Error while locking "%s": %s', self._lock.name, err) return + self._is_locked = True + self.async_write_ha_state() + async def async_unlock(self, **kwargs): """Unlock the lock.""" try: @@ -63,6 +66,9 @@ class SimpliSafeLock(SimpliSafeEntity, LockEntity): LOGGER.error('Error while unlocking "%s": %s', self._lock.name, err) return + self._is_locked = False + self.async_write_ha_state() + @callback def async_update_from_rest_api(self): """Update the entity with the provided REST API data.""" From 6c084ae6ce567ec9f21125e529675aeeda61626e Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 11 Mar 2021 16:51:03 +0100 Subject: [PATCH 299/831] Update integrations a-e to override extra_state_attributes() (#47756) --- homeassistant/components/abode/__init__.py | 4 ++-- .../components/abode/alarm_control_panel.py | 2 +- homeassistant/components/accuweather/sensor.py | 2 +- .../components/aemet/abstract_aemet_sensor.py | 2 +- homeassistant/components/aftership/sensor.py | 2 +- homeassistant/components/agent_dvr/camera.py | 2 +- homeassistant/components/airly/air_quality.py | 2 +- homeassistant/components/airly/sensor.py | 2 +- homeassistant/components/airnow/sensor.py | 2 +- homeassistant/components/airvisual/__init__.py | 2 +- .../components/alarmdecoder/alarm_control_panel.py | 2 +- .../components/alarmdecoder/binary_sensor.py | 2 +- homeassistant/components/alpha_vantage/sensor.py | 4 ++-- homeassistant/components/amcrest/camera.py | 2 +- homeassistant/components/amcrest/sensor.py | 2 +- .../components/android_ip_webcam/__init__.py | 2 +- homeassistant/components/androidtv/media_player.py | 2 +- .../components/arlo/alarm_control_panel.py | 2 +- homeassistant/components/arlo/camera.py | 2 +- homeassistant/components/arlo/sensor.py | 2 +- homeassistant/components/asuswrt/device_tracker.py | 2 +- homeassistant/components/asuswrt/sensor.py | 2 +- homeassistant/components/atome/sensor.py | 2 +- homeassistant/components/august/lock.py | 2 +- homeassistant/components/august/sensor.py | 2 +- homeassistant/components/aurora/__init__.py | 2 +- homeassistant/components/automation/__init__.py | 2 +- homeassistant/components/awair/sensor.py | 2 +- homeassistant/components/azure_devops/sensor.py | 2 +- homeassistant/components/bayesian/binary_sensor.py | 2 +- homeassistant/components/bbox/sensor.py | 4 ++-- homeassistant/components/bitcoin/sensor.py | 2 +- .../components/blink/alarm_control_panel.py | 2 +- homeassistant/components/blink/camera.py | 2 +- homeassistant/components/blockchain/sensor.py | 2 +- homeassistant/components/bluesound/media_player.py | 2 +- .../components/bmw_connected_drive/__init__.py | 2 +- .../bmw_connected_drive/binary_sensor.py | 2 +- .../components/bmw_connected_drive/lock.py | 2 +- homeassistant/components/brother/sensor.py | 2 +- .../components/brottsplatskartan/sensor.py | 2 +- homeassistant/components/brunt/cover.py | 2 +- homeassistant/components/buienradar/sensor.py | 2 +- homeassistant/components/caldav/calendar.py | 2 +- .../components/canary/alarm_control_panel.py | 2 +- homeassistant/components/canary/sensor.py | 2 +- homeassistant/components/cert_expiry/sensor.py | 2 +- homeassistant/components/citybikes/sensor.py | 2 +- homeassistant/components/co2signal/sensor.py | 2 +- homeassistant/components/coinbase/sensor.py | 4 ++-- .../components/comed_hourly_pricing/sensor.py | 2 +- homeassistant/components/command_line/sensor.py | 2 +- homeassistant/components/coronavirus/sensor.py | 2 +- homeassistant/components/cpuspeed/sensor.py | 2 +- homeassistant/components/cups/sensor.py | 6 +++--- homeassistant/components/currencylayer/sensor.py | 2 +- homeassistant/components/darksky/sensor.py | 4 ++-- homeassistant/components/deconz/binary_sensor.py | 2 +- homeassistant/components/deconz/climate.py | 2 +- homeassistant/components/deconz/light.py | 6 +++--- homeassistant/components/deconz/sensor.py | 4 ++-- homeassistant/components/delijn/sensor.py | 2 +- homeassistant/components/demo/remote.py | 2 +- homeassistant/components/demo/sensor.py | 2 +- homeassistant/components/demo/vacuum.py | 4 ++-- homeassistant/components/denonavr/media_player.py | 2 +- homeassistant/components/derivative/sensor.py | 2 +- homeassistant/components/deutsche_bahn/sensor.py | 2 +- homeassistant/components/device_tracker/legacy.py | 2 +- .../components/digital_ocean/binary_sensor.py | 2 +- homeassistant/components/digital_ocean/switch.py | 2 +- homeassistant/components/directv/media_player.py | 2 +- homeassistant/components/discogs/sensor.py | 2 +- homeassistant/components/dlink/switch.py | 2 +- homeassistant/components/doods/image_processing.py | 2 +- homeassistant/components/dovado/sensor.py | 2 +- .../components/dublin_bus_transport/sensor.py | 2 +- .../components/dwd_weather_warnings/sensor.py | 2 +- homeassistant/components/dyson/air_quality.py | 2 +- homeassistant/components/dyson/fan.py | 6 +++--- homeassistant/components/dyson/vacuum.py | 2 +- homeassistant/components/eafm/sensor.py | 2 +- homeassistant/components/ebusd/sensor.py | 2 +- homeassistant/components/ecobee/climate.py | 2 +- homeassistant/components/ecovacs/vacuum.py | 2 +- homeassistant/components/edl21/sensor.py | 2 +- homeassistant/components/eight_sleep/sensor.py | 4 ++-- homeassistant/components/elkm1/__init__.py | 2 +- .../components/elkm1/alarm_control_panel.py | 2 +- homeassistant/components/elkm1/sensor.py | 8 ++++---- homeassistant/components/elv/switch.py | 2 +- homeassistant/components/emoncms/sensor.py | 2 +- homeassistant/components/enigma2/media_player.py | 2 +- homeassistant/components/enphase_envoy/sensor.py | 2 +- .../components/entur_public_transport/sensor.py | 2 +- .../components/environment_canada/camera.py | 2 +- .../components/environment_canada/sensor.py | 2 +- .../components/envisalink/binary_sensor.py | 2 +- homeassistant/components/envisalink/sensor.py | 2 +- homeassistant/components/epson/media_player.py | 2 +- homeassistant/components/eq3btsmart/climate.py | 2 +- homeassistant/components/etherscan/sensor.py | 2 +- homeassistant/components/evohome/__init__.py | 2 +- homeassistant/components/ezviz/camera.py | 2 +- tests/components/arlo/test_sensor.py | 4 ++-- tests/components/ecobee/test_climate.py | 14 +++++++------- 106 files changed, 130 insertions(+), 130 deletions(-) diff --git a/homeassistant/components/abode/__init__.py b/homeassistant/components/abode/__init__.py index 47e03eea44c..c1c89951c3f 100644 --- a/homeassistant/components/abode/__init__.py +++ b/homeassistant/components/abode/__init__.py @@ -363,7 +363,7 @@ class AbodeDevice(AbodeEntity): return self._device.name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, @@ -411,7 +411,7 @@ class AbodeAutomation(AbodeEntity): return self._automation.name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION, "type": "CUE automation"} diff --git a/homeassistant/components/abode/alarm_control_panel.py b/homeassistant/components/abode/alarm_control_panel.py index c508d0f0240..6d0c030e3e1 100644 --- a/homeassistant/components/abode/alarm_control_panel.py +++ b/homeassistant/components/abode/alarm_control_panel.py @@ -69,7 +69,7 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity): self._device.set_away() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/accuweather/sensor.py b/homeassistant/components/accuweather/sensor.py index 90058e254dc..aa7f604e27f 100644 --- a/homeassistant/components/accuweather/sensor.py +++ b/homeassistant/components/accuweather/sensor.py @@ -141,7 +141,7 @@ class AccuWeatherSensor(CoordinatorEntity): return SENSOR_TYPES[self.kind][self._unit_system] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.forecast_day is not None: if self.kind in ["WindDay", "WindNight", "WindGustDay", "WindGustNight"]: diff --git a/homeassistant/components/aemet/abstract_aemet_sensor.py b/homeassistant/components/aemet/abstract_aemet_sensor.py index 6b7c3c69fee..7699a5f99a4 100644 --- a/homeassistant/components/aemet/abstract_aemet_sensor.py +++ b/homeassistant/components/aemet/abstract_aemet_sensor.py @@ -52,6 +52,6 @@ class AbstractAemetSensor(CoordinatorEntity): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/aftership/sensor.py b/homeassistant/components/aftership/sensor.py index 2d9021f8009..754b3ce72eb 100644 --- a/homeassistant/components/aftership/sensor.py +++ b/homeassistant/components/aftership/sensor.py @@ -134,7 +134,7 @@ class AfterShipSensor(Entity): return "packages" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return attributes for the sensor.""" return self._attributes diff --git a/homeassistant/components/agent_dvr/camera.py b/homeassistant/components/agent_dvr/camera.py index 571b5239de7..7904f98216b 100644 --- a/homeassistant/components/agent_dvr/camera.py +++ b/homeassistant/components/agent_dvr/camera.py @@ -108,7 +108,7 @@ class AgentCamera(MjpegCamera): self._removed = True @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the Agent DVR camera state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/airly/air_quality.py b/homeassistant/components/airly/air_quality.py index 4c4c239a84b..f89a804ab3b 100644 --- a/homeassistant/components/airly/air_quality.py +++ b/homeassistant/components/airly/air_quality.py @@ -117,7 +117,7 @@ class AirlyAirQuality(CoordinatorEntity, AirQualityEntity): } @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = { LABEL_AQI_DESCRIPTION: self.coordinator.data[ATTR_API_CAQI_DESCRIPTION], diff --git a/homeassistant/components/airly/sensor.py b/homeassistant/components/airly/sensor.py index 789dbbb4657..dae167559e0 100644 --- a/homeassistant/components/airly/sensor.py +++ b/homeassistant/components/airly/sensor.py @@ -102,7 +102,7 @@ class AirlySensor(CoordinatorEntity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/airnow/sensor.py b/homeassistant/components/airnow/sensor.py index 4488098701f..ea2ff9856fb 100644 --- a/homeassistant/components/airnow/sensor.py +++ b/homeassistant/components/airnow/sensor.py @@ -84,7 +84,7 @@ class AirNowSensor(CoordinatorEntity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.kind == ATTR_API_AQI: self._attrs[SENSOR_AQI_ATTR_DESCR] = self.coordinator.data[ diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index e900bfa65de..6254d533a0f 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -372,7 +372,7 @@ class AirVisualEntity(CoordinatorEntity): self._unit = None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return self._attrs diff --git a/homeassistant/components/alarmdecoder/alarm_control_panel.py b/homeassistant/components/alarmdecoder/alarm_control_panel.py index 5e3118b1d3d..9cab2afa43c 100644 --- a/homeassistant/components/alarmdecoder/alarm_control_panel.py +++ b/homeassistant/components/alarmdecoder/alarm_control_panel.py @@ -163,7 +163,7 @@ class AlarmDecoderAlarmPanel(AlarmControlPanelEntity): return self._code_arm_required @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { "ac_power": self._ac_power, diff --git a/homeassistant/components/alarmdecoder/binary_sensor.py b/homeassistant/components/alarmdecoder/binary_sensor.py index 55bf13d7fef..4cc3bb6b5cf 100644 --- a/homeassistant/components/alarmdecoder/binary_sensor.py +++ b/homeassistant/components/alarmdecoder/binary_sensor.py @@ -118,7 +118,7 @@ class AlarmDecoderBinarySensor(BinarySensorEntity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attr = {CONF_ZONE_NUMBER: self._zone_number} if self._rfid and self._rfstate is not None: diff --git a/homeassistant/components/alpha_vantage/sensor.py b/homeassistant/components/alpha_vantage/sensor.py index 0d0aec47915..cd161b1870c 100644 --- a/homeassistant/components/alpha_vantage/sensor.py +++ b/homeassistant/components/alpha_vantage/sensor.py @@ -133,7 +133,7 @@ class AlphaVantageSensor(Entity): return self.values["1. open"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.values is not None: return { @@ -193,7 +193,7 @@ class AlphaVantageForeignExchange(Entity): return self._icon @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.values is not None: return { diff --git a/homeassistant/components/amcrest/camera.py b/homeassistant/components/amcrest/camera.py index a8aabc233d1..046da7b270d 100644 --- a/homeassistant/components/amcrest/camera.py +++ b/homeassistant/components/amcrest/camera.py @@ -266,7 +266,7 @@ class AmcrestCam(Camera): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the Amcrest-specific camera state attributes.""" attr = {} if self._audio_enabled is not None: diff --git a/homeassistant/components/amcrest/sensor.py b/homeassistant/components/amcrest/sensor.py index 44ebcfcdb95..15a610b23b4 100644 --- a/homeassistant/components/amcrest/sensor.py +++ b/homeassistant/components/amcrest/sensor.py @@ -66,7 +66,7 @@ class AmcrestSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/android_ip_webcam/__init__.py b/homeassistant/components/android_ip_webcam/__init__.py index 905d0262862..54a281feacd 100644 --- a/homeassistant/components/android_ip_webcam/__init__.py +++ b/homeassistant/components/android_ip_webcam/__init__.py @@ -321,7 +321,7 @@ class AndroidIPCamEntity(Entity): return self._ipcam.available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" state_attr = {ATTR_HOST: self._host} if self._ipcam.status_data is None: diff --git a/homeassistant/components/androidtv/media_player.py b/homeassistant/components/androidtv/media_player.py index 4d17b4ecdad..b2a8ceffc9f 100644 --- a/homeassistant/components/androidtv/media_player.py +++ b/homeassistant/components/androidtv/media_player.py @@ -469,7 +469,7 @@ class ADBDevice(MediaPlayerEntity): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Provide the last ADB command's response and the device's HDMI input as attributes.""" return { "adb_response": self._adb_response, diff --git a/homeassistant/components/arlo/alarm_control_panel.py b/homeassistant/components/arlo/alarm_control_panel.py index 47328d5cbc2..dd899cbd04f 100644 --- a/homeassistant/components/arlo/alarm_control_panel.py +++ b/homeassistant/components/arlo/alarm_control_panel.py @@ -136,7 +136,7 @@ class ArloBaseStation(AlarmControlPanelEntity): return self._base_station.name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/arlo/camera.py b/homeassistant/components/arlo/camera.py index 36e32181702..c1848661429 100644 --- a/homeassistant/components/arlo/camera.py +++ b/homeassistant/components/arlo/camera.py @@ -108,7 +108,7 @@ class ArloCam(Camera): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { name: value diff --git a/homeassistant/components/arlo/sensor.py b/homeassistant/components/arlo/sensor.py index d5d583de22c..1e0c55c5d31 100644 --- a/homeassistant/components/arlo/sensor.py +++ b/homeassistant/components/arlo/sensor.py @@ -183,7 +183,7 @@ class ArloSensor(Entity): self._state = None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attrs = {} diff --git a/homeassistant/components/asuswrt/device_tracker.py b/homeassistant/components/asuswrt/device_tracker.py index 385b25755b0..f5cb9b934e3 100644 --- a/homeassistant/components/asuswrt/device_tracker.py +++ b/homeassistant/components/asuswrt/device_tracker.py @@ -103,7 +103,7 @@ class AsusWrtDevice(ScannerEntity): return self._icon @property - def device_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> Dict[str, any]: """Return the attributes.""" return self._attrs diff --git a/homeassistant/components/asuswrt/sensor.py b/homeassistant/components/asuswrt/sensor.py index 2751e161ea9..0cd427d3b64 100644 --- a/homeassistant/components/asuswrt/sensor.py +++ b/homeassistant/components/asuswrt/sensor.py @@ -159,7 +159,7 @@ class AsusWrtSensor(CoordinatorEntity): return self._device_class @property - def device_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> Dict[str, any]: """Return the attributes.""" return {"hostname": self._router.host} diff --git a/homeassistant/components/atome/sensor.py b/homeassistant/components/atome/sensor.py index 1a8585653fe..e50f7cd5de6 100644 --- a/homeassistant/components/atome/sensor.py +++ b/homeassistant/components/atome/sensor.py @@ -243,7 +243,7 @@ class AtomeSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/august/lock.py b/homeassistant/components/august/lock.py index e16c603d919..4b4ae906190 100644 --- a/homeassistant/components/august/lock.py +++ b/homeassistant/components/august/lock.py @@ -105,7 +105,7 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity): return self._changed_by @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" attributes = {ATTR_BATTERY_LEVEL: self._detail.battery_level} diff --git a/homeassistant/components/august/sensor.py b/homeassistant/components/august/sensor.py index 6004a07f605..9dba5bb2766 100644 --- a/homeassistant/components/august/sensor.py +++ b/homeassistant/components/august/sensor.py @@ -166,7 +166,7 @@ class AugustOperatorSensor(AugustEntityMixin, RestoreEntity, Entity): self._entity_picture = lock_activity.operator_thumbnail_url @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" attributes = {} diff --git a/homeassistant/components/aurora/__init__.py b/homeassistant/components/aurora/__init__.py index 044b118b030..321b25de7b9 100644 --- a/homeassistant/components/aurora/__init__.py +++ b/homeassistant/components/aurora/__init__.py @@ -168,7 +168,7 @@ class AuroraEntity(CoordinatorEntity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"attribution": ATTRIBUTION} diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 50b9fc43bf5..24121359ac0 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -535,7 +535,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return automation attributes.""" if self._id is None: return None diff --git a/homeassistant/components/awair/sensor.py b/homeassistant/components/awair/sensor.py index 421fa3d8a26..d685b3ec17b 100644 --- a/homeassistant/components/awair/sensor.py +++ b/homeassistant/components/awair/sensor.py @@ -179,7 +179,7 @@ class AwairSensor(CoordinatorEntity): return SENSOR_TYPES[self._kind][ATTR_UNIT] @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the Awair Index alongside state attributes. The Awair Index is a subjective score ranging from 0-4 (inclusive) that diff --git a/homeassistant/components/azure_devops/sensor.py b/homeassistant/components/azure_devops/sensor.py index 6f259afb9a9..a26f0c65f9c 100644 --- a/homeassistant/components/azure_devops/sensor.py +++ b/homeassistant/components/azure_devops/sensor.py @@ -92,7 +92,7 @@ class AzureDevOpsSensor(AzureDevOpsDeviceEntity): return self._state @property - def device_state_attributes(self) -> object: + def extra_state_attributes(self) -> object: """Return the attributes of the sensor.""" return self._attributes diff --git a/homeassistant/components/bayesian/binary_sensor.py b/homeassistant/components/bayesian/binary_sensor.py index 69553e921eb..6879e278bab 100644 --- a/homeassistant/components/bayesian/binary_sensor.py +++ b/homeassistant/components/bayesian/binary_sensor.py @@ -384,7 +384,7 @@ class BayesianBinarySensor(BinarySensorEntity): return self._device_class @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" attr_observations_list = [ diff --git a/homeassistant/components/bbox/sensor.py b/homeassistant/components/bbox/sensor.py index 13c8f5bb03f..8d6708c6342 100644 --- a/homeassistant/components/bbox/sensor.py +++ b/homeassistant/components/bbox/sensor.py @@ -115,7 +115,7 @@ class BboxUptimeSensor(Entity): return self._icon @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} @@ -167,7 +167,7 @@ class BboxSensor(Entity): return self._icon @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/bitcoin/sensor.py b/homeassistant/components/bitcoin/sensor.py index c748b2f72f9..f6f5a24d22f 100644 --- a/homeassistant/components/bitcoin/sensor.py +++ b/homeassistant/components/bitcoin/sensor.py @@ -110,7 +110,7 @@ class BitcoinSensor(Entity): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/blink/alarm_control_panel.py b/homeassistant/components/blink/alarm_control_panel.py index dbcb6d30143..ed2b46acaa1 100644 --- a/homeassistant/components/blink/alarm_control_panel.py +++ b/homeassistant/components/blink/alarm_control_panel.py @@ -62,7 +62,7 @@ class BlinkSyncModule(AlarmControlPanelEntity): return f"{DOMAIN} {self._name}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attr = self.sync.attributes attr["network_info"] = self.data.networks diff --git a/homeassistant/components/blink/camera.py b/homeassistant/components/blink/camera.py index d4282bed606..a25b978ee7b 100644 --- a/homeassistant/components/blink/camera.py +++ b/homeassistant/components/blink/camera.py @@ -60,7 +60,7 @@ class BlinkCamera(Camera): return self._unique_id @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the camera attributes.""" return self._camera.attributes diff --git a/homeassistant/components/blockchain/sensor.py b/homeassistant/components/blockchain/sensor.py index feb9d582cff..790051dad96 100644 --- a/homeassistant/components/blockchain/sensor.py +++ b/homeassistant/components/blockchain/sensor.py @@ -75,7 +75,7 @@ class BlockchainSensor(Entity): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/bluesound/media_player.py b/homeassistant/components/bluesound/media_player.py index 0ace6f5679a..dff45ca68bd 100644 --- a/homeassistant/components/bluesound/media_player.py +++ b/homeassistant/components/bluesound/media_player.py @@ -850,7 +850,7 @@ class BluesoundPlayer(MediaPlayerEntity): _LOGGER.error("Master not found %s", master_device) @property - def device_state_attributes(self): + def extra_state_attributes(self): """List members in group.""" attributes = {} if self._group_list: diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index 3a653ec252f..ebf1fd6f74e 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -334,7 +334,7 @@ class BMWConnectedDriveBaseEntity(Entity): } @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return self._attrs diff --git a/homeassistant/components/bmw_connected_drive/binary_sensor.py b/homeassistant/components/bmw_connected_drive/binary_sensor.py index cad5426d548..bebb55bbde0 100644 --- a/homeassistant/components/bmw_connected_drive/binary_sensor.py +++ b/homeassistant/components/bmw_connected_drive/binary_sensor.py @@ -109,7 +109,7 @@ class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, BinarySensorEntity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the binary sensor.""" vehicle_state = self._vehicle.state result = self._attrs.copy() diff --git a/homeassistant/components/bmw_connected_drive/lock.py b/homeassistant/components/bmw_connected_drive/lock.py index 0d281e78f14..97c9be7216b 100644 --- a/homeassistant/components/bmw_connected_drive/lock.py +++ b/homeassistant/components/bmw_connected_drive/lock.py @@ -52,7 +52,7 @@ class BMWLock(BMWConnectedDriveBaseEntity, LockEntity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the lock.""" vehicle_state = self._vehicle.state result = self._attrs.copy() diff --git a/homeassistant/components/brother/sensor.py b/homeassistant/components/brother/sensor.py index a379d9b4154..dcbf92fba7d 100644 --- a/homeassistant/components/brother/sensor.py +++ b/homeassistant/components/brother/sensor.py @@ -88,7 +88,7 @@ class BrotherPrinterSensor(CoordinatorEntity): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" remaining_pages = None drum_counter = None diff --git a/homeassistant/components/brottsplatskartan/sensor.py b/homeassistant/components/brottsplatskartan/sensor.py index 7b2c3e585e3..b75edcc7f4a 100644 --- a/homeassistant/components/brottsplatskartan/sensor.py +++ b/homeassistant/components/brottsplatskartan/sensor.py @@ -99,7 +99,7 @@ class BrottsplatskartanSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/brunt/cover.py b/homeassistant/components/brunt/cover.py index ceb56ba03fa..9c539fe51fe 100644 --- a/homeassistant/components/brunt/cover.py +++ b/homeassistant/components/brunt/cover.py @@ -131,7 +131,7 @@ class BruntDevice(CoverEntity): return self.move_state == 2 @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the detailed device state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index 4e35542b581..ae57ed3c43c 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -430,7 +430,7 @@ class BrSensor(Entity): return self._entity_picture @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.type.startswith(PRECIPITATION_FORECAST): result = {ATTR_ATTRIBUTION: self._attribution} diff --git a/homeassistant/components/caldav/calendar.py b/homeassistant/components/caldav/calendar.py index 66b3c974306..62be361df3b 100644 --- a/homeassistant/components/caldav/calendar.py +++ b/homeassistant/components/caldav/calendar.py @@ -123,7 +123,7 @@ class WebDavCalendarEventDevice(CalendarEventDevice): self._offset_reached = False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return {"offset_reached": self._offset_reached} diff --git a/homeassistant/components/canary/alarm_control_panel.py b/homeassistant/components/canary/alarm_control_panel.py index e4dda0f9f33..d0beece8df2 100644 --- a/homeassistant/components/canary/alarm_control_panel.py +++ b/homeassistant/components/canary/alarm_control_panel.py @@ -87,7 +87,7 @@ class CanaryAlarm(CoordinatorEntity, AlarmControlPanelEntity): return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"private": self.location.is_private} diff --git a/homeassistant/components/canary/sensor.py b/homeassistant/components/canary/sensor.py index 99dcdf48fce..2d5a7885fbf 100644 --- a/homeassistant/components/canary/sensor.py +++ b/homeassistant/components/canary/sensor.py @@ -163,7 +163,7 @@ class CanarySensor(CoordinatorEntity, Entity): return self._sensor_type[2] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" reading = self.reading diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py index 0e329b1898f..582205f9c0d 100644 --- a/homeassistant/components/cert_expiry/sensor.py +++ b/homeassistant/components/cert_expiry/sensor.py @@ -68,7 +68,7 @@ class CertExpiryEntity(CoordinatorEntity): return "mdi:certificate" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return additional sensor state attributes.""" return { "is_valid": self.coordinator.is_cert_valid, diff --git a/homeassistant/components/citybikes/sensor.py b/homeassistant/components/citybikes/sensor.py index 924ef2fa6b4..2dd60078bd5 100644 --- a/homeassistant/components/citybikes/sensor.py +++ b/homeassistant/components/citybikes/sensor.py @@ -286,7 +286,7 @@ class CityBikesStation(Entity): break @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._station_data: return { diff --git a/homeassistant/components/co2signal/sensor.py b/homeassistant/components/co2signal/sensor.py index a61615d0e23..2153a8a7af5 100644 --- a/homeassistant/components/co2signal/sensor.py +++ b/homeassistant/components/co2signal/sensor.py @@ -91,7 +91,7 @@ class CO2Sensor(Entity): return CO2_INTENSITY_UNIT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the last update.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/coinbase/sensor.py b/homeassistant/components/coinbase/sensor.py index f191bb778f4..1d239ba457e 100644 --- a/homeassistant/components/coinbase/sensor.py +++ b/homeassistant/components/coinbase/sensor.py @@ -71,7 +71,7 @@ class AccountSensor(Entity): return CURRENCY_ICONS.get(self._unit_of_measurement, DEFAULT_COIN_ICON) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, @@ -120,7 +120,7 @@ class ExchangeRateSensor(Entity): return CURRENCY_ICONS.get(self.currency, DEFAULT_COIN_ICON) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/comed_hourly_pricing/sensor.py b/homeassistant/components/comed_hourly_pricing/sensor.py index 90830d52236..c1a784f619c 100644 --- a/homeassistant/components/comed_hourly_pricing/sensor.py +++ b/homeassistant/components/comed_hourly_pricing/sensor.py @@ -97,7 +97,7 @@ class ComedHourlyPricingSensor(Entity): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/command_line/sensor.py b/homeassistant/components/command_line/sensor.py index 3e6f384cca3..fc99befb821 100644 --- a/homeassistant/components/command_line/sensor.py +++ b/homeassistant/components/command_line/sensor.py @@ -95,7 +95,7 @@ class CommandSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/coronavirus/sensor.py b/homeassistant/components/coronavirus/sensor.py index 7f0e0c230e6..acfc5569f34 100644 --- a/homeassistant/components/coronavirus/sensor.py +++ b/homeassistant/components/coronavirus/sensor.py @@ -73,6 +73,6 @@ class CoronavirusSensor(CoordinatorEntity): return "people" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/cpuspeed/sensor.py b/homeassistant/components/cpuspeed/sensor.py index df1a224bcce..f21513f616e 100644 --- a/homeassistant/components/cpuspeed/sensor.py +++ b/homeassistant/components/cpuspeed/sensor.py @@ -54,7 +54,7 @@ class CpuSpeedSensor(Entity): return FREQUENCY_GIGAHERTZ @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.info is not None: attrs = { diff --git a/homeassistant/components/cups/sensor.py b/homeassistant/components/cups/sensor.py index 72d2aa62ae0..54e674b1811 100644 --- a/homeassistant/components/cups/sensor.py +++ b/homeassistant/components/cups/sensor.py @@ -131,7 +131,7 @@ class CupsSensor(Entity): return ICON_PRINTER @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" if self._printer is None: return None @@ -193,7 +193,7 @@ class IPPSensor(Entity): return PRINTER_STATES.get(key, key) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" if self._attributes is None: return None @@ -271,7 +271,7 @@ class MarkerSensor(Entity): return PERCENTAGE @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" if self._attributes is None: return None diff --git a/homeassistant/components/currencylayer/sensor.py b/homeassistant/components/currencylayer/sensor.py index f2cb29515b0..66b26eb0692 100644 --- a/homeassistant/components/currencylayer/sensor.py +++ b/homeassistant/components/currencylayer/sensor.py @@ -86,7 +86,7 @@ class CurrencylayerSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/darksky/sensor.py b/homeassistant/components/darksky/sensor.py index 0bafdbb3d81..7e6463dc08e 100644 --- a/homeassistant/components/darksky/sensor.py +++ b/homeassistant/components/darksky/sensor.py @@ -620,7 +620,7 @@ class DarkSkySensor(Entity): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} @@ -739,7 +739,7 @@ class DarkSkyAlertSensor(Entity): return "mdi:alert-circle-outline" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._alerts diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index 616206949ed..99f559eec3d 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -92,7 +92,7 @@ class DeconzBinarySensor(DeconzDevice, BinarySensorEntity): return DEVICE_CLASS.get(type(self._device)) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" attr = {} diff --git a/homeassistant/components/deconz/climate.py b/homeassistant/components/deconz/climate.py index 44111fbbb1e..dad3f3adf67 100644 --- a/homeassistant/components/deconz/climate.py +++ b/homeassistant/components/deconz/climate.py @@ -244,7 +244,7 @@ class DeconzThermostat(DeconzDevice, ClimateEntity): return TEMP_CELSIUS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the thermostat.""" attr = {} diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 2da435c5530..f7ae45781ac 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -223,7 +223,7 @@ class DeconzBaseLight(DeconzDevice, LightEntity): await self._device.async_set_state(data) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return {"is_deconz_group": self._device.type == "LightGroup"} @@ -275,9 +275,9 @@ class DeconzGroup(DeconzBaseLight): } @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - attributes = dict(super().device_state_attributes) + attributes = dict(super().extra_state_attributes) attributes["all_on"] = self._device.all_on return attributes diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index e3a7e6a001a..b08ec0d091b 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -155,7 +155,7 @@ class DeconzSensor(DeconzDevice): return UNIT_OF_MEASUREMENT.get(type(self._device)) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" attr = {} @@ -235,7 +235,7 @@ class DeconzBattery(DeconzDevice): return PERCENTAGE @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the battery.""" attr = {} diff --git a/homeassistant/components/delijn/sensor.py b/homeassistant/components/delijn/sensor.py index 0058816d318..44b70c4e7a8 100644 --- a/homeassistant/components/delijn/sensor.py +++ b/homeassistant/components/delijn/sensor.py @@ -126,6 +126,6 @@ class DeLijnPublicTransportSensor(Entity): return "mdi:bus" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return attributes for the sensor.""" return self._attributes diff --git a/homeassistant/components/demo/remote.py b/homeassistant/components/demo/remote.py index 9d12621fef1..98e949f38c3 100644 --- a/homeassistant/components/demo/remote.py +++ b/homeassistant/components/demo/remote.py @@ -49,7 +49,7 @@ class DemoRemote(RemoteEntity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device state attributes.""" if self._last_command_sent is not None: return {"last_command_sent": self._last_command_sent} diff --git a/homeassistant/components/demo/sensor.py b/homeassistant/components/demo/sensor.py index 99aadf356d7..49930a35377 100644 --- a/homeassistant/components/demo/sensor.py +++ b/homeassistant/components/demo/sensor.py @@ -96,7 +96,7 @@ class DemoSensor(Entity): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._battery: return {ATTR_BATTERY_LEVEL: self._battery} diff --git a/homeassistant/components/demo/vacuum.py b/homeassistant/components/demo/vacuum.py index ebb6b57ce14..39413a1b9f7 100644 --- a/homeassistant/components/demo/vacuum.py +++ b/homeassistant/components/demo/vacuum.py @@ -140,7 +140,7 @@ class DemoVacuum(VacuumEntity): return max(0, min(100, self._battery_level)) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device state attributes.""" return {ATTR_CLEANED_AREA: round(self._cleaned_area, 2)} @@ -288,7 +288,7 @@ class StateDemoVacuum(StateVacuumEntity): return FAN_SPEEDS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device state attributes.""" return {ATTR_CLEANED_AREA: round(self._cleaned_area, 2)} diff --git a/homeassistant/components/denonavr/media_player.py b/homeassistant/components/denonavr/media_player.py index 73fe0f2152d..c1fbb15a263 100644 --- a/homeassistant/components/denonavr/media_player.py +++ b/homeassistant/components/denonavr/media_player.py @@ -306,7 +306,7 @@ class DenonDevice(MediaPlayerEntity): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" if ( self._sound_mode_raw is not None diff --git a/homeassistant/components/derivative/sensor.py b/homeassistant/components/derivative/sensor.py index 7c2cc444a5a..ddbe1d19e31 100644 --- a/homeassistant/components/derivative/sensor.py +++ b/homeassistant/components/derivative/sensor.py @@ -211,7 +211,7 @@ class DerivativeSensor(RestoreEntity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_SOURCE_ID: self._sensor_source_id} diff --git a/homeassistant/components/deutsche_bahn/sensor.py b/homeassistant/components/deutsche_bahn/sensor.py index b3e4cd432ac..1de6609c756 100644 --- a/homeassistant/components/deutsche_bahn/sensor.py +++ b/homeassistant/components/deutsche_bahn/sensor.py @@ -65,7 +65,7 @@ class DeutscheBahnSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" connections = self.data.connections[0] if len(self.data.connections) > 1: diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index b7583d80f82..e947ae7b828 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -675,7 +675,7 @@ class Device(RestoreEntity): return attributes @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device state attributes.""" return self._attributes diff --git a/homeassistant/components/digital_ocean/binary_sensor.py b/homeassistant/components/digital_ocean/binary_sensor.py index eb1345df45c..9a9f82c36d2 100644 --- a/homeassistant/components/digital_ocean/binary_sensor.py +++ b/homeassistant/components/digital_ocean/binary_sensor.py @@ -79,7 +79,7 @@ class DigitalOceanBinarySensor(BinarySensorEntity): return DEVICE_CLASS_MOVING @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the Digital Ocean droplet.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/digital_ocean/switch.py b/homeassistant/components/digital_ocean/switch.py index 811b844e35c..0678b9ab1a1 100644 --- a/homeassistant/components/digital_ocean/switch.py +++ b/homeassistant/components/digital_ocean/switch.py @@ -71,7 +71,7 @@ class DigitalOceanSwitch(SwitchEntity): return self.data.status == "active" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the Digital Ocean droplet.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/directv/media_player.py b/homeassistant/components/directv/media_player.py index dfd88ca885b..ee290ba5f2c 100644 --- a/homeassistant/components/directv/media_player.py +++ b/homeassistant/components/directv/media_player.py @@ -124,7 +124,7 @@ class DIRECTVMediaPlayer(DIRECTVEntity, MediaPlayerEntity): self._assumed_state = self._is_recorded @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" if self._is_standby: return {} diff --git a/homeassistant/components/discogs/sensor.py b/homeassistant/components/discogs/sensor.py index 4d78b540a0c..6f45a4ab959 100644 --- a/homeassistant/components/discogs/sensor.py +++ b/homeassistant/components/discogs/sensor.py @@ -121,7 +121,7 @@ class DiscogsSensor(Entity): return SENSORS[self._type]["unit_of_measurement"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes of the sensor.""" if self._state is None or self._attrs is None: return None diff --git a/homeassistant/components/dlink/switch.py b/homeassistant/components/dlink/switch.py index c173c879ad1..432bc7ec0b3 100644 --- a/homeassistant/components/dlink/switch.py +++ b/homeassistant/components/dlink/switch.py @@ -71,7 +71,7 @@ class SmartPlugSwitch(SwitchEntity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" try: ui_temp = self.units.temperature(int(self.data.temperature), TEMP_CELSIUS) diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index fb2b6daecda..3e41c1871bf 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -221,7 +221,7 @@ class Doods(ImageProcessingEntity): return self._total_matches @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return { ATTR_MATCHES: self._matches, diff --git a/homeassistant/components/dovado/sensor.py b/homeassistant/components/dovado/sensor.py index 46ff402788e..8c8dffbb8b6 100644 --- a/homeassistant/components/dovado/sensor.py +++ b/homeassistant/components/dovado/sensor.py @@ -105,6 +105,6 @@ class DovadoSensor(Entity): return SENSORS[self._sensor][2] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {k: v for k, v in self._data.state.items() if k not in ["date", "time"]} diff --git a/homeassistant/components/dublin_bus_transport/sensor.py b/homeassistant/components/dublin_bus_transport/sensor.py index 96e485a94e6..5b0cbaf9f18 100644 --- a/homeassistant/components/dublin_bus_transport/sensor.py +++ b/homeassistant/components/dublin_bus_transport/sensor.py @@ -87,7 +87,7 @@ class DublinPublicTransportSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._times is not None: next_up = "None" diff --git a/homeassistant/components/dwd_weather_warnings/sensor.py b/homeassistant/components/dwd_weather_warnings/sensor.py index 79beebb005d..af91e279600 100644 --- a/homeassistant/components/dwd_weather_warnings/sensor.py +++ b/homeassistant/components/dwd_weather_warnings/sensor.py @@ -119,7 +119,7 @@ class DwdWeatherWarningsSensor(Entity): return self._api.api.expected_warning_level @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the DWD-Weather-Warnings.""" data = { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/dyson/air_quality.py b/homeassistant/components/dyson/air_quality.py index 3bf4f2bb34c..48b66fe7683 100644 --- a/homeassistant/components/dyson/air_quality.py +++ b/homeassistant/components/dyson/air_quality.py @@ -88,6 +88,6 @@ class DysonAirSensor(DysonEntity, AirQualityEntity): return int(self._device.environmental_state.volatile_organic_compounds) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return {ATTR_VOC: self.volatile_organic_compounds} diff --git a/homeassistant/components/dyson/fan.py b/homeassistant/components/dyson/fan.py index a8e737bb48b..5b69092ac5d 100644 --- a/homeassistant/components/dyson/fan.py +++ b/homeassistant/components/dyson/fan.py @@ -200,7 +200,7 @@ class DysonFanEntity(DysonEntity, FanEntity): return SUPPORT_OSCILLATE | SUPPORT_SET_SPEED @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return optional state attributes.""" return { ATTR_NIGHT_MODE: self.night_mode, @@ -455,10 +455,10 @@ class DysonPureCoolEntity(DysonFanEntity): return int(self._device.state.carbon_filter_state) @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return optional state attributes.""" return { - **super().device_state_attributes, + **super().extra_state_attributes, ATTR_ANGLE_LOW: self.angle_low, ATTR_ANGLE_HIGH: self.angle_high, ATTR_FLOW_DIRECTION_FRONT: self.flow_direction_front, diff --git a/homeassistant/components/dyson/vacuum.py b/homeassistant/components/dyson/vacuum.py index 466b409c342..f4035d33cf3 100644 --- a/homeassistant/components/dyson/vacuum.py +++ b/homeassistant/components/dyson/vacuum.py @@ -95,7 +95,7 @@ class Dyson360EyeDevice(DysonEntity, VacuumEntity): return ["Quiet", "Max"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the specific state attributes of this vacuum cleaner.""" return {ATTR_POSITION: str(self._device.state.position)} diff --git a/homeassistant/components/eafm/sensor.py b/homeassistant/components/eafm/sensor.py index 746f6f34abc..9e2d4e5cef9 100644 --- a/homeassistant/components/eafm/sensor.py +++ b/homeassistant/components/eafm/sensor.py @@ -156,7 +156,7 @@ class Measurement(CoordinatorEntity): return UNIT_MAPPING.get(measure["unit"], measure["unitName"]) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the sensor specific state attributes.""" return {ATTR_ATTRIBUTION: self.attribution} diff --git a/homeassistant/components/ebusd/sensor.py b/homeassistant/components/ebusd/sensor.py index badb94a6f85..53dac4eb4c0 100644 --- a/homeassistant/components/ebusd/sensor.py +++ b/homeassistant/components/ebusd/sensor.py @@ -55,7 +55,7 @@ class EbusdSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if self._type == 1 and self._state is not None: schedule = { diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index 089f0950854..c3224410993 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -519,7 +519,7 @@ class Thermostat(ClimateEntity): return CURRENT_HVAC_IDLE @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" status = self.thermostat["equipmentStatus"] return { diff --git a/homeassistant/components/ecovacs/vacuum.py b/homeassistant/components/ecovacs/vacuum.py index 6ad51e6c474..934833c0f95 100644 --- a/homeassistant/components/ecovacs/vacuum.py +++ b/homeassistant/components/ecovacs/vacuum.py @@ -189,7 +189,7 @@ class EcovacsVacuum(VacuumEntity): self.device.run(sucks.VacBotCommand(command, params)) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device-specific state attributes of this vacuum.""" data = {} data[ATTR_ERROR] = self._error diff --git a/homeassistant/components/edl21/sensor.py b/homeassistant/components/edl21/sensor.py index dc0f51abe61..1f21eaa4004 100644 --- a/homeassistant/components/edl21/sensor.py +++ b/homeassistant/components/edl21/sensor.py @@ -269,7 +269,7 @@ class EDL21Entity(Entity): return self._telegram.get("value") @property - def device_state_attributes(self): + def extra_state_attributes(self): """Enumerate supported attributes.""" return { self._state_attrs[k]: v diff --git a/homeassistant/components/eight_sleep/sensor.py b/homeassistant/components/eight_sleep/sensor.py index 43bcb4c2f09..f858309d02c 100644 --- a/homeassistant/components/eight_sleep/sensor.py +++ b/homeassistant/components/eight_sleep/sensor.py @@ -110,7 +110,7 @@ class EightHeatSensor(EightSleepHeatEntity): self._state = self._usrobj.heating_level @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device state attributes.""" return { ATTR_TARGET_HEAT: self._usrobj.target_heating_level, @@ -202,7 +202,7 @@ class EightUserSensor(EightSleepUserEntity): self._state = self._usrobj.current_values["stage"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device state attributes.""" if self._attr is None: # Skip attributes if sensor type doesn't support diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index 3dddd670bb4..568b3109227 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -429,7 +429,7 @@ class ElkEntity(Entity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the default attributes of the element.""" return {**self._element.as_dict(), **self.initial_attrs()} diff --git a/homeassistant/components/elkm1/alarm_control_panel.py b/homeassistant/components/elkm1/alarm_control_panel.py index 8f752cd9adf..756166c86a6 100644 --- a/homeassistant/components/elkm1/alarm_control_panel.py +++ b/homeassistant/components/elkm1/alarm_control_panel.py @@ -173,7 +173,7 @@ class ElkArea(ElkAttachedEntity, AlarmControlPanelEntity, RestoreEntity): return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Attributes of the area.""" attrs = self.initial_attrs() elmt = self._element diff --git a/homeassistant/components/elkm1/sensor.py b/homeassistant/components/elkm1/sensor.py index c6442af2e44..7d63f283f0b 100644 --- a/homeassistant/components/elkm1/sensor.py +++ b/homeassistant/components/elkm1/sensor.py @@ -136,7 +136,7 @@ class ElkKeypad(ElkSensor): return "mdi:thermometer-lines" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Attributes of the sensor.""" attrs = self.initial_attrs() attrs["area"] = self._element.area + 1 @@ -163,7 +163,7 @@ class ElkPanel(ElkSensor): return "mdi:home" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Attributes of the sensor.""" attrs = self.initial_attrs() attrs["system_trouble_status"] = self._element.system_trouble_status @@ -190,7 +190,7 @@ class ElkSetting(ElkSensor): self._state = self._element.value @property - def device_state_attributes(self): + def extra_state_attributes(self): """Attributes of the sensor.""" attrs = self.initial_attrs() attrs["value_format"] = SettingFormat(self._element.value_format).name.lower() @@ -227,7 +227,7 @@ class ElkZone(ElkSensor): return f"mdi:{zone_icons.get(self._element.definition, 'alarm-bell')}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Attributes of the sensor.""" attrs = self.initial_attrs() attrs["physical_status"] = ZonePhysicalStatus( diff --git a/homeassistant/components/elv/switch.py b/homeassistant/components/elv/switch.py index 12b21c23d1a..48eb9675277 100644 --- a/homeassistant/components/elv/switch.py +++ b/homeassistant/components/elv/switch.py @@ -74,7 +74,7 @@ class SmartPlugSwitch(SwitchEntity): self._pca.turn_off(self._device_id) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._emeter_params diff --git a/homeassistant/components/emoncms/sensor.py b/homeassistant/components/emoncms/sensor.py index dca9c870022..523350532a0 100644 --- a/homeassistant/components/emoncms/sensor.py +++ b/homeassistant/components/emoncms/sensor.py @@ -175,7 +175,7 @@ class EmonCmsSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attributes of the sensor.""" return { ATTR_FEEDID: self._elem["id"], diff --git a/homeassistant/components/enigma2/media_player.py b/homeassistant/components/enigma2/media_player.py index 4baa6aaf047..1c32b1ab805 100644 --- a/homeassistant/components/enigma2/media_player.py +++ b/homeassistant/components/enigma2/media_player.py @@ -251,7 +251,7 @@ class Enigma2Device(MediaPlayerEntity): self.e2_box.update() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes. isRecording: Is the box currently recording. diff --git a/homeassistant/components/enphase_envoy/sensor.py b/homeassistant/components/enphase_envoy/sensor.py index 64b4fdf66ad..e40c8e94372 100644 --- a/homeassistant/components/enphase_envoy/sensor.py +++ b/homeassistant/components/enphase_envoy/sensor.py @@ -202,7 +202,7 @@ class Envoy(CoordinatorEntity): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if ( self._type == "inverters" diff --git a/homeassistant/components/entur_public_transport/sensor.py b/homeassistant/components/entur_public_transport/sensor.py index 883b5c43d7e..23c895c58a1 100644 --- a/homeassistant/components/entur_public_transport/sensor.py +++ b/homeassistant/components/entur_public_transport/sensor.py @@ -172,7 +172,7 @@ class EnturPublicTransportSensor(Entity): return self._state @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" self._attributes[ATTR_ATTRIBUTION] = ATTRIBUTION self._attributes[ATTR_STOP_ID] = self._stop diff --git a/homeassistant/components/environment_canada/camera.py b/homeassistant/components/environment_canada/camera.py index 7021d637cfd..019dcb1aee5 100644 --- a/homeassistant/components/environment_canada/camera.py +++ b/homeassistant/components/environment_canada/camera.py @@ -81,7 +81,7 @@ class ECCamera(Camera): return "Environment Canada Radar" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return {ATTR_ATTRIBUTION: CONF_ATTRIBUTION, ATTR_UPDATED: self.timestamp} diff --git a/homeassistant/components/environment_canada/sensor.py b/homeassistant/components/environment_canada/sensor.py index 20eb6fac9ee..f178b3c6275 100644 --- a/homeassistant/components/environment_canada/sensor.py +++ b/homeassistant/components/environment_canada/sensor.py @@ -95,7 +95,7 @@ class ECSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._attr diff --git a/homeassistant/components/envisalink/binary_sensor.py b/homeassistant/components/envisalink/binary_sensor.py index 54445660484..22089ee7907 100644 --- a/homeassistant/components/envisalink/binary_sensor.py +++ b/homeassistant/components/envisalink/binary_sensor.py @@ -56,7 +56,7 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorEntity): async_dispatcher_connect(self.hass, SIGNAL_ZONE_UPDATE, self._update_callback) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attr = {} diff --git a/homeassistant/components/envisalink/sensor.py b/homeassistant/components/envisalink/sensor.py index 3f3711b2e40..9551b51b3e9 100644 --- a/homeassistant/components/envisalink/sensor.py +++ b/homeassistant/components/envisalink/sensor.py @@ -66,7 +66,7 @@ class EnvisalinkSensor(EnvisalinkDevice, Entity): return self._info["status"]["alpha"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._info["status"] diff --git a/homeassistant/components/epson/media_player.py b/homeassistant/components/epson/media_player.py index 24ed62067f9..6115cdd6ef4 100644 --- a/homeassistant/components/epson/media_player.py +++ b/homeassistant/components/epson/media_player.py @@ -215,7 +215,7 @@ class EpsonProjectorMediaPlayer(MediaPlayerEntity): await self._projector.send_command(BACK) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" if self._cmode is None: return {} diff --git a/homeassistant/components/eq3btsmart/climate.py b/homeassistant/components/eq3btsmart/climate.py index 43c6bd9f35b..f803c9c0bd5 100644 --- a/homeassistant/components/eq3btsmart/climate.py +++ b/homeassistant/components/eq3btsmart/climate.py @@ -155,7 +155,7 @@ class EQ3BTSmartThermostat(ClimateEntity): return self._thermostat.max_temp @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" dev_specific = { ATTR_STATE_AWAY_END: self._thermostat.away_end, diff --git a/homeassistant/components/etherscan/sensor.py b/homeassistant/components/etherscan/sensor.py index 1c14ce578c1..e56b49181d4 100644 --- a/homeassistant/components/etherscan/sensor.py +++ b/homeassistant/components/etherscan/sensor.py @@ -70,7 +70,7 @@ class EtherscanSensor(Entity): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/evohome/__init__.py b/homeassistant/components/evohome/__init__.py index 268e7709af3..006fbb1610d 100644 --- a/homeassistant/components/evohome/__init__.py +++ b/homeassistant/components/evohome/__init__.py @@ -556,7 +556,7 @@ class EvoDevice(Entity): return self._name @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the evohome-specific state attributes.""" status = self._device_state_attrs if "systemModeStatus" in status: diff --git a/homeassistant/components/ezviz/camera.py b/homeassistant/components/ezviz/camera.py index b9b2463314b..4cce0e68654 100644 --- a/homeassistant/components/ezviz/camera.py +++ b/homeassistant/components/ezviz/camera.py @@ -158,7 +158,7 @@ class HassEzvizCamera(Camera): return True @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the Ezviz-specific camera state attributes.""" return { # if privacy == true, the device closed the lid or did a 180° tilt diff --git a/tests/components/arlo/test_sensor.py b/tests/components/arlo/test_sensor.py index 5d729a5a658..b8389d1903f 100644 --- a/tests/components/arlo/test_sensor.py +++ b/tests/components/arlo/test_sensor.py @@ -194,7 +194,7 @@ def test_update_captured_today(captured_sensor): def _test_attributes(sensor_type): data = _get_named_tuple({"model_id": "TEST123"}) sensor = _get_sensor("test", sensor_type, data) - attrs = sensor.device_state_attributes + attrs = sensor.extra_state_attributes assert attrs.get(ATTR_ATTRIBUTION) == "Data provided by arlo.netgear.com" assert attrs.get("brand") == "Netgear Arlo" assert attrs.get("model") == "TEST123" @@ -211,7 +211,7 @@ def test_state_attributes(): def test_attributes_total_cameras(cameras_sensor): """Test attributes for total cameras sensor type.""" - attrs = cameras_sensor.device_state_attributes + attrs = cameras_sensor.extra_state_attributes assert attrs.get(ATTR_ATTRIBUTION) == "Data provided by arlo.netgear.com" assert attrs.get("brand") == "Netgear Arlo" assert attrs.get("model") is None diff --git a/tests/components/ecobee/test_climate.py b/tests/components/ecobee/test_climate.py index 975fabcf9ab..92ad310f68d 100644 --- a/tests/components/ecobee/test_climate.py +++ b/tests/components/ecobee/test_climate.py @@ -147,7 +147,7 @@ async def test_hvac_mode2(ecobee_fixture, thermostat): assert thermostat.hvac_mode == "heat" -async def test_device_state_attributes(ecobee_fixture, thermostat): +async def test_extra_state_attributes(ecobee_fixture, thermostat): """Test device state attributes property.""" ecobee_fixture["equipmentStatus"] = "heatPump2" assert { @@ -155,7 +155,7 @@ async def test_device_state_attributes(ecobee_fixture, thermostat): "climate_mode": "Climate1", "fan_min_on_time": 10, "equipment_running": "heatPump2", - } == thermostat.device_state_attributes + } == thermostat.extra_state_attributes ecobee_fixture["equipmentStatus"] = "auxHeat2" assert { @@ -163,21 +163,21 @@ async def test_device_state_attributes(ecobee_fixture, thermostat): "climate_mode": "Climate1", "fan_min_on_time": 10, "equipment_running": "auxHeat2", - } == thermostat.device_state_attributes + } == thermostat.extra_state_attributes ecobee_fixture["equipmentStatus"] = "compCool1" assert { "fan": "off", "climate_mode": "Climate1", "fan_min_on_time": 10, "equipment_running": "compCool1", - } == thermostat.device_state_attributes + } == thermostat.extra_state_attributes ecobee_fixture["equipmentStatus"] = "" assert { "fan": "off", "climate_mode": "Climate1", "fan_min_on_time": 10, "equipment_running": "", - } == thermostat.device_state_attributes + } == thermostat.extra_state_attributes ecobee_fixture["equipmentStatus"] = "Unknown" assert { @@ -185,7 +185,7 @@ async def test_device_state_attributes(ecobee_fixture, thermostat): "climate_mode": "Climate1", "fan_min_on_time": 10, "equipment_running": "Unknown", - } == thermostat.device_state_attributes + } == thermostat.extra_state_attributes ecobee_fixture["program"]["currentClimateRef"] = "c2" assert { @@ -193,7 +193,7 @@ async def test_device_state_attributes(ecobee_fixture, thermostat): "climate_mode": "Climate2", "fan_min_on_time": 10, "equipment_running": "Unknown", - } == thermostat.device_state_attributes + } == thermostat.extra_state_attributes async def test_is_aux_heat_on(ecobee_fixture, thermostat): From af4d06b12e7237de90b62b5aaa31025acee05a2e Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 11 Mar 2021 16:57:47 +0100 Subject: [PATCH 300/831] Update integrations f-i to override extra_state_attributes() (#47757) --- .../components/faa_delays/binary_sensor.py | 2 +- .../components/facebox/image_processing.py | 2 +- homeassistant/components/fibaro/__init__.py | 2 +- homeassistant/components/fido/sensor.py | 2 +- homeassistant/components/filesize/sensor.py | 2 +- homeassistant/components/filter/sensor.py | 2 +- homeassistant/components/fints/sensor.py | 4 ++-- .../fireservicerota/binary_sensor.py | 2 +- .../components/fireservicerota/sensor.py | 2 +- .../components/fireservicerota/switch.py | 2 +- homeassistant/components/fitbit/sensor.py | 2 +- homeassistant/components/fixer/sensor.py | 2 +- homeassistant/components/flexit/climate.py | 2 +- .../components/flic/binary_sensor.py | 2 +- .../components/flick_electric/sensor.py | 2 +- homeassistant/components/flo/binary_sensor.py | 2 +- homeassistant/components/flunearyou/sensor.py | 2 +- homeassistant/components/folder/sensor.py | 2 +- .../components/freebox/device_tracker.py | 2 +- homeassistant/components/freebox/sensor.py | 2 +- homeassistant/components/fritzbox/climate.py | 2 +- homeassistant/components/fritzbox/sensor.py | 2 +- homeassistant/components/fritzbox/switch.py | 2 +- .../components/fritzbox_callmonitor/sensor.py | 2 +- homeassistant/components/garadget/cover.py | 2 +- .../components/garmin_connect/sensor.py | 2 +- .../components/gdacs/geo_location.py | 2 +- homeassistant/components/gdacs/sensor.py | 2 +- homeassistant/components/geizhals/sensor.py | 2 +- .../components/geniushub/__init__.py | 4 ++-- homeassistant/components/geniushub/sensor.py | 2 +- .../geo_json_events/geo_location.py | 2 +- .../components/geo_rss_events/sensor.py | 2 +- .../components/geofency/device_tracker.py | 2 +- .../geonetnz_quakes/geo_location.py | 2 +- .../components/geonetnz_quakes/sensor.py | 2 +- .../components/geonetnz_volcano/sensor.py | 2 +- homeassistant/components/gios/air_quality.py | 2 +- homeassistant/components/github/sensor.py | 2 +- homeassistant/components/gitlab_ci/sensor.py | 2 +- homeassistant/components/gitter/sensor.py | 2 +- homeassistant/components/gogogate2/cover.py | 2 +- homeassistant/components/gogogate2/sensor.py | 2 +- homeassistant/components/google/calendar.py | 2 +- .../components/google_travel_time/sensor.py | 2 +- homeassistant/components/gpsd/sensor.py | 2 +- .../components/gpslogger/device_tracker.py | 2 +- .../components/greeneye_monitor/sensor.py | 4 ++-- homeassistant/components/group/cover.py | 2 +- homeassistant/components/group/light.py | 2 +- homeassistant/components/gtfs/sensor.py | 2 +- homeassistant/components/guardian/__init__.py | 2 +- homeassistant/components/habitica/sensor.py | 2 +- homeassistant/components/harmony/remote.py | 2 +- .../components/haveibeenpwned/sensor.py | 2 +- homeassistant/components/hddtemp/sensor.py | 2 +- homeassistant/components/hdmi_cec/__init__.py | 2 +- homeassistant/components/heos/media_player.py | 2 +- .../components/here_travel_time/sensor.py | 2 +- .../components/hikvision/binary_sensor.py | 2 +- .../components/history_stats/sensor.py | 2 +- .../components/hive/binary_sensor.py | 2 +- homeassistant/components/hive/climate.py | 2 +- homeassistant/components/hive/light.py | 2 +- homeassistant/components/hive/sensor.py | 2 +- homeassistant/components/hive/switch.py | 2 +- .../components/homeassistant/scene.py | 2 +- .../homekit_controller/air_quality.py | 2 +- .../homekit_controller/alarm_control_panel.py | 2 +- .../components/homekit_controller/cover.py | 4 ++-- .../components/homekit_controller/lock.py | 2 +- .../components/homekit_controller/switch.py | 4 ++-- .../homematicip_cloud/binary_sensor.py | 20 +++++++++---------- .../components/homematicip_cloud/climate.py | 4 ++-- .../homematicip_cloud/generic_entity.py | 2 +- .../components/homematicip_cloud/light.py | 8 ++++---- .../components/homematicip_cloud/sensor.py | 16 +++++++-------- .../components/homematicip_cloud/switch.py | 4 ++-- homeassistant/components/homeworks/light.py | 2 +- homeassistant/components/honeywell/climate.py | 2 +- homeassistant/components/hp_ilo/sensor.py | 2 +- .../components/huawei_lte/binary_sensor.py | 2 +- .../components/huawei_lte/device_tracker.py | 10 +++++----- homeassistant/components/hue/binary_sensor.py | 4 ++-- homeassistant/components/hue/light.py | 2 +- homeassistant/components/hue/sensor.py | 4 ++-- homeassistant/components/hue/sensor_base.py | 2 +- .../hunterdouglas_powerview/cover.py | 2 +- .../hunterdouglas_powerview/scene.py | 2 +- .../hvv_departures/binary_sensor.py | 2 +- .../components/hvv_departures/sensor.py | 2 +- .../components/hydrawise/__init__.py | 2 +- .../components/icloud/device_tracker.py | 2 +- homeassistant/components/icloud/sensor.py | 2 +- .../components/ign_sismologia/geo_location.py | 2 +- homeassistant/components/ihc/ihcdevice.py | 2 +- .../components/imap_email_content/sensor.py | 2 +- .../components/incomfort/binary_sensor.py | 2 +- homeassistant/components/incomfort/climate.py | 2 +- homeassistant/components/incomfort/sensor.py | 2 +- .../components/incomfort/water_heater.py | 2 +- homeassistant/components/insteon/climate.py | 4 ++-- .../components/insteon/insteon_entity.py | 2 +- .../components/integration/sensor.py | 2 +- .../components/intesishome/climate.py | 2 +- homeassistant/components/ios/sensor.py | 2 +- homeassistant/components/iota/__init__.py | 2 +- homeassistant/components/iota/sensor.py | 2 +- homeassistant/components/iperf3/sensor.py | 2 +- homeassistant/components/ipp/sensor.py | 4 ++-- homeassistant/components/iqvia/__init__.py | 2 +- .../components/irish_rail_transport/sensor.py | 2 +- homeassistant/components/iss/binary_sensor.py | 2 +- .../components/isy994/binary_sensor.py | 4 ++-- homeassistant/components/isy994/light.py | 4 ++-- homeassistant/components/isy994/sensor.py | 2 +- homeassistant/components/izone/climate.py | 4 ++-- tests/components/honeywell/test_climate.py | 6 +++--- .../imap_email_content/test_sensor.py | 16 +++++++-------- 119 files changed, 165 insertions(+), 165 deletions(-) diff --git a/homeassistant/components/faa_delays/binary_sensor.py b/homeassistant/components/faa_delays/binary_sensor.py index 6c5876b7017..b96ee24a5bc 100644 --- a/homeassistant/components/faa_delays/binary_sensor.py +++ b/homeassistant/components/faa_delays/binary_sensor.py @@ -68,7 +68,7 @@ class FAABinarySensor(CoordinatorEntity, BinarySensorEntity): return f"{self._id}_{self._sensor_type}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return attributes for sensor.""" if self._sensor_type == "GROUND_DELAY": self._attrs["average"] = self.coordinator.data.ground_delay.average diff --git a/homeassistant/components/facebox/image_processing.py b/homeassistant/components/facebox/image_processing.py index 6a460ac305b..5c90ce73560 100644 --- a/homeassistant/components/facebox/image_processing.py +++ b/homeassistant/components/facebox/image_processing.py @@ -263,7 +263,7 @@ class FaceClassifyEntity(ImageProcessingFaceEntity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the classifier attributes.""" return { "matched_faces": self._matched, diff --git a/homeassistant/components/fibaro/__init__.py b/homeassistant/components/fibaro/__init__.py index d2a81468e05..9ce284d51ab 100644 --- a/homeassistant/components/fibaro/__init__.py +++ b/homeassistant/components/fibaro/__init__.py @@ -506,7 +506,7 @@ class FibaroDevice(Entity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" attr = {"fibaro_id": self.fibaro_device.id} diff --git a/homeassistant/components/fido/sensor.py b/homeassistant/components/fido/sensor.py index 22522d1ab74..fe9c60c85a6 100644 --- a/homeassistant/components/fido/sensor.py +++ b/homeassistant/components/fido/sensor.py @@ -125,7 +125,7 @@ class FidoSensor(Entity): return self._icon @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {"number": self._number} diff --git a/homeassistant/components/filesize/sensor.py b/homeassistant/components/filesize/sensor.py index 27122a3cb9c..312805e4942 100644 --- a/homeassistant/components/filesize/sensor.py +++ b/homeassistant/components/filesize/sensor.py @@ -76,7 +76,7 @@ class Filesize(Entity): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other details about the sensor state.""" return { "path": self._path, diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py index d46709924ea..f4be797d12b 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -351,7 +351,7 @@ class SensorFilter(Entity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_ENTITY_ID: self._entity} diff --git a/homeassistant/components/fints/sensor.py b/homeassistant/components/fints/sensor.py index 6cd62333a87..4ccd2b6f848 100644 --- a/homeassistant/components/fints/sensor.py +++ b/homeassistant/components/fints/sensor.py @@ -193,7 +193,7 @@ class FinTsAccount(Entity): return self._currency @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Additional attributes of the sensor.""" attributes = {ATTR_ACCOUNT: self._account.iban, ATTR_ACCOUNT_TYPE: "balance"} if self._client.name: @@ -238,7 +238,7 @@ class FinTsHoldingsAccount(Entity): return ICON @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Additional attributes of the sensor. Lists each holding of the account with the current value. diff --git a/homeassistant/components/fireservicerota/binary_sensor.py b/homeassistant/components/fireservicerota/binary_sensor.py index 3f04adc2f72..29fc97ae503 100644 --- a/homeassistant/components/fireservicerota/binary_sensor.py +++ b/homeassistant/components/fireservicerota/binary_sensor.py @@ -62,7 +62,7 @@ class ResponseBinarySensor(CoordinatorEntity, BinarySensorEntity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return available attributes for binary sensor.""" attr = {} if not self.coordinator.data: diff --git a/homeassistant/components/fireservicerota/sensor.py b/homeassistant/components/fireservicerota/sensor.py index 83272eff926..7dc8f546c98 100644 --- a/homeassistant/components/fireservicerota/sensor.py +++ b/homeassistant/components/fireservicerota/sensor.py @@ -64,7 +64,7 @@ class IncidentsSensor(RestoreEntity): return False @property - def device_state_attributes(self) -> object: + def extra_state_attributes(self) -> object: """Return available attributes for sensor.""" attr = {} data = self._state_attributes diff --git a/homeassistant/components/fireservicerota/switch.py b/homeassistant/components/fireservicerota/switch.py index 7519270ca5c..e2385f02e5c 100644 --- a/homeassistant/components/fireservicerota/switch.py +++ b/homeassistant/components/fireservicerota/switch.py @@ -73,7 +73,7 @@ class ResponseSwitch(SwitchEntity): return self._client.on_duty @property - def device_state_attributes(self) -> object: + def extra_state_attributes(self) -> object: """Return available attributes for switch.""" attr = {} if not self._state_attributes: diff --git a/homeassistant/components/fitbit/sensor.py b/homeassistant/components/fitbit/sensor.py index 387eb78448c..b90d33adaff 100644 --- a/homeassistant/components/fitbit/sensor.py +++ b/homeassistant/components/fitbit/sensor.py @@ -457,7 +457,7 @@ class FitbitSensor(Entity): return f"mdi:{FITBIT_RESOURCES_LIST[self.resource_type][2]}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {} diff --git a/homeassistant/components/fixer/sensor.py b/homeassistant/components/fixer/sensor.py index 99ebbdd6bb6..9641f596e61 100644 --- a/homeassistant/components/fixer/sensor.py +++ b/homeassistant/components/fixer/sensor.py @@ -75,7 +75,7 @@ class ExchangeRateSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.data.rate is not None: return { diff --git a/homeassistant/components/flexit/climate.py b/homeassistant/components/flexit/climate.py index 450d09edeb8..ef399796898 100644 --- a/homeassistant/components/flexit/climate.py +++ b/homeassistant/components/flexit/climate.py @@ -93,7 +93,7 @@ class Flexit(ClimateEntity): self._current_operation = self.unit.get_operation @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return { "filter_hours": self._filter_hours, diff --git a/homeassistant/components/flic/binary_sensor.py b/homeassistant/components/flic/binary_sensor.py index e81f8f2f5b0..6ddaddced0d 100644 --- a/homeassistant/components/flic/binary_sensor.py +++ b/homeassistant/components/flic/binary_sensor.py @@ -186,7 +186,7 @@ class FlicButton(BinarySensorEntity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return {"address": self.address} diff --git a/homeassistant/components/flick_electric/sensor.py b/homeassistant/components/flick_electric/sensor.py index 9d441ce7574..4f152c54e3c 100644 --- a/homeassistant/components/flick_electric/sensor.py +++ b/homeassistant/components/flick_electric/sensor.py @@ -61,7 +61,7 @@ class FlickPricingSensor(Entity): return UNIT_NAME @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/flo/binary_sensor.py b/homeassistant/components/flo/binary_sensor.py index 3ab1152f83e..96909b3bd9c 100644 --- a/homeassistant/components/flo/binary_sensor.py +++ b/homeassistant/components/flo/binary_sensor.py @@ -48,7 +48,7 @@ class FloPendingAlertsBinarySensor(FloEntity, BinarySensorEntity): return self._device.has_alerts @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if not self._device.has_alerts: return {} diff --git a/homeassistant/components/flunearyou/sensor.py b/homeassistant/components/flunearyou/sensor.py index 8bb5f1317d1..225ed15d98b 100644 --- a/homeassistant/components/flunearyou/sensor.py +++ b/homeassistant/components/flunearyou/sensor.py @@ -100,7 +100,7 @@ class FluNearYouSensor(CoordinatorEntity): self._unit = unit @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return self._attrs diff --git a/homeassistant/components/folder/sensor.py b/homeassistant/components/folder/sensor.py index 9a062133718..cfbfd670d05 100644 --- a/homeassistant/components/folder/sensor.py +++ b/homeassistant/components/folder/sensor.py @@ -92,7 +92,7 @@ class Folder(Entity): return self.ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other details about the sensor state.""" return { "path": self._folder_path, diff --git a/homeassistant/components/freebox/device_tracker.py b/homeassistant/components/freebox/device_tracker.py index 10c5b8eb2c5..8b1b214c33a 100644 --- a/homeassistant/components/freebox/device_tracker.py +++ b/homeassistant/components/freebox/device_tracker.py @@ -105,7 +105,7 @@ class FreeboxDevice(ScannerEntity): return self._icon @property - def device_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> Dict[str, any]: """Return the attributes.""" return self._attrs diff --git a/homeassistant/components/freebox/sensor.py b/homeassistant/components/freebox/sensor.py index b8881ad7949..d014d85f6dc 100644 --- a/homeassistant/components/freebox/sensor.py +++ b/homeassistant/components/freebox/sensor.py @@ -180,7 +180,7 @@ class FreeboxCallSensor(FreeboxSensor): self._state = len(self._call_list_for_type) @property - def device_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> Dict[str, any]: """Return device specific state attributes.""" return { dt_util.utc_from_timestamp(call["datetime"]).isoformat(): call["name"] diff --git a/homeassistant/components/fritzbox/climate.py b/homeassistant/components/fritzbox/climate.py index 4abe82776a9..50f56f3d510 100644 --- a/homeassistant/components/fritzbox/climate.py +++ b/homeassistant/components/fritzbox/climate.py @@ -191,7 +191,7 @@ class FritzboxThermostat(ClimateEntity): return MAX_TEMPERATURE @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" attrs = { ATTR_STATE_BATTERY_LOW: self._device.battery_low, diff --git a/homeassistant/components/fritzbox/sensor.py b/homeassistant/components/fritzbox/sensor.py index 85238d80f27..470d110429c 100644 --- a/homeassistant/components/fritzbox/sensor.py +++ b/homeassistant/components/fritzbox/sensor.py @@ -80,7 +80,7 @@ class FritzBoxTempSensor(Entity): self._fritz.login() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" attrs = { ATTR_STATE_DEVICE_LOCKED: self._device.device_lock, diff --git a/homeassistant/components/fritzbox/switch.py b/homeassistant/components/fritzbox/switch.py index b179464182f..50c60f7bb39 100644 --- a/homeassistant/components/fritzbox/switch.py +++ b/homeassistant/components/fritzbox/switch.py @@ -93,7 +93,7 @@ class FritzboxSwitch(SwitchEntity): self._fritz.login() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" attrs = {} attrs[ATTR_STATE_DEVICE_LOCKED] = self._device.device_lock diff --git a/homeassistant/components/fritzbox_callmonitor/sensor.py b/homeassistant/components/fritzbox_callmonitor/sensor.py index 891bf8131d6..82ce075268b 100644 --- a/homeassistant/components/fritzbox_callmonitor/sensor.py +++ b/homeassistant/components/fritzbox_callmonitor/sensor.py @@ -169,7 +169,7 @@ class FritzBoxCallSensor(Entity): return ICON_PHONE @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._prefixes: self._attributes[ATTR_PREFIXES] = self._prefixes diff --git a/homeassistant/components/garadget/cover.py b/homeassistant/components/garadget/cover.py index 9c089e85811..206aaa6614d 100644 --- a/homeassistant/components/garadget/cover.py +++ b/homeassistant/components/garadget/cover.py @@ -135,7 +135,7 @@ class GaradgetCover(CoverEntity): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" data = {} diff --git a/homeassistant/components/garmin_connect/sensor.py b/homeassistant/components/garmin_connect/sensor.py index 5d18f0a0dd0..d2a6419cc75 100644 --- a/homeassistant/components/garmin_connect/sensor.py +++ b/homeassistant/components/garmin_connect/sensor.py @@ -120,7 +120,7 @@ class GarminConnectSensor(Entity): return self._unit @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return attributes for sensor.""" if not self._data.data: return {} diff --git a/homeassistant/components/gdacs/geo_location.py b/homeassistant/components/gdacs/geo_location.py index 890c9f8e050..a2178bb78e0 100644 --- a/homeassistant/components/gdacs/geo_location.py +++ b/homeassistant/components/gdacs/geo_location.py @@ -213,7 +213,7 @@ class GdacsEvent(GeolocationEvent): return LENGTH_KILOMETERS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = {} for key, value in ( diff --git a/homeassistant/components/gdacs/sensor.py b/homeassistant/components/gdacs/sensor.py index fbbb199499b..c3642181427 100644 --- a/homeassistant/components/gdacs/sensor.py +++ b/homeassistant/components/gdacs/sensor.py @@ -129,7 +129,7 @@ class GdacsSensor(Entity): return DEFAULT_UNIT_OF_MEASUREMENT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = {} for key, value in ( diff --git a/homeassistant/components/geizhals/sensor.py b/homeassistant/components/geizhals/sensor.py index 43e41e25e5e..5f11ae7d0ca 100644 --- a/homeassistant/components/geizhals/sensor.py +++ b/homeassistant/components/geizhals/sensor.py @@ -72,7 +72,7 @@ class Geizwatch(Entity): return self._device.prices[0] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" while len(self._device.prices) < 4: self._device.prices.append("None") diff --git a/homeassistant/components/geniushub/__init__.py b/homeassistant/components/geniushub/__init__.py index def964157e4..15a2c8e7686 100644 --- a/homeassistant/components/geniushub/__init__.py +++ b/homeassistant/components/geniushub/__init__.py @@ -250,7 +250,7 @@ class GeniusDevice(GeniusEntity): self._last_comms = self._state_attr = None @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the device state attributes.""" attrs = {} attrs["assigned_zone"] = self._device.data["assignedZones"][0]["name"] @@ -317,7 +317,7 @@ class GeniusZone(GeniusEntity): return self._zone.name @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the device state attributes.""" status = {k: v for k, v in self._zone.data.items() if k in GH_ZONE_ATTRS} return {"status": status} diff --git a/homeassistant/components/geniushub/sensor.py b/homeassistant/components/geniushub/sensor.py index 7e4fe81fc77..9d87fb46cb0 100644 --- a/homeassistant/components/geniushub/sensor.py +++ b/homeassistant/components/geniushub/sensor.py @@ -106,7 +106,7 @@ class GeniusIssue(GeniusEntity): return len(self._issues) @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the device state attributes.""" return {f"{self._level}_list": self._issues} diff --git a/homeassistant/components/geo_json_events/geo_location.py b/homeassistant/components/geo_json_events/geo_location.py index 40386648138..feb0b98b3fa 100644 --- a/homeassistant/components/geo_json_events/geo_location.py +++ b/homeassistant/components/geo_json_events/geo_location.py @@ -201,7 +201,7 @@ class GeoJsonLocationEvent(GeolocationEvent): return LENGTH_KILOMETERS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if not self._external_id: return {} diff --git a/homeassistant/components/geo_rss_events/sensor.py b/homeassistant/components/geo_rss_events/sensor.py index c75234f5f2b..00ff63982fd 100644 --- a/homeassistant/components/geo_rss_events/sensor.py +++ b/homeassistant/components/geo_rss_events/sensor.py @@ -137,7 +137,7 @@ class GeoRssServiceSensor(Entity): return DEFAULT_ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._state_attributes diff --git a/homeassistant/components/geofency/device_tracker.py b/homeassistant/components/geofency/device_tracker.py index f090f516fb1..5a58e73d44a 100644 --- a/homeassistant/components/geofency/device_tracker.py +++ b/homeassistant/components/geofency/device_tracker.py @@ -56,7 +56,7 @@ class GeofencyEntity(TrackerEntity, RestoreEntity): self._unique_id = device @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific attributes.""" return self._attributes diff --git a/homeassistant/components/geonetnz_quakes/geo_location.py b/homeassistant/components/geonetnz_quakes/geo_location.py index 718b4c06b9c..62086f059f4 100644 --- a/homeassistant/components/geonetnz_quakes/geo_location.py +++ b/homeassistant/components/geonetnz_quakes/geo_location.py @@ -184,7 +184,7 @@ class GeonetnzQuakesEvent(GeolocationEvent): return LENGTH_KILOMETERS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = {} for key, value in ( diff --git a/homeassistant/components/geonetnz_quakes/sensor.py b/homeassistant/components/geonetnz_quakes/sensor.py index 1cb2d0dc091..e5476757024 100644 --- a/homeassistant/components/geonetnz_quakes/sensor.py +++ b/homeassistant/components/geonetnz_quakes/sensor.py @@ -130,7 +130,7 @@ class GeonetnzQuakesSensor(Entity): return DEFAULT_UNIT_OF_MEASUREMENT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = {} for key, value in ( diff --git a/homeassistant/components/geonetnz_volcano/sensor.py b/homeassistant/components/geonetnz_volcano/sensor.py index 3d5d0681f02..7044b3c5609 100644 --- a/homeassistant/components/geonetnz_volcano/sensor.py +++ b/homeassistant/components/geonetnz_volcano/sensor.py @@ -149,7 +149,7 @@ class GeonetnzVolcanoSensor(Entity): return "alert level" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = {} for key, value in ( diff --git a/homeassistant/components/gios/air_quality.py b/homeassistant/components/gios/air_quality.py index 2853570ce58..ab83191a1ac 100644 --- a/homeassistant/components/gios/air_quality.py +++ b/homeassistant/components/gios/air_quality.py @@ -131,7 +131,7 @@ class GiosAirQuality(CoordinatorEntity, AirQualityEntity): } @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" # Different measuring stations have different sets of sensors. We don't know # what data we will get. diff --git a/homeassistant/components/github/sensor.py b/homeassistant/components/github/sensor.py index 80d05ae1b9c..f5f40c270a8 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -119,7 +119,7 @@ class GitHubSensor(Entity): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = { ATTR_PATH: self._repository_path, diff --git a/homeassistant/components/gitlab_ci/sensor.py b/homeassistant/components/gitlab_ci/sensor.py index 9edbe9733a8..5eaa62e601d 100644 --- a/homeassistant/components/gitlab_ci/sensor.py +++ b/homeassistant/components/gitlab_ci/sensor.py @@ -99,7 +99,7 @@ class GitLabSensor(Entity): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/gitter/sensor.py b/homeassistant/components/gitter/sensor.py index aff6dc17923..3f7888d8593 100644 --- a/homeassistant/components/gitter/sensor.py +++ b/homeassistant/components/gitter/sensor.py @@ -76,7 +76,7 @@ class GitterSensor(Entity): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_USERNAME: self._username, diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index 37c362893c8..053c35c171a 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -122,6 +122,6 @@ class DeviceCover(GoGoGate2Entity, CoverEntity): await self._api.async_close_door(self._get_door().door_id) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"door_id": self._get_door().door_id} diff --git a/homeassistant/components/gogogate2/sensor.py b/homeassistant/components/gogogate2/sensor.py index c4f6cff9dfa..ed53779a95a 100644 --- a/homeassistant/components/gogogate2/sensor.py +++ b/homeassistant/components/gogogate2/sensor.py @@ -50,7 +50,7 @@ class DoorSensor(GoGoGate2Entity): return door.voltage # This is a percentage, not an absolute voltage @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" door = self._get_door() if door.sensorid is not None: diff --git a/homeassistant/components/google/calendar.py b/homeassistant/components/google/calendar.py index 9040d37935d..2cc66121948 100644 --- a/homeassistant/components/google/calendar.py +++ b/homeassistant/components/google/calendar.py @@ -80,7 +80,7 @@ class GoogleCalendarEventDevice(CalendarEventDevice): self.entity_id = entity_id @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return {"offset_reached": self._offset_reached} diff --git a/homeassistant/components/google_travel_time/sensor.py b/homeassistant/components/google_travel_time/sensor.py index 098c6d2d59c..109b65a4225 100644 --- a/homeassistant/components/google_travel_time/sensor.py +++ b/homeassistant/components/google_travel_time/sensor.py @@ -230,7 +230,7 @@ class GoogleTravelTimeSensor(Entity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._matrix is None: return None diff --git a/homeassistant/components/gpsd/sensor.py b/homeassistant/components/gpsd/sensor.py index ea238269e59..978589e9a74 100644 --- a/homeassistant/components/gpsd/sensor.py +++ b/homeassistant/components/gpsd/sensor.py @@ -94,7 +94,7 @@ class GpsdSensor(Entity): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the GPS.""" return { ATTR_LATITUDE: self.agps_thread.data_stream.lat, diff --git a/homeassistant/components/gpslogger/device_tracker.py b/homeassistant/components/gpslogger/device_tracker.py index 6999f26d752..25701e8c2e7 100644 --- a/homeassistant/components/gpslogger/device_tracker.py +++ b/homeassistant/components/gpslogger/device_tracker.py @@ -79,7 +79,7 @@ class GPSLoggerEntity(TrackerEntity, RestoreEntity): return self._battery @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific attributes.""" return self._attributes diff --git a/homeassistant/components/greeneye_monitor/sensor.py b/homeassistant/components/greeneye_monitor/sensor.py index f026bdfe3a4..84352328312 100644 --- a/homeassistant/components/greeneye_monitor/sensor.py +++ b/homeassistant/components/greeneye_monitor/sensor.py @@ -175,7 +175,7 @@ class CurrentSensor(GEMSensor): return self._sensor.watts @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return total wattseconds in the state dictionary.""" if not self._sensor: return None @@ -242,7 +242,7 @@ class PulseCounter(GEMSensor): return f"{self._counted_quantity}/{self._time_unit}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return total pulses in the data dictionary.""" if not self._sensor: return None diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index b52546c48d7..da842ee9f00 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -208,7 +208,7 @@ class CoverGroup(GroupEntity, CoverEntity): return self._tilt_position @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes for the cover group.""" return {ATTR_ENTITY_ID: self._entities} diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index 5720948bc01..482bdbafdab 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -187,7 +187,7 @@ class LightGroup(GroupEntity, light.LightEntity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes for the light group.""" return {ATTR_ENTITY_ID: self._entity_ids} diff --git a/homeassistant/components/gtfs/sensor.py b/homeassistant/components/gtfs/sensor.py index d21ab67f053..23f9e2a8021 100644 --- a/homeassistant/components/gtfs/sensor.py +++ b/homeassistant/components/gtfs/sensor.py @@ -569,7 +569,7 @@ class GTFSDepartureSensor(Entity): return self._available @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/guardian/__init__.py b/homeassistant/components/guardian/__init__.py index 465e1d730af..98f35d34e14 100644 --- a/homeassistant/components/guardian/__init__.py +++ b/homeassistant/components/guardian/__init__.py @@ -246,7 +246,7 @@ class GuardianEntity(CoordinatorEntity): return self._device_info @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/habitica/sensor.py b/homeassistant/components/habitica/sensor.py index 29e494d89ee..4d61a86de75 100644 --- a/homeassistant/components/habitica/sensor.py +++ b/homeassistant/components/habitica/sensor.py @@ -200,7 +200,7 @@ class HabitipyTaskSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of all user tasks.""" if self._updater.tasks is not None: all_received_tasks = self._updater.tasks diff --git a/homeassistant/components/harmony/remote.py b/homeassistant/components/harmony/remote.py index 55af62e6d39..a09f32ee95e 100644 --- a/homeassistant/components/harmony/remote.py +++ b/homeassistant/components/harmony/remote.py @@ -193,7 +193,7 @@ class HarmonyRemote(ConnectionStateMixin, remote.RemoteEntity, RestoreEntity): return self._data.activity_names @property - def device_state_attributes(self): + def extra_state_attributes(self): """Add platform specific attributes.""" return { ATTR_ACTIVITY_STARTING: self._activity_starting, diff --git a/homeassistant/components/haveibeenpwned/sensor.py b/homeassistant/components/haveibeenpwned/sensor.py index 0f5a9b5ebfd..5aa336905e9 100644 --- a/homeassistant/components/haveibeenpwned/sensor.py +++ b/homeassistant/components/haveibeenpwned/sensor.py @@ -80,7 +80,7 @@ class HaveIBeenPwnedSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attributes of the sensor.""" val = {ATTR_ATTRIBUTION: ATTRIBUTION} if self._email not in self._data.data: diff --git a/homeassistant/components/hddtemp/sensor.py b/homeassistant/components/hddtemp/sensor.py index a1052b0440a..e5f6621defe 100644 --- a/homeassistant/components/hddtemp/sensor.py +++ b/homeassistant/components/hddtemp/sensor.py @@ -88,7 +88,7 @@ class HddTempSensor(Entity): return self._unit @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" if self._details is not None: return {ATTR_DEVICE: self._details[0], ATTR_MODEL: self._details[1]} diff --git a/homeassistant/components/hdmi_cec/__init__.py b/homeassistant/components/hdmi_cec/__init__.py index c9a5d27a3be..2f821c1d3a7 100644 --- a/homeassistant/components/hdmi_cec/__init__.py +++ b/homeassistant/components/hdmi_cec/__init__.py @@ -450,7 +450,7 @@ class CecEntity(Entity): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" state_attr = {} if self.vendor_id is not None: diff --git a/homeassistant/components/heos/media_player.py b/homeassistant/components/heos/media_player.py index 15d3c4573db..6e271bf60cd 100644 --- a/homeassistant/components/heos/media_player.py +++ b/homeassistant/components/heos/media_player.py @@ -262,7 +262,7 @@ class HeosMediaPlayer(MediaPlayerEntity): } @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Get additional attribute about the state.""" return { "media_album_id": self._player.now_playing_media.album_id, diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index e9506fceed5..e9b3f4ff9a4 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -271,7 +271,7 @@ class HERETravelTimeSensor(Entity): return self._name @property - def device_state_attributes( + def extra_state_attributes( self, ) -> Optional[Dict[str, Union[None, float, str, bool]]]: """Return the state attributes.""" diff --git a/homeassistant/components/hikvision/binary_sensor.py b/homeassistant/components/hikvision/binary_sensor.py index 90c4b6ce8b9..0d57278c826 100644 --- a/homeassistant/components/hikvision/binary_sensor.py +++ b/homeassistant/components/hikvision/binary_sensor.py @@ -252,7 +252,7 @@ class HikvisionBinarySensor(BinarySensorEntity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attr = {ATTR_LAST_TRIP_TIME: self._sensor_last_update()} diff --git a/homeassistant/components/history_stats/sensor.py b/homeassistant/components/history_stats/sensor.py index 6778e893f6f..7658146d102 100644 --- a/homeassistant/components/history_stats/sensor.py +++ b/homeassistant/components/history_stats/sensor.py @@ -174,7 +174,7 @@ class HistoryStatsSensor(Entity): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" if self.value is None: return {} diff --git a/homeassistant/components/hive/binary_sensor.py b/homeassistant/components/hive/binary_sensor.py index 41f1dacc8f3..eed08c45b3a 100644 --- a/homeassistant/components/hive/binary_sensor.py +++ b/homeassistant/components/hive/binary_sensor.py @@ -69,7 +69,7 @@ class HiveBinarySensorEntity(HiveEntity, BinarySensorEntity): return True @property - def device_state_attributes(self): + def extra_state_attributes(self): """Show Device Attributes.""" return { ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE), diff --git a/homeassistant/components/hive/climate.py b/homeassistant/components/hive/climate.py index f1901147f17..c0b33dbb3ae 100644 --- a/homeassistant/components/hive/climate.py +++ b/homeassistant/components/hive/climate.py @@ -94,7 +94,7 @@ class HiveClimateEntity(HiveEntity, ClimateEntity): return self.device["deviceData"]["online"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Show Device Attributes.""" return {ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE)} diff --git a/homeassistant/components/hive/light.py b/homeassistant/components/hive/light.py index f458c27d019..12779ef9d2e 100644 --- a/homeassistant/components/hive/light.py +++ b/homeassistant/components/hive/light.py @@ -56,7 +56,7 @@ class HiveDeviceLight(HiveEntity, LightEntity): return self.device["deviceData"]["online"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Show Device Attributes.""" return { ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE), diff --git a/homeassistant/components/hive/sensor.py b/homeassistant/components/hive/sensor.py index e828dff9b4e..fe413a35b2f 100644 --- a/homeassistant/components/hive/sensor.py +++ b/homeassistant/components/hive/sensor.py @@ -68,7 +68,7 @@ class HiveSensorEntity(HiveEntity, Entity): return self.device["status"]["state"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE)} diff --git a/homeassistant/components/hive/switch.py b/homeassistant/components/hive/switch.py index 8ab820589cf..821f48dbf97 100644 --- a/homeassistant/components/hive/switch.py +++ b/homeassistant/components/hive/switch.py @@ -47,7 +47,7 @@ class HiveDevicePlug(HiveEntity, SwitchEntity): return self.device["deviceData"].get("online") @property - def device_state_attributes(self): + def extra_state_attributes(self): """Show Device Attributes.""" return { ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE), diff --git a/homeassistant/components/homeassistant/scene.py b/homeassistant/components/homeassistant/scene.py index 1ff3915f121..cca2c601493 100644 --- a/homeassistant/components/homeassistant/scene.py +++ b/homeassistant/components/homeassistant/scene.py @@ -298,7 +298,7 @@ class HomeAssistantScene(Scene): return self.scene_config.id @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the scene state attributes.""" attributes = {ATTR_ENTITY_ID: list(self.scene_config.states)} unique_id = self.unique_id diff --git a/homeassistant/components/homekit_controller/air_quality.py b/homeassistant/components/homekit_controller/air_quality.py index 896034a2ca0..2a162eb2b2a 100644 --- a/homeassistant/components/homekit_controller/air_quality.py +++ b/homeassistant/components/homekit_controller/air_quality.py @@ -69,7 +69,7 @@ class HomeAirQualitySensor(HomeKitEntity, AirQualityEntity): return self.service.value(CharacteristicsTypes.DENSITY_VOC) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" data = {"air_quality_text": self.air_quality_text} diff --git a/homeassistant/components/homekit_controller/alarm_control_panel.py b/homeassistant/components/homekit_controller/alarm_control_panel.py index 621fb01ff74..c40252c9fdc 100644 --- a/homeassistant/components/homekit_controller/alarm_control_panel.py +++ b/homeassistant/components/homekit_controller/alarm_control_panel.py @@ -105,7 +105,7 @@ class HomeKitAlarmControlPanelEntity(HomeKitEntity, AlarmControlPanelEntity): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" battery_level = self.service.value(CharacteristicsTypes.BATTERY_LEVEL) diff --git a/homeassistant/components/homekit_controller/cover.py b/homeassistant/components/homekit_controller/cover.py index 6c945c81115..dd25e32b3c4 100644 --- a/homeassistant/components/homekit_controller/cover.py +++ b/homeassistant/components/homekit_controller/cover.py @@ -108,7 +108,7 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverEntity): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" obstruction_detected = self.service.value( CharacteristicsTypes.OBSTRUCTION_DETECTED @@ -235,7 +235,7 @@ class HomeKitWindowCover(HomeKitEntity, CoverEntity): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" obstruction_detected = self.service.value( CharacteristicsTypes.OBSTRUCTION_DETECTED diff --git a/homeassistant/components/homekit_controller/lock.py b/homeassistant/components/homekit_controller/lock.py index 8ac7fd608fd..09c02ce0ff9 100644 --- a/homeassistant/components/homekit_controller/lock.py +++ b/homeassistant/components/homekit_controller/lock.py @@ -63,7 +63,7 @@ class HomeKitLock(HomeKitEntity, LockEntity): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" attributes = {} diff --git a/homeassistant/components/homekit_controller/switch.py b/homeassistant/components/homekit_controller/switch.py index b9d0b273cb1..36ed379bc80 100644 --- a/homeassistant/components/homekit_controller/switch.py +++ b/homeassistant/components/homekit_controller/switch.py @@ -39,7 +39,7 @@ class HomeKitSwitch(HomeKitEntity, SwitchEntity): await self.async_put_characteristics({CharacteristicsTypes.ON: False}) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" outlet_in_use = self.service.value(CharacteristicsTypes.OUTLET_IN_USE) if outlet_in_use is not None: @@ -77,7 +77,7 @@ class HomeKitValve(HomeKitEntity, SwitchEntity): return self.service.value(CharacteristicsTypes.ACTIVE) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" attrs = {} diff --git a/homeassistant/components/homematicip_cloud/binary_sensor.py b/homeassistant/components/homematicip_cloud/binary_sensor.py index 4f1ae523ecc..0d21967b242 100644 --- a/homeassistant/components/homematicip_cloud/binary_sensor.py +++ b/homeassistant/components/homematicip_cloud/binary_sensor.py @@ -210,9 +210,9 @@ class HomematicipBaseActionSensor(HomematicipGenericEntity, BinarySensorEntity): return self._device.accelerationSensorTriggered @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the acceleration sensor.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes for attr, attr_key in SAM_DEVICE_ATTRIBUTES.items(): attr_value = getattr(self._device, attr, None) @@ -285,9 +285,9 @@ class HomematicipShutterContact(HomematicipMultiContactInterface, BinarySensorEn return DEVICE_CLASS_DOOR @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the Shutter Contact.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes if self.has_additional_state: window_state = getattr(self._device, "windowState", None) @@ -412,9 +412,9 @@ class HomematicipSunshineSensor(HomematicipGenericEntity, BinarySensorEntity): return self._device.sunshine @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the illuminance sensor.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes today_sunshine_duration = getattr(self._device, "todaySunshineDuration", None) if today_sunshine_duration: @@ -482,9 +482,9 @@ class HomematicipSecurityZoneSensorGroup(HomematicipGenericEntity, BinarySensorE return True @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the security zone group.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes for attr, attr_key in GROUP_ATTRIBUTES.items(): attr_value = getattr(self._device, attr, None) @@ -526,9 +526,9 @@ class HomematicipSecuritySensorGroup( super().__init__(hap, device, post="Sensors") @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the security group.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes smoke_detector_at = getattr(self._device, "smokeDetectorAlarmType", None) if smoke_detector_at: diff --git a/homeassistant/components/homematicip_cloud/climate.py b/homeassistant/components/homematicip_cloud/climate.py index dcd5aeb284e..c09bd2cc53e 100644 --- a/homeassistant/components/homematicip_cloud/climate.py +++ b/homeassistant/components/homematicip_cloud/climate.py @@ -237,9 +237,9 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): await self._device.set_active_profile(profile_idx) @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the access point.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes if self._device.controlMode == HMIP_ECO_CM: if self._indoor_climate.absenceType in [ diff --git a/homeassistant/components/homematicip_cloud/generic_entity.py b/homeassistant/components/homematicip_cloud/generic_entity.py index a1e13658d20..345446e78b2 100644 --- a/homeassistant/components/homematicip_cloud/generic_entity.py +++ b/homeassistant/components/homematicip_cloud/generic_entity.py @@ -232,7 +232,7 @@ class HomematicipGenericEntity(Entity): return None @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the generic entity.""" state_attr = {} diff --git a/homeassistant/components/homematicip_cloud/light.py b/homeassistant/components/homematicip_cloud/light.py index 1909ff818b9..c453cf516dc 100644 --- a/homeassistant/components/homematicip_cloud/light.py +++ b/homeassistant/components/homematicip_cloud/light.py @@ -90,9 +90,9 @@ class HomematicipLightMeasuring(HomematicipLight): """Representation of the HomematicIP measuring light.""" @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the light.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes current_power_w = self._device.currentPowerConsumption if current_power_w > 0.05: @@ -206,9 +206,9 @@ class HomematicipNotificationLight(HomematicipGenericEntity, LightEntity): return self._color_switcher.get(simple_rgb_color, [0.0, 0.0]) @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the notification light sensor.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes if self.is_on: state_attr[ATTR_COLOR_NAME] = self._func_channel.simpleRGBColorState diff --git a/homeassistant/components/homematicip_cloud/sensor.py b/homeassistant/components/homematicip_cloud/sensor.py index 9e202302c10..9f7c7517c3a 100644 --- a/homeassistant/components/homematicip_cloud/sensor.py +++ b/homeassistant/components/homematicip_cloud/sensor.py @@ -222,9 +222,9 @@ class HomematicipTemperatureSensor(HomematicipGenericEntity): return TEMP_CELSIUS @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the windspeed sensor.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes temperature_offset = getattr(self._device, "temperatureOffset", None) if temperature_offset: @@ -259,9 +259,9 @@ class HomematicipIlluminanceSensor(HomematicipGenericEntity): return LIGHT_LUX @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the wind speed sensor.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes for attr, attr_key in ILLUMINATION_DEVICE_ATTRIBUTES.items(): attr_value = getattr(self._device, attr, None) @@ -312,9 +312,9 @@ class HomematicipWindspeedSensor(HomematicipGenericEntity): return SPEED_KILOMETERS_PER_HOUR @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the wind speed sensor.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes wind_direction = getattr(self._device, "windDirection", None) if wind_direction is not None: @@ -354,9 +354,9 @@ class HomematicipPassageDetectorDeltaCounter(HomematicipGenericEntity): return self._device.leftRightCounterDelta @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the delta counter.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes state_attr[ATTR_LEFT_COUNTER] = self._device.leftCounter state_attr[ATTR_RIGHT_COUNTER] = self._device.rightCounter diff --git a/homeassistant/components/homematicip_cloud/switch.py b/homeassistant/components/homematicip_cloud/switch.py index f8c37d336d5..e75615bf336 100644 --- a/homeassistant/components/homematicip_cloud/switch.py +++ b/homeassistant/components/homematicip_cloud/switch.py @@ -141,9 +141,9 @@ class HomematicipGroupSwitch(HomematicipGenericEntity, SwitchEntity): return True @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the switch-group.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes if self._device.unreach: state_attr[ATTR_GROUP_MEMBER_UNREACHABLE] = True diff --git a/homeassistant/components/homeworks/light.py b/homeassistant/components/homeworks/light.py index a5a3b9ed077..f62477148f7 100644 --- a/homeassistant/components/homeworks/light.py +++ b/homeassistant/components/homeworks/light.py @@ -82,7 +82,7 @@ class HomeworksLight(HomeworksDevice, LightEntity): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Supported attributes.""" return {"homeworks_address": self._addr} diff --git a/homeassistant/components/honeywell/climate.py b/homeassistant/components/honeywell/climate.py index 4b87350aec8..a825916628d 100644 --- a/homeassistant/components/honeywell/climate.py +++ b/homeassistant/components/honeywell/climate.py @@ -197,7 +197,7 @@ class HoneywellUSThermostat(ClimateEntity): return self._device.name @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the device specific state attributes.""" data = {} data[ATTR_FAN_ACTION] = "running" if self._device.fan_running else "idle" diff --git a/homeassistant/components/hp_ilo/sensor.py b/homeassistant/components/hp_ilo/sensor.py index da597acb8b7..0126ab780ce 100644 --- a/homeassistant/components/hp_ilo/sensor.py +++ b/homeassistant/components/hp_ilo/sensor.py @@ -144,7 +144,7 @@ class HpIloSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return self._state_attributes diff --git a/homeassistant/components/huawei_lte/binary_sensor.py b/homeassistant/components/huawei_lte/binary_sensor.py index ea9d68bc843..525dd3352e7 100644 --- a/homeassistant/components/huawei_lte/binary_sensor.py +++ b/homeassistant/components/huawei_lte/binary_sensor.py @@ -142,7 +142,7 @@ class HuaweiLteMobileConnectionBinarySensor(HuaweiLteBaseBinarySensor): return True @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Get additional attributes related to connection status.""" attributes = {} if self._raw_state in CONNECTION_STATE_ATTRIBUTES: diff --git a/homeassistant/components/huawei_lte/device_tracker.py b/homeassistant/components/huawei_lte/device_tracker.py index 52e59b713dd..3e889263b76 100644 --- a/homeassistant/components/huawei_lte/device_tracker.py +++ b/homeassistant/components/huawei_lte/device_tracker.py @@ -126,11 +126,11 @@ class HuaweiLteScannerEntity(HuaweiLteBaseEntity, ScannerEntity): _is_connected: bool = attr.ib(init=False, default=False) _hostname: Optional[str] = attr.ib(init=False, default=None) - _device_state_attributes: Dict[str, Any] = attr.ib(init=False, factory=dict) + _extra_state_attributes: Dict[str, Any] = attr.ib(init=False, factory=dict) def __attrs_post_init__(self) -> None: """Initialize internal state.""" - self._device_state_attributes["mac_address"] = self.mac + self._extra_state_attributes["mac_address"] = self.mac @property def _entity_name(self) -> str: @@ -151,9 +151,9 @@ class HuaweiLteScannerEntity(HuaweiLteBaseEntity, ScannerEntity): return self._is_connected @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Get additional attributes related to entity state.""" - return self._device_state_attributes + return self._extra_state_attributes async def async_update(self) -> None: """Update state.""" @@ -162,6 +162,6 @@ class HuaweiLteScannerEntity(HuaweiLteBaseEntity, ScannerEntity): self._is_connected = host is not None if host is not None: self._hostname = host.get("HostName") - self._device_state_attributes = { + self._extra_state_attributes = { _better_snakecase(k): v for k, v in host.items() if k != "HostName" } diff --git a/homeassistant/components/hue/binary_sensor.py b/homeassistant/components/hue/binary_sensor.py index cfbe041aafe..e408e995ad4 100644 --- a/homeassistant/components/hue/binary_sensor.py +++ b/homeassistant/components/hue/binary_sensor.py @@ -31,9 +31,9 @@ class HuePresence(GenericZLLSensor, BinarySensorEntity): return self.sensor.presence @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - attributes = super().device_state_attributes + attributes = super().extra_state_attributes if "sensitivity" in self.sensor.config: attributes["sensitivity"] = self.sensor.config["sensitivity"] if "sensitivitymax" in self.sensor.config: diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index 6384e47b45e..8adde810fbe 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -535,7 +535,7 @@ class HueLight(CoordinatorEntity, LightEntity): await self.coordinator.async_request_refresh() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if not self.is_group: return {} diff --git a/homeassistant/components/hue/sensor.py b/homeassistant/components/hue/sensor.py index f5911bbb50c..a3c5fcc3fa7 100644 --- a/homeassistant/components/hue/sensor.py +++ b/homeassistant/components/hue/sensor.py @@ -58,9 +58,9 @@ class HueLightLevel(GenericHueGaugeSensorEntity): return round(float(10 ** ((self.sensor.lightlevel - 1) / 10000)), 2) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - attributes = super().device_state_attributes + attributes = super().extra_state_attributes attributes.update( { "lightlevel": self.sensor.lightlevel, diff --git a/homeassistant/components/hue/sensor_base.py b/homeassistant/components/hue/sensor_base.py index 263140464aa..9f764e04d28 100644 --- a/homeassistant/components/hue/sensor_base.py +++ b/homeassistant/components/hue/sensor_base.py @@ -197,6 +197,6 @@ class GenericZLLSensor(GenericHueSensor): """Representation of a Hue-brand, physical sensor.""" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return {"battery_level": self.sensor.battery} diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index e90b315fd16..9ac45c6bd9a 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -111,7 +111,7 @@ class PowerViewShade(ShadeEntity, CoverEntity): self._current_cover_position = MIN_POSITION @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {STATE_ATTRIBUTE_ROOM_NAME: self._room_name} diff --git a/homeassistant/components/hunterdouglas_powerview/scene.py b/homeassistant/components/hunterdouglas_powerview/scene.py index 33c7e7129fc..c30cde8d043 100644 --- a/homeassistant/components/hunterdouglas_powerview/scene.py +++ b/homeassistant/components/hunterdouglas_powerview/scene.py @@ -71,7 +71,7 @@ class PowerViewScene(HDEntity, Scene): return self._scene.name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {STATE_ATTRIBUTE_ROOM_NAME: self._room_name} diff --git a/homeassistant/components/hvv_departures/binary_sensor.py b/homeassistant/components/hvv_departures/binary_sensor.py index f625ef7c344..45ac0e45ad9 100644 --- a/homeassistant/components/hvv_departures/binary_sensor.py +++ b/homeassistant/components/hvv_departures/binary_sensor.py @@ -174,7 +174,7 @@ class HvvDepartureBinarySensor(CoordinatorEntity, BinarySensorEntity): return DEVICE_CLASS_PROBLEM @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if not ( self.coordinator.last_update_success diff --git a/homeassistant/components/hvv_departures/sensor.py b/homeassistant/components/hvv_departures/sensor.py index d6957e6beec..bbabb1b8caa 100644 --- a/homeassistant/components/hvv_departures/sensor.py +++ b/homeassistant/components/hvv_departures/sensor.py @@ -199,6 +199,6 @@ class HVVDepartureSensor(Entity): return DEVICE_CLASS_TIMESTAMP @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self.attr diff --git a/homeassistant/components/hydrawise/__init__.py b/homeassistant/components/hydrawise/__init__.py index 08827baae68..6fa05ebef16 100644 --- a/homeassistant/components/hydrawise/__init__.py +++ b/homeassistant/components/hydrawise/__init__.py @@ -151,7 +151,7 @@ class HydrawiseEntity(Entity): ] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION, "identifier": self.data.get("relay")} diff --git a/homeassistant/components/icloud/device_tracker.py b/homeassistant/components/icloud/device_tracker.py index 59554c001ef..77fcf0a3039 100644 --- a/homeassistant/components/icloud/device_tracker.py +++ b/homeassistant/components/icloud/device_tracker.py @@ -108,7 +108,7 @@ class IcloudTrackerEntity(TrackerEntity): return icon_for_icloud_device(self._device) @property - def device_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> Dict[str, any]: """Return the device state attributes.""" return self._device.state_attributes diff --git a/homeassistant/components/icloud/sensor.py b/homeassistant/components/icloud/sensor.py index 859148d8190..9a7c568112d 100644 --- a/homeassistant/components/icloud/sensor.py +++ b/homeassistant/components/icloud/sensor.py @@ -91,7 +91,7 @@ class IcloudDeviceBatterySensor(Entity): ) @property - def device_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> Dict[str, any]: """Return default attributes for the iCloud device entity.""" return self._device.state_attributes diff --git a/homeassistant/components/ign_sismologia/geo_location.py b/homeassistant/components/ign_sismologia/geo_location.py index 0db580701d0..fc9bdcbe87e 100644 --- a/homeassistant/components/ign_sismologia/geo_location.py +++ b/homeassistant/components/ign_sismologia/geo_location.py @@ -238,7 +238,7 @@ class IgnSismologiaLocationEvent(GeolocationEvent): return LENGTH_KILOMETERS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = {} for key, value in ( diff --git a/homeassistant/components/ihc/ihcdevice.py b/homeassistant/components/ihc/ihcdevice.py index 0b3dc763ca0..e351d2f38ea 100644 --- a/homeassistant/components/ihc/ihcdevice.py +++ b/homeassistant/components/ihc/ihcdevice.py @@ -42,7 +42,7 @@ class IHCDevice(Entity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if not self.info: return {} diff --git a/homeassistant/components/imap_email_content/sensor.py b/homeassistant/components/imap_email_content/sensor.py index 04d4ca97c5a..84dc527ac52 100644 --- a/homeassistant/components/imap_email_content/sensor.py +++ b/homeassistant/components/imap_email_content/sensor.py @@ -171,7 +171,7 @@ class EmailContentSensor(Entity): return self._message @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other state attributes for the message.""" return self._state_attributes diff --git a/homeassistant/components/incomfort/binary_sensor.py b/homeassistant/components/incomfort/binary_sensor.py index bf1340fb235..86cdf2d8687 100644 --- a/homeassistant/components/incomfort/binary_sensor.py +++ b/homeassistant/components/incomfort/binary_sensor.py @@ -40,6 +40,6 @@ class IncomfortFailed(IncomfortChild, BinarySensorEntity): return self._heater.status["is_failed"] @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the device state attributes.""" return {"fault_code": self._heater.status["fault_code"]} diff --git a/homeassistant/components/incomfort/climate.py b/homeassistant/components/incomfort/climate.py index 274308efe06..c3db86a79ac 100644 --- a/homeassistant/components/incomfort/climate.py +++ b/homeassistant/components/incomfort/climate.py @@ -39,7 +39,7 @@ class InComfortClimate(IncomfortChild, ClimateEntity): self._room = room @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the device state attributes.""" return {"status": self._room.status} diff --git a/homeassistant/components/incomfort/sensor.py b/homeassistant/components/incomfort/sensor.py index 692eecf2317..0672d19b2a9 100644 --- a/homeassistant/components/incomfort/sensor.py +++ b/homeassistant/components/incomfort/sensor.py @@ -95,6 +95,6 @@ class IncomfortTemperature(IncomfortSensor): self._unit_of_measurement = TEMP_CELSIUS @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the device state attributes.""" return {self._attr: self._heater.status[self._attr]} diff --git a/homeassistant/components/incomfort/water_heater.py b/homeassistant/components/incomfort/water_heater.py index da6e6d89315..b3db66bd93d 100644 --- a/homeassistant/components/incomfort/water_heater.py +++ b/homeassistant/components/incomfort/water_heater.py @@ -50,7 +50,7 @@ class IncomfortWaterHeater(IncomfortEntity, WaterHeaterEntity): return "mdi:thermometer-lines" @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the device state attributes.""" return {k: v for k, v in self._heater.status.items() if k in HEATER_ATTRS} diff --git a/homeassistant/components/insteon/climate.py b/homeassistant/components/insteon/climate.py index c699e76c4f3..536c30bc6b9 100644 --- a/homeassistant/components/insteon/climate.py +++ b/homeassistant/components/insteon/climate.py @@ -177,9 +177,9 @@ class InsteonClimateEntity(InsteonEntity, ClimateEntity): return CURRENT_HVAC_IDLE @property - def device_state_attributes(self): + def extra_state_attributes(self): """Provide attributes for display on device card.""" - attr = super().device_state_attributes + attr = super().extra_state_attributes humidifier = "off" if self._insteon_device.groups[DEHUMIDIFYING].value: humidifier = "dehumidifying" diff --git a/homeassistant/components/insteon/insteon_entity.py b/homeassistant/components/insteon/insteon_entity.py index 2234eb4750c..3f83440d690 100644 --- a/homeassistant/components/insteon/insteon_entity.py +++ b/homeassistant/components/insteon/insteon_entity.py @@ -75,7 +75,7 @@ class InsteonEntity(Entity): return f"{description} {self._insteon_device.address}{extension}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Provide attributes for display on device card.""" return {"insteon_address": self.address, "insteon_group": self.group} diff --git a/homeassistant/components/integration/sensor.py b/homeassistant/components/integration/sensor.py index 6c59035adb4..3be60dcd7f7 100644 --- a/homeassistant/components/integration/sensor.py +++ b/homeassistant/components/integration/sensor.py @@ -201,7 +201,7 @@ class IntegrationSensor(RestoreEntity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_SOURCE_ID: self._sensor_source_id} diff --git a/homeassistant/components/intesishome/climate.py b/homeassistant/components/intesishome/climate.py index a41161c7a6e..d58efddeb3c 100644 --- a/homeassistant/components/intesishome/climate.py +++ b/homeassistant/components/intesishome/climate.py @@ -212,7 +212,7 @@ class IntesisAC(ClimateEntity): return TEMP_CELSIUS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" attrs = {} if self._outdoor_temp: diff --git a/homeassistant/components/ios/sensor.py b/homeassistant/components/ios/sensor.py index ccbc118a681..a8cffacabe9 100644 --- a/homeassistant/components/ios/sensor.py +++ b/homeassistant/components/ios/sensor.py @@ -88,7 +88,7 @@ class IOSSensor(Entity): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" device = self._device[ios.ATTR_DEVICE] device_battery = self._device[ios.ATTR_BATTERY] diff --git a/homeassistant/components/iota/__init__.py b/homeassistant/components/iota/__init__.py index 41c5ad35d83..04db9140122 100644 --- a/homeassistant/components/iota/__init__.py +++ b/homeassistant/components/iota/__init__.py @@ -67,7 +67,7 @@ class IotaDevice(Entity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return {CONF_WALLET_NAME: self._name} diff --git a/homeassistant/components/iota/sensor.py b/homeassistant/components/iota/sensor.py index a6f689e8c2d..751324d61b1 100644 --- a/homeassistant/components/iota/sensor.py +++ b/homeassistant/components/iota/sensor.py @@ -85,7 +85,7 @@ class IotaNodeSensor(IotaDevice): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._attr diff --git a/homeassistant/components/iperf3/sensor.py b/homeassistant/components/iperf3/sensor.py index 749a3e83217..a8a79008817 100644 --- a/homeassistant/components/iperf3/sensor.py +++ b/homeassistant/components/iperf3/sensor.py @@ -55,7 +55,7 @@ class Iperf3Sensor(RestoreEntity): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/ipp/sensor.py b/homeassistant/components/ipp/sensor.py index a278f6e8ef9..b24e05f2720 100644 --- a/homeassistant/components/ipp/sensor.py +++ b/homeassistant/components/ipp/sensor.py @@ -117,7 +117,7 @@ class IPPMarkerSensor(IPPSensor): ) @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" return { ATTR_MARKER_HIGH_LEVEL: self.coordinator.data.markers[ @@ -160,7 +160,7 @@ class IPPPrinterSensor(IPPSensor): ) @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" return { ATTR_INFO: self.coordinator.data.info.printer_info, diff --git a/homeassistant/components/iqvia/__init__.py b/homeassistant/components/iqvia/__init__.py index 4dc066d7629..962f13ca07e 100644 --- a/homeassistant/components/iqvia/__init__.py +++ b/homeassistant/components/iqvia/__init__.py @@ -123,7 +123,7 @@ class IQVIAEntity(CoordinatorEntity): self._type = sensor_type @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return self._attrs diff --git a/homeassistant/components/irish_rail_transport/sensor.py b/homeassistant/components/irish_rail_transport/sensor.py index fba69a6f578..bd277020125 100644 --- a/homeassistant/components/irish_rail_transport/sensor.py +++ b/homeassistant/components/irish_rail_transport/sensor.py @@ -89,7 +89,7 @@ class IrishRailTransportSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._times: next_up = "None" diff --git a/homeassistant/components/iss/binary_sensor.py b/homeassistant/components/iss/binary_sensor.py index e1f0d7a19ce..787d7471d43 100644 --- a/homeassistant/components/iss/binary_sensor.py +++ b/homeassistant/components/iss/binary_sensor.py @@ -79,7 +79,7 @@ class IssBinarySensor(BinarySensorEntity): return DEFAULT_DEVICE_CLASS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.iss_data: attrs = { diff --git a/homeassistant/components/isy994/binary_sensor.py b/homeassistant/components/isy994/binary_sensor.py index e8c08d98aa7..807f99734b7 100644 --- a/homeassistant/components/isy994/binary_sensor.py +++ b/homeassistant/components/isy994/binary_sensor.py @@ -455,9 +455,9 @@ class ISYBinarySensorHeartbeat(ISYNodeEntity, BinarySensorEntity): return DEVICE_CLASS_BATTERY @property - def device_state_attributes(self): + def extra_state_attributes(self): """Get the state attributes for the device.""" - attr = super().device_state_attributes + attr = super().extra_state_attributes attr["parent_entity_id"] = self._parent_device.entity_id return attr diff --git a/homeassistant/components/isy994/light.py b/homeassistant/components/isy994/light.py index 7ff44863f6b..04800a3f211 100644 --- a/homeassistant/components/isy994/light.py +++ b/homeassistant/components/isy994/light.py @@ -98,9 +98,9 @@ class ISYLightEntity(ISYNodeEntity, LightEntity, RestoreEntity): _LOGGER.debug("Unable to turn on light") @property - def device_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> Dict: """Return the light attributes.""" - attribs = super().device_state_attributes + attribs = super().extra_state_attributes attribs[ATTR_LAST_BRIGHTNESS] = self._last_brightness return attribs diff --git a/homeassistant/components/isy994/sensor.py b/homeassistant/components/isy994/sensor.py index d3d192b1b3b..2f393227df5 100644 --- a/homeassistant/components/isy994/sensor.py +++ b/homeassistant/components/isy994/sensor.py @@ -117,7 +117,7 @@ class ISYSensorVariableEntity(ISYEntity): return convert_isy_value_to_hass(self._node.status, "", self._node.prec) @property - def device_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> Dict: """Get the state attributes for the device.""" return { "init_value": convert_isy_value_to_hass( diff --git a/homeassistant/components/izone/climate.py b/homeassistant/components/izone/climate.py index 77ce9cb2452..53651683725 100644 --- a/homeassistant/components/izone/climate.py +++ b/homeassistant/components/izone/climate.py @@ -282,7 +282,7 @@ class ControllerDevice(ClimateEntity): return PRECISION_TENTHS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" return { "supply_temperature": show_temp( @@ -660,7 +660,7 @@ class ZoneDevice(ClimateEntity): return self._zone.index @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" return { "airflow_max": self._zone.airflow_max, diff --git a/tests/components/honeywell/test_climate.py b/tests/components/honeywell/test_climate.py index 058988203e5..ee107d4985e 100644 --- a/tests/components/honeywell/test_climate.py +++ b/tests/components/honeywell/test_climate.py @@ -392,10 +392,10 @@ class TestHoneywellUS(unittest.TestCase): ATTR_FAN_MODES: somecomfort.FAN_MODES, ATTR_HVAC_MODES: somecomfort.SYSTEM_MODES, } - assert expected == self.honeywell.device_state_attributes + assert expected == self.honeywell.extra_state_attributes expected["fan"] = "idle" self.device.fan_running = False - assert expected == self.honeywell.device_state_attributes + assert expected == self.honeywell.extra_state_attributes def test_with_no_fan(self): """Test if there is on fan.""" @@ -407,7 +407,7 @@ class TestHoneywellUS(unittest.TestCase): ATTR_FAN_MODES: somecomfort.FAN_MODES, ATTR_HVAC_MODES: somecomfort.SYSTEM_MODES, } - assert expected == self.honeywell.device_state_attributes + assert expected == self.honeywell.extra_state_attributes def test_heat_away_mode(self): """Test setting the heat away mode.""" diff --git a/tests/components/imap_email_content/test_sensor.py b/tests/components/imap_email_content/test_sensor.py index 241dc4dc506..101896f4a33 100644 --- a/tests/components/imap_email_content/test_sensor.py +++ b/tests/components/imap_email_content/test_sensor.py @@ -48,12 +48,12 @@ async def test_allowed_sender(hass): sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() assert "Test" == sensor.state - assert "Test Message" == sensor.device_state_attributes["body"] - assert "sender@test.com" == sensor.device_state_attributes["from"] - assert "Test" == sensor.device_state_attributes["subject"] + assert "Test Message" == sensor.extra_state_attributes["body"] + assert "sender@test.com" == sensor.extra_state_attributes["from"] + assert "Test" == sensor.extra_state_attributes["subject"] assert ( datetime.datetime(2016, 1, 1, 12, 44, 57) - == sensor.device_state_attributes["date"] + == sensor.extra_state_attributes["date"] ) @@ -84,7 +84,7 @@ async def test_multi_part_with_text(hass): sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() assert "Link" == sensor.state - assert "Test Message" == sensor.device_state_attributes["body"] + assert "Test Message" == sensor.extra_state_attributes["body"] async def test_multi_part_only_html(hass): @@ -113,7 +113,7 @@ async def test_multi_part_only_html(hass): assert "Link" == sensor.state assert ( "Test Message" - == sensor.device_state_attributes["body"] + == sensor.extra_state_attributes["body"] ) @@ -141,7 +141,7 @@ async def test_multi_part_only_other_text(hass): sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() assert "Link" == sensor.state - assert "Test Message" == sensor.device_state_attributes["body"] + assert "Test Message" == sensor.extra_state_attributes["body"] async def test_multiple_emails(hass): @@ -183,7 +183,7 @@ async def test_multiple_emails(hass): assert "Test" == states[0].state assert "Test 2" == states[1].state - assert "Test Message 2" == sensor.device_state_attributes["body"] + assert "Test Message 2" == sensor.extra_state_attributes["body"] async def test_sender_not_allowed(hass): From 10848b9bdf62dcdd3867dc3d2c9627f07ceb20fc Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 11 Mar 2021 18:52:07 +0100 Subject: [PATCH 301/831] Recorder improvements (#47739) --- homeassistant/components/recorder/__init__.py | 9 +- homeassistant/components/recorder/models.py | 39 +++++++ homeassistant/components/recorder/purge.py | 21 ++-- homeassistant/components/recorder/util.py | 9 +- tests/components/recorder/common.py | 38 ++++++- tests/components/recorder/conftest.py | 36 +++++- tests/components/recorder/test_init.py | 27 +++-- tests/components/recorder/test_purge.py | 105 ++++++++++-------- 8 files changed, 207 insertions(+), 77 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 3935aa97eb8..9b84518b6d3 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -1,6 +1,5 @@ """Support for recording details.""" import asyncio -from collections import namedtuple import concurrent.futures from datetime import datetime import logging @@ -8,7 +7,7 @@ import queue import sqlite3 import threading import time -from typing import Any, Callable, List, Optional +from typing import Any, Callable, List, NamedTuple, Optional from sqlalchemy import create_engine, event as sqlalchemy_event, exc, select from sqlalchemy.orm import scoped_session, sessionmaker @@ -223,7 +222,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return await instance.async_db_ready -PurgeTask = namedtuple("PurgeTask", ["keep_days", "repack"]) +class PurgeTask(NamedTuple): + """Object to store information about purge task.""" + + keep_days: int + repack: bool class WaitTask: diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index 551abeac15a..6ed25e64eda 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -64,6 +64,15 @@ class Events(Base): # type: ignore Index("ix_events_event_type_time_fired", "event_type", "time_fired"), ) + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + @staticmethod def from_event(event, event_data=None): """Create an event database object from a native event.""" @@ -129,6 +138,17 @@ class States(Base): # type: ignore Index("ix_states_entity_id_last_updated", "entity_id", "last_updated"), ) + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + @staticmethod def from_event(event): """Create object from a state_changed event.""" @@ -185,6 +205,16 @@ class RecorderRuns(Base): # type: ignore __table_args__ = (Index("ix_recorder_runs_start_end", "start", "end"),) + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + def entity_ids(self, point_in_time=None): """Return the entity ids that existed in this run. @@ -219,6 +249,15 @@ class SchemaChanges(Base): # type: ignore schema_version = Column(Integer) changed = Column(DateTime(timezone=True), default=dt_util.utcnow) + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + def process_timestamp(ts): """Process a timestamp into datetime object.""" diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index ac10dadc227..3717ed49f30 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -28,14 +28,16 @@ def purge_old_data(instance: Recorder, purge_days: int, repack: bool) -> bool: Cleans up an timeframe of an hour, based on the oldest record. """ purge_before = dt_util.utcnow() - timedelta(days=purge_days) - _LOGGER.debug("Purging states and events before target %s", purge_before) + _LOGGER.debug( + "Purging states and events before target %s", + purge_before.isoformat(sep=" ", timespec="seconds"), + ) try: with session_scope(session=instance.get_session()) as session: # type: ignore # Purge a max of MAX_ROWS_TO_PURGE, based on the oldest states or events record event_ids = _select_event_ids_to_purge(session, purge_before) state_ids = _select_state_ids_to_purge(session, purge_before, event_ids) if state_ids: - _disconnect_states_about_to_be_purged(session, state_ids) _purge_state_ids(session, state_ids) if event_ids: _purge_event_ids(session, event_ids) @@ -66,7 +68,7 @@ def purge_old_data(instance: Recorder, purge_days: int, repack: bool) -> bool: return True -def _select_event_ids_to_purge(session: Session, purge_before: datetime) -> list: +def _select_event_ids_to_purge(session: Session, purge_before: datetime) -> list[int]: """Return a list of event ids to purge.""" events = ( session.query(Events.event_id) @@ -79,8 +81,8 @@ def _select_event_ids_to_purge(session: Session, purge_before: datetime) -> list def _select_state_ids_to_purge( - session: Session, purge_before: datetime, event_ids: list -) -> list: + session: Session, purge_before: datetime, event_ids: list[int] +) -> list[int]: """Return a list of state ids to purge.""" if not event_ids: return [] @@ -94,7 +96,9 @@ def _select_state_ids_to_purge( return [state.state_id for state in states] -def _disconnect_states_about_to_be_purged(session: Session, state_ids: list) -> None: +def _purge_state_ids(session: Session, state_ids: list[int]) -> None: + """Disconnect states and delete by state id.""" + # Update old_state_id to NULL before deleting to ensure # the delete does not fail due to a foreign key constraint # since some databases (MSSQL) cannot do the ON DELETE SET NULL @@ -106,9 +110,6 @@ def _disconnect_states_about_to_be_purged(session: Session, state_ids: list) -> ) _LOGGER.debug("Updated %s states to remove old_state_id", disconnected_rows) - -def _purge_state_ids(session: Session, state_ids: list) -> None: - """Delete by state id.""" deleted_rows = ( session.query(States) .filter(States.state_id.in_(state_ids)) @@ -117,7 +118,7 @@ def _purge_state_ids(session: Session, state_ids: list) -> None: _LOGGER.debug("Deleted %s states", deleted_rows) -def _purge_event_ids(session: Session, event_ids: list) -> None: +def _purge_event_ids(session: Session, event_ids: list[int]) -> None: """Delete by event id.""" deleted_rows = ( session.query(Events) diff --git a/homeassistant/components/recorder/util.py b/homeassistant/components/recorder/util.py index b945386de82..b04a4fb7f1f 100644 --- a/homeassistant/components/recorder/util.py +++ b/homeassistant/components/recorder/util.py @@ -1,4 +1,7 @@ """SQLAlchemy util functions.""" +from __future__ import annotations + +from collections.abc import Generator from contextlib import contextmanager from datetime import timedelta import logging @@ -6,7 +9,9 @@ import os import time from sqlalchemy.exc import OperationalError, SQLAlchemyError +from sqlalchemy.orm.session import Session +from homeassistant.helpers.typing import HomeAssistantType import homeassistant.util.dt as dt_util from .const import CONF_DB_INTEGRITY_CHECK, DATA_INSTANCE, SQLITE_URL_PREFIX @@ -25,7 +30,9 @@ MAX_RESTART_TIME = timedelta(minutes=10) @contextmanager -def session_scope(*, hass=None, session=None): +def session_scope( + *, hass: HomeAssistantType | None = None, session: Session | None = None +) -> Generator[Session, None, None]: """Provide a transactional scope around a series of operations.""" if session is None and hass is not None: session = hass.data[DATA_INSTANCE].get_session() diff --git a/tests/components/recorder/common.py b/tests/components/recorder/common.py index d2b731777e2..79f0f1f00d0 100644 --- a/tests/components/recorder/common.py +++ b/tests/components/recorder/common.py @@ -1,14 +1,15 @@ """Common test utils for working with recorder.""" - from datetime import timedelta +from homeassistant import core as ha from homeassistant.components import recorder +from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util -from tests.common import fire_time_changed +from tests.common import async_fire_time_changed, fire_time_changed -def wait_recording_done(hass): +def wait_recording_done(hass: HomeAssistantType) -> None: """Block till recording is done.""" hass.block_till_done() trigger_db_commit(hass) @@ -17,18 +18,45 @@ def wait_recording_done(hass): hass.block_till_done() -async def async_wait_recording_done(hass): +async def async_wait_recording_done_without_instance(hass: HomeAssistantType) -> None: """Block till recording is done.""" await hass.loop.run_in_executor(None, wait_recording_done, hass) -def trigger_db_commit(hass): +def trigger_db_commit(hass: HomeAssistantType) -> None: """Force the recorder to commit.""" for _ in range(recorder.DEFAULT_COMMIT_INTERVAL): # We only commit on time change fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=1)) +async def async_wait_recording_done( + hass: HomeAssistantType, + instance: recorder.Recorder, +) -> None: + """Async wait until recording is done.""" + await hass.async_block_till_done() + async_trigger_db_commit(hass) + await hass.async_block_till_done() + await async_recorder_block_till_done(hass, instance) + await hass.async_block_till_done() + + +@ha.callback +def async_trigger_db_commit(hass: HomeAssistantType) -> None: + """Fore the recorder to commit. Async friendly.""" + for _ in range(recorder.DEFAULT_COMMIT_INTERVAL): + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=1)) + + +async def async_recorder_block_till_done( + hass: HomeAssistantType, + instance: recorder.Recorder, +) -> None: + """Non blocking version of recorder.block_till_done().""" + await hass.async_add_executor_job(instance.block_till_done) + + def corrupt_db_file(test_db_file): """Corrupt an sqlite3 database file.""" with open(test_db_file, "w+") as fhandle: diff --git a/tests/components/recorder/conftest.py b/tests/components/recorder/conftest.py index d91a86402ac..6eadb1c62ed 100644 --- a/tests/components/recorder/conftest.py +++ b/tests/components/recorder/conftest.py @@ -1,10 +1,24 @@ """Common test tools.""" +from __future__ import annotations + +from collections.abc import AsyncGenerator +from typing import Awaitable, Callable, cast import pytest +from homeassistant.components.recorder import Recorder from homeassistant.components.recorder.const import DATA_INSTANCE +from homeassistant.helpers.typing import ConfigType, HomeAssistantType -from tests.common import get_test_home_assistant, init_recorder_component +from .common import async_recorder_block_till_done + +from tests.common import ( + async_init_recorder_component, + get_test_home_assistant, + init_recorder_component, +) + +SetupRecorderInstanceT = Callable[..., Awaitable[Recorder]] @pytest.fixture @@ -22,3 +36,23 @@ def hass_recorder(): yield setup_recorder hass.stop() + + +@pytest.fixture +async def async_setup_recorder_instance() -> AsyncGenerator[ + SetupRecorderInstanceT, None +]: + """Yield callable to setup recorder instance.""" + + async def async_setup_recorder( + hass: HomeAssistantType, config: ConfigType | None = None + ) -> Recorder: + """Setup and return recorder instance.""" # noqa: D401 + await async_init_recorder_component(hass, config) + await hass.async_block_till_done() + instance = cast(Recorder, hass.data[DATA_INSTANCE]) + await async_recorder_block_till_done(hass, instance) + assert isinstance(instance, Recorder) + return instance + + yield async_setup_recorder diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index 63f4b9887c6..b3c58995b37 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -28,10 +28,17 @@ from homeassistant.const import ( STATE_UNLOCKED, ) from homeassistant.core import Context, CoreState, callback +from homeassistant.helpers.typing import HomeAssistantType from homeassistant.setup import async_setup_component, setup_component from homeassistant.util import dt as dt_util -from .common import async_wait_recording_done, corrupt_db_file, wait_recording_done +from .common import ( + async_wait_recording_done, + async_wait_recording_done_without_instance, + corrupt_db_file, + wait_recording_done, +) +from .conftest import SetupRecorderInstanceT from tests.common import ( async_init_recorder_component, @@ -62,17 +69,19 @@ async def test_shutdown_before_startup_finishes(hass): assert run_info.end is not None -def test_saving_state(hass, hass_recorder): +async def test_saving_state( + hass: HomeAssistantType, async_setup_recorder_instance: SetupRecorderInstanceT +): """Test saving and restoring a state.""" - hass = hass_recorder() + instance = await async_setup_recorder_instance(hass) entity_id = "test.recorder" state = "restoring_from_db" attributes = {"test_attr": 5, "test_attr_10": "nice"} - hass.states.set(entity_id, state, attributes) + hass.states.async_set(entity_id, state, attributes) - wait_recording_done(hass) + await async_wait_recording_done(hass, instance) with session_scope(hass=hass) as session: db_states = list(session.query(States)) @@ -690,15 +699,15 @@ async def test_database_corruption_while_running(hass, tmpdir, caplog): hass.states.async_set("test.lost", "on", {}) - await async_wait_recording_done(hass) + await async_wait_recording_done_without_instance(hass) await hass.async_add_executor_job(corrupt_db_file, test_db_file) - await async_wait_recording_done(hass) + await async_wait_recording_done_without_instance(hass) # This state will not be recorded because # the database corruption will be discovered # and we will have to rollback to recover hass.states.async_set("test.one", "off", {}) - await async_wait_recording_done(hass) + await async_wait_recording_done_without_instance(hass) assert "Unrecoverable sqlite3 database corruption detected" in caplog.text assert "The system will rename the corrupt database file" in caplog.text @@ -706,7 +715,7 @@ async def test_database_corruption_while_running(hass, tmpdir, caplog): # This state should go into the new database hass.states.async_set("test.two", "on", {}) - await async_wait_recording_done(hass) + await async_wait_recording_done_without_instance(hass) def _get_last_state(): with session_scope(hass=hass) as session: diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index aaf53000865..3535a58d33d 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -3,19 +3,23 @@ from datetime import timedelta import json from homeassistant.components import recorder -from homeassistant.components.recorder.const import DATA_INSTANCE from homeassistant.components.recorder.models import Events, RecorderRuns, States from homeassistant.components.recorder.purge import purge_old_data from homeassistant.components.recorder.util import session_scope +from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util -from .common import wait_recording_done +from .common import async_wait_recording_done +from .conftest import SetupRecorderInstanceT -def test_purge_old_states(hass, hass_recorder): +async def test_purge_old_states( + hass: HomeAssistantType, async_setup_recorder_instance: SetupRecorderInstanceT +): """Test deleting old states.""" - hass = hass_recorder() - _add_test_states(hass) + instance = await async_setup_recorder_instance(hass) + + await _add_test_states(hass, instance) # make sure we start with 6 states with session_scope(hass=hass) as session: @@ -28,7 +32,7 @@ def test_purge_old_states(hass, hass_recorder): assert events.count() == 6 # run purge_old_data() - finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False) + finished = purge_old_data(instance, 4, repack=False) assert not finished assert states.count() == 2 @@ -36,35 +40,41 @@ def test_purge_old_states(hass, hass_recorder): assert states_after_purge[1].old_state_id == states_after_purge[0].state_id assert states_after_purge[0].old_state_id is None - finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False) + finished = purge_old_data(instance, 4, repack=False) assert finished assert states.count() == 2 -def test_purge_old_events(hass, hass_recorder): +async def test_purge_old_events( + hass: HomeAssistantType, async_setup_recorder_instance: SetupRecorderInstanceT +): """Test deleting old events.""" - hass = hass_recorder() - _add_test_events(hass) + instance = await async_setup_recorder_instance(hass) + + await _add_test_events(hass, instance) with session_scope(hass=hass) as session: events = session.query(Events).filter(Events.event_type.like("EVENT_TEST%")) assert events.count() == 6 # run purge_old_data() - finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False) + finished = purge_old_data(instance, 4, repack=False) assert not finished assert events.count() == 2 # we should only have 2 events left - finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False) + finished = purge_old_data(instance, 4, repack=False) assert finished assert events.count() == 2 -def test_purge_old_recorder_runs(hass, hass_recorder): +async def test_purge_old_recorder_runs( + hass: HomeAssistantType, async_setup_recorder_instance: SetupRecorderInstanceT +): """Test deleting old recorder runs keeps current run.""" - hass = hass_recorder() - _add_test_recorder_runs(hass) + instance = await async_setup_recorder_instance(hass) + + await _add_test_recorder_runs(hass, instance) # make sure we start with 7 recorder runs with session_scope(hass=hass) as session: @@ -72,21 +82,26 @@ def test_purge_old_recorder_runs(hass, hass_recorder): assert recorder_runs.count() == 7 # run purge_old_data() - finished = purge_old_data(hass.data[DATA_INSTANCE], 0, repack=False) + finished = purge_old_data(instance, 0, repack=False) assert not finished - finished = purge_old_data(hass.data[DATA_INSTANCE], 0, repack=False) + finished = purge_old_data(instance, 0, repack=False) assert finished assert recorder_runs.count() == 1 -def test_purge_method(hass, hass_recorder, caplog): +async def test_purge_method( + hass: HomeAssistantType, + async_setup_recorder_instance: SetupRecorderInstanceT, + caplog, +): """Test purge method.""" - hass = hass_recorder() + instance = await async_setup_recorder_instance(hass) + service_data = {"keep_days": 4} - _add_test_events(hass) - _add_test_states(hass) - _add_test_recorder_runs(hass) + await _add_test_events(hass, instance) + await _add_test_states(hass, instance) + await _add_test_recorder_runs(hass, instance) # make sure we start with 6 states with session_scope(hass=hass) as session: @@ -99,28 +114,26 @@ def test_purge_method(hass, hass_recorder, caplog): recorder_runs = session.query(RecorderRuns) assert recorder_runs.count() == 7 - hass.data[DATA_INSTANCE].block_till_done() - wait_recording_done(hass) + await hass.async_block_till_done() + await async_wait_recording_done(hass, instance) # run purge method - no service data, use defaults - hass.services.call("recorder", "purge") - hass.block_till_done() + await hass.services.async_call("recorder", "purge") + await hass.async_block_till_done() # Small wait for recorder thread - hass.data[DATA_INSTANCE].block_till_done() - wait_recording_done(hass) + await async_wait_recording_done(hass, instance) # only purged old events assert states.count() == 4 assert events.count() == 4 # run purge method - correct service data - hass.services.call("recorder", "purge", service_data=service_data) - hass.block_till_done() + await hass.services.async_call("recorder", "purge", service_data=service_data) + await hass.async_block_till_done() # Small wait for recorder thread - hass.data[DATA_INSTANCE].block_till_done() - wait_recording_done(hass) + await async_wait_recording_done(hass, instance) # we should only have 2 states left after purging assert states.count() == 2 @@ -135,23 +148,21 @@ def test_purge_method(hass, hass_recorder, caplog): # run purge method - correct service data, with repack service_data["repack"] = True - hass.services.call("recorder", "purge", service_data=service_data) - hass.block_till_done() - hass.data[DATA_INSTANCE].block_till_done() - wait_recording_done(hass) + await hass.services.async_call("recorder", "purge", service_data=service_data) + await hass.async_block_till_done() + await async_wait_recording_done(hass, instance) assert "Vacuuming SQL DB to free space" in caplog.text -def _add_test_states(hass): +async def _add_test_states(hass: HomeAssistantType, instance: recorder.Recorder): """Add multiple states to the db for testing.""" utcnow = dt_util.utcnow() five_days_ago = utcnow - timedelta(days=5) eleven_days_ago = utcnow - timedelta(days=11) attributes = {"test_attr": 5, "test_attr_10": "nice"} - hass.block_till_done() - hass.data[DATA_INSTANCE].block_till_done() - wait_recording_done(hass) + await hass.async_block_till_done() + await async_wait_recording_done(hass, instance) with recorder.session_scope(hass=hass) as session: old_state_id = None @@ -191,16 +202,15 @@ def _add_test_states(hass): old_state_id = state.state_id -def _add_test_events(hass): +async def _add_test_events(hass: HomeAssistantType, instance: recorder.Recorder): """Add a few events for testing.""" utcnow = dt_util.utcnow() five_days_ago = utcnow - timedelta(days=5) eleven_days_ago = utcnow - timedelta(days=11) event_data = {"test_attr": 5, "test_attr_10": "nice"} - hass.block_till_done() - hass.data[DATA_INSTANCE].block_till_done() - wait_recording_done(hass) + await hass.async_block_till_done() + await async_wait_recording_done(hass, instance) with recorder.session_scope(hass=hass) as session: for event_id in range(6): @@ -225,15 +235,14 @@ def _add_test_events(hass): ) -def _add_test_recorder_runs(hass): +async def _add_test_recorder_runs(hass: HomeAssistantType, instance: recorder.Recorder): """Add a few recorder_runs for testing.""" utcnow = dt_util.utcnow() five_days_ago = utcnow - timedelta(days=5) eleven_days_ago = utcnow - timedelta(days=11) - hass.block_till_done() - hass.data[DATA_INSTANCE].block_till_done() - wait_recording_done(hass) + await hass.async_block_till_done() + await async_wait_recording_done(hass, instance) with recorder.session_scope(hass=hass) as session: for rec_id in range(6): From 1095905f8c031eafab182b27bb1f3601b5d839a5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 11 Mar 2021 19:41:01 +0100 Subject: [PATCH 302/831] Add DataUpdateCoordinator to Verisure (#47574) --- homeassistant/components/verisure/__init__.py | 100 +++++++----- .../verisure/alarm_control_panel.py | 98 ++++++------ .../components/verisure/binary_sensor.py | 48 +++--- homeassistant/components/verisure/camera.py | 53 ++++--- homeassistant/components/verisure/lock.py | 108 ++++++------- homeassistant/components/verisure/sensor.py | 89 +++++------ homeassistant/components/verisure/switch.py | 51 ++++--- requirements_test_all.txt | 3 - tests/components/verisure/__init__.py | 1 - .../verisure/test_ethernet_status.py | 67 -------- tests/components/verisure/test_lock.py | 144 ------------------ 11 files changed, 287 insertions(+), 475 deletions(-) delete mode 100644 tests/components/verisure/__init__.py delete mode 100644 tests/components/verisure/test_ethernet_status.py delete mode 100644 tests/components/verisure/test_lock.py diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index ccb479814ab..00d970f133f 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -5,7 +5,11 @@ from datetime import timedelta from typing import Any, Literal from jsonpath import jsonpath -import verisure +from verisure import ( + Error as VerisureError, + ResponseError as VerisureResponseError, + Session as Verisure, +) import voluptuous as vol from homeassistant.const import ( @@ -19,6 +23,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.util import Throttle from .const import ( @@ -52,8 +57,6 @@ PLATFORMS = [ "binary_sensor", ] -HUB = None - CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( @@ -83,31 +86,43 @@ CONFIG_SCHEMA = vol.Schema( DEVICE_SERIAL_SCHEMA = vol.Schema({vol.Required(ATTR_DEVICE_SERIAL): cv.string}) -def setup(hass: HomeAssistant, config: ConfigType) -> bool: +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Verisure integration.""" - global HUB # pylint: disable=global-statement - HUB = VerisureHub(config[DOMAIN]) - HUB.update_overview = Throttle(config[DOMAIN][CONF_SCAN_INTERVAL])( - HUB.update_overview + verisure = Verisure(config[DOMAIN][CONF_USERNAME], config[DOMAIN][CONF_PASSWORD]) + coordinator = VerisureDataUpdateCoordinator( + hass, session=verisure, domain_config=config[DOMAIN] ) - if not HUB.login(): + + if not await hass.async_add_executor_job(coordinator.login): + LOGGER.error("Login failed") return False - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: HUB.logout()) - HUB.update_overview() + + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, lambda event: coordinator.logout() + ) + + await coordinator.async_refresh() + if not coordinator.last_update_success: + LOGGER.error("Update failed") + return False + + hass.data[DOMAIN] = coordinator for platform in PLATFORMS: - discovery.load_platform(hass, platform, DOMAIN, {}, config) + hass.async_create_task( + discovery.async_load_platform(hass, platform, DOMAIN, {}, config) + ) async def capture_smartcam(service): """Capture a new picture from a smartcam.""" device_id = service.data[ATTR_DEVICE_SERIAL] try: - await hass.async_add_executor_job(HUB.smartcam_capture, device_id) + await hass.async_add_executor_job(coordinator.smartcam_capture, device_id) LOGGER.debug("Capturing new image from %s", ATTR_DEVICE_SERIAL) - except verisure.Error as ex: + except VerisureError as ex: LOGGER.error("Could not capture image, %s", ex) - hass.services.register( + hass.services.async_register( DOMAIN, SERVICE_CAPTURE_SMARTCAM, capture_smartcam, schema=DEVICE_SERIAL_SCHEMA ) @@ -115,12 +130,12 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: """Disable autolock on a doorlock.""" device_id = service.data[ATTR_DEVICE_SERIAL] try: - await hass.async_add_executor_job(HUB.disable_autolock, device_id) + await hass.async_add_executor_job(coordinator.disable_autolock, device_id) LOGGER.debug("Disabling autolock on%s", ATTR_DEVICE_SERIAL) - except verisure.Error as ex: + except VerisureError as ex: LOGGER.error("Could not disable autolock, %s", ex) - hass.services.register( + hass.services.async_register( DOMAIN, SERVICE_DISABLE_AUTOLOCK, disable_autolock, schema=DEVICE_SERIAL_SCHEMA ) @@ -128,38 +143,39 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: """Enable autolock on a doorlock.""" device_id = service.data[ATTR_DEVICE_SERIAL] try: - await hass.async_add_executor_job(HUB.enable_autolock, device_id) + await hass.async_add_executor_job(coordinator.enable_autolock, device_id) LOGGER.debug("Enabling autolock on %s", ATTR_DEVICE_SERIAL) - except verisure.Error as ex: + except VerisureError as ex: LOGGER.error("Could not enable autolock, %s", ex) - hass.services.register( + hass.services.async_register( DOMAIN, SERVICE_ENABLE_AUTOLOCK, enable_autolock, schema=DEVICE_SERIAL_SCHEMA ) return True -class VerisureHub: - """A Verisure hub wrapper class.""" +class VerisureDataUpdateCoordinator(DataUpdateCoordinator): + """A Verisure Data Update Coordinator.""" - def __init__(self, domain_config: ConfigType): + def __init__( + self, hass: HomeAssistant, domain_config: ConfigType, session: Verisure + ) -> None: """Initialize the Verisure hub.""" - self.overview = {} self.imageseries = {} - self.config = domain_config - - self.session = verisure.Session( - domain_config[CONF_USERNAME], domain_config[CONF_PASSWORD] - ) - self.giid = domain_config.get(CONF_GIID) + self.session = session + + super().__init__( + hass, LOGGER, name=DOMAIN, update_interval=domain_config[CONF_SCAN_INTERVAL] + ) + def login(self) -> bool: """Login to Verisure.""" try: self.session.login() - except verisure.Error as ex: + except VerisureError as ex: LOGGER.error("Could not log in to verisure, %s", ex) return False if self.giid: @@ -170,7 +186,7 @@ class VerisureHub: """Logout from Verisure.""" try: self.session.logout() - except verisure.Error as ex: + except VerisureError as ex: LOGGER.error("Could not log out from verisure, %s", ex) return False return True @@ -179,22 +195,22 @@ class VerisureHub: """Set installation GIID.""" try: self.session.set_giid(self.giid) - except verisure.Error as ex: + except VerisureError as ex: LOGGER.error("Could not set installation GIID, %s", ex) return False return True - def update_overview(self) -> None: - """Update the overview.""" + async def _async_update_data(self) -> dict: + """Fetch data from Verisure.""" try: - self.overview = self.session.get_overview() - except verisure.ResponseError as ex: + return await self.hass.async_add_executor_job(self.session.get_overview) + except VerisureResponseError as ex: LOGGER.error("Could not read overview, %s", ex) if ex.status_code == HTTP_SERVICE_UNAVAILABLE: # Service unavailable LOGGER.info("Trying to log in again") - self.login() - else: - raise + await self.hass.async_add_executor_job(self.login) + return {} + raise @Throttle(timedelta(seconds=60)) def update_smartcam_imageseries(self) -> None: @@ -216,7 +232,7 @@ class VerisureHub: def get(self, jpath: str, *args) -> list[Any] | Literal[False]: """Get values from the overview that matches the jsonpath.""" - res = jsonpath(self.overview, jpath % args) + res = jsonpath(self.data, jpath % args) return res or [] def get_first(self, jpath: str, *args) -> Any | None: diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index c791bfc38dc..d0a93fb45f9 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -1,7 +1,7 @@ """Support for Verisure alarm control panels.""" from __future__ import annotations -from time import sleep +import asyncio from typing import Any, Callable from homeassistant.components.alarm_control_panel import ( @@ -16,12 +16,14 @@ from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, + STATE_ALARM_PENDING, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import HUB as hub -from .const import CONF_ALARM, CONF_CODE_DIGITS, CONF_GIID, LOGGER +from . import VerisureDataUpdateCoordinator +from .const import CONF_ALARM, CONF_GIID, DOMAIN, LOGGER def setup_platform( @@ -31,51 +33,53 @@ def setup_platform( discovery_info: dict[str, Any] | None = None, ) -> None: """Set up the Verisure platform.""" + coordinator = hass.data[DOMAIN] alarms = [] - if int(hub.config.get(CONF_ALARM, 1)): - hub.update_overview() - alarms.append(VerisureAlarm()) + if int(coordinator.config.get(CONF_ALARM, 1)): + alarms.append(VerisureAlarm(coordinator)) add_entities(alarms) -def set_arm_state(state: str, code: str | None = None) -> None: - """Send set arm state command.""" - transaction_id = hub.session.set_arm_state(code, state)[ - "armStateChangeTransactionId" - ] - LOGGER.info("verisure set arm state %s", state) - transaction = {} - while "result" not in transaction: - sleep(0.5) - transaction = hub.session.get_arm_state_transaction(transaction_id) - hub.update_overview() - - -class VerisureAlarm(AlarmControlPanelEntity): +class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity): """Representation of a Verisure alarm status.""" - def __init__(self): + coordinator: VerisureDataUpdateCoordinator + + def __init__(self, coordinator: VerisureDataUpdateCoordinator) -> None: """Initialize the Verisure alarm panel.""" + super().__init__(coordinator) self._state = None - self._digits = hub.config.get(CONF_CODE_DIGITS) - self._changed_by = None @property def name(self) -> str: """Return the name of the device.""" - giid = hub.config.get(CONF_GIID) + giid = self.coordinator.config.get(CONF_GIID) if giid is not None: - aliass = {i["giid"]: i["alias"] for i in hub.session.installations} + aliass = { + i["giid"]: i["alias"] for i in self.coordinator.session.installations + } if giid in aliass: return "{} alarm".format(aliass[giid]) LOGGER.error("Verisure installation giid not found: %s", giid) - return "{} alarm".format(hub.session.installations[0]["alias"]) + return "{} alarm".format(self.coordinator.session.installations[0]["alias"]) @property def state(self) -> str | None: """Return the state of the device.""" + status = self.coordinator.get_first("$.armState.statusType") + if status == "DISARMED": + self._state = STATE_ALARM_DISARMED + elif status == "ARMED_HOME": + self._state = STATE_ALARM_ARMED_HOME + elif status == "ARMED_AWAY": + self._state = STATE_ALARM_ARMED_AWAY + elif status == "PENDING": + self._state = STATE_ALARM_PENDING + else: + LOGGER.error("Unknown alarm state %s", status) + return self._state @property @@ -91,30 +95,32 @@ class VerisureAlarm(AlarmControlPanelEntity): @property def changed_by(self) -> str | None: """Return the last change triggered by.""" - return self._changed_by + return self.coordinator.get_first("$.armState.name") - def update(self) -> None: - """Update alarm status.""" - hub.update_overview() - status = hub.get_first("$.armState.statusType") - if status == "DISARMED": - self._state = STATE_ALARM_DISARMED - elif status == "ARMED_HOME": - self._state = STATE_ALARM_ARMED_HOME - elif status == "ARMED_AWAY": - self._state = STATE_ALARM_ARMED_AWAY - elif status != "PENDING": - LOGGER.error("Unknown alarm state %s", status) - self._changed_by = hub.get_first("$.armState.name") + async def _async_set_arm_state(self, state: str, code: str | None = None) -> None: + """Send set arm state command.""" + arm_state = await self.hass.async_add_executor_job( + self.coordinator.session.set_arm_state, code, state + ) + LOGGER.debug("Verisure set arm state %s", state) + transaction = {} + while "result" not in transaction: + await asyncio.sleep(0.5) + transaction = await self.hass.async_add_executor_job( + self.coordinator.session.get_arm_state_transaction, + arm_state["armStateChangeTransactionId"], + ) - def alarm_disarm(self, code: str | None = None) -> None: + await self.coordinator.async_refresh() + + async def async_alarm_disarm(self, code: str | None = None) -> None: """Send disarm command.""" - set_arm_state("DISARMED", code) + await self._async_set_arm_state("DISARMED", code) - def alarm_arm_home(self, code: str | None = None) -> None: + async def async_alarm_arm_home(self, code: str | None = None) -> None: """Send arm home command.""" - set_arm_state("ARMED_HOME", code) + await self._async_set_arm_state("ARMED_HOME", code) - def alarm_arm_away(self, code: str | None = None) -> None: + async def async_alarm_arm_away(self, code: str | None = None) -> None: """Send arm away command.""" - set_arm_state("ARMED_AWAY", code) + await self._async_set_arm_state("ARMED_AWAY", code) diff --git a/homeassistant/components/verisure/binary_sensor.py b/homeassistant/components/verisure/binary_sensor.py index e30f008dba9..bdefa2af858 100644 --- a/homeassistant/components/verisure/binary_sensor.py +++ b/homeassistant/components/verisure/binary_sensor.py @@ -9,8 +9,9 @@ from homeassistant.components.binary_sensor import ( ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import CONF_DOOR_WINDOW, HUB as hub +from . import CONF_DOOR_WINDOW, DOMAIN, VerisureDataUpdateCoordinator def setup_platform( @@ -20,34 +21,39 @@ def setup_platform( discovery_info: dict[str, Any] | None = None, ) -> None: """Set up the Verisure binary sensors.""" - sensors = [] - hub.update_overview() + coordinator = hass.data[DOMAIN] - if int(hub.config.get(CONF_DOOR_WINDOW, 1)): + sensors = [VerisureEthernetStatus(coordinator)] + + if int(coordinator.config.get(CONF_DOOR_WINDOW, 1)): sensors.extend( [ - VerisureDoorWindowSensor(device_label) - for device_label in hub.get( + VerisureDoorWindowSensor(coordinator, device_label) + for device_label in coordinator.get( "$.doorWindow.doorWindowDevice[*].deviceLabel" ) ] ) - sensors.extend([VerisureEthernetStatus()]) add_entities(sensors) -class VerisureDoorWindowSensor(BinarySensorEntity): +class VerisureDoorWindowSensor(CoordinatorEntity, BinarySensorEntity): """Representation of a Verisure door window sensor.""" - def __init__(self, device_label: str): + coordinator: VerisureDataUpdateCoordinator + + def __init__( + self, coordinator: VerisureDataUpdateCoordinator, device_label: str + ) -> None: """Initialize the Verisure door window sensor.""" + super().__init__(coordinator) self._device_label = device_label @property def name(self) -> str: """Return the name of the binary sensor.""" - return hub.get_first( + return self.coordinator.get_first( "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].area", self._device_label, ) @@ -56,7 +62,7 @@ class VerisureDoorWindowSensor(BinarySensorEntity): def is_on(self) -> bool: """Return the state of the sensor.""" return ( - hub.get_first( + self.coordinator.get_first( "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].state", self._device_label, ) @@ -67,22 +73,19 @@ class VerisureDoorWindowSensor(BinarySensorEntity): def available(self) -> bool: """Return True if entity is available.""" return ( - hub.get_first( + self.coordinator.get_first( "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')]", self._device_label, ) is not None ) - # pylint: disable=no-self-use - def update(self) -> None: - """Update the state of the sensor.""" - hub.update_overview() - -class VerisureEthernetStatus(BinarySensorEntity): +class VerisureEthernetStatus(CoordinatorEntity, BinarySensorEntity): """Representation of a Verisure VBOX internet status.""" + coordinator: VerisureDataUpdateCoordinator + @property def name(self) -> str: """Return the name of the binary sensor.""" @@ -91,17 +94,12 @@ class VerisureEthernetStatus(BinarySensorEntity): @property def is_on(self) -> bool: """Return the state of the sensor.""" - return hub.get_first("$.ethernetConnectedNow") + return self.coordinator.get_first("$.ethernetConnectedNow") @property def available(self) -> bool: """Return True if entity is available.""" - return hub.get_first("$.ethernetConnectedNow") is not None - - # pylint: disable=no-self-use - def update(self) -> None: - """Update the state of the sensor.""" - hub.update_overview() + return self.coordinator.get_first("$.ethernetConnectedNow") is not None @property def device_class(self) -> str: diff --git a/homeassistant/components/verisure/camera.py b/homeassistant/components/verisure/camera.py index ad6840c0614..4e15b7a88b2 100644 --- a/homeassistant/components/verisure/camera.py +++ b/homeassistant/components/verisure/camera.py @@ -3,15 +3,16 @@ from __future__ import annotations import errno import os -from typing import Any, Callable, Literal +from typing import Any, Callable from homeassistant.components.camera import Camera from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import HUB as hub -from .const import CONF_SMARTCAM, LOGGER +from . import VerisureDataUpdateCoordinator +from .const import CONF_SMARTCAM, DOMAIN, LOGGER def setup_platform( @@ -19,31 +20,39 @@ def setup_platform( config: dict[str, Any], add_entities: Callable[[list[Entity], bool], None], discovery_info: dict[str, Any] | None = None, -) -> None | Literal[False]: +) -> None: """Set up the Verisure Camera.""" - if not int(hub.config.get(CONF_SMARTCAM, 1)): - return False + coordinator = hass.data[DOMAIN] + if not int(coordinator.config.get(CONF_SMARTCAM, 1)): + return directory_path = hass.config.config_dir if not os.access(directory_path, os.R_OK): LOGGER.error("file path %s is not readable", directory_path) - return False + return - hub.update_overview() - smartcams = [ - VerisureSmartcam(hass, device_label, directory_path) - for device_label in hub.get("$.customerImageCameras[*].deviceLabel") - ] - - add_entities(smartcams) + add_entities( + [ + VerisureSmartcam(hass, coordinator, device_label, directory_path) + for device_label in coordinator.get("$.customerImageCameras[*].deviceLabel") + ] + ) -class VerisureSmartcam(Camera): +class VerisureSmartcam(CoordinatorEntity, Camera): """Representation of a Verisure camera.""" - def __init__(self, hass: HomeAssistant, device_label: str, directory_path: str): + coordinator = VerisureDataUpdateCoordinator + + def __init__( + self, + hass: HomeAssistant, + coordinator: VerisureDataUpdateCoordinator, + device_label: str, + directory_path: str, + ): """Initialize Verisure File Camera component.""" - super().__init__() + super().__init__(coordinator) self._device_label = device_label self._directory_path = directory_path @@ -63,8 +72,8 @@ class VerisureSmartcam(Camera): def check_imagelist(self) -> None: """Check the contents of the image list.""" - hub.update_smartcam_imageseries() - image_ids = hub.get_image_info( + self.coordinator.update_smartcam_imageseries() + image_ids = self.coordinator.get_image_info( "$.imageSeries[?(@.deviceLabel=='%s')].image[0].imageId", self._device_label ) if not image_ids: @@ -77,7 +86,9 @@ class VerisureSmartcam(Camera): new_image_path = os.path.join( self._directory_path, "{}{}".format(new_image_id, ".jpg") ) - hub.session.download_image(self._device_label, new_image_id, new_image_path) + self.coordinator.session.download_image( + self._device_label, new_image_id, new_image_path + ) LOGGER.debug("Old image_id=%s", self._image_id) self.delete_image() @@ -99,6 +110,6 @@ class VerisureSmartcam(Camera): @property def name(self) -> str: """Return the name of this camera.""" - return hub.get_first( + return self.coordinator.get_first( "$.customerImageCameras[?(@.deviceLabel=='%s')].area", self._device_label ) diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index b2e1cfb3db0..8fc067308fb 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -1,16 +1,17 @@ """Support for Verisure locks.""" from __future__ import annotations -from time import monotonic, sleep +import asyncio from typing import Any, Callable from homeassistant.components.lock import LockEntity from homeassistant.const import ATTR_CODE, STATE_LOCKED, STATE_UNLOCKED from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import HUB as hub -from .const import CONF_CODE_DIGITS, CONF_DEFAULT_LOCK_CODE, CONF_LOCKS, LOGGER +from . import VerisureDataUpdateCoordinator +from .const import CONF_CODE_DIGITS, CONF_DEFAULT_LOCK_CODE, CONF_LOCKS, DOMAIN, LOGGER def setup_platform( @@ -20,48 +21,48 @@ def setup_platform( discovery_info: dict[str, Any] | None = None, ) -> None: """Set up the Verisure lock platform.""" + coordinator = hass.data[DOMAIN] locks = [] - if int(hub.config.get(CONF_LOCKS, 1)): - hub.update_overview() + if int(coordinator.config.get(CONF_LOCKS, 1)): locks.extend( [ - VerisureDoorlock(device_label) - for device_label in hub.get("$.doorLockStatusList[*].deviceLabel") + VerisureDoorlock(coordinator, device_label) + for device_label in coordinator.get( + "$.doorLockStatusList[*].deviceLabel" + ) ] ) add_entities(locks) -class VerisureDoorlock(LockEntity): +class VerisureDoorlock(CoordinatorEntity, LockEntity): """Representation of a Verisure doorlock.""" - def __init__(self, device_label: str): + coordinator: VerisureDataUpdateCoordinator + + def __init__( + self, coordinator: VerisureDataUpdateCoordinator, device_label: str + ) -> None: """Initialize the Verisure lock.""" + super().__init__(coordinator) self._device_label = device_label self._state = None - self._digits = hub.config.get(CONF_CODE_DIGITS) - self._changed_by = None - self._change_timestamp = 0 - self._default_lock_code = hub.config.get(CONF_DEFAULT_LOCK_CODE) + self._digits = coordinator.config.get(CONF_CODE_DIGITS) + self._default_lock_code = coordinator.config.get(CONF_DEFAULT_LOCK_CODE) @property def name(self) -> str: """Return the name of the lock.""" - return hub.get_first( + return self.coordinator.get_first( "$.doorLockStatusList[?(@.deviceLabel=='%s')].area", self._device_label ) - @property - def state(self) -> str | None: - """Return the state of the lock.""" - return self._state - @property def available(self) -> bool: """Return True if entity is available.""" return ( - hub.get_first( + self.coordinator.get_first( "$.doorLockStatusList[?(@.deviceLabel=='%s')]", self._device_label ) is not None @@ -70,78 +71,65 @@ class VerisureDoorlock(LockEntity): @property def changed_by(self) -> str | None: """Last change triggered by.""" - return self._changed_by + return self.coordinator.get_first( + "$.doorLockStatusList[?(@.deviceLabel=='%s')].userString", + self._device_label, + ) @property def code_format(self) -> str: """Return the required six digit code.""" return "^\\d{%s}$" % self._digits - def update(self) -> None: - """Update lock status.""" - if monotonic() - self._change_timestamp < 10: - return - hub.update_overview() - status = hub.get_first( - "$.doorLockStatusList[?(@.deviceLabel=='%s')].lockedState", - self._device_label, - ) - if status == "UNLOCKED": - self._state = STATE_UNLOCKED - elif status == "LOCKED": - self._state = STATE_LOCKED - elif status != "PENDING": - LOGGER.error("Unknown lock state %s", status) - self._changed_by = hub.get_first( - "$.doorLockStatusList[?(@.deviceLabel=='%s')].userString", - self._device_label, - ) - @property def is_locked(self) -> bool: """Return true if lock is locked.""" - return self._state == STATE_LOCKED + status = self.coordinator.get_first( + "$.doorLockStatusList[?(@.deviceLabel=='%s')].lockedState", + self._device_label, + ) + return status == "LOCKED" - def unlock(self, **kwargs) -> None: + async def async_unlock(self, **kwargs) -> None: """Send unlock command.""" - if self._state is None: - return - code = kwargs.get(ATTR_CODE, self._default_lock_code) if code is None: LOGGER.error("Code required but none provided") return - self.set_lock_state(code, STATE_UNLOCKED) + await self.async_set_lock_state(code, STATE_UNLOCKED) - def lock(self, **kwargs) -> None: + async def async_lock(self, **kwargs) -> None: """Send lock command.""" - if self._state == STATE_LOCKED: - return - code = kwargs.get(ATTR_CODE, self._default_lock_code) if code is None: LOGGER.error("Code required but none provided") return - self.set_lock_state(code, STATE_LOCKED) + await self.async_set_lock_state(code, STATE_LOCKED) - def set_lock_state(self, code: str, state: str) -> None: + async def async_set_lock_state(self, code: str, state: str) -> None: """Send set lock state command.""" - lock_state = "lock" if state == STATE_LOCKED else "unlock" - transaction_id = hub.session.set_lock_state( - code, self._device_label, lock_state - )["doorLockStateChangeTransactionId"] + target_state = "lock" if state == STATE_LOCKED else "unlock" + lock_state = await self.hass.async_add_executor_job( + self.coordinator.session.set_lock_state, + code, + self._device_label, + target_state, + ) + LOGGER.debug("Verisure doorlock %s", state) transaction = {} attempts = 0 while "result" not in transaction: - transaction = hub.session.get_lock_state_transaction(transaction_id) + transaction = await self.hass.async_add_executor_job( + self.coordinator.session.get_lock_state_transaction, + lock_state["doorLockStateChangeTransactionId"], + ) attempts += 1 if attempts == 30: break if attempts > 1: - sleep(0.5) + await asyncio.sleep(0.5) if transaction["result"] == "OK": self._state = state - self._change_timestamp = monotonic() diff --git a/homeassistant/components/verisure/sensor.py b/homeassistant/components/verisure/sensor.py index 6582dfc409a..483d03a1bb5 100644 --- a/homeassistant/components/verisure/sensor.py +++ b/homeassistant/components/verisure/sensor.py @@ -6,9 +6,10 @@ from typing import Any, Callable from homeassistant.const import PERCENTAGE, TEMP_CELSIUS from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import HUB as hub -from .const import CONF_HYDROMETERS, CONF_MOUSE, CONF_THERMOMETERS +from . import VerisureDataUpdateCoordinator +from .const import CONF_HYDROMETERS, CONF_MOUSE, CONF_THERMOMETERS, DOMAIN def setup_platform( @@ -18,34 +19,34 @@ def setup_platform( discovery_info: dict[str, Any] | None = None, ) -> None: """Set up the Verisure platform.""" - sensors = [] - hub.update_overview() + coordinator = hass.data[DOMAIN] - if int(hub.config.get(CONF_THERMOMETERS, 1)): + sensors = [] + if int(coordinator.config.get(CONF_THERMOMETERS, 1)): sensors.extend( [ - VerisureThermometer(device_label) - for device_label in hub.get( + VerisureThermometer(coordinator, device_label) + for device_label in coordinator.get( "$.climateValues[?(@.temperature)].deviceLabel" ) ] ) - if int(hub.config.get(CONF_HYDROMETERS, 1)): + if int(coordinator.config.get(CONF_HYDROMETERS, 1)): sensors.extend( [ - VerisureHygrometer(device_label) - for device_label in hub.get( + VerisureHygrometer(coordinator, device_label) + for device_label in coordinator.get( "$.climateValues[?(@.humidity)].deviceLabel" ) ] ) - if int(hub.config.get(CONF_MOUSE, 1)): + if int(coordinator.config.get(CONF_MOUSE, 1)): sensors.extend( [ - VerisureMouseDetection(device_label) - for device_label in hub.get( + VerisureMouseDetection(coordinator, device_label) + for device_label in coordinator.get( "$.eventCounts[?(@.deviceType=='MOUSE1')].deviceLabel" ) ] @@ -54,18 +55,23 @@ def setup_platform( add_entities(sensors) -class VerisureThermometer(Entity): +class VerisureThermometer(CoordinatorEntity, Entity): """Representation of a Verisure thermometer.""" - def __init__(self, device_label: str): + coordinator: VerisureDataUpdateCoordinator + + def __init__( + self, coordinator: VerisureDataUpdateCoordinator, device_label: str + ) -> None: """Initialize the sensor.""" + super().__init__(coordinator) self._device_label = device_label @property def name(self) -> str: """Return the name of the device.""" return ( - hub.get_first( + self.coordinator.get_first( "$.climateValues[?(@.deviceLabel=='%s')].deviceArea", self._device_label ) + " temperature" @@ -74,7 +80,7 @@ class VerisureThermometer(Entity): @property def state(self) -> str | None: """Return the state of the device.""" - return hub.get_first( + return self.coordinator.get_first( "$.climateValues[?(@.deviceLabel=='%s')].temperature", self._device_label ) @@ -82,7 +88,7 @@ class VerisureThermometer(Entity): def available(self) -> bool: """Return True if entity is available.""" return ( - hub.get_first( + self.coordinator.get_first( "$.climateValues[?(@.deviceLabel=='%s')].temperature", self._device_label, ) @@ -94,24 +100,24 @@ class VerisureThermometer(Entity): """Return the unit of measurement of this entity.""" return TEMP_CELSIUS - # pylint: disable=no-self-use - def update(self) -> None: - """Update the sensor.""" - hub.update_overview() - -class VerisureHygrometer(Entity): +class VerisureHygrometer(CoordinatorEntity, Entity): """Representation of a Verisure hygrometer.""" - def __init__(self, device_label: str): + coordinator: VerisureDataUpdateCoordinator + + def __init__( + self, coordinator: VerisureDataUpdateCoordinator, device_label: str + ) -> None: """Initialize the sensor.""" + super().__init__(coordinator) self._device_label = device_label @property def name(self) -> str: """Return the name of the device.""" return ( - hub.get_first( + self.coordinator.get_first( "$.climateValues[?(@.deviceLabel=='%s')].deviceArea", self._device_label ) + " humidity" @@ -120,7 +126,7 @@ class VerisureHygrometer(Entity): @property def state(self) -> str | None: """Return the state of the device.""" - return hub.get_first( + return self.coordinator.get_first( "$.climateValues[?(@.deviceLabel=='%s')].humidity", self._device_label ) @@ -128,7 +134,7 @@ class VerisureHygrometer(Entity): def available(self) -> bool: """Return True if entity is available.""" return ( - hub.get_first( + self.coordinator.get_first( "$.climateValues[?(@.deviceLabel=='%s')].humidity", self._device_label ) is not None @@ -139,24 +145,24 @@ class VerisureHygrometer(Entity): """Return the unit of measurement of this entity.""" return PERCENTAGE - # pylint: disable=no-self-use - def update(self) -> None: - """Update the sensor.""" - hub.update_overview() - -class VerisureMouseDetection(Entity): +class VerisureMouseDetection(CoordinatorEntity, Entity): """Representation of a Verisure mouse detector.""" - def __init__(self, device_label): + coordinator: VerisureDataUpdateCoordinator + + def __init__( + self, coordinator: VerisureDataUpdateCoordinator, device_label: str + ) -> None: """Initialize the sensor.""" + super().__init__(coordinator) self._device_label = device_label @property def name(self) -> str: """Return the name of the device.""" return ( - hub.get_first( + self.coordinator.get_first( "$.eventCounts[?(@.deviceLabel=='%s')].area", self._device_label ) + " mouse" @@ -165,7 +171,7 @@ class VerisureMouseDetection(Entity): @property def state(self) -> str | None: """Return the state of the device.""" - return hub.get_first( + return self.coordinator.get_first( "$.eventCounts[?(@.deviceLabel=='%s')].detections", self._device_label ) @@ -173,7 +179,9 @@ class VerisureMouseDetection(Entity): def available(self) -> bool: """Return True if entity is available.""" return ( - hub.get_first("$.eventCounts[?(@.deviceLabel=='%s')]", self._device_label) + self.coordinator.get_first( + "$.eventCounts[?(@.deviceLabel=='%s')]", self._device_label + ) is not None ) @@ -181,8 +189,3 @@ class VerisureMouseDetection(Entity): def unit_of_measurement(self) -> str: """Return the unit of measurement of this entity.""" return "Mice" - - # pylint: disable=no-self-use - def update(self) -> None: - """Update the sensor.""" - hub.update_overview() diff --git a/homeassistant/components/verisure/switch.py b/homeassistant/components/verisure/switch.py index e5e19bd6d13..9329d94331a 100644 --- a/homeassistant/components/verisure/switch.py +++ b/homeassistant/components/verisure/switch.py @@ -2,13 +2,15 @@ from __future__ import annotations from time import monotonic -from typing import Any, Callable, Literal +from typing import Any, Callable from homeassistant.components.switch import SwitchEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import CONF_SMARTPLUGS, HUB as hub +from . import VerisureDataUpdateCoordinator +from .const import CONF_SMARTPLUGS, DOMAIN def setup_platform( @@ -16,25 +18,31 @@ def setup_platform( config: dict[str, Any], add_entities: Callable[[list[Entity], bool], None], discovery_info: dict[str, Any] | None = None, -) -> None | Literal[False]: +) -> None: """Set up the Verisure switch platform.""" - if not int(hub.config.get(CONF_SMARTPLUGS, 1)): - return False + coordinator = hass.data[DOMAIN] - hub.update_overview() - switches = [ - VerisureSmartplug(device_label) - for device_label in hub.get("$.smartPlugs[*].deviceLabel") - ] + if not int(coordinator.config.get(CONF_SMARTPLUGS, 1)): + return - add_entities(switches) + add_entities( + [ + VerisureSmartplug(coordinator, device_label) + for device_label in coordinator.get("$.smartPlugs[*].deviceLabel") + ] + ) -class VerisureSmartplug(SwitchEntity): +class VerisureSmartplug(CoordinatorEntity, SwitchEntity): """Representation of a Verisure smartplug.""" - def __init__(self, device_id: str): + coordinator: VerisureDataUpdateCoordinator + + def __init__( + self, coordinator: VerisureDataUpdateCoordinator, device_id: str + ) -> None: """Initialize the Verisure device.""" + super().__init__(coordinator) self._device_label = device_id self._change_timestamp = 0 self._state = False @@ -42,7 +50,7 @@ class VerisureSmartplug(SwitchEntity): @property def name(self) -> str: """Return the name or location of the smartplug.""" - return hub.get_first( + return self.coordinator.get_first( "$.smartPlugs[?(@.deviceLabel == '%s')].area", self._device_label ) @@ -52,7 +60,7 @@ class VerisureSmartplug(SwitchEntity): if monotonic() - self._change_timestamp < 10: return self._state self._state = ( - hub.get_first( + self.coordinator.get_first( "$.smartPlugs[?(@.deviceLabel == '%s')].currentState", self._device_label, ) @@ -64,23 +72,20 @@ class VerisureSmartplug(SwitchEntity): def available(self) -> bool: """Return True if entity is available.""" return ( - hub.get_first("$.smartPlugs[?(@.deviceLabel == '%s')]", self._device_label) + self.coordinator.get_first( + "$.smartPlugs[?(@.deviceLabel == '%s')]", self._device_label + ) is not None ) def turn_on(self, **kwargs) -> None: """Set smartplug status on.""" - hub.session.set_smartplug_state(self._device_label, True) + self.coordinator.session.set_smartplug_state(self._device_label, True) self._state = True self._change_timestamp = monotonic() def turn_off(self, **kwargs) -> None: """Set smartplug status off.""" - hub.session.set_smartplug_state(self._device_label, False) + self.coordinator.session.set_smartplug_state(self._device_label, False) self._state = False self._change_timestamp = monotonic() - - # pylint: disable=no-self-use - def update(self) -> None: - """Get the latest date of the smartplug.""" - hub.update_overview() diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 94f1bbba464..2b77de8022b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1166,9 +1166,6 @@ uvcclient==0.11.0 # homeassistant.components.vilfo vilfo-api-client==0.3.2 -# homeassistant.components.verisure -vsure==1.7.2 - # homeassistant.components.vultr vultr==0.1.2 diff --git a/tests/components/verisure/__init__.py b/tests/components/verisure/__init__.py deleted file mode 100644 index 0382661dbe3..00000000000 --- a/tests/components/verisure/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for Verisure integration.""" diff --git a/tests/components/verisure/test_ethernet_status.py b/tests/components/verisure/test_ethernet_status.py deleted file mode 100644 index 611adde19d9..00000000000 --- a/tests/components/verisure/test_ethernet_status.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Test Verisure ethernet status.""" -from contextlib import contextmanager -from unittest.mock import patch - -from homeassistant.components.verisure import DOMAIN as VERISURE_DOMAIN -from homeassistant.const import STATE_UNAVAILABLE -from homeassistant.setup import async_setup_component - -CONFIG = { - "verisure": { - "username": "test", - "password": "test", - "alarm": False, - "door_window": False, - "hygrometers": False, - "mouse": False, - "smartplugs": False, - "thermometers": False, - "smartcam": False, - } -} - - -@contextmanager -def mock_hub(config, response): - """Extensively mock out a verisure hub.""" - hub_prefix = "homeassistant.components.verisure.binary_sensor.hub" - verisure_prefix = "verisure.Session" - with patch(verisure_prefix) as session, patch(hub_prefix) as hub: - session.login.return_value = True - - hub.config = config["verisure"] - hub.get.return_value = response - hub.get_first.return_value = response.get("ethernetConnectedNow", None) - - yield hub - - -async def setup_verisure(hass, config, response): - """Set up mock verisure.""" - with mock_hub(config, response): - await async_setup_component(hass, VERISURE_DOMAIN, config) - await hass.async_block_till_done() - - -async def test_verisure_no_ethernet_status(hass): - """Test no data from API.""" - await setup_verisure(hass, CONFIG, {}) - assert len(hass.states.async_all()) == 1 - entity_id = hass.states.async_entity_ids()[0] - assert hass.states.get(entity_id).state == STATE_UNAVAILABLE - - -async def test_verisure_ethernet_status_disconnected(hass): - """Test disconnected.""" - await setup_verisure(hass, CONFIG, {"ethernetConnectedNow": False}) - assert len(hass.states.async_all()) == 1 - entity_id = hass.states.async_entity_ids()[0] - assert hass.states.get(entity_id).state == "off" - - -async def test_verisure_ethernet_status_connected(hass): - """Test connected.""" - await setup_verisure(hass, CONFIG, {"ethernetConnectedNow": True}) - assert len(hass.states.async_all()) == 1 - entity_id = hass.states.async_entity_ids()[0] - assert hass.states.get(entity_id).state == "on" diff --git a/tests/components/verisure/test_lock.py b/tests/components/verisure/test_lock.py deleted file mode 100644 index d41bbab2037..00000000000 --- a/tests/components/verisure/test_lock.py +++ /dev/null @@ -1,144 +0,0 @@ -"""Tests for the Verisure platform.""" - -from contextlib import contextmanager -from unittest.mock import call, patch - -from homeassistant.components.lock import ( - DOMAIN as LOCK_DOMAIN, - SERVICE_LOCK, - SERVICE_UNLOCK, -) -from homeassistant.components.verisure import DOMAIN as VERISURE_DOMAIN -from homeassistant.const import STATE_UNLOCKED -from homeassistant.setup import async_setup_component - -NO_DEFAULT_LOCK_CODE_CONFIG = { - "verisure": { - "username": "test", - "password": "test", - "locks": True, - "alarm": False, - "door_window": False, - "hygrometers": False, - "mouse": False, - "smartplugs": False, - "thermometers": False, - "smartcam": False, - } -} - -DEFAULT_LOCK_CODE_CONFIG = { - "verisure": { - "username": "test", - "password": "test", - "locks": True, - "default_lock_code": "9999", - "alarm": False, - "door_window": False, - "hygrometers": False, - "mouse": False, - "smartplugs": False, - "thermometers": False, - "smartcam": False, - } -} - -LOCKS = ["door_lock"] - - -@contextmanager -def mock_hub(config, get_response=LOCKS[0]): - """Extensively mock out a verisure hub.""" - hub_prefix = "homeassistant.components.verisure.lock.hub" - # Since there is no conf to disable ethernet status, mock hub for - # binary sensor too - hub_binary_sensor = "homeassistant.components.verisure.binary_sensor.hub" - verisure_prefix = "verisure.Session" - with patch(verisure_prefix) as session, patch(hub_prefix) as hub: - session.login.return_value = True - - hub.config = config["verisure"] - hub.get.return_value = LOCKS - hub.get_first.return_value = get_response.upper() - hub.session.set_lock_state.return_value = { - "doorLockStateChangeTransactionId": "test" - } - hub.session.get_lock_state_transaction.return_value = {"result": "OK"} - - with patch(hub_binary_sensor, hub): - yield hub - - -async def setup_verisure_locks(hass, config): - """Set up mock verisure locks.""" - with mock_hub(config): - await async_setup_component(hass, VERISURE_DOMAIN, config) - await hass.async_block_till_done() - # lock.door_lock, ethernet_status - assert len(hass.states.async_all()) == 2 - - -async def test_verisure_no_default_code(hass): - """Test configs without a default lock code.""" - await setup_verisure_locks(hass, NO_DEFAULT_LOCK_CODE_CONFIG) - with mock_hub(NO_DEFAULT_LOCK_CODE_CONFIG, STATE_UNLOCKED) as hub: - - mock = hub.session.set_lock_state - await hass.services.async_call( - LOCK_DOMAIN, SERVICE_LOCK, {"entity_id": "lock.door_lock"} - ) - await hass.async_block_till_done() - assert mock.call_count == 0 - - await hass.services.async_call( - LOCK_DOMAIN, SERVICE_LOCK, {"entity_id": "lock.door_lock", "code": "12345"} - ) - await hass.async_block_till_done() - assert mock.call_args == call("12345", LOCKS[0], "lock") - - mock.reset_mock() - await hass.services.async_call( - LOCK_DOMAIN, SERVICE_UNLOCK, {"entity_id": "lock.door_lock"} - ) - await hass.async_block_till_done() - assert mock.call_count == 0 - - await hass.services.async_call( - LOCK_DOMAIN, - SERVICE_UNLOCK, - {"entity_id": "lock.door_lock", "code": "12345"}, - ) - await hass.async_block_till_done() - assert mock.call_args == call("12345", LOCKS[0], "unlock") - - -async def test_verisure_default_code(hass): - """Test configs with a default lock code.""" - await setup_verisure_locks(hass, DEFAULT_LOCK_CODE_CONFIG) - with mock_hub(DEFAULT_LOCK_CODE_CONFIG, STATE_UNLOCKED) as hub: - mock = hub.session.set_lock_state - await hass.services.async_call( - LOCK_DOMAIN, SERVICE_LOCK, {"entity_id": "lock.door_lock"} - ) - await hass.async_block_till_done() - assert mock.call_args == call("9999", LOCKS[0], "lock") - - await hass.services.async_call( - LOCK_DOMAIN, SERVICE_UNLOCK, {"entity_id": "lock.door_lock"} - ) - await hass.async_block_till_done() - assert mock.call_args == call("9999", LOCKS[0], "unlock") - - await hass.services.async_call( - LOCK_DOMAIN, SERVICE_LOCK, {"entity_id": "lock.door_lock", "code": "12345"} - ) - await hass.async_block_till_done() - assert mock.call_args == call("12345", LOCKS[0], "lock") - - await hass.services.async_call( - LOCK_DOMAIN, - SERVICE_UNLOCK, - {"entity_id": "lock.door_lock", "code": "12345"}, - ) - await hass.async_block_till_done() - assert mock.call_args == call("12345", LOCKS[0], "unlock") From 14a59d290a3f25e58a705a156945e3853257d9c4 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 11 Mar 2021 20:11:25 +0100 Subject: [PATCH 303/831] Update integrations j-o to override extra_state_attributes() (#47758) --- homeassistant/components/homematic/entity.py | 2 +- homeassistant/components/isy994/entity.py | 4 ++-- .../components/jewish_calendar/sensor.py | 4 ++-- .../components/kaiterra/air_quality.py | 2 +- .../components/keba/binary_sensor.py | 2 +- homeassistant/components/keba/sensor.py | 2 +- .../keenetic_ndms2/device_tracker.py | 2 +- homeassistant/components/kef/media_player.py | 2 +- homeassistant/components/kira/sensor.py | 2 +- homeassistant/components/kiwi/lock.py | 2 +- homeassistant/components/knx/binary_sensor.py | 2 +- homeassistant/components/lacrosse/sensor.py | 2 +- homeassistant/components/lastfm/sensor.py | 2 +- .../components/launch_library/sensor.py | 2 +- .../components/linode/binary_sensor.py | 2 +- homeassistant/components/linode/switch.py | 2 +- .../components/linux_battery/sensor.py | 2 +- homeassistant/components/litejet/light.py | 2 +- homeassistant/components/litejet/scene.py | 2 +- homeassistant/components/litejet/switch.py | 2 +- .../components/litterrobot/vacuum.py | 2 +- homeassistant/components/local_file/camera.py | 2 +- .../components/logi_circle/camera.py | 2 +- .../components/logi_circle/sensor.py | 2 +- homeassistant/components/london_air/sensor.py | 2 +- .../components/london_underground/sensor.py | 2 +- homeassistant/components/luftdaten/sensor.py | 2 +- .../components/lutron/binary_sensor.py | 2 +- homeassistant/components/lutron/cover.py | 2 +- homeassistant/components/lutron/light.py | 2 +- homeassistant/components/lutron/switch.py | 4 ++-- .../components/lutron_caseta/__init__.py | 2 +- .../components/lutron_caseta/binary_sensor.py | 2 +- homeassistant/components/lyft/sensor.py | 2 +- .../components/magicseaweed/sensor.py | 2 +- .../components/manual/alarm_control_panel.py | 2 +- .../manual_mqtt/alarm_control_panel.py | 2 +- homeassistant/components/maxcube/climate.py | 2 +- homeassistant/components/melcloud/climate.py | 4 ++-- .../components/melcloud/water_heater.py | 2 +- .../components/meteo_france/sensor.py | 6 ++--- .../components/meteoalarm/binary_sensor.py | 2 +- homeassistant/components/metoffice/sensor.py | 2 +- homeassistant/components/mfi/switch.py | 2 +- homeassistant/components/mhz19/sensor.py | 2 +- .../components/microsoft_face/__init__.py | 2 +- homeassistant/components/miflora/sensor.py | 2 +- .../components/mikrotik/device_tracker.py | 2 +- homeassistant/components/mill/climate.py | 2 +- homeassistant/components/min_max/sensor.py | 2 +- .../components/minecraft_server/__init__.py | 2 +- .../components/minecraft_server/sensor.py | 10 ++++---- .../components/mobile_app/device_tracker.py | 2 +- homeassistant/components/mobile_app/entity.py | 2 +- .../components/modem_callerid/sensor.py | 2 +- .../components/mold_indicator/sensor.py | 2 +- .../components/motion_blinds/cover.py | 2 +- .../components/motion_blinds/sensor.py | 4 ++-- .../mqtt/device_tracker/schema_discovery.py | 24 +++++++++---------- homeassistant/components/mqtt/mixins.py | 2 +- homeassistant/components/mqtt_room/sensor.py | 2 +- homeassistant/components/mvglive/sensor.py | 2 +- homeassistant/components/mychevy/sensor.py | 2 +- homeassistant/components/mysensors/device.py | 2 +- .../components/mysensors/device_tracker.py | 2 +- homeassistant/components/n26/sensor.py | 6 ++--- homeassistant/components/neato/camera.py | 2 +- homeassistant/components/neato/vacuum.py | 2 +- .../nederlandse_spoorwegen/sensor.py | 2 +- homeassistant/components/nello/lock.py | 2 +- homeassistant/components/netatmo/camera.py | 2 +- homeassistant/components/netatmo/climate.py | 2 +- homeassistant/components/netatmo/sensor.py | 2 +- homeassistant/components/nexia/climate.py | 4 ++-- homeassistant/components/nexia/entity.py | 2 +- homeassistant/components/nexia/scene.py | 4 ++-- homeassistant/components/nextbus/sensor.py | 2 +- homeassistant/components/nightscout/sensor.py | 2 +- homeassistant/components/nilu/air_quality.py | 2 +- .../components/nissan_leaf/__init__.py | 2 +- .../components/nissan_leaf/switch.py | 4 ++-- homeassistant/components/nmbs/sensor.py | 4 ++-- homeassistant/components/noaa_tides/sensor.py | 2 +- .../components/norway_air/air_quality.py | 2 +- homeassistant/components/notion/__init__.py | 2 +- .../components/nsw_fuel_station/sensor.py | 2 +- .../geo_location.py | 2 +- homeassistant/components/nuki/lock.py | 2 +- homeassistant/components/nut/sensor.py | 2 +- .../components/nx584/binary_sensor.py | 2 +- .../components/oasa_telematics/sensor.py | 2 +- homeassistant/components/ohmconnect/sensor.py | 2 +- homeassistant/components/omnilogic/common.py | 2 +- .../components/onewire/onewire_entities.py | 2 +- .../components/onkyo/media_player.py | 2 +- .../components/openexchangerates/sensor.py | 2 +- homeassistant/components/opengarage/cover.py | 12 +++++----- homeassistant/components/opensky/sensor.py | 2 +- homeassistant/components/openuv/__init__.py | 2 +- .../openweathermap/abstract_owm_sensor.py | 2 +- .../components/osramlightify/light.py | 2 +- homeassistant/components/ovo_energy/sensor.py | 8 +++---- .../components/owntracks/device_tracker.py | 2 +- homeassistant/components/ozw/climate.py | 4 ++-- homeassistant/components/ozw/entity.py | 2 +- homeassistant/components/ozw/sensor.py | 4 ++-- tests/components/kira/test_sensor.py | 2 +- tests/components/mfi/test_switch.py | 4 ++-- tests/components/mhz19/test_sensor.py | 4 ++-- tests/components/nx584/test_binary_sensor.py | 2 +- 110 files changed, 150 insertions(+), 150 deletions(-) diff --git a/homeassistant/components/homematic/entity.py b/homeassistant/components/homematic/entity.py index bb87d691fc0..3643871c607 100644 --- a/homeassistant/components/homematic/entity.py +++ b/homeassistant/components/homematic/entity.py @@ -71,7 +71,7 @@ class HMDevice(Entity): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" # Static attributes attr = { diff --git a/homeassistant/components/isy994/entity.py b/homeassistant/components/isy994/entity.py index a484b56b145..f3dbe579dd8 100644 --- a/homeassistant/components/isy994/entity.py +++ b/homeassistant/components/isy994/entity.py @@ -134,7 +134,7 @@ class ISYNodeEntity(ISYEntity): """Representation of a ISY Nodebase (Node/Group) entity.""" @property - def device_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> Dict: """Get the state attributes for the device. The 'aux_properties' in the pyisy Node class are combined with the @@ -186,7 +186,7 @@ class ISYProgramEntity(ISYEntity): self._actions = actions @property - def device_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> Dict: """Get the state attributes for the device.""" attr = {} if self._actions: diff --git a/homeassistant/components/jewish_calendar/sensor.py b/homeassistant/components/jewish_calendar/sensor.py index 6881f29b963..14336b1f935 100644 --- a/homeassistant/components/jewish_calendar/sensor.py +++ b/homeassistant/components/jewish_calendar/sensor.py @@ -111,7 +111,7 @@ class JewishCalendarSensor(Entity): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._type != "holiday": return {} @@ -153,7 +153,7 @@ class JewishCalendarTimeSensor(JewishCalendarSensor): return DEVICE_CLASS_TIMESTAMP @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {} diff --git a/homeassistant/components/kaiterra/air_quality.py b/homeassistant/components/kaiterra/air_quality.py index ae5df387884..68377d6b254 100644 --- a/homeassistant/components/kaiterra/air_quality.py +++ b/homeassistant/components/kaiterra/air_quality.py @@ -96,7 +96,7 @@ class KaiterraAirQuality(AirQualityEntity): return f"{self._device_id}_air_quality" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" data = {} attributes = [ diff --git a/homeassistant/components/keba/binary_sensor.py b/homeassistant/components/keba/binary_sensor.py index 3fed7bbf5ab..29292470155 100644 --- a/homeassistant/components/keba/binary_sensor.py +++ b/homeassistant/components/keba/binary_sensor.py @@ -71,7 +71,7 @@ class KebaBinarySensor(BinarySensorEntity): return self._is_on @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the binary sensor.""" return self._attributes diff --git a/homeassistant/components/keba/sensor.py b/homeassistant/components/keba/sensor.py index f7993c28393..ed85ccd06a6 100644 --- a/homeassistant/components/keba/sensor.py +++ b/homeassistant/components/keba/sensor.py @@ -114,7 +114,7 @@ class KebaSensor(Entity): return self._unit @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the binary sensor.""" return self._attributes diff --git a/homeassistant/components/keenetic_ndms2/device_tracker.py b/homeassistant/components/keenetic_ndms2/device_tracker.py index 9df222a326c..a8b0f943dd6 100644 --- a/homeassistant/components/keenetic_ndms2/device_tracker.py +++ b/homeassistant/components/keenetic_ndms2/device_tracker.py @@ -207,7 +207,7 @@ class KeeneticTracker(ScannerEntity): return self._router.available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if self.is_connected: return { diff --git a/homeassistant/components/kef/media_player.py b/homeassistant/components/kef/media_player.py index a36fe11ef65..5316568ab52 100644 --- a/homeassistant/components/kef/media_player.py +++ b/homeassistant/components/kef/media_player.py @@ -395,7 +395,7 @@ class KefMediaPlayer(MediaPlayerEntity): self._update_dsp_task_remover = None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the DSP settings of the KEF device.""" return self._dsp or {} diff --git a/homeassistant/components/kira/sensor.py b/homeassistant/components/kira/sensor.py index 2d6322918c7..a8c49d1c04b 100644 --- a/homeassistant/components/kira/sensor.py +++ b/homeassistant/components/kira/sensor.py @@ -55,7 +55,7 @@ class KiraReceiver(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return {CONF_DEVICE: self._device} diff --git a/homeassistant/components/kiwi/lock.py b/homeassistant/components/kiwi/lock.py index 047eaa1ed3c..6a94cc5a393 100644 --- a/homeassistant/components/kiwi/lock.py +++ b/homeassistant/components/kiwi/lock.py @@ -86,7 +86,7 @@ class KiwiLock(LockEntity): return self._state == STATE_LOCKED @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" return self._device_attrs diff --git a/homeassistant/components/knx/binary_sensor.py b/homeassistant/components/knx/binary_sensor.py index f7ec3e80fa1..ecb79664afd 100644 --- a/homeassistant/components/knx/binary_sensor.py +++ b/homeassistant/components/knx/binary_sensor.py @@ -38,7 +38,7 @@ class KNXBinarySensor(KnxEntity, BinarySensorEntity): return self._device.is_on() @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return device specific state attributes.""" if self._device.counter is not None: return {ATTR_COUNTER: self._device.counter} diff --git a/homeassistant/components/lacrosse/sensor.py b/homeassistant/components/lacrosse/sensor.py index f65c792ddb0..35cdcecddba 100644 --- a/homeassistant/components/lacrosse/sensor.py +++ b/homeassistant/components/lacrosse/sensor.py @@ -138,7 +138,7 @@ class LaCrosseSensor(Entity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attributes = { "low_battery": self._low_battery, diff --git a/homeassistant/components/lastfm/sensor.py b/homeassistant/components/lastfm/sensor.py index 56124e2c0fe..2f599ad37fd 100644 --- a/homeassistant/components/lastfm/sensor.py +++ b/homeassistant/components/lastfm/sensor.py @@ -107,7 +107,7 @@ class LastfmSensor(Entity): self._state = f"{now_playing.artist} - {now_playing.title}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/launch_library/sensor.py b/homeassistant/components/launch_library/sensor.py index ef816eef0ba..366cd4e7d44 100644 --- a/homeassistant/components/launch_library/sensor.py +++ b/homeassistant/components/launch_library/sensor.py @@ -76,7 +76,7 @@ class LaunchLibrarySensor(Entity): return "mdi:rocket" @property - def device_state_attributes(self) -> Optional[dict]: + def extra_state_attributes(self) -> Optional[dict]: """Return attributes for the sensor.""" if self.next_launch: return { diff --git a/homeassistant/components/linode/binary_sensor.py b/homeassistant/components/linode/binary_sensor.py index bb81a022891..70a15eaf4e0 100644 --- a/homeassistant/components/linode/binary_sensor.py +++ b/homeassistant/components/linode/binary_sensor.py @@ -75,7 +75,7 @@ class LinodeBinarySensor(BinarySensorEntity): return DEVICE_CLASS_MOVING @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the Linode Node.""" return self._attrs diff --git a/homeassistant/components/linode/switch.py b/homeassistant/components/linode/switch.py index c9207ec1be7..9002cb7bd11 100644 --- a/homeassistant/components/linode/switch.py +++ b/homeassistant/components/linode/switch.py @@ -67,7 +67,7 @@ class LinodeSwitch(SwitchEntity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the Linode Node.""" return self._attrs diff --git a/homeassistant/components/linux_battery/sensor.py b/homeassistant/components/linux_battery/sensor.py index f4d4e92cb78..feefa34a7a7 100644 --- a/homeassistant/components/linux_battery/sensor.py +++ b/homeassistant/components/linux_battery/sensor.py @@ -101,7 +101,7 @@ class LinuxBatterySensor(Entity): return PERCENTAGE @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" if self._system == "android": return { diff --git a/homeassistant/components/litejet/light.py b/homeassistant/components/litejet/light.py index 27ce904cc2c..5248afb4dbd 100644 --- a/homeassistant/components/litejet/light.py +++ b/homeassistant/components/litejet/light.py @@ -85,7 +85,7 @@ class LiteJetLight(LightEntity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return {ATTR_NUMBER: self._index} diff --git a/homeassistant/components/litejet/scene.py b/homeassistant/components/litejet/scene.py index daadfce90dc..5ae0aec9559 100644 --- a/homeassistant/components/litejet/scene.py +++ b/homeassistant/components/litejet/scene.py @@ -47,7 +47,7 @@ class LiteJetScene(Scene): return f"{self._entry_id}_{self._index}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device-specific state attributes.""" return {ATTR_NUMBER: self._index} diff --git a/homeassistant/components/litejet/switch.py b/homeassistant/components/litejet/switch.py index b782a4a9d98..343d8393f1c 100644 --- a/homeassistant/components/litejet/switch.py +++ b/homeassistant/components/litejet/switch.py @@ -77,7 +77,7 @@ class LiteJetSwitch(SwitchEntity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device-specific state attributes.""" return {ATTR_NUMBER: self._index} diff --git a/homeassistant/components/litterrobot/vacuum.py b/homeassistant/components/litterrobot/vacuum.py index 4fe76d446f4..a36ef656361 100644 --- a/homeassistant/components/litterrobot/vacuum.py +++ b/homeassistant/components/litterrobot/vacuum.py @@ -111,7 +111,7 @@ class LitterRobotCleaner(LitterRobotEntity, VacuumEntity): raise NotImplementedError() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return { "clean_cycle_wait_time_minutes": self.robot.clean_cycle_wait_time_minutes, diff --git a/homeassistant/components/local_file/camera.py b/homeassistant/components/local_file/camera.py index b0b84677183..c94aeff24b0 100644 --- a/homeassistant/components/local_file/camera.py +++ b/homeassistant/components/local_file/camera.py @@ -105,6 +105,6 @@ class LocalFile(Camera): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the camera state attributes.""" return {"file_path": self._file_path} diff --git a/homeassistant/components/logi_circle/camera.py b/homeassistant/components/logi_circle/camera.py index 20bc829d75d..1afeb190c8b 100644 --- a/homeassistant/components/logi_circle/camera.py +++ b/homeassistant/components/logi_circle/camera.py @@ -125,7 +125,7 @@ class LogiCam(Camera): } @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" state = { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/logi_circle/sensor.py b/homeassistant/components/logi_circle/sensor.py index 4a5fedaf57a..3d980b1dae3 100644 --- a/homeassistant/components/logi_circle/sensor.py +++ b/homeassistant/components/logi_circle/sensor.py @@ -83,7 +83,7 @@ class LogiSensor(Entity): } @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" state = { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/london_air/sensor.py b/homeassistant/components/london_air/sensor.py index a8bebc20cf5..c39d77585a8 100644 --- a/homeassistant/components/london_air/sensor.py +++ b/homeassistant/components/london_air/sensor.py @@ -124,7 +124,7 @@ class AirSensor(Entity): return self.ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other details about the sensor state.""" attrs = {} attrs["updated"] = self._updated diff --git a/homeassistant/components/london_underground/sensor.py b/homeassistant/components/london_underground/sensor.py index c39ef2885b0..acb605901c9 100644 --- a/homeassistant/components/london_underground/sensor.py +++ b/homeassistant/components/london_underground/sensor.py @@ -78,7 +78,7 @@ class LondonTubeSensor(Entity): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other details about the sensor state.""" self.attrs["Description"] = self._description return self.attrs diff --git a/homeassistant/components/luftdaten/sensor.py b/homeassistant/components/luftdaten/sensor.py index 515d8ad577f..5985c63801e 100644 --- a/homeassistant/components/luftdaten/sensor.py +++ b/homeassistant/components/luftdaten/sensor.py @@ -94,7 +94,7 @@ class LuftdatenSensor(Entity): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" self._attrs[ATTR_ATTRIBUTION] = DEFAULT_ATTRIBUTION diff --git a/homeassistant/components/lutron/binary_sensor.py b/homeassistant/components/lutron/binary_sensor.py index f77a2b120da..6fb394d333c 100644 --- a/homeassistant/components/lutron/binary_sensor.py +++ b/homeassistant/components/lutron/binary_sensor.py @@ -49,6 +49,6 @@ class LutronOccupancySensor(LutronDevice, BinarySensorEntity): return f"{self._area_name} Occupancy" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"lutron_integration_id": self._lutron_device.id} diff --git a/homeassistant/components/lutron/cover.py b/homeassistant/components/lutron/cover.py index f1faed32161..6ee53950ef2 100644 --- a/homeassistant/components/lutron/cover.py +++ b/homeassistant/components/lutron/cover.py @@ -64,6 +64,6 @@ class LutronCover(LutronDevice, CoverEntity): _LOGGER.debug("Lutron ID: %d updated to %f", self._lutron_device.id, level) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"lutron_integration_id": self._lutron_device.id} diff --git a/homeassistant/components/lutron/light.py b/homeassistant/components/lutron/light.py index d74d24f71a1..de94b6d6ead 100644 --- a/homeassistant/components/lutron/light.py +++ b/homeassistant/components/lutron/light.py @@ -65,7 +65,7 @@ class LutronLight(LutronDevice, LightEntity): self._lutron_device.level = 0 @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"lutron_integration_id": self._lutron_device.id} diff --git a/homeassistant/components/lutron/switch.py b/homeassistant/components/lutron/switch.py index 21586aaa266..f78f46b6733 100644 --- a/homeassistant/components/lutron/switch.py +++ b/homeassistant/components/lutron/switch.py @@ -42,7 +42,7 @@ class LutronSwitch(LutronDevice, SwitchEntity): self._lutron_device.level = 0 @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"lutron_integration_id": self._lutron_device.id} @@ -75,7 +75,7 @@ class LutronLed(LutronDevice, SwitchEntity): self._lutron_device.state = 0 @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { "keypad": self._keypad_name, diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index 07851db1e23..89eef781c25 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -349,7 +349,7 @@ class LutronCasetaDevice(Entity): } @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"device_id": self.device_id, "zone_id": self._device["zone"]} diff --git a/homeassistant/components/lutron_caseta/binary_sensor.py b/homeassistant/components/lutron_caseta/binary_sensor.py index b58afd22a90..c2fc311de43 100644 --- a/homeassistant/components/lutron_caseta/binary_sensor.py +++ b/homeassistant/components/lutron_caseta/binary_sensor.py @@ -69,6 +69,6 @@ class LutronOccupancySensor(LutronCasetaDevice, BinarySensorEntity): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"device_id": self.device_id} diff --git a/homeassistant/components/lyft/sensor.py b/homeassistant/components/lyft/sensor.py index 98084b28f0c..872281f685c 100644 --- a/homeassistant/components/lyft/sensor.py +++ b/homeassistant/components/lyft/sensor.py @@ -110,7 +110,7 @@ class LyftSensor(Entity): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" params = { "Product ID": self._product["ride_type"], diff --git a/homeassistant/components/magicseaweed/sensor.py b/homeassistant/components/magicseaweed/sensor.py index 9364bee27b2..406f5e53955 100644 --- a/homeassistant/components/magicseaweed/sensor.py +++ b/homeassistant/components/magicseaweed/sensor.py @@ -136,7 +136,7 @@ class MagicSeaweedSensor(Entity): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/manual/alarm_control_panel.py b/homeassistant/components/manual/alarm_control_panel.py index 2313bcace19..00c155615ee 100644 --- a/homeassistant/components/manual/alarm_control_panel.py +++ b/homeassistant/components/manual/alarm_control_panel.py @@ -394,7 +394,7 @@ class ManualAlarm(alarm.AlarmControlPanelEntity, RestoreEntity): return check @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.state == STATE_ALARM_PENDING or self.state == STATE_ALARM_ARMING: return { diff --git a/homeassistant/components/manual_mqtt/alarm_control_panel.py b/homeassistant/components/manual_mqtt/alarm_control_panel.py index f11938396a7..2fa0e631c1d 100644 --- a/homeassistant/components/manual_mqtt/alarm_control_panel.py +++ b/homeassistant/components/manual_mqtt/alarm_control_panel.py @@ -415,7 +415,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanelEntity): return check @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.state != STATE_ALARM_PENDING: return {} diff --git a/homeassistant/components/maxcube/climate.py b/homeassistant/components/maxcube/climate.py index 5db4cc1e7bd..87da5953f26 100644 --- a/homeassistant/components/maxcube/climate.py +++ b/homeassistant/components/maxcube/climate.py @@ -286,7 +286,7 @@ class MaxCubeClimate(ClimateEntity): return @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" cube = self._cubehandle.cube device = cube.device_by_rf(self._rf_address) diff --git a/homeassistant/components/melcloud/climate.py b/homeassistant/components/melcloud/climate.py index 4c409ec5a4d..1abb86cf5e5 100644 --- a/homeassistant/components/melcloud/climate.py +++ b/homeassistant/components/melcloud/climate.py @@ -138,7 +138,7 @@ class AtaDeviceClimate(MelCloudClimate): return self._name @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the optional state attributes with device specific additions.""" attr = {} @@ -310,7 +310,7 @@ class AtwDeviceZoneClimate(MelCloudClimate): return f"{self._name} {self._zone.name}" @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the optional state attributes with device specific additions.""" data = { ATTR_STATUS: ATW_ZONE_HVAC_MODE_LOOKUP.get( diff --git a/homeassistant/components/melcloud/water_heater.py b/homeassistant/components/melcloud/water_heater.py index ae10b5140f7..3474fc07540 100644 --- a/homeassistant/components/melcloud/water_heater.py +++ b/homeassistant/components/melcloud/water_heater.py @@ -72,7 +72,7 @@ class AtwWaterHeater(WaterHeaterEntity): await self._device.set({PROPERTY_POWER: False}) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes with device specific additions.""" data = {ATTR_STATUS: self._device.status} return data diff --git a/homeassistant/components/meteo_france/sensor.py b/homeassistant/components/meteo_france/sensor.py index 4802c20c1e5..74b4ab5a6d1 100644 --- a/homeassistant/components/meteo_france/sensor.py +++ b/homeassistant/components/meteo_france/sensor.py @@ -154,7 +154,7 @@ class MeteoFranceSensor(CoordinatorEntity): return SENSOR_TYPES[self._type][ENTITY_ENABLE] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} @@ -177,7 +177,7 @@ class MeteoFranceRainSensor(MeteoFranceSensor): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" reference_dt = self.coordinator.data.forecast[0]["dt"] return { @@ -208,7 +208,7 @@ class MeteoFranceAlertSensor(MeteoFranceSensor): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { **readeable_phenomenoms_dict(self.coordinator.data.phenomenons_max_colors), diff --git a/homeassistant/components/meteoalarm/binary_sensor.py b/homeassistant/components/meteoalarm/binary_sensor.py index 6b13d03ebba..6d237c696f6 100644 --- a/homeassistant/components/meteoalarm/binary_sensor.py +++ b/homeassistant/components/meteoalarm/binary_sensor.py @@ -73,7 +73,7 @@ class MeteoAlertBinarySensor(BinarySensorEntity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" self._attributes[ATTR_ATTRIBUTION] = ATTRIBUTION return self._attributes diff --git a/homeassistant/components/metoffice/sensor.py b/homeassistant/components/metoffice/sensor.py index aed763ca4a4..2f4dd72b3a1 100644 --- a/homeassistant/components/metoffice/sensor.py +++ b/homeassistant/components/metoffice/sensor.py @@ -171,7 +171,7 @@ class MetOfficeCurrentSensor(Entity): return SENSOR_TYPES[self._type][1] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/mfi/switch.py b/homeassistant/components/mfi/switch.py index 21963140547..150f81298cd 100644 --- a/homeassistant/components/mfi/switch.py +++ b/homeassistant/components/mfi/switch.py @@ -107,7 +107,7 @@ class MfiSwitch(SwitchEntity): return int(self._port.data.get("active_pwr", 0)) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes for the device.""" return { "volts": round(self._port.data.get("v_rms", 0), 1), diff --git a/homeassistant/components/mhz19/sensor.py b/homeassistant/components/mhz19/sensor.py index e77f17c9140..f26481f9d25 100644 --- a/homeassistant/components/mhz19/sensor.py +++ b/homeassistant/components/mhz19/sensor.py @@ -107,7 +107,7 @@ class MHZ19Sensor(Entity): self._ppm = data.get(SENSOR_CO2) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" result = {} if self._sensor_type == SENSOR_TEMPERATURE and self._ppm is not None: diff --git a/homeassistant/components/microsoft_face/__init__.py b/homeassistant/components/microsoft_face/__init__.py index b9046429603..9f7131d1935 100644 --- a/homeassistant/components/microsoft_face/__init__.py +++ b/homeassistant/components/microsoft_face/__init__.py @@ -231,7 +231,7 @@ class MicrosoftFaceGroupEntity(Entity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" attr = {} for name, p_id in self._api.store[self._id].items(): diff --git a/homeassistant/components/miflora/sensor.py b/homeassistant/components/miflora/sensor.py index fa1d8b57734..8db81ecf5dd 100644 --- a/homeassistant/components/miflora/sensor.py +++ b/homeassistant/components/miflora/sensor.py @@ -189,7 +189,7 @@ class MiFloraSensor(Entity): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return {ATTR_LAST_SUCCESSFUL_UPDATE: self.last_successful_update} diff --git a/homeassistant/components/mikrotik/device_tracker.py b/homeassistant/components/mikrotik/device_tracker.py index b9e0b051aba..025eff8d07a 100644 --- a/homeassistant/components/mikrotik/device_tracker.py +++ b/homeassistant/components/mikrotik/device_tracker.py @@ -123,7 +123,7 @@ class MikrotikHubTracker(ScannerEntity): return self.hub.available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if self.is_connected: return {k: v for k, v in self.device.attrs.items() if k not in FILTER_ATTRS} diff --git a/homeassistant/components/mill/climate.py b/homeassistant/components/mill/climate.py index 0bb94242d64..7a1adc6a0bc 100644 --- a/homeassistant/components/mill/climate.py +++ b/homeassistant/components/mill/climate.py @@ -107,7 +107,7 @@ class MillHeater(ClimateEntity): return self._heater.name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" res = { "open_window": self._heater.open_window, diff --git a/homeassistant/components/min_max/sensor.py b/homeassistant/components/min_max/sensor.py index d2ffb9f5ec0..5bea9379690 100644 --- a/homeassistant/components/min_max/sensor.py +++ b/homeassistant/components/min_max/sensor.py @@ -188,7 +188,7 @@ class MinMaxSensor(Entity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return { attr: getattr(self, attr) diff --git a/homeassistant/components/minecraft_server/__init__.py b/homeassistant/components/minecraft_server/__init__.py index 164bb264f90..0e7096881f5 100644 --- a/homeassistant/components/minecraft_server/__init__.py +++ b/homeassistant/components/minecraft_server/__init__.py @@ -246,7 +246,7 @@ class MinecraftServerEntity(Entity): "sw_version": self._server.protocol_version, } self._device_class = device_class - self._device_state_attributes = None + self._extra_state_attributes = None self._disconnect_dispatcher = None @property diff --git a/homeassistant/components/minecraft_server/sensor.py b/homeassistant/components/minecraft_server/sensor.py index 171ff9d1701..8cc626390dd 100644 --- a/homeassistant/components/minecraft_server/sensor.py +++ b/homeassistant/components/minecraft_server/sensor.py @@ -141,19 +141,19 @@ class MinecraftServerPlayersOnlineSensor(MinecraftServerSensorEntity): """Update online players state and device state attributes.""" self._state = self._server.players_online - device_state_attributes = None + extra_state_attributes = None players_list = self._server.players_list if players_list is not None: if len(players_list) != 0: - device_state_attributes = {ATTR_PLAYERS_LIST: self._server.players_list} + extra_state_attributes = {ATTR_PLAYERS_LIST: self._server.players_list} - self._device_state_attributes = device_state_attributes + self._extra_state_attributes = extra_state_attributes @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return players list in device state attributes.""" - return self._device_state_attributes + return self._extra_state_attributes class MinecraftServerPlayersMaxSensor(MinecraftServerSensorEntity): diff --git a/homeassistant/components/mobile_app/device_tracker.py b/homeassistant/components/mobile_app/device_tracker.py index f0cd30074fa..1b006f69827 100644 --- a/homeassistant/components/mobile_app/device_tracker.py +++ b/homeassistant/components/mobile_app/device_tracker.py @@ -56,7 +56,7 @@ class MobileAppEntity(TrackerEntity, RestoreEntity): return self._data.get(ATTR_BATTERY) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific attributes.""" attrs = {} for key in ATTR_KEYS: diff --git a/homeassistant/components/mobile_app/entity.py b/homeassistant/components/mobile_app/entity.py index 748f680da5e..2f30c4b9f1b 100644 --- a/homeassistant/components/mobile_app/entity.py +++ b/homeassistant/components/mobile_app/entity.py @@ -81,7 +81,7 @@ class MobileAppEntity(RestoreEntity): return self._config.get(ATTR_SENSOR_DEVICE_CLASS) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return self._config[ATTR_SENSOR_ATTRIBUTES] diff --git a/homeassistant/components/modem_callerid/sensor.py b/homeassistant/components/modem_callerid/sensor.py index c58a4b67eed..f91b6d7a169 100644 --- a/homeassistant/components/modem_callerid/sensor.py +++ b/homeassistant/components/modem_callerid/sensor.py @@ -86,7 +86,7 @@ class ModemCalleridSensor(Entity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/mold_indicator/sensor.py b/homeassistant/components/mold_indicator/sensor.py index e2d9909c7ca..126b57cb412 100644 --- a/homeassistant/components/mold_indicator/sensor.py +++ b/homeassistant/components/mold_indicator/sensor.py @@ -375,7 +375,7 @@ class MoldIndicator(Entity): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._is_metric: return { diff --git a/homeassistant/components/motion_blinds/cover.py b/homeassistant/components/motion_blinds/cover.py index 3087401c3ae..4ac09a5fd11 100644 --- a/homeassistant/components/motion_blinds/cover.py +++ b/homeassistant/components/motion_blinds/cover.py @@ -294,7 +294,7 @@ class MotionTDBUDevice(MotionPositionDevice): return self._blind.position[self._motor_key] == 100 @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" attributes = {} if self._blind.position is not None: diff --git a/homeassistant/components/motion_blinds/sensor.py b/homeassistant/components/motion_blinds/sensor.py index f8a673b3079..9f80c85b959 100644 --- a/homeassistant/components/motion_blinds/sensor.py +++ b/homeassistant/components/motion_blinds/sensor.py @@ -88,7 +88,7 @@ class MotionBatterySensor(CoordinatorEntity, Entity): return self._blind.battery_level @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return {ATTR_BATTERY_VOLTAGE: self._blind.battery_voltage} @@ -134,7 +134,7 @@ class MotionTDBUBatterySensor(MotionBatterySensor): return self._blind.battery_level[self._motor[0]] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" attributes = {} if self._blind.battery_voltage is not None: diff --git a/homeassistant/components/mqtt/device_tracker/schema_discovery.py b/homeassistant/components/mqtt/device_tracker/schema_discovery.py index a1279f512a5..d6688636bb2 100644 --- a/homeassistant/components/mqtt/device_tracker/schema_discovery.py +++ b/homeassistant/components/mqtt/device_tracker/schema_discovery.py @@ -109,32 +109,32 @@ class MqttDeviceTracker(MqttEntity, TrackerEntity): @property def latitude(self): - """Return latitude if provided in device_state_attributes or None.""" + """Return latitude if provided in extra_state_attributes or None.""" if ( - self.device_state_attributes is not None - and ATTR_LATITUDE in self.device_state_attributes + self.extra_state_attributes is not None + and ATTR_LATITUDE in self.extra_state_attributes ): - return self.device_state_attributes[ATTR_LATITUDE] + return self.extra_state_attributes[ATTR_LATITUDE] return None @property def location_accuracy(self): - """Return location accuracy if provided in device_state_attributes or None.""" + """Return location accuracy if provided in extra_state_attributes or None.""" if ( - self.device_state_attributes is not None - and ATTR_GPS_ACCURACY in self.device_state_attributes + self.extra_state_attributes is not None + and ATTR_GPS_ACCURACY in self.extra_state_attributes ): - return self.device_state_attributes[ATTR_GPS_ACCURACY] + return self.extra_state_attributes[ATTR_GPS_ACCURACY] return None @property def longitude(self): - """Return longitude if provided in device_state_attributes or None.""" + """Return longitude if provided in extra_state_attributes or None.""" if ( - self.device_state_attributes is not None - and ATTR_LONGITUDE in self.device_state_attributes + self.extra_state_attributes is not None + and ATTR_LONGITUDE in self.extra_state_attributes ): - return self.device_state_attributes[ATTR_LONGITUDE] + return self.extra_state_attributes[ATTR_LONGITUDE] return None @property diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 8bcb5e5ca97..898072de5f9 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -229,7 +229,7 @@ class MqttAttributes(Entity): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/mqtt_room/sensor.py b/homeassistant/components/mqtt_room/sensor.py index 3b61003e601..e15dfd179da 100644 --- a/homeassistant/components/mqtt_room/sensor.py +++ b/homeassistant/components/mqtt_room/sensor.py @@ -135,7 +135,7 @@ class MQTTRoomSensor(Entity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_DISTANCE: self._distance} diff --git a/homeassistant/components/mvglive/sensor.py b/homeassistant/components/mvglive/sensor.py index 2ceca024a6f..15b0d2d218e 100644 --- a/homeassistant/components/mvglive/sensor.py +++ b/homeassistant/components/mvglive/sensor.py @@ -114,7 +114,7 @@ class MVGLiveSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" dep = self.data.departures if not dep: diff --git a/homeassistant/components/mychevy/sensor.py b/homeassistant/components/mychevy/sensor.py index df8b136741a..832f1a03f25 100644 --- a/homeassistant/components/mychevy/sensor.py +++ b/homeassistant/components/mychevy/sensor.py @@ -172,7 +172,7 @@ class EVSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return all the state attributes.""" return self._state_attributes diff --git a/homeassistant/components/mysensors/device.py b/homeassistant/components/mysensors/device.py index 25b892d70b3..d5ae6c0c156 100644 --- a/homeassistant/components/mysensors/device.py +++ b/homeassistant/components/mysensors/device.py @@ -122,7 +122,7 @@ class MySensorsDevice: return f"{self.node_name} {self.child_id}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" node = self.gateway.sensors[self.node_id] child = node.children[self.child_id] diff --git a/homeassistant/components/mysensors/device_tracker.py b/homeassistant/components/mysensors/device_tracker.py index d1f89e4fe04..068029af960 100644 --- a/homeassistant/components/mysensors/device_tracker.py +++ b/homeassistant/components/mysensors/device_tracker.py @@ -72,5 +72,5 @@ class MySensorsDeviceScanner(mysensors.device.MySensorsDevice): host_name=self.name, gps=(latitude, longitude), battery=node.battery_level, - attributes=self.device_state_attributes, + attributes=self.extra_state_attributes, ) diff --git a/homeassistant/components/n26/sensor.py b/homeassistant/components/n26/sensor.py index b9a8b21f9d0..df687d9689a 100644 --- a/homeassistant/components/n26/sensor.py +++ b/homeassistant/components/n26/sensor.py @@ -86,7 +86,7 @@ class N26Account(Entity): return self._data.balance.get("currency") @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Additional attributes of the sensor.""" attributes = { ATTR_IBAN: self._data.balance.get("iban"), @@ -147,7 +147,7 @@ class N26Card(Entity): return self._card["status"] @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Additional attributes of the sensor.""" attributes = { "apple_pay_eligible": self._card.get("applePayEligible"), @@ -220,7 +220,7 @@ class N26Space(Entity): return self._space["balance"]["currency"] @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Additional attributes of the sensor.""" goal_value = "" if "goal" in self._space: diff --git a/homeassistant/components/neato/camera.py b/homeassistant/components/neato/camera.py index 1698a1d944a..74a3cb4bc77 100644 --- a/homeassistant/components/neato/camera.py +++ b/homeassistant/components/neato/camera.py @@ -126,7 +126,7 @@ class NeatoCleaningMap(Camera): return {"identifiers": {(NEATO_DOMAIN, self._robot_serial)}} @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the vacuum cleaner.""" data = {} diff --git a/homeassistant/components/neato/vacuum.py b/homeassistant/components/neato/vacuum.py index ce4156244b7..3b6711d3b72 100644 --- a/homeassistant/components/neato/vacuum.py +++ b/homeassistant/components/neato/vacuum.py @@ -284,7 +284,7 @@ class NeatoConnectedVacuum(StateVacuumEntity): return self._robot_serial @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the vacuum cleaner.""" data = {} diff --git a/homeassistant/components/nederlandse_spoorwegen/sensor.py b/homeassistant/components/nederlandse_spoorwegen/sensor.py index 3d15e3c4d9b..7d90c2c3c0d 100644 --- a/homeassistant/components/nederlandse_spoorwegen/sensor.py +++ b/homeassistant/components/nederlandse_spoorwegen/sensor.py @@ -124,7 +124,7 @@ class NSDepartureSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if not self._trips: return diff --git a/homeassistant/components/nello/lock.py b/homeassistant/components/nello/lock.py index 61241660847..93e63b05da9 100644 --- a/homeassistant/components/nello/lock.py +++ b/homeassistant/components/nello/lock.py @@ -48,7 +48,7 @@ class NelloLock(LockEntity): return True @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" return self._device_attrs diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index 5163c9582b0..b02d77e45fb 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -217,7 +217,7 @@ class NetatmoCamera(NetatmoBase, Camera): return response.content @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the Netatmo-specific camera state attributes.""" return { "id": self._id, diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index d12ee9263db..c2a6e484771 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -418,7 +418,7 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): self.async_write_ha_state() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the thermostat.""" attr = {} diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index efda94d6399..ac86e86b960 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -536,7 +536,7 @@ class NetatmoPublicSensor(NetatmoBase): return self._device_class @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attributes of the device.""" attrs = {} diff --git a/homeassistant/components/nexia/climate.py b/homeassistant/components/nexia/climate.py index 2b3f7de4489..4084f4d297c 100644 --- a/homeassistant/components/nexia/climate.py +++ b/homeassistant/components/nexia/climate.py @@ -354,9 +354,9 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): return self._thermostat.is_emergency_heat_active() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" - data = super().device_state_attributes + data = super().extra_state_attributes data[ATTR_ZONE_STATUS] = self._zone.get_status() diff --git a/homeassistant/components/nexia/entity.py b/homeassistant/components/nexia/entity.py index 62f6e8275c4..fc69c7ef389 100644 --- a/homeassistant/components/nexia/entity.py +++ b/homeassistant/components/nexia/entity.py @@ -33,7 +33,7 @@ class NexiaEntity(CoordinatorEntity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/nexia/scene.py b/homeassistant/components/nexia/scene.py index d3a6691d59e..495a8fb4d3a 100644 --- a/homeassistant/components/nexia/scene.py +++ b/homeassistant/components/nexia/scene.py @@ -41,9 +41,9 @@ class NexiaAutomationScene(NexiaEntity, Scene): self._automation = automation @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the scene specific state attributes.""" - data = super().device_state_attributes + data = super().extra_state_attributes data[ATTR_DESCRIPTION] = self._automation.description return data diff --git a/homeassistant/components/nextbus/sensor.py b/homeassistant/components/nextbus/sensor.py index 2b5da2a97fa..3357d84fd69 100644 --- a/homeassistant/components/nextbus/sensor.py +++ b/homeassistant/components/nextbus/sensor.py @@ -156,7 +156,7 @@ class NextBusDepartureSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return additional state attributes.""" return self._attributes diff --git a/homeassistant/components/nightscout/sensor.py b/homeassistant/components/nightscout/sensor.py index efa625577d9..53f13f3b69b 100644 --- a/homeassistant/components/nightscout/sensor.py +++ b/homeassistant/components/nightscout/sensor.py @@ -115,6 +115,6 @@ class NightscoutSensor(Entity): return switcher.get(self._attributes[ATTR_DIRECTION], "mdi:cloud-question") @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/nilu/air_quality.py b/homeassistant/components/nilu/air_quality.py index 8e851592de3..d6fcad3ac7e 100644 --- a/homeassistant/components/nilu/air_quality.py +++ b/homeassistant/components/nilu/air_quality.py @@ -175,7 +175,7 @@ class NiluSensor(AirQualityEntity): return ATTRIBUTION @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return other details about the sensor state.""" return self._attrs diff --git a/homeassistant/components/nissan_leaf/__init__.py b/homeassistant/components/nissan_leaf/__init__.py index bace7f14556..967857aedc5 100644 --- a/homeassistant/components/nissan_leaf/__init__.py +++ b/homeassistant/components/nissan_leaf/__init__.py @@ -450,7 +450,7 @@ class LeafEntity(Entity): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return default attributes for Nissan leaf entities.""" return { "next_update": self.car.next_update, diff --git a/homeassistant/components/nissan_leaf/switch.py b/homeassistant/components/nissan_leaf/switch.py index d95d3e4ed39..2b8d557c2dd 100644 --- a/homeassistant/components/nissan_leaf/switch.py +++ b/homeassistant/components/nissan_leaf/switch.py @@ -37,9 +37,9 @@ class LeafClimateSwitch(LeafEntity, ToggleEntity): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return climate control attributes.""" - attrs = super().device_state_attributes + attrs = super().extra_state_attributes attrs["updated_on"] = self.car.last_climate_response return attrs diff --git a/homeassistant/components/nmbs/sensor.py b/homeassistant/components/nmbs/sensor.py index bdf4658434c..ac6753ce0d6 100644 --- a/homeassistant/components/nmbs/sensor.py +++ b/homeassistant/components/nmbs/sensor.py @@ -126,7 +126,7 @@ class NMBSLiveBoard(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the sensor attributes if data is available.""" if self._state is None or not self._attrs: return None @@ -202,7 +202,7 @@ class NMBSSensor(Entity): return "mdi:train" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return sensor attributes if data is available.""" if self._state is None or not self._attrs: return None diff --git a/homeassistant/components/noaa_tides/sensor.py b/homeassistant/components/noaa_tides/sensor.py index 0759c5093c8..b6771d9293a 100644 --- a/homeassistant/components/noaa_tides/sensor.py +++ b/homeassistant/components/noaa_tides/sensor.py @@ -90,7 +90,7 @@ class NOAATidesAndCurrentsSensor(Entity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of this device.""" attr = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} if self.data is None: diff --git a/homeassistant/components/norway_air/air_quality.py b/homeassistant/components/norway_air/air_quality.py index 8e6c13260e5..788f900ef70 100644 --- a/homeassistant/components/norway_air/air_quality.py +++ b/homeassistant/components/norway_air/air_quality.py @@ -80,7 +80,7 @@ class AirSensor(AirQualityEntity): return ATTRIBUTION @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return other details about the sensor state.""" return { "level": self._api.data.get("level"), diff --git a/homeassistant/components/notion/__init__.py b/homeassistant/components/notion/__init__.py index c2cbdb85289..c8c385143cf 100644 --- a/homeassistant/components/notion/__init__.py +++ b/homeassistant/components/notion/__init__.py @@ -180,7 +180,7 @@ class NotionEntity(CoordinatorEntity): return self._device_class @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/nsw_fuel_station/sensor.py b/homeassistant/components/nsw_fuel_station/sensor.py index b6c0d1a5d9b..0bf82c1d162 100644 --- a/homeassistant/components/nsw_fuel_station/sensor.py +++ b/homeassistant/components/nsw_fuel_station/sensor.py @@ -168,7 +168,7 @@ class StationPriceSensor(Entity): return None @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes of the device.""" return { ATTR_STATION_ID: self._station_data.station_id, diff --git a/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py b/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py index 12ae9d8990a..0467b9cc353 100644 --- a/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py +++ b/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py @@ -284,7 +284,7 @@ class NswRuralFireServiceLocationEvent(GeolocationEvent): return LENGTH_KILOMETERS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = {} for key, value in ( diff --git a/homeassistant/components/nuki/lock.py b/homeassistant/components/nuki/lock.py index 818784a2b2e..360153d14fe 100644 --- a/homeassistant/components/nuki/lock.py +++ b/homeassistant/components/nuki/lock.py @@ -99,7 +99,7 @@ class NukiDeviceEntity(LockEntity, ABC): """Return true if lock is locked.""" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" data = { ATTR_BATTERY_CRITICAL: self._nuki_device.battery_critical, diff --git a/homeassistant/components/nut/sensor.py b/homeassistant/components/nut/sensor.py index 174405e22e2..d4fdd03adc8 100644 --- a/homeassistant/components/nut/sensor.py +++ b/homeassistant/components/nut/sensor.py @@ -160,7 +160,7 @@ class NUTSensor(CoordinatorEntity): return self._unit @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the sensor attributes.""" return {ATTR_STATE: _format_display_state(self._data.status)} diff --git a/homeassistant/components/nx584/binary_sensor.py b/homeassistant/components/nx584/binary_sensor.py index 2db3531f879..058ac6c5795 100644 --- a/homeassistant/components/nx584/binary_sensor.py +++ b/homeassistant/components/nx584/binary_sensor.py @@ -105,7 +105,7 @@ class NX584ZoneSensor(BinarySensorEntity): return self._zone["state"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"zone_number": self._zone["number"]} diff --git a/homeassistant/components/oasa_telematics/sensor.py b/homeassistant/components/oasa_telematics/sensor.py index 4bf6b395d5f..8af74b5cd0e 100644 --- a/homeassistant/components/oasa_telematics/sensor.py +++ b/homeassistant/components/oasa_telematics/sensor.py @@ -79,7 +79,7 @@ class OASATelematicsSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" params = {} if self._times is not None: diff --git a/homeassistant/components/ohmconnect/sensor.py b/homeassistant/components/ohmconnect/sensor.py index 7c7331990ea..6c7c04b25cb 100644 --- a/homeassistant/components/ohmconnect/sensor.py +++ b/homeassistant/components/ohmconnect/sensor.py @@ -56,7 +56,7 @@ class OhmconnectSensor(Entity): return "Inactive" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"Address": self._data.get("address"), "ID": self._ohmid} diff --git a/homeassistant/components/omnilogic/common.py b/homeassistant/components/omnilogic/common.py index 791d81b6757..6f7ee6e5eb5 100644 --- a/homeassistant/components/omnilogic/common.py +++ b/homeassistant/components/omnilogic/common.py @@ -141,7 +141,7 @@ class OmniLogicEntity(CoordinatorEntity): return self._icon @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attributes.""" return self._attrs diff --git a/homeassistant/components/onewire/onewire_entities.py b/homeassistant/components/onewire/onewire_entities.py index 9238bb5d32c..724783b5686 100644 --- a/homeassistant/components/onewire/onewire_entities.py +++ b/homeassistant/components/onewire/onewire_entities.py @@ -58,7 +58,7 @@ class OneWireBaseEntity(Entity): return self._unit_of_measurement @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" return {"device_file": self._device_file, "raw_value": self._value_raw} diff --git a/homeassistant/components/onkyo/media_player.py b/homeassistant/components/onkyo/media_player.py index 7ac9b5fdfc6..3d882884aa2 100644 --- a/homeassistant/components/onkyo/media_player.py +++ b/homeassistant/components/onkyo/media_player.py @@ -392,7 +392,7 @@ class OnkyoDevice(MediaPlayerEntity): return self._source_list @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return self._attributes diff --git a/homeassistant/components/openexchangerates/sensor.py b/homeassistant/components/openexchangerates/sensor.py index 9846e305291..f3f0d825ff6 100644 --- a/homeassistant/components/openexchangerates/sensor.py +++ b/homeassistant/components/openexchangerates/sensor.py @@ -79,7 +79,7 @@ class OpenexchangeratesSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other attributes of the sensor.""" attr = self.rest.data attr[ATTR_ATTRIBUTION] = ATTRIBUTION diff --git a/homeassistant/components/opengarage/cover.py b/homeassistant/components/opengarage/cover.py index cf6825c867b..154cb4df3ae 100644 --- a/homeassistant/components/opengarage/cover.py +++ b/homeassistant/components/opengarage/cover.py @@ -92,7 +92,7 @@ class OpenGarageCover(CoverEntity): self._open_garage = open_garage self._state = None self._state_before_move = None - self._device_state_attributes = {} + self._extra_state_attributes = {} self._available = True self._device_id = device_id @@ -107,9 +107,9 @@ class OpenGarageCover(CoverEntity): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - return self._device_state_attributes + return self._extra_state_attributes @property def is_closed(self): @@ -154,11 +154,11 @@ class OpenGarageCover(CoverEntity): _LOGGER.debug("%s status: %s", self._name, self._state) if status.get("rssi") is not None: - self._device_state_attributes[ATTR_SIGNAL_STRENGTH] = status.get("rssi") + self._extra_state_attributes[ATTR_SIGNAL_STRENGTH] = status.get("rssi") if status.get("dist") is not None: - self._device_state_attributes[ATTR_DISTANCE_SENSOR] = status.get("dist") + self._extra_state_attributes[ATTR_DISTANCE_SENSOR] = status.get("dist") if self._state is not None: - self._device_state_attributes[ATTR_DOOR_STATE] = self._state + self._extra_state_attributes[ATTR_DOOR_STATE] = self._state self._available = True diff --git a/homeassistant/components/opensky/sensor.py b/homeassistant/components/opensky/sensor.py index 06132e83e88..e5ffb2384f5 100644 --- a/homeassistant/components/opensky/sensor.py +++ b/homeassistant/components/opensky/sensor.py @@ -174,7 +174,7 @@ class OpenSkySensor(Entity): self._previously_tracked = currently_tracked @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: OPENSKY_ATTRIBUTION} diff --git a/homeassistant/components/openuv/__init__.py b/homeassistant/components/openuv/__init__.py index 387b7547514..aeefe435845 100644 --- a/homeassistant/components/openuv/__init__.py +++ b/homeassistant/components/openuv/__init__.py @@ -187,7 +187,7 @@ class OpenUvEntity(Entity): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/openweathermap/abstract_owm_sensor.py b/homeassistant/components/openweathermap/abstract_owm_sensor.py index 809d2c2e572..a69f542589b 100644 --- a/homeassistant/components/openweathermap/abstract_owm_sensor.py +++ b/homeassistant/components/openweathermap/abstract_owm_sensor.py @@ -57,7 +57,7 @@ class AbstractOpenWeatherMapSensor(Entity): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/osramlightify/light.py b/homeassistant/components/osramlightify/light.py index 49c32da69bc..e01ad970488 100644 --- a/homeassistant/components/osramlightify/light.py +++ b/homeassistant/components/osramlightify/light.py @@ -269,7 +269,7 @@ class Luminary(LightEntity): return self._unique_id @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return self._device_attributes diff --git a/homeassistant/components/ovo_energy/sensor.py b/homeassistant/components/ovo_energy/sensor.py index 2f2e1b8dd50..3171b3231dc 100644 --- a/homeassistant/components/ovo_energy/sensor.py +++ b/homeassistant/components/ovo_energy/sensor.py @@ -100,7 +100,7 @@ class OVOEnergyLastElectricityReading(OVOEnergySensor): return usage.electricity[-1].consumption @property - def device_state_attributes(self) -> object: + def extra_state_attributes(self) -> object: """Return the attributes of the sensor.""" usage: OVODailyUsage = self.coordinator.data if usage is None or not usage.electricity: @@ -135,7 +135,7 @@ class OVOEnergyLastGasReading(OVOEnergySensor): return usage.gas[-1].consumption @property - def device_state_attributes(self) -> object: + def extra_state_attributes(self) -> object: """Return the attributes of the sensor.""" usage: OVODailyUsage = self.coordinator.data if usage is None or not usage.gas: @@ -171,7 +171,7 @@ class OVOEnergyLastElectricityCost(OVOEnergySensor): return usage.electricity[-1].cost.amount @property - def device_state_attributes(self) -> object: + def extra_state_attributes(self) -> object: """Return the attributes of the sensor.""" usage: OVODailyUsage = self.coordinator.data if usage is None or not usage.electricity: @@ -207,7 +207,7 @@ class OVOEnergyLastGasCost(OVOEnergySensor): return usage.gas[-1].cost.amount @property - def device_state_attributes(self) -> object: + def extra_state_attributes(self) -> object: """Return the attributes of the sensor.""" usage: OVODailyUsage = self.coordinator.data if usage is None or not usage.gas: diff --git a/homeassistant/components/owntracks/device_tracker.py b/homeassistant/components/owntracks/device_tracker.py index 8a8fdc52fb1..d50e5b9c414 100644 --- a/homeassistant/components/owntracks/device_tracker.py +++ b/homeassistant/components/owntracks/device_tracker.py @@ -74,7 +74,7 @@ class OwnTracksEntity(TrackerEntity, RestoreEntity): return self._data.get("battery") @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific attributes.""" return self._data.get("attributes") diff --git a/homeassistant/components/ozw/climate.py b/homeassistant/components/ozw/climate.py index a74fd869f0f..a6532af4d26 100644 --- a/homeassistant/components/ozw/climate.py +++ b/homeassistant/components/ozw/climate.py @@ -308,9 +308,9 @@ class ZWaveClimateEntity(ZWaveDeviceEntity, ClimateEntity): self.values.mode.send_value(preset_mode_value) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" - data = super().device_state_attributes + data = super().extra_state_attributes if self.values.fan_action: data[ATTR_FAN_ACTION] = self.values.fan_action.value if self.values.valve_position: diff --git a/homeassistant/components/ozw/entity.py b/homeassistant/components/ozw/entity.py index c1cb9617a5c..305601a2333 100644 --- a/homeassistant/components/ozw/entity.py +++ b/homeassistant/components/ozw/entity.py @@ -209,7 +209,7 @@ class ZWaveDeviceEntity(Entity): return device_info @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" return {const.ATTR_NODE_ID: self.values.primary.node.node_id} diff --git a/homeassistant/components/ozw/sensor.py b/homeassistant/components/ozw/sensor.py index 5bd0d1c482f..db695bcf6bc 100644 --- a/homeassistant/components/ozw/sensor.py +++ b/homeassistant/components/ozw/sensor.py @@ -149,9 +149,9 @@ class ZWaveListSensor(ZwaveSensorBase): return self.values.primary.value["Selected_id"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" - attributes = super().device_state_attributes + attributes = super().extra_state_attributes # add the value's label as property attributes["label"] = self.values.primary.value["Selected"] return attributes diff --git a/tests/components/kira/test_sensor.py b/tests/components/kira/test_sensor.py index cd4bee60ae6..b835a25ae90 100644 --- a/tests/components/kira/test_sensor.py +++ b/tests/components/kira/test_sensor.py @@ -47,4 +47,4 @@ class TestKiraSensor(unittest.TestCase): sensor._update_callback(codeTuple) assert sensor.state == codeName - assert sensor.device_state_attributes == {kira.CONF_DEVICE: deviceName} + assert sensor.extra_state_attributes == {kira.CONF_DEVICE: deviceName} diff --git a/tests/components/mfi/test_switch.py b/tests/components/mfi/test_switch.py index 0409a4f387a..1e9f56853c3 100644 --- a/tests/components/mfi/test_switch.py +++ b/tests/components/mfi/test_switch.py @@ -118,7 +118,7 @@ async def test_current_power_w_no_data(port, switch): assert switch.current_power_w == 0 -async def test_device_state_attributes(port, switch): +async def test_extra_state_attributes(port, switch): """Test the state attributes.""" port.data = {"v_rms": 1.25, "i_rms": 2.75} - assert switch.device_state_attributes == {"volts": 1.2, "amps": 2.8} + assert switch.extra_state_attributes == {"volts": 1.2, "amps": 2.8} diff --git a/tests/components/mhz19/test_sensor.py b/tests/components/mhz19/test_sensor.py index 5b6e1e9fa37..e827b5dfbd2 100644 --- a/tests/components/mhz19/test_sensor.py +++ b/tests/components/mhz19/test_sensor.py @@ -93,7 +93,7 @@ async def test_co2_sensor(mock_function): assert sensor.state == 1000 assert sensor.unit_of_measurement == CONCENTRATION_PARTS_PER_MILLION assert sensor.should_poll - assert sensor.device_state_attributes == {"temperature": 24} + assert sensor.extra_state_attributes == {"temperature": 24} @patch("pmsensor.co2sensor.read_mh_z19_with_temperature", return_value=(1000, 24)) @@ -107,7 +107,7 @@ async def test_temperature_sensor(mock_function): assert sensor.state == 24 assert sensor.unit_of_measurement == TEMP_CELSIUS assert sensor.should_poll - assert sensor.device_state_attributes == {"co2_concentration": 1000} + assert sensor.extra_state_attributes == {"co2_concentration": 1000} @patch("pmsensor.co2sensor.read_mh_z19_with_temperature", return_value=(1000, 24)) diff --git a/tests/components/nx584/test_binary_sensor.py b/tests/components/nx584/test_binary_sensor.py index c12e8e24ebc..d6d410b3700 100644 --- a/tests/components/nx584/test_binary_sensor.py +++ b/tests/components/nx584/test_binary_sensor.py @@ -146,7 +146,7 @@ def test_nx584_zone_sensor_normal(): assert "foo" == sensor.name assert not sensor.should_poll assert sensor.is_on - assert sensor.device_state_attributes["zone_number"] == 1 + assert sensor.extra_state_attributes["zone_number"] == 1 zone["state"] = False assert not sensor.is_on From 1fc8e32d8662c1cdc4b097c9ffc3dd98e16af654 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 11 Mar 2021 20:16:26 +0100 Subject: [PATCH 304/831] Update integrations t-z to override extra_state_attributes() (#47760) --- .../components/tado/binary_sensor.py | 2 +- homeassistant/components/tado/climate.py | 2 +- homeassistant/components/tado/sensor.py | 4 +-- homeassistant/components/tahoma/__init__.py | 2 +- .../components/tahoma/binary_sensor.py | 4 +-- homeassistant/components/tahoma/cover.py | 4 +-- homeassistant/components/tahoma/lock.py | 4 +-- homeassistant/components/tahoma/scene.py | 2 +- homeassistant/components/tahoma/sensor.py | 4 +-- homeassistant/components/tahoma/switch.py | 4 +-- .../components/tank_utility/sensor.py | 2 +- .../components/tankerkoenig/sensor.py | 2 +- homeassistant/components/tautulli/sensor.py | 2 +- homeassistant/components/tellduslive/entry.py | 2 +- .../components/template/template_entity.py | 2 +- .../components/tensorflow/image_processing.py | 2 +- homeassistant/components/tesla/__init__.py | 2 +- .../components/tesla/device_tracker.py | 4 +-- homeassistant/components/tesla/sensor.py | 2 +- .../components/thermoworks_smoke/sensor.py | 2 +- .../components/thethingsnetwork/sensor.py | 2 +- .../components/threshold/binary_sensor.py | 2 +- homeassistant/components/tibber/sensor.py | 18 ++++++------ .../components/tile/device_tracker.py | 2 +- homeassistant/components/tmb/sensor.py | 2 +- homeassistant/components/tod/binary_sensor.py | 2 +- homeassistant/components/todoist/calendar.py | 2 +- homeassistant/components/toon/climate.py | 2 +- .../totalconnect/alarm_control_panel.py | 8 +++--- .../components/totalconnect/binary_sensor.py | 2 +- homeassistant/components/tplink/light.py | 2 +- homeassistant/components/tplink/switch.py | 2 +- .../components/traccar/device_tracker.py | 2 +- homeassistant/components/tradfri/cover.py | 2 +- .../components/trafikverket_train/sensor.py | 2 +- .../trafikverket_weatherstation/sensor.py | 2 +- .../components/transmission/sensor.py | 2 +- .../components/transport_nsw/sensor.py | 2 +- homeassistant/components/travisci/sensor.py | 2 +- .../components/trend/binary_sensor.py | 2 +- homeassistant/components/twitch/sensor.py | 2 +- .../components/uk_transport/sensor.py | 4 +-- .../components/unifi/device_tracker.py | 4 +-- homeassistant/components/unifi/switch.py | 2 +- .../components/universal/media_player.py | 2 +- homeassistant/components/upb/__init__.py | 2 +- homeassistant/components/upcloud/__init__.py | 2 +- .../components/updater/binary_sensor.py | 2 +- .../components/uptimerobot/binary_sensor.py | 2 +- homeassistant/components/uscis/sensor.py | 2 +- .../usgs_earthquakes_feed/geo_location.py | 2 +- .../components/utility_meter/sensor.py | 2 +- homeassistant/components/uvc/camera.py | 2 +- homeassistant/components/vallox/fan.py | 2 +- homeassistant/components/vasttrafik/sensor.py | 2 +- homeassistant/components/venstar/climate.py | 2 +- homeassistant/components/vera/__init__.py | 2 +- homeassistant/components/vera/lock.py | 4 +-- homeassistant/components/vera/scene.py | 2 +- homeassistant/components/version/sensor.py | 2 +- homeassistant/components/vesync/fan.py | 2 +- homeassistant/components/vesync/switch.py | 2 +- .../components/viaggiatreno/sensor.py | 2 +- homeassistant/components/vicare/climate.py | 2 +- .../components/volvooncall/__init__.py | 2 +- .../components/vultr/binary_sensor.py | 2 +- homeassistant/components/vultr/switch.py | 2 +- homeassistant/components/waqi/sensor.py | 2 +- .../components/waze_travel_time/sensor.py | 2 +- .../components/webostv/media_player.py | 2 +- homeassistant/components/wemo/fan.py | 2 +- homeassistant/components/wemo/switch.py | 2 +- homeassistant/components/whois/sensor.py | 2 +- homeassistant/components/wink/__init__.py | 10 +++---- .../components/wink/alarm_control_panel.py | 2 +- .../components/wink/binary_sensor.py | 20 ++++++------- homeassistant/components/wink/climate.py | 4 +-- homeassistant/components/wink/lock.py | 4 +-- homeassistant/components/wink/sensor.py | 4 +-- homeassistant/components/wink/switch.py | 4 +-- homeassistant/components/wink/water_heater.py | 2 +- .../components/wirelesstag/__init__.py | 2 +- homeassistant/components/wled/light.py | 2 +- homeassistant/components/wled/sensor.py | 2 +- homeassistant/components/wled/switch.py | 6 ++-- homeassistant/components/wolflink/sensor.py | 2 +- .../components/worldtidesinfo/sensor.py | 2 +- homeassistant/components/wsdot/sensor.py | 2 +- .../components/wunderground/sensor.py | 20 ++++++------- homeassistant/components/xbox_live/sensor.py | 2 +- .../components/xiaomi_aqara/__init__.py | 10 +++---- .../components/xiaomi_aqara/binary_sensor.py | 28 +++++++++---------- homeassistant/components/xiaomi_aqara/lock.py | 2 +- .../components/xiaomi_aqara/sensor.py | 2 +- .../components/xiaomi_aqara/switch.py | 4 +-- .../components/xiaomi_miio/air_quality.py | 2 +- homeassistant/components/xiaomi_miio/fan.py | 2 +- homeassistant/components/xiaomi_miio/light.py | 2 +- .../components/xiaomi_miio/sensor.py | 2 +- .../components/xiaomi_miio/switch.py | 2 +- .../components/xiaomi_miio/vacuum.py | 2 +- .../components/yandex_transport/sensor.py | 2 +- homeassistant/components/yeelight/light.py | 2 +- homeassistant/components/zabbix/sensor.py | 2 +- homeassistant/components/zamg/sensor.py | 2 +- homeassistant/components/zestimate/sensor.py | 2 +- homeassistant/components/zha/climate.py | 2 +- homeassistant/components/zha/entity.py | 8 +++--- homeassistant/components/zha/light.py | 2 +- homeassistant/components/zha/lock.py | 2 +- homeassistant/components/zha/sensor.py | 2 +- homeassistant/components/zodiac/sensor.py | 2 +- homeassistant/components/zwave/__init__.py | 2 +- homeassistant/components/zwave/climate.py | 4 +-- homeassistant/components/zwave/lock.py | 4 +-- homeassistant/components/zwave/node_entity.py | 2 +- homeassistant/components/zwave_js/climate.py | 2 +- homeassistant/components/zwave_js/sensor.py | 2 +- tests/components/uvc/test_camera.py | 2 +- tests/components/vultr/test_binary_sensor.py | 2 +- tests/components/vultr/test_switch.py | 2 +- tests/components/wsdot/test_sensor.py | 4 +-- tests/components/zwave/test_climate.py | 4 +-- tests/components/zwave/test_init.py | 2 +- tests/components/zwave/test_lock.py | 26 ++++++++--------- tests/components/zwave/test_node_entity.py | 17 ++++------- .../test/image_processing.py | 2 +- 127 files changed, 218 insertions(+), 223 deletions(-) diff --git a/homeassistant/components/tado/binary_sensor.py b/homeassistant/components/tado/binary_sensor.py index 068c3a7ce93..9f68aa8a4e7 100644 --- a/homeassistant/components/tado/binary_sensor.py +++ b/homeassistant/components/tado/binary_sensor.py @@ -231,7 +231,7 @@ class TadoZoneBinarySensor(TadoZoneEntity, BinarySensorEntity): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._state_attributes diff --git a/homeassistant/components/tado/climate.py b/homeassistant/components/tado/climate.py index 9547617a36b..b86eb08b1b0 100644 --- a/homeassistant/components/tado/climate.py +++ b/homeassistant/components/tado/climate.py @@ -462,7 +462,7 @@ class TadoClimate(TadoZoneEntity, ClimateEntity): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return temperature offset.""" return self._tado_zone_temp_offset diff --git a/homeassistant/components/tado/sensor.py b/homeassistant/components/tado/sensor.py index 8d38d9eab96..cfafeb14ddf 100644 --- a/homeassistant/components/tado/sensor.py +++ b/homeassistant/components/tado/sensor.py @@ -132,7 +132,7 @@ class TadoHomeSensor(TadoHomeEntity, Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._state_attributes @@ -237,7 +237,7 @@ class TadoZoneSensor(TadoZoneEntity, Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._state_attributes diff --git a/homeassistant/components/tahoma/__init__.py b/homeassistant/components/tahoma/__init__.py index b322d454002..8db7b23a8ce 100644 --- a/homeassistant/components/tahoma/__init__.py +++ b/homeassistant/components/tahoma/__init__.py @@ -137,7 +137,7 @@ class TahomaDevice(Entity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return {"tahoma_device_id": self.tahoma_device.url} diff --git a/homeassistant/components/tahoma/binary_sensor.py b/homeassistant/components/tahoma/binary_sensor.py index af06bf5ca4c..c0946013469 100644 --- a/homeassistant/components/tahoma/binary_sensor.py +++ b/homeassistant/components/tahoma/binary_sensor.py @@ -57,10 +57,10 @@ class TahomaBinarySensor(TahomaDevice, BinarySensorEntity): return self._icon @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attr = {} - super_attr = super().device_state_attributes + super_attr = super().extra_state_attributes if super_attr is not None: attr.update(super_attr) diff --git a/homeassistant/components/tahoma/cover.py b/homeassistant/components/tahoma/cover.py index 2eec9160811..a02f21fb5e1 100644 --- a/homeassistant/components/tahoma/cover.py +++ b/homeassistant/components/tahoma/cover.py @@ -194,10 +194,10 @@ class TahomaCover(TahomaDevice, CoverEntity): return TAHOMA_DEVICE_CLASSES.get(self.tahoma_device.type) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attr = {} - super_attr = super().device_state_attributes + super_attr = super().extra_state_attributes if super_attr is not None: attr.update(super_attr) diff --git a/homeassistant/components/tahoma/lock.py b/homeassistant/components/tahoma/lock.py index 93d82bffc99..3d160cd95b3 100644 --- a/homeassistant/components/tahoma/lock.py +++ b/homeassistant/components/tahoma/lock.py @@ -78,12 +78,12 @@ class TahomaLock(TahomaDevice, LockEntity): return self._lock_status == STATE_LOCKED @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the lock state attributes.""" attr = { ATTR_BATTERY_LEVEL: self._battery_level, } - super_attr = super().device_state_attributes + super_attr = super().extra_state_attributes if super_attr is not None: attr.update(super_attr) return attr diff --git a/homeassistant/components/tahoma/scene.py b/homeassistant/components/tahoma/scene.py index 1d53b65d5d5..3cfa26a5f89 100644 --- a/homeassistant/components/tahoma/scene.py +++ b/homeassistant/components/tahoma/scene.py @@ -37,6 +37,6 @@ class TahomaScene(Scene): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the scene.""" return {"tahoma_scene_oid": self.tahoma_scene.oid} diff --git a/homeassistant/components/tahoma/sensor.py b/homeassistant/components/tahoma/sensor.py index fb1129cfa0e..91629137318 100644 --- a/homeassistant/components/tahoma/sensor.py +++ b/homeassistant/components/tahoma/sensor.py @@ -110,10 +110,10 @@ class TahomaSensor(TahomaDevice, Entity): _LOGGER.debug("Update %s, value: %d", self._name, self.current_value) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attr = {} - super_attr = super().device_state_attributes + super_attr = super().extra_state_attributes if super_attr is not None: attr.update(super_attr) diff --git a/homeassistant/components/tahoma/switch.py b/homeassistant/components/tahoma/switch.py index 808f80d8cfa..2ea68b93e6b 100644 --- a/homeassistant/components/tahoma/switch.py +++ b/homeassistant/components/tahoma/switch.py @@ -105,10 +105,10 @@ class TahomaSwitch(TahomaDevice, SwitchEntity): return bool(self._state == STATE_ON) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attr = {} - super_attr = super().device_state_attributes + super_attr = super().extra_state_attributes if super_attr is not None: attr.update(super_attr) diff --git a/homeassistant/components/tank_utility/sensor.py b/homeassistant/components/tank_utility/sensor.py index ab1cc8b23da..d0566ec7c9e 100644 --- a/homeassistant/components/tank_utility/sensor.py +++ b/homeassistant/components/tank_utility/sensor.py @@ -97,7 +97,7 @@ class TankUtilitySensor(Entity): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attributes of the device.""" return self._attributes diff --git a/homeassistant/components/tankerkoenig/sensor.py b/homeassistant/components/tankerkoenig/sensor.py index 6985072b065..a1cbed2ba11 100644 --- a/homeassistant/components/tankerkoenig/sensor.py +++ b/homeassistant/components/tankerkoenig/sensor.py @@ -125,7 +125,7 @@ class FuelPriceSensor(CoordinatorEntity): return f"{self._station_id}_{self._fuel_type}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attributes of the device.""" data = self.coordinator.data[self._station_id] diff --git a/homeassistant/components/tautulli/sensor.py b/homeassistant/components/tautulli/sensor.py index ed96bb62ace..2d2d005e95d 100644 --- a/homeassistant/components/tautulli/sensor.py +++ b/homeassistant/components/tautulli/sensor.py @@ -130,7 +130,7 @@ class TautulliSensor(Entity): return "Watching" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return attributes for the sensor.""" return self._attributes diff --git a/homeassistant/components/tellduslive/entry.py b/homeassistant/components/tellduslive/entry.py index 851823385dc..4453622b21e 100644 --- a/homeassistant/components/tellduslive/entry.py +++ b/homeassistant/components/tellduslive/entry.py @@ -81,7 +81,7 @@ class TelldusLiveEntity(Entity): return self._client.is_available(self.device_id) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {} if self._battery_level: diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index f350eb87d61..0ae540edf97 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -168,7 +168,7 @@ class TemplateEntity(Entity): return self._entity_picture @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/tensorflow/image_processing.py b/homeassistant/components/tensorflow/image_processing.py index 65240dc04dc..0b8fafd57a8 100644 --- a/homeassistant/components/tensorflow/image_processing.py +++ b/homeassistant/components/tensorflow/image_processing.py @@ -279,7 +279,7 @@ class TensorFlowImageProcessor(ImageProcessingEntity): return self._total_matches @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return { ATTR_MATCHES: self._matches, diff --git a/homeassistant/components/tesla/__init__.py b/homeassistant/components/tesla/__init__.py index a2ed908948f..76b7f872024 100644 --- a/homeassistant/components/tesla/__init__.py +++ b/homeassistant/components/tesla/__init__.py @@ -307,7 +307,7 @@ class TeslaDevice(CoordinatorEntity): return ICONS.get(self.tesla_device.type) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" attr = self._attributes if self.tesla_device.has_battery(): diff --git a/homeassistant/components/tesla/device_tracker.py b/homeassistant/components/tesla/device_tracker.py index cac89d58d3a..16e8ce6dbe2 100644 --- a/homeassistant/components/tesla/device_tracker.py +++ b/homeassistant/components/tesla/device_tracker.py @@ -42,9 +42,9 @@ class TeslaDeviceEntity(TeslaDevice, TrackerEntity): return SOURCE_TYPE_GPS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" - attr = super().device_state_attributes.copy() + attr = super().extra_state_attributes.copy() location = self.tesla_device.get_location() if location: attr.update( diff --git a/homeassistant/components/tesla/sensor.py b/homeassistant/components/tesla/sensor.py index 3b66845c786..9094a7d6b01 100644 --- a/homeassistant/components/tesla/sensor.py +++ b/homeassistant/components/tesla/sensor.py @@ -81,7 +81,7 @@ class TeslaSensor(TeslaDevice, Entity): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" attr = self._attributes.copy() if self.tesla_device.type == "charging rate sensor": diff --git a/homeassistant/components/thermoworks_smoke/sensor.py b/homeassistant/components/thermoworks_smoke/sensor.py index 83a2fd12d24..d768f009364 100644 --- a/homeassistant/components/thermoworks_smoke/sensor.py +++ b/homeassistant/components/thermoworks_smoke/sensor.py @@ -124,7 +124,7 @@ class ThermoworksSmokeSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/thethingsnetwork/sensor.py b/homeassistant/components/thethingsnetwork/sensor.py index 0afec8f7510..d758a20f384 100644 --- a/homeassistant/components/thethingsnetwork/sensor.py +++ b/homeassistant/components/thethingsnetwork/sensor.py @@ -92,7 +92,7 @@ class TtnDataSensor(Entity): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" if self._ttn_data_storage.data is not None: return { diff --git a/homeassistant/components/threshold/binary_sensor.py b/homeassistant/components/threshold/binary_sensor.py index fa05fc09687..5bd6f77253b 100644 --- a/homeassistant/components/threshold/binary_sensor.py +++ b/homeassistant/components/threshold/binary_sensor.py @@ -144,7 +144,7 @@ class ThresholdSensor(BinarySensorEntity): return TYPE_UPPER @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return { ATTR_ENTITY_ID: self._entity_id, diff --git a/homeassistant/components/tibber/sensor.py b/homeassistant/components/tibber/sensor.py index bb5ebe8011f..01e050fdd24 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -54,7 +54,7 @@ class TibberSensor(Entity): self._last_updated = None self._state = None self._is_available = False - self._device_state_attributes = {} + self._extra_state_attributes = {} self._name = tibber_home.info["viewer"]["home"]["appNickname"] if self._name is None: self._name = tibber_home.info["viewer"]["home"]["address"].get( @@ -63,9 +63,9 @@ class TibberSensor(Entity): self._spread_load_constant = randrange(3600) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - return self._device_state_attributes + return self._extra_state_attributes @property def model(self): @@ -121,10 +121,10 @@ class TibberSensorElPrice(TibberSensor): res = self._tibber_home.current_price_data() self._state, price_level, self._last_updated = res - self._device_state_attributes["price_level"] = price_level + self._extra_state_attributes["price_level"] = price_level attrs = self._tibber_home.current_attributes() - self._device_state_attributes.update(attrs) + self._extra_state_attributes.update(attrs) self._is_available = self._state is not None @property @@ -165,11 +165,11 @@ class TibberSensorElPrice(TibberSensor): except (asyncio.TimeoutError, aiohttp.ClientError): return data = self._tibber_home.info["viewer"]["home"] - self._device_state_attributes["app_nickname"] = data["appNickname"] - self._device_state_attributes["grid_company"] = data["meteringPointData"][ + self._extra_state_attributes["app_nickname"] = data["appNickname"] + self._extra_state_attributes["grid_company"] = data["meteringPointData"][ "gridCompany" ] - self._device_state_attributes["estimated_annual_consumption"] = data[ + self._extra_state_attributes["estimated_annual_consumption"] = data[ "meteringPointData" ]["estimatedAnnualConsumption"] @@ -197,7 +197,7 @@ class TibberSensorRT(TibberSensor): for key, value in live_measurement.items(): if value is None: continue - self._device_state_attributes[key] = value + self._extra_state_attributes[key] = value self.async_write_ha_state() diff --git a/homeassistant/components/tile/device_tracker.py b/homeassistant/components/tile/device_tracker.py index f7cc4e1736e..7571e235ef1 100644 --- a/homeassistant/components/tile/device_tracker.py +++ b/homeassistant/components/tile/device_tracker.py @@ -81,7 +81,7 @@ class TileDeviceTracker(CoordinatorEntity, TrackerEntity): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return self._attrs diff --git a/homeassistant/components/tmb/sensor.py b/homeassistant/components/tmb/sensor.py index f731b912d65..c3f813d796a 100644 --- a/homeassistant/components/tmb/sensor.py +++ b/homeassistant/components/tmb/sensor.py @@ -101,7 +101,7 @@ class TMBSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the last update.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/tod/binary_sensor.py b/homeassistant/components/tod/binary_sensor.py index fde26acf604..26e0ead680a 100644 --- a/homeassistant/components/tod/binary_sensor.py +++ b/homeassistant/components/tod/binary_sensor.py @@ -109,7 +109,7 @@ class TodSensor(BinarySensorEntity): return self._next_update @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return { ATTR_AFTER: self.after.astimezone(self.hass.config.time_zone).isoformat(), diff --git a/homeassistant/components/todoist/calendar.py b/homeassistant/components/todoist/calendar.py index b9ab4fe6dbb..976462c95fa 100644 --- a/homeassistant/components/todoist/calendar.py +++ b/homeassistant/components/todoist/calendar.py @@ -284,7 +284,7 @@ class TodoistProjectDevice(CalendarEventDevice): return await self.data.async_get_events(hass, start_date, end_date) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if self.data.event is None: # No tasks, we don't REALLY need to show anything. diff --git a/homeassistant/components/toon/climate.py b/homeassistant/components/toon/climate.py index ba3ef8ee807..99b76600dce 100644 --- a/homeassistant/components/toon/climate.py +++ b/homeassistant/components/toon/climate.py @@ -114,7 +114,7 @@ class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateEntity): return DEFAULT_MAX_TEMP @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the current state of the burner.""" return {"heating_type": self.coordinator.data.agreement.heating_type} diff --git a/homeassistant/components/totalconnect/alarm_control_panel.py b/homeassistant/components/totalconnect/alarm_control_panel.py index affff382365..c277198b683 100644 --- a/homeassistant/components/totalconnect/alarm_control_panel.py +++ b/homeassistant/components/totalconnect/alarm_control_panel.py @@ -44,7 +44,7 @@ class TotalConnectAlarm(alarm.AlarmControlPanelEntity): self._location_id = location_id self._client = client self._state = None - self._device_state_attributes = {} + self._extra_state_attributes = {} @property def name(self): @@ -62,9 +62,9 @@ class TotalConnectAlarm(alarm.AlarmControlPanelEntity): return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" - return self._device_state_attributes + return self._extra_state_attributes def update(self): """Return the state of the device.""" @@ -109,7 +109,7 @@ class TotalConnectAlarm(alarm.AlarmControlPanelEntity): state = None self._state = state - self._device_state_attributes = attr + self._extra_state_attributes = attr def alarm_disarm(self, code=None): """Send disarm command.""" diff --git a/homeassistant/components/totalconnect/binary_sensor.py b/homeassistant/components/totalconnect/binary_sensor.py index 6bee603d1b1..ef02c5d1fd3 100644 --- a/homeassistant/components/totalconnect/binary_sensor.py +++ b/homeassistant/components/totalconnect/binary_sensor.py @@ -73,7 +73,7 @@ class TotalConnectBinarySensor(BinarySensorEntity): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attributes = { "zone_id": self._zone_id, diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index ceb0944efe6..31b2319ead0 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -163,7 +163,7 @@ class TPLinkSmartBulb(LightEntity): return self._is_available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._emeter_params diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py index 23000fe7b59..dec20edec65 100644 --- a/homeassistant/components/tplink/switch.py +++ b/homeassistant/components/tplink/switch.py @@ -100,7 +100,7 @@ class SmartPlugSwitch(SwitchEntity): self.smartplug.turn_off() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._emeter_params diff --git a/homeassistant/components/traccar/device_tracker.py b/homeassistant/components/traccar/device_tracker.py index cdb20339510..d558129e323 100644 --- a/homeassistant/components/traccar/device_tracker.py +++ b/homeassistant/components/traccar/device_tracker.py @@ -345,7 +345,7 @@ class TraccarEntity(TrackerEntity, RestoreEntity): return self._battery @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific attributes.""" return self._attributes diff --git a/homeassistant/components/tradfri/cover.py b/homeassistant/components/tradfri/cover.py index 72597637bd3..4c7cde1dfd1 100644 --- a/homeassistant/components/tradfri/cover.py +++ b/homeassistant/components/tradfri/cover.py @@ -29,7 +29,7 @@ class TradfriCover(TradfriBaseDevice, CoverEntity): self._refresh(device) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_MODEL: self._device.device_info.model_number} diff --git a/homeassistant/components/trafikverket_train/sensor.py b/homeassistant/components/trafikverket_train/sensor.py index 12f3cf73e50..45899422564 100644 --- a/homeassistant/components/trafikverket_train/sensor.py +++ b/homeassistant/components/trafikverket_train/sensor.py @@ -153,7 +153,7 @@ class TrainSensor(Entity): self._delay_in_minutes = self._state.get_delay_time() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._state is None: return None diff --git a/homeassistant/components/trafikverket_weatherstation/sensor.py b/homeassistant/components/trafikverket_weatherstation/sensor.py index bb1bad67f82..f2e2521a90b 100644 --- a/homeassistant/components/trafikverket_weatherstation/sensor.py +++ b/homeassistant/components/trafikverket_weatherstation/sensor.py @@ -172,7 +172,7 @@ class TrafikverketWeatherStation(Entity): return self._icon @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of Trafikverket Weatherstation.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index 2a24b80be16..d2b52771124 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -148,7 +148,7 @@ class TransmissionTorrentsSensor(TransmissionSensor): return "Torrents" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes, if any.""" info = _torrents_info( torrents=self._tm_client.api.torrents, diff --git a/homeassistant/components/transport_nsw/sensor.py b/homeassistant/components/transport_nsw/sensor.py index 2aa27f8c4b3..f6d4e40e4e8 100644 --- a/homeassistant/components/transport_nsw/sensor.py +++ b/homeassistant/components/transport_nsw/sensor.py @@ -87,7 +87,7 @@ class TransportNSWSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._times is not None: return { diff --git a/homeassistant/components/travisci/sensor.py b/homeassistant/components/travisci/sensor.py index 8a548d8cb6b..6464667bffe 100644 --- a/homeassistant/components/travisci/sensor.py +++ b/homeassistant/components/travisci/sensor.py @@ -124,7 +124,7 @@ class TravisCISensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {} attrs[ATTR_ATTRIBUTION] = ATTRIBUTION diff --git a/homeassistant/components/trend/binary_sensor.py b/homeassistant/components/trend/binary_sensor.py index b7079a3311a..52a72eca8c9 100644 --- a/homeassistant/components/trend/binary_sensor.py +++ b/homeassistant/components/trend/binary_sensor.py @@ -146,7 +146,7 @@ class SensorTrend(BinarySensorEntity): return self._device_class @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return { ATTR_ENTITY_ID: self._entity_id, diff --git a/homeassistant/components/twitch/sensor.py b/homeassistant/components/twitch/sensor.py index 4b019628158..0e5abb58747 100644 --- a/homeassistant/components/twitch/sensor.py +++ b/homeassistant/components/twitch/sensor.py @@ -88,7 +88,7 @@ class TwitchSensor(Entity): return self._preview @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attr = dict(self._statistics) diff --git a/homeassistant/components/uk_transport/sensor.py b/homeassistant/components/uk_transport/sensor.py index e7c01479a96..80b97b186be 100644 --- a/homeassistant/components/uk_transport/sensor.py +++ b/homeassistant/components/uk_transport/sensor.py @@ -191,7 +191,7 @@ class UkTransportLiveBusTimeSensor(UkTransportSensor): self._state = None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other details about the sensor state.""" attrs = {} if self._data is not None: @@ -261,7 +261,7 @@ class UkTransportLiveTrainTimeSensor(UkTransportSensor): self._state = None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other details about the sensor state.""" attrs = {} if self._data is not None: diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index ac28f7475f6..dddb4d0e5e3 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -249,7 +249,7 @@ class UniFiClientTracker(UniFiClient, ScannerEntity): return f"{self.client.mac}-{self.controller.site}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the client state attributes.""" raw = self.client.raw @@ -421,7 +421,7 @@ class UniFiDeviceTracker(UniFiBase, ScannerEntity): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if self.device.state == 0: return {} diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index 6c8b6ea35cd..da3139317d1 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -238,7 +238,7 @@ class UniFiPOEClientSwitch(UniFiClient, SwitchEntity, RestoreEntity): await self.device.async_set_port_poe_mode(self.client.sw_port, "off") @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = { "power": self.port.poe_power, diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index 2702f26a3d3..c814abf5a82 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -502,7 +502,7 @@ class UniversalMediaPlayer(MediaPlayerEntity): return flags @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" active_child = self._child_state return {ATTR_ACTIVE_CHILD: active_child.entity_id} if active_child else {} diff --git a/homeassistant/components/upb/__init__.py b/homeassistant/components/upb/__init__.py index bf23be16af2..bd591447472 100644 --- a/homeassistant/components/upb/__init__.py +++ b/homeassistant/components/upb/__init__.py @@ -110,7 +110,7 @@ class UpbEntity(Entity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the default attributes of the element.""" return self._element.as_dict() diff --git a/homeassistant/components/upcloud/__init__.py b/homeassistant/components/upcloud/__init__.py index bb96ef0f1d6..eba61058ca0 100644 --- a/homeassistant/components/upcloud/__init__.py +++ b/homeassistant/components/upcloud/__init__.py @@ -296,7 +296,7 @@ class UpCloudServerEntity(CoordinatorEntity): return DEFAULT_COMPONENT_DEVICE_CLASS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the UpCloud server.""" return { x: getattr(self._server, x, None) diff --git a/homeassistant/components/updater/binary_sensor.py b/homeassistant/components/updater/binary_sensor.py index 36e05513d43..93d19029992 100644 --- a/homeassistant/components/updater/binary_sensor.py +++ b/homeassistant/components/updater/binary_sensor.py @@ -35,7 +35,7 @@ class UpdaterBinary(CoordinatorEntity, BinarySensorEntity): return self.coordinator.data.update_available @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the optional state attributes.""" if not self.coordinator.data: return None diff --git a/homeassistant/components/uptimerobot/binary_sensor.py b/homeassistant/components/uptimerobot/binary_sensor.py index e31d8b44b10..6c0bb63c70f 100644 --- a/homeassistant/components/uptimerobot/binary_sensor.py +++ b/homeassistant/components/uptimerobot/binary_sensor.py @@ -75,7 +75,7 @@ class UptimeRobotBinarySensor(BinarySensorEntity): return DEVICE_CLASS_CONNECTIVITY @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the binary sensor.""" return {ATTR_ATTRIBUTION: ATTRIBUTION, ATTR_TARGET: self._target} diff --git a/homeassistant/components/uscis/sensor.py b/homeassistant/components/uscis/sensor.py index 2e15a4b1422..5f26a0ff82e 100644 --- a/homeassistant/components/uscis/sensor.py +++ b/homeassistant/components/uscis/sensor.py @@ -60,7 +60,7 @@ class UscisSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/usgs_earthquakes_feed/geo_location.py b/homeassistant/components/usgs_earthquakes_feed/geo_location.py index 9fd98de42df..364e0599b75 100644 --- a/homeassistant/components/usgs_earthquakes_feed/geo_location.py +++ b/homeassistant/components/usgs_earthquakes_feed/geo_location.py @@ -280,7 +280,7 @@ class UsgsEarthquakesEvent(GeolocationEvent): return DEFAULT_UNIT_OF_MEASUREMENT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = {} for key, value in ( diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index e8d551ba280..09f788806f6 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -324,7 +324,7 @@ class UtilityMeterSensor(RestoreEntity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" state_attr = { ATTR_SOURCE_ID: self._sensor_source_id, diff --git a/homeassistant/components/uvc/camera.py b/homeassistant/components/uvc/camera.py index a20b99d673a..82f59a40131 100644 --- a/homeassistant/components/uvc/camera.py +++ b/homeassistant/components/uvc/camera.py @@ -111,7 +111,7 @@ class UnifiVideoCamera(Camera): return 0 @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the camera state attributes.""" attr = {} if self.motion_detection_enabled: diff --git a/homeassistant/components/vallox/fan.py b/homeassistant/components/vallox/fan.py index 525bf00f50e..e167791e702 100644 --- a/homeassistant/components/vallox/fan.py +++ b/homeassistant/components/vallox/fan.py @@ -83,7 +83,7 @@ class ValloxFan(FanEntity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return { ATTR_PROFILE_FAN_SPEED_HOME["description"]: self._fan_speed_home, diff --git a/homeassistant/components/vasttrafik/sensor.py b/homeassistant/components/vasttrafik/sensor.py index 882274f8e84..d1a461d94fa 100644 --- a/homeassistant/components/vasttrafik/sensor.py +++ b/homeassistant/components/vasttrafik/sensor.py @@ -106,7 +106,7 @@ class VasttrafikDepartureSensor(Entity): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/venstar/climate.py b/homeassistant/components/venstar/climate.py index 261339f70dd..b4d8264a3ab 100644 --- a/homeassistant/components/venstar/climate.py +++ b/homeassistant/components/venstar/climate.py @@ -200,7 +200,7 @@ class VenstarThermostat(ClimateEntity): return FAN_AUTO @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" return { ATTR_FAN_STATE: self._client.fanstate, diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index 4bfa72b5eb6..ff8dc8b96d1 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -248,7 +248,7 @@ class VeraDevice(Generic[DeviceType], Entity): return self.vera_device.should_poll @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the device.""" attr = {} diff --git a/homeassistant/components/vera/lock.py b/homeassistant/components/vera/lock.py index b77f17d3b0a..f98319b6ccf 100644 --- a/homeassistant/components/vera/lock.py +++ b/homeassistant/components/vera/lock.py @@ -61,14 +61,14 @@ class VeraLock(VeraDevice[veraApi.VeraLock], LockEntity): return self._state == STATE_LOCKED @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Who unlocked the lock and did a low battery alert fire. Reports on the previous poll cycle. changed_by_name is a string like 'Bob'. low_battery is 1 if an alert fired, 0 otherwise. """ - data = super().device_state_attributes + data = super().extra_state_attributes last_user = self.vera_device.get_last_user_alert() if last_user is not None: diff --git a/homeassistant/components/vera/scene.py b/homeassistant/components/vera/scene.py index 2274b67f683..4ecbe8c724e 100644 --- a/homeassistant/components/vera/scene.py +++ b/homeassistant/components/vera/scene.py @@ -53,6 +53,6 @@ class VeraScene(Scene): return self._name @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the scene.""" return {"vera_scene_id": self.vera_scene.vera_scene_id} diff --git a/homeassistant/components/version/sensor.py b/homeassistant/components/version/sensor.py index c030383a4a6..645cde63459 100644 --- a/homeassistant/components/version/sensor.py +++ b/homeassistant/components/version/sensor.py @@ -118,7 +118,7 @@ class VersionSensor(Entity): return self.haversion.api.version @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return attributes for the sensor.""" return self.haversion.api.version_data diff --git a/homeassistant/components/vesync/fan.py b/homeassistant/components/vesync/fan.py index 1d1320d8d78..d01d3d4dc5d 100644 --- a/homeassistant/components/vesync/fan.py +++ b/homeassistant/components/vesync/fan.py @@ -101,7 +101,7 @@ class VeSyncFanHA(VeSyncDevice, FanEntity): return self.smartfan.uuid @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the fan.""" return { "mode": self.smartfan.mode, diff --git a/homeassistant/components/vesync/switch.py b/homeassistant/components/vesync/switch.py index 0ce4b931def..1d01e340b20 100644 --- a/homeassistant/components/vesync/switch.py +++ b/homeassistant/components/vesync/switch.py @@ -72,7 +72,7 @@ class VeSyncSwitchHA(VeSyncBaseSwitch, SwitchEntity): self.smartplug = plug @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" if not hasattr(self.smartplug, "weekly_energy_total"): return {} diff --git a/homeassistant/components/viaggiatreno/sensor.py b/homeassistant/components/viaggiatreno/sensor.py index 5de968f5eac..e886b9d9728 100644 --- a/homeassistant/components/viaggiatreno/sensor.py +++ b/homeassistant/components/viaggiatreno/sensor.py @@ -119,7 +119,7 @@ class ViaggiaTrenoSensor(Entity): return self._unit @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return extra attributes.""" self._attributes[ATTR_ATTRIBUTION] = ATTRIBUTION return self._attributes diff --git a/homeassistant/components/vicare/climate.py b/homeassistant/components/vicare/climate.py index d1accd8ea0a..4878ea330f4 100644 --- a/homeassistant/components/vicare/climate.py +++ b/homeassistant/components/vicare/climate.py @@ -278,7 +278,7 @@ class ViCareClimate(ClimateEntity): self._api.activateProgram(vicare_program) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Show Device Attributes.""" return self._attributes diff --git a/homeassistant/components/volvooncall/__init__.py b/homeassistant/components/volvooncall/__init__.py index 743bb903c72..556a5f25114 100644 --- a/homeassistant/components/volvooncall/__init__.py +++ b/homeassistant/components/volvooncall/__init__.py @@ -277,7 +277,7 @@ class VolvoEntity(Entity): return True @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return dict( self.instrument.attributes, diff --git a/homeassistant/components/vultr/binary_sensor.py b/homeassistant/components/vultr/binary_sensor.py index c1b60479e7a..c62d5136aa6 100644 --- a/homeassistant/components/vultr/binary_sensor.py +++ b/homeassistant/components/vultr/binary_sensor.py @@ -86,7 +86,7 @@ class VultrBinarySensor(BinarySensorEntity): return DEFAULT_DEVICE_CLASS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the Vultr subscription.""" return { ATTR_ALLOWED_BANDWIDTH: self.data.get("allowed_bandwidth_gb"), diff --git a/homeassistant/components/vultr/switch.py b/homeassistant/components/vultr/switch.py index a9c43717a71..f93c4f444d6 100644 --- a/homeassistant/components/vultr/switch.py +++ b/homeassistant/components/vultr/switch.py @@ -80,7 +80,7 @@ class VultrSwitch(SwitchEntity): return "mdi:server" if self.is_on else "mdi:server-off" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the Vultr subscription.""" return { ATTR_ALLOWED_BANDWIDTH: self.data.get("allowed_bandwidth_gb"), diff --git a/homeassistant/components/waqi/sensor.py b/homeassistant/components/waqi/sensor.py index ec18880b5ba..ac43da68641 100644 --- a/homeassistant/components/waqi/sensor.py +++ b/homeassistant/components/waqi/sensor.py @@ -151,7 +151,7 @@ class WaqiSensor(Entity): return "AQI" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the last update.""" attrs = {} diff --git a/homeassistant/components/waze_travel_time/sensor.py b/homeassistant/components/waze_travel_time/sensor.py index 0357825cb12..be2cf7ca2da 100644 --- a/homeassistant/components/waze_travel_time/sensor.py +++ b/homeassistant/components/waze_travel_time/sensor.py @@ -176,7 +176,7 @@ class WazeTravelTime(Entity): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the last update.""" if self._waze_data.duration is None: return None diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index 4807d780a48..0fc442b37e9 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -318,7 +318,7 @@ class LgWebOSMediaPlayerEntity(MediaPlayerEntity): return supported @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" if self._client.sound_output is None and self.state == STATE_OFF: return {} diff --git a/homeassistant/components/wemo/fan.py b/homeassistant/components/wemo/fan.py index d8f54057557..a3da5edae76 100644 --- a/homeassistant/components/wemo/fan.py +++ b/homeassistant/components/wemo/fan.py @@ -115,7 +115,7 @@ class WemoHumidifier(WemoSubscriptionEntity, FanEntity): return "mdi:water-percent" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return { ATTR_CURRENT_HUMIDITY: self._current_humidity, diff --git a/homeassistant/components/wemo/switch.py b/homeassistant/components/wemo/switch.py index 15b38550b93..5e97031786c 100644 --- a/homeassistant/components/wemo/switch.py +++ b/homeassistant/components/wemo/switch.py @@ -59,7 +59,7 @@ class WemoSwitch(WemoSubscriptionEntity, SwitchEntity): self._mode_string = None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" attr = {} if self.maker_params: diff --git a/homeassistant/components/whois/sensor.py b/homeassistant/components/whois/sensor.py index 0e3c0c6e0da..72c992456a7 100644 --- a/homeassistant/components/whois/sensor.py +++ b/homeassistant/components/whois/sensor.py @@ -81,7 +81,7 @@ class WhoisSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Get the more info attributes.""" return self._attributes diff --git a/homeassistant/components/wink/__init__.py b/homeassistant/components/wink/__init__.py index 26666bf4b15..198bddc937b 100644 --- a/homeassistant/components/wink/__init__.py +++ b/homeassistant/components/wink/__init__.py @@ -778,7 +778,7 @@ class WinkDevice(Entity): return self.wink.pubnub_channel is None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attributes = {} battery = self._battery_level @@ -855,9 +855,9 @@ class WinkSirenDevice(WinkDevice): return "mdi:bell-ring" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - attributes = super().device_state_attributes + attributes = super().extra_state_attributes auto_shutoff = self.wink.auto_shutoff() if auto_shutoff is not None: @@ -913,9 +913,9 @@ class WinkNimbusDialDevice(WinkDevice): return f"{self.parent.name()} dial {self.wink.index() + 1}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - attributes = super().device_state_attributes + attributes = super().extra_state_attributes dial_attributes = self.dial_attributes() return {**attributes, **dial_attributes} diff --git a/homeassistant/components/wink/alarm_control_panel.py b/homeassistant/components/wink/alarm_control_panel.py index 5c45cc7b03d..2f5ac83c6f5 100644 --- a/homeassistant/components/wink/alarm_control_panel.py +++ b/homeassistant/components/wink/alarm_control_panel.py @@ -70,6 +70,6 @@ class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanelEntity): self.wink.set_mode("away") @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"private": self.wink.private()} diff --git a/homeassistant/components/wink/binary_sensor.py b/homeassistant/components/wink/binary_sensor.py index 77ff464a5bf..ea864e912f0 100644 --- a/homeassistant/components/wink/binary_sensor.py +++ b/homeassistant/components/wink/binary_sensor.py @@ -119,18 +119,18 @@ class WinkBinarySensorEntity(WinkDevice, BinarySensorEntity): return SENSOR_TYPES.get(self.capability) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - return super().device_state_attributes + return super().extra_state_attributes class WinkSmokeDetector(WinkBinarySensorEntity): """Representation of a Wink Smoke detector.""" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - _attributes = super().device_state_attributes + _attributes = super().extra_state_attributes _attributes["test_activated"] = self.wink.test_activated() return _attributes @@ -139,9 +139,9 @@ class WinkHub(WinkBinarySensorEntity): """Representation of a Wink Hub.""" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - _attributes = super().device_state_attributes + _attributes = super().extra_state_attributes _attributes["update_needed"] = self.wink.update_needed() _attributes["firmware_version"] = self.wink.firmware_version() _attributes["pairing_mode"] = self.wink.pairing_mode() @@ -159,9 +159,9 @@ class WinkRemote(WinkBinarySensorEntity): """Representation of a Wink Lutron Connected bulb remote.""" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - _attributes = super().device_state_attributes + _attributes = super().extra_state_attributes _attributes["button_on_pressed"] = self.wink.button_on_pressed() _attributes["button_off_pressed"] = self.wink.button_off_pressed() _attributes["button_up_pressed"] = self.wink.button_up_pressed() @@ -178,9 +178,9 @@ class WinkButton(WinkBinarySensorEntity): """Representation of a Wink Relay button.""" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - _attributes = super().device_state_attributes + _attributes = super().extra_state_attributes _attributes["pressed"] = self.wink.pressed() _attributes["long_pressed"] = self.wink.long_pressed() return _attributes diff --git a/homeassistant/components/wink/climate.py b/homeassistant/components/wink/climate.py index 7ee05f0a729..4c783e6bde1 100644 --- a/homeassistant/components/wink/climate.py +++ b/homeassistant/components/wink/climate.py @@ -99,7 +99,7 @@ class WinkThermostat(WinkDevice, ClimateEntity): return TEMP_CELSIUS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional device state attributes.""" data = {} if self.external_temperature is not None: @@ -396,7 +396,7 @@ class WinkAC(WinkDevice, ClimateEntity): return TEMP_CELSIUS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional device state attributes.""" data = {} data[ATTR_TOTAL_CONSUMPTION] = self.wink.total_consumption() diff --git a/homeassistant/components/wink/lock.py b/homeassistant/components/wink/lock.py index f82b74e7712..63a67d9f1ac 100644 --- a/homeassistant/components/wink/lock.py +++ b/homeassistant/components/wink/lock.py @@ -187,9 +187,9 @@ class WinkLockDevice(WinkDevice, LockEntity): self.wink.set_alarm_mode(mode) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - super_attrs = super().device_state_attributes + super_attrs = super().extra_state_attributes sensitivity = dict_value_to_key( ALARM_SENSITIVITY_MAP, self.wink.alarm_sensitivity() ) diff --git a/homeassistant/components/wink/sensor.py b/homeassistant/components/wink/sensor.py index cd3eb756fb3..d2de4c43945 100644 --- a/homeassistant/components/wink/sensor.py +++ b/homeassistant/components/wink/sensor.py @@ -83,9 +83,9 @@ class WinkSensorDevice(WinkDevice): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - super_attrs = super().device_state_attributes + super_attrs = super().extra_state_attributes try: super_attrs["egg_times"] = self.wink.eggs() except AttributeError: diff --git a/homeassistant/components/wink/switch.py b/homeassistant/components/wink/switch.py index 2632036095a..d377ae0cddf 100644 --- a/homeassistant/components/wink/switch.py +++ b/homeassistant/components/wink/switch.py @@ -48,9 +48,9 @@ class WinkToggleDevice(WinkDevice, ToggleEntity): self.wink.set_state(False) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - attributes = super().device_state_attributes + attributes = super().extra_state_attributes try: event = self.wink.last_event() if event is not None: diff --git a/homeassistant/components/wink/water_heater.py b/homeassistant/components/wink/water_heater.py index 0ce31762c7a..bf5e8434746 100644 --- a/homeassistant/components/wink/water_heater.py +++ b/homeassistant/components/wink/water_heater.py @@ -66,7 +66,7 @@ class WinkWaterHeater(WinkDevice, WaterHeaterEntity): return TEMP_CELSIUS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional device state attributes.""" data = {} data[ATTR_VACATION_MODE] = self.wink.vacation_mode_enabled() diff --git a/homeassistant/components/wirelesstag/__init__.py b/homeassistant/components/wirelesstag/__init__.py index 83e92c2250b..0efbc80f13c 100644 --- a/homeassistant/components/wirelesstag/__init__.py +++ b/homeassistant/components/wirelesstag/__init__.py @@ -272,7 +272,7 @@ class WirelessTagBaseSensor(Entity): self._state = self.updated_state_value() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_BATTERY_LEVEL: int(self._tag.battery_remaining * 100), diff --git a/homeassistant/components/wled/light.py b/homeassistant/components/wled/light.py index f89cf06a44c..d25c0fc9064 100644 --- a/homeassistant/components/wled/light.py +++ b/homeassistant/components/wled/light.py @@ -188,7 +188,7 @@ class WLEDSegmentLight(LightEntity, WLEDDeviceEntity): return super().available @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" playlist = self.coordinator.data.state.playlist if playlist == -1: diff --git a/homeassistant/components/wled/sensor.py b/homeassistant/components/wled/sensor.py index 89d76776a82..da002e1e8f0 100644 --- a/homeassistant/components/wled/sensor.py +++ b/homeassistant/components/wled/sensor.py @@ -92,7 +92,7 @@ class WLEDEstimatedCurrentSensor(WLEDSensor): ) @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" return { ATTR_LED_COUNT: self.coordinator.data.info.leds.count, diff --git a/homeassistant/components/wled/switch.py b/homeassistant/components/wled/switch.py index 38ebd0e9b29..e58b32425cb 100644 --- a/homeassistant/components/wled/switch.py +++ b/homeassistant/components/wled/switch.py @@ -72,7 +72,7 @@ class WLEDNightlightSwitch(WLEDSwitch): ) @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" return { ATTR_DURATION: self.coordinator.data.state.nightlight.duration, @@ -110,7 +110,7 @@ class WLEDSyncSendSwitch(WLEDSwitch): ) @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" return {ATTR_UDP_PORT: self.coordinator.data.info.udp_port} @@ -144,7 +144,7 @@ class WLEDSyncReceiveSwitch(WLEDSwitch): ) @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" return {ATTR_UDP_PORT: self.coordinator.data.info.udp_port} diff --git a/homeassistant/components/wolflink/sensor.py b/homeassistant/components/wolflink/sensor.py index 201979d4dc3..9ea7f9d1163 100644 --- a/homeassistant/components/wolflink/sensor.py +++ b/homeassistant/components/wolflink/sensor.py @@ -69,7 +69,7 @@ class WolfLinkSensor(CoordinatorEntity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { "parameter_id": self.wolf_object.parameter_id, diff --git a/homeassistant/components/worldtidesinfo/sensor.py b/homeassistant/components/worldtidesinfo/sensor.py index aaa9f2d1585..43c9446b6ce 100644 --- a/homeassistant/components/worldtidesinfo/sensor.py +++ b/homeassistant/components/worldtidesinfo/sensor.py @@ -72,7 +72,7 @@ class WorldTidesInfoSensor(Entity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of this device.""" attr = {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/wsdot/sensor.py b/homeassistant/components/wsdot/sensor.py index 786fd07f626..34ad5a37ec8 100644 --- a/homeassistant/components/wsdot/sensor.py +++ b/homeassistant/components/wsdot/sensor.py @@ -120,7 +120,7 @@ class WashingtonStateTravelTimeSensor(WashingtonStateTransportSensor): self._state = self._data.get(ATTR_CURRENT_TIME) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other details about the sensor state.""" if self._data is not None: attrs = {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/wunderground/sensor.py b/homeassistant/components/wunderground/sensor.py index e1bd79b7ea0..1f4332c35e7 100644 --- a/homeassistant/components/wunderground/sensor.py +++ b/homeassistant/components/wunderground/sensor.py @@ -70,7 +70,7 @@ class WUSensorConfig: unit_of_measurement: Optional[str] = None, entity_picture=None, icon: str = "mdi:gauge", - device_state_attributes=None, + extra_state_attributes=None, device_class=None, ): """Initialize sensor configuration. @@ -82,7 +82,7 @@ class WUSensorConfig: :param unit_of_measurement: unit of measurement :param entity_picture: value or callback returning URL of entity picture :param icon: icon name or URL - :param device_state_attributes: dictionary of attributes, or callable that returns it + :param extra_state_attributes: dictionary of attributes, or callable that returns it """ self.friendly_name = friendly_name self.unit_of_measurement = unit_of_measurement @@ -90,7 +90,7 @@ class WUSensorConfig: self.value = value self.entity_picture = entity_picture self.icon = icon - self.device_state_attributes = device_state_attributes or {} + self.extra_state_attributes = extra_state_attributes or {} self.device_class = device_class @@ -121,7 +121,7 @@ class WUCurrentConditionsSensorConfig(WUSensorConfig): entity_picture=lambda wu: wu.data["current_observation"]["icon_url"] if icon is None else None, - device_state_attributes={ + extra_state_attributes={ "date": lambda wu: wu.data["current_observation"]["observation_time"] }, device_class=device_class, @@ -152,7 +152,7 @@ class WUDailyTextForecastSensorConfig(WUSensorConfig): "forecastday" ][period]["icon_url"], unit_of_measurement=unit_of_measurement, - device_state_attributes={ + extra_state_attributes={ "date": lambda wu: wu.data["forecast"]["txt_forecast"]["date"] }, ) @@ -201,7 +201,7 @@ class WUDailySimpleForecastSensorConfig(WUSensorConfig): if not icon else None, icon=icon, - device_state_attributes={ + extra_state_attributes={ "date": lambda wu: wu.data["forecast"]["simpleforecast"]["forecastday"][ period ]["date"]["pretty"] @@ -227,7 +227,7 @@ class WUHourlyForecastSensorConfig(WUSensorConfig): feature="hourly", value=lambda wu: wu.data["hourly_forecast"][period][field], entity_picture=lambda wu: wu.data["hourly_forecast"][period]["icon_url"], - device_state_attributes={ + extra_state_attributes={ "temp_c": lambda wu: wu.data["hourly_forecast"][period]["temp"][ "metric" ], @@ -315,7 +315,7 @@ class WUAlertsSensorConfig(WUSensorConfig): icon=lambda wu: "mdi:alert-circle-outline" if wu.data["alerts"] else "mdi:check-circle-outline", - device_state_attributes=self._get_attributes, + extra_state_attributes=self._get_attributes, ) @staticmethod @@ -1157,7 +1157,7 @@ class WUndergroundSensor(Entity): def _update_attrs(self): """Parse and update device state attributes.""" - attrs = self._cfg_expand("device_state_attributes", {}) + attrs = self._cfg_expand("extra_state_attributes", {}) for (attr, callback) in attrs.items(): if callable(callback): @@ -1185,7 +1185,7 @@ class WUndergroundSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/xbox_live/sensor.py b/homeassistant/components/xbox_live/sensor.py index 300fcbfb095..780051b2d87 100644 --- a/homeassistant/components/xbox_live/sensor.py +++ b/homeassistant/components/xbox_live/sensor.py @@ -104,7 +104,7 @@ class XboxSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attributes = {"gamerscore": self._gamerscore, "tier": self._tier} diff --git a/homeassistant/components/xiaomi_aqara/__init__.py b/homeassistant/components/xiaomi_aqara/__init__.py index 53a9427d30a..ba7f717f421 100644 --- a/homeassistant/components/xiaomi_aqara/__init__.py +++ b/homeassistant/components/xiaomi_aqara/__init__.py @@ -241,7 +241,7 @@ class XiaomiDevice(Entity): self._type = device_type self._write_to_hub = xiaomi_hub.write_to_hub self._get_from_hub = xiaomi_hub.get_from_hub - self._device_state_attributes = {} + self._extra_state_attributes = {} self._remove_unavailability_tracker = None self._xiaomi_hub = xiaomi_hub self.parse_data(device["data"], device["raw_data"]) @@ -319,9 +319,9 @@ class XiaomiDevice(Entity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - return self._device_state_attributes + return self._extra_state_attributes @callback def _async_set_unavailable(self, now): @@ -364,11 +364,11 @@ class XiaomiDevice(Entity): max_volt = 3300 min_volt = 2800 voltage = data[voltage_key] - self._device_state_attributes[ATTR_VOLTAGE] = round(voltage / 1000.0, 2) + self._extra_state_attributes[ATTR_VOLTAGE] = round(voltage / 1000.0, 2) voltage = min(voltage, max_volt) voltage = max(voltage, min_volt) percent = ((voltage - min_volt) / (max_volt - min_volt)) * 100 - self._device_state_attributes[ATTR_BATTERY_LEVEL] = round(percent, 1) + self._extra_state_attributes[ATTR_BATTERY_LEVEL] = round(percent, 1) return True def parse_data(self, data, raw_data): diff --git a/homeassistant/components/xiaomi_aqara/binary_sensor.py b/homeassistant/components/xiaomi_aqara/binary_sensor.py index 8fbecee46e9..3d9437e3778 100644 --- a/homeassistant/components/xiaomi_aqara/binary_sensor.py +++ b/homeassistant/components/xiaomi_aqara/binary_sensor.py @@ -170,10 +170,10 @@ class XiaomiNatgasSensor(XiaomiBinarySensor): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {ATTR_DENSITY: self._density} - attrs.update(super().device_state_attributes) + attrs.update(super().extra_state_attributes) return attrs def parse_data(self, data, raw_data): @@ -214,10 +214,10 @@ class XiaomiMotionSensor(XiaomiBinarySensor): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {ATTR_NO_MOTION_SINCE: self._no_motion_since} - attrs.update(super().device_state_attributes) + attrs.update(super().extra_state_attributes) return attrs @callback @@ -308,10 +308,10 @@ class XiaomiDoorSensor(XiaomiBinarySensor): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {ATTR_OPEN_SINCE: self._open_since} - attrs.update(super().device_state_attributes) + attrs.update(super().extra_state_attributes) return attrs def parse_data(self, data, raw_data): @@ -389,10 +389,10 @@ class XiaomiSmokeSensor(XiaomiBinarySensor): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {ATTR_DENSITY: self._density} - attrs.update(super().device_state_attributes) + attrs.update(super().extra_state_attributes) return attrs def parse_data(self, data, raw_data): @@ -424,10 +424,10 @@ class XiaomiVibration(XiaomiBinarySensor): super().__init__(device, name, xiaomi_hub, data_key, None, config_entry) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {ATTR_LAST_ACTION: self._last_action} - attrs.update(super().device_state_attributes) + attrs.update(super().extra_state_attributes) return attrs def parse_data(self, data, raw_data): @@ -459,10 +459,10 @@ class XiaomiButton(XiaomiBinarySensor): super().__init__(device, name, xiaomi_hub, data_key, None, config_entry) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {ATTR_LAST_ACTION: self._last_action} - attrs.update(super().device_state_attributes) + attrs.update(super().extra_state_attributes) return attrs def parse_data(self, data, raw_data): @@ -519,10 +519,10 @@ class XiaomiCube(XiaomiBinarySensor): super().__init__(device, "Cube", xiaomi_hub, data_key, None, config_entry) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {ATTR_LAST_ACTION: self._last_action} - attrs.update(super().device_state_attributes) + attrs.update(super().extra_state_attributes) return attrs def parse_data(self, data, raw_data): diff --git a/homeassistant/components/xiaomi_aqara/lock.py b/homeassistant/components/xiaomi_aqara/lock.py index 7c5334e0f5c..5afb1701e33 100644 --- a/homeassistant/components/xiaomi_aqara/lock.py +++ b/homeassistant/components/xiaomi_aqara/lock.py @@ -50,7 +50,7 @@ class XiaomiAqaraLock(LockEntity, XiaomiDevice): return self._changed_by @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" attributes = {ATTR_VERIFIED_WRONG_TIMES: self._verified_wrong_times} return attributes diff --git a/homeassistant/components/xiaomi_aqara/sensor.py b/homeassistant/components/xiaomi_aqara/sensor.py index 5b1d3467d25..969980bf7c8 100644 --- a/homeassistant/components/xiaomi_aqara/sensor.py +++ b/homeassistant/components/xiaomi_aqara/sensor.py @@ -194,7 +194,7 @@ class XiaomiBatterySensor(XiaomiDevice): succeed = super().parse_voltage(data) if not succeed: return False - battery_level = int(self._device_state_attributes.pop(ATTR_BATTERY_LEVEL)) + battery_level = int(self._extra_state_attributes.pop(ATTR_BATTERY_LEVEL)) if battery_level <= 0 or battery_level > 100: return False self._state = battery_level diff --git a/homeassistant/components/xiaomi_aqara/switch.py b/homeassistant/components/xiaomi_aqara/switch.py index 6e75ddb487e..8b16b6491c7 100644 --- a/homeassistant/components/xiaomi_aqara/switch.py +++ b/homeassistant/components/xiaomi_aqara/switch.py @@ -157,7 +157,7 @@ class XiaomiGenericSwitch(XiaomiDevice, SwitchEntity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._supports_power_consumption: attrs = { @@ -167,7 +167,7 @@ class XiaomiGenericSwitch(XiaomiDevice, SwitchEntity): } else: attrs = {} - attrs.update(super().device_state_attributes) + attrs.update(super().extra_state_attributes) return attrs @property diff --git a/homeassistant/components/xiaomi_miio/air_quality.py b/homeassistant/components/xiaomi_miio/air_quality.py index b278a60bd48..1a5aabcc141 100644 --- a/homeassistant/components/xiaomi_miio/air_quality.py +++ b/homeassistant/components/xiaomi_miio/air_quality.py @@ -165,7 +165,7 @@ class AirMonitorB1(XiaomiMiioEntity, AirQualityEntity): return self._humidity @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" data = {} diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index addb1a3cbca..cc8ee74feb0 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -651,7 +651,7 @@ class XiaomiGenericDevice(XiaomiMiioEntity, FanEntity): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._state_attrs diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index c0590fbb332..c6d8b67bb07 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -260,7 +260,7 @@ class XiaomiPhilipsAbstractLight(XiaomiMiioEntity, LightEntity): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._state_attrs diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index 6d43b835f1c..5769c1fb475 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -185,7 +185,7 @@ class XiaomiAirQualityMonitor(XiaomiMiioEntity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._state_attrs diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index 0a35e8e0a35..fd75e9f0088 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -252,7 +252,7 @@ class XiaomiPlugGenericSwitch(XiaomiMiioEntity, SwitchEntity): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._state_attrs diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py index bbb4cfd1b7f..b6cb2b76ae6 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -305,7 +305,7 @@ class MiroboVacuum(XiaomiMiioEntity, StateVacuumEntity): ] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the specific state attributes of this vacuum cleaner.""" attrs = {} if self.vacuum_state is not None: diff --git a/homeassistant/components/yandex_transport/sensor.py b/homeassistant/components/yandex_transport/sensor.py index 957844e519d..0df073f581d 100644 --- a/homeassistant/components/yandex_transport/sensor.py +++ b/homeassistant/components/yandex_transport/sensor.py @@ -124,7 +124,7 @@ class DiscoverYandexTransport(Entity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index e4044303ef0..4c1cb9a0e82 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -560,7 +560,7 @@ class YeelightGenericLight(YeelightEntity, LightEntity): return YEELIGHT_MONO_EFFECT_LIST @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" attributes = { "flowing": self.device.is_color_flow_enabled, diff --git a/homeassistant/components/zabbix/sensor.py b/homeassistant/components/zabbix/sensor.py index 3fa29a07896..4ef0da85daa 100644 --- a/homeassistant/components/zabbix/sensor.py +++ b/homeassistant/components/zabbix/sensor.py @@ -116,7 +116,7 @@ class ZabbixTriggerCountSensor(Entity): self._state = len(triggers) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._attributes diff --git a/homeassistant/components/zamg/sensor.py b/homeassistant/components/zamg/sensor.py index 4852e874672..ef0a476f612 100644 --- a/homeassistant/components/zamg/sensor.py +++ b/homeassistant/components/zamg/sensor.py @@ -157,7 +157,7 @@ class ZamgSensor(Entity): return SENSOR_TYPES[self.variable][1] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/zestimate/sensor.py b/homeassistant/components/zestimate/sensor.py index cdf7e6304ad..ed15d42b7e3 100644 --- a/homeassistant/components/zestimate/sensor.py +++ b/homeassistant/components/zestimate/sensor.py @@ -86,7 +86,7 @@ class ZestimateDataSensor(Entity): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attributes = {} if self.data is not None: diff --git a/homeassistant/components/zha/climate.py b/homeassistant/components/zha/climate.py index ab0f15f7559..f947012d3af 100644 --- a/homeassistant/components/zha/climate.py +++ b/homeassistant/components/zha/climate.py @@ -185,7 +185,7 @@ class Thermostat(ZhaEntity, ClimateEntity): return self._thrm.local_temp / ZCL_TEMP @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" data = {} if self.hvac_mode: diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index 3c8f0c59cb1..c19bad21455 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -44,7 +44,7 @@ class BaseZhaEntity(LogMixin, entity.Entity): self._should_poll: bool = False self._unique_id: str = unique_id self._state: Any = None - self._device_state_attributes: Dict[str, Any] = {} + self._extra_state_attributes: Dict[str, Any] = {} self._zha_device: ZhaDeviceType = zha_device self._unsubs: List[CALLABLE_T] = [] self.remove_future: Awaitable[None] = None @@ -65,9 +65,9 @@ class BaseZhaEntity(LogMixin, entity.Entity): return self._zha_device @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return device specific state attributes.""" - return self._device_state_attributes + return self._extra_state_attributes @property def force_update(self) -> bool: @@ -101,7 +101,7 @@ class BaseZhaEntity(LogMixin, entity.Entity): @callback def async_update_state_attribute(self, key: str, value: Any) -> None: """Update a single device state attribute.""" - self._device_state_attributes.update({key: value}) + self._extra_state_attributes.update({key: value}) self.async_write_ha_state() @callback diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index 32b8a064054..e7d9be62374 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -135,7 +135,7 @@ class BaseLight(LogMixin, light.LightEntity): self._identify_channel = None @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return state attributes.""" attributes = {"off_brightness": self._off_brightness} return attributes diff --git a/homeassistant/components/zha/lock.py b/homeassistant/components/zha/lock.py index 5cc7d7c56f6..2ed186d807c 100644 --- a/homeassistant/components/zha/lock.py +++ b/homeassistant/components/zha/lock.py @@ -73,7 +73,7 @@ class ZhaDoorLock(ZhaEntity, LockEntity): return self._state == STATE_LOCKED @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return state attributes.""" return self.state_attributes diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 84d2dfb06bb..926fd4555e1 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -178,7 +178,7 @@ class Battery(Sensor): return value @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return device state attrs for battery sensors.""" state_attrs = {} battery_size = self._channel.cluster.get("battery_size") diff --git a/homeassistant/components/zodiac/sensor.py b/homeassistant/components/zodiac/sensor.py index 5113c5c6e18..b602d7a50c4 100644 --- a/homeassistant/components/zodiac/sensor.py +++ b/homeassistant/components/zodiac/sensor.py @@ -196,7 +196,7 @@ class ZodiacSensor(Entity): return ZODIAC_ICONS.get(self._state) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 649f17ff08f..b8265f1e089 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -1362,7 +1362,7 @@ class ZWaveDeviceEntity(ZWaveBaseEntity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" attrs = { const.ATTR_NODE_ID: self.node_id, diff --git a/homeassistant/components/zwave/climate.py b/homeassistant/components/zwave/climate.py index 9c9c1ed6128..20eb52c2c19 100644 --- a/homeassistant/components/zwave/climate.py +++ b/homeassistant/components/zwave/climate.py @@ -571,9 +571,9 @@ class ZWaveClimateBase(ZWaveDeviceEntity, ClimateEntity): self.values.zxt_120_swing_mode.data = swing_mode @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" - data = super().device_state_attributes + data = super().extra_state_attributes if self._fan_action: data[ATTR_FAN_ACTION] = self._fan_action return data diff --git a/homeassistant/components/zwave/lock.py b/homeassistant/components/zwave/lock.py index c9601679f57..605a64ada5d 100644 --- a/homeassistant/components/zwave/lock.py +++ b/homeassistant/components/zwave/lock.py @@ -374,9 +374,9 @@ class ZwaveLock(ZWaveDeviceEntity, LockEntity): self.values.primary.data = False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" - data = super().device_state_attributes + data = super().extra_state_attributes if self._notification: data[ATTR_NOTIFICATION] = self._notification if self._lock_status: diff --git a/homeassistant/components/zwave/node_entity.py b/homeassistant/components/zwave/node_entity.py index 05e5951f921..3fa26439ad5 100644 --- a/homeassistant/components/zwave/node_entity.py +++ b/homeassistant/components/zwave/node_entity.py @@ -351,7 +351,7 @@ class ZWaveNodeEntity(ZWaveBaseEntity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" attrs = { ATTR_NODE_ID: self.node_id, diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 26e9e730283..1a15c45049b 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -326,7 +326,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): return None @property - def device_state_attributes(self) -> Optional[Dict[str, str]]: + def extra_state_attributes(self) -> Optional[Dict[str, str]]: """Return the optional state attributes.""" if ( self._fan_state diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index 8e22323c733..a6d44cb62d0 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -199,7 +199,7 @@ class ZWaveListSensor(ZwaveSensorBase): ) @property - def device_state_attributes(self) -> Optional[Dict[str, str]]: + def extra_state_attributes(self) -> Optional[Dict[str, str]]: """Return the device specific state attributes.""" # add the value's int value as property for multi-value (list) items return {"value": self.info.primary_value.value} diff --git a/tests/components/uvc/test_camera.py b/tests/components/uvc/test_camera.py index 1dd44625ebe..24d73272f18 100644 --- a/tests/components/uvc/test_camera.py +++ b/tests/components/uvc/test_camera.py @@ -257,7 +257,7 @@ class TestUVC(unittest.TestCase): assert not self.uvc.is_recording assert ( datetime(2021, 1, 8, 1, 56, 32, 367000) - == self.uvc.device_state_attributes["last_recording_start_time"] + == self.uvc.extra_state_attributes["last_recording_start_time"] ) self.nvr.get_camera.return_value["recordingIndicator"] = "DISABLED" diff --git a/tests/components/vultr/test_binary_sensor.py b/tests/components/vultr/test_binary_sensor.py index ab12bfda12c..7fb0c90362a 100644 --- a/tests/components/vultr/test_binary_sensor.py +++ b/tests/components/vultr/test_binary_sensor.py @@ -76,7 +76,7 @@ class TestVultrBinarySensorSetup(unittest.TestCase): assert "Vultr {}" == device.name device.update() - device_attrs = device.device_state_attributes + device_attrs = device.extra_state_attributes if device.subscription == "555555": assert "Vultr Another Server" == device.name diff --git a/tests/components/vultr/test_switch.py b/tests/components/vultr/test_switch.py index 12af400a44a..d6b7392ca9c 100644 --- a/tests/components/vultr/test_switch.py +++ b/tests/components/vultr/test_switch.py @@ -77,7 +77,7 @@ class TestVultrSwitchSetup(unittest.TestCase): tested += 1 device.update() - device_attrs = device.device_state_attributes + device_attrs = device.extra_state_attributes if device.subscription == "555555": assert device.name == "Vultr Another Server" diff --git a/tests/components/wsdot/test_sensor.py b/tests/components/wsdot/test_sensor.py index 1b1c2e090f2..bbb56efdeda 100644 --- a/tests/components/wsdot/test_sensor.py +++ b/tests/components/wsdot/test_sensor.py @@ -50,9 +50,9 @@ async def test_setup(hass, requests_mock): assert sensor.name == "I90 EB" assert sensor.state == 11 assert ( - sensor.device_state_attributes[ATTR_DESCRIPTION] + sensor.extra_state_attributes[ATTR_DESCRIPTION] == "Downtown Seattle to Downtown Bellevue via I-90" ) - assert sensor.device_state_attributes[ATTR_TIME_UPDATED] == datetime( + assert sensor.extra_state_attributes[ATTR_TIME_UPDATED] == datetime( 2017, 1, 21, 15, 10, tzinfo=timezone(timedelta(hours=-8)) ) diff --git a/tests/components/zwave/test_climate.py b/tests/components/zwave/test_climate.py index 5275ca79506..1afe9617097 100644 --- a/tests/components/zwave/test_climate.py +++ b/tests/components/zwave/test_climate.py @@ -845,10 +845,10 @@ def test_hvac_action_value_changed_unknown(device_unknown): def test_fan_action_value_changed(device): """Test values changed for climate device.""" - assert device.device_state_attributes[climate.ATTR_FAN_ACTION] == 7 + assert device.extra_state_attributes[climate.ATTR_FAN_ACTION] == 7 device.values.fan_action.data = 9 value_changed(device.values.fan_action) - assert device.device_state_attributes[climate.ATTR_FAN_ACTION] == 9 + assert device.extra_state_attributes[climate.ATTR_FAN_ACTION] == 9 def test_aux_heat_unsupported_set(device): diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index 878513da579..e76754a9872 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -242,7 +242,7 @@ async def test_device_entity(hass, mock_openzwave): assert not device.should_poll assert device.unique_id == "10-11" assert device.name == "Mock Node Sensor" - assert device.device_state_attributes[zwave.ATTR_POWER] == 50.123 + assert device.extra_state_attributes[zwave.ATTR_POWER] == 50.123 async def test_node_removed(hass, mock_openzwave): diff --git a/tests/components/zwave/test_lock.py b/tests/components/zwave/test_lock.py index d5b6d0a0d27..9050f06f87b 100644 --- a/tests/components/zwave/test_lock.py +++ b/tests/components/zwave/test_lock.py @@ -98,7 +98,7 @@ def test_track_message_workaround(mock_openzwave): device = lock.get_device(node=node, values=values) value_changed(values.primary) assert device.is_locked - assert device.device_state_attributes[lock.ATTR_NOTIFICATION] == "RF Lock" + assert device.extra_state_attributes[lock.ATTR_NOTIFICATION] == "RF Lock" # Simulate a keypad unlock. We trigger a value_changed() which simulates # the Alarm notification received from the lock. Then, we trigger @@ -113,7 +113,7 @@ def test_track_message_workaround(mock_openzwave): value_changed(values.primary) assert not device.is_locked assert ( - device.device_state_attributes[lock.ATTR_LOCK_STATUS] + device.extra_state_attributes[lock.ATTR_LOCK_STATUS] == "Unlocked with Keypad by user 3" ) @@ -122,7 +122,7 @@ def test_track_message_workaround(mock_openzwave): node.stats["lastReceivedMessage"][5] = const.COMMAND_CLASS_DOOR_LOCK value_changed(values.primary) assert device.is_locked - assert device.device_state_attributes[lock.ATTR_NOTIFICATION] == "RF Lock" + assert device.extra_state_attributes[lock.ATTR_NOTIFICATION] == "RF Lock" def test_v2btze_value_changed(mock_openzwave): @@ -198,7 +198,7 @@ def test_lock_access_control(mock_openzwave): ) device = lock.get_device(node=node, values=values, node_config={}) - assert device.device_state_attributes[lock.ATTR_NOTIFICATION] == "Lock Jammed" + assert device.extra_state_attributes[lock.ATTR_NOTIFICATION] == "Lock Jammed" def test_lock_alarm_type(mock_openzwave): @@ -212,28 +212,28 @@ def test_lock_alarm_type(mock_openzwave): ) device = lock.get_device(node=node, values=values, node_config={}) - assert lock.ATTR_LOCK_STATUS not in device.device_state_attributes + assert lock.ATTR_LOCK_STATUS not in device.extra_state_attributes values.alarm_type.data = 21 value_changed(values.alarm_type) assert ( - device.device_state_attributes[lock.ATTR_LOCK_STATUS] == "Manually Locked None" + device.extra_state_attributes[lock.ATTR_LOCK_STATUS] == "Manually Locked None" ) values.alarm_type.data = 18 value_changed(values.alarm_type) assert ( - device.device_state_attributes[lock.ATTR_LOCK_STATUS] + device.extra_state_attributes[lock.ATTR_LOCK_STATUS] == "Locked with Keypad by user None" ) values.alarm_type.data = 161 value_changed(values.alarm_type) - assert device.device_state_attributes[lock.ATTR_LOCK_STATUS] == "Tamper Alarm: None" + assert device.extra_state_attributes[lock.ATTR_LOCK_STATUS] == "Tamper Alarm: None" values.alarm_type.data = 9 value_changed(values.alarm_type) - assert device.device_state_attributes[lock.ATTR_LOCK_STATUS] == "Deadbolt Jammed" + assert device.extra_state_attributes[lock.ATTR_LOCK_STATUS] == "Deadbolt Jammed" def test_lock_alarm_level(mock_openzwave): @@ -247,14 +247,14 @@ def test_lock_alarm_level(mock_openzwave): ) device = lock.get_device(node=node, values=values, node_config={}) - assert lock.ATTR_LOCK_STATUS not in device.device_state_attributes + assert lock.ATTR_LOCK_STATUS not in device.extra_state_attributes values.alarm_type.data = 21 values.alarm_level.data = 1 value_changed(values.alarm_type) value_changed(values.alarm_level) assert ( - device.device_state_attributes[lock.ATTR_LOCK_STATUS] + device.extra_state_attributes[lock.ATTR_LOCK_STATUS] == "Manually Locked by Key Cylinder or Inside thumb turn" ) @@ -263,7 +263,7 @@ def test_lock_alarm_level(mock_openzwave): value_changed(values.alarm_type) value_changed(values.alarm_level) assert ( - device.device_state_attributes[lock.ATTR_LOCK_STATUS] + device.extra_state_attributes[lock.ATTR_LOCK_STATUS] == "Locked with Keypad by user alice" ) @@ -272,7 +272,7 @@ def test_lock_alarm_level(mock_openzwave): value_changed(values.alarm_type) value_changed(values.alarm_level) assert ( - device.device_state_attributes[lock.ATTR_LOCK_STATUS] + device.extra_state_attributes[lock.ATTR_LOCK_STATUS] == "Tamper Alarm: Too many keypresses" ) diff --git a/tests/components/zwave/test_node_entity.py b/tests/components/zwave/test_node_entity.py index ba77aabc923..c47201fb168 100644 --- a/tests/components/zwave/test_node_entity.py +++ b/tests/components/zwave/test_node_entity.py @@ -193,8 +193,7 @@ async def test_application_version(hass, mock_openzwave): # Make sure application version isn't set before assert ( - node_entity.ATTR_APPLICATION_VERSION - not in entity.device_state_attributes.keys() + node_entity.ATTR_APPLICATION_VERSION not in entity.extra_state_attributes.keys() ) # Add entity to hass @@ -212,9 +211,7 @@ async def test_application_version(hass, mock_openzwave): ) await hass.async_block_till_done() - assert ( - entity.device_state_attributes[node_entity.ATTR_APPLICATION_VERSION] == "5.10" - ) + assert entity.extra_state_attributes[node_entity.ATTR_APPLICATION_VERSION] == "5.10" # Fire off a changed value = mock_zwave.MockValue( @@ -227,9 +224,7 @@ async def test_application_version(hass, mock_openzwave): ) await hass.async_block_till_done() - assert ( - entity.device_state_attributes[node_entity.ATTR_APPLICATION_VERSION] == "4.14" - ) + assert entity.extra_state_attributes[node_entity.ATTR_APPLICATION_VERSION] == "4.14" async def test_network_node_changed_from_value(hass, mock_openzwave): @@ -306,7 +301,7 @@ async def test_node_changed(hass, mock_openzwave): "node_name": "Mock Node", "manufacturer_name": "Test Manufacturer", "product_name": "Test Product", - } == entity.device_state_attributes + } == entity.extra_state_attributes node.get_values.return_value = {1: mock_zwave.MockValue(data=1800)} zwave_network.manager.getNodeStatistics.return_value = { @@ -616,12 +611,12 @@ async def test_node_changed(hass, mock_openzwave): "sentCnt": 7, "sentFailed": 1, "sentTS": "2017-03-27 15:38:15:620 ", - } == entity.device_state_attributes + } == entity.extra_state_attributes node.can_wake_up_value = False entity.node_changed() - assert "wake_up_interval" not in entity.device_state_attributes + assert "wake_up_interval" not in entity.extra_state_attributes async def test_name(hass, mock_openzwave): diff --git a/tests/testing_config/custom_components/test/image_processing.py b/tests/testing_config/custom_components/test/image_processing.py index a2004fe32bd..343c60a78fe 100644 --- a/tests/testing_config/custom_components/test/image_processing.py +++ b/tests/testing_config/custom_components/test/image_processing.py @@ -41,7 +41,7 @@ class TestImageProcessing(ImageProcessingEntity): return self._count @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return {"image": self._image} From 14ff6d4d1ff307de6b644496ece4e50acb1d68db Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 11 Mar 2021 21:23:20 +0100 Subject: [PATCH 305/831] Update integrations p-s to override extra_state_attributes() (#47759) --- homeassistant/components/pencom/switch.py | 2 +- homeassistant/components/pi_hole/sensor.py | 2 +- homeassistant/components/ping/binary_sensor.py | 2 +- homeassistant/components/plaato/entity.py | 2 +- homeassistant/components/plex/media_player.py | 2 +- homeassistant/components/plex/sensor.py | 2 +- .../components/plugwise/binary_sensor.py | 2 +- homeassistant/components/plugwise/climate.py | 2 +- homeassistant/components/point/__init__.py | 2 +- homeassistant/components/poolsense/sensor.py | 2 +- homeassistant/components/powerwall/sensor.py | 2 +- homeassistant/components/proliphix/climate.py | 2 +- homeassistant/components/push/camera.py | 2 +- homeassistant/components/pushbullet/sensor.py | 2 +- homeassistant/components/pvoutput/sensor.py | 2 +- .../components/pvpc_hourly_pricing/sensor.py | 2 +- .../components/qld_bushfire/geo_location.py | 2 +- homeassistant/components/qnap/sensor.py | 10 +++++----- homeassistant/components/qvr_pro/camera.py | 2 +- homeassistant/components/rachio/switch.py | 4 ++-- homeassistant/components/radarr/sensor.py | 2 +- homeassistant/components/radiotherm/climate.py | 2 +- homeassistant/components/rainbird/switch.py | 2 +- homeassistant/components/raincloud/__init__.py | 2 +- homeassistant/components/raincloud/switch.py | 2 +- .../components/rainmachine/__init__.py | 2 +- homeassistant/components/random/sensor.py | 2 +- .../components/recollect_waste/sensor.py | 2 +- homeassistant/components/reddit/sensor.py | 2 +- homeassistant/components/rejseplanen/sensor.py | 2 +- homeassistant/components/repetier/sensor.py | 2 +- homeassistant/components/rest/sensor.py | 2 +- homeassistant/components/rflink/light.py | 4 ++-- homeassistant/components/rfxtrx/__init__.py | 2 +- homeassistant/components/ring/binary_sensor.py | 4 ++-- homeassistant/components/ring/camera.py | 2 +- homeassistant/components/ring/entity.py | 2 +- homeassistant/components/ring/sensor.py | 4 ++-- homeassistant/components/ripple/sensor.py | 2 +- .../components/risco/binary_sensor.py | 2 +- homeassistant/components/risco/sensor.py | 2 +- .../components/rituals_perfume_genie/switch.py | 2 +- homeassistant/components/roomba/braava.py | 4 ++-- homeassistant/components/roomba/irobot_base.py | 2 +- homeassistant/components/roomba/roomba.py | 4 ++-- .../components/sense/binary_sensor.py | 2 +- homeassistant/components/sense/sensor.py | 8 ++++---- homeassistant/components/sensibo/climate.py | 2 +- homeassistant/components/serial/sensor.py | 2 +- homeassistant/components/sesame/lock.py | 2 +- .../components/seventeentrack/sensor.py | 4 ++-- homeassistant/components/sharkiq/vacuum.py | 2 +- .../components/shelly/binary_sensor.py | 4 ++-- homeassistant/components/shelly/entity.py | 18 ++++++++---------- homeassistant/components/shelly/sensor.py | 2 +- homeassistant/components/shodan/sensor.py | 2 +- homeassistant/components/sigfox/sensor.py | 2 +- .../components/sighthound/image_processing.py | 2 +- .../components/simplisafe/__init__.py | 2 +- homeassistant/components/simulated/sensor.py | 2 +- homeassistant/components/skybeacon/sensor.py | 4 ++-- homeassistant/components/skybell/__init__.py | 2 +- .../components/skybell/binary_sensor.py | 4 ++-- homeassistant/components/slide/cover.py | 2 +- homeassistant/components/sma/sensor.py | 2 +- .../components/smart_meter_texas/sensor.py | 2 +- .../components/smartthings/climate.py | 2 +- homeassistant/components/smartthings/cover.py | 2 +- homeassistant/components/smartthings/lock.py | 2 +- homeassistant/components/smartthings/scene.py | 2 +- homeassistant/components/smarttub/sensor.py | 4 ++-- homeassistant/components/smhi/weather.py | 2 +- homeassistant/components/sms/sensor.py | 2 +- .../components/snapcast/media_player.py | 4 ++-- homeassistant/components/sochain/sensor.py | 2 +- homeassistant/components/socialblade/sensor.py | 2 +- homeassistant/components/solaredge/sensor.py | 8 ++++---- .../components/solaredge_local/sensor.py | 2 +- homeassistant/components/sonarr/sensor.py | 12 ++++++------ homeassistant/components/sonos/media_player.py | 2 +- .../components/soundtouch/media_player.py | 2 +- .../components/speedtestdotnet/sensor.py | 2 +- homeassistant/components/spotcrime/sensor.py | 2 +- homeassistant/components/sql/sensor.py | 2 +- .../components/squeezebox/media_player.py | 2 +- homeassistant/components/srp_energy/sensor.py | 2 +- .../components/starline/device_tracker.py | 2 +- homeassistant/components/starline/lock.py | 2 +- homeassistant/components/starline/sensor.py | 2 +- homeassistant/components/starline/switch.py | 2 +- homeassistant/components/statistics/sensor.py | 2 +- .../components/steam_online/sensor.py | 2 +- .../components/stiebel_eltron/climate.py | 2 +- .../components/stookalert/binary_sensor.py | 2 +- homeassistant/components/suez_water/sensor.py | 2 +- homeassistant/components/supervisord/sensor.py | 2 +- .../components/surepetcare/binary_sensor.py | 6 +++--- homeassistant/components/surepetcare/sensor.py | 4 ++-- .../swiss_hydrological_data/sensor.py | 2 +- .../swiss_public_transport/sensor.py | 2 +- homeassistant/components/switchbot/switch.py | 2 +- .../components/switcher_kis/switch.py | 2 +- homeassistant/components/syncthru/sensor.py | 2 +- .../components/synology_dsm/__init__.py | 2 +- .../components/synology_dsm/binary_sensor.py | 2 +- tests/components/srp_energy/test_sensor.py | 4 ++-- 106 files changed, 144 insertions(+), 146 deletions(-) diff --git a/homeassistant/components/pencom/switch.py b/homeassistant/components/pencom/switch.py index 7f193bc09a1..5621846e496 100644 --- a/homeassistant/components/pencom/switch.py +++ b/homeassistant/components/pencom/switch.py @@ -93,6 +93,6 @@ class PencomRelay(SwitchEntity): self._state = self._hub.get(self._board, self._addr) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return supported attributes.""" return {"board": self._board, "addr": self._addr} diff --git a/homeassistant/components/pi_hole/sensor.py b/homeassistant/components/pi_hole/sensor.py index 2f5873b14c1..4bd4c7b7f6f 100644 --- a/homeassistant/components/pi_hole/sensor.py +++ b/homeassistant/components/pi_hole/sensor.py @@ -73,6 +73,6 @@ class PiHoleSensor(PiHoleEntity): return self.api.data[self._condition] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the Pi-hole.""" return {ATTR_BLOCKED_DOMAINS: self.api.data["domains_being_blocked"]} diff --git a/homeassistant/components/ping/binary_sensor.py b/homeassistant/components/ping/binary_sensor.py index 98c36c01d98..a91f7235254 100644 --- a/homeassistant/components/ping/binary_sensor.py +++ b/homeassistant/components/ping/binary_sensor.py @@ -105,7 +105,7 @@ class PingBinarySensor(BinarySensorEntity): return self._ping.available @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the ICMP checo request.""" if self._ping.data is not False: return { diff --git a/homeassistant/components/plaato/entity.py b/homeassistant/components/plaato/entity.py index 7cb1a77a9fb..a28dfefb567 100644 --- a/homeassistant/components/plaato/entity.py +++ b/homeassistant/components/plaato/entity.py @@ -68,7 +68,7 @@ class PlaatoEntity(entity.Entity): return device_info @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the monitored installation.""" if self._attributes: return { diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py index 1a57186bd9b..d32abf86ddb 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -522,7 +522,7 @@ class PlexMediaPlayer(MediaPlayerEntity): _LOGGER.error("Timed out playing on %s", self.name) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the scene state attributes.""" attributes = {} for attr in [ diff --git a/homeassistant/components/plex/sensor.py b/homeassistant/components/plex/sensor.py index 8c3733a7450..47dd05a557b 100644 --- a/homeassistant/components/plex/sensor.py +++ b/homeassistant/components/plex/sensor.py @@ -89,7 +89,7 @@ class PlexSensor(Entity): return "mdi:plex" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._server.sensor_attributes diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index 825d27d59bb..023ffa3de70 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -143,7 +143,7 @@ class PwNotifySensor(SmileBinarySensor, BinarySensorEntity): self._attributes = {} @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index c8a2191963e..3efd6dbc3ca 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -124,7 +124,7 @@ class PwThermostat(SmileGateway, ClimateEntity): return SUPPORT_FLAGS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" attributes = {} if self._schema_names: diff --git a/homeassistant/components/point/__init__.py b/homeassistant/components/point/__init__.py index a92f4f3f14f..e5c209004de 100644 --- a/homeassistant/components/point/__init__.py +++ b/homeassistant/components/point/__init__.py @@ -296,7 +296,7 @@ class MinutPointEntity(Entity): return self._id @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return status of device.""" attrs = self.device.device_status attrs["last_heard_from"] = as_local(self.last_update).strftime( diff --git a/homeassistant/components/poolsense/sensor.py b/homeassistant/components/poolsense/sensor.py index a64cc0aef61..bf5c3eb0163 100644 --- a/homeassistant/components/poolsense/sensor.py +++ b/homeassistant/components/poolsense/sensor.py @@ -108,6 +108,6 @@ class PoolSenseSensor(PoolSenseEntity, Entity): return SENSORS[self.info_type]["unit"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/powerwall/sensor.py b/homeassistant/components/powerwall/sensor.py index 5026d2fb357..3b4d7918cf7 100644 --- a/homeassistant/components/powerwall/sensor.py +++ b/homeassistant/components/powerwall/sensor.py @@ -136,7 +136,7 @@ class PowerWallEnergySensor(PowerWallEntity): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" meter = self.coordinator.data[POWERWALL_API_METERS].get_meter(self._meter) return { diff --git a/homeassistant/components/proliphix/climate.py b/homeassistant/components/proliphix/climate.py index 5dff4725ea0..a293642038e 100644 --- a/homeassistant/components/proliphix/climate.py +++ b/homeassistant/components/proliphix/climate.py @@ -84,7 +84,7 @@ class ProliphixThermostat(ClimateEntity): return PRECISION_TENTHS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" return {ATTR_FAN: self._pdp.fan_state} diff --git a/homeassistant/components/push/camera.py b/homeassistant/components/push/camera.py index 31f2f88dac7..ff0ac45c139 100644 --- a/homeassistant/components/push/camera.py +++ b/homeassistant/components/push/camera.py @@ -175,7 +175,7 @@ class PushCamera(Camera): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { name: value diff --git a/homeassistant/components/pushbullet/sensor.py b/homeassistant/components/pushbullet/sensor.py index ff18e86aad9..f7aaa693c67 100644 --- a/homeassistant/components/pushbullet/sensor.py +++ b/homeassistant/components/pushbullet/sensor.py @@ -85,7 +85,7 @@ class PushBulletNotificationSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return all known attributes of the sensor.""" return self._state_attributes diff --git a/homeassistant/components/pvoutput/sensor.py b/homeassistant/components/pvoutput/sensor.py index 32d33f19e80..9e88cb0a664 100644 --- a/homeassistant/components/pvoutput/sensor.py +++ b/homeassistant/components/pvoutput/sensor.py @@ -100,7 +100,7 @@ class PvoutputSensor(Entity): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the monitored installation.""" if self.pvcoutput is not None: return { diff --git a/homeassistant/components/pvpc_hourly_pricing/sensor.py b/homeassistant/components/pvpc_hourly_pricing/sensor.py index a9b53c970bd..b3486f9d534 100644 --- a/homeassistant/components/pvpc_hourly_pricing/sensor.py +++ b/homeassistant/components/pvpc_hourly_pricing/sensor.py @@ -112,7 +112,7 @@ class ElecPriceSensor(RestoreEntity): return self._pvpc_data.state_available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._pvpc_data.attributes diff --git a/homeassistant/components/qld_bushfire/geo_location.py b/homeassistant/components/qld_bushfire/geo_location.py index f608f6e12ae..0887e6b7cdd 100644 --- a/homeassistant/components/qld_bushfire/geo_location.py +++ b/homeassistant/components/qld_bushfire/geo_location.py @@ -234,7 +234,7 @@ class QldBushfireLocationEvent(GeolocationEvent): return LENGTH_KILOMETERS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = {} for key, value in ( diff --git a/homeassistant/components/qnap/sensor.py b/homeassistant/components/qnap/sensor.py index 11faba0f210..5f7695e5a60 100644 --- a/homeassistant/components/qnap/sensor.py +++ b/homeassistant/components/qnap/sensor.py @@ -268,7 +268,7 @@ class QNAPMemorySensor(QNAPSensor): return round(used / total * 100) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._api.data: data = self._api.data["system_stats"]["memory"] @@ -294,7 +294,7 @@ class QNAPNetworkSensor(QNAPSensor): return round_nicely(data["rx"] / 1024 / 1024) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._api.data: data = self._api.data["system_stats"]["nics"][self.monitor_device] @@ -322,7 +322,7 @@ class QNAPSystemSensor(QNAPSensor): return int(self._api.data["system_stats"]["system"]["temp_c"]) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._api.data: data = self._api.data["system_stats"] @@ -360,7 +360,7 @@ class QNAPDriveSensor(QNAPSensor): return f"{server_name} {self.var_name} (Drive {self.monitor_device})" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._api.data: data = self._api.data["smart_drive_health"][self.monitor_device] @@ -394,7 +394,7 @@ class QNAPVolumeSensor(QNAPSensor): return round(used_gb / total_gb * 100) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._api.data: data = self._api.data["volumes"][self.monitor_device] diff --git a/homeassistant/components/qvr_pro/camera.py b/homeassistant/components/qvr_pro/camera.py index 9dd8e3c4f20..2f4353063d1 100644 --- a/homeassistant/components/qvr_pro/camera.py +++ b/homeassistant/components/qvr_pro/camera.py @@ -82,7 +82,7 @@ class QVRProCamera(Camera): return self._brand @property - def device_state_attributes(self): + def extra_state_attributes(self): """Get the state attributes.""" attrs = {"qvr_guid": self.guid} diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index 44a17acaecf..726a6e26ce5 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -388,7 +388,7 @@ class RachioZone(RachioSwitch): return self._entity_picture @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the optional state attributes.""" props = {ATTR_ZONE_NUMBER: self._zone_number, ATTR_ZONE_SUMMARY: self._summary} if self._shade_type: @@ -494,7 +494,7 @@ class RachioSchedule(RachioSwitch): return "mdi:water" if self.schedule_is_enabled else "mdi:water-off" @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the optional state attributes.""" return { ATTR_SCHEDULE_SUMMARY: self._summary, diff --git a/homeassistant/components/radarr/sensor.py b/homeassistant/components/radarr/sensor.py index 27365271014..9baed6c41c7 100644 --- a/homeassistant/components/radarr/sensor.py +++ b/homeassistant/components/radarr/sensor.py @@ -144,7 +144,7 @@ class RadarrSensor(Entity): return self._unit @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" attributes = {} if self.type == "upcoming": diff --git a/homeassistant/components/radiotherm/climate.py b/homeassistant/components/radiotherm/climate.py index f09ef95170f..d7bca1175cb 100644 --- a/homeassistant/components/radiotherm/climate.py +++ b/homeassistant/components/radiotherm/climate.py @@ -181,7 +181,7 @@ class RadioThermostat(ClimateEntity): return PRECISION_HALVES @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" return {ATTR_FAN_ACTION: self._fstate} diff --git a/homeassistant/components/rainbird/switch.py b/homeassistant/components/rainbird/switch.py index bccd4d2986c..7acb9740616 100644 --- a/homeassistant/components/rainbird/switch.py +++ b/homeassistant/components/rainbird/switch.py @@ -78,7 +78,7 @@ class RainBirdSwitch(SwitchEntity): self._attributes = {ATTR_DURATION: self._duration, "zone": self._zone} @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return state attributes.""" return self._attributes diff --git a/homeassistant/components/raincloud/__init__.py b/homeassistant/components/raincloud/__init__.py index 5955ef67168..6b0ca39df03 100644 --- a/homeassistant/components/raincloud/__init__.py +++ b/homeassistant/components/raincloud/__init__.py @@ -166,7 +166,7 @@ class RainCloudEntity(Entity): return UNIT_OF_MEASUREMENT_MAP.get(self._sensor_type) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION, "identifier": self.data.serial} diff --git a/homeassistant/components/raincloud/switch.py b/homeassistant/components/raincloud/switch.py index d6733412cac..d15f5c7c047 100644 --- a/homeassistant/components/raincloud/switch.py +++ b/homeassistant/components/raincloud/switch.py @@ -83,7 +83,7 @@ class RainCloudSwitch(RainCloudEntity, SwitchEntity): self._state = self.data.auto_watering @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/rainmachine/__init__.py b/homeassistant/components/rainmachine/__init__.py index b4d8510cc77..e71e8a1f6d2 100644 --- a/homeassistant/components/rainmachine/__init__.py +++ b/homeassistant/components/rainmachine/__init__.py @@ -225,7 +225,7 @@ class RainMachineEntity(CoordinatorEntity): } @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/random/sensor.py b/homeassistant/components/random/sensor.py index 58d996bc6ec..7584fe17405 100644 --- a/homeassistant/components/random/sensor.py +++ b/homeassistant/components/random/sensor.py @@ -74,7 +74,7 @@ class RandomSensor(Entity): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attributes of the sensor.""" return {ATTR_MAXIMUM: self._maximum, ATTR_MINIMUM: self._minimum} diff --git a/homeassistant/components/recollect_waste/sensor.py b/homeassistant/components/recollect_waste/sensor.py index 66ced51b77f..0822cdb1f3a 100644 --- a/homeassistant/components/recollect_waste/sensor.py +++ b/homeassistant/components/recollect_waste/sensor.py @@ -86,7 +86,7 @@ class ReCollectWasteSensor(CoordinatorEntity): self._state = None @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/reddit/sensor.py b/homeassistant/components/reddit/sensor.py index 7a04fb6a8ae..153b6636cc4 100644 --- a/homeassistant/components/reddit/sensor.py +++ b/homeassistant/components/reddit/sensor.py @@ -105,7 +105,7 @@ class RedditSensor(Entity): return len(self._subreddit_data) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_SUBREDDIT: self._subreddit, diff --git a/homeassistant/components/rejseplanen/sensor.py b/homeassistant/components/rejseplanen/sensor.py index 30d57a3d9dc..ea55d56b8df 100644 --- a/homeassistant/components/rejseplanen/sensor.py +++ b/homeassistant/components/rejseplanen/sensor.py @@ -110,7 +110,7 @@ class RejseplanenTransportSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if not self._times: return {ATTR_STOP_ID: self._stop_id, ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/repetier/sensor.py b/homeassistant/components/repetier/sensor.py index e342b2d341e..a2b86792aa7 100644 --- a/homeassistant/components/repetier/sensor.py +++ b/homeassistant/components/repetier/sensor.py @@ -66,7 +66,7 @@ class RepetierSensor(Entity): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return sensor attributes.""" return self._attributes diff --git a/homeassistant/components/rest/sensor.py b/homeassistant/components/rest/sensor.py index 0699d9dc07c..5ff5e87c3e6 100644 --- a/homeassistant/components/rest/sensor.py +++ b/homeassistant/components/rest/sensor.py @@ -119,7 +119,7 @@ class RestSensor(RestEntity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/rflink/light.py b/homeassistant/components/rflink/light.py index fe74c979396..2a97472f000 100644 --- a/homeassistant/components/rflink/light.py +++ b/homeassistant/components/rflink/light.py @@ -194,7 +194,7 @@ class DimmableRflinkLight(SwitchableRflinkDevice, LightEntity): return self._brightness @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if self._brightness is None: return {} @@ -256,7 +256,7 @@ class HybridRflinkLight(SwitchableRflinkDevice, LightEntity): return self._brightness @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if self._brightness is None: return {} diff --git a/homeassistant/components/rfxtrx/__init__.py b/homeassistant/components/rfxtrx/__init__.py index c9e8c0dee75..649a573c5b4 100644 --- a/homeassistant/components/rfxtrx/__init__.py +++ b/homeassistant/components/rfxtrx/__init__.py @@ -505,7 +505,7 @@ class RfxtrxEntity(RestoreEntity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if not self._event: return None diff --git a/homeassistant/components/ring/binary_sensor.py b/homeassistant/components/ring/binary_sensor.py index bbfbbf1690e..18ce87e722e 100644 --- a/homeassistant/components/ring/binary_sensor.py +++ b/homeassistant/components/ring/binary_sensor.py @@ -111,9 +111,9 @@ class RingBinarySensor(RingEntityMixin, BinarySensorEntity): return self._unique_id @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - attrs = super().device_state_attributes + attrs = super().extra_state_attributes if self._active_alert is None: return attrs diff --git a/homeassistant/components/ring/camera.py b/homeassistant/components/ring/camera.py index bd5950b81a9..8f827aee7d2 100644 --- a/homeassistant/components/ring/camera.py +++ b/homeassistant/components/ring/camera.py @@ -93,7 +93,7 @@ class RingCam(RingEntityMixin, Camera): return self._device.id @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/ring/entity.py b/homeassistant/components/ring/entity.py index 6eb87cb8f9b..7a1c8ae7bdf 100644 --- a/homeassistant/components/ring/entity.py +++ b/homeassistant/components/ring/entity.py @@ -38,7 +38,7 @@ class RingEntityMixin: return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/ring/sensor.py b/homeassistant/components/ring/sensor.py index 0a1cc85230f..276d3839438 100644 --- a/homeassistant/components/ring/sensor.py +++ b/homeassistant/components/ring/sensor.py @@ -180,9 +180,9 @@ class HistoryRingSensor(RingSensor): return self._latest_event["created_at"].isoformat() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - attrs = super().device_state_attributes + attrs = super().extra_state_attributes if self._latest_event: attrs["created_at"] = self._latest_event["created_at"] diff --git a/homeassistant/components/ripple/sensor.py b/homeassistant/components/ripple/sensor.py index ab0da77b173..97cf8b4a794 100644 --- a/homeassistant/components/ripple/sensor.py +++ b/homeassistant/components/ripple/sensor.py @@ -57,7 +57,7 @@ class RippleSensor(Entity): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/risco/binary_sensor.py b/homeassistant/components/risco/binary_sensor.py index ba01b70686b..ba32429c154 100644 --- a/homeassistant/components/risco/binary_sensor.py +++ b/homeassistant/components/risco/binary_sensor.py @@ -60,7 +60,7 @@ class RiscoBinarySensor(BinarySensorEntity, RiscoEntity): return binary_sensor_unique_id(self._risco, self._zone_id) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"zone_id": self._zone_id, "bypassed": self._zone.bypassed} diff --git a/homeassistant/components/risco/sensor.py b/homeassistant/components/risco/sensor.py index 43d763a35fa..846444e5fbd 100644 --- a/homeassistant/components/risco/sensor.py +++ b/homeassistant/components/risco/sensor.py @@ -94,7 +94,7 @@ class RiscoSensor(CoordinatorEntity): return self._event.time @property - def device_state_attributes(self): + def extra_state_attributes(self): """State attributes.""" if self._event is None: return None diff --git a/homeassistant/components/rituals_perfume_genie/switch.py b/homeassistant/components/rituals_perfume_genie/switch.py index 7041d22f4b8..471be52b054 100644 --- a/homeassistant/components/rituals_perfume_genie/switch.py +++ b/homeassistant/components/rituals_perfume_genie/switch.py @@ -66,7 +66,7 @@ class DiffuserSwitch(SwitchEntity): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = { "fan_speed": self._diffuser.data["hub"]["attributes"]["speedc"], diff --git a/homeassistant/components/roomba/braava.py b/homeassistant/components/roomba/braava.py index 1a3d106bf80..90298078e42 100644 --- a/homeassistant/components/roomba/braava.py +++ b/homeassistant/components/roomba/braava.py @@ -116,9 +116,9 @@ class BraavaJet(IRobotVacuum): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" - state_attrs = super().device_state_attributes + state_attrs = super().extra_state_attributes # Get Braava state state = self.vacuum_state diff --git a/homeassistant/components/roomba/irobot_base.py b/homeassistant/components/roomba/irobot_base.py index 7dd045a1137..9d6a0f5cafc 100644 --- a/homeassistant/components/roomba/irobot_base.py +++ b/homeassistant/components/roomba/irobot_base.py @@ -168,7 +168,7 @@ class IRobotVacuum(IRobotEntity, StateVacuumEntity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" state = self.vacuum_state diff --git a/homeassistant/components/roomba/roomba.py b/homeassistant/components/roomba/roomba.py index 0a9aec0b608..5f960aeaae0 100644 --- a/homeassistant/components/roomba/roomba.py +++ b/homeassistant/components/roomba/roomba.py @@ -23,9 +23,9 @@ class RoombaVacuum(IRobotVacuum): """Basic Roomba robot (without carpet boost).""" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" - state_attrs = super().device_state_attributes + state_attrs = super().extra_state_attributes # Get bin state bin_raw_state = self.vacuum_state.get("bin", {}) diff --git a/homeassistant/components/sense/binary_sensor.py b/homeassistant/components/sense/binary_sensor.py index bc06721ae5e..ae5e4fc95bc 100644 --- a/homeassistant/components/sense/binary_sensor.py +++ b/homeassistant/components/sense/binary_sensor.py @@ -101,7 +101,7 @@ class SenseDevice(BinarySensorEntity): return self._id @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/sense/sensor.py b/homeassistant/components/sense/sensor.py index 25fa5943bd5..7b6e415d4a3 100644 --- a/homeassistant/components/sense/sensor.py +++ b/homeassistant/components/sense/sensor.py @@ -163,7 +163,7 @@ class SenseActiveSensor(Entity): return POWER_WATT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} @@ -247,7 +247,7 @@ class SenseVoltageSensor(Entity): return VOLT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} @@ -333,7 +333,7 @@ class SenseTrendsSensor(Entity): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} @@ -415,7 +415,7 @@ class SenseEnergyDevice(Entity): return POWER_WATT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index 26276650752..10ceaa39a38 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -191,7 +191,7 @@ class SensiboClimate(ClimateEntity): return self._external_state or super().state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"battery": self.current_battery} diff --git a/homeassistant/components/serial/sensor.py b/homeassistant/components/serial/sensor.py index e0bf23a2514..02590ccfe8f 100644 --- a/homeassistant/components/serial/sensor.py +++ b/homeassistant/components/serial/sensor.py @@ -241,7 +241,7 @@ class SerialSensor(Entity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attributes of the entity (if any JSON present).""" return self._attributes diff --git a/homeassistant/components/sesame/lock.py b/homeassistant/components/sesame/lock.py index 9c86c262235..acd71b7c9e7 100644 --- a/homeassistant/components/sesame/lock.py +++ b/homeassistant/components/sesame/lock.py @@ -86,7 +86,7 @@ class SesameDevice(LockEntity): self._responsive = status["responsive"] @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" return { ATTR_DEVICE_ID: self._device_id, diff --git a/homeassistant/components/seventeentrack/sensor.py b/homeassistant/components/seventeentrack/sensor.py index 07cfe9ca66f..15110f6a0c3 100644 --- a/homeassistant/components/seventeentrack/sensor.py +++ b/homeassistant/components/seventeentrack/sensor.py @@ -109,7 +109,7 @@ class SeventeenTrackSummarySensor(Entity): return self._state is not None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return self._attrs @@ -190,7 +190,7 @@ class SeventeenTrackPackageSensor(Entity): return self._data.packages.get(self._tracking_number) is not None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return self._attrs diff --git a/homeassistant/components/sharkiq/vacuum.py b/homeassistant/components/sharkiq/vacuum.py index 9684dde45e6..5b4254eebb7 100644 --- a/homeassistant/components/sharkiq/vacuum.py +++ b/homeassistant/components/sharkiq/vacuum.py @@ -255,7 +255,7 @@ class SharkVacuumEntity(CoordinatorEntity, StateVacuumEntity): return self.sharkiq.get_property_value(Properties.LOW_LIGHT_MISSION) @property - def device_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> Dict: """Return a dictionary of device state attributes specific to sharkiq.""" data = { ATTR_ERROR_CODE: self.error_code, diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index 18220fc9e3a..385b3b30c36 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -47,7 +47,7 @@ SENSORS = { name="Gas", device_class=DEVICE_CLASS_GAS, value=lambda value: value in ["mild", "heavy"], - device_state_attributes=lambda block: {"detected": block.gas}, + extra_state_attributes=lambda block: {"detected": block.gas}, ), ("sensor", "smoke"): BlockAttributeDescription( name="Smoke", device_class=DEVICE_CLASS_SMOKE @@ -95,7 +95,7 @@ REST_SENSORS = { icon="mdi:update", value=lambda status, _: status["update"]["has_update"], default_enabled=False, - device_state_attributes=lambda status: { + extra_state_attributes=lambda status: { "latest_stable_version": status["update"]["new_version"], "installed_version": status["update"]["old_version"], }, diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index 71ab4703c79..9457cbaf370 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -150,9 +150,7 @@ class BlockAttributeDescription: available: Optional[Callable[[aioshelly.Block], bool]] = None # Callable (settings, block), return true if entity should be removed removal_condition: Optional[Callable[[dict, aioshelly.Block], bool]] = None - device_state_attributes: Optional[ - Callable[[aioshelly.Block], Optional[dict]] - ] = None + extra_state_attributes: Optional[Callable[[aioshelly.Block], Optional[dict]]] = None @dataclass @@ -165,7 +163,7 @@ class RestAttributeDescription: value: Callable[[dict, Any], Any] = None device_class: Optional[str] = None default_enabled: bool = True - device_state_attributes: Optional[Callable[[dict], Optional[dict]]] = None + extra_state_attributes: Optional[Callable[[dict], Optional[dict]]] = None class ShellyBlockEntity(entity.Entity): @@ -293,12 +291,12 @@ class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity): return self.description.available(self.block) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - if self.description.device_state_attributes is None: + if self.description.extra_state_attributes is None: return None - return self.description.device_state_attributes(self.block) + return self.description.extra_state_attributes(self.block) class ShellyRestAttributeEntity(update_coordinator.CoordinatorEntity): @@ -369,12 +367,12 @@ class ShellyRestAttributeEntity(update_coordinator.CoordinatorEntity): return f"{self.wrapper.mac}-{self.attribute}" @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" - if self.description.device_state_attributes is None: + if self.description.extra_state_attributes is None: return None - return self.description.device_state_attributes(self.wrapper.device.status) + return self.description.extra_state_attributes(self.wrapper.device.status) class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEntity): diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index 10237629223..aac5ec81ec3 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -159,7 +159,7 @@ SENSORS = { unit=PERCENTAGE, icon="mdi:progress-wrench", value=lambda value: round(100 - (value / 3600 / SHAIR_MAX_WORK_HOURS), 1), - device_state_attributes=lambda block: { + extra_state_attributes=lambda block: { "Operational hours": round(block.totalWorkTime / 3600, 1) }, ), diff --git a/homeassistant/components/shodan/sensor.py b/homeassistant/components/shodan/sensor.py index d2a6a28fbe4..397e05a35ca 100644 --- a/homeassistant/components/shodan/sensor.py +++ b/homeassistant/components/shodan/sensor.py @@ -78,7 +78,7 @@ class ShodanSensor(Entity): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/sigfox/sensor.py b/homeassistant/components/sigfox/sensor.py index 1de3cfeb8a0..3bf0f084e51 100644 --- a/homeassistant/components/sigfox/sensor.py +++ b/homeassistant/components/sigfox/sensor.py @@ -155,6 +155,6 @@ class SigfoxDevice(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other details about the last message.""" return self._message_data diff --git a/homeassistant/components/sighthound/image_processing.py b/homeassistant/components/sighthound/image_processing.py index e15fab1aaa3..fa636eb757f 100644 --- a/homeassistant/components/sighthound/image_processing.py +++ b/homeassistant/components/sighthound/image_processing.py @@ -170,7 +170,7 @@ class SighthoundEntity(ImageProcessingEntity): return ATTR_PEOPLE @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attributes.""" if not self._last_detection: return {} diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index a6d95e1d9af..485284b3293 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -634,7 +634,7 @@ class SimpliSafeEntity(CoordinatorEntity): return self._device_info @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/simulated/sensor.py b/homeassistant/components/simulated/sensor.py index 7f484b712c1..dc4872cb2aa 100644 --- a/homeassistant/components/simulated/sensor.py +++ b/homeassistant/components/simulated/sensor.py @@ -137,7 +137,7 @@ class SimulatedSensor(Entity): return self._unit @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other details about the sensor state.""" return { "amplitude": self._amp, diff --git a/homeassistant/components/skybeacon/sensor.py b/homeassistant/components/skybeacon/sensor.py index 9b759327dca..bff9e311844 100644 --- a/homeassistant/components/skybeacon/sensor.py +++ b/homeassistant/components/skybeacon/sensor.py @@ -86,7 +86,7 @@ class SkybeaconHumid(Entity): return PERCENTAGE @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_DEVICE: "SKYBEACON", ATTR_MODEL: 1} @@ -115,7 +115,7 @@ class SkybeaconTemp(Entity): return TEMP_CELSIUS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_DEVICE: "SKYBEACON", ATTR_MODEL: 1} diff --git a/homeassistant/components/skybell/__init__.py b/homeassistant/components/skybell/__init__.py index c1c9d76314c..2acb729d767 100644 --- a/homeassistant/components/skybell/__init__.py +++ b/homeassistant/components/skybell/__init__.py @@ -82,7 +82,7 @@ class SkybellDevice(Entity): self._device.refresh() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/skybell/binary_sensor.py b/homeassistant/components/skybell/binary_sensor.py index 8949a58fa01..7e075fba38a 100644 --- a/homeassistant/components/skybell/binary_sensor.py +++ b/homeassistant/components/skybell/binary_sensor.py @@ -76,9 +76,9 @@ class SkybellBinarySensor(SkybellDevice, BinarySensorEntity): return self._device_class @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - attrs = super().device_state_attributes + attrs = super().extra_state_attributes attrs["event_date"] = self._event.get("createdAt") diff --git a/homeassistant/components/slide/cover.py b/homeassistant/components/slide/cover.py index 470cf9e5a1f..9e925af3391 100644 --- a/homeassistant/components/slide/cover.py +++ b/homeassistant/components/slide/cover.py @@ -55,7 +55,7 @@ class SlideCover(CoverEntity): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return {ATTR_ID: self._id} diff --git a/homeassistant/components/sma/sensor.py b/homeassistant/components/sma/sensor.py index 94bab40a3b7..bc4457e2838 100644 --- a/homeassistant/components/sma/sensor.py +++ b/homeassistant/components/sma/sensor.py @@ -196,7 +196,7 @@ class SMAsensor(Entity): return self._sensor.unit @property - def device_state_attributes(self): # Can be remove from 0.99 + def extra_state_attributes(self): # Can be remove from 0.99 """Return the state attributes of the sensor.""" return self._attr diff --git a/homeassistant/components/smart_meter_texas/sensor.py b/homeassistant/components/smart_meter_texas/sensor.py index e65fbdcb531..42084fff836 100644 --- a/homeassistant/components/smart_meter_texas/sensor.py +++ b/homeassistant/components/smart_meter_texas/sensor.py @@ -65,7 +65,7 @@ class SmartMeterTexasSensor(CoordinatorEntity, RestoreEntity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" attributes = { METER_NUMBER: self.meter.meter, diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py index 6ce872cdac7..d99cc1d60cf 100644 --- a/homeassistant/components/smartthings/climate.py +++ b/homeassistant/components/smartthings/climate.py @@ -415,7 +415,7 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateEntity): return self._device.status.temperature @property - def device_state_attributes(self): + def extra_state_attributes(self): """ Return device specific state attributes. diff --git a/homeassistant/components/smartthings/cover.py b/homeassistant/components/smartthings/cover.py index ddc52ec3f6c..7b837faca1c 100644 --- a/homeassistant/components/smartthings/cover.py +++ b/homeassistant/components/smartthings/cover.py @@ -147,7 +147,7 @@ class SmartThingsCover(SmartThingsEntity, CoverEntity): return self._device_class @property - def device_state_attributes(self): + def extra_state_attributes(self): """Get additional state attributes.""" return self._state_attrs diff --git a/homeassistant/components/smartthings/lock.py b/homeassistant/components/smartthings/lock.py index d6b615b47a7..55370e99993 100644 --- a/homeassistant/components/smartthings/lock.py +++ b/homeassistant/components/smartthings/lock.py @@ -57,7 +57,7 @@ class SmartThingsLock(SmartThingsEntity, LockEntity): return self._device.status.lock == ST_STATE_LOCKED @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" state_attrs = {} status = self._device.status.attributes[Attribute.lock] diff --git a/homeassistant/components/smartthings/scene.py b/homeassistant/components/smartthings/scene.py index 11ee6dc83e1..e3d93c663fa 100644 --- a/homeassistant/components/smartthings/scene.py +++ b/homeassistant/components/smartthings/scene.py @@ -24,7 +24,7 @@ class SmartThingsScene(Scene): await self._scene.execute() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Get attributes about the state.""" return { "icon": self._scene.icon, diff --git a/homeassistant/components/smarttub/sensor.py b/homeassistant/components/smarttub/sensor.py index 563c16b3ff1..99b2e80262d 100644 --- a/homeassistant/components/smarttub/sensor.py +++ b/homeassistant/components/smarttub/sensor.py @@ -70,7 +70,7 @@ class SmartTubPrimaryFiltrationCycle(SmartTubSensor): return self._state.status.name.lower() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" state = self._state return { @@ -96,7 +96,7 @@ class SmartTubSecondaryFiltrationCycle(SmartTubSensor): return self._state.status.name.lower() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" state = self._state return { diff --git a/homeassistant/components/smhi/weather.py b/homeassistant/components/smhi/weather.py index c13982ee15d..ca5e7f7ac23 100644 --- a/homeassistant/components/smhi/weather.py +++ b/homeassistant/components/smhi/weather.py @@ -235,7 +235,7 @@ class SmhiWeather(WeatherEntity): return data @property - def device_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> Dict: """Return SMHI specific attributes.""" if self.cloudiness: return {ATTR_SMHI_CLOUDINESS: self.cloudiness} diff --git a/homeassistant/components/sms/sensor.py b/homeassistant/components/sms/sensor.py index e775b4a0e05..660b1a70c01 100644 --- a/homeassistant/components/sms/sensor.py +++ b/homeassistant/components/sms/sensor.py @@ -75,7 +75,7 @@ class GSMSignalSensor(Entity): _LOGGER.error("Failed to read signal quality: %s", exc) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the sensor attributes.""" return self._state diff --git a/homeassistant/components/snapcast/media_player.py b/homeassistant/components/snapcast/media_player.py index ab4b2415034..e1c5b7d875b 100644 --- a/homeassistant/components/snapcast/media_player.py +++ b/homeassistant/components/snapcast/media_player.py @@ -164,7 +164,7 @@ class SnapcastGroupDevice(MediaPlayerEntity): return list(self._group.streams_by_name().keys()) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" name = f"{self._group.friendly_name} {GROUP_SUFFIX}" return {"friendly_name": name} @@ -261,7 +261,7 @@ class SnapcastClientDevice(MediaPlayerEntity): return STATE_OFF @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" state_attrs = {} if self.latency is not None: diff --git a/homeassistant/components/sochain/sensor.py b/homeassistant/components/sochain/sensor.py index 8f704471339..5acc8e8432a 100644 --- a/homeassistant/components/sochain/sensor.py +++ b/homeassistant/components/sochain/sensor.py @@ -69,7 +69,7 @@ class SochainSensor(Entity): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/socialblade/sensor.py b/homeassistant/components/socialblade/sensor.py index 3d53e76a27a..3d3331f8af2 100644 --- a/homeassistant/components/socialblade/sensor.py +++ b/homeassistant/components/socialblade/sensor.py @@ -64,7 +64,7 @@ class SocialBladeSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._attributes: return self._attributes diff --git a/homeassistant/components/solaredge/sensor.py b/homeassistant/components/solaredge/sensor.py index 8609e578e5e..24932618195 100644 --- a/homeassistant/components/solaredge/sensor.py +++ b/homeassistant/components/solaredge/sensor.py @@ -162,7 +162,7 @@ class SolarEdgeDetailsSensor(SolarEdgeSensor): """Representation of an SolarEdge Monitoring API details sensor.""" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self.data_service.attributes @@ -182,7 +182,7 @@ class SolarEdgeInventorySensor(SolarEdgeSensor): self._json_key = SENSOR_TYPES[self.sensor_key][0] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self.data_service.attributes.get(self._json_key) @@ -202,7 +202,7 @@ class SolarEdgeEnergyDetailsSensor(SolarEdgeSensor): self._json_key = SENSOR_TYPES[self.sensor_key][0] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self.data_service.attributes.get(self._json_key) @@ -232,7 +232,7 @@ class SolarEdgePowerFlowSensor(SolarEdgeSensor): return DEVICE_CLASS_POWER @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self.data_service.attributes.get(self._json_key) diff --git a/homeassistant/components/solaredge_local/sensor.py b/homeassistant/components/solaredge_local/sensor.py index 59b0a5e8856..9d5eca149cf 100644 --- a/homeassistant/components/solaredge_local/sensor.py +++ b/homeassistant/components/solaredge_local/sensor.py @@ -257,7 +257,7 @@ class SolarEdgeSensor(Entity): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._attr: try: diff --git a/homeassistant/components/sonarr/sensor.py b/homeassistant/components/sonarr/sensor.py index 8a625846744..ca489d95cfd 100644 --- a/homeassistant/components/sonarr/sensor.py +++ b/homeassistant/components/sonarr/sensor.py @@ -131,7 +131,7 @@ class SonarrCommandsSensor(SonarrSensor): self._commands = await self.sonarr.commands() @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" attrs = {} @@ -172,7 +172,7 @@ class SonarrDiskspaceSensor(SonarrSensor): self._total_free = sum([disk.free for disk in self._disks]) @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" attrs = {} @@ -217,7 +217,7 @@ class SonarrQueueSensor(SonarrSensor): self._queue = await self.sonarr.queue() @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" attrs = {} @@ -258,7 +258,7 @@ class SonarrSeriesSensor(SonarrSensor): self._items = await self.sonarr.series() @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" attrs = {} @@ -301,7 +301,7 @@ class SonarrUpcomingSensor(SonarrSensor): ) @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" attrs = {} @@ -342,7 +342,7 @@ class SonarrWantedSensor(SonarrSensor): self._total = self._results.total @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" attrs = {} diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 1a9e9ef58df..0b01ff94462 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -1366,7 +1366,7 @@ class SonosEntity(MediaPlayerEntity): self.soco.remove_from_queue(queue_position) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return entity specific state attributes.""" attributes = {ATTR_SONOS_GROUP: [e.entity_id for e in self._sonos_group]} diff --git a/homeassistant/components/soundtouch/media_player.py b/homeassistant/components/soundtouch/media_player.py index 83c8192ccb2..1b07f01e92a 100644 --- a/homeassistant/components/soundtouch/media_player.py +++ b/homeassistant/components/soundtouch/media_player.py @@ -440,7 +440,7 @@ class SoundTouchDevice(MediaPlayerEntity): self._device.add_zone_slave([slave.device for slave in slaves]) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return entity specific state attributes.""" attributes = {} diff --git a/homeassistant/components/speedtestdotnet/sensor.py b/homeassistant/components/speedtestdotnet/sensor.py index 5607d2570c9..6f0cb4124fe 100644 --- a/homeassistant/components/speedtestdotnet/sensor.py +++ b/homeassistant/components/speedtestdotnet/sensor.py @@ -67,7 +67,7 @@ class SpeedtestSensor(CoordinatorEntity, RestoreEntity): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if not self.coordinator.data: return None diff --git a/homeassistant/components/spotcrime/sensor.py b/homeassistant/components/spotcrime/sensor.py index 30aa80b5e7d..e44bd81ed51 100644 --- a/homeassistant/components/spotcrime/sensor.py +++ b/homeassistant/components/spotcrime/sensor.py @@ -103,7 +103,7 @@ class SpotCrimeSensor(Entity): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/sql/sensor.py b/homeassistant/components/sql/sensor.py index 670f5e66146..ccec918832e 100644 --- a/homeassistant/components/sql/sensor.py +++ b/homeassistant/components/sql/sensor.py @@ -120,7 +120,7 @@ class SQLSensor(Entity): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/squeezebox/media_player.py b/homeassistant/components/squeezebox/media_player.py index b87695dd159..c57f95266ff 100644 --- a/homeassistant/components/squeezebox/media_player.py +++ b/homeassistant/components/squeezebox/media_player.py @@ -265,7 +265,7 @@ class SqueezeBoxEntity(MediaPlayerEntity): self._remove_dispatcher = None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device-specific attributes.""" squeezebox_attr = { attr: getattr(self, attr) diff --git a/homeassistant/components/srp_energy/sensor.py b/homeassistant/components/srp_energy/sensor.py index 36a8798b05b..cc39bf0b898 100644 --- a/homeassistant/components/srp_energy/sensor.py +++ b/homeassistant/components/srp_energy/sensor.py @@ -122,7 +122,7 @@ class SrpEntity(entity.Entity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if not self.coordinator.data: return None diff --git a/homeassistant/components/starline/device_tracker.py b/homeassistant/components/starline/device_tracker.py index 6f202bbae52..59b9f5b4f95 100644 --- a/homeassistant/components/starline/device_tracker.py +++ b/homeassistant/components/starline/device_tracker.py @@ -26,7 +26,7 @@ class StarlineDeviceTracker(StarlineEntity, TrackerEntity, RestoreEntity): super().__init__(account, device, "location", "Location") @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific attributes.""" return self._account.gps_attrs(self._device) diff --git a/homeassistant/components/starline/lock.py b/homeassistant/components/starline/lock.py index 0b158451fb3..f19fa4896ba 100644 --- a/homeassistant/components/starline/lock.py +++ b/homeassistant/components/starline/lock.py @@ -31,7 +31,7 @@ class StarlineLock(StarlineEntity, LockEntity): return super().available and self._device.online @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the lock. Possible dictionary keys: diff --git a/homeassistant/components/starline/sensor.py b/homeassistant/components/starline/sensor.py index 8aba1b54269..a8782a87892 100644 --- a/homeassistant/components/starline/sensor.py +++ b/homeassistant/components/starline/sensor.py @@ -109,7 +109,7 @@ class StarlineSensor(StarlineEntity, Entity): return self._device_class @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" if self._key == "balance": return self._account.balance_attrs(self._device) diff --git a/homeassistant/components/starline/switch.py b/homeassistant/components/starline/switch.py index c50a7bb4973..b3214390a44 100644 --- a/homeassistant/components/starline/switch.py +++ b/homeassistant/components/starline/switch.py @@ -53,7 +53,7 @@ class StarlineSwitch(StarlineEntity, SwitchEntity): return super().available and self._device.online @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the switch.""" if self._key == "ign": return self._account.engine_attrs(self._device) diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py index 11cddc88c87..3bf70060da9 100644 --- a/homeassistant/components/statistics/sensor.py +++ b/homeassistant/components/statistics/sensor.py @@ -184,7 +184,7 @@ class StatisticsSensor(Entity): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" if not self.is_binary: return { diff --git a/homeassistant/components/steam_online/sensor.py b/homeassistant/components/steam_online/sensor.py index dbe83177537..e62922c67f4 100644 --- a/homeassistant/components/steam_online/sensor.py +++ b/homeassistant/components/steam_online/sensor.py @@ -194,7 +194,7 @@ class SteamSensor(Entity): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attr = {} if self._game is not None: diff --git a/homeassistant/components/stiebel_eltron/climate.py b/homeassistant/components/stiebel_eltron/climate.py index d8c32575b17..5ae7a9230f7 100644 --- a/homeassistant/components/stiebel_eltron/climate.py +++ b/homeassistant/components/stiebel_eltron/climate.py @@ -96,7 +96,7 @@ class StiebelEltron(ClimateEntity): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return {"filter_alarm": self._filter_alarm} diff --git a/homeassistant/components/stookalert/binary_sensor.py b/homeassistant/components/stookalert/binary_sensor.py index a1c36e9a10e..033af78560c 100644 --- a/homeassistant/components/stookalert/binary_sensor.py +++ b/homeassistant/components/stookalert/binary_sensor.py @@ -57,7 +57,7 @@ class StookalertBinarySensor(BinarySensorEntity): self._api_handler = api_handler @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attribute(s) of the sensor.""" state_attr = {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/suez_water/sensor.py b/homeassistant/components/suez_water/sensor.py index 3bca3484298..53f6b3e9c14 100644 --- a/homeassistant/components/suez_water/sensor.py +++ b/homeassistant/components/suez_water/sensor.py @@ -73,7 +73,7 @@ class SuezSensor(Entity): return VOLUME_LITERS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/supervisord/sensor.py b/homeassistant/components/supervisord/sensor.py index 8e1d6f89eea..0817f10ed5c 100644 --- a/homeassistant/components/supervisord/sensor.py +++ b/homeassistant/components/supervisord/sensor.py @@ -61,7 +61,7 @@ class SupervisorProcessSensor(Entity): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_DESCRIPTION: self._info.get("description"), diff --git a/homeassistant/components/surepetcare/binary_sensor.py b/homeassistant/components/surepetcare/binary_sensor.py index 2a624b580ac..64e27669786 100644 --- a/homeassistant/components/surepetcare/binary_sensor.py +++ b/homeassistant/components/surepetcare/binary_sensor.py @@ -151,7 +151,7 @@ class Hub(SurePetcareBinarySensor): return self.available @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the device.""" attributes = None if self._state: @@ -179,7 +179,7 @@ class Pet(SurePetcareBinarySensor): return False @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the device.""" attributes = None if self._state: @@ -232,7 +232,7 @@ class DeviceConnectivity(SurePetcareBinarySensor): return self.available @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the device.""" attributes = None if self._state: diff --git a/homeassistant/components/surepetcare/sensor.py b/homeassistant/components/surepetcare/sensor.py index e2d3d070867..54e7f4d5773 100644 --- a/homeassistant/components/surepetcare/sensor.py +++ b/homeassistant/components/surepetcare/sensor.py @@ -125,7 +125,7 @@ class Flap(SurePetcareSensor): return SureLockStateID(self._state["locking"]["mode"]).name.capitalize() @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the device.""" attributes = None if self._state: @@ -166,7 +166,7 @@ class SureBattery(SurePetcareSensor): return DEVICE_CLASS_BATTERY @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return state attributes.""" attributes = None if self._state: diff --git a/homeassistant/components/swiss_hydrological_data/sensor.py b/homeassistant/components/swiss_hydrological_data/sensor.py index 61423312b2a..27071afd112 100644 --- a/homeassistant/components/swiss_hydrological_data/sensor.py +++ b/homeassistant/components/swiss_hydrological_data/sensor.py @@ -119,7 +119,7 @@ class SwissHydrologicalDataSensor(Entity): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attrs = {} diff --git a/homeassistant/components/swiss_public_transport/sensor.py b/homeassistant/components/swiss_public_transport/sensor.py index 2c7fb483eff..1a7b97ce439 100644 --- a/homeassistant/components/swiss_public_transport/sensor.py +++ b/homeassistant/components/swiss_public_transport/sensor.py @@ -94,7 +94,7 @@ class SwissPublicTransportSensor(Entity): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._opendata is None: return diff --git a/homeassistant/components/switchbot/switch.py b/homeassistant/components/switchbot/switch.py index 89ba7b5be5e..faf230507a2 100644 --- a/homeassistant/components/switchbot/switch.py +++ b/homeassistant/components/switchbot/switch.py @@ -86,6 +86,6 @@ class SwitchBot(SwitchEntity, RestoreEntity): return self._name @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes.""" return {"last_run_success": self._last_run_success} diff --git a/homeassistant/components/switcher_kis/switch.py b/homeassistant/components/switcher_kis/switch.py index 6b4b5026c2f..99d50c0c559 100644 --- a/homeassistant/components/switcher_kis/switch.py +++ b/homeassistant/components/switcher_kis/switch.py @@ -139,7 +139,7 @@ class SwitcherControl(SwitchEntity): return self._device_data.power_consumption @property - def device_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> Dict: """Return the optional state attributes.""" attribs = {} diff --git a/homeassistant/components/syncthru/sensor.py b/homeassistant/components/syncthru/sensor.py index 639ec3ac6cb..bfada33bf38 100644 --- a/homeassistant/components/syncthru/sensor.py +++ b/homeassistant/components/syncthru/sensor.py @@ -121,7 +121,7 @@ class SyncThruSensor(Entity): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._attributes diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 50921944a6d..88654f02b21 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -610,7 +610,7 @@ class SynologyDSMBaseEntity(CoordinatorEntity): return self._class @property - def device_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> Dict[str, any]: """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/synology_dsm/binary_sensor.py b/homeassistant/components/synology_dsm/binary_sensor.py index 6e89f3d7a84..042e46c636e 100644 --- a/homeassistant/components/synology_dsm/binary_sensor.py +++ b/homeassistant/components/synology_dsm/binary_sensor.py @@ -71,7 +71,7 @@ class SynoDSMSecurityBinarySensor(SynologyDSMBaseEntity, BinarySensorEntity): return bool(self._api.security) @property - def device_state_attributes(self) -> Dict[str, str]: + def extra_state_attributes(self) -> Dict[str, str]: """Return security checks details.""" return self._api.security.status_by_check diff --git a/tests/components/srp_energy/test_sensor.py b/tests/components/srp_energy/test_sensor.py index a93e56b7b93..069dc9eb64f 100644 --- a/tests/components/srp_energy/test_sensor.py +++ b/tests/components/srp_energy/test_sensor.py @@ -91,7 +91,7 @@ async def test_srp_entity(hass): assert srp_entity.icon == ICON assert srp_entity.usage == "2.00" assert srp_entity.should_poll is False - assert srp_entity.device_state_attributes[ATTR_ATTRIBUTION] == ATTRIBUTION + assert srp_entity.extra_state_attributes[ATTR_ATTRIBUTION] == ATTRIBUTION assert srp_entity.available is not None await srp_entity.async_added_to_hass() @@ -104,7 +104,7 @@ async def test_srp_entity_no_data(hass): """Test the SrpEntity.""" fake_coordinator = MagicMock(data=False) srp_entity = SrpEntity(fake_coordinator) - assert srp_entity.device_state_attributes is None + assert srp_entity.extra_state_attributes is None async def test_srp_entity_no_coord_data(hass): From 4bafd03dffbca3551608e9a88aa582055829bc1b Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Thu, 11 Mar 2021 22:18:16 +0100 Subject: [PATCH 306/831] Consistent spelling of "PIN" (#47771) --- homeassistant/components/blink/services.yaml | 4 ++-- homeassistant/components/blink/strings.json | 2 +- homeassistant/components/ecobee/strings.json | 2 +- homeassistant/components/hangouts/strings.json | 4 ++-- homeassistant/components/nest/strings.json | 2 +- homeassistant/components/zwave/lock.py | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/blink/services.yaml b/homeassistant/components/blink/services.yaml index dc6491e2139..6ea4e2aa9ac 100644 --- a/homeassistant/components/blink/services.yaml +++ b/homeassistant/components/blink/services.yaml @@ -21,8 +21,8 @@ save_video: example: "/tmp/video.mp4" send_pin: - description: Send a new pin to blink for 2FA. + description: Send a new PIN to blink for 2FA. fields: pin: - description: Pin received from blink. Leave empty if you only received a verification email. + description: PIN received from blink. Leave empty if you only received a verification email. example: "abc123" diff --git a/homeassistant/components/blink/strings.json b/homeassistant/components/blink/strings.json index db9bdf96273..6e438b58590 100644 --- a/homeassistant/components/blink/strings.json +++ b/homeassistant/components/blink/strings.json @@ -11,7 +11,7 @@ "2fa": { "title": "Two-factor authentication", "data": { "2fa": "Two-factor code" }, - "description": "Enter the pin sent to your email" + "description": "Enter the PIN sent to your email" } }, "error": { diff --git a/homeassistant/components/ecobee/strings.json b/homeassistant/components/ecobee/strings.json index 78f0708134c..19f379de7d9 100644 --- a/homeassistant/components/ecobee/strings.json +++ b/homeassistant/components/ecobee/strings.json @@ -10,7 +10,7 @@ }, "authorize": { "title": "Authorize app on ecobee.com", - "description": "Please authorize this app at https://www.ecobee.com/consumerportal/index.html with pin code:\n\n{pin}\n\nThen, press Submit." + "description": "Please authorize this app at https://www.ecobee.com/consumerportal/index.html with PIN code:\n\n{pin}\n\nThen, press Submit." } }, "error": { diff --git a/homeassistant/components/hangouts/strings.json b/homeassistant/components/hangouts/strings.json index bed46e823d9..0128363a1ab 100644 --- a/homeassistant/components/hangouts/strings.json +++ b/homeassistant/components/hangouts/strings.json @@ -7,7 +7,7 @@ "error": { "invalid_login": "Invalid Login, please try again.", "invalid_2fa": "Invalid 2 Factor Authentication, please try again.", - "invalid_2fa_method": "Invalid 2FA Method (Verify on Phone)." + "invalid_2fa_method": "Invalid 2FA Method (verify on Phone)." }, "step": { "user": { @@ -20,7 +20,7 @@ }, "2fa": { "data": { - "2fa": "2FA Pin" + "2fa": "2FA PIN" }, "title": "2-Factor-Authentication" } diff --git a/homeassistant/components/nest/strings.json b/homeassistant/components/nest/strings.json index 6ce529621aa..26ec49c0d75 100644 --- a/homeassistant/components/nest/strings.json +++ b/homeassistant/components/nest/strings.json @@ -17,7 +17,7 @@ }, "link": { "title": "Link Nest Account", - "description": "To link your Nest account, [authorize your account]({url}).\n\nAfter authorization, copy-paste the provided pin code below.", + "description": "To link your Nest account, [authorize your account]({url}).\n\nAfter authorization, copy-paste the provided PIN code below.", "data": { "code": "[%key:common::config_flow::data::pin%]" } diff --git a/homeassistant/components/zwave/lock.py b/homeassistant/components/zwave/lock.py index 605a64ada5d..e6228a29334 100644 --- a/homeassistant/components/zwave/lock.py +++ b/homeassistant/components/zwave/lock.py @@ -95,7 +95,7 @@ LOCK_ALARM_TYPE = { "27": "Auto re-lock", "33": "User deleted: ", "112": "Master code changed or User added: ", - "113": "Duplicate Pin-code: ", + "113": "Duplicate PIN code: ", "130": "RF module, power restored", "144": "Unlocked by NFC Tag or Card by user ", "161": "Tamper Alarm: ", From 3ebc262b7fc5d8cb027c8aa4f4b56bf67891a2dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Cla=C3=9Fen?= Date: Thu, 11 Mar 2021 22:54:27 +0100 Subject: [PATCH 307/831] Upgrade numato-gpio to 0.10.0 (#47539) This adds support for devices sending '\n\r' end-of-line sequences. --- homeassistant/components/numato/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/numato/manifest.json b/homeassistant/components/numato/manifest.json index 4b7dcd9e372..6138f401ec2 100644 --- a/homeassistant/components/numato/manifest.json +++ b/homeassistant/components/numato/manifest.json @@ -2,6 +2,6 @@ "domain": "numato", "name": "Numato USB GPIO Expander", "documentation": "https://www.home-assistant.io/integrations/numato", - "requirements": ["numato-gpio==0.8.0"], + "requirements": ["numato-gpio==0.10.0"], "codeowners": ["@clssn"] } diff --git a/requirements_all.txt b/requirements_all.txt index 5170f3b2574..6cdbb1dd65d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1008,7 +1008,7 @@ nsw-fuel-api-client==1.0.10 nuheat==0.3.0 # homeassistant.components.numato -numato-gpio==0.8.0 +numato-gpio==0.10.0 # homeassistant.components.iqvia # homeassistant.components.opencv diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2b77de8022b..e2363c528fd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -517,7 +517,7 @@ nsw-fuel-api-client==1.0.10 nuheat==0.3.0 # homeassistant.components.numato -numato-gpio==0.8.0 +numato-gpio==0.10.0 # homeassistant.components.iqvia # homeassistant.components.opencv From 7ca5e969cc874258aa8cba2570818d269f3e2cd6 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 11 Mar 2021 16:28:38 -0700 Subject: [PATCH 308/831] Fix zwave_js target_temp_low (#47762) --- homeassistant/components/zwave_js/climate.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 1a15c45049b..ceb46982949 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -287,7 +287,12 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): @property def target_temperature_low(self) -> Optional[float]: """Return the lowbound target temperature we try to reach.""" - return self.target_temperature + if self._current_mode and self._current_mode.value is None: + # guard missing value + return None + if len(self._current_mode_setpoint_enums) > 1: + return self.target_temperature + return None @property def preset_mode(self) -> Optional[str]: From daab9f9810994d20a4cfcb2f83b07e6160bd74c7 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 11 Mar 2021 17:35:11 -0600 Subject: [PATCH 309/831] Bump plexapi to 4.4.1 (#47766) --- homeassistant/components/plex/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index 49388bdfdb6..1319e4bbf49 100644 --- a/homeassistant/components/plex/manifest.json +++ b/homeassistant/components/plex/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/plex", "requirements": [ - "plexapi==4.4.0", + "plexapi==4.4.1", "plexauth==0.0.6", "plexwebsocket==0.0.12" ], diff --git a/requirements_all.txt b/requirements_all.txt index 6cdbb1dd65d..e0260abb04d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1132,7 +1132,7 @@ pillow==8.1.2 pizzapi==0.0.3 # homeassistant.components.plex -plexapi==4.4.0 +plexapi==4.4.1 # homeassistant.components.plex plexauth==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e2363c528fd..fbd8bbd6bc7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -578,7 +578,7 @@ pilight==0.1.1 pillow==8.1.2 # homeassistant.components.plex -plexapi==4.4.0 +plexapi==4.4.1 # homeassistant.components.plex plexauth==0.0.6 From 66605b5994451b2e0c5ef0bbab3649e402d6d326 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 12 Mar 2021 00:37:34 +0100 Subject: [PATCH 310/831] Upgrade adguardhome to v0.5.0 (#47774) --- homeassistant/components/adguard/__init__.py | 14 +++++++++----- homeassistant/components/adguard/manifest.json | 2 +- homeassistant/components/adguard/sensor.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/adguard/__init__.py b/homeassistant/components/adguard/__init__.py index 6ad7d9579a8..4015bd31bf2 100644 --- a/homeassistant/components/adguard/__init__.py +++ b/homeassistant/components/adguard/__init__.py @@ -81,24 +81,28 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool async def add_url(call) -> None: """Service call to add a new filter subscription to AdGuard Home.""" await adguard.filtering.add_url( - call.data.get(CONF_NAME), call.data.get(CONF_URL) + allowlist=False, name=call.data.get(CONF_NAME), url=call.data.get(CONF_URL) ) async def remove_url(call) -> None: """Service call to remove a filter subscription from AdGuard Home.""" - await adguard.filtering.remove_url(call.data.get(CONF_URL)) + await adguard.filtering.remove_url(allowlist=False, url=call.data.get(CONF_URL)) async def enable_url(call) -> None: """Service call to enable a filter subscription in AdGuard Home.""" - await adguard.filtering.enable_url(call.data.get(CONF_URL)) + await adguard.filtering.enable_url(allowlist=False, url=call.data.get(CONF_URL)) async def disable_url(call) -> None: """Service call to disable a filter subscription in AdGuard Home.""" - await adguard.filtering.disable_url(call.data.get(CONF_URL)) + await adguard.filtering.disable_url( + allowlist=False, url=call.data.get(CONF_URL) + ) async def refresh(call) -> None: """Service call to refresh the filter subscriptions in AdGuard Home.""" - await adguard.filtering.refresh(call.data.get(CONF_FORCE)) + await adguard.filtering.refresh( + allowlist=False, force=call.data.get(CONF_FORCE) + ) hass.services.async_register( DOMAIN, SERVICE_ADD_URL, add_url, schema=SERVICE_ADD_URL_SCHEMA diff --git a/homeassistant/components/adguard/manifest.json b/homeassistant/components/adguard/manifest.json index 0bcd25569a5..dd23e561364 100644 --- a/homeassistant/components/adguard/manifest.json +++ b/homeassistant/components/adguard/manifest.json @@ -3,6 +3,6 @@ "name": "AdGuard Home", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/adguard", - "requirements": ["adguardhome==0.4.2"], + "requirements": ["adguardhome==0.5.0"], "codeowners": ["@frenck"] } diff --git a/homeassistant/components/adguard/sensor.py b/homeassistant/components/adguard/sensor.py index edd9fe22ba9..3a93d9fbb35 100644 --- a/homeassistant/components/adguard/sensor.py +++ b/homeassistant/components/adguard/sensor.py @@ -232,4 +232,4 @@ class AdGuardHomeRulesCountSensor(AdGuardHomeSensor): async def _adguard_update(self) -> None: """Update AdGuard Home entity.""" - self._state = await self.adguard.filtering.rules_count() + self._state = await self.adguard.filtering.rules_count(allowlist=False) diff --git a/requirements_all.txt b/requirements_all.txt index e0260abb04d..140273de629 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -111,7 +111,7 @@ adb-shell[async]==0.2.1 adext==0.4.1 # homeassistant.components.adguard -adguardhome==0.4.2 +adguardhome==0.5.0 # homeassistant.components.advantage_air advantage_air==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fbd8bbd6bc7..a2aaf83b113 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -51,7 +51,7 @@ adb-shell[async]==0.2.1 adext==0.4.1 # homeassistant.components.adguard -adguardhome==0.4.2 +adguardhome==0.5.0 # homeassistant.components.advantage_air advantage_air==0.2.1 From 92852b9c10e9454f713f1ffc68fe6c87bf686b27 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 12 Mar 2021 04:03:30 +0100 Subject: [PATCH 311/831] Add apply_filter attribute to recorder.purge service (#45826) --- homeassistant/components/recorder/__init__.py | 18 +- homeassistant/components/recorder/purge.py | 75 ++- .../components/recorder/services.yaml | 8 + tests/components/recorder/test_purge.py | 429 +++++++++++++++++- 4 files changed, 522 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 9b84518b6d3..f8f95fd7ccc 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -52,11 +52,13 @@ SERVICE_DISABLE = "disable" ATTR_KEEP_DAYS = "keep_days" ATTR_REPACK = "repack" +ATTR_APPLY_FILTER = "apply_filter" SERVICE_PURGE_SCHEMA = vol.Schema( { vol.Optional(ATTR_KEEP_DAYS): cv.positive_int, vol.Optional(ATTR_REPACK, default=False): cv.boolean, + vol.Optional(ATTR_APPLY_FILTER, default=False): cv.boolean, } ) SERVICE_ENABLE_SCHEMA = vol.Schema({}) @@ -227,6 +229,7 @@ class PurgeTask(NamedTuple): keep_days: int repack: bool + apply_filter: bool class WaitTask: @@ -309,8 +312,9 @@ class Recorder(threading.Thread): """Trigger an adhoc purge retaining keep_days worth of data.""" keep_days = kwargs.get(ATTR_KEEP_DAYS, self.keep_days) repack = kwargs.get(ATTR_REPACK) + apply_filter = kwargs.get(ATTR_APPLY_FILTER) - self.queue.put(PurgeTask(keep_days, repack)) + self.queue.put(PurgeTask(keep_days, repack, apply_filter)) def run(self): """Start processing events to save.""" @@ -364,7 +368,9 @@ class Recorder(threading.Thread): @callback def async_purge(now): """Trigger the purge.""" - self.queue.put(PurgeTask(self.keep_days, repack=False)) + self.queue.put( + PurgeTask(self.keep_days, repack=False, apply_filter=False) + ) # Purge every night at 4:12am self.hass.helpers.event.track_time_change( @@ -425,8 +431,12 @@ class Recorder(threading.Thread): """Process one event.""" if isinstance(event, PurgeTask): # Schedule a new purge task if this one didn't finish - if not purge.purge_old_data(self, event.keep_days, event.repack): - self.queue.put(PurgeTask(event.keep_days, event.repack)) + if not purge.purge_old_data( + self, event.keep_days, event.repack, event.apply_filter + ): + self.queue.put( + PurgeTask(event.keep_days, event.repack, event.apply_filter) + ) return if isinstance(event, WaitTask): self._queue_watch.set() diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index 3717ed49f30..ef626a744c4 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -8,6 +8,7 @@ from typing import TYPE_CHECKING from sqlalchemy.exc import OperationalError, SQLAlchemyError from sqlalchemy.orm.session import Session +from sqlalchemy.sql.expression import distinct import homeassistant.util.dt as dt_util @@ -22,7 +23,9 @@ if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__) -def purge_old_data(instance: Recorder, purge_days: int, repack: bool) -> bool: +def purge_old_data( + instance: Recorder, purge_days: int, repack: bool, apply_filter: bool = False +) -> bool: """Purge events and states older than purge_days ago. Cleans up an timeframe of an hour, based on the oldest record. @@ -45,6 +48,9 @@ def purge_old_data(instance: Recorder, purge_days: int, repack: bool) -> bool: # return false, as we are not done yet. _LOGGER.debug("Purging hasn't fully completed yet") return False + if apply_filter and _purge_filtered_data(instance, session) is False: + _LOGGER.debug("Cleanup filtered data hasn't fully completed yet") + return False _purge_old_recorder_runs(instance, session, purge_before) if repack: repack_database(instance) @@ -140,3 +146,70 @@ def _purge_old_recorder_runs( .delete(synchronize_session=False) ) _LOGGER.debug("Deleted %s recorder_runs", deleted_rows) + + +def _purge_filtered_data(instance: Recorder, session: Session) -> bool: + """Remove filtered states and events that shouldn't be in the database.""" + _LOGGER.debug("Cleanup filtered data") + + # Check if excluded entity_ids are in database + excluded_entity_ids: list[str] = [ + entity_id + for (entity_id,) in session.query(distinct(States.entity_id)).all() + if not instance.entity_filter(entity_id) + ] + if len(excluded_entity_ids) > 0: + _purge_filtered_states(session, excluded_entity_ids) + return False + + # Check if excluded event_types are in database + excluded_event_types: list[str] = [ + event_type + for (event_type,) in session.query(distinct(Events.event_type)).all() + if event_type in instance.exclude_t + ] + if len(excluded_event_types) > 0: + _purge_filtered_events(session, excluded_event_types) + return False + + return True + + +def _purge_filtered_states(session: Session, excluded_entity_ids: list[str]) -> None: + """Remove filtered states and linked events.""" + state_ids: list[int] + event_ids: list[int | None] + state_ids, event_ids = zip( + *( + session.query(States.state_id, States.event_id) + .filter(States.entity_id.in_(excluded_entity_ids)) + .limit(MAX_ROWS_TO_PURGE) + .all() + ) + ) + event_ids = [id_ for id_ in event_ids if id_ is not None] + _LOGGER.debug( + "Selected %s state_ids to remove that should be filtered", len(state_ids) + ) + _purge_state_ids(session, state_ids) + _purge_event_ids(session, event_ids) # type: ignore # type of event_ids already narrowed to 'list[int]' + + +def _purge_filtered_events(session: Session, excluded_event_types: list[str]) -> None: + """Remove filtered events and linked states.""" + events: list[Events] = ( + session.query(Events.event_id) + .filter(Events.event_type.in_(excluded_event_types)) + .limit(MAX_ROWS_TO_PURGE) + .all() + ) + event_ids: list[int] = [event.event_id for event in events] + _LOGGER.debug( + "Selected %s event_ids to remove that should be filtered", len(event_ids) + ) + states: list[States] = ( + session.query(States.state_id).filter(States.event_id.in_(event_ids)).all() + ) + state_ids: list[int] = [state.state_id for state in states] + _purge_state_ids(session, state_ids) + _purge_event_ids(session, event_ids) diff --git a/homeassistant/components/recorder/services.yaml b/homeassistant/components/recorder/services.yaml index 2be5b0e095e..2c4f35b5e7a 100644 --- a/homeassistant/components/recorder/services.yaml +++ b/homeassistant/components/recorder/services.yaml @@ -25,6 +25,14 @@ purge: selector: boolean: + apply_filter: + name: Apply filter + description: Apply entity_id and event_type filter in addition to time based purge. + example: true + default: false + selector: + boolean: + disable: description: Stop the recording of events and state changes diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index 3535a58d33d..db3906595db 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -1,15 +1,18 @@ """Test data purging.""" -from datetime import timedelta +from datetime import datetime, timedelta import json +from sqlalchemy.orm.session import Session + from homeassistant.components import recorder from homeassistant.components.recorder.models import Events, RecorderRuns, States from homeassistant.components.recorder.purge import purge_old_data from homeassistant.components.recorder.util import session_scope -from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.const import EVENT_STATE_CHANGED +from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.util import dt as dt_util -from .common import async_wait_recording_done +from .common import async_recorder_block_till_done, async_wait_recording_done from .conftest import SetupRecorderInstanceT @@ -154,6 +157,394 @@ async def test_purge_method( assert "Vacuuming SQL DB to free space" in caplog.text +async def test_purge_edge_case( + hass: HomeAssistantType, + async_setup_recorder_instance: SetupRecorderInstanceT, +): + """Test states and events are purged even if they occurred shortly before purge_before.""" + + async def _add_db_entries(hass: HomeAssistantType, timestamp: datetime) -> None: + with recorder.session_scope(hass=hass) as session: + session.add( + Events( + event_id=1001, + event_type="EVENT_TEST_PURGE", + event_data="{}", + origin="LOCAL", + created=timestamp, + time_fired=timestamp, + ) + ) + session.add( + States( + entity_id="test.recorder2", + domain="sensor", + state="purgeme", + attributes="{}", + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + event_id=1001, + ) + ) + + instance = await async_setup_recorder_instance(hass, None) + await async_wait_recording_done(hass, instance) + + service_data = {"keep_days": 2} + timestamp = dt_util.utcnow() - timedelta(days=2, minutes=1) + + await _add_db_entries(hass, timestamp) + with session_scope(hass=hass) as session: + states = session.query(States) + assert states.count() == 1 + + events = session.query(Events).filter(Events.event_type == "EVENT_TEST_PURGE") + assert events.count() == 1 + + await hass.services.async_call( + recorder.DOMAIN, recorder.SERVICE_PURGE, service_data + ) + await hass.async_block_till_done() + + await async_recorder_block_till_done(hass, instance) + await async_wait_recording_done(hass, instance) + + assert states.count() == 0 + assert events.count() == 0 + + +async def test_purge_filtered_states( + hass: HomeAssistantType, + async_setup_recorder_instance: SetupRecorderInstanceT, +): + """Test filtered states are purged.""" + config: ConfigType = {"exclude": {"entities": ["sensor.excluded"]}} + instance = await async_setup_recorder_instance(hass, config) + assert instance.entity_filter("sensor.excluded") is False + + def _add_db_entries(hass: HomeAssistantType) -> None: + with recorder.session_scope(hass=hass) as session: + # Add states and state_changed events that should be purged + for days in range(1, 4): + timestamp = dt_util.utcnow() - timedelta(days=days) + for event_id in range(1000, 1020): + _add_state_and_state_changed_event( + session, + "sensor.excluded", + "purgeme", + timestamp, + event_id * days, + ) + # Add state **without** state_changed event that should be purged + timestamp = dt_util.utcnow() - timedelta(days=1) + session.add( + States( + entity_id="sensor.excluded", + domain="sensor", + state="purgeme", + attributes="{}", + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + ) + ) + # Add states and state_changed events that should be keeped + timestamp = dt_util.utcnow() - timedelta(days=2) + for event_id in range(200, 210): + _add_state_and_state_changed_event( + session, + "sensor.keep", + "keep", + timestamp, + event_id, + ) + # Add states with linked old_state_ids that need to be handled + timestamp = dt_util.utcnow() - timedelta(days=0) + state_1 = States( + entity_id="sensor.linked_old_state_id", + domain="sensor", + state="keep", + attributes="{}", + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + old_state_id=1, + ) + timestamp = dt_util.utcnow() - timedelta(days=4) + state_2 = States( + entity_id="sensor.linked_old_state_id", + domain="sensor", + state="keep", + attributes="{}", + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + old_state_id=2, + ) + state_3 = States( + entity_id="sensor.linked_old_state_id", + domain="sensor", + state="keep", + attributes="{}", + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + old_state_id=62, # keep + ) + session.add_all((state_1, state_2, state_3)) + # Add event that should be keeped + session.add( + Events( + event_id=100, + event_type="EVENT_KEEP", + event_data="{}", + origin="LOCAL", + created=timestamp, + time_fired=timestamp, + ) + ) + + service_data = {"keep_days": 10} + _add_db_entries(hass) + + with session_scope(hass=hass) as session: + states = session.query(States) + assert states.count() == 74 + + events_state_changed = session.query(Events).filter( + Events.event_type == EVENT_STATE_CHANGED + ) + events_keep = session.query(Events).filter(Events.event_type == "EVENT_KEEP") + assert events_state_changed.count() == 70 + assert events_keep.count() == 1 + + # Normal purge doesn't remove excluded entities + await hass.services.async_call( + recorder.DOMAIN, recorder.SERVICE_PURGE, service_data + ) + await hass.async_block_till_done() + + await async_recorder_block_till_done(hass, instance) + await async_wait_recording_done(hass, instance) + + assert states.count() == 74 + assert events_state_changed.count() == 70 + assert events_keep.count() == 1 + + # Test with 'apply_filter' = True + service_data["apply_filter"] = True + await hass.services.async_call( + recorder.DOMAIN, recorder.SERVICE_PURGE, service_data + ) + await hass.async_block_till_done() + + await async_recorder_block_till_done(hass, instance) + await async_wait_recording_done(hass, instance) + + await async_recorder_block_till_done(hass, instance) + await async_wait_recording_done(hass, instance) + + assert states.count() == 13 + assert events_state_changed.count() == 10 + assert events_keep.count() == 1 + + states_sensor_excluded = session.query(States).filter( + States.entity_id == "sensor.excluded" + ) + assert states_sensor_excluded.count() == 0 + + session.query(States).get(71).old_state_id is None + session.query(States).get(72).old_state_id is None + session.query(States).get(73).old_state_id == 62 # should have been keeped + + +async def test_purge_filtered_events( + hass: HomeAssistantType, + async_setup_recorder_instance: SetupRecorderInstanceT, +): + """Test filtered events are purged.""" + config: ConfigType = {"exclude": {"event_types": ["EVENT_PURGE"]}} + instance = await async_setup_recorder_instance(hass, config) + + def _add_db_entries(hass: HomeAssistantType) -> None: + with recorder.session_scope(hass=hass) as session: + # Add events that should be purged + for days in range(1, 4): + timestamp = dt_util.utcnow() - timedelta(days=days) + for event_id in range(1000, 1020): + session.add( + Events( + event_id=event_id * days, + event_type="EVENT_PURGE", + event_data="{}", + origin="LOCAL", + created=timestamp, + time_fired=timestamp, + ) + ) + + # Add states and state_changed events that should be keeped + timestamp = dt_util.utcnow() - timedelta(days=1) + for event_id in range(200, 210): + _add_state_and_state_changed_event( + session, + "sensor.keep", + "keep", + timestamp, + event_id, + ) + + service_data = {"keep_days": 10} + _add_db_entries(hass) + + with session_scope(hass=hass) as session: + events_purge = session.query(Events).filter(Events.event_type == "EVENT_PURGE") + events_keep = session.query(Events).filter( + Events.event_type == EVENT_STATE_CHANGED + ) + states = session.query(States) + + assert events_purge.count() == 60 + assert events_keep.count() == 10 + assert states.count() == 10 + + # Normal purge doesn't remove excluded events + await hass.services.async_call( + recorder.DOMAIN, recorder.SERVICE_PURGE, service_data + ) + await hass.async_block_till_done() + + await async_recorder_block_till_done(hass, instance) + await async_wait_recording_done(hass, instance) + + assert events_purge.count() == 60 + assert events_keep.count() == 10 + assert states.count() == 10 + + # Test with 'apply_filter' = True + service_data["apply_filter"] = True + await hass.services.async_call( + recorder.DOMAIN, recorder.SERVICE_PURGE, service_data + ) + await hass.async_block_till_done() + + await async_recorder_block_till_done(hass, instance) + await async_wait_recording_done(hass, instance) + + await async_recorder_block_till_done(hass, instance) + await async_wait_recording_done(hass, instance) + + assert events_purge.count() == 0 + assert events_keep.count() == 10 + assert states.count() == 10 + + +async def test_purge_filtered_events_state_changed( + hass: HomeAssistantType, + async_setup_recorder_instance: SetupRecorderInstanceT, +): + """Test filtered state_changed events are purged. This should also remove all states.""" + config: ConfigType = {"exclude": {"event_types": [EVENT_STATE_CHANGED]}} + instance = await async_setup_recorder_instance(hass, config) + # Assert entity_id is NOT excluded + assert instance.entity_filter("sensor.excluded") is True + + def _add_db_entries(hass: HomeAssistantType) -> None: + with recorder.session_scope(hass=hass) as session: + # Add states and state_changed events that should be purged + for days in range(1, 4): + timestamp = dt_util.utcnow() - timedelta(days=days) + for event_id in range(1000, 1020): + _add_state_and_state_changed_event( + session, + "sensor.excluded", + "purgeme", + timestamp, + event_id * days, + ) + # Add events that should be keeped + timestamp = dt_util.utcnow() - timedelta(days=1) + for event_id in range(200, 210): + session.add( + Events( + event_id=event_id, + event_type="EVENT_KEEP", + event_data="{}", + origin="LOCAL", + created=timestamp, + time_fired=timestamp, + ) + ) + # Add states with linked old_state_ids that need to be handled + timestamp = dt_util.utcnow() - timedelta(days=0) + state_1 = States( + entity_id="sensor.linked_old_state_id", + domain="sensor", + state="keep", + attributes="{}", + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + old_state_id=1, + ) + timestamp = dt_util.utcnow() - timedelta(days=4) + state_2 = States( + entity_id="sensor.linked_old_state_id", + domain="sensor", + state="keep", + attributes="{}", + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + old_state_id=2, + ) + state_3 = States( + entity_id="sensor.linked_old_state_id", + domain="sensor", + state="keep", + attributes="{}", + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + old_state_id=62, # keep + ) + session.add_all((state_1, state_2, state_3)) + + service_data = {"keep_days": 10, "apply_filter": True} + _add_db_entries(hass) + + with session_scope(hass=hass) as session: + events_keep = session.query(Events).filter(Events.event_type == "EVENT_KEEP") + events_purge = session.query(Events).filter( + Events.event_type == EVENT_STATE_CHANGED + ) + states = session.query(States) + + assert events_keep.count() == 10 + assert events_purge.count() == 60 + assert states.count() == 63 + + await hass.services.async_call( + recorder.DOMAIN, recorder.SERVICE_PURGE, service_data + ) + await hass.async_block_till_done() + + await async_recorder_block_till_done(hass, instance) + await async_wait_recording_done(hass, instance) + + await async_recorder_block_till_done(hass, instance) + await async_wait_recording_done(hass, instance) + + assert events_keep.count() == 10 + assert events_purge.count() == 0 + assert states.count() == 3 + + session.query(States).get(61).old_state_id is None + session.query(States).get(62).old_state_id is None + session.query(States).get(63).old_state_id == 62 # should have been keeped + + async def _add_test_states(hass: HomeAssistantType, instance: recorder.Recorder): """Add multiple states to the db for testing.""" utcnow = dt_util.utcnow() @@ -260,3 +651,35 @@ async def _add_test_recorder_runs(hass: HomeAssistantType, instance: recorder.Re end=timestamp + timedelta(days=1), ) ) + + +def _add_state_and_state_changed_event( + session: Session, + entity_id: str, + state: str, + timestamp: datetime, + event_id: int, +) -> None: + """Add state and state_changed event to database for testing.""" + session.add( + States( + entity_id=entity_id, + domain="sensor", + state=state, + attributes="{}", + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + event_id=event_id, + ) + ) + session.add( + Events( + event_id=event_id, + event_type=EVENT_STATE_CHANGED, + event_data="{}", + origin="LOCAL", + created=timestamp, + time_fired=timestamp, + ) + ) From 9ca0cd546432991384900ef5686e961a57543d1f Mon Sep 17 00:00:00 2001 From: Felipe Martins Diel <41558831+felipediel@users.noreply.github.com> Date: Fri, 12 Mar 2021 02:34:56 -0300 Subject: [PATCH 312/831] Bump broadlink from 0.16.0 to 0.17.0 (#47779) --- .../components/broadlink/config_flow.py | 6 +- homeassistant/components/broadlink/const.py | 25 ++- homeassistant/components/broadlink/device.py | 4 +- .../components/broadlink/manifest.json | 2 +- homeassistant/components/broadlink/remote.py | 160 ++++++++++-------- homeassistant/components/broadlink/switch.py | 47 ++--- homeassistant/components/broadlink/updater.py | 63 +++---- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/broadlink/__init__.py | 13 +- 10 files changed, 157 insertions(+), 167 deletions(-) diff --git a/homeassistant/components/broadlink/config_flow.py b/homeassistant/components/broadlink/config_flow.py index a309e4eb603..3e21765dbed 100644 --- a/homeassistant/components/broadlink/config_flow.py +++ b/homeassistant/components/broadlink/config_flow.py @@ -39,11 +39,7 @@ class BroadlinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_set_device(self, device, raise_on_progress=True): """Define a device for the config flow.""" - supported_types = { - device_type - for device_types in DOMAINS_AND_TYPES - for device_type in device_types[1] - } + supported_types = set.union(*DOMAINS_AND_TYPES.values()) if device.type not in supported_types: _LOGGER.error( "Unsupported device: %s. If it worked before, please open " diff --git a/homeassistant/components/broadlink/const.py b/homeassistant/components/broadlink/const.py index b10f7e74ba7..fd060d23b35 100644 --- a/homeassistant/components/broadlink/const.py +++ b/homeassistant/components/broadlink/const.py @@ -5,11 +5,26 @@ from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN DOMAIN = "broadlink" -DOMAINS_AND_TYPES = ( - (REMOTE_DOMAIN, ("RM2", "RM4")), - (SENSOR_DOMAIN, ("A1", "RM2", "RM4")), - (SWITCH_DOMAIN, ("BG1", "MP1", "RM2", "RM4", "SP1", "SP2", "SP4", "SP4B")), -) +DOMAINS_AND_TYPES = { + REMOTE_DOMAIN: {"RM4MINI", "RM4PRO", "RMMINI", "RMMINIB", "RMPRO"}, + SENSOR_DOMAIN: {"A1", "RM4MINI", "RM4PRO", "RMPRO"}, + SWITCH_DOMAIN: { + "BG1", + "MP1", + "RM4MINI", + "RM4PRO", + "RMMINI", + "RMMINIB", + "RMPRO", + "SP1", + "SP2", + "SP2S", + "SP3", + "SP3S", + "SP4", + "SP4B", + }, +} DEFAULT_PORT = 80 DEFAULT_TIMEOUT = 5 diff --git a/homeassistant/components/broadlink/device.py b/homeassistant/components/broadlink/device.py index be9c7626ac1..c460040c12b 100644 --- a/homeassistant/components/broadlink/device.py +++ b/homeassistant/components/broadlink/device.py @@ -22,9 +22,9 @@ from .updater import get_update_manager _LOGGER = logging.getLogger(__name__) -def get_domains(device_type): +def get_domains(dev_type): """Return the domains available for a device type.""" - return {domain for domain, types in DOMAINS_AND_TYPES if device_type in types} + return {d for d, t in DOMAINS_AND_TYPES.items() if dev_type in t} class BroadlinkDevice: diff --git a/homeassistant/components/broadlink/manifest.json b/homeassistant/components/broadlink/manifest.json index 0562bc306a5..8d3b16b4582 100644 --- a/homeassistant/components/broadlink/manifest.json +++ b/homeassistant/components/broadlink/manifest.json @@ -2,7 +2,7 @@ "domain": "broadlink", "name": "Broadlink", "documentation": "https://www.home-assistant.io/integrations/broadlink", - "requirements": ["broadlink==0.16.0"], + "requirements": ["broadlink==0.17.0"], "codeowners": ["@danielhiversen", "@felipediel"], "config_flow": true } diff --git a/homeassistant/components/broadlink/remote.py b/homeassistant/components/broadlink/remote.py index 116c97aeb31..30043f487b1 100644 --- a/homeassistant/components/broadlink/remote.py +++ b/homeassistant/components/broadlink/remote.py @@ -26,6 +26,8 @@ from homeassistant.components.remote import ( DOMAIN as RM_DOMAIN, PLATFORM_SCHEMA, SERVICE_DELETE_COMMAND, + SERVICE_LEARN_COMMAND, + SERVICE_SEND_COMMAND, SUPPORT_DELETE_COMMAND, SUPPORT_LEARN_COMMAND, RemoteEntity, @@ -129,6 +131,7 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity): self._codes = {} self._flags = defaultdict(int) self._state = True + self._lock = asyncio.Lock() @property def name(self): @@ -171,39 +174,44 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity): "sw_version": self._device.fw_version, } - def get_code(self, command, device): - """Return a code and a boolean indicating a toggle command. + def _extract_codes(self, commands, device=None): + """Extract a list of codes. If the command starts with `b64:`, extract the code from it. - Otherwise, extract the code from the dictionary, using the device - and command as keys. + Otherwise, extract the code from storage, using the command and + device as keys. - You need to change the flag whenever a toggle command is sent - successfully. Use `self._flags[device] ^= 1`. + The codes are returned in sublists. For toggle commands, the + sublist contains two codes that must be sent alternately with + each call. """ - if command.startswith("b64:"): - code, is_toggle_cmd = command[4:], False + code_list = [] + for cmd in commands: + if cmd.startswith("b64:"): + codes = [cmd[4:]] - else: - if device is None: - raise KeyError("You need to specify a device") - - try: - code = self._codes[device][command] - except KeyError as err: - raise KeyError("Command not found") from err - - # For toggle commands, alternate between codes in a list. - if isinstance(code, list): - code = code[self._flags[device]] - is_toggle_cmd = True else: - is_toggle_cmd = False + if device is None: + raise ValueError("You need to specify a device") - try: - return data_packet(code), is_toggle_cmd - except ValueError as err: - raise ValueError("Invalid code") from err + try: + codes = self._codes[device][cmd] + except KeyError as err: + raise ValueError(f"Command not found: {repr(cmd)}") from err + + if isinstance(codes, list): + codes = codes[:] + else: + codes = [codes] + + for idx, code in enumerate(codes): + try: + codes[idx] = data_packet(code) + except ValueError as err: + raise ValueError(f"Invalid code: {repr(code)}") from err + + code_list.append(codes) + return code_list @callback def get_codes(self): @@ -261,44 +269,50 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity): device = kwargs.get(ATTR_DEVICE) repeat = kwargs[ATTR_NUM_REPEATS] delay = kwargs[ATTR_DELAY_SECS] + service = f"{RM_DOMAIN}.{SERVICE_SEND_COMMAND}" if not self._state: _LOGGER.warning( - "remote.send_command canceled: %s entity is turned off", self.entity_id + "%s canceled: %s entity is turned off", service, self.entity_id ) return - should_delay = False + try: + code_list = self._extract_codes(commands, device) + except ValueError as err: + _LOGGER.error("Failed to call %s: %s", service, err) + raise - for _, cmd in product(range(repeat), commands): - if should_delay: + rf_flags = {0xB2, 0xD7} + if not hasattr(self._device.api, "sweep_frequency") and any( + c[0] in rf_flags for codes in code_list for c in codes + ): + err_msg = f"{self.entity_id} doesn't support sending RF commands" + _LOGGER.error("Failed to call %s: %s", service, err_msg) + raise ValueError(err_msg) + + at_least_one_sent = False + for _, codes in product(range(repeat), code_list): + if at_least_one_sent: await asyncio.sleep(delay) - try: - code, is_toggle_cmd = self.get_code(cmd, device) - - except (KeyError, ValueError) as err: - _LOGGER.error("Failed to send '%s': %s", cmd, err) - should_delay = False - continue + if len(codes) > 1: + code = codes[self._flags[device]] + else: + code = codes[0] try: await self._device.async_request(self._device.api.send_data, code) - - except (AuthorizationError, NetworkTimeoutError, OSError) as err: - _LOGGER.error("Failed to send '%s': %s", cmd, err) + except (BroadlinkException, OSError) as err: + _LOGGER.error("Error during %s: %s", service, err) break - except BroadlinkException as err: - _LOGGER.error("Failed to send '%s': %s", cmd, err) - should_delay = False - continue - - should_delay = True - if is_toggle_cmd: + if len(codes) > 1: self._flags[device] ^= 1 + at_least_one_sent = True - self._flag_storage.async_delay_save(self.get_flags, FLAG_SAVE_DELAY) + if at_least_one_sent: + self._flag_storage.async_delay_save(self.get_flags, FLAG_SAVE_DELAY) async def async_learn_command(self, **kwargs): """Learn a list of commands from a remote.""" @@ -307,39 +321,47 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity): command_type = kwargs[ATTR_COMMAND_TYPE] device = kwargs[ATTR_DEVICE] toggle = kwargs[ATTR_ALTERNATIVE] + service = f"{RM_DOMAIN}.{SERVICE_LEARN_COMMAND}" if not self._state: _LOGGER.warning( - "remote.learn_command canceled: %s entity is turned off", self.entity_id + "%s canceled: %s entity is turned off", service, self.entity_id ) return - if command_type == COMMAND_TYPE_IR: - learn_command = self._async_learn_ir_command - else: - learn_command = self._async_learn_rf_command + async with self._lock: + if command_type == COMMAND_TYPE_IR: + learn_command = self._async_learn_ir_command - should_store = False + elif hasattr(self._device.api, "sweep_frequency"): + learn_command = self._async_learn_rf_command - for command in commands: - try: - code = await learn_command(command) - if toggle: - code = [code, await learn_command(command)] + else: + err_msg = f"{self.entity_id} doesn't support learning RF commands" + _LOGGER.error("Failed to call %s: %s", service, err_msg) + raise ValueError(err_msg) - except (AuthorizationError, NetworkTimeoutError, OSError) as err: - _LOGGER.error("Failed to learn '%s': %s", command, err) - break + should_store = False - except BroadlinkException as err: - _LOGGER.error("Failed to learn '%s': %s", command, err) - continue + for command in commands: + try: + code = await learn_command(command) + if toggle: + code = [code, await learn_command(command)] - self._codes.setdefault(device, {}).update({command: code}) - should_store = True + except (AuthorizationError, NetworkTimeoutError, OSError) as err: + _LOGGER.error("Failed to learn '%s': %s", command, err) + break - if should_store: - await self._code_storage.async_save(self._codes) + except BroadlinkException as err: + _LOGGER.error("Failed to learn '%s': %s", command, err) + continue + + self._codes.setdefault(device, {}).update({command: code}) + should_store = True + + if should_store: + await self._code_storage.async_save(self._codes) async def _async_learn_ir_command(self, command): """Learn an infrared command.""" diff --git a/homeassistant/components/broadlink/switch.py b/homeassistant/components/broadlink/switch.py index b4cd43ac493..0a98530c806 100644 --- a/homeassistant/components/broadlink/switch.py +++ b/homeassistant/components/broadlink/switch.py @@ -109,7 +109,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Broadlink switch.""" device = hass.data[DOMAIN].devices[config_entry.entry_id] - if device.api.type in {"RM2", "RM4"}: + if device.api.type in {"RM4MINI", "RM4PRO", "RMMINI", "RMMINIB", "RMPRO"}: platform_data = hass.data[DOMAIN].platforms.get(SWITCH_DOMAIN, {}) user_defined_switches = platform_data.get(device.api.mac, {}) switches = [ @@ -119,12 +119,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): elif device.api.type == "SP1": switches = [BroadlinkSP1Switch(device)] - elif device.api.type == "SP2": + elif device.api.type in {"SP2", "SP2S", "SP3", "SP3S", "SP4", "SP4B"}: switches = [BroadlinkSP2Switch(device)] - elif device.api.type in {"SP4", "SP4B"}: - switches = [BroadlinkSP4Switch(device)] - elif device.api.type == "BG1": switches = [BroadlinkBG1Slot(device, slot) for slot in range(1, 3)] @@ -143,7 +140,6 @@ class BroadlinkSwitch(SwitchEntity, RestoreEntity, ABC): self._command_on = command_on self._command_off = command_off self._coordinator = device.update_manager.coordinator - self._device_class = None self._state = None @property @@ -174,7 +170,7 @@ class BroadlinkSwitch(SwitchEntity, RestoreEntity, ABC): @property def device_class(self): """Return device class.""" - return self._device_class + return DEVICE_CLASS_SWITCH @property def device_info(self): @@ -254,7 +250,6 @@ class BroadlinkSP1Switch(BroadlinkSwitch): def __init__(self, device): """Initialize the switch.""" super().__init__(device, 1, 0) - self._device_class = DEVICE_CLASS_OUTLET @property def unique_id(self): @@ -277,10 +272,8 @@ class BroadlinkSP2Switch(BroadlinkSP1Switch): def __init__(self, device, *args, **kwargs): """Initialize the switch.""" super().__init__(device, *args, **kwargs) - self._state = self._coordinator.data["state"] - self._load_power = self._coordinator.data["load_power"] - if device.api.model == "SC1": - self._device_class = DEVICE_CLASS_SWITCH + self._state = self._coordinator.data["pwr"] + self._load_power = self._coordinator.data.get("power") @property def assumed_state(self): @@ -292,33 +285,12 @@ class BroadlinkSP2Switch(BroadlinkSP1Switch): """Return the current power usage in Watt.""" return self._load_power - @callback - def update_data(self): - """Update data.""" - if self._coordinator.last_update_success: - self._state = self._coordinator.data["state"] - self._load_power = self._coordinator.data["load_power"] - self.async_write_ha_state() - - -class BroadlinkSP4Switch(BroadlinkSP1Switch): - """Representation of a Broadlink SP4 switch.""" - - def __init__(self, device, *args, **kwargs): - """Initialize the switch.""" - super().__init__(device, *args, **kwargs) - self._state = self._coordinator.data["pwr"] - - @property - def assumed_state(self): - """Return True if unable to access real state of the switch.""" - return False - @callback def update_data(self): """Update data.""" if self._coordinator.last_update_success: self._state = self._coordinator.data["pwr"] + self._load_power = self._coordinator.data.get("power") self.async_write_ha_state() @@ -330,7 +302,6 @@ class BroadlinkMP1Slot(BroadlinkSwitch): super().__init__(device, 1, 0) self._slot = slot self._state = self._coordinator.data[f"s{slot}"] - self._device_class = DEVICE_CLASS_OUTLET @property def unique_id(self): @@ -374,7 +345,6 @@ class BroadlinkBG1Slot(BroadlinkSwitch): super().__init__(device, 1, 0) self._slot = slot self._state = self._coordinator.data[f"pwr{slot}"] - self._device_class = DEVICE_CLASS_OUTLET @property def unique_id(self): @@ -391,6 +361,11 @@ class BroadlinkBG1Slot(BroadlinkSwitch): """Return True if unable to access real state of the switch.""" return False + @property + def device_class(self): + """Return device class.""" + return DEVICE_CLASS_OUTLET + @callback def update_data(self): """Update data.""" diff --git a/homeassistant/components/broadlink/updater.py b/homeassistant/components/broadlink/updater.py index c9b273218b5..8401dba8c0d 100644 --- a/homeassistant/components/broadlink/updater.py +++ b/homeassistant/components/broadlink/updater.py @@ -1,17 +1,9 @@ """Support for fetching data from Broadlink devices.""" from abc import ABC, abstractmethod from datetime import timedelta -from functools import partial import logging -import broadlink as blk -from broadlink.exceptions import ( - AuthorizationError, - BroadlinkException, - CommandNotSupportedError, - NetworkTimeoutError, - StorageError, -) +from broadlink.exceptions import AuthorizationError, BroadlinkException from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.util import dt @@ -21,17 +13,20 @@ _LOGGER = logging.getLogger(__name__) def get_update_manager(device): """Return an update manager for a given Broadlink device.""" - if device.api.model.startswith("RM mini"): - return BroadlinkRMMini3UpdateManager(device) - update_managers = { "A1": BroadlinkA1UpdateManager, "BG1": BroadlinkBG1UpdateManager, "MP1": BroadlinkMP1UpdateManager, - "RM2": BroadlinkRMUpdateManager, - "RM4": BroadlinkRMUpdateManager, + "RM4MINI": BroadlinkRMUpdateManager, + "RM4PRO": BroadlinkRMUpdateManager, + "RMMINI": BroadlinkRMUpdateManager, + "RMMINIB": BroadlinkRMUpdateManager, + "RMPRO": BroadlinkRMUpdateManager, "SP1": BroadlinkSP1UpdateManager, "SP2": BroadlinkSP2UpdateManager, + "SP2S": BroadlinkSP2UpdateManager, + "SP3": BroadlinkSP2UpdateManager, + "SP3S": BroadlinkSP2UpdateManager, "SP4": BroadlinkSP4UpdateManager, "SP4B": BroadlinkSP4UpdateManager, } @@ -114,28 +109,18 @@ class BroadlinkMP1UpdateManager(BroadlinkUpdateManager): return await self.device.async_request(self.device.api.check_power) -class BroadlinkRMMini3UpdateManager(BroadlinkUpdateManager): - """Manages updates for Broadlink RM mini 3 devices.""" - - async def async_fetch_data(self): - """Fetch data from the device.""" - hello = partial( - blk.discover, - discover_ip_address=self.device.api.host[0], - timeout=self.device.api.timeout, - ) - devices = await self.device.hass.async_add_executor_job(hello) - if not devices: - raise NetworkTimeoutError("The device is offline") - return {} - - class BroadlinkRMUpdateManager(BroadlinkUpdateManager): - """Manages updates for Broadlink RM2 and RM4 devices.""" + """Manages updates for Broadlink remotes.""" async def async_fetch_data(self): """Fetch data from the device.""" - return await self.device.async_request(self.device.api.check_sensors) + device = self.device + + if hasattr(device.api, "check_sensors"): + return await device.async_request(device.api.check_sensors) + + await device.async_request(device.api.update) + return {} class BroadlinkSP1UpdateManager(BroadlinkUpdateManager): @@ -151,14 +136,14 @@ class BroadlinkSP2UpdateManager(BroadlinkUpdateManager): async def async_fetch_data(self): """Fetch data from the device.""" + device = self.device + data = {} - data["state"] = await self.device.async_request(self.device.api.check_power) - try: - data["load_power"] = await self.device.async_request( - self.device.api.get_energy - ) - except (CommandNotSupportedError, StorageError): - data["load_power"] = None + data["pwr"] = await device.async_request(device.api.check_power) + + if hasattr(device.api, "get_energy"): + data["power"] = await device.async_request(device.api.get_energy) + return data diff --git a/requirements_all.txt b/requirements_all.txt index 140273de629..b0fae5c32ee 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -381,7 +381,7 @@ boto3==1.9.252 bravia-tv==1.0.8 # homeassistant.components.broadlink -broadlink==0.16.0 +broadlink==0.17.0 # homeassistant.components.brother brother==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a2aaf83b113..96f6d6bb4ef 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -211,7 +211,7 @@ bond-api==0.1.11 bravia-tv==1.0.8 # homeassistant.components.broadlink -broadlink==0.16.0 +broadlink==0.17.0 # homeassistant.components.brother brother==0.2.1 diff --git a/tests/components/broadlink/__init__.py b/tests/components/broadlink/__init__.py index 7185c605f5c..780887551f2 100644 --- a/tests/components/broadlink/__init__.py +++ b/tests/components/broadlink/__init__.py @@ -12,7 +12,7 @@ BROADLINK_DEVICES = { "34ea34befc25", "RM mini 3", "Broadlink", - "RM2", + "RMMINI", 0x2737, 57, 8, @@ -22,7 +22,7 @@ BROADLINK_DEVICES = { "34ea34b43b5a", "RM mini 3", "Broadlink", - "RM4", + "RMMINIB", 0x5F36, 44017, 10, @@ -32,7 +32,7 @@ BROADLINK_DEVICES = { "34ea34b43d22", "RM pro", "Broadlink", - "RM2", + "RMPRO", 0x2787, 20025, 7, @@ -42,7 +42,7 @@ BROADLINK_DEVICES = { "34ea34c43f31", "RM4 pro", "Broadlink", - "RM4", + "RM4PRO", 0x6026, 52, 4, @@ -62,7 +62,7 @@ BROADLINK_DEVICES = { "34ea34b61d2c", "LB1", "Broadlink", - "SmartBulb", + "LB1", 0x504E, 57, 5, @@ -96,9 +96,6 @@ class BroadlinkDevice: with patch( "homeassistant.components.broadlink.device.blk.gendevice", return_value=mock_api, - ), patch( - "homeassistant.components.broadlink.updater.blk.discover", - return_value=[mock_api], ): await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() From e2f0b1842780c036ff952446b604e43039298cf8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 11 Mar 2021 19:35:24 -1000 Subject: [PATCH 313/831] Adjust insteon fan speed range to valid upper bound (#47765) --- homeassistant/components/insteon/fan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/insteon/fan.py b/homeassistant/components/insteon/fan.py index 596c254dc65..00ada3e9a58 100644 --- a/homeassistant/components/insteon/fan.py +++ b/homeassistant/components/insteon/fan.py @@ -17,7 +17,7 @@ from .const import SIGNAL_ADD_ENTITIES from .insteon_entity import InsteonEntity from .utils import async_add_insteon_entities -SPEED_RANGE = (0x00, 0xFF) # off is not included +SPEED_RANGE = (1, 255) # off is not included async def async_setup_entry(hass, config_entry, async_add_entities): From 7f60edd7e7360c55fac3c4ea884de3dbdada8ea0 Mon Sep 17 00:00:00 2001 From: James Nimmo Date: Fri, 12 Mar 2021 18:39:44 +1300 Subject: [PATCH 314/831] Bump pyIntesisHome to v1.7.6 (#47500) --- homeassistant/components/intesishome/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/intesishome/manifest.json b/homeassistant/components/intesishome/manifest.json index 4131811807a..d17014cdf0d 100644 --- a/homeassistant/components/intesishome/manifest.json +++ b/homeassistant/components/intesishome/manifest.json @@ -3,5 +3,5 @@ "name": "IntesisHome", "documentation": "https://www.home-assistant.io/integrations/intesishome", "codeowners": ["@jnimmo"], - "requirements": ["pyintesishome==1.7.5"] + "requirements": ["pyintesishome==1.7.6"] } diff --git a/requirements_all.txt b/requirements_all.txt index b0fae5c32ee..5f4e3a67e5b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1444,7 +1444,7 @@ pyicloud==0.10.2 pyinsteon==1.0.9 # homeassistant.components.intesishome -pyintesishome==1.7.5 +pyintesishome==1.7.6 # homeassistant.components.ipma pyipma==2.0.5 From 33c4eb343419a55908f6951d8a37d31e78efdc51 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 11 Mar 2021 19:52:04 -1000 Subject: [PATCH 315/831] Log the full exception when the recorder fails to setup (#47770) --- homeassistant/components/recorder/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index f8f95fd7ccc..12fb1b38c25 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -400,7 +400,7 @@ class Recorder(threading.Thread): migration.migrate_schema(self) self._setup_run() except Exception as err: # pylint: disable=broad-except - _LOGGER.error( + _LOGGER.exception( "Error during connection setup to %s: %s (retrying in %s seconds)", self.db_url, err, From f4b775b1254095ae4c37e234c9925acb9161d77d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 11 Mar 2021 20:05:03 -1000 Subject: [PATCH 316/831] Cleanup homekit and remove aid storage from hass.data (#47488) --- homeassistant/components/homekit/__init__.py | 104 ++++---- homeassistant/components/homekit/const.py | 1 - homeassistant/components/homekit/util.py | 4 +- tests/components/homekit/conftest.py | 4 +- tests/components/homekit/test_homekit.py | 235 ++++++++----------- tests/components/homekit/test_util.py | 4 +- 6 files changed, 147 insertions(+), 205 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 23492b12ccc..7c787c7e7be 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -34,7 +34,7 @@ from homeassistant.const import ( SERVICE_RELOAD, ) from homeassistant.core import CoreState, HomeAssistant, callback -from homeassistant.exceptions import ConfigEntryNotReady, Unauthorized +from homeassistant.exceptions import Unauthorized from homeassistant.helpers import device_registry, entity_registry import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import BASE_FILTER_SCHEMA, FILTER_SCHEMA @@ -58,7 +58,6 @@ from . import ( # noqa: F401 from .accessories import HomeBridge, HomeDriver, get_accessory from .aidmanager import AccessoryAidStorage from .const import ( - AID_STORAGE, ATTR_INTERGRATION, ATTR_MANUFACTURER, ATTR_MODEL, @@ -241,9 +240,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): port = conf[CONF_PORT] _LOGGER.debug("Begin setup HomeKit for %s", name) - aid_storage = AccessoryAidStorage(hass, entry.entry_id) - - await aid_storage.async_initialize() # ip_address and advertise_ip are yaml only ip_address = conf.get(CONF_IP_ADDRESS) advertise_ip = conf.get(CONF_ADVERTISE_IP) @@ -276,26 +272,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): entry.entry_id, entry.title, ) - zeroconf_instance = await zeroconf.async_get_instance(hass) - - # If the previous instance hasn't cleaned up yet - # we need to wait a bit - try: - await hass.async_add_executor_job(homekit.setup, zeroconf_instance) - except (OSError, AttributeError) as ex: - _LOGGER.warning( - "%s could not be setup because the local port %s is in use", name, port - ) - raise ConfigEntryNotReady from ex - - undo_listener = entry.add_update_listener(_async_update_listener) hass.data[DOMAIN][entry.entry_id] = { - AID_STORAGE: aid_storage, HOMEKIT: homekit, - UNDO_UPDATE_LISTENER: undo_listener, + UNDO_UPDATE_LISTENER: entry.add_update_listener(_async_update_listener), } + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, homekit.async_stop) + if hass.state == CoreState.running: await homekit.async_start() elif auto_start: @@ -463,6 +447,7 @@ class HomeKit: self._entry_id = entry_id self._entry_title = entry_title self._homekit_mode = homekit_mode + self.aid_storage = None self.status = STATUS_READY self.bridge = None @@ -470,7 +455,6 @@ class HomeKit: def setup(self, zeroconf_instance): """Set up bridge and accessory driver.""" - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop) ip_addr = self._ip_address or get_local_ip() persist_file = get_persist_fullpath_for_entry_id(self.hass, self._entry_id) @@ -503,10 +487,9 @@ class HomeKit: self.driver.config_changed() return - aid_storage = self.hass.data[DOMAIN][self._entry_id][AID_STORAGE] removed = [] for entity_id in entity_ids: - aid = aid_storage.get_or_allocate_aid_for_entity_id(entity_id) + aid = self.aid_storage.get_or_allocate_aid_for_entity_id(entity_id) if aid not in self.bridge.accessories: continue @@ -531,9 +514,6 @@ class HomeKit: def add_bridge_accessory(self, state): """Try adding accessory to bridge if configured beforehand.""" - if not self._filter(state.entity_id): - return - # The bridge itself counts as an accessory if len(self.bridge.accessories) + 1 >= MAX_DEVICES: _LOGGER.warning( @@ -555,9 +535,7 @@ class HomeKit: state.entity_id, ) - aid = self.hass.data[DOMAIN][self._entry_id][ - AID_STORAGE - ].get_or_allocate_aid_for_entity_id(state.entity_id) + aid = self.aid_storage.get_or_allocate_aid_for_entity_id(state.entity_id) conf = self._config.pop(state.entity_id, {}) # If an accessory cannot be created or added due to an exception # of any kind (usually in pyhap) it should not prevent @@ -578,15 +556,10 @@ class HomeKit: acc = self.bridge.accessories.pop(aid) return acc - async def async_start(self, *args): - """Start the accessory driver.""" - if self.status != STATUS_READY: - return - self.status = STATUS_WAIT - - ent_reg = await entity_registry.async_get_registry(self.hass) - dev_reg = await device_registry.async_get_registry(self.hass) - + async def async_configure_accessories(self): + """Configure accessories for the included states.""" + dev_reg = device_registry.async_get(self.hass) + ent_reg = entity_registry.async_get(self.hass) device_lookup = ent_reg.async_get_device_class_lookup( { (BINARY_SENSOR_DOMAIN, DEVICE_CLASS_BATTERY_CHARGING), @@ -597,10 +570,9 @@ class HomeKit: } ) - bridged_states = [] + entity_states = [] for state in self.hass.states.async_all(): entity_id = state.entity_id - if not self._filter(entity_id): continue @@ -611,17 +583,40 @@ class HomeKit: ) self._async_configure_linked_sensors(ent_reg_ent, device_lookup, state) - bridged_states.append(state) + entity_states.append(state) - self._async_register_bridge(dev_reg) - await self._async_start(bridged_states) + return entity_states + + async def async_start(self, *args): + """Load storage and start.""" + if self.status != STATUS_READY: + return + self.status = STATUS_WAIT + zc_instance = await zeroconf.async_get_instance(self.hass) + await self.hass.async_add_executor_job(self.setup, zc_instance) + self.aid_storage = AccessoryAidStorage(self.hass, self._entry_id) + await self.aid_storage.async_initialize() + await self._async_create_accessories() + self._async_register_bridge() _LOGGER.debug("Driver start for %s", self._name) await self.driver.async_start() self.status = STATUS_RUNNING + if self.driver.state.paired: + return + + show_setup_message( + self.hass, + self._entry_id, + accessory_friendly_name(self._entry_title, self.driver.accessory), + self.driver.state.pincode, + self.driver.accessory.xhm_uri(), + ) + @callback - def _async_register_bridge(self, dev_reg): + def _async_register_bridge(self): """Register the bridge as a device so homekit_controller and exclude it from discovery.""" + dev_reg = device_registry.async_get(self.hass) formatted_mac = device_registry.format_mac(self.driver.state.mac) # Connections and identifiers are both used here. # @@ -645,8 +640,9 @@ class HomeKit: identifiers={identifier}, connections={connection}, manufacturer=MANUFACTURER, - name=self._name, - model=f"Home Assistant HomeKit {hk_mode_name}", + name=accessory_friendly_name(self._entry_title, self.driver.accessory), + model=f"HomeKit {hk_mode_name}", + entry_type="service", ) @callback @@ -663,14 +659,13 @@ class HomeKit: for device_id in devices_to_purge: dev_reg.async_remove_device(device_id) - async def _async_start(self, entity_states): - """Start the accessory.""" + async def _async_create_accessories(self): + """Create the accessories.""" + entity_states = await self.async_configure_accessories() if self._homekit_mode == HOMEKIT_MODE_ACCESSORY: state = entity_states[0] conf = self._config.pop(state.entity_id, {}) acc = get_accessory(self.hass, self.driver, state, STANDALONE_AID, conf) - - self.driver.add_accessory(acc) else: self.bridge = HomeBridge(self.hass, self.driver, self._name) for state in entity_states: @@ -679,15 +674,6 @@ class HomeKit: await self.hass.async_add_executor_job(self.driver.add_accessory, acc) - if not self.driver.state.paired: - show_setup_message( - self.hass, - self._entry_id, - accessory_friendly_name(self._entry_title, self.driver.accessory), - self.driver.state.pincode, - self.driver.accessory.xhm_uri(), - ) - async def async_stop(self, *args): """Stop the accessory driver.""" if self.status != STATUS_RUNNING: diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index 840e9ebe607..ff45306b351 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -5,7 +5,6 @@ DEBOUNCE_TIMEOUT = 0.5 DEVICE_PRECISION_LEEWAY = 6 DOMAIN = "homekit" HOMEKIT_FILE = ".homekit.state" -AID_STORAGE = "homekit-aid-allocations" HOMEKIT_PAIRING_QR = "homekit-pairing-qr" HOMEKIT_PAIRING_QR_SECRET = "homekit-pairing-qr-secret" HOMEKIT = "homekit" diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 46b893bb96d..10550cf4a11 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -487,8 +487,10 @@ def accessory_friendly_name(hass_name, accessory): see both to identify the accessory. """ accessory_mdns_name = accessory.display_name - if hass_name.startswith(accessory_mdns_name): + if hass_name.casefold().startswith(accessory_mdns_name.casefold()): return hass_name + if accessory_mdns_name.casefold().startswith(hass_name.casefold()): + return accessory_mdns_name return f"{hass_name} ({accessory_mdns_name})" diff --git a/tests/components/homekit/conftest.py b/tests/components/homekit/conftest.py index 228b5f07837..469a0a7deb7 100644 --- a/tests/components/homekit/conftest.py +++ b/tests/components/homekit/conftest.py @@ -14,7 +14,9 @@ def hk_driver(loop): """Return a custom AccessoryDriver instance for HomeKit accessory init.""" with patch("pyhap.accessory_driver.Zeroconf"), patch( "pyhap.accessory_driver.AccessoryEncoder" - ), patch("pyhap.accessory_driver.HAPServer"), patch( + ), patch("pyhap.accessory_driver.HAPServer.async_stop"), patch( + "pyhap.accessory_driver.HAPServer.async_start" + ), patch( "pyhap.accessory_driver.AccessoryDriver.publish" ), patch( "pyhap.accessory_driver.AccessoryDriver.persist" diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 4d2fbfe951d..6d5f3faae95 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -1,4 +1,5 @@ """Tests for the HomeKit component.""" +import asyncio import os from typing import Dict from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch @@ -23,7 +24,6 @@ from homeassistant.components.homekit import ( ) from homeassistant.components.homekit.accessories import HomeBridge from homeassistant.components.homekit.const import ( - AID_STORAGE, BRIDGE_NAME, BRIDGE_SERIAL_NUMBER, CONF_AUTO_START, @@ -47,7 +47,6 @@ from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, EVENT_HOMEASSISTANT_STARTED, - EVENT_HOMEASSISTANT_STOP, PERCENTAGE, SERVICE_RELOAD, STATE_ON, @@ -98,8 +97,28 @@ def _mock_homekit(hass, entry, homekit_mode, entity_filter=None): ) +def _mock_homekit_bridge(hass, entry): + homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) + homekit.driver = MagicMock() + return homekit + + +def _mock_accessories(accessory_count): + accessories = {} + for idx in range(accessory_count + 1): + accessories[idx + 1000] = MagicMock(async_stop=AsyncMock()) + return accessories + + +def _mock_pyhap_bridge(): + return MagicMock( + aid=1, accessories=_mock_accessories(10), display_name="HomeKit Bridge" + ) + + async def test_setup_min(hass, mock_zeroconf): """Test async_setup with min config options.""" + await async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, data={CONF_NAME: BRIDGE_NAME, CONF_PORT: DEFAULT_PORT}, @@ -126,18 +145,16 @@ async def test_setup_min(hass, mock_zeroconf): entry.entry_id, entry.title, ) - assert mock_homekit().setup.called is True # Test auto start enabled - mock_homekit.reset_mock() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() - - mock_homekit().async_start.assert_called() + assert mock_homekit().async_start.called is True async def test_setup_auto_start_disabled(hass, mock_zeroconf): """Test async_setup with auto start disabled and test service calls.""" + await async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, data={CONF_NAME: "Test Name", CONF_PORT: 11111, CONF_IP_ADDRESS: "172.0.0.0"}, @@ -164,7 +181,6 @@ async def test_setup_auto_start_disabled(hass, mock_zeroconf): entry.entry_id, entry.title, ) - assert mock_homekit().setup.called is True # Test auto_start disabled homekit.reset_mock() @@ -237,9 +253,6 @@ async def test_homekit_setup(hass, hk_driver, mock_zeroconf): ) assert homekit.driver.safe_mode is False - # Test if stop listener is setup - assert hass.bus.async_listeners().get(EVENT_HOMEASSISTANT_STOP) == 1 - async def test_homekit_setup_ip_address(hass, hk_driver, mock_zeroconf): """Test setup with given IP address.""" @@ -321,40 +334,37 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver, mock_zeroconf): async def test_homekit_add_accessory(hass, mock_zeroconf): """Add accessory if config exists and get_acc returns an accessory.""" - + await async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} ) entry.add_to_hass(hass) - homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) - homekit.driver = "driver" - homekit.bridge = mock_bridge = Mock() - homekit.bridge.accessories = range(10) - homekit.async_start = AsyncMock() + homekit = _mock_homekit_bridge(hass, entry) + mock_acc = Mock(category="any") with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - mock_acc = Mock(category="any") + homekit.bridge = _mock_pyhap_bridge() with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc: mock_get_acc.side_effect = [None, mock_acc, None] state = State("light.demo", "on") homekit.add_bridge_accessory(state) mock_get_acc.assert_called_with(hass, ANY, ANY, 1403373688, {}) - assert not mock_bridge.add_accessory.called + assert not homekit.bridge.add_accessory.called state = State("demo.test", "on") homekit.add_bridge_accessory(state) mock_get_acc.assert_called_with(hass, ANY, ANY, 600325356, {}) - assert mock_bridge.add_accessory.called + assert homekit.bridge.add_accessory.called state = State("demo.test_2", "on") homekit.add_bridge_accessory(state) mock_get_acc.assert_called_with(hass, ANY, ANY, 1467253281, {}) - assert mock_bridge.add_accessory.called + assert homekit.bridge.add_accessory.called @pytest.mark.parametrize("acc_category", [CATEGORY_TELEVISION, CATEGORY_CAMERA]) @@ -362,29 +372,27 @@ async def test_homekit_warn_add_accessory_bridge( hass, acc_category, mock_zeroconf, caplog ): """Test we warn when adding cameras or tvs to a bridge.""" + await async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} ) entry.add_to_hass(hass) - homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) - homekit.driver = "driver" - homekit.bridge = mock_bridge = Mock() - homekit.bridge.accessories = range(10) - homekit.async_start = AsyncMock() + homekit = _mock_homekit_bridge(hass, entry) with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() mock_camera_acc = Mock(category=acc_category) + homekit.bridge = _mock_pyhap_bridge() with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc: mock_get_acc.side_effect = [None, mock_camera_acc, None] state = State("camera.test", "on") homekit.add_bridge_accessory(state) mock_get_acc.assert_called_with(hass, ANY, ANY, 1508819236, {}) - assert not mock_bridge.add_accessory.called + assert not homekit.bridge.add_accessory.called assert "accessory mode" in caplog.text @@ -396,12 +404,12 @@ async def test_homekit_remove_accessory(hass, mock_zeroconf): homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) homekit.driver = "driver" - homekit.bridge = mock_bridge = Mock() - mock_bridge.accessories = {"light.demo": "acc"} + homekit.bridge = _mock_pyhap_bridge() + homekit.bridge.accessories = {"light.demo": "acc"} acc = homekit.remove_bridge_accessory("light.demo") assert acc == "acc" - assert len(mock_bridge.accessories) == 0 + assert len(homekit.bridge.accessories) == 0 async def test_homekit_entity_filter(hass, mock_zeroconf): @@ -413,20 +421,14 @@ async def test_homekit_entity_filter(hass, mock_zeroconf): homekit.bridge = Mock() homekit.bridge.accessories = {} + hass.states.async_set("cover.test", "open") + hass.states.async_set("demo.test", "on") + hass.states.async_set("light.demo", "on") - with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc: - mock_get_acc.return_value = None - - homekit.add_bridge_accessory(State("cover.test", "open")) - assert mock_get_acc.called is True - mock_get_acc.reset_mock() - - homekit.add_bridge_accessory(State("demo.test", "on")) - assert mock_get_acc.called is True - mock_get_acc.reset_mock() - - homekit.add_bridge_accessory(State("light.demo", "light")) - assert mock_get_acc.called is False + filtered_states = await homekit.async_configure_accessories() + assert hass.states.get("cover.test") in filtered_states + assert hass.states.get("demo.test") in filtered_states + assert hass.states.get("light.demo") not in filtered_states async def test_homekit_entity_glob_filter(hass, mock_zeroconf): @@ -441,39 +443,29 @@ async def test_homekit_entity_glob_filter(hass, mock_zeroconf): homekit.bridge = Mock() homekit.bridge.accessories = {} - with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc: - mock_get_acc.return_value = None + hass.states.async_set("cover.test", "open") + hass.states.async_set("demo.test", "on") + hass.states.async_set("cover.excluded_test", "open") + hass.states.async_set("light.included_test", "on") - homekit.add_bridge_accessory(State("cover.test", "open")) - assert mock_get_acc.called is True - mock_get_acc.reset_mock() - - homekit.add_bridge_accessory(State("demo.test", "on")) - assert mock_get_acc.called is True - mock_get_acc.reset_mock() - - homekit.add_bridge_accessory(State("cover.excluded_test", "open")) - assert mock_get_acc.called is False - mock_get_acc.reset_mock() - - homekit.add_bridge_accessory(State("light.included_test", "light")) - assert mock_get_acc.called is True - mock_get_acc.reset_mock() + filtered_states = await homekit.async_configure_accessories() + assert hass.states.get("cover.test") in filtered_states + assert hass.states.get("demo.test") in filtered_states + assert hass.states.get("cover.excluded_test") not in filtered_states + assert hass.states.get("light.included_test") in filtered_states -async def test_homekit_start(hass, hk_driver, device_reg): +async def test_homekit_start(hass, hk_driver, mock_zeroconf, device_reg): """Test HomeKit start method.""" entry = await async_init_integration(hass) - pin = b"123-45-678" homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) homekit.bridge = Mock() homekit.bridge.accessories = [] homekit.driver = hk_driver - # pylint: disable=protected-access - homekit._filter = Mock(return_value=True) - homekit.driver.accessory = Accessory(hk_driver, "any") + acc = Accessory(hk_driver, "any") + homekit.driver.accessory = acc connection = (device_registry.CONNECTION_NETWORK_MAC, "AA:BB:CC:DD:EE:FF") bridge_with_wrong_mac = device_reg.async_get_or_create( @@ -491,8 +483,6 @@ async def test_homekit_start(hass, hk_driver, device_reg): with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch( f"{PATH_HOMEKIT}.show_setup_message" ) as mock_setup_msg, patch( - "pyhap.accessory_driver.AccessoryDriver.add_accessory" - ) as hk_driver_add_acc, patch( "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() @@ -500,9 +490,8 @@ async def test_homekit_start(hass, hk_driver, device_reg): await hass.async_block_till_done() mock_add_acc.assert_any_call(state) mock_setup_msg.assert_called_with( - hass, entry.entry_id, "Mock Title (any)", pin, ANY + hass, entry.entry_id, "Mock Title (Home Assistant Bridge)", ANY, ANY ) - hk_driver_add_acc.assert_called_with(homekit.bridge) assert hk_driver_start.called assert homekit.status == STATUS_RUNNING @@ -526,8 +515,6 @@ async def test_homekit_start(hass, hk_driver, device_reg): with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch( f"{PATH_HOMEKIT}.show_setup_message" ) as mock_setup_msg, patch( - "pyhap.accessory_driver.AccessoryDriver.add_accessory" - ) as hk_driver_add_acc, patch( "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() @@ -545,7 +532,6 @@ async def test_homekit_start(hass, hk_driver, device_reg): async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroconf): """Test HomeKit start method.""" - pin = b"123-45-678" entry = MockConfigEntry( domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} ) @@ -565,17 +551,14 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroc with patch(f"{PATH_HOMEKIT}.get_accessory", side_effect=Exception), patch( f"{PATH_HOMEKIT}.show_setup_message" ) as mock_setup_msg, patch( - "pyhap.accessory_driver.AccessoryDriver.add_accessory", - ) as hk_driver_add_acc, patch( "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() await hass.async_block_till_done() mock_setup_msg.assert_called_with( - hass, entry.entry_id, "Mock Title (any)", pin, ANY + hass, entry.entry_id, "Mock Title (Home Assistant Bridge)", ANY, ANY ) - hk_driver_add_acc.assert_called_with(homekit.bridge) assert hk_driver_start.called assert homekit.status == STATUS_RUNNING @@ -616,27 +599,23 @@ async def test_homekit_stop(hass): async def test_homekit_reset_accessories(hass, mock_zeroconf): """Test adding too many accessories to HomeKit.""" + await async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} ) entity_id = "light.demo" homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) - homekit.bridge = Mock() - homekit.bridge.accessories = {} - with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), patch( - f"{PATH_HOMEKIT}.HomeKit.setup" - ), patch("pyhap.accessory.Bridge.add_accessory") as mock_add_accessory, patch( + "pyhap.accessory.Bridge.add_accessory" + ) as mock_add_accessory, patch( "pyhap.accessory_driver.AccessoryDriver.config_changed" ) as hk_driver_config_changed, patch( "pyhap.accessory_driver.AccessoryDriver.async_start" ): await async_init_entry(hass, entry) - aid = hass.data[DOMAIN][entry.entry_id][ - AID_STORAGE - ].get_or_allocate_aid_for_entity_id(entity_id) + aid = homekit.aid_storage.get_or_allocate_aid_for_entity_id(entity_id) homekit.bridge.accessories = {aid: "acc"} homekit.status = STATUS_RUNNING @@ -675,10 +654,8 @@ async def test_homekit_too_many_accessories(hass, hk_driver, caplog, mock_zeroco hass.states.async_set("light.demo3", "on") with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( - "pyhap.accessory_driver.AccessoryDriver.add_accessory" - ), patch(f"{PATH_HOMEKIT}.show_setup_message"), patch( - f"{PATH_HOMEKIT}.HomeBridge", _mock_bridge - ): + f"{PATH_HOMEKIT}.show_setup_message" + ), patch(f"{PATH_HOMEKIT}.HomeBridge", _mock_bridge): await homekit.async_start() await hass.async_block_till_done() assert "would exceed" in caplog.text @@ -693,9 +670,7 @@ async def test_homekit_finds_linked_batteries( homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) homekit.driver = hk_driver - # pylint: disable=protected-access - homekit._filter = Mock(return_value=True) - homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge") + homekit.bridge = MagicMock() config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -735,17 +710,15 @@ async def test_homekit_finds_linked_batteries( ) hass.states.async_set(light.entity_id, STATE_ON) - with patch.object(homekit.bridge, "add_accessory"), patch( - f"{PATH_HOMEKIT}.show_setup_message" - ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" - ): + with patch(f"{PATH_HOMEKIT}.show_setup_message"), patch( + f"{PATH_HOMEKIT}.get_accessory" + ) as mock_get_acc, patch("pyhap.accessory_driver.AccessoryDriver.async_start"): await homekit.async_start() await hass.async_block_till_done() mock_get_acc.assert_called_with( hass, - hk_driver, + ANY, ANY, ANY, { @@ -766,8 +739,6 @@ async def test_homekit_async_get_integration_fails( homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) homekit.driver = hk_driver - # pylint: disable=protected-access - homekit._filter = Mock(return_value=True) homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge") config_entry = MockConfigEntry(domain="test", data={}) @@ -817,7 +788,7 @@ async def test_homekit_async_get_integration_fails( mock_get_acc.assert_called_with( hass, - hk_driver, + ANY, ANY, ANY, { @@ -832,6 +803,7 @@ async def test_homekit_async_get_integration_fails( async def test_yaml_updates_update_config_entry_for_name(hass, mock_zeroconf): """Test async_setup with imported config.""" + await async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, source=SOURCE_IMPORT, @@ -861,7 +833,6 @@ async def test_yaml_updates_update_config_entry_for_name(hass, mock_zeroconf): entry.entry_id, entry.title, ) - assert mock_homekit().setup.called is True # Test auto start enabled mock_homekit.reset_mock() @@ -871,20 +842,6 @@ async def test_yaml_updates_update_config_entry_for_name(hass, mock_zeroconf): mock_homekit().async_start.assert_called() -async def test_raise_config_entry_not_ready(hass, mock_zeroconf): - """Test async_setup when the port is not available.""" - entry = MockConfigEntry( - domain=DOMAIN, - data={CONF_NAME: BRIDGE_NAME, CONF_PORT: DEFAULT_PORT}, - options={}, - ) - entry.add_to_hass(hass) - - with patch(f"{PATH_HOMEKIT}.HomeKit.setup", side_effect=OSError): - assert not await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - async def test_homekit_uses_system_zeroconf(hass, hk_driver, mock_zeroconf): """Test HomeKit uses system zeroconf.""" entry = MockConfigEntry( @@ -917,13 +874,12 @@ async def test_homekit_ignored_missing_devices( hass, hk_driver, device_reg, entity_reg, mock_zeroconf ): """Test HomeKit handles a device in the entity registry but missing from the device registry.""" + await async_setup_component(hass, "persistent_notification", {}) entry = await async_init_integration(hass) homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) homekit.driver = hk_driver - # pylint: disable=protected-access - homekit._filter = Mock(return_value=True) - homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge") + homekit.bridge = _mock_pyhap_bridge() config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -952,25 +908,28 @@ async def test_homekit_ignored_missing_devices( light = entity_reg.async_get_or_create( "light", "powerwall", "demo", device_id=device_entry.id ) - + before_removal = entity_reg.entities.copy() # Delete the device to make sure we fallback # to using the platform device_reg.async_remove_device(device_entry.id) + # Wait for the entities to be removed + await asyncio.sleep(0) + await asyncio.sleep(0) + # Restore the registry + entity_reg.entities = before_removal hass.states.async_set(light.entity_id, STATE_ON) hass.states.async_set("light.two", STATE_ON) - with patch.object(homekit.bridge, "add_accessory"), patch( - f"{PATH_HOMEKIT}.show_setup_message" - ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" - ): + with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( + f"{PATH_HOMEKIT}.HomeBridge", return_value=homekit.bridge + ), patch("pyhap.accessory_driver.AccessoryDriver.async_start"): await homekit.async_start() - await hass.async_block_till_done() + await hass.async_block_till_done() mock_get_acc.assert_any_call( hass, - hk_driver, + ANY, ANY, ANY, { @@ -990,8 +949,6 @@ async def test_homekit_finds_linked_motion_sensors( homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) homekit.driver = hk_driver - # pylint: disable=protected-access - homekit._filter = Mock(return_value=True) homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge") config_entry = MockConfigEntry(domain="test", data={}) @@ -1032,7 +989,7 @@ async def test_homekit_finds_linked_motion_sensors( mock_get_acc.assert_called_with( hass, - hk_driver, + ANY, ANY, ANY, { @@ -1053,7 +1010,6 @@ async def test_homekit_finds_linked_humidity_sensors( homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) homekit.driver = hk_driver - homekit._filter = Mock(return_value=True) homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge") config_entry = MockConfigEntry(domain="test", data={}) @@ -1097,7 +1053,7 @@ async def test_homekit_finds_linked_humidity_sensors( mock_get_acc.assert_called_with( hass, - hk_driver, + ANY, ANY, ANY, { @@ -1111,6 +1067,7 @@ async def test_homekit_finds_linked_humidity_sensors( async def test_reload(hass, mock_zeroconf): """Test we can reload from yaml.""" + await async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, source=SOURCE_IMPORT, @@ -1121,7 +1078,6 @@ async def test_reload(hass, mock_zeroconf): with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit: mock_homekit.return_value = homekit = Mock() - type(homekit).async_start = AsyncMock() assert await async_setup_component( hass, "homekit", {"homekit": {CONF_NAME: "reloadable", CONF_PORT: 12345}} ) @@ -1140,7 +1096,6 @@ async def test_reload(hass, mock_zeroconf): entry.entry_id, entry.title, ) - assert mock_homekit().setup.called is True yaml_path = os.path.join( _get_fixtures_base_path(), "fixtures", @@ -1156,7 +1111,6 @@ async def test_reload(hass, mock_zeroconf): "pyhap.accessory_driver.AccessoryDriver.async_start" ): mock_homekit2.return_value = homekit = Mock() - type(homekit).async_start = AsyncMock() await hass.services.async_call( "homekit", SERVICE_RELOAD, @@ -1178,33 +1132,30 @@ async def test_reload(hass, mock_zeroconf): entry.entry_id, entry.title, ) - assert mock_homekit2().setup.called is True def _get_fixtures_base_path(): return os.path.dirname(os.path.dirname(os.path.dirname(__file__))) -async def test_homekit_start_in_accessory_mode(hass, hk_driver, device_reg): +async def test_homekit_start_in_accessory_mode( + hass, hk_driver, mock_zeroconf, device_reg +): """Test HomeKit start method in accessory mode.""" entry = await async_init_integration(hass) - pin = b"123-45-678" - homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_ACCESSORY) homekit.bridge = Mock() homekit.bridge.accessories = [] homekit.driver = hk_driver - # pylint: disable=protected-access - homekit._filter = Mock(return_value=True) homekit.driver.accessory = Accessory(hk_driver, "any") hass.states.async_set("light.demo", "on") with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.add_accessory" - ), patch(f"{PATH_HOMEKIT}.show_setup_message") as mock_setup_msg, patch( + f"{PATH_HOMEKIT}.show_setup_message" + ) as mock_setup_msg, patch( "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() @@ -1212,7 +1163,7 @@ async def test_homekit_start_in_accessory_mode(hass, hk_driver, device_reg): await hass.async_block_till_done() mock_add_acc.assert_not_called() mock_setup_msg.assert_called_with( - hass, entry.entry_id, "Mock Title (any)", pin, ANY + hass, entry.entry_id, "Mock Title (demo)", ANY, ANY ) assert hk_driver_start.called assert homekit.status == STATUS_RUNNING diff --git a/tests/components/homekit/test_util.py b/tests/components/homekit/test_util.py index 9b03d616002..c458d1dc4ef 100644 --- a/tests/components/homekit/test_util.py +++ b/tests/components/homekit/test_util.py @@ -294,5 +294,7 @@ async def test_accessory_friendly_name(): accessory = Mock() accessory.display_name = "same" - assert accessory_friendly_name("same", accessory) == "same" + assert accessory_friendly_name("Same", accessory) == "Same" assert accessory_friendly_name("hass title", accessory) == "hass title (same)" + accessory.display_name = "Hass title 123" + assert accessory_friendly_name("hass title", accessory) == "Hass title 123" From fa0c544bf572303e3d16b4dad9fac0d2ae29bce4 Mon Sep 17 00:00:00 2001 From: MatsNl <37705266+MatsNl@users.noreply.github.com> Date: Fri, 12 Mar 2021 07:15:45 +0100 Subject: [PATCH 317/831] Improve Atag integration and bump version to 0.3.5.3 (#47778) Co-authored-by: Paulus Schoutsen --- homeassistant/components/atag/__init__.py | 57 +++++++--------- homeassistant/components/atag/climate.py | 46 ++++++------- homeassistant/components/atag/config_flow.py | 16 ++--- homeassistant/components/atag/manifest.json | 2 +- homeassistant/components/atag/sensor.py | 17 ++--- homeassistant/components/atag/strings.json | 1 - homeassistant/components/atag/water_heater.py | 12 ++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/atag/__init__.py | 68 ++++++++++++------- tests/components/atag/test_climate.py | 49 ++++++------- tests/components/atag/test_config_flow.py | 53 ++++++--------- tests/components/atag/test_init.py | 17 +---- 13 files changed, 160 insertions(+), 182 deletions(-) diff --git a/homeassistant/components/atag/__init__.py b/homeassistant/components/atag/__init__.py index c4cffe1c41a..02aab5036b3 100644 --- a/homeassistant/components/atag/__init__.py +++ b/homeassistant/components/atag/__init__.py @@ -31,17 +31,34 @@ async def async_setup(hass: HomeAssistant, config): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Atag integration from a config entry.""" - session = async_get_clientsession(hass) - coordinator = AtagDataUpdateCoordinator(hass, session, entry) + async def _async_update_data(): + """Update data via library.""" + with async_timeout.timeout(20): + try: + await atag.update() + except AtagException as err: + raise UpdateFailed(err) from err + return atag + + atag = AtagOne( + session=async_get_clientsession(hass), **entry.data, device=entry.unique_id + ) + coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + name=DOMAIN.title(), + update_method=_async_update_data, + update_interval=timedelta(seconds=60), + ) + await coordinator.async_refresh() if not coordinator.last_update_success: raise ConfigEntryNotReady - hass.data.setdefault(DOMAIN, {}) - hass.data[DOMAIN][entry.entry_id] = coordinator + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator if entry.unique_id is None: - hass.config_entries.async_update_entry(entry, unique_id=coordinator.atag.id) + hass.config_entries.async_update_entry(entry, unique_id=atag.id) for platform in PLATFORMS: hass.async_create_task( @@ -51,28 +68,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): return True -class AtagDataUpdateCoordinator(DataUpdateCoordinator): - """Define an object to hold Atag data.""" - - def __init__(self, hass, session, entry): - """Initialize.""" - self.atag = AtagOne(session=session, **entry.data) - - super().__init__( - hass, _LOGGER, name=DOMAIN, update_interval=timedelta(seconds=30) - ) - - async def _async_update_data(self): - """Update data via library.""" - with async_timeout.timeout(20): - try: - if not await self.atag.update(): - raise UpdateFailed("No data received") - except AtagException as error: - raise UpdateFailed(error) from error - return self.atag.report - - async def async_unload_entry(hass, entry): """Unload Atag config entry.""" unload_ok = all( @@ -91,7 +86,7 @@ async def async_unload_entry(hass, entry): class AtagEntity(CoordinatorEntity): """Defines a base Atag entity.""" - def __init__(self, coordinator: AtagDataUpdateCoordinator, atag_id: str) -> None: + def __init__(self, coordinator: DataUpdateCoordinator, atag_id: str) -> None: """Initialize the Atag entity.""" super().__init__(coordinator) @@ -101,8 +96,8 @@ class AtagEntity(CoordinatorEntity): @property def device_info(self) -> dict: """Return info for device registry.""" - device = self.coordinator.atag.id - version = self.coordinator.atag.apiversion + device = self.coordinator.data.id + version = self.coordinator.data.apiversion return { "identifiers": {(DOMAIN, device)}, "name": "Atag Thermostat", @@ -119,4 +114,4 @@ class AtagEntity(CoordinatorEntity): @property def unique_id(self): """Return a unique ID to use for this entity.""" - return f"{self.coordinator.atag.id}-{self._id}" + return f"{self.coordinator.data.id}-{self._id}" diff --git a/homeassistant/components/atag/climate.py b/homeassistant/components/atag/climate.py index ad46fefe8c2..a2aa5cf16e4 100644 --- a/homeassistant/components/atag/climate.py +++ b/homeassistant/components/atag/climate.py @@ -16,16 +16,14 @@ from homeassistant.const import ATTR_TEMPERATURE from . import CLIMATE, DOMAIN, AtagEntity -PRESET_SCHEDULE = "Auto" -PRESET_MANUAL = "Manual" -PRESET_EXTEND = "Extend" -SUPPORT_PRESET = [ - PRESET_MANUAL, - PRESET_SCHEDULE, - PRESET_EXTEND, - PRESET_AWAY, - PRESET_BOOST, -] +PRESET_MAP = { + "Manual": "manual", + "Auto": "automatic", + "Extend": "extend", + PRESET_AWAY: "vacation", + PRESET_BOOST: "fireplace", +} +PRESET_INVERTED = {v: k for k, v in PRESET_MAP.items()} SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE HVAC_MODES = [HVAC_MODE_AUTO, HVAC_MODE_HEAT] @@ -47,8 +45,8 @@ class AtagThermostat(AtagEntity, ClimateEntity): @property def hvac_mode(self) -> Optional[str]: """Return hvac operation ie. heat, cool mode.""" - if self.coordinator.atag.climate.hvac_mode in HVAC_MODES: - return self.coordinator.atag.climate.hvac_mode + if self.coordinator.data.climate.hvac_mode in HVAC_MODES: + return self.coordinator.data.climate.hvac_mode return None @property @@ -59,46 +57,46 @@ class AtagThermostat(AtagEntity, ClimateEntity): @property def hvac_action(self) -> Optional[str]: """Return the current running hvac operation.""" - if self.coordinator.atag.climate.status: - return CURRENT_HVAC_HEAT - return CURRENT_HVAC_IDLE + is_active = self.coordinator.data.climate.status + return CURRENT_HVAC_HEAT if is_active else CURRENT_HVAC_IDLE @property - def temperature_unit(self): + def temperature_unit(self) -> Optional[str]: """Return the unit of measurement.""" - return self.coordinator.atag.climate.temp_unit + return self.coordinator.data.climate.temp_unit @property def current_temperature(self) -> Optional[float]: """Return the current temperature.""" - return self.coordinator.atag.climate.temperature + return self.coordinator.data.climate.temperature @property def target_temperature(self) -> Optional[float]: """Return the temperature we try to reach.""" - return self.coordinator.atag.climate.target_temperature + return self.coordinator.data.climate.target_temperature @property def preset_mode(self) -> Optional[str]: """Return the current preset mode, e.g., auto, manual, fireplace, extend, etc.""" - return self.coordinator.atag.climate.preset_mode + preset = self.coordinator.data.climate.preset_mode + return PRESET_INVERTED.get(preset) @property def preset_modes(self) -> Optional[List[str]]: """Return a list of available preset modes.""" - return SUPPORT_PRESET + return list(PRESET_MAP.keys()) async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" - await self.coordinator.atag.climate.set_temp(kwargs.get(ATTR_TEMPERATURE)) + await self.coordinator.data.climate.set_temp(kwargs.get(ATTR_TEMPERATURE)) self.async_write_ha_state() async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target hvac mode.""" - await self.coordinator.atag.climate.set_hvac_mode(hvac_mode) + await self.coordinator.data.climate.set_hvac_mode(hvac_mode) self.async_write_ha_state() async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" - await self.coordinator.atag.climate.set_preset_mode(preset_mode) + await self.coordinator.data.climate.set_preset_mode(PRESET_MAP[preset_mode]) self.async_write_ha_state() diff --git a/homeassistant/components/atag/config_flow.py b/homeassistant/components/atag/config_flow.py index 865159aa658..b1dcedd58dc 100644 --- a/homeassistant/components/atag/config_flow.py +++ b/homeassistant/components/atag/config_flow.py @@ -3,14 +3,13 @@ import pyatag import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_EMAIL, CONF_HOST, CONF_PORT +from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.helpers.aiohttp_client import async_get_clientsession from . import DOMAIN # pylint: disable=unused-import DATA_SCHEMA = { vol.Required(CONF_HOST): str, - vol.Optional(CONF_EMAIL): str, vol.Required(CONF_PORT, default=pyatag.const.DEFAULT_PORT): vol.Coerce(int), } @@ -26,15 +25,14 @@ class AtagConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if not user_input: return await self._show_form() - session = async_get_clientsession(self.hass) - try: - atag = pyatag.AtagOne(session=session, **user_input) - await atag.authorize() - await atag.update(force=True) - except pyatag.errors.Unauthorized: + atag = pyatag.AtagOne(session=async_get_clientsession(self.hass), **user_input) + try: + await atag.update() + + except pyatag.Unauthorized: return await self._show_form({"base": "unauthorized"}) - except pyatag.errors.AtagException: + except pyatag.AtagException: return await self._show_form({"base": "cannot_connect"}) await self.async_set_unique_id(atag.id) diff --git a/homeassistant/components/atag/manifest.json b/homeassistant/components/atag/manifest.json index 5e94afb06d3..1154a120f91 100644 --- a/homeassistant/components/atag/manifest.json +++ b/homeassistant/components/atag/manifest.json @@ -3,6 +3,6 @@ "name": "Atag", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/atag/", - "requirements": ["pyatag==0.3.4.4"], + "requirements": ["pyatag==0.3.5.3"], "codeowners": ["@MatsNL"] } diff --git a/homeassistant/components/atag/sensor.py b/homeassistant/components/atag/sensor.py index d6abe16ffdb..3123e245711 100644 --- a/homeassistant/components/atag/sensor.py +++ b/homeassistant/components/atag/sensor.py @@ -26,10 +26,7 @@ SENSORS = { async def async_setup_entry(hass, config_entry, async_add_entities): """Initialize sensor platform from config entry.""" coordinator = hass.data[DOMAIN][config_entry.entry_id] - entities = [] - for sensor in SENSORS: - entities.append(AtagSensor(coordinator, sensor)) - async_add_entities(entities) + async_add_entities([AtagSensor(coordinator, sensor) for sensor in SENSORS]) class AtagSensor(AtagEntity): @@ -43,32 +40,32 @@ class AtagSensor(AtagEntity): @property def state(self): """Return the state of the sensor.""" - return self.coordinator.data[self._id].state + return self.coordinator.data.report[self._id].state @property def icon(self): """Return icon.""" - return self.coordinator.data[self._id].icon + return self.coordinator.data.report[self._id].icon @property def device_class(self): """Return deviceclass.""" - if self.coordinator.data[self._id].sensorclass in [ + if self.coordinator.data.report[self._id].sensorclass in [ DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, ]: - return self.coordinator.data[self._id].sensorclass + return self.coordinator.data.report[self._id].sensorclass return None @property def unit_of_measurement(self): """Return measure.""" - if self.coordinator.data[self._id].measure in [ + if self.coordinator.data.report[self._id].measure in [ PRESSURE_BAR, TEMP_CELSIUS, TEMP_FAHRENHEIT, PERCENTAGE, TIME_HOURS, ]: - return self.coordinator.data[self._id].measure + return self.coordinator.data.report[self._id].measure return None diff --git a/homeassistant/components/atag/strings.json b/homeassistant/components/atag/strings.json index b06e9188b5b..39ed972524d 100644 --- a/homeassistant/components/atag/strings.json +++ b/homeassistant/components/atag/strings.json @@ -5,7 +5,6 @@ "title": "Connect to the device", "data": { "host": "[%key:common::config_flow::data::host%]", - "email": "[%key:common::config_flow::data::email%]", "port": "[%key:common::config_flow::data::port%]" } } diff --git a/homeassistant/components/atag/water_heater.py b/homeassistant/components/atag/water_heater.py index f9c2a4625bb..dac56edf89d 100644 --- a/homeassistant/components/atag/water_heater.py +++ b/homeassistant/components/atag/water_heater.py @@ -35,12 +35,12 @@ class AtagWaterHeater(AtagEntity, WaterHeaterEntity): @property def current_temperature(self): """Return the current temperature.""" - return self.coordinator.atag.dhw.temperature + return self.coordinator.data.dhw.temperature @property def current_operation(self): """Return current operation.""" - operation = self.coordinator.atag.dhw.current_operation + operation = self.coordinator.data.dhw.current_operation return operation if operation in self.operation_list else STATE_OFF @property @@ -50,20 +50,20 @@ class AtagWaterHeater(AtagEntity, WaterHeaterEntity): async def async_set_temperature(self, **kwargs): """Set new target temperature.""" - if await self.coordinator.atag.dhw.set_temp(kwargs.get(ATTR_TEMPERATURE)): + if await self.coordinator.data.dhw.set_temp(kwargs.get(ATTR_TEMPERATURE)): self.async_write_ha_state() @property def target_temperature(self): """Return the setpoint if water demand, otherwise return base temp (comfort level).""" - return self.coordinator.atag.dhw.target_temperature + return self.coordinator.data.dhw.target_temperature @property def max_temp(self): """Return the maximum temperature.""" - return self.coordinator.atag.dhw.max_temp + return self.coordinator.data.dhw.max_temp @property def min_temp(self): """Return the minimum temperature.""" - return self.coordinator.atag.dhw.min_temp + return self.coordinator.data.dhw.min_temp diff --git a/requirements_all.txt b/requirements_all.txt index 5f4e3a67e5b..2a532ee9ae8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1269,7 +1269,7 @@ pyalmond==0.0.2 pyarlo==0.2.4 # homeassistant.components.atag -pyatag==0.3.4.4 +pyatag==0.3.5.3 # homeassistant.components.netatmo pyatmo==4.2.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 96f6d6bb4ef..1dab1e8837b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -667,7 +667,7 @@ pyalmond==0.0.2 pyarlo==0.2.4 # homeassistant.components.atag -pyatag==0.3.4.4 +pyatag==0.3.5.3 # homeassistant.components.netatmo pyatmo==4.2.2 diff --git a/tests/components/atag/__init__.py b/tests/components/atag/__init__.py index 52d53ee9948..c41632b9715 100644 --- a/tests/components/atag/__init__.py +++ b/tests/components/atag/__init__.py @@ -1,7 +1,7 @@ """Tests for the Atag integration.""" -from homeassistant.components.atag import DOMAIN -from homeassistant.const import CONF_EMAIL, CONF_HOST, CONF_PORT, CONTENT_TYPE_JSON +from homeassistant.components.atag import DOMAIN, AtagException +from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry @@ -9,12 +9,15 @@ from tests.test_util.aiohttp import AiohttpClientMocker USER_INPUT = { CONF_HOST: "127.0.0.1", - CONF_EMAIL: "atag@domain.com", CONF_PORT: 10000, } UID = "xxxx-xxxx-xxxx_xx-xx-xxx-xxx" -PAIR_REPLY = {"pair_reply": {"status": {"device_id": UID}, "acc_status": 2}} -UPDATE_REPLY = {"update_reply": {"status": {"device_id": UID}, "acc_status": 2}} +AUTHORIZED = 2 +UNAUTHORIZED = 3 +PAIR_REPLY = {"pair_reply": {"status": {"device_id": UID}, "acc_status": AUTHORIZED}} +UPDATE_REPLY = { + "update_reply": {"status": {"device_id": UID}, "acc_status": AUTHORIZED} +} RECEIVE_REPLY = { "retrieve_reply": { "status": {"device_id": UID}, @@ -46,35 +49,52 @@ RECEIVE_REPLY = { "dhw_max_set": 65, "dhw_min_set": 40, }, - "acc_status": 2, + "acc_status": AUTHORIZED, } } +def mock_connection( + aioclient_mock: AiohttpClientMocker, authorized=True, conn_error=False +) -> None: + """Mock the requests to Atag endpoint.""" + if conn_error: + aioclient_mock.post( + "http://127.0.0.1:10000/pair", + exc=AtagException, + ) + aioclient_mock.post( + "http://127.0.0.1:10000/retrieve", + exc=AtagException, + ) + return + PAIR_REPLY["pair_reply"].update( + {"acc_status": AUTHORIZED if authorized else UNAUTHORIZED} + ) + RECEIVE_REPLY["retrieve_reply"].update( + {"acc_status": AUTHORIZED if authorized else UNAUTHORIZED} + ) + aioclient_mock.post( + "http://127.0.0.1:10000/retrieve", + json=RECEIVE_REPLY, + ) + aioclient_mock.post( + "http://127.0.0.1:10000/update", + json=UPDATE_REPLY, + ) + aioclient_mock.post( + "http://127.0.0.1:10000/pair", + json=PAIR_REPLY, + ) + + async def init_integration( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, - rgbw: bool = False, skip_setup: bool = False, ) -> MockConfigEntry: """Set up the Atag integration in Home Assistant.""" - - aioclient_mock.post( - "http://127.0.0.1:10000/retrieve", - json=RECEIVE_REPLY, - headers={"Content-Type": CONTENT_TYPE_JSON}, - ) - aioclient_mock.post( - "http://127.0.0.1:10000/update", - json=UPDATE_REPLY, - headers={"Content-Type": CONTENT_TYPE_JSON}, - ) - aioclient_mock.post( - "http://127.0.0.1:10000/pair", - json=PAIR_REPLY, - headers={"Content-Type": CONTENT_TYPE_JSON}, - ) - + mock_connection(aioclient_mock) entry = MockConfigEntry(domain=DOMAIN, data=USER_INPUT) entry.add_to_hass(hass) diff --git a/tests/components/atag/test_climate.py b/tests/components/atag/test_climate.py index e263ade7c75..3c9a9c3f820 100644 --- a/tests/components/atag/test_climate.py +++ b/tests/components/atag/test_climate.py @@ -1,7 +1,7 @@ """Tests for the Atag climate platform.""" from unittest.mock import PropertyMock, patch -from homeassistant.components.atag import CLIMATE, DOMAIN +from homeassistant.components.atag.climate import CLIMATE, DOMAIN, PRESET_MAP from homeassistant.components.climate import ( ATTR_HVAC_ACTION, ATTR_HVAC_MODE, @@ -11,11 +11,8 @@ from homeassistant.components.climate import ( SERVICE_SET_PRESET_MODE, SERVICE_SET_TEMPERATURE, ) -from homeassistant.components.climate.const import CURRENT_HVAC_HEAT, PRESET_AWAY -from homeassistant.components.homeassistant import ( - DOMAIN as HA_DOMAIN, - SERVICE_UPDATE_ENTITY, -) +from homeassistant.components.climate.const import CURRENT_HVAC_IDLE, PRESET_AWAY +from homeassistant.components.homeassistant import DOMAIN as HA_DOMAIN from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_UNKNOWN from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -31,17 +28,13 @@ async def test_climate( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test the creation and values of Atag climate device.""" - with patch("pyatag.entities.Climate.status"): - entry = await init_integration(hass, aioclient_mock) - registry = er.async_get(hass) + await init_integration(hass, aioclient_mock) + entity_registry = er.async_get(hass) - assert registry.async_is_registered(CLIMATE_ID) - entry = registry.async_get(CLIMATE_ID) - assert entry.unique_id == f"{UID}-{CLIMATE}" - assert ( - hass.states.get(CLIMATE_ID).attributes[ATTR_HVAC_ACTION] - == CURRENT_HVAC_HEAT - ) + assert entity_registry.async_is_registered(CLIMATE_ID) + entity = entity_registry.async_get(CLIMATE_ID) + assert entity.unique_id == f"{UID}-{CLIMATE}" + assert hass.states.get(CLIMATE_ID).attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE async def test_setting_climate( @@ -67,7 +60,7 @@ async def test_setting_climate( blocking=True, ) await hass.async_block_till_done() - mock_set_preset.assert_called_once_with(PRESET_AWAY) + mock_set_preset.assert_called_once_with(PRESET_MAP[PRESET_AWAY]) with patch("pyatag.entities.Climate.set_hvac_mode") as mock_set_hvac: await hass.services.async_call( @@ -93,18 +86,18 @@ async def test_incorrect_modes( assert hass.states.get(CLIMATE_ID).state == STATE_UNKNOWN -async def test_update_service( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +async def test_update_failed( + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, ) -> None: - """Test the updater service is called.""" - await init_integration(hass, aioclient_mock) + """Test data is not destroyed on update failure.""" + entry = await init_integration(hass, aioclient_mock) await async_setup_component(hass, HA_DOMAIN, {}) - with patch("pyatag.AtagOne.update") as updater: - await hass.services.async_call( - HA_DOMAIN, - SERVICE_UPDATE_ENTITY, - {ATTR_ENTITY_ID: CLIMATE_ID}, - blocking=True, - ) + assert hass.states.get(CLIMATE_ID).state == HVAC_MODE_HEAT + coordinator = hass.data[DOMAIN][entry.entry_id] + with patch("pyatag.AtagOne.update", side_effect=TimeoutError) as updater: + await coordinator.async_refresh() await hass.async_block_till_done() updater.assert_called_once() + assert not coordinator.last_update_success + assert coordinator.data.id == UID diff --git a/tests/components/atag/test_config_flow.py b/tests/components/atag/test_config_flow.py index 81375792c71..a92e73ae18e 100644 --- a/tests/components/atag/test_config_flow.py +++ b/tests/components/atag/test_config_flow.py @@ -1,24 +1,18 @@ """Tests for the Atag config flow.""" from unittest.mock import PropertyMock, patch -from pyatag import errors - from homeassistant import config_entries, data_entry_flow from homeassistant.components.atag import DOMAIN from homeassistant.core import HomeAssistant -from tests.components.atag import ( - PAIR_REPLY, - RECEIVE_REPLY, - UID, - USER_INPUT, - init_integration, -) +from . import UID, USER_INPUT, init_integration, mock_connection + from tests.test_util.aiohttp import AiohttpClientMocker -async def test_show_form(hass): +async def test_show_form(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker): """Test that the form is served with no input.""" + mock_connection(aioclient_mock) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -48,28 +42,30 @@ async def test_adding_second_device( assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY -async def test_connection_error(hass): +async def test_connection_error( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +): """Test we show user form on Atag connection error.""" - with patch("pyatag.AtagOne.authorize", side_effect=errors.AtagException()): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_USER}, - data=USER_INPUT, - ) + mock_connection(aioclient_mock, conn_error=True) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + data=USER_INPUT, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" assert result["errors"] == {"base": "cannot_connect"} -async def test_unauthorized(hass): +async def test_unauthorized(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker): """Test we show correct form when Unauthorized error is raised.""" - with patch("pyatag.AtagOne.authorize", side_effect=errors.Unauthorized()): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_USER}, - data=USER_INPUT, - ) + mock_connection(aioclient_mock, authorized=False) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + data=USER_INPUT, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" assert result["errors"] == {"base": "unauthorized"} @@ -79,14 +75,7 @@ async def test_full_flow_implementation( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test registering an integration and finishing flow works.""" - aioclient_mock.post( - "http://127.0.0.1:10000/pair", - json=PAIR_REPLY, - ) - aioclient_mock.post( - "http://127.0.0.1:10000/retrieve", - json=RECEIVE_REPLY, - ) + mock_connection(aioclient_mock) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, diff --git a/tests/components/atag/test_init.py b/tests/components/atag/test_init.py index b86de8a8be5..7b7f3c1e33a 100644 --- a/tests/components/atag/test_init.py +++ b/tests/components/atag/test_init.py @@ -1,13 +1,11 @@ """Tests for the ATAG integration.""" -from unittest.mock import patch - -import aiohttp from homeassistant.components.atag import DOMAIN from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY from homeassistant.core import HomeAssistant -from tests.components.atag import init_integration +from . import init_integration, mock_connection + from tests.test_util.aiohttp import AiohttpClientMocker @@ -15,20 +13,11 @@ async def test_config_entry_not_ready( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test configuration entry not ready on library error.""" - aioclient_mock.post("http://127.0.0.1:10000/retrieve", exc=aiohttp.ClientError) + mock_connection(aioclient_mock, conn_error=True) entry = await init_integration(hass, aioclient_mock) assert entry.state == ENTRY_STATE_SETUP_RETRY -async def test_config_entry_empty_reply( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker -) -> None: - """Test configuration entry not ready when library returns False.""" - with patch("pyatag.AtagOne.update", return_value=False): - entry = await init_integration(hass, aioclient_mock) - assert entry.state == ENTRY_STATE_SETUP_RETRY - - async def test_unload_config_entry( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: From 2a22c54fcb5ef299e4d274a537922c14115ce447 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 11 Mar 2021 23:12:26 -0800 Subject: [PATCH 318/831] Store the correct context in the trace (#47785) --- homeassistant/components/automation/__init__.py | 10 +++++----- tests/components/automation/test_websocket_api.py | 5 ++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 24121359ac0..862a664976b 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -390,8 +390,12 @@ class AutomationEntity(ToggleEntity, RestoreEntity): reason = f' by {run_variables["trigger"]["description"]}' self._logger.debug("Automation triggered%s", reason) + # Create a new context referring to the old context. + parent_id = None if context is None else context.id + trigger_context = Context(parent_id=parent_id) + with trace_automation( - self.hass, self.unique_id, self._raw_config, context + self.hass, self.unique_id, self._raw_config, trigger_context ) as automation_trace: if self._variables: try: @@ -421,10 +425,6 @@ class AutomationEntity(ToggleEntity, RestoreEntity): # Prepare tracing the execution of the automation's actions automation_trace.set_action_trace(trace_get()) - # Create a new context referring to the old context. - parent_id = None if context is None else context.id - trigger_context = Context(parent_id=parent_id) - self.async_set_context(trigger_context) event_data = { ATTR_NAME: self._name, diff --git a/tests/components/automation/test_websocket_api.py b/tests/components/automation/test_websocket_api.py index 106f687f4ee..99b9540b06e 100644 --- a/tests/components/automation/test_websocket_api.py +++ b/tests/components/automation/test_websocket_api.py @@ -3,6 +3,7 @@ from unittest.mock import patch from homeassistant.bootstrap import async_setup_component from homeassistant.components import automation, config +from homeassistant.core import Context from tests.common import assert_lists_same from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @@ -52,7 +53,8 @@ async def test_get_automation_trace(hass, hass_ws_client): client = await hass_ws_client() # Trigger "sun" automation - hass.bus.async_fire("test_event") + context = Context() + hass.bus.async_fire("test_event", context=context) await hass.async_block_till_done() # List traces @@ -73,6 +75,7 @@ async def test_get_automation_trace(hass, hass_ws_client): response = await client.receive_json() assert response["success"] trace = response["result"] + assert trace["context"]["parent_id"] == context.id assert len(trace["action_trace"]) == 1 assert len(trace["action_trace"]["action/0"]) == 1 assert trace["action_trace"]["action/0"][0]["error"] From ff94e920e4e436c874f3f8de115792eaeac99b67 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 11 Mar 2021 23:18:09 -0800 Subject: [PATCH 319/831] Do not use AsyncTrackStates (#47255) --- homeassistant/components/api/__init__.py | 24 +++++++++++------- homeassistant/helpers/state.py | 10 +++++++- tests/components/api/test_init.py | 20 +++++++++------ tests/helpers/conftest.py | 31 ++++++++++++++++++++++++ tests/helpers/test_frame.py | 27 +++------------------ tests/helpers/test_state.py | 4 +-- 6 files changed, 73 insertions(+), 43 deletions(-) create mode 100644 tests/helpers/conftest.py diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index e40a9332c38..47c6518f7b1 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -37,7 +37,6 @@ from homeassistant.helpers import template from homeassistant.helpers.json import JSONEncoder from homeassistant.helpers.network import NoURLAvailableError, get_url from homeassistant.helpers.service import async_get_all_descriptions -from homeassistant.helpers.state import AsyncTrackStates from homeassistant.helpers.system_info import async_get_system_info _LOGGER = logging.getLogger(__name__) @@ -367,20 +366,27 @@ class APIDomainServicesView(HomeAssistantView): Returns a list of changed states. """ - hass = request.app["hass"] + hass: ha.HomeAssistant = request.app["hass"] body = await request.text() try: data = json.loads(body) if body else None except ValueError: return self.json_message("Data should be valid JSON.", HTTP_BAD_REQUEST) - with AsyncTrackStates(hass) as changed_states: - try: - await hass.services.async_call( - domain, service, data, blocking=True, context=self.context(request) - ) - except (vol.Invalid, ServiceNotFound) as ex: - raise HTTPBadRequest() from ex + context = self.context(request) + + try: + await hass.services.async_call( + domain, service, data, blocking=True, context=context + ) + except (vol.Invalid, ServiceNotFound) as ex: + raise HTTPBadRequest() from ex + + changed_states = [] + + for state in hass.states.async_all(): + if state.context is context: + changed_states.append(state) return self.json(changed_states) diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 87112cd9133..eda026c8563 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -22,6 +22,7 @@ from homeassistant.core import Context, State from homeassistant.loader import IntegrationNotFound, async_get_integration, bind_hass import homeassistant.util.dt as dt_util +from .frame import report from .typing import HomeAssistantType _LOGGER = logging.getLogger(__name__) @@ -35,6 +36,9 @@ class AsyncTrackStates: when with-block is exited. Must be run within the event loop. + + Deprecated. Remove after June 2021. + Warning added via `get_changed_since`. """ def __init__(self, hass: HomeAssistantType) -> None: @@ -61,7 +65,11 @@ class AsyncTrackStates: def get_changed_since( states: Iterable[State], utc_point_in_time: dt.datetime ) -> List[State]: - """Return list of states that have been changed since utc_point_in_time.""" + """Return list of states that have been changed since utc_point_in_time. + + Deprecated. Remove after June 2021. + """ + report("uses deprecated `get_changed_since`") return [state for state in states if state.last_updated >= utc_point_in_time] diff --git a/tests/components/api/test_init.py b/tests/components/api/test_init.py index 678a8096af5..ffda908a29b 100644 --- a/tests/components/api/test_init.py +++ b/tests/components/api/test_init.py @@ -270,7 +270,6 @@ async def test_api_call_service_no_data(hass, mock_api_client): async def test_api_call_service_with_data(hass, mock_api_client): """Test if the API allows us to call a service.""" - test_value = [] @ha.callback def listener(service_call): @@ -278,17 +277,24 @@ async def test_api_call_service_with_data(hass, mock_api_client): Also test if our data came through. """ - if "test" in service_call.data: - test_value.append(1) + hass.states.async_set( + "test.data", + "on", + {"data": service_call.data["test"]}, + context=service_call.context, + ) hass.services.async_register("test_domain", "test_service", listener) - await mock_api_client.post( + resp = await mock_api_client.post( "/api/services/test_domain/test_service", json={"test": 1} ) - - await hass.async_block_till_done() - assert len(test_value) == 1 + data = await resp.json() + assert len(data) == 1 + state = data[0] + assert state["entity_id"] == "test.data" + assert state["state"] == "on" + assert state["attributes"] == {"data": 1} async def test_api_template(hass, mock_api_client): diff --git a/tests/helpers/conftest.py b/tests/helpers/conftest.py new file mode 100644 index 00000000000..4b3b9bf465d --- /dev/null +++ b/tests/helpers/conftest.py @@ -0,0 +1,31 @@ +"""Fixtures for helpers.""" +from unittest.mock import Mock, patch + +import pytest + + +@pytest.fixture +def mock_integration_frame(): + """Mock as if we're calling code from inside an integration.""" + correct_frame = Mock( + filename="/home/paulus/homeassistant/components/hue/light.py", + lineno="23", + line="self.light.is_on", + ) + with patch( + "homeassistant.helpers.frame.extract_stack", + return_value=[ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + correct_frame, + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ], + ): + yield correct_frame diff --git a/tests/helpers/test_frame.py b/tests/helpers/test_frame.py index 7fc46b3699d..b198a16adb1 100644 --- a/tests/helpers/test_frame.py +++ b/tests/helpers/test_frame.py @@ -6,34 +6,13 @@ import pytest from homeassistant.helpers import frame -async def test_extract_frame_integration(caplog): +async def test_extract_frame_integration(caplog, mock_integration_frame): """Test extracting the current frame from integration context.""" - correct_frame = Mock( - filename="/home/paulus/homeassistant/components/hue/light.py", - lineno="23", - line="self.light.is_on", - ) - with patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - correct_frame, - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ], - ): - found_frame, integration, path = frame.get_integration_frame() + found_frame, integration, path = frame.get_integration_frame() assert integration == "hue" assert path == "homeassistant/components/" - assert found_frame == correct_frame + assert found_frame == mock_integration_frame async def test_extract_frame_integration_with_excluded_intergration(caplog): diff --git a/tests/helpers/test_state.py b/tests/helpers/test_state.py index 89b0f3c6850..aa1d148fbd7 100644 --- a/tests/helpers/test_state.py +++ b/tests/helpers/test_state.py @@ -25,7 +25,7 @@ from homeassistant.util import dt as dt_util from tests.common import async_mock_service -async def test_async_track_states(hass): +async def test_async_track_states(hass, mock_integration_frame): """Test AsyncTrackStates context manager.""" point1 = dt_util.utcnow() point2 = point1 + timedelta(seconds=5) @@ -82,7 +82,7 @@ async def test_call_to_component(hass): ) -async def test_get_changed_since(hass): +async def test_get_changed_since(hass, mock_integration_frame): """Test get_changed_since.""" point1 = dt_util.utcnow() point2 = point1 + timedelta(seconds=5) From 6610e821bcb3217a38abec2d11ccc4d2c2191b82 Mon Sep 17 00:00:00 2001 From: Guido Schmitz Date: Fri, 12 Mar 2021 11:37:39 +0100 Subject: [PATCH 320/831] Bump devolo_home_control_api to 0.17.0 (#47790) --- homeassistant/components/devolo_home_control/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/devolo_home_control/manifest.json b/homeassistant/components/devolo_home_control/manifest.json index 0ffa991493c..1ae8e9bbb59 100644 --- a/homeassistant/components/devolo_home_control/manifest.json +++ b/homeassistant/components/devolo_home_control/manifest.json @@ -2,7 +2,7 @@ "domain": "devolo_home_control", "name": "devolo Home Control", "documentation": "https://www.home-assistant.io/integrations/devolo_home_control", - "requirements": ["devolo-home-control-api==0.16.0"], + "requirements": ["devolo-home-control-api==0.17.0"], "after_dependencies": ["zeroconf"], "config_flow": true, "codeowners": ["@2Fake", "@Shutgun"], diff --git a/requirements_all.txt b/requirements_all.txt index 2a532ee9ae8..bcea3b679b8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -476,7 +476,7 @@ deluge-client==1.7.1 denonavr==0.9.10 # homeassistant.components.devolo_home_control -devolo-home-control-api==0.16.0 +devolo-home-control-api==0.17.0 # homeassistant.components.directv directv==0.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1dab1e8837b..1a9c1bd7b9d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -258,7 +258,7 @@ defusedxml==0.6.0 denonavr==0.9.10 # homeassistant.components.devolo_home_control -devolo-home-control-api==0.16.0 +devolo-home-control-api==0.17.0 # homeassistant.components.directv directv==0.4.0 From 40c28aa38a883dac47eab1e9a6e2f539f3d5854f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 12 Mar 2021 00:41:01 -1000 Subject: [PATCH 321/831] Ensure homekit reset accessory service can target any entity (#47787) --- homeassistant/components/homekit/services.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/homekit/services.yaml b/homeassistant/components/homekit/services.yaml index 6f9c005ed64..a6b09a80e7f 100644 --- a/homeassistant/components/homekit/services.yaml +++ b/homeassistant/components/homekit/services.yaml @@ -9,3 +9,5 @@ reload: reset_accessory: description: Reset a HomeKit accessory target: + entity: {} + From 514516baccd8fe6a1a566db633247c2eaef51874 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 12 Mar 2021 13:52:46 +0100 Subject: [PATCH 322/831] Remove unused COVER_SCHEMA from gogogate2 cover (#47170) --- homeassistant/components/gogogate2/cover.py | 26 ++++++--------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index 053c35c171a..2d97edbfe43 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -1,8 +1,8 @@ """Support for Gogogate2 garage Doors.""" +import logging from typing import Callable, List, Optional from gogogate2_api.common import AbstractDoor, DoorStatus, get_configured_doors -import voluptuous as vol from homeassistant.components.cover import ( DEVICE_CLASS_GARAGE, @@ -12,14 +12,7 @@ from homeassistant.components.cover import ( CoverEntity, ) from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import ( - CONF_DEVICE, - CONF_IP_ADDRESS, - CONF_PASSWORD, - CONF_USERNAME, -) from homeassistant.core import HomeAssistant -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from .common import ( @@ -27,24 +20,19 @@ from .common import ( GoGoGate2Entity, get_data_update_coordinator, ) -from .const import DEVICE_TYPE_GOGOGATE2, DEVICE_TYPE_ISMARTGATE, DOMAIN +from .const import DOMAIN -COVER_SCHEMA = vol.Schema( - { - vol.Required(CONF_IP_ADDRESS): cv.string, - vol.Required(CONF_DEVICE, default=DEVICE_TYPE_GOGOGATE2): vol.In( - (DEVICE_TYPE_GOGOGATE2, DEVICE_TYPE_ISMARTGATE) - ), - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string, - } -) +_LOGGER = logging.getLogger(__name__) async def async_setup_platform( hass: HomeAssistant, config: dict, add_entities: Callable, discovery_info=None ) -> None: """Convert old style file configs to new style configs.""" + _LOGGER.warning( + "Loading gogogate2 via platform config is deprecated. The configuration" + " has been migrated to a config entry and can be safely removed." + ) hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=config From bf5028df2bf5d69c97c3f697e32e8563209e5c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20St=C3=A5hl?= Date: Fri, 12 Mar 2021 16:19:14 +0100 Subject: [PATCH 323/831] Bump pyatv to 0.7.7 (#47798) * Bump pyatv to 0.7.7 * Change to assume name always exist in config entry --- homeassistant/components/apple_tv/__init__.py | 7 ++++--- homeassistant/components/apple_tv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index 75c4d6ccc8a..6460a501992 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -181,7 +181,7 @@ class AppleTVManager: This is a callback function from pyatv.interface.DeviceListener. """ _LOGGER.warning( - 'Connection lost to Apple TV "%s"', self.config_entry.data.get(CONF_NAME) + 'Connection lost to Apple TV "%s"', self.config_entry.data[CONF_NAME] ) self._connection_was_lost = True self._handle_disconnect() @@ -268,7 +268,7 @@ class AppleTVManager: """Problem to authenticate occurred that needs intervention.""" _LOGGER.debug("Authentication error, reconfigure integration") - name = self.config_entry.data.get(CONF_NAME) + name = self.config_entry.data[CONF_NAME] identifier = self.config_entry.unique_id self.hass.components.persistent_notification.create( @@ -337,7 +337,8 @@ class AppleTVManager: self._connection_attempts = 0 if self._connection_was_lost: _LOGGER.info( - 'Connection was re-established to Apple TV "%s"', self.atv.service.name + 'Connection was re-established to Apple TV "%s"', + self.config_entry.data[CONF_NAME], ) self._connection_was_lost = False diff --git a/homeassistant/components/apple_tv/manifest.json b/homeassistant/components/apple_tv/manifest.json index 66ae2864dc4..a60c5db3a06 100644 --- a/homeassistant/components/apple_tv/manifest.json +++ b/homeassistant/components/apple_tv/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/apple_tv", "requirements": [ - "pyatv==0.7.6" + "pyatv==0.7.7" ], "zeroconf": [ "_mediaremotetv._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index bcea3b679b8..a6a280a5311 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1278,7 +1278,7 @@ pyatmo==4.2.2 pyatome==0.1.1 # homeassistant.components.apple_tv -pyatv==0.7.6 +pyatv==0.7.7 # homeassistant.components.bbox pybbox==0.0.5-alpha diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1a9c1bd7b9d..b89ac619320 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -673,7 +673,7 @@ pyatag==0.3.5.3 pyatmo==4.2.2 # homeassistant.components.apple_tv -pyatv==0.7.6 +pyatv==0.7.7 # homeassistant.components.blackbird pyblackbird==0.5 From 04b335afe9427f4a4b2f9c03d3e403e64c97d4ce Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 12 Mar 2021 09:04:02 -0800 Subject: [PATCH 324/831] Allow filtering the logbook by context_id (#47783) --- homeassistant/components/logbook/__init__.py | 15 ++++++ tests/components/logbook/test_init.py | 50 +++++++++++++++++--- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 3b77e6e6409..44c9171f244 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -231,6 +231,12 @@ class LogbookView(HomeAssistantView): hass = request.app["hass"] entity_matches_only = "entity_matches_only" in request.query + context_id = request.query.get("context_id") + + if entity_ids and context_id: + return self.json_message( + "Can't combine entity with context_id", HTTP_BAD_REQUEST + ) def json_events(): """Fetch events and generate JSON.""" @@ -243,6 +249,7 @@ class LogbookView(HomeAssistantView): self.filters, self.entities_filter, entity_matches_only, + context_id, ) ) @@ -413,8 +420,13 @@ def _get_events( filters=None, entities_filter=None, entity_matches_only=False, + context_id=None, ): """Get events for a period of time.""" + assert not ( + entity_ids and context_id + ), "can't pass in both entity_ids and context_id" + entity_attr_cache = EntityAttributeCache(hass) context_lookup = {None: None} @@ -466,6 +478,9 @@ def _get_events( filters.entity_filter() | (Events.event_type != EVENT_STATE_CHANGED) ) + if context_id is not None: + query = query.filter(Events.context_id == context_id) + query = query.order_by(Events.time_fired) return list( diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index cd3fb519ded..3dab7e6c2fb 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -1801,17 +1801,52 @@ async def test_empty_config(hass, hass_client): _assert_entry(entries[1], name="blu", entity_id=entity_id) -async def _async_fetch_logbook(client): +async def test_context_filter(hass, hass_client): + """Test we can filter by context.""" + await hass.async_add_executor_job(init_recorder_component, hass) + assert await async_setup_component(hass, "logbook", {}) + await hass.async_add_executor_job(hass.data[recorder.DATA_INSTANCE].block_till_done) + + entity_id = "switch.blu" + context = ha.Context() + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + hass.states.async_set(entity_id, None) + hass.states.async_set(entity_id, "on", context=context) + hass.states.async_set(entity_id, "off") + hass.states.async_set(entity_id, "unknown", context=context) + + await _async_commit_and_wait(hass) + client = await hass_client() + + # Test results + entries = await _async_fetch_logbook(client, {"context_id": context.id}) + + assert len(entries) == 2 + _assert_entry(entries[0], entity_id=entity_id, state="on") + _assert_entry(entries[1], entity_id=entity_id, state="unknown") + + # Test we can't combine context filter with entity_id filter + response = await client.get( + "/api/logbook", params={"context_id": context.id, "entity": entity_id} + ) + assert response.status == 400 + + +async def _async_fetch_logbook(client, params=None): + if params is None: + params = {} # Today time 00:00:00 start = dt_util.utcnow().date() start_date = datetime(start.year, start.month, start.day) - timedelta(hours=24) + if "end_time" not in params: + params["end_time"] = str(start + timedelta(hours=48)) + # Test today entries without filters - end_time = start + timedelta(hours=48) - response = await client.get( - f"/api/logbook/{start_date.isoformat()}?end_time={end_time}" - ) + response = await client.get(f"/api/logbook/{start_date.isoformat()}", params=params) assert response.status == 200 return await response.json() @@ -1825,7 +1860,7 @@ async def _async_commit_and_wait(hass): def _assert_entry( - entry, when=None, name=None, message=None, domain=None, entity_id=None + entry, when=None, name=None, message=None, domain=None, entity_id=None, state=None ): """Assert an entry is what is expected.""" if when: @@ -1843,6 +1878,9 @@ def _assert_entry( if entity_id: assert entity_id == entry["entity_id"] + if state: + assert state == entry["state"] + class MockLazyEventPartialState(ha.Event): """Minimal mock of a Lazy event.""" From 72cb1f54802e8c1943e758fc27585233fea9c91d Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Fri, 12 Mar 2021 18:19:55 +0100 Subject: [PATCH 325/831] Add ambient sensors to nut integration (#47411) --- homeassistant/components/nut/const.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/nut/const.py b/homeassistant/components/nut/const.py index cc70b33f763..b8b315df59e 100644 --- a/homeassistant/components/nut/const.py +++ b/homeassistant/components/nut/const.py @@ -1,6 +1,7 @@ """The nut component.""" from homeassistant.components.sensor import ( DEVICE_CLASS_BATTERY, + DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, ) @@ -45,7 +46,7 @@ SENSOR_TYPES = { "ups.temperature": [ "UPS Temperature", TEMP_CELSIUS, - "mdi:thermometer", + None, DEVICE_CLASS_TEMPERATURE, ], "ups.load": ["Load", PERCENTAGE, "mdi:gauge", None], @@ -83,13 +84,13 @@ SENSOR_TYPES = { "ups.realpower": [ "Current Real Power", POWER_WATT, - "mdi:flash", + None, DEVICE_CLASS_POWER, ], "ups.realpower.nominal": [ "Nominal Real Power", POWER_WATT, - "mdi:flash", + None, DEVICE_CLASS_POWER, ], "ups.beeper.status": ["Beeper Status", "", "mdi:information-outline", None], @@ -102,7 +103,7 @@ SENSOR_TYPES = { "battery.charge": [ "Battery Charge", PERCENTAGE, - "mdi:gauge", + None, DEVICE_CLASS_BATTERY, ], "battery.charge.low": ["Low Battery Setpoint", PERCENTAGE, "mdi:gauge", None], @@ -139,7 +140,7 @@ SENSOR_TYPES = { "battery.temperature": [ "Battery Temperature", TEMP_CELSIUS, - "mdi:thermometer", + None, DEVICE_CLASS_TEMPERATURE, ], "battery.runtime": ["Battery Runtime", TIME_SECONDS, "mdi:timer-outline", None], @@ -216,6 +217,18 @@ SENSOR_TYPES = { "mdi:flash", None, ], + "ambient.humidity": [ + "Ambient Humidity", + PERCENTAGE, + None, + DEVICE_CLASS_HUMIDITY, + ], + "ambient.temperature": [ + "Ambient Temperature", + TEMP_CELSIUS, + None, + DEVICE_CLASS_TEMPERATURE, + ], } STATE_TYPES = { From 3115bf9aaba476b69b82c75fab0ac1930ff31d26 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 12 Mar 2021 19:04:56 +0100 Subject: [PATCH 326/831] Add temperature sensor for gogogate2 wireless door sensor (#47754) * Add temperature sensor for gogogate2 wireless door sensor * Chain sensor generators --- homeassistant/components/gogogate2/common.py | 10 ++- homeassistant/components/gogogate2/cover.py | 4 +- homeassistant/components/gogogate2/sensor.py | 85 +++++++++++++++++-- tests/components/gogogate2/test_sensor.py | 88 ++++++++++++++------ 4 files changed, 152 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/gogogate2/common.py b/homeassistant/components/gogogate2/common.py index 761f9211921..404a22e3312 100644 --- a/homeassistant/components/gogogate2/common.py +++ b/homeassistant/components/gogogate2/common.py @@ -69,12 +69,13 @@ class GoGoGate2Entity(CoordinatorEntity): config_entry: ConfigEntry, data_update_coordinator: DeviceDataUpdateCoordinator, door: AbstractDoor, + unique_id: str, ) -> None: """Initialize gogogate2 base entity.""" super().__init__(data_update_coordinator) self._config_entry = config_entry self._door = door - self._unique_id = cover_unique_id(config_entry, door) + self._unique_id = unique_id @property def unique_id(self) -> Optional[str]: @@ -137,6 +138,13 @@ def cover_unique_id(config_entry: ConfigEntry, door: AbstractDoor) -> str: return f"{config_entry.unique_id}_{door.door_id}" +def sensor_unique_id( + config_entry: ConfigEntry, door: AbstractDoor, sensor_type: str +) -> str: + """Generate a cover entity unique id.""" + return f"{config_entry.unique_id}_{door.door_id}_{sensor_type}" + + def get_api(config_data: dict) -> AbstractGateApi: """Get an api object for config data.""" gate_class = GogoGate2Api diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index 2d97edbfe43..9a1b53f7bee 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -18,6 +18,7 @@ from homeassistant.helpers.entity import Entity from .common import ( DeviceDataUpdateCoordinator, GoGoGate2Entity, + cover_unique_id, get_data_update_coordinator, ) from .const import DOMAIN @@ -66,7 +67,8 @@ class DeviceCover(GoGoGate2Entity, CoverEntity): door: AbstractDoor, ) -> None: """Initialize the object.""" - super().__init__(config_entry, data_update_coordinator, door) + unique_id = cover_unique_id(config_entry, door) + super().__init__(config_entry, data_update_coordinator, door, unique_id) self._api = data_update_coordinator.api self._is_available = True diff --git a/homeassistant/components/gogogate2/sensor.py b/homeassistant/components/gogogate2/sensor.py index ed53779a95a..eea557639ad 100644 --- a/homeassistant/components/gogogate2/sensor.py +++ b/homeassistant/components/gogogate2/sensor.py @@ -1,14 +1,24 @@ """Support for Gogogate2 garage Doors.""" +from itertools import chain from typing import Callable, List, Optional -from gogogate2_api.common import get_configured_doors +from gogogate2_api.common import AbstractDoor, get_configured_doors from homeassistant.config_entries import ConfigEntry -from homeassistant.const import DEVICE_CLASS_BATTERY +from homeassistant.const import ( + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_TEMPERATURE, + TEMP_CELSIUS, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity -from .common import GoGoGate2Entity, get_data_update_coordinator +from .common import ( + DeviceDataUpdateCoordinator, + GoGoGate2Entity, + get_data_update_coordinator, + sensor_unique_id, +) SENSOR_ID_WIRED = "WIRE" @@ -21,17 +31,33 @@ async def async_setup_entry( """Set up the config entry.""" data_update_coordinator = get_data_update_coordinator(hass, config_entry) - async_add_entities( + sensors = chain( [ - DoorSensor(config_entry, data_update_coordinator, door) + DoorSensorBattery(config_entry, data_update_coordinator, door) for door in get_configured_doors(data_update_coordinator.data) if door.sensorid and door.sensorid != SENSOR_ID_WIRED - ] + ], + [ + DoorSensorTemperature(config_entry, data_update_coordinator, door) + for door in get_configured_doors(data_update_coordinator.data) + if door.sensorid and door.sensorid != SENSOR_ID_WIRED + ], ) + async_add_entities(sensors) -class DoorSensor(GoGoGate2Entity): - """Sensor entity for goggate2.""" +class DoorSensorBattery(GoGoGate2Entity): + """Battery sensor entity for gogogate2 door sensor.""" + + def __init__( + self, + config_entry: ConfigEntry, + data_update_coordinator: DeviceDataUpdateCoordinator, + door: AbstractDoor, + ) -> None: + """Initialize the object.""" + unique_id = sensor_unique_id(config_entry, door, "battery") + super().__init__(config_entry, data_update_coordinator, door, unique_id) @property def name(self): @@ -56,3 +82,46 @@ class DoorSensor(GoGoGate2Entity): if door.sensorid is not None: return {"door_id": door.door_id, "sensor_id": door.sensorid} return None + + +class DoorSensorTemperature(GoGoGate2Entity): + """Temperature sensor entity for gogogate2 door sensor.""" + + def __init__( + self, + config_entry: ConfigEntry, + data_update_coordinator: DeviceDataUpdateCoordinator, + door: AbstractDoor, + ) -> None: + """Initialize the object.""" + unique_id = sensor_unique_id(config_entry, door, "temperature") + super().__init__(config_entry, data_update_coordinator, door, unique_id) + + @property + def name(self): + """Return the name of the door.""" + return f"{self._get_door().name} temperature" + + @property + def device_class(self): + """Return the class of this device, from component DEVICE_CLASSES.""" + return DEVICE_CLASS_TEMPERATURE + + @property + def state(self): + """Return the state of the entity.""" + door = self._get_door() + return door.temperature + + @property + def unit_of_measurement(self): + """Return the unit_of_measurement.""" + return TEMP_CELSIUS + + @property + def device_state_attributes(self): + """Return the state attributes.""" + door = self._get_door() + if door.sensorid is not None: + return {"door_id": door.door_id, "sensor_id": door.sensorid} + return None diff --git a/tests/components/gogogate2/test_sensor.py b/tests/components/gogogate2/test_sensor.py index f8eb9bf88b7..020989c003a 100644 --- a/tests/components/gogogate2/test_sensor.py +++ b/tests/components/gogogate2/test_sensor.py @@ -20,11 +20,13 @@ from homeassistant.components.gogogate2.const import DEVICE_TYPE_ISMARTGATE, DOM from homeassistant.config_entries import SOURCE_USER from homeassistant.const import ( ATTR_DEVICE_CLASS, + ATTR_UNIT_OF_MEASUREMENT, CONF_DEVICE, CONF_IP_ADDRESS, CONF_PASSWORD, CONF_USERNAME, DEVICE_CLASS_BATTERY, + DEVICE_CLASS_TEMPERATURE, STATE_UNAVAILABLE, STATE_UNKNOWN, ) @@ -34,7 +36,7 @@ from homeassistant.util.dt import utcnow from tests.common import MockConfigEntry, async_fire_time_changed -def _mocked_gogogate_sensor_response(battery_level: int): +def _mocked_gogogate_sensor_response(battery_level: int, temperature: float): return GogoGate2InfoResponse( user="user1", gogogatename="gogogatename0", @@ -55,7 +57,7 @@ def _mocked_gogogate_sensor_response(battery_level: int): sensorid="ABCD", camera=False, events=2, - temperature=None, + temperature=temperature, voltage=battery_level, ), door2=GogoGate2Door( @@ -69,7 +71,7 @@ def _mocked_gogogate_sensor_response(battery_level: int): sensorid="WIRE", camera=False, events=0, - temperature=None, + temperature=temperature, voltage=battery_level, ), door3=GogoGate2Door( @@ -83,7 +85,7 @@ def _mocked_gogogate_sensor_response(battery_level: int): sensorid=None, camera=False, events=0, - temperature=None, + temperature=temperature, voltage=battery_level, ), outputs=Outputs(output1=True, output2=False, output3=True), @@ -92,7 +94,7 @@ def _mocked_gogogate_sensor_response(battery_level: int): ) -def _mocked_ismartgate_sensor_response(battery_level: int): +def _mocked_ismartgate_sensor_response(battery_level: int, temperature: float): return ISmartGateInfoResponse( user="user1", ismartgatename="ismartgatename0", @@ -115,7 +117,7 @@ def _mocked_ismartgate_sensor_response(battery_level: int): sensorid="ABCD", camera=False, events=2, - temperature=None, + temperature=temperature, enabled=True, apicode="apicode0", customimage=False, @@ -132,7 +134,7 @@ def _mocked_ismartgate_sensor_response(battery_level: int): sensorid="WIRE", camera=False, events=2, - temperature=None, + temperature=temperature, enabled=True, apicode="apicode0", customimage=False, @@ -149,7 +151,7 @@ def _mocked_ismartgate_sensor_response(battery_level: int): sensorid=None, camera=False, events=0, - temperature=None, + temperature=temperature, enabled=True, apicode="apicode0", customimage=False, @@ -164,16 +166,23 @@ def _mocked_ismartgate_sensor_response(battery_level: int): async def test_sensor_update(gogogate2api_mock, hass: HomeAssistant) -> None: """Test data update.""" - expected_attributes = { + bat_attributes = { "device_class": "battery", "door_id": 1, "friendly_name": "Door1 battery", "sensor_id": "ABCD", } + temp_attributes = { + "device_class": "temperature", + "door_id": 1, + "friendly_name": "Door1 temperature", + "sensor_id": "ABCD", + "unit_of_measurement": "°C", + } api = MagicMock(GogoGate2Api) api.async_activate.return_value = GogoGate2ActivateResponse(result=True) - api.async_info.return_value = _mocked_gogogate_sensor_response(25) + api.async_info.return_value = _mocked_gogogate_sensor_response(25, 7.0) gogogate2api_mock.return_value = api config_entry = MockConfigEntry( @@ -189,31 +198,40 @@ async def test_sensor_update(gogogate2api_mock, hass: HomeAssistant) -> None: assert hass.states.get("cover.door1") is None assert hass.states.get("cover.door2") is None - assert hass.states.get("cover.door2") is None + assert hass.states.get("cover.door3") is None assert hass.states.get("sensor.door1_battery") is None assert hass.states.get("sensor.door2_battery") is None - assert hass.states.get("sensor.door2_battery") is None + assert hass.states.get("sensor.door3_battery") is None + assert hass.states.get("sensor.door1_temperature") is None + assert hass.states.get("sensor.door2_temperature") is None + assert hass.states.get("sensor.door3_temperature") is None assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert hass.states.get("cover.door1") assert hass.states.get("cover.door2") - assert hass.states.get("cover.door2") + assert hass.states.get("cover.door3") assert hass.states.get("sensor.door1_battery").state == "25" + assert dict(hass.states.get("sensor.door1_battery").attributes) == bat_attributes + assert hass.states.get("sensor.door2_battery") is None + assert hass.states.get("sensor.door2_battery") is None + assert hass.states.get("sensor.door1_temperature").state == "7.0" assert ( - dict(hass.states.get("sensor.door1_battery").attributes) == expected_attributes + dict(hass.states.get("sensor.door1_temperature").attributes) == temp_attributes ) - assert hass.states.get("sensor.door2_battery") is None - assert hass.states.get("sensor.door2_battery") is None + assert hass.states.get("sensor.door2_temperature") is None + assert hass.states.get("sensor.door3_temperature") is None - api.async_info.return_value = _mocked_gogogate_sensor_response(40) + api.async_info.return_value = _mocked_gogogate_sensor_response(40, 10.0) async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) await hass.async_block_till_done() assert hass.states.get("sensor.door1_battery").state == "40" + assert hass.states.get("sensor.door1_temperature").state == "10.0" - api.async_info.return_value = _mocked_gogogate_sensor_response(None) + api.async_info.return_value = _mocked_gogogate_sensor_response(None, None) async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) await hass.async_block_till_done() assert hass.states.get("sensor.door1_battery").state == STATE_UNKNOWN + assert hass.states.get("sensor.door1_temperature").state == STATE_UNKNOWN assert await hass.config_entries.async_unload(config_entry.entry_id) assert not hass.states.async_entity_ids(DOMAIN) @@ -222,14 +240,21 @@ async def test_sensor_update(gogogate2api_mock, hass: HomeAssistant) -> None: @patch("homeassistant.components.gogogate2.common.ISmartGateApi") async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None: """Test availability.""" - expected_attributes = { + bat_attributes = { "device_class": "battery", "door_id": 1, "friendly_name": "Door1 battery", "sensor_id": "ABCD", } + temp_attributes = { + "device_class": "temperature", + "door_id": 1, + "friendly_name": "Door1 temperature", + "sensor_id": "ABCD", + "unit_of_measurement": "°C", + } - sensor_response = _mocked_ismartgate_sensor_response(35) + sensor_response = _mocked_ismartgate_sensor_response(35, -4.0) api = MagicMock(ISmartGateApi) api.async_info.return_value = sensor_response ismartgateapi_mock.return_value = api @@ -248,34 +273,47 @@ async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None: assert hass.states.get("cover.door1") is None assert hass.states.get("cover.door2") is None - assert hass.states.get("cover.door2") is None + assert hass.states.get("cover.door3") is None assert hass.states.get("sensor.door1_battery") is None assert hass.states.get("sensor.door2_battery") is None - assert hass.states.get("sensor.door2_battery") is None + assert hass.states.get("sensor.door3_battery") is None assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert hass.states.get("cover.door1") assert hass.states.get("cover.door2") - assert hass.states.get("cover.door2") + assert hass.states.get("cover.door3") assert hass.states.get("sensor.door1_battery").state == "35" assert hass.states.get("sensor.door2_battery") is None - assert hass.states.get("sensor.door2_battery") is None + assert hass.states.get("sensor.door3_battery") is None + assert hass.states.get("sensor.door1_temperature").state == "-4.0" + assert hass.states.get("sensor.door2_temperature") is None + assert hass.states.get("sensor.door3_temperature") is None assert ( hass.states.get("sensor.door1_battery").attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_BATTERY ) + assert ( + hass.states.get("sensor.door1_temperature").attributes[ATTR_DEVICE_CLASS] + == DEVICE_CLASS_TEMPERATURE + ) + assert ( + hass.states.get("sensor.door1_temperature").attributes[ATTR_UNIT_OF_MEASUREMENT] + == "°C" + ) api.async_info.side_effect = Exception("Error") async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) await hass.async_block_till_done() assert hass.states.get("sensor.door1_battery").state == STATE_UNAVAILABLE + assert hass.states.get("sensor.door1_temperature").state == STATE_UNAVAILABLE api.async_info.side_effect = None api.async_info.return_value = sensor_response async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) await hass.async_block_till_done() assert hass.states.get("sensor.door1_battery").state == "35" + assert dict(hass.states.get("sensor.door1_battery").attributes) == bat_attributes assert ( - dict(hass.states.get("sensor.door1_battery").attributes) == expected_attributes + dict(hass.states.get("sensor.door1_temperature").attributes) == temp_attributes ) From 13cd2f52d88adae5b5456ae3f07e6827054b56e4 Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Fri, 12 Mar 2021 13:49:59 -0500 Subject: [PATCH 327/831] Return property_key in zwave_js get_config_parameters websocket (#47808) --- homeassistant/components/zwave_js/api.py | 1 + tests/components/zwave_js/test_api.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 599183eba7e..055115db7b9 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -371,6 +371,7 @@ def websocket_get_config_parameters( metadata = zwave_value.metadata result[value_id] = { "property": zwave_value.property_, + "property_key": zwave_value.property_key, "configuration_value_type": zwave_value.configuration_value_type.value, "metadata": { "description": metadata.description, diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index aa085836b65..dd8679ddf73 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -73,10 +73,14 @@ async def test_websocket_api(hass, integration, multisensor_6, hass_ws_client): assert len(result) == 61 key = "52-112-0-2" assert result[key]["property"] == 2 + assert result[key]["property_key"] is None assert result[key]["metadata"]["type"] == "number" assert result[key]["configuration_value_type"] == "enumerated" assert result[key]["metadata"]["states"] + key = "52-112-0-201-255" + assert result[key]["property_key"] == 255 + # Test getting non-existent node fails await ws_client.send_json( { From 9a98dcf432ae55d000c58729760a195d010b1943 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Fri, 12 Mar 2021 20:03:47 +0100 Subject: [PATCH 328/831] Add HomeKit support for new CO / CO2 device class (#47737) --- homeassistant/components/demo/sensor.py | 19 +++++++++++++++++++ .../components/homekit/accessories.py | 6 +++--- homeassistant/components/homekit/const.py | 2 -- .../components/homekit/type_sensors.py | 4 +++- .../homekit/test_get_accessories.py | 16 ++++++++++++++-- 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/demo/sensor.py b/homeassistant/components/demo/sensor.py index 49930a35377..9c005d2d7f5 100644 --- a/homeassistant/components/demo/sensor.py +++ b/homeassistant/components/demo/sensor.py @@ -1,6 +1,9 @@ """Demo platform that has a couple of fake sensors.""" from homeassistant.const import ( ATTR_BATTERY_LEVEL, + CONCENTRATION_PARTS_PER_MILLION, + DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, PERCENTAGE, @@ -31,6 +34,22 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= PERCENTAGE, None, ), + DemoSensor( + "sensor_3", + "Carbon monoxide", + 54, + DEVICE_CLASS_CO, + CONCENTRATION_PARTS_PER_MILLION, + None, + ), + DemoSensor( + "sensor_4", + "Carbon dioxide", + 54, + DEVICE_CLASS_CO2, + CONCENTRATION_PARTS_PER_MILLION, + 14, + ), ] ) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 7e68daf4b62..b6ff11aa26d 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -22,6 +22,8 @@ from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, CONF_TYPE, + DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, @@ -54,8 +56,6 @@ from .const import ( CONF_LINKED_BATTERY_SENSOR, CONF_LOW_BATTERY_THRESHOLD, DEFAULT_LOW_BATTERY_THRESHOLD, - DEVICE_CLASS_CO, - DEVICE_CLASS_CO2, DEVICE_CLASS_PM25, EVENT_HOMEKIT_CHANGED, HK_CHARGING, @@ -167,7 +167,7 @@ def get_accessory(hass, driver, state, aid, config): a_type = "AirQualitySensor" elif device_class == DEVICE_CLASS_CO: a_type = "CarbonMonoxideSensor" - elif device_class == DEVICE_CLASS_CO2 or DEVICE_CLASS_CO2 in state.entity_id: + elif device_class == DEVICE_CLASS_CO2 or "co2" in state.entity_id: a_type = "CarbonDioxideSensor" elif device_class == DEVICE_CLASS_ILLUMINANCE or unit in ("lm", LIGHT_LUX): a_type = "LightSensor" diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index ff45306b351..abfc6a2aa38 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -237,8 +237,6 @@ PROP_CELSIUS = {"minValue": -273, "maxValue": 999} PROP_VALID_VALUES = "ValidValues" # #### Device Classes #### -DEVICE_CLASS_CO = "co" -DEVICE_CLASS_CO2 = "co2" DEVICE_CLASS_DOOR = "door" DEVICE_CLASS_GARAGE_DOOR = "garage_door" DEVICE_CLASS_GAS = "gas" diff --git a/homeassistant/components/homekit/type_sensors.py b/homeassistant/components/homekit/type_sensors.py index 28c7ea26009..b6cc4b05125 100644 --- a/homeassistant/components/homekit/type_sensors.py +++ b/homeassistant/components/homekit/type_sensors.py @@ -6,6 +6,8 @@ from pyhap.const import CATEGORY_SENSOR from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_UNIT_OF_MEASUREMENT, + DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, STATE_HOME, STATE_ON, TEMP_CELSIUS, @@ -30,7 +32,6 @@ from .const import ( CHAR_MOTION_DETECTED, CHAR_OCCUPANCY_DETECTED, CHAR_SMOKE_DETECTED, - DEVICE_CLASS_CO2, DEVICE_CLASS_DOOR, DEVICE_CLASS_GARAGE_DOOR, DEVICE_CLASS_GAS, @@ -60,6 +61,7 @@ from .util import convert_to_float, density_to_air_quality, temperature_to_homek _LOGGER = logging.getLogger(__name__) BINARY_SENSOR_SERVICE_MAP = { + DEVICE_CLASS_CO: (SERV_CARBON_MONOXIDE_SENSOR, CHAR_CARBON_MONOXIDE_DETECTED, int), DEVICE_CLASS_CO2: (SERV_CARBON_DIOXIDE_SENSOR, CHAR_CARBON_DIOXIDE_DETECTED, int), DEVICE_CLASS_DOOR: (SERV_CONTACT_SENSOR, CHAR_CONTACT_SENSOR_STATE, int), DEVICE_CLASS_GARAGE_DOOR: (SERV_CONTACT_SENSOR, CHAR_CONTACT_SENSOR_STATE, int), diff --git a/tests/components/homekit/test_get_accessories.py b/tests/components/homekit/test_get_accessories.py index 70d59408011..1c68ae7d001 100644 --- a/tests/components/homekit/test_get_accessories.py +++ b/tests/components/homekit/test_get_accessories.py @@ -26,6 +26,8 @@ from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, CONF_TYPE, + DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, LIGHT_LUX, PERCENTAGE, TEMP_CELSIUS, @@ -186,9 +188,19 @@ def test_type_media_player(type_name, entity_id, state, attrs, config): ("BinarySensor", "person.someone", "home", {}), ("AirQualitySensor", "sensor.air_quality_pm25", "40", {}), ("AirQualitySensor", "sensor.air_quality", "40", {ATTR_DEVICE_CLASS: "pm25"}), - ("CarbonMonoxideSensor", "sensor.airmeter", "2", {ATTR_DEVICE_CLASS: "co"}), + ( + "CarbonMonoxideSensor", + "sensor.co", + "2", + {ATTR_DEVICE_CLASS: DEVICE_CLASS_CO}, + ), ("CarbonDioxideSensor", "sensor.airmeter_co2", "500", {}), - ("CarbonDioxideSensor", "sensor.airmeter", "500", {ATTR_DEVICE_CLASS: "co2"}), + ( + "CarbonDioxideSensor", + "sensor.co2", + "500", + {ATTR_DEVICE_CLASS: DEVICE_CLASS_CO2}, + ), ( "HumiditySensor", "sensor.humidity", From 597bf67f5a8483f1e032e43678103cb33bec561f Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 12 Mar 2021 20:55:11 +0100 Subject: [PATCH 329/831] UniFi has changed to not report uptime in epoch form (#47492) --- homeassistant/components/unifi/sensor.py | 7 +- tests/components/unifi/test_sensor.py | 340 +++++++++++++---------- 2 files changed, 199 insertions(+), 148 deletions(-) diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index f78ec614da1..77e8f5afbe4 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -3,6 +3,9 @@ Support for bandwidth sensors of network clients. Support for uptime sensors of network clients. """ + +from datetime import datetime, timedelta + from homeassistant.components.sensor import DEVICE_CLASS_TIMESTAMP, DOMAIN from homeassistant.const import DATA_MEGABYTES from homeassistant.core import callback @@ -140,8 +143,10 @@ class UniFiUpTimeSensor(UniFiClient): return f"{super().name} {self.TYPE.capitalize()}" @property - def state(self) -> int: + def state(self) -> datetime: """Return the uptime of the client.""" + if self.client.uptime < 1000000000: + return (dt_util.now() - timedelta(seconds=self.client.uptime)).isoformat() return dt_util.utc_from_timestamp(float(self.client.uptime)).isoformat() async def options_updated(self) -> None: diff --git a/tests/components/unifi/test_sensor.py b/tests/components/unifi/test_sensor.py index db1794e0878..eec4fba7df9 100644 --- a/tests/components/unifi/test_sensor.py +++ b/tests/components/unifi/test_sensor.py @@ -1,5 +1,7 @@ """UniFi sensor platform tests.""" -from copy import deepcopy + +from datetime import datetime +from unittest.mock import patch from aiounifi.controller import MESSAGE_CLIENT, MESSAGE_CLIENT_REMOVED @@ -13,40 +15,10 @@ from homeassistant.components.unifi.const import ( DOMAIN as UNIFI_DOMAIN, ) from homeassistant.helpers.dispatcher import async_dispatcher_send +import homeassistant.util.dt as dt_util from .test_controller import setup_unifi_integration -CLIENTS = [ - { - "hostname": "Wired client hostname", - "ip": "10.0.0.1", - "is_wired": True, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:01", - "name": "Wired client name", - "oui": "Producer", - "sw_mac": "00:00:00:00:01:01", - "sw_port": 1, - "wired-rx_bytes": 1234000000, - "wired-tx_bytes": 5678000000, - "uptime": 1600094505, - }, - { - "hostname": "Wireless client hostname", - "ip": "10.0.0.2", - "is_wired": False, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:02", - "name": "Wireless client name", - "oui": "Producer", - "sw_mac": "00:00:00:00:01:01", - "sw_port": 2, - "rx_bytes": 1234000000, - "tx_bytes": 5678000000, - "uptime": 1600094505, - }, -] - async def test_no_clients(hass, aioclient_mock): """Test the update_clients function when no clients are found.""" @@ -62,112 +34,93 @@ async def test_no_clients(hass, aioclient_mock): assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 0 -async def test_sensors(hass, aioclient_mock, mock_unifi_websocket): - """Test the update_items function with some clients.""" +async def test_bandwidth_sensors(hass, aioclient_mock, mock_unifi_websocket): + """Verify that bandwidth sensors are working as expected.""" + wired_client = { + "hostname": "Wired client", + "is_wired": True, + "mac": "00:00:00:00:00:01", + "oui": "Producer", + "wired-rx_bytes": 1234000000, + "wired-tx_bytes": 5678000000, + } + wireless_client = { + "is_wired": False, + "mac": "00:00:00:00:00:02", + "name": "Wireless client", + "oui": "Producer", + "rx_bytes": 2345000000, + "tx_bytes": 6789000000, + } + options = { + CONF_ALLOW_BANDWIDTH_SENSORS: True, + CONF_ALLOW_UPTIME_SENSORS: False, + CONF_TRACK_CLIENTS: False, + CONF_TRACK_DEVICES: False, + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, - options={ - CONF_ALLOW_BANDWIDTH_SENSORS: True, - CONF_ALLOW_UPTIME_SENSORS: True, - CONF_TRACK_CLIENTS: False, - CONF_TRACK_DEVICES: False, - }, - clients_response=CLIENTS, + options=options, + clients_response=[wired_client, wireless_client], ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 6 + assert len(hass.states.async_all()) == 5 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 4 + assert hass.states.get("sensor.wired_client_rx").state == "1234.0" + assert hass.states.get("sensor.wired_client_tx").state == "5678.0" + assert hass.states.get("sensor.wireless_client_rx").state == "2345.0" + assert hass.states.get("sensor.wireless_client_tx").state == "6789.0" - wired_client_rx = hass.states.get("sensor.wired_client_name_rx") - assert wired_client_rx.state == "1234.0" + # Verify state update - wired_client_tx = hass.states.get("sensor.wired_client_name_tx") - assert wired_client_tx.state == "5678.0" - - wired_client_uptime = hass.states.get("sensor.wired_client_name_uptime") - assert wired_client_uptime.state == "2020-09-14T14:41:45+00:00" - - wireless_client_rx = hass.states.get("sensor.wireless_client_name_rx") - assert wireless_client_rx.state == "1234.0" - - wireless_client_tx = hass.states.get("sensor.wireless_client_name_tx") - assert wireless_client_tx.state == "5678.0" - - wireless_client_uptime = hass.states.get("sensor.wireless_client_name_uptime") - assert wireless_client_uptime.state == "2020-09-14T14:41:45+00:00" - - clients = deepcopy(CLIENTS) - clients[0]["is_wired"] = False - clients[1]["rx_bytes"] = 2345000000 - clients[1]["tx_bytes"] = 6789000000 - clients[1]["uptime"] = 1600180860 + wireless_client["rx_bytes"] = 3456000000 + wireless_client["tx_bytes"] = 7891000000 mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": clients, + "data": [wireless_client], } ) await hass.async_block_till_done() - wireless_client_rx = hass.states.get("sensor.wireless_client_name_rx") - assert wireless_client_rx.state == "2345.0" + assert hass.states.get("sensor.wireless_client_rx").state == "3456.0" + assert hass.states.get("sensor.wireless_client_tx").state == "7891.0" - wireless_client_tx = hass.states.get("sensor.wireless_client_name_tx") - assert wireless_client_tx.state == "6789.0" + # Disable option - wireless_client_uptime = hass.states.get("sensor.wireless_client_name_uptime") - assert wireless_client_uptime.state == "2020-09-15T14:41:00+00:00" - - hass.config_entries.async_update_entry( - config_entry, - options={ - CONF_ALLOW_BANDWIDTH_SENSORS: False, - CONF_ALLOW_UPTIME_SENSORS: False, - }, - ) + options[CONF_ALLOW_BANDWIDTH_SENSORS] = False + hass.config_entries.async_update_entry(config_entry, options=options.copy()) await hass.async_block_till_done() - wireless_client_rx = hass.states.get("sensor.wireless_client_name_rx") - assert wireless_client_rx is None + assert len(hass.states.async_all()) == 1 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 0 + assert hass.states.get("sensor.wireless_client_rx") is None + assert hass.states.get("sensor.wireless_client_tx") is None + assert hass.states.get("sensor.wired_client_rx") is None + assert hass.states.get("sensor.wired_client_tx") is None - wireless_client_tx = hass.states.get("sensor.wireless_client_name_tx") - assert wireless_client_tx is None + # Enable option - wired_client_uptime = hass.states.get("sensor.wired_client_name_uptime") - assert wired_client_uptime is None - - wireless_client_uptime = hass.states.get("sensor.wireless_client_name_uptime") - assert wireless_client_uptime is None - - hass.config_entries.async_update_entry( - config_entry, - options={ - CONF_ALLOW_BANDWIDTH_SENSORS: True, - CONF_ALLOW_UPTIME_SENSORS: True, - }, - ) + options[CONF_ALLOW_BANDWIDTH_SENSORS] = True + hass.config_entries.async_update_entry(config_entry, options=options.copy()) await hass.async_block_till_done() - wireless_client_rx = hass.states.get("sensor.wireless_client_name_rx") - assert wireless_client_rx.state == "2345.0" - - wireless_client_tx = hass.states.get("sensor.wireless_client_name_tx") - assert wireless_client_tx.state == "6789.0" - - wireless_client_uptime = hass.states.get("sensor.wireless_client_name_uptime") - assert wireless_client_uptime.state == "2020-09-15T14:41:00+00:00" - - wired_client_uptime = hass.states.get("sensor.wired_client_name_uptime") - assert wired_client_uptime.state == "2020-09-14T14:41:45+00:00" + assert len(hass.states.async_all()) == 5 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 4 + assert hass.states.get("sensor.wireless_client_rx") + assert hass.states.get("sensor.wireless_client_tx") + assert hass.states.get("sensor.wired_client_rx") + assert hass.states.get("sensor.wired_client_tx") # Try to add the sensors again, using a signal - clients_connected = set() + + clients_connected = {wired_client["mac"], wireless_client["mac"]} devices_connected = set() - clients_connected.add(clients[0]["mac"]) - clients_connected.add(clients[1]["mac"]) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] async_dispatcher_send( hass, @@ -175,14 +128,123 @@ async def test_sensors(hass, aioclient_mock, mock_unifi_websocket): clients_connected, devices_connected, ) - await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 6 + assert len(hass.states.async_all()) == 5 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 4 + + +async def test_uptime_sensors(hass, aioclient_mock, mock_unifi_websocket): + """Verify that uptime sensors are working as expected.""" + client1 = { + "mac": "00:00:00:00:00:01", + "name": "client1", + "oui": "Producer", + "uptime": 1609506061, + } + client2 = { + "hostname": "Client2", + "mac": "00:00:00:00:00:02", + "oui": "Producer", + "uptime": 60, + } + options = { + CONF_ALLOW_BANDWIDTH_SENSORS: False, + CONF_ALLOW_UPTIME_SENSORS: True, + CONF_TRACK_CLIENTS: False, + CONF_TRACK_DEVICES: False, + } + + now = datetime(2021, 1, 1, 1, tzinfo=dt_util.UTC) + with patch("homeassistant.util.dt.now", return_value=now): + config_entry = await setup_unifi_integration( + hass, + aioclient_mock, + options=options, + clients_response=[client1, client2], + ) + + assert len(hass.states.async_all()) == 3 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 2 + assert hass.states.get("sensor.client1_uptime").state == "2021-01-01T13:01:01+00:00" + assert hass.states.get("sensor.client2_uptime").state == "2021-01-01T00:59:00+00:00" + + # Verify state update + + client1["uptime"] = 1609506062 + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client1], + } + ) + await hass.async_block_till_done() + + assert hass.states.get("sensor.client1_uptime").state == "2021-01-01T13:01:02+00:00" + + # Disable option + + options[CONF_ALLOW_UPTIME_SENSORS] = False + hass.config_entries.async_update_entry(config_entry, options=options.copy()) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 0 + assert hass.states.get("sensor.client1_uptime") is None + assert hass.states.get("sensor.client2_uptime") is None + + # Enable option + + options[CONF_ALLOW_UPTIME_SENSORS] = True + with patch("homeassistant.util.dt.now", return_value=now): + hass.config_entries.async_update_entry(config_entry, options=options.copy()) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 3 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 2 + assert hass.states.get("sensor.client1_uptime") + assert hass.states.get("sensor.client2_uptime") + + # Try to add the sensors again, using a signal + + clients_connected = {client1["mac"], client2["mac"]} + devices_connected = set() + + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + + async_dispatcher_send( + hass, + controller.signal_update, + clients_connected, + devices_connected, + ) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 3 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 2 async def test_remove_sensors(hass, aioclient_mock, mock_unifi_websocket): - """Test the remove_items function with some clients.""" + """Verify removing of clients work as expected.""" + wired_client = { + "hostname": "Wired client", + "is_wired": True, + "mac": "00:00:00:00:00:01", + "oui": "Producer", + "wired-rx_bytes": 1234000000, + "wired-tx_bytes": 5678000000, + "uptime": 1600094505, + } + wireless_client = { + "is_wired": False, + "mac": "00:00:00:00:00:02", + "name": "Wireless client", + "oui": "Producer", + "rx_bytes": 2345000000, + "tx_bytes": 6789000000, + "uptime": 60, + } + await setup_unifi_integration( hass, aioclient_mock, @@ -190,51 +252,35 @@ async def test_remove_sensors(hass, aioclient_mock, mock_unifi_websocket): CONF_ALLOW_BANDWIDTH_SENSORS: True, CONF_ALLOW_UPTIME_SENSORS: True, }, - clients_response=CLIENTS, + clients_response=[wired_client, wireless_client], ) + assert len(hass.states.async_all()) == 9 assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 6 assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 + assert hass.states.get("sensor.wired_client_rx") + assert hass.states.get("sensor.wired_client_tx") + assert hass.states.get("sensor.wired_client_uptime") + assert hass.states.get("sensor.wireless_client_rx") + assert hass.states.get("sensor.wireless_client_tx") + assert hass.states.get("sensor.wireless_client_uptime") - wired_client_rx = hass.states.get("sensor.wired_client_name_rx") - assert wired_client_rx is not None - wired_client_tx = hass.states.get("sensor.wired_client_name_tx") - assert wired_client_tx is not None - - wired_client_uptime = hass.states.get("sensor.wired_client_name_uptime") - assert wired_client_uptime is not None - - wireless_client_rx = hass.states.get("sensor.wireless_client_name_rx") - assert wireless_client_rx is not None - wireless_client_tx = hass.states.get("sensor.wireless_client_name_tx") - assert wireless_client_tx is not None - - wireless_client_uptime = hass.states.get("sensor.wireless_client_name_uptime") - assert wireless_client_uptime is not None + # Remove wired client mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT_REMOVED}, - "data": [CLIENTS[0]], + "data": [wired_client], } ) await hass.async_block_till_done() + assert len(hass.states.async_all()) == 5 assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 3 assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 - - wired_client_rx = hass.states.get("sensor.wired_client_name_rx") - assert wired_client_rx is None - wired_client_tx = hass.states.get("sensor.wired_client_name_tx") - assert wired_client_tx is None - - wired_client_uptime = hass.states.get("sensor.wired_client_name_uptime") - assert wired_client_uptime is None - - wireless_client_rx = hass.states.get("sensor.wireless_client_name_rx") - assert wireless_client_rx is not None - wireless_client_tx = hass.states.get("sensor.wireless_client_name_tx") - assert wireless_client_tx is not None - - wireless_client_uptime = hass.states.get("sensor.wireless_client_name_uptime") - assert wireless_client_uptime is not None + assert hass.states.get("sensor.wired_client_rx") is None + assert hass.states.get("sensor.wired_client_tx") is None + assert hass.states.get("sensor.wired_client_uptime") is None + assert hass.states.get("sensor.wireless_client_rx") + assert hass.states.get("sensor.wireless_client_tx") + assert hass.states.get("sensor.wireless_client_uptime") From 6b03c8d1269ec53174f6c094b34326e5c5299afd Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 12 Mar 2021 21:02:15 +0100 Subject: [PATCH 330/831] Improve deCONZ init tests (#47825) Use patch.dict rather than deep copy to change DECONZ_WEB_REQUEST --- tests/components/deconz/test_init.py | 46 ++++++++++++---------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index 46b563ca007..6583372d7bd 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -1,7 +1,6 @@ """Test deCONZ component setup process.""" import asyncio -from copy import deepcopy from unittest.mock import patch from homeassistant.components.deconz import ( @@ -71,15 +70,14 @@ async def test_setup_entry_multiple_gateways(hass, aioclient_mock): config_entry = await setup_deconz_integration(hass, aioclient_mock) aioclient_mock.clear_requests() - data = deepcopy(DECONZ_WEB_REQUEST) - data["config"]["bridgeid"] = "01234E56789B" - config_entry2 = await setup_deconz_integration( - hass, - aioclient_mock, - get_state_response=data, - entry_id="2", - unique_id="01234E56789B", - ) + data = {"config": {"bridgeid": "01234E56789B"}} + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry2 = await setup_deconz_integration( + hass, + aioclient_mock, + entry_id="2", + unique_id="01234E56789B", + ) assert len(hass.data[DECONZ_DOMAIN]) == 2 assert hass.data[DECONZ_DOMAIN][config_entry.unique_id].master @@ -100,15 +98,14 @@ async def test_unload_entry_multiple_gateways(hass, aioclient_mock): config_entry = await setup_deconz_integration(hass, aioclient_mock) aioclient_mock.clear_requests() - data = deepcopy(DECONZ_WEB_REQUEST) - data["config"]["bridgeid"] = "01234E56789B" - config_entry2 = await setup_deconz_integration( - hass, - aioclient_mock, - get_state_response=data, - entry_id="2", - unique_id="01234E56789B", - ) + data = {"config": {"bridgeid": "01234E56789B"}} + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry2 = await setup_deconz_integration( + hass, + aioclient_mock, + entry_id="2", + unique_id="01234E56789B", + ) assert len(hass.data[DECONZ_DOMAIN]) == 2 @@ -154,12 +151,8 @@ async def test_update_group_unique_id(hass): await async_update_group_unique_id(hass, entry) assert entry.data == {CONF_API_KEY: "1", CONF_HOST: "2", CONF_PORT: "3"} - - old_entity = registry.async_get(f"{LIGHT_DOMAIN}.old") - assert old_entity.unique_id == f"{new_unique_id}-OLD" - - new_entity = registry.async_get(f"{LIGHT_DOMAIN}.new") - assert new_entity.unique_id == f"{new_unique_id}-NEW" + assert registry.async_get(f"{LIGHT_DOMAIN}.old").unique_id == f"{new_unique_id}-OLD" + assert registry.async_get(f"{LIGHT_DOMAIN}.new").unique_id == f"{new_unique_id}-NEW" async def test_update_group_unique_id_no_legacy_group_id(hass): @@ -184,5 +177,4 @@ async def test_update_group_unique_id_no_legacy_group_id(hass): await async_update_group_unique_id(hass, entry) - old_entity = registry.async_get(f"{LIGHT_DOMAIN}.old") - assert old_entity.unique_id == f"{old_unique_id}-OLD" + assert registry.async_get(f"{LIGHT_DOMAIN}.old").unique_id == f"{old_unique_id}-OLD" From 786cbcc1d631eb1c7135fee5f3a1b2d400482e50 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 12 Mar 2021 21:03:29 +0100 Subject: [PATCH 331/831] Introduction of deCONZ websocket fixture (#47812) --- tests/components/deconz/conftest.py | 27 +++++++++++++++ tests/components/deconz/test_gateway.py | 46 ++++++++++++++++++------- 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/tests/components/deconz/conftest.py b/tests/components/deconz/conftest.py index 5cf122213c4..a7825b80ea2 100644 --- a/tests/components/deconz/conftest.py +++ b/tests/components/deconz/conftest.py @@ -1,2 +1,29 @@ """deconz conftest.""" + +from typing import Optional +from unittest.mock import patch + +import pytest + from tests.components.light.conftest import mock_light_profiles # noqa: F401 + + +@pytest.fixture(autouse=True) +def mock_deconz_websocket(): + """No real websocket allowed.""" + with patch("pydeconz.gateway.WSClient") as mock: + + async def make_websocket_call(data: Optional[dict] = None, state: str = ""): + """Generate a websocket call.""" + pydeconz_gateway_session_handler = mock.call_args[0][3] + + if data: + mock.return_value.data = data + await pydeconz_gateway_session_handler(signal="data") + elif state: + mock.return_value.state = state + await pydeconz_gateway_session_handler(signal="state") + else: + raise NotImplementedError + + yield make_websocket_call diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index 5c1642ba8f7..6743ae0696a 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -4,6 +4,7 @@ from copy import deepcopy from unittest.mock import Mock, patch import pydeconz +from pydeconz.websocket import STATE_RUNNING, STATE_STARTING import pytest from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN @@ -29,8 +30,14 @@ from homeassistant.components.ssdp import ( ) from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import CONN_CLASS_LOCAL_PUSH, SOURCE_SSDP -from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT, CONTENT_TYPE_JSON -from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.const import ( + CONF_API_KEY, + CONF_HOST, + CONF_PORT, + CONTENT_TYPE_JSON, + STATE_OFF, + STATE_UNAVAILABLE, +) from tests.common import MockConfigEntry @@ -116,8 +123,7 @@ async def setup_deconz_integration( if aioclient_mock: mock_deconz_request(aioclient_mock, config, get_state_response) - with patch("pydeconz.DeconzSession.start", return_value=True): - await hass.config_entries.async_setup(config_entry.entry_id) + await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() return config_entry @@ -173,21 +179,35 @@ async def test_gateway_setup_fails(hass): assert not hass.data[DECONZ_DOMAIN] -async def test_connection_status_signalling(hass, aioclient_mock): +async def test_connection_status_signalling( + hass, aioclient_mock, mock_deconz_websocket +): """Make sure that connection status triggers a dispatcher send.""" - config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "sensors": { + "1": { + "name": "presence", + "type": "ZHAPresence", + "state": {"presence": False}, + "config": {"on": True, "reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) - event_call = Mock() - unsub = async_dispatcher_connect(hass, gateway.signal_reachable, event_call) + assert hass.states.get("binary_sensor.presence").state == STATE_OFF - gateway.async_connection_status_callback(False) + await mock_deconz_websocket(state=STATE_STARTING) await hass.async_block_till_done() - assert gateway.available is False - assert len(event_call.mock_calls) == 1 + assert hass.states.get("binary_sensor.presence").state == STATE_UNAVAILABLE - unsub() + await mock_deconz_websocket(state=STATE_RUNNING) + await hass.async_block_till_done() + + assert hass.states.get("binary_sensor.presence").state == STATE_OFF async def test_update_address(hass, aioclient_mock): From 7826f6e3f8048786c3dec1004919f3bd260bdda2 Mon Sep 17 00:00:00 2001 From: Klaas Schoute Date: Fri, 12 Mar 2021 21:52:43 +0100 Subject: [PATCH 332/831] Update cloud integration to 0.42.0 (#47818) --- homeassistant/components/cloud/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/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index 9d27de13309..b854cb4578d 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -2,7 +2,7 @@ "domain": "cloud", "name": "Home Assistant Cloud", "documentation": "https://www.home-assistant.io/integrations/cloud", - "requirements": ["hass-nabucasa==0.41.0"], + "requirements": ["hass-nabucasa==0.42.0"], "dependencies": ["http", "webhook", "alexa"], "after_dependencies": ["google_assistant"], "codeowners": ["@home-assistant/cloud"] diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index bcbe614ca20..878834c5075 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -14,7 +14,7 @@ cryptography==3.3.2 defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 -hass-nabucasa==0.41.0 +hass-nabucasa==0.42.0 home-assistant-frontend==20210302.6 httpx==0.16.1 jinja2>=2.11.3 diff --git a/requirements_all.txt b/requirements_all.txt index a6a280a5311..532318d5910 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -727,7 +727,7 @@ habitipy==0.2.0 hangups==0.4.11 # homeassistant.components.cloud -hass-nabucasa==0.41.0 +hass-nabucasa==0.42.0 # homeassistant.components.splunk hass_splunk==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b89ac619320..b8259e517b6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -388,7 +388,7 @@ habitipy==0.2.0 hangups==0.4.11 # homeassistant.components.cloud -hass-nabucasa==0.41.0 +hass-nabucasa==0.42.0 # homeassistant.components.tasmota hatasmota==0.2.9 From 362e7226e93f7f91199b8fa82b02c64607140430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20Kr=C3=B6ner?= Date: Fri, 12 Mar 2021 21:55:13 +0100 Subject: [PATCH 333/831] Additional sensors for OpenWeatherMap (#47806) --- .../components/openweathermap/const.py | 19 ++++++++++++++++++- .../weather_update_coordinator.py | 6 +++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/openweathermap/const.py b/homeassistant/components/openweathermap/const.py index 8457ceb65e9..4ab61b486f1 100644 --- a/homeassistant/components/openweathermap/const.py +++ b/homeassistant/components/openweathermap/const.py @@ -35,6 +35,7 @@ from homeassistant.const import ( PRESSURE_HPA, SPEED_METERS_PER_SECOND, TEMP_CELSIUS, + UV_INDEX, ) DOMAIN = "openweathermap" @@ -47,6 +48,7 @@ ENTRY_NAME = "name" ENTRY_WEATHER_COORDINATOR = "weather_coordinator" ATTR_API_PRECIPITATION = "precipitation" ATTR_API_DATETIME = "datetime" +ATTR_API_DEW_POINT = "dew_point" ATTR_API_WEATHER = "weather" ATTR_API_TEMPERATURE = "temperature" ATTR_API_FEELS_LIKE_TEMPERATURE = "feels_like_temperature" @@ -58,6 +60,7 @@ ATTR_API_CONDITION = "condition" ATTR_API_CLOUDS = "clouds" ATTR_API_RAIN = "rain" ATTR_API_SNOW = "snow" +ATTR_API_UV_INDEX = "uv_index" ATTR_API_WEATHER_CODE = "weather_code" ATTR_API_FORECAST = "forecast" SENSOR_NAME = "sensor_name" @@ -81,6 +84,7 @@ DEFAULT_FORECAST_MODE = FORECAST_MODE_ONECALL_DAILY MONITORED_CONDITIONS = [ ATTR_API_WEATHER, + ATTR_API_DEW_POINT, ATTR_API_TEMPERATURE, ATTR_API_FEELS_LIKE_TEMPERATURE, ATTR_API_WIND_SPEED, @@ -90,6 +94,7 @@ MONITORED_CONDITIONS = [ ATTR_API_CLOUDS, ATTR_API_RAIN, ATTR_API_SNOW, + ATTR_API_UV_INDEX, ATTR_API_CONDITION, ATTR_API_WEATHER_CODE, ] @@ -187,6 +192,11 @@ CONDITION_CLASSES = { } WEATHER_SENSOR_TYPES = { ATTR_API_WEATHER: {SENSOR_NAME: "Weather"}, + ATTR_API_DEW_POINT: { + SENSOR_NAME: "Dew Point", + SENSOR_UNIT: TEMP_CELSIUS, + SENSOR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, + }, ATTR_API_TEMPERATURE: { SENSOR_NAME: "Temperature", SENSOR_UNIT: TEMP_CELSIUS, @@ -215,13 +225,20 @@ WEATHER_SENSOR_TYPES = { ATTR_API_CLOUDS: {SENSOR_NAME: "Cloud coverage", SENSOR_UNIT: PERCENTAGE}, ATTR_API_RAIN: {SENSOR_NAME: "Rain", SENSOR_UNIT: LENGTH_MILLIMETERS}, ATTR_API_SNOW: {SENSOR_NAME: "Snow", SENSOR_UNIT: LENGTH_MILLIMETERS}, + ATTR_API_UV_INDEX: { + SENSOR_NAME: "UV Index", + SENSOR_UNIT: UV_INDEX, + }, ATTR_API_CONDITION: {SENSOR_NAME: "Condition"}, ATTR_API_WEATHER_CODE: {SENSOR_NAME: "Weather Code"}, } FORECAST_SENSOR_TYPES = { ATTR_FORECAST_CONDITION: {SENSOR_NAME: "Condition"}, ATTR_FORECAST_PRECIPITATION: {SENSOR_NAME: "Precipitation"}, - ATTR_FORECAST_PRECIPITATION_PROBABILITY: {SENSOR_NAME: "Precipitation probability"}, + ATTR_FORECAST_PRECIPITATION_PROBABILITY: { + SENSOR_NAME: "Precipitation probability", + SENSOR_UNIT: PERCENTAGE, + }, ATTR_FORECAST_PRESSURE: {SENSOR_NAME: "Pressure"}, ATTR_FORECAST_TEMP: { SENSOR_NAME: "Temperature", diff --git a/homeassistant/components/openweathermap/weather_update_coordinator.py b/homeassistant/components/openweathermap/weather_update_coordinator.py index 07b8c507c87..26ada47bef6 100644 --- a/homeassistant/components/openweathermap/weather_update_coordinator.py +++ b/homeassistant/components/openweathermap/weather_update_coordinator.py @@ -25,6 +25,7 @@ from homeassistant.util import dt from .const import ( ATTR_API_CLOUDS, ATTR_API_CONDITION, + ATTR_API_DEW_POINT, ATTR_API_FEELS_LIKE_TEMPERATURE, ATTR_API_FORECAST, ATTR_API_HUMIDITY, @@ -32,6 +33,7 @@ from .const import ( ATTR_API_RAIN, ATTR_API_SNOW, ATTR_API_TEMPERATURE, + ATTR_API_UV_INDEX, ATTR_API_WEATHER, ATTR_API_WEATHER_CODE, ATTR_API_WIND_BEARING, @@ -119,6 +121,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): ATTR_API_FEELS_LIKE_TEMPERATURE: current_weather.temperature("celsius").get( "feels_like" ), + ATTR_API_DEW_POINT: (round(current_weather.dewpoint / 100, 1)), ATTR_API_PRESSURE: current_weather.pressure.get("press"), ATTR_API_HUMIDITY: current_weather.humidity, ATTR_API_WIND_BEARING: current_weather.wind().get("deg"), @@ -128,6 +131,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): ATTR_API_SNOW: self._get_snow(current_weather.snow), ATTR_API_WEATHER: current_weather.detailed_status, ATTR_API_CONDITION: self._get_condition(current_weather.weather_code), + ATTR_API_UV_INDEX: current_weather.uvi, ATTR_API_WEATHER_CODE: current_weather.weather_code, ATTR_API_FORECAST: forecast_weather, } @@ -151,7 +155,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): entry.rain, entry.snow ), ATTR_FORECAST_PRECIPITATION_PROBABILITY: ( - entry.precipitation_probability * 100 + round(entry.precipitation_probability * 100) ), ATTR_FORECAST_PRESSURE: entry.pressure.get("press"), ATTR_FORECAST_WIND_SPEED: entry.wind().get("speed"), From 07aeb8d160118fb3d0827ec02f69288debe25b7d Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Fri, 12 Mar 2021 21:57:02 +0100 Subject: [PATCH 334/831] Fix Netatmo event handling (#47792) --- homeassistant/components/netatmo/webhook.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/netatmo/webhook.py b/homeassistant/components/netatmo/webhook.py index 5ecc3d41789..54db95e9aa0 100644 --- a/homeassistant/components/netatmo/webhook.py +++ b/homeassistant/components/netatmo/webhook.py @@ -76,16 +76,18 @@ def async_send_event(hass, event_type, data): {"type": event_type, "data": data}, ) - if event_type not in EVENT_ID_MAP: - return + event_data = { + "type": event_type, + "data": data, + } - data_device_id = data[EVENT_ID_MAP[event_type]] + if event_type in EVENT_ID_MAP: + data_device_id = data[EVENT_ID_MAP[event_type]] + event_data[ATTR_DEVICE_ID] = hass.data[DOMAIN][DATA_DEVICE_IDS].get( + data_device_id + ) hass.bus.async_fire( event_type=NETATMO_EVENT, - event_data={ - "type": event_type, - "data": data, - ATTR_DEVICE_ID: hass.data[DOMAIN][DATA_DEVICE_IDS].get(data_device_id), - }, + event_data=event_data, ) From 2178e27fb4c62271d4872e16838331defed82226 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 12 Mar 2021 23:17:27 +0100 Subject: [PATCH 335/831] Fix unclean shutdown of recorder test (#47791) --- tests/components/recorder/test_migrate.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/components/recorder/test_migrate.py b/tests/components/recorder/test_migrate.py index c29dad2d495..3bde17ab8ef 100644 --- a/tests/components/recorder/test_migrate.py +++ b/tests/components/recorder/test_migrate.py @@ -8,7 +8,8 @@ from sqlalchemy.exc import InternalError, OperationalError, ProgrammingError from sqlalchemy.pool import StaticPool from homeassistant.bootstrap import async_setup_component -from homeassistant.components.recorder import const, migration, models +from homeassistant.components.recorder import RecorderRuns, const, migration, models +import homeassistant.util.dt as dt_util from tests.components.recorder import models_original @@ -51,8 +52,16 @@ async def test_schema_migrate(hass): throwing exceptions. Maintaining a set of assertions based on schema inspection could quickly become quite cumbersome. """ + + def _mock_setup_run(self): + self.run_info = RecorderRuns( + start=self.recording_start, created=dt_util.utcnow() + ) + with patch("sqlalchemy.create_engine", new=create_engine_test), patch( - "homeassistant.components.recorder.Recorder._setup_run" + "homeassistant.components.recorder.Recorder._setup_run", + side_effect=_mock_setup_run, + autospec=True, ) as setup_run: await async_setup_component( hass, "recorder", {"recorder": {"db_url": "sqlite://"}} From 547fd7d352a42194ffc1de74bdc3ef4be35f00f1 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Fri, 12 Mar 2021 21:06:37 -0500 Subject: [PATCH 336/831] fix exception on device removal (#47803) --- homeassistant/components/zha/core/gateway.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index c57c7269723..3a65787cd48 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -347,7 +347,8 @@ class ZHAGateway: remove_tasks = [] for entity_ref in entity_refs: remove_tasks.append(entity_ref.remove_future) - await asyncio.wait(remove_tasks) + if remove_tasks: + await asyncio.wait(remove_tasks) reg_device = self.ha_device_registry.async_get(device.device_id) if reg_device is not None: self.ha_device_registry.async_remove_device(reg_device.id) From eccdf85b2992e279370ceedcfe4172fdb27fc58d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 12 Mar 2021 21:21:24 -0800 Subject: [PATCH 337/831] Bump frontend to 20210313.0 (#47844) --- 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 694be0382f7..a254c7d129a 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20210302.6" + "home-assistant-frontend==20210313.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 878834c5075..c604ab5c706 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.42.0 -home-assistant-frontend==20210302.6 +home-assistant-frontend==20210313.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 532318d5910..90e1746e524 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.6 +home-assistant-frontend==20210313.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b8259e517b6..de07ac79a55 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.6 +home-assistant-frontend==20210313.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 30f99177c7bb6a255558821c7f0518139cf0fc37 Mon Sep 17 00:00:00 2001 From: Raj Laud <50647620+rajlaud@users.noreply.github.com> Date: Sat, 13 Mar 2021 01:34:20 -0600 Subject: [PATCH 338/831] Fix missing integer cast in squeezebox config flow (#47846) --- homeassistant/components/squeezebox/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/squeezebox/config_flow.py b/homeassistant/components/squeezebox/config_flow.py index 9edff5f9a2a..cadded67bd1 100644 --- a/homeassistant/components/squeezebox/config_flow.py +++ b/homeassistant/components/squeezebox/config_flow.py @@ -80,7 +80,7 @@ class SqueezeboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.discovery_info = { CONF_HOST: server.host, - CONF_PORT: server.port, + CONF_PORT: int(server.port), "uuid": server.uuid, } _LOGGER.debug("Discovered server: %s", self.discovery_info) From 02a82d3f00c610f94d3366cc34540bdfa94a2c8e Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sat, 13 Mar 2021 01:53:26 -0800 Subject: [PATCH 339/831] Add timeouts in stream tests to prevent possible hangs (#47545) * Add timeouts on recving packets Add a timeout when recving packets from the worker thread in case it hangs. Add an exit condition just in case the while loop goes on forever. * Add a timeout to recorder thread join. * Wait for recorder thread to be invoked in tests Remove the while loop and instead wait for segments to be produced by the background worker thread. * Allow worker to resume before stopping to fix timeouts * Lower test timeout further * Remove test_stream_ended since it is flaky This test doesn't really add additional value on top of other tests. --- tests/components/stream/conftest.py | 1 + tests/components/stream/test_hls.py | 35 +---------------- tests/components/stream/test_recorder.py | 50 +++++++++++++----------- 3 files changed, 31 insertions(+), 55 deletions(-) diff --git a/tests/components/stream/conftest.py b/tests/components/stream/conftest.py index 1b017667ee6..ead2018b528 100644 --- a/tests/components/stream/conftest.py +++ b/tests/components/stream/conftest.py @@ -32,6 +32,7 @@ class WorkerSync: def resume(self): """Allow the worker thread to finalize the stream.""" + logging.debug("waking blocked worker") self._event.set() def blocking_finish(self, stream: Stream): diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index b554ee6b20a..ab0c21efdfb 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -20,6 +20,8 @@ from tests.components.stream.common import generate_h264_video STREAM_SOURCE = "some-stream-source" SEQUENCE_BYTES = io.BytesIO(b"some-bytes") DURATION = 10 +TEST_TIMEOUT = 5.0 # Lower than 9s home assistant timeout +MAX_ABORT_SEGMENTS = 20 # Abort test to avoid looping forever class HlsClient: @@ -187,39 +189,6 @@ async def test_stream_timeout_after_stop(hass, hass_client, stream_worker_sync): await hass.async_block_till_done() -async def test_stream_ended(hass, stream_worker_sync): - """Test hls stream packets ended.""" - await async_setup_component(hass, "stream", {"stream": {}}) - - stream_worker_sync.pause() - - # Setup demo HLS track - source = generate_h264_video() - stream = create_stream(hass, source) - track = stream.add_provider("hls") - - # Request stream - stream.add_provider("hls") - stream.start() - stream.endpoint_url("hls") - - # Run it dead - while True: - segment = await track.recv() - if segment is None: - break - segments = segment.sequence - # Allow worker to finalize once enough of the stream is been consumed - if segments > 1: - stream_worker_sync.resume() - - assert segments > 1 - assert not track.get_segment() - - # Stop stream, if it hasn't quit already - stream.stop() - - async def test_stream_keepalive(hass): """Test hls stream retries the stream when keepalive=True.""" await async_setup_component(hass, "stream", {"stream": {}}) diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py index 48fe48d3337..2b44c16243b 100644 --- a/tests/components/stream/test_recorder.py +++ b/tests/components/stream/test_recorder.py @@ -1,10 +1,13 @@ """The tests for hls streams.""" +import asyncio from datetime import timedelta import logging import os import threading +from typing import Deque from unittest.mock import patch +import async_timeout import av import pytest @@ -18,7 +21,8 @@ import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed from tests.components.stream.common import generate_h264_video -TEST_TIMEOUT = 10 +TEST_TIMEOUT = 7.0 # Lower than 9s home assistant timeout +MAX_ABORT_SEGMENTS = 20 # Abort test to avoid looping forever class SaveRecordWorkerSync: @@ -32,23 +36,33 @@ class SaveRecordWorkerSync: def __init__(self): """Initialize SaveRecordWorkerSync.""" self.reset() + self._segments = None - def recorder_save_worker(self, *args, **kwargs): + def recorder_save_worker(self, file_out: str, segments: Deque[Segment]): """Mock method for patch.""" logging.debug("recorder_save_worker thread started") assert self._save_thread is None + self._segments = segments self._save_thread = threading.current_thread() self._save_event.set() - def join(self): + async def get_segments(self): + """Return the recorded video segments.""" + with async_timeout.timeout(TEST_TIMEOUT): + await self._save_event.wait() + return self._segments + + async def join(self): """Verify save worker was invoked and block on shutdown.""" - assert self._save_event.wait(timeout=TEST_TIMEOUT) - self._save_thread.join() + with async_timeout.timeout(TEST_TIMEOUT): + await self._save_event.wait() + self._save_thread.join(timeout=TEST_TIMEOUT) + assert not self._save_thread.is_alive() def reset(self): """Reset callback state for reuse in tests.""" self._save_thread = None - self._save_event = threading.Event() + self._save_event = asyncio.Event() @pytest.fixture() @@ -63,7 +77,7 @@ def record_worker_sync(hass): yield sync -async def test_record_stream(hass, hass_client, stream_worker_sync, record_worker_sync): +async def test_record_stream(hass, hass_client, record_worker_sync): """ Test record stream. @@ -73,29 +87,21 @@ async def test_record_stream(hass, hass_client, stream_worker_sync, record_worke """ await async_setup_component(hass, "stream", {"stream": {}}) - stream_worker_sync.pause() - # Setup demo track source = generate_h264_video() stream = create_stream(hass, source) with patch.object(hass.config, "is_allowed_path", return_value=True): await stream.async_record("/example/path") - recorder = stream.add_provider("recorder") - while True: - segment = await recorder.recv() - if not segment: - break - segments = segment.sequence - if segments > 1: - stream_worker_sync.resume() - - stream.stop() - assert segments > 1 + # After stream decoding finishes, the record worker thread starts + segments = await record_worker_sync.get_segments() + assert len(segments) >= 1 # Verify that the save worker was invoked, then block until its # thread completes and is shutdown completely to avoid thread leaks. - record_worker_sync.join() + await record_worker_sync.join() + + stream.stop() async def test_record_lookback( @@ -250,4 +256,4 @@ async def test_record_stream_audio( # Verify that the save worker was invoked, then block until its # thread completes and is shutdown completely to avoid thread leaks. - record_worker_sync.join() + await record_worker_sync.join() From c7b9a0715d2a5ae1d6f7c2752da3d304fa763131 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sat, 13 Mar 2021 06:43:03 -0500 Subject: [PATCH 340/831] Fix zwave_js preset supported feature (#47819) --- homeassistant/components/zwave_js/climate.py | 4 +- tests/components/zwave_js/test_climate.py | 82 +------------------- 2 files changed, 7 insertions(+), 79 deletions(-) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index ceb46982949..16139683011 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -162,7 +162,9 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): add_to_watched_value_ids=True, ) self._set_modes_and_presets() - self._supported_features = SUPPORT_PRESET_MODE + self._supported_features = 0 + if len(self._hvac_presets) > 1: + self._supported_features |= SUPPORT_PRESET_MODE # If any setpoint value exists, we can assume temperature # can be set if any(self._setpoint_values.values()): diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index a31aad19603..637fb96f1ba 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -9,7 +9,6 @@ from homeassistant.components.climate.const import ( ATTR_HVAC_ACTION, ATTR_HVAC_MODE, ATTR_HVAC_MODES, - ATTR_PRESET_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_IDLE, @@ -19,13 +18,10 @@ from homeassistant.components.climate.const import ( HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, - PRESET_NONE, SERVICE_SET_FAN_MODE, SERVICE_SET_HVAC_MODE, - SERVICE_SET_PRESET_MODE, SERVICE_SET_TEMPERATURE, SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, ) @@ -63,51 +59,15 @@ async def test_thermostat_v2( assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.2 assert state.attributes[ATTR_TEMPERATURE] == 22.2 assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE - assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE assert state.attributes[ATTR_FAN_MODE] == "Auto low" assert state.attributes[ATTR_FAN_STATE] == "Idle / off" assert ( state.attributes[ATTR_SUPPORTED_FEATURES] - == SUPPORT_PRESET_MODE - | SUPPORT_TARGET_TEMPERATURE + == SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_RANGE | SUPPORT_FAN_MODE ) - # Test setting preset mode - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_PRESET_MODE, - { - ATTR_ENTITY_ID: CLIMATE_RADIO_THERMOSTAT_ENTITY, - ATTR_PRESET_MODE: PRESET_NONE, - }, - blocking=True, - ) - - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] - assert args["command"] == "node.set_value" - assert args["nodeId"] == 13 - assert args["valueId"] == { - "commandClassName": "Thermostat Mode", - "commandClass": 64, - "endpoint": 1, - "property": "mode", - "propertyName": "mode", - "metadata": { - "type": "number", - "readable": True, - "writeable": True, - "min": 0, - "max": 31, - "label": "Thermostat mode", - "states": {"0": "Off", "1": "Heat", "2": "Cool", "3": "Auto"}, - }, - "value": 1, - } - assert args["value"] == 1 - client.async_send_command_no_wait.reset_mock() # Test setting hvac mode @@ -314,20 +274,6 @@ async def test_thermostat_v2( client.async_send_command_no_wait.reset_mock() - with pytest.raises(ValueError): - # Test setting unknown preset mode - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_PRESET_MODE, - { - ATTR_ENTITY_ID: CLIMATE_RADIO_THERMOSTAT_ENTITY, - ATTR_PRESET_MODE: "unknown_preset", - }, - blocking=True, - ) - - assert len(client.async_send_command_no_wait.call_args_list) == 0 - # Test setting invalid hvac mode with pytest.raises(ValueError): await hass.services.async_call( @@ -340,18 +286,6 @@ async def test_thermostat_v2( blocking=True, ) - # Test setting invalid preset mode - with pytest.raises(ValueError): - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_PRESET_MODE, - { - ATTR_ENTITY_ID: CLIMATE_RADIO_THERMOSTAT_ENTITY, - ATTR_PRESET_MODE: "invalid_mode", - }, - blocking=True, - ) - client.async_send_command_no_wait.reset_mock() # Test setting fan mode @@ -422,11 +356,7 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat assert state.state == HVAC_MODE_HEAT assert state.attributes[ATTR_TEMPERATURE] == 14 assert state.attributes[ATTR_HVAC_MODES] == [HVAC_MODE_HEAT] - assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE - assert ( - state.attributes[ATTR_SUPPORTED_FEATURES] - == SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE - ) + assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_TARGET_TEMPERATURE client.async_send_command_no_wait.reset_mock() @@ -509,11 +439,7 @@ async def test_thermostat_heatit(hass, client, climate_heatit_z_trm3, integratio assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.9 assert state.attributes[ATTR_TEMPERATURE] == 22.5 assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE - assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE - assert ( - state.attributes[ATTR_SUPPORTED_FEATURES] - == SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE - ) + assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_TARGET_TEMPERATURE async def test_thermostat_srt321_hrt4_zw(hass, client, srt321_hrt4_zw, integration): @@ -530,4 +456,4 @@ async def test_thermostat_srt321_hrt4_zw(hass, client, srt321_hrt4_zw, integrati HVAC_MODE_HEAT, ] assert state.attributes[ATTR_CURRENT_TEMPERATURE] is None - assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_PRESET_MODE + assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0 From eddb97b6fdb3d84b67717d850c46b658d390dab3 Mon Sep 17 00:00:00 2001 From: tdorsey Date: Sat, 13 Mar 2021 15:26:48 -0500 Subject: [PATCH 341/831] Fix spelling of automatically in roomba/lutron_caseta components (#47856) --- homeassistant/components/lutron_caseta/translations/en.json | 4 ++-- homeassistant/components/roomba/translations/en.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/lutron_caseta/translations/en.json b/homeassistant/components/lutron_caseta/translations/en.json index 96c00d6cb42..48b305ac7fa 100644 --- a/homeassistant/components/lutron_caseta/translations/en.json +++ b/homeassistant/components/lutron_caseta/translations/en.json @@ -23,7 +23,7 @@ "host": "Host" }, "description": "Enter the IP address of the device.", - "title": "Automaticlly connect to the bridge" + "title": "Automatically connect to the bridge" } } }, @@ -73,4 +73,4 @@ "release": "\"{subtype}\" released" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/roomba/translations/en.json b/homeassistant/components/roomba/translations/en.json index 9c373d649aa..11c9c6e27fd 100644 --- a/homeassistant/components/roomba/translations/en.json +++ b/homeassistant/components/roomba/translations/en.json @@ -15,7 +15,7 @@ "host": "Host" }, "description": "Select a Roomba or Braava.", - "title": "Automaticlly connect to the device" + "title": "Automatically connect to the device" }, "link": { "description": "Press and hold the Home button on {name} until the device generates a sound (about two seconds).", @@ -59,4 +59,4 @@ } } } -} \ No newline at end of file +} From 263023a152f1885ebbeaa67a8f20e61e8ab5a62b Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Sat, 13 Mar 2021 20:32:38 +0000 Subject: [PATCH 342/831] Update aiolyric to v1.0.6 (#47871) --- homeassistant/components/lyric/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lyric/manifest.json b/homeassistant/components/lyric/manifest.json index 460eb6e2a3d..6aa028e2636 100644 --- a/homeassistant/components/lyric/manifest.json +++ b/homeassistant/components/lyric/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/lyric", "dependencies": ["http"], - "requirements": ["aiolyric==1.0.5"], + "requirements": ["aiolyric==1.0.6"], "codeowners": ["@timmo001"], "quality_scale": "silver", "dhcp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 90e1746e524..eb196269990 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -197,7 +197,7 @@ aiolifx_effects==0.2.2 aiolip==1.1.4 # homeassistant.components.lyric -aiolyric==1.0.5 +aiolyric==1.0.6 # homeassistant.components.keyboard_remote aionotify==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index de07ac79a55..d0215a056db 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -119,7 +119,7 @@ aiokafka==0.6.0 aiolip==1.1.4 # homeassistant.components.lyric -aiolyric==1.0.5 +aiolyric==1.0.6 # homeassistant.components.notion aionotion==1.1.0 From 518c86a0ab68c6a96319132ae0f274790aa83aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20St=C3=A5hl?= Date: Sat, 13 Mar 2021 21:39:04 +0100 Subject: [PATCH 343/831] Add device_info to Apple TV entities (#47837) --- homeassistant/components/apple_tv/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index 6460a501992..6a78e0b451c 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -151,6 +151,13 @@ class AppleTVEntity(Entity): """No polling needed for Apple TV.""" return False + @property + def device_info(self): + """Return the device info.""" + return { + "identifiers": {(DOMAIN, self._identifier)}, + } + class AppleTVManager: """Connection and power manager for an Apple TV. From 7d8eb49d883c43d370e768b53ace4d663b720686 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sat, 13 Mar 2021 16:06:07 -0500 Subject: [PATCH 344/831] Bump up ZHA dependency (#47873) --- 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 df3c0fcfda9..5c66e2cf605 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zha", "requirements": [ - "bellows==0.22.0", + "bellows==0.23.0", "pyserial==3.5", "pyserial-asyncio==0.5", "zha-quirks==0.0.54", diff --git a/requirements_all.txt b/requirements_all.txt index eb196269990..607dc94f5af 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -340,7 +340,7 @@ beautifulsoup4==4.9.3 # beewi_smartclim==0.0.10 # homeassistant.components.zha -bellows==0.22.0 +bellows==0.23.0 # homeassistant.components.bmw_connected_drive bimmer_connected==0.7.15 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d0215a056db..e53a94f0cd9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -193,7 +193,7 @@ azure-eventhub==5.1.0 base36==0.1.1 # homeassistant.components.zha -bellows==0.22.0 +bellows==0.23.0 # homeassistant.components.bmw_connected_drive bimmer_connected==0.7.15 From c53a462b3db6165a9b6ce20acd6c0f5130384bf4 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 13 Mar 2021 22:27:05 +0100 Subject: [PATCH 345/831] Fix zwave_js preset mode lookup (#47851) --- homeassistant/components/zwave_js/climate.py | 2 +- tests/components/zwave_js/common.py | 1 + tests/components/zwave_js/conftest.py | 14 + tests/components/zwave_js/test_climate.py | 121 +++ .../climate_eurotronic_spirit_z_state.json | 716 ++++++++++++++++++ 5 files changed, 853 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/zwave_js/climate_eurotronic_spirit_z_state.json diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 16139683011..9276ec2ed0b 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -304,7 +304,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): return None if self._current_mode and int(self._current_mode.value) not in THERMOSTAT_MODES: return_val: str = self._current_mode.metadata.states.get( - self._current_mode.value + str(self._current_mode.value) ) return return_val return PRESET_NONE diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index ec54e139404..f90a2e48ffa 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -15,6 +15,7 @@ PROPERTY_DOOR_STATUS_BINARY_SENSOR = ( ) CLIMATE_RADIO_THERMOSTAT_ENTITY = "climate.z_wave_thermostat" CLIMATE_DANFOSS_LC13_ENTITY = "climate.living_connect_z_thermostat" +CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY = "climate.thermostatic_valve" CLIMATE_FLOOR_THERMOSTAT_ENTITY = "climate.floor_thermostat" CLIMATE_MAIN_HEAT_ACTIONNER = "climate.main_heat_actionner" BULB_6_MULTI_COLOR_LIGHT_ENTITY = "light.bulb_6_multi_color" diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index c9f7d35eb13..fa7df4e16a1 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -228,6 +228,12 @@ def climate_danfoss_lc_13_state_fixture(): return json.loads(load_fixture("zwave_js/climate_danfoss_lc_13_state.json")) +@pytest.fixture(name="climate_eurotronic_spirit_z_state", scope="session") +def climate_eurotronic_spirit_z_state_fixture(): + """Load the climate Eurotronic Spirit Z thermostat node state fixture data.""" + return json.loads(load_fixture("zwave_js/climate_eurotronic_spirit_z_state.json")) + + @pytest.fixture(name="climate_heatit_z_trm3_state", scope="session") def climate_heatit_z_trm3_state_fixture(): """Load the climate HEATIT Z-TRM3 thermostat node state fixture data.""" @@ -419,6 +425,14 @@ def climate_danfoss_lc_13_fixture(client, climate_danfoss_lc_13_state): return node +@pytest.fixture(name="climate_eurotronic_spirit_z") +def climate_eurotronic_spirit_z_fixture(client, climate_eurotronic_spirit_z_state): + """Mock a climate radio danfoss LC-13 node.""" + node = Node(client, climate_eurotronic_spirit_z_state) + client.driver.controller.nodes[node.node_id] = node + return node + + @pytest.fixture(name="climate_heatit_z_trm3") def climate_heatit_z_trm3_fixture(client, climate_heatit_z_trm3_state): """Mock a climate radio HEATIT Z-TRM3 node.""" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index 637fb96f1ba..c69804b0158 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -9,6 +9,7 @@ from homeassistant.components.climate.const import ( ATTR_HVAC_ACTION, ATTR_HVAC_MODE, ATTR_HVAC_MODES, + ATTR_PRESET_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_IDLE, @@ -18,8 +19,10 @@ from homeassistant.components.climate.const import ( HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, + PRESET_NONE, SERVICE_SET_FAN_MODE, SERVICE_SET_HVAC_MODE, + SERVICE_SET_PRESET_MODE, SERVICE_SET_TEMPERATURE, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE, @@ -34,6 +37,7 @@ from homeassistant.const import ( from .common import ( CLIMATE_DANFOSS_LC13_ENTITY, + CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY, CLIMATE_FLOOR_THERMOSTAT_ENTITY, CLIMATE_MAIN_HEAT_ACTIONNER, CLIMATE_RADIO_THERMOSTAT_ENTITY, @@ -457,3 +461,120 @@ async def test_thermostat_srt321_hrt4_zw(hass, client, srt321_hrt4_zw, integrati ] assert state.attributes[ATTR_CURRENT_TEMPERATURE] is None assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0 + + +async def test_preset_and_no_setpoint( + hass, client, climate_eurotronic_spirit_z, integration +): + """Test preset without setpoint value.""" + node = climate_eurotronic_spirit_z + + state = hass.states.get(CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY) + assert state + assert state.state == HVAC_MODE_HEAT + assert state.attributes[ATTR_TEMPERATURE] == 22 + + # Test setting preset mode Full power + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + { + ATTR_ENTITY_ID: CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY, + ATTR_PRESET_MODE: "Full power", + }, + blocking=True, + ) + + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 8 + assert args["valueId"] == { + "commandClassName": "Thermostat Mode", + "commandClass": 64, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "min": 0, + "max": 31, + "label": "Thermostat mode", + "states": { + "0": "Off", + "1": "Heat", + "11": "Energy heat", + "15": "Full power", + }, + }, + "value": 1, + } + assert args["value"] == 15 + + client.async_send_command_no_wait.reset_mock() + + # Test Full power preset update from value updated event + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 8, + "args": { + "commandClassName": "Thermostat Mode", + "commandClass": 64, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": 15, + "prevValue": 1, + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY) + assert state.state == HVAC_MODE_HEAT + assert state.attributes[ATTR_TEMPERATURE] is None + assert state.attributes[ATTR_PRESET_MODE] == "Full power" + + with pytest.raises(ValueError): + # Test setting invalid preset mode + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + { + ATTR_ENTITY_ID: CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY, + ATTR_PRESET_MODE: "invalid_preset", + }, + blocking=True, + ) + + assert len(client.async_send_command_no_wait.call_args_list) == 0 + + client.async_send_command_no_wait.reset_mock() + + # Restore hvac mode by setting preset None + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + { + ATTR_ENTITY_ID: CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY, + ATTR_PRESET_MODE: PRESET_NONE, + }, + blocking=True, + ) + + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 8 + assert args["valueId"]["commandClass"] == 64 + assert args["valueId"]["endpoint"] == 0 + assert args["valueId"]["property"] == "mode" + assert args["value"] == 1 + + client.async_send_command_no_wait.reset_mock() diff --git a/tests/fixtures/zwave_js/climate_eurotronic_spirit_z_state.json b/tests/fixtures/zwave_js/climate_eurotronic_spirit_z_state.json new file mode 100644 index 00000000000..8dff31a5225 --- /dev/null +++ b/tests/fixtures/zwave_js/climate_eurotronic_spirit_z_state.json @@ -0,0 +1,716 @@ +{ + "nodeId": 8, + "index": 0, + "installerIcon": 4608, + "userIcon": 4608, + "status": 4, + "ready": true, + "deviceClass": { + "basic": { + "key": 4, + "label": "Routing Slave" + }, + "generic": { + "key": 8, + "label": "Thermostat" + }, + "specific": { + "key": 6, + "label": "Thermostat General V2" + }, + "mandatorySupportedCCs": [ + "Basic", + "Manufacturer Specific", + "Thermostat Mode", + "Thermostat Setpoint", + "Version" + ], + "mandatoryControlCCs": [] + }, + "isListening": false, + "isFrequentListening": true, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 4, + "isBeaming": true, + "manufacturerId": 328, + "productId": 1, + "productType": 3, + "firmwareVersion": "0.16", + "zwavePlusVersion": 1, + "nodeType": 0, + "roleType": 7, + "deviceConfig": { + "manufacturerId": 328, + "manufacturer": "Eurotronics", + "label": "Spirit", + "description": "Thermostatic Valve", + "devices": [ + { + "productType": "0x0003", + "productId": "0x0001" + }, + { + "productType": "0x0003", + "productId": "0x0003" + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + }, + "paramInformation": { + "_map": {} + } + }, + "label": "Spirit", + "neighbors": [ + 1, + 5, + 9, + 10, + 12, + 18, + 20, + 21, + 22 + ], + "interviewAttempts": 1, + "endpoints": [ + { + "nodeId": 8, + "index": 0, + "installerIcon": 4608, + "userIcon": 4608 + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 99, + "label": "Target value" + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "duration", + "propertyName": "duration", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Transition duration" + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 99, + "label": "Current value" + }, + "value": 8 + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Up", + "propertyName": "Up", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Up)", + "ccSpecific": { + "switchType": 2 + } + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Down", + "propertyName": "Down", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Down)", + "ccSpecific": { + "switchType": 2 + } + } + }, + { + "endpoint": 0, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Air temperature", + "propertyName": "Air temperature", + "ccVersion": 5, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "°C", + "label": "Air temperature", + "ccSpecific": { + "sensorType": 1, + "scale": 0 + } + }, + "value": 23.73 + }, + { + "endpoint": 0, + "commandClass": 64, + "commandClassName": "Thermostat Mode", + "property": "mode", + "propertyName": "mode", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 31, + "label": "Thermostat mode", + "states": { + "0": "Off", + "1": "Heat", + "11": "Energy heat", + "15": "Full power" + } + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 64, + "commandClassName": "Thermostat Mode", + "property": "manufacturerData", + "propertyName": "manufacturerData", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + } + }, + { + "endpoint": 0, + "commandClass": 67, + "commandClassName": "Thermostat Setpoint", + "property": "setpoint", + "propertyKey": 1, + "propertyName": "setpoint", + "propertyKeyName": "Heating", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 8, + "max": 28, + "unit": "°C", + "ccSpecific": { + "setpointType": 1 + } + }, + "value": 22 + }, + { + "endpoint": 0, + "commandClass": 67, + "commandClassName": "Thermostat Setpoint", + "property": "setpoint", + "propertyKey": 11, + "propertyName": "setpoint", + "propertyKeyName": "Energy Save Heating", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 8, + "max": 28, + "unit": "°C", + "ccSpecific": { + "setpointType": 11 + } + }, + "value": 18 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 1, + "propertyName": "LCD Invert", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "LCD-content normal", + "1": "LCD-content inverted (UK Edition)" + }, + "label": "LCD Invert", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 2, + "propertyName": "LCD Timeout", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 30, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "LCD Timeout", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 3, + "propertyName": "Backlight", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 1, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Backlight disabled", + "1": "Backlight enabled" + }, + "label": "Backlight", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 4, + "propertyName": "Battery report", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 1, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "system notification", + "1": "Send battery status unsolicited once a day." + }, + "label": "Battery report", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 5, + "propertyName": "Measured Temperature report", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 50, + "default": 5, + "format": 0, + "allowManualEntry": true, + "label": "Measured Temperature report", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 6, + "propertyName": "Valve opening percentage report", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "Valve opening percentage report", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 7, + "propertyName": "Window open detection", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 3, + "default": 2, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Disabled", + "1": "Sensitivity low", + "2": "Sensitivity medium", + "3": "Sensitivity high" + }, + "label": "Window open detection", + "isFromConfig": true + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 8, + "propertyName": "Temperature Offset", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": -128, + "max": 50, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "Temperature Offset", + "description": "Measured Temperature offset", + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 113, + "commandClassName": "Notification", + "property": "alarmType", + "propertyName": "alarmType", + "ccVersion": 8, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Alarm Type" + } + }, + { + "endpoint": 0, + "commandClass": 113, + "commandClassName": "Notification", + "property": "alarmLevel", + "propertyName": "alarmLevel", + "ccVersion": 8, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Alarm Level" + } + }, + { + "endpoint": 0, + "commandClass": 113, + "commandClassName": "Notification", + "property": "Power Management", + "propertyKey": "Battery maintenance status", + "propertyName": "Power Management", + "propertyKeyName": "Battery maintenance status", + "ccVersion": 8, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Battery maintenance status", + "states": { + "0": "idle", + "10": "Replace battery soon", + "11": "Replace battery now" + }, + "ccSpecific": { + "notificationType": 8 + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 113, + "commandClassName": "Notification", + "property": "System", + "propertyKey": "Hardware status", + "propertyName": "System", + "propertyKeyName": "Hardware status", + "ccVersion": 8, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Hardware status", + "states": { + "0": "idle", + "3": "System hardware failure (with failure code)" + }, + "ccSpecific": { + "notificationType": 9 + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 328 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "local", + "propertyName": "local", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Local protection state", + "states": { + "0": "Unprotected", + "1": "ProtectedBySequence", + "2": "NoOperationPossible" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 128, + "commandClassName": "Battery", + "property": "level", + "propertyName": "level", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 100, + "unit": "%", + "label": "Battery level" + }, + "value": 90 + }, + { + "endpoint": 0, + "commandClass": 128, + "commandClassName": "Battery", + "property": "isLow", + "propertyName": "isLow", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Low battery level" + }, + "value": false + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "4.61" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": [ + "0.16" + ] + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version" + } + } + ] +} From 47114c5f4f2574eb87186ca5ce655908b6544345 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Sun, 14 Mar 2021 08:04:49 +0000 Subject: [PATCH 346/831] Update service config for lyric (#47857) --- homeassistant/components/lyric/services.yaml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lyric/services.yaml b/homeassistant/components/lyric/services.yaml index b4ea74a9644..69c802d90aa 100644 --- a/homeassistant/components/lyric/services.yaml +++ b/homeassistant/components/lyric/services.yaml @@ -1,9 +1,18 @@ set_hold_time: + name: Set Hold Time description: "Sets the time to hold until" + target: + device: + integration: lyric + entity: + integration: lyric + domain: climate fields: - entity_id: - description: Name(s) of entities to change - example: "climate.thermostat" time_period: + name: Time Period description: Time to hold until + default: "01:00:00" example: "01:00:00" + required: true + selector: + text: From 984f02882bb867143cbf732ebfde0dc1ef85911d Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Sun, 14 Mar 2021 08:05:47 +0000 Subject: [PATCH 347/831] Add HVAC action to Lyric climate platform (#47876) --- homeassistant/components/lyric/climate.py | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/homeassistant/components/lyric/climate.py b/homeassistant/components/lyric/climate.py index 41e8fa90b67..6424083158a 100644 --- a/homeassistant/components/lyric/climate.py +++ b/homeassistant/components/lyric/climate.py @@ -11,6 +11,10 @@ from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, + CURRENT_HVAC_COOL, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, @@ -41,6 +45,10 @@ _LOGGER = logging.getLogger(__name__) SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE +LYRIC_HVAC_ACTION_OFF = "EquipmentOff" +LYRIC_HVAC_ACTION_HEAT = "Heat" +LYRIC_HVAC_ACTION_COOL = "Cool" + LYRIC_HVAC_MODE_OFF = "Off" LYRIC_HVAC_MODE_HEAT = "Heat" LYRIC_HVAC_MODE_COOL = "Cool" @@ -60,6 +68,20 @@ HVAC_MODES = { LYRIC_HVAC_MODE_HEAT_COOL: HVAC_MODE_HEAT_COOL, } + +HVAC_ACTIONS = { + LYRIC_HVAC_ACTION_OFF: CURRENT_HVAC_OFF, + LYRIC_HVAC_ACTION_HEAT: CURRENT_HVAC_HEAT, + LYRIC_HVAC_ACTION_COOL: CURRENT_HVAC_COOL, +} + +HVAC_MODES = { + LYRIC_HVAC_MODE_OFF: HVAC_MODE_OFF, + LYRIC_HVAC_MODE_HEAT: HVAC_MODE_HEAT, + LYRIC_HVAC_MODE_COOL: HVAC_MODE_COOL, + LYRIC_HVAC_MODE_HEAT_COOL: HVAC_MODE_HEAT_COOL, +} + SERVICE_HOLD_TIME = "set_hold_time" ATTR_TIME_PERIOD = "time_period" @@ -152,6 +174,14 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity): """Return the current temperature.""" return self.device.indoorTemperature + @property + def hvac_action(self) -> str: + """Return the current hvac action.""" + action = HVAC_ACTIONS.get(self.device.operationStatus.mode, None) + if action == CURRENT_HVAC_OFF and self.hvac_mode != HVAC_MODE_OFF: + action = CURRENT_HVAC_IDLE + return action + @property def hvac_mode(self) -> str: """Return the hvac mode.""" From 60838cf7edd82c9165dd323325148bd35969ecbc Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 14 Mar 2021 10:38:09 +0100 Subject: [PATCH 348/831] Verisure: Remove JSONPath, unique IDs, small cleanups (#47870) Co-authored-by: Martin Hjelmare --- homeassistant/components/verisure/__init__.py | 137 +++--------------- .../verisure/alarm_control_panel.py | 14 +- .../components/verisure/binary_sensor.py | 51 ++++--- homeassistant/components/verisure/camera.py | 50 ++++--- .../components/verisure/coordinator.py | 126 ++++++++++++++++ homeassistant/components/verisure/lock.py | 42 ++---- .../components/verisure/manifest.json | 2 +- homeassistant/components/verisure/sensor.py | 121 +++++++--------- homeassistant/components/verisure/switch.py | 32 ++-- requirements_all.txt | 1 - requirements_test_all.txt | 1 - 11 files changed, 287 insertions(+), 290 deletions(-) create mode 100644 homeassistant/components/verisure/coordinator.py diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index 00d970f133f..16250915f45 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -1,30 +1,27 @@ """Support for Verisure devices.""" from __future__ import annotations -from datetime import timedelta -from typing import Any, Literal - -from jsonpath import jsonpath -from verisure import ( - Error as VerisureError, - ResponseError as VerisureResponseError, - Session as Verisure, -) +from verisure import Error as VerisureError import voluptuous as vol +from homeassistant.components.alarm_control_panel import ( + DOMAIN as ALARM_CONTROL_PANEL_DOMAIN, +) +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN +from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN +from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import ( CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP, - HTTP_SERVICE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from homeassistant.util import Throttle from .const import ( ATTR_DEVICE_SERIAL, @@ -47,14 +44,15 @@ from .const import ( SERVICE_DISABLE_AUTOLOCK, SERVICE_ENABLE_AUTOLOCK, ) +from .coordinator import VerisureDataUpdateCoordinator PLATFORMS = [ - "sensor", - "switch", - "alarm_control_panel", - "lock", - "camera", - "binary_sensor", + ALARM_CONTROL_PANEL_DOMAIN, + BINARY_SENSOR_DOMAIN, + CAMERA_DOMAIN, + LOCK_DOMAIN, + SENSOR_DOMAIN, + SWITCH_DOMAIN, ] CONFIG_SCHEMA = vol.Schema( @@ -88,18 +86,13 @@ DEVICE_SERIAL_SCHEMA = vol.Schema({vol.Required(ATTR_DEVICE_SERIAL): cv.string}) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Verisure integration.""" - verisure = Verisure(config[DOMAIN][CONF_USERNAME], config[DOMAIN][CONF_PASSWORD]) - coordinator = VerisureDataUpdateCoordinator( - hass, session=verisure, domain_config=config[DOMAIN] - ) + coordinator = VerisureDataUpdateCoordinator(hass, config=config[DOMAIN]) - if not await hass.async_add_executor_job(coordinator.login): + if not await coordinator.async_login(): LOGGER.error("Login failed") return False - hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, lambda event: coordinator.logout() - ) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, coordinator.async_logout) await coordinator.async_refresh() if not coordinator.last_update_success: @@ -152,95 +145,3 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: DOMAIN, SERVICE_ENABLE_AUTOLOCK, enable_autolock, schema=DEVICE_SERIAL_SCHEMA ) return True - - -class VerisureDataUpdateCoordinator(DataUpdateCoordinator): - """A Verisure Data Update Coordinator.""" - - def __init__( - self, hass: HomeAssistant, domain_config: ConfigType, session: Verisure - ) -> None: - """Initialize the Verisure hub.""" - self.imageseries = {} - self.config = domain_config - self.giid = domain_config.get(CONF_GIID) - - self.session = session - - super().__init__( - hass, LOGGER, name=DOMAIN, update_interval=domain_config[CONF_SCAN_INTERVAL] - ) - - def login(self) -> bool: - """Login to Verisure.""" - try: - self.session.login() - except VerisureError as ex: - LOGGER.error("Could not log in to verisure, %s", ex) - return False - if self.giid: - return self.set_giid() - return True - - def logout(self) -> bool: - """Logout from Verisure.""" - try: - self.session.logout() - except VerisureError as ex: - LOGGER.error("Could not log out from verisure, %s", ex) - return False - return True - - def set_giid(self) -> bool: - """Set installation GIID.""" - try: - self.session.set_giid(self.giid) - except VerisureError as ex: - LOGGER.error("Could not set installation GIID, %s", ex) - return False - return True - - async def _async_update_data(self) -> dict: - """Fetch data from Verisure.""" - try: - return await self.hass.async_add_executor_job(self.session.get_overview) - except VerisureResponseError as ex: - LOGGER.error("Could not read overview, %s", ex) - if ex.status_code == HTTP_SERVICE_UNAVAILABLE: # Service unavailable - LOGGER.info("Trying to log in again") - await self.hass.async_add_executor_job(self.login) - return {} - raise - - @Throttle(timedelta(seconds=60)) - def update_smartcam_imageseries(self) -> None: - """Update the image series.""" - self.imageseries = self.session.get_camera_imageseries() - - @Throttle(timedelta(seconds=30)) - def smartcam_capture(self, device_id: str) -> None: - """Capture a new image from a smartcam.""" - self.session.capture_image(device_id) - - def disable_autolock(self, device_id: str) -> None: - """Disable autolock.""" - self.session.set_lock_config(device_id, auto_lock_enabled=False) - - def enable_autolock(self, device_id: str) -> None: - """Enable autolock.""" - self.session.set_lock_config(device_id, auto_lock_enabled=True) - - def get(self, jpath: str, *args) -> list[Any] | Literal[False]: - """Get values from the overview that matches the jsonpath.""" - res = jsonpath(self.data, jpath % args) - return res or [] - - def get_first(self, jpath: str, *args) -> Any | None: - """Get first value from the overview that matches the jsonpath.""" - res = self.get(jpath, *args) - return res[0] if res else None - - def get_image_info(self, jpath: str, *args) -> list[Any] | Literal[False]: - """Get values from the imageseries that matches the jsonpath.""" - res = jsonpath(self.imageseries, jpath % args) - return res or [] diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index d0a93fb45f9..94fbfe69bd0 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -22,8 +22,8 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import VerisureDataUpdateCoordinator from .const import CONF_ALARM, CONF_GIID, DOMAIN, LOGGER +from .coordinator import VerisureDataUpdateCoordinator def setup_platform( @@ -56,19 +56,19 @@ class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity): giid = self.coordinator.config.get(CONF_GIID) if giid is not None: aliass = { - i["giid"]: i["alias"] for i in self.coordinator.session.installations + i["giid"]: i["alias"] for i in self.coordinator.verisure.installations } if giid in aliass: return "{} alarm".format(aliass[giid]) LOGGER.error("Verisure installation giid not found: %s", giid) - return "{} alarm".format(self.coordinator.session.installations[0]["alias"]) + return "{} alarm".format(self.coordinator.verisure.installations[0]["alias"]) @property def state(self) -> str | None: """Return the state of the device.""" - status = self.coordinator.get_first("$.armState.statusType") + status = self.coordinator.data["alarm"]["statusType"] if status == "DISARMED": self._state = STATE_ALARM_DISARMED elif status == "ARMED_HOME": @@ -95,19 +95,19 @@ class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity): @property def changed_by(self) -> str | None: """Return the last change triggered by.""" - return self.coordinator.get_first("$.armState.name") + return self.coordinator.data["alarm"]["name"] async def _async_set_arm_state(self, state: str, code: str | None = None) -> None: """Send set arm state command.""" arm_state = await self.hass.async_add_executor_job( - self.coordinator.session.set_arm_state, code, state + self.coordinator.verisure.set_arm_state, code, state ) LOGGER.debug("Verisure set arm state %s", state) transaction = {} while "result" not in transaction: await asyncio.sleep(0.5) transaction = await self.hass.async_add_executor_job( - self.coordinator.session.get_arm_state_transaction, + self.coordinator.verisure.get_arm_state_transaction, arm_state["armStateChangeTransactionId"], ) diff --git a/homeassistant/components/verisure/binary_sensor.py b/homeassistant/components/verisure/binary_sensor.py index bdefa2af858..66eb5031072 100644 --- a/homeassistant/components/verisure/binary_sensor.py +++ b/homeassistant/components/verisure/binary_sensor.py @@ -5,33 +5,32 @@ from typing import Any, Callable from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_OPENING, BinarySensorEntity, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import CONF_DOOR_WINDOW, DOMAIN, VerisureDataUpdateCoordinator +from . import CONF_DOOR_WINDOW, DOMAIN +from .coordinator import VerisureDataUpdateCoordinator def setup_platform( hass: HomeAssistant, config: dict[str, Any], - add_entities: Callable[[list[Entity], bool], None], + add_entities: Callable[[list[CoordinatorEntity]], None], discovery_info: dict[str, Any] | None = None, ) -> None: """Set up the Verisure binary sensors.""" coordinator = hass.data[DOMAIN] - sensors = [VerisureEthernetStatus(coordinator)] + sensors: list[CoordinatorEntity] = [VerisureEthernetStatus(coordinator)] if int(coordinator.config.get(CONF_DOOR_WINDOW, 1)): sensors.extend( [ - VerisureDoorWindowSensor(coordinator, device_label) - for device_label in coordinator.get( - "$.doorWindow.doorWindowDevice[*].deviceLabel" - ) + VerisureDoorWindowSensor(coordinator, serial_number) + for serial_number in coordinator.data["door_window"] ] ) @@ -44,40 +43,40 @@ class VerisureDoorWindowSensor(CoordinatorEntity, BinarySensorEntity): coordinator: VerisureDataUpdateCoordinator def __init__( - self, coordinator: VerisureDataUpdateCoordinator, device_label: str + self, coordinator: VerisureDataUpdateCoordinator, serial_number: str ) -> None: """Initialize the Verisure door window sensor.""" super().__init__(coordinator) - self._device_label = device_label + self.serial_number = serial_number @property def name(self) -> str: """Return the name of the binary sensor.""" - return self.coordinator.get_first( - "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].area", - self._device_label, - ) + return self.coordinator.data["door_window"][self.serial_number]["area"] + + @property + def unique_id(self) -> str: + """Return the unique ID for this alarm control panel.""" + return f"{self.serial_number}_door_window" + + @property + def device_class(self) -> str: + """Return the class of this device, from component DEVICE_CLASSES.""" + return DEVICE_CLASS_OPENING @property def is_on(self) -> bool: """Return the state of the sensor.""" return ( - self.coordinator.get_first( - "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].state", - self._device_label, - ) - == "OPEN" + self.coordinator.data["door_window"][self.serial_number]["state"] == "OPEN" ) @property def available(self) -> bool: """Return True if entity is available.""" return ( - self.coordinator.get_first( - "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')]", - self._device_label, - ) - is not None + super().available + and self.serial_number in self.coordinator.data["door_window"] ) @@ -94,12 +93,12 @@ class VerisureEthernetStatus(CoordinatorEntity, BinarySensorEntity): @property def is_on(self) -> bool: """Return the state of the sensor.""" - return self.coordinator.get_first("$.ethernetConnectedNow") + return self.coordinator.data["ethernet"] @property def available(self) -> bool: """Return True if entity is available.""" - return self.coordinator.get_first("$.ethernetConnectedNow") is not None + return super().available and self.coordinator.data["ethernet"] is not None @property def device_class(self) -> str: diff --git a/homeassistant/components/verisure/camera.py b/homeassistant/components/verisure/camera.py index 4e15b7a88b2..6f22b17b848 100644 --- a/homeassistant/components/verisure/camera.py +++ b/homeassistant/components/verisure/camera.py @@ -8,33 +8,28 @@ from typing import Any, Callable from homeassistant.components.camera import Camera from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import VerisureDataUpdateCoordinator from .const import CONF_SMARTCAM, DOMAIN, LOGGER +from .coordinator import VerisureDataUpdateCoordinator def setup_platform( hass: HomeAssistant, config: dict[str, Any], - add_entities: Callable[[list[Entity], bool], None], + add_entities: Callable[[list[VerisureSmartcam]], None], discovery_info: dict[str, Any] | None = None, ) -> None: """Set up the Verisure Camera.""" - coordinator = hass.data[DOMAIN] + coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN] if not int(coordinator.config.get(CONF_SMARTCAM, 1)): return - directory_path = hass.config.config_dir - if not os.access(directory_path, os.R_OK): - LOGGER.error("file path %s is not readable", directory_path) - return - + assert hass.config.config_dir add_entities( [ - VerisureSmartcam(hass, coordinator, device_label, directory_path) - for device_label in coordinator.get("$.customerImageCameras[*].deviceLabel") + VerisureSmartcam(hass, coordinator, serial_number, hass.config.config_dir) + for serial_number in coordinator.data["cameras"] ] ) @@ -48,13 +43,13 @@ class VerisureSmartcam(CoordinatorEntity, Camera): self, hass: HomeAssistant, coordinator: VerisureDataUpdateCoordinator, - device_label: str, + serial_number: str, directory_path: str, ): """Initialize Verisure File Camera component.""" super().__init__(coordinator) - self._device_label = device_label + self.serial_number = serial_number self._directory_path = directory_path self._image = None self._image_id = None @@ -73,21 +68,27 @@ class VerisureSmartcam(CoordinatorEntity, Camera): def check_imagelist(self) -> None: """Check the contents of the image list.""" self.coordinator.update_smartcam_imageseries() - image_ids = self.coordinator.get_image_info( - "$.imageSeries[?(@.deviceLabel=='%s')].image[0].imageId", self._device_label - ) - if not image_ids: + + images = self.coordinator.imageseries.get("imageSeries", []) + new_image_id = None + for image in images: + if image["deviceLabel"] == self.serial_number: + new_image_id = image["image"][0]["imageId"] + break + + if not new_image_id: return - new_image_id = image_ids[0] + if new_image_id in ("-1", self._image_id): LOGGER.debug("The image is the same, or loading image_id") return + LOGGER.debug("Download new image %s", new_image_id) new_image_path = os.path.join( self._directory_path, "{}{}".format(new_image_id, ".jpg") ) - self.coordinator.session.download_image( - self._device_label, new_image_id, new_image_path + self.coordinator.verisure.download_image( + self.serial_number, new_image_id, new_image_path ) LOGGER.debug("Old image_id=%s", self._image_id) self.delete_image() @@ -110,6 +111,9 @@ class VerisureSmartcam(CoordinatorEntity, Camera): @property def name(self) -> str: """Return the name of this camera.""" - return self.coordinator.get_first( - "$.customerImageCameras[?(@.deviceLabel=='%s')].area", self._device_label - ) + return self.coordinator.data["cameras"][self.serial_number]["area"] + + @property + def unique_id(self) -> str: + """Return the unique ID for this camera.""" + return self.serial_number diff --git a/homeassistant/components/verisure/coordinator.py b/homeassistant/components/verisure/coordinator.py new file mode 100644 index 00000000000..9de81429c5c --- /dev/null +++ b/homeassistant/components/verisure/coordinator.py @@ -0,0 +1,126 @@ +"""DataUpdateCoordinator for the Verisure integration.""" +from __future__ import annotations + +from datetime import timedelta + +from verisure import ( + Error as VerisureError, + ResponseError as VerisureResponseError, + Session as Verisure, +) + +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, HTTP_SERVICE_UNAVAILABLE +from homeassistant.core import HomeAssistant +from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.util import Throttle + +from .const import CONF_GIID, DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER + + +class VerisureDataUpdateCoordinator(DataUpdateCoordinator): + """A Verisure Data Update Coordinator.""" + + def __init__(self, hass: HomeAssistant, config: ConfigType) -> None: + """Initialize the Verisure hub.""" + self.imageseries = {} + self.config = config + self.giid = config.get(CONF_GIID) + + self.verisure = Verisure( + username=config[CONF_USERNAME], password=config[CONF_PASSWORD] + ) + + super().__init__( + hass, LOGGER, name=DOMAIN, update_interval=DEFAULT_SCAN_INTERVAL + ) + + async def async_login(self) -> bool: + """Login to Verisure.""" + try: + await self.hass.async_add_executor_job(self.verisure.login) + except VerisureError as ex: + LOGGER.error("Could not log in to verisure, %s", ex) + return False + if self.giid: + return await self.async_set_giid() + return True + + async def async_logout(self) -> bool: + """Logout from Verisure.""" + try: + await self.hass.async_add_executor_job(self.verisure.logout) + except VerisureError as ex: + LOGGER.error("Could not log out from verisure, %s", ex) + return False + return True + + async def async_set_giid(self) -> bool: + """Set installation GIID.""" + try: + await self.hass.async_add_executor_job(self.verisure.set_giid, self.giid) + except VerisureError as ex: + LOGGER.error("Could not set installation GIID, %s", ex) + return False + return True + + async def _async_update_data(self) -> dict: + """Fetch data from Verisure.""" + try: + overview = await self.hass.async_add_executor_job( + self.verisure.get_overview + ) + except VerisureResponseError as ex: + LOGGER.error("Could not read overview, %s", ex) + if ex.status_code == HTTP_SERVICE_UNAVAILABLE: # Service unavailable + LOGGER.info("Trying to log in again") + await self.async_login() + return {} + raise + + # Store data in a way Home Assistant can easily consume it + return { + "alarm": overview["armState"], + "ethernet": overview.get("ethernetConnectedNow"), + "cameras": { + device["deviceLabel"]: device + for device in overview["customerImageCameras"] + }, + "climate": { + device["deviceLabel"]: device for device in overview["climateValues"] + }, + "door_window": { + device["deviceLabel"]: device + for device in overview["doorWindow"]["doorWindowDevice"] + }, + "locks": { + device["deviceLabel"]: device + for device in overview["doorLockStatusList"] + }, + "mice": { + device["deviceLabel"]: device + for device in overview["eventCounts"] + if device["deviceType"] == "MOUSE1" + }, + "smart_plugs": { + device["deviceLabel"]: device for device in overview["smartPlugs"] + }, + } + + @Throttle(timedelta(seconds=60)) + def update_smartcam_imageseries(self) -> None: + """Update the image series.""" + self.imageseries = self.verisure.get_camera_imageseries() + + @Throttle(timedelta(seconds=30)) + def smartcam_capture(self, device_id: str) -> None: + """Capture a new image from a smartcam.""" + self.verisure.capture_image(device_id) + + def disable_autolock(self, device_id: str) -> None: + """Disable autolock.""" + self.verisure.set_lock_config(device_id, auto_lock_enabled=False) + + def enable_autolock(self, device_id: str) -> None: + """Enable autolock.""" + self.verisure.set_lock_config(device_id, auto_lock_enabled=True) diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index 8fc067308fb..99118850117 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -7,17 +7,16 @@ from typing import Any, Callable from homeassistant.components.lock import LockEntity from homeassistant.const import ATTR_CODE, STATE_LOCKED, STATE_UNLOCKED from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import VerisureDataUpdateCoordinator from .const import CONF_CODE_DIGITS, CONF_DEFAULT_LOCK_CODE, CONF_LOCKS, DOMAIN, LOGGER +from .coordinator import VerisureDataUpdateCoordinator def setup_platform( hass: HomeAssistant, config: dict[str, Any], - add_entities: Callable[[list[Entity], bool], None], + add_entities: Callable[[list[VerisureDoorlock]], None], discovery_info: dict[str, Any] | None = None, ) -> None: """Set up the Verisure lock platform.""" @@ -26,10 +25,8 @@ def setup_platform( if int(coordinator.config.get(CONF_LOCKS, 1)): locks.extend( [ - VerisureDoorlock(coordinator, device_label) - for device_label in coordinator.get( - "$.doorLockStatusList[*].deviceLabel" - ) + VerisureDoorlock(coordinator, serial_number) + for serial_number in coordinator.data["locks"] ] ) @@ -42,11 +39,11 @@ class VerisureDoorlock(CoordinatorEntity, LockEntity): coordinator: VerisureDataUpdateCoordinator def __init__( - self, coordinator: VerisureDataUpdateCoordinator, device_label: str + self, coordinator: VerisureDataUpdateCoordinator, serial_number: str ) -> None: """Initialize the Verisure lock.""" super().__init__(coordinator) - self._device_label = device_label + self.serial_number = serial_number self._state = None self._digits = coordinator.config.get(CONF_CODE_DIGITS) self._default_lock_code = coordinator.config.get(CONF_DEFAULT_LOCK_CODE) @@ -54,27 +51,19 @@ class VerisureDoorlock(CoordinatorEntity, LockEntity): @property def name(self) -> str: """Return the name of the lock.""" - return self.coordinator.get_first( - "$.doorLockStatusList[?(@.deviceLabel=='%s')].area", self._device_label - ) + return self.coordinator.data["locks"][self.serial_number]["area"] @property def available(self) -> bool: """Return True if entity is available.""" return ( - self.coordinator.get_first( - "$.doorLockStatusList[?(@.deviceLabel=='%s')]", self._device_label - ) - is not None + super().available and self.serial_number in self.coordinator.data["locks"] ) @property def changed_by(self) -> str | None: """Last change triggered by.""" - return self.coordinator.get_first( - "$.doorLockStatusList[?(@.deviceLabel=='%s')].userString", - self._device_label, - ) + return self.coordinator.data["locks"][self.serial_number].get("userString") @property def code_format(self) -> str: @@ -84,11 +73,10 @@ class VerisureDoorlock(CoordinatorEntity, LockEntity): @property def is_locked(self) -> bool: """Return true if lock is locked.""" - status = self.coordinator.get_first( - "$.doorLockStatusList[?(@.deviceLabel=='%s')].lockedState", - self._device_label, + return ( + self.coordinator.data["locks"][self.serial_number]["lockedState"] + == "LOCKED" ) - return status == "LOCKED" async def async_unlock(self, **kwargs) -> None: """Send unlock command.""" @@ -112,9 +100,9 @@ class VerisureDoorlock(CoordinatorEntity, LockEntity): """Send set lock state command.""" target_state = "lock" if state == STATE_LOCKED else "unlock" lock_state = await self.hass.async_add_executor_job( - self.coordinator.session.set_lock_state, + self.coordinator.verisure.set_lock_state, code, - self._device_label, + self.serial_number, target_state, ) @@ -123,7 +111,7 @@ class VerisureDoorlock(CoordinatorEntity, LockEntity): attempts = 0 while "result" not in transaction: transaction = await self.hass.async_add_executor_job( - self.coordinator.session.get_lock_state_transaction, + self.coordinator.verisure.get_lock_state_transaction, lock_state["doorLockStateChangeTransactionId"], ) attempts += 1 diff --git a/homeassistant/components/verisure/manifest.json b/homeassistant/components/verisure/manifest.json index 814b5f148fa..744f7fb706c 100644 --- a/homeassistant/components/verisure/manifest.json +++ b/homeassistant/components/verisure/manifest.json @@ -2,6 +2,6 @@ "domain": "verisure", "name": "Verisure", "documentation": "https://www.home-assistant.io/integrations/verisure", - "requirements": ["jsonpath==0.82", "vsure==1.7.2"], + "requirements": ["vsure==1.7.2"], "codeowners": ["@frenck"] } diff --git a/homeassistant/components/verisure/sensor.py b/homeassistant/components/verisure/sensor.py index 483d03a1bb5..2a4e4759369 100644 --- a/homeassistant/components/verisure/sensor.py +++ b/homeassistant/components/verisure/sensor.py @@ -8,47 +8,43 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import VerisureDataUpdateCoordinator from .const import CONF_HYDROMETERS, CONF_MOUSE, CONF_THERMOMETERS, DOMAIN +from .coordinator import VerisureDataUpdateCoordinator def setup_platform( hass: HomeAssistant, config: dict[str, Any], - add_entities: Callable[[list[Entity], bool], None], + add_entities: Callable[[list[CoordinatorEntity], bool], None], discovery_info: dict[str, Any] | None = None, ) -> None: """Set up the Verisure platform.""" coordinator = hass.data[DOMAIN] - sensors = [] + sensors: list[CoordinatorEntity] = [] if int(coordinator.config.get(CONF_THERMOMETERS, 1)): sensors.extend( [ - VerisureThermometer(coordinator, device_label) - for device_label in coordinator.get( - "$.climateValues[?(@.temperature)].deviceLabel" - ) + VerisureThermometer(coordinator, serial_number) + for serial_number, values in coordinator.data["climate"].items() + if "temperature" in values ] ) if int(coordinator.config.get(CONF_HYDROMETERS, 1)): sensors.extend( [ - VerisureHygrometer(coordinator, device_label) - for device_label in coordinator.get( - "$.climateValues[?(@.humidity)].deviceLabel" - ) + VerisureHygrometer(coordinator, serial_number) + for serial_number, values in coordinator.data["climate"].items() + if "humidity" in values ] ) if int(coordinator.config.get(CONF_MOUSE, 1)): sensors.extend( [ - VerisureMouseDetection(coordinator, device_label) - for device_label in coordinator.get( - "$.eventCounts[?(@.deviceType=='MOUSE1')].deviceLabel" - ) + VerisureMouseDetection(coordinator, serial_number) + for serial_number in coordinator.data["mice"] ] ) @@ -61,38 +57,35 @@ class VerisureThermometer(CoordinatorEntity, Entity): coordinator: VerisureDataUpdateCoordinator def __init__( - self, coordinator: VerisureDataUpdateCoordinator, device_label: str + self, coordinator: VerisureDataUpdateCoordinator, serial_number: str ) -> None: """Initialize the sensor.""" super().__init__(coordinator) - self._device_label = device_label + self.serial_number = serial_number @property def name(self) -> str: - """Return the name of the device.""" - return ( - self.coordinator.get_first( - "$.climateValues[?(@.deviceLabel=='%s')].deviceArea", self._device_label - ) - + " temperature" - ) + """Return the name of the entity.""" + name = self.coordinator.data["climate"][self.serial_number]["deviceArea"] + return f"{name} Temperature" + + @property + def unique_id(self) -> str: + """Return the unique ID for this entity.""" + return f"{self.serial_number}_temperature" @property def state(self) -> str | None: - """Return the state of the device.""" - return self.coordinator.get_first( - "$.climateValues[?(@.deviceLabel=='%s')].temperature", self._device_label - ) + """Return the state of the entity.""" + return self.coordinator.data["climate"][self.serial_number]["temperature"] @property def available(self) -> bool: """Return True if entity is available.""" return ( - self.coordinator.get_first( - "$.climateValues[?(@.deviceLabel=='%s')].temperature", - self._device_label, - ) - is not None + super().available + and self.serial_number in self.coordinator.data["climate"] + and "temperature" in self.coordinator.data["climate"][self.serial_number] ) @property @@ -107,37 +100,35 @@ class VerisureHygrometer(CoordinatorEntity, Entity): coordinator: VerisureDataUpdateCoordinator def __init__( - self, coordinator: VerisureDataUpdateCoordinator, device_label: str + self, coordinator: VerisureDataUpdateCoordinator, serial_number: str ) -> None: """Initialize the sensor.""" super().__init__(coordinator) - self._device_label = device_label + self.serial_number = serial_number @property def name(self) -> str: - """Return the name of the device.""" - return ( - self.coordinator.get_first( - "$.climateValues[?(@.deviceLabel=='%s')].deviceArea", self._device_label - ) - + " humidity" - ) + """Return the name of the entity.""" + name = self.coordinator.data["climate"][self.serial_number]["deviceArea"] + return f"{name} Humidity" + + @property + def unique_id(self) -> str: + """Return the unique ID for this entity.""" + return f"{self.serial_number}_humidity" @property def state(self) -> str | None: - """Return the state of the device.""" - return self.coordinator.get_first( - "$.climateValues[?(@.deviceLabel=='%s')].humidity", self._device_label - ) + """Return the state of the entity.""" + return self.coordinator.data["climate"][self.serial_number]["humidity"] @property def available(self) -> bool: """Return True if entity is available.""" return ( - self.coordinator.get_first( - "$.climateValues[?(@.deviceLabel=='%s')].humidity", self._device_label - ) - is not None + super().available + and self.serial_number in self.coordinator.data["climate"] + and "humidity" in self.coordinator.data["climate"][self.serial_number] ) @property @@ -152,37 +143,35 @@ class VerisureMouseDetection(CoordinatorEntity, Entity): coordinator: VerisureDataUpdateCoordinator def __init__( - self, coordinator: VerisureDataUpdateCoordinator, device_label: str + self, coordinator: VerisureDataUpdateCoordinator, serial_number: str ) -> None: """Initialize the sensor.""" super().__init__(coordinator) - self._device_label = device_label + self.serial_number = serial_number @property def name(self) -> str: - """Return the name of the device.""" - return ( - self.coordinator.get_first( - "$.eventCounts[?(@.deviceLabel=='%s')].area", self._device_label - ) - + " mouse" - ) + """Return the name of the entity.""" + name = self.coordinator.data["mice"][self.serial_number]["area"] + return f"{name} Mouse" + + @property + def unique_id(self) -> str: + """Return the unique ID for this entity.""" + return f"{self.serial_number}_mice" @property def state(self) -> str | None: """Return the state of the device.""" - return self.coordinator.get_first( - "$.eventCounts[?(@.deviceLabel=='%s')].detections", self._device_label - ) + return self.coordinator.data["mice"][self.serial_number]["detections"] @property def available(self) -> bool: """Return True if entity is available.""" return ( - self.coordinator.get_first( - "$.eventCounts[?(@.deviceLabel=='%s')]", self._device_label - ) - is not None + super().available + and self.serial_number in self.coordinator.data["mice"] + and "detections" in self.coordinator.data["mice"][self.serial_number] ) @property diff --git a/homeassistant/components/verisure/switch.py b/homeassistant/components/verisure/switch.py index 9329d94331a..9ce0d3ce5df 100644 --- a/homeassistant/components/verisure/switch.py +++ b/homeassistant/components/verisure/switch.py @@ -6,17 +6,16 @@ from typing import Any, Callable from homeassistant.components.switch import SwitchEntity from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import VerisureDataUpdateCoordinator from .const import CONF_SMARTPLUGS, DOMAIN +from .coordinator import VerisureDataUpdateCoordinator def setup_platform( hass: HomeAssistant, config: dict[str, Any], - add_entities: Callable[[list[Entity], bool], None], + add_entities: Callable[[list[CoordinatorEntity]], None], discovery_info: dict[str, Any] | None = None, ) -> None: """Set up the Verisure switch platform.""" @@ -27,8 +26,8 @@ def setup_platform( add_entities( [ - VerisureSmartplug(coordinator, device_label) - for device_label in coordinator.get("$.smartPlugs[*].deviceLabel") + VerisureSmartplug(coordinator, serial_number) + for serial_number in coordinator.data["smart_plugs"] ] ) @@ -39,20 +38,18 @@ class VerisureSmartplug(CoordinatorEntity, SwitchEntity): coordinator: VerisureDataUpdateCoordinator def __init__( - self, coordinator: VerisureDataUpdateCoordinator, device_id: str + self, coordinator: VerisureDataUpdateCoordinator, serial_number: str ) -> None: """Initialize the Verisure device.""" super().__init__(coordinator) - self._device_label = device_id + self.serial_number = serial_number self._change_timestamp = 0 self._state = False @property def name(self) -> str: """Return the name or location of the smartplug.""" - return self.coordinator.get_first( - "$.smartPlugs[?(@.deviceLabel == '%s')].area", self._device_label - ) + return self.coordinator.data["smart_plugs"][self.serial_number]["area"] @property def is_on(self) -> bool: @@ -60,10 +57,7 @@ class VerisureSmartplug(CoordinatorEntity, SwitchEntity): if monotonic() - self._change_timestamp < 10: return self._state self._state = ( - self.coordinator.get_first( - "$.smartPlugs[?(@.deviceLabel == '%s')].currentState", - self._device_label, - ) + self.coordinator.data["smart_plugs"][self.serial_number]["currentState"] == "ON" ) return self._state @@ -72,20 +66,18 @@ class VerisureSmartplug(CoordinatorEntity, SwitchEntity): def available(self) -> bool: """Return True if entity is available.""" return ( - self.coordinator.get_first( - "$.smartPlugs[?(@.deviceLabel == '%s')]", self._device_label - ) - is not None + super().available + and self.serial_number in self.coordinator.data["smart_plugs"] ) def turn_on(self, **kwargs) -> None: """Set smartplug status on.""" - self.coordinator.session.set_smartplug_state(self._device_label, True) + self.coordinator.verisure.set_smartplug_state(self.serial_number, True) self._state = True self._change_timestamp = monotonic() def turn_off(self, **kwargs) -> None: """Set smartplug status off.""" - self.coordinator.session.set_smartplug_state(self._device_label, False) + self.coordinator.verisure.set_smartplug_state(self.serial_number, False) self._state = False self._change_timestamp = monotonic() diff --git a/requirements_all.txt b/requirements_all.txt index 607dc94f5af..9840e88a1ca 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -829,7 +829,6 @@ influxdb==5.2.3 iperf3==0.1.11 # homeassistant.components.rest -# homeassistant.components.verisure jsonpath==0.82 # homeassistant.components.kaiterra diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e53a94f0cd9..60a62290db7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -446,7 +446,6 @@ influxdb-client==1.14.0 influxdb==5.2.3 # homeassistant.components.rest -# homeassistant.components.verisure jsonpath==0.82 # homeassistant.components.konnected From 50b5fc486082d101f0c40ca06bdc0522716af2c3 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sun, 14 Mar 2021 12:32:19 +0100 Subject: [PATCH 349/831] Add Xiaomi Miio subdevice lightbulb support (#46660) * Xiaomi Miio: add subdevice lightbulb support * fix tests * process revieuw comments * bump python-miio to 0.5.5 * bump python-miio to 0.5.5 * fix imports --- .../components/xiaomi_miio/__init__.py | 2 +- homeassistant/components/xiaomi_miio/light.py | 66 ++++++++++++++++++- .../components/xiaomi_miio/manifest.json | 2 +- .../components/xiaomi_miio/sensor.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 70 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index 069520ada7d..e426322e5dc 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -2,7 +2,7 @@ from datetime import timedelta import logging -from miio.gateway import GatewayException +from miio.gateway.gateway import GatewayException from homeassistant import config_entries, core from homeassistant.const import CONF_HOST, CONF_TOKEN diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index c6d8b67bb07..f6cd468ad00 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -7,7 +7,7 @@ import logging from math import ceil from miio import Ceil, DeviceException, PhilipsBulb, PhilipsEyecare, PhilipsMoonlight -from miio.gateway import ( +from miio.gateway.gateway import ( GATEWAY_MODEL_AC_V1, GATEWAY_MODEL_AC_V2, GATEWAY_MODEL_AC_V3, @@ -36,6 +36,7 @@ from .const import ( CONF_GATEWAY, CONF_MODEL, DOMAIN, + KEY_COORDINATOR, MODELS_LIGHT, MODELS_LIGHT_BULB, MODELS_LIGHT_CEILING, @@ -52,6 +53,7 @@ from .const import ( SERVICE_SET_SCENE, ) from .device import XiaomiMiioEntity +from .gateway import XiaomiGatewayDevice _LOGGER = logging.getLogger(__name__) @@ -148,6 +150,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( XiaomiGatewayLight(gateway, config_entry.title, config_entry.unique_id) ) + # Gateway sub devices + sub_devices = gateway.devices + coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR] + for sub_device in sub_devices.values(): + if sub_device.device_type == "LightBulb": + entities.append( + XiaomiGatewayBulb(coordinator, sub_device, config_entry) + ) if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: if DATA_KEY not in hass.data: @@ -1042,3 +1052,57 @@ class XiaomiGatewayLight(LightEntity): self._brightness_pct = state_dict["brightness"] self._rgb = state_dict["rgb"] self._hs = color.color_RGB_to_hs(*self._rgb) + + +class XiaomiGatewayBulb(XiaomiGatewayDevice, LightEntity): + """Representation of Xiaomi Gateway Bulb.""" + + @property + def brightness(self): + """Return the brightness of the light.""" + return round((self._sub_device.status["brightness"] * 255) / 100) + + @property + def color_temp(self): + """Return current color temperature.""" + return self._sub_device.status["color_temp"] + + @property + def is_on(self): + """Return true if light is on.""" + return self._sub_device.status["status"] == "on" + + @property + def min_mireds(self): + """Return min cct.""" + return self._sub_device.status["cct_min"] + + @property + def max_mireds(self): + """Return max cct.""" + return self._sub_device.status["cct_max"] + + @property + def supported_features(self): + """Return the supported features.""" + return SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP + + async def async_turn_on(self, **kwargs): + """Instruct the light to turn on.""" + await self.hass.async_add_executor_job(self._sub_device.on) + + if ATTR_COLOR_TEMP in kwargs: + color_temp = kwargs[ATTR_COLOR_TEMP] + await self.hass.async_add_executor_job( + self._sub_device.set_color_temp, color_temp + ) + + if ATTR_BRIGHTNESS in kwargs: + brightness = round((kwargs[ATTR_BRIGHTNESS] * 100) / 255) + await self.hass.async_add_executor_job( + self._sub_device.set_brightness, brightness + ) + + async def async_turn_off(self, **kwargsf): + """Instruct the light to turn off.""" + await self.hass.async_add_executor_job(self._sub_device.off) diff --git a/homeassistant/components/xiaomi_miio/manifest.json b/homeassistant/components/xiaomi_miio/manifest.json index 2536b0e0aa7..6f8069be681 100644 --- a/homeassistant/components/xiaomi_miio/manifest.json +++ b/homeassistant/components/xiaomi_miio/manifest.json @@ -3,7 +3,7 @@ "name": "Xiaomi Miio", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/xiaomi_miio", - "requirements": ["construct==2.10.56", "python-miio==0.5.4"], + "requirements": ["construct==2.10.56", "python-miio==0.5.5"], "codeowners": ["@rytilahti", "@syssi", "@starkillerOG"], "zeroconf": ["_miio._udp.local."] } diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index 5769c1fb475..cdb38ba9515 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -3,7 +3,7 @@ from dataclasses import dataclass import logging from miio import AirQualityMonitor, DeviceException -from miio.gateway import ( +from miio.gateway.gateway import ( GATEWAY_MODEL_AC_V1, GATEWAY_MODEL_AC_V2, GATEWAY_MODEL_AC_V3, diff --git a/requirements_all.txt b/requirements_all.txt index 9840e88a1ca..d37eb5571fc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1795,7 +1795,7 @@ python-juicenet==1.0.1 # python-lirc==1.2.3 # homeassistant.components.xiaomi_miio -python-miio==0.5.4 +python-miio==0.5.5 # homeassistant.components.mpd python-mpd2==3.0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 60a62290db7..564a3b49e09 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -935,7 +935,7 @@ python-izone==1.1.4 python-juicenet==1.0.1 # homeassistant.components.xiaomi_miio -python-miio==0.5.4 +python-miio==0.5.5 # homeassistant.components.nest python-nest==4.1.0 From fea944bcead3dbac3107f3a07aece8985af36cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hjelseth=20H=C3=B8yer?= Date: Sun, 14 Mar 2021 13:44:07 +0100 Subject: [PATCH 350/831] Upgrade Tibber library to 0.16.2 (#47892) --- homeassistant/components/tibber/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tibber/manifest.json b/homeassistant/components/tibber/manifest.json index 652804859da..108f05d5625 100644 --- a/homeassistant/components/tibber/manifest.json +++ b/homeassistant/components/tibber/manifest.json @@ -2,7 +2,7 @@ "domain": "tibber", "name": "Tibber", "documentation": "https://www.home-assistant.io/integrations/tibber", - "requirements": ["pyTibber==0.16.1"], + "requirements": ["pyTibber==0.16.2"], "codeowners": ["@danielhiversen"], "quality_scale": "silver", "config_flow": true diff --git a/requirements_all.txt b/requirements_all.txt index d37eb5571fc..e68c490c126 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1235,7 +1235,7 @@ pyRFXtrx==0.26.1 # pySwitchmate==0.4.6 # homeassistant.components.tibber -pyTibber==0.16.1 +pyTibber==0.16.2 # homeassistant.components.dlink pyW215==0.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 564a3b49e09..b42db89bded 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -645,7 +645,7 @@ pyMetno==0.8.1 pyRFXtrx==0.26.1 # homeassistant.components.tibber -pyTibber==0.16.1 +pyTibber==0.16.2 # homeassistant.components.nextbus py_nextbusnext==0.1.4 From 4d61f6f8c2526b9b9e49563ef0e2d2c77c390ea4 Mon Sep 17 00:00:00 2001 From: jugla <59493499+jugla@users.noreply.github.com> Date: Sun, 14 Mar 2021 16:04:08 +0100 Subject: [PATCH 351/831] Reduce number of iqair request (#47890) --- homeassistant/components/airvisual/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index 6254d533a0f..7450ff2afcf 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -80,9 +80,9 @@ def async_get_cloud_api_update_interval(hass, api_key, num_consumers): This will shift based on the number of active consumers, thus keeping the user under the monthly API limit. """ - # Assuming 10,000 calls per month and a "smallest possible month" of 28 days; note + # Assuming 10,000 calls per month and a "largest possible month" of 31 days; note # that we give a buffer of 1500 API calls for any drift, restarts, etc.: - minutes_between_api_calls = ceil(1 / (8500 / 28 / 24 / 60 / num_consumers)) + minutes_between_api_calls = ceil(num_consumers * 31 * 24 * 60 / 8500) LOGGER.debug( "Leveling API key usage (%s): %s consumers, %s minutes between updates", From d32c364d7f9e138e0dd9363b34b3cb39f4afcd06 Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Sun, 14 Mar 2021 18:52:47 +0100 Subject: [PATCH 352/831] Update pyhomematic to 0.1.72 (#47906) --- homeassistant/components/homematic/const.py | 4 ++++ homeassistant/components/homematic/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homematic/const.py b/homeassistant/components/homematic/const.py index e8fa272b0e5..864441c2aa6 100644 --- a/homeassistant/components/homematic/const.py +++ b/homeassistant/components/homematic/const.py @@ -60,6 +60,7 @@ HM_DEVICE_TYPES = { "IPWSwitch", "IOSwitchWireless", "IPWIODevice", + "IPSwitchBattery", ], DISCOVER_LIGHTS: [ "Dimmer", @@ -119,6 +120,8 @@ HM_DEVICE_TYPES = { "ValveBox", "IPKeyBlind", "IPKeyBlindTilt", + "IPLanRouter", + "TempModuleSTE2", ], DISCOVER_CLIMATE: [ "Thermostat", @@ -163,6 +166,7 @@ HM_DEVICE_TYPES = { "IPWMotionDection", "IPAlarmSensor", "IPRainSensor", + "IPLanRouter", ], DISCOVER_COVER: [ "Blind", diff --git a/homeassistant/components/homematic/manifest.json b/homeassistant/components/homematic/manifest.json index 36414b606f9..d81dc97cdb7 100644 --- a/homeassistant/components/homematic/manifest.json +++ b/homeassistant/components/homematic/manifest.json @@ -2,6 +2,6 @@ "domain": "homematic", "name": "Homematic", "documentation": "https://www.home-assistant.io/integrations/homematic", - "requirements": ["pyhomematic==0.1.71"], + "requirements": ["pyhomematic==0.1.72"], "codeowners": ["@pvizeli", "@danielperna84"] } diff --git a/requirements_all.txt b/requirements_all.txt index e68c490c126..a170b00e617 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1431,7 +1431,7 @@ pyhik==0.2.8 pyhiveapi==0.3.4.4 # homeassistant.components.homematic -pyhomematic==0.1.71 +pyhomematic==0.1.72 # homeassistant.components.homeworks pyhomeworks==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b42db89bded..3104ba205de 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -748,7 +748,7 @@ pyhaversion==3.4.2 pyheos==0.7.2 # homeassistant.components.homematic -pyhomematic==0.1.71 +pyhomematic==0.1.72 # homeassistant.components.icloud pyicloud==0.10.2 From 9dabc988fbd8fd1219f947372cb0296952e87273 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 14 Mar 2021 23:48:47 +0000 Subject: [PATCH 353/831] Bump frontend to 20210314.0 --- 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 a254c7d129a..9d6bf462a48 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20210313.0" + "home-assistant-frontend==20210314.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index c604ab5c706..8bb7d1cbad5 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.42.0 -home-assistant-frontend==20210313.0 +home-assistant-frontend==20210314.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index a170b00e617..e3404081073 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210313.0 +home-assistant-frontend==20210314.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3104ba205de..f3ee7613da4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210313.0 +home-assistant-frontend==20210314.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 61a2460c875b5386c1331cdebce1cdd93b76b00e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Mar 2021 16:46:21 -1000 Subject: [PATCH 354/831] Improve error reporting in recorder purge test (#47929) --- homeassistant/components/recorder/models.py | 9 ++++++--- tests/components/recorder/test_purge.py | 11 +++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index 6ed25e64eda..ef7181c9c03 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -207,11 +207,14 @@ class RecorderRuns(Base): # type: ignore def __repr__(self) -> str: """Return string representation of instance for debugging.""" + end = ( + f"'{self.end.isoformat(sep=' ', timespec='seconds')}'" if self.end else None + ) return ( f"" ) diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index db3906595db..e6bc3a99a97 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -105,6 +105,8 @@ async def test_purge_method( await _add_test_events(hass, instance) await _add_test_states(hass, instance) await _add_test_recorder_runs(hass, instance) + await hass.async_block_till_done() + await async_wait_recording_done(hass, instance) # make sure we start with 6 states with session_scope(hass=hass) as session: @@ -116,9 +118,7 @@ async def test_purge_method( recorder_runs = session.query(RecorderRuns) assert recorder_runs.count() == 7 - - await hass.async_block_till_done() - await async_wait_recording_done(hass, instance) + runs_before_purge = recorder_runs.all() # run purge method - no service data, use defaults await hass.services.async_call("recorder", "purge") @@ -145,7 +145,10 @@ async def test_purge_method( assert events.count() == 2 # now we should only have 3 recorder runs left - assert recorder_runs.count() == 3 + runs = recorder_runs.all() + assert runs[0] == runs_before_purge[0] + assert runs[1] == runs_before_purge[5] + assert runs[2] == runs_before_purge[6] assert not ("EVENT_TEST_PURGE" in (event.event_type for event in events.all())) From 15aa00d6ccc98904c3382ba0794e4787c0e7c22a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Mar 2021 18:14:46 -1000 Subject: [PATCH 355/831] Fix homekit checking for port cleanup too many times (#47836) * Fix homekit checking for port cleanup too many times The loop should have terminated as soon as the port was available * coverage * tweak homekit shutdown wait --- homeassistant/components/homekit/__init__.py | 14 +++++--- tests/components/homekit/test_homekit.py | 35 +++++++++++++++++++- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 7c787c7e7be..8b606036a48 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -117,6 +117,8 @@ STATUS_RUNNING = 1 STATUS_STOPPED = 2 STATUS_WAIT = 3 +PORT_CLEANUP_CHECK_INTERVAL_SECS = 1 + def _has_all_unique_names_and_ports(bridges): """Validate that each homekit bridge configured has a unique name.""" @@ -306,12 +308,16 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): if homekit.status == STATUS_RUNNING: await homekit.async_stop() + logged_shutdown_wait = False for _ in range(0, SHUTDOWN_TIMEOUT): - if not await hass.async_add_executor_job( - port_is_available, entry.data[CONF_PORT] - ): + if await hass.async_add_executor_job(port_is_available, entry.data[CONF_PORT]): + break + + if not logged_shutdown_wait: _LOGGER.info("Waiting for the HomeKit server to shutdown") - await asyncio.sleep(1) + logged_shutdown_wait = True + + await asyncio.sleep(PORT_CLEANUP_CHECK_INTERVAL_SECS) hass.data[DOMAIN].pop(entry.entry_id) diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 6d5f3faae95..5895aae351e 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -9,7 +9,7 @@ from pyhap.const import CATEGORY_CAMERA, CATEGORY_TELEVISION import pytest from homeassistant import config as hass_config -from homeassistant.components import zeroconf +from homeassistant.components import homekit as homekit_base, zeroconf from homeassistant.components.binary_sensor import ( DEVICE_CLASS_BATTERY_CHARGING, DEVICE_CLASS_MOTION, @@ -1167,3 +1167,36 @@ async def test_homekit_start_in_accessory_mode( ) assert hk_driver_start.called assert homekit.status == STATUS_RUNNING + + +async def test_wait_for_port_to_free(hass, hk_driver, mock_zeroconf, caplog): + """Test we wait for the port to free before declaring unload success.""" + await async_setup_component(hass, "persistent_notification", {}) + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_NAME: BRIDGE_NAME, CONF_PORT: DEFAULT_PORT}, + options={}, + ) + entry.add_to_hass(hass) + + with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( + f"{PATH_HOMEKIT}.HomeKit.async_stop" + ), patch(f"{PATH_HOMEKIT}.port_is_available", return_value=True) as port_mock: + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + assert "Waiting for the HomeKit server to shutdown" not in caplog.text + assert port_mock.called + + with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( + f"{PATH_HOMEKIT}.HomeKit.async_stop" + ), patch.object(homekit_base, "PORT_CLEANUP_CHECK_INTERVAL_SECS", 0), patch( + f"{PATH_HOMEKIT}.port_is_available", return_value=False + ) as port_mock: + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + assert "Waiting for the HomeKit server to shutdown" in caplog.text + assert port_mock.called From 8795608ae31fa8e95424371a260e31778bef802a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Mar 2021 19:42:49 -1000 Subject: [PATCH 356/831] Add suggested area support to august (#47930) --- homeassistant/components/august/entity.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/august/entity.py b/homeassistant/components/august/entity.py index b6c677a63b6..b2a93948449 100644 --- a/homeassistant/components/august/entity.py +++ b/homeassistant/components/august/entity.py @@ -5,6 +5,8 @@ from homeassistant.helpers.entity import Entity from . import DOMAIN from .const import MANUFACTURER +DEVICE_TYPES = ["keypad", "lock", "camera", "doorbell", "door", "bell"] + class AugustEntityMixin(Entity): """Base implementation for August device.""" @@ -31,12 +33,14 @@ class AugustEntityMixin(Entity): @property def device_info(self): """Return the device_info of the device.""" + name = self._device.device_name return { "identifiers": {(DOMAIN, self._device_id)}, - "name": self._device.device_name, + "name": name, "manufacturer": MANUFACTURER, "sw_version": self._detail.firmware_version, "model": self._detail.model, + "suggested_area": _remove_device_types(name, DEVICE_TYPES), } @callback @@ -56,3 +60,19 @@ class AugustEntityMixin(Entity): self._device_id, self._update_from_data_and_write_state ) ) + + +def _remove_device_types(name, device_types): + """Strip device types from a string. + + August stores the name as Master Bed Lock + or Master Bed Door. We can come up with a + reasonable suggestion by removing the supported + device types from the string. + """ + lower_name = name.lower() + for device_type in device_types: + device_type_with_space = f" {device_type}" + if lower_name.endswith(device_type_with_space): + lower_name = lower_name[: -len(device_type_with_space)] + return name[: len(lower_name)] From 0be1389cf4b80d2879d35371ceffe944f6ef6659 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Mon, 15 Mar 2021 06:44:04 +0100 Subject: [PATCH 357/831] Bump accuweather library (#47915) --- homeassistant/components/accuweather/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/accuweather/manifest.json b/homeassistant/components/accuweather/manifest.json index b03c0e51018..fd91f62ae33 100644 --- a/homeassistant/components/accuweather/manifest.json +++ b/homeassistant/components/accuweather/manifest.json @@ -2,7 +2,7 @@ "domain": "accuweather", "name": "AccuWeather", "documentation": "https://www.home-assistant.io/integrations/accuweather/", - "requirements": ["accuweather==0.1.0"], + "requirements": ["accuweather==0.1.1"], "codeowners": ["@bieniu"], "config_flow": true, "quality_scale": "platinum" diff --git a/requirements_all.txt b/requirements_all.txt index e3404081073..9d1e9baf5f6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -96,7 +96,7 @@ WazeRouteCalculator==0.12 abodepy==1.2.0 # homeassistant.components.accuweather -accuweather==0.1.0 +accuweather==0.1.1 # homeassistant.components.bmp280 adafruit-circuitpython-bmp280==3.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f3ee7613da4..6b8f17bffd0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -42,7 +42,7 @@ WSDiscovery==2.0.0 abodepy==1.2.0 # homeassistant.components.accuweather -accuweather==0.1.0 +accuweather==0.1.1 # homeassistant.components.androidtv adb-shell[async]==0.2.1 From be2be4e867cb57632f6d68094f98bf590ef85d87 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Mon, 15 Mar 2021 06:44:13 +0100 Subject: [PATCH 358/831] Bump gios library (#47917) --- homeassistant/components/gios/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/gios/manifest.json b/homeassistant/components/gios/manifest.json index ff1f82e6ce3..3f520525a5a 100644 --- a/homeassistant/components/gios/manifest.json +++ b/homeassistant/components/gios/manifest.json @@ -3,7 +3,7 @@ "name": "GIOŚ", "documentation": "https://www.home-assistant.io/integrations/gios", "codeowners": ["@bieniu"], - "requirements": ["gios==0.2.0"], + "requirements": ["gios==0.2.1"], "config_flow": true, "quality_scale": "platinum" } diff --git a/requirements_all.txt b/requirements_all.txt index 9d1e9baf5f6..834c3e910fa 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -655,7 +655,7 @@ georss_qld_bushfire_alert_client==0.3 getmac==0.8.2 # homeassistant.components.gios -gios==0.2.0 +gios==0.2.1 # homeassistant.components.gitter gitterpy==0.1.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6b8f17bffd0..aa7afdeb66a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -349,7 +349,7 @@ georss_qld_bushfire_alert_client==0.3 getmac==0.8.2 # homeassistant.components.gios -gios==0.2.0 +gios==0.2.1 # homeassistant.components.glances glances_api==0.2.0 From e91be3f9f533222c7fdedaf8955bb12e67875f6b Mon Sep 17 00:00:00 2001 From: unaiur Date: Mon, 15 Mar 2021 06:45:14 +0100 Subject: [PATCH 359/831] Upgrade to maxcube-api-0.4.1 (#47910) This new version implements a workaround for a hardware bug that causes a factory reset of the full MAX! service. See https://github.com/hackercowboy/python-maxcube-api/issues/12 for more details. --- homeassistant/components/maxcube/__init__.py | 4 ++-- homeassistant/components/maxcube/manifest.json | 2 +- requirements_all.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/maxcube/__init__.py b/homeassistant/components/maxcube/__init__.py index ffd156b5e00..e38f08809a7 100644 --- a/homeassistant/components/maxcube/__init__.py +++ b/homeassistant/components/maxcube/__init__.py @@ -4,7 +4,6 @@ from socket import timeout from threading import Lock import time -from maxcube.connection import MaxCubeConnection from maxcube.cube import MaxCube import voluptuous as vol @@ -60,7 +59,7 @@ def setup(hass, config): scan_interval = gateway[CONF_SCAN_INTERVAL].total_seconds() try: - cube = MaxCube(MaxCubeConnection(host, port)) + cube = MaxCube(host, port) hass.data[DATA_KEY][host] = MaxCubeHandle(cube, scan_interval) except timeout as ex: _LOGGER.error("Unable to connect to Max!Cube gateway: %s", str(ex)) @@ -86,6 +85,7 @@ class MaxCubeHandle: def __init__(self, cube, scan_interval): """Initialize the Cube Handle.""" self.cube = cube + self.cube.use_persistent_connection = scan_interval <= 300 # seconds self.scan_interval = scan_interval self.mutex = Lock() self._updatets = time.monotonic() diff --git a/homeassistant/components/maxcube/manifest.json b/homeassistant/components/maxcube/manifest.json index e6badb254f7..ddc21bd2358 100644 --- a/homeassistant/components/maxcube/manifest.json +++ b/homeassistant/components/maxcube/manifest.json @@ -2,6 +2,6 @@ "domain": "maxcube", "name": "eQ-3 MAX!", "documentation": "https://www.home-assistant.io/integrations/maxcube", - "requirements": ["maxcube-api==0.3.0"], + "requirements": ["maxcube-api==0.4.1"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 834c3e910fa..b762b5fd168 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -910,7 +910,7 @@ magicseaweed==1.0.3 matrix-client==0.3.2 # homeassistant.components.maxcube -maxcube-api==0.3.0 +maxcube-api==0.4.1 # homeassistant.components.mythicbeastsdns mbddns==0.1.2 From bcadccf7aa5f9d07119e93515ee620bf0352e51c Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 14 Mar 2021 22:49:21 -0700 Subject: [PATCH 360/831] Invalidate HLS Stream on nest url refresh failure (#47869) This will ensure that the HLS stream is re-created and fetches a new url. --- homeassistant/components/nest/camera_sdm.py | 3 ++ tests/components/nest/camera_sdm_test.py | 37 ++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/nest/camera_sdm.py b/homeassistant/components/nest/camera_sdm.py index cc2730fad8a..dd84b53d719 100644 --- a/homeassistant/components/nest/camera_sdm.py +++ b/homeassistant/components/nest/camera_sdm.py @@ -145,6 +145,9 @@ class NestCamera(Camera): _LOGGER.debug("Failed to extend stream: %s", err) # Next attempt to catch a url will get a new one self._stream = None + if self.stream: + self.stream.stop() + self.stream = None return # Update the stream worker with the latest valid url if self.stream: diff --git a/tests/components/nest/camera_sdm_test.py b/tests/components/nest/camera_sdm_test.py index 57747ad9f59..3c0c0fdb4db 100644 --- a/tests/components/nest/camera_sdm_test.py +++ b/tests/components/nest/camera_sdm_test.py @@ -256,7 +256,10 @@ async def test_refresh_expired_stream_token(hass, auth): # Request a stream for the camera entity to exercise nest cam + camera interaction # and shutdown on url expiration - await camera.async_request_stream(hass, cam.entity_id, "hls") + with patch("homeassistant.components.camera.create_stream") as create_stream: + hls_url = await camera.async_request_stream(hass, "camera.my_camera", fmt="hls") + assert hls_url.startswith("/api/hls/") # Includes access token + assert create_stream.called stream_source = await camera.async_get_stream_source(hass, "camera.my_camera") assert stream_source == "rtsp://some/url?auth=g.1.streamingToken" @@ -273,6 +276,13 @@ async def test_refresh_expired_stream_token(hass, auth): stream_source = await camera.async_get_stream_source(hass, "camera.my_camera") assert stream_source == "rtsp://some/url?auth=g.2.streamingToken" + # HLS stream is not re-created, just the source is updated + with patch("homeassistant.components.camera.create_stream") as create_stream: + hls_url1 = await camera.async_request_stream( + hass, "camera.my_camera", fmt="hls" + ) + assert hls_url == hls_url1 + # Next alarm is well before stream_2_expiration, no change next_update = now + datetime.timedelta(seconds=100) await fire_alarm(hass, next_update) @@ -285,6 +295,13 @@ async def test_refresh_expired_stream_token(hass, auth): stream_source = await camera.async_get_stream_source(hass, "camera.my_camera") assert stream_source == "rtsp://some/url?auth=g.3.streamingToken" + # HLS stream is still not re-created + with patch("homeassistant.components.camera.create_stream") as create_stream: + hls_url2 = await camera.async_request_stream( + hass, "camera.my_camera", fmt="hls" + ) + assert hls_url == hls_url2 + async def test_stream_response_already_expired(hass, auth): """Test a API response returning an expired stream url.""" @@ -363,12 +380,20 @@ async def test_refresh_expired_stream_failure(hass, auth): make_stream_url_response(expiration=stream_2_expiration, token_num=2), ] await async_setup_camera(hass, DEVICE_TRAITS, auth=auth) + assert await async_setup_component(hass, "stream", {}) assert len(hass.states.async_all()) == 1 cam = hass.states.get("camera.my_camera") assert cam is not None assert cam.state == STATE_IDLE + # Request an HLS stream + with patch("homeassistant.components.camera.create_stream") as create_stream: + + hls_url = await camera.async_request_stream(hass, "camera.my_camera", fmt="hls") + assert hls_url.startswith("/api/hls/") # Includes access token + assert create_stream.called + stream_source = await camera.async_get_stream_source(hass, "camera.my_camera") assert stream_source == "rtsp://some/url?auth=g.1.streamingToken" @@ -381,6 +406,16 @@ async def test_refresh_expired_stream_failure(hass, auth): stream_source = await camera.async_get_stream_source(hass, "camera.my_camera") assert stream_source == "rtsp://some/url?auth=g.2.streamingToken" + # Requesting an HLS stream will create an entirely new stream + with patch("homeassistant.components.camera.create_stream") as create_stream: + # The HLS stream endpoint was invalidated, with a new auth token + hls_url2 = await camera.async_request_stream( + hass, "camera.my_camera", fmt="hls" + ) + assert hls_url != hls_url2 + assert hls_url2.startswith("/api/hls/") # Includes access token + assert create_stream.called + async def test_camera_image_from_last_event(hass, auth): """Test an image generated from an event.""" From fbf322523465efd94d744090aefa1f67e0869ae6 Mon Sep 17 00:00:00 2001 From: Brandon Rothweiler Date: Mon, 15 Mar 2021 01:57:39 -0400 Subject: [PATCH 361/831] Address review comments and minor fix for Mazda integration (#47702) * Address comments from code review * Fix handling of missing sensor values * Use default timeout for get_vehicles * Fix test_update_auth_failure --- homeassistant/components/mazda/__init__.py | 11 ++-- homeassistant/components/mazda/config_flow.py | 14 +++--- homeassistant/components/mazda/manifest.json | 2 +- homeassistant/components/mazda/sensor.py | 50 +++++++++++-------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/mazda/test_init.py | 29 +++++++---- 7 files changed, 64 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/mazda/__init__.py b/homeassistant/components/mazda/__init__.py index 1b1e6584a0e..8731fa256a6 100644 --- a/homeassistant/components/mazda/__init__.py +++ b/homeassistant/components/mazda/__init__.py @@ -32,6 +32,12 @@ _LOGGER = logging.getLogger(__name__) PLATFORMS = ["sensor"] +async def with_timeout(task, timeout_seconds=10): + """Run an async task with a timeout.""" + async with async_timeout.timeout(timeout_seconds): + return await task + + async def async_setup(hass: HomeAssistant, config: dict): """Set up the Mazda Connected Services component.""" hass.data[DOMAIN] = {} @@ -69,11 +75,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_update_data(): """Fetch data from Mazda API.""" - - async def with_timeout(task): - async with async_timeout.timeout(10): - return await task - try: vehicles = await with_timeout(mazda_client.get_vehicles()) diff --git a/homeassistant/components/mazda/config_flow.py b/homeassistant/components/mazda/config_flow.py index 53c08b9bd69..ef0f35e4e8e 100644 --- a/homeassistant/components/mazda/config_flow.py +++ b/homeassistant/components/mazda/config_flow.py @@ -40,15 +40,15 @@ class MazdaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if user_input is not None: await self.async_set_unique_id(user_input[CONF_EMAIL].lower()) + websession = aiohttp_client.async_get_clientsession(self.hass) + mazda_client = MazdaAPI( + user_input[CONF_EMAIL], + user_input[CONF_PASSWORD], + user_input[CONF_REGION], + websession, + ) try: - websession = aiohttp_client.async_get_clientsession(self.hass) - mazda_client = MazdaAPI( - user_input[CONF_EMAIL], - user_input[CONF_PASSWORD], - user_input[CONF_REGION], - websession, - ) await mazda_client.validate_credentials() except MazdaAuthenticationException: errors["base"] = "invalid_auth" diff --git a/homeassistant/components/mazda/manifest.json b/homeassistant/components/mazda/manifest.json index b3826d42318..c3a05a351c3 100644 --- a/homeassistant/components/mazda/manifest.json +++ b/homeassistant/components/mazda/manifest.json @@ -3,7 +3,7 @@ "name": "Mazda Connected Services", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/mazda", - "requirements": ["pymazda==0.0.8"], + "requirements": ["pymazda==0.0.9"], "codeowners": ["@bdr99"], "quality_scale": "platinum" } \ No newline at end of file diff --git a/homeassistant/components/mazda/sensor.py b/homeassistant/components/mazda/sensor.py index fa03eb7f410..a05291673e8 100644 --- a/homeassistant/components/mazda/sensor.py +++ b/homeassistant/components/mazda/sensor.py @@ -91,7 +91,13 @@ class MazdaFuelDistanceSensor(MazdaEntity): fuel_distance_km = self.coordinator.data[self.index]["status"][ "fuelDistanceRemainingKm" ] - return round(self.hass.config.units.length(fuel_distance_km, LENGTH_KILOMETERS)) + return ( + None + if fuel_distance_km is None + else round( + self.hass.config.units.length(fuel_distance_km, LENGTH_KILOMETERS) + ) + ) class MazdaOdometerSensor(MazdaEntity): @@ -124,7 +130,11 @@ class MazdaOdometerSensor(MazdaEntity): def state(self): """Return the state of the sensor.""" odometer_km = self.coordinator.data[self.index]["status"]["odometerKm"] - return round(self.hass.config.units.length(odometer_km, LENGTH_KILOMETERS)) + return ( + None + if odometer_km is None + else round(self.hass.config.units.length(odometer_km, LENGTH_KILOMETERS)) + ) class MazdaFrontLeftTirePressureSensor(MazdaEntity): @@ -154,11 +164,10 @@ class MazdaFrontLeftTirePressureSensor(MazdaEntity): @property def state(self): """Return the state of the sensor.""" - return round( - self.coordinator.data[self.index]["status"]["tirePressure"][ - "frontLeftTirePressurePsi" - ] - ) + tire_pressure = self.coordinator.data[self.index]["status"]["tirePressure"][ + "frontLeftTirePressurePsi" + ] + return None if tire_pressure is None else round(tire_pressure) class MazdaFrontRightTirePressureSensor(MazdaEntity): @@ -188,11 +197,10 @@ class MazdaFrontRightTirePressureSensor(MazdaEntity): @property def state(self): """Return the state of the sensor.""" - return round( - self.coordinator.data[self.index]["status"]["tirePressure"][ - "frontRightTirePressurePsi" - ] - ) + tire_pressure = self.coordinator.data[self.index]["status"]["tirePressure"][ + "frontRightTirePressurePsi" + ] + return None if tire_pressure is None else round(tire_pressure) class MazdaRearLeftTirePressureSensor(MazdaEntity): @@ -222,11 +230,10 @@ class MazdaRearLeftTirePressureSensor(MazdaEntity): @property def state(self): """Return the state of the sensor.""" - return round( - self.coordinator.data[self.index]["status"]["tirePressure"][ - "rearLeftTirePressurePsi" - ] - ) + tire_pressure = self.coordinator.data[self.index]["status"]["tirePressure"][ + "rearLeftTirePressurePsi" + ] + return None if tire_pressure is None else round(tire_pressure) class MazdaRearRightTirePressureSensor(MazdaEntity): @@ -256,8 +263,7 @@ class MazdaRearRightTirePressureSensor(MazdaEntity): @property def state(self): """Return the state of the sensor.""" - return round( - self.coordinator.data[self.index]["status"]["tirePressure"][ - "rearRightTirePressurePsi" - ] - ) + tire_pressure = self.coordinator.data[self.index]["status"]["tirePressure"][ + "rearRightTirePressurePsi" + ] + return None if tire_pressure is None else round(tire_pressure) diff --git a/requirements_all.txt b/requirements_all.txt index b762b5fd168..b4d6d93bdae 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1518,7 +1518,7 @@ pymailgunner==1.4 pymata-express==1.19 # homeassistant.components.mazda -pymazda==0.0.8 +pymazda==0.0.9 # homeassistant.components.mediaroom pymediaroom==0.6.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index aa7afdeb66a..7ff9b14e6d7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -802,7 +802,7 @@ pymailgunner==1.4 pymata-express==1.19 # homeassistant.components.mazda -pymazda==0.0.8 +pymazda==0.0.9 # homeassistant.components.melcloud pymelcloud==2.5.2 diff --git a/tests/components/mazda/test_init.py b/tests/components/mazda/test_init.py index d0352682f53..ebd118260bc 100644 --- a/tests/components/mazda/test_init.py +++ b/tests/components/mazda/test_init.py @@ -1,18 +1,22 @@ """Tests for the Mazda Connected Services integration.""" +from datetime import timedelta +import json from unittest.mock import patch from pymazda import MazdaAuthenticationException, MazdaException -from homeassistant.components.mazda.const import DATA_COORDINATOR, DOMAIN +from homeassistant.components.mazda.const import DOMAIN from homeassistant.config_entries import ( ENTRY_STATE_LOADED, + ENTRY_STATE_NOT_LOADED, ENTRY_STATE_SETUP_ERROR, ENTRY_STATE_SETUP_RETRY, ) from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_REGION from homeassistant.core import HomeAssistant +from homeassistant.util import dt as dt_util -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_fire_time_changed, load_fixture from tests.components.mazda import init_integration FIXTURE_USER_INPUT = { @@ -60,10 +64,21 @@ async def test_init_auth_failure(hass: HomeAssistant): async def test_update_auth_failure(hass: HomeAssistant): """Test auth failure during data update.""" + get_vehicles_fixture = json.loads(load_fixture("mazda/get_vehicles.json")) + get_vehicle_status_fixture = json.loads( + load_fixture("mazda/get_vehicle_status.json") + ) + with patch( "homeassistant.components.mazda.MazdaAPI.validate_credentials", return_value=True, - ), patch("homeassistant.components.mazda.MazdaAPI.get_vehicles", return_value={}): + ), patch( + "homeassistant.components.mazda.MazdaAPI.get_vehicles", + return_value=get_vehicles_fixture, + ), patch( + "homeassistant.components.mazda.MazdaAPI.get_vehicle_status", + return_value=get_vehicle_status_fixture, + ): config_entry = MockConfigEntry(domain=DOMAIN, data=FIXTURE_USER_INPUT) config_entry.add_to_hass(hass) @@ -74,15 +89,11 @@ async def test_update_auth_failure(hass: HomeAssistant): assert len(entries) == 1 assert entries[0].state == ENTRY_STATE_LOADED - coordinator = hass.data[DOMAIN][config_entry.entry_id][DATA_COORDINATOR] with patch( - "homeassistant.components.mazda.MazdaAPI.validate_credentials", - side_effect=MazdaAuthenticationException("Login failed"), - ), patch( "homeassistant.components.mazda.MazdaAPI.get_vehicles", side_effect=MazdaAuthenticationException("Login failed"), ): - await coordinator.async_refresh() + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=61)) await hass.async_block_till_done() flows = hass.config_entries.flow.async_progress() @@ -97,4 +108,4 @@ async def test_unload_config_entry(hass: HomeAssistant) -> None: await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() - assert not hass.data.get(DOMAIN) + assert entry.state == ENTRY_STATE_NOT_LOADED From 9ec4c07753f12da47dba351bbe2f1156664d6a63 Mon Sep 17 00:00:00 2001 From: Patrick Decat Date: Mon, 15 Mar 2021 06:57:56 +0100 Subject: [PATCH 362/831] Update openwrt-luci-rpc from 1.1.6 to 1.1.8 (#47848) --- homeassistant/components/luci/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/luci/manifest.json b/homeassistant/components/luci/manifest.json index 3b51aab6e4a..95fd6fc35ad 100644 --- a/homeassistant/components/luci/manifest.json +++ b/homeassistant/components/luci/manifest.json @@ -2,6 +2,6 @@ "domain": "luci", "name": "OpenWRT (luci)", "documentation": "https://www.home-assistant.io/integrations/luci", - "requirements": ["openwrt-luci-rpc==1.1.6"], + "requirements": ["openwrt-luci-rpc==1.1.8"], "codeowners": ["@mzdrale"] } diff --git a/requirements_all.txt b/requirements_all.txt index b4d6d93bdae..03fd077d9be 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1061,7 +1061,7 @@ opensensemap-api==0.1.5 openwebifpy==3.2.7 # homeassistant.components.luci -openwrt-luci-rpc==1.1.6 +openwrt-luci-rpc==1.1.8 # homeassistant.components.ubus openwrt-ubus-rpc==0.0.2 From 7fe3c472e9cc4a494af61893c21d04686c61782a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 15 Mar 2021 02:41:25 -0700 Subject: [PATCH 363/831] Improve bad JSON data reporting (#47932) * Improve bad data reporting * Fix tests Co-authored-by: Erik --- homeassistant/util/json.py | 15 +++++++++------ tests/components/websocket_api/test_http.py | 4 ++-- tests/util/test_json.py | 19 +++++++++++++++++-- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/homeassistant/util/json.py b/homeassistant/util/json.py index e906462a250..2ce98ffef77 100644 --- a/homeassistant/util/json.py +++ b/homeassistant/util/json.py @@ -112,12 +112,15 @@ def find_paths_unserializable_data( except (ValueError, TypeError): pass - # We convert states and events to dict so we can find bad data inside it - if isinstance(obj, State): - obj_path += f"(state: {obj.entity_id})" - obj = obj.as_dict() - elif isinstance(obj, Event): - obj_path += f"(event: {obj.event_type})" + # We convert objects with as_dict to their dict values so we can find bad data inside it + if hasattr(obj, "as_dict"): + desc = obj.__class__.__name__ + if isinstance(obj, State): + desc += f": {obj.entity_id}" + elif isinstance(obj, Event): + desc += f": {obj.event_type}" + + obj_path += f"({desc})" obj = obj.as_dict() if isinstance(obj, dict): diff --git a/tests/components/websocket_api/test_http.py b/tests/components/websocket_api/test_http.py index d3cf4b854f8..f3952f1dc4b 100644 --- a/tests/components/websocket_api/test_http.py +++ b/tests/components/websocket_api/test_http.py @@ -67,7 +67,7 @@ async def test_pending_msg_peak(hass, mock_low_peak, hass_ws_client, caplog): async def test_non_json_message(hass, websocket_client, caplog): - """Test trying to serialze non JSON objects.""" + """Test trying to serialize non JSON objects.""" bad_data = object() hass.states.async_set("test_domain.entity", "testing", {"bad": bad_data}) await websocket_client.send_json({"id": 5, "type": "get_states"}) @@ -77,6 +77,6 @@ async def test_non_json_message(hass, websocket_client, caplog): assert msg["type"] == const.TYPE_RESULT assert not msg["success"] assert ( - f"Unable to serialize to JSON. Bad data found at $.result[0](state: test_domain.entity).attributes.bad={bad_data}(" + f"Unable to serialize to JSON. Bad data found at $.result[0](State: test_domain.entity).attributes.bad={bad_data}(" in caplog.text ) diff --git a/tests/util/test_json.py b/tests/util/test_json.py index 1cbaaae7d23..1d82f5972a3 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -153,7 +153,7 @@ def test_find_unserializable_data(): [State("mock_domain.mock_entity", "on", {"bad": bad_data})], dump=partial(dumps, cls=MockJSONEncoder), ) - == {"$[0](state: mock_domain.mock_entity).attributes.bad": bad_data} + == {"$[0](State: mock_domain.mock_entity).attributes.bad": bad_data} ) assert ( @@ -161,5 +161,20 @@ def test_find_unserializable_data(): [Event("bad_event", {"bad_attribute": bad_data})], dump=partial(dumps, cls=MockJSONEncoder), ) - == {"$[0](event: bad_event).data.bad_attribute": bad_data} + == {"$[0](Event: bad_event).data.bad_attribute": bad_data} + ) + + class BadData: + def __init__(self): + self.bla = bad_data + + def as_dict(self): + return {"bla": self.bla} + + assert ( + find_paths_unserializable_data( + BadData(), + dump=partial(dumps, cls=MockJSONEncoder), + ) + == {"$(BadData).bla": bad_data} ) From 99d1e3e71d80fcfcf395536c2d6c1df1c025e423 Mon Sep 17 00:00:00 2001 From: Drzony Date: Mon, 15 Mar 2021 11:24:07 +0100 Subject: [PATCH 364/831] MQTT Light: Use flash attribute in async_turn_off (#47919) --- .../components/mqtt/light/schema_json.py | 26 ++++++++++--------- tests/components/light/common.py | 12 ++++++--- tests/components/mqtt/test_light_json.py | 18 +++++++++++++ 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 99c48aa1c8f..8f2d1dda0a7 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -341,6 +341,18 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): """Flag supported features.""" return self._supported_features + def _set_flash_and_transition(self, message, **kwargs): + if ATTR_TRANSITION in kwargs: + message["transition"] = kwargs[ATTR_TRANSITION] + + if ATTR_FLASH in kwargs: + flash = kwargs.get(ATTR_FLASH) + + if flash == FLASH_LONG: + message["flash"] = self._flash_times[CONF_FLASH_TIME_LONG] + elif flash == FLASH_SHORT: + message["flash"] = self._flash_times[CONF_FLASH_TIME_SHORT] + async def async_turn_on(self, **kwargs): """Turn the device on. @@ -380,16 +392,7 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): self._hs = kwargs[ATTR_HS_COLOR] should_update = True - if ATTR_FLASH in kwargs: - flash = kwargs.get(ATTR_FLASH) - - if flash == FLASH_LONG: - message["flash"] = self._flash_times[CONF_FLASH_TIME_LONG] - elif flash == FLASH_SHORT: - message["flash"] = self._flash_times[CONF_FLASH_TIME_SHORT] - - if ATTR_TRANSITION in kwargs: - message["transition"] = kwargs[ATTR_TRANSITION] + self._set_flash_and_transition(message, **kwargs) if ATTR_BRIGHTNESS in kwargs and self._config[CONF_BRIGHTNESS]: brightness_normalized = kwargs[ATTR_BRIGHTNESS] / DEFAULT_BRIGHTNESS_SCALE @@ -449,8 +452,7 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): """ message = {"state": "OFF"} - if ATTR_TRANSITION in kwargs: - message["transition"] = kwargs[ATTR_TRANSITION] + self._set_flash_and_transition(message, **kwargs) mqtt.async_publish( self.hass, diff --git a/tests/components/light/common.py b/tests/components/light/common.py index a9991bf3594..20ace3641cd 100644 --- a/tests/components/light/common.py +++ b/tests/components/light/common.py @@ -111,16 +111,20 @@ async def async_turn_on( @bind_hass -def turn_off(hass, entity_id=ENTITY_MATCH_ALL, transition=None): +def turn_off(hass, entity_id=ENTITY_MATCH_ALL, transition=None, flash=None): """Turn all or specified light off.""" - hass.add_job(async_turn_off, hass, entity_id, transition) + hass.add_job(async_turn_off, hass, entity_id, transition, flash) -async def async_turn_off(hass, entity_id=ENTITY_MATCH_ALL, transition=None): +async def async_turn_off(hass, entity_id=ENTITY_MATCH_ALL, transition=None, flash=None): """Turn all or specified light off.""" data = { key: value - for key, value in [(ATTR_ENTITY_ID, entity_id), (ATTR_TRANSITION, transition)] + for key, value in [ + (ATTR_ENTITY_ID, entity_id), + (ATTR_TRANSITION, transition), + (ATTR_FLASH, flash), + ] if value is not None } diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index 022df109f38..bdb81e5e5e4 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -876,6 +876,24 @@ async def test_flash_short_and_long(hass, mqtt_mock): state = hass.states.get("light.test") assert state.state == STATE_ON + await common.async_turn_off(hass, "light.test", flash="short") + + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", JsonValidator('{"state": "OFF", "flash": 5}'), 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_OFF + + await common.async_turn_off(hass, "light.test", flash="long") + + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", JsonValidator('{"state": "OFF", "flash": 15}'), 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_OFF + async def test_transition(hass, mqtt_mock): """Test for transition time being sent when included.""" From b2efcb3c22c55770ed2c8cd032d30ea3c1822e88 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 15 Mar 2021 12:15:34 +0100 Subject: [PATCH 365/831] Support all Xiaomi Miio gateway switches (#46657) * Support all gateway switches * fix checks * process revieuw * Update homeassistant/components/xiaomi_miio/switch.py Co-authored-by: Martin Hjelmare * generilize variable matching * fix styling Co-authored-by: Martin Hjelmare --- .../components/xiaomi_miio/__init__.py | 2 +- .../components/xiaomi_miio/sensor.py | 5 ++ .../components/xiaomi_miio/switch.py | 68 ++++++++++++++++++- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index e426322e5dc..e194225409a 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -26,7 +26,7 @@ from .gateway import ConnectXiaomiGateway _LOGGER = logging.getLogger(__name__) -GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "switch", "light"] +GATEWAY_PLATFORMS = ["alarm_control_panel", "light", "sensor", "switch"] SWITCH_PLATFORMS = ["switch"] FAN_PLATFORMS = ["fan"] LIGHT_PLATFORMS = ["light"] diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index cdb38ba9515..e2b645a10ea 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -21,9 +21,11 @@ from homeassistant.const import ( CONF_TOKEN, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_POWER, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, PERCENTAGE, + POWER_WATT, PRESSURE_HPA, TEMP_CELSIUS, ) @@ -77,6 +79,9 @@ GATEWAY_SENSOR_TYPES = { "pressure": SensorType( unit=PRESSURE_HPA, icon=None, device_class=DEVICE_CLASS_PRESSURE ), + "load_power": SensorType( + unit=POWER_WATT, icon=None, device_class=DEVICE_CLASS_POWER + ), } diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index fd75e9f0088..adb18b0de5a 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -7,7 +7,11 @@ from miio import AirConditioningCompanionV3, ChuangmiPlug, DeviceException, Powe from miio.powerstrip import PowerMode import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity +from homeassistant.components.switch import ( + DEVICE_CLASS_SWITCH, + PLATFORM_SCHEMA, + SwitchEntity, +) from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_ENTITY_ID, @@ -25,12 +29,14 @@ from .const import ( CONF_GATEWAY, CONF_MODEL, DOMAIN, + KEY_COORDINATOR, SERVICE_SET_POWER_MODE, SERVICE_SET_POWER_PRICE, SERVICE_SET_WIFI_LED_OFF, SERVICE_SET_WIFI_LED_ON, ) from .device import XiaomiMiioEntity +from .gateway import XiaomiGatewayDevice _LOGGER = logging.getLogger(__name__) @@ -40,6 +46,13 @@ DATA_KEY = "switch.xiaomi_miio" MODEL_POWER_STRIP_V2 = "zimi.powerstrip.v2" MODEL_PLUG_V3 = "chuangmi.plug.v3" +KEY_CHANNEL = "channel" +GATEWAY_SWITCH_VARS = { + "status_ch0": {KEY_CHANNEL: 0}, + "status_ch1": {KEY_CHANNEL: 1}, + "status_ch2": {KEY_CHANNEL: 2}, +} + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, @@ -135,6 +148,25 @@ async def async_setup_entry(hass, config_entry, async_add_entities): model = config_entry.data[CONF_MODEL] unique_id = config_entry.unique_id + if config_entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY: + gateway = hass.data[DOMAIN][config_entry.entry_id][CONF_GATEWAY] + # Gateway sub devices + sub_devices = gateway.devices + coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR] + for sub_device in sub_devices.values(): + if sub_device.device_type != "Switch": + continue + switch_variables = set(sub_device.status) & set(GATEWAY_SWITCH_VARS) + if switch_variables: + entities.extend( + [ + XiaomiGatewaySwitch( + coordinator, sub_device, config_entry, variable + ) + for variable in switch_variables + ] + ) + if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE or ( config_entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY and model == "lumi.acpartner.v3" @@ -227,6 +259,40 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, update_before_add=True) +class XiaomiGatewaySwitch(XiaomiGatewayDevice, SwitchEntity): + """Representation of a XiaomiGatewaySwitch.""" + + def __init__(self, coordinator, sub_device, entry, variable): + """Initialize the XiaomiSensor.""" + super().__init__(coordinator, sub_device, entry) + self._channel = GATEWAY_SWITCH_VARS[variable][KEY_CHANNEL] + self._data_key = f"status_ch{self._channel}" + self._unique_id = f"{sub_device.sid}-ch{self._channel}" + self._name = f"{sub_device.name} ch{self._channel} ({sub_device.sid})" + + @property + def device_class(self): + """Return the device class of this entity.""" + return DEVICE_CLASS_SWITCH + + @property + def is_on(self): + """Return true if switch is on.""" + return self._sub_device.status[self._data_key] == "on" + + async def async_turn_on(self, **kwargs): + """Turn the switch on.""" + await self.hass.async_add_executor_job(self._sub_device.on, self._channel) + + async def async_turn_off(self, **kwargs): + """Turn the switch off.""" + await self.hass.async_add_executor_job(self._sub_device.off, self._channel) + + async def async_toggle(self, **kwargs): + """Toggle the switch.""" + await self.hass.async_add_executor_job(self._sub_device.toggle, self._channel) + + class XiaomiPlugGenericSwitch(XiaomiMiioEntity, SwitchEntity): """Representation of a Xiaomi Plug Generic.""" From 1aa4fd4cc909a5a8258a871d0210b44095650e9a Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 15 Mar 2021 12:25:11 +0100 Subject: [PATCH 366/831] Make Xiaomi Miio unavailable device independent (#47795) * make unavailable independent * fix data is None * process review comments --- .../components/xiaomi_miio/__init__.py | 24 +++++++++++++------ homeassistant/components/xiaomi_miio/const.py | 2 ++ .../components/xiaomi_miio/gateway.py | 10 +++++++- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index e194225409a..ccecc835b43 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -7,9 +7,10 @@ from miio.gateway.gateway import GatewayException from homeassistant import config_entries, core from homeassistant.const import CONF_HOST, CONF_TOKEN from homeassistant.helpers import device_registry as dr -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ( + ATTR_AVAILABLE, CONF_DEVICE, CONF_FLOW_TYPE, CONF_GATEWAY, @@ -86,13 +87,22 @@ async def async_setup_gateway_entry( sw_version=gateway_info.firmware_version, ) - async def async_update_data(): + def update_data(): """Fetch data from the subdevice.""" - try: - for sub_device in gateway.gateway_device.devices.values(): - await hass.async_add_executor_job(sub_device.update) - except GatewayException as ex: - raise UpdateFailed("Got exception while fetching the state") from ex + data = {} + for sub_device in gateway.gateway_device.devices.values(): + try: + sub_device.update() + except GatewayException as ex: + _LOGGER.error("Got exception while fetching the state: %s", ex) + data[sub_device.sid] = {ATTR_AVAILABLE: False} + else: + data[sub_device.sid] = {ATTR_AVAILABLE: True} + return data + + async def async_update_data(): + """Fetch data from the subdevice using async_add_executor_job.""" + return await hass.async_add_executor_job(update_data) # Create update coordinator coordinator = DataUpdateCoordinator( diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index 977e390f26b..ddde0c77229 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -9,6 +9,8 @@ CONF_MAC = "mac" KEY_COORDINATOR = "coordinator" +ATTR_AVAILABLE = "available" + # Fan Models MODEL_AIRPURIFIER_V1 = "zhimi.airpurifier.v1" MODEL_AIRPURIFIER_V2 = "zhimi.airpurifier.v2" diff --git a/homeassistant/components/xiaomi_miio/gateway.py b/homeassistant/components/xiaomi_miio/gateway.py index 356b19dc89a..be96f77240a 100644 --- a/homeassistant/components/xiaomi_miio/gateway.py +++ b/homeassistant/components/xiaomi_miio/gateway.py @@ -6,7 +6,7 @@ from miio import DeviceException, gateway from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN +from .const import ATTR_AVAILABLE, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -89,3 +89,11 @@ class XiaomiGatewayDevice(CoordinatorEntity, Entity): "model": self._sub_device.model, "sw_version": self._sub_device.firmware_version, } + + @property + def available(self): + """Return if entity is available.""" + if self.coordinator.data is None: + return False + + return self.coordinator.data[self._sub_device.sid][ATTR_AVAILABLE] From cfeb8eb06a13e0a01289e383416e8ce6484f8be9 Mon Sep 17 00:00:00 2001 From: Khole Date: Mon, 15 Mar 2021 11:27:10 +0000 Subject: [PATCH 367/831] Add Hive config flow (#47300) * Add Hive UI * Fix tests and review updates * Slimmed down config_flow * Fix tests * Updated Services.yaml with extra ui attributes * cleanup config flow * Update config entry * Remove ATTR_AVAILABLE * Fix Re-Auth Test * Added more tests. * Update tests --- .coveragerc | 8 +- homeassistant/components/hive/__init__.py | 181 +++--- .../components/hive/binary_sensor.py | 25 +- homeassistant/components/hive/climate.py | 61 +- homeassistant/components/hive/config_flow.py | 171 ++++++ homeassistant/components/hive/const.py | 20 + homeassistant/components/hive/light.py | 29 +- homeassistant/components/hive/manifest.json | 3 +- homeassistant/components/hive/sensor.py | 32 +- homeassistant/components/hive/services.yaml | 54 +- homeassistant/components/hive/strings.json | 53 ++ homeassistant/components/hive/switch.py | 32 +- .../components/hive/translations/en.json | 53 ++ homeassistant/components/hive/water_heater.py | 62 +- homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 2 +- requirements_test_all.txt | 3 + tests/components/hive/test_config_flow.py | 576 ++++++++++++++++++ 18 files changed, 1165 insertions(+), 201 deletions(-) create mode 100644 homeassistant/components/hive/config_flow.py create mode 100644 homeassistant/components/hive/const.py create mode 100644 homeassistant/components/hive/strings.json create mode 100644 homeassistant/components/hive/translations/en.json create mode 100644 tests/components/hive/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index db940ed642b..72d6e1e9293 100644 --- a/.coveragerc +++ b/.coveragerc @@ -386,7 +386,13 @@ omit = homeassistant/components/hikvisioncam/switch.py homeassistant/components/hisense_aehw4a1/* homeassistant/components/hitron_coda/device_tracker.py - homeassistant/components/hive/* + homeassistant/components/hive/__init__.py + homeassistant/components/hive/climate.py + homeassistant/components/hive/binary_sensor.py + homeassistant/components/hive/light.py + homeassistant/components/hive/sensor.py + homeassistant/components/hive/switch.py + homeassistant/components/hive/water_heater.py homeassistant/components/hlk_sw16/__init__.py homeassistant/components/hlk_sw16/switch.py homeassistant/components/home_connect/* diff --git a/homeassistant/components/hive/__init__.py b/homeassistant/components/hive/__init__.py index 331ab37224f..040ef7b4674 100644 --- a/homeassistant/components/hive/__init__.py +++ b/homeassistant/components/hive/__init__.py @@ -1,43 +1,26 @@ """Support for the Hive devices and services.""" +import asyncio from functools import wraps import logging -from pyhiveapi import Hive +from aiohttp.web_exceptions import HTTPException +from apyhiveapi import Hive +from apyhiveapi.helper.hive_exceptions import HiveReauthRequired import voluptuous as vol -from homeassistant.const import ( - ATTR_ENTITY_ID, - ATTR_TEMPERATURE, - CONF_PASSWORD, - CONF_SCAN_INTERVAL, - CONF_USERNAME, -) -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.discovery import async_load_platform +from homeassistant import config_entries +from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, ) from homeassistant.helpers.entity import Entity -_LOGGER = logging.getLogger(__name__) +from .const import DOMAIN, PLATFORM_LOOKUP, PLATFORMS -ATTR_AVAILABLE = "available" -DOMAIN = "hive" -DATA_HIVE = "data_hive" -SERVICES = ["Heating", "HotWater", "TRV"] -SERVICE_BOOST_HOT_WATER = "boost_hot_water" -SERVICE_BOOST_HEATING = "boost_heating" -ATTR_TIME_PERIOD = "time_period" -ATTR_MODE = "on_off" -DEVICETYPES = { - "binary_sensor": "device_list_binary_sensor", - "climate": "device_list_climate", - "water_heater": "device_list_water_heater", - "light": "device_list_light", - "switch": "device_list_plug", - "sensor": "device_list_sensor", -} +_LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema( { @@ -52,101 +35,88 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) -BOOST_HEATING_SCHEMA = vol.Schema( - { - vol.Required(ATTR_ENTITY_ID): cv.entity_id, - vol.Required(ATTR_TIME_PERIOD): vol.All( - cv.time_period, cv.positive_timedelta, lambda td: td.total_seconds() // 60 - ), - vol.Optional(ATTR_TEMPERATURE, default="25.0"): vol.Coerce(float), - } -) - -BOOST_HOT_WATER_SCHEMA = vol.Schema( - { - vol.Required(ATTR_ENTITY_ID): cv.entity_id, - vol.Optional(ATTR_TIME_PERIOD, default="00:30:00"): vol.All( - cv.time_period, cv.positive_timedelta, lambda td: td.total_seconds() // 60 - ), - vol.Required(ATTR_MODE): cv.string, - } -) - async def async_setup(hass, config): - """Set up the Hive Component.""" + """Hive configuration setup.""" + hass.data[DOMAIN] = {} - async def heating_boost(service): - """Handle the service call.""" + if DOMAIN not in config: + return True - entity_lookup = hass.data[DOMAIN]["entity_lookup"] - hive_id = entity_lookup.get(service.data[ATTR_ENTITY_ID]) - if not hive_id: - # log or raise error - _LOGGER.error("Cannot boost entity id entered") - return + conf = config[DOMAIN] - minutes = service.data[ATTR_TIME_PERIOD] - temperature = service.data[ATTR_TEMPERATURE] + if not hass.config_entries.async_entries(DOMAIN): + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_USERNAME: conf[CONF_USERNAME], + CONF_PASSWORD: conf[CONF_PASSWORD], + }, + ) + ) + return True - hive.heating.turn_boost_on(hive_id, minutes, temperature) - async def hot_water_boost(service): - """Handle the service call.""" - entity_lookup = hass.data[DOMAIN]["entity_lookup"] - hive_id = entity_lookup.get(service.data[ATTR_ENTITY_ID]) - if not hive_id: - # log or raise error - _LOGGER.error("Cannot boost entity id entered") - return - minutes = service.data[ATTR_TIME_PERIOD] - mode = service.data[ATTR_MODE] +async def async_setup_entry(hass, entry): + """Set up Hive from a config entry.""" - if mode == "on": - hive.hotwater.turn_boost_on(hive_id, minutes) - elif mode == "off": - hive.hotwater.turn_boost_off(hive_id) + websession = aiohttp_client.async_get_clientsession(hass) + hive = Hive(websession) + hive_config = dict(entry.data) - hive = Hive() + hive_config["options"] = {} + hive_config["options"].update( + {CONF_SCAN_INTERVAL: dict(entry.options).get(CONF_SCAN_INTERVAL, 120)} + ) + hass.data[DOMAIN][entry.entry_id] = hive - config = {} - config["username"] = config[DOMAIN][CONF_USERNAME] - config["password"] = config[DOMAIN][CONF_PASSWORD] - config["update_interval"] = config[DOMAIN][CONF_SCAN_INTERVAL] - - devices = await hive.session.startSession(config) - - if devices is None: - _LOGGER.error("Hive API initialization failed") + try: + devices = await hive.session.startSession(hive_config) + except HTTPException as error: + _LOGGER.error("Could not connect to the internet: %s", error) + raise ConfigEntryNotReady() from error + except HiveReauthRequired: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "unique_id": entry.unique_id, + }, + data=entry.data, + ) + ) return False - hass.data[DOMAIN][DATA_HIVE] = hive - hass.data[DOMAIN]["entity_lookup"] = {} - - for ha_type in DEVICETYPES: - devicelist = devices.get(DEVICETYPES[ha_type]) - if devicelist: + for ha_type, hive_type in PLATFORM_LOOKUP.items(): + device_list = devices.get(hive_type) + if device_list: hass.async_create_task( - async_load_platform(hass, ha_type, DOMAIN, devicelist, config) + hass.config_entries.async_forward_entry_setup(entry, ha_type) ) - if ha_type == "climate": - hass.services.async_register( - DOMAIN, - SERVICE_BOOST_HEATING, - heating_boost, - schema=BOOST_HEATING_SCHEMA, - ) - if ha_type == "water_heater": - hass.services.async_register( - DOMAIN, - SERVICE_BOOST_HOT_WATER, - hot_water_boost, - schema=BOOST_HOT_WATER_SCHEMA, - ) return True +async def async_unload_entry(hass, entry): + """Unload a config entry.""" + + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok + + def refresh_system(func): """Force update all entities after state change.""" @@ -173,6 +143,3 @@ class HiveEntity(Entity): self.async_on_remove( async_dispatcher_connect(self.hass, DOMAIN, self.async_write_ha_state) ) - if self.device["hiveType"] in SERVICES: - entity_lookup = self.hass.data[DOMAIN]["entity_lookup"] - entity_lookup[self.entity_id] = self.device["hiveID"] diff --git a/homeassistant/components/hive/binary_sensor.py b/homeassistant/components/hive/binary_sensor.py index eed08c45b3a..d5f1ca53afd 100644 --- a/homeassistant/components/hive/binary_sensor.py +++ b/homeassistant/components/hive/binary_sensor.py @@ -10,7 +10,8 @@ from homeassistant.components.binary_sensor import ( BinarySensorEntity, ) -from . import ATTR_AVAILABLE, ATTR_MODE, DATA_HIVE, DOMAIN, HiveEntity +from . import HiveEntity +from .const import ATTR_MODE, DOMAIN DEVICETYPE = { "contactsensor": DEVICE_CLASS_OPENING, @@ -24,13 +25,11 @@ PARALLEL_UPDATES = 0 SCAN_INTERVAL = timedelta(seconds=15) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Hive Binary Sensor.""" - if discovery_info is None: - return +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Hive thermostat based on a config entry.""" - hive = hass.data[DOMAIN].get(DATA_HIVE) - devices = hive.devices.get("binary_sensor") + hive = hass.data[DOMAIN][entry.entry_id] + devices = hive.session.deviceList.get("binary_sensor") entities = [] if devices: for dev in devices: @@ -49,7 +48,14 @@ class HiveBinarySensorEntity(HiveEntity, BinarySensorEntity): @property def device_info(self): """Return device information.""" - return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} + return { + "identifiers": {(DOMAIN, self.device["device_id"])}, + "name": self.device["device_name"], + "model": self.device["deviceData"]["model"], + "manufacturer": self.device["deviceData"]["manufacturer"], + "sw_version": self.device["deviceData"]["version"], + "via_device": (DOMAIN, self.device["parentDevice"]), + } @property def device_class(self): @@ -72,7 +78,6 @@ class HiveBinarySensorEntity(HiveEntity, BinarySensorEntity): def extra_state_attributes(self): """Show Device Attributes.""" return { - ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE), ATTR_MODE: self.attributes.get(ATTR_MODE), } @@ -84,5 +89,5 @@ class HiveBinarySensorEntity(HiveEntity, BinarySensorEntity): async def async_update(self): """Update all Node data from Hive.""" await self.hive.session.updateData(self.device) - self.device = await self.hive.sensor.get_sensor(self.device) + self.device = await self.hive.sensor.getSensor(self.device) self.attributes = self.device.get("attributes", {}) diff --git a/homeassistant/components/hive/climate.py b/homeassistant/components/hive/climate.py index c0b33dbb3ae..31b4bd273ad 100644 --- a/homeassistant/components/hive/climate.py +++ b/homeassistant/components/hive/climate.py @@ -1,6 +1,8 @@ """Support for the Hive climate devices.""" from datetime import timedelta +import voluptuous as vol + from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_HEAT, @@ -15,8 +17,10 @@ from homeassistant.components.climate.const import ( SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.helpers import config_validation as cv, entity_platform -from . import ATTR_AVAILABLE, DATA_HIVE, DOMAIN, HiveEntity, refresh_system +from . import HiveEntity, refresh_system +from .const import ATTR_TIME_PERIOD, DOMAIN, SERVICE_BOOST_HEATING HIVE_TO_HASS_STATE = { "SCHEDULE": HVAC_MODE_AUTO, @@ -45,19 +49,32 @@ PARALLEL_UPDATES = 0 SCAN_INTERVAL = timedelta(seconds=15) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Hive thermostat.""" - if discovery_info is None: - return +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Hive thermostat based on a config entry.""" - hive = hass.data[DOMAIN].get(DATA_HIVE) - devices = hive.devices.get("climate") + hive = hass.data[DOMAIN][entry.entry_id] + devices = hive.session.deviceList.get("climate") entities = [] if devices: for dev in devices: entities.append(HiveClimateEntity(hive, dev)) async_add_entities(entities, True) + platform = entity_platform.current_platform.get() + + platform.async_register_entity_service( + SERVICE_BOOST_HEATING, + { + vol.Required(ATTR_TIME_PERIOD): vol.All( + cv.time_period, + cv.positive_timedelta, + lambda td: td.total_seconds() // 60, + ), + vol.Optional(ATTR_TEMPERATURE, default="25.0"): vol.Coerce(float), + }, + "async_heating_boost", + ) + class HiveClimateEntity(HiveEntity, ClimateEntity): """Hive Climate Device.""" @@ -76,7 +93,14 @@ class HiveClimateEntity(HiveEntity, ClimateEntity): @property def device_info(self): """Return device information.""" - return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} + return { + "identifiers": {(DOMAIN, self.device["device_id"])}, + "name": self.device["device_name"], + "model": self.device["deviceData"]["model"], + "manufacturer": self.device["deviceData"]["manufacturer"], + "sw_version": self.device["deviceData"]["version"], + "via_device": (DOMAIN, self.device["parentDevice"]), + } @property def supported_features(self): @@ -93,11 +117,6 @@ class HiveClimateEntity(HiveEntity, ClimateEntity): """Return if the device is available.""" return self.device["deviceData"]["online"] - @property - def extra_state_attributes(self): - """Show Device Attributes.""" - return {ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE)} - @property def hvac_modes(self): """Return the list of available hvac operation modes. @@ -160,27 +179,31 @@ class HiveClimateEntity(HiveEntity, ClimateEntity): async def async_set_hvac_mode(self, hvac_mode): """Set new target hvac mode.""" new_mode = HASS_TO_HIVE_STATE[hvac_mode] - await self.hive.heating.set_mode(self.device, new_mode) + await self.hive.heating.setMode(self.device, new_mode) @refresh_system async def async_set_temperature(self, **kwargs): """Set new target temperature.""" new_temperature = kwargs.get(ATTR_TEMPERATURE) if new_temperature is not None: - await self.hive.heating.set_target_temperature(self.device, new_temperature) + await self.hive.heating.setTargetTemperature(self.device, new_temperature) @refresh_system async def async_set_preset_mode(self, preset_mode): """Set new preset mode.""" if preset_mode == PRESET_NONE and self.preset_mode == PRESET_BOOST: - await self.hive.heating.turn_boost_off(self.device) + await self.hive.heating.turnBoostOff(self.device) elif preset_mode == PRESET_BOOST: curtemp = round(self.current_temperature * 2) / 2 temperature = curtemp + 0.5 - await self.hive.heating.turn_boost_on(self.device, 30, temperature) + await self.hive.heating.turnBoostOn(self.device, 30, temperature) + + @refresh_system + async def async_heating_boost(self, time_period, temperature): + """Handle boost heating service call.""" + await self.hive.heating.turnBoostOn(self.device, time_period, temperature) async def async_update(self): """Update all Node data from Hive.""" await self.hive.session.updateData(self.device) - self.device = await self.hive.heating.get_heating(self.device) - self.attributes.update(self.device.get("attributes", {})) + self.device = await self.hive.heating.getHeating(self.device) diff --git a/homeassistant/components/hive/config_flow.py b/homeassistant/components/hive/config_flow.py new file mode 100644 index 00000000000..ff58e8d96df --- /dev/null +++ b/homeassistant/components/hive/config_flow.py @@ -0,0 +1,171 @@ +"""Config Flow for Hive.""" + +from apyhiveapi import Auth +from apyhiveapi.helper.hive_exceptions import ( + HiveApiError, + HiveInvalid2FACode, + HiveInvalidPassword, + HiveInvalidUsername, +) +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME +from homeassistant.core import callback + +from .const import ( # pylint:disable=unused-import + CONF_CODE, + CONFIG_ENTRY_VERSION, + DOMAIN, +) + + +class HiveFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a Hive config flow.""" + + VERSION = CONFIG_ENTRY_VERSION + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + def __init__(self): + """Initialize the config flow.""" + self.hive_auth = None + self.data = {} + self.tokens = {} + self.entry = None + + async def async_step_user(self, user_input=None): + """Prompt user input. Create or edit entry.""" + errors = {} + # Login to Hive with user data. + if user_input is not None: + self.data.update(user_input) + self.hive_auth = Auth( + username=self.data[CONF_USERNAME], password=self.data[CONF_PASSWORD] + ) + + # Get user from existing entry and abort if already setup + self.entry = await self.async_set_unique_id(self.data[CONF_USERNAME]) + if self.context["source"] != config_entries.SOURCE_REAUTH: + self._abort_if_unique_id_configured() + + # Login to the Hive. + try: + self.tokens = await self.hive_auth.login() + except HiveInvalidUsername: + errors["base"] = "invalid_username" + except HiveInvalidPassword: + errors["base"] = "invalid_password" + except HiveApiError: + errors["base"] = "no_internet_available" + + if self.tokens.get("ChallengeName") == "SMS_MFA": + # Complete SMS 2FA. + return await self.async_step_2fa() + + if not errors: + # Complete the entry setup. + try: + return await self.async_setup_hive_entry() + except UnknownHiveError: + errors["base"] = "unknown" + + # Show User Input form. + schema = vol.Schema( + {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} + ) + return self.async_show_form(step_id="user", data_schema=schema, errors=errors) + + async def async_step_2fa(self, user_input=None): + """Handle 2fa step.""" + errors = {} + + if user_input and user_input["2fa"] == "0000": + self.tokens = await self.hive_auth.login() + elif user_input: + try: + self.tokens = await self.hive_auth.sms_2fa( + user_input["2fa"], self.tokens + ) + except HiveInvalid2FACode: + errors["base"] = "invalid_code" + except HiveApiError: + errors["base"] = "no_internet_available" + + if not errors: + try: + return await self.async_setup_hive_entry() + except UnknownHiveError: + errors["base"] = "unknown" + + schema = vol.Schema({vol.Required(CONF_CODE): str}) + return self.async_show_form(step_id="2fa", data_schema=schema, errors=errors) + + async def async_setup_hive_entry(self): + """Finish setup and create the config entry.""" + + if "AuthenticationResult" not in self.tokens: + raise UnknownHiveError + + # Setup the config entry + self.data["tokens"] = self.tokens + if self.context["source"] == config_entries.SOURCE_REAUTH: + self.hass.config_entries.async_update_entry( + self.entry, title=self.data["username"], data=self.data + ) + await self.hass.config_entries.async_reload(self.entry.entry_id) + return self.async_abort(reason="reauth_successful") + return self.async_create_entry(title=self.data["username"], data=self.data) + + async def async_step_reauth(self, user_input=None): + """Re Authenticate a user.""" + data = { + CONF_USERNAME: user_input[CONF_USERNAME], + CONF_PASSWORD: user_input[CONF_PASSWORD], + } + return await self.async_step_user(data) + + async def async_step_import(self, user_input=None): + """Import user.""" + return await self.async_step_user(user_input) + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Hive options callback.""" + return HiveOptionsFlowHandler(config_entry) + + +class HiveOptionsFlowHandler(config_entries.OptionsFlow): + """Config flow options for Hive.""" + + def __init__(self, config_entry): + """Initialize Hive options flow.""" + self.hive = None + self.config_entry = config_entry + self.interval = config_entry.options.get(CONF_SCAN_INTERVAL, 120) + + async def async_step_init(self, user_input=None): + """Manage the options.""" + return await self.async_step_user() + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + self.hive = self.hass.data["hive"][self.config_entry.entry_id] + errors = {} + if user_input is not None: + new_interval = user_input.get(CONF_SCAN_INTERVAL) + await self.hive.updateInterval(new_interval) + return self.async_create_entry(title="", data=user_input) + + schema = vol.Schema( + { + vol.Optional(CONF_SCAN_INTERVAL, default=self.interval): vol.All( + vol.Coerce(int), vol.Range(min=30) + ) + } + ) + return self.async_show_form(step_id="user", data_schema=schema, errors=errors) + + +class UnknownHiveError(Exception): + """Catch unknown hive error.""" diff --git a/homeassistant/components/hive/const.py b/homeassistant/components/hive/const.py new file mode 100644 index 00000000000..ea416fbfe32 --- /dev/null +++ b/homeassistant/components/hive/const.py @@ -0,0 +1,20 @@ +"""Constants for Hive.""" +ATTR_MODE = "mode" +ATTR_TIME_PERIOD = "time_period" +ATTR_ONOFF = "on_off" +CONF_CODE = "2fa" +CONFIG_ENTRY_VERSION = 1 +DEFAULT_NAME = "Hive" +DOMAIN = "hive" +PLATFORMS = ["binary_sensor", "climate", "light", "sensor", "switch", "water_heater"] +PLATFORM_LOOKUP = { + "binary_sensor": "binary_sensor", + "climate": "climate", + "light": "light", + "sensor": "sensor", + "switch": "switch", + "water_heater": "water_heater", +} +SERVICE_BOOST_HOT_WATER = "boost_hot_water" +SERVICE_BOOST_HEATING = "boost_heating" +WATER_HEATER_MODES = ["on", "off"] diff --git a/homeassistant/components/hive/light.py b/homeassistant/components/hive/light.py index 12779ef9d2e..46e8c5b5790 100644 --- a/homeassistant/components/hive/light.py +++ b/homeassistant/components/hive/light.py @@ -12,19 +12,18 @@ from homeassistant.components.light import ( ) import homeassistant.util.color as color_util -from . import ATTR_AVAILABLE, ATTR_MODE, DATA_HIVE, DOMAIN, HiveEntity, refresh_system +from . import HiveEntity, refresh_system +from .const import ATTR_MODE, DOMAIN PARALLEL_UPDATES = 0 SCAN_INTERVAL = timedelta(seconds=15) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Hive Light.""" - if discovery_info is None: - return +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Hive thermostat based on a config entry.""" - hive = hass.data[DOMAIN].get(DATA_HIVE) - devices = hive.devices.get("light") + hive = hass.data[DOMAIN][entry.entry_id] + devices = hive.session.deviceList.get("light") entities = [] if devices: for dev in devices: @@ -43,7 +42,14 @@ class HiveDeviceLight(HiveEntity, LightEntity): @property def device_info(self): """Return device information.""" - return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} + return { + "identifiers": {(DOMAIN, self.device["device_id"])}, + "name": self.device["device_name"], + "model": self.device["deviceData"]["model"], + "manufacturer": self.device["deviceData"]["manufacturer"], + "sw_version": self.device["deviceData"]["version"], + "via_device": (DOMAIN, self.device["parentDevice"]), + } @property def name(self): @@ -59,7 +65,6 @@ class HiveDeviceLight(HiveEntity, LightEntity): def extra_state_attributes(self): """Show Device Attributes.""" return { - ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE), ATTR_MODE: self.attributes.get(ATTR_MODE), } @@ -117,14 +122,14 @@ class HiveDeviceLight(HiveEntity, LightEntity): saturation = int(get_new_color[1]) new_color = (hue, saturation, 100) - await self.hive.light.turn_on( + await self.hive.light.turnOn( self.device, new_brightness, new_color_temp, new_color ) @refresh_system async def async_turn_off(self, **kwargs): """Instruct the light to turn off.""" - await self.hive.light.turn_off(self.device) + await self.hive.light.turnOff(self.device) @property def supported_features(self): @@ -142,5 +147,5 @@ class HiveDeviceLight(HiveEntity, LightEntity): async def async_update(self): """Update all Node data from Hive.""" await self.hive.session.updateData(self.device) - self.device = await self.hive.light.get_light(self.device) + self.device = await self.hive.light.getLight(self.device) self.attributes.update(self.device.get("attributes", {})) diff --git a/homeassistant/components/hive/manifest.json b/homeassistant/components/hive/manifest.json index 27f235949bf..f8f40401599 100644 --- a/homeassistant/components/hive/manifest.json +++ b/homeassistant/components/hive/manifest.json @@ -1,9 +1,10 @@ { "domain": "hive", "name": "Hive", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/hive", "requirements": [ - "pyhiveapi==0.3.4.4" + "pyhiveapi==0.3.9" ], "codeowners": [ "@Rendili", diff --git a/homeassistant/components/hive/sensor.py b/homeassistant/components/hive/sensor.py index fe413a35b2f..53cc643250c 100644 --- a/homeassistant/components/hive/sensor.py +++ b/homeassistant/components/hive/sensor.py @@ -5,7 +5,8 @@ from datetime import timedelta from homeassistant.components.sensor import DEVICE_CLASS_BATTERY from homeassistant.helpers.entity import Entity -from . import ATTR_AVAILABLE, DATA_HIVE, DOMAIN, HiveEntity +from . import HiveEntity +from .const import DOMAIN PARALLEL_UPDATES = 0 SCAN_INTERVAL = timedelta(seconds=15) @@ -14,18 +15,15 @@ DEVICETYPE = { } -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Hive Sensor.""" - if discovery_info is None: - return +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Hive thermostat based on a config entry.""" - hive = hass.data[DOMAIN].get(DATA_HIVE) - devices = hive.devices.get("sensor") + hive = hass.data[DOMAIN][entry.entry_id] + devices = hive.session.deviceList.get("sensor") entities = [] if devices: for dev in devices: - if dev["hiveType"] in DEVICETYPE: - entities.append(HiveSensorEntity(hive, dev)) + entities.append(HiveSensorEntity(hive, dev)) async_add_entities(entities, True) @@ -40,7 +38,14 @@ class HiveSensorEntity(HiveEntity, Entity): @property def device_info(self): """Return device information.""" - return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} + return { + "identifiers": {(DOMAIN, self.device["device_id"])}, + "name": self.device["device_name"], + "model": self.device["deviceData"]["model"], + "manufacturer": self.device["deviceData"]["manufacturer"], + "sw_version": self.device["deviceData"]["version"], + "via_device": (DOMAIN, self.device["parentDevice"]), + } @property def available(self): @@ -67,12 +72,7 @@ class HiveSensorEntity(HiveEntity, Entity): """Return the state of the sensor.""" return self.device["status"]["state"] - @property - def extra_state_attributes(self): - """Return the state attributes.""" - return {ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE)} - async def async_update(self): """Update all Node data from Hive.""" await self.hive.session.updateData(self.device) - self.device = await self.hive.sensor.get_sensor(self.device) + self.device = await self.hive.sensor.getSensor(self.device) diff --git a/homeassistant/components/hive/services.yaml b/homeassistant/components/hive/services.yaml index f09baea7655..f029af7b0b5 100644 --- a/homeassistant/components/hive/services.yaml +++ b/homeassistant/components/hive/services.yaml @@ -1,24 +1,62 @@ boost_heating: + name: Boost Heating description: Set the boost mode ON defining the period of time and the desired target temperature for the boost. fields: entity_id: - description: Enter the entity_id for the device required to set the boost mode. - example: "climate.heating" + name: Entity ID + description: Select entity_id to boost. + required: true + example: climate.heating + selector: + entity: + integration: hive + domain: climate time_period: + name: Time Period description: Set the time period for the boost. - example: "01:30:00" + required: true + example: 01:30:00 + selector: + time: temperature: + name: Temperature description: Set the target temperature for the boost period. - example: "20.5" + required: true + example: 20.5 + selector: + number: + min: 7 + max: 35 + step: 0.5 + unit_of_measurement: degrees + mode: slider boost_hot_water: - description: "Set the boost mode ON or OFF defining the period of time for the boost." + name: Boost Hotwater + description: Set the boost mode ON or OFF defining the period of time for the boost. fields: entity_id: - description: Enter the entity_id for the device reuired to set the boost mode. - example: "water_heater.hot_water" + name: Entity ID + description: Select entity_id to boost. + required: true + example: water_heater.hot_water + selector: + entity: + integration: hive + domain: water_heater time_period: + name: Time Period description: Set the time period for the boost. - example: "01:30:00" + required: true + example: 01:30:00 + selector: + time: on_off: + name: Mode description: Set the boost function on or off. + required: true example: "on" + selector: + select: + options: + - "on" + - "off" diff --git a/homeassistant/components/hive/strings.json b/homeassistant/components/hive/strings.json new file mode 100644 index 00000000000..0a7a587b2db --- /dev/null +++ b/homeassistant/components/hive/strings.json @@ -0,0 +1,53 @@ +{ + "config": { + "step": { + "user": { + "title": "Hive Login", + "description": "Enter your Hive login information and configuration.", + "data": { + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]", + "scan_interval": "Scan Interval (seconds)" + } + }, + "2fa": { + "title": "Hive Two-factor Authentication.", + "description": "Enter your Hive authentication code. \n \n Please enter code 0000 to request another code.", + "data": { + "2fa": "Two-factor code" + } + }, + "reauth": { + "title": "Hive Login", + "description": "Re-enter your Hive login information.", + "data": { + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + } + } + }, + "error": { + "invalid_username": "Failed to sign into Hive. Your email address is not recognised.", + "invalid_password": "Failed to sign into Hive. Incorrect password please try again.", + "invalid_code": "Failed to sign into Hive. Your two-factor authentication code was incorrect.", + "no_internet_available": "An internet connection is required to connect to Hive.", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "unknown_entry": "Unable to find existing entry.", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" + } + }, + "options": { + "step": { + "user": { + "title": "Options for Hive", + "description": "Update the scan interval to poll for data more often.", + "data": { + "scan_interval": "Scan Interval (seconds)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/switch.py b/homeassistant/components/hive/switch.py index 821f48dbf97..acc2040db00 100644 --- a/homeassistant/components/hive/switch.py +++ b/homeassistant/components/hive/switch.py @@ -3,19 +3,18 @@ from datetime import timedelta from homeassistant.components.switch import SwitchEntity -from . import ATTR_AVAILABLE, ATTR_MODE, DATA_HIVE, DOMAIN, HiveEntity, refresh_system +from . import HiveEntity, refresh_system +from .const import ATTR_MODE, DOMAIN PARALLEL_UPDATES = 0 SCAN_INTERVAL = timedelta(seconds=15) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Hive Switch.""" - if discovery_info is None: - return +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Hive thermostat based on a config entry.""" - hive = hass.data[DOMAIN].get(DATA_HIVE) - devices = hive.devices.get("switch") + hive = hass.data[DOMAIN][entry.entry_id] + devices = hive.session.deviceList.get("switch") entities = [] if devices: for dev in devices: @@ -34,7 +33,15 @@ class HiveDevicePlug(HiveEntity, SwitchEntity): @property def device_info(self): """Return device information.""" - return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} + if self.device["hiveType"] == "activeplug": + return { + "identifiers": {(DOMAIN, self.device["device_id"])}, + "name": self.device["device_name"], + "model": self.device["deviceData"]["model"], + "manufacturer": self.device["deviceData"]["manufacturer"], + "sw_version": self.device["deviceData"]["version"], + "via_device": (DOMAIN, self.device["parentDevice"]), + } @property def name(self): @@ -50,7 +57,6 @@ class HiveDevicePlug(HiveEntity, SwitchEntity): def extra_state_attributes(self): """Show Device Attributes.""" return { - ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE), ATTR_MODE: self.attributes.get(ATTR_MODE), } @@ -67,16 +73,14 @@ class HiveDevicePlug(HiveEntity, SwitchEntity): @refresh_system async def async_turn_on(self, **kwargs): """Turn the switch on.""" - if self.device["hiveType"] == "activeplug": - await self.hive.switch.turn_on(self.device) + await self.hive.switch.turnOn(self.device) @refresh_system async def async_turn_off(self, **kwargs): """Turn the device off.""" - if self.device["hiveType"] == "activeplug": - await self.hive.switch.turn_off(self.device) + await self.hive.switch.turnOff(self.device) async def async_update(self): """Update all Node data from Hive.""" await self.hive.session.updateData(self.device) - self.device = await self.hive.switch.get_plug(self.device) + self.device = await self.hive.switch.getPlug(self.device) diff --git a/homeassistant/components/hive/translations/en.json b/homeassistant/components/hive/translations/en.json new file mode 100644 index 00000000000..1d491d64ebf --- /dev/null +++ b/homeassistant/components/hive/translations/en.json @@ -0,0 +1,53 @@ +{ + "config": { + "step": { + "user": { + "title": "Hive Login", + "description": "Enter your Hive login information and configuration.", + "data": { + "username": "Username", + "password": "Password", + "scan_interval": "Scan Interval (seconds)" + } + }, + "2fa": { + "title": "Hive Two-factor Authentication.", + "description": "Enter your Hive authentication code. \n \n Please enter code 0000 to request another code.", + "data": { + "2fa": "Two-factor code" + } + }, + "reauth": { + "title": "Hive Login", + "description": "Re-enter your Hive login information.", + "data": { + "username": "Username", + "password": "Password" + } + } + }, + "error": { + "invalid_username": "Failed to sign into Hive. Your email address is not recognised.", + "invalid_password": "Failed to sign into Hive. Incorrect password please try again.", + "invalid_code": "Failed to sign into Hive. Your two-factor authentication code was incorrect.", + "no_internet_available": "An internet connection is required to connect to Hive.", + "unknown": "Unexpected error" + }, + "abort": { + "already_configured": "Account is already configured", + "unknown_entry": "Unable to find existing entry.", + "reauth_successful": "Re-authentication was successful" + } + }, + "options": { + "step": { + "user": { + "title": "Options for Hive", + "description": "Update the scan interval to poll for data more often.", + "data": { + "scan_interval": "Scan Interval (seconds)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/water_heater.py b/homeassistant/components/hive/water_heater.py index 56e98a690b8..5d8eb590ea7 100644 --- a/homeassistant/components/hive/water_heater.py +++ b/homeassistant/components/hive/water_heater.py @@ -2,6 +2,8 @@ from datetime import timedelta +import voluptuous as vol + from homeassistant.components.water_heater import ( STATE_ECO, STATE_OFF, @@ -10,8 +12,16 @@ from homeassistant.components.water_heater import ( WaterHeaterEntity, ) from homeassistant.const import TEMP_CELSIUS +from homeassistant.helpers import config_validation as cv, entity_platform -from . import DATA_HIVE, DOMAIN, HiveEntity, refresh_system +from . import HiveEntity, refresh_system +from .const import ( + ATTR_ONOFF, + ATTR_TIME_PERIOD, + DOMAIN, + SERVICE_BOOST_HOT_WATER, + WATER_HEATER_MODES, +) SUPPORT_FLAGS_HEATER = SUPPORT_OPERATION_MODE HOTWATER_NAME = "Hot Water" @@ -32,19 +42,32 @@ HASS_TO_HIVE_STATE = { SUPPORT_WATER_HEATER = [STATE_ECO, STATE_ON, STATE_OFF] -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Hive Hotwater.""" - if discovery_info is None: - return +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Hive thermostat based on a config entry.""" - hive = hass.data[DOMAIN].get(DATA_HIVE) - devices = hive.devices.get("water_heater") + hive = hass.data[DOMAIN][entry.entry_id] + devices = hive.session.deviceList.get("water_heater") entities = [] if devices: for dev in devices: entities.append(HiveWaterHeater(hive, dev)) async_add_entities(entities, True) + platform = entity_platform.current_platform.get() + + platform.async_register_entity_service( + SERVICE_BOOST_HOT_WATER, + { + vol.Optional(ATTR_TIME_PERIOD, default="00:30:00"): vol.All( + cv.time_period, + cv.positive_timedelta, + lambda td: td.total_seconds() // 60, + ), + vol.Required(ATTR_ONOFF): vol.In(WATER_HEATER_MODES), + }, + "async_hot_water_boost", + ) + class HiveWaterHeater(HiveEntity, WaterHeaterEntity): """Hive Water Heater Device.""" @@ -57,7 +80,14 @@ class HiveWaterHeater(HiveEntity, WaterHeaterEntity): @property def device_info(self): """Return device information.""" - return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} + return { + "identifiers": {(DOMAIN, self.device["device_id"])}, + "name": self.device["device_name"], + "model": self.device["deviceData"]["model"], + "manufacturer": self.device["deviceData"]["manufacturer"], + "sw_version": self.device["deviceData"]["version"], + "via_device": (DOMAIN, self.device["parentDevice"]), + } @property def supported_features(self): @@ -92,20 +122,28 @@ class HiveWaterHeater(HiveEntity, WaterHeaterEntity): @refresh_system async def async_turn_on(self, **kwargs): """Turn on hotwater.""" - await self.hive.hotwater.set_mode(self.device, "MANUAL") + await self.hive.hotwater.setMode(self.device, "MANUAL") @refresh_system async def async_turn_off(self, **kwargs): """Turn on hotwater.""" - await self.hive.hotwater.set_mode(self.device, "OFF") + await self.hive.hotwater.setMode(self.device, "OFF") @refresh_system async def async_set_operation_mode(self, operation_mode): """Set operation mode.""" new_mode = HASS_TO_HIVE_STATE[operation_mode] - await self.hive.hotwater.set_mode(self.device, new_mode) + await self.hive.hotwater.setMode(self.device, new_mode) + + @refresh_system + async def async_hot_water_boost(self, time_period, on_off): + """Handle the service call.""" + if on_off == "on": + await self.hive.hotwater.turnBoostOn(self.device, time_period) + elif on_off == "off": + await self.hive.hotwater.turnBoostOff(self.device) async def async_update(self): """Update all Node data from Hive.""" await self.hive.session.updateData(self.device) - self.device = await self.hive.hotwater.get_hotwater(self.device) + self.device = await self.hive.hotwater.getHotwater(self.device) diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index a0a846c0d44..057ebe74865 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -92,6 +92,7 @@ FLOWS = [ "harmony", "heos", "hisense_aehw4a1", + "hive", "hlk_sw16", "home_connect", "homekit", diff --git a/requirements_all.txt b/requirements_all.txt index 03fd077d9be..a749acabf99 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1428,7 +1428,7 @@ pyheos==0.7.2 pyhik==0.2.8 # homeassistant.components.hive -pyhiveapi==0.3.4.4 +pyhiveapi==0.3.9 # homeassistant.components.homematic pyhomematic==0.1.72 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7ff9b14e6d7..82425db675b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -747,6 +747,9 @@ pyhaversion==3.4.2 # homeassistant.components.heos pyheos==0.7.2 +# homeassistant.components.hive +pyhiveapi==0.3.9 + # homeassistant.components.homematic pyhomematic==0.1.72 diff --git a/tests/components/hive/test_config_flow.py b/tests/components/hive/test_config_flow.py new file mode 100644 index 00000000000..dae69eebd96 --- /dev/null +++ b/tests/components/hive/test_config_flow.py @@ -0,0 +1,576 @@ +"""Test the Hive config flow.""" +from unittest.mock import patch + +from apyhiveapi.helper import hive_exceptions + +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.hive.const import CONF_CODE, DOMAIN +from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME + +from tests.common import MockConfigEntry + +USERNAME = "username@home-assistant.com" +UPDATED_USERNAME = "updated_username@home-assistant.com" +PASSWORD = "test-password" +UPDATED_PASSWORD = "updated-password" +INCORRECT_PASSWORD = "incoreect-password" +SCAN_INTERVAL = 120 +UPDATED_SCAN_INTERVAL = 60 +MFA_CODE = "1234" +MFA_RESEND_CODE = "0000" +MFA_INVALID_CODE = "HIVE" + + +async def test_import_flow(hass): + """Check import flow.""" + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SUCCESS", + "AuthenticationResult": { + "RefreshToken": "mock-refresh-token", + "AccessToken": "mock-access-token", + }, + }, + ), patch( + "homeassistant.components.hive.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.hive.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == USERNAME + assert result["data"] == { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + "tokens": { + "AuthenticationResult": { + "AccessToken": "mock-access-token", + "RefreshToken": "mock-refresh-token", + }, + "ChallengeName": "SUCCESS", + }, + } + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_user_flow(hass): + """Test the user flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SUCCESS", + "AuthenticationResult": { + "RefreshToken": "mock-refresh-token", + "AccessToken": "mock-access-token", + }, + }, + ), patch( + "homeassistant.components.hive.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.hive.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + await hass.async_block_till_done() + + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == USERNAME + assert result2["data"] == { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + "tokens": { + "AuthenticationResult": { + "AccessToken": "mock-access-token", + "RefreshToken": "mock-refresh-token", + }, + "ChallengeName": "SUCCESS", + }, + } + + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + +async def test_user_flow_2fa(hass): + """Test user flow with 2FA.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SMS_MFA", + }, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + }, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == CONF_CODE + assert result2["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.sms_2fa", + return_value={ + "ChallengeName": "SUCCESS", + "AuthenticationResult": { + "RefreshToken": "mock-refresh-token", + "AccessToken": "mock-access-token", + }, + }, + ), patch( + "homeassistant.components.hive.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.hive.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], {CONF_CODE: MFA_CODE} + ) + await hass.async_block_till_done() + + assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result3["title"] == USERNAME + assert result3["data"] == { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + "tokens": { + "AuthenticationResult": { + "AccessToken": "mock-access-token", + "RefreshToken": "mock-refresh-token", + }, + "ChallengeName": "SUCCESS", + }, + } + + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + +async def test_reauth_flow(hass): + """Test the reauth flow.""" + + await setup.async_setup_component(hass, "persistent_notification", {}) + mock_config = MockConfigEntry( + domain=DOMAIN, + unique_id=USERNAME, + data={ + CONF_USERNAME: USERNAME, + CONF_PASSWORD: INCORRECT_PASSWORD, + "tokens": { + "AccessToken": "mock-access-token", + "RefreshToken": "mock-refresh-token", + }, + }, + ) + mock_config.add_to_hass(hass) + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + side_effect=hive_exceptions.HiveInvalidPassword(), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "unique_id": mock_config.unique_id, + }, + data=mock_config.data, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "invalid_password"} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SUCCESS", + "AuthenticationResult": { + "RefreshToken": "mock-refresh-token", + "AccessToken": "mock-access-token", + }, + }, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: UPDATED_PASSWORD, + }, + ) + await hass.async_block_till_done() + + assert mock_config.data.get("username") == USERNAME + assert mock_config.data.get("password") == UPDATED_PASSWORD + assert result2["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result2["reason"] == "reauth_successful" + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + +async def test_option_flow(hass): + """Test config flow options.""" + + entry = MockConfigEntry( + domain=DOMAIN, + title=USERNAME, + data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init( + entry.entry_id, + data=None, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={CONF_SCAN_INTERVAL: UPDATED_SCAN_INTERVAL} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"][CONF_SCAN_INTERVAL] == UPDATED_SCAN_INTERVAL + + +async def test_user_flow_2fa_send_new_code(hass): + """Resend a 2FA code if it didn't arrive.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SMS_MFA", + }, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + }, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == CONF_CODE + assert result2["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SMS_MFA", + }, + ): + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], {CONF_CODE: MFA_RESEND_CODE} + ) + await hass.async_block_till_done() + + assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result3["step_id"] == CONF_CODE + assert result3["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.sms_2fa", + return_value={ + "ChallengeName": "SUCCESS", + "AuthenticationResult": { + "RefreshToken": "mock-refresh-token", + "AccessToken": "mock-access-token", + }, + }, + ), patch( + "homeassistant.components.hive.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.hive.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result4 = await hass.config_entries.flow.async_configure( + result3["flow_id"], {CONF_CODE: MFA_CODE} + ) + await hass.async_block_till_done() + + assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result4["title"] == USERNAME + assert result4["data"] == { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + "tokens": { + "AuthenticationResult": { + "AccessToken": "mock-access-token", + "RefreshToken": "mock-refresh-token", + }, + "ChallengeName": "SUCCESS", + }, + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + +async def test_abort_if_existing_entry(hass): + """Check flow abort when an entry already exist.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + unique_id=USERNAME, + data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + options={CONF_SCAN_INTERVAL: SCAN_INTERVAL}, + ) + config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + data={ + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + +async def test_user_flow_invalid_username(hass): + """Test user flow with invalid username.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + side_effect=hive_exceptions.HiveInvalidUsername(), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "user" + assert result2["errors"] == {"base": "invalid_username"} + + +async def test_user_flow_invalid_password(hass): + """Test user flow with invalid password.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + side_effect=hive_exceptions.HiveInvalidPassword(), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "user" + assert result2["errors"] == {"base": "invalid_password"} + + +async def test_user_flow_no_internet_connection(hass): + """Test user flow with no internet connection.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + side_effect=hive_exceptions.HiveApiError(), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "user" + assert result2["errors"] == {"base": "no_internet_available"} + + +async def test_user_flow_2fa_no_internet_connection(hass): + """Test user flow with no internet connection.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SMS_MFA", + }, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == CONF_CODE + assert result2["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.sms_2fa", + side_effect=hive_exceptions.HiveApiError(), + ): + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + {CONF_CODE: MFA_CODE}, + ) + + assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result3["step_id"] == CONF_CODE + assert result3["errors"] == {"base": "no_internet_available"} + + +async def test_user_flow_2fa_invalid_code(hass): + """Test user flow with 2FA.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SMS_MFA", + }, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == CONF_CODE + assert result2["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.sms_2fa", + side_effect=hive_exceptions.HiveInvalid2FACode(), + ): + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_CODE: MFA_INVALID_CODE}, + ) + assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result3["step_id"] == CONF_CODE + assert result3["errors"] == {"base": "invalid_code"} + + +async def test_user_flow_unknown_error(hass): + """Test user flow when unknown error occurs.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={"ChallengeName": "FAILED", "InvalidAuthenticationResult": {}}, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + await hass.async_block_till_done() + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["errors"] == {"base": "unknown"} + + +async def test_user_flow_2fa_unknown_error(hass): + """Test 2fa flow when unknown error occurs.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SMS_MFA", + }, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == CONF_CODE + + with patch( + "homeassistant.components.hive.config_flow.Auth.sms_2fa", + return_value={"ChallengeName": "FAILED", "InvalidAuthenticationResult": {}}, + ): + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + {CONF_CODE: MFA_CODE}, + ) + await hass.async_block_till_done() + + assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result3["errors"] == {"base": "unknown"} From 6b98583bc1f6f0672fc19fe6a224c46282f0f180 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Mon, 15 Mar 2021 12:45:36 +0100 Subject: [PATCH 368/831] Add tests for Netatmo climate (#46392) * Add tests for Netatmo climate * Add comments and fake webhook events * Split tests * Split tests * Clean up * Fix coveragerc * Fix requirements * Remove freezegun dependency * Move async_block_till_done to * Call async_handle_webhook directly * Use async_handle_webhook directly p2 * Exclude helper.py from * Remove assertion of implementation details * Use the webhook integration handler * Extract function --- .coveragerc | 2 - homeassistant/components/netatmo/climate.py | 11 +- tests/components/netatmo/common.py | 44 ++ tests/components/netatmo/conftest.py | 127 +++++ tests/components/netatmo/test_climate.py | 539 ++++++++++++++++++ tests/fixtures/netatmo/gethomedata.json | 318 +++++++++++ tests/fixtures/netatmo/getstationsdata.json | 600 ++++++++++++++++++++ tests/fixtures/netatmo/homesdata.json | 595 +++++++++++++++++++ tests/fixtures/netatmo/homestatus.json | 113 ++++ 9 files changed, 2343 insertions(+), 6 deletions(-) create mode 100644 tests/components/netatmo/common.py create mode 100644 tests/components/netatmo/conftest.py create mode 100644 tests/components/netatmo/test_climate.py create mode 100644 tests/fixtures/netatmo/gethomedata.json create mode 100644 tests/fixtures/netatmo/getstationsdata.json create mode 100644 tests/fixtures/netatmo/homesdata.json create mode 100644 tests/fixtures/netatmo/homestatus.json diff --git a/.coveragerc b/.coveragerc index 72d6e1e9293..d406698cbb1 100644 --- a/.coveragerc +++ b/.coveragerc @@ -635,8 +635,6 @@ omit = homeassistant/components/netatmo/__init__.py homeassistant/components/netatmo/api.py homeassistant/components/netatmo/camera.py - homeassistant/components/netatmo/climate.py - homeassistant/components/netatmo/const.py homeassistant/components/netatmo/data_handler.py homeassistant/components/netatmo/helper.py homeassistant/components/netatmo/light.py diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index c2a6e484771..3b05e263f02 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -2,6 +2,7 @@ import logging from typing import List, Optional +import pyatmo import voluptuous as vol from homeassistant.components.climate import ClimateEntity @@ -251,7 +252,7 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): """Handle webhook events.""" data = event["data"] - if not data.get("home"): + if data.get("home") is None: return home = data["home"] @@ -569,7 +570,9 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): schedule_id = sid if not schedule_id: - _LOGGER.error("You passed an invalid schedule") + _LOGGER.error( + "%s is not a invalid schedule", kwargs.get(ATTR_SCHEDULE_NAME) + ) return self._data.switch_home_schedule(home_id=self._home_id, schedule_id=schedule_id) @@ -586,7 +589,7 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): return {**super().device_info, "suggested_area": self._room_data["name"]} -def interpolate(batterylevel, module_type): +def interpolate(batterylevel: int, module_type: str) -> int: """Interpolate battery level depending on device type.""" na_battery_levels = { NA_THERM: { @@ -628,7 +631,7 @@ def interpolate(batterylevel, module_type): return int(pct) -def get_all_home_ids(home_data): +def get_all_home_ids(home_data: pyatmo.HomeData) -> List[str]: """Get all the home ids returned by NetAtmo API.""" if home_data is None: return [] diff --git a/tests/components/netatmo/common.py b/tests/components/netatmo/common.py new file mode 100644 index 00000000000..c8014a9b2a9 --- /dev/null +++ b/tests/components/netatmo/common.py @@ -0,0 +1,44 @@ +"""Common methods used across tests for Netatmo.""" +import json + +from tests.common import load_fixture + +CLIENT_ID = "1234" +CLIENT_SECRET = "5678" +ALL_SCOPES = [ + "read_station", + "read_camera", + "access_camera", + "write_camera", + "read_presence", + "access_presence", + "write_presence", + "read_homecoach", + "read_smokedetector", + "read_thermostat", + "write_thermostat", +] + + +def fake_post_request(**args): + """Return fake data.""" + if "url" not in args: + return "{}" + + endpoint = args["url"].split("/")[-1] + if endpoint in [ + "setpersonsaway", + "setpersonshome", + "setstate", + "setroomthermpoint", + "setthermmode", + "switchhomeschedule", + ]: + return f'{{"{endpoint}": true}}' + + return json.loads(load_fixture(f"netatmo/{endpoint}.json")) + + +def fake_post_request_no_data(**args): + """Fake error during requesting backend data.""" + return "{}" diff --git a/tests/components/netatmo/conftest.py b/tests/components/netatmo/conftest.py new file mode 100644 index 00000000000..b18b70f323e --- /dev/null +++ b/tests/components/netatmo/conftest.py @@ -0,0 +1,127 @@ +"""Provide common Netatmo fixtures.""" +from contextlib import contextmanager +from time import time +from unittest.mock import patch + +import pytest + +from .common import ALL_SCOPES, fake_post_request, fake_post_request_no_data + +from tests.common import MockConfigEntry + + +@pytest.fixture(name="config_entry") +async def mock_config_entry_fixture(hass): + """Mock a config entry.""" + mock_entry = MockConfigEntry( + domain="netatmo", + data={ + "auth_implementation": "cloud", + "token": { + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": 60, + "expires_at": time() + 1000, + "scope": " ".join(ALL_SCOPES), + }, + }, + options={ + "weather_areas": { + "Home avg": { + "lat_ne": 32.2345678, + "lon_ne": -117.1234567, + "lat_sw": 32.1234567, + "lon_sw": -117.2345678, + "show_on_map": False, + "area_name": "Home avg", + "mode": "avg", + }, + "Home max": { + "lat_ne": 32.2345678, + "lon_ne": -117.1234567, + "lat_sw": 32.1234567, + "lon_sw": -117.2345678, + "show_on_map": True, + "area_name": "Home max", + "mode": "max", + }, + } + }, + ) + mock_entry.add_to_hass(hass) + + return mock_entry + + +@contextmanager +def selected_platforms(platforms=["camera", "climate", "light", "sensor"]): + """Restrict loaded platforms to list given.""" + with patch("homeassistant.components.netatmo.PLATFORMS", platforms), patch( + "homeassistant.components.netatmo.api.ConfigEntryNetatmoAuth" + ) as mock_auth, patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), patch( + "homeassistant.components.webhook.async_generate_url" + ): + mock_auth.return_value.post_request.side_effect = fake_post_request + yield + + +@pytest.fixture(name="entry") +async def mock_entry_fixture(hass, config_entry): + """Mock setup of all platforms.""" + with selected_platforms(): + await hass.config_entries.async_setup(config_entry.entry_id) + return config_entry + + +@pytest.fixture(name="sensor_entry") +async def mock_sensor_entry_fixture(hass, config_entry): + """Mock setup of sensor platform.""" + with selected_platforms(["sensor"]): + await hass.config_entries.async_setup(config_entry.entry_id) + return config_entry + + +@pytest.fixture(name="camera_entry") +async def mock_camera_entry_fixture(hass, config_entry): + """Mock setup of camera platform.""" + with selected_platforms(["camera"]): + await hass.config_entries.async_setup(config_entry.entry_id) + return config_entry + + +@pytest.fixture(name="light_entry") +async def mock_light_entry_fixture(hass, config_entry): + """Mock setup of light platform.""" + with selected_platforms(["light"]): + await hass.config_entries.async_setup(config_entry.entry_id) + return config_entry + + +@pytest.fixture(name="climate_entry") +async def mock_climate_entry_fixture(hass, config_entry): + """Mock setup of climate platform.""" + with selected_platforms(["climate"]): + await hass.config_entries.async_setup(config_entry.entry_id) + + await hass.async_block_till_done() + return config_entry + + +@pytest.fixture(name="entry_error") +async def mock_entry_error_fixture(hass, config_entry): + """Mock erroneous setup of platforms.""" + with patch( + "homeassistant.components.netatmo.api.ConfigEntryNetatmoAuth" + ) as mock_auth, patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), patch( + "homeassistant.components.webhook.async_generate_url" + ): + mock_auth.return_value.post_request.side_effect = fake_post_request_no_data + await hass.config_entries.async_setup(config_entry.entry_id) + + await hass.async_block_till_done() + return config_entry diff --git a/tests/components/netatmo/test_climate.py b/tests/components/netatmo/test_climate.py new file mode 100644 index 00000000000..e7ad1669769 --- /dev/null +++ b/tests/components/netatmo/test_climate.py @@ -0,0 +1,539 @@ +"""The tests for the Netatmo climate platform.""" +from unittest.mock import Mock + +import pytest + +from homeassistant.components.climate import ( + DOMAIN as CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + SERVICE_SET_PRESET_MODE, + SERVICE_SET_TEMPERATURE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, +) +from homeassistant.components.climate.const import ( + ATTR_HVAC_MODE, + ATTR_PRESET_MODE, + HVAC_MODE_AUTO, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + PRESET_AWAY, + PRESET_BOOST, +) +from homeassistant.components.netatmo import climate +from homeassistant.components.netatmo.climate import ( + NA_THERM, + NA_VALVE, + PRESET_FROST_GUARD, + PRESET_SCHEDULE, +) +from homeassistant.components.netatmo.const import ( + ATTR_SCHEDULE_NAME, + SERVICE_SET_SCHEDULE, +) +from homeassistant.components.webhook import async_handle_webhook +from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_WEBHOOK_ID +from homeassistant.util.aiohttp import MockRequest + + +async def simulate_webhook(hass, webhook_id, response): + """Simulate a webhook event.""" + request = MockRequest(content=response, mock_source="test") + await async_handle_webhook(hass, webhook_id, request) + await hass.async_block_till_done() + + +async def test_webhook_event_handling_thermostats(hass, climate_entry): + """Test service and webhook event handling with thermostats.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_livingroom = "climate.netatmo_livingroom" + + assert hass.states.get(climate_entity_livingroom).state == "auto" + assert ( + hass.states.get(climate_entity_livingroom).attributes["preset_mode"] + == "Schedule" + ) + assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 12 + + # Test service setting the temperature + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_TEMPERATURE: 21}, + blocking=True, + ) + await hass.async_block_till_done() + + # Fake webhook thermostat manual set point + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",' + b'"home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE",' + b'"rooms": [{ "id": "2746182631", "name": "Livingroom", "type": "livingroom",' + b'"therm_setpoint_mode": "manual", "therm_setpoint_temperature": 21,' + b'"therm_setpoint_end_time": 1612734552}], "modules": [{"id": "12:34:56:00:01:ae",' + b'"name": "Livingroom", "type": "NATherm1"}]}, "mode": "manual", "event_type": "set_point",' + b'"temperature": 21, "push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_livingroom).state == "heat" + assert ( + hass.states.get(climate_entity_livingroom).attributes["preset_mode"] + == "Schedule" + ) + assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 21 + + # Test service setting the HVAC mode to "heat" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_HVAC_MODE: HVAC_MODE_HEAT}, + blocking=True, + ) + await hass.async_block_till_done() + + # Fake webhook thermostat mode change to "Max" + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",' + b'"home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE",' + b'"rooms": [{"id": "2746182631", "name": "Livingroom", "type": "livingroom",' + b'"therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],' + b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"}]},' + b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_livingroom).state == "heat" + assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 30 + + # Test service setting the HVAC mode to "off" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_HVAC_MODE: HVAC_MODE_OFF}, + blocking=True, + ) + await hass.async_block_till_done() + + # Fake webhook turn thermostat off + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",' + b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",' + b'"rooms": [{"id": "2746182631","name": "Livingroom","type": "livingroom",' + b'"therm_setpoint_mode": "off"}],"modules": [{"id": "12:34:56:00:01:ae",' + b'"name": "Livingroom", "type": "NATherm1"}]}, "mode": "off", "event_type": "set_point",' + b'"push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_livingroom).state == "off" + + # Test service setting the HVAC mode to "auto" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_HVAC_MODE: HVAC_MODE_AUTO}, + blocking=True, + ) + await hass.async_block_till_done() + + # Fake webhook thermostat mode cancel set point + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b","room_id": "2746182631",' + b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",' + b'"rooms": [{"id": "2746182631","name": "Livingroom","type": "livingroom",' + b'"therm_setpoint_mode": "home"}], "modules": [{"id": "12:34:56:00:01:ae",' + b'"name": "Livingroom", "type": "NATherm1"}]}, "mode": "home",' + b'"event_type": "cancel_set_point", "push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_livingroom).state == "auto" + assert ( + hass.states.get(climate_entity_livingroom).attributes["preset_mode"] + == "Schedule" + ) + + +async def test_service_preset_mode_frost_guard_thermostat(hass, climate_entry): + """Test service with frost guard preset for thermostats.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_livingroom = "climate.netatmo_livingroom" + + assert hass.states.get(climate_entity_livingroom).state == "auto" + assert ( + hass.states.get(climate_entity_livingroom).attributes["preset_mode"] + == "Schedule" + ) + + # Test service setting the preset mode to "frost guard" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + { + ATTR_ENTITY_ID: climate_entity_livingroom, + ATTR_PRESET_MODE: PRESET_FROST_GUARD, + }, + blocking=True, + ) + await hass.async_block_till_done() + + # Fake webhook thermostat mode change to "Frost Guard" + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b","user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' + b'"event_type": "therm_mode", "home": {"id": "91763b24c43d3e344f424e8b",' + b'"therm_mode": "hg"}, "mode": "hg", "previous_mode": "schedule",' + b'"push_type":"home_event_changed"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_livingroom).state == "auto" + assert ( + hass.states.get(climate_entity_livingroom).attributes["preset_mode"] + == "Frost Guard" + ) + + # Test service setting the preset mode to "frost guard" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + { + ATTR_ENTITY_ID: climate_entity_livingroom, + ATTR_PRESET_MODE: PRESET_SCHEDULE, + }, + blocking=True, + ) + await hass.async_block_till_done() + + # Test webhook thermostat mode change to "Schedule" + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b","user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' + b'"event_type": "therm_mode", "home": {"id": "91763b24c43d3e344f424e8b",' + b'"therm_mode": "schedule"}, "mode": "schedule", "previous_mode": "hg",' + b'"push_type": "home_event_changed"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_livingroom).state == "auto" + assert ( + hass.states.get(climate_entity_livingroom).attributes["preset_mode"] + == "Schedule" + ) + + +async def test_service_preset_modes_thermostat(hass, climate_entry): + """Test service with preset modes for thermostats.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_livingroom = "climate.netatmo_livingroom" + + assert hass.states.get(climate_entity_livingroom).state == "auto" + assert ( + hass.states.get(climate_entity_livingroom).attributes["preset_mode"] + == "Schedule" + ) + + # Test service setting the preset mode to "away" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_PRESET_MODE: PRESET_AWAY}, + blocking=True, + ) + await hass.async_block_till_done() + + # Fake webhook thermostat mode change to "Away" + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b","user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", ' + b'"event_type": "therm_mode","home": {"id": "91763b24c43d3e344f424e8b",' + b'"therm_mode": "away"},"mode": "away","previous_mode": "schedule",' + b'"push_type": "home_event_changed"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_livingroom).state == "auto" + assert ( + hass.states.get(climate_entity_livingroom).attributes["preset_mode"] == "away" + ) + + # Test service setting the preset mode to "boost" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_PRESET_MODE: PRESET_BOOST}, + blocking=True, + ) + await hass.async_block_till_done() + + # TFakeest webhook thermostat mode change to "Max" + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email":"john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",' + b'"home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE",' + b'"rooms": [{"id": "2746182631", "name": "Livingroom", "type": "livingroom",' + b'"therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],' + b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"}]},' + b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_livingroom).state == "heat" + assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 30 + + +async def test_webhook_event_handling_no_data(hass, climate_entry): + """Test service and webhook event handling with erroneous data.""" + # Test webhook without home entry + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' + b'"push_type": "home_event_changed"}' + ) + await simulate_webhook(hass, webhook_id, response) + + # Test webhook with different home id + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "3d3e344f491763b24c424e8b",' + b'"room_id": "2746182631", "home": {"id": "3d3e344f491763b24c424e8b",' + b'"name": "MYHOME","country": "DE", "rooms": [], "modules": []}, "mode": "home",' + b'"event_type": "cancel_set_point", "push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + # Test webhook without room entries + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' + b'"room_id": "2746182631", "home": {"id": "91763b24c43d3e344f424e8b",' + b'"name": "MYHOME", "country": "DE", "rooms": [], "modules": []}, "mode": "home",' + b'"event_type": "cancel_set_point","push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + +async def test_service_schedule_thermostats(hass, climate_entry, caplog): + """Test service for selecting Netatmo schedule with thermostats.""" + climate_entity_livingroom = "climate.netatmo_livingroom" + + # Test setting a valid schedule + await hass.services.async_call( + "netatmo", + SERVICE_SET_SCHEDULE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_SCHEDULE_NAME: "Winter"}, + blocking=True, + ) + await hass.async_block_till_done() + + assert ( + "Setting 91763b24c43d3e344f424e8b schedule to Winter (b1b54a2f45795764f59d50d8)" + in caplog.text + ) + + # Test setting an invalid schedule + await hass.services.async_call( + "netatmo", + SERVICE_SET_SCHEDULE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_SCHEDULE_NAME: "summer"}, + blocking=True, + ) + await hass.async_block_till_done() + + assert "summer is not a invalid schedule" in caplog.text + + +async def test_service_preset_mode_already_boost_valves(hass, climate_entry): + """Test service with boost preset for valves when already in boost mode.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_entrada = "climate.netatmo_entrada" + + assert hass.states.get(climate_entity_entrada).state == "auto" + assert ( + hass.states.get(climate_entity_entrada).attributes["preset_mode"] + == "Frost Guard" + ) + assert hass.states.get(climate_entity_entrada).attributes["temperature"] == 7 + + # Test webhook valve mode change to "Max" + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' + b'"room_id": "2833524037", "home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME",' + b'"country": "DE","rooms": [{"id": "2833524037", "name": "Entrada", "type": "lobby",' + b'"therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],' + b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}]},' + b'"mode": "max","event_type": "set_point","push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + # Test service setting the preset mode to "boost" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: climate_entity_entrada, ATTR_PRESET_MODE: PRESET_BOOST}, + blocking=True, + ) + await hass.async_block_till_done() + + # Test webhook valve mode change to "Max" + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' + b'"room_id": "2833524037", "home": {"id": "91763b24c43d3e344f424e8b",' + b'"name": "MYHOME","country": "DE","rooms": [{"id": "2833524037", "name": "Entrada",' + b'"type": "lobby", "therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],' + b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}]},' + b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_entrada).state == "heat" + assert hass.states.get(climate_entity_entrada).attributes["temperature"] == 30 + + +async def test_service_preset_mode_boost_valves(hass, climate_entry): + """Test service with boost preset for valves.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_entrada = "climate.netatmo_entrada" + + # Test service setting the preset mode to "boost" + assert hass.states.get(climate_entity_entrada).state == "auto" + assert hass.states.get(climate_entity_entrada).attributes["temperature"] == 7 + + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: climate_entity_entrada, ATTR_PRESET_MODE: PRESET_BOOST}, + blocking=True, + ) + await hass.async_block_till_done() + + # Fake backend response + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' + b'"room_id": "2833524037", "home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME",' + b'"country": "DE", "rooms": [{"id": "2833524037","name": "Entrada","type": "lobby",' + b'"therm_setpoint_mode": "max","therm_setpoint_end_time": 1612749189}],' + b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}]},' + b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_entrada).state == "heat" + assert hass.states.get(climate_entity_entrada).attributes["temperature"] == 30 + + +async def test_service_preset_mode_invalid(hass, climate_entry, caplog): + """Test service with invalid preset.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: "climate.netatmo_cocina", ATTR_PRESET_MODE: "invalid"}, + blocking=True, + ) + await hass.async_block_till_done() + + assert "Preset mode 'invalid' not available" in caplog.text + + +async def test_valves_service_turn_off(hass, climate_entry): + """Test service turn off for valves.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_entrada = "climate.netatmo_entrada" + + # Test turning valve off + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: climate_entity_entrada}, + blocking=True, + ) + await hass.async_block_till_done() + + # Fake backend response for valve being turned off + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2833524037",' + b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",' + b'"rooms": [{"id": "2833524037","name": "Entrada","type": "lobby",' + b'"therm_setpoint_mode": "off"}], "modules": [{"id": "12:34:56:00:01:ae","name": "Entrada",' + b'"type": "NRV"}]}, "mode": "off", "event_type": "set_point", "push_type":"display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_entrada).state == "off" + + +async def test_valves_service_turn_on(hass, climate_entry): + """Test service turn on for valves.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_entrada = "climate.netatmo_entrada" + + # Test turning valve on + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: climate_entity_entrada}, + blocking=True, + ) + await hass.async_block_till_done() + + # Fake backend response for valve being turned on + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2833524037",' + b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",' + b'"rooms": [{"id": "2833524037","name": "Entrada","type": "lobby",' + b'"therm_setpoint_mode": "home"}], "modules": [{"id": "12:34:56:00:01:ae",' + b'"name": "Entrada", "type": "NRV"}]}, "mode": "home", "event_type": "cancel_set_point",' + b'"push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_entrada).state == "auto" + + +@pytest.mark.parametrize( + "batterylevel, module_type, expected", + [ + (4101, NA_THERM, 100), + (3601, NA_THERM, 80), + (3450, NA_THERM, 65), + (3301, NA_THERM, 50), + (3001, NA_THERM, 20), + (2799, NA_THERM, 0), + (3201, NA_VALVE, 100), + (2701, NA_VALVE, 80), + (2550, NA_VALVE, 65), + (2401, NA_VALVE, 50), + (2201, NA_VALVE, 20), + (2001, NA_VALVE, 0), + ], +) +async def test_interpolate(batterylevel, module_type, expected): + """Test interpolation of battery levels depending on device type.""" + assert climate.interpolate(batterylevel, module_type) == expected + + +async def test_get_all_home_ids(): + """Test extracting all home ids returned by NetAtmo API.""" + # Test with backend returning no data + assert climate.get_all_home_ids(None) == [] + + # Test with fake data + home_data = Mock() + home_data.homes = { + "123": {"id": "123", "name": "Home 1", "modules": [], "therm_schedules": []}, + "987": {"id": "987", "name": "Home 2", "modules": [], "therm_schedules": []}, + } + expected = ["123", "987"] + assert climate.get_all_home_ids(home_data) == expected diff --git a/tests/fixtures/netatmo/gethomedata.json b/tests/fixtures/netatmo/gethomedata.json new file mode 100644 index 00000000000..db7d6aa438d --- /dev/null +++ b/tests/fixtures/netatmo/gethomedata.json @@ -0,0 +1,318 @@ +{ + "body": { + "homes": [ + { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "persons": [ + { + "id": "91827374-7e04-5298-83ad-a0cb8372dff1", + "last_seen": 1557071156, + "out_of_sight": true, + "face": { + "id": "d74fad765b9100ef480720a9", + "version": 1, + "key": "a4a95c24b808a89f8d1730fb69ecdf2bb8b72039d2c69928b029d67fc40cb2d7", + "url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c24b808a89f8d1730fb69ecdf2bb8b72039d2c69928b029d67fc40cb2d7" + }, + "pseudo": "John Doe" + }, + { + "id": "91827375-7e04-5298-83ae-a0cb8372dff2", + "last_seen": 1560600726, + "out_of_sight": true, + "face": { + "id": "d74fad765b9100ef480720a9", + "version": 3, + "key": "a4a95c24b808a89f8d1730039d2c69928b029d67fc40cb2d7fb69ecdf2bb8b72", + "url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c24b808a89f8d1730039d2c69928b029d67fc40cb2d7fb69ecdf2bb8b72" + }, + "pseudo": "Jane Doe" + }, + { + "id": "91827376-7e04-5298-83af-a0cb8372dff3", + "last_seen": 1560626666, + "out_of_sight": false, + "face": { + "id": "d74fad765b9100ef480720a9", + "version": 1, + "key": "a4a95c2d1730fb69ecdf2bb8b72039d2c69928b029d67fc40cb2d74b808a89f8", + "url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c2d1730fb69ecdf2bb8b72039d2c69928b029d67fc40cb2d74b808a89f8" + }, + "pseudo": "Richard Doe" + }, + { + "id": "91827376-7e04-5298-83af-a0cb8372dff4", + "last_seen": 1560621666, + "out_of_sight": true, + "face": { + "id": "d0ef44fad765b980720710a9", + "version": 1, + "key": "ab029da89f84a95c2d1730fb67fc40cb2d74b80869ecdf2bb8b72039d2c69928", + "url": "https://netatmocameraimage.blob.core.windows.net/production/d0ef44fad765b980720710a9ab029da89f84a95c2d1730fb67fc40cb2d74b80869ecdf2bb8b72039d2c69928" + } + } + ], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin" + }, + "cameras": [ + { + "id": "12:34:56:00:f1:62", + "type": "NACamera", + "status": "on", + "vpn_url": "https://prodvpn-eu-2.netatmo.net/restricted/10.255.248.91/6d278460699e56180d47ab47169efb31/MpEylTU2MDYzNjRVD-LJxUnIndumKzLboeAwMDqTTg,,", + "is_local": true, + "sd_status": "on", + "alim_status": "on", + "name": "Hall", + "modules": [ + { + "id": "12:34:56:00:f2:f1", + "type": "NIS", + "battery_percent": 84, + "rf": 68, + "status": "no_news", + "monitoring": "on", + "alim_source": "battery", + "tamper_detection_enabled": true, + "name": "Welcome's Siren" + } + ], + "use_pin_code": false, + "last_setup": 1544828430 + }, + { + "id": "12:34:56:00:a5:a4", + "type": "NOC", + "status": "on", + "vpn_url": "https://prodvpn-eu-2.netatmo.net/restricted/10.255.248.91/6d278460699e56180d47ab47169efb31/MpEylTU2MDYzNjRVD-LJxUnIndumKzLboeAwMDqTTw,,", + "is_local": false, + "sd_status": "on", + "alim_status": "on", + "name": "Garden", + "last_setup": 1563737661, + "light_mode_status": "auto" + } + ], + "smokedetectors": [ + { + "id": "12:34:56:00:8b:a2", + "type": "NSD", + "last_setup": 1567261859, + "name": "Hall" + }, + { + "id": "12:34:56:00:8b:ac", + "type": "NSD", + "last_setup": 1567262759, + "name": "Kitchen" + } + ], + "events": [ + { + "id": "a1b2c3d4e5f6abcdef123456", + "type": "person", + "time": 1560604700, + "camera_id": "12:34:56:00:f1:62", + "device_id": "12:34:56:00:f1:62", + "person_id": "91827374-7e04-5298-83ad-a0cb8372dff1", + "video_status": "deleted", + "is_arrival": false, + "message": "John Doe gesehen" + }, + { + "id": "a1b2c3d4e5f6abcdef123457", + "type": "person_away", + "time": 1560602400, + "camera_id": "12:34:56:00:f1:62", + "device_id": "12:34:56:00:f1:62", + "person_id": "91827374-7e04-5298-83ad-a0cb8372dff1", + "message": "John Doe hat das Haus verlassen", + "sub_message": "John Doe gilt als abwesend, da das mit diesem Profil verbundene Telefon den Bereich des Hauses verlassen hat." + }, + { + "id": "a1b2c3d4e5f6abcdef123458", + "type": "person", + "time": 1560601200, + "camera_id": "12:34:56:00:f1:62", + "device_id": "12:34:56:00:f1:62", + "person_id": "91827374-7e04-5298-83ad-a0cb8372dff1", + "video_status": "deleted", + "is_arrival": false, + "message": "John Doe gesehen" + }, + { + "id": "a1b2c3d4e5f6abcdef123459", + "type": "person", + "time": 1560600100, + "camera_id": "12:34:56:00:f1:62", + "device_id": "12:34:56:00:f1:62", + "person_id": "91827375-7e04-5298-83ae-a0cb8372dff2", + "snapshot": { + "id": "d74fad765b9100ef480720a9", + "version": 1, + "key": "a4a95c24b808a89f8d1730039d2c69928b029d67fc40cb2d7fb69ecdf2bb8b72", + "url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c24b808a89f8d1730039d2c69928b029d67fc40cb2d7fb69ecdf2bb8b72" + }, + "video_id": "12345678-36bc-4b9a-9762-5194e707ed51", + "video_status": "available", + "is_arrival": false, + "message": "Jane Doe gesehen" + }, + { + "id": "a1b2c3d4e5f6abcdef12345a", + "type": "person", + "time": 1560603600, + "camera_id": "12:34:56:00:f1:62", + "device_id": "12:34:56:00:f1:62", + "person_id": "91827375-7e04-5298-83ae-a0cb8372dff3", + "snapshot": { + "id": "532dde8d17554c022ab071b8", + "version": 1, + "key": "9fbe490fffacf45b8416241946541b031a004a09b6747feb6c38c3ccbc456b28", + "url": "https://netatmocameraimage.blob.core.windows.net/production/532dde8d17554c022ab071b89fbe490fffacf45b8416241946541b031a004a09b6747feb6c38c3ccbc456b28" + }, + "video_id": "12345678-1234-46cb-ad8f-23d893874099", + "video_status": "available", + "is_arrival": false, + "message": "Bewegung erkannt" + }, + { + "id": "a1b2c3d4e5f6abcdef12345b", + "type": "movement", + "time": 1560506200, + "camera_id": "12:34:56:00:f1:62", + "device_id": "12:34:56:00:f1:62", + "category": "human", + "snapshot": { + "id": "532dde8d17554c022ab071b9", + "version": 1, + "key": "8fbe490fffacf45b8416241946541b031a004a09b6747feb6c38c3ccbc456b28", + "url": "https://netatmocameraimage.blob.core.windows.net/production/532dde8d17554c022ab071b98fbe490fffacf45b8416241946541b031a004a09b6747feb6c38c3ccbc456b28" + }, + "vignette": { + "id": "5dc021b5dea854bd2321707a", + "version": 1, + "key": "58c5a05bd6bd908f6bf368865ef7355231c44215f8eb7ae458c919b2c67b4944", + "url": "https://netatmocameraimage.blob.core.windows.net/production/5dc021b5dea854bd2321707a58c5a05bd6bd908f6bf368865ef7355231c44215f8eb7ae458c919b2c67b4944" + }, + "video_id": "12345678-1234-46cb-ad8f-23d89387409a", + "video_status": "available", + "message": "Bewegung erkannt" + }, + { + "id": "a1b2c3d4e5f6abcdef12345c", + "type": "sound_test", + "time": 1560506210, + "camera_id": "12:34:56:00:8b:a2", + "device_id": "12:34:56:00:8b:a2", + "sub_type": 0, + "message": "Hall: Alarmton erfolgreich getestet" + }, + { + "id": "a1b2c3d4e5f6abcdef12345d", + "type": "wifi_status", + "time": 1560506220, + "camera_id": "12:34:56:00:8b:a2", + "device_id": "12:34:56:00:8b:a2", + "sub_type": 1, + "message": "Hall:WLAN-Verbindung erfolgreich hergestellt" + }, + { + "id": "a1b2c3d4e5f6abcdef12345e", + "type": "outdoor", + "time": 1560643100, + "camera_id": "12:34:56:00:a5:a4", + "device_id": "12:34:56:00:a5:a4", + "video_id": "string", + "video_status": "available", + "event_list": [ + { + "type": "string", + "time": 1560643100, + "offset": 0, + "id": "c81bcf7b-2cfg-4ac9-8455-487ed00c0000", + "message": "Animal détecté", + "snapshot": { + "id": "5715e16849c75xxxx00000000xxxxx", + "version": 1, + "key": "7ac578d05030d0e170643a787ee0a29663dxxx00000xxxxx00000", + "url": "https://netatmocameraimage.blob.core.windows.net/production/1aa" + }, + "vignette": { + "id": "5715e16849c75xxxx00000000xxxxx", + "version": 1, + "key": "7ac578d05030d0e170643a787ee0a29663dxxx00000xxxxx00000", + "url": "https://netatmocameraimage.blob.core.windows.net/production/1aa00000" + } + }, + { + "type": "string", + "time": 1560506222, + "offset": 0, + "id": "c81bcf7b-2cfg-4ac9-8455-487ed00c0000", + "message": "Animal détecté", + "snapshot": { + "filename": "vod\/af74631d-8311-42dc-825b-82e3abeaab09\/events\/c53b-aze7a.jpg" + }, + "vignette": { + "filename": "vod\/af74631d-8311-42dc-825b-82e3abeaab09\/events\/c5.jpg" + } + } + ] + } + ] + }, + { + "id": "91763b24c43d3e344f424e8c", + "persons": [], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin" + }, + "cameras": [ + { + "id": "12:34:56:00:a5:a5", + "type": "NOC", + "status": "on", + "vpn_url": "https://prodvpn-eu-2.netatmo.net/restricted/10.255.248.91/6d278460699e56180d47ab47169efb31/MpEylTU2MDYzNjRVD-LJxUnIndumKzLboeAwMDqTTz,,", + "is_local": true, + "sd_status": "on", + "alim_status": "on", + "name": "Street", + "last_setup": 1563737561, + "light_mode_status": "auto" + } + ], + "smokedetectors": [] + }, + { + "id": "91763b24c43d3e344f424e8d", + "persons": [], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin" + }, + "cameras": [], + "smokedetectors": [] + } + ], + "user": { + "reg_locale": "de-DE", + "lang": "de-DE", + "country": "DE", + "mail": "john@doe.com" + }, + "global_info": { + "show_tags": true + } + }, + "status": "ok", + "time_exec": 0.03621506690979, + "time_server": 1560626960 +} \ No newline at end of file diff --git a/tests/fixtures/netatmo/getstationsdata.json b/tests/fixtures/netatmo/getstationsdata.json new file mode 100644 index 00000000000..2a18c7bd280 --- /dev/null +++ b/tests/fixtures/netatmo/getstationsdata.json @@ -0,0 +1,600 @@ +{ + "body": { + "devices": [ + { + "_id": "12:34:56:37:11:ca", + "cipher_id": "enc:16:zjiZF/q8jTScXVdDa/kvhUAIUPGeYszaD1ClEf8byAJkRjxc5oth7cAocrMUIApX", + "date_setup": 1544558432, + "last_setup": 1544558432, + "type": "NAMain", + "last_status_store": 1559413181, + "module_name": "NetatmoIndoor", + "firmware": 137, + "last_upgrade": 1544558433, + "wifi_status": 45, + "reachable": true, + "co2_calibrating": false, + "station_name": "MyStation", + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure" + ], + "place": { + "altitude": 664, + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin", + "location": [ + 52.516263, + 13.377726 + ] + }, + "dashboard_data": { + "time_utc": 1559413171, + "Temperature": 24.6, + "CO2": 749, + "Humidity": 36, + "Noise": 37, + "Pressure": 1017.3, + "AbsolutePressure": 939.7, + "min_temp": 23.4, + "max_temp": 25.6, + "date_min_temp": 1559371924, + "date_max_temp": 1559411964, + "temp_trend": "stable", + "pressure_trend": "down" + }, + "modules": [ + { + "_id": "12:34:56:36:fc:de", + "type": "NAModule1", + "module_name": "NetatmoOutdoor", + "data_type": [ + "Temperature", + "Humidity" + ], + "last_setup": 1544558433, + "reachable": true, + "dashboard_data": { + "time_utc": 1559413157, + "Temperature": 28.6, + "Humidity": 24, + "min_temp": 16.9, + "max_temp": 30.3, + "date_min_temp": 1559365579, + "date_max_temp": 1559404698, + "temp_trend": "down" + }, + "firmware": 46, + "last_message": 1559413177, + "last_seen": 1559413157, + "rf_status": 65, + "battery_vp": 5738, + "battery_percent": 87 + }, + { + "_id": "12:34:56:07:bb:3e", + "type": "NAModule4", + "module_name": "Kitchen", + "data_type": [ + "Temperature", + "CO2", + "Humidity" + ], + "last_setup": 1548956696, + "reachable": true, + "dashboard_data": { + "time_utc": 1559413125, + "Temperature": 28, + "CO2": 503, + "Humidity": 26, + "min_temp": 25, + "max_temp": 28, + "date_min_temp": 1559371577, + "date_max_temp": 1559412561, + "temp_trend": "up" + }, + "firmware": 44, + "last_message": 1559413177, + "last_seen": 1559413177, + "rf_status": 73, + "battery_vp": 5687, + "battery_percent": 83 + }, + { + "_id": "12:34:56:07:bb:0e", + "type": "NAModule4", + "module_name": "Livingroom", + "data_type": [ + "Temperature", + "CO2", + "Humidity" + ], + "last_setup": 1548957209, + "reachable": true, + "dashboard_data": { + "time_utc": 1559413093, + "Temperature": 26.4, + "CO2": 451, + "Humidity": 31, + "min_temp": 25.1, + "max_temp": 26.4, + "date_min_temp": 1559365290, + "date_max_temp": 1559413093, + "temp_trend": "stable" + }, + "firmware": 44, + "last_message": 1559413177, + "last_seen": 1559413093, + "rf_status": 84, + "battery_vp": 5626, + "battery_percent": 79 + }, + { + "_id": "12:34:56:03:1b:e4", + "type": "NAModule2", + "module_name": "Garden", + "data_type": [ + "Wind" + ], + "last_setup": 1549193862, + "reachable": true, + "dashboard_data": { + "time_utc": 1559413170, + "WindStrength": 4, + "WindAngle": 217, + "GustStrength": 9, + "GustAngle": 206, + "max_wind_str": 21, + "max_wind_angle": 217, + "date_max_wind_str": 1559386669 + }, + "firmware": 19, + "last_message": 1559413177, + "last_seen": 1559413177, + "rf_status": 59, + "battery_vp": 5689, + "battery_percent": 85 + }, + { + "_id": "12:34:56:05:51:20", + "type": "NAModule3", + "module_name": "Yard", + "data_type": [ + "Rain" + ], + "last_setup": 1549194580, + "reachable": true, + "dashboard_data": { + "time_utc": 1559413170, + "Rain": 0, + "sum_rain_24": 0, + "sum_rain_1": 0 + }, + "firmware": 8, + "last_message": 1559413177, + "last_seen": 1559413170, + "rf_status": 67, + "battery_vp": 5860, + "battery_percent": 93 + } + ] + }, + { + "_id": "12 :34: 56:36:fd:3c", + "station_name": "Valley Road", + "date_setup": 1545897146, + "last_setup": 1545897146, + "type": "NAMain", + "last_status_store": 1581835369, + "firmware": 137, + "last_upgrade": 1545897125, + "wifi_status": 53, + "reachable": true, + "co2_calibrating": false, + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure" + ], + "place": { + "altitude": 69, + "city": "Valley", + "country": "AU", + "timezone": "Australia/Hobart", + "location": [ + 148.444226, + -41.721282 + ] + }, + "read_only": true, + "dashboard_data": { + "time_utc": 1581835330, + "Temperature": 22.4, + "CO2": 471, + "Humidity": 46, + "Noise": 47, + "Pressure": 1011.5, + "AbsolutePressure": 1002.8, + "min_temp": 18.1, + "max_temp": 22.5, + "date_max_temp": 1581829891, + "date_min_temp": 1581794878, + "temp_trend": "stable", + "pressure_trend": "stable" + }, + "modules": [ + { + "_id": "12 :34: 56:36:e6:c0", + "type": "NAModule1", + "module_name": "Module", + "data_type": [ + "Temperature", + "Humidity" + ], + "last_setup": 1545897146, + "battery_percent": 22, + "reachable": false, + "firmware": 46, + "last_message": 1572497781, + "last_seen": 1572497742, + "rf_status": 88, + "battery_vp": 4118 + }, + { + "_id": "12:34:56:05:25:6e", + "type": "NAModule3", + "module_name": "Rain Gauge", + "data_type": [ + "Rain" + ], + "last_setup": 1553997427, + "battery_percent": 82, + "reachable": true, + "firmware": 8, + "last_message": 1581835362, + "last_seen": 1581835354, + "rf_status": 78, + "battery_vp": 5594, + "dashboard_data": { + "time_utc": 1581835329, + "Rain": 0, + "sum_rain_1": 0, + "sum_rain_24": 0 + } + } + ] + }, + { + "_id": "12:34:56:32:a7:60", + "home_name": "Ateljen", + "date_setup": 1566714693, + "last_setup": 1566714693, + "type": "NAMain", + "last_status_store": 1588481079, + "module_name": "Indoor", + "firmware": 177, + "last_upgrade": 1566714694, + "wifi_status": 50, + "reachable": true, + "co2_calibrating": false, + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure" + ], + "place": { + "altitude": 93, + "city": "Gothenburg", + "country": "SE", + "timezone": "Europe/Stockholm", + "location": [ + 11.6136629, + 57.7006827 + ] + }, + "dashboard_data": { + "time_utc": 1588481073, + "Temperature": 18.2, + "CO2": 542, + "Humidity": 45, + "Noise": 45, + "Pressure": 1013, + "AbsolutePressure": 1001.9, + "min_temp": 18.2, + "max_temp": 19.5, + "date_max_temp": 1588456861, + "date_min_temp": 1588479561, + "temp_trend": "stable", + "pressure_trend": "up" + }, + "modules": [ + { + "_id": "12:34:56:32:db:06", + "type": "NAModule1", + "last_setup": 1587635819, + "data_type": [ + "Temperature", + "Humidity" + ], + "battery_percent": 100, + "reachable": false, + "firmware": 255, + "last_message": 0, + "last_seen": 0, + "rf_status": 255, + "battery_vp": 65535 + } + ] + }, + { + "_id": "12:34:56:1c:68:2e", + "station_name": "Bol\u00e5s", + "date_setup": 1470935400, + "last_setup": 1470935400, + "type": "NAMain", + "last_status_store": 1588481399, + "module_name": "Inne - Nere", + "firmware": 177, + "last_upgrade": 1470935401, + "wifi_status": 13, + "reachable": true, + "co2_calibrating": false, + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure" + ], + "place": { + "altitude": 93, + "city": "Gothenburg", + "country": "SE", + "timezone": "Europe/Stockholm", + "location": [ + 11.6136629, + 57.7006827 + ] + }, + "dashboard_data": { + "time_utc": 1588481387, + "Temperature": 20.8, + "CO2": 674, + "Humidity": 41, + "Noise": 34, + "Pressure": 1012.1, + "AbsolutePressure": 1001, + "min_temp": 20.8, + "max_temp": 22.2, + "date_max_temp": 1588456859, + "date_min_temp": 1588480176, + "temp_trend": "stable", + "pressure_trend": "up" + }, + "modules": [ + { + "_id": "12:34:56:02:b3:da", + "type": "NAModule3", + "module_name": "Regnm\u00e4tare", + "last_setup": 1470937706, + "data_type": [ + "Rain" + ], + "battery_percent": 81, + "reachable": true, + "firmware": 12, + "last_message": 1588481393, + "last_seen": 1588481386, + "rf_status": 67, + "battery_vp": 5582, + "dashboard_data": { + "time_utc": 1588481386, + "Rain": 0, + "sum_rain_1": 0, + "sum_rain_24": 0.1 + } + }, + { + "_id": "12:34:56:03:76:60", + "type": "NAModule4", + "module_name": "Inne - Uppe", + "last_setup": 1470938089, + "data_type": [ + "Temperature", + "CO2", + "Humidity" + ], + "battery_percent": 14, + "reachable": true, + "firmware": 50, + "last_message": 1588481393, + "last_seen": 1588481374, + "rf_status": 70, + "battery_vp": 4448, + "dashboard_data": { + "time_utc": 1588481374, + "Temperature": 19.6, + "CO2": 696, + "Humidity": 41, + "min_temp": 19.6, + "max_temp": 20.5, + "date_max_temp": 1588456817, + "date_min_temp": 1588481374, + "temp_trend": "stable" + } + }, + { + "_id": "12:34:56:32:db:06", + "type": "NAModule1", + "module_name": "Ute", + "last_setup": 1566326027, + "data_type": [ + "Temperature", + "Humidity" + ], + "battery_percent": 81, + "reachable": true, + "firmware": 50, + "last_message": 1588481393, + "last_seen": 1588481380, + "rf_status": 61, + "battery_vp": 5544, + "dashboard_data": { + "time_utc": 1588481380, + "Temperature": 6.4, + "Humidity": 91, + "min_temp": 3.6, + "max_temp": 6.4, + "date_max_temp": 1588481380, + "date_min_temp": 1588471383, + "temp_trend": "up" + } + } + ] + }, + { + "_id": "12:34:56:1d:68:2e", + "date_setup": 1470935500, + "last_setup": 1470935500, + "type": "NAMain", + "last_status_store": 1588481399, + "module_name": "Basisstation", + "firmware": 177, + "last_upgrade": 1470935401, + "wifi_status": 13, + "reachable": true, + "co2_calibrating": false, + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure" + ], + "place": { + "altitude": 93, + "city": "Gothenburg", + "country": "SE", + "timezone": "Europe/Stockholm", + "location": [ + 11.6136629, + 57.7006827 + ] + }, + "dashboard_data": { + "time_utc": 1588481387, + "Temperature": 20.8, + "CO2": 674, + "Humidity": 41, + "Noise": 34, + "Pressure": 1012.1, + "AbsolutePressure": 1001, + "min_temp": 20.8, + "max_temp": 22.2, + "date_max_temp": 1588456859, + "date_min_temp": 1588480176, + "temp_trend": "stable", + "pressure_trend": "up" + }, + "modules": [] + }, + { + "_id": "12:34:56:58:c8:54", + "date_setup": 1605594014, + "last_setup": 1605594014, + "type": "NAMain", + "last_status_store": 1605878352, + "firmware": 178, + "wifi_status": 47, + "reachable": true, + "co2_calibrating": false, + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure" + ], + "place": { + "altitude": 65, + "city": "Njurunda District", + "country": "SE", + "timezone": "Europe/Stockholm", + "location": [ + 17.123456, + 62.123456 + ] + }, + "station_name": "Njurunda (Indoor)", + "home_id": "5fb36b9ec68fd10c6467ca65", + "home_name": "Njurunda", + "dashboard_data": { + "time_utc": 1605878349, + "Temperature": 19.7, + "CO2": 993, + "Humidity": 40, + "Noise": 40, + "Pressure": 1015.6, + "AbsolutePressure": 1007.8, + "min_temp": 19.7, + "max_temp": 20.4, + "date_max_temp": 1605826917, + "date_min_temp": 1605873207, + "temp_trend": "stable", + "pressure_trend": "up" + }, + "modules": [ + { + "_id": "12:34:56:58:e6:38", + "type": "NAModule1", + "last_setup": 1605594034, + "data_type": [ + "Temperature", + "Humidity" + ], + "battery_percent": 100, + "reachable": true, + "firmware": 50, + "last_message": 1605878347, + "last_seen": 1605878328, + "rf_status": 62, + "battery_vp": 6198, + "dashboard_data": { + "time_utc": 1605878328, + "Temperature": 0.6, + "Humidity": 77, + "min_temp": -2.1, + "max_temp": 1.5, + "date_max_temp": 1605865920, + "date_min_temp": 1605826904, + "temp_trend": "down" + } + } + ] + } + ], + "user": { + "mail": "john@doe.com", + "administrative": { + "lang": "de-DE", + "reg_locale": "de-DE", + "country": "DE", + "unit": 0, + "windunit": 0, + "pressureunit": 0, + "feel_like_algo": 0 + } + } + }, + "status": "ok", + "time_exec": 0.91107702255249, + "time_server": 1559413602 +} \ No newline at end of file diff --git a/tests/fixtures/netatmo/homesdata.json b/tests/fixtures/netatmo/homesdata.json new file mode 100644 index 00000000000..aecab91550c --- /dev/null +++ b/tests/fixtures/netatmo/homesdata.json @@ -0,0 +1,595 @@ +{ + "body": { + "homes": [ + { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "altitude": 112, + "coordinates": [ + 52.516263, + 13.377726 + ], + "country": "DE", + "timezone": "Europe/Berlin", + "rooms": [ + { + "id": "2746182631", + "name": "Livingroom", + "type": "livingroom", + "module_ids": [ + "12:34:56:00:01:ae" + ] + }, + { + "id": "3688132631", + "name": "Hall", + "type": "custom", + "module_ids": [ + "12:34:56:00:f1:62" + ] + }, + { + "id": "2833524037", + "name": "Entrada", + "type": "lobby", + "module_ids": [ + "12:34:56:03:a5:54" + ] + }, + { + "id": "2940411577", + "name": "Cocina", + "type": "kitchen", + "module_ids": [ + "12:34:56:03:a0:ac" + ] + } + ], + "modules": [ + { + "id": "12:34:56:00:fa:d0", + "type": "NAPlug", + "name": "Thermostat", + "setup_date": 1494963356, + "modules_bridged": [ + "12:34:56:00:01:ae", + "12:34:56:03:a0:ac", + "12:34:56:03:a5:54" + ] + }, + { + "id": "12:34:56:00:01:ae", + "type": "NATherm1", + "name": "Livingroom", + "setup_date": 1494963356, + "room_id": "2746182631", + "bridge": "12:34:56:00:fa:d0" + }, + { + "id": "12:34:56:03:a5:54", + "type": "NRV", + "name": "Valve1", + "setup_date": 1554549767, + "room_id": "2833524037", + "bridge": "12:34:56:00:fa:d0" + }, + { + "id": "12:34:56:03:a0:ac", + "type": "NRV", + "name": "Valve2", + "setup_date": 1554554444, + "room_id": "2940411577", + "bridge": "12:34:56:00:fa:d0" + }, + { + "id": "12:34:56:00:f1:62", + "type": "NACamera", + "name": "Hall", + "setup_date": 1544828430, + "room_id": "3688132631" + } + ], + "therm_schedules": [ + { + "zones": [ + { + "type": 0, + "name": "Comfort", + "rooms_temp": [ + { + "temp": 21, + "room_id": "2746182631" + } + ], + "id": 0 + }, + { + "type": 1, + "name": "Night", + "rooms_temp": [ + { + "temp": 17, + "room_id": "2746182631" + } + ], + "id": 1 + }, + { + "type": 5, + "name": "Eco", + "rooms_temp": [ + { + "temp": 17, + "room_id": "2746182631" + } + ], + "id": 4 + } + ], + "timetable": [ + { + "zone_id": 1, + "m_offset": 0 + }, + { + "zone_id": 0, + "m_offset": 360 + }, + { + "zone_id": 4, + "m_offset": 420 + }, + { + "zone_id": 0, + "m_offset": 960 + }, + { + "zone_id": 1, + "m_offset": 1410 + }, + { + "zone_id": 0, + "m_offset": 1800 + }, + { + "zone_id": 4, + "m_offset": 1860 + }, + { + "zone_id": 0, + "m_offset": 2400 + }, + { + "zone_id": 1, + "m_offset": 2850 + }, + { + "zone_id": 0, + "m_offset": 3240 + }, + { + "zone_id": 4, + "m_offset": 3300 + }, + { + "zone_id": 0, + "m_offset": 3840 + }, + { + "zone_id": 1, + "m_offset": 4290 + }, + { + "zone_id": 0, + "m_offset": 4680 + }, + { + "zone_id": 4, + "m_offset": 4740 + }, + { + "zone_id": 0, + "m_offset": 5280 + }, + { + "zone_id": 1, + "m_offset": 5730 + }, + { + "zone_id": 0, + "m_offset": 6120 + }, + { + "zone_id": 4, + "m_offset": 6180 + }, + { + "zone_id": 0, + "m_offset": 6720 + }, + { + "zone_id": 1, + "m_offset": 7170 + }, + { + "zone_id": 0, + "m_offset": 7620 + }, + { + "zone_id": 1, + "m_offset": 8610 + }, + { + "zone_id": 0, + "m_offset": 9060 + }, + { + "zone_id": 1, + "m_offset": 10050 + } + ], + "hg_temp": 7, + "away_temp": 14, + "name": "Default", + "selected": true, + "id": "591b54a2764ff4d50d8b5795", + "type": "therm" + }, + { + "zones": [ + { + "type": 0, + "name": "Comfort", + "rooms_temp": [ + { + "temp": 21, + "room_id": "2746182631" + } + ], + "id": 0 + }, + { + "type": 1, + "name": "Night", + "rooms_temp": [ + { + "temp": 17, + "room_id": "2746182631" + } + ], + "id": 1 + }, + { + "type": 5, + "name": "Eco", + "rooms_temp": [ + { + "temp": 17, + "room_id": "2746182631" + } + ], + "id": 4 + } + ], + "timetable": [ + { + "zone_id": 1, + "m_offset": 0 + }, + { + "zone_id": 0, + "m_offset": 360 + }, + { + "zone_id": 4, + "m_offset": 420 + }, + { + "zone_id": 0, + "m_offset": 960 + }, + { + "zone_id": 1, + "m_offset": 1410 + }, + { + "zone_id": 0, + "m_offset": 1800 + }, + { + "zone_id": 4, + "m_offset": 1860 + }, + { + "zone_id": 0, + "m_offset": 2400 + }, + { + "zone_id": 1, + "m_offset": 2850 + }, + { + "zone_id": 0, + "m_offset": 3240 + }, + { + "zone_id": 4, + "m_offset": 3300 + }, + { + "zone_id": 0, + "m_offset": 3840 + }, + { + "zone_id": 1, + "m_offset": 4290 + }, + { + "zone_id": 0, + "m_offset": 4680 + }, + { + "zone_id": 4, + "m_offset": 4740 + }, + { + "zone_id": 0, + "m_offset": 5280 + }, + { + "zone_id": 1, + "m_offset": 5730 + }, + { + "zone_id": 0, + "m_offset": 6120 + }, + { + "zone_id": 4, + "m_offset": 6180 + }, + { + "zone_id": 0, + "m_offset": 6720 + }, + { + "zone_id": 1, + "m_offset": 7170 + }, + { + "zone_id": 0, + "m_offset": 7620 + }, + { + "zone_id": 1, + "m_offset": 8610 + }, + { + "zone_id": 0, + "m_offset": 9060 + }, + { + "zone_id": 1, + "m_offset": 10050 + } + ], + "hg_temp": 7, + "away_temp": 14, + "name": "Winter", + "id": "b1b54a2f45795764f59d50d8", + "type": "therm" + } + ], + "therm_setpoint_default_duration": 120, + "persons": [ + { + "id": "91827374-7e04-5298-83ad-a0cb8372dff1", + "pseudo": "John Doe", + "url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c24b808a89f8d1730fb69ecdf2bb8b72039d2c69928b029d67fc40cb2d7" + }, + { + "id": "91827375-7e04-5298-83ae-a0cb8372dff2", + "pseudo": "Jane Doe", + "url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c24b808a89f8d1730039d2c69928b029d67fc40cb2d7fb69ecdf2bb8b72" + }, + { + "id": "91827376-7e04-5298-83af-a0cb8372dff3", + "pseudo": "Richard Doe", + "url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c2d1730fb69ecdf2bb8b72039d2c69928b029d67fc40cb2d74b808a89f8" + } + ], + "schedules": [ + { + "zones": [ + { + "type": 0, + "name": "Komfort", + "rooms_temp": [ + { + "temp": 21, + "room_id": "2746182631" + } + ], + "id": 0, + "rooms": [ + { + "id": "2746182631", + "therm_setpoint_temperature": 21 + } + ] + }, + { + "type": 1, + "name": "Nacht", + "rooms_temp": [ + { + "temp": 17, + "room_id": "2746182631" + } + ], + "id": 1, + "rooms": [ + { + "id": "2746182631", + "therm_setpoint_temperature": 17 + } + ] + }, + { + "type": 5, + "name": "Eco", + "rooms_temp": [ + { + "temp": 17, + "room_id": "2746182631" + } + ], + "id": 4, + "rooms": [ + { + "id": "2746182631", + "therm_setpoint_temperature": 17 + } + ] + } + ], + "timetable": [ + { + "zone_id": 1, + "m_offset": 0 + }, + { + "zone_id": 0, + "m_offset": 360 + }, + { + "zone_id": 4, + "m_offset": 420 + }, + { + "zone_id": 0, + "m_offset": 960 + }, + { + "zone_id": 1, + "m_offset": 1410 + }, + { + "zone_id": 0, + "m_offset": 1800 + }, + { + "zone_id": 4, + "m_offset": 1860 + }, + { + "zone_id": 0, + "m_offset": 2400 + }, + { + "zone_id": 1, + "m_offset": 2850 + }, + { + "zone_id": 0, + "m_offset": 3240 + }, + { + "zone_id": 4, + "m_offset": 3300 + }, + { + "zone_id": 0, + "m_offset": 3840 + }, + { + "zone_id": 1, + "m_offset": 4290 + }, + { + "zone_id": 0, + "m_offset": 4680 + }, + { + "zone_id": 4, + "m_offset": 4740 + }, + { + "zone_id": 0, + "m_offset": 5280 + }, + { + "zone_id": 1, + "m_offset": 5730 + }, + { + "zone_id": 0, + "m_offset": 6120 + }, + { + "zone_id": 4, + "m_offset": 6180 + }, + { + "zone_id": 0, + "m_offset": 6720 + }, + { + "zone_id": 1, + "m_offset": 7170 + }, + { + "zone_id": 0, + "m_offset": 7620 + }, + { + "zone_id": 1, + "m_offset": 8610 + }, + { + "zone_id": 0, + "m_offset": 9060 + }, + { + "zone_id": 1, + "m_offset": 10050 + } + ], + "hg_temp": 7, + "away_temp": 14, + "name": "Default", + "id": "591b54a2764ff4d50d8b5795", + "selected": true, + "type": "therm" + } + ], + "therm_mode": "schedule" + }, + { + "id": "91763b24c43d3e344f424e8c", + "altitude": 112, + "coordinates": [ + 52.516263, + 13.377726 + ], + "country": "DE", + "timezone": "Europe/Berlin", + "therm_setpoint_default_duration": 180, + "therm_mode": "schedule" + } + ], + "user": { + "email": "john@doe.com", + "language": "de-DE", + "locale": "de-DE", + "feel_like_algorithm": 0, + "unit_pressure": 0, + "unit_system": 0, + "unit_wind": 0, + "id": "91763b24c43d3e344f424e8b" + } + }, + "status": "ok", + "time_exec": 0.056135892868042, + "time_server": 1559171003 +} \ No newline at end of file diff --git a/tests/fixtures/netatmo/homestatus.json b/tests/fixtures/netatmo/homestatus.json new file mode 100644 index 00000000000..5d508ea03b0 --- /dev/null +++ b/tests/fixtures/netatmo/homestatus.json @@ -0,0 +1,113 @@ +{ + "status": "ok", + "time_server": 1559292039, + "body": { + "home": { + "modules": [ + { + "id": "12:34:56:00:f1:62", + "type": "NACamera", + "monitoring": "on", + "sd_status": 4, + "alim_status": 2, + "locked": false, + "vpn_url": "https://prodvpn-eu-2.netatmo.net/restricted/10.255.123.45/609e27de5699fb18147ab47d06846631/MTRPn_BeWCav5RBq4U1OMDruTW4dkQ0NuMwNDAw11g,,", + "is_local": true + }, + { + "id": "12:34:56:00:fa:d0", + "type": "NAPlug", + "firmware_revision": 174, + "rf_strength": 107, + "wifi_strength": 42 + }, + { + "id": "12:34:56:00:01:ae", + "reachable": true, + "type": "NATherm1", + "firmware_revision": 65, + "rf_strength": 58, + "battery_level": 3793, + "boiler_valve_comfort_boost": false, + "boiler_status": false, + "anticipating": false, + "bridge": "12:34:56:00:fa:d0", + "battery_state": "high" + }, + { + "id": "12:34:56:03:a5:54", + "reachable": true, + "type": "NRV", + "firmware_revision": 79, + "rf_strength": 51, + "battery_level": 3025, + "bridge": "12:34:56:00:fa:d0", + "battery_state": "full" + }, + { + "id": "12:34:56:03:a0:ac", + "reachable": true, + "type": "NRV", + "firmware_revision": 79, + "rf_strength": 59, + "battery_level": 2329, + "bridge": "12:34:56:00:fa:d0", + "battery_state": "full" + } + ], + "rooms": [ + { + "id": "2746182631", + "reachable": true, + "therm_measured_temperature": 19.8, + "therm_setpoint_temperature": 12, + "therm_setpoint_mode": "schedule", + "therm_setpoint_start_time": 1559229567, + "therm_setpoint_end_time": 0 + }, + { + "id": "2940411577", + "reachable": true, + "therm_measured_temperature": 5, + "heating_power_request": 1, + "therm_setpoint_temperature": 7, + "therm_setpoint_mode": "away", + "therm_setpoint_start_time": 0, + "therm_setpoint_end_time": 0, + "anticipating": false, + "open_window": false + }, + { + "id": "2833524037", + "reachable": true, + "therm_measured_temperature": 24.5, + "heating_power_request": 0, + "therm_setpoint_temperature": 7, + "therm_setpoint_mode": "hg", + "therm_setpoint_start_time": 0, + "therm_setpoint_end_time": 0, + "anticipating": false, + "open_window": false + } + ], + "id": "91763b24c43d3e344f424e8b", + "persons": [ + { + "id": "91827374-7e04-5298-83ad-a0cb8372dff1", + "last_seen": 1557071156, + "out_of_sight": true + }, + { + "id": "91827375-7e04-5298-83ae-a0cb8372dff2", + "last_seen": 1559282761, + "out_of_sight": false + }, + { + "id": "91827376-7e04-5298-83af-a0cb8372dff3", + "last_seen": 1559224132, + "out_of_sight": true + } + ] + } + } +} \ No newline at end of file From 87d62cbbb83bf177b78bb0233234c2ea3d5d49e7 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 15 Mar 2021 12:50:19 +0100 Subject: [PATCH 369/831] Fix target of WLED services (#47938) --- homeassistant/components/wled/services.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/homeassistant/components/wled/services.yaml b/homeassistant/components/wled/services.yaml index d6927610a47..3ade18cb70e 100644 --- a/homeassistant/components/wled/services.yaml +++ b/homeassistant/components/wled/services.yaml @@ -2,6 +2,9 @@ effect: name: Set effect description: Control the effect settings of WLED. target: + entity: + integration: wled + domain: light fields: effect: name: Effect @@ -48,6 +51,9 @@ preset: name: Set preset description: Set a preset for the WLED device. target: + entity: + integration: wled + domain: light fields: preset: name: Preset ID From b645dabd664a28173d43ff66f915a9820fe5f237 Mon Sep 17 00:00:00 2001 From: Colin O'Dell Date: Mon, 15 Mar 2021 07:53:45 -0400 Subject: [PATCH 370/831] Upgrade qnapstats library to 0.3.1 (#47907) Fixes #47674 --- homeassistant/components/qnap/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/qnap/manifest.json b/homeassistant/components/qnap/manifest.json index b83d37f0d3c..29750683abf 100644 --- a/homeassistant/components/qnap/manifest.json +++ b/homeassistant/components/qnap/manifest.json @@ -2,6 +2,6 @@ "domain": "qnap", "name": "QNAP", "documentation": "https://www.home-assistant.io/integrations/qnap", - "requirements": ["qnapstats==0.3.0"], + "requirements": ["qnapstats==0.3.1"], "codeowners": ["@colinodell"] } diff --git a/requirements_all.txt b/requirements_all.txt index a749acabf99..68ba25f4659 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1922,7 +1922,7 @@ pyzbar==0.1.7 pyzerproc==0.4.8 # homeassistant.components.qnap -qnapstats==0.3.0 +qnapstats==0.3.1 # homeassistant.components.quantum_gateway quantum-gateway==0.0.5 From fb34d1a56e7f0329cb21ca97aa2e476b804b14b3 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 15 Mar 2021 12:06:57 +0000 Subject: [PATCH 371/831] Clean up Lyric (#47899) --- homeassistant/components/lyric/climate.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/homeassistant/components/lyric/climate.py b/homeassistant/components/lyric/climate.py index 6424083158a..4338a6e92cc 100644 --- a/homeassistant/components/lyric/climate.py +++ b/homeassistant/components/lyric/climate.py @@ -68,20 +68,12 @@ HVAC_MODES = { LYRIC_HVAC_MODE_HEAT_COOL: HVAC_MODE_HEAT_COOL, } - HVAC_ACTIONS = { LYRIC_HVAC_ACTION_OFF: CURRENT_HVAC_OFF, LYRIC_HVAC_ACTION_HEAT: CURRENT_HVAC_HEAT, LYRIC_HVAC_ACTION_COOL: CURRENT_HVAC_COOL, } -HVAC_MODES = { - LYRIC_HVAC_MODE_OFF: HVAC_MODE_OFF, - LYRIC_HVAC_MODE_HEAT: HVAC_MODE_HEAT, - LYRIC_HVAC_MODE_COOL: HVAC_MODE_COOL, - LYRIC_HVAC_MODE_HEAT_COOL: HVAC_MODE_HEAT_COOL, -} - SERVICE_HOLD_TIME = "set_hold_time" ATTR_TIME_PERIOD = "time_period" From 50b7b1cc7a80690c191ca964410adad268014b21 Mon Sep 17 00:00:00 2001 From: Andre Lengwenus Date: Mon, 15 Mar 2021 13:45:13 +0100 Subject: [PATCH 372/831] Migrate LCN configuration to ConfigEntry (Part 1) (#44090) --- .coveragerc | 13 +- homeassistant/components/lcn/__init__.py | 232 ++++++++++-------- homeassistant/components/lcn/binary_sensor.py | 99 +++++--- homeassistant/components/lcn/climate.py | 73 +++--- homeassistant/components/lcn/config_flow.py | 97 ++++++++ homeassistant/components/lcn/const.py | 7 + homeassistant/components/lcn/cover.py | 89 ++++--- homeassistant/components/lcn/helpers.py | 185 +++++++++++++- homeassistant/components/lcn/light.py | 79 +++--- homeassistant/components/lcn/manifest.json | 1 + homeassistant/components/lcn/scene.py | 61 +++-- homeassistant/components/lcn/sensor.py | 90 ++++--- homeassistant/components/lcn/services.py | 43 +++- homeassistant/components/lcn/switch.py | 70 +++--- requirements_test_all.txt | 3 + tests/components/lcn/__init__.py | 1 + tests/components/lcn/test_config_flow.py | 92 +++++++ 17 files changed, 885 insertions(+), 350 deletions(-) create mode 100644 homeassistant/components/lcn/config_flow.py create mode 100644 tests/components/lcn/__init__.py create mode 100644 tests/components/lcn/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index d406698cbb1..3c6bf16f6ba 100644 --- a/.coveragerc +++ b/.coveragerc @@ -505,7 +505,18 @@ omit = homeassistant/components/lastfm/sensor.py homeassistant/components/launch_library/const.py homeassistant/components/launch_library/sensor.py - homeassistant/components/lcn/* + homeassistant/components/lcn/__init__.py + homeassistant/components/lcn/binary_sensor.py + homeassistant/components/lcn/climate.py + homeassistant/components/lcn/const.py + homeassistant/components/lcn/cover.py + homeassistant/components/lcn/helpers.py + homeassistant/components/lcn/light.py + homeassistant/components/lcn/scene.py + homeassistant/components/lcn/schemas.py + homeassistant/components/lcn/sensor.py + homeassistant/components/lcn/services.py + homeassistant/components/lcn/switch.py homeassistant/components/lg_netcast/media_player.py homeassistant/components/lg_soundbar/media_player.py homeassistant/components/life360/* diff --git a/homeassistant/components/lcn/__init__.py b/homeassistant/components/lcn/__init__.py index fe9195584ea..9384fbed29d 100644 --- a/homeassistant/components/lcn/__init__.py +++ b/homeassistant/components/lcn/__init__.py @@ -1,137 +1,162 @@ """Support for LCN devices.""" +import asyncio import logging import pypck +from homeassistant import config_entries from homeassistant.const import ( - CONF_BINARY_SENSORS, - CONF_COVERS, - CONF_HOST, - CONF_LIGHTS, + CONF_IP_ADDRESS, CONF_NAME, CONF_PASSWORD, CONF_PORT, - CONF_SENSORS, - CONF_SWITCHES, + CONF_RESOURCE, CONF_USERNAME, ) -from homeassistant.helpers.discovery import async_load_platform +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity import Entity -from .const import ( - CONF_CLIMATES, - CONF_CONNECTIONS, - CONF_DIM_MODE, - CONF_SCENES, - CONF_SK_NUM_TRIES, - DATA_LCN, - DOMAIN, -) +from .const import CONF_DIM_MODE, CONF_SK_NUM_TRIES, CONNECTION, DOMAIN +from .helpers import generate_unique_id, import_lcn_config from .schemas import CONFIG_SCHEMA # noqa: F401 -from .services import ( - DynText, - Led, - LockKeys, - LockRegulator, - OutputAbs, - OutputRel, - OutputToggle, - Pck, - Relays, - SendKeys, - VarAbs, - VarRel, - VarReset, -) +from .services import SERVICES + +PLATFORMS = ["binary_sensor", "climate", "cover", "light", "scene", "sensor", "switch"] _LOGGER = logging.getLogger(__name__) async def async_setup(hass, config): """Set up the LCN component.""" - hass.data[DATA_LCN] = {} + if DOMAIN not in config: + return True - conf_connections = config[DOMAIN][CONF_CONNECTIONS] - connections = [] - for conf_connection in conf_connections: - connection_name = conf_connection.get(CONF_NAME) + # initialize a config_flow for all LCN configurations read from + # configuration.yaml + config_entries_data = import_lcn_config(config[DOMAIN]) - settings = { - "SK_NUM_TRIES": conf_connection[CONF_SK_NUM_TRIES], - "DIM_MODE": pypck.lcn_defs.OutputPortDimMode[ - conf_connection[CONF_DIM_MODE] - ], - } - - connection = pypck.connection.PchkConnectionManager( - conf_connection[CONF_HOST], - conf_connection[CONF_PORT], - conf_connection[CONF_USERNAME], - conf_connection[CONF_PASSWORD], - settings=settings, - connection_id=connection_name, - ) - - try: - # establish connection to PCHK server - await hass.async_create_task(connection.async_connect(timeout=15)) - connections.append(connection) - _LOGGER.info('LCN connected to "%s"', connection_name) - except TimeoutError: - _LOGGER.error('Connection to PCHK server "%s" failed', connection_name) - return False - - hass.data[DATA_LCN][CONF_CONNECTIONS] = connections - - # load platforms - for component, conf_key in ( - ("binary_sensor", CONF_BINARY_SENSORS), - ("climate", CONF_CLIMATES), - ("cover", CONF_COVERS), - ("light", CONF_LIGHTS), - ("scene", CONF_SCENES), - ("sensor", CONF_SENSORS), - ("switch", CONF_SWITCHES), - ): - if conf_key in config[DOMAIN]: - hass.async_create_task( - async_load_platform( - hass, component, DOMAIN, config[DOMAIN][conf_key], config - ) + for config_entry_data in config_entries_data: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=config_entry_data, ) + ) + return True + + +async def async_setup_entry(hass, config_entry): + """Set up a connection to PCHK host from a config entry.""" + hass.data.setdefault(DOMAIN, {}) + if config_entry.entry_id in hass.data[DOMAIN]: + return False + + settings = { + "SK_NUM_TRIES": config_entry.data[CONF_SK_NUM_TRIES], + "DIM_MODE": pypck.lcn_defs.OutputPortDimMode[config_entry.data[CONF_DIM_MODE]], + } + + # connect to PCHK + lcn_connection = pypck.connection.PchkConnectionManager( + config_entry.data[CONF_IP_ADDRESS], + config_entry.data[CONF_PORT], + config_entry.data[CONF_USERNAME], + config_entry.data[CONF_PASSWORD], + settings=settings, + connection_id=config_entry.entry_id, + ) + try: + # establish connection to PCHK server + await lcn_connection.async_connect(timeout=15) + except pypck.connection.PchkAuthenticationError: + _LOGGER.warning('Authentication on PCHK "%s" failed', config_entry.title) + return False + except pypck.connection.PchkLicenseError: + _LOGGER.warning( + 'Maximum number of connections on PCHK "%s" was ' + "reached. An additional license key is required", + config_entry.title, + ) + return False + except TimeoutError: + _LOGGER.warning('Connection to PCHK "%s" failed', config_entry.title) + return False + + _LOGGER.debug('LCN connected to "%s"', config_entry.title) + hass.data[DOMAIN][config_entry.entry_id] = { + CONNECTION: lcn_connection, + } + + # remove orphans from entity registry which are in ConfigEntry but were removed + # from configuration.yaml + if config_entry.source == config_entries.SOURCE_IMPORT: + entity_registry = await er.async_get_registry(hass) + entity_registry.async_clear_config_entry(config_entry.entry_id) + + # forward config_entry to components + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, component) + ) # register service calls - for service_name, service in ( - ("output_abs", OutputAbs), - ("output_rel", OutputRel), - ("output_toggle", OutputToggle), - ("relays", Relays), - ("var_abs", VarAbs), - ("var_reset", VarReset), - ("var_rel", VarRel), - ("lock_regulator", LockRegulator), - ("led", Led), - ("send_keys", SendKeys), - ("lock_keys", LockKeys), - ("dyn_text", DynText), - ("pck", Pck), - ): - hass.services.async_register( - DOMAIN, service_name, service(hass).async_call_service, service.schema - ) + for service_name, service in SERVICES: + if not hass.services.has_service(DOMAIN, service_name): + hass.services.async_register( + DOMAIN, service_name, service(hass).async_call_service, service.schema + ) return True -class LcnEntity(Entity): - """Parent class for all devices associated with the LCN component.""" +async def async_unload_entry(hass, config_entry): + """Close connection to PCHK host represented by config_entry.""" + # forward unloading to platforms + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(config_entry, component) + for component in PLATFORMS + ] + ) + ) - def __init__(self, config, device_connection): + if unload_ok and config_entry.entry_id in hass.data[DOMAIN]: + host = hass.data[DOMAIN].pop(config_entry.entry_id) + await host[CONNECTION].async_close() + + # unregister service calls + if unload_ok and not hass.data[DOMAIN]: # check if this is the last entry to unload + for service_name, _ in SERVICES: + hass.services.async_remove(DOMAIN, service_name) + + return unload_ok + + +class LcnEntity(Entity): + """Parent class for all entities associated with the LCN component.""" + + def __init__(self, config, entry_id, device_connection): """Initialize the LCN device.""" self.config = config + self.entry_id = entry_id self.device_connection = device_connection + self._unregister_for_inputs = None self._name = config[CONF_NAME] + @property + def unique_id(self): + """Return a unique ID.""" + unique_device_id = generate_unique_id( + ( + self.device_connection.seg_id, + self.device_connection.addr_id, + self.device_connection.is_group, + ) + ) + return f"{self.entry_id}-{unique_device_id}-{self.config[CONF_RESOURCE]}" + @property def should_poll(self): """Lcn device entity pushes its state to HA.""" @@ -140,7 +165,14 @@ class LcnEntity(Entity): async def async_added_to_hass(self): """Run when entity about to be added to hass.""" if not self.device_connection.is_group: - self.device_connection.register_for_inputs(self.input_received) + self._unregister_for_inputs = self.device_connection.register_for_inputs( + self.input_received + ) + + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + if self._unregister_for_inputs is not None: + self._unregister_for_inputs() @property def name(self): diff --git a/homeassistant/components/lcn/binary_sensor.py b/homeassistant/components/lcn/binary_sensor.py index 56a5ea6e646..3bea502cc76 100644 --- a/homeassistant/components/lcn/binary_sensor.py +++ b/homeassistant/components/lcn/binary_sensor.py @@ -1,49 +1,56 @@ """Support for LCN binary sensors.""" import pypck -from homeassistant.components.binary_sensor import BinarySensorEntity -from homeassistant.const import CONF_ADDRESS, CONF_SOURCE +from homeassistant.components.binary_sensor import ( + DOMAIN as DOMAIN_BINARY_SENSOR, + BinarySensorEntity, +) +from homeassistant.const import CONF_ADDRESS, CONF_DOMAIN, CONF_ENTITIES, CONF_SOURCE from . import LcnEntity -from .const import BINSENSOR_PORTS, CONF_CONNECTIONS, DATA_LCN, SETPOINTS -from .helpers import get_connection +from .const import BINSENSOR_PORTS, CONF_DOMAIN_DATA, SETPOINTS +from .helpers import get_device_connection -async def async_setup_platform( - hass, hass_config, async_add_entities, discovery_info=None -): - """Set up the LCN binary sensor platform.""" - if discovery_info is None: - return +def create_lcn_binary_sensor_entity(hass, entity_config, config_entry): + """Set up an entity for this domain.""" + device_connection = get_device_connection( + hass, tuple(entity_config[CONF_ADDRESS]), config_entry + ) - devices = [] - for config in discovery_info: - address, connection_id = config[CONF_ADDRESS] - addr = pypck.lcn_addr.LcnAddr(*address) - connections = hass.data[DATA_LCN][CONF_CONNECTIONS] - connection = get_connection(connections, connection_id) - address_connection = connection.get_address_conn(addr) + if entity_config[CONF_DOMAIN_DATA][CONF_SOURCE] in SETPOINTS: + return LcnRegulatorLockSensor( + entity_config, config_entry.entry_id, device_connection + ) + if entity_config[CONF_DOMAIN_DATA][CONF_SOURCE] in BINSENSOR_PORTS: + return LcnBinarySensor(entity_config, config_entry.entry_id, device_connection) + # in KEY + return LcnLockKeysSensor(entity_config, config_entry.entry_id, device_connection) - if config[CONF_SOURCE] in SETPOINTS: - device = LcnRegulatorLockSensor(config, address_connection) - elif config[CONF_SOURCE] in BINSENSOR_PORTS: - device = LcnBinarySensor(config, address_connection) - else: # in KEYS - device = LcnLockKeysSensor(config, address_connection) - devices.append(device) +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up LCN switch entities from a config entry.""" + entities = [] - async_add_entities(devices) + for entity_config in config_entry.data[CONF_ENTITIES]: + if entity_config[CONF_DOMAIN] == DOMAIN_BINARY_SENSOR: + entities.append( + create_lcn_binary_sensor_entity(hass, entity_config, config_entry) + ) + + async_add_entities(entities) class LcnRegulatorLockSensor(LcnEntity, BinarySensorEntity): """Representation of a LCN binary sensor for regulator locks.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN binary sensor.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.setpoint_variable = pypck.lcn_defs.Var[config[CONF_SOURCE]] + self.setpoint_variable = pypck.lcn_defs.Var[ + config[CONF_DOMAIN_DATA][CONF_SOURCE] + ] self._value = None @@ -55,6 +62,14 @@ class LcnRegulatorLockSensor(LcnEntity, BinarySensorEntity): self.setpoint_variable ) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler( + self.setpoint_variable + ) + @property def is_on(self): """Return true if the binary sensor is on.""" @@ -75,11 +90,13 @@ class LcnRegulatorLockSensor(LcnEntity, BinarySensorEntity): class LcnBinarySensor(LcnEntity, BinarySensorEntity): """Representation of a LCN binary sensor for binary sensor ports.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN binary sensor.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.bin_sensor_port = pypck.lcn_defs.BinSensorPort[config[CONF_SOURCE]] + self.bin_sensor_port = pypck.lcn_defs.BinSensorPort[ + config[CONF_DOMAIN_DATA][CONF_SOURCE] + ] self._value = None @@ -91,6 +108,14 @@ class LcnBinarySensor(LcnEntity, BinarySensorEntity): self.bin_sensor_port ) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler( + self.bin_sensor_port + ) + @property def is_on(self): """Return true if the binary sensor is on.""" @@ -108,11 +133,11 @@ class LcnBinarySensor(LcnEntity, BinarySensorEntity): class LcnLockKeysSensor(LcnEntity, BinarySensorEntity): """Representation of a LCN sensor for key locks.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN sensor.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.source = pypck.lcn_defs.Key[config[CONF_SOURCE]] + self.source = pypck.lcn_defs.Key[config[CONF_DOMAIN_DATA][CONF_SOURCE]] self._value = None async def async_added_to_hass(self): @@ -121,6 +146,12 @@ class LcnLockKeysSensor(LcnEntity, BinarySensorEntity): if not self.device_connection.is_group: await self.device_connection.activate_status_request_handler(self.source) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler(self.source) + @property def is_on(self): """Return true if the binary sensor is on.""" diff --git a/homeassistant/components/lcn/climate.py b/homeassistant/components/lcn/climate.py index e3269a51cd6..056abcda2b0 100644 --- a/homeassistant/components/lcn/climate.py +++ b/homeassistant/components/lcn/climate.py @@ -1,68 +1,76 @@ """Support for LCN climate control.""" - import pypck -from homeassistant.components.climate import ClimateEntity, const +from homeassistant.components.climate import ( + DOMAIN as DOMAIN_CLIMATE, + ClimateEntity, + const, +) from homeassistant.const import ( ATTR_TEMPERATURE, CONF_ADDRESS, + CONF_DOMAIN, + CONF_ENTITIES, CONF_SOURCE, CONF_UNIT_OF_MEASUREMENT, ) from . import LcnEntity from .const import ( - CONF_CONNECTIONS, + CONF_DOMAIN_DATA, CONF_LOCKABLE, CONF_MAX_TEMP, CONF_MIN_TEMP, CONF_SETPOINT, - DATA_LCN, ) -from .helpers import get_connection +from .helpers import get_device_connection PARALLEL_UPDATES = 0 -async def async_setup_platform( - hass, hass_config, async_add_entities, discovery_info=None -): - """Set up the LCN climate platform.""" - if discovery_info is None: - return +def create_lcn_climate_entity(hass, entity_config, config_entry): + """Set up an entity for this domain.""" + device_connection = get_device_connection( + hass, tuple(entity_config[CONF_ADDRESS]), config_entry + ) - devices = [] - for config in discovery_info: - address, connection_id = config[CONF_ADDRESS] - addr = pypck.lcn_addr.LcnAddr(*address) - connections = hass.data[DATA_LCN][CONF_CONNECTIONS] - connection = get_connection(connections, connection_id) - address_connection = connection.get_address_conn(addr) + return LcnClimate(entity_config, config_entry.entry_id, device_connection) - devices.append(LcnClimate(config, address_connection)) - async_add_entities(devices) +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up LCN switch entities from a config entry.""" + entities = [] + + for entity_config in config_entry.data[CONF_ENTITIES]: + if entity_config[CONF_DOMAIN] == DOMAIN_CLIMATE: + entities.append( + create_lcn_climate_entity(hass, entity_config, config_entry) + ) + + async_add_entities(entities) class LcnClimate(LcnEntity, ClimateEntity): """Representation of a LCN climate device.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize of a LCN climate device.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.variable = pypck.lcn_defs.Var[config[CONF_SOURCE]] - self.setpoint = pypck.lcn_defs.Var[config[CONF_SETPOINT]] - self.unit = pypck.lcn_defs.VarUnit.parse(config[CONF_UNIT_OF_MEASUREMENT]) + self.variable = pypck.lcn_defs.Var[config[CONF_DOMAIN_DATA][CONF_SOURCE]] + self.setpoint = pypck.lcn_defs.Var[config[CONF_DOMAIN_DATA][CONF_SETPOINT]] + self.unit = pypck.lcn_defs.VarUnit.parse( + config[CONF_DOMAIN_DATA][CONF_UNIT_OF_MEASUREMENT] + ) self.regulator_id = pypck.lcn_defs.Var.to_set_point_id(self.setpoint) - self.is_lockable = config[CONF_LOCKABLE] - self._max_temp = config[CONF_MAX_TEMP] - self._min_temp = config[CONF_MIN_TEMP] + self.is_lockable = config[CONF_DOMAIN_DATA][CONF_LOCKABLE] + self._max_temp = config[CONF_DOMAIN_DATA][CONF_MAX_TEMP] + self._min_temp = config[CONF_DOMAIN_DATA][CONF_MIN_TEMP] self._current_temperature = None self._target_temperature = None - self._is_on = None + self._is_on = True async def async_added_to_hass(self): """Run when entity about to be added to hass.""" @@ -71,6 +79,13 @@ class LcnClimate(LcnEntity, ClimateEntity): await self.device_connection.activate_status_request_handler(self.variable) await self.device_connection.activate_status_request_handler(self.setpoint) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler(self.variable) + await self.device_connection.cancel_status_request_handler(self.setpoint) + @property def supported_features(self): """Return the list of supported features.""" diff --git a/homeassistant/components/lcn/config_flow.py b/homeassistant/components/lcn/config_flow.py new file mode 100644 index 00000000000..fe353cdb4c5 --- /dev/null +++ b/homeassistant/components/lcn/config_flow.py @@ -0,0 +1,97 @@ +"""Config flow to configure the LCN integration.""" +import logging + +import pypck + +from homeassistant import config_entries +from homeassistant.const import ( + CONF_HOST, + CONF_IP_ADDRESS, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, +) + +from .const import CONF_DIM_MODE, CONF_SK_NUM_TRIES, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +def get_config_entry(hass, data): + """Check config entries for already configured entries based on the ip address/port.""" + return next( + ( + entry + for entry in hass.config_entries.async_entries(DOMAIN) + if entry.data[CONF_IP_ADDRESS] == data[CONF_IP_ADDRESS] + and entry.data[CONF_PORT] == data[CONF_PORT] + ), + None, + ) + + +async def validate_connection(host_name, data): + """Validate if a connection to LCN can be established.""" + host = data[CONF_IP_ADDRESS] + port = data[CONF_PORT] + username = data[CONF_USERNAME] + password = data[CONF_PASSWORD] + sk_num_tries = data[CONF_SK_NUM_TRIES] + dim_mode = data[CONF_DIM_MODE] + + settings = { + "SK_NUM_TRIES": sk_num_tries, + "DIM_MODE": pypck.lcn_defs.OutputPortDimMode[dim_mode], + } + + _LOGGER.debug("Validating connection parameters to PCHK host '%s'", host_name) + + connection = pypck.connection.PchkConnectionManager( + host, port, username, password, settings=settings + ) + + await connection.async_connect(timeout=5) + + _LOGGER.debug("LCN connection validated") + await connection.async_close() + return data + + +@config_entries.HANDLERS.register(DOMAIN) +class LcnFlowHandler(config_entries.ConfigFlow): + """Handle a LCN config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + + async def async_step_import(self, data): + """Import existing configuration from LCN.""" + host_name = data[CONF_HOST] + # validate the imported connection parameters + try: + await validate_connection(host_name, data) + except pypck.connection.PchkAuthenticationError: + _LOGGER.warning('Authentication on PCHK "%s" failed', host_name) + return self.async_abort(reason="authentication_error") + except pypck.connection.PchkLicenseError: + _LOGGER.warning( + 'Maximum number of connections on PCHK "%s" was ' + "reached. An additional license key is required", + host_name, + ) + return self.async_abort(reason="license_error") + except TimeoutError: + _LOGGER.warning('Connection to PCHK "%s" failed', host_name) + return self.async_abort(reason="connection_timeout") + + # check if we already have a host with the same address configured + entry = get_config_entry(self.hass, data) + if entry: + entry.source = config_entries.SOURCE_IMPORT + self.hass.config_entries.async_update_entry(entry, data=data) + return self.async_abort(reason="existing_configuration_updated") + + return self.async_create_entry( + title=f"{host_name}", + data=data, + ) diff --git a/homeassistant/components/lcn/const.py b/homeassistant/components/lcn/const.py index 3dcac6fb55f..4e3e765ace0 100644 --- a/homeassistant/components/lcn/const.py +++ b/homeassistant/components/lcn/const.py @@ -14,6 +14,13 @@ DOMAIN = "lcn" DATA_LCN = "lcn" DEFAULT_NAME = "pchk" +CONNECTION = "connection" +CONF_HARDWARE_SERIAL = "hardware_serial" +CONF_SOFTWARE_SERIAL = "software_serial" +CONF_HARDWARE_TYPE = "hardware_type" +CONF_RESOURCE = "resource" +CONF_DOMAIN_DATA = "domain_data" + CONF_CONNECTIONS = "connections" CONF_SK_NUM_TRIES = "sk_num_tries" CONF_OUTPUT = "output" diff --git a/homeassistant/components/lcn/cover.py b/homeassistant/components/lcn/cover.py index 3d7c2a06a3b..bf777ad93f2 100644 --- a/homeassistant/components/lcn/cover.py +++ b/homeassistant/components/lcn/cover.py @@ -1,53 +1,54 @@ """Support for LCN covers.""" + import pypck -from homeassistant.components.cover import CoverEntity -from homeassistant.const import CONF_ADDRESS +from homeassistant.components.cover import DOMAIN as DOMAIN_COVER, CoverEntity +from homeassistant.const import CONF_ADDRESS, CONF_DOMAIN, CONF_ENTITIES from . import LcnEntity -from .const import CONF_CONNECTIONS, CONF_MOTOR, CONF_REVERSE_TIME, DATA_LCN -from .helpers import get_connection +from .const import CONF_DOMAIN_DATA, CONF_MOTOR, CONF_REVERSE_TIME +from .helpers import get_device_connection PARALLEL_UPDATES = 0 -async def async_setup_platform( - hass, hass_config, async_add_entities, discovery_info=None -): - """Setups the LCN cover platform.""" - if discovery_info is None: - return +def create_lcn_cover_entity(hass, entity_config, config_entry): + """Set up an entity for this domain.""" + device_connection = get_device_connection( + hass, tuple(entity_config[CONF_ADDRESS]), config_entry + ) - devices = [] - for config in discovery_info: - address, connection_id = config[CONF_ADDRESS] - addr = pypck.lcn_addr.LcnAddr(*address) - connections = hass.data[DATA_LCN][CONF_CONNECTIONS] - connection = get_connection(connections, connection_id) - address_connection = connection.get_address_conn(addr) + if entity_config[CONF_DOMAIN_DATA][CONF_MOTOR] in "OUTPUTS": + return LcnOutputsCover(entity_config, config_entry.entry_id, device_connection) + # in RELAYS + return LcnRelayCover(entity_config, config_entry.entry_id, device_connection) - if config[CONF_MOTOR] == "OUTPUTS": - devices.append(LcnOutputsCover(config, address_connection)) - else: # RELAYS - devices.append(LcnRelayCover(config, address_connection)) - async_add_entities(devices) +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up LCN cover entities from a config entry.""" + entities = [] + + for entity_config in config_entry.data[CONF_ENTITIES]: + if entity_config[CONF_DOMAIN] == DOMAIN_COVER: + entities.append(create_lcn_cover_entity(hass, entity_config, config_entry)) + + async_add_entities(entities) class LcnOutputsCover(LcnEntity, CoverEntity): """Representation of a LCN cover connected to output ports.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN cover.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) self.output_ids = [ pypck.lcn_defs.OutputPort["OUTPUTUP"].value, pypck.lcn_defs.OutputPort["OUTPUTDOWN"].value, ] - if CONF_REVERSE_TIME in config: + if CONF_REVERSE_TIME in config[CONF_DOMAIN_DATA]: self.reverse_time = pypck.lcn_defs.MotorReverseTime[ - config[CONF_REVERSE_TIME] + config[CONF_DOMAIN_DATA][CONF_REVERSE_TIME] ] else: self.reverse_time = None @@ -59,12 +60,24 @@ class LcnOutputsCover(LcnEntity, CoverEntity): async def async_added_to_hass(self): """Run when entity about to be added to hass.""" await super().async_added_to_hass() - await self.device_connection.activate_status_request_handler( - pypck.lcn_defs.OutputPort["OUTPUTUP"] - ) - await self.device_connection.activate_status_request_handler( - pypck.lcn_defs.OutputPort["OUTPUTDOWN"] - ) + if not self.device_connection.is_group: + await self.device_connection.activate_status_request_handler( + pypck.lcn_defs.OutputPort["OUTPUTUP"] + ) + await self.device_connection.activate_status_request_handler( + pypck.lcn_defs.OutputPort["OUTPUTDOWN"] + ) + + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler( + pypck.lcn_defs.OutputPort["OUTPUTUP"] + ) + await self.device_connection.cancel_status_request_handler( + pypck.lcn_defs.OutputPort["OUTPUTDOWN"] + ) @property def is_closed(self): @@ -146,11 +159,11 @@ class LcnOutputsCover(LcnEntity, CoverEntity): class LcnRelayCover(LcnEntity, CoverEntity): """Representation of a LCN cover connected to relays.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN cover.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.motor = pypck.lcn_defs.MotorPort[config[CONF_MOTOR]] + self.motor = pypck.lcn_defs.MotorPort[config[CONF_DOMAIN_DATA][CONF_MOTOR]] self.motor_port_onoff = self.motor.value * 2 self.motor_port_updown = self.motor_port_onoff + 1 @@ -164,6 +177,12 @@ class LcnRelayCover(LcnEntity, CoverEntity): if not self.device_connection.is_group: await self.device_connection.activate_status_request_handler(self.motor) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler(self.motor) + @property def is_closed(self): """Return if the cover is closed.""" diff --git a/homeassistant/components/lcn/helpers.py b/homeassistant/components/lcn/helpers.py index 18342aa1d98..3f93ec95a69 100644 --- a/homeassistant/components/lcn/helpers.py +++ b/homeassistant/components/lcn/helpers.py @@ -1,11 +1,42 @@ """Helpers for LCN component.""" import re +import pypck import voluptuous as vol -from homeassistant.const import CONF_NAME +from homeassistant.const import ( + CONF_ADDRESS, + CONF_BINARY_SENSORS, + CONF_COVERS, + CONF_DEVICES, + CONF_DOMAIN, + CONF_ENTITIES, + CONF_HOST, + CONF_IP_ADDRESS, + CONF_LIGHTS, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_SENSORS, + CONF_SWITCHES, + CONF_USERNAME, +) -from .const import DEFAULT_NAME +from .const import ( + CONF_CLIMATES, + CONF_CONNECTIONS, + CONF_DIM_MODE, + CONF_DOMAIN_DATA, + CONF_HARDWARE_SERIAL, + CONF_HARDWARE_TYPE, + CONF_RESOURCE, + CONF_SCENES, + CONF_SK_NUM_TRIES, + CONF_SOFTWARE_SERIAL, + CONNECTION, + DEFAULT_NAME, + DOMAIN, +) # Regex for address validation PATTERN_ADDRESS = re.compile( @@ -13,17 +44,145 @@ PATTERN_ADDRESS = re.compile( ) -def get_connection(connections, connection_id=None): - """Return the connection object from list.""" - if connection_id is None: - connection = connections[0] - else: - for connection in connections: - if connection.connection_id == connection_id: - break - else: - raise ValueError("Unknown connection_id.") - return connection +DOMAIN_LOOKUP = { + CONF_BINARY_SENSORS: "binary_sensor", + CONF_CLIMATES: "climate", + CONF_COVERS: "cover", + CONF_LIGHTS: "light", + CONF_SCENES: "scene", + CONF_SENSORS: "sensor", + CONF_SWITCHES: "switch", +} + + +def get_device_connection(hass, address, config_entry): + """Return a lcn device_connection.""" + host_connection = hass.data[DOMAIN][config_entry.entry_id][CONNECTION] + addr = pypck.lcn_addr.LcnAddr(*address) + return host_connection.get_address_conn(addr) + + +def get_resource(domain_name, domain_data): + """Return the resource for the specified domain_data.""" + if domain_name in ["switch", "light"]: + return domain_data["output"] + if domain_name in ["binary_sensor", "sensor"]: + return domain_data["source"] + if domain_name == "cover": + return domain_data["motor"] + if domain_name == "climate": + return f'{domain_data["source"]}.{domain_data["setpoint"]}' + if domain_name == "scene": + return f'{domain_data["register"]}.{domain_data["scene"]}' + raise ValueError("Unknown domain") + + +def generate_unique_id(address): + """Generate a unique_id from the given parameters.""" + is_group = "g" if address[2] else "m" + return f"{is_group}{address[0]:03d}{address[1]:03d}" + + +def import_lcn_config(lcn_config): + """Convert lcn settings from configuration.yaml to config_entries data. + + Create a list of config_entry data structures like: + + "data": { + "host": "pchk", + "ip_address": "192.168.2.41", + "port": 4114, + "username": "lcn", + "password": "lcn, + "sk_num_tries: 0, + "dim_mode: "STEPS200", + "devices": [ + { + "address": (0, 7, False) + "name": "", + "hardware_serial": -1, + "software_serial": -1, + "hardware_type": -1 + }, ... + ], + "entities": [ + { + "address": (0, 7, False) + "name": "Light_Output1", + "resource": "output1", + "domain": "light", + "domain_data": { + "output": "OUTPUT1", + "dimmable": True, + "transition": 5000.0 + } + }, ... + ] + } + """ + data = {} + for connection in lcn_config[CONF_CONNECTIONS]: + host = { + CONF_HOST: connection[CONF_NAME], + CONF_IP_ADDRESS: connection[CONF_HOST], + CONF_PORT: connection[CONF_PORT], + CONF_USERNAME: connection[CONF_USERNAME], + CONF_PASSWORD: connection[CONF_PASSWORD], + CONF_SK_NUM_TRIES: connection[CONF_SK_NUM_TRIES], + CONF_DIM_MODE: connection[CONF_DIM_MODE], + CONF_DEVICES: [], + CONF_ENTITIES: [], + } + data[connection[CONF_NAME]] = host + + for confkey, domain_config in lcn_config.items(): + if confkey == CONF_CONNECTIONS: + continue + domain = DOMAIN_LOOKUP[confkey] + # loop over entities in configuration.yaml + for domain_data in domain_config: + # remove name and address from domain_data + entity_name = domain_data.pop(CONF_NAME) + address, host_name = domain_data.pop(CONF_ADDRESS) + + if host_name is None: + host_name = DEFAULT_NAME + + # check if we have a new device config + for device_config in data[host_name][CONF_DEVICES]: + if address == device_config[CONF_ADDRESS]: + break + else: # create new device_config + device_config = { + CONF_ADDRESS: address, + CONF_NAME: "", + CONF_HARDWARE_SERIAL: -1, + CONF_SOFTWARE_SERIAL: -1, + CONF_HARDWARE_TYPE: -1, + } + + data[host_name][CONF_DEVICES].append(device_config) + + # insert entity config + resource = get_resource(domain, domain_data).lower() + for entity_config in data[host_name][CONF_ENTITIES]: + if ( + address == entity_config[CONF_ADDRESS] + and resource == entity_config[CONF_RESOURCE] + and domain == entity_config[CONF_DOMAIN] + ): + break + else: # create new entity_config + entity_config = { + CONF_ADDRESS: address, + CONF_NAME: entity_name, + CONF_RESOURCE: resource, + CONF_DOMAIN: domain, + CONF_DOMAIN_DATA: domain_data.copy(), + } + data[host_name][CONF_ENTITIES].append(entity_config) + + return list(data.values()) def has_unique_host_names(hosts): diff --git a/homeassistant/components/lcn/light.py b/homeassistant/components/lcn/light.py index 8a76056ff46..8697d8e0319 100644 --- a/homeassistant/components/lcn/light.py +++ b/homeassistant/components/lcn/light.py @@ -1,68 +1,69 @@ """Support for LCN lights.""" + import pypck from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_TRANSITION, + DOMAIN as DOMAIN_LIGHT, SUPPORT_BRIGHTNESS, SUPPORT_TRANSITION, LightEntity, ) -from homeassistant.const import CONF_ADDRESS +from homeassistant.const import CONF_ADDRESS, CONF_DOMAIN, CONF_ENTITIES from . import LcnEntity from .const import ( - CONF_CONNECTIONS, CONF_DIMMABLE, + CONF_DOMAIN_DATA, CONF_OUTPUT, CONF_TRANSITION, - DATA_LCN, OUTPUT_PORTS, ) -from .helpers import get_connection +from .helpers import get_device_connection PARALLEL_UPDATES = 0 -async def async_setup_platform( - hass, hass_config, async_add_entities, discovery_info=None -): - """Set up the LCN light platform.""" - if discovery_info is None: - return +def create_lcn_light_entity(hass, entity_config, config_entry): + """Set up an entity for this domain.""" + device_connection = get_device_connection( + hass, tuple(entity_config[CONF_ADDRESS]), config_entry + ) - devices = [] - for config in discovery_info: - address, connection_id = config[CONF_ADDRESS] - addr = pypck.lcn_addr.LcnAddr(*address) - connections = hass.data[DATA_LCN][CONF_CONNECTIONS] - connection = get_connection(connections, connection_id) - address_connection = connection.get_address_conn(addr) + if entity_config[CONF_DOMAIN_DATA][CONF_OUTPUT] in OUTPUT_PORTS: + return LcnOutputLight(entity_config, config_entry.entry_id, device_connection) + # in RELAY_PORTS + return LcnRelayLight(entity_config, config_entry.entry_id, device_connection) - if config[CONF_OUTPUT] in OUTPUT_PORTS: - device = LcnOutputLight(config, address_connection) - else: # in RELAY_PORTS - device = LcnRelayLight(config, address_connection) - devices.append(device) +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up LCN light entities from a config entry.""" + entities = [] - async_add_entities(devices) + for entity_config in config_entry.data[CONF_ENTITIES]: + if entity_config[CONF_DOMAIN] == DOMAIN_LIGHT: + entities.append(create_lcn_light_entity(hass, entity_config, config_entry)) + + async_add_entities(entities) class LcnOutputLight(LcnEntity, LightEntity): """Representation of a LCN light for output ports.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN light.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.output = pypck.lcn_defs.OutputPort[config[CONF_OUTPUT]] + self.output = pypck.lcn_defs.OutputPort[config[CONF_DOMAIN_DATA][CONF_OUTPUT]] - self._transition = pypck.lcn_defs.time_to_ramp_value(config[CONF_TRANSITION]) - self.dimmable = config[CONF_DIMMABLE] + self._transition = pypck.lcn_defs.time_to_ramp_value( + config[CONF_DOMAIN_DATA][CONF_TRANSITION] + ) + self.dimmable = config[CONF_DOMAIN_DATA][CONF_DIMMABLE] self._brightness = 255 - self._is_on = None + self._is_on = False self._is_dimming_to_zero = False async def async_added_to_hass(self): @@ -71,6 +72,12 @@ class LcnOutputLight(LcnEntity, LightEntity): if not self.device_connection.is_group: await self.device_connection.activate_status_request_handler(self.output) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler(self.output) + @property def supported_features(self): """Flag supported features.""" @@ -145,13 +152,13 @@ class LcnOutputLight(LcnEntity, LightEntity): class LcnRelayLight(LcnEntity, LightEntity): """Representation of a LCN light for relay ports.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN light.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.output = pypck.lcn_defs.RelayPort[config[CONF_OUTPUT]] + self.output = pypck.lcn_defs.RelayPort[config[CONF_DOMAIN_DATA][CONF_OUTPUT]] - self._is_on = None + self._is_on = False async def async_added_to_hass(self): """Run when entity about to be added to hass.""" @@ -159,6 +166,12 @@ class LcnRelayLight(LcnEntity, LightEntity): if not self.device_connection.is_group: await self.device_connection.activate_status_request_handler(self.output) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler(self.output) + @property def is_on(self): """Return True if entity is on.""" diff --git a/homeassistant/components/lcn/manifest.json b/homeassistant/components/lcn/manifest.json index c5077bdf409..5c8be5829e0 100644 --- a/homeassistant/components/lcn/manifest.json +++ b/homeassistant/components/lcn/manifest.json @@ -1,6 +1,7 @@ { "domain": "lcn", "name": "LCN", + "config_flow": false, "documentation": "https://www.home-assistant.io/integrations/lcn", "requirements": [ "pypck==0.7.9" diff --git a/homeassistant/components/lcn/scene.py b/homeassistant/components/lcn/scene.py index 1c359607fb2..8f770df7668 100644 --- a/homeassistant/components/lcn/scene.py +++ b/homeassistant/components/lcn/scene.py @@ -1,72 +1,69 @@ """Support for LCN scenes.""" -from typing import Any import pypck -from homeassistant.components.scene import Scene -from homeassistant.const import CONF_ADDRESS, CONF_SCENE +from homeassistant.components.scene import DOMAIN as DOMAIN_SCENE, Scene +from homeassistant.const import CONF_ADDRESS, CONF_DOMAIN, CONF_ENTITIES, CONF_SCENE from . import LcnEntity from .const import ( - CONF_CONNECTIONS, + CONF_DOMAIN_DATA, CONF_OUTPUTS, CONF_REGISTER, CONF_TRANSITION, - DATA_LCN, OUTPUT_PORTS, ) -from .helpers import get_connection +from .helpers import get_device_connection PARALLEL_UPDATES = 0 -async def async_setup_platform( - hass, hass_config, async_add_entities, discovery_info=None -): - """Set up the LCN scene platform.""" - if discovery_info is None: - return +def create_lcn_scene_entity(hass, entity_config, config_entry): + """Set up an entity for this domain.""" + device_connection = get_device_connection( + hass, tuple(entity_config[CONF_ADDRESS]), config_entry + ) - devices = [] - for config in discovery_info: - address, connection_id = config[CONF_ADDRESS] - addr = pypck.lcn_addr.LcnAddr(*address) - connections = hass.data[DATA_LCN][CONF_CONNECTIONS] - connection = get_connection(connections, connection_id) - address_connection = connection.get_address_conn(addr) + return LcnScene(entity_config, config_entry.entry_id, device_connection) - devices.append(LcnScene(config, address_connection)) - async_add_entities(devices) +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up LCN switch entities from a config entry.""" + entities = [] + + for entity_config in config_entry.data[CONF_ENTITIES]: + if entity_config[CONF_DOMAIN] == DOMAIN_SCENE: + entities.append(create_lcn_scene_entity(hass, entity_config, config_entry)) + + async_add_entities(entities) class LcnScene(LcnEntity, Scene): """Representation of a LCN scene.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN scene.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.register_id = config[CONF_REGISTER] - self.scene_id = config[CONF_SCENE] + self.register_id = config[CONF_DOMAIN_DATA][CONF_REGISTER] + self.scene_id = config[CONF_DOMAIN_DATA][CONF_SCENE] self.output_ports = [] self.relay_ports = [] - for port in config[CONF_OUTPUTS]: + for port in config[CONF_DOMAIN_DATA][CONF_OUTPUTS]: if port in OUTPUT_PORTS: self.output_ports.append(pypck.lcn_defs.OutputPort[port]) else: # in RELEAY_PORTS self.relay_ports.append(pypck.lcn_defs.RelayPort[port]) - if config[CONF_TRANSITION] is None: + if config[CONF_DOMAIN_DATA][CONF_TRANSITION] is None: self.transition = None else: - self.transition = pypck.lcn_defs.time_to_ramp_value(config[CONF_TRANSITION]) + self.transition = pypck.lcn_defs.time_to_ramp_value( + config[CONF_DOMAIN_DATA][CONF_TRANSITION] + ) - async def async_added_to_hass(self): - """Run when entity about to be added to hass.""" - - async def async_activate(self, **kwargs: Any) -> None: + async def async_activate(self, **kwargs): """Activate scene.""" await self.device_connection.activate_scene( self.register_id, diff --git a/homeassistant/components/lcn/sensor.py b/homeassistant/components/lcn/sensor.py index 11932dccea8..510df46cd1e 100644 --- a/homeassistant/components/lcn/sensor.py +++ b/homeassistant/components/lcn/sensor.py @@ -1,55 +1,67 @@ """Support for LCN sensors.""" + import pypck -from homeassistant.const import CONF_ADDRESS, CONF_SOURCE, CONF_UNIT_OF_MEASUREMENT +from homeassistant.components.sensor import DOMAIN as DOMAIN_SENSOR +from homeassistant.const import ( + CONF_ADDRESS, + CONF_DOMAIN, + CONF_ENTITIES, + CONF_SOURCE, + CONF_UNIT_OF_MEASUREMENT, +) from . import LcnEntity from .const import ( - CONF_CONNECTIONS, - DATA_LCN, + CONF_DOMAIN_DATA, LED_PORTS, S0_INPUTS, SETPOINTS, THRESHOLDS, VARIABLES, ) -from .helpers import get_connection +from .helpers import get_device_connection -async def async_setup_platform( - hass, hass_config, async_add_entities, discovery_info=None -): - """Set up the LCN sensor platform.""" - if discovery_info is None: - return +def create_lcn_sensor_entity(hass, entity_config, config_entry): + """Set up an entity for this domain.""" + device_connection = get_device_connection( + hass, tuple(entity_config[CONF_ADDRESS]), config_entry + ) - devices = [] - for config in discovery_info: - address, connection_id = config[CONF_ADDRESS] - addr = pypck.lcn_addr.LcnAddr(*address) - connections = hass.data[DATA_LCN][CONF_CONNECTIONS] - connection = get_connection(connections, connection_id) - device_connection = connection.get_address_conn(addr) + if ( + entity_config[CONF_DOMAIN_DATA][CONF_SOURCE] + in VARIABLES + SETPOINTS + THRESHOLDS + S0_INPUTS + ): + return LcnVariableSensor( + entity_config, config_entry.entry_id, device_connection + ) + # in LED_PORTS + LOGICOP_PORTS + return LcnLedLogicSensor(entity_config, config_entry.entry_id, device_connection) - if config[CONF_SOURCE] in VARIABLES + SETPOINTS + THRESHOLDS + S0_INPUTS: - device = LcnVariableSensor(config, device_connection) - else: # in LED_PORTS + LOGICOP_PORTS - device = LcnLedLogicSensor(config, device_connection) - devices.append(device) +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up LCN switch entities from a config entry.""" + entities = [] - async_add_entities(devices) + for entity_config in config_entry.data[CONF_ENTITIES]: + if entity_config[CONF_DOMAIN] == DOMAIN_SENSOR: + entities.append(create_lcn_sensor_entity(hass, entity_config, config_entry)) + + async_add_entities(entities) class LcnVariableSensor(LcnEntity): """Representation of a LCN sensor for variables.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN sensor.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.variable = pypck.lcn_defs.Var[config[CONF_SOURCE]] - self.unit = pypck.lcn_defs.VarUnit.parse(config[CONF_UNIT_OF_MEASUREMENT]) + self.variable = pypck.lcn_defs.Var[config[CONF_DOMAIN_DATA][CONF_SOURCE]] + self.unit = pypck.lcn_defs.VarUnit.parse( + config[CONF_DOMAIN_DATA][CONF_UNIT_OF_MEASUREMENT] + ) self._value = None @@ -59,6 +71,12 @@ class LcnVariableSensor(LcnEntity): if not self.device_connection.is_group: await self.device_connection.activate_status_request_handler(self.variable) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler(self.variable) + @property def state(self): """Return the state of the entity.""" @@ -84,14 +102,16 @@ class LcnVariableSensor(LcnEntity): class LcnLedLogicSensor(LcnEntity): """Representation of a LCN sensor for leds and logicops.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN sensor.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - if config[CONF_SOURCE] in LED_PORTS: - self.source = pypck.lcn_defs.LedPort[config[CONF_SOURCE]] + if config[CONF_DOMAIN_DATA][CONF_SOURCE] in LED_PORTS: + self.source = pypck.lcn_defs.LedPort[config[CONF_DOMAIN_DATA][CONF_SOURCE]] else: - self.source = pypck.lcn_defs.LogicOpPort[config[CONF_SOURCE]] + self.source = pypck.lcn_defs.LogicOpPort[ + config[CONF_DOMAIN_DATA][CONF_SOURCE] + ] self._value = None @@ -101,6 +121,12 @@ class LcnLedLogicSensor(LcnEntity): if not self.device_connection.is_group: await self.device_connection.activate_status_request_handler(self.source) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler(self.source) + @property def state(self): """Return the state of the entity.""" diff --git a/homeassistant/components/lcn/services.py b/homeassistant/components/lcn/services.py index d7d8acf4f29..c6c33270264 100644 --- a/homeassistant/components/lcn/services.py +++ b/homeassistant/components/lcn/services.py @@ -6,6 +6,7 @@ import voluptuous as vol from homeassistant.const import ( CONF_ADDRESS, CONF_BRIGHTNESS, + CONF_HOST, CONF_STATE, CONF_UNIT_OF_MEASUREMENT, TIME_SECONDS, @@ -13,7 +14,6 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv from .const import ( - CONF_CONNECTIONS, CONF_KEYS, CONF_LED, CONF_OUTPUT, @@ -28,7 +28,7 @@ from .const import ( CONF_TRANSITION, CONF_VALUE, CONF_VARIABLE, - DATA_LCN, + DOMAIN, LED_PORTS, LED_STATUS, OUTPUT_PORTS, @@ -41,7 +41,7 @@ from .const import ( VARIABLES, ) from .helpers import ( - get_connection, + get_device_connection, is_address, is_key_lock_states_string, is_relays_states_string, @@ -56,18 +56,20 @@ class LcnServiceCall: def __init__(self, hass): """Initialize service call.""" self.hass = hass - self.connections = hass.data[DATA_LCN][CONF_CONNECTIONS] def get_device_connection(self, service): - """Get device connection object.""" - addr, connection_id = service.data[CONF_ADDRESS] - addr = pypck.lcn_addr.LcnAddr(*addr) - if connection_id is None: - connection = self.connections[0] - else: - connection = get_connection(self.connections, connection_id) + """Get address connection object.""" + address, host_name = service.data[CONF_ADDRESS] - return connection.get_address_conn(addr) + for config_entry in self.hass.config_entries.async_entries(DOMAIN): + if config_entry.data[CONF_HOST] == host_name: + device_connection = get_device_connection( + self.hass, address, config_entry + ) + if device_connection is None: + raise ValueError("Wrong address.") + return device_connection + raise ValueError("Invalid host name.") async def async_call_service(self, service): """Execute service call.""" @@ -392,3 +394,20 @@ class Pck(LcnServiceCall): pck = service.data[CONF_PCK] device_connection = self.get_device_connection(service) await device_connection.pck(pck) + + +SERVICES = ( + ("output_abs", OutputAbs), + ("output_rel", OutputRel), + ("output_toggle", OutputToggle), + ("relays", Relays), + ("var_abs", VarAbs), + ("var_reset", VarReset), + ("var_rel", VarRel), + ("lock_regulator", LockRegulator), + ("led", Led), + ("send_keys", SendKeys), + ("lock_keys", LockKeys), + ("dyn_text", DynText), + ("pck", Pck), +) diff --git a/homeassistant/components/lcn/switch.py b/homeassistant/components/lcn/switch.py index 5fe624b04bf..1429bf67f7e 100644 --- a/homeassistant/components/lcn/switch.py +++ b/homeassistant/components/lcn/switch.py @@ -1,49 +1,49 @@ """Support for LCN switches.""" + import pypck -from homeassistant.components.switch import SwitchEntity -from homeassistant.const import CONF_ADDRESS +from homeassistant.components.switch import DOMAIN as DOMAIN_SWITCH, SwitchEntity +from homeassistant.const import CONF_ADDRESS, CONF_DOMAIN, CONF_ENTITIES from . import LcnEntity -from .const import CONF_CONNECTIONS, CONF_OUTPUT, DATA_LCN, OUTPUT_PORTS -from .helpers import get_connection +from .const import CONF_DOMAIN_DATA, CONF_OUTPUT, OUTPUT_PORTS +from .helpers import get_device_connection PARALLEL_UPDATES = 0 -async def async_setup_platform( - hass, hass_config, async_add_entities, discovery_info=None -): - """Set up the LCN switch platform.""" - if discovery_info is None: - return +def create_lcn_switch_entity(hass, entity_config, config_entry): + """Set up an entity for this domain.""" + device_connection = get_device_connection( + hass, tuple(entity_config[CONF_ADDRESS]), config_entry + ) - devices = [] - for config in discovery_info: - address, connection_id = config[CONF_ADDRESS] - addr = pypck.lcn_addr.LcnAddr(*address) - connections = hass.data[DATA_LCN][CONF_CONNECTIONS] - connection = get_connection(connections, connection_id) - address_connection = connection.get_address_conn(addr) + if entity_config[CONF_DOMAIN_DATA][CONF_OUTPUT] in OUTPUT_PORTS: + return LcnOutputSwitch(entity_config, config_entry.entry_id, device_connection) + # in RELAY_PORTS + return LcnRelaySwitch(entity_config, config_entry.entry_id, device_connection) - if config[CONF_OUTPUT] in OUTPUT_PORTS: - device = LcnOutputSwitch(config, address_connection) - else: # in RELAY_PORTS - device = LcnRelaySwitch(config, address_connection) - devices.append(device) +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up LCN switch entities from a config entry.""" - async_add_entities(devices) + entities = [] + + for entity_config in config_entry.data[CONF_ENTITIES]: + if entity_config[CONF_DOMAIN] == DOMAIN_SWITCH: + entities.append(create_lcn_switch_entity(hass, entity_config, config_entry)) + + async_add_entities(entities) class LcnOutputSwitch(LcnEntity, SwitchEntity): """Representation of a LCN switch for output ports.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN switch.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.output = pypck.lcn_defs.OutputPort[config[CONF_OUTPUT]] + self.output = pypck.lcn_defs.OutputPort[config[CONF_DOMAIN_DATA][CONF_OUTPUT]] self._is_on = None @@ -53,6 +53,12 @@ class LcnOutputSwitch(LcnEntity, SwitchEntity): if not self.device_connection.is_group: await self.device_connection.activate_status_request_handler(self.output) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler(self.output) + @property def is_on(self): """Return True if entity is on.""" @@ -87,11 +93,11 @@ class LcnOutputSwitch(LcnEntity, SwitchEntity): class LcnRelaySwitch(LcnEntity, SwitchEntity): """Representation of a LCN switch for relay ports.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN switch.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.output = pypck.lcn_defs.RelayPort[config[CONF_OUTPUT]] + self.output = pypck.lcn_defs.RelayPort[config[CONF_DOMAIN_DATA][CONF_OUTPUT]] self._is_on = None @@ -101,6 +107,12 @@ class LcnRelaySwitch(LcnEntity, SwitchEntity): if not self.device_connection.is_group: await self.device_connection.activate_status_request_handler(self.output) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler(self.output) + @property def is_on(self): """Return True if entity is on.""" diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 82425db675b..1721ef8c2b5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -863,6 +863,9 @@ pyowm==3.2.0 # homeassistant.components.onewire pyownet==0.10.0.post1 +# homeassistant.components.lcn +pypck==0.7.9 + # homeassistant.components.plaato pyplaato==0.0.15 diff --git a/tests/components/lcn/__init__.py b/tests/components/lcn/__init__.py new file mode 100644 index 00000000000..6ca398de93f --- /dev/null +++ b/tests/components/lcn/__init__.py @@ -0,0 +1 @@ +"""Tests for LCN.""" diff --git a/tests/components/lcn/test_config_flow.py b/tests/components/lcn/test_config_flow.py new file mode 100644 index 00000000000..325552f62d3 --- /dev/null +++ b/tests/components/lcn/test_config_flow.py @@ -0,0 +1,92 @@ +"""Tests for the LCN config flow.""" +from unittest.mock import patch + +from pypck.connection import PchkAuthenticationError, PchkLicenseError +import pytest + +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.lcn.const import CONF_DIM_MODE, CONF_SK_NUM_TRIES, DOMAIN +from homeassistant.const import ( + CONF_HOST, + CONF_IP_ADDRESS, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, +) + +from tests.common import MockConfigEntry + +IMPORT_DATA = { + CONF_HOST: "pchk", + CONF_IP_ADDRESS: "127.0.0.1", + CONF_PORT: 4114, + CONF_USERNAME: "lcn", + CONF_PASSWORD: "lcn", + CONF_SK_NUM_TRIES: 0, + CONF_DIM_MODE: "STEPS200", +} + + +async def test_step_import(hass): + """Test for import step.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch("pypck.connection.PchkConnectionManager.async_connect"), patch( + "homeassistant.components.lcn.async_setup", return_value=True + ), patch("homeassistant.components.lcn.async_setup_entry", return_value=True): + data = IMPORT_DATA.copy() + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=data + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "pchk" + assert result["data"] == IMPORT_DATA + + +async def test_step_import_existing_host(hass): + """Test for update of config_entry if imported host already exists.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + # Create config entry and add it to hass + mock_data = IMPORT_DATA.copy() + mock_data.update({CONF_SK_NUM_TRIES: 3, CONF_DIM_MODE: 50}) + mock_entry = MockConfigEntry(domain=DOMAIN, data=mock_data) + mock_entry.add_to_hass(hass) + # Inititalize a config flow with different data but same host address + with patch("pypck.connection.PchkConnectionManager.async_connect"): + imported_data = IMPORT_DATA.copy() + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=imported_data + ) + await hass.async_block_till_done() + + # Check if config entry was updated + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "existing_configuration_updated" + assert mock_entry.source == config_entries.SOURCE_IMPORT + assert mock_entry.data == IMPORT_DATA + + +@pytest.mark.parametrize( + "error,reason", + [ + (PchkAuthenticationError, "authentication_error"), + (PchkLicenseError, "license_error"), + (TimeoutError, "connection_timeout"), + ], +) +async def test_step_import_error(hass, error, reason): + """Test for authentication error is handled correctly.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch( + "pypck.connection.PchkConnectionManager.async_connect", side_effect=error + ): + data = IMPORT_DATA.copy() + data.update({CONF_HOST: "pchk"}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=data + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == reason From 48808dc2ada27fa915a19552108a3ce6ef86c00d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 15 Mar 2021 14:43:32 +0100 Subject: [PATCH 373/831] Upgrade vsure to 1.7.3 (#47946) --- homeassistant/components/verisure/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/verisure/manifest.json b/homeassistant/components/verisure/manifest.json index 744f7fb706c..05f07e926e0 100644 --- a/homeassistant/components/verisure/manifest.json +++ b/homeassistant/components/verisure/manifest.json @@ -2,6 +2,6 @@ "domain": "verisure", "name": "Verisure", "documentation": "https://www.home-assistant.io/integrations/verisure", - "requirements": ["vsure==1.7.2"], + "requirements": ["vsure==1.7.3"], "codeowners": ["@frenck"] } diff --git a/requirements_all.txt b/requirements_all.txt index 68ba25f4659..87cde66dd57 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2278,7 +2278,7 @@ volkszaehler==0.2.1 volvooncall==0.8.12 # homeassistant.components.verisure -vsure==1.7.2 +vsure==1.7.3 # homeassistant.components.vasttrafik vtjp==0.1.14 From 37c53e0a047f764828c57704863ca741d40af4df Mon Sep 17 00:00:00 2001 From: David McClosky Date: Mon, 15 Mar 2021 10:07:36 -0400 Subject: [PATCH 374/831] Sort supported features in vlc_telnet (#46800) --- .../components/vlc_telnet/media_player.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/vlc_telnet/media_player.py b/homeassistant/components/vlc_telnet/media_player.py index 68b3c373c7a..8f557b1e0a4 100644 --- a/homeassistant/components/vlc_telnet/media_player.py +++ b/homeassistant/components/vlc_telnet/media_player.py @@ -46,17 +46,17 @@ DEFAULT_PORT = 4212 MAX_VOLUME = 500 SUPPORT_VLC = ( - SUPPORT_PAUSE - | SUPPORT_SEEK - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_PREVIOUS_TRACK + SUPPORT_CLEAR_PLAYLIST | SUPPORT_NEXT_TRACK - | SUPPORT_PLAY_MEDIA - | SUPPORT_STOP - | SUPPORT_CLEAR_PLAYLIST + | SUPPORT_PAUSE | SUPPORT_PLAY + | SUPPORT_PLAY_MEDIA + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_SEEK | SUPPORT_SHUFFLE_SET + | SUPPORT_STOP + | SUPPORT_VOLUME_MUTE + | SUPPORT_VOLUME_SET ) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { From b9a26cf5391365d4dfdb6c22aee88c5d577eafc8 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 15 Mar 2021 15:08:45 +0100 Subject: [PATCH 375/831] Add zwave_js dev docs readme (#47621) --- homeassistant/components/zwave_js/README.md | 49 ++++++++++++++++++ .../docs/running_z_wave_js_server.png | Bin 0 -> 3786 bytes .../zwave_js/docs/z_wave_js_connection.png | Bin 0 -> 10602 bytes 3 files changed, 49 insertions(+) create mode 100644 homeassistant/components/zwave_js/README.md create mode 100644 homeassistant/components/zwave_js/docs/running_z_wave_js_server.png create mode 100644 homeassistant/components/zwave_js/docs/z_wave_js_connection.png diff --git a/homeassistant/components/zwave_js/README.md b/homeassistant/components/zwave_js/README.md new file mode 100644 index 00000000000..920fc4a6a0b --- /dev/null +++ b/homeassistant/components/zwave_js/README.md @@ -0,0 +1,49 @@ +# Z-Wave JS Architecture + +This document describes the architecture of Z-Wave JS in Home Assistant and how the integration is connected all the way to the Z-Wave USB stick controller. + +## Architecture + +### Connection diagram + +![alt text][connection_diagram] + +#### Z-Wave USB stick + +Communicates with devices via the Z-Wave radio and stores device pairing. + +#### Z-Wave JS + +Represents the USB stick serial protocol as devices. + +#### Z-Wave JS Server + +Forward the state of Z-Wave JS over a WebSocket connection. + +#### Z-Wave JS Server Python + +Consumes the WebSocket connection and makes the Z-Wave JS state available in Python. + +#### Z-Wave JS integration + +Represents Z-Wave devices in Home Assistant and allows control. + +#### Home Assistant + +Best home automation platform in the world. + +### Running Z-Wave JS Server + +![alt text][running_zwave_js_server] + +Z-Wave JS Server can be run as a standalone Node app. + +It can also run as part of Z-Wave JS 2 MQTT, which is also a standalone Node app. + +Both apps are available as Home Assistant add-ons. There are also Docker containers etc. + +[connection_diagram]: docs/z_wave_js_connection.png "Connection Diagram" +[//]: # (https://docs.google.com/drawings/d/10yrczSRwV4kjQwzDnCLGoAJkePaB0BMVb1sWZeeDO7U/edit?usp=sharing) + +[running_zwave_js_server]: docs/running_z_wave_js_server.png "Running Z-Wave JS Server" +[//]: # (https://docs.google.com/drawings/d/1YhSVNuss3fa1VFTKQLaACxXg7y6qo742n2oYpdLRs7E/edit?usp=sharing) diff --git a/homeassistant/components/zwave_js/docs/running_z_wave_js_server.png b/homeassistant/components/zwave_js/docs/running_z_wave_js_server.png new file mode 100644 index 0000000000000000000000000000000000000000..53b5bdd3f8f3dc4ae8e1216f011b46dc25d58e17 GIT binary patch literal 3786 zcmcInc{J4h_y2rmn86@3_O;2DHAcqXn8ITyj4g#0M1-0|Wc{dYBa-mgjRx5&%T$cg z*s?^%l0-_jY>_2;EctoP@2~Iq{{6jwyzlFt_dTz3U-$h!_nezRwl?SEk>CLUfX{+t zY6k!?;yz|^!S~NmU$tNRuAh9$!EE0n5J(st-aE0jk9)>ftQ}o)%3i`bI3%QGZYO7Y zUk`oJK7>GWp?G z%dJD-3!XMSe%X4_=X%V8+_2b`Yq##`6HL+ypEM0jzG07Nl`^gbMjVut7r+SW7#b_8 zsCSMo95+7|awkbpShT+Dlc>04Mp2d1#mgjX2NO%1V2Sdg9GjeU?%w{UZ~V9~>fq}EqY9sgdv(X&qOWI#7rgB4TYeRA?U0*4Tp*u>w~nDDx*=JN9XbqXB^!+&KPpfKHTQ<{+p+afyO1NKr+ATIat#x&tL(u)I9408EWIrGfc8k z?TQRrtT5^k#9#S4rnzq%b+H&Ge9kCiXS``jDa20!j0m^@ni+IeCC2w+AVhE_+`DGY z2^7{hxoB(qtil&aX1+pbmvFl6E8mh@Zc$xLLw7j|gt_V5LT%6R!)sJx6B7v+fZPm; zJ>bj&KRMr1>yr~UCx)#Yf6_V2YdMTaSP-~b5^Q15r(w{S2$wKjb_?2)#oAX31o$X| zV^e=%tE=6zI(-GLLx*F+`B~u{e(BULmYb+*M%$+?=SuNgV<9^tNcoA8fLII?=wGBA54)Z3qt=LU z@V6J|n^Ga5zfVfAQR+myUB1L&N@Ak4t_4xI?hS;_E)FkF*R%eS5GX3KsF?`+P4g-p+k`|OwMZ<;(sX4geeMR_vxs0(cI+*iQx+l+ zMzSLdqL1qJC|qwHlgh6be8a|wP!GgSGH6Coe56_7Mg1O?rv`6p?rlOTlJE*6Qz9qY zUcSW-!{%F3$G$5~JEX^}1UXbUSRz>7T)#L-E_YlB2~cPkTHSegfNU~w4b&INRYt}4 zq`iv05B7Lltep}g=lQxC^;yA~O?jMC)9L1S%Y8uBfZjU{B(mTaU!Bo*)%!4iPjNMO zi0dRhR?iC;b7d#nVXN+?PuOoE_IdGjpBT{M9aYcUijAMK`?nNTtWOub(Oc!F!WO1) zMn+H45jZOY`ssO~Srh_T6>?RUaoUJjav+n6Dr}OK6|Y9_NeTSrv3*YyE9DIrGZwN5 zw;;UzI?)Kb(w8QOZ&j1<#TD1%5Nt2$Dus7U8=klM&pZ5l^l;{J#48J2QVNffYK2L}TcE~_-uCqTUq z#rmmlKaCs5pE`xG<9a176njKg`TU|W$Dn=bIau?hVoh4NYTK3n7o{()I$mC|bb>PA ziDQ_|koiRv093Vn-IFNiBks@mj0em+R4<**YX7+Tok{M=3n0*I(nK| z+EF4VQje}}5xJ^8E(hK2Vs)5xR|loOy`IXkE_qF9K2b1CRwZc8F2U)(M z&#yRugEi-(M(=Mt48!G4%x9*zCgimpp(}v9MUakEXuRiHz##2hBM?zi-Mf~1H1jsY z$h@J5U!*C!Y)bSiEIVL)oSsiumMh`&$(Gs#TzVdeM^ITABA<)h^Rw2VIeiQP4X$`8gn%B}V(t%+Y!q{Ar$c#CpMzx32wqDSnkjcHX~ z(h`L-XWm(N0e)Mz<6*Aubw^`I(#$+DL7XG}O4TRi&kk9#hoEGvXpztXduFH1?BV?h z^6B=)VmO}Vl&h6PjNVOXG$Af@`=u*jA6x!5L75ReVZJ4A5b|mg$}2%X49g-r`Y50{Mqs6o+s_GpYJ2?ck-(A2VApB z_$jM#R=q^%wnU0fG_8yomr_eq>cMNRcybPhC;c%$p5rjYqMpPLwlh-0dA2P;f4!Rm zBkw>IWql|x9@3YX6(pHV`fIEzf%$G9&k8;9|JhVA#_KUl$0qX_GP#}yAq$a$8jErm zq=T2LDs!|ZnS^2Y@^om)Xousn36-s%35?M`K8@(hXaT!5`Vx4Rp(9x@AYZJW+!s^g z8X%Q0aMxIh$WFss@)m4r)=WT}S_KZ=!={)0N4NLzG~^$H1HmHfl&C3^c(t*Cq5Dwj zK`1|U`B5l#yRV-+S|4+B%m)EreI%lI?`UaI?nD3WFB$m zHDZv~D9k@>Cu#N3r;3Vcx7|>i3>}kTFKSaJ ztkev0mV~50E+VkXi3MVW{s(x_5D<3SSbCxBqr^KIG7SUU{6QpGsocD+#OyJ&~EgmqEXgP6xS!EQ*32pHkq)hu7C$E;;kq1Y|!rfA=JCI>7Uvp zb;iHe6R8^8Bvv*htFja@Dr|RjiGBIxQgr#m{)l!}=`boU6PH~>H}ODgX$M@8pDilf za0ny9MUB5$F&{e4j8s58aU*oih7dQxRW@i^-R;*h&ZV5Av$c-9G~5xduoa#HI<_f; z^(S%;iE4QQ(E~VhV5!ybIl|ziv$e09J5X`hsERw&1^+TknjJ{Z?P=G_tM$5$FMLe4 zzp<%VDu?ItSnL+xa4oG5p1mOlFQ*E^m`n!q_fTtY#(-(>EZo3Ynb?Yl_;OT{(9mCA z6jwBVG(VgsE%M!K2g-HINbA+*FX-}YhbY(HH%pFVQ&*|HrIwL0P+ChnJc2zS19Li!{n4tQb)zSPaiBZX~~_8m4Qmg1@L*ho*{Q1 ziCZ;t;^g!lBO3Q;s1KBi(K_zc!wKX)sfg&s@D;gdx2Q=ktagWtiW~{BD)iss^kw-q zJ+>ITUq4T~h*~Lh+8dlPr1@U;_h1U_euu_2zn*Du$Ke;Bd`JTwJA2_0D`)T0>vnh} zmViJ^YWC~*@3K}A;@8XqeCdO=B-IPrPFATU2JL+=ab-{Lr;RF>S*_r)1;HJz8l)qx z{|KK}HY2|2DaA(x_S^rgoZK)}GCkk3FkW2mVz|ewtQe2WeqOSpOn0B!i*Sa0(Ew1K^-A5ynN+gMfJ+EXYBY zA_762DF94~9}6*IBmfjb3J}kM@(@6UbJ3mx7$VTNpXPMgYa{$4Y`xoa_r=L}MSz6` zSSss#Iom|^m}#nHDtgui8|{VWisn!eB+<@JED0n3d$9rlC?9F5J*Uc0mJ0j#+QQ7* K^y!IDIR z!^g(KX&YIb*f@?(%kLQb#Ky_1XJ#vjP-0=@q@{-kp^^xQh{x8BUJuTZQBV<+kVPhD zGBB|?cm|3|%e(nUU}9lgIeT;R3O4n92#rn4MAr{3?ePc*GqSMRy84>fy41fNmr+pR z6A}{;l~hpGOv$f&mRlB|S@JYF*E=-EGZ@+P;j@OG$=j(lMRlFDqL(U~`W6lza!Tqi z+uv8U_8VF{3@(1_nplAm5H|Eq=2y3%8{hgx#ATG!j;$V+H^0j%Z!D?r^p8rY>l}&A zC{)!pY#mxq-@#J;bLRp9dRqE`dx-T91suY~rp@#Z=pCo?>vwsa^y9*b6o>C`C9`Uu zdNH@H^81HsE30>##mE*Jri_qp0sw3lYKrm(AuC5S0{DIWgn;HvtYq1zK9=3t4DSU1YJJ_b^bd{qf6ZEMx>$|+h?0m>r? z6_*2&Kp-u}V(W-*AN#oy z=FZ{ewz8~CyijQ_;;n+OCHFA@>fj#HcLnIHoxIy3f$HQ4d*qCl;tJ=`vHm2~qKa`; z7PWnLM%IlX9y?=?qk3!7@ypVN3E+Zfra~}Ryj9`*65=vx|0?U;0{y0Nj&kt1XP+Ld zly-pW^xz%1$|Uw%CNsj{%AL`42$L!o*(~}C2c;F$&-pQm9{?BN_65N?1gR$YWp!S0 z-7}yXOcVafScgQv$Fs=#l|wUXXsu~TfQdtD62moajqLx5!FRMP=u`Zt;JZA*u&wDV z!M4A{Xk!#Ldz)+_(FU|j7vxh<%tC@&Jg-OgxSn2U%wq~W+l%3C7a>Nrty1pRoanJb6iDwyLXj3;l;MGJGb$|k3EYeC0_cUs(p zzsRgkM);DyG*ggx1FlLK?_6p?GkKqAeZ|{jMHOL^HP4VJ#|a}p2#Ps>q{cN%&Nz8t2yrR0dpmU+1rM1sxEo;!Osx7FXGgvl^Gh@XGhoN|__?J!R z)SDJ$L*lP$7fgljnO#~F@s}n0+MBAe!%dd{ab12j13~>SiV(Ip@FzD^0Fkzn9jN4v z5clW8OBTA?d_z7APQlBo@D@Mp385_F6U*Wi!(0&oJQ9pk zF<8`rfR|$l%`~Hl-S*!7L`Vq@ev=>&mAb{7cb+zw10@$RA?!LCXJ#b4sc&PH#6n@4 zG*5RYI&ppi=2U3KgNIexA8^EO_vqpydRZ8bfe=mu$==6>BEhxKnY)R^SB`EbI*-Wa z80W>_fEkC9T$h_E!R!q{h2{Ve-(a_u-h9{D~>F~^s9(9cV=9n*k%7}mm{9@;be z(dKoRRpjl`-1h=Sf8V-(-!yXOsrK!b5w1X=i*0w&b7iq8`a* zO-d+;Uphz>lq9fhrmA3Sma8!5vhjQCfRwAFYOy%1`HrD}cI`|p?HMiMb1;)1LJ^iY z3M7(%Ghr%jBNP9r^MQ6iE!wU9h1ZEJ~0+td)3T8Wgv#08} zrvO&7gnf;0xNgYnio`leq5CvTw-?LD2=Eq&8JWJfMr4`159SE`5ek9zEL<;3}Q@4#$z*>_1=qnIHXGf}}S9fY4=wSF0e=mj%zm1i! zKDGqmjayJMxH|c&3x7v}8(2v=R%#u4X1mWJK5pm&h&uzFaCQK*Uyp0S#kGkJ-70i! z()DHP2J#cliG&3xde#j-Owdgh!m=P z*|6j$EV9M_stbQWhcaH95q-kIjfMS;)R53+zNPr~U5B0u<3{iL%?1Wb_$l*54CarC zlVzNMaNtxSxxR2Gf~_b2O@-4m<97}non5=|dcB*UG7@cScCl1 z50$qlO*A0_u8QV(GZU@Ku@Hsx2CIs!Dk|maTD8Lh$28hmi*;%2xcjxr)mXPYtxJsf z43r3en@vIAsPL7B)N@uH)wPqnLQUk_M6Za2g(B-rm4d%fLpHc0w*c5$C;F750J4&1 z@v7W|lM|(YEn+IMh`s4(y-%4hLvzZ9l3cvu@yYuu{5eiQL(7!9mOi|yO&_#!29z|f z550Yf;3y$dJ(N{P9ltP)BC8=qEu}4KLtpv@gsslK&D(-^vV??R7wl#T*s_lnyW?lOZrX2+ilF9!12Zq-b9=Ee-(A;Tx7H&$}lW@;DU_<1j&cdoCO>ZFD)g(NlFoD1_4 z-8(_ zz(c4nhANA=BXuLqh*PYUOBgBCQ-ih?vDaBbJ9Kg^EV=87OPs{~pnt zuMA&lCJ{_FbrGMyq4BCIA0e#yU+Hg{@UuNK2GRa&!ZY?@iU{W#X_>9*h3XGVBwS4n zMn7#>Zq~Ef#^!H#5p^X;NW*fO-bj(8UrtPoH0%Zp6S1v9#Iu}LWsdpjw+@tzbnFCz zM6cq{@oYTUHcgoxI-}!KTnoamQZ`0bX%ywZI&Vx(n=W4{zn?9ec^?!SBnL6O)~d*x zuV>1{;Ljup=g=Ba^+g)Y>8d*O$y@S~m2_68isfBGzm=CtT znsk^pr45^D$=KgRUK0}C)`9EV4El>b^Cy6NAXCowD?4$S}={4|l za_luLb!LYuq)hy4dqFvXIPbS!==E)nWG4C$KU+RrbWq;dEq8UrfrhD2CUZ$AN=g#6MXj7^-1i*?`0LAE>TnWqoUI9UgZRHDE7BO}`2{xw03#OA%3{ zHmcr+i`0vr6w-(5hqT;u-uI*s z?(;2H-J!)p+86%Xr8@$Xi9@;kBszrtssX%i(l1^24mzHJBH)1N&0r)GJF(mKL-OF4 zk8s%+%f!m19THK;r6Zp>5J+;c@hMzv^SCoL4?NAfv|%}fk^6L*eY`+e zGZQenS8%L&ThiX@ANoQXSc0l8RaJ-@MozIjFZ8jr(%@;}{g(860YQv19x){gzp70} z9~>nQih)3}{|85`4W>-X>;`m-aN@|lC;YOlpzw8|(FZ0sKANHkE#333c*(t;icipF z2hWm{yxPD6dpDtPt5F{Np~WGQP47PT)U728Ps+<39M(&?vZoAQpFA&>!!`$TxGU@r zo1s-X;)rp`Nhw-+7o&V1xl)<;%t+l?I%fKNm5vono(iFGFR%=A0QF|ZF96p1507+K zrLIAaVso4QsuW8Z-zVb1MMu;37eT0X*b6I;aP#<#SPYxX=c|>dUI@gOMt5LSo`jX&690wFAJ*Q{@^)6 z-fI7|hT^cq1kmW*R2S?%^bEZGX}4f0IEYhcSc7lb?31r}*X9%1ZNC}gsJpV7#~%v; z?U&Kl&&tCu0=`~`qH~-#B9eY4KUe>YyX*{}`MFyF6UGtv-Ne391OAHsd-?L}L{YN1 zW*`AHt|Tm`zL@t#$|~KhBug0)N6D#rAl#96vj)q;ZMw{2^EK-S@M#^(a$KYLr!K)N zy?{MH!*HJtFGtmnXXZf+9dnuXkq}s*ntrKv#7mH7;4q(6jgkh{_&x_i4$=e1B$SPR zmiPt}#?}V*S-9pQoR+rEoZ~fL^7@?H#kVPa_s9rZX#f~4k|p)mIRJ_8Q)wy%AJLo} z#)VLFUe3!H^u87c-9Rq_sEW5V-*F9Xrkak3dH3=AEO6tW_MdPX|1m z;;r!ukmpyyntQxLsc z7OKQYL`GWziTw)&{tJEriC#caw%21GH>w` zp>IDQ_sjzBHH<7HE`iQqIe$x4+Kf3Cz zqK2R#o{XI2;%A^L~$_v}n3vi3XIcFk)K-UlbS6nN1l_XU z*Elp_U$!B+s>ctvPZ9XZGhF7oBV$%d41JB2jl`ev{5$dAE0AG+dV1LTY!Dp& zbP4n(h3AFJh;nkOeg*9Pp-63(IM=VflIPCMQbJW{vL_(P;Sp8K|X#pZsCaK>U;M-2cF9 zrqKX|`#&M--vD-C48iK%ezyua{ttNjZ{}cH4|f@Mj4kB@u?qq*4aef-q(9Lq`TfAx zpyu*sex>C=VWL}&by?=CoGvCc3i?m=CK!>YjA(O(>F^Yirq}P_(tl4{bXeXZD4|aK z{3YZwJpw=QUO{TKy0yOo-;&{$%RY@n*JH~O8~bjhoGfIO9+6DXc=ZX`(l(@l<_q8j zZq4~o?uA}rBcxM+*&g1;070`}s2@k%IPA9=+~YCKf<4e(<1ZETlP87U3P7@nj5958 zpiZQWFQP*>?iF5(z2WP-iD0}zU*LxL{WlgCw*Kx*h}k?|{>|PVjO7h-F@9-6B&idU zv;uE($L4=EW!GOuB4JLA>E8C8t6O9D5Y|jT&>{7ScbT@8Rw3a_)Uw>Ne911%6J|N zgPosO)Oli)0uJm@y+?#fRI~(~d4e&L=a1g!zeIBG)F`ybLPyc{;}`W9$Hf}xwVCx7 z2%*`vGF_6&4wI{|WmMVKHfS{t9B@>i8HF9e%Tgj0t{S})deTLS5OjdNCxcK%E zW63y`Xi3UuG2NsvayqeW>Xxc2^i7%c9t2%c1aZUADJ0{sQm9Sj(P&=ccI}Dd=YpB+ znA&?Vr4P>3RJnpDw1Mh1zg5?sIRez>JhaZpKCon%uBVEw0Ub4_TMKRs00Xa*z2w9$ zgr3S$TM5r2#8qkOU$1hw6exPRbdPi^hXna~>6mKa8MfhYtW^7brJOIUE9m7mV=`L5 zh;r3ic&hvMlj0(%LlU&P8wA$9KC986^UeNh$$TgB;5IRl_wueHYAF3TW-5YtMl9$! z9|{1(+fqLL7#?JV1H_C5I$-=$nEnUUZmH%M146K7F#k1F^78nvP9OfXuh-{~?xXzR zX}ShUytR)Fc@7MC+Oq2w%h$zvLy24UDT03FpLW_RDE5{NZV3pZHesQs>D2FR3l?PtFD-^%_L=?`WDs9K$aOOH>pt(9zO2O2 zFpxfK>omW9rO5coo`-qf&##u=30<|CTa61N*ahG{O#uU_KZ^ur(!DjJ>^#KJ@ z&|I9N1gg()v3fh74BkAH zTTFy(Bglkv#}Hx6%3E7;Y>2*%y^BsAHNhx<=fmNS5XCva-r`V;NGQ?=C`*Ih44ds3 z7)5^uyUPAlIbQwpHtK2gOrUTt}%1pkkCX^-D8KaBY;Hw{p^9&U+{_n zQdv#Y_-W}4laYBfhIHB$XWbZT+*N(!k#3oUc8drCNO1I%^FnG-bvB2NA4b6KQ0Qg} zhfDSZf5V*!0JoQEFg|jR*N+y{=ExAum~d@3Mye-0G8s_TCl-=dSd;$*OqGi=TSL6} z?jz>%!iW|-CuAF8^eA@b6fBUztn+=YrpY-&i5hxiRkcLXMCn8z5p;P(o&Cj4Pn|az zCbBHg1BDYm9y;T%)d!7b*Doj5J@* zK}VCQra#Y(6d{T&A7j5o0LVy2EdGYbf8+Z!y;N0r^o6Ny#gn8)(~|f{3Wagwk@=se zE!m6B&tu)6snY?mm;|yn;n;{C`vTDp6bn-alCF=>?*QZh7$rPB;sbubTr|^R$CjdS z7IP8eM(x8x4Lt)8@6&QS!ud&;QE3x-1*m@rf|PdelnZ@973ZjGinnb!koO_5qPS1= zY-IHp7q$vxpGi$+voZ#Xb3j-`@%XycC|?mGk)I<;_Lii~LdG`tibp4z z=E_9MGIB)H60k5&a@G|dCJ5p;Nu3Ujty~yVXH-l3VAI)^l6C&V%_KZ!l*L{%5tbBU z*5{0t^oHXlu~hu*p=o}4WxK|0EKW*RWN=lY_!^{<^hRoNSvzel&e&+zE0d$pLT%iywL z-pBI@06m{X9219>(NvBB#t#IC_ws;~wJjhwM?OQ`DQa=KME-FX5QY|(&WA1X-A%hI zY^jY09Cnbij(sw3M)fdW0PeIj6prB%oldOYX2IM(B#6co%=mapxu+wj!`#uudP|e> zjk9ubbq+P-Z+&QlvoHzXsyk;uYF>Ro9VgQIh;2{$Ayi5WVx8sPS?IA0&7zAiNq3R_ zsfp|x@dz}ZAz4dXWW<4=-tZCxG*z3PnuU2@-zCC8!$#m@8>HY-Dvf5SLgCCIFkI39 zmgFHe`A?U`ZTMLJip4hh52S)P)#+^0o-CI1!W@V62up04{)lQ4cj{Gk=6LY2s3 zajZBg@r#C&U+?dPRmP%mmb6#vpC0(Mv^wq{wW>+^A?PH~X|y^ju$qdekiyH3EaP%Z z!~PX71o(#cd}frLUKil*4#JoDu-Z81kYZl<1KEJkU+{F@MLvKE=R&I!Hl4btV{4BE zGG_bP^8gN39!ZSey_Ybz3RuY53W6={G2s%WIp&`A4>^FFr)*j|X#m)m{CN1s0RDgM&O`|*XP=N>jEeU@TX)LaPX8NWHpGs zJ(_O@#-<|^Ss!sjy4*>Dfe-nng;d^n$zc$O_k;}z2$_;NSqlP`)^cU+giNO-95dhr z_+S(m+3cSH`BU@a4cxT%cE0ROYyv3$V?X`xfZ9RR_D^d~i2fT)|Gn{Uwr|vMeQZk_ zKcZpA73+hug$GK4gFqOScJkXSoFNTiUmY=%YQB4{PSn5I`tg_~VMn&36QY zEp*wUt0FZ{bP&**{d_MexCin|BylI47v2ym;2-7Dx02}b5j32Pz&L)~5rk>n01w`( zCkY(Jw+N(~gQ4ui4({x{kLoB=h0}$>Q$B)vzwx}^lCLkuBb>6#Y@;x8TBgs?*qicF zxQgRFui9axPj;0}aB_gmRb|0$fMzKFW*?obr9UG=KyaipoKocDnur@8==oSXJMHi7`y=^K`QC93d4Ahh<)9Se{_lnwZFz)X|NeNc0ADwl6tbBsgp&Ws5 zL0{0wxEp#e3vp2_x;y5VSdc`=jdy7^&*ym}d?8?) z0akNFJSI86(}z;Y2Ny7c6Vl(eCLcz3;sfxg#=}a$`z<|FdTd!AIr1TYpBEe$f;m~} zAqPF1{J}oB6q10jLojR=aJb8Aa8Gy&U@eG4n?5v%ds&i9&1?~V;d=!xGKciolI;oM zGES#5K*I{lXntZRY80Tyl?tt&adzh7h4q$~X$KcS=pk$kh-Ph6)jFxHlJ5pGb)wwkhcxqwZ+ z%_uC^DanB)aRk9|u8G0IzQwgd? zyfT1elc_3TWa^+;{@dxz@HCwv5C8<=Wk1HdA9S^O7ZOZFZu5W1c4O$@euD1yq<<;+ zZvpXd4E>DL$Z;4+Y#D!=++PFidJyA4!IzH}Pag?MeEvQ7y-CxNcx2Jb<2MBxo5ch1 zN+sPr^GxdqT@Ug1I-HL|7}PVtWeLj4r->jFa}t-m(2Zjm&Ebyf8`vI5SZO`J4YtRJ z<|uIr!}6+rC7*vl52m6^@xw;!40TbS?YMh4esoL6Y{U8ceyMi|LNd8S z&7b`OMLhOXQ8t>eIE5L?*%wD59i$1bdSkWC<->V;D){1FvXbIFwo}3Kx!{aYM;|Mi zgV!)1yLo^oqw>)~whcON3L&UZZ^#38Xsk)LTSf7!mI^IH&2YeIQjJ3t-ZP*$Pn$PB zJgedAYh-?XJ?22Hx(`F7q%iV?oN%@>HVz?{$`tV}y`ZY#bhxP|9HET2)rGq-Z@M=a zNU@i%sot@c^$P5VX#aa%!Hl`^9DL)j>t%$6^_4%#yrb;5xv+_9i>^lmDKXdBbY`}> zvAuw(_XxMMx@Fz}y9U{~!>S5$C_mmQh`3i^K&Pb^_>R^1iSikfew43o*e z5h8LkLYC#injs`ur@$jD6IHJ z7lSsnkKW}OCW4D$crfO(k%$GUgnB4n-cvOS9dw9oNf>OCo(#vWLRX|qlQSEsQFXR8 zm&m+d7_mS@4S4w_4tL!IY(G>&#?BxYW0f=DQL|lLa~SWIeDTd^OP5Go)WVWSr^I)0 zW^v*Nfq_oNIB3+B)~zq*3y9YeY+l~>c8!O&A{^W*^fK{lFtd$RTtlX zIO`OyNXZ+!-2WytLo$2T4fg#B)h%?DZ*3FPPuzhiR^Q!u1brEMuTpH!v_HH6kZZE0 zR9qR}2q?E7Yvc+KE`7V!jtrLa>6_B>WAgF?Twy*0W2zfJT`qeJ`cu@#%io)S-uMfk zh{%A;YyZPGH}?dX8vtMnNG!7#JmG_3Us(=;Hc literal 0 HcmV?d00001 From 5d5a110a20c96989a05497bfa9bd531e03b03276 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 15 Mar 2021 15:11:41 +0100 Subject: [PATCH 376/831] None optional hass typing in base entity and notify (#47528) --- .../components/automation/__init__.py | 4 ++-- homeassistant/components/bond/entity.py | 1 - .../components/climacell/config_flow.py | 1 - homeassistant/components/group/__init__.py | 3 --- homeassistant/components/group/cover.py | 1 - homeassistant/components/group/light.py | 1 - .../components/huawei_lte/__init__.py | 1 - homeassistant/components/hyperion/light.py | 1 - homeassistant/components/hyperion/switch.py | 1 - homeassistant/components/notify/__init__.py | 16 ++++++++------- homeassistant/components/number/__init__.py | 1 - homeassistant/components/remote/__init__.py | 3 --- homeassistant/components/scene/__init__.py | 1 - homeassistant/components/switch/light.py | 2 -- homeassistant/components/zwave_js/climate.py | 1 - .../components/zwave_js/config_flow.py | 4 ---- homeassistant/components/zwave_js/entity.py | 2 -- homeassistant/helpers/config_entry_flow.py | 3 --- .../helpers/config_entry_oauth2_flow.py | 2 -- homeassistant/helpers/entity.py | 20 ++++++------------- homeassistant/helpers/restore_state.py | 4 +--- homeassistant/helpers/template.py | 2 -- 22 files changed, 18 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 862a664976b..4555a336cc3 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -517,14 +517,14 @@ class AutomationEntity(ToggleEntity, RestoreEntity): if self._trigger_variables: try: variables = self._trigger_variables.async_render( - cast(HomeAssistant, self.hass), None, limited=True + self.hass, None, limited=True ) except template.TemplateError as err: self._logger.error("Error rendering trigger variables: %s", err) return None return await async_initialize_triggers( - cast(HomeAssistant, self.hass), + self.hass, self._trigger_config, self.async_trigger, DOMAIN, diff --git a/homeassistant/components/bond/entity.py b/homeassistant/components/bond/entity.py index cd120d79d56..b56c87f692f 100644 --- a/homeassistant/components/bond/entity.py +++ b/homeassistant/components/bond/entity.py @@ -158,7 +158,6 @@ class BondEntity(Entity): await super().async_added_to_hass() self._update_lock = Lock() self._bpup_subs.subscribe(self._device_id, self._async_bpup_callback) - assert self.hass is not None self.async_on_remove( async_track_time_interval( self.hass, self._async_update_if_bpup_not_alive, _FALLBACK_SCAN_INTERVAL diff --git a/homeassistant/components/climacell/config_flow.py b/homeassistant/components/climacell/config_flow.py index 09e02f3f559..7048e4e5c2a 100644 --- a/homeassistant/components/climacell/config_flow.py +++ b/homeassistant/components/climacell/config_flow.py @@ -110,7 +110,6 @@ class ClimaCellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self, user_input: Dict[str, Any] = None ) -> Dict[str, Any]: """Handle the initial step.""" - assert self.hass errors = {} if user_input is not None: await self.async_set_unique_id( diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index f185601ce87..1ce0b3e71d7 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -395,7 +395,6 @@ class GroupEntity(Entity): async def async_added_to_hass(self) -> None: """Register listeners.""" - assert self.hass is not None async def _update_at_start(_): await self.async_update() @@ -405,8 +404,6 @@ class GroupEntity(Entity): async def async_defer_or_update_ha_state(self) -> None: """Only update once at start.""" - assert self.hass is not None - if self.hass.state != CoreState.running: return diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index da842ee9f00..c19e81778a0 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -155,7 +155,6 @@ class CoverGroup(GroupEntity, CoverEntity): await self.async_update_supported_features( entity_id, new_state, update_state=False ) - assert self.hass is not None self.async_on_remove( async_track_state_change_event( self.hass, self._entities, self._update_supported_features_event diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index 482bdbafdab..cebc697efae 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -103,7 +103,6 @@ class LightGroup(GroupEntity, light.LightEntity): self.async_set_context(event.context) await self.async_defer_or_update_ha_state() - assert self.hass self.async_on_remove( async_track_state_change_event( self.hass, self._entity_ids, async_state_changed_listener diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index 341f9c0a118..2d33d1a1822 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -636,7 +636,6 @@ class HuaweiLteBaseEntity(Entity): async def async_added_to_hass(self) -> None: """Connect to update signals.""" - assert self.hass is not None self._unsub_handlers.append( async_dispatcher_connect(self.hass, UPDATE_SIGNAL, self._async_maybe_update) ) diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index 838b69cd52b..89f70c8f24d 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -397,7 +397,6 @@ class HyperionBaseLight(LightEntity): async def async_added_to_hass(self) -> None: """Register callbacks when entity added to hass.""" - assert self.hass self.async_on_remove( async_dispatcher_connect( self.hass, diff --git a/homeassistant/components/hyperion/switch.py b/homeassistant/components/hyperion/switch.py index e2da80e5093..f6317a8f396 100644 --- a/homeassistant/components/hyperion/switch.py +++ b/homeassistant/components/hyperion/switch.py @@ -193,7 +193,6 @@ class HyperionComponentSwitch(SwitchEntity): async def async_added_to_hass(self) -> None: """Register callbacks when entity added to hass.""" - assert self.hass self.async_on_remove( async_dispatcher_connect( self.hass, diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index 7be66dc3c59..5e1493a68eb 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -2,7 +2,7 @@ import asyncio from functools import partial import logging -from typing import Any, Dict, Optional, cast +from typing import Any, Dict, cast import voluptuous as vol @@ -114,7 +114,11 @@ def _async_integration_has_notify_services( class BaseNotificationService: """An abstract class for notification services.""" - hass: Optional[HomeAssistantType] = None + # While not purely typed, it makes typehinting more useful for us + # and removes the need for constant None checks or asserts. + # Ignore types: https://github.com/PyCQA/pylint/issues/3167 + hass: HomeAssistantType = None # type: ignore + # Name => target registered_targets: Dict[str, str] @@ -130,7 +134,9 @@ class BaseNotificationService: kwargs can contain ATTR_TITLE to specify a title. """ - await self.hass.async_add_executor_job(partial(self.send_message, message, **kwargs)) # type: ignore + await self.hass.async_add_executor_job( + partial(self.send_message, message, **kwargs) + ) async def _async_notify_message_service(self, service: ServiceCall) -> None: """Handle sending notification message service calls.""" @@ -175,8 +181,6 @@ class BaseNotificationService: async def async_register_services(self) -> None: """Create or update the notify services.""" - assert self.hass - if hasattr(self, "targets"): stale_targets = set(self.registered_targets) @@ -232,8 +236,6 @@ class BaseNotificationService: async def async_unregister_services(self) -> None: """Unregister the notify services.""" - assert self.hass - if self.registered_targets: remove_targets = set(self.registered_targets) for remove_target_name in remove_targets: diff --git a/homeassistant/components/number/__init__.py b/homeassistant/components/number/__init__.py index 31a0bcd7762..933b07e4f58 100644 --- a/homeassistant/components/number/__init__.py +++ b/homeassistant/components/number/__init__.py @@ -110,5 +110,4 @@ class NumberEntity(Entity): async def async_set_value(self, value: float) -> None: """Set new value.""" - assert self.hass is not None await self.hass.async_add_executor_job(self.set_value, value) diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index 720f7c5ff16..12b81402d61 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -173,7 +173,6 @@ class RemoteEntity(ToggleEntity): async def async_send_command(self, command: Iterable[str], **kwargs: Any) -> None: """Send commands to a device.""" - assert self.hass is not None await self.hass.async_add_executor_job( ft.partial(self.send_command, command, **kwargs) ) @@ -184,7 +183,6 @@ class RemoteEntity(ToggleEntity): async def async_learn_command(self, **kwargs: Any) -> None: """Learn a command from a device.""" - assert self.hass is not None await self.hass.async_add_executor_job(ft.partial(self.learn_command, **kwargs)) def delete_command(self, **kwargs: Any) -> None: @@ -193,7 +191,6 @@ class RemoteEntity(ToggleEntity): async def async_delete_command(self, **kwargs: Any) -> None: """Delete commands from the database.""" - assert self.hass is not None await self.hass.async_add_executor_job( ft.partial(self.delete_command, **kwargs) ) diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index 245d21770ee..4bc63d585be 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -104,7 +104,6 @@ class Scene(Entity): async def async_activate(self, **kwargs: Any) -> None: """Activate scene. Try to get entities into requested state.""" - assert self.hass task = self.hass.async_add_job(ft.partial(self.activate, **kwargs)) if task: await task diff --git a/homeassistant/components/switch/light.py b/homeassistant/components/switch/light.py index 2650bd61bfb..fc8638162e7 100644 --- a/homeassistant/components/switch/light.py +++ b/homeassistant/components/switch/light.py @@ -120,13 +120,11 @@ class LightSwitch(LightEntity): async def async_added_to_hass(self) -> None: """Register callbacks.""" - assert self.hass is not None self._switch_state = self.hass.states.get(self._switch_entity_id) @callback def async_state_changed_listener(*_: Any) -> None: """Handle child updates.""" - assert self.hass is not None self._switch_state = self.hass.states.get(self._switch_entity_id) self.async_write_ha_state() diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 9276ec2ed0b..3f6b26f536f 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -373,7 +373,6 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" - assert self.hass hvac_mode: Optional[str] = kwargs.get(ATTR_HVAC_MODE) if hvac_mode is not None: diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index 4929f7e7869..8ba2909cf20 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -89,7 +89,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self, user_input: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Handle the initial step.""" - assert self.hass # typing if is_hassio(self.hass): # type: ignore # no-untyped-call return await self.async_step_on_supervisor() @@ -266,7 +265,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self, user_input: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Start Z-Wave JS add-on.""" - assert self.hass if not self.start_task: self.start_task = self.hass.async_create_task(self._async_start_addon()) return self.async_show_progress( @@ -289,7 +287,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _async_start_addon(self) -> None: """Start the Z-Wave JS add-on.""" - assert self.hass addon_manager: AddonManager = get_addon_manager(self.hass) try: await addon_manager.async_schedule_start_addon() @@ -327,7 +324,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): Get add-on discovery info and server version info. Set unique id and abort if already configured. """ - assert self.hass if not self.ws_address: discovery_info = await self._async_get_addon_discovery_info() self.ws_address = f"ws://{discovery_info['host']}:{discovery_info['port']}" diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 7620323d940..34b5940bfea 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -46,7 +46,6 @@ class ZWaveBaseEntity(Entity): async def async_poll_value(self, refresh_all_values: bool) -> None: """Poll a value.""" - assert self.hass if not refresh_all_values: self.hass.async_create_task( self.info.node.async_poll_value(self.info.primary_value) @@ -75,7 +74,6 @@ class ZWaveBaseEntity(Entity): async def async_added_to_hass(self) -> None: """Call when entity is added.""" - assert self.hass # typing # Add value_changed callbacks. self.async_on_remove( self.info.node.on(EVENT_VALUE_UPDATED, self._value_changed) diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index 69248186b53..fea188ca336 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -59,7 +59,6 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow): return self.async_abort(reason="no_devices_found") # Cancel the discovered one. - assert self.hass is not None for flow in in_progress: self.hass.config_entries.flow.async_abort(flow["flow_id"]) @@ -91,7 +90,6 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow): return self.async_abort(reason="single_instance_allowed") # Cancel other flows. - assert self.hass is not None in_progress = self._async_in_progress() for flow in in_progress: self.hass.config_entries.flow.async_abort(flow["flow_id"]) @@ -144,7 +142,6 @@ class WebhookFlowHandler(config_entries.ConfigFlow): if user_input is None: return self.async_show_form(step_id="user") - assert self.hass is not None webhook_id = self.hass.components.webhook.async_generate_id() if ( diff --git a/homeassistant/helpers/config_entry_oauth2_flow.py b/homeassistant/helpers/config_entry_oauth2_flow.py index 653d07a333e..691715452ea 100644 --- a/homeassistant/helpers/config_entry_oauth2_flow.py +++ b/homeassistant/helpers/config_entry_oauth2_flow.py @@ -234,7 +234,6 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): self, user_input: Optional[dict] = None ) -> dict: """Handle a flow start.""" - assert self.hass implementations = await async_get_implementations(self.hass, self.DOMAIN) if user_input is not None: @@ -318,7 +317,6 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): """Handle a flow initialized by discovery.""" await self.async_set_unique_id(self.DOMAIN) - assert self.hass is not None if self.hass.config_entries.async_entries(self.DOMAIN): return self.async_abort(reason="already_configured") diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 7afe1b2ad25..791b1e251cd 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -89,10 +89,13 @@ class Entity(ABC): # SAFE TO OVERWRITE # The properties and methods here are safe to overwrite when inheriting # this class. These may be used to customize the behavior of the entity. - entity_id = None # type: str + entity_id: str = None # type: ignore # Owning hass instance. Will be set by EntityPlatform - hass: Optional[HomeAssistant] = None + # While not purely typed, it makes typehinting more useful for us + # and removes the need for constant None checks or asserts. + # Ignore types: https://github.com/PyCQA/pylint/issues/3167 + hass: HomeAssistant = None # type: ignore # Owning platform instance. Will be set by EntityPlatform platform: Optional[EntityPlatform] = None @@ -391,7 +394,6 @@ class Entity(ABC): ) # Overwrite properties that have been set in the config file. - assert self.hass is not None if DATA_CUSTOMIZE in self.hass.data: attr.update(self.hass.data[DATA_CUSTOMIZE].get(self.entity_id)) @@ -432,7 +434,6 @@ class Entity(ABC): If state is changed more than once before the ha state change task has been executed, the intermediate state transitions will be missed. """ - assert self.hass is not None self.hass.add_job(self.async_update_ha_state(force_refresh)) # type: ignore @callback @@ -448,7 +449,6 @@ class Entity(ABC): been executed, the intermediate state transitions will be missed. """ if force_refresh: - assert self.hass is not None self.hass.async_create_task(self.async_update_ha_state(force_refresh)) else: self.async_write_ha_state() @@ -532,7 +532,7 @@ class Entity(ABC): @callback def add_to_platform_abort(self) -> None: """Abort adding an entity to a platform.""" - self.hass = None + self.hass = None # type: ignore self.platform = None self.parallel_updates = None self._added = False @@ -553,8 +553,6 @@ class Entity(ABC): If the entity doesn't have a non disabled entry in the entity registry, or if force_remove=True, its state will be removed. """ - assert self.hass is not None - if self.platform and not self._added: raise HomeAssistantError( f"Entity {self.entity_id} async_remove called twice" @@ -597,8 +595,6 @@ class Entity(ABC): Not to be extended by integrations. """ - assert self.hass is not None - if self.platform: info = {"domain": self.platform.platform_name} @@ -628,7 +624,6 @@ class Entity(ABC): Not to be extended by integrations. """ if self.platform: - assert self.hass is not None self.hass.data[DATA_ENTITY_SOURCE].pop(self.entity_id) async def _async_registry_updated(self, event: Event) -> None: @@ -642,7 +637,6 @@ class Entity(ABC): if data["action"] != "update": return - assert self.hass is not None ent_reg = await self.hass.helpers.entity_registry.async_get_registry() old = self.registry_entry self.registry_entry = ent_reg.async_get(data["entity_id"]) @@ -717,7 +711,6 @@ class ToggleEntity(Entity): async def async_turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" - assert self.hass is not None await self.hass.async_add_executor_job(ft.partial(self.turn_on, **kwargs)) def turn_off(self, **kwargs: Any) -> None: @@ -726,7 +719,6 @@ class ToggleEntity(Entity): async def async_turn_off(self, **kwargs: Any) -> None: """Turn the entity off.""" - assert self.hass is not None await self.hass.async_add_executor_job(ft.partial(self.turn_off, **kwargs)) def toggle(self, **kwargs: Any) -> None: diff --git a/homeassistant/helpers/restore_state.py b/homeassistant/helpers/restore_state.py index 4f738887ce3..51ebc6fa774 100644 --- a/homeassistant/helpers/restore_state.py +++ b/homeassistant/helpers/restore_state.py @@ -235,7 +235,6 @@ class RestoreEntity(Entity): async def async_internal_added_to_hass(self) -> None: """Register this entity as a restorable entity.""" - assert self.hass is not None _, data = await asyncio.gather( super().async_internal_added_to_hass(), RestoreStateData.async_get_instance(self.hass), @@ -244,7 +243,6 @@ class RestoreEntity(Entity): async def async_internal_will_remove_from_hass(self) -> None: """Run when entity will be removed from hass.""" - assert self.hass is not None _, data = await asyncio.gather( super().async_internal_will_remove_from_hass(), RestoreStateData.async_get_instance(self.hass), @@ -255,7 +253,7 @@ class RestoreEntity(Entity): """Get the entity state from the previous run.""" if self.hass is None or self.entity_id is None: # Return None if this entity isn't added to hass yet - _LOGGER.warning("Cannot get last state. Entity not added to hass") + _LOGGER.warning("Cannot get last state. Entity not added to hass") # type: ignore[unreachable] return None data = await RestoreStateData.async_get_instance(self.hass) if self.entity_id not in data.last_states: diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 8b3f6ec6e59..7f768354035 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -425,8 +425,6 @@ class Template: This method must be run in the event loop. """ - assert self.hass - if self.is_static: return False From 8239fb76d14aef5c3298cdad67d67a205c4f65c5 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Mon, 15 Mar 2021 16:42:28 +0100 Subject: [PATCH 377/831] Bump brother library (#47949) --- homeassistant/components/brother/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/brother/manifest.json b/homeassistant/components/brother/manifest.json index 15828e5f05a..13933b7bf60 100644 --- a/homeassistant/components/brother/manifest.json +++ b/homeassistant/components/brother/manifest.json @@ -3,7 +3,7 @@ "name": "Brother Printer", "documentation": "https://www.home-assistant.io/integrations/brother", "codeowners": ["@bieniu"], - "requirements": ["brother==0.2.1"], + "requirements": ["brother==0.2.2"], "zeroconf": [{ "type": "_printer._tcp.local.", "name": "brother*" }], "config_flow": true, "quality_scale": "platinum" diff --git a/requirements_all.txt b/requirements_all.txt index 87cde66dd57..d1e27d28076 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -384,7 +384,7 @@ bravia-tv==1.0.8 broadlink==0.17.0 # homeassistant.components.brother -brother==0.2.1 +brother==0.2.2 # homeassistant.components.brottsplatskartan brottsplatskartan==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1721ef8c2b5..836f9762a06 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -214,7 +214,7 @@ bravia-tv==1.0.8 broadlink==0.17.0 # homeassistant.components.brother -brother==0.2.1 +brother==0.2.2 # homeassistant.components.bsblan bsblan==0.4.0 From 93c38551d3bf6381cb4d3d5cdc34189fc3de7830 Mon Sep 17 00:00:00 2001 From: Nathan Tilley Date: Mon, 15 Mar 2021 11:20:47 -0500 Subject: [PATCH 378/831] Implement Wake On Lan Dummy State (#47719) Co-authored-by: Martin Hjelmare --- CODEOWNERS | 1 + .../components/wake_on_lan/manifest.json | 2 +- .../components/wake_on_lan/switch.py | 23 ++++++++++- tests/components/wake_on_lan/test_switch.py | 41 +++++++++++++++++++ 4 files changed, 64 insertions(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index bece933d9c8..263e5337c58 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -521,6 +521,7 @@ homeassistant/components/vizio/* @raman325 homeassistant/components/vlc_telnet/* @rodripf @dmcc homeassistant/components/volkszaehler/* @fabaff homeassistant/components/volumio/* @OnFreund +homeassistant/components/wake_on_lan/* @ntilley905 homeassistant/components/waqi/* @andrey-git homeassistant/components/watson_tts/* @rutkai homeassistant/components/weather/* @fabaff diff --git a/homeassistant/components/wake_on_lan/manifest.json b/homeassistant/components/wake_on_lan/manifest.json index c66f87ae26e..bcd7ef58c8c 100644 --- a/homeassistant/components/wake_on_lan/manifest.json +++ b/homeassistant/components/wake_on_lan/manifest.json @@ -3,5 +3,5 @@ "name": "Wake on LAN", "documentation": "https://www.home-assistant.io/integrations/wake_on_lan", "requirements": ["wakeonlan==1.1.6"], - "codeowners": [] + "codeowners": ["@ntilley905"] } diff --git a/homeassistant/components/wake_on_lan/switch.py b/homeassistant/components/wake_on_lan/switch.py index 491bae782c5..eba6897647b 100644 --- a/homeassistant/components/wake_on_lan/switch.py +++ b/homeassistant/components/wake_on_lan/switch.py @@ -57,7 +57,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): broadcast_port, ) ], - True, + host is not None, ) @@ -86,6 +86,7 @@ class WolSwitch(SwitchEntity): Script(hass, off_action, name, domain) if off_action else None ) self._state = False + self._assumed_state = host is None @property def is_on(self): @@ -97,6 +98,16 @@ class WolSwitch(SwitchEntity): """Return the name of the switch.""" return self._name + @property + def assumed_state(self): + """Return true if no host is provided.""" + return self._assumed_state + + @property + def should_poll(self): + """Return false if assumed state is true.""" + return not self._assumed_state + def turn_on(self, **kwargs): """Turn the device on.""" service_kwargs = {} @@ -114,13 +125,21 @@ class WolSwitch(SwitchEntity): wakeonlan.send_magic_packet(self._mac_address, **service_kwargs) + if self._assumed_state: + self._state = True + self.async_write_ha_state() + def turn_off(self, **kwargs): """Turn the device off if an off action is present.""" if self._off_script is not None: self._off_script.run(context=self._context) + if self._assumed_state: + self._state = False + self.async_write_ha_state() + def update(self): - """Check if device is on and update the state.""" + """Check if device is on and update the state. Only called if assumed state is false.""" if platform.system().lower() == "windows": ping_cmd = [ "ping", diff --git a/tests/components/wake_on_lan/test_switch.py b/tests/components/wake_on_lan/test_switch.py index c2e32f77ccf..7b41fd4d75c 100644 --- a/tests/components/wake_on_lan/test_switch.py +++ b/tests/components/wake_on_lan/test_switch.py @@ -275,3 +275,44 @@ async def test_invalid_hostname_windows(hass): state = hass.states.get("switch.wake_on_lan") assert STATE_OFF == state.state + + +async def test_no_hostname_state(hass): + """Test that the state updates if we do not pass in a hostname.""" + + assert await async_setup_component( + hass, + switch.DOMAIN, + { + "switch": { + "platform": "wake_on_lan", + "mac": "00-01-02-03-04-05", + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("switch.wake_on_lan") + assert state.state == STATE_OFF + + with patch.object(subprocess, "call", return_value=0): + + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.wake_on_lan"}, + blocking=True, + ) + + state = hass.states.get("switch.wake_on_lan") + assert state.state == STATE_ON + + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.wake_on_lan"}, + blocking=True, + ) + + state = hass.states.get("switch.wake_on_lan") + assert state.state == STATE_OFF From 9e05aa2d1f91beb67ddf52266fa6e07081babadf Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Mon, 15 Mar 2021 18:20:10 +0100 Subject: [PATCH 379/831] Update state translation strings for water_heater (#46588) --- homeassistant/components/water_heater/strings.json | 11 +++++++++++ .../components/water_heater/translations/en.json | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/homeassistant/components/water_heater/strings.json b/homeassistant/components/water_heater/strings.json index 8f5709ac155..71fc1dc9328 100644 --- a/homeassistant/components/water_heater/strings.json +++ b/homeassistant/components/water_heater/strings.json @@ -4,5 +4,16 @@ "turn_on": "Turn on {entity_name}", "turn_off": "Turn off {entity_name}" } + }, + "state": { + "_": { + "off": "[%key:common::state::off%]", + "eco": "Eco", + "electric": "Electric", + "gas": "Gas", + "high_demand": "High Demand", + "heat_pump": "Heat Pump", + "performance": "Performance" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/en.json b/homeassistant/components/water_heater/translations/en.json index d6abfbb995d..6885fcb198e 100644 --- a/homeassistant/components/water_heater/translations/en.json +++ b/homeassistant/components/water_heater/translations/en.json @@ -4,5 +4,16 @@ "turn_off": "Turn off {entity_name}", "turn_on": "Turn on {entity_name}" } + }, + "state": { + "_": { + "eco": "Eco", + "electric": "Electric", + "gas": "Gas", + "heat_pump": "Heat Pump", + "high_demand": "High Demand", + "off": "Off", + "performance": "Performance" + } } } \ No newline at end of file From 28c80c11331eaafbac7efc6e39360ddc06954d3e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 15 Mar 2021 08:19:19 -1000 Subject: [PATCH 380/831] Ensure recorder purge tests can handle multiple purge cycle (#47956) Since a purge can generate another purge task, we now wait for three recorder queue completions by default. --- tests/components/recorder/common.py | 18 +++++++++++++ tests/components/recorder/test_purge.py | 35 +++++++++++++++---------- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/tests/components/recorder/common.py b/tests/components/recorder/common.py index 79f0f1f00d0..0bc4cfbfeb9 100644 --- a/tests/components/recorder/common.py +++ b/tests/components/recorder/common.py @@ -8,6 +8,8 @@ from homeassistant.util import dt as dt_util from tests.common import async_fire_time_changed, fire_time_changed +DEFAULT_PURGE_TASKS = 3 + def wait_recording_done(hass: HomeAssistantType) -> None: """Block till recording is done.""" @@ -42,6 +44,22 @@ async def async_wait_recording_done( await hass.async_block_till_done() +async def async_wait_purge_done( + hass: HomeAssistantType, instance: recorder.Recorder, max: int = None +) -> None: + """Wait for max number of purge events. + + Because a purge may insert another PurgeTask into + the queue after the WaitTask finishes, we need up to + a maximum number of WaitTasks that we will put into the + queue. + """ + if not max: + max = DEFAULT_PURGE_TASKS + for _ in range(max + 1): + await async_wait_recording_done(hass, instance) + + @ha.callback def async_trigger_db_commit(hass: HomeAssistantType) -> None: """Fore the recorder to commit. Async friendly.""" diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index e6bc3a99a97..bbc16cef825 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -12,7 +12,11 @@ from homeassistant.const import EVENT_STATE_CHANGED from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.util import dt as dt_util -from .common import async_recorder_block_till_done, async_wait_recording_done +from .common import ( + async_recorder_block_till_done, + async_wait_purge_done, + async_wait_recording_done, +) from .conftest import SetupRecorderInstanceT @@ -120,12 +124,15 @@ async def test_purge_method( assert recorder_runs.count() == 7 runs_before_purge = recorder_runs.all() + await hass.async_block_till_done() + await async_wait_purge_done(hass, instance) + # run purge method - no service data, use defaults await hass.services.async_call("recorder", "purge") await hass.async_block_till_done() # Small wait for recorder thread - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) # only purged old events assert states.count() == 4 @@ -136,7 +143,7 @@ async def test_purge_method( await hass.async_block_till_done() # Small wait for recorder thread - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) # we should only have 2 states left after purging assert states.count() == 2 @@ -156,7 +163,7 @@ async def test_purge_method( service_data["repack"] = True await hass.services.async_call("recorder", "purge", service_data=service_data) await hass.async_block_till_done() - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) assert "Vacuuming SQL DB to free space" in caplog.text @@ -192,7 +199,7 @@ async def test_purge_edge_case( ) instance = await async_setup_recorder_instance(hass, None) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) service_data = {"keep_days": 2} timestamp = dt_util.utcnow() - timedelta(days=2, minutes=1) @@ -211,7 +218,7 @@ async def test_purge_edge_case( await hass.async_block_till_done() await async_recorder_block_till_done(hass, instance) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) assert states.count() == 0 assert events.count() == 0 @@ -329,7 +336,7 @@ async def test_purge_filtered_states( await hass.async_block_till_done() await async_recorder_block_till_done(hass, instance) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) assert states.count() == 74 assert events_state_changed.count() == 70 @@ -343,10 +350,10 @@ async def test_purge_filtered_states( await hass.async_block_till_done() await async_recorder_block_till_done(hass, instance) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) await async_recorder_block_till_done(hass, instance) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) assert states.count() == 13 assert events_state_changed.count() == 10 @@ -419,7 +426,7 @@ async def test_purge_filtered_events( await hass.async_block_till_done() await async_recorder_block_till_done(hass, instance) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) assert events_purge.count() == 60 assert events_keep.count() == 10 @@ -433,10 +440,10 @@ async def test_purge_filtered_events( await hass.async_block_till_done() await async_recorder_block_till_done(hass, instance) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) await async_recorder_block_till_done(hass, instance) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) assert events_purge.count() == 0 assert events_keep.count() == 10 @@ -534,10 +541,10 @@ async def test_purge_filtered_events_state_changed( await hass.async_block_till_done() await async_recorder_block_till_done(hass, instance) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) await async_recorder_block_till_done(hass, instance) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) assert events_keep.count() == 10 assert events_purge.count() == 0 From 8b3dccb1b46f5ce8c42db875631986ff2f12d9e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 15 Mar 2021 19:31:34 +0100 Subject: [PATCH 381/831] Use ClientTimeout for hassio send_command (#47957) --- homeassistant/components/hassio/handler.py | 32 ++++++++++++---------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/hassio/handler.py b/homeassistant/components/hassio/handler.py index 6bc3cb345a5..303d6770255 100644 --- a/homeassistant/components/hassio/handler.py +++ b/homeassistant/components/hassio/handler.py @@ -4,7 +4,6 @@ import logging import os import aiohttp -import async_timeout from homeassistant.components.http import ( CONF_SERVER_HOST, @@ -52,7 +51,12 @@ def api_data(funct): class HassIO: """Small API wrapper for Hass.io.""" - def __init__(self, loop, websession, ip): + def __init__( + self, + loop: asyncio.AbstractEventLoop, + websession: aiohttp.ClientSession, + ip: str, + ) -> None: """Initialize Hass.io API.""" self.loop = loop self.websession = websession @@ -187,20 +191,20 @@ class HassIO: This method is a coroutine. """ try: - with async_timeout.timeout(timeout): - request = await self.websession.request( - method, - f"http://{self._ip}{command}", - json=payload, - headers={X_HASSIO: os.environ.get("HASSIO_TOKEN", "")}, - ) + request = await self.websession.request( + method, + f"http://{self._ip}{command}", + json=payload, + headers={X_HASSIO: os.environ.get("HASSIO_TOKEN", "")}, + timeout=aiohttp.ClientTimeout(total=timeout), + ) - if request.status not in (HTTP_OK, HTTP_BAD_REQUEST): - _LOGGER.error("%s return code %d", command, request.status) - raise HassioAPIError() + if request.status not in (HTTP_OK, HTTP_BAD_REQUEST): + _LOGGER.error("%s return code %d", command, request.status) + raise HassioAPIError() - answer = await request.json() - return answer + answer = await request.json() + return answer except asyncio.TimeoutError: _LOGGER.error("Timeout on %s request", command) From 07c197687f0c0748d9ebfe7fd14b4e27cfb5b00c Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Mon, 15 Mar 2021 19:42:13 +0100 Subject: [PATCH 382/831] improve debug logging (#47858) --- .../components/synology_dsm/__init__.py | 85 +++++++++++++------ 1 file changed, 59 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 88654f02b21..002538061c8 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -191,7 +191,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): try: await api.async_setup() except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: - _LOGGER.debug("Unable to connect to DSM during setup: %s", err) + _LOGGER.debug( + "Unable to connect to DSM '%s' during setup: %s", entry.unique_id, err + ) raise ConfigEntryNotReady from err hass.data.setdefault(DOMAIN, {}) @@ -401,7 +403,8 @@ class SynoApi: self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY) ) _LOGGER.debug( - "State of Surveillance_station during setup:%s", + "State of Surveillance_station during setup of '%s': %s", + self._entry.unique_id, self._with_surveillance_station, ) @@ -413,9 +416,7 @@ class SynoApi: @callback def subscribe(self, api_key, unique_id): """Subscribe an entity to API fetches.""" - _LOGGER.debug( - "Subscribe new entity - api_key:%s, unique_id:%s", api_key, unique_id - ) + _LOGGER.debug("Subscribe new entity: %s", unique_id) if api_key not in self._fetching_entities: self._fetching_entities[api_key] = set() self._fetching_entities[api_key].add(unique_id) @@ -423,9 +424,7 @@ class SynoApi: @callback def unsubscribe() -> None: """Unsubscribe an entity from API fetches (when disable).""" - _LOGGER.debug( - "Unsubscribe new entity - api_key:%s, unique_id:%s", api_key, unique_id - ) + _LOGGER.debug("Unsubscribe entity: %s", unique_id) self._fetching_entities[api_key].remove(unique_id) if len(self._fetching_entities[api_key]) == 0: self._fetching_entities.pop(api_key) @@ -437,7 +436,9 @@ class SynoApi: """Determine if we should fetch each API, if one entity needs it.""" # Entities not added yet, fetch all if not self._fetching_entities: - _LOGGER.debug("Entities not added yet, fetch all") + _LOGGER.debug( + "Entities not added yet, fetch all for '%s'", self._entry.unique_id + ) return # Determine if we should fetch an API @@ -460,32 +461,47 @@ class SynoApi: # Reset not used API, information is not reset since it's used in device_info if not self._with_security: - _LOGGER.debug("Disable security api from being updated") + _LOGGER.debug( + "Disable security api from being updated for '%s'", + self._entry.unique_id, + ) self.dsm.reset(self.security) self.security = None if not self._with_storage: - _LOGGER.debug("Disable storage api from being updated") + _LOGGER.debug( + "Disable storage api from being updatedf or '%s'", self._entry.unique_id + ) self.dsm.reset(self.storage) self.storage = None if not self._with_system: - _LOGGER.debug("Disable system api from being updated") + _LOGGER.debug( + "Disable system api from being updated for '%s'", self._entry.unique_id + ) self.dsm.reset(self.system) self.system = None if not self._with_upgrade: - _LOGGER.debug("Disable upgrade api from being updated") + _LOGGER.debug( + "Disable upgrade api from being updated for '%s'", self._entry.unique_id + ) self.dsm.reset(self.upgrade) self.upgrade = None if not self._with_utilisation: - _LOGGER.debug("Disable utilisation api from being updated") + _LOGGER.debug( + "Disable utilisation api from being updated for '%s'", + self._entry.unique_id, + ) self.dsm.reset(self.utilisation) self.utilisation = None if not self._with_surveillance_station: - _LOGGER.debug("Disable surveillance_station api from being updated") + _LOGGER.debug( + "Disable surveillance_station api from being updated for '%s'", + self._entry.unique_id, + ) self.dsm.reset(self.surveillance_station) self.surveillance_station = None @@ -496,27 +512,32 @@ class SynoApi: self.network.update() if self._with_security: - _LOGGER.debug("Enable security api for updates") + _LOGGER.debug("Enable security api updates for '%s'", self._entry.unique_id) self.security = self.dsm.security if self._with_storage: - _LOGGER.debug("Enable storage api for updates") + _LOGGER.debug("Enable storage api updates for '%s'", self._entry.unique_id) self.storage = self.dsm.storage if self._with_upgrade: - _LOGGER.debug("Enable upgrade api for updates") + _LOGGER.debug("Enable upgrade api updates for '%s'", self._entry.unique_id) self.upgrade = self.dsm.upgrade if self._with_system: - _LOGGER.debug("Enable system api for updates") + _LOGGER.debug("Enable system api updates for '%s'", self._entry.unique_id) self.system = self.dsm.system if self._with_utilisation: - _LOGGER.debug("Enable utilisation api for updates") + _LOGGER.debug( + "Enable utilisation api updates for '%s'", self._entry.unique_id + ) self.utilisation = self.dsm.utilisation if self._with_surveillance_station: - _LOGGER.debug("Enable surveillance_station api for updates") + _LOGGER.debug( + "Enable surveillance_station api updates for '%s'", + self._entry.unique_id, + ) self.surveillance_station = self.dsm.surveillance_station async def async_reboot(self): @@ -524,7 +545,10 @@ class SynoApi: try: await self._hass.async_add_executor_job(self.system.reboot) except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: - _LOGGER.error("Reboot not possible, please try again later") + _LOGGER.error( + "Reboot of '%s' not possible, please try again later", + self._entry.unique_id, + ) _LOGGER.debug("Exception:%s", err) async def async_shutdown(self): @@ -532,7 +556,10 @@ class SynoApi: try: await self._hass.async_add_executor_job(self.system.shutdown) except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: - _LOGGER.error("Shutdown not possible, please try again later") + _LOGGER.error( + "Shutdown of '%s' not possible, please try again later", + self._entry.unique_id, + ) _LOGGER.debug("Exception:%s", err) async def async_unload(self): @@ -540,11 +567,13 @@ class SynoApi: try: await self._hass.async_add_executor_job(self.dsm.logout) except (SynologyDSMAPIErrorException, SynologyDSMRequestException) as err: - _LOGGER.debug("Logout not possible:%s", err) + _LOGGER.debug( + "Logout from '%s' not possible:%s", self._entry.unique_id, err + ) async def async_update(self, now=None): """Update function for updating API information.""" - _LOGGER.debug("Start data update") + _LOGGER.debug("Start data update for '%s'", self._entry.unique_id) self._async_setup_api_requests() try: await self._hass.async_add_executor_job( @@ -554,7 +583,11 @@ class SynoApi: _LOGGER.warning( "Connection error during update, fallback by reloading the entry" ) - _LOGGER.debug("Connection error during update with exception: %s", err) + _LOGGER.debug( + "Connection error during update of '%s' with exception: %s", + self._entry.unique_id, + err, + ) await self._hass.config_entries.async_reload(self._entry.entry_id) return From 9f4c2f6260ad73c01f058b35503e39ea10c630b6 Mon Sep 17 00:00:00 2001 From: RadekHvizdos <10856567+RadekHvizdos@users.noreply.github.com> Date: Mon, 15 Mar 2021 20:02:02 +0100 Subject: [PATCH 383/831] Add suggested_area to MQTT discovery (#47903) * Add suggested_area to MQTT Discovery This adds suggested_area to MQTT discovery, so that the discovered devices could be automatically added to the proper area. * Add abbreviation for MQTT suggested_area * Remove extra whitespace * Remove extra whitespace #2 * Added tests for MQTT Dicovery of suggested_area * Fix test for MQTT suggested_area * Better tests of MQTT suggested_area Changes made as per feedback from @emontnemery --- homeassistant/components/mqtt/abbreviations.py | 1 + homeassistant/components/mqtt/mixins.py | 5 +++++ tests/components/mqtt/test_common.py | 4 ++++ tests/components/mqtt/test_discovery.py | 3 ++- 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/abbreviations.py b/homeassistant/components/mqtt/abbreviations.py index 8868d487f93..868e2fdd791 100644 --- a/homeassistant/components/mqtt/abbreviations.py +++ b/homeassistant/components/mqtt/abbreviations.py @@ -203,4 +203,5 @@ DEVICE_ABBREVIATIONS = { "mf": "manufacturer", "mdl": "model", "sw": "sw_version", + "sa": "suggested_area", } diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 898072de5f9..5737dad255c 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -63,6 +63,7 @@ CONF_MODEL = "model" CONF_SW_VERSION = "sw_version" CONF_VIA_DEVICE = "via_device" CONF_DEPRECATED_VIA_HUB = "via_hub" +CONF_SUGGESTED_AREA = "suggested_area" MQTT_AVAILABILITY_SINGLE_SCHEMA = vol.Schema( { @@ -129,6 +130,7 @@ MQTT_ENTITY_DEVICE_INFO_SCHEMA = vol.All( vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_SW_VERSION): cv.string, vol.Optional(CONF_VIA_DEVICE): cv.string, + vol.Optional(CONF_SUGGESTED_AREA): cv.string, } ), validate_device_has_at_least_one_identifier, @@ -491,6 +493,9 @@ def device_info_from_config(config): if CONF_VIA_DEVICE in config: info["via_device"] = (DOMAIN, config[CONF_VIA_DEVICE]) + if CONF_SUGGESTED_AREA in config: + info["suggested_area"] = config[CONF_SUGGESTED_AREA] + return info diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index f1243918e4e..43f27373a3e 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -20,6 +20,7 @@ DEFAULT_CONFIG_DEVICE_INFO_ID = { "name": "Beer", "model": "Glass", "sw_version": "0.1-beta", + "suggested_area": "default_area", } DEFAULT_CONFIG_DEVICE_INFO_MAC = { @@ -28,6 +29,7 @@ DEFAULT_CONFIG_DEVICE_INFO_MAC = { "name": "Beer", "model": "Glass", "sw_version": "0.1-beta", + "suggested_area": "default_area", } @@ -739,6 +741,7 @@ async def help_test_entity_device_info_with_identifier(hass, mqtt_mock, domain, assert device.name == "Beer" assert device.model == "Glass" assert device.sw_version == "0.1-beta" + assert device.suggested_area == "default_area" async def help_test_entity_device_info_with_connection(hass, mqtt_mock, domain, config): @@ -764,6 +767,7 @@ async def help_test_entity_device_info_with_connection(hass, mqtt_mock, domain, assert device.name == "Beer" assert device.model == "Glass" assert device.sw_version == "0.1-beta" + assert device.suggested_area == "default_area" async def help_test_entity_device_info_remove(hass, mqtt_mock, domain, config): diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index fed0dfa54d6..d55c8e0eccc 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -442,7 +442,8 @@ async def test_discovery_expansion(hass, mqtt_mock, caplog): ' "name":"DiscoveryExpansionTest1 Device",' ' "mdl":"Generic",' ' "sw":"1.2.3.4",' - ' "mf":"None"' + ' "mf":"None",' + ' "sa":"default_area"' " }" "}" ) From 059e9e830727c245ce7f3ac64d065ec3f6501b85 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 15 Mar 2021 20:30:44 +0100 Subject: [PATCH 384/831] Add config flow to Verisure (#47880) Co-authored-by: Paulus Schoutsen --- .coveragerc | 9 +- homeassistant/components/verisure/__init__.py | 157 ++++-- .../verisure/alarm_control_panel.py | 35 +- .../components/verisure/binary_sensor.py | 32 +- homeassistant/components/verisure/camera.py | 27 +- .../components/verisure/config_flow.py | 186 +++++++ homeassistant/components/verisure/const.py | 18 +- .../components/verisure/coordinator.py | 34 +- homeassistant/components/verisure/lock.py | 50 +- .../components/verisure/manifest.json | 4 +- homeassistant/components/verisure/sensor.py | 56 +-- .../components/verisure/strings.json | 39 ++ homeassistant/components/verisure/switch.py | 34 +- .../components/verisure/translations/en.json | 39 ++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/dhcp.py | 4 + requirements_test_all.txt | 3 + tests/components/verisure/__init__.py | 1 + tests/components/verisure/test_config_flow.py | 467 ++++++++++++++++++ 19 files changed, 996 insertions(+), 200 deletions(-) create mode 100644 homeassistant/components/verisure/config_flow.py create mode 100644 homeassistant/components/verisure/strings.json create mode 100644 homeassistant/components/verisure/translations/en.json create mode 100644 tests/components/verisure/__init__.py create mode 100644 tests/components/verisure/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 3c6bf16f6ba..d6bdfb9b091 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1064,7 +1064,14 @@ omit = homeassistant/components/velbus/switch.py homeassistant/components/velux/* homeassistant/components/venstar/climate.py - homeassistant/components/verisure/* + homeassistant/components/verisure/__init__.py + homeassistant/components/verisure/alarm_control_panel.py + homeassistant/components/verisure/binary_sensor.py + homeassistant/components/verisure/camera.py + homeassistant/components/verisure/coordinator.py + homeassistant/components/verisure/lock.py + homeassistant/components/verisure/sensor.py + homeassistant/components/verisure/switch.py homeassistant/components/versasense/* homeassistant/components/vesync/__init__.py homeassistant/components/vesync/common.py diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index 16250915f45..e34bf5c5650 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -1,6 +1,10 @@ """Support for Verisure devices.""" from __future__ import annotations +import asyncio +import os +from typing import Any + from verisure import Error as VerisureError import voluptuous as vol @@ -12,34 +16,28 @@ from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( + CONF_EMAIL, CONF_PASSWORD, - CONF_SCAN_INTERVAL, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers import discovery +from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.storage import STORAGE_DIR from .const import ( ATTR_DEVICE_SERIAL, - CONF_ALARM, CONF_CODE_DIGITS, CONF_DEFAULT_LOCK_CODE, - CONF_DOOR_WINDOW, CONF_GIID, - CONF_HYDROMETERS, - CONF_LOCKS, - CONF_MOUSE, - CONF_SMARTCAM, - CONF_SMARTPLUGS, - CONF_THERMOMETERS, - DEFAULT_SCAN_INTERVAL, + CONF_LOCK_CODE_DIGITS, + CONF_LOCK_DEFAULT_CODE, + DEFAULT_LOCK_CODE_DIGITS, DOMAIN, LOGGER, - MIN_SCAN_INTERVAL, SERVICE_CAPTURE_SMARTCAM, SERVICE_DISABLE_AUTOLOCK, SERVICE_ENABLE_AUTOLOCK, @@ -56,54 +54,101 @@ PLATFORMS = [ ] CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_ALARM, default=True): cv.boolean, - vol.Optional(CONF_CODE_DIGITS, default=4): cv.positive_int, - vol.Optional(CONF_DOOR_WINDOW, default=True): cv.boolean, - vol.Optional(CONF_GIID): cv.string, - vol.Optional(CONF_HYDROMETERS, default=True): cv.boolean, - vol.Optional(CONF_LOCKS, default=True): cv.boolean, - vol.Optional(CONF_DEFAULT_LOCK_CODE): cv.string, - vol.Optional(CONF_MOUSE, default=True): cv.boolean, - vol.Optional(CONF_SMARTPLUGS, default=True): cv.boolean, - vol.Optional(CONF_THERMOMETERS, default=True): cv.boolean, - vol.Optional(CONF_SMARTCAM, default=True): cv.boolean, - vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): ( - vol.All(cv.time_period, vol.Clamp(min=MIN_SCAN_INTERVAL)) - ), - } - ) - }, + vol.All( + cv.deprecated(DOMAIN), + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_CODE_DIGITS): cv.positive_int, + vol.Optional(CONF_GIID): cv.string, + vol.Optional(CONF_DEFAULT_LOCK_CODE): cv.string, + }, + extra=vol.ALLOW_EXTRA, + ) + }, + ), extra=vol.ALLOW_EXTRA, ) + DEVICE_SERIAL_SCHEMA = vol.Schema({vol.Required(ATTR_DEVICE_SERIAL): cv.string}) -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: +async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool: """Set up the Verisure integration.""" - coordinator = VerisureDataUpdateCoordinator(hass, config=config[DOMAIN]) + if DOMAIN in config: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={ + CONF_EMAIL: config[DOMAIN][CONF_USERNAME], + CONF_PASSWORD: config[DOMAIN][CONF_PASSWORD], + CONF_GIID: config[DOMAIN].get(CONF_GIID), + CONF_LOCK_CODE_DIGITS: config[DOMAIN].get(CONF_CODE_DIGITS), + CONF_LOCK_DEFAULT_CODE: config[DOMAIN].get(CONF_LOCK_DEFAULT_CODE), + }, + ) + ) + + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Verisure from a config entry.""" + # Migrate old YAML settings (hidden in the config entry), + # to config entry options. Can be removed after YAML support is gone. + if CONF_LOCK_CODE_DIGITS in entry.data or CONF_DEFAULT_LOCK_CODE in entry.data: + options = entry.options.copy() + + if ( + CONF_LOCK_CODE_DIGITS in entry.data + and CONF_LOCK_CODE_DIGITS not in entry.options + and entry.data[CONF_LOCK_CODE_DIGITS] != DEFAULT_LOCK_CODE_DIGITS + ): + options.update( + { + CONF_LOCK_CODE_DIGITS: entry.data[CONF_LOCK_CODE_DIGITS], + } + ) + + if ( + CONF_DEFAULT_LOCK_CODE in entry.data + and CONF_DEFAULT_LOCK_CODE not in entry.options + ): + options.update( + { + CONF_DEFAULT_LOCK_CODE: entry.data[CONF_DEFAULT_LOCK_CODE], + } + ) + + data = entry.data.copy() + data.pop(CONF_LOCK_CODE_DIGITS, None) + data.pop(CONF_DEFAULT_LOCK_CODE, None) + hass.config_entries.async_update_entry(entry, data=data, options=options) + + # Continue as normal... + coordinator = VerisureDataUpdateCoordinator(hass, entry=entry) if not await coordinator.async_login(): - LOGGER.error("Login failed") + LOGGER.error("Could not login to Verisure, aborting setting up integration") return False hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, coordinator.async_logout) await coordinator.async_refresh() if not coordinator.last_update_success: - LOGGER.error("Update failed") - return False + raise ConfigEntryNotReady - hass.data[DOMAIN] = coordinator + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][entry.entry_id] = coordinator + # Set up all platforms for this device/entry. for platform in PLATFORMS: hass.async_create_task( - discovery.async_load_platform(hass, platform, DOMAIN, {}, config) + hass.config_entries.async_forward_entry_setup(entry, platform) ) async def capture_smartcam(service): @@ -145,3 +190,31 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: DOMAIN, SERVICE_ENABLE_AUTOLOCK, enable_autolock, schema=DEVICE_SERIAL_SCHEMA ) return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload Verisure config entry.""" + unload_ok = all( + await asyncio.gather( + *( + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS + ) + ) + ) + + if not unload_ok: + return False + + cookie_file = hass.config.path(STORAGE_DIR, f"verisure_{entry.entry_id}") + try: + await hass.async_add_executor_job(os.unlink, cookie_file) + except FileNotFoundError: + pass + + del hass.data[DOMAIN][entry.entry_id] + + if not hass.data[DOMAIN]: + del hass.data[DOMAIN] + + return True diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index 94fbfe69bd0..8a2a05e2005 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio -from typing import Any, Callable +from typing import Callable, Iterable from homeassistant.components.alarm_control_panel import ( FORMAT_NUMBER, @@ -12,6 +12,7 @@ from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, SUPPORT_ALARM_ARM_HOME, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, @@ -22,22 +23,17 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import CONF_ALARM, CONF_GIID, DOMAIN, LOGGER +from .const import CONF_GIID, DOMAIN, LOGGER from .coordinator import VerisureDataUpdateCoordinator -def setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: dict[str, Any], - add_entities: Callable[[list[Entity], bool], None], - discovery_info: dict[str, Any] | None = None, + entry: ConfigEntry, + async_add_entities: Callable[[Iterable[Entity]], None], ) -> None: - """Set up the Verisure platform.""" - coordinator = hass.data[DOMAIN] - alarms = [] - if int(coordinator.config.get(CONF_ALARM, 1)): - alarms.append(VerisureAlarm(coordinator)) - add_entities(alarms) + """Set up Verisure alarm control panel from a config entry.""" + async_add_entities([VerisureAlarm(coordinator=hass.data[DOMAIN][entry.entry_id])]) class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity): @@ -53,17 +49,12 @@ class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity): @property def name(self) -> str: """Return the name of the device.""" - giid = self.coordinator.config.get(CONF_GIID) - if giid is not None: - aliass = { - i["giid"]: i["alias"] for i in self.coordinator.verisure.installations - } - if giid in aliass: - return "{} alarm".format(aliass[giid]) + return "Verisure Alarm" - LOGGER.error("Verisure installation giid not found: %s", giid) - - return "{} alarm".format(self.coordinator.verisure.installations[0]["alias"]) + @property + def unique_id(self) -> str: + """Return the unique ID for this alarm control panel.""" + return self.coordinator.entry.data[CONF_GIID] @property def state(self) -> str | None: diff --git a/homeassistant/components/verisure/binary_sensor.py b/homeassistant/components/verisure/binary_sensor.py index 66eb5031072..4289a6f8ffc 100644 --- a/homeassistant/components/verisure/binary_sensor.py +++ b/homeassistant/components/verisure/binary_sensor.py @@ -1,40 +1,38 @@ """Support for Verisure binary sensors.""" from __future__ import annotations -from typing import Any, Callable +from typing import Callable, Iterable from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, DEVICE_CLASS_OPENING, BinarySensorEntity, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import CONF_DOOR_WINDOW, DOMAIN +from . import DOMAIN from .coordinator import VerisureDataUpdateCoordinator -def setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: dict[str, Any], - add_entities: Callable[[list[CoordinatorEntity]], None], - discovery_info: dict[str, Any] | None = None, + entry: ConfigEntry, + async_add_entities: Callable[[Iterable[Entity]], None], ) -> None: - """Set up the Verisure binary sensors.""" - coordinator = hass.data[DOMAIN] + """Set up Verisure sensors based on a config entry.""" + coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - sensors: list[CoordinatorEntity] = [VerisureEthernetStatus(coordinator)] + sensors: list[Entity] = [VerisureEthernetStatus(coordinator)] - if int(coordinator.config.get(CONF_DOOR_WINDOW, 1)): - sensors.extend( - [ - VerisureDoorWindowSensor(coordinator, serial_number) - for serial_number in coordinator.data["door_window"] - ] - ) + sensors.extend( + VerisureDoorWindowSensor(coordinator, serial_number) + for serial_number in coordinator.data["door_window"] + ) - add_entities(sensors) + async_add_entities(sensors) class VerisureDoorWindowSensor(CoordinatorEntity, BinarySensorEntity): diff --git a/homeassistant/components/verisure/camera.py b/homeassistant/components/verisure/camera.py index 6f22b17b848..ee9fe6577de 100644 --- a/homeassistant/components/verisure/camera.py +++ b/homeassistant/components/verisure/camera.py @@ -3,34 +3,31 @@ from __future__ import annotations import errno import os -from typing import Any, Callable +from typing import Callable, Iterable from homeassistant.components.camera import Camera +from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import CONF_SMARTCAM, DOMAIN, LOGGER +from .const import DOMAIN, LOGGER from .coordinator import VerisureDataUpdateCoordinator -def setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: dict[str, Any], - add_entities: Callable[[list[VerisureSmartcam]], None], - discovery_info: dict[str, Any] | None = None, + entry: ConfigEntry, + async_add_entities: Callable[[Iterable[Entity]], None], ) -> None: - """Set up the Verisure Camera.""" - coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN] - if not int(coordinator.config.get(CONF_SMARTCAM, 1)): - return + """Set up Verisure sensors based on a config entry.""" + coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] assert hass.config.config_dir - add_entities( - [ - VerisureSmartcam(hass, coordinator, serial_number, hass.config.config_dir) - for serial_number in coordinator.data["cameras"] - ] + async_add_entities( + VerisureSmartcam(hass, coordinator, serial_number, hass.config.config_dir) + for serial_number in coordinator.data["cameras"] ) diff --git a/homeassistant/components/verisure/config_flow.py b/homeassistant/components/verisure/config_flow.py new file mode 100644 index 00000000000..ce9c76874f1 --- /dev/null +++ b/homeassistant/components/verisure/config_flow.py @@ -0,0 +1,186 @@ +"""Config flow for Verisure integration.""" +from __future__ import annotations + +from typing import Any + +from verisure import ( + Error as VerisureError, + LoginError as VerisureLoginError, + ResponseError as VerisureResponseError, + Session as Verisure, +) +import voluptuous as vol + +from homeassistant.config_entries import ( + CONN_CLASS_CLOUD_POLL, + ConfigEntry, + ConfigFlow, + OptionsFlow, +) +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD +from homeassistant.core import callback + +from .const import ( # pylint:disable=unused-import + CONF_GIID, + CONF_LOCK_CODE_DIGITS, + CONF_LOCK_DEFAULT_CODE, + DEFAULT_LOCK_CODE_DIGITS, + DOMAIN, + LOGGER, +) + + +class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN): + """Handle a config flow for Verisure.""" + + VERSION = 1 + CONNECTION_CLASS = CONN_CLASS_CLOUD_POLL + + installations: dict[str, str] + email: str + password: str + + # These can be removed after YAML import has been removed. + giid: str | None = None + settings: dict[str, int | str] + + def __init__(self): + """Initialize.""" + self.settings = {} + + @staticmethod + @callback + def async_get_options_flow(config_entry: ConfigEntry) -> VerisureOptionsFlowHandler: + """Get the options flow for this handler.""" + return VerisureOptionsFlowHandler(config_entry) + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: + """Handle the initial step.""" + errors: dict[str, str] = {} + + if user_input is not None: + verisure = Verisure( + username=user_input[CONF_EMAIL], password=user_input[CONF_PASSWORD] + ) + try: + await self.hass.async_add_executor_job(verisure.login) + except VerisureLoginError as ex: + LOGGER.debug("Could not log in to Verisure, %s", ex) + errors["base"] = "invalid_auth" + except (VerisureError, VerisureResponseError) as ex: + LOGGER.debug("Unexpected response from Verisure, %s", ex) + errors["base"] = "unknown" + else: + self.email = user_input[CONF_EMAIL] + self.password = user_input[CONF_PASSWORD] + self.installations = { + inst["giid"]: f"{inst['alias']} ({inst['street']})" + for inst in verisure.installations + } + + return await self.async_step_installation() + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_EMAIL): str, + vol.Required(CONF_PASSWORD): str, + } + ), + errors=errors, + ) + + async def async_step_installation( + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: + """Select Verisure installation to add.""" + if len(self.installations) == 1: + user_input = {CONF_GIID: list(self.installations)[0]} + elif self.giid and self.giid in self.installations: + user_input = {CONF_GIID: self.giid} + + if user_input is None: + return self.async_show_form( + step_id="installation", + data_schema=vol.Schema( + {vol.Required(CONF_GIID): vol.In(self.installations)} + ), + ) + + await self.async_set_unique_id(user_input[CONF_GIID]) + self._abort_if_unique_id_configured() + + return self.async_create_entry( + title=self.installations[user_input[CONF_GIID]], + data={ + CONF_EMAIL: self.email, + CONF_PASSWORD: self.password, + CONF_GIID: user_input[CONF_GIID], + **self.settings, + }, + ) + + async def async_step_import(self, user_input: dict[str, Any]) -> dict[str, Any]: + """Import Verisure YAML configuration.""" + if user_input[CONF_GIID]: + self.giid = user_input[CONF_GIID] + await self.async_set_unique_id(self.giid) + self._abort_if_unique_id_configured() + else: + # The old YAML configuration could handle 1 single Verisure instance. + # Therefore, if we don't know the GIID, we can use the discovery + # without a unique ID logic, to prevent re-import/discovery. + await self._async_handle_discovery_without_unique_id() + + # Settings, later to be converted to config entry options + if user_input[CONF_LOCK_CODE_DIGITS]: + self.settings[CONF_LOCK_CODE_DIGITS] = user_input[CONF_LOCK_CODE_DIGITS] + if user_input[CONF_LOCK_DEFAULT_CODE]: + self.settings[CONF_LOCK_DEFAULT_CODE] = user_input[CONF_LOCK_DEFAULT_CODE] + + return await self.async_step_user(user_input) + + +class VerisureOptionsFlowHandler(OptionsFlow): + """Handle Verisure options.""" + + def __init__(self, entry: ConfigEntry) -> None: + """Initialize Verisure options flow.""" + self.entry = entry + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: + """Manage Verisure options.""" + errors = {} + + if user_input is not None: + if len(user_input[CONF_LOCK_DEFAULT_CODE]) not in [ + 0, + user_input[CONF_LOCK_CODE_DIGITS], + ]: + errors["base"] = "code_format_mismatch" + else: + return self.async_create_entry(title="", data=user_input) + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Optional( + CONF_LOCK_CODE_DIGITS, + default=self.entry.options.get( + CONF_LOCK_CODE_DIGITS, DEFAULT_LOCK_CODE_DIGITS + ), + ): int, + vol.Optional( + CONF_LOCK_DEFAULT_CODE, + default=self.entry.options.get(CONF_LOCK_DEFAULT_CODE), + ): str, + } + ), + errors=errors, + ) diff --git a/homeassistant/components/verisure/const.py b/homeassistant/components/verisure/const.py index 89dcfa396aa..3e00eb1ddb3 100644 --- a/homeassistant/components/verisure/const.py +++ b/homeassistant/components/verisure/const.py @@ -8,21 +8,17 @@ LOGGER = logging.getLogger(__package__) ATTR_DEVICE_SERIAL = "device_serial" -CONF_ALARM = "alarm" -CONF_CODE_DIGITS = "code_digits" -CONF_DOOR_WINDOW = "door_window" CONF_GIID = "giid" -CONF_HYDROMETERS = "hygrometers" -CONF_LOCKS = "locks" -CONF_DEFAULT_LOCK_CODE = "default_lock_code" -CONF_MOUSE = "mouse" -CONF_SMARTPLUGS = "smartplugs" -CONF_THERMOMETERS = "thermometers" -CONF_SMARTCAM = "smartcam" +CONF_LOCK_CODE_DIGITS = "lock_code_digits" +CONF_LOCK_DEFAULT_CODE = "lock_default_code" DEFAULT_SCAN_INTERVAL = timedelta(minutes=1) -MIN_SCAN_INTERVAL = timedelta(minutes=1) +DEFAULT_LOCK_CODE_DIGITS = 4 SERVICE_CAPTURE_SMARTCAM = "capture_smartcam" SERVICE_DISABLE_AUTOLOCK = "disable_autolock" SERVICE_ENABLE_AUTOLOCK = "enable_autolock" + +# Legacy; to remove after YAML removal +CONF_CODE_DIGITS = "code_digits" +CONF_DEFAULT_LOCK_CODE = "default_lock_code" diff --git a/homeassistant/components/verisure/coordinator.py b/homeassistant/components/verisure/coordinator.py index 9de81429c5c..63eb5ac2f68 100644 --- a/homeassistant/components/verisure/coordinator.py +++ b/homeassistant/components/verisure/coordinator.py @@ -9,9 +9,10 @@ from verisure import ( Session as Verisure, ) -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, HTTP_SERVICE_UNAVAILABLE -from homeassistant.core import HomeAssistant -from homeassistant.helpers.typing import ConfigType +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, HTTP_SERVICE_UNAVAILABLE +from homeassistant.core import Event, HomeAssistant +from homeassistant.helpers.storage import STORAGE_DIR from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.util import Throttle @@ -21,14 +22,15 @@ from .const import CONF_GIID, DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER class VerisureDataUpdateCoordinator(DataUpdateCoordinator): """A Verisure Data Update Coordinator.""" - def __init__(self, hass: HomeAssistant, config: ConfigType) -> None: + def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: """Initialize the Verisure hub.""" self.imageseries = {} - self.config = config - self.giid = config.get(CONF_GIID) + self.entry = entry self.verisure = Verisure( - username=config[CONF_USERNAME], password=config[CONF_PASSWORD] + username=entry.data[CONF_EMAIL], + password=entry.data[CONF_PASSWORD], + cookieFileName=hass.config.path(STORAGE_DIR, f"verisure_{entry.entry_id}"), ) super().__init__( @@ -42,11 +44,14 @@ class VerisureDataUpdateCoordinator(DataUpdateCoordinator): except VerisureError as ex: LOGGER.error("Could not log in to verisure, %s", ex) return False - if self.giid: - return await self.async_set_giid() + + await self.hass.async_add_executor_job( + self.verisure.set_giid, self.entry.data[CONF_GIID] + ) + return True - async def async_logout(self) -> bool: + async def async_logout(self, _event: Event) -> bool: """Logout from Verisure.""" try: await self.hass.async_add_executor_job(self.verisure.logout) @@ -55,15 +60,6 @@ class VerisureDataUpdateCoordinator(DataUpdateCoordinator): return False return True - async def async_set_giid(self) -> bool: - """Set installation GIID.""" - try: - await self.hass.async_add_executor_job(self.verisure.set_giid, self.giid) - except VerisureError as ex: - LOGGER.error("Could not set installation GIID, %s", ex) - return False - return True - async def _async_update_data(self) -> dict: """Fetch data from Verisure.""" try: diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index 99118850117..816243a454d 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -2,35 +2,36 @@ from __future__ import annotations import asyncio -from typing import Any, Callable +from typing import Callable, Iterable from homeassistant.components.lock import LockEntity +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_CODE, STATE_LOCKED, STATE_UNLOCKED from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import CONF_CODE_DIGITS, CONF_DEFAULT_LOCK_CODE, CONF_LOCKS, DOMAIN, LOGGER +from .const import ( + CONF_LOCK_CODE_DIGITS, + CONF_LOCK_DEFAULT_CODE, + DEFAULT_LOCK_CODE_DIGITS, + DOMAIN, + LOGGER, +) from .coordinator import VerisureDataUpdateCoordinator -def setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: dict[str, Any], - add_entities: Callable[[list[VerisureDoorlock]], None], - discovery_info: dict[str, Any] | None = None, + entry: ConfigEntry, + async_add_entities: Callable[[Iterable[Entity]], None], ) -> None: - """Set up the Verisure lock platform.""" - coordinator = hass.data[DOMAIN] - locks = [] - if int(coordinator.config.get(CONF_LOCKS, 1)): - locks.extend( - [ - VerisureDoorlock(coordinator, serial_number) - for serial_number in coordinator.data["locks"] - ] - ) - - add_entities(locks) + """Set up Verisure alarm control panel from a config entry.""" + coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + VerisureDoorlock(coordinator, serial_number) + for serial_number in coordinator.data["locks"] + ) class VerisureDoorlock(CoordinatorEntity, LockEntity): @@ -45,8 +46,9 @@ class VerisureDoorlock(CoordinatorEntity, LockEntity): super().__init__(coordinator) self.serial_number = serial_number self._state = None - self._digits = coordinator.config.get(CONF_CODE_DIGITS) - self._default_lock_code = coordinator.config.get(CONF_DEFAULT_LOCK_CODE) + self._digits = coordinator.entry.options.get( + CONF_LOCK_CODE_DIGITS, DEFAULT_LOCK_CODE_DIGITS + ) @property def name(self) -> str: @@ -80,7 +82,9 @@ class VerisureDoorlock(CoordinatorEntity, LockEntity): async def async_unlock(self, **kwargs) -> None: """Send unlock command.""" - code = kwargs.get(ATTR_CODE, self._default_lock_code) + code = kwargs.get( + ATTR_CODE, self.coordinator.entry.options.get(CONF_LOCK_DEFAULT_CODE) + ) if code is None: LOGGER.error("Code required but none provided") return @@ -89,7 +93,9 @@ class VerisureDoorlock(CoordinatorEntity, LockEntity): async def async_lock(self, **kwargs) -> None: """Send lock command.""" - code = kwargs.get(ATTR_CODE, self._default_lock_code) + code = kwargs.get( + ATTR_CODE, self.coordinator.entry.options.get(CONF_LOCK_DEFAULT_CODE) + ) if code is None: LOGGER.error("Code required but none provided") return diff --git a/homeassistant/components/verisure/manifest.json b/homeassistant/components/verisure/manifest.json index 05f07e926e0..074ef4f955c 100644 --- a/homeassistant/components/verisure/manifest.json +++ b/homeassistant/components/verisure/manifest.json @@ -3,5 +3,7 @@ "name": "Verisure", "documentation": "https://www.home-assistant.io/integrations/verisure", "requirements": ["vsure==1.7.3"], - "codeowners": ["@frenck"] + "codeowners": ["@frenck"], + "config_flow": true, + "dhcp": [{ "macaddress": "0023C1*" }] } diff --git a/homeassistant/components/verisure/sensor.py b/homeassistant/components/verisure/sensor.py index 2a4e4759369..37b02161879 100644 --- a/homeassistant/components/verisure/sensor.py +++ b/homeassistant/components/verisure/sensor.py @@ -1,54 +1,44 @@ """Support for Verisure sensors.""" from __future__ import annotations -from typing import Any, Callable +from typing import Callable, Iterable +from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE, TEMP_CELSIUS from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import CONF_HYDROMETERS, CONF_MOUSE, CONF_THERMOMETERS, DOMAIN +from .const import DOMAIN from .coordinator import VerisureDataUpdateCoordinator -def setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: dict[str, Any], - add_entities: Callable[[list[CoordinatorEntity], bool], None], - discovery_info: dict[str, Any] | None = None, + entry: ConfigEntry, + async_add_entities: Callable[[Iterable[Entity]], None], ) -> None: - """Set up the Verisure platform.""" - coordinator = hass.data[DOMAIN] + """Set up Verisure sensors based on a config entry.""" + coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - sensors: list[CoordinatorEntity] = [] - if int(coordinator.config.get(CONF_THERMOMETERS, 1)): - sensors.extend( - [ - VerisureThermometer(coordinator, serial_number) - for serial_number, values in coordinator.data["climate"].items() - if "temperature" in values - ] - ) + sensors: list[Entity] = [ + VerisureThermometer(coordinator, serial_number) + for serial_number, values in coordinator.data["climate"].items() + if "temperature" in values + ] - if int(coordinator.config.get(CONF_HYDROMETERS, 1)): - sensors.extend( - [ - VerisureHygrometer(coordinator, serial_number) - for serial_number, values in coordinator.data["climate"].items() - if "humidity" in values - ] - ) + sensors.extend( + VerisureHygrometer(coordinator, serial_number) + for serial_number, values in coordinator.data["climate"].items() + if "humidity" in values + ) - if int(coordinator.config.get(CONF_MOUSE, 1)): - sensors.extend( - [ - VerisureMouseDetection(coordinator, serial_number) - for serial_number in coordinator.data["mice"] - ] - ) + sensors.extend( + VerisureMouseDetection(coordinator, serial_number) + for serial_number in coordinator.data["mice"] + ) - add_entities(sensors) + async_add_entities(sensors) class VerisureThermometer(CoordinatorEntity, Entity): diff --git a/homeassistant/components/verisure/strings.json b/homeassistant/components/verisure/strings.json new file mode 100644 index 00000000000..0c7f513f8ee --- /dev/null +++ b/homeassistant/components/verisure/strings.json @@ -0,0 +1,39 @@ +{ + "config": { + "step": { + "user": { + "data": { + "description": "Sign-in with your Verisure My Pages account.", + "email": "[%key:common::config_flow::data::email%]", + "password": "[%key:common::config_flow::data::password%]" + } + }, + "installation": { + "description": "Home Assistant found multiple Verisure installations in your My Pages account. Please, select the installation to add to Home Assistant.", + "data": { + "giid": "Installation" + } + } + }, + "error": { + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + } + }, + "options": { + "step": { + "init": { + "data": { + "lock_code_digits": "Number of digits in PIN code for locks", + "lock_default_code": "Default PIN code for locks, used if none is given" + } + } + }, + "error": { + "code_format_mismatch": "The default PIN code does not match the required number of digits" + } + } +} diff --git a/homeassistant/components/verisure/switch.py b/homeassistant/components/verisure/switch.py index 9ce0d3ce5df..887d052bd81 100644 --- a/homeassistant/components/verisure/switch.py +++ b/homeassistant/components/verisure/switch.py @@ -2,33 +2,28 @@ from __future__ import annotations from time import monotonic -from typing import Any, Callable +from typing import Callable, Iterable from homeassistant.components.switch import SwitchEntity +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import CONF_SMARTPLUGS, DOMAIN +from .const import DOMAIN from .coordinator import VerisureDataUpdateCoordinator -def setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: dict[str, Any], - add_entities: Callable[[list[CoordinatorEntity]], None], - discovery_info: dict[str, Any] | None = None, + entry: ConfigEntry, + async_add_entities: Callable[[Iterable[Entity]], None], ) -> None: - """Set up the Verisure switch platform.""" - coordinator = hass.data[DOMAIN] - - if not int(coordinator.config.get(CONF_SMARTPLUGS, 1)): - return - - add_entities( - [ - VerisureSmartplug(coordinator, serial_number) - for serial_number in coordinator.data["smart_plugs"] - ] + """Set up Verisure alarm control panel from a config entry.""" + coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + VerisureSmartplug(coordinator, serial_number) + for serial_number in coordinator.data["smart_plugs"] ) @@ -51,6 +46,11 @@ class VerisureSmartplug(CoordinatorEntity, SwitchEntity): """Return the name or location of the smartplug.""" return self.coordinator.data["smart_plugs"][self.serial_number]["area"] + @property + def unique_id(self) -> str: + """Return the unique ID for this alarm control panel.""" + return self.serial_number + @property def is_on(self) -> bool: """Return true if on.""" diff --git a/homeassistant/components/verisure/translations/en.json b/homeassistant/components/verisure/translations/en.json new file mode 100644 index 00000000000..85c7acc167e --- /dev/null +++ b/homeassistant/components/verisure/translations/en.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Account is already configured" + }, + "error": { + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "installation": { + "data": { + "giid": "Installation" + }, + "description": "Home Assistant found multiple Verisure installations in your My Pages account. Please, select the installation to add to Home Assistant." + }, + "user": { + "data": { + "description": "Sign-in with your Verisure My Pages account.", + "email": "Email", + "password": "Password" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "The default PIN code does not match the required number of digits" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Number of digits in PIN code for locks", + "lock_default_code": "Default PIN code for locks, used if none is given" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 057ebe74865..a3bcef9047f 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -247,6 +247,7 @@ FLOWS = [ "upnp", "velbus", "vera", + "verisure", "vesync", "vilfo", "vizio", diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 31ee42bc48c..b3e10c90621 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -153,5 +153,9 @@ DHCP = [ "domain": "toon", "hostname": "eneco-*", "macaddress": "74C63B*" + }, + { + "domain": "verisure", + "macaddress": "0023C1*" } ] diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 836f9762a06..ed005d34d03 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1171,6 +1171,9 @@ uvcclient==0.11.0 # homeassistant.components.vilfo vilfo-api-client==0.3.2 +# homeassistant.components.verisure +vsure==1.7.3 + # homeassistant.components.vultr vultr==0.1.2 diff --git a/tests/components/verisure/__init__.py b/tests/components/verisure/__init__.py new file mode 100644 index 00000000000..08339e46c6f --- /dev/null +++ b/tests/components/verisure/__init__.py @@ -0,0 +1 @@ +"""Tests for the Verisure integration.""" diff --git a/tests/components/verisure/test_config_flow.py b/tests/components/verisure/test_config_flow.py new file mode 100644 index 00000000000..97f0f9731b1 --- /dev/null +++ b/tests/components/verisure/test_config_flow.py @@ -0,0 +1,467 @@ +"""Test the Verisure config flow.""" +from __future__ import annotations + +from unittest.mock import PropertyMock, patch + +import pytest +from verisure import Error as VerisureError, LoginError as VerisureLoginError + +from homeassistant import config_entries +from homeassistant.components.dhcp import MAC_ADDRESS +from homeassistant.components.verisure.const import ( + CONF_GIID, + CONF_LOCK_CODE_DIGITS, + CONF_LOCK_DEFAULT_CODE, + DEFAULT_LOCK_CODE_DIGITS, + DOMAIN, +) +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import ( + RESULT_TYPE_ABORT, + RESULT_TYPE_CREATE_ENTRY, + RESULT_TYPE_FORM, +) + +from tests.common import MockConfigEntry + +TEST_INSTALLATIONS = [ + {"giid": "12345", "alias": "ascending", "street": "12345th street"}, + {"giid": "54321", "alias": "descending", "street": "54321th street"}, +] +TEST_INSTALLATION = [TEST_INSTALLATIONS[0]] + + +async def test_full_user_flow_single_installation(hass: HomeAssistant) -> None: + """Test a full user initiated configuration flow with a single installation.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["step_id"] == "user" + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.verisure.config_flow.Verisure", + ) as mock_verisure, patch( + "homeassistant.components.verisure.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.verisure.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + type(mock_verisure.return_value).installations = PropertyMock( + return_value=TEST_INSTALLATION + ) + mock_verisure.login.return_value = True + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "email": "verisure_my_pages@example.com", + "password": "SuperS3cr3t!", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "ascending (12345th street)" + assert result2["data"] == { + CONF_GIID: "12345", + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_PASSWORD: "SuperS3cr3t!", + } + + assert len(mock_verisure.mock_calls) == 2 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_full_user_flow_multiple_installations(hass: HomeAssistant) -> None: + """Test a full user initiated configuration flow with multiple installations.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["step_id"] == "user" + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.verisure.config_flow.Verisure", + ) as mock_verisure: + type(mock_verisure.return_value).installations = PropertyMock( + return_value=TEST_INSTALLATIONS + ) + mock_verisure.login.return_value = True + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "email": "verisure_my_pages@example.com", + "password": "SuperS3cr3t!", + }, + ) + await hass.async_block_till_done() + + assert result2["step_id"] == "installation" + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] is None + + with patch( + "homeassistant.components.verisure.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.verisure.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], {"giid": "54321"} + ) + await hass.async_block_till_done() + + assert result3["type"] == RESULT_TYPE_CREATE_ENTRY + assert result3["title"] == "descending (54321th street)" + assert result3["data"] == { + CONF_GIID: "54321", + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_PASSWORD: "SuperS3cr3t!", + } + + assert len(mock_verisure.mock_calls) == 2 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_invalid_login(hass: HomeAssistant) -> None: + """Test a flow with an invalid Verisure My Pages login.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.verisure.config_flow.Verisure.login", + side_effect=VerisureLoginError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "email": "verisure_my_pages@example.com", + "password": "SuperS3cr3t!", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["step_id"] == "user" + assert result2["errors"] == {"base": "invalid_auth"} + + +async def test_unknown_error(hass: HomeAssistant) -> None: + """Test a flow with an invalid Verisure My Pages login.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.verisure.config_flow.Verisure.login", + side_effect=VerisureError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "email": "verisure_my_pages@example.com", + "password": "SuperS3cr3t!", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["step_id"] == "user" + assert result2["errors"] == {"base": "unknown"} + + +async def test_dhcp(hass: HomeAssistant) -> None: + """Test that DHCP discovery works.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + data={MAC_ADDRESS: "01:23:45:67:89:ab"}, + context={"source": config_entries.SOURCE_DHCP}, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "user" + + +@pytest.mark.parametrize( + "input,output", + [ + ( + { + CONF_LOCK_CODE_DIGITS: 5, + CONF_LOCK_DEFAULT_CODE: "12345", + }, + { + CONF_LOCK_CODE_DIGITS: 5, + CONF_LOCK_DEFAULT_CODE: "12345", + }, + ), + ( + { + CONF_LOCK_DEFAULT_CODE: "", + }, + { + CONF_LOCK_DEFAULT_CODE: "", + CONF_LOCK_CODE_DIGITS: DEFAULT_LOCK_CODE_DIGITS, + }, + ), + ], +) +async def test_options_flow( + hass: HomeAssistant, input: dict[str, int | str], output: dict[str, int | str] +) -> None: + """Test options config flow.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="12345", + data={}, + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.verisure.async_setup", return_value=True + ), patch( + "homeassistant.components.verisure.async_setup_entry", + return_value=True, + ): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input=input, + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["data"] == output + + +async def test_options_flow_code_format_mismatch(hass: HomeAssistant) -> None: + """Test options config flow with a code format mismatch.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="12345", + data={}, + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.verisure.async_setup", return_value=True + ), patch( + "homeassistant.components.verisure.async_setup_entry", + return_value=True, + ): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "init" + assert result["errors"] == {} + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_LOCK_CODE_DIGITS: 5, + CONF_LOCK_DEFAULT_CODE: "123", + }, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "init" + assert result["errors"] == {"base": "code_format_mismatch"} + + +# +# Below this line are tests that can be removed once the YAML configuration +# has been removed from this integration. +# +@pytest.mark.parametrize( + "giid,installations", + [ + ("12345", TEST_INSTALLATION), + ("12345", TEST_INSTALLATIONS), + (None, TEST_INSTALLATION), + ], +) +async def test_imports( + hass: HomeAssistant, giid: str | None, installations: dict[str, str] +) -> None: + """Test a YAML import with/without known giid on single/multiple installations.""" + with patch( + "homeassistant.components.verisure.config_flow.Verisure", + ) as mock_verisure, patch( + "homeassistant.components.verisure.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.verisure.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + type(mock_verisure.return_value).installations = PropertyMock( + return_value=installations + ) + mock_verisure.login.return_value = True + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_GIID: giid, + CONF_LOCK_CODE_DIGITS: 10, + CONF_LOCK_DEFAULT_CODE: "123456", + CONF_PASSWORD: "SuperS3cr3t!", + }, + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "ascending (12345th street)" + assert result["data"] == { + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_GIID: "12345", + CONF_LOCK_CODE_DIGITS: 10, + CONF_LOCK_DEFAULT_CODE: "123456", + CONF_PASSWORD: "SuperS3cr3t!", + } + + assert len(mock_verisure.mock_calls) == 2 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_imports_invalid_login(hass: HomeAssistant) -> None: + """Test a YAML import that results in a invalid login.""" + with patch( + "homeassistant.components.verisure.config_flow.Verisure.login", + side_effect=VerisureLoginError, + ): + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_GIID: None, + CONF_LOCK_CODE_DIGITS: None, + CONF_LOCK_DEFAULT_CODE: None, + CONF_PASSWORD: "SuperS3cr3t!", + }, + ) + + assert result["step_id"] == "user" + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {"base": "invalid_auth"} + + with patch( + "homeassistant.components.verisure.config_flow.Verisure", + ) as mock_verisure, patch( + "homeassistant.components.verisure.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.verisure.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + type(mock_verisure.return_value).installations = PropertyMock( + return_value=TEST_INSTALLATION + ) + mock_verisure.login.return_value = True + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "email": "verisure_my_pages@example.com", + "password": "SuperS3cr3t!", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "ascending (12345th street)" + assert result2["data"] == { + CONF_GIID: "12345", + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_PASSWORD: "SuperS3cr3t!", + } + + assert len(mock_verisure.mock_calls) == 2 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_imports_needs_user_installation_choice(hass: HomeAssistant) -> None: + """Test a YAML import that needs to use to decide on the installation.""" + with patch( + "homeassistant.components.verisure.config_flow.Verisure", + ) as mock_verisure: + type(mock_verisure.return_value).installations = PropertyMock( + return_value=TEST_INSTALLATIONS + ) + mock_verisure.login.return_value = True + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_GIID: None, + CONF_LOCK_CODE_DIGITS: None, + CONF_LOCK_DEFAULT_CODE: None, + CONF_PASSWORD: "SuperS3cr3t!", + }, + ) + + assert result["step_id"] == "installation" + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] is None + + with patch( + "homeassistant.components.verisure.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.verisure.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {"giid": "12345"} + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "ascending (12345th street)" + assert result2["data"] == { + CONF_GIID: "12345", + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_PASSWORD: "SuperS3cr3t!", + } + + assert len(mock_verisure.mock_calls) == 2 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +@pytest.mark.parametrize("giid", ["12345", None]) +async def test_import_already_exists(hass: HomeAssistant, giid: str | None) -> None: + """Test that import flow aborts if exists.""" + MockConfigEntry(domain=DOMAIN, data={}, unique_id="12345").add_to_hass(hass) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_PASSWORD: "SuperS3cr3t!", + CONF_GIID: giid, + }, + ) + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" From 40c12997ed5c366901661a5206f027e8ca8217f8 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 15 Mar 2021 20:51:24 +0100 Subject: [PATCH 385/831] Add zwave_js sensor humidity device class (#47953) --- homeassistant/components/zwave_js/sensor.py | 18 ++++++++++++------ tests/components/zwave_js/common.py | 1 + tests/components/zwave_js/test_sensor.py | 9 +++++++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index a6d44cb62d0..d6cc9aabd49 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -14,7 +14,12 @@ from homeassistant.components.sensor import ( DOMAIN as SENSOR_DOMAIN, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.const import ( + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -83,11 +88,12 @@ class ZwaveSensorBase(ZWaveBaseEntity): if self.info.primary_value.metadata.unit == "kWh": return DEVICE_CLASS_ENERGY return DEVICE_CLASS_POWER - if ( - isinstance(self.info.primary_value.property_, str) - and "temperature" in self.info.primary_value.property_.lower() - ): - return DEVICE_CLASS_TEMPERATURE + if isinstance(self.info.primary_value.property_, str): + property_lower = self.info.primary_value.property_.lower() + if "humidity" in property_lower: + return DEVICE_CLASS_HUMIDITY + if "temperature" in property_lower: + return DEVICE_CLASS_TEMPERATURE if self.info.primary_value.metadata.unit == "W": return DEVICE_CLASS_POWER if self.info.primary_value.metadata.unit == "Lux": diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index f90a2e48ffa..0592f89902f 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -1,5 +1,6 @@ """Provide common test tools for Z-Wave JS.""" AIR_TEMPERATURE_SENSOR = "sensor.multisensor_6_air_temperature" +HUMIDITY_SENSOR = "sensor.multisensor_6_humidity" ENERGY_SENSOR = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed_2" POWER_SENSOR = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed" SWITCH_ENTITY = "switch.smart_plug_with_two_usb_ports" diff --git a/tests/components/zwave_js/test_sensor.py b/tests/components/zwave_js/test_sensor.py index 398d831e692..7b4cd8bcce6 100644 --- a/tests/components/zwave_js/test_sensor.py +++ b/tests/components/zwave_js/test_sensor.py @@ -1,6 +1,7 @@ """Test the Z-Wave JS sensor platform.""" from homeassistant.const import ( DEVICE_CLASS_ENERGY, + DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, ENERGY_KILO_WATT_HOUR, @@ -12,6 +13,7 @@ from homeassistant.helpers import entity_registry as er from .common import ( AIR_TEMPERATURE_SENSOR, ENERGY_SENSOR, + HUMIDITY_SENSOR, NOTIFICATION_MOTION_SENSOR, POWER_SENSOR, ) @@ -26,6 +28,13 @@ async def test_numeric_sensor(hass, multisensor_6, integration): assert state.attributes["unit_of_measurement"] == TEMP_CELSIUS assert state.attributes["device_class"] == DEVICE_CLASS_TEMPERATURE + state = hass.states.get(HUMIDITY_SENSOR) + + assert state + assert state.state == "65.0" + assert state.attributes["unit_of_measurement"] == "%" + assert state.attributes["device_class"] == DEVICE_CLASS_HUMIDITY + async def test_energy_sensors(hass, hank_binary_switch, integration): """Test power and energy sensors.""" From 9fd973d8e8c00adbd31d816cad85af5ec46add6a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 15 Mar 2021 22:50:28 +0100 Subject: [PATCH 386/831] Move Verisure services to entity services (#47905) --- homeassistant/components/verisure/__init__.py | 46 ------------------- homeassistant/components/verisure/camera.py | 20 +++++++- homeassistant/components/verisure/const.py | 2 - .../components/verisure/coordinator.py | 8 ---- homeassistant/components/verisure/lock.py | 38 +++++++++++++++ .../components/verisure/services.yaml | 27 +++++++++-- 6 files changed, 79 insertions(+), 62 deletions(-) diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index e34bf5c5650..5f8b2119310 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -5,7 +5,6 @@ import asyncio import os from typing import Any -from verisure import Error as VerisureError import voluptuous as vol from homeassistant.components.alarm_control_panel import ( @@ -29,7 +28,6 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.storage import STORAGE_DIR from .const import ( - ATTR_DEVICE_SERIAL, CONF_CODE_DIGITS, CONF_DEFAULT_LOCK_CODE, CONF_GIID, @@ -38,9 +36,6 @@ from .const import ( DEFAULT_LOCK_CODE_DIGITS, DOMAIN, LOGGER, - SERVICE_CAPTURE_SMARTCAM, - SERVICE_DISABLE_AUTOLOCK, - SERVICE_ENABLE_AUTOLOCK, ) from .coordinator import VerisureDataUpdateCoordinator @@ -73,9 +68,6 @@ CONFIG_SCHEMA = vol.Schema( ) -DEVICE_SERIAL_SCHEMA = vol.Schema({vol.Required(ATTR_DEVICE_SERIAL): cv.string}) - - async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool: """Set up the Verisure integration.""" if DOMAIN in config: @@ -151,44 +143,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.config_entries.async_forward_entry_setup(entry, platform) ) - async def capture_smartcam(service): - """Capture a new picture from a smartcam.""" - device_id = service.data[ATTR_DEVICE_SERIAL] - try: - await hass.async_add_executor_job(coordinator.smartcam_capture, device_id) - LOGGER.debug("Capturing new image from %s", ATTR_DEVICE_SERIAL) - except VerisureError as ex: - LOGGER.error("Could not capture image, %s", ex) - - hass.services.async_register( - DOMAIN, SERVICE_CAPTURE_SMARTCAM, capture_smartcam, schema=DEVICE_SERIAL_SCHEMA - ) - - async def disable_autolock(service): - """Disable autolock on a doorlock.""" - device_id = service.data[ATTR_DEVICE_SERIAL] - try: - await hass.async_add_executor_job(coordinator.disable_autolock, device_id) - LOGGER.debug("Disabling autolock on%s", ATTR_DEVICE_SERIAL) - except VerisureError as ex: - LOGGER.error("Could not disable autolock, %s", ex) - - hass.services.async_register( - DOMAIN, SERVICE_DISABLE_AUTOLOCK, disable_autolock, schema=DEVICE_SERIAL_SCHEMA - ) - - async def enable_autolock(service): - """Enable autolock on a doorlock.""" - device_id = service.data[ATTR_DEVICE_SERIAL] - try: - await hass.async_add_executor_job(coordinator.enable_autolock, device_id) - LOGGER.debug("Enabling autolock on %s", ATTR_DEVICE_SERIAL) - except VerisureError as ex: - LOGGER.error("Could not enable autolock, %s", ex) - - hass.services.async_register( - DOMAIN, SERVICE_ENABLE_AUTOLOCK, enable_autolock, schema=DEVICE_SERIAL_SCHEMA - ) return True diff --git a/homeassistant/components/verisure/camera.py b/homeassistant/components/verisure/camera.py index ee9fe6577de..776211b5cf3 100644 --- a/homeassistant/components/verisure/camera.py +++ b/homeassistant/components/verisure/camera.py @@ -5,14 +5,17 @@ import errno import os from typing import Callable, Iterable +from verisure import Error as VerisureError + from homeassistant.components.camera import Camera from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_platform import current_platform from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN, LOGGER +from .const import DOMAIN, LOGGER, SERVICE_CAPTURE_SMARTCAM from .coordinator import VerisureDataUpdateCoordinator @@ -24,6 +27,13 @@ async def async_setup_entry( """Set up Verisure sensors based on a config entry.""" coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + platform = current_platform.get() + platform.async_register_entity_service( + SERVICE_CAPTURE_SMARTCAM, + {}, + VerisureSmartcam.capture_smartcam.__name__, + ) + assert hass.config.config_dir async_add_entities( VerisureSmartcam(hass, coordinator, serial_number, hass.config.config_dir) @@ -114,3 +124,11 @@ class VerisureSmartcam(CoordinatorEntity, Camera): def unique_id(self) -> str: """Return the unique ID for this camera.""" return self.serial_number + + def capture_smartcam(self) -> None: + """Capture a new picture from a smartcam.""" + try: + self.coordinator.smartcam_capture(self.serial_number) + LOGGER.debug("Capturing new image from %s", self.serial_number) + except VerisureError as ex: + LOGGER.error("Could not capture image, %s", ex) diff --git a/homeassistant/components/verisure/const.py b/homeassistant/components/verisure/const.py index 3e00eb1ddb3..107d146e708 100644 --- a/homeassistant/components/verisure/const.py +++ b/homeassistant/components/verisure/const.py @@ -6,8 +6,6 @@ DOMAIN = "verisure" LOGGER = logging.getLogger(__package__) -ATTR_DEVICE_SERIAL = "device_serial" - CONF_GIID = "giid" CONF_LOCK_CODE_DIGITS = "lock_code_digits" CONF_LOCK_DEFAULT_CODE = "lock_default_code" diff --git a/homeassistant/components/verisure/coordinator.py b/homeassistant/components/verisure/coordinator.py index 63eb5ac2f68..b118979f586 100644 --- a/homeassistant/components/verisure/coordinator.py +++ b/homeassistant/components/verisure/coordinator.py @@ -112,11 +112,3 @@ class VerisureDataUpdateCoordinator(DataUpdateCoordinator): def smartcam_capture(self, device_id: str) -> None: """Capture a new image from a smartcam.""" self.verisure.capture_image(device_id) - - def disable_autolock(self, device_id: str) -> None: - """Disable autolock.""" - self.verisure.set_lock_config(device_id, auto_lock_enabled=False) - - def enable_autolock(self, device_id: str) -> None: - """Enable autolock.""" - self.verisure.set_lock_config(device_id, auto_lock_enabled=True) diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index 816243a454d..0431d0aee41 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -4,11 +4,14 @@ from __future__ import annotations import asyncio from typing import Callable, Iterable +from verisure import Error as VerisureError + from homeassistant.components.lock import LockEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_CODE, STATE_LOCKED, STATE_UNLOCKED from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_platform import current_platform from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ( @@ -17,6 +20,8 @@ from .const import ( DEFAULT_LOCK_CODE_DIGITS, DOMAIN, LOGGER, + SERVICE_DISABLE_AUTOLOCK, + SERVICE_ENABLE_AUTOLOCK, ) from .coordinator import VerisureDataUpdateCoordinator @@ -28,6 +33,19 @@ async def async_setup_entry( ) -> None: """Set up Verisure alarm control panel from a config entry.""" coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + platform = current_platform.get() + platform.async_register_entity_service( + SERVICE_DISABLE_AUTOLOCK, + {}, + VerisureDoorlock.disable_autolock.__name__, + ) + platform.async_register_entity_service( + SERVICE_ENABLE_AUTOLOCK, + {}, + VerisureDoorlock.enable_autolock.__name__, + ) + async_add_entities( VerisureDoorlock(coordinator, serial_number) for serial_number in coordinator.data["locks"] @@ -127,3 +145,23 @@ class VerisureDoorlock(CoordinatorEntity, LockEntity): await asyncio.sleep(0.5) if transaction["result"] == "OK": self._state = state + + def disable_autolock(self) -> None: + """Disable autolock on a doorlock.""" + try: + self.coordinator.verisure.set_lock_config( + self.serial_number, auto_lock_enabled=False + ) + LOGGER.debug("Disabling autolock on %s", self.serial_number) + except VerisureError as ex: + LOGGER.error("Could not disable autolock, %s", ex) + + def enable_autolock(self) -> None: + """Enable autolock on a doorlock.""" + try: + self.coordinator.verisure.set_lock_config( + self.serial_number, auto_lock_enabled=True + ) + LOGGER.debug("Enabling autolock on %s", self.serial_number) + except VerisureError as ex: + LOGGER.error("Could not enable autolock, %s", ex) diff --git a/homeassistant/components/verisure/services.yaml b/homeassistant/components/verisure/services.yaml index 885b8597549..2a4e2a008be 100644 --- a/homeassistant/components/verisure/services.yaml +++ b/homeassistant/components/verisure/services.yaml @@ -1,6 +1,23 @@ capture_smartcam: - description: Capture a new image from a smartcam. - fields: - device_serial: - description: The serial number of the smartcam you want to capture an image from. - example: 2DEU AT5Z + name: Capture SmartCam image + description: Capture a new image from a Verisure SmartCam + target: + entity: + integration: verisure + domain: camera + +enable_autolock: + name: Enable autolock + description: Enable autolock of a Verisure Lockguard Smartlock + target: + entity: + integration: verisure + domain: lock + +disable_autolock: + name: Disable autolock + description: Disable autolock of a Verisure Lockguard Smartlock + target: + entity: + integration: verisure + domain: lock From 5f627df6f8d07331e8385f3e24efa0a0e92abdbf Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 15 Mar 2021 23:59:41 +0100 Subject: [PATCH 387/831] Add devices to Verisure integration (#47913) Co-authored-by: Paulus Schoutsen --- .../verisure/alarm_control_panel.py | 12 ++++- .../components/verisure/binary_sensor.py | 32 +++++++++++- homeassistant/components/verisure/camera.py | 37 +++++++++----- homeassistant/components/verisure/const.py | 14 ++++++ homeassistant/components/verisure/lock.py | 21 +++++++- homeassistant/components/verisure/sensor.py | 49 ++++++++++++++++++- homeassistant/components/verisure/switch.py | 19 +++++-- 7 files changed, 163 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index 8a2a05e2005..54e36ac65de 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio -from typing import Callable, Iterable +from typing import Any, Callable, Iterable from homeassistant.components.alarm_control_panel import ( FORMAT_NUMBER, @@ -56,6 +56,16 @@ class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity): """Return the unique ID for this alarm control panel.""" return self.coordinator.entry.data[CONF_GIID] + @property + def device_info(self) -> dict[str, Any]: + """Return device information about this entity.""" + return { + "name": "Verisure Alarm", + "manufacturer": "Verisure", + "model": "VBox", + "identifiers": {(DOMAIN, self.coordinator.entry.data[CONF_GIID])}, + } + @property def state(self) -> str | None: """Return the state of the device.""" diff --git a/homeassistant/components/verisure/binary_sensor.py b/homeassistant/components/verisure/binary_sensor.py index 4289a6f8ffc..864785c7cd0 100644 --- a/homeassistant/components/verisure/binary_sensor.py +++ b/homeassistant/components/verisure/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Verisure binary sensors.""" from __future__ import annotations -from typing import Callable, Iterable +from typing import Any, Callable, Iterable from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, @@ -13,7 +13,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import DOMAIN +from .const import CONF_GIID, DOMAIN from .coordinator import VerisureDataUpdateCoordinator @@ -57,6 +57,19 @@ class VerisureDoorWindowSensor(CoordinatorEntity, BinarySensorEntity): """Return the unique ID for this alarm control panel.""" return f"{self.serial_number}_door_window" + @property + def device_info(self) -> dict[str, Any]: + """Return device information about this entity.""" + area = self.coordinator.data["door_window"][self.serial_number]["area"] + return { + "name": area, + "suggested_area": area, + "manufacturer": "Verisure", + "model": "Shock Sensor Detector", + "identifiers": {(DOMAIN, self.serial_number)}, + "via_device": (DOMAIN, self.coordinator.entry.data[CONF_GIID]), + } + @property def device_class(self) -> str: """Return the class of this device, from component DEVICE_CLASSES.""" @@ -88,6 +101,21 @@ class VerisureEthernetStatus(CoordinatorEntity, BinarySensorEntity): """Return the name of the binary sensor.""" return "Verisure Ethernet status" + @property + def unique_id(self) -> str: + """Return the unique ID for this binary sensor.""" + return f"{self.coordinator.entry.data[CONF_GIID]}_ethernet" + + @property + def device_info(self) -> dict[str, Any]: + """Return device information about this entity.""" + return { + "name": "Verisure Alarm", + "manufacturer": "Verisure", + "model": "VBox", + "identifiers": {(DOMAIN, self.coordinator.entry.data[CONF_GIID])}, + } + @property def is_on(self) -> bool: """Return the state of the sensor.""" diff --git a/homeassistant/components/verisure/camera.py b/homeassistant/components/verisure/camera.py index 776211b5cf3..006f9c28ef5 100644 --- a/homeassistant/components/verisure/camera.py +++ b/homeassistant/components/verisure/camera.py @@ -3,7 +3,7 @@ from __future__ import annotations import errno import os -from typing import Callable, Iterable +from typing import Any, Callable, Iterable from verisure import Error as VerisureError @@ -15,7 +15,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_platform import current_platform from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN, LOGGER, SERVICE_CAPTURE_SMARTCAM +from .const import CONF_GIID, DOMAIN, LOGGER, SERVICE_CAPTURE_SMARTCAM from .coordinator import VerisureDataUpdateCoordinator @@ -62,6 +62,29 @@ class VerisureSmartcam(CoordinatorEntity, Camera): self._image_id = None hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.delete_image) + @property + def name(self) -> str: + """Return the name of this camera.""" + return self.coordinator.data["cameras"][self.serial_number]["area"] + + @property + def unique_id(self) -> str: + """Return the unique ID for this camera.""" + return self.serial_number + + @property + def device_info(self) -> dict[str, Any]: + """Return device information about this entity.""" + area = self.coordinator.data["cameras"][self.serial_number]["area"] + return { + "name": area, + "suggested_area": area, + "manufacturer": "Verisure", + "model": "SmartCam", + "identifiers": {(DOMAIN, self.serial_number)}, + "via_device": (DOMAIN, self.coordinator.entry.data[CONF_GIID]), + } + def camera_image(self) -> bytes | None: """Return image response.""" self.check_imagelist() @@ -115,16 +138,6 @@ class VerisureSmartcam(CoordinatorEntity, Camera): if error.errno != errno.ENOENT: raise - @property - def name(self) -> str: - """Return the name of this camera.""" - return self.coordinator.data["cameras"][self.serial_number]["area"] - - @property - def unique_id(self) -> str: - """Return the unique ID for this camera.""" - return self.serial_number - def capture_smartcam(self) -> None: """Capture a new picture from a smartcam.""" try: diff --git a/homeassistant/components/verisure/const.py b/homeassistant/components/verisure/const.py index 107d146e708..8e39e0594dd 100644 --- a/homeassistant/components/verisure/const.py +++ b/homeassistant/components/verisure/const.py @@ -17,6 +17,20 @@ SERVICE_CAPTURE_SMARTCAM = "capture_smartcam" SERVICE_DISABLE_AUTOLOCK = "disable_autolock" SERVICE_ENABLE_AUTOLOCK = "enable_autolock" +# Mapping of device types to a human readable name +DEVICE_TYPE_NAME = { + "CAMERAPIR2": "Camera detector", + "HOMEPAD1": "VoiceBox", + "HUMIDITY1": "Climate sensor", + "PIR2": "Camera detector", + "SIREN1": "Siren", + "SMARTCAMERA1": "SmartCam", + "SMOKE2": "Smoke detector", + "SMOKE3": "Smoke detector", + "VOICEBOX1": "VoiceBox", + "WATER1": "Water detector", +} + # Legacy; to remove after YAML removal CONF_CODE_DIGITS = "code_digits" CONF_DEFAULT_LOCK_CODE = "default_lock_code" diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index 0431d0aee41..d4708c68e88 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio -from typing import Callable, Iterable +from typing import Any, Callable, Iterable from verisure import Error as VerisureError @@ -15,6 +15,7 @@ from homeassistant.helpers.entity_platform import current_platform from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ( + CONF_GIID, CONF_LOCK_CODE_DIGITS, CONF_LOCK_DEFAULT_CODE, DEFAULT_LOCK_CODE_DIGITS, @@ -73,6 +74,24 @@ class VerisureDoorlock(CoordinatorEntity, LockEntity): """Return the name of the lock.""" return self.coordinator.data["locks"][self.serial_number]["area"] + @property + def unique_id(self) -> str: + """Return the unique ID for this entity.""" + return self.serial_number + + @property + def device_info(self) -> dict[str, Any]: + """Return device information about this entity.""" + area = self.coordinator.data["locks"][self.serial_number]["area"] + return { + "name": area, + "suggested_area": area, + "manufacturer": "Verisure", + "model": "Lockguard Smartlock", + "identifiers": {(DOMAIN, self.serial_number)}, + "via_device": (DOMAIN, self.coordinator.entry.data[CONF_GIID]), + } + @property def available(self) -> bool: """Return True if entity is available.""" diff --git a/homeassistant/components/verisure/sensor.py b/homeassistant/components/verisure/sensor.py index 37b02161879..614bd0a386d 100644 --- a/homeassistant/components/verisure/sensor.py +++ b/homeassistant/components/verisure/sensor.py @@ -1,7 +1,7 @@ """Support for Verisure sensors.""" from __future__ import annotations -from typing import Callable, Iterable +from typing import Any, Callable, Iterable from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE, TEMP_CELSIUS @@ -9,7 +9,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN +from .const import CONF_GIID, DEVICE_TYPE_NAME, DOMAIN from .coordinator import VerisureDataUpdateCoordinator @@ -64,6 +64,22 @@ class VerisureThermometer(CoordinatorEntity, Entity): """Return the unique ID for this entity.""" return f"{self.serial_number}_temperature" + @property + def device_info(self) -> dict[str, Any]: + """Return device information about this entity.""" + device_type = self.coordinator.data["climate"][self.serial_number].get( + "deviceType" + ) + area = self.coordinator.data["climate"][self.serial_number]["deviceArea"] + return { + "name": area, + "suggested_area": area, + "manufacturer": "Verisure", + "model": DEVICE_TYPE_NAME.get(device_type, device_type), + "identifiers": {(DOMAIN, self.serial_number)}, + "via_device": (DOMAIN, self.coordinator.entry.data[CONF_GIID]), + } + @property def state(self) -> str | None: """Return the state of the entity.""" @@ -107,6 +123,22 @@ class VerisureHygrometer(CoordinatorEntity, Entity): """Return the unique ID for this entity.""" return f"{self.serial_number}_humidity" + @property + def device_info(self) -> dict[str, Any]: + """Return device information about this entity.""" + device_type = self.coordinator.data["climate"][self.serial_number].get( + "deviceType" + ) + area = self.coordinator.data["climate"][self.serial_number]["deviceArea"] + return { + "name": area, + "suggested_area": area, + "manufacturer": "Verisure", + "model": DEVICE_TYPE_NAME.get(device_type, device_type), + "identifiers": {(DOMAIN, self.serial_number)}, + "via_device": (DOMAIN, self.coordinator.entry.data[CONF_GIID]), + } + @property def state(self) -> str | None: """Return the state of the entity.""" @@ -150,6 +182,19 @@ class VerisureMouseDetection(CoordinatorEntity, Entity): """Return the unique ID for this entity.""" return f"{self.serial_number}_mice" + @property + def device_info(self) -> dict[str, Any]: + """Return device information about this entity.""" + area = self.coordinator.data["mice"][self.serial_number]["area"] + return { + "name": area, + "suggested_area": area, + "manufacturer": "Verisure", + "model": "Mouse detector", + "identifiers": {(DOMAIN, self.serial_number)}, + "via_device": (DOMAIN, self.coordinator.entry.data[CONF_GIID]), + } + @property def state(self) -> str | None: """Return the state of the device.""" diff --git a/homeassistant/components/verisure/switch.py b/homeassistant/components/verisure/switch.py index 887d052bd81..534db9c7a50 100644 --- a/homeassistant/components/verisure/switch.py +++ b/homeassistant/components/verisure/switch.py @@ -2,7 +2,7 @@ from __future__ import annotations from time import monotonic -from typing import Callable, Iterable +from typing import Any, Callable, Iterable from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry @@ -10,7 +10,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN +from .const import CONF_GIID, DOMAIN from .coordinator import VerisureDataUpdateCoordinator @@ -48,9 +48,22 @@ class VerisureSmartplug(CoordinatorEntity, SwitchEntity): @property def unique_id(self) -> str: - """Return the unique ID for this alarm control panel.""" + """Return the unique ID for this entity.""" return self.serial_number + @property + def device_info(self) -> dict[str, Any]: + """Return device information about this entity.""" + area = self.coordinator.data["smart_plugs"][self.serial_number]["area"] + return { + "name": area, + "suggested_area": area, + "manufacturer": "Verisure", + "model": "SmartPlug", + "identifiers": {(DOMAIN, self.serial_number)}, + "via_device": (DOMAIN, self.coordinator.entry.data[CONF_GIID]), + } + @property def is_on(self) -> bool: """Return true if on.""" From f82e59c32a10e093d00401b4daeeaa368548cd9d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 16 Mar 2021 00:51:04 +0100 Subject: [PATCH 388/831] Make it possible to list debug traces for a specific automation (#47744) --- homeassistant/components/automation/trace.py | 7 +- .../components/automation/websocket_api.py | 17 +++- homeassistant/helpers/condition.py | 4 +- homeassistant/helpers/script.py | 4 +- homeassistant/helpers/trace.py | 7 +- .../automation/test_websocket_api.py | 86 +++++++++++++------ 6 files changed, 86 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/automation/trace.py b/homeassistant/components/automation/trace.py index ebd981f2541..351ca1ed979 100644 --- a/homeassistant/components/automation/trace.py +++ b/homeassistant/components/automation/trace.py @@ -109,6 +109,7 @@ class AutomationTrace: trigger = self._variables.get("trigger", {}).get("description") result = { + "automation_id": self._unique_id, "last_action": last_action, "last_condition": last_condition, "run_id": self.run_id, @@ -196,11 +197,9 @@ def get_debug_traces_for_automation(hass, automation_id, summary=False): @callback def get_debug_traces(hass, summary=False): """Return a serializable list of debug traces.""" - traces = {} + traces = [] for automation_id in hass.data[DATA_AUTOMATION_TRACE]: - traces[automation_id] = get_debug_traces_for_automation( - hass, automation_id, summary - ) + traces.extend(get_debug_traces_for_automation(hass, automation_id, summary)) return traces diff --git a/homeassistant/components/automation/websocket_api.py b/homeassistant/components/automation/websocket_api.py index aaebbef7f83..bb47dd58ff9 100644 --- a/homeassistant/components/automation/websocket_api.py +++ b/homeassistant/components/automation/websocket_api.py @@ -21,7 +21,7 @@ from homeassistant.helpers.script import ( debug_stop, ) -from .trace import get_debug_trace, get_debug_traces +from .trace import get_debug_trace, get_debug_traces, get_debug_traces_for_automation # mypy: allow-untyped-calls, allow-untyped-defs @@ -50,7 +50,7 @@ def async_setup(hass: HomeAssistant) -> None: } ) def websocket_automation_trace_get(hass, connection, msg): - """Get automation traces.""" + """Get an automation trace.""" automation_id = msg["automation_id"] run_id = msg["run_id"] @@ -61,10 +61,19 @@ def websocket_automation_trace_get(hass, connection, msg): @callback @websocket_api.require_admin -@websocket_api.websocket_command({vol.Required("type"): "automation/trace/list"}) +@websocket_api.websocket_command( + {vol.Required("type"): "automation/trace/list", vol.Optional("automation_id"): str} +) def websocket_automation_trace_list(hass, connection, msg): """Summarize automation traces.""" - automation_traces = get_debug_traces(hass, summary=True) + automation_id = msg.get("automation_id") + + if not automation_id: + automation_traces = get_debug_traces(hass, summary=True) + else: + automation_traces = get_debug_traces_for_automation( + hass, automation_id, summary=True + ) connection.send_result(msg["id"], automation_traces) diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 71bbaa5f0f4..10b59645ed0 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -77,8 +77,8 @@ ConditionCheckerType = Callable[[HomeAssistant, TemplateVarsType], bool] def condition_trace_append(variables: TemplateVarsType, path: str) -> TraceElement: """Append a TraceElement to trace[path].""" - trace_element = TraceElement(variables) - trace_append_element(trace_element, path) + trace_element = TraceElement(variables, path) + trace_append_element(trace_element) return trace_element diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index a2df055331d..f1732aef316 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -137,8 +137,8 @@ SCRIPT_DEBUG_CONTINUE_ALL = "script_debug_continue_all" def action_trace_append(variables, path): """Append a TraceElement to trace[path].""" - trace_element = TraceElement(variables) - trace_append_element(trace_element, path, ACTION_TRACE_NODE_MAX_LEN) + trace_element = TraceElement(variables, path) + trace_append_element(trace_element, ACTION_TRACE_NODE_MAX_LEN) return trace_element diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py index e0c67a1de54..a7cd63de479 100644 --- a/homeassistant/helpers/trace.py +++ b/homeassistant/helpers/trace.py @@ -11,9 +11,10 @@ import homeassistant.util.dt as dt_util class TraceElement: """Container for trace data.""" - def __init__(self, variables: TemplateVarsType): + def __init__(self, variables: TemplateVarsType, path: str): """Container for trace data.""" self._error: Optional[Exception] = None + self.path: str = path self._result: Optional[dict] = None self._timestamp = dt_util.utcnow() @@ -42,7 +43,7 @@ class TraceElement: def as_dict(self) -> Dict[str, Any]: """Return dictionary version of this TraceElement.""" - result: Dict[str, Any] = {"timestamp": self._timestamp} + result: Dict[str, Any] = {"path": self.path, "timestamp": self._timestamp} if self._variables: result["changed_variables"] = self._variables if self._error is not None: @@ -129,10 +130,10 @@ def trace_path_get() -> str: def trace_append_element( trace_element: TraceElement, - path: str, maxlen: Optional[int] = None, ) -> None: """Append a TraceElement to trace[path].""" + path = trace_element.path trace = trace_cv.get() if trace is None: trace = {} diff --git a/tests/components/automation/test_websocket_api.py b/tests/components/automation/test_websocket_api.py index 99b9540b06e..84c85e224e5 100644 --- a/tests/components/automation/test_websocket_api.py +++ b/tests/components/automation/test_websocket_api.py @@ -9,6 +9,20 @@ from tests.common import assert_lists_same from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 +def _find_run_id(traces, automation_id): + """Find newest run_id for an automation.""" + for trace in reversed(traces): + if trace["automation_id"] == automation_id: + return trace["run_id"] + + return None + + +def _find_traces_for_automation(traces, automation_id): + """Find traces for an automation.""" + return [trace for trace in traces if trace["automation_id"] == automation_id] + + async def test_get_automation_trace(hass, hass_ws_client): """Test tracing an automation.""" id = 1 @@ -61,7 +75,7 @@ async def test_get_automation_trace(hass, hass_ws_client): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - run_id = response["result"]["sun"][-1]["run_id"] + run_id = _find_run_id(response["result"], "sun") # Get trace await client.send_json( @@ -97,7 +111,7 @@ async def test_get_automation_trace(hass, hass_ws_client): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - run_id = response["result"]["moon"][-1]["run_id"] + run_id = _find_run_id(response["result"], "moon") # Get trace await client.send_json( @@ -134,7 +148,7 @@ async def test_get_automation_trace(hass, hass_ws_client): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - run_id = response["result"]["moon"][-1]["run_id"] + run_id = _find_run_id(response["result"], "moon") # Get trace await client.send_json( @@ -168,7 +182,7 @@ async def test_get_automation_trace(hass, hass_ws_client): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - run_id = response["result"]["moon"][-1]["run_id"] + run_id = _find_run_id(response["result"], "moon") # Get trace await client.send_json( @@ -237,7 +251,7 @@ async def test_automation_trace_overflow(hass, hass_ws_client): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - assert response["result"] == {} + assert response["result"] == [] # Trigger "sun" and "moon" automation once hass.bus.async_fire("test_event") @@ -248,9 +262,9 @@ async def test_automation_trace_overflow(hass, hass_ws_client): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - assert len(response["result"]["moon"]) == 1 - moon_run_id = response["result"]["moon"][0]["run_id"] - assert len(response["result"]["sun"]) == 1 + assert len(_find_traces_for_automation(response["result"], "moon")) == 1 + moon_run_id = _find_run_id(response["result"], "moon") + assert len(_find_traces_for_automation(response["result"], "sun")) == 1 # Trigger "moon" automation enough times to overflow the number of stored traces for _ in range(automation.trace.STORED_TRACES): @@ -260,13 +274,15 @@ async def test_automation_trace_overflow(hass, hass_ws_client): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - assert len(response["result"]["moon"]) == automation.trace.STORED_TRACES - assert len(response["result"]["sun"]) == 1 - assert int(response["result"]["moon"][0]["run_id"]) == int(moon_run_id) + 1 + moon_traces = _find_traces_for_automation(response["result"], "moon") + assert len(moon_traces) == automation.trace.STORED_TRACES + assert moon_traces[0] + assert int(moon_traces[0]["run_id"]) == int(moon_run_id) + 1 assert ( - int(response["result"]["moon"][-1]["run_id"]) + int(moon_traces[-1]["run_id"]) == int(moon_run_id) + automation.trace.STORED_TRACES ) + assert len(_find_traces_for_automation(response["result"], "sun")) == 1 async def test_list_automation_traces(hass, hass_ws_client): @@ -315,7 +331,14 @@ async def test_list_automation_traces(hass, hass_ws_client): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - assert response["result"] == {} + assert response["result"] == [] + + await client.send_json( + {"id": next_id(), "type": "automation/trace/list", "automation_id": "sun"} + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == [] # Trigger "sun" automation hass.bus.async_fire("test_event") @@ -325,8 +348,23 @@ async def test_list_automation_traces(hass, hass_ws_client): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - assert "moon" not in response["result"] - assert len(response["result"]["sun"]) == 1 + assert len(response["result"]) == 1 + assert len(_find_traces_for_automation(response["result"], "sun")) == 1 + + await client.send_json( + {"id": next_id(), "type": "automation/trace/list", "automation_id": "sun"} + ) + response = await client.receive_json() + assert response["success"] + assert len(response["result"]) == 1 + assert len(_find_traces_for_automation(response["result"], "sun")) == 1 + + await client.send_json( + {"id": next_id(), "type": "automation/trace/list", "automation_id": "moon"} + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == [] # Trigger "moon" automation, with passing condition hass.bus.async_fire("test_event2") @@ -344,9 +382,9 @@ async def test_list_automation_traces(hass, hass_ws_client): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - assert len(response["result"]["moon"]) == 3 - assert len(response["result"]["sun"]) == 1 - trace = response["result"]["sun"][0] + assert len(_find_traces_for_automation(response["result"], "moon")) == 3 + assert len(_find_traces_for_automation(response["result"], "sun")) == 1 + trace = _find_traces_for_automation(response["result"], "sun")[0] assert trace["last_action"] == "action/0" assert trace["last_condition"] is None assert trace["error"] == "Unable to find service test.automation" @@ -355,7 +393,7 @@ async def test_list_automation_traces(hass, hass_ws_client): assert trace["trigger"] == "event 'test_event'" assert trace["unique_id"] == "sun" - trace = response["result"]["moon"][0] + trace = _find_traces_for_automation(response["result"], "moon")[0] assert trace["last_action"] == "action/0" assert trace["last_condition"] == "condition/0" assert "error" not in trace @@ -364,7 +402,7 @@ async def test_list_automation_traces(hass, hass_ws_client): assert trace["trigger"] == "event 'test_event2'" assert trace["unique_id"] == "moon" - trace = response["result"]["moon"][1] + trace = _find_traces_for_automation(response["result"], "moon")[1] assert trace["last_action"] is None assert trace["last_condition"] == "condition/0" assert "error" not in trace @@ -373,7 +411,7 @@ async def test_list_automation_traces(hass, hass_ws_client): assert trace["trigger"] == "event 'test_event3'" assert trace["unique_id"] == "moon" - trace = response["result"]["moon"][2] + trace = _find_traces_for_automation(response["result"], "moon")[2] assert trace["last_action"] == "action/0" assert trace["last_condition"] == "condition/0" assert "error" not in trace @@ -396,7 +434,7 @@ async def test_automation_breakpoints(hass, hass_ws_client): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - trace = response["result"][automation_id][-1] + trace = _find_traces_for_automation(response["result"], automation_id)[-1] assert trace["last_action"] == expected_action assert trace["state"] == expected_state return trace["run_id"] @@ -567,7 +605,7 @@ async def test_automation_breakpoints_2(hass, hass_ws_client): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - trace = response["result"][automation_id][-1] + trace = _find_traces_for_automation(response["result"], automation_id)[-1] assert trace["last_action"] == expected_action assert trace["state"] == expected_state return trace["run_id"] @@ -674,7 +712,7 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - trace = response["result"][automation_id][-1] + trace = _find_traces_for_automation(response["result"], automation_id)[-1] assert trace["last_action"] == expected_action assert trace["state"] == expected_state return trace["run_id"] From c11b85af2f9938a1c3225e2bc3efc9a273e79420 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Tue, 16 Mar 2021 00:04:36 +0000 Subject: [PATCH 389/831] [ci skip] Translation update --- .../components/abode/translations/he.json | 11 ++ .../components/abode/translations/hu.json | 11 +- .../components/abode/translations/id.json | 35 ++++ .../components/abode/translations/ko.json | 14 +- .../accuweather/translations/he.json | 11 ++ .../accuweather/translations/hu.json | 29 +++ .../accuweather/translations/id.json | 41 ++++ .../accuweather/translations/ko.json | 20 +- .../accuweather/translations/nl.json | 5 + .../accuweather/translations/sensor.hu.json | 9 + .../accuweather/translations/sensor.id.json | 9 + .../accuweather/translations/sensor.ko.json | 9 + .../components/acmeda/translations/hu.json | 7 + .../components/acmeda/translations/id.json | 15 ++ .../components/adguard/translations/bg.json | 4 +- .../components/adguard/translations/ca.json | 2 +- .../components/adguard/translations/cs.json | 4 +- .../components/adguard/translations/da.json | 4 +- .../components/adguard/translations/de.json | 4 +- .../adguard/translations/es-419.json | 4 +- .../components/adguard/translations/es.json | 4 +- .../components/adguard/translations/et.json | 2 +- .../components/adguard/translations/he.json | 1 + .../components/adguard/translations/hu.json | 7 +- .../components/adguard/translations/id.json | 21 +- .../components/adguard/translations/it.json | 2 +- .../components/adguard/translations/ko.json | 6 +- .../components/adguard/translations/lb.json | 4 +- .../components/adguard/translations/nl.json | 4 +- .../components/adguard/translations/no.json | 2 +- .../components/adguard/translations/pl.json | 2 +- .../adguard/translations/pt-BR.json | 4 +- .../components/adguard/translations/pt.json | 2 +- .../components/adguard/translations/sl.json | 4 +- .../components/adguard/translations/sv.json | 4 +- .../components/adguard/translations/uk.json | 4 +- .../adguard/translations/zh-Hant.json | 4 +- .../advantage_air/translations/hu.json | 3 + .../advantage_air/translations/id.json | 20 ++ .../advantage_air/translations/ko.json | 4 +- .../components/aemet/translations/hu.json | 21 ++ .../components/aemet/translations/id.json | 22 +++ .../components/aemet/translations/ko.json | 3 +- .../components/aemet/translations/lb.json | 12 ++ .../components/aemet/translations/nl.json | 1 + .../components/agent_dvr/translations/hu.json | 4 + .../components/agent_dvr/translations/id.json | 20 ++ .../components/airly/translations/de.json | 4 +- .../components/airly/translations/es.json | 4 +- .../components/airly/translations/et.json | 4 +- .../components/airly/translations/fr.json | 4 +- .../components/airly/translations/he.json | 11 ++ .../components/airly/translations/hu.json | 11 +- .../components/airly/translations/id.json | 30 +++ .../components/airly/translations/it.json | 4 +- .../components/airly/translations/ko.json | 9 +- .../components/airly/translations/nl.json | 7 + .../components/airly/translations/no.json | 4 +- .../components/airly/translations/pl.json | 4 +- .../airly/translations/zh-Hans.json | 3 +- .../components/airnow/translations/bg.json | 7 + .../components/airnow/translations/de.json | 1 + .../components/airnow/translations/hu.json | 24 +++ .../components/airnow/translations/id.json | 26 +++ .../components/airnow/translations/ko.json | 11 +- .../components/airvisual/translations/bg.json | 12 ++ .../components/airvisual/translations/he.json | 8 +- .../components/airvisual/translations/hu.json | 27 ++- .../components/airvisual/translations/id.json | 77 ++++++++ .../components/airvisual/translations/ko.json | 21 +- .../components/airvisual/translations/nl.json | 12 +- .../alarm_control_panel/translations/id.json | 39 +++- .../alarm_control_panel/translations/ko.json | 42 ++-- .../translations/zh-Hans.json | 23 +++ .../alarmdecoder/translations/hu.json | 30 +++ .../alarmdecoder/translations/id.json | 74 +++++++ .../alarmdecoder/translations/ko.json | 36 ++-- .../components/almond/translations/ca.json | 2 +- .../components/almond/translations/cs.json | 4 +- .../components/almond/translations/da.json | 4 +- .../components/almond/translations/de.json | 4 +- .../almond/translations/es-419.json | 4 +- .../components/almond/translations/es.json | 4 +- .../components/almond/translations/et.json | 2 +- .../components/almond/translations/fr.json | 4 +- .../components/almond/translations/hu.json | 12 +- .../components/almond/translations/id.json | 19 ++ .../components/almond/translations/it.json | 2 +- .../components/almond/translations/ko.json | 6 +- .../components/almond/translations/lb.json | 4 +- .../components/almond/translations/nl.json | 8 +- .../components/almond/translations/no.json | 2 +- .../components/almond/translations/sl.json | 4 +- .../components/almond/translations/sv.json | 4 +- .../components/almond/translations/uk.json | 4 +- .../almond/translations/zh-Hant.json | 4 +- .../ambiclimate/translations/hu.json | 6 +- .../ambiclimate/translations/id.json | 22 +++ .../ambiclimate/translations/ko.json | 4 +- .../ambient_station/translations/hu.json | 5 +- .../ambient_station/translations/id.json | 20 ++ .../ambient_station/translations/nl.json | 2 +- .../components/apple_tv/translations/hu.json | 13 +- .../components/apple_tv/translations/id.json | 64 ++++++ .../components/apple_tv/translations/ko.json | 45 ++++- .../components/apple_tv/translations/nl.json | 23 +++ .../components/arcam_fmj/translations/hu.json | 10 + .../components/arcam_fmj/translations/id.json | 27 +++ .../components/arcam_fmj/translations/ko.json | 4 +- .../components/arcam_fmj/translations/pt.json | 2 + .../components/asuswrt/translations/bg.json | 17 ++ .../components/asuswrt/translations/hu.json | 33 ++++ .../components/asuswrt/translations/id.json | 45 +++++ .../components/asuswrt/translations/ko.json | 27 ++- .../components/asuswrt/translations/nl.json | 9 + .../components/atag/translations/hu.json | 4 + .../components/atag/translations/id.json | 21 ++ .../components/august/translations/ca.json | 2 +- .../components/august/translations/he.json | 12 ++ .../components/august/translations/hu.json | 16 ++ .../components/august/translations/id.json | 32 +++ .../components/august/translations/nl.json | 4 +- .../august/translations/zh-Hant.json | 2 +- .../components/aurora/translations/hu.json | 2 +- .../components/aurora/translations/id.json | 26 +++ .../components/aurora/translations/ko.json | 12 +- .../components/auth/translations/id.json | 27 ++- .../components/auth/translations/ko.json | 8 +- .../components/auth/translations/zh-Hant.json | 4 +- .../automation/translations/id.json | 4 +- .../components/awair/translations/hu.json | 23 ++- .../components/awair/translations/id.json | 29 +++ .../components/axis/translations/he.json | 11 ++ .../components/axis/translations/hu.json | 9 +- .../components/axis/translations/id.json | 37 ++++ .../components/axis/translations/nl.json | 10 + .../azure_devops/translations/hu.json | 10 +- .../azure_devops/translations/id.json | 32 +++ .../azure_devops/translations/ko.json | 22 ++- .../azure_devops/translations/nl.json | 9 +- .../binary_sensor/translations/id.json | 116 ++++++++++- .../binary_sensor/translations/ko.json | 184 ++++++++++-------- .../binary_sensor/translations/zh-Hans.json | 49 +++-- .../components/blebox/translations/hu.json | 7 +- .../components/blebox/translations/id.json | 24 +++ .../components/blebox/translations/ko.json | 4 +- .../components/blink/translations/en.json | 2 +- .../components/blink/translations/et.json | 2 +- .../components/blink/translations/fr.json | 2 +- .../components/blink/translations/hu.json | 11 +- .../components/blink/translations/id.json | 40 ++++ .../components/blink/translations/ko.json | 2 +- .../components/blink/translations/nl.json | 4 + .../components/blink/translations/no.json | 2 +- .../components/blink/translations/pl.json | 2 +- .../blink/translations/zh-Hant.json | 4 +- .../bmw_connected_drive/translations/bg.json | 12 ++ .../bmw_connected_drive/translations/hu.json | 20 ++ .../bmw_connected_drive/translations/id.json | 30 +++ .../bmw_connected_drive/translations/ko.json | 11 ++ .../components/bond/translations/hu.json | 21 ++ .../components/bond/translations/id.json | 28 +++ .../components/bond/translations/ko.json | 5 +- .../components/bond/translations/nl.json | 4 +- .../components/braviatv/translations/hu.json | 27 ++- .../components/braviatv/translations/id.json | 39 ++++ .../components/braviatv/translations/ko.json | 4 +- .../components/braviatv/translations/nl.json | 8 +- .../components/broadlink/translations/hu.json | 41 +++- .../components/broadlink/translations/id.json | 47 +++++ .../components/broadlink/translations/ko.json | 23 +-- .../components/broadlink/translations/nl.json | 3 +- .../components/brother/translations/hu.json | 2 +- .../components/brother/translations/id.json | 30 +++ .../components/brother/translations/ko.json | 2 +- .../components/bsblan/translations/hu.json | 8 +- .../components/bsblan/translations/id.json | 24 +++ .../components/bsblan/translations/ko.json | 2 +- .../components/calendar/translations/id.json | 4 +- .../components/calendar/translations/ko.json | 2 +- .../components/canary/translations/hu.json | 30 +++ .../components/canary/translations/id.json | 31 +++ .../components/canary/translations/ko.json | 6 +- .../components/cast/translations/ca.json | 23 +++ .../components/cast/translations/de.json | 23 +++ .../components/cast/translations/en.json | 1 + .../components/cast/translations/es.json | 23 +++ .../components/cast/translations/et.json | 23 +++ .../components/cast/translations/fr.json | 23 +++ .../components/cast/translations/hu.json | 29 ++- .../components/cast/translations/id.json | 29 ++- .../components/cast/translations/it.json | 23 +++ .../components/cast/translations/ko.json | 25 ++- .../components/cast/translations/nl.json | 29 ++- .../components/cast/translations/no.json | 23 +++ .../components/cast/translations/pl.json | 23 +++ .../components/cast/translations/pt.json | 7 + .../components/cast/translations/ru.json | 23 +++ .../components/cast/translations/zh-Hans.json | 11 ++ .../components/cast/translations/zh-Hant.json | 23 +++ .../cert_expiry/translations/hu.json | 1 + .../cert_expiry/translations/id.json | 24 +++ .../cert_expiry/translations/nl.json | 2 +- .../components/climacell/translations/bg.json | 16 ++ .../components/climacell/translations/de.json | 3 +- .../components/climacell/translations/es.json | 4 +- .../components/climacell/translations/hu.json | 28 +++ .../components/climacell/translations/id.json | 34 ++++ .../components/climacell/translations/ko.json | 34 ++++ .../components/climacell/translations/nl.json | 6 +- .../components/climacell/translations/pl.json | 34 ++++ .../components/climacell/translations/pt.json | 19 ++ .../climacell/translations/zh-Hans.json | 12 ++ .../components/climate/translations/id.json | 21 +- .../components/climate/translations/ko.json | 14 +- .../climate/translations/zh-Hans.json | 4 +- .../components/cloud/translations/hu.json | 2 + .../components/cloud/translations/id.json | 16 ++ .../components/cloud/translations/ko.json | 16 ++ .../components/cloud/translations/nl.json | 1 + .../cloudflare/translations/hu.json | 5 +- .../cloudflare/translations/id.json | 35 ++++ .../cloudflare/translations/ko.json | 22 ++- .../cloudflare/translations/nl.json | 3 +- .../configurator/translations/id.json | 2 +- .../components/control4/translations/hu.json | 14 ++ .../components/control4/translations/id.json | 31 +++ .../components/control4/translations/nl.json | 10 + .../coolmaster/translations/id.json | 22 +++ .../coronavirus/translations/hu.json | 2 +- .../coronavirus/translations/id.json | 15 ++ .../coronavirus/translations/nl.json | 2 +- .../components/cover/translations/hu.json | 3 +- .../components/cover/translations/id.json | 29 ++- .../components/cover/translations/ko.json | 37 ++-- .../cover/translations/zh-Hans.json | 6 +- .../components/daikin/translations/hu.json | 6 +- .../components/daikin/translations/id.json | 24 +++ .../components/daikin/translations/nl.json | 2 +- .../components/deconz/translations/bg.json | 4 +- .../components/deconz/translations/ca.json | 4 +- .../components/deconz/translations/cs.json | 4 +- .../components/deconz/translations/da.json | 4 +- .../components/deconz/translations/de.json | 4 +- .../deconz/translations/es-419.json | 4 +- .../components/deconz/translations/es.json | 4 +- .../components/deconz/translations/et.json | 2 +- .../components/deconz/translations/fr.json | 4 +- .../components/deconz/translations/hu.json | 41 ++-- .../components/deconz/translations/id.json | 94 ++++++++- .../components/deconz/translations/it.json | 2 +- .../components/deconz/translations/ko.json | 63 +++--- .../components/deconz/translations/lb.json | 4 +- .../components/deconz/translations/nl.json | 4 +- .../components/deconz/translations/no.json | 2 +- .../components/deconz/translations/pt-BR.json | 4 +- .../components/deconz/translations/pt.json | 4 +- .../components/deconz/translations/sl.json | 4 +- .../components/deconz/translations/sv.json | 4 +- .../components/deconz/translations/uk.json | 4 +- .../deconz/translations/zh-Hans.json | 4 +- .../deconz/translations/zh-Hant.json | 4 +- .../components/demo/translations/id.json | 21 ++ .../components/denonavr/translations/de.json | 3 +- .../components/denonavr/translations/hu.json | 8 + .../components/denonavr/translations/id.json | 48 +++++ .../components/denonavr/translations/ko.json | 1 + .../components/denonavr/translations/nl.json | 8 +- .../device_tracker/translations/id.json | 12 +- .../device_tracker/translations/ko.json | 8 +- .../device_tracker/translations/zh-Hans.json | 4 +- .../devolo_home_control/translations/he.json | 3 +- .../devolo_home_control/translations/hu.json | 11 +- .../devolo_home_control/translations/id.json | 20 ++ .../components/dexcom/translations/hu.json | 22 ++- .../components/dexcom/translations/id.json | 32 +++ .../components/dexcom/translations/nl.json | 10 + .../dialogflow/translations/hu.json | 6 +- .../dialogflow/translations/id.json | 17 ++ .../dialogflow/translations/ko.json | 4 +- .../components/directv/translations/hu.json | 3 +- .../components/directv/translations/id.json | 22 +++ .../components/directv/translations/ko.json | 2 +- .../components/directv/translations/nl.json | 6 +- .../components/doorbird/translations/hu.json | 7 +- .../components/doorbird/translations/id.json | 36 ++++ .../components/doorbird/translations/ko.json | 4 +- .../components/doorbird/translations/nl.json | 6 +- .../components/dsmr/translations/fr.json | 4 + .../components/dsmr/translations/id.json | 17 ++ .../components/dsmr/translations/ko.json | 10 + .../components/dunehd/translations/hu.json | 12 +- .../components/dunehd/translations/id.json | 21 ++ .../components/dunehd/translations/ko.json | 2 +- .../components/dunehd/translations/nl.json | 4 +- .../components/eafm/translations/id.json | 17 ++ .../components/eafm/translations/ko.json | 6 +- .../components/eafm/translations/nl.json | 7 + .../components/ebusd/translations/id.json | 6 + .../components/ecobee/translations/ca.json | 2 +- .../components/ecobee/translations/en.json | 2 +- .../components/ecobee/translations/et.json | 2 +- .../components/ecobee/translations/fr.json | 2 +- .../components/ecobee/translations/hu.json | 3 + .../components/ecobee/translations/id.json | 24 +++ .../components/ecobee/translations/ko.json | 2 +- .../components/ecobee/translations/no.json | 2 +- .../components/econet/translations/bg.json | 11 ++ .../components/econet/translations/hu.json | 21 ++ .../components/econet/translations/id.json | 22 +++ .../components/econet/translations/ko.json | 3 +- .../components/elgato/translations/hu.json | 3 +- .../components/elgato/translations/id.json | 25 +++ .../components/elgato/translations/ko.json | 4 +- .../components/elgato/translations/nl.json | 6 +- .../components/elkm1/translations/he.json | 12 ++ .../components/elkm1/translations/hu.json | 6 + .../components/elkm1/translations/id.json | 27 +++ .../components/elkm1/translations/ko.json | 6 +- .../components/elkm1/translations/nl.json | 6 +- .../emulated_roku/translations/hu.json | 7 +- .../emulated_roku/translations/id.json | 20 ++ .../emulated_roku/translations/nl.json | 6 +- .../components/enocean/translations/hu.json | 7 + .../components/enocean/translations/id.json | 25 +++ .../components/enocean/translations/ko.json | 4 +- .../components/epson/translations/hu.json | 2 +- .../components/epson/translations/id.json | 16 ++ .../components/esphome/translations/he.json | 11 ++ .../components/esphome/translations/hu.json | 4 +- .../components/esphome/translations/id.json | 24 ++- .../components/esphome/translations/ko.json | 4 +- .../components/esphome/translations/nl.json | 2 +- .../faa_delays/translations/bg.json | 14 ++ .../faa_delays/translations/de.json | 13 ++ .../faa_delays/translations/es.json | 4 +- .../faa_delays/translations/hu.json | 20 ++ .../faa_delays/translations/id.json | 21 ++ .../faa_delays/translations/ko.json | 21 ++ .../faa_delays/translations/pl.json | 21 ++ .../faa_delays/translations/pt.json | 8 + .../faa_delays/translations/zh-Hans.json | 9 + .../components/fan/translations/id.json | 24 ++- .../components/fan/translations/ko.json | 12 +- .../fireservicerota/translations/hu.json | 19 +- .../fireservicerota/translations/id.json | 29 +++ .../fireservicerota/translations/ko.json | 4 +- .../components/firmata/translations/fr.json | 4 + .../components/firmata/translations/hu.json | 7 + .../components/firmata/translations/id.json | 7 + .../flick_electric/translations/de.json | 1 + .../flick_electric/translations/hu.json | 11 +- .../flick_electric/translations/id.json | 23 +++ .../components/flo/translations/hu.json | 14 ++ .../components/flo/translations/id.json | 21 ++ .../components/flume/translations/he.json | 12 ++ .../components/flume/translations/hu.json | 8 + .../components/flume/translations/id.json | 24 +++ .../components/flume/translations/nl.json | 4 +- .../flunearyou/translations/he.json | 11 ++ .../flunearyou/translations/hu.json | 18 ++ .../flunearyou/translations/id.json | 20 ++ .../flunearyou/translations/ko.json | 2 +- .../flunearyou/translations/nl.json | 2 +- .../flunearyou/translations/zh-Hans.json | 7 + .../forked_daapd/translations/de.json | 1 + .../forked_daapd/translations/hu.json | 13 +- .../forked_daapd/translations/id.json | 42 ++++ .../forked_daapd/translations/ko.json | 3 +- .../forked_daapd/translations/nl.json | 4 +- .../components/foscam/translations/bg.json | 14 ++ .../components/foscam/translations/hu.json | 26 +++ .../components/foscam/translations/id.json | 26 +++ .../components/foscam/translations/ko.json | 6 +- .../components/freebox/translations/hu.json | 7 +- .../components/freebox/translations/id.json | 25 +++ .../components/freebox/translations/ko.json | 2 +- .../components/freebox/translations/nl.json | 6 +- .../components/fritzbox/translations/bg.json | 12 ++ .../components/fritzbox/translations/he.json | 18 ++ .../components/fritzbox/translations/hu.json | 17 ++ .../components/fritzbox/translations/id.json | 39 ++++ .../components/fritzbox/translations/ko.json | 7 +- .../components/fritzbox/translations/nl.json | 9 +- .../fritzbox_callmonitor/translations/bg.json | 13 ++ .../fritzbox_callmonitor/translations/hu.json | 26 +++ .../fritzbox_callmonitor/translations/id.json | 41 ++++ .../fritzbox_callmonitor/translations/ko.json | 20 ++ .../fritzbox_callmonitor/translations/nl.json | 20 ++ .../garmin_connect/translations/he.json | 12 ++ .../garmin_connect/translations/hu.json | 6 +- .../garmin_connect/translations/id.json | 23 +++ .../components/gdacs/translations/hu.json | 2 +- .../components/gdacs/translations/id.json | 15 ++ .../components/geofency/translations/hu.json | 6 +- .../components/geofency/translations/id.json | 17 ++ .../components/geofency/translations/ko.json | 4 +- .../geonetnz_quakes/translations/hu.json | 3 + .../geonetnz_quakes/translations/id.json | 16 ++ .../geonetnz_quakes/translations/nl.json | 2 +- .../geonetnz_volcano/translations/hu.json | 3 + .../geonetnz_volcano/translations/id.json | 15 ++ .../geonetnz_volcano/translations/ko.json | 2 +- .../components/gios/translations/hu.json | 6 +- .../components/gios/translations/id.json | 27 +++ .../components/gios/translations/ko.json | 7 +- .../components/gios/translations/nl.json | 4 +- .../components/glances/translations/he.json | 11 ++ .../components/glances/translations/hu.json | 8 +- .../components/glances/translations/id.json | 36 ++++ .../components/glances/translations/ko.json | 2 +- .../components/goalzero/translations/hu.json | 20 ++ .../components/goalzero/translations/id.json | 22 +++ .../components/goalzero/translations/ko.json | 6 +- .../components/gogogate2/translations/hu.json | 4 +- .../components/gogogate2/translations/id.json | 22 +++ .../components/gpslogger/translations/hu.json | 6 +- .../components/gpslogger/translations/id.json | 17 ++ .../components/gpslogger/translations/ko.json | 4 +- .../components/gree/translations/de.json | 2 +- .../components/gree/translations/hu.json | 13 ++ .../components/gree/translations/id.json | 13 ++ .../components/gree/translations/ko.json | 2 +- .../components/group/translations/id.json | 10 +- .../components/guardian/translations/hu.json | 10 + .../components/guardian/translations/id.json | 21 ++ .../components/guardian/translations/nl.json | 4 +- .../components/habitica/translations/bg.json | 11 ++ .../components/habitica/translations/hu.json | 18 ++ .../components/habitica/translations/id.json | 20 ++ .../components/habitica/translations/ko.json | 5 +- .../components/habitica/translations/nl.json | 5 +- .../components/habitica/translations/pt.json | 16 ++ .../components/hangouts/translations/ca.json | 4 +- .../components/hangouts/translations/en.json | 4 +- .../components/hangouts/translations/et.json | 4 +- .../components/hangouts/translations/fr.json | 2 +- .../components/hangouts/translations/hu.json | 6 +- .../components/hangouts/translations/id.json | 21 +- .../components/hangouts/translations/ko.json | 2 +- .../components/hangouts/translations/nl.json | 6 +- .../components/hangouts/translations/no.json | 4 +- .../components/hangouts/translations/pl.json | 2 +- .../hangouts/translations/zh-Hant.json | 8 +- .../components/harmony/translations/hu.json | 7 + .../components/harmony/translations/id.json | 36 ++++ .../components/harmony/translations/nl.json | 4 +- .../components/hassio/translations/af.json | 2 +- .../components/hassio/translations/bg.json | 2 +- .../components/hassio/translations/ca.json | 2 +- .../components/hassio/translations/cs.json | 2 +- .../components/hassio/translations/cy.json | 2 +- .../components/hassio/translations/da.json | 2 +- .../components/hassio/translations/de.json | 2 +- .../components/hassio/translations/el.json | 2 +- .../components/hassio/translations/en.json | 2 +- .../hassio/translations/es-419.json | 2 +- .../components/hassio/translations/es.json | 2 +- .../components/hassio/translations/et.json | 2 +- .../components/hassio/translations/eu.json | 2 +- .../components/hassio/translations/fa.json | 2 +- .../components/hassio/translations/fi.json | 2 +- .../components/hassio/translations/fr.json | 8 +- .../components/hassio/translations/he.json | 2 +- .../components/hassio/translations/hr.json | 2 +- .../components/hassio/translations/hu.json | 6 +- .../components/hassio/translations/hy.json | 2 +- .../components/hassio/translations/id.json | 19 ++ .../components/hassio/translations/is.json | 2 +- .../components/hassio/translations/it.json | 2 +- .../components/hassio/translations/ja.json | 2 +- .../components/hassio/translations/ko.json | 18 +- .../components/hassio/translations/lb.json | 2 +- .../components/hassio/translations/lt.json | 2 +- .../components/hassio/translations/lv.json | 2 +- .../components/hassio/translations/nb.json | 3 - .../components/hassio/translations/nl.json | 3 +- .../components/hassio/translations/nn.json | 2 +- .../components/hassio/translations/no.json | 2 +- .../components/hassio/translations/pl.json | 2 +- .../components/hassio/translations/pt-BR.json | 2 +- .../components/hassio/translations/pt.json | 2 +- .../components/hassio/translations/ro.json | 2 +- .../components/hassio/translations/ru.json | 2 +- .../components/hassio/translations/sk.json | 2 +- .../components/hassio/translations/sl.json | 2 +- .../components/hassio/translations/sv.json | 2 +- .../components/hassio/translations/th.json | 2 +- .../components/hassio/translations/tr.json | 2 +- .../components/hassio/translations/uk.json | 2 +- .../components/hassio/translations/vi.json | 2 +- .../hassio/translations/zh-Hans.json | 2 +- .../hassio/translations/zh-Hant.json | 4 +- .../components/heos/translations/hu.json | 3 + .../components/heos/translations/id.json | 19 ++ .../components/heos/translations/ko.json | 4 +- .../hisense_aehw4a1/translations/hu.json | 4 +- .../hisense_aehw4a1/translations/id.json | 13 ++ .../hisense_aehw4a1/translations/ko.json | 2 +- .../hisense_aehw4a1/translations/nl.json | 4 +- .../components/hive/translations/ca.json | 53 +++++ .../components/hive/translations/de.json | 17 ++ .../components/hive/translations/en.json | 94 ++++----- .../components/hive/translations/et.json | 53 +++++ .../components/hive/translations/fr.json | 53 +++++ .../components/hive/translations/ko.json | 53 +++++ .../components/hive/translations/pt.json | 22 +++ .../components/hive/translations/ru.json | 53 +++++ .../components/hive/translations/zh-Hant.json | 53 +++++ .../components/hlk_sw16/translations/hu.json | 14 ++ .../components/hlk_sw16/translations/id.json | 21 ++ .../home_connect/translations/hu.json | 6 +- .../home_connect/translations/id.json | 16 ++ .../homeassistant/translations/fr.json | 2 +- .../homeassistant/translations/he.json | 7 + .../homeassistant/translations/hu.json | 4 +- .../homeassistant/translations/id.json | 21 ++ .../homeassistant/translations/ko.json | 21 ++ .../homeassistant/translations/nl.json | 2 +- .../components/homekit/translations/he.json | 3 +- .../components/homekit/translations/hu.json | 40 +++- .../components/homekit/translations/id.json | 75 +++++++ .../components/homekit/translations/ko.json | 34 ++-- .../components/homekit/translations/nl.json | 18 +- .../homekit_controller/translations/bg.json | 14 ++ .../homekit_controller/translations/hu.json | 24 ++- .../homekit_controller/translations/id.json | 71 +++++++ .../homekit_controller/translations/ko.json | 22 +-- .../homekit_controller/translations/nl.json | 14 +- .../homematicip_cloud/translations/hu.json | 10 +- .../homematicip_cloud/translations/id.json | 24 +-- .../homematicip_cloud/translations/ko.json | 6 +- .../homematicip_cloud/translations/nl.json | 12 +- .../huawei_lte/translations/he.json | 11 ++ .../huawei_lte/translations/hu.json | 10 +- .../huawei_lte/translations/id.json | 42 ++++ .../huawei_lte/translations/ko.json | 2 +- .../components/hue/translations/hu.json | 18 +- .../components/hue/translations/id.json | 59 +++++- .../components/hue/translations/ko.json | 10 +- .../components/hue/translations/nl.json | 6 +- .../components/hue/translations/zh-Hans.json | 7 + .../huisbaasje/translations/bg.json | 12 ++ .../huisbaasje/translations/hu.json | 21 ++ .../huisbaasje/translations/id.json | 21 ++ .../humidifier/translations/hu.json | 28 +++ .../humidifier/translations/id.json | 28 +++ .../humidifier/translations/ko.json | 22 +-- .../humidifier/translations/nl.json | 7 + .../humidifier/translations/zh-Hans.json | 5 +- .../translations/hu.json | 4 +- .../translations/id.json | 23 +++ .../hvv_departures/translations/he.json | 12 ++ .../hvv_departures/translations/hu.json | 27 +++ .../hvv_departures/translations/id.json | 47 +++++ .../hvv_departures/translations/ko.json | 4 +- .../hvv_departures/translations/nl.json | 28 ++- .../components/hyperion/translations/hu.json | 15 +- .../components/hyperion/translations/id.json | 53 +++++ .../components/hyperion/translations/ko.json | 31 +++ .../components/hyperion/translations/nl.json | 11 +- .../components/iaqualink/translations/he.json | 11 ++ .../components/iaqualink/translations/hu.json | 3 + .../components/iaqualink/translations/id.json | 20 ++ .../components/iaqualink/translations/ko.json | 4 +- .../components/icloud/translations/de.json | 1 + .../components/icloud/translations/he.json | 1 + .../components/icloud/translations/hu.json | 8 +- .../components/icloud/translations/id.json | 46 +++++ .../components/icloud/translations/ko.json | 3 +- .../components/icloud/translations/nl.json | 2 +- .../components/ifttt/translations/hu.json | 6 +- .../components/ifttt/translations/id.json | 17 ++ .../components/ifttt/translations/ko.json | 6 +- .../image_processing/translations/id.json | 2 +- .../input_boolean/translations/id.json | 4 +- .../components/insteon/translations/he.json | 12 ++ .../components/insteon/translations/hu.json | 72 +++++++ .../components/insteon/translations/id.json | 109 +++++++++++ .../components/insteon/translations/ko.json | 57 ++++-- .../components/insteon/translations/nl.json | 6 +- .../components/ios/translations/hu.json | 4 +- .../components/ios/translations/id.json | 4 +- .../components/ios/translations/ko.json | 2 +- .../components/ios/translations/nl.json | 4 +- .../components/ipma/translations/hu.json | 7 +- .../components/ipma/translations/id.json | 24 +++ .../components/ipma/translations/ko.json | 5 + .../components/ipma/translations/nl.json | 9 +- .../components/ipp/translations/hu.json | 7 +- .../components/ipp/translations/id.json | 35 ++++ .../components/ipp/translations/ko.json | 4 +- .../components/ipp/translations/nl.json | 6 +- .../components/iqvia/translations/hu.json | 7 + .../components/iqvia/translations/id.json | 10 +- .../islamic_prayer_times/translations/hu.json | 7 + .../islamic_prayer_times/translations/id.json | 23 +++ .../islamic_prayer_times/translations/ko.json | 2 +- .../components/isy994/translations/hu.json | 11 +- .../components/isy994/translations/id.json | 40 ++++ .../components/isy994/translations/ko.json | 2 +- .../components/isy994/translations/nl.json | 3 +- .../components/izone/translations/hu.json | 4 + .../components/izone/translations/id.json | 13 ++ .../components/izone/translations/ko.json | 2 +- .../components/juicenet/translations/de.json | 2 +- .../components/juicenet/translations/hu.json | 20 ++ .../components/juicenet/translations/id.json | 21 ++ .../components/juicenet/translations/ko.json | 2 +- .../keenetic_ndms2/translations/bg.json | 14 ++ .../keenetic_ndms2/translations/en.json | 1 + .../keenetic_ndms2/translations/hu.json | 21 ++ .../keenetic_ndms2/translations/id.json | 36 ++++ .../keenetic_ndms2/translations/ko.json | 10 +- .../keenetic_ndms2/translations/nl.json | 10 +- .../components/kmtronic/translations/bg.json | 15 ++ .../components/kmtronic/translations/ca.json | 9 + .../components/kmtronic/translations/en.json | 3 +- .../components/kmtronic/translations/es.json | 19 ++ .../components/kmtronic/translations/et.json | 9 + .../components/kmtronic/translations/fr.json | 9 + .../components/kmtronic/translations/hu.json | 21 ++ .../components/kmtronic/translations/id.json | 21 ++ .../components/kmtronic/translations/it.json | 9 + .../components/kmtronic/translations/ko.json | 30 +++ .../components/kmtronic/translations/nl.json | 9 + .../components/kmtronic/translations/no.json | 9 + .../components/kmtronic/translations/pl.json | 9 + .../components/kmtronic/translations/pt.json | 30 +++ .../components/kmtronic/translations/ru.json | 9 + .../kmtronic/translations/zh-Hant.json | 9 + .../components/kodi/translations/hu.json | 36 +++- .../components/kodi/translations/id.json | 50 +++++ .../components/kodi/translations/ko.json | 17 +- .../components/kodi/translations/nl.json | 1 + .../components/konnected/translations/hu.json | 13 ++ .../components/konnected/translations/id.json | 108 ++++++++++ .../components/konnected/translations/ko.json | 4 +- .../components/konnected/translations/nl.json | 2 +- .../components/kulersky/translations/de.json | 2 +- .../components/kulersky/translations/hu.json | 6 +- .../components/kulersky/translations/id.json | 13 ++ .../components/kulersky/translations/ko.json | 2 +- .../components/life360/translations/he.json | 11 ++ .../components/life360/translations/hu.json | 8 +- .../components/life360/translations/id.json | 22 ++- .../components/lifx/translations/hu.json | 4 +- .../components/lifx/translations/id.json | 13 ++ .../components/lifx/translations/ko.json | 4 +- .../components/lifx/translations/nl.json | 4 +- .../components/light/translations/id.json | 22 ++- .../components/light/translations/ko.json | 20 +- .../components/litejet/translations/bg.json | 11 ++ .../components/litejet/translations/hu.json | 15 ++ .../components/litejet/translations/id.json | 19 ++ .../components/litejet/translations/ko.json | 19 ++ .../components/litejet/translations/nl.json | 1 + .../components/litejet/translations/pt.json | 14 ++ .../litterrobot/translations/bg.json | 12 ++ .../litterrobot/translations/es.json | 20 ++ .../litterrobot/translations/hu.json | 20 ++ .../litterrobot/translations/id.json | 20 ++ .../litterrobot/translations/ko.json | 20 ++ .../litterrobot/translations/pt.json | 17 ++ .../components/local_ip/translations/de.json | 2 +- .../components/local_ip/translations/hu.json | 4 + .../components/local_ip/translations/id.json | 17 ++ .../components/local_ip/translations/ko.json | 2 +- .../components/local_ip/translations/nl.json | 2 +- .../components/locative/translations/de.json | 2 +- .../components/locative/translations/hu.json | 8 +- .../components/locative/translations/id.json | 17 ++ .../components/locative/translations/ko.json | 4 +- .../components/locative/translations/nl.json | 2 +- .../components/lock/translations/id.json | 17 +- .../components/lock/translations/ko.json | 14 +- .../components/lock/translations/zh-Hans.json | 10 +- .../logi_circle/translations/hu.json | 8 +- .../logi_circle/translations/id.json | 28 +++ .../logi_circle/translations/ko.json | 2 +- .../logi_circle/translations/nl.json | 2 +- .../components/lovelace/translations/id.json | 10 + .../components/lovelace/translations/ko.json | 10 + .../components/luftdaten/translations/hu.json | 2 + .../components/luftdaten/translations/id.json | 18 ++ .../lutron_caseta/translations/bg.json | 12 ++ .../lutron_caseta/translations/en.json | 2 +- .../lutron_caseta/translations/et.json | 2 +- .../lutron_caseta/translations/he.json | 9 + .../lutron_caseta/translations/hu.json | 29 +++ .../lutron_caseta/translations/id.json | 70 +++++++ .../lutron_caseta/translations/ko.json | 58 +++++- .../lutron_caseta/translations/lb.json | 32 +++ .../lutron_caseta/translations/nl.json | 28 ++- .../components/lyric/translations/hu.json | 16 ++ .../components/lyric/translations/id.json | 16 ++ .../components/mailgun/translations/hu.json | 6 +- .../components/mailgun/translations/id.json | 17 ++ .../components/mailgun/translations/ko.json | 4 +- .../components/mazda/translations/bg.json | 21 ++ .../components/mazda/translations/de.json | 12 +- .../components/mazda/translations/hu.json | 33 ++++ .../components/mazda/translations/id.json | 35 ++++ .../components/mazda/translations/ko.json | 18 +- .../components/mazda/translations/nl.json | 11 +- .../media_player/translations/hu.json | 7 + .../media_player/translations/id.json | 24 ++- .../media_player/translations/ko.json | 17 +- .../media_player/translations/zh-Hans.json | 7 + .../components/melcloud/translations/he.json | 12 ++ .../components/melcloud/translations/hu.json | 5 + .../components/melcloud/translations/id.json | 22 +++ .../components/melcloud/translations/ko.json | 2 +- .../components/met/translations/he.json | 11 ++ .../components/met/translations/hu.json | 3 + .../components/met/translations/id.json | 6 + .../meteo_france/translations/hu.json | 20 +- .../meteo_france/translations/id.json | 36 ++++ .../meteo_france/translations/ko.json | 8 +- .../meteo_france/translations/nl.json | 12 ++ .../components/metoffice/translations/he.json | 11 ++ .../components/metoffice/translations/hu.json | 16 +- .../components/metoffice/translations/id.json | 22 +++ .../components/metoffice/translations/nl.json | 1 + .../components/mikrotik/translations/he.json | 12 ++ .../components/mikrotik/translations/hu.json | 5 +- .../components/mikrotik/translations/id.json | 36 ++++ .../components/mill/translations/hu.json | 3 + .../components/mill/translations/id.json | 18 ++ .../minecraft_server/translations/hu.json | 2 +- .../minecraft_server/translations/id.json | 22 +++ .../mobile_app/translations/ca.json | 3 +- .../mobile_app/translations/en.json | 3 +- .../mobile_app/translations/et.json | 3 +- .../mobile_app/translations/fr.json | 3 +- .../mobile_app/translations/hu.json | 3 +- .../mobile_app/translations/id.json | 18 ++ .../mobile_app/translations/it.json | 3 +- .../mobile_app/translations/ko.json | 12 +- .../mobile_app/translations/nl.json | 3 +- .../mobile_app/translations/no.json | 3 +- .../mobile_app/translations/pl.json | 3 +- .../mobile_app/translations/ru.json | 3 +- .../mobile_app/translations/zh-Hans.json | 8 +- .../mobile_app/translations/zh-Hant.json | 3 +- .../components/monoprice/translations/hu.json | 7 + .../components/monoprice/translations/id.json | 40 ++++ .../components/monoprice/translations/nl.json | 4 +- .../moon/translations/sensor.id.json | 14 ++ .../motion_blinds/translations/bg.json | 11 ++ .../motion_blinds/translations/hu.json | 27 +++ .../motion_blinds/translations/id.json | 37 ++++ .../motion_blinds/translations/ko.json | 16 +- .../components/mqtt/translations/bg.json | 4 +- .../components/mqtt/translations/ca.json | 4 +- .../components/mqtt/translations/cs.json | 4 +- .../components/mqtt/translations/da.json | 4 +- .../components/mqtt/translations/de.json | 4 +- .../components/mqtt/translations/es-419.json | 4 +- .../components/mqtt/translations/es.json | 4 +- .../components/mqtt/translations/et.json | 4 +- .../components/mqtt/translations/fr.json | 4 +- .../components/mqtt/translations/hu.json | 23 ++- .../components/mqtt/translations/id.json | 62 +++++- .../components/mqtt/translations/ko.json | 27 +-- .../components/mqtt/translations/lb.json | 4 +- .../components/mqtt/translations/nl.json | 11 +- .../components/mqtt/translations/no.json | 4 +- .../components/mqtt/translations/pl.json | 2 +- .../components/mqtt/translations/pt-BR.json | 4 +- .../components/mqtt/translations/pt.json | 4 +- .../components/mqtt/translations/ro.json | 2 +- .../components/mqtt/translations/sl.json | 4 +- .../components/mqtt/translations/sv.json | 4 +- .../components/mqtt/translations/uk.json | 4 +- .../components/mqtt/translations/zh-Hans.json | 4 +- .../components/mqtt/translations/zh-Hant.json | 4 +- .../components/mullvad/translations/bg.json | 15 ++ .../components/mullvad/translations/es.json | 12 ++ .../components/mullvad/translations/hu.json | 21 ++ .../components/mullvad/translations/id.json | 22 +++ .../components/mullvad/translations/ko.json | 22 +++ .../components/mullvad/translations/pl.json | 22 +++ .../components/mullvad/translations/pt.json | 21 ++ .../mullvad/translations/zh-Hans.json | 20 ++ .../components/myq/translations/he.json | 12 ++ .../components/myq/translations/hu.json | 8 + .../components/myq/translations/id.json | 21 ++ .../components/mysensors/translations/bg.json | 22 +++ .../components/mysensors/translations/hu.json | 31 +++ .../components/mysensors/translations/id.json | 75 +++++++ .../components/mysensors/translations/ko.json | 65 ++++++- .../components/mysensors/translations/nl.json | 3 + .../components/neato/translations/de.json | 2 +- .../components/neato/translations/he.json | 11 ++ .../components/neato/translations/hu.json | 24 ++- .../components/neato/translations/id.json | 37 ++++ .../components/neato/translations/ko.json | 3 +- .../components/nest/translations/ca.json | 2 +- .../components/nest/translations/en.json | 2 +- .../components/nest/translations/et.json | 2 +- .../components/nest/translations/fr.json | 2 +- .../components/nest/translations/hu.json | 22 ++- .../components/nest/translations/id.json | 40 +++- .../components/nest/translations/ko.json | 17 +- .../components/nest/translations/nl.json | 8 +- .../components/nest/translations/no.json | 2 +- .../components/nest/translations/ru.json | 2 +- .../components/nest/translations/sv.json | 5 + .../components/netatmo/translations/de.json | 22 +++ .../components/netatmo/translations/hu.json | 28 ++- .../components/netatmo/translations/id.json | 64 ++++++ .../components/netatmo/translations/ko.json | 32 ++- .../components/netatmo/translations/nl.json | 7 +- .../components/netatmo/translations/pl.json | 22 +++ .../netatmo/translations/zh-Hans.json | 16 ++ .../components/nexia/translations/he.json | 12 ++ .../components/nexia/translations/hu.json | 11 +- .../components/nexia/translations/id.json | 21 ++ .../components/nexia/translations/ko.json | 2 +- .../components/nexia/translations/nl.json | 2 +- .../nightscout/translations/hu.json | 14 ++ .../nightscout/translations/id.json | 23 +++ .../nightscout/translations/ko.json | 5 +- .../components/notify/translations/hu.json | 2 +- .../components/notify/translations/id.json | 2 +- .../components/notion/translations/he.json | 11 ++ .../components/notion/translations/hu.json | 4 + .../components/notion/translations/id.json | 20 ++ .../components/notion/translations/nl.json | 2 +- .../components/nuheat/translations/he.json | 12 ++ .../components/nuheat/translations/hu.json | 9 +- .../components/nuheat/translations/id.json | 24 +++ .../components/nuheat/translations/ko.json | 4 +- .../components/nuheat/translations/nl.json | 4 +- .../components/nuki/translations/bg.json | 11 ++ .../components/nuki/translations/hu.json | 18 ++ .../components/nuki/translations/id.json | 18 ++ .../components/number/translations/de.json | 3 + .../components/number/translations/hu.json | 8 + .../components/number/translations/id.json | 8 + .../components/number/translations/ko.json | 8 + .../number/translations/zh-Hans.json | 7 + .../components/nut/translations/ca.json | 4 + .../components/nut/translations/de.json | 4 + .../components/nut/translations/et.json | 4 + .../components/nut/translations/fr.json | 4 + .../components/nut/translations/he.json | 12 ++ .../components/nut/translations/hu.json | 13 ++ .../components/nut/translations/id.json | 50 +++++ .../components/nut/translations/it.json | 4 + .../components/nut/translations/ko.json | 6 +- .../components/nut/translations/nl.json | 6 +- .../components/nut/translations/no.json | 4 + .../components/nut/translations/pl.json | 4 + .../components/nut/translations/ru.json | 4 + .../components/nut/translations/zh-Hans.json | 11 ++ .../components/nut/translations/zh-Hant.json | 4 + .../components/nws/translations/he.json | 11 ++ .../components/nws/translations/hu.json | 11 +- .../components/nws/translations/id.json | 23 +++ .../components/nws/translations/ko.json | 2 +- .../components/nws/translations/nl.json | 6 +- .../components/nzbget/translations/hu.json | 35 ++++ .../components/nzbget/translations/id.json | 35 ++++ .../components/nzbget/translations/ko.json | 6 +- .../components/omnilogic/translations/de.json | 9 + .../components/omnilogic/translations/hu.json | 29 +++ .../components/omnilogic/translations/id.json | 29 +++ .../components/omnilogic/translations/ko.json | 2 +- .../onboarding/translations/id.json | 4 +- .../ondilo_ico/translations/de.json | 3 +- .../ondilo_ico/translations/hu.json | 16 ++ .../ondilo_ico/translations/id.json | 17 ++ .../ondilo_ico/translations/ko.json | 3 +- .../components/onewire/translations/de.json | 6 +- .../components/onewire/translations/hu.json | 8 +- .../components/onewire/translations/id.json | 26 +++ .../components/onewire/translations/ko.json | 12 +- .../components/onvif/translations/he.json | 6 + .../components/onvif/translations/hu.json | 20 +- .../components/onvif/translations/id.json | 59 ++++++ .../opentherm_gw/translations/hu.json | 3 +- .../opentherm_gw/translations/id.json | 30 +++ .../components/openuv/translations/hu.json | 3 + .../components/openuv/translations/id.json | 7 +- .../components/openuv/translations/ko.json | 2 +- .../components/openuv/translations/nl.json | 4 +- .../openweathermap/translations/he.json | 11 ++ .../openweathermap/translations/hu.json | 35 ++++ .../openweathermap/translations/id.json | 35 ++++ .../openweathermap/translations/ko.json | 4 +- .../ovo_energy/translations/de.json | 4 +- .../ovo_energy/translations/hu.json | 14 +- .../ovo_energy/translations/id.json | 27 +++ .../ovo_energy/translations/ko.json | 6 +- .../ovo_energy/translations/nl.json | 3 +- .../components/owntracks/translations/hu.json | 3 + .../components/owntracks/translations/id.json | 16 ++ .../components/owntracks/translations/ko.json | 6 +- .../components/ozw/translations/fr.json | 14 +- .../components/ozw/translations/hu.json | 13 +- .../components/ozw/translations/id.json | 41 ++++ .../components/ozw/translations/ko.json | 28 ++- .../components/ozw/translations/nl.json | 7 +- .../components/ozw/translations/zh-Hant.json | 20 +- .../panasonic_viera/translations/hu.json | 20 +- .../panasonic_viera/translations/id.json | 30 +++ .../panasonic_viera/translations/nl.json | 10 +- .../components/person/translations/id.json | 2 +- .../philips_js/translations/bg.json | 8 + .../philips_js/translations/de.json | 1 + .../philips_js/translations/es.json | 4 + .../philips_js/translations/hu.json | 21 ++ .../philips_js/translations/id.json | 26 +++ .../philips_js/translations/it.json | 2 + .../philips_js/translations/ko.json | 8 + .../philips_js/translations/nl.json | 7 + .../philips_js/translations/pl.json | 2 + .../philips_js/translations/zh-Hans.json | 7 + .../components/pi_hole/translations/hu.json | 12 +- .../components/pi_hole/translations/id.json | 29 +++ .../components/pi_hole/translations/ko.json | 1 + .../components/plaato/translations/de.json | 2 +- .../components/plaato/translations/en.json | 2 +- .../components/plaato/translations/et.json | 2 +- .../components/plaato/translations/hu.json | 13 +- .../components/plaato/translations/id.json | 54 +++++ .../components/plaato/translations/ko.json | 40 +++- .../components/plaato/translations/nl.json | 11 ++ .../components/plaato/translations/no.json | 2 +- .../components/plaato/translations/pl.json | 4 +- .../components/plant/translations/id.json | 6 +- .../components/plex/translations/hu.json | 13 +- .../components/plex/translations/id.json | 62 ++++++ .../components/plugwise/translations/hu.json | 22 ++- .../components/plugwise/translations/id.json | 42 ++++ .../components/plugwise/translations/ko.json | 13 +- .../components/plugwise/translations/nl.json | 8 +- .../plum_lightpad/translations/hu.json | 11 ++ .../plum_lightpad/translations/id.json | 18 ++ .../components/point/translations/de.json | 2 +- .../components/point/translations/hu.json | 5 +- .../components/point/translations/id.json | 32 +++ .../components/point/translations/ko.json | 6 +- .../components/point/translations/nl.json | 14 +- .../components/poolsense/translations/de.json | 2 +- .../components/poolsense/translations/hu.json | 13 ++ .../components/poolsense/translations/id.json | 20 ++ .../components/powerwall/translations/bg.json | 11 ++ .../components/powerwall/translations/hu.json | 13 +- .../components/powerwall/translations/id.json | 25 +++ .../components/powerwall/translations/ko.json | 4 +- .../components/powerwall/translations/nl.json | 5 +- .../components/profiler/translations/hu.json | 4 +- .../components/profiler/translations/id.json | 12 ++ .../components/profiler/translations/ko.json | 2 +- .../progettihwsw/translations/hu.json | 26 +++ .../progettihwsw/translations/id.json | 41 ++++ .../progettihwsw/translations/ko.json | 4 +- .../components/ps4/translations/hu.json | 11 +- .../components/ps4/translations/id.json | 41 ++++ .../components/ps4/translations/ko.json | 8 +- .../components/ps4/translations/nl.json | 14 +- .../pvpc_hourly_pricing/translations/hu.json | 7 + .../pvpc_hourly_pricing/translations/id.json | 17 ++ .../pvpc_hourly_pricing/translations/ko.json | 2 +- .../pvpc_hourly_pricing/translations/nl.json | 2 +- .../components/rachio/translations/hu.json | 8 + .../components/rachio/translations/id.json | 30 +++ .../components/rachio/translations/ko.json | 2 +- .../components/rachio/translations/nl.json | 4 +- .../rainmachine/translations/he.json | 11 ++ .../rainmachine/translations/hu.json | 9 +- .../rainmachine/translations/id.json | 30 +++ .../rainmachine/translations/ko.json | 10 + .../recollect_waste/translations/hu.json | 7 + .../recollect_waste/translations/id.json | 28 +++ .../recollect_waste/translations/ko.json | 21 ++ .../components/remote/translations/hu.json | 15 ++ .../components/remote/translations/id.json | 19 +- .../components/remote/translations/ko.json | 14 +- .../remote/translations/zh-Hans.json | 4 +- .../components/rfxtrx/translations/de.json | 3 +- .../components/rfxtrx/translations/fr.json | 10 +- .../components/rfxtrx/translations/hu.json | 19 +- .../components/rfxtrx/translations/id.json | 73 +++++++ .../components/rfxtrx/translations/ko.json | 50 ++++- .../components/rfxtrx/translations/nl.json | 6 + .../components/ring/translations/he.json | 12 ++ .../components/ring/translations/hu.json | 6 +- .../components/ring/translations/id.json | 26 +++ .../components/ring/translations/zh-Hant.json | 4 +- .../components/risco/translations/hu.json | 21 ++ .../components/risco/translations/id.json | 55 ++++++ .../components/risco/translations/ko.json | 23 +-- .../translations/bg.json | 11 ++ .../translations/es.json | 12 ++ .../translations/hu.json | 20 ++ .../translations/id.json | 21 ++ .../translations/ko.json | 21 ++ .../components/roku/translations/hu.json | 16 +- .../components/roku/translations/id.json | 29 +++ .../components/roku/translations/ko.json | 6 +- .../components/roku/translations/nl.json | 8 +- .../components/roomba/translations/bg.json | 12 ++ .../components/roomba/translations/ca.json | 2 +- .../components/roomba/translations/en.json | 2 +- .../components/roomba/translations/et.json | 2 +- .../components/roomba/translations/he.json | 11 ++ .../components/roomba/translations/hu.json | 41 ++++ .../components/roomba/translations/id.json | 62 ++++++ .../components/roomba/translations/ko.json | 23 ++- .../components/roomba/translations/lb.json | 1 + .../components/roomba/translations/nl.json | 12 +- .../components/roon/translations/hu.json | 12 ++ .../components/roon/translations/id.json | 24 +++ .../components/roon/translations/ko.json | 6 +- .../components/rpi_power/translations/de.json | 3 +- .../components/rpi_power/translations/hu.json | 13 ++ .../components/rpi_power/translations/id.json | 14 ++ .../components/rpi_power/translations/ko.json | 4 +- .../ruckus_unleashed/translations/he.json | 16 ++ .../ruckus_unleashed/translations/hu.json | 8 +- .../ruckus_unleashed/translations/id.json | 21 ++ .../components/samsungtv/translations/hu.json | 4 +- .../components/samsungtv/translations/id.json | 25 +++ .../components/samsungtv/translations/ko.json | 4 +- .../components/scene/translations/id.json | 2 +- .../components/script/translations/id.json | 4 +- .../season/translations/sensor.id.json | 16 ++ .../components/sense/translations/he.json | 11 ++ .../components/sense/translations/hu.json | 8 + .../components/sense/translations/id.json | 21 ++ .../components/sense/translations/ko.json | 2 +- .../components/sense/translations/nl.json | 4 +- .../components/sensor/translations/ca.json | 4 + .../components/sensor/translations/en.json | 4 + .../components/sensor/translations/et.json | 4 + .../components/sensor/translations/fr.json | 4 + .../components/sensor/translations/hu.json | 3 +- .../components/sensor/translations/id.json | 40 +++- .../components/sensor/translations/it.json | 4 + .../components/sensor/translations/ko.json | 48 +++-- .../components/sensor/translations/nl.json | 4 + .../components/sensor/translations/no.json | 4 + .../components/sensor/translations/pl.json | 4 + .../components/sensor/translations/ru.json | 4 + .../sensor/translations/zh-Hant.json | 4 + .../components/sentry/translations/hu.json | 5 +- .../components/sentry/translations/id.json | 34 ++++ .../components/sentry/translations/ko.json | 17 +- .../components/sharkiq/translations/hu.json | 29 +++ .../components/sharkiq/translations/id.json | 29 +++ .../components/shelly/translations/bg.json | 10 + .../components/shelly/translations/de.json | 19 +- .../components/shelly/translations/hu.json | 39 +++- .../components/shelly/translations/id.json | 47 +++++ .../components/shelly/translations/ko.json | 25 ++- .../components/shelly/translations/nl.json | 9 +- .../shopping_list/translations/hu.json | 2 +- .../shopping_list/translations/id.json | 14 ++ .../shopping_list/translations/nl.json | 2 +- .../simplisafe/translations/he.json | 11 ++ .../simplisafe/translations/hu.json | 14 +- .../simplisafe/translations/id.json | 45 +++++ .../simplisafe/translations/ko.json | 8 +- .../simplisafe/translations/nl.json | 2 +- .../components/smappee/translations/hu.json | 16 +- .../components/smappee/translations/id.json | 35 ++++ .../components/smappee/translations/ko.json | 16 +- .../components/smappee/translations/nl.json | 1 + .../smart_meter_texas/translations/hu.json | 13 ++ .../smart_meter_texas/translations/id.json | 20 ++ .../components/smarthab/translations/hu.json | 8 + .../components/smarthab/translations/id.json | 19 ++ .../components/smarthab/translations/ko.json | 2 +- .../smartthings/translations/hu.json | 10 +- .../smartthings/translations/id.json | 38 ++++ .../smartthings/translations/ko.json | 12 +- .../smartthings/translations/nl.json | 6 +- .../components/smarttub/translations/bg.json | 14 ++ .../components/smarttub/translations/hu.json | 22 +++ .../components/smarttub/translations/id.json | 22 +++ .../components/smarttub/translations/ko.json | 4 +- .../components/smarttub/translations/nl.json | 1 + .../components/smarttub/translations/pt.json | 20 ++ .../components/smhi/translations/he.json | 11 ++ .../components/smhi/translations/id.json | 18 ++ .../components/sms/translations/hu.json | 15 +- .../components/sms/translations/id.json | 20 ++ .../components/sms/translations/ko.json | 2 +- .../components/solaredge/translations/hu.json | 5 + .../components/solaredge/translations/id.json | 25 +++ .../components/solaredge/translations/ko.json | 4 +- .../components/solarlog/translations/hu.json | 6 +- .../components/solarlog/translations/id.json | 20 ++ .../components/soma/translations/hu.json | 3 +- .../components/soma/translations/id.json | 24 +++ .../components/soma/translations/ko.json | 4 +- .../components/somfy/translations/hu.json | 10 +- .../components/somfy/translations/id.json | 18 ++ .../components/somfy/translations/ko.json | 2 +- .../somfy_mylink/translations/bg.json | 11 ++ .../somfy_mylink/translations/he.json | 9 + .../somfy_mylink/translations/hu.json | 32 +++ .../somfy_mylink/translations/id.json | 53 +++++ .../somfy_mylink/translations/ko.json | 34 +++- .../somfy_mylink/translations/nl.json | 4 + .../components/sonarr/translations/hu.json | 34 +++- .../components/sonarr/translations/id.json | 40 ++++ .../components/sonarr/translations/ko.json | 3 +- .../components/sonarr/translations/nl.json | 12 ++ .../components/songpal/translations/hu.json | 7 +- .../components/songpal/translations/id.json | 22 +++ .../components/sonos/translations/hu.json | 4 +- .../components/sonos/translations/id.json | 6 +- .../components/sonos/translations/ko.json | 4 +- .../components/sonos/translations/nl.json | 4 +- .../speedtestdotnet/translations/de.json | 2 +- .../speedtestdotnet/translations/hu.json | 22 +++ .../speedtestdotnet/translations/id.json | 24 +++ .../speedtestdotnet/translations/ko.json | 4 +- .../speedtestdotnet/translations/nl.json | 11 ++ .../components/spider/translations/hu.json | 20 ++ .../components/spider/translations/id.json | 20 ++ .../components/spider/translations/ko.json | 5 +- .../components/spider/translations/nl.json | 3 +- .../components/spotify/translations/hu.json | 13 +- .../components/spotify/translations/id.json | 27 +++ .../components/spotify/translations/ko.json | 13 +- .../components/spotify/translations/nl.json | 5 + .../squeezebox/translations/hu.json | 25 ++- .../squeezebox/translations/id.json | 31 +++ .../srp_energy/translations/hu.json | 15 +- .../srp_energy/translations/id.json | 24 +++ .../srp_energy/translations/ko.json | 8 +- .../srp_energy/translations/nl.json | 1 + .../components/starline/translations/he.json | 12 ++ .../components/starline/translations/hu.json | 2 +- .../components/starline/translations/id.json | 41 ++++ .../starline/translations/zh-Hant.json | 2 +- .../components/subaru/translations/bg.json | 16 ++ .../components/subaru/translations/hu.json | 38 ++++ .../components/subaru/translations/id.json | 44 +++++ .../components/subaru/translations/ko.json | 44 +++++ .../components/subaru/translations/nl.json | 6 + .../components/sun/translations/id.json | 2 +- .../components/switch/translations/id.json | 19 +- .../components/switch/translations/ko.json | 14 +- .../switch/translations/zh-Hans.json | 2 +- .../components/syncthru/translations/hu.json | 17 ++ .../components/syncthru/translations/id.json | 27 +++ .../components/syncthru/translations/nl.json | 4 +- .../synology_dsm/translations/he.json | 16 ++ .../synology_dsm/translations/hu.json | 29 ++- .../synology_dsm/translations/id.json | 55 ++++++ .../synology_dsm/translations/nl.json | 10 +- .../synology_dsm/translations/zh-Hant.json | 4 +- .../system_health/translations/id.json | 3 + .../components/tado/translations/he.json | 12 ++ .../components/tado/translations/hu.json | 8 + .../components/tado/translations/id.json | 32 +++ .../components/tado/translations/ko.json | 2 +- .../components/tado/translations/nl.json | 2 +- .../components/tag/translations/hu.json | 3 + .../components/tag/translations/id.json | 3 + .../components/tasmota/translations/hu.json | 7 + .../components/tasmota/translations/id.json | 22 +++ .../components/tasmota/translations/ko.json | 17 +- .../tellduslive/translations/hu.json | 10 +- .../tellduslive/translations/id.json | 26 +++ .../tellduslive/translations/ko.json | 4 +- .../tellduslive/translations/nl.json | 2 +- .../components/tesla/translations/he.json | 12 ++ .../components/tesla/translations/hu.json | 9 + .../components/tesla/translations/id.json | 33 ++++ .../components/tesla/translations/nl.json | 2 +- .../components/tibber/translations/hu.json | 10 +- .../components/tibber/translations/id.json | 21 ++ .../components/tile/translations/hu.json | 29 +++ .../components/tile/translations/id.json | 29 +++ .../components/toon/translations/hu.json | 10 + .../components/toon/translations/id.json | 25 +++ .../components/toon/translations/ko.json | 2 +- .../totalconnect/translations/es.json | 3 + .../totalconnect/translations/he.json | 17 ++ .../totalconnect/translations/hu.json | 15 ++ .../totalconnect/translations/id.json | 32 +++ .../totalconnect/translations/ko.json | 17 +- .../totalconnect/translations/nl.json | 10 +- .../components/tplink/translations/hu.json | 8 + .../components/tplink/translations/id.json | 13 ++ .../components/tplink/translations/ko.json | 2 +- .../components/tplink/translations/nl.json | 4 +- .../components/traccar/translations/hu.json | 6 +- .../components/traccar/translations/id.json | 17 ++ .../components/traccar/translations/ko.json | 6 +- .../components/tradfri/translations/hu.json | 5 +- .../components/tradfri/translations/id.json | 7 +- .../components/tradfri/translations/ko.json | 2 +- .../components/tradfri/translations/nl.json | 4 +- .../transmission/translations/he.json | 11 ++ .../transmission/translations/hu.json | 8 +- .../transmission/translations/id.json | 36 ++++ .../transmission/translations/ko.json | 2 +- .../components/tuya/translations/de.json | 1 + .../components/tuya/translations/hu.json | 18 +- .../components/tuya/translations/id.json | 65 +++++++ .../components/tuya/translations/ko.json | 37 +++- .../components/tuya/translations/nl.json | 3 + .../components/tuya/translations/pl.json | 1 + .../twentemilieu/translations/hu.json | 6 + .../twentemilieu/translations/id.json | 22 +++ .../twentemilieu/translations/ko.json | 2 +- .../components/twilio/translations/de.json | 2 +- .../components/twilio/translations/hu.json | 8 +- .../components/twilio/translations/id.json | 17 ++ .../components/twilio/translations/ko.json | 4 +- .../components/twilio/translations/nl.json | 2 +- .../components/twinkly/translations/hu.json | 15 ++ .../components/twinkly/translations/id.json | 19 ++ .../components/twinkly/translations/ko.json | 9 + .../components/unifi/translations/he.json | 11 ++ .../components/unifi/translations/hu.json | 10 +- .../components/unifi/translations/id.json | 69 +++++++ .../components/unifi/translations/ko.json | 7 +- .../components/unifi/translations/lb.json | 1 + .../components/unifi/translations/nl.json | 7 +- .../components/upb/translations/hu.json | 18 ++ .../components/upb/translations/id.json | 23 +++ .../components/upb/translations/ko.json | 4 +- .../components/upb/translations/pt.json | 1 + .../components/upcloud/translations/hu.json | 2 +- .../components/upcloud/translations/id.json | 25 +++ .../components/upcloud/translations/ko.json | 9 + .../updater/translations/zh-Hant.json | 2 +- .../components/upnp/translations/de.json | 1 + .../components/upnp/translations/hu.json | 12 +- .../components/upnp/translations/id.json | 21 ++ .../components/upnp/translations/nl.json | 8 +- .../components/vacuum/translations/id.json | 20 +- .../components/vacuum/translations/ko.json | 12 +- .../components/vacuum/translations/nl.json | 2 +- .../vacuum/translations/zh-Hans.json | 3 +- .../components/velbus/translations/de.json | 2 +- .../components/velbus/translations/hu.json | 11 ++ .../components/velbus/translations/id.json | 20 ++ .../components/vera/translations/id.json | 30 +++ .../components/vera/translations/ko.json | 8 +- .../components/verisure/translations/ca.json | 39 ++++ .../components/verisure/translations/et.json | 39 ++++ .../components/verisure/translations/fr.json | 39 ++++ .../components/verisure/translations/pt.json | 11 ++ .../components/vesync/translations/he.json | 11 ++ .../components/vesync/translations/hu.json | 6 + .../components/vesync/translations/id.json | 19 ++ .../components/vesync/translations/ko.json | 4 +- .../components/vilfo/translations/hu.json | 8 + .../components/vilfo/translations/id.json | 22 +++ .../components/vilfo/translations/ko.json | 2 +- .../components/vilfo/translations/nl.json | 10 +- .../components/vizio/translations/hu.json | 16 +- .../components/vizio/translations/id.json | 54 +++++ .../components/vizio/translations/ko.json | 10 +- .../components/vizio/translations/nl.json | 2 +- .../components/volumio/translations/hu.json | 19 +- .../components/volumio/translations/id.json | 24 +++ .../components/volumio/translations/ko.json | 7 +- .../components/volumio/translations/nl.json | 7 +- .../water_heater/translations/ca.json | 11 ++ .../water_heater/translations/et.json | 11 ++ .../water_heater/translations/fr.json | 11 ++ .../water_heater/translations/id.json | 8 + .../water_heater/translations/ko.json | 19 ++ .../water_heater/translations/pt.json | 5 + .../water_heater/translations/ru.json | 11 ++ .../components/wemo/translations/hu.json | 8 + .../components/wemo/translations/id.json | 13 ++ .../components/wemo/translations/ko.json | 4 +- .../components/wiffi/translations/hu.json | 16 ++ .../components/wiffi/translations/id.json | 25 +++ .../components/wiffi/translations/nl.json | 2 +- .../components/wilight/translations/hu.json | 5 + .../components/wilight/translations/id.json | 16 ++ .../components/wilight/translations/ko.json | 7 +- .../components/withings/translations/hu.json | 13 +- .../components/withings/translations/id.json | 33 ++++ .../components/withings/translations/ko.json | 4 +- .../components/withings/translations/nl.json | 3 +- .../components/wled/translations/hu.json | 2 +- .../components/wled/translations/id.json | 24 +++ .../components/wled/translations/ko.json | 4 +- .../components/wled/translations/nl.json | 4 +- .../components/wolflink/translations/hu.json | 18 ++ .../components/wolflink/translations/id.json | 27 +++ .../components/wolflink/translations/nl.json | 6 + .../wolflink/translations/sensor.de.json | 1 + .../wolflink/translations/sensor.hu.json | 7 + .../wolflink/translations/sensor.id.json | 47 +++++ .../wolflink/translations/sensor.ko.json | 70 ++++++- .../wolflink/translations/sensor.nl.json | 24 ++- .../components/xbox/translations/hu.json | 12 +- .../components/xbox/translations/id.json | 17 ++ .../components/xbox/translations/ko.json | 2 +- .../xiaomi_aqara/translations/de.json | 3 +- .../xiaomi_aqara/translations/hu.json | 11 +- .../xiaomi_aqara/translations/id.json | 43 ++++ .../xiaomi_aqara/translations/ko.json | 9 +- .../xiaomi_aqara/translations/nl.json | 10 +- .../xiaomi_miio/translations/bg.json | 11 ++ .../xiaomi_miio/translations/ca.json | 2 +- .../xiaomi_miio/translations/de.json | 12 +- .../xiaomi_miio/translations/hu.json | 16 +- .../xiaomi_miio/translations/id.json | 42 ++++ .../xiaomi_miio/translations/it.json | 2 +- .../xiaomi_miio/translations/ko.json | 11 +- .../xiaomi_miio/translations/nl.json | 9 +- .../xiaomi_miio/translations/no.json | 2 +- .../xiaomi_miio/translations/pl.json | 2 +- .../xiaomi_miio/translations/ru.json | 2 +- .../xiaomi_miio/translations/zh-Hant.json | 2 +- .../components/yeelight/translations/hu.json | 4 +- .../components/yeelight/translations/id.json | 38 ++++ .../components/yeelight/translations/ko.json | 12 +- .../components/zerproc/translations/id.json | 13 ++ .../components/zerproc/translations/ko.json | 2 +- .../components/zha/translations/hu.json | 9 +- .../components/zha/translations/id.json | 91 +++++++++ .../components/zha/translations/ko.json | 51 ++--- .../components/zha/translations/nl.json | 5 +- .../zodiac/translations/sensor.hu.json | 18 ++ .../zodiac/translations/sensor.id.json | 18 ++ .../zodiac/translations/sensor.ko.json | 24 +-- .../components/zone/translations/id.json | 2 +- .../zoneminder/translations/hu.json | 19 +- .../zoneminder/translations/id.json | 34 ++++ .../zoneminder/translations/ko.json | 10 +- .../components/zwave/translations/hu.json | 3 +- .../components/zwave/translations/id.json | 23 ++- .../components/zwave/translations/ko.json | 2 +- .../components/zwave/translations/nl.json | 6 +- .../components/zwave_js/translations/bg.json | 19 ++ .../components/zwave_js/translations/en.json | 6 + .../components/zwave_js/translations/fr.json | 4 +- .../components/zwave_js/translations/hu.json | 49 +++++ .../components/zwave_js/translations/id.json | 61 ++++++ .../components/zwave_js/translations/ko.json | 35 +++- .../components/zwave_js/translations/nl.json | 7 +- .../zwave_js/translations/zh-Hant.json | 30 +-- 1350 files changed, 17396 insertions(+), 1767 deletions(-) create mode 100644 homeassistant/components/abode/translations/he.json create mode 100644 homeassistant/components/abode/translations/id.json create mode 100644 homeassistant/components/accuweather/translations/he.json create mode 100644 homeassistant/components/accuweather/translations/hu.json create mode 100644 homeassistant/components/accuweather/translations/id.json create mode 100644 homeassistant/components/accuweather/translations/sensor.hu.json create mode 100644 homeassistant/components/accuweather/translations/sensor.id.json create mode 100644 homeassistant/components/accuweather/translations/sensor.ko.json create mode 100644 homeassistant/components/acmeda/translations/hu.json create mode 100644 homeassistant/components/acmeda/translations/id.json create mode 100644 homeassistant/components/advantage_air/translations/id.json create mode 100644 homeassistant/components/aemet/translations/hu.json create mode 100644 homeassistant/components/aemet/translations/id.json create mode 100644 homeassistant/components/aemet/translations/lb.json create mode 100644 homeassistant/components/agent_dvr/translations/id.json create mode 100644 homeassistant/components/airly/translations/he.json create mode 100644 homeassistant/components/airly/translations/id.json create mode 100644 homeassistant/components/airnow/translations/bg.json create mode 100644 homeassistant/components/airnow/translations/hu.json create mode 100644 homeassistant/components/airnow/translations/id.json create mode 100644 homeassistant/components/airvisual/translations/bg.json create mode 100644 homeassistant/components/airvisual/translations/id.json create mode 100644 homeassistant/components/alarmdecoder/translations/id.json create mode 100644 homeassistant/components/almond/translations/id.json create mode 100644 homeassistant/components/ambiclimate/translations/id.json create mode 100644 homeassistant/components/ambient_station/translations/id.json create mode 100644 homeassistant/components/apple_tv/translations/id.json create mode 100644 homeassistant/components/arcam_fmj/translations/id.json create mode 100644 homeassistant/components/asuswrt/translations/bg.json create mode 100644 homeassistant/components/asuswrt/translations/hu.json create mode 100644 homeassistant/components/asuswrt/translations/id.json create mode 100644 homeassistant/components/atag/translations/id.json create mode 100644 homeassistant/components/august/translations/he.json create mode 100644 homeassistant/components/august/translations/id.json create mode 100644 homeassistant/components/aurora/translations/id.json create mode 100644 homeassistant/components/awair/translations/id.json create mode 100644 homeassistant/components/axis/translations/he.json create mode 100644 homeassistant/components/axis/translations/id.json create mode 100644 homeassistant/components/azure_devops/translations/id.json create mode 100644 homeassistant/components/blebox/translations/id.json create mode 100644 homeassistant/components/blink/translations/id.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/bg.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/hu.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/id.json create mode 100644 homeassistant/components/bond/translations/id.json create mode 100644 homeassistant/components/braviatv/translations/id.json create mode 100644 homeassistant/components/broadlink/translations/id.json create mode 100644 homeassistant/components/brother/translations/id.json create mode 100644 homeassistant/components/bsblan/translations/id.json create mode 100644 homeassistant/components/canary/translations/hu.json create mode 100644 homeassistant/components/canary/translations/id.json create mode 100644 homeassistant/components/cert_expiry/translations/id.json create mode 100644 homeassistant/components/climacell/translations/bg.json create mode 100644 homeassistant/components/climacell/translations/hu.json create mode 100644 homeassistant/components/climacell/translations/id.json create mode 100644 homeassistant/components/climacell/translations/ko.json create mode 100644 homeassistant/components/climacell/translations/pl.json create mode 100644 homeassistant/components/climacell/translations/pt.json create mode 100644 homeassistant/components/climacell/translations/zh-Hans.json create mode 100644 homeassistant/components/cloud/translations/id.json create mode 100644 homeassistant/components/cloud/translations/ko.json create mode 100644 homeassistant/components/cloudflare/translations/id.json create mode 100644 homeassistant/components/control4/translations/id.json create mode 100644 homeassistant/components/coolmaster/translations/id.json create mode 100644 homeassistant/components/coronavirus/translations/id.json create mode 100644 homeassistant/components/daikin/translations/id.json create mode 100644 homeassistant/components/demo/translations/id.json create mode 100644 homeassistant/components/denonavr/translations/id.json create mode 100644 homeassistant/components/devolo_home_control/translations/id.json create mode 100644 homeassistant/components/dexcom/translations/id.json create mode 100644 homeassistant/components/dialogflow/translations/id.json create mode 100644 homeassistant/components/directv/translations/id.json create mode 100644 homeassistant/components/doorbird/translations/id.json create mode 100644 homeassistant/components/dsmr/translations/id.json create mode 100644 homeassistant/components/dunehd/translations/id.json create mode 100644 homeassistant/components/eafm/translations/id.json create mode 100644 homeassistant/components/ebusd/translations/id.json create mode 100644 homeassistant/components/ecobee/translations/id.json create mode 100644 homeassistant/components/econet/translations/bg.json create mode 100644 homeassistant/components/econet/translations/hu.json create mode 100644 homeassistant/components/econet/translations/id.json create mode 100644 homeassistant/components/elgato/translations/id.json create mode 100644 homeassistant/components/elkm1/translations/he.json create mode 100644 homeassistant/components/elkm1/translations/id.json create mode 100644 homeassistant/components/emulated_roku/translations/id.json create mode 100644 homeassistant/components/enocean/translations/hu.json create mode 100644 homeassistant/components/enocean/translations/id.json create mode 100644 homeassistant/components/epson/translations/id.json create mode 100644 homeassistant/components/esphome/translations/he.json create mode 100644 homeassistant/components/faa_delays/translations/bg.json create mode 100644 homeassistant/components/faa_delays/translations/hu.json create mode 100644 homeassistant/components/faa_delays/translations/id.json create mode 100644 homeassistant/components/faa_delays/translations/ko.json create mode 100644 homeassistant/components/faa_delays/translations/pl.json create mode 100644 homeassistant/components/faa_delays/translations/pt.json create mode 100644 homeassistant/components/faa_delays/translations/zh-Hans.json create mode 100644 homeassistant/components/fireservicerota/translations/id.json create mode 100644 homeassistant/components/firmata/translations/hu.json create mode 100644 homeassistant/components/firmata/translations/id.json create mode 100644 homeassistant/components/flick_electric/translations/id.json create mode 100644 homeassistant/components/flo/translations/id.json create mode 100644 homeassistant/components/flume/translations/he.json create mode 100644 homeassistant/components/flume/translations/id.json create mode 100644 homeassistant/components/flunearyou/translations/he.json create mode 100644 homeassistant/components/flunearyou/translations/hu.json create mode 100644 homeassistant/components/flunearyou/translations/id.json create mode 100644 homeassistant/components/flunearyou/translations/zh-Hans.json create mode 100644 homeassistant/components/forked_daapd/translations/id.json create mode 100644 homeassistant/components/foscam/translations/bg.json create mode 100644 homeassistant/components/foscam/translations/hu.json create mode 100644 homeassistant/components/foscam/translations/id.json create mode 100644 homeassistant/components/freebox/translations/id.json create mode 100644 homeassistant/components/fritzbox/translations/bg.json create mode 100644 homeassistant/components/fritzbox/translations/he.json create mode 100644 homeassistant/components/fritzbox/translations/id.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/bg.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/hu.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/id.json create mode 100644 homeassistant/components/garmin_connect/translations/he.json create mode 100644 homeassistant/components/garmin_connect/translations/id.json create mode 100644 homeassistant/components/gdacs/translations/id.json create mode 100644 homeassistant/components/geofency/translations/id.json create mode 100644 homeassistant/components/geonetnz_quakes/translations/id.json create mode 100644 homeassistant/components/geonetnz_volcano/translations/id.json create mode 100644 homeassistant/components/gios/translations/id.json create mode 100644 homeassistant/components/glances/translations/he.json create mode 100644 homeassistant/components/glances/translations/id.json create mode 100644 homeassistant/components/goalzero/translations/hu.json create mode 100644 homeassistant/components/goalzero/translations/id.json create mode 100644 homeassistant/components/gogogate2/translations/id.json create mode 100644 homeassistant/components/gpslogger/translations/id.json create mode 100644 homeassistant/components/gree/translations/hu.json create mode 100644 homeassistant/components/gree/translations/id.json create mode 100644 homeassistant/components/guardian/translations/id.json create mode 100644 homeassistant/components/habitica/translations/bg.json create mode 100644 homeassistant/components/habitica/translations/hu.json create mode 100644 homeassistant/components/habitica/translations/id.json create mode 100644 homeassistant/components/habitica/translations/pt.json create mode 100644 homeassistant/components/harmony/translations/id.json create mode 100644 homeassistant/components/hassio/translations/id.json delete mode 100644 homeassistant/components/hassio/translations/nb.json create mode 100644 homeassistant/components/heos/translations/id.json create mode 100644 homeassistant/components/hisense_aehw4a1/translations/id.json create mode 100644 homeassistant/components/hive/translations/ca.json create mode 100644 homeassistant/components/hive/translations/de.json create mode 100644 homeassistant/components/hive/translations/et.json create mode 100644 homeassistant/components/hive/translations/fr.json create mode 100644 homeassistant/components/hive/translations/ko.json create mode 100644 homeassistant/components/hive/translations/pt.json create mode 100644 homeassistant/components/hive/translations/ru.json create mode 100644 homeassistant/components/hive/translations/zh-Hant.json create mode 100644 homeassistant/components/hlk_sw16/translations/id.json create mode 100644 homeassistant/components/home_connect/translations/id.json create mode 100644 homeassistant/components/homeassistant/translations/he.json create mode 100644 homeassistant/components/homeassistant/translations/id.json create mode 100644 homeassistant/components/homeassistant/translations/ko.json create mode 100644 homeassistant/components/homekit/translations/id.json create mode 100644 homeassistant/components/homekit_controller/translations/id.json create mode 100644 homeassistant/components/huawei_lte/translations/he.json create mode 100644 homeassistant/components/huawei_lte/translations/id.json create mode 100644 homeassistant/components/huisbaasje/translations/bg.json create mode 100644 homeassistant/components/huisbaasje/translations/hu.json create mode 100644 homeassistant/components/huisbaasje/translations/id.json create mode 100644 homeassistant/components/humidifier/translations/hu.json create mode 100644 homeassistant/components/humidifier/translations/id.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/id.json create mode 100644 homeassistant/components/hvv_departures/translations/he.json create mode 100644 homeassistant/components/hvv_departures/translations/hu.json create mode 100644 homeassistant/components/hvv_departures/translations/id.json create mode 100644 homeassistant/components/hyperion/translations/id.json create mode 100644 homeassistant/components/iaqualink/translations/he.json create mode 100644 homeassistant/components/iaqualink/translations/id.json create mode 100644 homeassistant/components/icloud/translations/id.json create mode 100644 homeassistant/components/ifttt/translations/id.json create mode 100644 homeassistant/components/insteon/translations/he.json create mode 100644 homeassistant/components/insteon/translations/hu.json create mode 100644 homeassistant/components/insteon/translations/id.json create mode 100644 homeassistant/components/ipma/translations/id.json create mode 100644 homeassistant/components/ipp/translations/id.json create mode 100644 homeassistant/components/iqvia/translations/hu.json create mode 100644 homeassistant/components/islamic_prayer_times/translations/hu.json create mode 100644 homeassistant/components/islamic_prayer_times/translations/id.json create mode 100644 homeassistant/components/isy994/translations/id.json create mode 100644 homeassistant/components/izone/translations/id.json create mode 100644 homeassistant/components/juicenet/translations/hu.json create mode 100644 homeassistant/components/juicenet/translations/id.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/bg.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/hu.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/id.json create mode 100644 homeassistant/components/kmtronic/translations/bg.json create mode 100644 homeassistant/components/kmtronic/translations/es.json create mode 100644 homeassistant/components/kmtronic/translations/hu.json create mode 100644 homeassistant/components/kmtronic/translations/id.json create mode 100644 homeassistant/components/kmtronic/translations/ko.json create mode 100644 homeassistant/components/kmtronic/translations/pt.json create mode 100644 homeassistant/components/kodi/translations/id.json create mode 100644 homeassistant/components/konnected/translations/id.json create mode 100644 homeassistant/components/kulersky/translations/id.json create mode 100644 homeassistant/components/life360/translations/he.json create mode 100644 homeassistant/components/lifx/translations/id.json create mode 100644 homeassistant/components/litejet/translations/bg.json create mode 100644 homeassistant/components/litejet/translations/hu.json create mode 100644 homeassistant/components/litejet/translations/id.json create mode 100644 homeassistant/components/litejet/translations/ko.json create mode 100644 homeassistant/components/litejet/translations/pt.json create mode 100644 homeassistant/components/litterrobot/translations/bg.json create mode 100644 homeassistant/components/litterrobot/translations/es.json create mode 100644 homeassistant/components/litterrobot/translations/hu.json create mode 100644 homeassistant/components/litterrobot/translations/id.json create mode 100644 homeassistant/components/litterrobot/translations/ko.json create mode 100644 homeassistant/components/litterrobot/translations/pt.json create mode 100644 homeassistant/components/local_ip/translations/id.json create mode 100644 homeassistant/components/locative/translations/id.json create mode 100644 homeassistant/components/logi_circle/translations/id.json create mode 100644 homeassistant/components/lovelace/translations/id.json create mode 100644 homeassistant/components/lovelace/translations/ko.json create mode 100644 homeassistant/components/luftdaten/translations/id.json create mode 100644 homeassistant/components/lutron_caseta/translations/bg.json create mode 100644 homeassistant/components/lutron_caseta/translations/he.json create mode 100644 homeassistant/components/lutron_caseta/translations/hu.json create mode 100644 homeassistant/components/lutron_caseta/translations/id.json create mode 100644 homeassistant/components/lyric/translations/hu.json create mode 100644 homeassistant/components/lyric/translations/id.json create mode 100644 homeassistant/components/mailgun/translations/id.json create mode 100644 homeassistant/components/mazda/translations/bg.json create mode 100644 homeassistant/components/mazda/translations/hu.json create mode 100644 homeassistant/components/mazda/translations/id.json create mode 100644 homeassistant/components/melcloud/translations/he.json create mode 100644 homeassistant/components/melcloud/translations/id.json create mode 100644 homeassistant/components/met/translations/he.json create mode 100644 homeassistant/components/meteo_france/translations/id.json create mode 100644 homeassistant/components/metoffice/translations/he.json create mode 100644 homeassistant/components/metoffice/translations/id.json create mode 100644 homeassistant/components/mikrotik/translations/he.json create mode 100644 homeassistant/components/mikrotik/translations/id.json create mode 100644 homeassistant/components/mill/translations/id.json create mode 100644 homeassistant/components/minecraft_server/translations/id.json create mode 100644 homeassistant/components/mobile_app/translations/id.json create mode 100644 homeassistant/components/monoprice/translations/id.json create mode 100644 homeassistant/components/moon/translations/sensor.id.json create mode 100644 homeassistant/components/motion_blinds/translations/bg.json create mode 100644 homeassistant/components/motion_blinds/translations/hu.json create mode 100644 homeassistant/components/motion_blinds/translations/id.json create mode 100644 homeassistant/components/mullvad/translations/bg.json create mode 100644 homeassistant/components/mullvad/translations/hu.json create mode 100644 homeassistant/components/mullvad/translations/id.json create mode 100644 homeassistant/components/mullvad/translations/ko.json create mode 100644 homeassistant/components/mullvad/translations/pl.json create mode 100644 homeassistant/components/mullvad/translations/pt.json create mode 100644 homeassistant/components/mullvad/translations/zh-Hans.json create mode 100644 homeassistant/components/myq/translations/he.json create mode 100644 homeassistant/components/myq/translations/id.json create mode 100644 homeassistant/components/mysensors/translations/bg.json create mode 100644 homeassistant/components/mysensors/translations/hu.json create mode 100644 homeassistant/components/mysensors/translations/id.json create mode 100644 homeassistant/components/neato/translations/he.json create mode 100644 homeassistant/components/neato/translations/id.json create mode 100644 homeassistant/components/netatmo/translations/id.json create mode 100644 homeassistant/components/netatmo/translations/zh-Hans.json create mode 100644 homeassistant/components/nexia/translations/he.json create mode 100644 homeassistant/components/nexia/translations/id.json create mode 100644 homeassistant/components/nightscout/translations/id.json create mode 100644 homeassistant/components/notion/translations/he.json create mode 100644 homeassistant/components/notion/translations/id.json create mode 100644 homeassistant/components/nuheat/translations/he.json create mode 100644 homeassistant/components/nuheat/translations/id.json create mode 100644 homeassistant/components/nuki/translations/bg.json create mode 100644 homeassistant/components/nuki/translations/hu.json create mode 100644 homeassistant/components/nuki/translations/id.json create mode 100644 homeassistant/components/number/translations/de.json create mode 100644 homeassistant/components/number/translations/hu.json create mode 100644 homeassistant/components/number/translations/id.json create mode 100644 homeassistant/components/number/translations/ko.json create mode 100644 homeassistant/components/number/translations/zh-Hans.json create mode 100644 homeassistant/components/nut/translations/he.json create mode 100644 homeassistant/components/nut/translations/id.json create mode 100644 homeassistant/components/nws/translations/he.json create mode 100644 homeassistant/components/nws/translations/id.json create mode 100644 homeassistant/components/nzbget/translations/hu.json create mode 100644 homeassistant/components/nzbget/translations/id.json create mode 100644 homeassistant/components/omnilogic/translations/hu.json create mode 100644 homeassistant/components/omnilogic/translations/id.json create mode 100644 homeassistant/components/ondilo_ico/translations/hu.json create mode 100644 homeassistant/components/ondilo_ico/translations/id.json create mode 100644 homeassistant/components/onewire/translations/id.json create mode 100644 homeassistant/components/onvif/translations/id.json create mode 100644 homeassistant/components/opentherm_gw/translations/id.json create mode 100644 homeassistant/components/openweathermap/translations/he.json create mode 100644 homeassistant/components/openweathermap/translations/hu.json create mode 100644 homeassistant/components/openweathermap/translations/id.json create mode 100644 homeassistant/components/ovo_energy/translations/id.json create mode 100644 homeassistant/components/owntracks/translations/id.json create mode 100644 homeassistant/components/ozw/translations/id.json create mode 100644 homeassistant/components/panasonic_viera/translations/id.json create mode 100644 homeassistant/components/philips_js/translations/bg.json create mode 100644 homeassistant/components/philips_js/translations/hu.json create mode 100644 homeassistant/components/philips_js/translations/id.json create mode 100644 homeassistant/components/philips_js/translations/zh-Hans.json create mode 100644 homeassistant/components/pi_hole/translations/id.json create mode 100644 homeassistant/components/plaato/translations/id.json create mode 100644 homeassistant/components/plex/translations/id.json create mode 100644 homeassistant/components/plugwise/translations/id.json create mode 100644 homeassistant/components/plum_lightpad/translations/id.json create mode 100644 homeassistant/components/point/translations/id.json create mode 100644 homeassistant/components/poolsense/translations/id.json create mode 100644 homeassistant/components/powerwall/translations/bg.json create mode 100644 homeassistant/components/powerwall/translations/id.json create mode 100644 homeassistant/components/profiler/translations/id.json create mode 100644 homeassistant/components/progettihwsw/translations/id.json create mode 100644 homeassistant/components/ps4/translations/id.json create mode 100644 homeassistant/components/pvpc_hourly_pricing/translations/hu.json create mode 100644 homeassistant/components/pvpc_hourly_pricing/translations/id.json create mode 100644 homeassistant/components/rachio/translations/id.json create mode 100644 homeassistant/components/rainmachine/translations/he.json create mode 100644 homeassistant/components/rainmachine/translations/id.json create mode 100644 homeassistant/components/recollect_waste/translations/id.json create mode 100644 homeassistant/components/rfxtrx/translations/id.json create mode 100644 homeassistant/components/ring/translations/he.json create mode 100644 homeassistant/components/ring/translations/id.json create mode 100644 homeassistant/components/risco/translations/id.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/bg.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/hu.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/id.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/ko.json create mode 100644 homeassistant/components/roku/translations/id.json create mode 100644 homeassistant/components/roomba/translations/bg.json create mode 100644 homeassistant/components/roomba/translations/he.json create mode 100644 homeassistant/components/roomba/translations/id.json create mode 100644 homeassistant/components/roon/translations/id.json create mode 100644 homeassistant/components/rpi_power/translations/hu.json create mode 100644 homeassistant/components/rpi_power/translations/id.json create mode 100644 homeassistant/components/ruckus_unleashed/translations/he.json create mode 100644 homeassistant/components/ruckus_unleashed/translations/id.json create mode 100644 homeassistant/components/samsungtv/translations/id.json create mode 100644 homeassistant/components/season/translations/sensor.id.json create mode 100644 homeassistant/components/sense/translations/he.json create mode 100644 homeassistant/components/sense/translations/id.json create mode 100644 homeassistant/components/sentry/translations/id.json create mode 100644 homeassistant/components/sharkiq/translations/hu.json create mode 100644 homeassistant/components/sharkiq/translations/id.json create mode 100644 homeassistant/components/shelly/translations/bg.json create mode 100644 homeassistant/components/shelly/translations/id.json create mode 100644 homeassistant/components/shopping_list/translations/id.json create mode 100644 homeassistant/components/simplisafe/translations/he.json create mode 100644 homeassistant/components/simplisafe/translations/id.json create mode 100644 homeassistant/components/smappee/translations/id.json create mode 100644 homeassistant/components/smart_meter_texas/translations/id.json create mode 100644 homeassistant/components/smarthab/translations/id.json create mode 100644 homeassistant/components/smartthings/translations/id.json create mode 100644 homeassistant/components/smarttub/translations/bg.json create mode 100644 homeassistant/components/smarttub/translations/hu.json create mode 100644 homeassistant/components/smarttub/translations/id.json create mode 100644 homeassistant/components/smarttub/translations/pt.json create mode 100644 homeassistant/components/smhi/translations/he.json create mode 100644 homeassistant/components/smhi/translations/id.json create mode 100644 homeassistant/components/sms/translations/id.json create mode 100644 homeassistant/components/solaredge/translations/id.json create mode 100644 homeassistant/components/solarlog/translations/id.json create mode 100644 homeassistant/components/soma/translations/id.json create mode 100644 homeassistant/components/somfy/translations/id.json create mode 100644 homeassistant/components/somfy_mylink/translations/bg.json create mode 100644 homeassistant/components/somfy_mylink/translations/he.json create mode 100644 homeassistant/components/somfy_mylink/translations/hu.json create mode 100644 homeassistant/components/somfy_mylink/translations/id.json create mode 100644 homeassistant/components/sonarr/translations/id.json create mode 100644 homeassistant/components/songpal/translations/id.json create mode 100644 homeassistant/components/speedtestdotnet/translations/hu.json create mode 100644 homeassistant/components/speedtestdotnet/translations/id.json create mode 100644 homeassistant/components/spider/translations/hu.json create mode 100644 homeassistant/components/spider/translations/id.json create mode 100644 homeassistant/components/spotify/translations/id.json create mode 100644 homeassistant/components/squeezebox/translations/id.json create mode 100644 homeassistant/components/srp_energy/translations/id.json create mode 100644 homeassistant/components/starline/translations/he.json create mode 100644 homeassistant/components/starline/translations/id.json create mode 100644 homeassistant/components/subaru/translations/bg.json create mode 100644 homeassistant/components/subaru/translations/hu.json create mode 100644 homeassistant/components/subaru/translations/id.json create mode 100644 homeassistant/components/subaru/translations/ko.json create mode 100644 homeassistant/components/syncthru/translations/id.json create mode 100644 homeassistant/components/synology_dsm/translations/id.json create mode 100644 homeassistant/components/system_health/translations/id.json create mode 100644 homeassistant/components/tado/translations/he.json create mode 100644 homeassistant/components/tado/translations/id.json create mode 100644 homeassistant/components/tag/translations/hu.json create mode 100644 homeassistant/components/tag/translations/id.json create mode 100644 homeassistant/components/tasmota/translations/id.json create mode 100644 homeassistant/components/tellduslive/translations/id.json create mode 100644 homeassistant/components/tesla/translations/he.json create mode 100644 homeassistant/components/tesla/translations/id.json create mode 100644 homeassistant/components/tibber/translations/id.json create mode 100644 homeassistant/components/tile/translations/hu.json create mode 100644 homeassistant/components/tile/translations/id.json create mode 100644 homeassistant/components/toon/translations/hu.json create mode 100644 homeassistant/components/toon/translations/id.json create mode 100644 homeassistant/components/totalconnect/translations/he.json create mode 100644 homeassistant/components/totalconnect/translations/id.json create mode 100644 homeassistant/components/tplink/translations/hu.json create mode 100644 homeassistant/components/tplink/translations/id.json create mode 100644 homeassistant/components/traccar/translations/id.json create mode 100644 homeassistant/components/transmission/translations/he.json create mode 100644 homeassistant/components/transmission/translations/id.json create mode 100644 homeassistant/components/tuya/translations/id.json create mode 100644 homeassistant/components/twentemilieu/translations/id.json create mode 100644 homeassistant/components/twilio/translations/id.json create mode 100644 homeassistant/components/twinkly/translations/hu.json create mode 100644 homeassistant/components/twinkly/translations/id.json create mode 100644 homeassistant/components/unifi/translations/he.json create mode 100644 homeassistant/components/unifi/translations/id.json create mode 100644 homeassistant/components/upb/translations/hu.json create mode 100644 homeassistant/components/upb/translations/id.json create mode 100644 homeassistant/components/upcloud/translations/id.json create mode 100644 homeassistant/components/upnp/translations/id.json create mode 100644 homeassistant/components/velbus/translations/hu.json create mode 100644 homeassistant/components/velbus/translations/id.json create mode 100644 homeassistant/components/vera/translations/id.json create mode 100644 homeassistant/components/verisure/translations/ca.json create mode 100644 homeassistant/components/verisure/translations/et.json create mode 100644 homeassistant/components/verisure/translations/fr.json create mode 100644 homeassistant/components/verisure/translations/pt.json create mode 100644 homeassistant/components/vesync/translations/he.json create mode 100644 homeassistant/components/vesync/translations/id.json create mode 100644 homeassistant/components/vilfo/translations/id.json create mode 100644 homeassistant/components/vizio/translations/id.json create mode 100644 homeassistant/components/volumio/translations/id.json create mode 100644 homeassistant/components/water_heater/translations/id.json create mode 100644 homeassistant/components/water_heater/translations/ko.json create mode 100644 homeassistant/components/wemo/translations/hu.json create mode 100644 homeassistant/components/wemo/translations/id.json create mode 100644 homeassistant/components/wiffi/translations/id.json create mode 100644 homeassistant/components/wilight/translations/id.json create mode 100644 homeassistant/components/withings/translations/id.json create mode 100644 homeassistant/components/wled/translations/id.json create mode 100644 homeassistant/components/wolflink/translations/id.json create mode 100644 homeassistant/components/wolflink/translations/sensor.hu.json create mode 100644 homeassistant/components/wolflink/translations/sensor.id.json create mode 100644 homeassistant/components/xbox/translations/id.json create mode 100644 homeassistant/components/xiaomi_aqara/translations/id.json create mode 100644 homeassistant/components/xiaomi_miio/translations/bg.json create mode 100644 homeassistant/components/xiaomi_miio/translations/id.json create mode 100644 homeassistant/components/yeelight/translations/id.json create mode 100644 homeassistant/components/zerproc/translations/id.json create mode 100644 homeassistant/components/zha/translations/id.json create mode 100644 homeassistant/components/zodiac/translations/sensor.hu.json create mode 100644 homeassistant/components/zodiac/translations/sensor.id.json create mode 100644 homeassistant/components/zoneminder/translations/id.json create mode 100644 homeassistant/components/zwave_js/translations/bg.json create mode 100644 homeassistant/components/zwave_js/translations/hu.json create mode 100644 homeassistant/components/zwave_js/translations/id.json diff --git a/homeassistant/components/abode/translations/he.json b/homeassistant/components/abode/translations/he.json new file mode 100644 index 00000000000..6f4191da70d --- /dev/null +++ b/homeassistant/components/abode/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/abode/translations/hu.json b/homeassistant/components/abode/translations/hu.json index 5df508d0f33..260416b07bb 100644 --- a/homeassistant/components/abode/translations/hu.json +++ b/homeassistant/components/abode/translations/hu.json @@ -1,16 +1,25 @@ { "config": { "abort": { - "single_instance_allowed": "Csak egyetlen Abode konfigur\u00e1ci\u00f3 enged\u00e9lyezett." + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "invalid_mfa_code": "\u00c9rv\u00e9nytelen MFA k\u00f3d" }, "step": { "mfa": { "data": { "mfa_code": "MFA k\u00f3d (6 jegy\u0171)" + }, + "title": "Add meg az Abode MFA k\u00f3dj\u00e1t" + }, + "reauth_confirm": { + "data": { + "password": "Jelsz\u00f3", + "username": "E-mail" } }, "user": { diff --git a/homeassistant/components/abode/translations/id.json b/homeassistant/components/abode/translations/id.json new file mode 100644 index 00000000000..2dc79c833b2 --- /dev/null +++ b/homeassistant/components/abode/translations/id.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "reauth_successful": "Autentikasi ulang berhasil", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "invalid_mfa_code": "Kode MFA tidak valid" + }, + "step": { + "mfa": { + "data": { + "mfa_code": "Kode MFA (6 digit)" + }, + "title": "Masukkan kode MFA Anda untuk Abode" + }, + "reauth_confirm": { + "data": { + "password": "Kata Sandi", + "username": "Email" + }, + "title": "Masukkan informasi masuk Abode Anda" + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Email" + }, + "title": "Masukkan informasi masuk Abode Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/abode/translations/ko.json b/homeassistant/components/abode/translations/ko.json index a9756447adf..85d3ef81aeb 100644 --- a/homeassistant/components/abode/translations/ko.json +++ b/homeassistant/components/abode/translations/ko.json @@ -2,18 +2,26 @@ "config": { "abort": { "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_mfa_code": "MFA \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { + "mfa": { + "data": { + "mfa_code": "MFA \ucf54\ub4dc (6\uc790\ub9ac)" + }, + "title": "Abode\uc5d0 \ub300\ud55c MFA \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" + }, "reauth_confirm": { "data": { "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc774\uba54\uc77c" - } + }, + "title": "Abode \ub85c\uadf8\uc778 \uc815\ubcf4 \uc785\ub825\ud558\uae30" }, "user": { "data": { diff --git a/homeassistant/components/accuweather/translations/he.json b/homeassistant/components/accuweather/translations/he.json new file mode 100644 index 00000000000..4c49313d977 --- /dev/null +++ b/homeassistant/components/accuweather/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/hu.json b/homeassistant/components/accuweather/translations/hu.json new file mode 100644 index 00000000000..8a0f7f5a198 --- /dev/null +++ b/homeassistant/components/accuweather/translations/hu.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs" + }, + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g", + "name": "N\u00e9v" + }, + "title": "AccuWeather" + } + } + }, + "options": { + "step": { + "user": { + "title": "AccuWeather be\u00e1ll\u00edt\u00e1sok" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/id.json b/homeassistant/components/accuweather/translations/id.json new file mode 100644 index 00000000000..970b3a026b7 --- /dev/null +++ b/homeassistant/components/accuweather/translations/id.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_api_key": "Kunci API tidak valid", + "requests_exceeded": "Jumlah permintaan yang diizinkan ke API Accuweather telah terlampaui. Anda harus menunggu atau mengubah Kunci API." + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "longitude": "Bujur", + "name": "Nama" + }, + "description": "Jika Anda memerlukan bantuan tentang konfigurasi, baca di sini: https://www.home-assistant.io/integrations/accuweather/\n\nBeberapa sensor tidak diaktifkan secara default. Anda dapat mengaktifkannya di registri entitas setelah konfigurasi integrasi.\nPrakiraan cuaca tidak diaktifkan secara default. Anda dapat mengaktifkannya di opsi integrasi.", + "title": "AccuWeather" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "forecast": "Prakiraan cuaca" + }, + "description": "Karena keterbatasan versi gratis kunci API AccuWeather, ketika Anda mengaktifkan prakiraan cuaca, pembaruan data akan dilakukan setiap 80 menit, bukan setiap 40 menit.", + "title": "Opsi AccuWeather" + } + } + }, + "system_health": { + "info": { + "can_reach_server": "Keterjangkauan server AccuWeather", + "remaining_requests": "Sisa permintaan yang diizinkan" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/ko.json b/homeassistant/components/accuweather/translations/ko.json index 2f0a01e094b..d992d0bfdd4 100644 --- a/homeassistant/components/accuweather/translations/ko.json +++ b/homeassistant/components/accuweather/translations/ko.json @@ -1,11 +1,12 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "requests_exceeded": "Accuweather API\uc5d0 \ud5c8\uc6a9\ub41c \uc694\uccad \uc218\uac00 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uae30\ub2e4\ub9ac\uac70\ub098 API \ud0a4\ub97c \ubcc0\uacbd\ud574\uc57c \ud569\ub2c8\ub2e4." }, "step": { "user": { @@ -15,15 +16,26 @@ "longitude": "\uacbd\ub3c4", "name": "\uc774\ub984" }, - "description": "\uad6c\uc131\uc5d0 \ub300\ud55c \ub3c4\uc6c0\uc774 \ud544\uc694\ud55c \uacbd\uc6b0 \ub2e4\uc74c\uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694:\nhttps://www.home-assistant.io/integrations/accuweather/\n\n\uc77c\ubd80 \uc13c\uc11c\ub294 \uae30\ubcf8\uc801\uc73c\ub85c \ud65c\uc131\ud654\ub418\uc5b4 \uc788\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \uc5f0\ub3d9 \uad6c\uc131 \ud6c4 \uad6c\uc131\uc694\uc18c \ub808\uc9c0\uc2a4\ud2b8\ub9ac\uc5d0\uc11c \ud65c\uc131\ud654\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.\n\uc77c\uae30\uc608\ubcf4\ub294 \uae30\ubcf8\uc801\uc73c\ub85c \ud65c\uc131\ud654\ub418\uc5b4 \uc788\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \uc5f0\ub3d9 \uc635\uc158\uc5d0\uc11c \ud65c\uc131\ud654\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "description": "\uad6c\uc131\uc5d0 \ub300\ud55c \ub3c4\uc6c0\uc774 \ud544\uc694\ud55c \uacbd\uc6b0 \ub2e4\uc74c\uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694: https://www.home-assistant.io/integrations/accuweather/\n\n\uc77c\ubd80 \uc13c\uc11c\ub294 \uae30\ubcf8\uc801\uc73c\ub85c \ud65c\uc131\ud654\ub418\uc5b4 \uc788\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uad6c\uc131 \ud6c4 \uad6c\uc131\uc694\uc18c \ub808\uc9c0\uc2a4\ud2b8\ub9ac\uc5d0\uc11c \ud65c\uc131\ud654\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.\n\uc77c\uae30\uc608\ubcf4\ub294 \uae30\ubcf8\uc801\uc73c\ub85c \ud65c\uc131\ud654\ub418\uc5b4 \uc788\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc635\uc158\uc5d0\uc11c \ud65c\uc131\ud654\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "title": "AccuWeather" } } }, "options": { "step": { "user": { - "description": "\ubb34\ub8cc \ubc84\uc804\uc758 AccuWeather API \ud0a4\ub85c \uc77c\uae30\uc608\ubcf4\ub97c \ud65c\uc131\ud654\ud55c \uacbd\uc6b0 \uc81c\ud55c\uc0ac\ud56d\uc73c\ub85c \uc778\ud574 \uc5c5\ub370\uc774\ud2b8\ub294 40 \ubd84\uc774 \uc544\ub2cc 80 \ubd84\ub9c8\ub2e4 \uc218\ud589\ub429\ub2c8\ub2e4." + "data": { + "forecast": "\ub0a0\uc528 \uc608\ubcf4" + }, + "description": "\ubb34\ub8cc \ubc84\uc804\uc758 AccuWeather API \ud0a4\ub85c \uc77c\uae30\uc608\ubcf4\ub97c \ud65c\uc131\ud654\ud55c \uacbd\uc6b0 \uc81c\ud55c\uc0ac\ud56d\uc73c\ub85c \uc778\ud574 \uc5c5\ub370\uc774\ud2b8\ub294 40 \ubd84\uc774 \uc544\ub2cc 80 \ubd84\ub9c8\ub2e4 \uc218\ud589\ub429\ub2c8\ub2e4.", + "title": "AccuWeather \uc635\uc158" } } + }, + "system_health": { + "info": { + "can_reach_server": "AccuWeather \uc11c\ubc84 \uc5f0\uacb0", + "remaining_requests": "\ub0a8\uc740 \ud5c8\uc6a9 \uc694\uccad \ud69f\uc218" + } } } \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/nl.json b/homeassistant/components/accuweather/translations/nl.json index 4bf5f9fce45..342df3cca78 100644 --- a/homeassistant/components/accuweather/translations/nl.json +++ b/homeassistant/components/accuweather/translations/nl.json @@ -31,5 +31,10 @@ "title": "AccuWeather-opties" } } + }, + "system_health": { + "info": { + "can_reach_server": "Kan AccuWeather server bereiken" + } } } \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/sensor.hu.json b/homeassistant/components/accuweather/translations/sensor.hu.json new file mode 100644 index 00000000000..49f2fe41ab3 --- /dev/null +++ b/homeassistant/components/accuweather/translations/sensor.hu.json @@ -0,0 +1,9 @@ +{ + "state": { + "accuweather__pressure_tendency": { + "falling": "Cs\u00f6kken\u0151", + "rising": "Emelked\u0151", + "steady": "\u00c1lland\u00f3" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/sensor.id.json b/homeassistant/components/accuweather/translations/sensor.id.json new file mode 100644 index 00000000000..8ce99bbc8c3 --- /dev/null +++ b/homeassistant/components/accuweather/translations/sensor.id.json @@ -0,0 +1,9 @@ +{ + "state": { + "accuweather__pressure_tendency": { + "falling": "Turun", + "rising": "Naik", + "steady": "Tetap" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/sensor.ko.json b/homeassistant/components/accuweather/translations/sensor.ko.json new file mode 100644 index 00000000000..287974fa3fd --- /dev/null +++ b/homeassistant/components/accuweather/translations/sensor.ko.json @@ -0,0 +1,9 @@ +{ + "state": { + "accuweather__pressure_tendency": { + "falling": "\ud558\uac15", + "rising": "\uc0c1\uc2b9", + "steady": "\uc548\uc815" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/acmeda/translations/hu.json b/homeassistant/components/acmeda/translations/hu.json new file mode 100644 index 00000000000..6105977de80 --- /dev/null +++ b/homeassistant/components/acmeda/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/acmeda/translations/id.json b/homeassistant/components/acmeda/translations/id.json new file mode 100644 index 00000000000..6e80d134f5a --- /dev/null +++ b/homeassistant/components/acmeda/translations/id.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan" + }, + "step": { + "user": { + "data": { + "id": "ID Host" + }, + "title": "Pilih hub untuk ditambahkan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/adguard/translations/bg.json b/homeassistant/components/adguard/translations/bg.json index 1edfc9d8da6..82c658e7c15 100644 --- a/homeassistant/components/adguard/translations/bg.json +++ b/homeassistant/components/adguard/translations/bg.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 Home Assistant \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0437\u0432\u0430 \u0441 AdGuard Home, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d \u043e\u0442 Hass.io \u0434\u043e\u0431\u0430\u0432\u043a\u0430\u0442\u0430: {addon} ?", - "title": "AdGuard Home \u0447\u0440\u0435\u0437 Hass.io \u0434\u043e\u0431\u0430\u0432\u043a\u0430" + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 Home Assistant \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0437\u0432\u0430 \u0441 AdGuard Home, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d \u043e\u0442 Supervisor \u0434\u043e\u0431\u0430\u0432\u043a\u0430\u0442\u0430: {addon} ?", + "title": "AdGuard Home \u0447\u0440\u0435\u0437 Supervisor \u0434\u043e\u0431\u0430\u0432\u043a\u0430" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/ca.json b/homeassistant/components/adguard/translations/ca.json index 422fde9479a..8c8086813aa 100644 --- a/homeassistant/components/adguard/translations/ca.json +++ b/homeassistant/components/adguard/translations/ca.json @@ -10,7 +10,7 @@ "step": { "hassio_confirm": { "description": "Vols configurar Home Assistant perqu\u00e8 es connecti amb l'AdGuard Home proporcionat pel complement de Hass.io: {addon}?", - "title": "AdGuard Home (complement de Hass.io)" + "title": "AdGuard Home via complement de Hass.io" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/cs.json b/homeassistant/components/adguard/translations/cs.json index 27b9d291fc2..00531088a08 100644 --- a/homeassistant/components/adguard/translations/cs.json +++ b/homeassistant/components/adguard/translations/cs.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "Chcete nakonfigurovat slu\u017ebu Home Assistant pro p\u0159ipojen\u00ed k AddGuard pomoc\u00ed hass.io {addon}?", - "title": "AdGuard prost\u0159ednictv\u00edm dopl\u0148ku Hass.io" + "description": "Chcete nakonfigurovat slu\u017ebu Home Assistant pro p\u0159ipojen\u00ed k AddGuard pomoc\u00ed Supervisor {addon}?", + "title": "AdGuard prost\u0159ednictv\u00edm dopl\u0148ku Supervisor" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/da.json b/homeassistant/components/adguard/translations/da.json index 927fd03d50a..79a1937eba8 100644 --- a/homeassistant/components/adguard/translations/da.json +++ b/homeassistant/components/adguard/translations/da.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til AdGuard Home leveret af Hass.io-tilf\u00f8jelsen: {addon}?", - "title": "AdGuard Home via Hass.io-tilf\u00f8jelse" + "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til AdGuard Home leveret af Supervisor-tilf\u00f8jelsen: {addon}?", + "title": "AdGuard Home via Supervisor-tilf\u00f8jelse" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/de.json b/homeassistant/components/adguard/translations/de.json index 67746b3abcf..2731f3f7eba 100644 --- a/homeassistant/components/adguard/translations/de.json +++ b/homeassistant/components/adguard/translations/de.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass eine Verbindung mit AdGuard Home als Hass.io-Add-On hergestellt wird: {addon}?", - "title": "AdGuard Home \u00fcber das Hass.io Add-on" + "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass eine Verbindung mit AdGuard Home als Supervisor-Add-On hergestellt wird: {addon}?", + "title": "AdGuard Home \u00fcber das Supervisor Add-on" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/es-419.json b/homeassistant/components/adguard/translations/es-419.json index 5efdfae1802..8fac53b61ab 100644 --- a/homeassistant/components/adguard/translations/es-419.json +++ b/homeassistant/components/adguard/translations/es-419.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "\u00bfDesea configurar Home Assistant para conectarse a la p\u00e1gina principal de AdGuard proporcionada por el complemento Hass.io: {addon}?", - "title": "AdGuard Home a trav\u00e9s del complemento Hass.io" + "description": "\u00bfDesea configurar Home Assistant para conectarse a la p\u00e1gina principal de AdGuard proporcionada por el complemento Supervisor: {addon}?", + "title": "AdGuard Home a trav\u00e9s del complemento Supervisor" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/es.json b/homeassistant/components/adguard/translations/es.json index a165a9b1c09..3ffdb6b9eb0 100644 --- a/homeassistant/components/adguard/translations/es.json +++ b/homeassistant/components/adguard/translations/es.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "\u00bfDesea configurar Home Assistant para conectarse al AdGuard Home proporcionado por el complemento Hass.io: {addon} ?", - "title": "AdGuard Home a trav\u00e9s del complemento Hass.io" + "description": "\u00bfDesea configurar Home Assistant para conectarse al AdGuard Home proporcionado por el complemento Supervisor: {addon} ?", + "title": "AdGuard Home a trav\u00e9s del complemento Supervisor" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/et.json b/homeassistant/components/adguard/translations/et.json index 3408d752522..800b7c37c49 100644 --- a/homeassistant/components/adguard/translations/et.json +++ b/homeassistant/components/adguard/translations/et.json @@ -10,7 +10,7 @@ "step": { "hassio_confirm": { "description": "Kas soovid seadistada Home Assistant-i \u00fchenduse AdGuard Home'iga mida pakub Hass.io lisandmoodul: {addon} ?", - "title": "AdGuard Home Hass.io pistikprogrammi kaudu" + "title": "AdGuard Home Hass.io lisandmooduli abil" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/he.json b/homeassistant/components/adguard/translations/he.json index 49c18fac88c..1471fd6603b 100644 --- a/homeassistant/components/adguard/translations/he.json +++ b/homeassistant/components/adguard/translations/he.json @@ -4,6 +4,7 @@ "user": { "data": { "host": "Host", + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", "port": "\u05e4\u05d5\u05e8\u05d8" } } diff --git a/homeassistant/components/adguard/translations/hu.json b/homeassistant/components/adguard/translations/hu.json index 3f67c765850..3813fae8f3c 100644 --- a/homeassistant/components/adguard/translations/hu.json +++ b/homeassistant/components/adguard/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, @@ -9,7 +12,9 @@ "host": "Hoszt", "password": "Jelsz\u00f3", "port": "Port", - "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" } } } diff --git a/homeassistant/components/adguard/translations/id.json b/homeassistant/components/adguard/translations/id.json index c5d61d91df0..d2e36cfe5b9 100644 --- a/homeassistant/components/adguard/translations/id.json +++ b/homeassistant/components/adguard/translations/id.json @@ -1,10 +1,27 @@ { "config": { + "abort": { + "existing_instance_updated": "Memperbarui konfigurasi yang ada.", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, "step": { + "hassio_confirm": { + "description": "Ingin mengonfigurasi Home Assistant untuk terhubung ke AdGuard Home yang disediakan oleh add-on Supervisor {addon}?", + "title": "AdGuard Home melalui add-on Home Assistant" + }, "user": { "data": { - "password": "Kata sandi" - } + "host": "Host", + "password": "Kata Sandi", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL", + "username": "Nama Pengguna", + "verify_ssl": "Verifikasi sertifikat SSL" + }, + "description": "Siapkan instans AdGuard Home Anda untuk pemantauan dan kontrol." } } } diff --git a/homeassistant/components/adguard/translations/it.json b/homeassistant/components/adguard/translations/it.json index 3df01316aa1..3758e093547 100644 --- a/homeassistant/components/adguard/translations/it.json +++ b/homeassistant/components/adguard/translations/it.json @@ -9,7 +9,7 @@ }, "step": { "hassio_confirm": { - "description": "Vuoi configurare Home Assistant per connettersi alla AdGuard Home fornita dal componente aggiuntivo di Hass.io: {addon}?", + "description": "Vuoi configurare Home Assistant per connettersi ad AdGuard Home fornito dal componente aggiuntivo di Hass.io: {addon}?", "title": "AdGuard Home tramite il componente aggiuntivo di Hass.io" }, "user": { diff --git a/homeassistant/components/adguard/translations/ko.json b/homeassistant/components/adguard/translations/ko.json index b14e627ca56..c60d16e53b1 100644 --- a/homeassistant/components/adguard/translations/ko.json +++ b/homeassistant/components/adguard/translations/ko.json @@ -2,15 +2,15 @@ "config": { "abort": { "existing_instance_updated": "\uae30\uc874 \uad6c\uc131\uc744 \uc5c5\ub370\uc774\ud2b8\ud588\uc2b5\ub2c8\ub2e4.", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "hassio_confirm": { - "description": "Hass.io {addon} \uc560\ub4dc\uc628\uc73c\ub85c AdGuard Home \uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant \ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Hass.io \uc560\ub4dc\uc628\uc758 AdGuard Home" + "description": "Hass.io {addon} \ucd94\uac00 \uae30\ub2a5\uc73c\ub85c AdGuard Home\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Hass.io \ucd94\uac00 \uae30\ub2a5\uc758 AdGuard Home" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/lb.json b/homeassistant/components/adguard/translations/lb.json index 135451c061f..ae7e6ad99be 100644 --- a/homeassistant/components/adguard/translations/lb.json +++ b/homeassistant/components/adguard/translations/lb.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mam AdGuard Home ze verbannen dee vum hass.io add-on {addon} bereet gestallt g\u00ebtt?", - "title": "AdGuard Home via Hass.io add-on" + "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mam AdGuard Home ze verbannen dee vum Supervisor add-on {addon} bereet gestallt g\u00ebtt?", + "title": "AdGuard Home via Supervisor add-on" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/nl.json b/homeassistant/components/adguard/translations/nl.json index 4c735333932..205193be8f8 100644 --- a/homeassistant/components/adguard/translations/nl.json +++ b/homeassistant/components/adguard/translations/nl.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "Wilt u Home Assistant configureren om verbinding te maken met AdGuard Home van de Hass.io-add-on: {addon}?", - "title": "AdGuard Home via Hass.io add-on" + "description": "Wilt u Home Assistant configureren om verbinding te maken met AdGuard Home van de Supervisor-add-on: {addon}?", + "title": "AdGuard Home via Supervisor add-on" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/no.json b/homeassistant/components/adguard/translations/no.json index 25046c8d38f..11c35c5895e 100644 --- a/homeassistant/components/adguard/translations/no.json +++ b/homeassistant/components/adguard/translations/no.json @@ -9,7 +9,7 @@ }, "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til \u00e5 koble til AdGuard Home gitt av Hass.io-tillegg {addon}?", + "description": "Vil du konfigurere Home Assistant for \u00e5 koble til AdGuard Home levert av Hass.io-tillegget: {addon} ?", "title": "AdGuard Home via Hass.io-tillegg" }, "user": { diff --git a/homeassistant/components/adguard/translations/pl.json b/homeassistant/components/adguard/translations/pl.json index 41cb2c019dd..f5c433a0bf4 100644 --- a/homeassistant/components/adguard/translations/pl.json +++ b/homeassistant/components/adguard/translations/pl.json @@ -9,7 +9,7 @@ }, "step": { "hassio_confirm": { - "description": "Czy chcesz skonfigurowa\u0107 Home Assistanta, aby po\u0142\u0105czy\u0142 si\u0119 z AdGuard Home przez dodatek Hass.io {addon}?", + "description": "Czy chcesz skonfigurowa\u0107 Home Assistanta, aby po\u0142\u0105czy\u0142 si\u0119 z AdGuard Home przez dodatek Hass.io: {addon}?", "title": "AdGuard Home przez dodatek Hass.io" }, "user": { diff --git a/homeassistant/components/adguard/translations/pt-BR.json b/homeassistant/components/adguard/translations/pt-BR.json index ae8899bb1a7..959c7ba3638 100644 --- a/homeassistant/components/adguard/translations/pt-BR.json +++ b/homeassistant/components/adguard/translations/pt-BR.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "Deseja configurar o Home Assistant para se conectar ao AdGuard Home fornecido pelo complemento Hass.io: {addon} ?", - "title": "AdGuard Home via add-on Hass.io" + "description": "Deseja configurar o Home Assistant para se conectar ao AdGuard Home fornecido pelo complemento Supervisor: {addon} ?", + "title": "AdGuard Home via add-on Supervisor" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/pt.json b/homeassistant/components/adguard/translations/pt.json index 5d8abfc9f56..a7e494936b8 100644 --- a/homeassistant/components/adguard/translations/pt.json +++ b/homeassistant/components/adguard/translations/pt.json @@ -8,7 +8,7 @@ }, "step": { "hassio_confirm": { - "title": "AdGuard Home via Hass.io add-on" + "title": "AdGuard Home via Supervisor add-on" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/sl.json b/homeassistant/components/adguard/translations/sl.json index 06ad40a17d6..34b03263ceb 100644 --- a/homeassistant/components/adguard/translations/sl.json +++ b/homeassistant/components/adguard/translations/sl.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "\u017delite konfigurirati Home Assistant-a za povezavo z AdGuard Home, ki ga ponuja Hass.io add-on {addon} ?", - "title": "AdGuard Home preko dodatka Hass.io" + "description": "\u017delite konfigurirati Home Assistant-a za povezavo z AdGuard Home, ki ga ponuja Supervisor add-on {addon} ?", + "title": "AdGuard Home preko dodatka Supervisor" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/sv.json b/homeassistant/components/adguard/translations/sv.json index 5b9a0f9a969..ca6158eaf32 100644 --- a/homeassistant/components/adguard/translations/sv.json +++ b/homeassistant/components/adguard/translations/sv.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "Vill du konfigurera Home Assistant f\u00f6r att ansluta till AdGuard Home som tillhandah\u00e5lls av Hass.io Add-on: {addon}?", - "title": "AdGuard Home via Hass.io-till\u00e4gget" + "description": "Vill du konfigurera Home Assistant f\u00f6r att ansluta till AdGuard Home som tillhandah\u00e5lls av Supervisor Add-on: {addon}?", + "title": "AdGuard Home via Supervisor-till\u00e4gget" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/uk.json b/homeassistant/components/adguard/translations/uk.json index 8c24fb0a877..28d02f25b7e 100644 --- a/homeassistant/components/adguard/translations/uk.json +++ b/homeassistant/components/adguard/translations/uk.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e AdGuard Home (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io \"{addon}\")?", - "title": "AdGuard Home (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io)" + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e AdGuard Home (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Supervisor \"{addon}\")?", + "title": "AdGuard Home (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Supervisor)" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/zh-Hant.json b/homeassistant/components/adguard/translations/zh-Hant.json index 8306b2daf70..250b2e0d891 100644 --- a/homeassistant/components/adguard/translations/zh-Hant.json +++ b/homeassistant/components/adguard/translations/zh-Hant.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u4f7f\u7528 Hass.io \u9644\u52a0\u7d44\u4ef6\uff1a{addon} \u9023\u7dda\u81f3 AdGuard Home\uff1f", - "title": "\u4f7f\u7528 Hass.io \u9644\u52a0\u7d44\u4ef6 AdGuard Home" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u4f7f\u7528 Hass.io \u9644\u52a0\u5143\u4ef6\uff1a{addon} \u9023\u7dda\u81f3 AdGuard Home\uff1f", + "title": "\u4f7f\u7528 Hass.io \u9644\u52a0\u5143\u4ef6 AdGuard Home" }, "user": { "data": { diff --git a/homeassistant/components/advantage_air/translations/hu.json b/homeassistant/components/advantage_air/translations/hu.json index 0da6d0d5304..e82e88da8d2 100644 --- a/homeassistant/components/advantage_air/translations/hu.json +++ b/homeassistant/components/advantage_air/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, diff --git a/homeassistant/components/advantage_air/translations/id.json b/homeassistant/components/advantage_air/translations/id.json new file mode 100644 index 00000000000..7993fa3be1d --- /dev/null +++ b/homeassistant/components/advantage_air/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "ip_address": "Alamat IP", + "port": "Port" + }, + "description": "Hubungkan ke API tablet dinding Advantage Air.", + "title": "Hubungkan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/advantage_air/translations/ko.json b/homeassistant/components/advantage_air/translations/ko.json index 444d8d38285..9a28cc499bc 100644 --- a/homeassistant/components/advantage_air/translations/ko.json +++ b/homeassistant/components/advantage_air/translations/ko.json @@ -11,7 +11,9 @@ "data": { "ip_address": "IP \uc8fc\uc18c", "port": "\ud3ec\ud2b8" - } + }, + "description": "\ubcbd\uc5d0 \ubd80\ucc29\ub41c Advantage Air \ud0dc\ube14\ub9bf\uc758 API\uc5d0 \uc5f0\uacb0\ud558\uae30", + "title": "\uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/aemet/translations/hu.json b/homeassistant/components/aemet/translations/hu.json new file mode 100644 index 00000000000..d810691046e --- /dev/null +++ b/homeassistant/components/aemet/translations/hu.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs" + }, + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g", + "name": "Az integr\u00e1ci\u00f3 neve" + }, + "title": "AEMET OpenData" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aemet/translations/id.json b/homeassistant/components/aemet/translations/id.json new file mode 100644 index 00000000000..fa678cbbbe0 --- /dev/null +++ b/homeassistant/components/aemet/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi" + }, + "error": { + "invalid_api_key": "Kunci API tidak valid" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "longitude": "Bujur", + "name": "Nama integrasi" + }, + "description": "Siapkan integrasi AEMET OpenData. Untuk menghasilkan kunci API, buka https://opendata.aemet.es/centrodedescargas/altaUsuario", + "title": "AEMET OpenData" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aemet/translations/ko.json b/homeassistant/components/aemet/translations/ko.json index edfb023a88b..95c11b018fe 100644 --- a/homeassistant/components/aemet/translations/ko.json +++ b/homeassistant/components/aemet/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" @@ -14,6 +14,7 @@ "longitude": "\uacbd\ub3c4", "name": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc774\ub984" }, + "description": "AEMET OpenData \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://opendata.aemet.es/centrodedescargas/altaUsuario \ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/aemet/translations/lb.json b/homeassistant/components/aemet/translations/lb.json new file mode 100644 index 00000000000..5ae5fff8664 --- /dev/null +++ b/homeassistant/components/aemet/translations/lb.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Numm vun der Integratioun" + }, + "title": "AEMET OpenData" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aemet/translations/nl.json b/homeassistant/components/aemet/translations/nl.json index 02415dde1e6..77589e20490 100644 --- a/homeassistant/components/aemet/translations/nl.json +++ b/homeassistant/components/aemet/translations/nl.json @@ -14,6 +14,7 @@ "longitude": "Lengtegraad", "name": "Naam van de integratie" }, + "description": "Stel AEMET OpenData-integratie in. Ga naar https://opendata.aemet.es/centrodedescargas/altaUsuario om een API-sleutel te genereren", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/agent_dvr/translations/hu.json b/homeassistant/components/agent_dvr/translations/hu.json index 1d28556ba1a..49968ceea75 100644 --- a/homeassistant/components/agent_dvr/translations/hu.json +++ b/homeassistant/components/agent_dvr/translations/hu.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { diff --git a/homeassistant/components/agent_dvr/translations/id.json b/homeassistant/components/agent_dvr/translations/id.json new file mode 100644 index 00000000000..f2ee1cc6622 --- /dev/null +++ b/homeassistant/components/agent_dvr/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port" + }, + "title": "Siapkan Agent DVR" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airly/translations/de.json b/homeassistant/components/airly/translations/de.json index 8004444fdb9..b13798c0ae3 100644 --- a/homeassistant/components/airly/translations/de.json +++ b/homeassistant/components/airly/translations/de.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "Airly-Server erreichen" + "can_reach_server": "Airly-Server erreichen", + "requests_per_day": "Erlaubte Anfragen pro Tag", + "requests_remaining": "Verbleibende erlaubte Anfragen" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/es.json b/homeassistant/components/airly/translations/es.json index a0ed36a7169..a96a2f62293 100644 --- a/homeassistant/components/airly/translations/es.json +++ b/homeassistant/components/airly/translations/es.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "Alcanzar el servidor Airly" + "can_reach_server": "Alcanzar el servidor Airly", + "requests_per_day": "Solicitudes permitidas por d\u00eda", + "requests_remaining": "Solicitudes permitidas restantes" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/et.json b/homeassistant/components/airly/translations/et.json index c5c9359c67f..6730e131ac2 100644 --- a/homeassistant/components/airly/translations/et.json +++ b/homeassistant/components/airly/translations/et.json @@ -23,8 +23,8 @@ "system_health": { "info": { "can_reach_server": "\u00dchendus Airly serveriga", - "requests_per_day": "Lubatud taotlusi p\u00e4evas", - "requests_remaining": "J\u00e4\u00e4nud lubatud taotlusi" + "requests_per_day": "Lubatud p\u00e4ringuid p\u00e4evas", + "requests_remaining": "Lubatud p\u00e4ringute arv" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/fr.json b/homeassistant/components/airly/translations/fr.json index 98407155f17..a23f455e0b8 100644 --- a/homeassistant/components/airly/translations/fr.json +++ b/homeassistant/components/airly/translations/fr.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "Acc\u00e8s au serveur Airly" + "can_reach_server": "Acc\u00e8s au serveur Airly", + "requests_per_day": "Demandes autoris\u00e9es par jour", + "requests_remaining": "Demandes autoris\u00e9es restantes" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/he.json b/homeassistant/components/airly/translations/he.json new file mode 100644 index 00000000000..4c49313d977 --- /dev/null +++ b/homeassistant/components/airly/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airly/translations/hu.json b/homeassistant/components/airly/translations/hu.json index b96734935a0..b9fd0c9e05c 100644 --- a/homeassistant/components/airly/translations/hu.json +++ b/homeassistant/components/airly/translations/hu.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "Ezen koordin\u00e1t\u00e1k Airly integr\u00e1ci\u00f3ja m\u00e1r konfigur\u00e1lva van." + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van" }, "error": { + "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs", "wrong_location": "Ezen a ter\u00fcleten nincs Airly m\u00e9r\u0151\u00e1llom\u00e1s." }, "step": { @@ -12,11 +13,17 @@ "api_key": "API kulcs", "latitude": "Sz\u00e9less\u00e9g", "longitude": "Hossz\u00fas\u00e1g", - "name": "Az integr\u00e1ci\u00f3 neve" + "name": "N\u00e9v" }, "description": "Az Airly leveg\u0151min\u0151s\u00e9gi integr\u00e1ci\u00f3j\u00e1nak be\u00e1ll\u00edt\u00e1sa. Api-kulcs l\u00e9trehoz\u00e1s\u00e1hoz nyissa meg a k\u00f6vetkez\u0151 weboldalt: https://developer.airly.eu/register", "title": "Airly" } } + }, + "system_health": { + "info": { + "requests_per_day": "Enged\u00e9lyezett k\u00e9r\u00e9sek naponta", + "requests_remaining": "Fennmarad\u00f3 enged\u00e9lyezett k\u00e9r\u00e9sek" + } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/id.json b/homeassistant/components/airly/translations/id.json new file mode 100644 index 00000000000..57b4c0d95f9 --- /dev/null +++ b/homeassistant/components/airly/translations/id.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi" + }, + "error": { + "invalid_api_key": "Kunci API tidak valid", + "wrong_location": "Tidak ada stasiun pengukur Airly di daerah ini." + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "longitude": "Bujur", + "name": "Nama" + }, + "description": "Siapkan integrasi kualitas udara Airly. Untuk membuat kunci API, buka https://developer.airly.eu/register", + "title": "Airly" + } + } + }, + "system_health": { + "info": { + "can_reach_server": "Keterjangkauan server Airly", + "requests_per_day": "Permintaan yang diizinkan per hari", + "requests_remaining": "Sisa permintaan yang diizinkan" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airly/translations/it.json b/homeassistant/components/airly/translations/it.json index bf6d7a461ce..385b8117437 100644 --- a/homeassistant/components/airly/translations/it.json +++ b/homeassistant/components/airly/translations/it.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "Raggiungi il server Airly" + "can_reach_server": "Raggiungi il server Airly", + "requests_per_day": "Richieste consentite al giorno", + "requests_remaining": "Richieste consentite rimanenti" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/ko.json b/homeassistant/components/airly/translations/ko.json index 95981ea5eb1..1f9db4a592e 100644 --- a/homeassistant/components/airly/translations/ko.json +++ b/homeassistant/components/airly/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", @@ -19,5 +19,12 @@ "title": "Airly" } } + }, + "system_health": { + "info": { + "can_reach_server": "Airly \uc11c\ubc84 \uc5f0\uacb0", + "requests_per_day": "\uc77c\uc77c \ud5c8\uc6a9 \uc694\uccad \ud69f\uc218", + "requests_remaining": "\ub0a8\uc740 \ud5c8\uc6a9 \uc694\uccad \ud69f\uc218" + } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/nl.json b/homeassistant/components/airly/translations/nl.json index a7bc9966f63..5ea975dfaef 100644 --- a/homeassistant/components/airly/translations/nl.json +++ b/homeassistant/components/airly/translations/nl.json @@ -19,5 +19,12 @@ "title": "Airly" } } + }, + "system_health": { + "info": { + "can_reach_server": "Kan Airly server bereiken", + "requests_per_day": "Toegestane verzoeken per dag", + "requests_remaining": "Resterende toegestane verzoeken" + } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/no.json b/homeassistant/components/airly/translations/no.json index b38568210ad..4c81422d93c 100644 --- a/homeassistant/components/airly/translations/no.json +++ b/homeassistant/components/airly/translations/no.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "N\u00e5 Airly-serveren" + "can_reach_server": "N\u00e5 Airly-serveren", + "requests_per_day": "Tillatte foresp\u00f8rsler per dag", + "requests_remaining": "Gjenv\u00e6rende tillatte foresp\u00f8rsler" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/pl.json b/homeassistant/components/airly/translations/pl.json index e36e6f86ec7..f205a569474 100644 --- a/homeassistant/components/airly/translations/pl.json +++ b/homeassistant/components/airly/translations/pl.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "Dost\u0119p do serwera Airly" + "can_reach_server": "Dost\u0119p do serwera Airly", + "requests_per_day": "Dozwolone dzienne \u017c\u0105dania", + "requests_remaining": "Pozosta\u0142o dozwolonych \u017c\u0105da\u0144" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/zh-Hans.json b/homeassistant/components/airly/translations/zh-Hans.json index 1a57bfbadf9..0f3c5137d31 100644 --- a/homeassistant/components/airly/translations/zh-Hans.json +++ b/homeassistant/components/airly/translations/zh-Hans.json @@ -13,7 +13,8 @@ }, "system_health": { "info": { - "can_reach_server": "\u53ef\u8bbf\u95ee Airly \u670d\u52a1\u5668" + "can_reach_server": "\u53ef\u8bbf\u95ee Airly \u670d\u52a1\u5668", + "requests_per_day": "\u5141\u8bb8\u6bcf\u5929\u8bf7\u6c42" } } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/bg.json b/homeassistant/components/airnow/translations/bg.json new file mode 100644 index 00000000000..5d274ec2b73 --- /dev/null +++ b/homeassistant/components/airnow/translations/bg.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/de.json b/homeassistant/components/airnow/translations/de.json index c98fc6d7415..8c2b47c1bd4 100644 --- a/homeassistant/components/airnow/translations/de.json +++ b/homeassistant/components/airnow/translations/de.json @@ -16,6 +16,7 @@ "latitude": "Breitengrad", "longitude": "L\u00e4ngengrad" }, + "description": "Richten Sie die AirNow-Luftqualit\u00e4tsintegration ein. Um den API-Schl\u00fcssel zu generieren, besuchen Sie https://docs.airnowapi.org/account/request/.", "title": "AirNow" } } diff --git a/homeassistant/components/airnow/translations/hu.json b/homeassistant/components/airnow/translations/hu.json new file mode 100644 index 00000000000..418450f2419 --- /dev/null +++ b/homeassistant/components/airnow/translations/hu.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "invalid_location": "Erre a helyre nem tal\u00e1lhat\u00f3 eredm\u00e9ny", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g" + }, + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/id.json b/homeassistant/components/airnow/translations/id.json new file mode 100644 index 00000000000..66fdff72fae --- /dev/null +++ b/homeassistant/components/airnow/translations/id.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "invalid_location": "Tidak ada hasil yang ditemukan untuk lokasi tersebut", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "longitude": "Bujur", + "radius": "Radius Stasiun (mil; opsional)" + }, + "description": "Siapkan integrasi kualitas udara AirNow. Untuk membuat kunci API buka https://docs.airnowapi.org/account/request/", + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/ko.json b/homeassistant/components/airnow/translations/ko.json index 6da62dffa2c..adfbf0be8ed 100644 --- a/homeassistant/components/airnow/translations/ko.json +++ b/homeassistant/components/airnow/translations/ko.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_location": "\ud574\ub2f9 \uc704\uce58\uc5d0 \uacb0\uacfc\uac00 \uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { @@ -13,9 +14,13 @@ "data": { "api_key": "API \ud0a4", "latitude": "\uc704\ub3c4", - "longitude": "\uacbd\ub3c4" - } + "longitude": "\uacbd\ub3c4", + "radius": "\uce21\uc815 \uc2a4\ud14c\uc774\uc158 \ubc18\uacbd (\ub9c8\uc77c; \uc120\ud0dd \uc0ac\ud56d)" + }, + "description": "AirNow \uacf5\uae30 \ud488\uc9c8 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://docs.airnowapi.org/account/request \ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694", + "title": "AirNow" } } - } + }, + "title": "AirNow" } \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/bg.json b/homeassistant/components/airvisual/translations/bg.json new file mode 100644 index 00000000000..7e463418576 --- /dev/null +++ b/homeassistant/components/airvisual/translations/bg.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "geography_by_name": { + "data": { + "city": "\u0413\u0440\u0430\u0434", + "country": "\u0421\u0442\u0440\u0430\u043d\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/he.json b/homeassistant/components/airvisual/translations/he.json index 7d1a1c696ed..e32efda96dc 100644 --- a/homeassistant/components/airvisual/translations/he.json +++ b/homeassistant/components/airvisual/translations/he.json @@ -6,7 +6,13 @@ "step": { "geography": { "data": { - "api_key": "\u05de\u05e4\u05ea\u05d7 API" + "api_key": "\u05de\u05e4\u05ea\u05d7 API", + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" + } + }, + "node_pro": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" } } } diff --git a/homeassistant/components/airvisual/translations/hu.json b/homeassistant/components/airvisual/translations/hu.json index 53ab734e505..89584cd128b 100644 --- a/homeassistant/components/airvisual/translations/hu.json +++ b/homeassistant/components/airvisual/translations/hu.json @@ -1,8 +1,13 @@ { "config": { + "abort": { + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van vagy a Node/Pro azonos\u00edt\u00f3 m\u00e1r regisztr\u00e1lva van.", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "general_error": "Ismeretlen hiba t\u00f6rt\u00e9nt." + "general_error": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt", + "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs" }, "step": { "geography": { @@ -12,8 +17,23 @@ "longitude": "Hossz\u00fas\u00e1g" } }, + "geography_by_coords": { + "data": { + "api_key": "API kulcs", + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g" + } + }, + "geography_by_name": { + "data": { + "api_key": "API kulcs", + "city": "V\u00e1ros", + "country": "Orsz\u00e1g" + } + }, "node_pro": { "data": { + "ip_address": "Hoszt", "password": "Jelsz\u00f3" } }, @@ -21,6 +41,11 @@ "data": { "api_key": "API kulcs" } + }, + "user": { + "data": { + "type": "Integr\u00e1ci\u00f3 t\u00edpusa" + } } } } diff --git a/homeassistant/components/airvisual/translations/id.json b/homeassistant/components/airvisual/translations/id.json new file mode 100644 index 00000000000..3c689338d9f --- /dev/null +++ b/homeassistant/components/airvisual/translations/id.json @@ -0,0 +1,77 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi atau ID Node/Pro sudah terdaftar.", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "general_error": "Kesalahan yang tidak diharapkan", + "invalid_api_key": "Kunci API tidak valid", + "location_not_found": "Lokasi tidak ditemukan" + }, + "step": { + "geography": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "longitude": "Bujur" + }, + "description": "Gunakan API cloud AirVisual untuk memantau lokasi geografis.", + "title": "Konfigurasikan Lokasi Geografi" + }, + "geography_by_coords": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "longitude": "Bujur" + }, + "description": "Gunakan API cloud AirVisual untuk memantau satu pasang lintang/bujur.", + "title": "Konfigurasikan Lokasi Geografi" + }, + "geography_by_name": { + "data": { + "api_key": "Kunci API", + "city": "Kota", + "country": "Negara", + "state": "negara bagian" + }, + "description": "Gunakan API cloud AirVisual untuk memantau kota/negara bagian/negara.", + "title": "Konfigurasikan Lokasi Geografi" + }, + "node_pro": { + "data": { + "ip_address": "Host", + "password": "Kata Sandi" + }, + "description": "Pantau unit AirVisual pribadi. Kata sandi dapat diambil dari antarmuka unit.", + "title": "Konfigurasikan AirVisual Node/Pro" + }, + "reauth_confirm": { + "data": { + "api_key": "Kunci API" + }, + "title": "Autentikasi Ulang AirVisual" + }, + "user": { + "data": { + "cloud_api": "Lokasi Geografis", + "node_pro": "AirVisual Node Pro", + "type": "Jenis Integrasi" + }, + "description": "Pilih jenis data AirVisual yang ingin dipantau.", + "title": "Konfigurasikan AirVisual" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Tampilkan lokasi geografi yang dipantau pada peta" + }, + "title": "Konfigurasikan AirVisual" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/ko.json b/homeassistant/components/airvisual/translations/ko.json index 8cf450e597b..3ab3dbcf286 100644 --- a/homeassistant/components/airvisual/translations/ko.json +++ b/homeassistant/components/airvisual/translations/ko.json @@ -1,13 +1,14 @@ { "config": { "abort": { - "already_configured": "Node/Pro ID \uac00 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uac70\ub098 \uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "already_configured": "Node/Pro ID\uac00 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uac70\ub098 \uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "general_error": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4", - "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "location_not_found": "\uc704\uce58\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." }, "step": { "geography": { @@ -24,12 +25,19 @@ "api_key": "API \ud0a4", "latitude": "\uc704\ub3c4", "longitude": "\uacbd\ub3c4" - } + }, + "description": "AirVisual \ud074\ub77c\uc6b0\ub4dc API\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc704\ub3c4/\uacbd\ub3c4\ub97c \ubaa8\ub2c8\ud130\ub9c1\ud569\ub2c8\ub2e4.", + "title": "\uc9c0\ub9ac\uc801 \uc704\uce58 \uad6c\uc131\ud558\uae30" }, "geography_by_name": { "data": { - "api_key": "API \ud0a4" - } + "api_key": "API \ud0a4", + "city": "\ub3c4\uc2dc", + "country": "\uad6d\uac00", + "state": "\uc8fc" + }, + "description": "AirVisual \ud074\ub77c\uc6b0\ub4dc API\ub97c \uc0ac\uc6a9\ud558\uc5ec \ub3c4\uc2dc/\uc8fc/\uad6d\uac00\ub97c \ubaa8\ub2c8\ud130\ub9c1\ud569\ub2c8\ub2e4.", + "title": "\uc9c0\ub9ac\uc801 \uc704\uce58 \uad6c\uc131\ud558\uae30" }, "node_pro": { "data": { @@ -42,7 +50,8 @@ "reauth_confirm": { "data": { "api_key": "API \ud0a4" - } + }, + "title": "AirVisual \uc7ac\uc778\uc99d\ud558\uae30" }, "user": { "data": { diff --git a/homeassistant/components/airvisual/translations/nl.json b/homeassistant/components/airvisual/translations/nl.json index ecf2322c801..ed81d6568ed 100644 --- a/homeassistant/components/airvisual/translations/nl.json +++ b/homeassistant/components/airvisual/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Deze co\u00f6rdinaten of Node / Pro ID zijn al geregistreerd.", + "already_configured": "Locatie is al geconfigureerd. of Node/Pro IDis al geregistreerd.", "reauth_successful": "Herauthenticatie was succesvol" }, "error": { @@ -25,15 +25,19 @@ "api_key": "API-sleutel", "latitude": "Breedtegraad", "longitude": "Lengtegraad" - } + }, + "description": "Gebruik de AirVisual-cloud-API om een lengte- / breedtegraad te bewaken.", + "title": "Configureer een geografie" }, "geography_by_name": { "data": { "api_key": "API-sleutel", "city": "Stad", - "country": "Land" + "country": "Land", + "state": "staat" }, - "description": "Gebruik de AirVisual-cloud-API om een stad/staat/land te bewaken." + "description": "Gebruik de AirVisual-cloud-API om een stad/staat/land te bewaken.", + "title": "Configureer een geografie" }, "node_pro": { "data": { diff --git a/homeassistant/components/alarm_control_panel/translations/id.json b/homeassistant/components/alarm_control_panel/translations/id.json index cbc3d31370c..f1676ce8c75 100644 --- a/homeassistant/components/alarm_control_panel/translations/id.json +++ b/homeassistant/components/alarm_control_panel/translations/id.json @@ -1,14 +1,37 @@ { + "device_automation": { + "action_type": { + "arm_away": "Aktifkan {entity_name} untuk keluar", + "arm_home": "Aktifkan {entity_name} untuk di rumah", + "arm_night": "Aktifkan {entity_name} untuk malam", + "disarm": "Nonaktifkan {entity_name}", + "trigger": "Picu {entity_name}" + }, + "condition_type": { + "is_armed_away": "{entity_name} diaktifkan untuk keluar", + "is_armed_home": "{entity_name} diaktifkan untuk di rumah", + "is_armed_night": "{entity_name} diaktifkan untuk malam", + "is_disarmed": "{entity_name} dinonaktifkan", + "is_triggered": "{entity_name} dipicu" + }, + "trigger_type": { + "armed_away": "{entity_name} diaktifkan untuk keluar", + "armed_home": "{entity_name} diaktifkan untuk di rumah", + "armed_night": "{entity_name} diaktifkan untuk malam", + "disarmed": "{entity_name} dinonaktifkan", + "triggered": "{entity_name} dipicu" + } + }, "state": { "_": { - "armed": "Bersenjata", - "armed_away": "Armed away", - "armed_custom_bypass": "Armed custom bypass", - "armed_home": "Armed home", - "armed_night": "Armed night", - "arming": "Mempersenjatai", - "disarmed": "Dilucuti", - "disarming": "Melucuti", + "armed": "Diaktifkan", + "armed_away": "Diaktifkan untuk keluar", + "armed_custom_bypass": "Diaktifkan khusus", + "armed_home": "Diaktifkan untuk di rumah", + "armed_night": "Diaktifkan untuk malam", + "arming": "Mengaktifkan", + "disarmed": "Dinonaktifkan", + "disarming": "Dinonaktifkan", "pending": "Tertunda", "triggered": "Terpicu" } diff --git a/homeassistant/components/alarm_control_panel/translations/ko.json b/homeassistant/components/alarm_control_panel/translations/ko.json index f6adb68fe66..0fd766ba0b9 100644 --- a/homeassistant/components/alarm_control_panel/translations/ko.json +++ b/homeassistant/components/alarm_control_panel/translations/ko.json @@ -1,35 +1,35 @@ { "device_automation": { "action_type": { - "arm_away": "{entity_name} \uc678\ucd9c\uacbd\ube44", - "arm_home": "{entity_name} \uc7ac\uc2e4\uacbd\ube44", - "arm_night": "{entity_name} \uc57c\uac04\uacbd\ube44", - "disarm": "{entity_name} \uacbd\ube44\ud574\uc81c", - "trigger": "{entity_name} \ud2b8\ub9ac\uac70" + "arm_away": "{entity_name}\uc744(\ub97c) \uc678\ucd9c\uacbd\ube44\ub85c \uc124\uc815\ud558\uae30", + "arm_home": "{entity_name}\uc744(\ub97c) \uc7ac\uc2e4\uacbd\ube44\ub85c \uc124\uc815\ud558\uae30", + "arm_night": "{entity_name}\uc744(\ub97c) \uc57c\uac04\uacbd\ube44\ub85c \uc124\uc815\ud558\uae30", + "disarm": "{entity_name}\uc744(\ub97c) \uacbd\ube44\ud574\uc81c\ub85c \uc124\uc815\ud558\uae30", + "trigger": "{entity_name}\uc744(\ub97c) \ud2b8\ub9ac\uac70\ud558\uae30" }, "condition_type": { - "is_armed_away": "{entity_name} \uc774(\uac00) \uc678\ucd9c \uacbd\ube44\ubaa8\ub4dc \uc0c1\ud0dc\uc774\uba74", - "is_armed_home": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uacbd\ube44\ubaa8\ub4dc \uc0c1\ud0dc\uc774\uba74", - "is_armed_night": "{entity_name} \uc774(\uac00) \uc57c\uac04 \uacbd\ube44\ubaa8\ub4dc \uc0c1\ud0dc\uc774\uba74", - "is_disarmed": "{entity_name} \uc774(\uac00) \ud574\uc81c \uc0c1\ud0dc\uc774\uba74", - "is_triggered": "{entity_name} \uc774(\uac00) \ud2b8\ub9ac\uac70\ub418\uc5c8\uc73c\uba74" + "is_armed_away": "{entity_name}\uc774(\uac00) \uc678\ucd9c \uacbd\ube44\ubaa8\ub4dc \uc0c1\ud0dc\uc774\uba74", + "is_armed_home": "{entity_name}\uc774(\uac00) \uc7ac\uc2e4 \uacbd\ube44\ubaa8\ub4dc \uc0c1\ud0dc\uc774\uba74", + "is_armed_night": "{entity_name}\uc774(\uac00) \uc57c\uac04 \uacbd\ube44\ubaa8\ub4dc \uc0c1\ud0dc\uc774\uba74", + "is_disarmed": "{entity_name}\uc774(\uac00) \ud574\uc81c \uc0c1\ud0dc\uc774\uba74", + "is_triggered": "{entity_name}\uc774(\uac00) \ud2b8\ub9ac\uac70 \ub418\uc5c8\uc73c\uba74" }, "trigger_type": { - "armed_away": "{entity_name} \uc774(\uac00) \uc678\ucd9c \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub420 \ub54c", - "armed_home": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub420 \ub54c", - "armed_night": "{entity_name} \uc774(\uac00) \uc57c\uac04 \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub420 \ub54c", - "disarmed": "{entity_name} \uc774(\uac00) \ud574\uc81c\ub420 \ub54c", - "triggered": "{entity_name} \uc774(\uac00) \ud2b8\ub9ac\uac70\ub420 \ub54c" + "armed_away": "{entity_name}\uc774(\uac00) \uc678\ucd9c \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5c8\uc744 \ub54c", + "armed_home": "{entity_name}\uc774(\uac00) \uc7ac\uc2e4 \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5c8\uc744 \ub54c", + "armed_night": "{entity_name}\uc774(\uac00) \uc57c\uac04 \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5c8\uc744 \ub54c", + "disarmed": "{entity_name}\uc774(\uac00) \ud574\uc81c\ub418\uc5c8\uc744 \ub54c", + "triggered": "{entity_name}\uc774(\uac00) \ud2b8\ub9ac\uac70\ub418\uc5c8\uc744 \ub54c" } }, "state": { "_": { - "armed": "\uacbd\ube44\uc911", - "armed_away": "\uacbd\ube44\uc911(\uc678\ucd9c)", - "armed_custom_bypass": "\uacbd\ube44\uc911(\uc0ac\uc6a9\uc790 \uc6b0\ud68c)", - "armed_home": "\uacbd\ube44\uc911(\uc7ac\uc2e4)", - "armed_night": "\uacbd\ube44\uc911(\uc57c\uac04)", - "arming": "\uacbd\ube44\uc911", + "armed": "\uacbd\ube44 \uc911", + "armed_away": "\uacbd\ube44 \uc911(\uc678\ucd9c)", + "armed_custom_bypass": "\uacbd\ube44 \uc911 (\uc0ac\uc6a9\uc790 \uc6b0\ud68c)", + "armed_home": "\uacbd\ube44 \uc911(\uc7ac\uc2e4)", + "armed_night": "\uacbd\ube44 \uc911(\uc57c\uac04)", + "arming": "\uacbd\ube44 \uc911", "disarmed": "\ud574\uc81c\ub428", "disarming": "\ud574\uc81c\uc911", "pending": "\ubcf4\ub958\uc911", diff --git a/homeassistant/components/alarm_control_panel/translations/zh-Hans.json b/homeassistant/components/alarm_control_panel/translations/zh-Hans.json index 749674e8e6e..fa819e71b49 100644 --- a/homeassistant/components/alarm_control_panel/translations/zh-Hans.json +++ b/homeassistant/components/alarm_control_panel/translations/zh-Hans.json @@ -1,4 +1,27 @@ { + "device_automation": { + "action_type": { + "arm_away": "{entity_name} \u79bb\u5bb6\u8b66\u6212", + "arm_home": "{entity_name} \u5728\u5bb6\u8b66\u6212", + "arm_night": "{entity_name} \u591c\u95f4\u8b66\u6212", + "disarm": "\u89e3\u9664 {entity_name} \u8b66\u6212", + "trigger": "\u89e6\u53d1 {entity_name}" + }, + "condition_type": { + "is_armed_away": "{entity_name} \u79bb\u5bb6\u8b66\u6212", + "is_armed_home": "{entity_name} \u5728\u5bb6\u8b66\u6212", + "is_armed_night": "{entity_name} \u591c\u95f4\u8b66\u6212", + "is_disarmed": "{entity_name} \u8b66\u6212\u5df2\u89e3\u9664", + "is_triggered": "{entity_name} \u8b66\u62a5\u5df2\u89e6\u53d1" + }, + "trigger_type": { + "armed_away": "{entity_name} \u79bb\u5bb6\u8b66\u6212", + "armed_home": "{entity_name} \u5728\u5bb6\u8b66\u6212", + "armed_night": "{entity_name} \u591c\u95f4\u8b66\u6212", + "disarmed": "{entity_name} \u8b66\u6212\u89e3\u9664", + "triggered": "{entity_name} \u89e6\u53d1\u8b66\u62a5" + } + }, "state": { "_": { "armed": "\u8b66\u6212", diff --git a/homeassistant/components/alarmdecoder/translations/hu.json b/homeassistant/components/alarmdecoder/translations/hu.json index 2d5f91cf373..8c80adcb3c0 100644 --- a/homeassistant/components/alarmdecoder/translations/hu.json +++ b/homeassistant/components/alarmdecoder/translations/hu.json @@ -1,10 +1,40 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "create_entry": { "default": "Sikeres csatlakoz\u00e1s az AlarmDecoderhez." }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "protocol": { + "data": { + "host": "Hoszt", + "port": "Port" + } + }, + "user": { + "data": { + "protocol": "Protokoll" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "edit_select": "Szerkeszt\u00e9s" + } + }, + "zone_details": { + "data": { + "zone_name": "Z\u00f3na neve" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/id.json b/homeassistant/components/alarmdecoder/translations/id.json new file mode 100644 index 00000000000..39c8282b36f --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/id.json @@ -0,0 +1,74 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "create_entry": { + "default": "Berhasil terhubung ke AlarmDecoder." + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "protocol": { + "data": { + "device_baudrate": "Laju Baud Perangkat", + "device_path": "Jalur Perangkat", + "host": "Host", + "port": "Port" + }, + "title": "Konfigurasikan pengaturan koneksi" + }, + "user": { + "data": { + "protocol": "Protokol" + }, + "title": "Pilih Protokol AlarmDecoder" + } + } + }, + "options": { + "error": { + "int": "Bidang di bawah ini harus berupa bilangan bulat.", + "loop_range": "RF Loop harus merupakan bilangan bulat antara 1 dan 4.", + "loop_rfid": "RF Loop tidak dapat digunakan tanpa RF Serial.", + "relay_inclusive": "Relay Address dan Relay Channel saling tergantung dan harus disertakan bersama-sama." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "Mode Malam Alternatif", + "auto_bypass": "Diaktifkan Secara Otomatis", + "code_arm_required": "Kode Diperlukan untuk Mengaktifkan" + }, + "title": "Konfigurasikan AlarmDecoder" + }, + "init": { + "data": { + "edit_select": "Edit" + }, + "description": "Apa yang ingin diedit?", + "title": "Konfigurasikan AlarmDecoder" + }, + "zone_details": { + "data": { + "zone_loop": "RF Loop", + "zone_name": "Nama Zona", + "zone_relayaddr": "Relay Address", + "zone_relaychan": "Relay Channel", + "zone_rfid": "RF Serial", + "zone_type": "Jenis Zona" + }, + "description": "Masukkan detail untuk zona {zone_number}. Untuk menghapus zona {zone_number}, kosongkan Nama Zona.", + "title": "Konfigurasikan AlarmDecoder" + }, + "zone_select": { + "data": { + "zone_number": "Nomor Zona" + }, + "description": "Masukkan nomor zona yang ingin ditambahkan, diedit, atau dihapus.", + "title": "Konfigurasikan AlarmDecoder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/ko.json b/homeassistant/components/alarmdecoder/translations/ko.json index 08383d37151..cdb63a01bfb 100644 --- a/homeassistant/components/alarmdecoder/translations/ko.json +++ b/homeassistant/components/alarmdecoder/translations/ko.json @@ -12,62 +12,62 @@ "step": { "protocol": { "data": { - "device_baudrate": "\uc7a5\uce58 \uc804\uc1a1 \uc18d\ub3c4", - "device_path": "\uc7a5\uce58 \uacbd\ub85c", + "device_baudrate": "\uae30\uae30 \uc804\uc1a1 \uc18d\ub3c4", + "device_path": "\uae30\uae30 \uacbd\ub85c", "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8" }, - "title": "\uc5f0\uacb0 \uc124\uc815 \uad6c\uc131" + "title": "\uc5f0\uacb0 \uc124\uc815 \uad6c\uc131\ud558\uae30" }, "user": { "data": { "protocol": "\ud504\ub85c\ud1a0\ucf5c" }, - "title": "AlarmDecoder \ud504\ub85c\ud1a0\ucf5c \uc120\ud0dd" + "title": "AlarmDecoder \ud504\ub85c\ud1a0\ucf5c \uc120\ud0dd\ud558\uae30" } } }, "options": { "error": { "int": "\uc544\ub798 \ud544\ub4dc\ub294 \uc815\uc218\uc5ec\uc57c \ud569\ub2c8\ub2e4.", - "loop_range": "RF \ub8e8\ud504\ub294 1\uc5d0\uc11c 4 \uc0ac\uc774\uc758 \uc815\uc218\uc5ec\uc57c \ud569\ub2c8\ub2e4.", - "loop_rfid": "RF \ub8e8\ud504\ub294 RF \uc2dc\ub9ac\uc5bc\uc5c6\uc774 \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", - "relay_inclusive": "\ub9b4\ub808\uc774 \uc8fc\uc18c\uc640 \ub9b4\ub808\uc774 \ucc44\ub110\uc740 \uc11c\ub85c \uc758\uc874\uc801\uc774\uba70 \ud568\uaed8 \ud3ec\ud568\ub418\uc5b4\uc57c\ud569\ub2c8\ub2e4." + "loop_range": "RF \ub8e8\ud504\ub294 1\uacfc 4 \uc0ac\uc774\uc758 \uc815\uc218\uc5ec\uc57c \ud569\ub2c8\ub2e4.", + "loop_rfid": "RF \ub8e8\ud504\ub294 RF \uc2dc\ub9ac\uc5bc \uc5c6\uc73c\uba74 \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", + "relay_inclusive": "\ub9b4\ub808\uc774 \uc8fc\uc18c\uc640 \ub9b4\ub808\uc774 \ucc44\ub110\uc740 \uc0c1\ud638 \uc758\uc874\uc801\uc774\uae30 \ub54c\ubb38\uc5d0 \ud568\uaed8 \ud3ec\ud568\ub418\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "step": { "arm_settings": { "data": { "alt_night_mode": "\ub300\uccb4 \uc57c\uac04 \ubaa8\ub4dc", - "auto_bypass": "\uacbd\ube44\uc911 \uc790\ub3d9 \uc6b0\ud68c", + "auto_bypass": "\uacbd\ube44 \uc911 \uc790\ub3d9 \uc6b0\ud68c", "code_arm_required": "\uacbd\ube44\uc5d0 \ud544\uc694\ud55c \ucf54\ub4dc" }, - "title": "AlarmDecoder \uad6c\uc131" + "title": "AlarmDecoder \uad6c\uc131\ud558\uae30" }, "init": { "data": { - "edit_select": "\ud3b8\uc9d1" + "edit_select": "\ud3b8\uc9d1\ud558\uae30" }, - "description": "\ubb34\uc5c7\uc744 \ud3b8\uc9d1 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "AlarmDecoder \uad6c\uc131" + "description": "\ubb34\uc5c7\uc744 \ud3b8\uc9d1\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "AlarmDecoder \uad6c\uc131\ud558\uae30" }, "zone_details": { "data": { "zone_loop": "RF \ub8e8\ud504", - "zone_name": "\uc601\uc5ed \uc774\ub984", + "zone_name": "\uad6c\uc5ed \uc774\ub984", "zone_relayaddr": "\ub9b4\ub808\uc774 \uc8fc\uc18c", "zone_relaychan": "\ub9b4\ub808\uc774 \ucc44\ub110", "zone_rfid": "RF \uc2dc\ub9ac\uc5bc", - "zone_type": "\uc601\uc5ed \uc720\ud615" + "zone_type": "\uad6c\uc5ed \uc720\ud615" }, - "description": "{zone_number} \uc601\uc5ed\uc5d0 \ub300\ud55c \uc138\ubd80 \uc815\ubcf4\ub97c \uc785\ub825\ud569\ub2c8\ub2e4. {zone_number} \uc601\uc5ed\uc744 \uc0ad\uc81c\ud558\ub824\uba74 \uc601\uc5ed \uc774\ub984\uc744 \ube44\uc6cc \ub461\ub2c8\ub2e4.", - "title": "AlarmDecoder \uad6c\uc131" + "description": "\uad6c\uc5ed {zone_number}\uc5d0 \ub300\ud55c \uc138\ubd80 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \uad6c\uc5ed {zone_number}\uc744(\ub97c) \uc0ad\uc81c\ud558\ub824\uba74 \uad6c\uc5ed \uc774\ub984\uc744 \ube44\uc6cc\ub450\uc138\uc694.", + "title": "AlarmDecoder \uad6c\uc131\ud558\uae30" }, "zone_select": { "data": { "zone_number": "\uad6c\uc5ed \ubc88\ud638" }, - "description": "\ucd94\uac00, \ud3b8\uc9d1 \ub610\ub294 \uc81c\uac70\ud560 \uc601\uc5ed \ubc88\ud638\ub97c \uc785\ub825\ud569\ub2c8\ub2e4.", - "title": "AlarmDecoder \uad6c\uc131" + "description": "\ucd94\uac00, \ud3b8\uc9d1 \ub610\ub294 \uc81c\uac70\ud560 \uad6c\uc5ed \ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "AlarmDecoder \uad6c\uc131\ud558\uae30" } } } diff --git a/homeassistant/components/almond/translations/ca.json b/homeassistant/components/almond/translations/ca.json index 8ba96e603f5..3f9ce635338 100644 --- a/homeassistant/components/almond/translations/ca.json +++ b/homeassistant/components/almond/translations/ca.json @@ -9,7 +9,7 @@ "step": { "hassio_confirm": { "description": "Vols configurar Home Assistant perqu\u00e8 es connecti amb Almond proporcionat pel complement de Hass.io: {addon}?", - "title": "Almond (complement de Hass.io)" + "title": "Almond via complement de Hass.io" }, "pick_implementation": { "title": "Selecciona el m\u00e8tode d'autenticaci\u00f3" diff --git a/homeassistant/components/almond/translations/cs.json b/homeassistant/components/almond/translations/cs.json index 1c667c9d55e..dc981403ad2 100644 --- a/homeassistant/components/almond/translations/cs.json +++ b/homeassistant/components/almond/translations/cs.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "Chcete nakonfigurovat slu\u017ebu Home Assistant pro p\u0159ipojen\u00ed k Almond pomoc\u00ed hass.io {addon}?", - "title": "Almond prost\u0159ednictv\u00edm dopl\u0148ku Hass.io" + "description": "Chcete nakonfigurovat slu\u017ebu Home Assistant pro p\u0159ipojen\u00ed k Almond pomoc\u00ed Supervisor {addon}?", + "title": "Almond prost\u0159ednictv\u00edm dopl\u0148ku Supervisor" }, "pick_implementation": { "title": "Vyberte metodu ov\u011b\u0159en\u00ed" diff --git a/homeassistant/components/almond/translations/da.json b/homeassistant/components/almond/translations/da.json index 37c66ea8efd..0e7a804acc6 100644 --- a/homeassistant/components/almond/translations/da.json +++ b/homeassistant/components/almond/translations/da.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til Almond leveret af Hass.io-tilf\u00f8jelsen: {addon}?", - "title": "Almond via Hass.io-tilf\u00f8jelse" + "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til Almond leveret af Supervisor-tilf\u00f8jelsen: {addon}?", + "title": "Almond via Supervisor-tilf\u00f8jelse" }, "pick_implementation": { "title": "V\u00e6lg godkendelsesmetode" diff --git a/homeassistant/components/almond/translations/de.json b/homeassistant/components/almond/translations/de.json index 5eb8c4940aa..1f69b1c09e4 100644 --- a/homeassistant/components/almond/translations/de.json +++ b/homeassistant/components/almond/translations/de.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass eine Verbindung mit Almond als Hass.io-Add-On hergestellt wird: {addon}?", - "title": "Almond \u00fcber das Hass.io Add-on" + "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass eine Verbindung mit Almond als Supervisor-Add-On hergestellt wird: {addon}?", + "title": "Almond \u00fcber das Supervisor Add-on" }, "pick_implementation": { "title": "W\u00e4hle die Authentifizierungsmethode" diff --git a/homeassistant/components/almond/translations/es-419.json b/homeassistant/components/almond/translations/es-419.json index 50a43d67b6d..ce1d655d69e 100644 --- a/homeassistant/components/almond/translations/es-419.json +++ b/homeassistant/components/almond/translations/es-419.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "\u00bfDesea configurar Home Assistant para conectarse a Almond proporcionado por el complemento Hass.io: {addon}?", - "title": "Almond a trav\u00e9s del complemento Hass.io" + "description": "\u00bfDesea configurar Home Assistant para conectarse a Almond proporcionado por el complemento Supervisor: {addon}?", + "title": "Almond a trav\u00e9s del complemento Supervisor" }, "pick_implementation": { "title": "Seleccione el m\u00e9todo de autenticaci\u00f3n" diff --git a/homeassistant/components/almond/translations/es.json b/homeassistant/components/almond/translations/es.json index f14d3cd04ee..4dc5e4ee1c0 100644 --- a/homeassistant/components/almond/translations/es.json +++ b/homeassistant/components/almond/translations/es.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "\u00bfDesea configurar Home Assistant para conectarse a Almond proporcionado por el complemento Hass.io: {addon} ?", - "title": "Almond a trav\u00e9s del complemento Hass.io" + "description": "\u00bfDesea configurar Home Assistant para conectarse a Almond proporcionado por el complemento Supervisor: {addon} ?", + "title": "Almond a trav\u00e9s del complemento Supervisor" }, "pick_implementation": { "title": "Seleccione el m\u00e9todo de autenticaci\u00f3n" diff --git a/homeassistant/components/almond/translations/et.json b/homeassistant/components/almond/translations/et.json index 55ab29b2a81..c8646d6f090 100644 --- a/homeassistant/components/almond/translations/et.json +++ b/homeassistant/components/almond/translations/et.json @@ -9,7 +9,7 @@ "step": { "hassio_confirm": { "description": "Kas soovid seadistada Home Assistant-i \u00fchendust Almondiga mida pakub Hass.io lisandmoodul: {addon} ?", - "title": "Almond Hass.io lisandmooduli kaudu" + "title": "Almond Hass.io lisandmooduli abil" }, "pick_implementation": { "title": "Vali tuvastusmeetod" diff --git a/homeassistant/components/almond/translations/fr.json b/homeassistant/components/almond/translations/fr.json index e4fb8610cd0..0e6a8e0be3f 100644 --- a/homeassistant/components/almond/translations/fr.json +++ b/homeassistant/components/almond/translations/fr.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "Voulez-vous configurer Home Assistant pour se connecter \u00e0 Almond fourni par le module compl\u00e9mentaire Hass.io: {addon} ?", - "title": "Almonf via le module compl\u00e9mentaire Hass.io" + "description": "Voulez-vous configurer Home Assistant pour qu'il se connecte \u00e0 Almond fourni par le module compl\u00e9mentaire Hass.io: {addon} ?", + "title": "Amande via le module compl\u00e9mentaire Hass.io" }, "pick_implementation": { "title": "S\u00e9lectionner une m\u00e9thode d'authentification" diff --git a/homeassistant/components/almond/translations/hu.json b/homeassistant/components/almond/translations/hu.json index 7654f66bc28..568cd7270de 100644 --- a/homeassistant/components/almond/translations/hu.json +++ b/homeassistant/components/almond/translations/hu.json @@ -1,16 +1,18 @@ { "config": { "abort": { - "cannot_connect": "Nem lehet csatlakozni az Almond szerverhez.", - "missing_configuration": "K\u00e9rj\u00fck, ellen\u0151rizze az Almond be\u00e1ll\u00edt\u00e1s\u00e1nak dokument\u00e1ci\u00f3j\u00e1t." + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "step": { "hassio_confirm": { - "description": "Be szeretn\u00e9 \u00e1ll\u00edtani a Home Assistant alkalmaz\u00e1st az Almondhoz val\u00f3 csatlakoz\u00e1shoz, amelyet a Hass.io kieg\u00e9sz\u00edt\u0151 biztos\u00edt: {addon} ?", - "title": "Almond a Hass.io kieg\u00e9sz\u00edt\u0151n kereszt\u00fcl" + "description": "Be szeretn\u00e9 \u00e1ll\u00edtani a Home Assistant alkalmaz\u00e1st az Almondhoz val\u00f3 csatlakoz\u00e1shoz, amelyet a Supervisor kieg\u00e9sz\u00edt\u0151 biztos\u00edt: {addon} ?", + "title": "Almond a Supervisor kieg\u00e9sz\u00edt\u0151n kereszt\u00fcl" }, "pick_implementation": { - "title": "V\u00e1lassza ki a hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" } } } diff --git a/homeassistant/components/almond/translations/id.json b/homeassistant/components/almond/translations/id.json new file mode 100644 index 00000000000..21a627132c4 --- /dev/null +++ b/homeassistant/components/almond/translations/id.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "cannot_connect": "Gagal terhubung", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "hassio_confirm": { + "description": "Ingin mengonfigurasi Home Assistant untuk terhubung ke Almond yang disediakan oleh add-on Supervisor {addon}?", + "title": "Almond melalui add-on Home Assistant" + }, + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/almond/translations/it.json b/homeassistant/components/almond/translations/it.json index ab5d7f86f19..7a41f00437b 100644 --- a/homeassistant/components/almond/translations/it.json +++ b/homeassistant/components/almond/translations/it.json @@ -8,7 +8,7 @@ }, "step": { "hassio_confirm": { - "description": "Vuoi configurare Home Assistant a connettersi ad Almond tramite il componente aggiuntivo Hass.io: {addon} ?", + "description": "Vuoi configurare Home Assistant per connettersi a Almond fornito dal componente aggiuntivo di Hass.io: {addon}?", "title": "Almond tramite il componente aggiuntivo di Hass.io" }, "pick_implementation": { diff --git a/homeassistant/components/almond/translations/ko.json b/homeassistant/components/almond/translations/ko.json index 062ef885c70..9fe759a6ba4 100644 --- a/homeassistant/components/almond/translations/ko.json +++ b/homeassistant/components/almond/translations/ko.json @@ -4,12 +4,12 @@ "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "hassio_confirm": { - "description": "Hass.io {addon} \uc560\ub4dc\uc628\uc73c\ub85c Almond \uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant \ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Hass.io \uc560\ub4dc\uc628\uc758 Almond" + "description": "Hass.io {addon} \ucd94\uac00 \uae30\ub2a5\uc73c\ub85c Almond\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Hass.io \ucd94\uac00 \uae30\ub2a5\uc758 Almond" }, "pick_implementation": { "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" diff --git a/homeassistant/components/almond/translations/lb.json b/homeassistant/components/almond/translations/lb.json index 5a59645cdaa..0e29d69bbed 100644 --- a/homeassistant/components/almond/translations/lb.json +++ b/homeassistant/components/almond/translations/lb.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mam Almond ze verbannen dee vun der hass.io Erweiderung {addon} bereet gestallt g\u00ebtt?", - "title": "Almond via Hass.io Erweiderung" + "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mam Almond ze verbannen dee vun der Supervisor Erweiderung {addon} bereet gestallt g\u00ebtt?", + "title": "Almond via Supervisor Erweiderung" }, "pick_implementation": { "title": "Wiel Authentifikatiouns Method aus" diff --git a/homeassistant/components/almond/translations/nl.json b/homeassistant/components/almond/translations/nl.json index 26bbc8dea87..6d3aaf29e97 100644 --- a/homeassistant/components/almond/translations/nl.json +++ b/homeassistant/components/almond/translations/nl.json @@ -2,17 +2,17 @@ "config": { "abort": { "cannot_connect": "Kan geen verbinding maken met de Almond-server.", - "missing_configuration": "Raadpleeg de documentatie over het instellen van Almond.", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "hassio_confirm": { - "description": "Wilt u Home Assistant configureren om verbinding te maken met Almond die wordt aangeboden door de hass.io add-on {addon} ?", - "title": "Almond via Hass.io add-on" + "description": "Wilt u Home Assistant configureren om verbinding te maken met Almond die wordt aangeboden door de Supervisor add-on {addon} ?", + "title": "Almond via Supervisor add-on" }, "pick_implementation": { - "title": "Kies de authenticatie methode" + "title": "Kies een authenticatie methode" } } } diff --git a/homeassistant/components/almond/translations/no.json b/homeassistant/components/almond/translations/no.json index 9cd22ca5bc5..e6ca8f16589 100644 --- a/homeassistant/components/almond/translations/no.json +++ b/homeassistant/components/almond/translations/no.json @@ -8,7 +8,7 @@ }, "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til \u00e5 koble til Almond levert av Hass.io-tillegg: {addon}?", + "description": "Vil du konfigurere Home Assistant for \u00e5 koble til Mandel levert av Hass.io-tillegget: {addon} ?", "title": "Almond via Hass.io-tillegg" }, "pick_implementation": { diff --git a/homeassistant/components/almond/translations/sl.json b/homeassistant/components/almond/translations/sl.json index 573df43876f..cb20393201f 100644 --- a/homeassistant/components/almond/translations/sl.json +++ b/homeassistant/components/almond/translations/sl.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "Ali \u017eelite konfigurirati Home Assistant za povezavo z Almondom, ki ga ponuja dodatek Hass.io: {addon} ?", - "title": "Almond prek dodatka Hass.io" + "description": "Ali \u017eelite konfigurirati Home Assistant za povezavo z Almondom, ki ga ponuja dodatek Supervisor: {addon} ?", + "title": "Almond prek dodatka Supervisor" }, "pick_implementation": { "title": "Izberite na\u010din preverjanja pristnosti" diff --git a/homeassistant/components/almond/translations/sv.json b/homeassistant/components/almond/translations/sv.json index 6a7dfdb970c..8b20512df9b 100644 --- a/homeassistant/components/almond/translations/sv.json +++ b/homeassistant/components/almond/translations/sv.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "Vill du konfigurera Home Assistant f\u00f6r att ansluta till Almond som tillhandah\u00e5lls av Hass.io-till\u00e4gget: {addon} ?", - "title": "Almond via Hass.io-till\u00e4gget" + "description": "Vill du konfigurera Home Assistant f\u00f6r att ansluta till Almond som tillhandah\u00e5lls av Supervisor-till\u00e4gget: {addon} ?", + "title": "Almond via Supervisor-till\u00e4gget" }, "pick_implementation": { "title": "V\u00e4lj autentiseringsmetod" diff --git a/homeassistant/components/almond/translations/uk.json b/homeassistant/components/almond/translations/uk.json index 7f8c12917bb..db96ef3d0a3 100644 --- a/homeassistant/components/almond/translations/uk.json +++ b/homeassistant/components/almond/translations/uk.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e Almond (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io \"{addon}\")?", - "title": "Almond (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io)" + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e Almond (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Supervisor \"{addon}\")?", + "title": "Almond (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Supervisor)" }, "pick_implementation": { "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" diff --git a/homeassistant/components/almond/translations/zh-Hant.json b/homeassistant/components/almond/translations/zh-Hant.json index 6312d4ecd18..c8004ecde4f 100644 --- a/homeassistant/components/almond/translations/zh-Hant.json +++ b/homeassistant/components/almond/translations/zh-Hant.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u4f7f\u7528 Hass.io \u9644\u52a0\u7d44\u4ef6\uff1a{addon} \u9023\u7dda\u81f3 Almond\uff1f", - "title": "\u4f7f\u7528 Hass.io \u9644\u52a0\u7d44\u4ef6 Almond" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u4f7f\u7528 Hass.io \u9644\u52a0\u5143\u4ef6\uff1a{addon} \u9023\u7dda\u81f3 Almond\uff1f", + "title": "\u4f7f\u7528 Hass.io \u9644\u52a0\u5143\u4ef6 Almond" }, "pick_implementation": { "title": "\u9078\u64c7\u9a57\u8b49\u6a21\u5f0f" diff --git a/homeassistant/components/ambiclimate/translations/hu.json b/homeassistant/components/ambiclimate/translations/hu.json index 19f706be1c8..04035f04cca 100644 --- a/homeassistant/components/ambiclimate/translations/hu.json +++ b/homeassistant/components/ambiclimate/translations/hu.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t." + }, "create_entry": { - "default": "Sikeres autentik\u00e1ci\u00f3" + "default": "Sikeres hiteles\u00edt\u00e9s" } } } \ No newline at end of file diff --git a/homeassistant/components/ambiclimate/translations/id.json b/homeassistant/components/ambiclimate/translations/id.json new file mode 100644 index 00000000000..66c30afcb09 --- /dev/null +++ b/homeassistant/components/ambiclimate/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "access_token": "Terjadi kesalahan yang tidak diketahui saat membuat token akses.", + "already_configured": "Akun sudah dikonfigurasi", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi." + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "error": { + "follow_link": "Buka tautan dan autentikasi sebelum menekan Kirim", + "no_token": "Tidak diautentikasi dengan Ambiclimate" + }, + "step": { + "auth": { + "description": "Buka [tautan ini] ({authorization_url}) dan **Izinkan** akses ke akun Ambiclimate Anda, lalu kembali dan tekan **Kirim** di bawah ini.\n(Pastikan URL panggil balik yang ditentukan adalah {cb_url})", + "title": "Autentikasi Ambiclimate" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambiclimate/translations/ko.json b/homeassistant/components/ambiclimate/translations/ko.json index c28affbebb3..e47c5041728 100644 --- a/homeassistant/components/ambiclimate/translations/ko.json +++ b/homeassistant/components/ambiclimate/translations/ko.json @@ -10,11 +10,11 @@ }, "error": { "follow_link": "\ud655\uc778\uc744 \ud074\ub9ad\ud558\uae30 \uc804\uc5d0 \ub9c1\ud06c\ub97c \ub530\ub77c \uc778\uc99d\uc744 \ubc1b\uc544\uc8fc\uc138\uc694", - "no_token": "Ambi Climate \ub85c \uc778\uc99d\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4" + "no_token": "Ambi Climate\ub85c \uc778\uc99d\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4" }, "step": { "auth": { - "description": "[\ub9c1\ud06c]({authorization_url}) \ub97c \ud074\ub9ad\ud558\uc5ec Ambiclimate \uacc4\uc815\uc5d0 \ub300\ud574 **\ud5c8\uc6a9**\ud55c \ub2e4\uc74c, \ub2e4\uc2dc \ub3cc\uc544\uc640\uc11c \ud558\ub2e8\uc758 **\ud655\uc778**\uc744 \ud074\ub9ad\ud574\uc8fc\uc138\uc694.\n(\ucf5c\ubc31 URL \uc774 {cb_url} \ub85c \uc9c0\uc815\ub418\uc5c8\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694)", + "description": "[\ub9c1\ud06c]({authorization_url})(\uc744)\ub97c \ud074\ub9ad\ud558\uc5ec Ambiclimate \uacc4\uc815\uc5d0 \ub300\ud574 **\ud5c8\uc6a9**\ud55c \ub2e4\uc74c, \ub2e4\uc2dc \ub3cc\uc544\uc640\uc11c \ud558\ub2e8\uc758 **\ud655\uc778**\uc744 \ud074\ub9ad\ud574\uc8fc\uc138\uc694.\n(\ucf5c\ubc31 URL\uc774 {cb_url}(\uc73c)\ub85c \uc9c0\uc815\ub418\uc5c8\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694)", "title": "Ambi Climate \uc778\uc99d\ud558\uae30" } } diff --git a/homeassistant/components/ambient_station/translations/hu.json b/homeassistant/components/ambient_station/translations/hu.json index e6b95634827..7c7e3a658b9 100644 --- a/homeassistant/components/ambient_station/translations/hu.json +++ b/homeassistant/components/ambient_station/translations/hu.json @@ -1,7 +1,10 @@ { "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, "error": { - "invalid_key": "\u00c9rv\u00e9nytelen API kulcs \u00e9s / vagy alkalmaz\u00e1skulcs", + "invalid_key": "\u00c9rv\u00e9nytelen API kulcs", "no_devices": "Nincs a fi\u00f3kodban tal\u00e1lhat\u00f3 eszk\u00f6z" }, "step": { diff --git a/homeassistant/components/ambient_station/translations/id.json b/homeassistant/components/ambient_station/translations/id.json new file mode 100644 index 00000000000..1b5a1dd0b21 --- /dev/null +++ b/homeassistant/components/ambient_station/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "invalid_key": "Kunci API tidak valid", + "no_devices": "Tidak ada perangkat yang ditemukan dalam akun" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "app_key": "Kunci Aplikasi" + }, + "title": "Isi informasi Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambient_station/translations/nl.json b/homeassistant/components/ambient_station/translations/nl.json index 02c8f0727f8..008bc10e084 100644 --- a/homeassistant/components/ambient_station/translations/nl.json +++ b/homeassistant/components/ambient_station/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Service is al geconfigureerd" }, "error": { - "invalid_key": "Ongeldige API-sleutel en/of applicatiesleutel", + "invalid_key": "Ongeldige API-sleutel", "no_devices": "Geen apparaten gevonden in account" }, "step": { diff --git a/homeassistant/components/apple_tv/translations/hu.json b/homeassistant/components/apple_tv/translations/hu.json index 26c02fabbb4..63bf29a73f1 100644 --- a/homeassistant/components/apple_tv/translations/hu.json +++ b/homeassistant/components/apple_tv/translations/hu.json @@ -1,15 +1,18 @@ { "config": { "abort": { - "no_devices_found": "Nincs eszk\u00f6z a h\u00e1l\u00f3zaton", - "unknown": "V\u00e1ratlan hiba" + "already_configured_device": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "invalid_auth": "Azonos\u00edt\u00e1s nem siker\u00fclt", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", - "unknown": "V\u00e1ratlan hiba" + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, + "flow_title": "Apple TV: {name}", "step": { "confirm": { "title": "Apple TV sikeresen hozz\u00e1adva" @@ -19,7 +22,7 @@ }, "pair_with_pin": { "data": { - "pin": "PIN K\u00f3d" + "pin": "PIN-k\u00f3d" }, "title": "P\u00e1ros\u00edt\u00e1s" }, diff --git a/homeassistant/components/apple_tv/translations/id.json b/homeassistant/components/apple_tv/translations/id.json new file mode 100644 index 00000000000..5646b498242 --- /dev/null +++ b/homeassistant/components/apple_tv/translations/id.json @@ -0,0 +1,64 @@ +{ + "config": { + "abort": { + "already_configured_device": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "backoff": "Perangkat tidak bisa menerima permintaan pemasangan saat ini (Anda mungkin telah berulang kali memasukkan kode PIN yang salah). Coba lagi nanti.", + "device_did_not_pair": "Tidak ada upaya untuk menyelesaikan proses pemasangan dari sisi perangkat.", + "invalid_config": "Konfigurasi untuk perangkat ini tidak lengkap. Coba tambahkan lagi.", + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "already_configured": "Perangkat sudah dikonfigurasi", + "invalid_auth": "Autentikasi tidak valid", + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "no_usable_service": "Perangkat ditemukan tetapi kami tidak dapat mengidentifikasi berbagai cara untuk membuat koneksi ke perangkat tersebut. Jika Anda terus melihat pesan ini, coba tentukan alamat IP-nya atau mulai ulang Apple TV Anda.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Apple TV: {name}", + "step": { + "confirm": { + "description": "Anda akan menambahkan Apple TV bernama `{name}` ke Home Assistant.\n\n** Untuk menyelesaikan proses, Anda mungkin harus memasukkan kode PIN beberapa kali.**\n\nPerhatikan bahwa Anda *tidak* akan dapat mematikan Apple TV dengan integrasi ini. Hanya pemutar media di Home Assistant yang akan dimatikan!", + "title": "Konfirmasikan menambahkan Apple TV" + }, + "pair_no_pin": { + "description": "Pemasangan diperlukan untuk layanan `{protocol}`. Masukkan PIN {pin} di Apple TV untuk melanjutkan.", + "title": "Memasangkan" + }, + "pair_with_pin": { + "data": { + "pin": "Kode PIN" + }, + "description": "Pemasangan diperlukan untuk protokol `{protocol}`. Masukkan kode PIN yang ditampilkan pada layar. Angka nol di awal harus dihilangkan. Misalnya, masukkan 123 jika kode yang ditampilkan adalah 0123.", + "title": "Memasangkan" + }, + "reconfigure": { + "description": "Apple TV ini mengalami masalah koneksi dan harus dikonfigurasi ulang.", + "title": "Konfigurasi ulang perangkat" + }, + "service_problem": { + "description": "Terjadi masalah saat protokol pemasangan `{protocol}`. Masalah ini akan diabaikan.", + "title": "Gagal menambahkan layanan" + }, + "user": { + "data": { + "device_input": "Perangkat" + }, + "description": "Mulai dengan memasukkan nama perangkat (misalnya Dapur atau Kamar Tidur) atau alamat IP Apple TV yang ingin ditambahkan. Jika ada perangkat yang ditemukan secara otomatis di jaringan Anda, perangkat tersebut akan ditampilkan di bawah ini.\n\nJika Anda tidak dapat melihat perangkat atau mengalami masalah, coba tentukan alamat IP perangkat.\n\n{devices}", + "title": "Siapkan Apple TV baru" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "start_off": "Jangan nyalakan perangkat saat memulai Home Assistant" + }, + "description": "Konfigurasikan pengaturan umum perangkat" + } + } + }, + "title": "Apple TV" +} \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/ko.json b/homeassistant/components/apple_tv/translations/ko.json index c7e664b0638..278dbec04e4 100644 --- a/homeassistant/components/apple_tv/translations/ko.json +++ b/homeassistant/components/apple_tv/translations/ko.json @@ -3,6 +3,9 @@ "abort": { "already_configured_device": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "backoff": "\uae30\uae30\uac00 \ud604\uc7ac \ud398\uc5b4\ub9c1 \uc694\uccad\uc744 \uc218\ub77d\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4(\uc798\ubabb\ub41c PIN \ucf54\ub4dc\ub97c \ub108\ubb34 \ub9ce\uc774 \uc785\ub825\ud588\uc744 \uc218 \uc788\uc74c). \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "device_did_not_pair": "\uae30\uae30\uc5d0\uc11c \ud398\uc5b4\ub9c1 \ud504\ub85c\uc138\uc2a4\ub97c \uc644\ub8cc\ud558\ub824\uace0 \uc2dc\ub3c4\ud558\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", + "invalid_config": "\uc774 \uae30\uae30\uc5d0 \ub300\ud55c \uad6c\uc131\uc774 \ubd88\uc644\uc804\ud569\ub2c8\ub2e4. \ub2e4\uc2dc \ucd94\uac00\ud574\uc8fc\uc138\uc694.", "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, @@ -10,14 +13,52 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "no_usable_service": "\uae30\uae30\ub97c \ucc3e\uc558\uc9c0\ub9cc \uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\ub294 \ubc29\ubc95\uc744 \uc2dd\ubcc4\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uba54\uc2dc\uc9c0\uac00 \uacc4\uc18d \ud45c\uc2dc\ub418\uba74 \ud574\ub2f9 IP \uc8fc\uc18c\ub97c \uc9c1\uc811 \uc9c0\uc815\ud574\uc8fc\uc2dc\uac70\ub098 Apple TV\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud574\uc8fc\uc138\uc694.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, + "flow_title": "Apple TV: {name}", "step": { + "confirm": { + "description": "Apple TV `{name}`\uc744(\ub97c) Home Assistant\uc5d0 \ucd94\uac00\ud558\ub824\uace0 \ud569\ub2c8\ub2e4.\n\n**\ud504\ub85c\uc138\uc2a4\ub97c \uc644\ub8cc\ud558\ub824\uba74 \uc5ec\ub7ec \uac1c\uc758 PIN \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc57c \ud560 \uc218\ub3c4 \uc788\uc2b5\ub2c8\ub2e4.**\n\n\uc774 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \ud1b5\ud574 Apple TV\uc758 \uc804\uc6d0\uc740 *\ub04c \uc218 \uc5c6\uc2b5\ub2c8\ub2e4*. Home Assistant\uc758 \ubbf8\ub514\uc5b4 \ud50c\ub808\uc774\uc5b4\ub9cc \uaebc\uc9d1\ub2c8\ub2e4!", + "title": "Apple TV \ucd94\uac00 \ud655\uc778\ud558\uae30" + }, + "pair_no_pin": { + "description": "`{protocol}` \uc11c\ube44\uc2a4\uc5d0 \ub300\ud55c \ud398\uc5b4\ub9c1\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \uacc4\uc18d\ud558\ub824\uba74 Apple TV\uc5d0 PIN {pin}\uc744(\ub97c) \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "\ud398\uc5b4\ub9c1\ud558\uae30" + }, "pair_with_pin": { "data": { "pin": "PIN \ucf54\ub4dc" - } + }, + "description": "`{protocol}` \ud504\ub85c\ud1a0\ucf5c\uc5d0 \ub300\ud55c \ud398\uc5b4\ub9c1\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \ud654\uba74\uc5d0 \ud45c\uc2dc\ub418\ub294 PIN \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc55e\uc790\ub9ac\uc758 0\uc740 \uc0dd\ub7b5\ub418\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc989, \ud45c\uc2dc\ub41c \ucf54\ub4dc\uac00 0123\uc774\uba74 123\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "\ud398\uc5b4\ub9c1\ud558\uae30" + }, + "reconfigure": { + "description": "\uc774 Apple TV\uc5d0 \uc5f0\uacb0 \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud558\uc5ec \ub2e4\uc2dc \uad6c\uc131\ud574\uc57c \ud569\ub2c8\ub2e4.", + "title": "\uae30\uae30 \uc7ac\uad6c\uc131" + }, + "service_problem": { + "description": "\ud504\ub85c\ud1a0\ucf5c `{protocol}`\uc744(\ub97c) \ud398\uc5b4\ub9c1\ud558\ub294 \ub3d9\uc548 \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\ub294 \ubb34\uc2dc\ub429\ub2c8\ub2e4.", + "title": "\uc11c\ube44\uc2a4\ub97c \ucd94\uac00\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "user": { + "data": { + "device_input": "\uae30\uae30" + }, + "description": "\uba3c\uc800 \ucd94\uac00\ud560 Apple TV\uc758 \uae30\uae30 \uc774\ub984(\uc608: \uc8fc\ubc29 \ub610\ub294 \uce68\uc2e4) \ub610\ub294 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uc7a5\uce58\uac00 \uc790\ub3d9\uc73c\ub85c \ubc1c\uacac\ub41c \uacbd\uc6b0 \ub2e4\uc74c\uacfc \uac19\uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4.\n\n\uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uac70\ub098 \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud55c \uacbd\uc6b0 \uae30\uae30 IP \uc8fc\uc18c\ub97c \uc9c1\uc811 \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\n{devices}", + "title": "\uc0c8\ub85c\uc6b4 Apple TV \uc124\uc815\ud558\uae30" } } - } + }, + "options": { + "step": { + "init": { + "data": { + "start_off": "Home Assistant\ub97c \uc2dc\uc791\ud560 \ub54c \uae30\uae30\ub97c \ucf1c\uc9c0 \ub9d0\uc544 \uc8fc\uc138\uc694" + }, + "description": "\uc77c\ubc18 \uae30\uae30 \uc124\uc815 \uad6c\uc131" + } + } + }, + "title": "Apple TV" } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/nl.json b/homeassistant/components/apple_tv/translations/nl.json index d809ac749b7..e313e972188 100644 --- a/homeassistant/components/apple_tv/translations/nl.json +++ b/homeassistant/components/apple_tv/translations/nl.json @@ -13,29 +13,52 @@ "already_configured": "Apparaat is al geconfigureerd", "invalid_auth": "Ongeldige authenticatie", "no_devices_found": "Geen apparaten gevonden op het netwerk", + "no_usable_service": "Er is een apparaat gevonden, maar er kon geen manier worden gevonden om er verbinding mee te maken. Als u dit bericht blijft zien, probeert u het IP-adres in te voeren of uw Apple TV opnieuw op te starten.", "unknown": "Onverwachte fout" }, "flow_title": "Apple TV: {name}", "step": { "confirm": { + "description": "U staat op het punt om de Apple TV met de naam `{name}` toe te voegen aan Home Assistant.\n\n**Om het proces te voltooien, moet u mogelijk meerdere PIN-codes invoeren.**\n\nLet op: u kunt uw Apple TV *niet* uitschakelen met deze integratie. Alleen de mediaspeler in Home Assistant wordt uitgeschakeld!", "title": "Bevestig het toevoegen van Apple TV" }, "pair_no_pin": { + "description": "Koppeling is vereist voor de `{protocol}` service. Voer de PIN {pin} in op uw Apple TV om verder te gaan.", "title": "Koppelen" }, "pair_with_pin": { "data": { "pin": "PIN-code" }, + "description": "Koppelen is vereist voor het `{protocol}` protocol. Voer de PIN-code in die op het scherm wordt getoond. Beginnende nullen moeten worden weggelaten, d.w.z. voer 123 in als de getoonde code 0123 is.", "title": "Koppelen" }, + "reconfigure": { + "description": "Deze Apple TV ondervindt verbindingsproblemen en moet opnieuw worden geconfigureerd.", + "title": "Apparaat herconfiguratie" + }, + "service_problem": { + "description": "Er is een probleem opgetreden tijdens het koppelen van protocol `{protocol}`. Dit wordt genegeerd.", + "title": "Dienst toevoegen mislukt" + }, "user": { "data": { "device_input": "Apparaat" }, + "description": "Begin met het invoeren van de apparaatnaam (bijv. Keuken of Slaapkamer) of het IP-adres van de Apple TV die u wilt toevoegen. Als er automatisch apparaten in uw netwerk zijn gevonden, worden deze hieronder weergegeven.\n\nAls u het apparaat niet kunt zien of problemen ondervindt, probeer dan het IP-adres van het apparaat in te voeren.\n\n{devices}", "title": "Stel een nieuwe Apple TV in" } } }, + "options": { + "step": { + "init": { + "data": { + "start_off": "Schakel het apparaat niet in wanneer u Home Assistant start" + }, + "description": "Algemene apparaatinstellingen configureren" + } + } + }, "title": "Apple TV" } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/hu.json b/homeassistant/components/arcam_fmj/translations/hu.json index 563ede56155..4af1181a265 100644 --- a/homeassistant/components/arcam_fmj/translations/hu.json +++ b/homeassistant/components/arcam_fmj/translations/hu.json @@ -1,7 +1,17 @@ { "config": { "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "port": "Port" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/id.json b/homeassistant/components/arcam_fmj/translations/id.json new file mode 100644 index 00000000000..96b10140948 --- /dev/null +++ b/homeassistant/components/arcam_fmj/translations/id.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "Arcam FMJ di {host}", + "step": { + "confirm": { + "description": "Ingin menambahkan Arcam FMJ `{host}` ke Home Assistant?" + }, + "user": { + "data": { + "host": "Host", + "port": "Port" + }, + "description": "Masukkan nama host atau alamat IP perangkat." + } + } + }, + "device_automation": { + "trigger_type": { + "turn_on": "{entity_name} diminta untuk dinyalakan" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/ko.json b/homeassistant/components/arcam_fmj/translations/ko.json index 532e5ef4c5f..29a2887f7e2 100644 --- a/homeassistant/components/arcam_fmj/translations/ko.json +++ b/homeassistant/components/arcam_fmj/translations/ko.json @@ -8,7 +8,7 @@ "flow_title": "Arcam FMJ: {host}", "step": { "confirm": { - "description": "Home Assistant \uc5d0 Arcam FMJ `{host}` \uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "Home Assistant\uc5d0 Arcam FMJ `{host}`\uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" }, "user": { "data": { @@ -21,7 +21,7 @@ }, "device_automation": { "trigger_type": { - "turn_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c0\ub3c4\ub85d \uc694\uccad\ub418\uc5c8\uc744 \ub54c" + "turn_on": "{entity_name}\uc774(\uac00) \ucf1c\uc9c0\ub3c4\ub85d \uc694\uccad\ub418\uc5c8\uc744 \ub54c" } } } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/pt.json b/homeassistant/components/arcam_fmj/translations/pt.json index 097e3d086d6..af72dfe96e2 100644 --- a/homeassistant/components/arcam_fmj/translations/pt.json +++ b/homeassistant/components/arcam_fmj/translations/pt.json @@ -1,6 +1,8 @@ { "config": { "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O processo de configura\u00e7\u00e3o j\u00e1 est\u00e1 a decorrer", "cannot_connect": "Falha na liga\u00e7\u00e3o" }, "error": { diff --git a/homeassistant/components/asuswrt/translations/bg.json b/homeassistant/components/asuswrt/translations/bg.json new file mode 100644 index 00000000000..dbb5f415f92 --- /dev/null +++ b/homeassistant/components/asuswrt/translations/bg.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "user": { + "data": { + "name": "\u0418\u043c\u0435", + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/hu.json b/homeassistant/components/asuswrt/translations/hu.json new file mode 100644 index 00000000000..4f47781a15c --- /dev/null +++ b/homeassistant/components/asuswrt/translations/hu.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_host": "\u00c9rv\u00e9nytelen hosztn\u00e9v vagy IP-c\u00edm", + "ssh_not_file": "Az SSH kulcsf\u00e1jl nem tal\u00e1lhat\u00f3", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "mode": "M\u00f3d", + "name": "N\u00e9v", + "password": "Jelsz\u00f3", + "port": "Port", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "title": "AsusWRT" + } + } + }, + "options": { + "step": { + "init": { + "title": "AsusWRT Be\u00e1ll\u00edt\u00e1sok" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/id.json b/homeassistant/components/asuswrt/translations/id.json new file mode 100644 index 00000000000..aa4eebd1f86 --- /dev/null +++ b/homeassistant/components/asuswrt/translations/id.json @@ -0,0 +1,45 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_host": "Nama host atau alamat IP tidak valid", + "pwd_and_ssh": "Hanya berikan kata sandi atau file kunci SSH", + "pwd_or_ssh": "Harap berikan kata sandi atau file kunci SSH", + "ssh_not_file": "File kunci SSH tidak ditemukan", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "mode": "Mode", + "name": "Nama", + "password": "Kata Sandi", + "port": "Port", + "protocol": "Protokol komunikasi yang akan digunakan", + "ssh_key": "Jalur ke file kunci SSH Anda (bukan kata sandi)", + "username": "Nama Pengguna" + }, + "description": "Tetapkan parameter yang diperlukan untuk terhubung ke router Anda", + "title": "AsusWRT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "consider_home": "Tenggang waktu dalam detik untuk perangkat dianggap sebagai keluar", + "dnsmasq": "Lokasi dnsmasq.leases di router file", + "interface": "Antarmuka statistik yang diinginkan (mis. eth0, eth1, dll)", + "require_ip": "Perangkat harus memiliki IP (untuk mode titik akses)", + "track_unknown": "Lacak perangkat yang tidak dikenal/tidak bernama" + }, + "title": "Opsi AsusWRT" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/ko.json b/homeassistant/components/asuswrt/translations/ko.json index de3de06e6b1..108d00adefb 100644 --- a/homeassistant/components/asuswrt/translations/ko.json +++ b/homeassistant/components/asuswrt/translations/ko.json @@ -1,11 +1,14 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "pwd_and_ssh": "\ube44\ubc00\ubc88\ud638 \ub610\ub294 SSH \ud0a4 \ud30c\uc77c\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4", + "pwd_or_ssh": "\ube44\ubc00\ubc88\ud638 \ub610\ub294 SSH \ud0a4 \ud30c\uc77c\uc744 \ub123\uc5b4\uc8fc\uc138\uc694", + "ssh_not_file": "SSH \ud0a4 \ud30c\uc77c\uc744 \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { @@ -16,8 +19,26 @@ "name": "\uc774\ub984", "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8", + "protocol": "\uc0ac\uc6a9\ud560 \ud1b5\uc2e0 \ud504\ub85c\ud1a0\ucf5c", + "ssh_key": "SSH \ud0a4 \ud30c\uc77c\uc758 \uacbd\ub85c (\ube44\ubc00\ubc88\ud638 \ub300\uccb4)", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - } + }, + "description": "\ub77c\uc6b0\ud130\uc5d0 \uc5f0\uacb0\ud558\ub294 \ub370 \ud544\uc694\ud55c \ub9e4\uac1c \ubcc0\uc218\ub97c \uc124\uc815\ud558\uae30", + "title": "AsusWRT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "consider_home": "\uae30\uae30\uac00 \uc678\ucd9c \uc0c1\ud0dc\ub85c \uac04\uc8fc\ub418\uae30 \uc804\uc5d0 \uae30\ub2e4\ub9ac\ub294 \uc2dc\uac04(\ucd08)", + "dnsmasq": "dnsmasq.lease \ud30c\uc77c\uc758 \ub77c\uc6b0\ud130 \uc704\uce58", + "interface": "\ud1b5\uacc4\ub97c \uc6d0\ud558\ub294 \uc778\ud130\ud398\uc774\uc2a4 (\uc608: eth0, eth1 \ub4f1)", + "require_ip": "\uae30\uae30\uc5d0\ub294 IP\uac00 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4 (\uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8 \ubaa8\ub4dc\uc778 \uacbd\uc6b0)", + "track_unknown": "\uc54c \uc218 \uc5c6\uac70\ub098 \uc774\ub984\uc774 \uc5c6\ub294 \uae30\uae30 \ucd94\uc801\ud558\uae30" + }, + "title": "AsusWRT \uc635\uc158" } } } diff --git a/homeassistant/components/asuswrt/translations/nl.json b/homeassistant/components/asuswrt/translations/nl.json index 9d1e76aaf2b..f6f347f771f 100644 --- a/homeassistant/components/asuswrt/translations/nl.json +++ b/homeassistant/components/asuswrt/translations/nl.json @@ -6,6 +6,8 @@ "error": { "cannot_connect": "Kan geen verbinding maken", "invalid_host": "Ongeldige hostnaam of IP-adres", + "pwd_and_ssh": "Geef alleen wachtwoord of SSH-sleutelbestand op", + "pwd_or_ssh": "Geef een wachtwoord of SSH-sleutelbestand op", "ssh_not_file": "SSH-sleutelbestand niet gevonden", "unknown": "Onverwachte fout" }, @@ -17,8 +19,11 @@ "name": "Naam", "password": "Wachtwoord", "port": "Poort", + "protocol": "Te gebruiken communicatieprotocol", + "ssh_key": "Pad naar uw SSH-sleutelbestand (in plaats van wachtwoord)", "username": "Gebruikersnaam" }, + "description": "Stel de vereiste parameter in om verbinding te maken met uw router", "title": "AsusWRT" } } @@ -27,6 +32,10 @@ "step": { "init": { "data": { + "consider_home": "Aantal seconden dat wordt gewacht voordat een apparaat als afwezig wordt beschouwd", + "dnsmasq": "De locatie in de router van de dnsmasq.leases-bestanden", + "interface": "De interface waarvan u statistieken wilt (bijv. Eth0, eth1 enz.)", + "require_ip": "Apparaten moeten een IP-adres hebben (voor toegangspuntmodus)", "track_unknown": "Volg onbekende / naamloze apparaten" }, "title": "AsusWRT-opties" diff --git a/homeassistant/components/atag/translations/hu.json b/homeassistant/components/atag/translations/hu.json index 1d28556ba1a..98e947ae643 100644 --- a/homeassistant/components/atag/translations/hu.json +++ b/homeassistant/components/atag/translations/hu.json @@ -1,11 +1,15 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { "user": { "data": { + "email": "E-mail", "host": "Hoszt", "port": "Port" } diff --git a/homeassistant/components/atag/translations/id.json b/homeassistant/components/atag/translations/id.json new file mode 100644 index 00000000000..24732f8c235 --- /dev/null +++ b/homeassistant/components/atag/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unauthorized": "Pemasangan ditolak, periksa perangkat untuk permintaan autentikasi" + }, + "step": { + "user": { + "data": { + "email": "Email", + "host": "Host", + "port": "Port" + }, + "title": "Hubungkan ke perangkat" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/ca.json b/homeassistant/components/august/translations/ca.json index 9d3e3535e46..f530b2cdc59 100644 --- a/homeassistant/components/august/translations/ca.json +++ b/homeassistant/components/august/translations/ca.json @@ -25,7 +25,7 @@ "code": "Codi de verificaci\u00f3" }, "description": "Comprova el teu {login_method} ({username}) i introdueix el codi de verificaci\u00f3 a continuaci\u00f3", - "title": "Autenticaci\u00f3 de dos factors" + "title": "Verificaci\u00f3 en dos passos" } } } diff --git a/homeassistant/components/august/translations/he.json b/homeassistant/components/august/translations/he.json new file mode 100644 index 00000000000..ac90b3264ea --- /dev/null +++ b/homeassistant/components/august/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/hu.json b/homeassistant/components/august/translations/hu.json index dee4ed9ee0f..dd2f6004354 100644 --- a/homeassistant/components/august/translations/hu.json +++ b/homeassistant/components/august/translations/hu.json @@ -1,11 +1,27 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { "password": "Jelsz\u00f3", + "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s (m\u00e1sodperc)", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } + }, + "validation": { + "data": { + "code": "Ellen\u0151rz\u0151 k\u00f3d" + }, + "title": "K\u00e9tfaktoros hiteles\u00edt\u00e9s" } } } diff --git a/homeassistant/components/august/translations/id.json b/homeassistant/components/august/translations/id.json new file mode 100644 index 00000000000..a66c43ce057 --- /dev/null +++ b/homeassistant/components/august/translations/id.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "login_method": "Metode Masuk", + "password": "Kata Sandi", + "timeout": "Tenggang waktu (detik)", + "username": "Nama Pengguna" + }, + "description": "Jika Metode Masuk adalah 'email', Nama Pengguna adalah alamat email. Jika Metode Masuk adalah 'telepon', Nama Pengguna adalah nomor telepon dalam format '+NNNNNNNNN'.", + "title": "Siapkan akun August" + }, + "validation": { + "data": { + "code": "Kode verifikasi" + }, + "description": "Periksa {login_method} ({username}) Anda dan masukkan kode verifikasi di bawah ini", + "title": "Autentikasi dua faktor" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/nl.json b/homeassistant/components/august/translations/nl.json index e48d27801cc..41af77b0c7c 100644 --- a/homeassistant/components/august/translations/nl.json +++ b/homeassistant/components/august/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Account al geconfigureerd", + "already_configured": "Account is al geconfigureerd", "reauth_successful": "Herauthenticatie was succesvol" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/august/translations/zh-Hant.json b/homeassistant/components/august/translations/zh-Hant.json index 667d8814659..d2721cd33cc 100644 --- a/homeassistant/components/august/translations/zh-Hant.json +++ b/homeassistant/components/august/translations/zh-Hant.json @@ -25,7 +25,7 @@ "code": "\u9a57\u8b49\u78bc" }, "description": "\u8acb\u78ba\u8a8d {login_method} ({username}) \u4e26\u65bc\u4e0b\u65b9\u8f38\u5165\u9a57\u8b49\u78bc", - "title": "\u5169\u6b65\u9a5f\u9a57\u8b49" + "title": "\u96d9\u91cd\u8a8d\u8b49" } } } diff --git a/homeassistant/components/aurora/translations/hu.json b/homeassistant/components/aurora/translations/hu.json index d5363860cbd..292ed552235 100644 --- a/homeassistant/components/aurora/translations/hu.json +++ b/homeassistant/components/aurora/translations/hu.json @@ -1,7 +1,7 @@ { "config": { "error": { - "cannot_connect": "Nem siker\u00fclt csatlakozni" + "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { "user": { diff --git a/homeassistant/components/aurora/translations/id.json b/homeassistant/components/aurora/translations/id.json new file mode 100644 index 00000000000..66cf534b7ae --- /dev/null +++ b/homeassistant/components/aurora/translations/id.json @@ -0,0 +1,26 @@ +{ + "config": { + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "latitude": "Lintang", + "longitude": "Bujur", + "name": "Nama" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "threshold": "Ambang (%)" + } + } + } + }, + "title": "Sensor Aurora NOAA" +} \ No newline at end of file diff --git a/homeassistant/components/aurora/translations/ko.json b/homeassistant/components/aurora/translations/ko.json index ea10c059f03..39f3c4d4281 100644 --- a/homeassistant/components/aurora/translations/ko.json +++ b/homeassistant/components/aurora/translations/ko.json @@ -12,5 +12,15 @@ } } } - } + }, + "options": { + "step": { + "init": { + "data": { + "threshold": "\uc784\uacc4\uac12 (%)" + } + } + } + }, + "title": "NOAA Aurora \uc13c\uc11c" } \ No newline at end of file diff --git a/homeassistant/components/auth/translations/id.json b/homeassistant/components/auth/translations/id.json index f6a22386f99..ed7bede5fff 100644 --- a/homeassistant/components/auth/translations/id.json +++ b/homeassistant/components/auth/translations/id.json @@ -1,13 +1,32 @@ { "mfa_setup": { - "totp": { + "notify": { + "abort": { + "no_available_service": "Tidak ada layanan notifikasi yang tersedia." + }, "error": { - "invalid_code": "Kode salah, coba lagi. Jika Anda mendapatkan kesalahan ini secara konsisten, pastikan jam pada sistem Home Assistant anda akurat." + "invalid_code": "Kode tifak valid, coba lagi." }, "step": { "init": { - "description": "Untuk mengaktifkan otentikasi dua faktor menggunakan password satu kali berbasis waktu, pindai kode QR dengan aplikasi otentikasi Anda. Jika Anda tidak memilikinya, kami menyarankan [Google Authenticator] (https://support.google.com/accounts/answer/1066447) atau [Authy] (https://authy.com/). \n\n {qr_code} \n \n Setelah memindai kode, masukkan kode enam digit dari aplikasi Anda untuk memverifikasi pengaturan. Jika Anda mengalami masalah saat memindai kode QR, lakukan pengaturan manual dengan kode ** ` {code} ` **.", - "title": "Siapkan otentikasi dua faktor menggunakan TOTP" + "description": "Pilih salah satu layanan notifikasi:", + "title": "Siapkan kata sandi sekali pakai yang dikirimkan oleh komponen notify" + }, + "setup": { + "description": "Kata sandi sekali pakai telah dikirim melalui **notify.{notify_service}**. Masukkan di bawah ini:", + "title": "Verifikasi penyiapan" + } + }, + "title": "Kata Sandi Sekali Pakai Notifikasi" + }, + "totp": { + "error": { + "invalid_code": "Kode tidak valid, coba lagi. Jika Anda terus mendapatkan kesalahan yang sama, pastikan jam pada sistem Home Assistant Anda sudah akurat." + }, + "step": { + "init": { + "description": "Untuk mengaktifkan autentikasi dua faktor menggunakan kata sandi sekali pakai berbasis waktu, pindai kode QR dengan aplikasi autentikasi Anda. Jika tidak punya, kami menyarankan aplikasi [Google Authenticator] (https://support.google.com/accounts/answer/1066447) atau [Authy] (https://authy.com/). \n\n {qr_code} \n \nSetelah memindai kode, masukkan kode enam digit dari aplikasi Anda untuk memverifikasi penyiapan. Jika mengalami masalah saat memindai kode QR, lakukan penyiapan manual dengan kode **`{code}`**.", + "title": "Siapkan autentikasi dua faktor menggunakan TOTP" } }, "title": "TOTP" diff --git a/homeassistant/components/auth/translations/ko.json b/homeassistant/components/auth/translations/ko.json index 80850bb58b4..09af8eb89bf 100644 --- a/homeassistant/components/auth/translations/ko.json +++ b/homeassistant/components/auth/translations/ko.json @@ -5,7 +5,7 @@ "no_available_service": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c \uc54c\ub9bc \uc11c\ube44\uc2a4\uac00 \uc5c6\uc2b5\ub2c8\ub2e4." }, "error": { - "invalid_code": "\uc798\ubabb\ub41c \ucf54\ub4dc\uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." + "invalid_code": "\ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." }, "step": { "init": { @@ -13,7 +13,7 @@ "title": "\uc54c\ub9bc \uad6c\uc131\uc694\uc18c\uac00 \uc81c\uacf5\ud558\ub294 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638 \uc124\uc815\ud558\uae30" }, "setup": { - "description": "**notify.{notify_service}** \uc5d0\uc11c \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \ubcf4\ub0c8\uc2b5\ub2c8\ub2e4. \uc544\ub798\uc758 \uacf5\ub780\uc5d0 \uc785\ub825\ud574\uc8fc\uc138\uc694:", + "description": "**notify.{notify_service}**\uc5d0\uc11c \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \ubcf4\ub0c8\uc2b5\ub2c8\ub2e4. \uc544\ub798\uc758 \uacf5\ub780\uc5d0 \uc785\ub825\ud574\uc8fc\uc138\uc694:", "title": "\uc124\uc815 \ud655\uc778\ud558\uae30" } }, @@ -21,11 +21,11 @@ }, "totp": { "error": { - "invalid_code": "\uc798\ubabb\ub41c \ucf54\ub4dc\uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694. \uc774 \uc624\ub958\uac00 \uc9c0\uc18d\uc801\uc73c\ub85c \ubc1c\uc0dd\ud55c\ub2e4\uba74 Home Assistant \uc758 \uc2dc\uac04\uc124\uc815\uc774 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694." + "invalid_code": "\ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694. \uc774 \uc624\ub958\uac00 \uc9c0\uc18d\uc801\uc73c\ub85c \ubc1c\uc0dd\ud55c\ub2e4\uba74 Home Assistant\uc758 \uc2dc\uac04\uc124\uc815\uc774 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694." }, "step": { "init": { - "description": "\uc2dc\uac04 \uae30\ubc18\uc758 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\ub294 2\ub2e8\uacc4 \uc778\uc99d\uc744 \uad6c\uc131\ud558\ub824\uba74 \uc778\uc99d\uc6a9 \uc571\uc744 \uc774\uc6a9\ud574\uc11c QR \ucf54\ub4dc\ub97c \uc2a4\uce94\ud574\uc8fc\uc138\uc694. \uc778\uc99d\uc6a9 \uc571\uc740 [Google OTP](https://support.google.com/accounts/answer/1066447) \ub610\ub294 [Authy](https://authy.com/) \ub97c \ucd94\ucc9c\ub4dc\ub9bd\ub2c8\ub2e4.\n\n{qr_code}\n\n\uc2a4\uce94 \ud6c4\uc5d0 \uc0dd\uc131\ub41c 6\uc790\ub9ac \ucf54\ub4dc\ub97c \uc785\ub825\ud558\uc5ec \uc124\uc815\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\uc8fc\uc138\uc694.", + "description": "\uc2dc\uac04 \uae30\ubc18\uc758 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\ub294 2\ub2e8\uacc4 \uc778\uc99d\uc744 \uad6c\uc131\ud558\ub824\uba74 \uc778\uc99d\uc6a9 \uc571\uc744 \uc774\uc6a9\ud574\uc11c QR \ucf54\ub4dc\ub97c \uc2a4\uce94\ud574\uc8fc\uc138\uc694. \uc778\uc99d\uc6a9 \uc571\uc740 [Google OTP](https://support.google.com/accounts/answer/1066447) \ub610\ub294 [Authy](https://authy.com/)\ub97c \ucd94\ucc9c\ub4dc\ub9bd\ub2c8\ub2e4.\n\n{qr_code}\n\n\uc2a4\uce94 \ud6c4\uc5d0 \uc0dd\uc131\ub41c 6\uc790\ub9ac \ucf54\ub4dc\ub97c \uc785\ub825\ud558\uc5ec \uc124\uc815\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\uc8fc\uc138\uc694.", "title": "TOTP 2\ub2e8\uacc4 \uc778\uc99d \uad6c\uc131\ud558\uae30" } }, diff --git a/homeassistant/components/auth/translations/zh-Hant.json b/homeassistant/components/auth/translations/zh-Hant.json index 96e7f21ac99..8e769cb5983 100644 --- a/homeassistant/components/auth/translations/zh-Hant.json +++ b/homeassistant/components/auth/translations/zh-Hant.json @@ -25,8 +25,8 @@ }, "step": { "init": { - "description": "\u6b32\u555f\u7528\u4e00\u6b21\u6027\u4e14\u5177\u6642\u6548\u6027\u7684\u5bc6\u78bc\u4e4b\u96d9\u91cd\u9a57\u8b49\u529f\u80fd\uff0c\u8acb\u4f7f\u7528\u60a8\u7684\u9a57\u8b49 App \u6383\u7784\u4e0b\u65b9\u7684 QR code \u3002\u5018\u82e5\u60a8\u5c1a\u672a\u5b89\u88dd\u4efb\u4f55 App\uff0c\u63a8\u85a6\u60a8\u4f7f\u7528 [Google Authenticator](https://support.google.com/accounts/answer/1066447) \u6216 [Authy](https://authy.com/)\u3002\n\n{qr_code}\n\n\u65bc\u641c\u5c0b\u4e4b\u5f8c\uff0c\u8f38\u5165 App \u4e2d\u7684\u516d\u4f4d\u6578\u5b57\u9032\u884c\u8a2d\u5b9a\u9a57\u8b49\u3002\u5047\u5982\u641c\u5c0b\u51fa\u73fe\u554f\u984c\uff0c\u8acb\u624b\u52d5\u8f38\u5165\u4ee5\u4e0b\u9a57\u8b49\u78bc **`{code}`**\u3002", - "title": "\u4f7f\u7528 TOTP \u8a2d\u5b9a\u96d9\u91cd\u9a57\u8b49" + "description": "\u6b32\u555f\u7528\u4e00\u6b21\u6027\u4e14\u5177\u6642\u6548\u6027\u7684\u5bc6\u78bc\u4e4b\u96d9\u91cd\u8a8d\u8b49\u529f\u80fd\uff0c\u8acb\u4f7f\u7528\u60a8\u7684\u9a57\u8b49 App \u6383\u7784\u4e0b\u65b9\u7684 QR code \u3002\u5018\u82e5\u60a8\u5c1a\u672a\u5b89\u88dd\u4efb\u4f55 App\uff0c\u63a8\u85a6\u60a8\u4f7f\u7528 [Google Authenticator](https://support.google.com/accounts/answer/1066447) \u6216 [Authy](https://authy.com/)\u3002\n\n{qr_code}\n\n\u65bc\u641c\u5c0b\u4e4b\u5f8c\uff0c\u8f38\u5165 App \u4e2d\u7684\u516d\u4f4d\u6578\u5b57\u9032\u884c\u8a2d\u5b9a\u9a57\u8b49\u3002\u5047\u5982\u641c\u5c0b\u51fa\u73fe\u554f\u984c\uff0c\u8acb\u624b\u52d5\u8f38\u5165\u4ee5\u4e0b\u9a57\u8b49\u78bc **`{code}`**\u3002", + "title": "\u4f7f\u7528 TOTP \u8a2d\u5b9a\u96d9\u91cd\u8a8d\u8b49" } }, "title": "TOTP" diff --git a/homeassistant/components/automation/translations/id.json b/homeassistant/components/automation/translations/id.json index eabfe0b64aa..58e8497c8b9 100644 --- a/homeassistant/components/automation/translations/id.json +++ b/homeassistant/components/automation/translations/id.json @@ -1,8 +1,8 @@ { "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" } }, "title": "Otomasi" diff --git a/homeassistant/components/awair/translations/hu.json b/homeassistant/components/awair/translations/hu.json index 436e8b1fb7d..53827adf344 100644 --- a/homeassistant/components/awair/translations/hu.json +++ b/homeassistant/components/awair/translations/hu.json @@ -1,7 +1,28 @@ { "config": { "abort": { - "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "invalid_access_token": "\u00c9rv\u00e9nytelen hozz\u00e1f\u00e9r\u00e9si token", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "reauth": { + "data": { + "access_token": "Hozz\u00e1f\u00e9r\u00e9si token", + "email": "E-mail" + }, + "description": "Add meg \u00fajra az Awair fejleszt\u0151i hozz\u00e1f\u00e9r\u00e9si tokent." + }, + "user": { + "data": { + "access_token": "Hozz\u00e1f\u00e9r\u00e9si token", + "email": "E-mail" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/awair/translations/id.json b/homeassistant/components/awair/translations/id.json new file mode 100644 index 00000000000..2c6fab90909 --- /dev/null +++ b/homeassistant/components/awair/translations/id.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "invalid_access_token": "Token akses tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "reauth": { + "data": { + "access_token": "Token Akses", + "email": "Email" + }, + "description": "Masukkan kembali token akses pengembang Awair Anda." + }, + "user": { + "data": { + "access_token": "Token Akses", + "email": "Email" + }, + "description": "Anda harus mendaftar untuk mendapatkan token akses pengembang Awair di: https://developer.getawair.com/onboard/login" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/translations/he.json b/homeassistant/components/axis/translations/he.json new file mode 100644 index 00000000000..3007c0e968c --- /dev/null +++ b/homeassistant/components/axis/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/translations/hu.json b/homeassistant/components/axis/translations/hu.json index 659c50e49e7..972690ede97 100644 --- a/homeassistant/components/axis/translations/hu.json +++ b/homeassistant/components/axis/translations/hu.json @@ -1,8 +1,13 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { - "already_configured": "Az eszk\u00f6zt m\u00e1r konfigur\u00e1ltuk", - "cannot_connect": "Sikertelen csatlakoz\u00e1s" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, "flow_title": "Axis eszk\u00f6z: {name} ({host})", "step": { diff --git a/homeassistant/components/axis/translations/id.json b/homeassistant/components/axis/translations/id.json new file mode 100644 index 00000000000..cdd498a8e6c --- /dev/null +++ b/homeassistant/components/axis/translations/id.json @@ -0,0 +1,37 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "link_local_address": "Tautan alamat lokal tidak didukung", + "not_axis_device": "Perangkat yang ditemukan bukan perangkat Axis" + }, + "error": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "flow_title": "{name} ({host})", + "step": { + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + }, + "title": "Siapkan perangkat Axis" + } + } + }, + "options": { + "step": { + "configure_stream": { + "data": { + "stream_profile": "Pilih profil streaming yang akan digunakan" + }, + "title": "Opsi streaming video perangkat Axis" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/translations/nl.json b/homeassistant/components/axis/translations/nl.json index 345e6622e93..8ba305366f9 100644 --- a/homeassistant/components/axis/translations/nl.json +++ b/homeassistant/components/axis/translations/nl.json @@ -23,5 +23,15 @@ "title": "Stel het Axis-apparaat in" } } + }, + "options": { + "step": { + "configure_stream": { + "data": { + "stream_profile": "Selecteer stream profiel om te gebruiken" + }, + "title": "Opties voor videostreams van Axis-apparaten" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/azure_devops/translations/hu.json b/homeassistant/components/azure_devops/translations/hu.json index 6bd42409877..460b6132048 100644 --- a/homeassistant/components/azure_devops/translations/hu.json +++ b/homeassistant/components/azure_devops/translations/hu.json @@ -1,11 +1,19 @@ { "config": { "abort": { - "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, "step": { "reauth": { "description": "A(z) {project_url} hiteles\u00edt\u00e9se nem siker\u00fclt. K\u00e9rj\u00fck, adja meg jelenlegi hiteles\u00edt\u0151 adatait." + }, + "user": { + "title": "Azure DevOps Project hozz\u00e1ad\u00e1sa" } } } diff --git a/homeassistant/components/azure_devops/translations/id.json b/homeassistant/components/azure_devops/translations/id.json new file mode 100644 index 00000000000..42292805b08 --- /dev/null +++ b/homeassistant/components/azure_devops/translations/id.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "project_error": "Tidak bisa mendapatkan info proyek." + }, + "flow_title": "Azure DevOps: {project_url}", + "step": { + "reauth": { + "data": { + "personal_access_token": "Token Akses Pribadi (PAT)" + }, + "description": "Autentikasi gagal untuk {project_url} . Masukkan kredensial Anda saat ini.", + "title": "Autentikasi ulang" + }, + "user": { + "data": { + "organization": "Organisasi", + "personal_access_token": "Token Akses Pribadi (PAT)", + "project": "Proyek" + }, + "description": "Siapkan instans Azure DevOps untuk mengakses proyek Anda. Token Akses Pribadi hanya diperlukan untuk proyek pribadi.", + "title": "Tambahkan Proyek Azure DevOps" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/azure_devops/translations/ko.json b/homeassistant/components/azure_devops/translations/ko.json index 555c548a142..4a8807e5f67 100644 --- a/homeassistant/components/azure_devops/translations/ko.json +++ b/homeassistant/components/azure_devops/translations/ko.json @@ -6,7 +6,27 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "project_error": "\ud504\ub85c\uc81d\ud2b8 \uc815\ubcf4\ub97c \uac00\uc838\uc62c \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." + }, + "flow_title": "Azure DevOps: {project_url}", + "step": { + "reauth": { + "data": { + "personal_access_token": "\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070 (PAT)" + }, + "description": "{project_url} \uc5d0 \ub300\ud55c \uc778\uc99d\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. \ud604\uc7ac \uc790\uaca9 \uc99d\uba85\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "\uc7ac\uc778\uc99d" + }, + "user": { + "data": { + "organization": "\uc870\uc9c1", + "personal_access_token": "\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070 (PAT)", + "project": "\ud504\ub85c\uc81d\ud2b8" + }, + "description": "\ud504\ub85c\uc81d\ud2b8\uc5d0 \uc561\uc138\uc2a4\ud560 Azure DevOps \uc778\uc2a4\ud134\uc2a4\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694. \uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070\uc740 \uac1c\uc778 \ud504\ub85c\uc81d\ud2b8\uc5d0\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4.", + "title": "Azure DevOps \ud504\ub85c\uc81d\ud2b8 \ucd94\uac00\ud558\uae30" + } } } } \ No newline at end of file diff --git a/homeassistant/components/azure_devops/translations/nl.json b/homeassistant/components/azure_devops/translations/nl.json index 07dc59e1a56..adf2a84e4e9 100644 --- a/homeassistant/components/azure_devops/translations/nl.json +++ b/homeassistant/components/azure_devops/translations/nl.json @@ -10,11 +10,18 @@ "project_error": "Kon geen projectinformatie ophalen." }, "step": { + "reauth": { + "data": { + "personal_access_token": "Persoonlijk toegangstoken (PAT)" + } + }, "user": { "data": { "organization": "Organisatie", + "personal_access_token": "Persoonlijk toegangstoken (PAT)", "project": "Project" - } + }, + "title": "Azure DevOps-project toevoegen" } } } diff --git a/homeassistant/components/binary_sensor/translations/id.json b/homeassistant/components/binary_sensor/translations/id.json index 4ca757da6e5..ac880aa28fa 100644 --- a/homeassistant/components/binary_sensor/translations/id.json +++ b/homeassistant/components/binary_sensor/translations/id.json @@ -1,13 +1,107 @@ { + "device_automation": { + "condition_type": { + "is_bat_low": "Baterai {entity_name} hampir habis", + "is_cold": "{entity_name} dingin", + "is_connected": "{entity_name} terhubung", + "is_gas": "{entity_name} mendeteksi gas", + "is_hot": "{entity_name} panas", + "is_light": "{entity_name} mendeteksi cahaya", + "is_locked": "{entity_name} terkunci", + "is_moist": "{entity_name} lembab", + "is_motion": "{entity_name} mendeteksi gerakan", + "is_moving": "{entity_name} bergerak", + "is_no_gas": "{entity_name} tidak mendeteksi gas", + "is_no_light": "{entity_name} tidak mendeteksi cahaya", + "is_no_motion": "{entity_name} tidak mendeteksi gerakan", + "is_no_problem": "{entity_name} tidak mendeteksi masalah", + "is_no_smoke": "{entity_name} tidak mendeteksi asap", + "is_no_sound": "{entity_name} tidak mendeteksi suara", + "is_no_vibration": "{entity_name} tidak mendeteksi getaran", + "is_not_bat_low": "Baterai {entity_name} normal", + "is_not_cold": "{entity_name} tidak dingin", + "is_not_connected": "{entity_name} terputus", + "is_not_hot": "{entity_name} tidak panas", + "is_not_locked": "{entity_name} tidak terkunci", + "is_not_moist": "{entity_name} kering", + "is_not_moving": "{entity_name} tidak bergerak", + "is_not_occupied": "{entity_name} tidak ditempati", + "is_not_open": "{entity_name} tertutup", + "is_not_plugged_in": "{entity_name} dicabut", + "is_not_powered": "{entity_name} tidak ditenagai", + "is_not_present": "{entity_name} tidak ada", + "is_not_unsafe": "{entity_name} aman", + "is_occupied": "{entity_name} ditempati", + "is_off": "{entity_name} mati", + "is_on": "{entity_name} nyala", + "is_open": "{entity_name} terbuka", + "is_plugged_in": "{entity_name} dicolokkan", + "is_powered": "{entity_name} ditenagai", + "is_present": "{entity_name} ada", + "is_problem": "{entity_name} mendeteksi masalah", + "is_smoke": "{entity_name} mendeteksi asap", + "is_sound": "{entity_name} mendeteksi suara", + "is_unsafe": "{entity_name} tidak aman", + "is_vibration": "{entity_name} mendeteksi getaran" + }, + "trigger_type": { + "bat_low": "Baterai {entity_name} hampir habis", + "cold": "{entity_name} menjadi dingin", + "connected": "{entity_name} terhubung", + "gas": "{entity_name} mulai mendeteksi gas", + "hot": "{entity_name} menjadi panas", + "light": "{entity_name} mulai mendeteksi cahaya", + "locked": "{entity_name} terkunci", + "moist": "{entity_name} menjadi lembab", + "motion": "{entity_name} mulai mendeteksi gerakan", + "moving": "{entity_name} mulai bergerak", + "no_gas": "{entity_name} berhenti mendeteksi gas", + "no_light": "{entity_name} berhenti mendeteksi cahaya", + "no_motion": "{entity_name} berhenti mendeteksi gerakan", + "no_problem": "{entity_name} berhenti mendeteksi masalah", + "no_smoke": "{entity_name} berhenti mendeteksi asap", + "no_sound": "{entity_name} berhenti mendeteksi suara", + "no_vibration": "{entity_name} berhenti mendeteksi getaran", + "not_bat_low": "Baterai {entity_name} normal", + "not_cold": "{entity_name} menjadi tidak dingin", + "not_connected": "{entity_name} terputus", + "not_hot": "{entity_name} menjadi tidak panas", + "not_locked": "{entity_name} tidak terkunci", + "not_moist": "{entity_name} menjadi kering", + "not_moving": "{entity_name} berhenti bergerak", + "not_occupied": "{entity_name} menjadi tidak ditempati", + "not_opened": "{entity_name} tertutup", + "not_plugged_in": "{entity_name} dicabut", + "not_powered": "{entity_name} tidak ditenagai", + "not_present": "{entity_name} tidak ada", + "not_unsafe": "{entity_name} menjadi aman", + "occupied": "{entity_name} menjadi ditempati", + "opened": "{entity_name} terbuka", + "plugged_in": "{entity_name} dicolokkan", + "powered": "{entity_name} ditenagai", + "present": "{entity_name} ada", + "problem": "{entity_name} mulai mendeteksi masalah", + "smoke": "{entity_name} mulai mendeteksi asap", + "sound": "{entity_name} mulai mendeteksi suara", + "turned_off": "{entity_name} dimatikan", + "turned_on": "{entity_name} dinyalakan", + "unsafe": "{entity_name} menjadi tidak aman", + "vibration": "{entity_name} mulai mendeteksi getaran" + } + }, "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" }, "battery": { "off": "Normal", "on": "Rendah" }, + "battery_charging": { + "off": "Tidak mengisi daya", + "on": "Mengisi daya" + }, "cold": { "off": "Normal", "on": "Dingin" @@ -25,13 +119,17 @@ "on": "Terbuka" }, "gas": { - "off": "Kosong", + "off": "Tidak ada", "on": "Terdeteksi" }, "heat": { "off": "Normal", "on": "Panas" }, + "light": { + "off": "Tidak ada cahaya", + "on": "Cahaya terdeteksi" + }, "lock": { "off": "Terkunci", "on": "Terbuka" @@ -44,6 +142,10 @@ "off": "Tidak ada", "on": "Terdeteksi" }, + "moving": { + "off": "Tidak bergerak", + "on": "Bergerak" + }, "occupancy": { "off": "Tidak ada", "on": "Terdeteksi" @@ -52,13 +154,17 @@ "off": "Tertutup", "on": "Terbuka" }, + "plug": { + "off": "Dicabut", + "on": "Dicolokkan" + }, "presence": { "off": "Keluar", - "on": "Rumah" + "on": "Di Rumah" }, "problem": { "off": "Oke", - "on": "Masalah" + "on": "Bermasalah" }, "safety": { "off": "Aman", diff --git a/homeassistant/components/binary_sensor/translations/ko.json b/homeassistant/components/binary_sensor/translations/ko.json index 0b8ef0b73d5..7a725fc6719 100644 --- a/homeassistant/components/binary_sensor/translations/ko.json +++ b/homeassistant/components/binary_sensor/translations/ko.json @@ -1,92 +1,92 @@ { "device_automation": { "condition_type": { - "is_bat_low": "{entity_name} \uc758 \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 \ubd80\uc871\ud558\uba74", - "is_cold": "{entity_name} \uc628\ub3c4\uac00 \ub0ae\uc73c\uba74", - "is_connected": "{entity_name} \uc774(\uac00) \uc5f0\uacb0\ub418\uc5b4 \uc788\uc73c\uba74", - "is_gas": "{entity_name} \uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud558\uba74", - "is_hot": "{entity_name} \uc628\ub3c4\uac00 \ub192\uc73c\uba74", - "is_light": "{entity_name} \uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud558\uba74", - "is_locked": "{entity_name} \uc774(\uac00) \uc7a0\uaca8\uc788\uc73c\uba74", - "is_moist": "{entity_name} \uc774(\uac00) \uc2b5\ud558\uba74", - "is_motion": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud558\uba74", - "is_moving": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc774\uba74", - "is_no_gas": "{entity_name} \uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", - "is_no_light": "{entity_name} \uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", - "is_no_motion": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", - "is_no_problem": "{entity_name} \uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", - "is_no_smoke": "{entity_name} \uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", - "is_no_sound": "{entity_name} \uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", - "is_no_vibration": "{entity_name} \uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", - "is_not_bat_low": "{entity_name} \uc758 \ubc30\ud130\ub9ac\uac00 \uc815\uc0c1\uc774\uba74", - "is_not_cold": "{entity_name} \uc628\ub3c4\uac00 \ub0ae\uc9c0 \uc54a\uc73c\uba74", - "is_not_connected": "{entity_name} \uc758 \uc5f0\uacb0\uc774 \ub04a\uc5b4\uc838 \uc788\ub2e4\uba74", - "is_not_hot": "{entity_name} \uc628\ub3c4\uac00 \ub192\uc9c0 \uc54a\uc73c\uba74", - "is_not_locked": "{entity_name} \uc774(\uac00) \uc7a0\uaca8\uc788\uc9c0 \uc54a\uc73c\uba74", - "is_not_moist": "{entity_name} \uc774(\uac00) \uac74\uc870\ud558\uba74", - "is_not_moving": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc774\uc9c0 \uc54a\uc73c\uba74", - "is_not_occupied": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uac00 \uc544\ub2c8\uba74", - "is_not_open": "{entity_name} \uc774(\uac00) \ub2eb\ud600 \uc788\uc73c\uba74", - "is_not_plugged_in": "{entity_name} \ud50c\ub7ec\uadf8\uac00 \ubf51\ud600 \uc788\uc73c\uba74", - "is_not_powered": "{entity_name} \uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uc9c0 \uc54a\uc73c\uba74", - "is_not_present": "{entity_name} \uc774(\uac00) \uc678\ucd9c \uc911\uc774\uba74", - "is_not_unsafe": "{entity_name} \uc774(\uac00) \uc548\uc804\ud558\uba74", - "is_occupied": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uc774\uba74", - "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", - "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74", - "is_open": "{entity_name} \uc774(\uac00) \uc5f4\ub824 \uc788\uc73c\uba74", - "is_plugged_in": "{entity_name} \ud50c\ub7ec\uadf8\uac00 \uaf3d\ud600 \uc788\uc73c\uba74", - "is_powered": "{entity_name} \uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uace0 \uc788\uc73c\uba74", - "is_present": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uc911\uc774\uba74", - "is_problem": "{entity_name} \uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud558\uba74", - "is_smoke": "{entity_name} \uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud558\uba74", - "is_sound": "{entity_name} \uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud558\uba74", - "is_unsafe": "{entity_name} \uc740(\ub294) \uc548\uc804\ud558\uc9c0 \uc54a\uc73c\uba74", - "is_vibration": "{entity_name} \uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uba74" + "is_bat_low": "{entity_name}\uc758 \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 \ubd80\uc871\ud558\uba74", + "is_cold": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ub0ae\uc73c\uba74", + "is_connected": "{entity_name}\uc774(\uac00) \uc5f0\uacb0\ub418\uc5b4 \uc788\uc73c\uba74", + "is_gas": "{entity_name}\uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud558\uace0 \uc788\uc73c\uba74", + "is_hot": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ub192\uc73c\uba74", + "is_light": "{entity_name}\uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud558\uace0 \uc788\uc73c\uba74", + "is_locked": "{entity_name}\uc774(\uac00) \uc7a0\uaca8\uc788\uc73c\uba74", + "is_moist": "{entity_name}\uc774(\uac00) \uc2b5\ud558\uba74", + "is_motion": "{entity_name}\uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud558\uace0 \uc788\uc73c\uba74", + "is_moving": "{entity_name}\uc774(\uac00) \uc6c0\uc9c1\uc774\uace0 \uc788\uc73c\uba74", + "is_no_gas": "{entity_name}\uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud558\uace0 \uc788\uc9c0 \uc54a\uc73c\uba74", + "is_no_light": "{entity_name}\uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud558\uace0 \uc788\uc9c0 \uc54a\uc73c\uba74", + "is_no_motion": "{entity_name}\uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud558\uace0 \uc788\uc9c0 \uc54a\uc73c\uba74", + "is_no_problem": "{entity_name}\uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud558\uace0 \uc788\uc9c0 \uc54a\uc73c\uba74", + "is_no_smoke": "{entity_name}\uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud558\uace0 \uc788\uc9c0 \uc54a\uc73c\uba74", + "is_no_sound": "{entity_name}\uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud558\uace0 \uc788\uc9c0 \uc54a\uc73c\uba74", + "is_no_vibration": "{entity_name}\uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uace0 \uc788\uc9c0 \uc54a\uc73c\uba74", + "is_not_bat_low": "{entity_name}\uc758 \ubc30\ud130\ub9ac\uac00 \uc815\uc0c1\uc774\uba74", + "is_not_cold": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ub0ae\uc9c0 \uc54a\uc73c\uba74", + "is_not_connected": "{entity_name}\uc758 \uc5f0\uacb0\uc774 \ub04a\uc5b4\uc838 \uc788\uc73c\uba74", + "is_not_hot": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ub192\uc9c0 \uc54a\uc73c\uba74", + "is_not_locked": "{entity_name}\uc774(\uac00) \uc7a0\uaca8\uc788\uc9c0 \uc54a\uc73c\uba74", + "is_not_moist": "{entity_name}\uc774(\uac00) \uac74\uc870\ud558\uba74", + "is_not_moving": "{entity_name}\uc774(\uac00) \uc6c0\uc9c1\uc774\uace0 \uc788\uc9c0 \uc54a\uc73c\uba74", + "is_not_occupied": "{entity_name}\uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uac00 \uc544\ub2c8\uba74", + "is_not_open": "{entity_name}\uc774(\uac00) \ub2eb\ud600 \uc788\uc73c\uba74", + "is_not_plugged_in": "{entity_name}\uc758 \ud50c\ub7ec\uadf8\uac00 \ubf51\ud600 \uc788\uc73c\uba74", + "is_not_powered": "{entity_name}\uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uace0 \uc788\uc9c0 \uc54a\uc73c\uba74", + "is_not_present": "{entity_name}\uc774(\uac00) \uc678\ucd9c \uc911\uc774\uba74", + "is_not_unsafe": "{entity_name}\uc774(\uac00) \uc548\uc804\ud558\uba74", + "is_occupied": "{entity_name}\uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uc774\uba74", + "is_off": "{entity_name}\uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name}\uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74", + "is_open": "{entity_name}\uc774(\uac00) \uc5f4\ub824 \uc788\uc73c\uba74", + "is_plugged_in": "{entity_name}\uc758 \ud50c\ub7ec\uadf8\uac00 \uaf3d\ud600 \uc788\uc73c\uba74", + "is_powered": "{entity_name}\uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uace0 \uc788\uc73c\uba74", + "is_present": "{entity_name}\uc774(\uac00) \uc7ac\uc2e4 \uc911\uc774\uba74", + "is_problem": "{entity_name}\uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud558\uace0 \uc788\uc73c\uba74", + "is_smoke": "{entity_name}\uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud558\uace0 \uc788\uc73c\uba74", + "is_sound": "{entity_name}\uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud558\uace0 \uc788\uc73c\uba74", + "is_unsafe": "{entity_name}\uc774(\uac00) \uc548\uc804\ud558\uc9c0 \uc54a\uc73c\uba74", + "is_vibration": "{entity_name}\uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uace0 \uc788\uc73c\uba74" }, "trigger_type": { - "bat_low": "{entity_name} \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 \ubd80\uc871\ud574\uc9c8 \ub54c", - "cold": "{entity_name} \uc628\ub3c4\uac00 \ub0ae\uc544\uc84c\uc744 \ub54c", - "connected": "{entity_name} \uc774(\uac00) \uc5f0\uacb0\ub420 \ub54c", - "gas": "{entity_name} \uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud560 \ub54c", - "hot": "{entity_name} \uc628\ub3c4\uac00 \ub192\uc544\uc84c\uc744 \ub54c", - "light": "{entity_name} \uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud560 \ub54c", - "locked": "{entity_name} \uc774(\uac00) \uc7a0\uae38 \ub54c", - "moist": "{entity_name} \uc774(\uac00) \uc2b5\ud574\uc9c8 \ub54c", - "motion": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud560 \ub54c", - "moving": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc77c \ub54c", - "no_gas": "{entity_name} \uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", - "no_light": "{entity_name} \uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", - "no_motion": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", - "no_problem": "{entity_name} \uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", - "no_smoke": "{entity_name} \uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", - "no_sound": "{entity_name} \uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", - "no_vibration": "{entity_name} \uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", - "not_bat_low": "{entity_name} \ubc30\ud130\ub9ac\uac00 \uc815\uc0c1\uc774 \ub420 \ub54c", - "not_cold": "{entity_name} \uc628\ub3c4\uac00 \ub0ae\uc9c0 \uc54a\uac8c \ub410\uc744 \ub54c", - "not_connected": "{entity_name} \uc758 \uc5f0\uacb0\uc774 \ub04a\uc5b4\uc9c8 \ub54c", - "not_hot": "{entity_name} \uc628\ub3c4\uac00 \ub192\uc9c0 \uc54a\uac8c \ub410\uc744 \ub54c", - "not_locked": "{entity_name} \uc758 \uc7a0\uae08\uc774 \ud574\uc81c\ub420 \ub54c", - "not_moist": "{entity_name} \uc774(\uac00) \uac74\uc870\ud574\uc9c8 \ub54c", - "not_moving": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc774\uc9c0 \uc54a\uc744 \ub54c", - "not_occupied": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uac00 \uc544\ub2c8\uac8c \ub420 \ub54c", - "not_opened": "{entity_name} \uc774(\uac00) \ub2eb\ud790 \ub54c", - "not_plugged_in": "{entity_name} \ud50c\ub7ec\uadf8\uac00 \ubf51\ud790 \ub54c", - "not_powered": "{entity_name} \uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uc9c0 \uc54a\uc744 \ub54c", - "not_present": "{entity_name} \uc774(\uac00) \uc678\ucd9c \uc0c1\ud0dc\uac00 \ub420 \ub54c", - "not_unsafe": "{entity_name} \uc740(\ub294) \uc548\uc804\ud574\uc9c8 \ub54c", - "occupied": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uac00 \ub420 \ub54c", - "opened": "{entity_name} \uc774(\uac00) \uc5f4\ub9b4 \ub54c", - "plugged_in": "{entity_name} \ud50c\ub7ec\uadf8\uac00 \uaf3d\ud790 \ub54c", - "powered": "{entity_name} \uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub420 \ub54c", - "present": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uac00 \ub420 \ub54c", - "problem": "{entity_name} \uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud560 \ub54c", - "smoke": "{entity_name} \uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud560 \ub54c", - "sound": "{entity_name} \uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud560 \ub54c", - "turned_off": "{entity_name} \uc774(\uac00) \uaebc\uc9c8 \ub54c", - "turned_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c8 \ub54c", - "unsafe": "{entity_name} \uc774(\uac00) \uc548\uc804\ud558\uc9c0 \uc54a\uc744 \ub54c", - "vibration": "{entity_name} \uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud560 \ub54c" + "bat_low": "{entity_name}\uc758 \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 \ubd80\uc871\ud574\uc84c\uc744 \ub54c", + "cold": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ub0ae\uc544\uc84c\uc744 \ub54c", + "connected": "{entity_name}\uc774(\uac00) \uc5f0\uacb0\ub418\uc5c8\uc744 \ub54c", + "gas": "{entity_name}\uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud558\uae30 \uc2dc\uc791\ud588\uc744 \ub54c", + "hot": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ub192\uc544\uc84c\uc744 \ub54c", + "light": "{entity_name}\uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud558\uae30 \uc2dc\uc791\ud588\uc744 \ub54c", + "locked": "{entity_name}\uc774(\uac00) \uc7a0\uacbc\uc744 \ub54c", + "moist": "{entity_name}\uc774(\uac00) \uc2b5\ud574\uc84c\uc744 \ub54c", + "motion": "{entity_name}\uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud558\uae30 \uc2dc\uc791\ud588\uc744 \ub54c", + "moving": "{entity_name}\uc774(\uac00) \uc6c0\uc9c1\uc774\uae30 \uc2dc\uc791\ud588\uc744 \ub54c", + "no_gas": "{entity_name}\uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "no_light": "{entity_name}\uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "no_motion": "{entity_name}\uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "no_problem": "{entity_name}\uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "no_smoke": "{entity_name}\uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "no_sound": "{entity_name}\uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "no_vibration": "{entity_name}\uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "not_bat_low": "{entity_name}\uc758 \ubc30\ud130\ub9ac\uac00 \uc815\uc0c1\uc774 \ub418\uc5c8\uc744 \ub54c", + "not_cold": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ub0ae\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "not_connected": "{entity_name}\uc758 \uc5f0\uacb0\uc774 \ub04a\uc5b4\uc84c\uc744 \ub54c", + "not_hot": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ub192\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "not_locked": "{entity_name}\uc758 \uc7a0\uae08\uc774 \ud574\uc81c\ub418\uc5c8\uc744 \ub54c", + "not_moist": "{entity_name}\uc774(\uac00) \uac74\uc870\ud574\uc84c\uc744 \ub54c", + "not_moving": "{entity_name}\uc774(\uac00) \uc6c0\uc9c1\uc774\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "not_occupied": "{entity_name}\uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uac00 \uc544\ub2c8\uac8c \ub418\uc5c8\uc744 \ub54c", + "not_opened": "{entity_name}\uc774(\uac00) \ub2eb\ud614\uc744 \ub54c", + "not_plugged_in": "{entity_name}\uc758 \ud50c\ub7ec\uadf8\uac00 \ubf51\ud614\uc744 \ub54c", + "not_powered": "{entity_name}\uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "not_present": "{entity_name}\uc774(\uac00) \uc678\ucd9c \uc0c1\ud0dc\uac00 \ub418\uc5c8\uc744 \ub54c", + "not_unsafe": "{entity_name}\uc774(\uac00) \uc548\uc804\ud574\uc84c\uc744 \ub54c", + "occupied": "{entity_name}\uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uac00 \ub418\uc5c8\uc744 \ub54c", + "opened": "{entity_name}\uc774(\uac00) \uc5f4\ub838\uc744 \ub54c", + "plugged_in": "{entity_name}\uc758 \ud50c\ub7ec\uadf8\uac00 \uaf3d\ud614\uc744 \ub54c", + "powered": "{entity_name}\uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uc5c8\uc744 \ub54c", + "present": "{entity_name}\uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uac00 \ub418\uc5c8\uc744 \ub54c", + "problem": "{entity_name}\uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud558\uae30 \uc2dc\uc791\ud588\uc744 \ub54c", + "smoke": "{entity_name}\uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud558\uae30 \uc2dc\uc791\ud588\uc744 \ub54c", + "sound": "{entity_name}\uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud558\uae30 \uc2dc\uc791\ud588\uc744 \ub54c", + "turned_off": "{entity_name}\uc774(\uac00) \uaebc\uc84c\uc744 \ub54c", + "turned_on": "{entity_name}\uc774(\uac00) \ucf1c\uc84c\uc744 \ub54c", + "unsafe": "{entity_name}\uc774(\uac00) \uc548\uc804\ud558\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "vibration": "{entity_name}\uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uae30 \uc2dc\uc791\ud588\uc744 \ub54c" } }, "state": { @@ -98,6 +98,10 @@ "off": "\ubcf4\ud1b5", "on": "\ub0ae\uc74c" }, + "battery_charging": { + "off": "\ucda9\uc804 \uc911\uc774 \uc544\ub2d8", + "on": "\ucda9\uc804 \uc911" + }, "cold": { "off": "\ubcf4\ud1b5", "on": "\uc800\uc628" @@ -122,6 +126,10 @@ "off": "\ubcf4\ud1b5", "on": "\uace0\uc628" }, + "light": { + "off": "\ube5b\uc774 \uc5c6\uc2b4", + "on": "\ube5b\uc744 \uac10\uc9c0\ud568" + }, "lock": { "off": "\uc7a0\uae40", "on": "\ud574\uc81c" @@ -134,6 +142,10 @@ "off": "\uc774\uc0c1\uc5c6\uc74c", "on": "\uac10\uc9c0\ub428" }, + "moving": { + "off": "\uc6c0\uc9c1\uc774\uc9c0 \uc54a\uc74c", + "on": "\uc6c0\uc9c1\uc784" + }, "occupancy": { "off": "\uc774\uc0c1\uc5c6\uc74c", "on": "\uac10\uc9c0\ub428" @@ -142,6 +154,10 @@ "off": "\ub2eb\ud798", "on": "\uc5f4\ub9bc" }, + "plug": { + "off": "\ud50c\ub7ec\uadf8\uac00 \ubf51\ud798", + "on": "\ud50c\ub7ec\uadf8\uac00 \uaf3d\ud798" + }, "presence": { "off": "\uc678\ucd9c", "on": "\uc7ac\uc2e4" diff --git a/homeassistant/components/binary_sensor/translations/zh-Hans.json b/homeassistant/components/binary_sensor/translations/zh-Hans.json index a44e16d78e2..82cd0d3ccfe 100644 --- a/homeassistant/components/binary_sensor/translations/zh-Hans.json +++ b/homeassistant/components/binary_sensor/translations/zh-Hans.json @@ -25,13 +25,13 @@ "is_not_locked": "{entity_name} \u5df2\u89e3\u9501", "is_not_moist": "{entity_name} \u5e72\u71e5", "is_not_moving": "{entity_name} \u9759\u6b62", - "is_not_occupied": "{entity_name}\u6ca1\u6709\u4eba", + "is_not_occupied": "{entity_name} \u65e0\u4eba", "is_not_open": "{entity_name} \u5df2\u5173\u95ed", "is_not_plugged_in": "{entity_name} \u672a\u63d2\u5165", "is_not_powered": "{entity_name} \u672a\u901a\u7535", "is_not_present": "{entity_name} \u4e0d\u5728\u5bb6", "is_not_unsafe": "{entity_name} \u5b89\u5168", - "is_occupied": "{entity_name}\u6709\u4eba", + "is_occupied": "{entity_name} \u6709\u4eba", "is_off": "{entity_name} \u5df2\u5173\u95ed", "is_on": "{entity_name} \u5df2\u5f00\u542f", "is_open": "{entity_name} \u5df2\u6253\u5f00", @@ -51,15 +51,42 @@ "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", - "locked": "{entity_name}\u5df2\u4e0a\u9501", + "locked": "{entity_name} \u88ab\u9501\u5b9a", + "moist": "{entity_name} \u53d8\u6e7f", "motion": "{entity_name} \u68c0\u6d4b\u5230\u6709\u4eba", - "moving": "{entity_name}\u5f00\u59cb\u79fb\u52a8", - "no_motion": "{entity_name} \u672a\u68c0\u6d4b\u5230\u6709\u4eba", - "not_bat_low": "{entity_name}\u7535\u91cf\u6b63\u5e38", - "not_locked": "{entity_name}\u5df2\u89e3\u9501", - "not_opened": "{entity_name}\u5df2\u5173\u95ed", + "moving": "{entity_name} \u5f00\u59cb\u79fb\u52a8", + "no_gas": "{entity_name} \u4e0d\u518d\u68c0\u6d4b\u5230\u71c3\u6c14\u6cc4\u6f0f", + "no_light": "{entity_name} \u4e0d\u518d\u68c0\u6d4b\u5230\u5149\u7ebf", + "no_motion": "{entity_name} \u4e0d\u518d\u68c0\u6d4b\u5230\u6709\u4eba", + "no_problem": "{entity_name} \u95ee\u9898\u89e3\u9664", + "no_smoke": "{entity_name} \u4e0d\u518d\u68c0\u6d4b\u5230\u70df\u96fe", + "no_sound": "{entity_name} \u4e0d\u518d\u68c0\u6d4b\u5230\u58f0\u97f3", + "no_vibration": "{entity_name} \u4e0d\u518d\u68c0\u6d4b\u5230\u632f\u52a8", + "not_bat_low": "{entity_name} \u7535\u6c60\u7535\u91cf\u6b63\u5e38", + "not_cold": "{entity_name} \u4e0d\u51b7\u4e86", + "not_connected": "{entity_name} \u65ad\u5f00", + "not_hot": "{entity_name} \u4e0d\u70ed\u4e86", + "not_locked": "{entity_name} \u89e3\u9501", + "not_moist": "{entity_name} \u53d8\u5e72", + "not_moving": "{entity_name} \u505c\u6b62\u79fb\u52a8", + "not_occupied": "{entity_name} \u4e0d\u518d\u6709\u4eba", + "not_opened": "{entity_name} \u5df2\u5173\u95ed", + "not_plugged_in": "{entity_name} \u88ab\u62d4\u51fa", + "not_powered": "{entity_name} \u6389\u7535", + "not_present": "{entity_name} \u4e0d\u5728\u5bb6", + "not_unsafe": "{entity_name} \u5b89\u5168\u4e86", + "occupied": "{entity_name} \u6709\u4eba", + "opened": "{entity_name} \u88ab\u6253\u5f00", + "plugged_in": "{entity_name} \u88ab\u63d2\u5165", + "powered": "{entity_name} \u4e0a\u7535", + "present": "{entity_name} \u5728\u5bb6", + "problem": "{entity_name} \u53d1\u73b0\u95ee\u9898", + "smoke": "{entity_name} \u5f00\u59cb\u68c0\u6d4b\u5230\u70df\u96fe", + "sound": "{entity_name} \u5f00\u59cb\u68c0\u6d4b\u5230\u58f0\u97f3", "turned_off": "{entity_name} \u88ab\u5173\u95ed", - "turned_on": "{entity_name} \u88ab\u6253\u5f00" + "turned_on": "{entity_name} \u88ab\u6253\u5f00", + "unsafe": "{entity_name} \u4e0d\u518d\u5b89\u5168", + "vibration": "{entity_name} \u5f00\u59cb\u68c0\u6d4b\u5230\u632f\u52a8" } }, "state": { @@ -120,8 +147,8 @@ "on": "\u6b63\u5728\u79fb\u52a8" }, "occupancy": { - "off": "\u672a\u89e6\u53d1", - "on": "\u5df2\u89e6\u53d1" + "off": "\u65e0\u4eba", + "on": "\u6709\u4eba" }, "opening": { "off": "\u5173\u95ed", diff --git a/homeassistant/components/blebox/translations/hu.json b/homeassistant/components/blebox/translations/hu.json index 9b0bf1c0ddf..9649d70d976 100644 --- a/homeassistant/components/blebox/translations/hu.json +++ b/homeassistant/components/blebox/translations/hu.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { - "unsupported_version": "A BleBox eszk\u00f6z elavult firmware-vel rendelkezik. El\u0151sz\u00f6r friss\u00edtse." + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt", + "unsupported_version": "A BleBox eszk\u00f6z elavult firmware-rel rendelkezik. El\u0151sz\u00f6r friss\u00edtsd." }, "flow_title": "BleBox eszk\u00f6z: {name} ({host})", "step": { diff --git a/homeassistant/components/blebox/translations/id.json b/homeassistant/components/blebox/translations/id.json new file mode 100644 index 00000000000..2ef604d1bff --- /dev/null +++ b/homeassistant/components/blebox/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "address_already_configured": "Perangkat BleBox sudah dikonfigurasi di {address}.", + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan", + "unsupported_version": "Firmware Perangkat BleBox sudah usang. Tingkatkan terlebih dulu." + }, + "flow_title": "Perangkat BleBox: {name} ({host})", + "step": { + "user": { + "data": { + "host": "Alamat IP", + "port": "Port" + }, + "description": "Siapkan BleBox Anda untuk diintegrasikan dengan Home Assistant.", + "title": "Siapkan perangkat BleBox Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blebox/translations/ko.json b/homeassistant/components/blebox/translations/ko.json index 81c7bf3af48..1032e873ae9 100644 --- a/homeassistant/components/blebox/translations/ko.json +++ b/homeassistant/components/blebox/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "address_already_configured": "BleBox \uae30\uae30\uac00 {address} \ub85c \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "address_already_configured": "BleBox \uae30\uae30\uac00 {address}(\uc73c)\ub85c \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { @@ -16,7 +16,7 @@ "host": "IP \uc8fc\uc18c", "port": "\ud3ec\ud2b8" }, - "description": "Home Assistant \uc5d0 BleBox \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4.", + "description": "Home Assistant\uc5d0 BleBox \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4.", "title": "BleBox \uae30\uae30 \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/blink/translations/en.json b/homeassistant/components/blink/translations/en.json index 9a0a2636d3e..c8c154418df 100644 --- a/homeassistant/components/blink/translations/en.json +++ b/homeassistant/components/blink/translations/en.json @@ -14,7 +14,7 @@ "data": { "2fa": "Two-factor code" }, - "description": "Enter the pin sent to your email", + "description": "Enter the PIN sent to your email", "title": "Two-factor authentication" }, "user": { diff --git a/homeassistant/components/blink/translations/et.json b/homeassistant/components/blink/translations/et.json index 24de0ccbefd..a5cae0eaae2 100644 --- a/homeassistant/components/blink/translations/et.json +++ b/homeassistant/components/blink/translations/et.json @@ -14,7 +14,7 @@ "data": { "2fa": "2FA kood" }, - "description": "Sisesta E-posti aadressile saadetud PIN-kood", + "description": "Sisesta e-posti aadressile saadetud PIN kood", "title": "Kaheastmeline tuvastamine (2FA)" }, "user": { diff --git a/homeassistant/components/blink/translations/fr.json b/homeassistant/components/blink/translations/fr.json index 83aaad902a1..23bb7fb91dd 100644 --- a/homeassistant/components/blink/translations/fr.json +++ b/homeassistant/components/blink/translations/fr.json @@ -14,7 +14,7 @@ "data": { "2fa": "Code \u00e0 deux facteurs" }, - "description": "Entrez le code PIN envoy\u00e9 \u00e0 votre e-mail", + "description": "Entrez le NIP envoy\u00e9 \u00e0 votre e-mail", "title": "Authentification \u00e0 deux facteurs" }, "user": { diff --git a/homeassistant/components/blink/translations/hu.json b/homeassistant/components/blink/translations/hu.json index 1150cda9ea9..e56b142a5b0 100644 --- a/homeassistant/components/blink/translations/hu.json +++ b/homeassistant/components/blink/translations/hu.json @@ -4,10 +4,19 @@ "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_access_token": "\u00c9rv\u00e9nytelen hozz\u00e1f\u00e9r\u00e9si token", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", - "unknown": "V\u00e1ratlan hiba" + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { + "2fa": { + "data": { + "2fa": "K\u00e9tfaktoros k\u00f3d" + }, + "description": "Add meg az e-mail c\u00edmedre k\u00fcld\u00f6tt pint", + "title": "K\u00e9tfaktoros hiteles\u00edt\u00e9s" + }, "user": { "data": { "password": "Jelsz\u00f3", diff --git a/homeassistant/components/blink/translations/id.json b/homeassistant/components/blink/translations/id.json new file mode 100644 index 00000000000..bdbc406bda7 --- /dev/null +++ b/homeassistant/components/blink/translations/id.json @@ -0,0 +1,40 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_access_token": "Token akses tidak valid", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "2fa": { + "data": { + "2fa": "Kode autentikasi dua faktor" + }, + "description": "Masukkan PIN yang dikirimkan ke email Anda", + "title": "Autentikasi dua faktor" + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Masuk dengan akun Blink" + } + } + }, + "options": { + "step": { + "simple_options": { + "data": { + "scan_interval": "Interval Pindai (detik)" + }, + "description": "Konfigurasikan integrasi Blink", + "title": "Opsi Blink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blink/translations/ko.json b/homeassistant/components/blink/translations/ko.json index 35ef0cefdef..6d42cdbb2e9 100644 --- a/homeassistant/components/blink/translations/ko.json +++ b/homeassistant/components/blink/translations/ko.json @@ -14,7 +14,7 @@ "data": { "2fa": "2\ub2e8\uacc4 \uc778\uc99d \ucf54\ub4dc" }, - "description": "\uc774\uba54\uc77c\ub85c \ubcf4\ub0b4\ub4dc\ub9b0 PIN \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "description": "\uc774\uba54\uc77c\ub85c \ubcf4\ub0b4\ub4dc\ub9b0 PIN\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694", "title": "2\ub2e8\uacc4 \uc778\uc99d" }, "user": { diff --git a/homeassistant/components/blink/translations/nl.json b/homeassistant/components/blink/translations/nl.json index f1f1ce7888b..3160ffe8ddd 100644 --- a/homeassistant/components/blink/translations/nl.json +++ b/homeassistant/components/blink/translations/nl.json @@ -29,6 +29,10 @@ "options": { "step": { "simple_options": { + "data": { + "scan_interval": "Scaninterval (seconden)" + }, + "description": "Configureer Blink-integratie", "title": "Blink opties" } } diff --git a/homeassistant/components/blink/translations/no.json b/homeassistant/components/blink/translations/no.json index 0b99005c382..90f8fcaa06b 100644 --- a/homeassistant/components/blink/translations/no.json +++ b/homeassistant/components/blink/translations/no.json @@ -14,7 +14,7 @@ "data": { "2fa": "Totrinnsbekreftelse kode" }, - "description": "Skriv inn pin-koden som ble sendt til din e-posten", + "description": "Skriv inn PIN-koden som er sendt til e-posten din", "title": "Totrinnsbekreftelse" }, "user": { diff --git a/homeassistant/components/blink/translations/pl.json b/homeassistant/components/blink/translations/pl.json index 13fe2f1fc93..72b6c32e5be 100644 --- a/homeassistant/components/blink/translations/pl.json +++ b/homeassistant/components/blink/translations/pl.json @@ -14,7 +14,7 @@ "data": { "2fa": "Kod uwierzytelniania dwusk\u0142adnikowego" }, - "description": "Wpisz kod PIN wys\u0142any na Tw\u00f3j adres e-mail. Je\u015bli.", + "description": "Wprowad\u017a kod PIN wys\u0142any na Tw\u00f3j adres e-mail.", "title": "Uwierzytelnianie dwusk\u0142adnikowe" }, "user": { diff --git a/homeassistant/components/blink/translations/zh-Hant.json b/homeassistant/components/blink/translations/zh-Hant.json index d2c42bf5531..6874efb6e31 100644 --- a/homeassistant/components/blink/translations/zh-Hant.json +++ b/homeassistant/components/blink/translations/zh-Hant.json @@ -12,10 +12,10 @@ "step": { "2fa": { "data": { - "2fa": "\u96d9\u91cd\u9a57\u8b49\u78bc" + "2fa": "\u96d9\u91cd\u8a8d\u8b49\u78bc" }, "description": "\u8f38\u5165\u90f5\u4ef6\u6240\u6536\u5230 PIN \u78bc", - "title": "\u96d9\u91cd\u9a57\u8b49" + "title": "\u96d9\u91cd\u8a8d\u8b49" }, "user": { "data": { diff --git a/homeassistant/components/bmw_connected_drive/translations/bg.json b/homeassistant/components/bmw_connected_drive/translations/bg.json new file mode 100644 index 00000000000..67a484573aa --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/bg.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/hu.json b/homeassistant/components/bmw_connected_drive/translations/hu.json new file mode 100644 index 00000000000..8724f525626 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/hu.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "region": "ConnectedDrive R\u00e9gi\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/id.json b/homeassistant/components/bmw_connected_drive/translations/id.json new file mode 100644 index 00000000000..e49e9202dbe --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/id.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "region": "Wilayah ConnectedDrive", + "username": "Nama Pengguna" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "Hanya baca (hanya sensor dan notifikasi, tidak ada eksekusi layanan, tidak ada fitur penguncian)", + "use_location": "Gunakan lokasi Asisten Rumah untuk polling lokasi mobil (diperlukan untuk kendaraan non i3/i8 yang diproduksi sebelum Juli 2014)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/ko.json b/homeassistant/components/bmw_connected_drive/translations/ko.json index 9cc079cf1cd..4c9872573be 100644 --- a/homeassistant/components/bmw_connected_drive/translations/ko.json +++ b/homeassistant/components/bmw_connected_drive/translations/ko.json @@ -11,9 +11,20 @@ "user": { "data": { "password": "\ube44\ubc00\ubc88\ud638", + "region": "ConnectedDrive \uc9c0\uc5ed", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" } } } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "\uc77d\uae30 \uc804\uc6a9(\uc13c\uc11c \ubc0f \uc54c\ub9bc\ub9cc \uac00\ub2a5, \uc11c\ube44\uc2a4 \uc2e4\ud589 \ubc0f \uc7a0\uae08 \uc5c6\uc74c)", + "use_location": "\ucc28\ub7c9 \uc704\uce58 \ud3f4\ub9c1\uc5d0 Home Assistant \uc704\uce58\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4(2014\ub144 7\uc6d4 \uc774\uc804\uc5d0 \uc0dd\uc0b0\ub41c i3/i8\uc774 \uc544\ub2cc \ucc28\ub7c9\uc758 \uacbd\uc6b0 \ud544\uc694)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/bond/translations/hu.json b/homeassistant/components/bond/translations/hu.json index 3b2d79a34a7..868ef455f5d 100644 --- a/homeassistant/components/bond/translations/hu.json +++ b/homeassistant/components/bond/translations/hu.json @@ -2,6 +2,27 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "old_firmware": "Nem t\u00e1mogatott r\u00e9gi firmware a Bond eszk\u00f6z\u00f6n - k\u00e9rj\u00fck friss\u00edtsd, miel\u0151tt folytatn\u00e1d", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "Bond: {name} ({host})", + "step": { + "confirm": { + "data": { + "access_token": "Hozz\u00e1f\u00e9r\u00e9si token" + }, + "description": "Szeretn\u00e9d be\u00e1ll\u00edtani a(z) {name}-t?" + }, + "user": { + "data": { + "access_token": "Hozz\u00e1f\u00e9r\u00e9si token", + "host": "Hoszt" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/bond/translations/id.json b/homeassistant/components/bond/translations/id.json new file mode 100644 index 00000000000..56c633cf31c --- /dev/null +++ b/homeassistant/components/bond/translations/id.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "old_firmware": "Firmware lama yang tidak didukung pada perangkat Bond - tingkatkan versi sebelum melanjutkan", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Bond: {name} ({host})", + "step": { + "confirm": { + "data": { + "access_token": "Token Akses" + }, + "description": "Ingin menyiapkan {name}?" + }, + "user": { + "data": { + "access_token": "Token Akses", + "host": "Host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bond/translations/ko.json b/homeassistant/components/bond/translations/ko.json index b44db53f7c8..33a56559f79 100644 --- a/homeassistant/components/bond/translations/ko.json +++ b/homeassistant/components/bond/translations/ko.json @@ -6,15 +6,16 @@ "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "old_firmware": "Bond \uae30\uae30\uc5d0\uc11c \uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \uc624\ub798\ub41c \ud38c\uc6e8\uc5b4\uc785\ub2c8\ub2e4. \uacc4\uc18d\ud558\uae30 \uc804\uc5d0 \uc5c5\uadf8\ub808\uc774\ub4dc\ud574\uc8fc\uc138\uc694.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, - "flow_title": "\ubcf8\ub4dc : {bond_id} ( {host} )", + "flow_title": "Bond: {name} ({host})", "step": { "confirm": { "data": { "access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070" }, - "description": "{bond_id} \ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "{name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/nl.json b/homeassistant/components/bond/translations/nl.json index a76c7a69d7f..a9086df3559 100644 --- a/homeassistant/components/bond/translations/nl.json +++ b/homeassistant/components/bond/translations/nl.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Kon niet verbinden", "invalid_auth": "Ongeldige authenticatie", + "old_firmware": "Niet-ondersteunde oude firmware op het Bond-apparaat - voer een upgrade uit voordat u doorgaat", "unknown": "Onverwachte fout" }, "flow_title": "Bond: {bond_id} ({host})", @@ -13,7 +14,8 @@ "confirm": { "data": { "access_token": "Toegangstoken" - } + }, + "description": "Wilt u {name} instellen?" }, "user": { "data": { diff --git a/homeassistant/components/braviatv/translations/hu.json b/homeassistant/components/braviatv/translations/hu.json index a87786df1e8..fbb23fdee04 100644 --- a/homeassistant/components/braviatv/translations/hu.json +++ b/homeassistant/components/braviatv/translations/hu.json @@ -1,15 +1,36 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "no_ip_control": "Az IP-vez\u00e9rl\u00e9s le van tiltva a TV-n, vagy a TV nem t\u00e1mogatja." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_host": "\u00c9rv\u00e9nytelen hosztn\u00e9v vagy IP-c\u00edm", + "unsupported_model": "A TV modell nem t\u00e1mogatott." + }, "step": { "authorize": { "data": { - "pin": "PIN k\u00f3d" - } + "pin": "PIN-k\u00f3d" + }, + "title": "Sony Bravia TV enged\u00e9lyez\u00e9se" }, "user": { "data": { "host": "Hoszt" - } + }, + "title": "Sony Bravia TV" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "ignored_sources": "Figyelmen k\u00edv\u00fcl hagyott forr\u00e1sok list\u00e1ja" + }, + "title": "A Sony Bravia TV be\u00e1ll\u00edt\u00e1sai" } } } diff --git a/homeassistant/components/braviatv/translations/id.json b/homeassistant/components/braviatv/translations/id.json new file mode 100644 index 00000000000..def84dacdbb --- /dev/null +++ b/homeassistant/components/braviatv/translations/id.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "no_ip_control": "Kontrol IP dinonaktifkan di TV Anda atau TV tidak didukung." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_host": "Nama host atau alamat IP tidak valid", + "unsupported_model": "Model TV Anda tidak didukung." + }, + "step": { + "authorize": { + "data": { + "pin": "Kode PIN" + }, + "description": "Masukkan kode PIN yang ditampilkan di TV Sony Bravia. \n\nJika kode PIN tidak ditampilkan, Anda harus membatalkan pendaftaran Home Assistant di TV, buka: Pengaturan -> Jaringan -> Pengaturan perangkat jarak jauh -> Batalkan pendaftaran perangkat jarak jauh.", + "title": "Otorisasi TV Sony Bravia" + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Siapkan integrasi TV Sony Bravia. Jika Anda memiliki masalah dengan konfigurasi, buka: https://www.home-assistant.io/integrations/braviatv \n\nPastikan TV Anda dinyalakan.", + "title": "TV Sony Bravia" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "ignored_sources": "Daftar sumber yang diabaikan" + }, + "title": "Pilihan untuk TV Sony Bravia" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/braviatv/translations/ko.json b/homeassistant/components/braviatv/translations/ko.json index 0bfb6b3f1b2..c20c6b2eb7b 100644 --- a/homeassistant/components/braviatv/translations/ko.json +++ b/homeassistant/components/braviatv/translations/ko.json @@ -6,7 +6,7 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unsupported_model": "\uc774 TV \ubaa8\ub378\uc740 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "step": { @@ -14,7 +14,7 @@ "data": { "pin": "PIN \ucf54\ub4dc" }, - "description": "Sony Bravia TV \uc5d0 \ud45c\uc2dc\ub41c PIN \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\nPIN \ucf54\ub4dc\uac00 \ud45c\uc2dc\ub418\uc9c0 \uc54a\uc73c\uba74 TV \uc5d0\uc11c Home Assistant \ub97c \ub4f1\ub85d \ud574\uc81c\ud558\uc5ec\uc57c \ud569\ub2c8\ub2e4. Settings -> Network -> Remote device settings -> Unregister remote device \ub85c \uc774\ub3d9\ud558\uc5ec \ub4f1\ub85d\uc744 \ud574\uc81c\ud574\uc8fc\uc138\uc694.", + "description": "Sony Bravia TV\uc5d0 \ud45c\uc2dc\ub41c PIN \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\nPIN \ucf54\ub4dc\uac00 \ud45c\uc2dc\ub418\uc9c0 \uc54a\uc73c\uba74 TV\uc5d0\uc11c Home Assistant\ub97c \ub4f1\ub85d \ud574\uc81c\ud558\uc5ec\uc57c \ud569\ub2c8\ub2e4. Settings -> Network -> Remote device settings -> Unregister remote device\ub85c \uc774\ub3d9\ud558\uc5ec \ub4f1\ub85d\uc744 \ud574\uc81c\ud574\uc8fc\uc138\uc694.", "title": "Sony Bravia TV \uc2b9\uc778\ud558\uae30" }, "user": { diff --git a/homeassistant/components/braviatv/translations/nl.json b/homeassistant/components/braviatv/translations/nl.json index b35d7de45cf..5354f5761ec 100644 --- a/homeassistant/components/braviatv/translations/nl.json +++ b/homeassistant/components/braviatv/translations/nl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Deze tv is al geconfigureerd.", + "already_configured": "Apparaat is al geconfigureerd", "no_ip_control": "IP-besturing is uitgeschakeld op uw tv of de tv wordt niet ondersteund." }, "error": { - "cannot_connect": "Geen verbinding, ongeldige host of PIN-code.", - "invalid_host": "Ongeldige hostnaam of IP-adres.", + "cannot_connect": "Kan geen verbinding maken", + "invalid_host": "Ongeldige hostnaam of IP-adres", "unsupported_model": "Uw tv-model wordt niet ondersteund." }, "step": { @@ -19,7 +19,7 @@ }, "user": { "data": { - "host": "Hostnaam of IP-adres van tv" + "host": "Host" }, "description": "Stel Sony Bravia TV-integratie in. Als je problemen hebt met de configuratie ga dan naar: https://www.home-assistant.io/integrations/braviatv \n\nZorg ervoor dat uw tv is ingeschakeld.", "title": "Sony Bravia TV" diff --git a/homeassistant/components/broadlink/translations/hu.json b/homeassistant/components/broadlink/translations/hu.json index 3b2d79a34a7..90213e99aec 100644 --- a/homeassistant/components/broadlink/translations/hu.json +++ b/homeassistant/components/broadlink/translations/hu.json @@ -1,7 +1,46 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_host": "\u00c9rv\u00e9nytelen hosztn\u00e9v vagy IP-c\u00edm", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_host": "\u00c9rv\u00e9nytelen hosztn\u00e9v vagy IP-c\u00edm", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "{name} ({model} a {host} c\u00edmen)", + "step": { + "auth": { + "title": "Hiteles\u00edt\u00e9s az eszk\u00f6z\u00f6n" + }, + "finish": { + "data": { + "name": "N\u00e9v" + }, + "title": "V\u00e1lassz egy nevet az eszk\u00f6znek" + }, + "reset": { + "description": "{name} ({model} a {host} c\u00edmen) z\u00e1rolva van. A hiteles\u00edt\u00e9shez \u00e9s a konfigur\u00e1ci\u00f3 befejez\u00e9s\u00e9hez fel kell oldani az eszk\u00f6z z\u00e1rol\u00e1s\u00e1t. Utas\u00edt\u00e1sok:\n 1. Nyisd meg a Broadlink alkalmaz\u00e1st.\n 2. Kattints az eszk\u00f6zre.\n 3. Kattints a jobb fels\u0151 sarokban tal\u00e1lhat\u00f3 `...` gombra.\n 4. G\u00f6rgess az oldal alj\u00e1ra.\n 5. Kapcsold ki a z\u00e1rol\u00e1s\u00e1t.", + "title": "Az eszk\u00f6z felold\u00e1sa" + }, + "unlock": { + "data": { + "unlock": "Igen, csin\u00e1ld." + }, + "description": "{name} ({model} a {host} c\u00edmen) z\u00e1rolva van. Ez hiteles\u00edt\u00e9si probl\u00e9m\u00e1khoz vezethet a Home Assistantban. Szeretn\u00e9d feloldani?", + "title": "Az eszk\u00f6z felold\u00e1sa (opcion\u00e1lis)" + }, + "user": { + "data": { + "host": "Hoszt", + "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s" + }, + "title": "Csatlakoz\u00e1s az eszk\u00f6zh\u00f6z" + } } } } \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/id.json b/homeassistant/components/broadlink/translations/id.json new file mode 100644 index 00000000000..89d9b17a800 --- /dev/null +++ b/homeassistant/components/broadlink/translations/id.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "cannot_connect": "Gagal terhubung", + "invalid_host": "Nama host atau alamat IP tidak valid", + "not_supported": "Perangkat tidak didukung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_host": "Nama host atau alamat IP tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "{name} ({model} di {host})", + "step": { + "auth": { + "title": "Autentikasi ke perangkat" + }, + "finish": { + "data": { + "name": "Nama" + }, + "title": "Pilih nama untuk perangkat" + }, + "reset": { + "description": "{name} ({model} di {host}) dikunci. Anda perlu membuka kunci perangkat untuk mengautentikasi dan menyelesaikan konfigurasi. Ikuti petunjuk berikut:\n1. Buka aplikasi Broadlink.\n2. Klik pada perangkat.\n3. Klik `\u2026` di pojok kanan atas.\n4. Gulir ke bagian bawah halaman.\n5. Nonaktifkan kunci.", + "title": "Buka kunci perangkat" + }, + "unlock": { + "data": { + "unlock": "Ya, lakukan." + }, + "description": "{name} ({model} di {host}) dikunci. Hal ini dapat menyebabkan masalah autentikasi di Home Assistant. Apakah Anda ingin membukanya?", + "title": "Buka kunci perangkat (opsional)" + }, + "user": { + "data": { + "host": "Host", + "timeout": "Tenggang waktu" + }, + "title": "Hubungkan ke perangkat" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/ko.json b/homeassistant/components/broadlink/translations/ko.json index 13cd17a8475..ac3ada0b831 100644 --- a/homeassistant/components/broadlink/translations/ko.json +++ b/homeassistant/components/broadlink/translations/ko.json @@ -4,42 +4,43 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "not_supported": "\uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \uc7a5\uce58", + "invalid_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "not_supported": "\uae30\uae30\uac00 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, - "flow_title": "{name} ({host} \uc758 {model})", + "flow_title": "{name} ({host}\uc758 {model})", "step": { "auth": { - "title": "\uc7a5\uce58\uc5d0 \uc778\uc99d" + "title": "\uae30\uae30\uc5d0 \uc778\uc99d\ud558\uae30" }, "finish": { "data": { "name": "\uc774\ub984" }, - "title": "\uc7a5\uce58 \uc774\ub984\uc744 \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624" + "title": "\uae30\uae30\uc5d0 \ub300\ud55c \uc774\ub984 \uc120\ud0dd\ud558\uae30" }, "reset": { - "title": "\uc7a5\uce58 \uc7a0\uae08 \ud574\uc81c" + "description": "{name} ({host}\uc758 {model})\uc774(\uac00) \uc7a0\uaca8 \uc788\uc2b5\ub2c8\ub2e4. \uad6c\uc131\uc744 \uc778\uc99d\ud558\uace0 \uc644\ub8cc\ud558\ub824\uba74 \uae30\uae30\uc758 \uc7a0\uae08\uc744 \ud574\uc81c\ud574\uc57c \ud569\ub2c8\ub2e4. \ub2e4\uc74c\uc744 \ub530\ub77c\uc8fc\uc138\uc694:\n1. Broadlink \uc571\uc744 \uc5f4\uc5b4\uc8fc\uc138\uc694.\n2. \uae30\uae30\ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694.\n3. \uc624\ub978\ucabd \uc0c1\ub2e8\uc758 `...`\ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694.\n4. \ud398\uc774\uc9c0 \ub9e8 \uc544\ub798\ub85c \uc2a4\ud06c\ub864\ud574\uc8fc\uc138\uc694.\n5. \uc7a0\uae08\uc744 \ud574\uc81c\ud574\uc8fc\uc138\uc694.", + "title": "\uae30\uae30 \uc7a0\uae08 \ud574\uc81c\ud558\uae30" }, "unlock": { "data": { - "unlock": "\uc608" + "unlock": "\uc608, \uc7a0\uae08 \ud574\uc81c\ud558\uaca0\uc2b5\ub2c8\ub2e4." }, - "description": "{name} ({host} \uc758 {model}) \uc774(\uac00) \uc7a0\uaca8 \uc788\uc2b5\ub2c8\ub2e4. \uc774\ub85c \uc778\ud574 Home Assistant \uc5d0\uc11c \uc778\uc99d \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc7a0\uae08\uc744 \ud574\uc81c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "\uc7a5\uce58 \uc7a0\uae08 \ud574\uc81c (\uc635\uc158)" + "description": "{name} ({host}\uc758 {model})\uc774(\uac00) \uc7a0\uaca8 \uc788\uc2b5\ub2c8\ub2e4. \uc774\ub85c \uc778\ud574 Home Assistant\uc5d0\uc11c \uc778\uc99d \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc7a0\uae08\uc744 \ud574\uc81c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\uae30\uae30 \uc7a0\uae08 \ud574\uc81c\ud558\uae30 (\uc120\ud0dd \uc0ac\ud56d)" }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8", "timeout": "\uc81c\ud55c \uc2dc\uac04" }, - "title": "\uc7a5\uce58\uc5d0 \uc5f0\uacb0" + "title": "\uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/broadlink/translations/nl.json b/homeassistant/components/broadlink/translations/nl.json index d2db5476555..138caf9a5b7 100644 --- a/homeassistant/components/broadlink/translations/nl.json +++ b/homeassistant/components/broadlink/translations/nl.json @@ -31,7 +31,8 @@ }, "user": { "data": { - "host": "Host" + "host": "Host", + "timeout": "Time-out" }, "title": "Verbinding maken met het apparaat" } diff --git a/homeassistant/components/brother/translations/hu.json b/homeassistant/components/brother/translations/hu.json index dd5711cc516..ae950f58f72 100644 --- a/homeassistant/components/brother/translations/hu.json +++ b/homeassistant/components/brother/translations/hu.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Ez a nyomtat\u00f3 m\u00e1r konfigur\u00e1lva van.", + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", "unsupported_model": "Ez a nyomtat\u00f3modell nem t\u00e1mogatott." }, "error": { diff --git a/homeassistant/components/brother/translations/id.json b/homeassistant/components/brother/translations/id.json new file mode 100644 index 00000000000..5e0b562017c --- /dev/null +++ b/homeassistant/components/brother/translations/id.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "unsupported_model": "Model printer ini tidak didukung." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "snmp_error": "Server SNMP dimatikan atau printer tidak didukung.", + "wrong_host": "Nama host atau alamat IP tidak valid." + }, + "flow_title": "Printer Brother: {model} {serial_number}", + "step": { + "user": { + "data": { + "host": "Host", + "type": "Jenis printer" + }, + "description": "Siapkan integrasi printer Brother. Jika Anda memiliki masalah dengan konfigurasi, buka: https://www.home-assistant.io/integrations/brother" + }, + "zeroconf_confirm": { + "data": { + "type": "Jenis printer" + }, + "description": "Ingin menambahkan Printer Brother {model} dengan nomor seri `{serial_number}` ke Home Assistant?", + "title": "Perangkat Printer Brother yang Ditemukan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/brother/translations/ko.json b/homeassistant/components/brother/translations/ko.json index 47722afdae5..2d3b587e475 100644 --- a/homeassistant/components/brother/translations/ko.json +++ b/homeassistant/components/brother/translations/ko.json @@ -22,7 +22,7 @@ "data": { "type": "\ud504\ub9b0\ud130\uc758 \uc885\ub958" }, - "description": "\uc2dc\ub9ac\uc5bc \ubc88\ud638 `{serial_number}` \ub85c \ube0c\ub77c\ub354 \ud504\ub9b0\ud130 {model} \uc744(\ub97c) Home Assistant \uc5d0 \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "\uc2dc\ub9ac\uc5bc \ubc88\ud638 `{serial_number}`\uc758 {model} \ube0c\ub77c\ub354 \ud504\ub9b0\ud130\ub97c Home Assistant\uc5d0 \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "\ubc1c\uacac\ub41c \ube0c\ub77c\ub354 \ud504\ub9b0\ud130" } } diff --git a/homeassistant/components/bsblan/translations/hu.json b/homeassistant/components/bsblan/translations/hu.json index 1d28556ba1a..50d250cc384 100644 --- a/homeassistant/components/bsblan/translations/hu.json +++ b/homeassistant/components/bsblan/translations/hu.json @@ -1,13 +1,19 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, + "flow_title": "BSB-Lan: {name}", "step": { "user": { "data": { "host": "Hoszt", - "port": "Port" + "password": "Jelsz\u00f3", + "port": "Port", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } } } diff --git a/homeassistant/components/bsblan/translations/id.json b/homeassistant/components/bsblan/translations/id.json new file mode 100644 index 00000000000..6e8ac0bd4cb --- /dev/null +++ b/homeassistant/components/bsblan/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "BSB-Lan: {name}", + "step": { + "user": { + "data": { + "host": "Host", + "passkey": "String kunci sandi", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + }, + "description": "Siapkan perangkat BSB-Lan Anda untuk diintegrasikan dengan Home Assistant.", + "title": "Hubungkan ke perangkat BSB-Lan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bsblan/translations/ko.json b/homeassistant/components/bsblan/translations/ko.json index 85703c7eeb7..65843a25833 100644 --- a/homeassistant/components/bsblan/translations/ko.json +++ b/homeassistant/components/bsblan/translations/ko.json @@ -16,7 +16,7 @@ "port": "\ud3ec\ud2b8", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "Home Assistant \uc5d0 BSB-Lan \uae30\uae30 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4.", + "description": "Home Assistant\uc5d0 BSB-Lan \uae30\uae30 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4.", "title": "BSB-Lan \uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/calendar/translations/id.json b/homeassistant/components/calendar/translations/id.json index 383a6ba77a1..e48c6e69b98 100644 --- a/homeassistant/components/calendar/translations/id.json +++ b/homeassistant/components/calendar/translations/id.json @@ -1,8 +1,8 @@ { "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" } }, "title": "Kalender" diff --git a/homeassistant/components/calendar/translations/ko.json b/homeassistant/components/calendar/translations/ko.json index af8622be7d7..fd1672fef56 100644 --- a/homeassistant/components/calendar/translations/ko.json +++ b/homeassistant/components/calendar/translations/ko.json @@ -5,5 +5,5 @@ "on": "\ucf1c\uc9d0" } }, - "title": "\uc77c\uc815" + "title": "\uce98\ub9b0\ub354" } \ No newline at end of file diff --git a/homeassistant/components/canary/translations/hu.json b/homeassistant/components/canary/translations/hu.json new file mode 100644 index 00000000000..c2c70fdbf22 --- /dev/null +++ b/homeassistant/components/canary/translations/hu.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "flow_title": "Canary: {name}", + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "title": "Csatlakoz\u00e1s a Canary-hoz" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s (opcion\u00e1lis)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/id.json b/homeassistant/components/canary/translations/id.json new file mode 100644 index 00000000000..5f092847b4d --- /dev/null +++ b/homeassistant/components/canary/translations/id.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "Canary: {name}", + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Hubungkan ke Canary" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "Argumen yang diteruskan ke ffmpeg untuk kamera", + "timeout": "Tenggang Waktu Permintaan (detik)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/ko.json b/homeassistant/components/canary/translations/ko.json index d02344a9027..c96049f16ff 100644 --- a/homeassistant/components/canary/translations/ko.json +++ b/homeassistant/components/canary/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { @@ -14,7 +14,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "title": "Canary\uc5d0 \uc5f0\uacb0" + "title": "Canary\uc5d0 \uc5f0\uacb0\ud558\uae30" } } }, @@ -22,7 +22,7 @@ "step": { "init": { "data": { - "ffmpeg_arguments": "\uce74\uba54\ub77c ffmpeg\uc5d0 \uc804\ub2ec \ub41c \uc778\uc218", + "ffmpeg_arguments": "\uce74\uba54\ub77c\uc5d0 \ub300\ud55c ffmpeg \uc804\ub2ec \uc778\uc218", "timeout": "\uc694\uccad \uc81c\ud55c \uc2dc\uac04 (\ucd08)" } } diff --git a/homeassistant/components/cast/translations/ca.json b/homeassistant/components/cast/translations/ca.json index dc21c371e60..9cb55f4d731 100644 --- a/homeassistant/components/cast/translations/ca.json +++ b/homeassistant/components/cast/translations/ca.json @@ -4,10 +4,33 @@ "no_devices_found": "No s'han trobat dispositius a la xarxa", "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, + "error": { + "invalid_known_hosts": "Els amfitrions coneguts han de ser una llista d'amfitrions separats per comes." + }, "step": { + "config": { + "data": { + "known_hosts": "Llista opcional d'amfitrions coneguts per si el descobriment mDNS deixa de funcionar." + }, + "description": "Introdueix la configuraci\u00f3 de Google Cast.", + "title": "Google Cast" + }, "confirm": { "description": "Vols comen\u00e7ar la configuraci\u00f3?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Els amfitrions coneguts han de ser una llista d'amfitrions separats per comes." + }, + "step": { + "options": { + "data": { + "known_hosts": "Llista opcional d'amfitrions coneguts per si el descobriment mDNS deixa de funcionar." + }, + "description": "Introdueix la configuraci\u00f3 de Google Cast." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/de.json b/homeassistant/components/cast/translations/de.json index 7ff1efb8ee0..a59b3421c10 100644 --- a/homeassistant/components/cast/translations/de.json +++ b/homeassistant/components/cast/translations/de.json @@ -4,10 +4,33 @@ "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden.", "single_instance_allowed": "Bereits konfiguriert. Es ist nur eine Konfiguration m\u00f6glich." }, + "error": { + "invalid_known_hosts": "Bekannte Hosts m\u00fcssen eine durch Kommata getrennte Liste von Hosts sein." + }, "step": { + "config": { + "data": { + "known_hosts": "Optionale Liste bekannter Hosts, wenn die mDNS-Erkennung nicht funktioniert." + }, + "description": "Bitte die Google Cast-Konfiguration eingeben.", + "title": "Google Cast" + }, "confirm": { "description": "M\u00f6chtest du Google Cast einrichten?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Bekannte Hosts m\u00fcssen eine durch Kommata getrennte Liste von Hosts sein." + }, + "step": { + "options": { + "data": { + "known_hosts": "Optionale Liste bekannter Hosts, wenn die mDNS-Erkennung nicht funktioniert." + }, + "description": "Bitte die Google Cast-Konfiguration eingeben." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/en.json b/homeassistant/components/cast/translations/en.json index 9ef2f10cda2..1bfdeb4df8d 100644 --- a/homeassistant/components/cast/translations/en.json +++ b/homeassistant/components/cast/translations/en.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "no_devices_found": "No devices found on the network", "single_instance_allowed": "Already configured. Only a single configuration possible." }, "error": { diff --git a/homeassistant/components/cast/translations/es.json b/homeassistant/components/cast/translations/es.json index 520df7ee4cd..07b090634e0 100644 --- a/homeassistant/components/cast/translations/es.json +++ b/homeassistant/components/cast/translations/es.json @@ -4,10 +4,33 @@ "no_devices_found": "No se encontraron dispositivos en la red", "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." }, + "error": { + "invalid_known_hosts": "Los hosts conocidos deben ser una lista de hosts separados por comas." + }, "step": { + "config": { + "data": { + "known_hosts": "Lista opcional de hosts conocidos si el descubrimiento mDNS no funciona." + }, + "description": "Introduce la configuraci\u00f3n de Google Cast.", + "title": "Google Cast" + }, "confirm": { "description": "\u00bfQuieres iniciar la configuraci\u00f3n?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Los hosts conocidos deben ser una lista de hosts separados por comas." + }, + "step": { + "options": { + "data": { + "known_hosts": "Lista opcional de hosts conocidos si el descubrimiento mDNS no funciona." + }, + "description": "Introduce la configuraci\u00f3n de Google Cast." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/et.json b/homeassistant/components/cast/translations/et.json index 05287b5a52b..9e126d50af0 100644 --- a/homeassistant/components/cast/translations/et.json +++ b/homeassistant/components/cast/translations/et.json @@ -4,10 +4,33 @@ "no_devices_found": "V\u00f5rgust ei leitud \u00fchtegi Google Casti seadet.", "single_instance_allowed": "Vajalik on ainult \u00fcks Google Casti konfiguratsioon." }, + "error": { + "invalid_known_hosts": "Teadaolevad hostid peab olema komaeraldusega hostide loend." + }, "step": { + "config": { + "data": { + "known_hosts": "Valikuline loend teadaolevatest hostidest kui mDNS-i tuvastamine ei t\u00f6\u00f6ta." + }, + "description": "Sisesta Google Casti andmed.", + "title": "Google Cast" + }, "confirm": { "description": "Kas soovid seadistada Google Casti?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Teadaolevad hostid peab olema komaeraldusega hostide loend." + }, + "step": { + "options": { + "data": { + "known_hosts": "Valikuline loend teadaolevatest hostidest kui mDNS-i tuvastamine ei t\u00f6\u00f6ta." + }, + "description": "Sisesta Google Casti andmed." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/fr.json b/homeassistant/components/cast/translations/fr.json index afa4b094fad..0acfd327e3e 100644 --- a/homeassistant/components/cast/translations/fr.json +++ b/homeassistant/components/cast/translations/fr.json @@ -4,10 +4,33 @@ "no_devices_found": "Aucun appareil Google Cast trouv\u00e9 sur le r\u00e9seau.", "single_instance_allowed": "Une seule configuration de Google Cast est n\u00e9cessaire." }, + "error": { + "invalid_known_hosts": "Les h\u00f4tes connus doivent \u00eatre une liste d'h\u00f4tes s\u00e9par\u00e9s par des virgules." + }, "step": { + "config": { + "data": { + "known_hosts": "Liste facultative des h\u00f4tes connus si la d\u00e9couverte mDNS ne fonctionne pas." + }, + "description": "Veuillez saisir la configuration de Google Cast.", + "title": "Google Cast" + }, "confirm": { "description": "Voulez-vous configurer Google Cast?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Les h\u00f4tes connus doivent \u00eatre une liste d'h\u00f4tes s\u00e9par\u00e9s par des virgules." + }, + "step": { + "options": { + "data": { + "known_hosts": "Liste facultative des h\u00f4tes connus si la d\u00e9couverte mDNS ne fonctionne pas." + }, + "description": "Veuillez saisir la configuration de Google Cast." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/hu.json b/homeassistant/components/cast/translations/hu.json index dc55cd224f8..4a6ef76f33c 100644 --- a/homeassistant/components/cast/translations/hu.json +++ b/homeassistant/components/cast/translations/hu.json @@ -1,12 +1,35 @@ { "config": { "abort": { - "no_devices_found": "Nem tal\u00e1lhat\u00f3k Google Cast eszk\u00f6z\u00f6k a h\u00e1l\u00f3zaton.", - "single_instance_allowed": "Csak egyetlen Google Cast konfigur\u00e1ci\u00f3 sz\u00fcks\u00e9ges." + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "invalid_known_hosts": "Az ismert hosztoknak vessz\u0151vel elv\u00e1lasztott hosztok list\u00e1j\u00e1nak kell lennie." }, "step": { + "config": { + "data": { + "known_hosts": "Opcion\u00e1lis lista az ismert hosztokr\u00f3l, ha az mDNS felder\u00edt\u00e9s nem m\u0171k\u00f6dik." + }, + "description": "K\u00e9rj\u00fck, add meg a Google Cast konfigur\u00e1ci\u00f3t.", + "title": "Google Cast" + }, "confirm": { - "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a Google Cast szolg\u00e1ltat\u00e1st?" + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?" + } + } + }, + "options": { + "error": { + "invalid_known_hosts": "Az ismert hosztoknak vessz\u0151vel elv\u00e1lasztott hosztok list\u00e1j\u00e1nak kell lennie." + }, + "step": { + "options": { + "data": { + "known_hosts": "Opcion\u00e1lis lista az ismert hosztokr\u00f3l, ha az mDNS felder\u00edt\u00e9s nem m\u0171k\u00f6dik." + }, + "description": "K\u00e9rj\u00fck, add meg a Google Cast konfigur\u00e1ci\u00f3t." } } } diff --git a/homeassistant/components/cast/translations/id.json b/homeassistant/components/cast/translations/id.json index d3e2bb5f360..240ee853609 100644 --- a/homeassistant/components/cast/translations/id.json +++ b/homeassistant/components/cast/translations/id.json @@ -1,12 +1,35 @@ { "config": { "abort": { - "no_devices_found": "Tidak ada perangkat Google Cast yang ditemukan pada jaringan.", - "single_instance_allowed": "Hanya satu konfigurasi Google Cast yang diperlukan." + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "invalid_known_hosts": "Host yang diketahui harus berupa daftar host yang dipisahkan koma." }, "step": { + "config": { + "data": { + "known_hosts": "Daftar opsional host yang diketahui jika penemuan mDNS tidak berfungsi." + }, + "description": "Masukkan konfigurasi Google Cast.", + "title": "Google Cast" + }, "confirm": { - "description": "Apakah Anda ingin menyiapkan Google Cast?" + "description": "Ingin memulai penyiapan?" + } + } + }, + "options": { + "error": { + "invalid_known_hosts": "Host yang diketahui harus berupa daftar host yang dipisahkan koma." + }, + "step": { + "options": { + "data": { + "known_hosts": "Daftar opsional host yang diketahui jika penemuan mDNS tidak berfungsi." + }, + "description": "Masukkan konfigurasi Google Cast." } } } diff --git a/homeassistant/components/cast/translations/it.json b/homeassistant/components/cast/translations/it.json index 0278fe07bfe..17abade539e 100644 --- a/homeassistant/components/cast/translations/it.json +++ b/homeassistant/components/cast/translations/it.json @@ -4,10 +4,33 @@ "no_devices_found": "Nessun dispositivo trovato sulla rete", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, + "error": { + "invalid_known_hosts": "Gli host noti devono essere un elenco di host separato da virgole." + }, "step": { + "config": { + "data": { + "known_hosts": "Elenco facoltativo di host noti se l'individuazione di mDNS non funziona." + }, + "description": "Inserisci la configurazione di Google Cast.", + "title": "Google Cast" + }, "confirm": { "description": "Vuoi iniziare la configurazione?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Gli host noti devono essere indicati sotto forma di un elenco di host separati da virgole." + }, + "step": { + "options": { + "data": { + "known_hosts": "Elenco facoltativo di host noti se l'individuazione di mDNS non funziona." + }, + "description": "Inserisci la configurazione di Google Cast." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/ko.json b/homeassistant/components/cast/translations/ko.json index 7011a61f757..3ae6a688527 100644 --- a/homeassistant/components/cast/translations/ko.json +++ b/homeassistant/components/cast/translations/ko.json @@ -2,12 +2,35 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + }, + "error": { + "invalid_known_hosts": "\uc54c\ub824\uc9c4 \ud638\uc2a4\ud2b8\ub294 \uc27c\ud45c\ub85c \uad6c\ubd84\ub41c \ud638\uc2a4\ud2b8 \ubaa9\ub85d\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "step": { + "config": { + "data": { + "known_hosts": "mDNS \uac80\uc0c9\uc774 \uc791\ub3d9\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0 \uc54c\ub824\uc9c4 \ud638\uc2a4\ud2b8\uc758 \uc120\ud0dd\uc801 \ubaa9\ub85d\uc785\ub2c8\ub2e4." + }, + "description": "Google Cast \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "Google Cast" + }, "confirm": { "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "\uc54c\ub824\uc9c4 \ud638\uc2a4\ud2b8\ub294 \uc27c\ud45c\ub85c \uad6c\ubd84\ub41c \ud638\uc2a4\ud2b8 \ubaa9\ub85d\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4." + }, + "step": { + "options": { + "data": { + "known_hosts": "mDNS \uac80\uc0c9\uc774 \uc791\ub3d9\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0 \uc54c\ub824\uc9c4 \ud638\uc2a4\ud2b8\uc758 \uc120\ud0dd\uc801 \ubaa9\ub85d\uc785\ub2c8\ub2e4." + }, + "description": "Google Cast \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/nl.json b/homeassistant/components/cast/translations/nl.json index d42ef3e850c..5c6cfae7c6b 100644 --- a/homeassistant/components/cast/translations/nl.json +++ b/homeassistant/components/cast/translations/nl.json @@ -1,12 +1,35 @@ { "config": { "abort": { - "no_devices_found": "Geen Google Cast-apparaten gevonden op het netwerk.", - "single_instance_allowed": "Er is slechts \u00e9\u00e9n configuratie van Google Cast nodig." + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, + "error": { + "invalid_known_hosts": "Bekende hosts moet een door komma's gescheiden lijst van hosts zijn." }, "step": { + "config": { + "data": { + "known_hosts": "Optionele lijst van bekende hosts indien mDNS discovery niet werkt." + }, + "description": "Voer de Google Cast configuratie in.", + "title": "Google Cast" + }, "confirm": { - "description": "Wilt u Google Cast instellen?" + "description": "Wil je beginnen met instellen?" + } + } + }, + "options": { + "error": { + "invalid_known_hosts": "Bekende hosts moet een door komma's gescheiden lijst van hosts zijn." + }, + "step": { + "options": { + "data": { + "known_hosts": "Optionele lijst van bekende hosts indien mDNS discovery niet werkt." + }, + "description": "Voer de Google Cast configuratie in." } } } diff --git a/homeassistant/components/cast/translations/no.json b/homeassistant/components/cast/translations/no.json index b3d6b5d782e..f92a5d649e4 100644 --- a/homeassistant/components/cast/translations/no.json +++ b/homeassistant/components/cast/translations/no.json @@ -4,10 +4,33 @@ "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket", "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, + "error": { + "invalid_known_hosts": "Kjente verter m\u00e5 v\u00e6re en kommaseparert liste over verter." + }, "step": { + "config": { + "data": { + "known_hosts": "Valgfri liste over kjente verter hvis mDNS-oppdagelse ikke fungerer." + }, + "description": "Angi Google Cast-konfigurasjonen.", + "title": "Google Cast" + }, "confirm": { "description": "Vil du starte oppsettet?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Kjente verter m\u00e5 v\u00e6re en kommaseparert liste over verter." + }, + "step": { + "options": { + "data": { + "known_hosts": "Valgfri liste over kjente verter hvis mDNS-oppdagelse ikke fungerer." + }, + "description": "Angi Google Cast-konfigurasjonen." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/pl.json b/homeassistant/components/cast/translations/pl.json index a8ee3fa57ac..e3a74ecd52e 100644 --- a/homeassistant/components/cast/translations/pl.json +++ b/homeassistant/components/cast/translations/pl.json @@ -4,10 +4,33 @@ "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." }, + "error": { + "invalid_known_hosts": "Znane hosty musz\u0105 by\u0107 list\u0105 host\u00f3w oddzielonych przecinkami." + }, "step": { + "config": { + "data": { + "known_hosts": "Opcjonalna lista znanych host\u00f3w, je\u015bli wykrywanie mDNS nie dzia\u0142a." + }, + "description": "Wprowad\u017a konfiguracj\u0119 Google Cast.", + "title": "Google Cast" + }, "confirm": { "description": "Czy chcesz rozpocz\u0105\u0107 konfiguracj\u0119?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Znane hosty musz\u0105 by\u0107 list\u0105 host\u00f3w oddzielonych przecinkami." + }, + "step": { + "options": { + "data": { + "known_hosts": "Opcjonalna lista znanych host\u00f3w, je\u015bli wykrywanie mDNS nie dzia\u0142a." + }, + "description": "Wprowad\u017a konfiguracj\u0119 Google Cast." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/pt.json b/homeassistant/components/cast/translations/pt.json index 9dd9a69a94c..32da71ec389 100644 --- a/homeassistant/components/cast/translations/pt.json +++ b/homeassistant/components/cast/translations/pt.json @@ -9,5 +9,12 @@ "description": "Deseja configurar o Google Cast?" } } + }, + "options": { + "step": { + "options": { + "description": "Por favor introduza a configura\u00e7\u00e3o do Google Cast" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/ru.json b/homeassistant/components/cast/translations/ru.json index 85a42bf1be5..e565cedbfad 100644 --- a/homeassistant/components/cast/translations/ru.json +++ b/homeassistant/components/cast/translations/ru.json @@ -4,10 +4,33 @@ "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438.", "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." }, + "error": { + "invalid_known_hosts": "\u0418\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0435 \u0445\u043e\u0441\u0442\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u044b \u0441\u043f\u0438\u0441\u043a\u043e\u043c, \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c \u0437\u0430\u043f\u044f\u0442\u044b\u043c\u0438." + }, "step": { + "config": { + "data": { + "known_hosts": "\u041d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0445 \u0445\u043e\u0441\u0442\u043e\u0432, \u0435\u0441\u043b\u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435 mDNS \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442." + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Google Cast.", + "title": "Google Cast" + }, "confirm": { "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "\u0418\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0435 \u0445\u043e\u0441\u0442\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u044b \u0441\u043f\u0438\u0441\u043a\u043e\u043c, \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c \u0437\u0430\u043f\u044f\u0442\u044b\u043c\u0438." + }, + "step": { + "options": { + "data": { + "known_hosts": "\u041d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0445 \u0445\u043e\u0441\u0442\u043e\u0432, \u0435\u0441\u043b\u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435 mDNS \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442." + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Google Cast." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/zh-Hans.json b/homeassistant/components/cast/translations/zh-Hans.json index 1c2024f8b81..0feaac56440 100644 --- a/homeassistant/components/cast/translations/zh-Hans.json +++ b/homeassistant/components/cast/translations/zh-Hans.json @@ -5,9 +5,20 @@ "single_instance_allowed": "Google Cast \u53ea\u9700\u8981\u914d\u7f6e\u4e00\u6b21\u3002" }, "step": { + "config": { + "description": "\u8bf7\u786e\u8ba4Goole Cast\u7684\u914d\u7f6e", + "title": "Google Cast" + }, "confirm": { "description": "\u60a8\u60f3\u8981\u914d\u7f6e Google Cast \u5417\uff1f" } } + }, + "options": { + "step": { + "options": { + "description": "\u8bf7\u786e\u8ba4Goole Cast\u7684\u914d\u7f6e" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/zh-Hant.json b/homeassistant/components/cast/translations/zh-Hant.json index 90c98e491df..4a32f61eeff 100644 --- a/homeassistant/components/cast/translations/zh-Hant.json +++ b/homeassistant/components/cast/translations/zh-Hant.json @@ -4,10 +4,33 @@ "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, + "error": { + "invalid_known_hosts": "\u5df2\u77e5\u4e3b\u6a5f\u5fc5\u9808\u4ee5\u9017\u865f\u5206\u4e3b\u6a5f\u5217\u8868\u3002" + }, "step": { + "config": { + "data": { + "known_hosts": "\u5047\u5982 mDNS \u63a2\u7d22\u7121\u6cd5\u4f5c\u7528\uff0c\u5247\u70ba\u5df2\u77e5\u4e3b\u6a5f\u7684\u9078\u9805\u5217\u8868\u3002" + }, + "description": "\u8acb\u8f38\u5165 Google Cast \u8a2d\u5b9a\u3002", + "title": "Google Cast" + }, "confirm": { "description": "\u662f\u5426\u8981\u958b\u59cb\u8a2d\u5b9a\uff1f" } } + }, + "options": { + "error": { + "invalid_known_hosts": "\u5df2\u77e5\u4e3b\u6a5f\u5fc5\u9808\u4ee5\u9017\u865f\u5206\u4e3b\u6a5f\u5217\u8868\u3002" + }, + "step": { + "options": { + "data": { + "known_hosts": "\u5047\u5982 mDNS \u63a2\u7d22\u7121\u6cd5\u4f5c\u7528\uff0c\u5247\u70ba\u5df2\u77e5\u4e3b\u6a5f\u7684\u9078\u9805\u5217\u8868\u3002" + }, + "description": "\u8acb\u8f38\u5165 Google Cast \u8a2d\u5b9a\u3002" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cert_expiry/translations/hu.json b/homeassistant/components/cert_expiry/translations/hu.json index 5bad24ecb6a..2ae516565e3 100644 --- a/homeassistant/components/cert_expiry/translations/hu.json +++ b/homeassistant/components/cert_expiry/translations/hu.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van", "import_failed": "Nem siker\u00fclt import\u00e1lni a konfigur\u00e1ci\u00f3t" }, "step": { diff --git a/homeassistant/components/cert_expiry/translations/id.json b/homeassistant/components/cert_expiry/translations/id.json new file mode 100644 index 00000000000..9fac285fe82 --- /dev/null +++ b/homeassistant/components/cert_expiry/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi", + "import_failed": "Impor dari konfigurasi gagal" + }, + "error": { + "connection_refused": "Sambungan ditolak saat menghubungkan ke host", + "connection_timeout": "Tenggang waktu terhubung ke host ini habis", + "resolve_failed": "Host ini tidak dapat ditemukan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama sertifikat", + "port": "Port" + }, + "title": "Tentukan sertifikat yang akan diuji" + } + } + }, + "title": "Informasi Kedaluwarsa Sertifikat" +} \ No newline at end of file diff --git a/homeassistant/components/cert_expiry/translations/nl.json b/homeassistant/components/cert_expiry/translations/nl.json index d844d28e62f..b29cdcaad71 100644 --- a/homeassistant/components/cert_expiry/translations/nl.json +++ b/homeassistant/components/cert_expiry/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Deze combinatie van host en poort is al geconfigureerd", + "already_configured": "Service is al geconfigureerd", "import_failed": "Importeren vanuit configuratie is mislukt" }, "error": { diff --git a/homeassistant/components/climacell/translations/bg.json b/homeassistant/components/climacell/translations/bg.json new file mode 100644 index 00000000000..6b1e4d3cba2 --- /dev/null +++ b/homeassistant/components/climacell/translations/bg.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "user": { + "data": { + "latitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0448\u0438\u0440\u0438\u043d\u0430", + "longitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0434\u044a\u043b\u0436\u0438\u043d\u0430", + "name": "\u0418\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/de.json b/homeassistant/components/climacell/translations/de.json index f18197e1cca..a91e222b3cd 100644 --- a/homeassistant/components/climacell/translations/de.json +++ b/homeassistant/components/climacell/translations/de.json @@ -15,5 +15,6 @@ } } } - } + }, + "title": "ClimaCell" } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/es.json b/homeassistant/components/climacell/translations/es.json index 4c4d8fcc9bb..52fd5d21166 100644 --- a/homeassistant/components/climacell/translations/es.json +++ b/homeassistant/components/climacell/translations/es.json @@ -1,7 +1,9 @@ { "config": { "error": { - "rate_limited": "Actualmente la tarifa est\u00e1 limitada, por favor int\u00e9ntelo m\u00e1s tarde." + "cannot_connect": "Fallo al conectar", + "rate_limited": "Actualmente la tarifa est\u00e1 limitada, por favor int\u00e9ntelo m\u00e1s tarde.", + "unknown": "Error inesperado" }, "step": { "user": { diff --git a/homeassistant/components/climacell/translations/hu.json b/homeassistant/components/climacell/translations/hu.json new file mode 100644 index 00000000000..fa0aa2ec0c7 --- /dev/null +++ b/homeassistant/components/climacell/translations/hu.json @@ -0,0 +1,28 @@ +{ + "config": { + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g", + "name": "N\u00e9v" + }, + "description": "Ha a Sz\u00e9less\u00e9g \u00e9s Hossz\u00fas\u00e1g nincs megadva, akkor a Home Assistant konfigur\u00e1ci\u00f3j\u00e1ban l\u00e9v\u0151 alap\u00e9rtelmezett \u00e9rt\u00e9keket fogjuk haszn\u00e1lni. Minden el\u0151rejelz\u00e9si t\u00edpushoz l\u00e9trej\u00f6n egy entit\u00e1s, de alap\u00e9rtelmez\u00e9s szerint csak az \u00e1ltalad kiv\u00e1lasztottak lesznek enged\u00e9lyezve." + } + } + }, + "options": { + "step": { + "init": { + "title": "Friss\u00edtse a ClimaCell be\u00e1ll\u00edt\u00e1sokat" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/id.json b/homeassistant/components/climacell/translations/id.json new file mode 100644 index 00000000000..132f4dcfcb7 --- /dev/null +++ b/homeassistant/components/climacell/translations/id.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_api_key": "Kunci API tidak valid", + "rate_limited": "Saat ini tingkatnya dibatasi, coba lagi nanti.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "longitude": "Bujur", + "name": "Nama" + }, + "description": "Jika Lintang dan Bujur tidak tersedia, nilai default dalam konfigurasi Home Assistant akan digunakan. Entitas akan dibuat untuk setiap jenis prakiraan tetapi hanya yang Anda pilih yang akan diaktifkan secara default." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Jenis Prakiraan", + "timestep": "Jarak Interval Prakiraan NowCast dalam Menit" + }, + "description": "Jika Anda memilih untuk mengaktifkan entitas prakiraan 'nowcast', Anda dapat mengonfigurasi jarak interval prakiraan dalam menit. Jumlah prakiraan yang diberikan tergantung pada nilai interval yang dipilih.", + "title": "Perbarui Opsi ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/ko.json b/homeassistant/components/climacell/translations/ko.json new file mode 100644 index 00000000000..6fc5a6d7e8b --- /dev/null +++ b/homeassistant/components/climacell/translations/ko.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "rate_limited": "\ud604\uc7ac \uc0ac\uc6a9 \ud69f\uc218\ub97c \ucd08\uacfc\ud588\uc2b5\ub2c8\ub2e4. \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "api_key": "API \ud0a4", + "latitude": "\uc704\ub3c4", + "longitude": "\uacbd\ub3c4", + "name": "\uc774\ub984" + }, + "description": "\uc704\ub3c4 \ubc0f \uacbd\ub3c4\uac00 \uc81c\uacf5\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 Home Assistant \uad6c\uc131\uc758 \uae30\ubcf8\uac12\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4. \uac01 \uc77c\uae30\uc608\ubcf4 \uc720\ud615\uc5d0 \ub300\ud574 \uad6c\uc131\uc694\uc18c\uac00 \uc0dd\uc131\ub418\uc9c0\ub9cc \uae30\ubcf8\uc801\uc73c\ub85c \uc120\ud0dd\ud55c \uad6c\uc131\uc694\uc18c\ub9cc \ud65c\uc131\ud654\ub429\ub2c8\ub2e4." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "\uc77c\uae30\uc608\ubcf4 \uc720\ud615", + "timestep": "\ub2e8\uae30\uc608\uce21 \uc77c\uae30\uc608\ubcf4 \uac04 \ucd5c\uc18c \uc2dc\uac04" + }, + "description": "`nowcast` \uc77c\uae30\uc608\ubcf4 \uad6c\uc131\uc694\uc18c\ub97c \uc0ac\uc6a9\ud558\ub3c4\ub85d \uc120\ud0dd\ud55c \uacbd\uc6b0 \uac01 \uc77c\uae30\uc608\ubcf4 \uc0ac\uc774\uc758 \uc2dc\uac04(\ubd84)\uc744 \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc81c\uacf5\ub41c \uc77c\uae30\uc608\ubcf4 \ud69f\uc218\ub294 \uc608\uce21 \uac04 \uc120\ud0dd\ud55c \uc2dc\uac04(\ubd84)\uc5d0 \ub530\ub77c \ub2ec\ub77c\uc9d1\ub2c8\ub2e4.", + "title": "ClimaCell \uc635\uc158 \uc5c5\ub370\uc774\ud2b8\ud558\uae30" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/nl.json b/homeassistant/components/climacell/translations/nl.json index 488a43ae24e..f267be34478 100644 --- a/homeassistant/components/climacell/translations/nl.json +++ b/homeassistant/components/climacell/translations/nl.json @@ -3,6 +3,7 @@ "error": { "cannot_connect": "Kan geen verbinding maken", "invalid_api_key": "Ongeldige API-sleutel", + "rate_limited": "Momenteel is een beperkt aantal aanvragen mogelijk, probeer later opnieuw.", "unknown": "Onverwachte fout" }, "step": { @@ -13,7 +14,7 @@ "longitude": "Lengtegraad", "name": "Naam" }, - "description": "Indien Breedtegraad en Lengtegraad niet worden opgegeven, worden de standaardwaarden in de Home Assistant-configuratie gebruikt. Er wordt een entiteit gemaakt voor elk voorspellingstype maar alleen degene die u selecteert worden standaard ingeschakeld." + "description": "Indien Breedtegraad en Lengtegraad niet worden opgegeven, worden de standaardwaarden in de Home Assistant-configuratie gebruikt. Er wordt een entiteit gemaakt voor elk voorspellingstype, maar alleen degenen die u selecteert worden standaard ingeschakeld." } } }, @@ -21,7 +22,8 @@ "step": { "init": { "data": { - "forecast_types": "Voorspellingstype(n)" + "forecast_types": "Voorspellingstype(n)", + "timestep": "Min. Tussen NowCast-voorspellingen" }, "description": "Als u ervoor kiest om de `nowcast` voorspellingsentiteit in te schakelen, kan u het aantal minuten tussen elke voorspelling configureren. Het aantal voorspellingen hangt af van het aantal gekozen minuten tussen de voorspellingen.", "title": "Update ClimaCell Opties" diff --git a/homeassistant/components/climacell/translations/pl.json b/homeassistant/components/climacell/translations/pl.json new file mode 100644 index 00000000000..6fc13aadc96 --- /dev/null +++ b/homeassistant/components/climacell/translations/pl.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_api_key": "Nieprawid\u0142owy klucz API", + "rate_limited": "Przekroczono limit, spr\u00f3buj ponownie p\u00f3\u017aniej.", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "api_key": "Klucz API", + "latitude": "Szeroko\u015b\u0107 geograficzna", + "longitude": "D\u0142ugo\u015b\u0107 geograficzna", + "name": "Nazwa" + }, + "description": "Je\u015bli szeroko\u015b\u0107 i d\u0142ugo\u015b\u0107 geograficzna nie zostan\u0105 podane, zostan\u0105 u\u017cyte domy\u015blne warto\u015bci z konfiguracji Home Assistanta. Zostanie utworzona encja dla ka\u017cdego typu prognozy, ale domy\u015blnie w\u0142\u0105czone bed\u0105 tylko te, kt\u00f3re wybierzesz." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Typ(y) prognozy", + "timestep": "Minuty pomi\u0119dzy prognozami" + }, + "description": "Je\u015bli zdecydujesz si\u0119 w\u0142\u0105czy\u0107 encj\u0119 prognozy \u201enowcast\u201d, mo\u017cesz skonfigurowa\u0107 liczb\u0119 minut mi\u0119dzy ka\u017cd\u0105 prognoz\u0105. Liczba dostarczonych prognoz zale\u017cy od liczby minut wybranych mi\u0119dzy prognozami.", + "title": "Opcje aktualizacji ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/pt.json b/homeassistant/components/climacell/translations/pt.json new file mode 100644 index 00000000000..8e05df2f1b5 --- /dev/null +++ b/homeassistant/components/climacell/translations/pt.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", + "invalid_api_key": "Chave de API inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "API Key", + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/zh-Hans.json b/homeassistant/components/climacell/translations/zh-Hans.json new file mode 100644 index 00000000000..315d060bc69 --- /dev/null +++ b/homeassistant/components/climacell/translations/zh-Hans.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "api_key": "API Key", + "name": "\u540d\u5b57" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climate/translations/id.json b/homeassistant/components/climate/translations/id.json index 4f1ec02379b..bdae7d60067 100644 --- a/homeassistant/components/climate/translations/id.json +++ b/homeassistant/components/climate/translations/id.json @@ -1,13 +1,28 @@ { + "device_automation": { + "action_type": { + "set_hvac_mode": "Ubah mode HVAC di {entity_name}", + "set_preset_mode": "Ubah prasetel di {entity_name}" + }, + "condition_type": { + "is_hvac_mode": "{entity_name} disetel ke mode HVAC tertentu", + "is_preset_mode": "{entity_name} disetel ke mode prasetel tertentu" + }, + "trigger_type": { + "current_humidity_changed": "Kelembaban terukur {entity_name} berubah", + "current_temperature_changed": "Suhu terukur {entity_name} berubah", + "hvac_mode_changed": "Mode HVAC {entity_name} berubah" + } + }, "state": { "_": { - "auto": "Auto", - "cool": "Sejuk", + "auto": "Otomatis", + "cool": "Dingin", "dry": "Kering", "fan_only": "Hanya kipas", "heat": "Panas", "heat_cool": "Panas/Dingin", - "off": "Off" + "off": "Mati" } }, "title": "Cuaca" diff --git a/homeassistant/components/climate/translations/ko.json b/homeassistant/components/climate/translations/ko.json index 0923d166040..7c7342ef95c 100644 --- a/homeassistant/components/climate/translations/ko.json +++ b/homeassistant/components/climate/translations/ko.json @@ -1,17 +1,17 @@ { "device_automation": { "action_type": { - "set_hvac_mode": "{entity_name} \uc758 HVAC \ubaa8\ub4dc \ubcc0\uacbd", - "set_preset_mode": "{entity_name} \uc758 \ud504\ub9ac\uc14b \ubcc0\uacbd" + "set_hvac_mode": "{entity_name}\uc758 HVAC \ubaa8\ub4dc \ubcc0\uacbd\ud558\uae30", + "set_preset_mode": "{entity_name}\uc758 \ud504\ub9ac\uc14b \ubcc0\uacbd\ud558\uae30" }, "condition_type": { - "is_hvac_mode": "{entity_name} \uc774(\uac00) \ud2b9\uc815 HVAC \ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5b4\uc788\uc73c\uba74", - "is_preset_mode": "{entity_name} \uc774(\uac00) \ud2b9\uc815 \ud504\ub9ac\uc14b \ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5b4\uc788\uc73c\uba74" + "is_hvac_mode": "{entity_name}\uc774(\uac00) \ud2b9\uc815 HVAC \ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5b4\uc788\uc73c\uba74", + "is_preset_mode": "{entity_name}\uc774(\uac00) \ud2b9\uc815 \ud504\ub9ac\uc14b \ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5b4\uc788\uc73c\uba74" }, "trigger_type": { - "current_humidity_changed": "{entity_name} \uc774(\uac00) \uc2b5\ub3c4 \ubcc0\ud654\ub97c \uac10\uc9c0\ud560 \ub54c", - "current_temperature_changed": "{entity_name} \uc774(\uac00) \uc628\ub3c4 \ubcc0\ud654\ub97c \uac10\uc9c0\ud560 \ub54c", - "hvac_mode_changed": "{entity_name} HVAC \ubaa8\ub4dc\uac00 \ubcc0\uacbd\ub420 \ub54c" + "current_humidity_changed": "{entity_name}\uc774(\uac00) \uc2b5\ub3c4 \ubcc0\ud654\ub97c \uac10\uc9c0\ud588\uc744 \ub54c", + "current_temperature_changed": "{entity_name}\uc774(\uac00) \uc628\ub3c4 \ubcc0\ud654\ub97c \uac10\uc9c0\ud588\uc744 \ub54c", + "hvac_mode_changed": "{entity_name}\uc758 HVAC \ubaa8\ub4dc\uac00 \ubcc0\uacbd\ub418\uc5c8\uc744 \ub54c" } }, "state": { diff --git a/homeassistant/components/climate/translations/zh-Hans.json b/homeassistant/components/climate/translations/zh-Hans.json index 9927cd679ae..a93125525e1 100644 --- a/homeassistant/components/climate/translations/zh-Hans.json +++ b/homeassistant/components/climate/translations/zh-Hans.json @@ -5,8 +5,8 @@ "set_preset_mode": "\u66f4\u6539 {entity_name} \u9884\u8bbe\u6a21\u5f0f" }, "condition_type": { - "is_hvac_mode": "{entity_name} \u88ab\u8bbe\u4e3a\u6307\u5b9a\u7684\u7a7a\u8c03\u6a21\u5f0f", - "is_preset_mode": "{entity_name} \u88ab\u8bbe\u4e3a\u6307\u5b9a\u7684\u9884\u8bbe\u6a21\u5f0f" + "is_hvac_mode": "{entity_name} \u5df2\u8bbe\u4e3a\u6307\u5b9a\u7684\u7a7a\u8c03\u6a21\u5f0f", + "is_preset_mode": "{entity_name} \u5df2\u8bbe\u4e3a\u6307\u5b9a\u7684\u9884\u8bbe\u6a21\u5f0f" }, "trigger_type": { "current_humidity_changed": "{entity_name} \u6d4b\u91cf\u7684\u5ba4\u5185\u6e7f\u5ea6\u53d8\u5316", diff --git a/homeassistant/components/cloud/translations/hu.json b/homeassistant/components/cloud/translations/hu.json index a2bea167b5e..8301806831b 100644 --- a/homeassistant/components/cloud/translations/hu.json +++ b/homeassistant/components/cloud/translations/hu.json @@ -2,6 +2,8 @@ "system_health": { "info": { "alexa_enabled": "Alexa enged\u00e9lyezve", + "can_reach_cert_server": "Tan\u00fas\u00edtv\u00e1nykiszolg\u00e1l\u00f3 el\u00e9r\u00e9se", + "can_reach_cloud": "Home Assistant Cloud el\u00e9r\u00e9se", "can_reach_cloud_auth": "Hiteles\u00edt\u00e9si kiszolg\u00e1l\u00f3 el\u00e9r\u00e9se", "google_enabled": "Google enged\u00e9lyezve", "logged_in": "Bejelentkezve", diff --git a/homeassistant/components/cloud/translations/id.json b/homeassistant/components/cloud/translations/id.json new file mode 100644 index 00000000000..1cff542796c --- /dev/null +++ b/homeassistant/components/cloud/translations/id.json @@ -0,0 +1,16 @@ +{ + "system_health": { + "info": { + "alexa_enabled": "Alexa Diaktifkan", + "can_reach_cert_server": "Keterjangkauan Server Sertifikat", + "can_reach_cloud": "Keterjangkauan Home Assistant Cloud", + "can_reach_cloud_auth": "Keterjangkauan Server Autentikasi", + "google_enabled": "Google Diaktifkan", + "logged_in": "Masuk", + "relayer_connected": "Relayer Terhubung", + "remote_connected": "Terhubung Jarak Jauh", + "remote_enabled": "Kontrol Jarak Jauh Diaktifkan", + "subscription_expiration": "Masa Kedaluwarsa Langganan" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloud/translations/ko.json b/homeassistant/components/cloud/translations/ko.json new file mode 100644 index 00000000000..94b939502de --- /dev/null +++ b/homeassistant/components/cloud/translations/ko.json @@ -0,0 +1,16 @@ +{ + "system_health": { + "info": { + "alexa_enabled": "Alexa \ud65c\uc131\ud654", + "can_reach_cert_server": "\uc778\uc99d\uc11c \uc11c\ubc84 \uc5f0\uacb0", + "can_reach_cloud": "Home Assistant Cloud \uc5f0\uacb0", + "can_reach_cloud_auth": "\uc778\uc99d \uc11c\ubc84 \uc5f0\uacb0", + "google_enabled": "Google \uc5b4\uc2dc\uc2a4\ud134\ud2b8 \ud65c\uc131\ud654", + "logged_in": "\ub85c\uadf8\uc778", + "relayer_connected": "\uc911\uacc4\uae30 \uc5f0\uacb0", + "remote_connected": "\uc6d0\uaca9 \uc81c\uc5b4 \uc5f0\uacb0", + "remote_enabled": "\uc6d0\uaca9 \uc81c\uc5b4 \ud65c\uc131\ud654", + "subscription_expiration": "\uad6c\ub3c5 \ub9cc\ub8cc" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloud/translations/nl.json b/homeassistant/components/cloud/translations/nl.json index d9aa78afecb..0ad7a528822 100644 --- a/homeassistant/components/cloud/translations/nl.json +++ b/homeassistant/components/cloud/translations/nl.json @@ -2,6 +2,7 @@ "system_health": { "info": { "alexa_enabled": "Alexa ingeschakeld", + "can_reach_cert_server": "Bereik Certificaatserver", "can_reach_cloud": "Bereik Home Assistant Cloud", "can_reach_cloud_auth": "Bereik authenticatieserver", "google_enabled": "Google ingeschakeld", diff --git a/homeassistant/components/cloudflare/translations/hu.json b/homeassistant/components/cloudflare/translations/hu.json index fa13d00617f..fed6f22d536 100644 --- a/homeassistant/components/cloudflare/translations/hu.json +++ b/homeassistant/components/cloudflare/translations/hu.json @@ -1,11 +1,12 @@ { "config": { "abort": { - "unknown": "V\u00e1ratlan hiba" + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "invalid_auth": "\u00c9rv\u00e9nytelen autentik\u00e1ci\u00f3", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "invalid_zone": "\u00c9rv\u00e9nytelen z\u00f3na" }, "flow_title": "Cloudflare: {name}", diff --git a/homeassistant/components/cloudflare/translations/id.json b/homeassistant/components/cloudflare/translations/id.json new file mode 100644 index 00000000000..98286398ea8 --- /dev/null +++ b/homeassistant/components/cloudflare/translations/id.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "invalid_zone": "Zona tidak valid" + }, + "flow_title": "Cloudflare: {name}", + "step": { + "records": { + "data": { + "records": "Catatan" + }, + "title": "Pilih Catatan untuk Diperbarui" + }, + "user": { + "data": { + "api_token": "Token API" + }, + "description": "Integrasi ini memerlukan Token API yang dibuat dengan izin Zone:Zone:Read and Zone:DNS:Edit untuk semua zona di akun Anda.", + "title": "Hubungkan ke Cloudflare" + }, + "zone": { + "data": { + "zone": "Zona" + }, + "title": "Pilih Zona yang akan Diperbarui" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloudflare/translations/ko.json b/homeassistant/components/cloudflare/translations/ko.json index d4f4eee49a8..4dbe263a138 100644 --- a/homeassistant/components/cloudflare/translations/ko.json +++ b/homeassistant/components/cloudflare/translations/ko.json @@ -1,18 +1,34 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_zone": "\uc601\uc5ed\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, + "flow_title": "Cloudflare: {name}", "step": { + "records": { + "data": { + "records": "\ub808\ucf54\ub4dc" + }, + "title": "\uc5c5\ub370\uc774\ud2b8\ud560 \ub808\ucf54\ub4dc \uc120\ud0dd\ud558\uae30" + }, "user": { "data": { "api_token": "API \ud1a0\ud070" - } + }, + "description": "\uc774 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub294 \uacc4\uc815\uc758 \ubaa8\ub4e0 \uc601\uc5ed\uc5d0 \ub300\ud574 Zone:Zone:Read \ubc0f Zone:DNS:Edit \uad8c\ud55c\uc73c\ub85c \uc0dd\uc131\ub41c API \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4.", + "title": "Cloudflare\uc5d0 \uc5f0\uacb0\ud558\uae30" + }, + "zone": { + "data": { + "zone": "\uc601\uc5ed" + }, + "title": "\uc5c5\ub370\uc774\ud2b8\ud560 \uc601\uc5ed \uc120\ud0dd\ud558\uae30" } } } diff --git a/homeassistant/components/cloudflare/translations/nl.json b/homeassistant/components/cloudflare/translations/nl.json index 35c765d5da7..e4f6c1180e2 100644 --- a/homeassistant/components/cloudflare/translations/nl.json +++ b/homeassistant/components/cloudflare/translations/nl.json @@ -26,7 +26,8 @@ "zone": { "data": { "zone": "Zone" - } + }, + "title": "Kies de zone die u wilt bijwerken" } } } diff --git a/homeassistant/components/configurator/translations/id.json b/homeassistant/components/configurator/translations/id.json index 759af513228..f345a39417b 100644 --- a/homeassistant/components/configurator/translations/id.json +++ b/homeassistant/components/configurator/translations/id.json @@ -1,7 +1,7 @@ { "state": { "_": { - "configure": "Konfigurasi", + "configure": "Konfigurasikan", "configured": "Terkonfigurasi" } }, diff --git a/homeassistant/components/control4/translations/hu.json b/homeassistant/components/control4/translations/hu.json index 3b2d79a34a7..68cb4fe23a9 100644 --- a/homeassistant/components/control4/translations/hu.json +++ b/homeassistant/components/control4/translations/hu.json @@ -2,6 +2,20 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "IP c\u00edm", + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/control4/translations/id.json b/homeassistant/components/control4/translations/id.json new file mode 100644 index 00000000000..4b8033c0873 --- /dev/null +++ b/homeassistant/components/control4/translations/id.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Alamat IP", + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Masukkan detail akun Control4 Anda dan alamat IP pengontrol lokal Anda." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Interval pembaruan dalam detik" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/control4/translations/nl.json b/homeassistant/components/control4/translations/nl.json index 1c4e7de05c9..d591b4631a4 100644 --- a/homeassistant/components/control4/translations/nl.json +++ b/homeassistant/components/control4/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, @@ -16,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Seconden tussen updates" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/coolmaster/translations/id.json b/homeassistant/components/coolmaster/translations/id.json new file mode 100644 index 00000000000..d12c10da25a --- /dev/null +++ b/homeassistant/components/coolmaster/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "error": { + "cannot_connect": "Gagal terhubung", + "no_units": "Tidak dapat menemukan perangkat HVAC di host CoolMasterNet." + }, + "step": { + "user": { + "data": { + "cool": "Mendukung mode dingin", + "dry": "Mendukung mode kering", + "fan_only": "Mendukung mode kipas saja", + "heat": "Mendukung mode panas", + "heat_cool": "Mendukung mode panas/dingin otomatis", + "host": "Host", + "off": "Bisa dimatikan" + }, + "title": "Siapkan detail koneksi CoolMasterNet Anda." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coronavirus/translations/hu.json b/homeassistant/components/coronavirus/translations/hu.json index fcee85c40e8..631454ec045 100644 --- a/homeassistant/components/coronavirus/translations/hu.json +++ b/homeassistant/components/coronavirus/translations/hu.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Ez az orsz\u00e1g m\u00e1r konfigur\u00e1lva van." + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" }, "step": { "user": { diff --git a/homeassistant/components/coronavirus/translations/id.json b/homeassistant/components/coronavirus/translations/id.json new file mode 100644 index 00000000000..e2626d16abb --- /dev/null +++ b/homeassistant/components/coronavirus/translations/id.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "step": { + "user": { + "data": { + "country": "Negara" + }, + "title": "Pilih negara untuk dipantau" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coronavirus/translations/nl.json b/homeassistant/components/coronavirus/translations/nl.json index d306894f7d0..fed3101b38e 100644 --- a/homeassistant/components/coronavirus/translations/nl.json +++ b/homeassistant/components/coronavirus/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Dit land is al geconfigureerd." + "already_configured": "Service is al geconfigureerd" }, "step": { "user": { diff --git a/homeassistant/components/cover/translations/hu.json b/homeassistant/components/cover/translations/hu.json index 6d48cca1251..87bd1c241c6 100644 --- a/homeassistant/components/cover/translations/hu.json +++ b/homeassistant/components/cover/translations/hu.json @@ -6,7 +6,8 @@ "open": "{entity_name} nyit\u00e1sa", "open_tilt": "{entity_name} d\u00f6nt\u00e9s nyit\u00e1sa", "set_position": "{entity_name} poz\u00edci\u00f3j\u00e1nak be\u00e1ll\u00edt\u00e1sa", - "set_tilt_position": "{entity_name} d\u00f6nt\u00e9si poz\u00edci\u00f3j\u00e1nak be\u00e1ll\u00edt\u00e1sa" + "set_tilt_position": "{entity_name} d\u00f6nt\u00e9si poz\u00edci\u00f3j\u00e1nak be\u00e1ll\u00edt\u00e1sa", + "stop": "{entity_name} meg\u00e1ll\u00edt\u00e1sa" }, "condition_type": { "is_closed": "{entity_name} z\u00e1rva van", diff --git a/homeassistant/components/cover/translations/id.json b/homeassistant/components/cover/translations/id.json index b38fcf86a17..d07f2f23ad2 100644 --- a/homeassistant/components/cover/translations/id.json +++ b/homeassistant/components/cover/translations/id.json @@ -1,9 +1,36 @@ { + "device_automation": { + "action_type": { + "close": "Tutup {entity_name}", + "close_tilt": "Tutup miring {entity_name}", + "open": "Buka {entity_name}", + "open_tilt": "Buka miring {entity_name}", + "set_position": "Tetapkan posisi {entity_name}", + "set_tilt_position": "Setel posisi miring {entity_name}", + "stop": "Hentikan {entity_name}" + }, + "condition_type": { + "is_closed": "{entity_name} tertutup", + "is_closing": "{entity_name} menutup", + "is_open": "{entity_name} terbuka", + "is_opening": "{entity_name} membuka", + "is_position": "Posisi {entity_name} saat ini adalah", + "is_tilt_position": "Posisi miring {entity_name} saat ini adalah" + }, + "trigger_type": { + "closed": "{entity_name} tertutup", + "closing": "{entity_name} menutup", + "opened": "{entity_name} terbuka", + "opening": "{entity_name} membuka", + "position": "Perubahan posisi {entity_name}", + "tilt_position": "Perubahan posisi kemiringan {entity_name}" + } + }, "state": { "_": { "closed": "Tertutup", "closing": "Menutup", - "open": "Buka", + "open": "Terbuka", "opening": "Membuka", "stopped": "Terhenti" } diff --git a/homeassistant/components/cover/translations/ko.json b/homeassistant/components/cover/translations/ko.json index 0a666a8bd82..71a48bd532d 100644 --- a/homeassistant/components/cover/translations/ko.json +++ b/homeassistant/components/cover/translations/ko.json @@ -1,28 +1,29 @@ { "device_automation": { "action_type": { - "close": "{entity_name} \ub2eb\uae30", - "close_tilt": "{entity_name} \ub2eb\uae30", - "open": "{entity_name} \uc5f4\uae30", - "open_tilt": "{entity_name} \uc5f4\uae30", - "set_position": "{entity_name} \uac1c\ud3d0 \uc704\uce58 \uc124\uc815\ud558\uae30", - "set_tilt_position": "{entity_name} \uac1c\ud3d0 \uae30\uc6b8\uae30 \uc124\uc815\ud558\uae30" + "close": "{entity_name}\uc744(\ub97c) \ub2eb\uae30", + "close_tilt": "{entity_name}\uc744(\ub97c) \ub2eb\uae30", + "open": "{entity_name}\uc744(\ub97c) \uc5f4\uae30", + "open_tilt": "{entity_name}\uc744(\ub97c) \uc5f4\uae30", + "set_position": "{entity_name}\uc758 \uac1c\ud3d0 \uc704\uce58 \uc124\uc815\ud558\uae30", + "set_tilt_position": "{entity_name}\uc758 \uac1c\ud3d0 \uae30\uc6b8\uae30 \uc124\uc815\ud558\uae30", + "stop": "{entity_name}\uc744(\ub97c) \uc815\uc9c0\ud558\uae30" }, "condition_type": { - "is_closed": "{entity_name} \uc774(\uac00) \ub2eb\ud600 \uc788\uc73c\uba74", - "is_closing": "{entity_name} \uc774(\uac00) \ub2eb\ud788\ub294 \uc911\uc774\uba74", - "is_open": "{entity_name} \uc774(\uac00) \uc5f4\ub824 \uc788\uc73c\uba74", - "is_opening": "{entity_name} \uc774(\uac00) \uc5f4\ub9ac\ub294 \uc911\uc774\uba74", - "is_position": "\ud604\uc7ac {entity_name} \uac1c\ud3d0 \uc704\uce58\uac00 ~ \uc774\uba74", - "is_tilt_position": "\ud604\uc7ac {entity_name} \uac1c\ud3d0 \uae30\uc6b8\uae30\uac00 ~ \uc774\uba74" + "is_closed": "{entity_name}\uc774(\uac00) \ub2eb\ud600 \uc788\uc73c\uba74", + "is_closing": "{entity_name}\uc774(\uac00) \ub2eb\ud788\ub294 \uc911\uc774\uba74", + "is_open": "{entity_name}\uc774(\uac00) \uc5f4\ub824 \uc788\uc73c\uba74", + "is_opening": "{entity_name}\uc774(\uac00) \uc5f4\ub9ac\ub294 \uc911\uc774\uba74", + "is_position": "\ud604\uc7ac {entity_name}\uc758 \uac1c\ud3d0 \uc704\uce58\uac00 ~ \uc774\uba74", + "is_tilt_position": "\ud604\uc7ac {entity_name}\uc758 \uac1c\ud3d0 \uae30\uc6b8\uae30\uac00 ~ \uc774\uba74" }, "trigger_type": { - "closed": "{entity_name} \uc774(\uac00) \ub2eb\ud790 \ub54c", - "closing": "{entity_name} \uc774(\uac00) \ub2eb\ud788\ub294 \uc911\uc77c \ub54c", - "opened": "{entity_name} \uc774(\uac00) \uc5f4\ub9b4 \ub54c", - "opening": "{entity_name} \uc774(\uac00) \uc5f4\ub9ac\ub294 \uc911\uc77c \ub54c", - "position": "{entity_name} \uac1c\ud3d0 \uc704\uce58\uac00 \ubcc0\ud560 \ub54c", - "tilt_position": "{entity_name} \uac1c\ud3d0 \uae30\uc6b8\uae30\uac00 \ubcc0\ud560 \ub54c" + "closed": "{entity_name}\uc774(\uac00) \ub2eb\ud614\uc744 \ub54c", + "closing": "{entity_name}\uc774(\uac00) \ub2eb\ud788\ub294 \uc911\uc77c \ub54c", + "opened": "{entity_name}\uc774(\uac00) \uc5f4\ub838\uc744 \ub54c", + "opening": "{entity_name}\uc774(\uac00) \uc5f4\ub9ac\ub294 \uc911\uc77c \ub54c", + "position": "{entity_name}\uc758 \uac1c\ud3d0 \uc704\uce58\uac00 \ubcc0\ud560 \ub54c", + "tilt_position": "{entity_name}\uc758 \uac1c\ud3d0 \uae30\uc6b8\uae30\uac00 \ubcc0\ud560 \ub54c" } }, "state": { diff --git a/homeassistant/components/cover/translations/zh-Hans.json b/homeassistant/components/cover/translations/zh-Hans.json index 04b25ad7cb8..765fcbeebe0 100644 --- a/homeassistant/components/cover/translations/zh-Hans.json +++ b/homeassistant/components/cover/translations/zh-Hans.json @@ -2,8 +2,11 @@ "device_automation": { "action_type": { "close": "\u5173\u95ed {entity_name}", + "close_tilt": "\u5173\u95ed {entity_name}", "open": "\u6253\u5f00 {entity_name}", + "open_tilt": "\u65cb\u5f00 {entity_name}", "set_position": "\u8bbe\u7f6e {entity_name} \u7684\u4f4d\u7f6e", + "set_tilt_position": "\u8bbe\u7f6e {entity_name} \u7684\u503e\u659c\u4f4d\u7f6e", "stop": "\u505c\u6b62 {entity_name}" }, "condition_type": { @@ -19,7 +22,8 @@ "closing": "{entity_name} \u6b63\u5728\u5173\u95ed", "opened": "{entity_name} \u5df2\u6253\u5f00", "opening": "{entity_name} \u6b63\u5728\u6253\u5f00", - "position": "{entity_name} \u7684\u4f4d\u7f6e\u53d8\u5316" + "position": "{entity_name} \u7684\u4f4d\u7f6e\u53d8\u5316", + "tilt_position": "{entity_name} \u7684\u503e\u659c\u4f4d\u7f6e\u53d8\u5316" } }, "state": { diff --git a/homeassistant/components/daikin/translations/hu.json b/homeassistant/components/daikin/translations/hu.json index ef589eb7f6d..f1cb7eab8f6 100644 --- a/homeassistant/components/daikin/translations/hu.json +++ b/homeassistant/components/daikin/translations/hu.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "unknown": "V\u00e1ratlan hiba" + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "user": { diff --git a/homeassistant/components/daikin/translations/id.json b/homeassistant/components/daikin/translations/id.json new file mode 100644 index 00000000000..8b7cfb5460e --- /dev/null +++ b/homeassistant/components/daikin/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "host": "Host", + "password": "Kata Sandi" + }, + "description": "Masukkan Alamat IP perangkat AC Daikin Anda. \n\nPerhatikan bahwa Kunci API dan Kata Sandi hanya digunakan untuk perangkat BRP072Cxx dan SKYFi.", + "title": "Konfigurasi AC Daikin" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/daikin/translations/nl.json b/homeassistant/components/daikin/translations/nl.json index e4cf54eb365..706a81b5f7f 100644 --- a/homeassistant/components/daikin/translations/nl.json +++ b/homeassistant/components/daikin/translations/nl.json @@ -16,7 +16,7 @@ "host": "Host", "password": "Wachtwoord" }, - "description": "Voer het IP-adres van uw Daikin AC in.", + "description": "Voer IP-adres van uw Daikin AC in.\n\nLet op dat API-sleutel en Wachtwoord alleen worden gebruikt door respectievelijk BRP072Cxx en SKYFi apparaten.", "title": "Daikin AC instellen" } } diff --git a/homeassistant/components/deconz/translations/bg.json b/homeassistant/components/deconz/translations/bg.json index 0aef2e3ec98..24e36ecbe55 100644 --- a/homeassistant/components/deconz/translations/bg.json +++ b/homeassistant/components/deconz/translations/bg.json @@ -13,8 +13,8 @@ "flow_title": "deCONZ Zigbee \u0448\u043b\u044e\u0437 ({host})", "step": { "hassio_confirm": { - "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 Home Assistant \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0437\u0432\u0430 \u0441 deCONZ \u0431\u0430\u0437\u043e\u0432\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d \u043e\u0442 \u0434\u043e\u0431\u0430\u0432\u043a\u0430\u0442\u0430 \u0437\u0430 hass.io {addon}?", - "title": "deCONZ Zigbee \u0431\u0430\u0437\u043e\u0432\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f \u0447\u0440\u0435\u0437 Hass.io \u0434\u043e\u0431\u0430\u0432\u043a\u0430" + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 Home Assistant \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0437\u0432\u0430 \u0441 deCONZ \u0431\u0430\u0437\u043e\u0432\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d \u043e\u0442 \u0434\u043e\u0431\u0430\u0432\u043a\u0430\u0442\u0430 \u0437\u0430 Supervisor {addon}?", + "title": "deCONZ Zigbee \u0431\u0430\u0437\u043e\u0432\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f \u0447\u0440\u0435\u0437 Supervisor \u0434\u043e\u0431\u0430\u0432\u043a\u0430" }, "link": { "description": "\u041e\u0442\u043a\u043b\u044e\u0447\u0438 deCONZ \u0448\u043b\u044e\u0437\u0430 \u0437\u0430 \u0434\u0430 \u0441\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0430 \u0441 Home Assistant.\n\n1. \u041e\u0442\u0438\u0434\u0435\u0442\u0435 \u043d\u0430 deCONZ Settings -> Gateway -> Advanced\n2. \u041d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0435 \u0431\u0443\u0442\u043e\u043d\u0430 \"Authenticate app\"", diff --git a/homeassistant/components/deconz/translations/ca.json b/homeassistant/components/deconz/translations/ca.json index 49374ee123f..5957dc88c03 100644 --- a/homeassistant/components/deconz/translations/ca.json +++ b/homeassistant/components/deconz/translations/ca.json @@ -14,8 +14,8 @@ "flow_title": "Passarel\u00b7la d'enlla\u00e7 deCONZ Zigbee ({host})", "step": { "hassio_confirm": { - "description": "Vols configurar Home Assistant perqu\u00e8 es connecti amb la passarel\u00b7la deCONZ proporcionada pel complement de Hass.io: {addon}?", - "title": "Passarel\u00b7la d'enlla\u00e7 deCONZ Zigbee (complement de Hass.io)" + "description": "Vols configurar Home Assistant perqu\u00e8 es connecti amb la passarel\u00b7la deCONZ proporcionada pel complement de Hass.io {addon}?", + "title": "Passarel\u00b7la d'enlla\u00e7 deCONZ Zigbee via complement de Hass.io" }, "link": { "description": "Desbloqueja la teva passarel\u00b7la d'enlla\u00e7 deCONZ per a registrar-te amb Home Assistant.\n\n1. V\u00e9s a la configuraci\u00f3 del sistema deCONZ -> Passarel\u00b7la -> Avan\u00e7at\n2. Prem el bot\u00f3 \"Autenticar applicaci\u00f3\"", diff --git a/homeassistant/components/deconz/translations/cs.json b/homeassistant/components/deconz/translations/cs.json index 52cbd607b7f..7e08a89ec31 100644 --- a/homeassistant/components/deconz/translations/cs.json +++ b/homeassistant/components/deconz/translations/cs.json @@ -14,8 +14,8 @@ "flow_title": "Br\u00e1na deCONZ ZigBee ({host})", "step": { "hassio_confirm": { - "description": "Chcete nakonfigurovat slu\u017ebu Home Assistant pro p\u0159ipojen\u00ed k deCONZ br\u00e1n\u011b pomoc\u00ed hass.io {addon}?", - "title": "deCONZ Zigbee br\u00e1na prost\u0159ednictv\u00edm dopl\u0148ku Hass.io" + "description": "Chcete nakonfigurovat slu\u017ebu Home Assistant pro p\u0159ipojen\u00ed k deCONZ br\u00e1n\u011b pomoc\u00ed Supervisor {addon}?", + "title": "deCONZ Zigbee br\u00e1na prost\u0159ednictv\u00edm dopl\u0148ku Supervisor" }, "link": { "description": "Odemkn\u011bte br\u00e1nu deCONZ pro registraci v Home Assistant.\n\n 1. P\u0159ejd\u011bte na Nastaven\u00ed deCONZ - > Br\u00e1na - > Pokro\u010dil\u00e9\n 2. Stiskn\u011bte tla\u010d\u00edtko \"Ov\u011b\u0159it aplikaci\"", diff --git a/homeassistant/components/deconz/translations/da.json b/homeassistant/components/deconz/translations/da.json index 50cdd242ad0..be165a206bf 100644 --- a/homeassistant/components/deconz/translations/da.json +++ b/homeassistant/components/deconz/translations/da.json @@ -13,8 +13,8 @@ "flow_title": "deCONZ Zigbee gateway ({host})", "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til deCONZ-gateway'en leveret af Hass.io-tilf\u00f8jelsen {addon}?", - "title": "deCONZ Zigbee-gateway via Hass.io-tilf\u00f8jelse" + "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til deCONZ-gateway'en leveret af Supervisor-tilf\u00f8jelsen {addon}?", + "title": "deCONZ Zigbee-gateway via Supervisor-tilf\u00f8jelse" }, "link": { "description": "L\u00e5s din deCONZ-gateway op for at registrere dig med Home Assistant. \n\n 1. G\u00e5 til deCONZ settings -> Gateway -> Advanced\n 2. Tryk p\u00e5 knappen \"Authenticate app\"", diff --git a/homeassistant/components/deconz/translations/de.json b/homeassistant/components/deconz/translations/de.json index 75b807b8848..a8575d212d6 100644 --- a/homeassistant/components/deconz/translations/de.json +++ b/homeassistant/components/deconz/translations/de.json @@ -14,8 +14,8 @@ "flow_title": "deCONZ Zigbee Gateway", "step": { "hassio_confirm": { - "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass er eine Verbindung mit dem deCONZ Gateway herstellt, der vom Hass.io Add-on {addon} bereitgestellt wird?", - "title": "deCONZ Zigbee Gateway \u00fcber das Hass.io Add-on" + "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass er eine Verbindung mit dem deCONZ Gateway herstellt, der vom Supervisor Add-on {addon} bereitgestellt wird?", + "title": "deCONZ Zigbee Gateway \u00fcber das Supervisor Add-on" }, "link": { "description": "Entsperre dein deCONZ-Gateway, um es bei Home Assistant zu registrieren. \n\n 1. Gehe in die deCONZ-Systemeinstellungen \n 2. Dr\u00fccke die Taste \"Gateway entsperren\"", diff --git a/homeassistant/components/deconz/translations/es-419.json b/homeassistant/components/deconz/translations/es-419.json index e454d080ad7..e439d1da949 100644 --- a/homeassistant/components/deconz/translations/es-419.json +++ b/homeassistant/components/deconz/translations/es-419.json @@ -13,8 +13,8 @@ "flow_title": "Puerta de enlace Zigbee deCONZ ({host})", "step": { "hassio_confirm": { - "description": "\u00bfDesea configurar Home Assistant para conectarse a la puerta de enlace deCONZ proporcionada por el complemento hass.io {addon}?", - "title": "deCONZ Zigbee gateway a trav\u00e9s del complemento Hass.io" + "description": "\u00bfDesea configurar Home Assistant para conectarse a la puerta de enlace deCONZ proporcionada por el complemento Supervisor {addon}?", + "title": "deCONZ Zigbee gateway a trav\u00e9s del complemento Supervisor" }, "link": { "description": "Desbloquee su puerta de enlace deCONZ para registrarse con Home Assistant. \n\n 1. Vaya a Configuraci\u00f3n deCONZ - > Gateway - > Avanzado \n 2. Presione el bot\u00f3n \"Autenticar aplicaci\u00f3n\"", diff --git a/homeassistant/components/deconz/translations/es.json b/homeassistant/components/deconz/translations/es.json index 62ab509e268..b237d84fafc 100644 --- a/homeassistant/components/deconz/translations/es.json +++ b/homeassistant/components/deconz/translations/es.json @@ -14,8 +14,8 @@ "flow_title": "pasarela deCONZ Zigbee ({host})", "step": { "hassio_confirm": { - "description": "\u00bfQuieres configurar Home Assistant para que se conecte al gateway de deCONZ proporcionado por el add-on {addon} de hass.io?", - "title": "Add-on deCONZ Zigbee v\u00eda Hass.io" + "description": "\u00bfQuieres configurar Home Assistant para que se conecte al gateway de deCONZ proporcionado por el add-on {addon} de Supervisor?", + "title": "Add-on deCONZ Zigbee v\u00eda Supervisor" }, "link": { "description": "Desbloquea tu gateway de deCONZ para registrarte con Home Assistant.\n\n1. Dir\u00edgete a deCONZ Settings -> Gateway -> Advanced\n2. Pulsa el bot\u00f3n \"Authenticate app\"", diff --git a/homeassistant/components/deconz/translations/et.json b/homeassistant/components/deconz/translations/et.json index 9f6644ea186..b949208a664 100644 --- a/homeassistant/components/deconz/translations/et.json +++ b/homeassistant/components/deconz/translations/et.json @@ -15,7 +15,7 @@ "step": { "hassio_confirm": { "description": "Kas soovid seadistada Home Assistant-i \u00fchenduse deCONZ-l\u00fc\u00fcsiga, mida pakub Hass.io lisandmoodul {addon} ?", - "title": "deCONZ Zigbee v\u00e4rav Hass.io pistikprogrammi kaudu" + "title": "deCONZ Zigbee l\u00fc\u00fcs Hass.io lisandmooduli abil" }, "link": { "description": "Home Assistanti registreerumiseks ava deCONZ-i l\u00fc\u00fcs.\n\n 1. Mine deCONZ Settings - > Gateway - > Advanced\n 2. Vajuta nuppu \"Authenticate app\"", diff --git a/homeassistant/components/deconz/translations/fr.json b/homeassistant/components/deconz/translations/fr.json index b64819471a0..d24b592ac10 100644 --- a/homeassistant/components/deconz/translations/fr.json +++ b/homeassistant/components/deconz/translations/fr.json @@ -14,8 +14,8 @@ "flow_title": "Passerelle deCONZ Zigbee ({host})", "step": { "hassio_confirm": { - "description": "Voulez-vous configurer Home Assistant pour qu'il se connecte \u00e0 la passerelle deCONZ fournie par l'add-on hass.io {addon} ?", - "title": "Passerelle deCONZ Zigbee via l'add-on Hass.io" + "description": "Voulez-vous configurer Home Assistant pour qu'il se connecte \u00e0 la passerelle deCONZ fournie par le module compl\u00e9mentaire Hass.io {addon} ?", + "title": "Passerelle deCONZ Zigbee via le module compl\u00e9mentaire Hass.io" }, "link": { "description": "D\u00e9verrouillez votre passerelle deCONZ pour vous enregistrer avec Home Assistant. \n\n 1. Acc\u00e9dez aux param\u00e8tres avanc\u00e9s du syst\u00e8me deCONZ \n 2. Cliquez sur \"D\u00e9verrouiller la passerelle\"", diff --git a/homeassistant/components/deconz/translations/hu.json b/homeassistant/components/deconz/translations/hu.json index 4651a7c08e0..61322087cbf 100644 --- a/homeassistant/components/deconz/translations/hu.json +++ b/homeassistant/components/deconz/translations/hu.json @@ -2,8 +2,9 @@ "config": { "abort": { "already_configured": "A bridge m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "Az \u00e1tj\u00e1r\u00f3 konfigur\u00e1ci\u00f3s folyamata m\u00e1r folyamatban van.", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", "no_bridges": "Nem tal\u00e1ltam deCONZ bridget", + "no_hardware_available": "Nincs deCONZ-hoz csatlakoztatott r\u00e1di\u00f3hardver", "not_deconz_bridge": "Nem egy deCONZ \u00e1tj\u00e1r\u00f3", "updated_instance": "A deCONZ-p\u00e9ld\u00e1ny \u00faj \u00e1llom\u00e1sc\u00edmmel friss\u00edtve" }, @@ -13,7 +14,7 @@ "flow_title": "deCONZ Zigbee \u00e1tj\u00e1r\u00f3 ({host})", "step": { "hassio_confirm": { - "title": "deCONZ Zigbee \u00e1tj\u00e1r\u00f3 a Hass.io kieg\u00e9sz\u00edt\u0151 seg\u00edts\u00e9g\u00e9vel" + "title": "deCONZ Zigbee \u00e1tj\u00e1r\u00f3 a Supervisor kieg\u00e9sz\u00edt\u0151 seg\u00edts\u00e9g\u00e9vel" }, "link": { "description": "Oldja fel a deCONZ \u00e1tj\u00e1r\u00f3t a Home Assistant-ban val\u00f3 regisztr\u00e1l\u00e1shoz.\n\n1. Menjen a deCONZ rendszer be\u00e1ll\u00edt\u00e1sokhoz\n2. Nyomja meg az \"\u00c1tj\u00e1r\u00f3 felold\u00e1sa\" gombot", @@ -51,30 +52,30 @@ }, "trigger_type": { "remote_awakened": "A k\u00e9sz\u00fcl\u00e9k fel\u00e9bredt", - "remote_button_double_press": "\" {subtype} \" gombra k\u00e9tszer kattintottak", - "remote_button_long_press": "A \" {subtype} \" gomb folyamatosan lenyomva", - "remote_button_long_release": "A \" {subtype} \" gomb hossz\u00fa megnyom\u00e1s ut\u00e1n elengedve", - "remote_button_quadruple_press": "\" {subtype} \" gombra n\u00e9gyszer kattintottak", - "remote_button_quintuple_press": "\" {subtype} \" gombra \u00f6tsz\u00f6r kattintottak", - "remote_button_rotated": "A gomb elforgatva: \" {subtype} \"", - "remote_button_rotation_stopped": "A (z) \" {subtype} \" gomb forg\u00e1sa le\u00e1llt", - "remote_button_short_press": "\" {subtype} \" gomb lenyomva", - "remote_button_short_release": "\"{alt\u00edpus}\" gomb elengedve", - "remote_button_triple_press": "\" {subtype} \" gombra h\u00e1romszor kattintottak", - "remote_double_tap": "Az \" {subtype} \" eszk\u00f6z dupla kattint\u00e1sa", + "remote_button_double_press": "\"{subtype}\" gombra k\u00e9tszer kattintottak", + "remote_button_long_press": "A \"{subtype}\" gomb folyamatosan lenyomva", + "remote_button_long_release": "A \"{subtype}\" gomb hossz\u00fa megnyom\u00e1s ut\u00e1n elengedve", + "remote_button_quadruple_press": "\"{subtype}\" gombra n\u00e9gyszer kattintottak", + "remote_button_quintuple_press": "\"{subtype}\" gombra \u00f6tsz\u00f6r kattintottak", + "remote_button_rotated": "A gomb elforgatva: \"{subtype}\"", + "remote_button_rotation_stopped": "A (z) \"{subtype}\" gomb forg\u00e1sa le\u00e1llt", + "remote_button_short_press": "\"{subtype}\" gomb lenyomva", + "remote_button_short_release": "\"{subtype}\" gomb elengedve", + "remote_button_triple_press": "\"{subtype}\" gombra h\u00e1romszor kattintottak", + "remote_double_tap": "Az \"{subtype}\" eszk\u00f6z dupla kattint\u00e1sa", "remote_double_tap_any_side": "A k\u00e9sz\u00fcl\u00e9k b\u00e1rmelyik oldal\u00e1n dupl\u00e1n koppint.", "remote_falling": "K\u00e9sz\u00fcl\u00e9k szabades\u00e9sben", "remote_flip_180_degrees": "180 fokkal megd\u00f6nt\u00f6tt eszk\u00f6z", "remote_flip_90_degrees": "90 fokkal megd\u00f6nt\u00f6tt eszk\u00f6z", "remote_gyro_activated": "A k\u00e9sz\u00fcl\u00e9k meg lett r\u00e1zva", - "remote_moved": "Az eszk\u00f6z a \" {subtype} \"-lal felfel\u00e9 mozgatva", + "remote_moved": "Az eszk\u00f6z a \"{subtype}\"-lal felfel\u00e9 mozgatva", "remote_moved_any_side": "A k\u00e9sz\u00fcl\u00e9k valamelyik oldal\u00e1val felfel\u00e9 mozogott", - "remote_rotate_from_side_1": "Az eszk\u00f6z a \"1. oldalr\u00f3l\" a \" {subtype} \" -ra fordult", - "remote_rotate_from_side_2": "Az eszk\u00f6z a \"2. oldalr\u00f3l\" a \" {subtype} \" -ra fordult", - "remote_rotate_from_side_3": "Az eszk\u00f6z a \"3. oldalr\u00f3l\" a \" {subtype} \" -ra fordult", - "remote_rotate_from_side_4": "Az eszk\u00f6z a \"4. oldalr\u00f3l\" a \" {subtype} \" -ra fordult", - "remote_rotate_from_side_5": "Az eszk\u00f6z a \"5. oldalr\u00f3l\" a \" {subtype} \" -ra fordult", - "remote_rotate_from_side_6": "Az eszk\u00f6z a \"6. oldalr\u00f3l\" a \" {subtype} \" -ra fordult", + "remote_rotate_from_side_1": "Az eszk\u00f6z az \"1. oldalr\u00f3l\" a \"{subtype}\"-ra fordult", + "remote_rotate_from_side_2": "Az eszk\u00f6z a \"2. oldalr\u00f3l\" a \"{subtype}\"-ra fordult", + "remote_rotate_from_side_3": "Az eszk\u00f6z a \"3. oldalr\u00f3l\" a \"{subtype}\"-ra fordult", + "remote_rotate_from_side_4": "Az eszk\u00f6z a \"4. oldalr\u00f3l\" a \"{subtype}\"-ra fordult", + "remote_rotate_from_side_5": "Az eszk\u00f6z az \"5. oldalr\u00f3l\" a \"{subtype}\"-ra fordult", + "remote_rotate_from_side_6": "Az eszk\u00f6z a \"6. oldalr\u00f3l\" a \"{subtype}\"-ra fordult", "remote_turned_clockwise": "A k\u00e9sz\u00fcl\u00e9k az \u00f3ramutat\u00f3 j\u00e1r\u00e1s\u00e1val megegyez\u0151en fordult", "remote_turned_counter_clockwise": "A k\u00e9sz\u00fcl\u00e9k az \u00f3ramutat\u00f3 j\u00e1r\u00e1s\u00e1val ellent\u00e9tes ir\u00e1nyban fordult" } diff --git a/homeassistant/components/deconz/translations/id.json b/homeassistant/components/deconz/translations/id.json index 0d46cf7c176..d7fb26f8d52 100644 --- a/homeassistant/components/deconz/translations/id.json +++ b/homeassistant/components/deconz/translations/id.json @@ -2,15 +2,103 @@ "config": { "abort": { "already_configured": "Bridge sudah dikonfigurasi", - "no_bridges": "deCONZ bridges tidak ditemukan" + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "no_bridges": "deCONZ bridge tidak ditemukan", + "no_hardware_available": "Tidak ada perangkat keras radio yang terhubung ke deCONZ", + "not_deconz_bridge": "Bukan bridge deCONZ", + "updated_instance": "Instans deCONZ yang diperbarui dengan alamat host baru" }, "error": { "no_key": "Tidak bisa mendapatkan kunci API" }, + "flow_title": "Gateway Zigbee deCONZ ({host})", "step": { + "hassio_confirm": { + "description": "Ingin mengonfigurasi Home Assistant untuk terhubung ke gateway deCONZ yang disediakan oleh add-on Supervisor {addon}?", + "title": "Gateway Zigbee deCONZ melalui add-on Supervisor" + }, "link": { - "description": "Buka gerbang deCONZ Anda untuk mendaftar dengan Home Assistant. \n\n 1. Pergi ke pengaturan sistem deCONZ \n 2. Tekan tombol \"Buka Kunci Gateway\"", - "title": "Tautan dengan deCONZ" + "description": "Buka gateway deCONZ Anda untuk mendaftarkan ke Home Assistant. \n\n1. Buka pengaturan sistem deCONZ \n2. Tekan tombol \"Authenticate app\"", + "title": "Tautkan dengan deCONZ" + }, + "manual_input": { + "data": { + "host": "Host", + "port": "Port" + } + }, + "user": { + "data": { + "host": "Pilih gateway deCONZ yang ditemukan" + } + } + } + }, + "device_automation": { + "trigger_subtype": { + "both_buttons": "Kedua tombol", + "bottom_buttons": "Tombol bawah", + "button_1": "Tombol pertama", + "button_2": "Tombol kedua", + "button_3": "Tombol ketiga", + "button_4": "Tombol keempat", + "close": "Tutup", + "dim_down": "Redupkan", + "dim_up": "Terangkan", + "left": "Kiri", + "open": "Buka", + "right": "Kanan", + "side_1": "Sisi 1", + "side_2": "Sisi 2", + "side_3": "Sisi 3", + "side_4": "Sisi 4", + "side_5": "Sisi 5", + "side_6": "Sisi 6", + "top_buttons": "Tombol atas", + "turn_off": "Matikan", + "turn_on": "Nyalakan" + }, + "trigger_type": { + "remote_awakened": "Perangkat terbangun", + "remote_button_double_press": "Tombol \"{subtype}\" diklik dua kali", + "remote_button_long_press": "Tombol \"{subtype}\" terus ditekan", + "remote_button_long_release": "Tombol \"{subtype}\" dilepaskan setelah ditekan lama", + "remote_button_quadruple_press": "Tombol \"{subtype}\" diklik empat kali", + "remote_button_quintuple_press": "Tombol \"{subtype}\" diklik lima kali", + "remote_button_rotated": "Tombol diputar \"{subtype}\"", + "remote_button_rotated_fast": "Tombol diputar cepat \"{subtype}\"", + "remote_button_rotation_stopped": "Pemutaran tombol \"{subtype}\" berhenti", + "remote_button_short_press": "Tombol \"{subtype}\" ditekan", + "remote_button_short_release": "Tombol \"{subtype}\" dilepaskan", + "remote_button_triple_press": "Tombol \"{subtype}\" diklik tiga kali", + "remote_double_tap": "Perangkat \"{subtype}\" diketuk dua kali", + "remote_double_tap_any_side": "Perangkat diketuk dua kali di sisi mana pun", + "remote_falling": "Perangkat jatuh bebas", + "remote_flip_180_degrees": "Perangkat dibalik 180 derajat", + "remote_flip_90_degrees": "Perangkat dibalik 90 derajat", + "remote_gyro_activated": "Perangkat diguncangkan", + "remote_moved": "Perangkat dipindahkan dengan \"{subtype}\" ke atas", + "remote_moved_any_side": "Perangkat dipindahkan dengan sisi mana pun menghadap ke atas", + "remote_rotate_from_side_1": "Perangkat diputar dari \"sisi 1\" ke \"{subtype}\"", + "remote_rotate_from_side_2": "Perangkat diputar dari \"sisi 2\" ke \"{subtype}\"", + "remote_rotate_from_side_3": "Perangkat diputar dari \"sisi 3\" ke \"{subtype}\"", + "remote_rotate_from_side_4": "Perangkat diputar dari \"sisi 4\" ke \"{subtype}\"", + "remote_rotate_from_side_5": "Perangkat diputar dari \"sisi 5\" ke \"{subtype}\"", + "remote_rotate_from_side_6": "Perangkat diputar dari \"sisi 6\" ke \"{subtype}\"", + "remote_turned_clockwise": "Perangkat diputar searah jarum jam", + "remote_turned_counter_clockwise": "Perangkat diputar berlawanan arah jarum jam" + } + }, + "options": { + "step": { + "deconz_devices": { + "data": { + "allow_clip_sensor": "Izinkan sensor CLIP deCONZ", + "allow_deconz_groups": "Izinkan grup lampu deCONZ", + "allow_new_devices": "Izinkan penambahan otomatis perangkat baru" + }, + "description": "Konfigurasikan visibilitas jenis perangkat deCONZ", + "title": "Opsi deCONZ" } } } diff --git a/homeassistant/components/deconz/translations/it.json b/homeassistant/components/deconz/translations/it.json index 00ecd316da7..fd81ebad8cf 100644 --- a/homeassistant/components/deconz/translations/it.json +++ b/homeassistant/components/deconz/translations/it.json @@ -15,7 +15,7 @@ "step": { "hassio_confirm": { "description": "Vuoi configurare Home Assistant per connettersi al gateway deCONZ fornito dal componente aggiuntivo di Hass.io: {addon}?", - "title": "Gateway Pigmee deCONZ tramite il componente aggiuntivo di Hass.io" + "title": "Gateway deCONZ Zigbee tramite il componente aggiuntivo di Hass.io" }, "link": { "description": "Sblocca il tuo gateway deCONZ per registrarti con Home Assistant.\n\n1. Vai a Impostazioni deCONZ -> Gateway -> Avanzate\n2. Premere il pulsante \"Autentica app\"", diff --git a/homeassistant/components/deconz/translations/ko.json b/homeassistant/components/deconz/translations/ko.json index bd8aef75dd6..ade0f11b759 100644 --- a/homeassistant/components/deconz/translations/ko.json +++ b/homeassistant/components/deconz/translations/ko.json @@ -4,6 +4,7 @@ "already_configured": "\ube0c\ub9ac\uc9c0\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "no_bridges": "\ubc1c\uacac\ub41c deCONZ \ube0c\ub9ac\uc9c0\uac00 \uc5c6\uc2b5\ub2c8\ub2e4", + "no_hardware_available": "deCONZ\uc5d0 \uc5f0\uacb0\ub41c \ubb34\uc120 \ud558\ub4dc\uc6e8\uc5b4\uac00 \uc5c6\uc2b5\ub2c8\ub2e4", "not_deconz_bridge": "deCONZ \ube0c\ub9ac\uc9c0\uac00 \uc544\ub2d9\ub2c8\ub2e4", "updated_instance": "deCONZ \uc778\uc2a4\ud134\uc2a4\ub97c \uc0c8\ub85c\uc6b4 \ud638\uc2a4\ud2b8 \uc8fc\uc18c\ub85c \uc5c5\ub370\uc774\ud2b8\ud588\uc2b5\ub2c8\ub2e4" }, @@ -13,11 +14,11 @@ "flow_title": "deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774 ({host})", "step": { "hassio_confirm": { - "description": "Hass.io {addon} \uc560\ub4dc\uc628\uc5d0\uc11c \uc81c\uacf5\ub41c deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant \ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Hass.io \uc560\ub4dc\uc628\uc758 deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774" + "description": "Hass.io {addon} \ucd94\uac00 \uae30\ub2a5\uc5d0\uc11c \uc81c\uacf5\ub41c deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Hass.io \ucd94\uac00 \uae30\ub2a5\uc758 deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774" }, "link": { - "description": "deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \uc5b8\ub77d\ud558\uc5ec Home Assistant \uc5d0 \uc5f0\uacb0\ud558\uae30.\n\n1. deCONZ \uc2dc\uc2a4\ud15c \uc124\uc815\uc73c\ub85c \uc774\ub3d9\ud558\uc138\uc694\n2. \"Authenticate app\" \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694", + "description": "Home Assistant\uc5d0 \ub4f1\ub85d\ud558\ub824\uba74 deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \uc7a0\uae08 \ud574\uc81c\ud574\uc8fc\uc138\uc694.\n\n 1. deCONZ \uc124\uc815 -> \uac8c\uc774\ud2b8\uc6e8\uc774 -> \uace0\uae09\uc73c\ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694\n 2. \"\uc571 \uc778\uc99d\ud558\uae30\" \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694", "title": "deCONZ \uc5f0\uacb0\ud558\uae30" }, "manual_input": { @@ -58,33 +59,34 @@ "turn_on": "\ucf1c\uae30" }, "trigger_type": { - "remote_awakened": "\uae30\uae30 \uc808\uc804 \ubaa8\ub4dc \ud574\uc81c\ub420 \ub54c", - "remote_button_double_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub450 \ubc88 \ub20c\ub9b4 \ub54c", - "remote_button_long_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uacc4\uc18d \ub20c\ub824\uc9c8 \ub54c", - "remote_button_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc190\uc744 \ub5c4 \ub54c", - "remote_button_quadruple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub124 \ubc88 \ub20c\ub9b4 \ub54c", - "remote_button_quintuple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub2e4\uc12f \ubc88 \ub20c\ub9b4 \ub54c", - "remote_button_rotated": "\"{subtype}\" \ub85c \ubc84\ud2bc\uc774 \ud68c\uc804\ub420 \ub54c", - "remote_button_rotation_stopped": "\"{subtype}\" \ub85c \ubc84\ud2bc\uc774 \ud68c\uc804\uc744 \uba48\ucd9c \ub54c", - "remote_button_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub9b4 \ub54c", - "remote_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5c4 \ub54c", - "remote_button_triple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uc138 \ubc88 \ub20c\ub9b4 \ub54c", - "remote_double_tap": "\uae30\uae30\uc758 \"{subtype}\" \uac00 \ub354\ube14 \ud0ed \ub420 \ub54c", - "remote_double_tap_any_side": "\uae30\uae30\uc758 \uc544\ubb34 \uba74\uc774\ub098 \ub354\ube14 \ud0ed \ub420 \ub54c", + "remote_awakened": "\uae30\uae30\uc758 \uc808\uc804 \ubaa8\ub4dc\uac00 \ud574\uc81c\ub418\uc5c8\uc744 \ub54c", + "remote_button_double_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub450 \ubc88 \ub20c\ub838\uc744 \ub54c", + "remote_button_long_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uacc4\uc18d \ub20c\ub838\uc744 \ub54c", + "remote_button_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \ub5bc\uc600\uc744 \ub54c", + "remote_button_quadruple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub124 \ubc88 \ub20c\ub838\uc744 \ub54c", + "remote_button_quintuple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub2e4\uc12f \ubc88 \ub20c\ub838\uc744 \ub54c", + "remote_button_rotated": "\"{subtype}\"(\uc73c)\ub85c \ubc84\ud2bc\uc774 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "remote_button_rotated_fast": "\"{subtype}\"(\uc73c)\ub85c \ubc84\ud2bc\uc774 \ube60\ub974\uac8c \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "remote_button_rotation_stopped": "\"{subtype}\"(\uc73c)\ub85c \ubc84\ud2bc\ud68c\uc804\uc774 \uba48\ucd94\uc5c8\uc744 \ub54c", + "remote_button_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub838\uc744 \ub54c", + "remote_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5bc\uc5c8\uc744 \ub54c", + "remote_button_triple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uc138 \ubc88 \ub20c\ub838\uc744 \ub54c", + "remote_double_tap": "\uae30\uae30\uc758 \"{subtype}\"\uc774(\uac00) \ub354\ube14 \ud0ed \ub418\uc5c8\uc744 \ub54c", + "remote_double_tap_any_side": "\uae30\uae30\uc758 \uc544\ubb34 \uba74\uc774\ub098 \ub354\ube14 \ud0ed \ub418\uc5c8\uc744 \ub54c", "remote_falling": "\uae30\uae30\uac00 \ub5a8\uc5b4\uc9c8 \ub54c", - "remote_flip_180_degrees": "\uae30\uae30\uac00 180\ub3c4\ub85c \ub4a4\uc9d1\uc5b4\uc9c8 \ub54c", - "remote_flip_90_degrees": "\uae30\uae30\uac00 90\ub3c4\ub85c \ub4a4\uc9d1\uc5b4\uc9c8 \ub54c", - "remote_gyro_activated": "\uae30\uae30\uac00 \ud754\ub4e4\ub9b4 \ub54c", - "remote_moved": "\uae30\uae30\uc758 \"{subtype}\" \uac00 \uc704\ub85c \ud5a5\ud55c\ucc44\ub85c \uc6c0\uc9c1\uc77c \ub54c", - "remote_moved_any_side": "\uae30\uae30\uc758 \uc544\ubb34 \uba74\uc774\ub098 \uc704\ub85c \ud5a5\ud55c\ucc44\ub85c \uc6c0\uc9c1\uc77c \ub54c", - "remote_rotate_from_side_1": "\"\uba74 1\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", - "remote_rotate_from_side_2": "\"\uba74 2\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", - "remote_rotate_from_side_3": "\"\uba74 3\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", - "remote_rotate_from_side_4": "\"\uba74 4\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", - "remote_rotate_from_side_5": "\"\uba74 5\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", - "remote_rotate_from_side_6": "\"\uba74 6\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", - "remote_turned_clockwise": "\uc2dc\uacc4 \ubc29\ud5a5\uc73c\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", - "remote_turned_counter_clockwise": "\ubc18\uc2dc\uacc4 \ubc29\ud5a5\uc73c\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c" + "remote_flip_180_degrees": "\uae30\uae30\uac00 180\ub3c4\ub85c \ub4a4\uc9d1\uc5b4\uc84c\uc744 \ub54c", + "remote_flip_90_degrees": "\uae30\uae30\uac00 90\ub3c4\ub85c \ub4a4\uc9d1\uc5b4\uc84c\uc744 \ub54c", + "remote_gyro_activated": "\uae30\uae30\uac00 \ud754\ub4e4\ub838\uc744 \ub54c", + "remote_moved": "\uae30\uae30\uc758 \"{subtype}\"\uc774(\uac00) \uc704\ub85c \ud5a5\ud55c \ucc44\ub85c \uc6c0\uc9c1\uc600\uc744 \ub54c", + "remote_moved_any_side": "\uae30\uae30\uc758 \uc544\ubb34 \uba74\uc774\ub098 \uc704\ub85c \ud5a5\ud55c\ucc44\ub85c \uc6c0\uc9c1\uc600\uc744 \ub54c", + "remote_rotate_from_side_1": "\"\uba74 1\" \uc5d0\uc11c \"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "remote_rotate_from_side_2": "\"\uba74 2\" \uc5d0\uc11c \"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "remote_rotate_from_side_3": "\"\uba74 3\" \uc5d0\uc11c \"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "remote_rotate_from_side_4": "\"\uba74 4\" \uc5d0\uc11c \"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "remote_rotate_from_side_5": "\"\uba74 5\" \uc5d0\uc11c \"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "remote_rotate_from_side_6": "\"\uba74 6\" \uc5d0\uc11c \"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "remote_turned_clockwise": "\uc2dc\uacc4 \ubc29\ud5a5\uc73c\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "remote_turned_counter_clockwise": "\ubc18\uc2dc\uacc4 \ubc29\ud5a5\uc73c\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c" } }, "options": { @@ -92,7 +94,8 @@ "deconz_devices": { "data": { "allow_clip_sensor": "deCONZ CLIP \uc13c\uc11c \ud5c8\uc6a9", - "allow_deconz_groups": "deCONZ \ub77c\uc774\ud2b8 \uadf8\ub8f9 \ud5c8\uc6a9" + "allow_deconz_groups": "deCONZ \ub77c\uc774\ud2b8 \uadf8\ub8f9 \ud5c8\uc6a9", + "allow_new_devices": "\uc0c8\ub85c\uc6b4 \uae30\uae30\uc758 \uc790\ub3d9 \ucd94\uac00 \ud5c8\uc6a9\ud558\uae30" }, "description": "deCONZ \uae30\uae30 \uc720\ud615\uc758 \ud45c\uc2dc \uc5ec\ubd80 \uad6c\uc131", "title": "deCONZ \uc635\uc158" diff --git a/homeassistant/components/deconz/translations/lb.json b/homeassistant/components/deconz/translations/lb.json index bb556842194..06b8dbacdc5 100644 --- a/homeassistant/components/deconz/translations/lb.json +++ b/homeassistant/components/deconz/translations/lb.json @@ -14,8 +14,8 @@ "flow_title": "deCONZ Zigbee gateway ({host})", "step": { "hassio_confirm": { - "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mat der deCONZ gateway ze verbannen d\u00e9i vum hass.io add-on {addon} bereet gestallt g\u00ebtt?", - "title": "deCONZ Zigbee gateway via Hass.io add-on" + "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mat der deCONZ gateway ze verbannen d\u00e9i vum Supervisor add-on {addon} bereet gestallt g\u00ebtt?", + "title": "deCONZ Zigbee gateway via Supervisor add-on" }, "link": { "description": "Entsperrt \u00e4r deCONZ gateway fir se mat Home Assistant ze registr\u00e9ieren.\n\n1. Gidd op deCONZ System Astellungen\n2. Dr\u00e9ckt \"Unlock\" Gateway Kn\u00e4ppchen", diff --git a/homeassistant/components/deconz/translations/nl.json b/homeassistant/components/deconz/translations/nl.json index 2d43ca63bfe..37833352f91 100644 --- a/homeassistant/components/deconz/translations/nl.json +++ b/homeassistant/components/deconz/translations/nl.json @@ -14,8 +14,8 @@ "flow_title": "deCONZ Zigbee gateway ( {host} )", "step": { "hassio_confirm": { - "description": "Wilt u de Home Assistant configureren om verbinding te maken met de deCONZ gateway van de hass.io add-on {addon}?", - "title": "deCONZ Zigbee Gateway via Hass.io add-on" + "description": "Wilt u de Home Assistant configureren om verbinding te maken met de deCONZ gateway van de Supervisor add-on {addon}?", + "title": "deCONZ Zigbee Gateway via Supervisor add-on" }, "link": { "description": "Ontgrendel je deCONZ gateway om te registreren met Home Assistant.\n\n1. Ga naar deCONZ systeeminstellingen (Instellingen -> Gateway -> Geavanceerd)\n2. Druk op de knop \"Gateway ontgrendelen\"", diff --git a/homeassistant/components/deconz/translations/no.json b/homeassistant/components/deconz/translations/no.json index c1435dbb186..4dcd693b5f4 100644 --- a/homeassistant/components/deconz/translations/no.json +++ b/homeassistant/components/deconz/translations/no.json @@ -14,7 +14,7 @@ "flow_title": "", "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til \u00e5 koble seg til deCONZ-gateway levert av Hass.io-tillegg {addon} ?", + "description": "Vil du konfigurere Home Assistant til \u00e5 koble til deCONZ gateway levert av Hass.io-tillegget {addon} ?", "title": "deCONZ Zigbee gateway via Hass.io-tillegg" }, "link": { diff --git a/homeassistant/components/deconz/translations/pt-BR.json b/homeassistant/components/deconz/translations/pt-BR.json index a13c94d82d7..450fa7707d1 100644 --- a/homeassistant/components/deconz/translations/pt-BR.json +++ b/homeassistant/components/deconz/translations/pt-BR.json @@ -12,8 +12,8 @@ }, "step": { "hassio_confirm": { - "description": "Deseja configurar o Home Assistant para conectar-se ao gateway deCONZ fornecido pelo add-on hass.io {addon} ?", - "title": "Gateway deCONZ Zigbee via add-on Hass.io" + "description": "Deseja configurar o Home Assistant para conectar-se ao gateway deCONZ fornecido pelo add-on Supervisor {addon} ?", + "title": "Gateway deCONZ Zigbee via add-on Supervisor" }, "link": { "description": "Desbloqueie o seu gateway deCONZ para se registar no Home Assistant. \n\n 1. V\u00e1 para as configura\u00e7\u00f5es do sistema deCONZ \n 2. Pressione o bot\u00e3o \"Desbloquear Gateway\"", diff --git a/homeassistant/components/deconz/translations/pt.json b/homeassistant/components/deconz/translations/pt.json index 725ce07a1b6..cc8b4ab19f2 100644 --- a/homeassistant/components/deconz/translations/pt.json +++ b/homeassistant/components/deconz/translations/pt.json @@ -11,8 +11,8 @@ }, "step": { "hassio_confirm": { - "description": "Deseja configurar o Home Assistant para se conectar ao gateway deCONZ fornecido pelo addon Hass.io {addon} ?", - "title": "Gateway Zigbee deCONZ via addon Hass.io" + "description": "Deseja configurar o Home Assistant para se conectar ao gateway deCONZ fornecido pelo addon Supervisor {addon} ?", + "title": "Gateway Zigbee deCONZ via addon Supervisor" }, "link": { "description": "Desbloqueie o seu gateway deCONZ para se registar no Home Assistant. \n\n 1. V\u00e1 para as configura\u00e7\u00f5es do sistema deCONZ \n 2. Pressione o bot\u00e3o \"Desbloquear Gateway\"", diff --git a/homeassistant/components/deconz/translations/sl.json b/homeassistant/components/deconz/translations/sl.json index cf5600c20e4..9e8ed42c07e 100644 --- a/homeassistant/components/deconz/translations/sl.json +++ b/homeassistant/components/deconz/translations/sl.json @@ -13,8 +13,8 @@ "flow_title": "deCONZ Zigbee prehod ({host})", "step": { "hassio_confirm": { - "description": "Ali \u017eelite konfigurirati Home Assistant za povezavo s prehodom deCONZ, ki ga ponuja dodatek Hass.io {addon} ?", - "title": "deCONZ Zigbee prehod preko dodatka Hass.io" + "description": "Ali \u017eelite konfigurirati Home Assistant za povezavo s prehodom deCONZ, ki ga ponuja dodatek Supervisor {addon} ?", + "title": "deCONZ Zigbee prehod preko dodatka Supervisor" }, "link": { "description": "Odklenite va\u0161 deCONZ gateway za registracijo s Home Assistant-om. \n1. Pojdite v deCONZ sistemske nastavitve\n2. Pritisnite tipko \"odkleni prehod\"", diff --git a/homeassistant/components/deconz/translations/sv.json b/homeassistant/components/deconz/translations/sv.json index 4d709a43af1..c9814734af0 100644 --- a/homeassistant/components/deconz/translations/sv.json +++ b/homeassistant/components/deconz/translations/sv.json @@ -13,8 +13,8 @@ "flow_title": "deCONZ Zigbee gateway ({host})", "step": { "hassio_confirm": { - "description": "Vill du konfigurera Home Assistant att ansluta till den deCONZ-gateway som tillhandah\u00e5lls av Hass.io-till\u00e4gget {addon}?", - "title": "deCONZ Zigbee gateway via Hass.io till\u00e4gg" + "description": "Vill du konfigurera Home Assistant att ansluta till den deCONZ-gateway som tillhandah\u00e5lls av Supervisor-till\u00e4gget {addon}?", + "title": "deCONZ Zigbee gateway via Supervisor till\u00e4gg" }, "link": { "description": "L\u00e5s upp din deCONZ-gateway f\u00f6r att registrera dig med Home Assistant. \n\n 1. G\u00e5 till deCONZ-systeminst\u00e4llningarna \n 2. Tryck p\u00e5 \"L\u00e5s upp gateway\"-knappen", diff --git a/homeassistant/components/deconz/translations/uk.json b/homeassistant/components/deconz/translations/uk.json index b5de362a731..3b09a517385 100644 --- a/homeassistant/components/deconz/translations/uk.json +++ b/homeassistant/components/deconz/translations/uk.json @@ -14,8 +14,8 @@ "flow_title": "\u0428\u043b\u044e\u0437 Zigbee deCONZ ({host})", "step": { "hassio_confirm": { - "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e deCONZ (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io \"{addon}\")?", - "title": "Zigbee \u0448\u043b\u044e\u0437 deCONZ (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io)" + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e deCONZ (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Supervisor \"{addon}\")?", + "title": "Zigbee \u0448\u043b\u044e\u0437 deCONZ (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Supervisor)" }, "link": { "description": "\u0420\u043e\u0437\u0431\u043b\u043e\u043a\u0443\u0439\u0442\u0435 \u0448\u043b\u044e\u0437 deCONZ \u0434\u043b\u044f \u0440\u0435\u0454\u0441\u0442\u0440\u0430\u0446\u0456\u0457 \u0432 Home Assistant: \n\n1. \u041f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0434\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u0438 deCONZ - > Gateway - > Advanced.\n2. \u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0443 \u00abAuthenticate app\u00bb.", diff --git a/homeassistant/components/deconz/translations/zh-Hans.json b/homeassistant/components/deconz/translations/zh-Hans.json index a85ed6d72ca..dfe8209fa1c 100644 --- a/homeassistant/components/deconz/translations/zh-Hans.json +++ b/homeassistant/components/deconz/translations/zh-Hans.json @@ -21,11 +21,11 @@ "side_3": "\u7b2c 3 \u9762", "side_4": "\u7b2c 4 \u9762", "side_5": "\u7b2c 5 \u9762", - "side_6": "\u7b2c 6 \u9762", - "turn_off": "\u5173\u95ed" + "side_6": "\u7b2c 6 \u9762" }, "trigger_type": { "remote_awakened": "\u8bbe\u5907\u5524\u9192", + "remote_button_rotation_stopped": "\u6309\u94ae \"{subtype}\" \u505c\u6b62\u65cb\u8f6c", "remote_double_tap": "\u8bbe\u5907\u7684\u201c{subtype}\u201d\u88ab\u8f7b\u6572\u4e24\u6b21", "remote_falling": "\u8bbe\u5907\u81ea\u7531\u843d\u4f53", "remote_gyro_activated": "\u8bbe\u5907\u6447\u6643", diff --git a/homeassistant/components/deconz/translations/zh-Hant.json b/homeassistant/components/deconz/translations/zh-Hant.json index 335aa73a67c..c17d2038127 100644 --- a/homeassistant/components/deconz/translations/zh-Hant.json +++ b/homeassistant/components/deconz/translations/zh-Hant.json @@ -14,8 +14,8 @@ "flow_title": "deCONZ Zigbee \u9598\u9053\u5668\uff08{host}\uff09", "step": { "hassio_confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u9023\u7dda\u81f3 Hass.io \u9644\u52a0\u6574\u5408 {addon} \u4e4b deCONZ \u9598\u9053\u5668\uff1f", - "title": "\u900f\u904e Hass.io \u9644\u52a0\u7d44\u4ef6 deCONZ Zigbee \u9598\u9053\u5668" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u9023\u7dda\u81f3 Hass.io \u9644\u52a0\u5143\u4ef6 {addon} \u4e4b deCONZ \u9598\u9053\u5668\uff1f", + "title": "\u4f7f\u7528 Hass.io \u9644\u52a0\u5143\u4ef6 deCONZ Zigbee \u9598\u9053\u5668" }, "link": { "description": "\u89e3\u9664 deCONZ \u9598\u9053\u5668\u9396\u5b9a\uff0c\u4ee5\u65bc Home Assistant \u9032\u884c\u8a3b\u518a\u3002\n\n1. \u9032\u5165 deCONZ \u7cfb\u7d71\u8a2d\u5b9a -> \u9598\u9053\u5668 -> \u9032\u968e\u8a2d\u5b9a\n2. \u6309\u4e0b\u300c\u8a8d\u8b49\u7a0b\u5f0f\uff08Authenticate app\uff09\u300d\u6309\u9215", diff --git a/homeassistant/components/demo/translations/id.json b/homeassistant/components/demo/translations/id.json new file mode 100644 index 00000000000..8adbeb3e3c4 --- /dev/null +++ b/homeassistant/components/demo/translations/id.json @@ -0,0 +1,21 @@ +{ + "options": { + "step": { + "options_1": { + "data": { + "bool": "Boolean opsional", + "constant": "Konstanta", + "int": "Input numerik" + } + }, + "options_2": { + "data": { + "multi": "Pilihan ganda", + "select": "Pilih salah satu opsi", + "string": "Nilai string" + } + } + } + }, + "title": "Demo" +} \ No newline at end of file diff --git a/homeassistant/components/denonavr/translations/de.json b/homeassistant/components/denonavr/translations/de.json index f52e6303091..e95aeb10b17 100644 --- a/homeassistant/components/denonavr/translations/de.json +++ b/homeassistant/components/denonavr/translations/de.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt" + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "cannot_connect": "Verbindung fehlgeschlagen. Bitte versuchen Sie es noch einmal. Trennen Sie ggf. Strom- und Ethernetkabel und verbinden Sie diese erneut." }, "step": { "select": { diff --git a/homeassistant/components/denonavr/translations/hu.json b/homeassistant/components/denonavr/translations/hu.json index aa56cb47741..41a1910bd56 100644 --- a/homeassistant/components/denonavr/translations/hu.json +++ b/homeassistant/components/denonavr/translations/hu.json @@ -2,10 +2,18 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", "cannot_connect": "Nem siker\u00fclt csatlakozni, k\u00e9rj\u00fck, pr\u00f3b\u00e1lja \u00fajra. A h\u00e1l\u00f3zati \u00e9s Ethernet k\u00e1belek kih\u00faz\u00e1sa \u00e9s \u00fajracsatlakoztat\u00e1sa seg\u00edthet" }, "error": { "discovery_error": "Nem siker\u00fclt megtal\u00e1lni a Denon AVR h\u00e1l\u00f3zati er\u0151s\u00edt\u0151t" + }, + "step": { + "user": { + "data": { + "host": "IP c\u00edm" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/denonavr/translations/id.json b/homeassistant/components/denonavr/translations/id.json new file mode 100644 index 00000000000..d78f547ef35 --- /dev/null +++ b/homeassistant/components/denonavr/translations/id.json @@ -0,0 +1,48 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "cannot_connect": "Gagal menyambungkan, coba lagi. Pemutusan sambungan daya listrik dan kabel ethernet lalu menyambungkannya kembali mungkin dapat membantu", + "not_denonavr_manufacturer": "Bukan Network Receiver Denon AVR, pabrikan yang ditemukan tidak sesuai", + "not_denonavr_missing": "Bukan Network Receiver AVR Denon, informasi penemuan tidak lengkap" + }, + "error": { + "discovery_error": "Gagal menemukan Network Receiver AVR Denon" + }, + "flow_title": "Network Receiver Denon AVR: {name}", + "step": { + "confirm": { + "description": "Konfirmasikan penambahan Receiver", + "title": "Network Receiver Denon AVR" + }, + "select": { + "data": { + "select_host": "Alamat IP Receiver" + }, + "description": "Jalankan penyiapan lagi jika ingin menghubungkan Receiver lainnya", + "title": "Pilih Receiver yang ingin dihubungkan" + }, + "user": { + "data": { + "host": "Alamat IP" + }, + "description": "Hubungkan ke Receiver Anda. Jika alamat IP tidak ditentukan, penemuan otomatis akan digunakan", + "title": "Network Receiver Denon AVR" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_all_sources": "Tampilkan semua sumber", + "zone2": "Siapkan Zona 2", + "zone3": "Siapkan Zona 3" + }, + "description": "Tentukan pengaturan opsional", + "title": "Network Receiver Denon AVR" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/denonavr/translations/ko.json b/homeassistant/components/denonavr/translations/ko.json index 71562ac53a4..c0121a1e2ca 100644 --- a/homeassistant/components/denonavr/translations/ko.json +++ b/homeassistant/components/denonavr/translations/ko.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694. \uc8fc\uc804\uc6d0 \ubc0f \uc774\ub354\ub137 \ucf00\uc774\ube14\uc744 \ubd84\ub9ac\ud55c \ud6c4 \ub2e4\uc2dc \uc5f0\uacb0\ud558\uba74 \ub3c4\uc6c0\uc774 \ub420 \uc218 \uc788\uc2b5\ub2c8\ub2e4", "not_denonavr_manufacturer": "Denon AVR \ub124\ud2b8\uc6cc\ud06c \ub9ac\uc2dc\ubc84\uac00 \uc544\ub2d9\ub2c8\ub2e4. \ubc1c\uacac\ub41c \uc81c\uc870\uc0ac\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", "not_denonavr_missing": "Denon AVR \ub124\ud2b8\uc6cc\ud06c \ub9ac\uc2dc\ubc84\uac00 \uc544\ub2d9\ub2c8\ub2e4. \uac80\uc0c9 \uc815\ubcf4\uac00 \uc644\uc804\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4" }, diff --git a/homeassistant/components/denonavr/translations/nl.json b/homeassistant/components/denonavr/translations/nl.json index 6a00e03765f..d96e81f2322 100644 --- a/homeassistant/components/denonavr/translations/nl.json +++ b/homeassistant/components/denonavr/translations/nl.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang" + "already_in_progress": "De configuratiestroom is al aan de gang", + "not_denonavr_manufacturer": "Geen Denon AVR Netwerk Receiver, ontdekte fabrikant komt niet overeen" }, "flow_title": "Denon AVR Network Receiver: {name}", "step": { @@ -27,6 +28,11 @@ "options": { "step": { "init": { + "data": { + "show_all_sources": "Toon alle bronnen", + "zone2": "Stel Zone 2 in", + "zone3": "Stel Zone 3 in" + }, "title": "Denon AVR Network Receivers" } } diff --git a/homeassistant/components/device_tracker/translations/id.json b/homeassistant/components/device_tracker/translations/id.json index 99baa5e1a76..be5c7e932ce 100644 --- a/homeassistant/components/device_tracker/translations/id.json +++ b/homeassistant/components/device_tracker/translations/id.json @@ -1,7 +1,17 @@ { + "device_automation": { + "condition_type": { + "is_home": "{entity_name} ada di rumah", + "is_not_home": "{entity_name} tidak ada di rumah" + }, + "trigger_type": { + "enters": "{entity_name} memasuki zona", + "leaves": "{entity_name} meninggalkan zona" + } + }, "state": { "_": { - "home": "Rumah", + "home": "Di Rumah", "not_home": "Keluar" } }, diff --git a/homeassistant/components/device_tracker/translations/ko.json b/homeassistant/components/device_tracker/translations/ko.json index e3e72d49c89..538db691b4f 100644 --- a/homeassistant/components/device_tracker/translations/ko.json +++ b/homeassistant/components/device_tracker/translations/ko.json @@ -1,8 +1,12 @@ { "device_automation": { "condition_type": { - "is_home": "{entity_name} \uc774(\uac00) \uc9d1\uc5d0 \uc788\uc73c\uba74", - "is_not_home": "{entity_name} \uc774(\uac00) \uc678\ucd9c \uc911\uc774\uba74" + "is_home": "{entity_name}\uc774(\uac00) \uc9d1\uc5d0 \uc788\uc73c\uba74", + "is_not_home": "{entity_name}\uc774(\uac00) \uc678\ucd9c \uc911\uc774\uba74" + }, + "trigger_type": { + "enters": "{entity_name}\uc774(\uac00) \uc9c0\uc5ed\uc5d0 \ub4e4\uc5b4\uac08 \ub54c", + "leaves": "{entity_name}\uc774(\uac00) \uc9c0\uc5ed\uc5d0\uc11c \ub098\uc62c \ub54c" } }, "state": { diff --git a/homeassistant/components/device_tracker/translations/zh-Hans.json b/homeassistant/components/device_tracker/translations/zh-Hans.json index c019a3dcda8..5d56de9b855 100644 --- a/homeassistant/components/device_tracker/translations/zh-Hans.json +++ b/homeassistant/components/device_tracker/translations/zh-Hans.json @@ -5,8 +5,8 @@ "is_not_home": "{entity_name} \u4e0d\u5728\u5bb6" }, "trigger_type": { - "enters": "{entity_name} \u8fdb\u5165\u533a\u57df", - "leaves": "{entity_name} \u79bb\u5f00\u533a\u57df" + "enters": "{entity_name} \u8fdb\u5165\u6307\u5b9a\u533a\u57df", + "leaves": "{entity_name} \u79bb\u5f00\u6307\u5b9a\u533a\u57df" } }, "state": { diff --git a/homeassistant/components/devolo_home_control/translations/he.json b/homeassistant/components/devolo_home_control/translations/he.json index 3007c0e968c..ac90b3264ea 100644 --- a/homeassistant/components/devolo_home_control/translations/he.json +++ b/homeassistant/components/devolo_home_control/translations/he.json @@ -3,7 +3,8 @@ "step": { "user": { "data": { - "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" } } } diff --git a/homeassistant/components/devolo_home_control/translations/hu.json b/homeassistant/components/devolo_home_control/translations/hu.json index ff2c2fc87b5..45b07f0adcb 100644 --- a/homeassistant/components/devolo_home_control/translations/hu.json +++ b/homeassistant/components/devolo_home_control/translations/hu.json @@ -1,9 +1,18 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, "step": { "user": { "data": { - "password": "Jelsz\u00f3" + "home_control_url": "Home Control URL", + "mydevolo_url": "mydevolo URL", + "password": "Jelsz\u00f3", + "username": "E-mail / devolo ID" } } } diff --git a/homeassistant/components/devolo_home_control/translations/id.json b/homeassistant/components/devolo_home_control/translations/id.json new file mode 100644 index 00000000000..8b7ce0171d5 --- /dev/null +++ b/homeassistant/components/devolo_home_control/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "home_control_url": "URL Home Control", + "mydevolo_url": "URL mydevolo", + "password": "Kata Sandi", + "username": "Email/ID devolo" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dexcom/translations/hu.json b/homeassistant/components/dexcom/translations/hu.json index 7a67a978ae1..45f38b22a84 100644 --- a/homeassistant/components/dexcom/translations/hu.json +++ b/homeassistant/components/dexcom/translations/hu.json @@ -4,7 +4,27 @@ "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" }, "error": { - "cannot_connect": "Sikertelen csatlakoz\u00e1s" + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "server": "Szerver", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "unit_of_measurement": "M\u00e9rt\u00e9kegys\u00e9g" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/dexcom/translations/id.json b/homeassistant/components/dexcom/translations/id.json new file mode 100644 index 00000000000..2802216e782 --- /dev/null +++ b/homeassistant/components/dexcom/translations/id.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "server": "Server", + "username": "Nama Pengguna" + }, + "description": "Masukkan kredensial Dexcom Share", + "title": "Siapkan integrasi Dexcom" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "unit_of_measurement": "Satuan pengukuran" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dexcom/translations/nl.json b/homeassistant/components/dexcom/translations/nl.json index 1dd597d28b4..6c5027efaae 100644 --- a/homeassistant/components/dexcom/translations/nl.json +++ b/homeassistant/components/dexcom/translations/nl.json @@ -12,9 +12,19 @@ "user": { "data": { "password": "Wachtwoord", + "server": "Server", "username": "Gebruikersnaam" } } } + }, + "options": { + "step": { + "init": { + "data": { + "unit_of_measurement": "Meeteenheid" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/dialogflow/translations/hu.json b/homeassistant/components/dialogflow/translations/hu.json index 04427a1efed..17f38b0262f 100644 --- a/homeassistant/components/dialogflow/translations/hu.json +++ b/homeassistant/components/dialogflow/translations/hu.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "webhook_not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." + }, "create_entry": { - "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, be kell \u00e1ll\u00edtania a [Dialogflow webhook integr\u00e1ci\u00f3j\u00e1t] ( {dialogflow_url} ). \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: \" {webhook_url} \"\n - M\u00f3dszer: POST\n - Tartalom t\u00edpusa: application/json \n\n Tov\u00e1bbi r\u00e9szletek\u00e9rt l\u00e1sd a [dokument\u00e1ci\u00f3t] ( {docs_url} )." + "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, be kell \u00e1ll\u00edtania a [Dialogflow webhook integr\u00e1ci\u00f3j\u00e1t]({dialogflow_url}). \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: `{webhook_url}`\n - M\u00f3dszer: POST\n - Tartalom t\u00edpusa: application/json \n\n Tov\u00e1bbi r\u00e9szletek\u00e9rt l\u00e1sd a [dokument\u00e1ci\u00f3t]({docs_url})." }, "step": { "user": { diff --git a/homeassistant/components/dialogflow/translations/id.json b/homeassistant/components/dialogflow/translations/id.json new file mode 100644 index 00000000000..046a04b1dc4 --- /dev/null +++ b/homeassistant/components/dialogflow/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." + }, + "create_entry": { + "default": "Untuk mengirim event ke Home Assistant, Anda harus menyiapkan [integrasi webhook dengan Dialogflow]({dialogflow_url}).\n\nIsi info berikut:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nBaca [dokumentasi]({docs_url}) tentang detail lebih lanjut." + }, + "step": { + "user": { + "description": "Yakin ingin menyiapkan Dialogflow?", + "title": "Siapkan Dialogflow Webhook" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/translations/ko.json b/homeassistant/components/dialogflow/translations/ko.json index 2b1be9657b4..2cd6d208f00 100644 --- a/homeassistant/components/dialogflow/translations/ko.json +++ b/homeassistant/components/dialogflow/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Dialogflow \uc6f9 \ud6c5]({dialogflow_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Dialogflow \uc6f9 \ud6c5]({dialogflow_url})\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/directv/translations/hu.json b/homeassistant/components/directv/translations/hu.json index 5d8fc929b92..0309eb35881 100644 --- a/homeassistant/components/directv/translations/hu.json +++ b/homeassistant/components/directv/translations/hu.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" diff --git a/homeassistant/components/directv/translations/id.json b/homeassistant/components/directv/translations/id.json new file mode 100644 index 00000000000..74f778d6cee --- /dev/null +++ b/homeassistant/components/directv/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "DirecTV: {name}", + "step": { + "ssdp_confirm": { + "description": "Ingin menyiapkan {name}?" + }, + "user": { + "data": { + "host": "Host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/directv/translations/ko.json b/homeassistant/components/directv/translations/ko.json index f2526418d08..ecbde981160 100644 --- a/homeassistant/components/directv/translations/ko.json +++ b/homeassistant/components/directv/translations/ko.json @@ -10,7 +10,7 @@ "flow_title": "DirecTV: {name}", "step": { "ssdp_confirm": { - "description": "{name} \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "{name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" }, "user": { "data": { diff --git a/homeassistant/components/directv/translations/nl.json b/homeassistant/components/directv/translations/nl.json index 2024368daf6..95709571234 100644 --- a/homeassistant/components/directv/translations/nl.json +++ b/homeassistant/components/directv/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "DirecTV-ontvanger is al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "unknown": "Onverwachte fout" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw" + "cannot_connect": "Kan geen verbinding maken" }, "flow_title": "DirecTV: {name}", "step": { @@ -18,7 +18,7 @@ }, "user": { "data": { - "host": "Host- of IP-adres" + "host": "Host" } } } diff --git a/homeassistant/components/doorbird/translations/hu.json b/homeassistant/components/doorbird/translations/hu.json index 618368433ac..3f74783b7ac 100644 --- a/homeassistant/components/doorbird/translations/hu.json +++ b/homeassistant/components/doorbird/translations/hu.json @@ -1,14 +1,19 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", - "unknown": "V\u00e1ratlan hiba" + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, + "flow_title": "DoorBird {name} ({host})", "step": { "user": { "data": { "host": "Hoszt", + "name": "Eszk\u00f6z neve", "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } diff --git a/homeassistant/components/doorbird/translations/id.json b/homeassistant/components/doorbird/translations/id.json new file mode 100644 index 00000000000..f708780ce31 --- /dev/null +++ b/homeassistant/components/doorbird/translations/id.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "link_local_address": "Tautan alamat lokal tidak didukung", + "not_doorbird_device": "Perangkat ini bukan DoorBird" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "DoorBird {name} ({host})", + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama Perangkat", + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Hubungkan ke DoorBird" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "events": "Daftar event yang dipisahkan koma." + }, + "description": "Tambahkan nama event yang dipisahkan koma untuk setiap event yang ingin dilacak. Setelah memasukkannya di sini, gunakan aplikasi DoorBird untuk menetapkannya ke event tertentu. Baca dokumentasi di https://www.home-assistant.io/integrations/doorbird/#events. Contoh: somebody_pressed_the_button, motion" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/doorbird/translations/ko.json b/homeassistant/components/doorbird/translations/ko.json index 819b3b51d10..85d00317c2d 100644 --- a/homeassistant/components/doorbird/translations/ko.json +++ b/homeassistant/components/doorbird/translations/ko.json @@ -19,7 +19,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "title": "DoorBird \uc5d0 \uc5f0\uacb0\ud558\uae30" + "title": "DoorBird\uc5d0 \uc5f0\uacb0\ud558\uae30" } } }, @@ -29,7 +29,7 @@ "data": { "events": "\uc27c\ud45c\ub85c \uad6c\ubd84\ub41c \uc774\ubca4\ud2b8 \ubaa9\ub85d." }, - "description": "\ucd94\uc801\ud558\ub824\ub294 \uac01 \uc774\ubca4\ud2b8\uc5d0 \ub300\ud574 \uc27c\ud45c\ub85c \uad6c\ubd84\ub41c \uc774\ubca4\ud2b8 \uc774\ub984\uc744 \ucd94\uac00\ud574\uc8fc\uc138\uc694. \uc5ec\uae30\uc5d0 \uc785\ub825\ud55c \ud6c4 DoorBird \uc571\uc744 \uc0ac\uc6a9\ud558\uc5ec \ud2b9\uc815 \uc774\ubca4\ud2b8\uc5d0 \ud560\ub2f9\ud574\uc8fc\uc138\uc694. \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 https://www.home-assistant.io/integrations/doorbird/#event \uc124\uba85\uc11c\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694. \uc608: someone_pressed_the_button, motion" + "description": "\ucd94\uc801\ud558\ub824\ub294 \uac01 \uc774\ubca4\ud2b8\uc5d0 \ub300\ud574 \uc27c\ud45c\ub85c \uad6c\ubd84\ub41c \uc774\ubca4\ud2b8 \uc774\ub984\uc744 \ucd94\uac00\ud574\uc8fc\uc138\uc694. \uc5ec\uae30\uc5d0 \uc785\ub825\ud55c \ud6c4 DoorBird \uc571\uc744 \uc0ac\uc6a9\ud558\uc5ec \ud2b9\uc815 \uc774\ubca4\ud2b8\uc5d0 \ud560\ub2f9\ud574\uc8fc\uc138\uc694. \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 https://www.home-assistant.io/integrations/doorbird/#event \uc124\uba85\uc11c\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694. \uc608: somebody_pressed_the_button, motion" } } } diff --git a/homeassistant/components/doorbird/translations/nl.json b/homeassistant/components/doorbird/translations/nl.json index 625367484b0..1c43ee2d9c2 100644 --- a/homeassistant/components/doorbird/translations/nl.json +++ b/homeassistant/components/doorbird/translations/nl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Deze DoorBird is al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "link_local_address": "Link-lokale adressen worden niet ondersteund", "not_doorbird_device": "Dit apparaat is geen DoorBird" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, @@ -14,7 +14,7 @@ "step": { "user": { "data": { - "host": "Host (IP-adres)", + "host": "Host", "name": "Apparaatnaam", "password": "Wachtwoord", "username": "Gebruikersnaam" diff --git a/homeassistant/components/dsmr/translations/fr.json b/homeassistant/components/dsmr/translations/fr.json index cb08a7865b3..d156aee8ca0 100644 --- a/homeassistant/components/dsmr/translations/fr.json +++ b/homeassistant/components/dsmr/translations/fr.json @@ -3,6 +3,10 @@ "abort": { "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" }, + "error": { + "one": "Vide", + "other": "Vide" + }, "step": { "one": "", "other": "Autre" diff --git a/homeassistant/components/dsmr/translations/id.json b/homeassistant/components/dsmr/translations/id.json new file mode 100644 index 00000000000..fd8299d61ed --- /dev/null +++ b/homeassistant/components/dsmr/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + } + }, + "options": { + "step": { + "init": { + "data": { + "time_between_update": "Interval minimum pembaruan entitas (dalam detik)" + }, + "title": "Opsi DSMR" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dsmr/translations/ko.json b/homeassistant/components/dsmr/translations/ko.json index 17dee71d640..73837e1b4f2 100644 --- a/homeassistant/components/dsmr/translations/ko.json +++ b/homeassistant/components/dsmr/translations/ko.json @@ -3,5 +3,15 @@ "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" } + }, + "options": { + "step": { + "init": { + "data": { + "time_between_update": "\uad6c\uc131\uc694\uc18c \uc5c6\ub370\uc774\ud2b8 \uac04 \ucd5c\uc18c \uc2dc\uac04 (\ucd08)" + }, + "title": "DSMR \uc635\uc158" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/dunehd/translations/hu.json b/homeassistant/components/dunehd/translations/hu.json index 44b4442dc31..cf0b593d546 100644 --- a/homeassistant/components/dunehd/translations/hu.json +++ b/homeassistant/components/dunehd/translations/hu.json @@ -4,7 +4,17 @@ "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, "error": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_host": "\u00c9rv\u00e9nytelen hosztn\u00e9v vagy IP-c\u00edm" + }, + "step": { + "user": { + "data": { + "host": "Hoszt" + }, + "title": "Dune HD" + } } } } \ No newline at end of file diff --git a/homeassistant/components/dunehd/translations/id.json b/homeassistant/components/dunehd/translations/id.json new file mode 100644 index 00000000000..25cb96bedea --- /dev/null +++ b/homeassistant/components/dunehd/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "invalid_host": "Nama host atau alamat IP tidak valid" + }, + "step": { + "user": { + "data": { + "host": "Host" + }, + "description": "Siapkan integrasi Dune HD. Jika Anda memiliki masalah dengan konfigurasi, buka: https://www.home-assistant.io/integrations/dunehd \n\nPastikan pemutar Anda dinyalakan.", + "title": "Dune HD" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dunehd/translations/ko.json b/homeassistant/components/dunehd/translations/ko.json index 45a59b4d75b..5c7feb27f7e 100644 --- a/homeassistant/components/dunehd/translations/ko.json +++ b/homeassistant/components/dunehd/translations/ko.json @@ -6,7 +6,7 @@ "error": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "invalid_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/dunehd/translations/nl.json b/homeassistant/components/dunehd/translations/nl.json index c8e16770db2..bb3dd7def47 100644 --- a/homeassistant/components/dunehd/translations/nl.json +++ b/homeassistant/components/dunehd/translations/nl.json @@ -12,7 +12,9 @@ "user": { "data": { "host": "Host" - } + }, + "description": "Stel Dune HD integratie in. Als u problemen heeft met de configuratie ga dan naar: https://www.home-assistant.io/integrations/dunehd \n\nZorg ervoor dat uw speler is ingeschakeld.", + "title": "Dune HD" } } } diff --git a/homeassistant/components/eafm/translations/id.json b/homeassistant/components/eafm/translations/id.json new file mode 100644 index 00000000000..656c9eb51ea --- /dev/null +++ b/homeassistant/components/eafm/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "no_stations": "Tidak ditemukan stasiun pemantau banjir." + }, + "step": { + "user": { + "data": { + "station": "Stasiun" + }, + "description": "Pilih stasiun yang ingin dipantau", + "title": "Lacak stasiun pemantauan banjir" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/eafm/translations/ko.json b/homeassistant/components/eafm/translations/ko.json index 36af97756ee..5c6c0a8aa33 100644 --- a/homeassistant/components/eafm/translations/ko.json +++ b/homeassistant/components/eafm/translations/ko.json @@ -2,15 +2,15 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "no_stations": "\ud64d\uc218 \ubaa8\ub2c8\ud130\ub9c1 \uc2a4\ud14c\uc774\uc158\uc774 \uc5c6\uc2b5\ub2c8\ub2e4." + "no_stations": "\ud64d\uc218 \ubaa8\ub2c8\ud130\ub9c1 \uc2a4\ud14c\uc774\uc158\uc744 \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." }, "step": { "user": { "data": { "station": "\uc2a4\ud14c\uc774\uc158" }, - "description": "\ubaa8\ub2c8\ud130\ub9c1\ud560 \uc2a4\ud14c\uc774\uc158 \uc120\ud0dd", - "title": "\ud64d\uc218 \ubaa8\ub2c8\ud130\ub9c1 \uc2a4\ud14c\uc774\uc158 \ucd94\uc801" + "description": "\ubaa8\ub2c8\ud130\ub9c1\ud560 \uc2a4\ud14c\uc774\uc158 \uc120\ud0dd\ud558\uae30", + "title": "\ud64d\uc218 \ubaa8\ub2c8\ud130\ub9c1 \uc2a4\ud14c\uc774\uc158 \ucd94\uc801\ud558\uae30" } } } diff --git a/homeassistant/components/eafm/translations/nl.json b/homeassistant/components/eafm/translations/nl.json index 8b2702b6708..0973f9ebd1a 100644 --- a/homeassistant/components/eafm/translations/nl.json +++ b/homeassistant/components/eafm/translations/nl.json @@ -2,6 +2,13 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd" + }, + "step": { + "user": { + "data": { + "station": "Station" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/ebusd/translations/id.json b/homeassistant/components/ebusd/translations/id.json new file mode 100644 index 00000000000..6b2aaa6e789 --- /dev/null +++ b/homeassistant/components/ebusd/translations/id.json @@ -0,0 +1,6 @@ +{ + "state": { + "day": "Siang", + "night": "Malam" + } +} \ No newline at end of file diff --git a/homeassistant/components/ecobee/translations/ca.json b/homeassistant/components/ecobee/translations/ca.json index 46d42d0774b..99b3f234df2 100644 --- a/homeassistant/components/ecobee/translations/ca.json +++ b/homeassistant/components/ecobee/translations/ca.json @@ -9,7 +9,7 @@ }, "step": { "authorize": { - "description": "Autoritza aquesta aplicaci\u00f3 a https://www.ecobee.com/consumerportal/index.html amb el codi pin seg\u00fcent: \n\n {pin} \n \n A continuaci\u00f3, prem Enviar.", + "description": "Autoritza aquesta aplicaci\u00f3 a https://www.ecobee.com/consumerportal/index.html amb el codi PIN: \n\n {pin} \n \n A continuaci\u00f3, prem Envia.", "title": "Autoritzaci\u00f3 de l'aplicaci\u00f3 a ecobee.com" }, "user": { diff --git a/homeassistant/components/ecobee/translations/en.json b/homeassistant/components/ecobee/translations/en.json index 1dfcc2f6a19..8a8beeb63c4 100644 --- a/homeassistant/components/ecobee/translations/en.json +++ b/homeassistant/components/ecobee/translations/en.json @@ -9,7 +9,7 @@ }, "step": { "authorize": { - "description": "Please authorize this app at https://www.ecobee.com/consumerportal/index.html with pin code:\n\n{pin}\n\nThen, press Submit.", + "description": "Please authorize this app at https://www.ecobee.com/consumerportal/index.html with PIN code:\n\n{pin}\n\nThen, press Submit.", "title": "Authorize app on ecobee.com" }, "user": { diff --git a/homeassistant/components/ecobee/translations/et.json b/homeassistant/components/ecobee/translations/et.json index 46c332a5356..452cbd578fa 100644 --- a/homeassistant/components/ecobee/translations/et.json +++ b/homeassistant/components/ecobee/translations/et.json @@ -9,7 +9,7 @@ }, "step": { "authorize": { - "description": "Tuvasta see rakendus aadressil https://www.ecobee.com/consumerportal/index.html koos PIN-koodiga:\n\n {pin}\n\n Seej\u00e4rel vajuta Esita.", + "description": "kinnita see rakendus aadressil https://www.ecobee.com/consumerportal/index.html PIN koodiga:\n\n {pin}\n\n Seej\u00e4rel vajuta Esita.", "title": "Rakenduse tuvastamine saidil ecobee.com" }, "user": { diff --git a/homeassistant/components/ecobee/translations/fr.json b/homeassistant/components/ecobee/translations/fr.json index cfb307053da..acbc909d881 100644 --- a/homeassistant/components/ecobee/translations/fr.json +++ b/homeassistant/components/ecobee/translations/fr.json @@ -9,7 +9,7 @@ }, "step": { "authorize": { - "description": "Veuillez autoriser cette application \u00e0 https://www.ecobee.com/consumerportal/index.html avec un code PIN :\n\n{pin}\n\nEnsuite, appuyez sur Soumettre.", + "description": "Veuillez autoriser cette application \u00e0 https://www.ecobee.com/consumerportal/index.html avec le code NIP :\n\n{pin}\n\nEnsuite, appuyez sur Soumettre.", "title": "Autoriser l'application sur ecobee.com" }, "user": { diff --git a/homeassistant/components/ecobee/translations/hu.json b/homeassistant/components/ecobee/translations/hu.json index bd620bc9685..a91478ff038 100644 --- a/homeassistant/components/ecobee/translations/hu.json +++ b/homeassistant/components/ecobee/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "error": { "pin_request_failed": "Hiba t\u00f6rt\u00e9nt a PIN-k\u00f3d ecobee-t\u0151l t\u00f6rt\u00e9n\u0151 k\u00e9r\u00e9sekor; ellen\u0151rizze, hogy az API-kulcs helyes-e.", "token_request_failed": "Hiba t\u00f6rt\u00e9nt a tokenek ecobee-t\u0151l t\u00f6rt\u00e9n\u0151 ig\u00e9nyl\u00e9se k\u00f6zben; pr\u00f3b\u00e1lkozzon \u00fajra." diff --git a/homeassistant/components/ecobee/translations/id.json b/homeassistant/components/ecobee/translations/id.json new file mode 100644 index 00000000000..7d23b0ca141 --- /dev/null +++ b/homeassistant/components/ecobee/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "pin_request_failed": "Terjadi kesalahan saat meminta PIN dari ecobee. Verifikasi apakah kunci API sudah benar.", + "token_request_failed": "Kesalahan saat meminta token dari ecobee. Coba lagi" + }, + "step": { + "authorize": { + "description": "Otorisasi aplikasi ini di https://www.ecobee.com/consumerportal/index.html dengan kode PIN:\n\n{pin}\n\nKemudian, tekan Kirim.", + "title": "Otorisasi aplikasi di ecobee.com" + }, + "user": { + "data": { + "api_key": "Kunci API" + }, + "description": "Masukkan kunci API yang diperoleh dari ecobee.com.", + "title": "Kunci API ecobee" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ecobee/translations/ko.json b/homeassistant/components/ecobee/translations/ko.json index 674b087620a..45406df54b6 100644 --- a/homeassistant/components/ecobee/translations/ko.json +++ b/homeassistant/components/ecobee/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "pin_request_failed": "ecobee \ub85c\ubd80\ud130 PIN \uc694\uccad\uc5d0 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4; API \ud0a4\uac00 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", diff --git a/homeassistant/components/ecobee/translations/no.json b/homeassistant/components/ecobee/translations/no.json index f3c2eceee44..0492ff76cc6 100644 --- a/homeassistant/components/ecobee/translations/no.json +++ b/homeassistant/components/ecobee/translations/no.json @@ -9,7 +9,7 @@ }, "step": { "authorize": { - "description": "Vennligst godkjenn denne appen p\u00e5 [https://www.ecobee.com/consumerportal](https://www.ecobee.com/consumerportal) med pin-kode:\n\n{pin}\n\nTrykk deretter p\u00e5 send.", + "description": "Autoriser denne appen p\u00e5 https://www.ecobee.com/consumerportal/index.html med PIN-kode: \n\n {pin}\n\n Trykk deretter p\u00e5 Send.", "title": "Godkjenn app p\u00e5 ecobee.com" }, "user": { diff --git a/homeassistant/components/econet/translations/bg.json b/homeassistant/components/econet/translations/bg.json new file mode 100644 index 00000000000..cef3726d759 --- /dev/null +++ b/homeassistant/components/econet/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/hu.json b/homeassistant/components/econet/translations/hu.json new file mode 100644 index 00000000000..065c648d4a0 --- /dev/null +++ b/homeassistant/components/econet/translations/hu.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "user": { + "data": { + "email": "E-mail", + "password": "Jelsz\u00f3" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/id.json b/homeassistant/components/econet/translations/id.json new file mode 100644 index 00000000000..467b58a8d27 --- /dev/null +++ b/homeassistant/components/econet/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Kata Sandi" + }, + "title": "Siapkan Akun Rheem EcoNet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/ko.json b/homeassistant/components/econet/translations/ko.json index f5c1381b8b1..40735cdb6d0 100644 --- a/homeassistant/components/econet/translations/ko.json +++ b/homeassistant/components/econet/translations/ko.json @@ -14,7 +14,8 @@ "data": { "email": "\uc774\uba54\uc77c", "password": "\ube44\ubc00\ubc88\ud638" - } + }, + "title": "Rheem EcoNet \uacc4\uc815 \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/elgato/translations/hu.json b/homeassistant/components/elgato/translations/hu.json index 3c69fd4562a..ef6404bd92d 100644 --- a/homeassistant/components/elgato/translations/hu.json +++ b/homeassistant/components/elgato/translations/hu.json @@ -1,12 +1,13 @@ { "config": { "abort": { - "already_configured": "Ez az Elgato Key Light eszk\u00f6z m\u00e1r konfigur\u00e1lva van.", + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, + "flow_title": "Elgato Key Light: {serial_number}", "step": { "user": { "data": { diff --git a/homeassistant/components/elgato/translations/id.json b/homeassistant/components/elgato/translations/id.json new file mode 100644 index 00000000000..b06691b9453 --- /dev/null +++ b/homeassistant/components/elgato/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "Elgato Key Light: {serial_number}", + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port" + }, + "description": "Siapkan Elgato Key Light Anda untuk diintegrasikan dengan Home Assistant." + }, + "zeroconf_confirm": { + "description": "Ingin menambahkan Elgato Key Light dengan nomor seri `{serial_number}` ke Home Assistant?", + "title": "Perangkat Elgato Key Light yang ditemukan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/translations/ko.json b/homeassistant/components/elgato/translations/ko.json index f2deb818431..2d3c7111b2e 100644 --- a/homeassistant/components/elgato/translations/ko.json +++ b/homeassistant/components/elgato/translations/ko.json @@ -14,10 +14,10 @@ "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8" }, - "description": "Home Assistant \uc5d0 Elgato Key Light \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4." + "description": "Home Assistant\uc5d0 Elgato Key Light \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4." }, "zeroconf_confirm": { - "description": "Elgato Key Light \uc2dc\ub9ac\uc5bc \ubc88\ud638 `{serial_number}` \uc744(\ub97c) Home Assistant \uc5d0 \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "\uc2dc\ub9ac\uc5bc \ubc88\ud638 `{serial_number}`\uc758 Elgato Key Light\ub97c Home Assistant\uc5d0 \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "\ubc1c\uacac\ub41c Elgato Key Light \uae30\uae30" } } diff --git a/homeassistant/components/elgato/translations/nl.json b/homeassistant/components/elgato/translations/nl.json index 81035cc898f..fcda6a7ca84 100644 --- a/homeassistant/components/elgato/translations/nl.json +++ b/homeassistant/components/elgato/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Dit Elgato Key Light apparaat is al geconfigureerd.", + "already_configured": "Apparaat is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken" }, "error": { @@ -11,8 +11,8 @@ "step": { "user": { "data": { - "host": "Hostnaam of IP-adres", - "port": "Poortnummer" + "host": "Host", + "port": "Poort" }, "description": "Stel uw Elgato Key Light in om te integreren met Home Assistant." }, diff --git a/homeassistant/components/elkm1/translations/he.json b/homeassistant/components/elkm1/translations/he.json new file mode 100644 index 00000000000..ac90b3264ea --- /dev/null +++ b/homeassistant/components/elkm1/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elkm1/translations/hu.json b/homeassistant/components/elkm1/translations/hu.json index dee4ed9ee0f..83862dfb75f 100644 --- a/homeassistant/components/elkm1/translations/hu.json +++ b/homeassistant/components/elkm1/translations/hu.json @@ -1,9 +1,15 @@ { "config": { + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { "password": "Jelsz\u00f3", + "protocol": "Protokoll", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } } diff --git a/homeassistant/components/elkm1/translations/id.json b/homeassistant/components/elkm1/translations/id.json new file mode 100644 index 00000000000..e7ddd3cf9ee --- /dev/null +++ b/homeassistant/components/elkm1/translations/id.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "address_already_configured": "ElkM1 dengan alamat ini sudah dikonfigurasi", + "already_configured": "ElkM1 dengan prefiks ini sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "address": "Alamat IP atau domain atau port serial jika terhubung melalui serial.", + "password": "Kata Sandi", + "prefix": "Prefiks unik (kosongkan jika hanya ada satu ElkM1).", + "protocol": "Protokol", + "temperature_unit": "Unit suhu yang digunakan ElkM1.", + "username": "Nama Pengguna" + }, + "description": "String alamat harus dalam format 'alamat[:port]' untuk 'aman' dan 'tidak aman'. Misalnya, '192.168.1.1'. Port bersifat opsional dan nilai baku adalah 2101 untuk 'tidak aman' dan 2601 untuk 'aman'. Untuk protokol serial, alamat harus dalam format 'tty[:baud]'. Misalnya, '/dev/ttyS1'. Baud bersifat opsional dan nilai bakunya adalah 115200.", + "title": "Hubungkan ke Kontrol Elk-M1" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elkm1/translations/ko.json b/homeassistant/components/elkm1/translations/ko.json index fb8c22ba5b2..507f741676a 100644 --- a/homeassistant/components/elkm1/translations/ko.json +++ b/homeassistant/components/elkm1/translations/ko.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "address_already_configured": "\uc774 \uc8fc\uc18c\ub85c ElkM1 \uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_configured": "\uc774 \uc811\ub450\uc0ac\ub85c ElkM1 \uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "address_already_configured": "\uc774 \uc8fc\uc18c\ub85c ElkM1\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_configured": "\uc774 \uc811\ub450\uc0ac\ub85c ElkM1\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", @@ -19,7 +19,7 @@ "temperature_unit": "ElkM1 \uc774 \uc0ac\uc6a9\ud558\ub294 \uc628\ub3c4 \ub2e8\uc704.", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "\uc8fc\uc18c \ubb38\uc790\uc5f4\uc740 '\ubcf4\uc548' \ubc0f '\ube44\ubcf4\uc548'\uc5d0 \ub300\ud574 'address[:port]' \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc608: '192.168.1.1'. \ud3ec\ud2b8\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 '\ube44\ubcf4\uc548' \uc758 \uacbd\uc6b0 2101 \uc774\uace0 '\ubcf4\uc548' \uc758 \uacbd\uc6b0 2601 \uc785\ub2c8\ub2e4. \uc2dc\ub9ac\uc5bc \ud504\ub85c\ud1a0\ucf5c\uc758 \uacbd\uc6b0 \uc8fc\uc18c\ub294 'tty[:baud]' \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc608: '/dev/ttyS1'. \ud1b5\uc2e0\uc18d\ub3c4 \ubc14\uc6b0\ub4dc\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 115200 \uc785\ub2c8\ub2e4.", + "description": "\uc8fc\uc18c \ubb38\uc790\uc5f4\uc740 '\ubcf4\uc548' \ubc0f '\ube44\ubcf4\uc548'\uc5d0 \ub300\ud574 'address[:port]' \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc608: '192.168.1.1'. \ud3ec\ud2b8\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 '\ube44\ubcf4\uc548' \uc758 \uacbd\uc6b0 2101 \uc774\uace0 '\ubcf4\uc548' \uc758 \uacbd\uc6b0 2601 \uc785\ub2c8\ub2e4. \uc2dc\ub9ac\uc5bc \ud504\ub85c\ud1a0\ucf5c\uc758 \uacbd\uc6b0 \uc8fc\uc18c\ub294 'tty[:baud]' \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc608: '/dev/ttyS1'. \uc804\uc1a1 \uc18d\ub3c4\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 115200 \uc785\ub2c8\ub2e4.", "title": "Elk-M1 \uc81c\uc5b4\uc5d0 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/elkm1/translations/nl.json b/homeassistant/components/elkm1/translations/nl.json index 9e7adf71c4b..de51e67b206 100644 --- a/homeassistant/components/elkm1/translations/nl.json +++ b/homeassistant/components/elkm1/translations/nl.json @@ -5,7 +5,7 @@ "already_configured": "Een ElkM1 met dit voorvoegsel is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, @@ -13,11 +13,11 @@ "user": { "data": { "address": "Het IP-adres of domein of seri\u00eble poort bij verbinding via serieel.", - "password": "Wachtwoord (alleen beveiligd).", + "password": "Wachtwoord", "prefix": "Een uniek voorvoegsel (laat dit leeg als u maar \u00e9\u00e9n ElkM1 heeft).", "protocol": "Protocol", "temperature_unit": "De temperatuureenheid die ElkM1 gebruikt.", - "username": "Gebruikersnaam (alleen beveiligd)." + "username": "Gebruikersnaam" }, "description": "De adresreeks moet de vorm 'adres [: poort]' hebben voor 'veilig' en 'niet-beveiligd'. Voorbeeld: '192.168.1.1'. De poort is optioneel en is standaard 2101 voor 'niet beveiligd' en 2601 voor 'beveiligd'. Voor het seri\u00eble protocol moet het adres de vorm 'tty [: baud]' hebben. Voorbeeld: '/ dev / ttyS1'. De baud is optioneel en is standaard ingesteld op 115200.", "title": "Maak verbinding met Elk-M1 Control" diff --git a/homeassistant/components/emulated_roku/translations/hu.json b/homeassistant/components/emulated_roku/translations/hu.json index 3d490ddbfeb..bccfe3bdcab 100644 --- a/homeassistant/components/emulated_roku/translations/hu.json +++ b/homeassistant/components/emulated_roku/translations/hu.json @@ -1,9 +1,12 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "step": { "user": { "data": { - "host_ip": "Hoszt IP", + "host_ip": "Hoszt IP c\u00edm", "listen_port": "Port figyel\u00e9se", "name": "N\u00e9v" }, @@ -11,5 +14,5 @@ } } }, - "title": "EmulatedRoku" + "title": "Emulated Roku" } \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/id.json b/homeassistant/components/emulated_roku/translations/id.json new file mode 100644 index 00000000000..9ffcedf5d19 --- /dev/null +++ b/homeassistant/components/emulated_roku/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "step": { + "user": { + "data": { + "advertise_ip": "Umumkan Alamat IP", + "advertise_port": "Umumkan Port", + "host_ip": "Alamat IP Host", + "name": "Nama", + "upnp_bind_multicast": "Bind multicast (True/False)" + }, + "title": "Tentukan konfigurasi server" + } + } + }, + "title": "Emulasi Roku" +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/nl.json b/homeassistant/components/emulated_roku/translations/nl.json index 54d544faee8..d4f31b7bb5d 100644 --- a/homeassistant/components/emulated_roku/translations/nl.json +++ b/homeassistant/components/emulated_roku/translations/nl.json @@ -6,8 +6,8 @@ "step": { "user": { "data": { - "advertise_ip": "Adverteer IP", - "advertise_port": "Adverterenpoort", + "advertise_ip": "Ip adres ontdekbaar", + "advertise_port": "Adverteer Poort", "host_ip": "Host IP", "listen_port": "Luisterpoort", "name": "Naam", @@ -17,5 +17,5 @@ } } }, - "title": "EmulatedRoku" + "title": "Emulated Roku" } \ No newline at end of file diff --git a/homeassistant/components/enocean/translations/hu.json b/homeassistant/components/enocean/translations/hu.json new file mode 100644 index 00000000000..065747fb39d --- /dev/null +++ b/homeassistant/components/enocean/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/enocean/translations/id.json b/homeassistant/components/enocean/translations/id.json new file mode 100644 index 00000000000..ccadfe55982 --- /dev/null +++ b/homeassistant/components/enocean/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "invalid_dongle_path": "Jalur dongle tidak valid", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "invalid_dongle_path": "Tidak ada dongle valid yang ditemukan untuk jalur ini" + }, + "step": { + "detect": { + "data": { + "path": "Jalur dongle USB" + }, + "title": "Pilih jalur ke dongle ENOcean Anda" + }, + "manual": { + "data": { + "path": "Jalur dongle USB" + }, + "title": "Masukkan jalur ke dongle ENOcean Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/enocean/translations/ko.json b/homeassistant/components/enocean/translations/ko.json index ba109ed58c0..a7480a72b3b 100644 --- a/homeassistant/components/enocean/translations/ko.json +++ b/homeassistant/components/enocean/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "invalid_dongle_path": "\ub3d9\uae00 \uacbd\ub85c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "invalid_dongle_path": "\uc774 \uacbd\ub85c\uc5d0 \uc720\ud6a8\ud55c \ub3d9\uae00\uc774 \uc5c6\uc2b5\ub2c8\ub2e4" @@ -18,7 +18,7 @@ "data": { "path": "USB \ub3d9\uae00 \uacbd\ub85c" }, - "title": "ENOcean \ub3d9\uae00 \uacbd\ub85c \uc785\ub825\ud558\uae30" + "title": "ENOcean \ub3d9\uae00 \uacbd\ub85c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" } } } diff --git a/homeassistant/components/epson/translations/hu.json b/homeassistant/components/epson/translations/hu.json index 5ff60755bfd..f2a380903ec 100644 --- a/homeassistant/components/epson/translations/hu.json +++ b/homeassistant/components/epson/translations/hu.json @@ -6,7 +6,7 @@ "step": { "user": { "data": { - "host": "Gazdag\u00e9p", + "host": "Hoszt", "name": "N\u00e9v", "port": "Port" } diff --git a/homeassistant/components/epson/translations/id.json b/homeassistant/components/epson/translations/id.json new file mode 100644 index 00000000000..ba2d36424f9 --- /dev/null +++ b/homeassistant/components/epson/translations/id.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/esphome/translations/he.json b/homeassistant/components/esphome/translations/he.json new file mode 100644 index 00000000000..648d007cc46 --- /dev/null +++ b/homeassistant/components/esphome/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "authenticate": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/esphome/translations/hu.json b/homeassistant/components/esphome/translations/hu.json index 46cb3228edb..6c4586fbd55 100644 --- a/homeassistant/components/esphome/translations/hu.json +++ b/homeassistant/components/esphome/translations/hu.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Az ESP-t m\u00e1r konfigur\u00e1ltad." + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van." }, "error": { "connection_error": "Nem lehet csatlakozni az ESP-hez. K\u00e9rlek gy\u0151z\u0151dj meg r\u00f3la, hogy a YAML f\u00e1jl tartalmaz egy \"api:\" sort.", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "resolve_error": "Az ESP c\u00edme nem oldhat\u00f3 fel. Ha a hiba tov\u00e1bbra is fenn\u00e1ll, k\u00e9rlek, \u00e1ll\u00edts be egy statikus IP-c\u00edmet: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" }, "flow_title": "ESPHome: {name}", diff --git a/homeassistant/components/esphome/translations/id.json b/homeassistant/components/esphome/translations/id.json index 9f6cd012949..a39a19e12db 100644 --- a/homeassistant/components/esphome/translations/id.json +++ b/homeassistant/components/esphome/translations/id.json @@ -1,14 +1,32 @@ { "config": { "abort": { - "already_configured": "ESP sudah dikonfigurasi" + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung" }, + "error": { + "connection_error": "Tidak dapat terhubung ke ESP. Pastikan file YAML Anda mengandung baris 'api:'.", + "invalid_auth": "Autentikasi tidak valid", + "resolve_error": "Tidak dapat menemukan alamat ESP. Jika kesalahan ini terus terjadi, atur alamat IP statis: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" + }, + "flow_title": "ESPHome: {name}", "step": { "authenticate": { "data": { - "password": "Kata kunci" + "password": "Kata Sandi" }, - "description": "Silakan masukkan kata kunci yang Anda atur di konfigurasi Anda." + "description": "Masukkan kata sandi yang ditetapkan di konfigurasi Anda untuk {name}." + }, + "discovery_confirm": { + "description": "Ingin menambahkan node ESPHome `{name}` ke Home Assistant?", + "title": "Perangkat node ESPHome yang ditemukan" + }, + "user": { + "data": { + "host": "Host", + "port": "Port" + }, + "description": "Masukkan pengaturan koneksi node [ESPHome](https://esphomelib.com/)." } } } diff --git a/homeassistant/components/esphome/translations/ko.json b/homeassistant/components/esphome/translations/ko.json index 18827f69024..3e98bdabeb5 100644 --- a/homeassistant/components/esphome/translations/ko.json +++ b/homeassistant/components/esphome/translations/ko.json @@ -5,7 +5,7 @@ "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4" }, "error": { - "connection_error": "ESP \uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. YAML \ud30c\uc77c\uc5d0 'api:' \ub97c \uad6c\uc131\ud588\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "connection_error": "ESP\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. YAML \ud30c\uc77c\uc5d0 'api:'\ub97c \uad6c\uc131\ud588\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "resolve_error": "ESP \uc758 \uc8fc\uc18c\ub97c \ud655\uc778\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uac00 \uacc4\uc18d \ubc1c\uc0dd\ud558\uba74 \uace0\uc815 IP \uc8fc\uc18c\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" }, @@ -18,7 +18,7 @@ "description": "{name} \uc758 \uad6c\uc131\uc5d0 \uc124\uc815\ud55c \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." }, "discovery_confirm": { - "description": "Home Assistant \uc5d0 ESPHome node `{name}` \uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Home Assistant\uc5d0 ESPHome node `{name}`\uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "\ubc1c\uacac\ub41c ESPHome node" }, "user": { diff --git a/homeassistant/components/esphome/translations/nl.json b/homeassistant/components/esphome/translations/nl.json index f32dbd1723e..1aae006feed 100644 --- a/homeassistant/components/esphome/translations/nl.json +++ b/homeassistant/components/esphome/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "ESP is al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al begonnen" }, "error": { diff --git a/homeassistant/components/faa_delays/translations/bg.json b/homeassistant/components/faa_delays/translations/bg.json new file mode 100644 index 00000000000..0995436221b --- /dev/null +++ b/homeassistant/components/faa_delays/translations/bg.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "user": { + "data": { + "id": "\u041b\u0435\u0442\u0438\u0449\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/de.json b/homeassistant/components/faa_delays/translations/de.json index 72b837c862c..9519c7d4470 100644 --- a/homeassistant/components/faa_delays/translations/de.json +++ b/homeassistant/components/faa_delays/translations/de.json @@ -1,8 +1,21 @@ { "config": { + "abort": { + "already_configured": "Dieser Flughafen ist bereits konfiguriert" + }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_airport": "Flughafencode ist ung\u00fcltig", "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "id": "Flughafen" + }, + "description": "Geben Sie einen US-Flughafencode im IATA-Format ein", + "title": "FAA Delays" + } } } } \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/es.json b/homeassistant/components/faa_delays/translations/es.json index 94eca99dda3..71f7fecef41 100644 --- a/homeassistant/components/faa_delays/translations/es.json +++ b/homeassistant/components/faa_delays/translations/es.json @@ -4,7 +4,9 @@ "already_configured": "Este aeropuerto ya est\u00e1 configurado." }, "error": { - "invalid_airport": "El c\u00f3digo del aeropuerto no es v\u00e1lido" + "cannot_connect": "Fallo al conectar", + "invalid_airport": "El c\u00f3digo del aeropuerto no es v\u00e1lido", + "unknown": "Error inesperado" }, "step": { "user": { diff --git a/homeassistant/components/faa_delays/translations/hu.json b/homeassistant/components/faa_delays/translations/hu.json new file mode 100644 index 00000000000..95dfabc213a --- /dev/null +++ b/homeassistant/components/faa_delays/translations/hu.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Ez a rep\u00fcl\u0151t\u00e9r m\u00e1r konfigur\u00e1lva van." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_airport": "A rep\u00fcl\u0151t\u00e9r k\u00f3dja \u00e9rv\u00e9nytelen", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "id": "Rep\u00fcl\u0151t\u00e9r" + }, + "title": "FAA Delays" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/id.json b/homeassistant/components/faa_delays/translations/id.json new file mode 100644 index 00000000000..4f4c3a93924 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Bandara ini sudah dikonfigurasi." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_airport": "Kode bandara tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "id": "Bandara" + }, + "description": "Masukkan Kode Bandara AS dalam Format IATA", + "title": "Penundaan FAA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/ko.json b/homeassistant/components/faa_delays/translations/ko.json new file mode 100644 index 00000000000..2d755e5de28 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/ko.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\uacf5\ud56d\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_airport": "\uacf5\ud56d \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "id": "\uacf5\ud56d" + }, + "description": "IATA \ud615\uc2dd\uc758 \ubbf8\uad6d \uacf5\ud56d \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "FAA \ud56d\uacf5 \uc5f0\ucc29 \uc815\ubcf4" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/pl.json b/homeassistant/components/faa_delays/translations/pl.json new file mode 100644 index 00000000000..7073597f529 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/pl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "To lotnisko jest ju\u017c skonfigurowane." + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_airport": "Kod lotniska jest nieprawid\u0142owy", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "id": "Lotnisko" + }, + "description": "Wprowad\u017a kod lotniska w Stanach w formacie IATA", + "title": "Op\u00f3\u017anienia FAA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/pt.json b/homeassistant/components/faa_delays/translations/pt.json new file mode 100644 index 00000000000..49cb628dd85 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/pt.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", + "unknown": "Erro inesperado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/zh-Hans.json b/homeassistant/components/faa_delays/translations/zh-Hans.json new file mode 100644 index 00000000000..4052f12f524 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/zh-Hans.json @@ -0,0 +1,9 @@ +{ + "config": { + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25", + "invalid_airport": "\u822a\u73ed\u53f7\u65e0\u6548", + "unknown": "\u9884\u671f\u5916\u7684\u9519\u8bef" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fan/translations/id.json b/homeassistant/components/fan/translations/id.json index b5324f36f6a..054ec10754c 100644 --- a/homeassistant/components/fan/translations/id.json +++ b/homeassistant/components/fan/translations/id.json @@ -1,9 +1,23 @@ { - "state": { - "_": { - "off": "Off", - "on": "On" + "device_automation": { + "action_type": { + "turn_off": "Matikan {entity_name}", + "turn_on": "Nyalakan {entity_name}" + }, + "condition_type": { + "is_off": "{entity_name} mati", + "is_on": "{entity_name} nyala" + }, + "trigger_type": { + "turned_off": "{entity_name} dimatikan", + "turned_on": "{entity_name} dinyalakan" } }, - "title": "Kipas angin" + "state": { + "_": { + "off": "Mati", + "on": "Nyala" + } + }, + "title": "Kipas Angin" } \ No newline at end of file diff --git a/homeassistant/components/fan/translations/ko.json b/homeassistant/components/fan/translations/ko.json index 5f6116d48d2..c2157f29e72 100644 --- a/homeassistant/components/fan/translations/ko.json +++ b/homeassistant/components/fan/translations/ko.json @@ -1,16 +1,16 @@ { "device_automation": { "action_type": { - "turn_off": "{entity_name} \ub044\uae30", - "turn_on": "{entity_name} \ucf1c\uae30" + "turn_off": "{entity_name}\uc744(\ub97c) \ub044\uae30", + "turn_on": "{entity_name}\uc744(\ub97c) \ucf1c\uae30" }, "condition_type": { - "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", - "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" + "is_off": "{entity_name}\uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name}\uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" }, "trigger_type": { - "turned_off": "{entity_name} \uc774(\uac00) \uaebc\uc9c8 \ub54c", - "turned_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c8 \ub54c" + "turned_off": "{entity_name}\uc774(\uac00) \uaebc\uc84c\uc744 \ub54c", + "turned_on": "{entity_name}\uc774(\uac00) \ucf1c\uc84c\uc744 \ub54c" } }, "state": { diff --git a/homeassistant/components/fireservicerota/translations/hu.json b/homeassistant/components/fireservicerota/translations/hu.json index 63c887ff281..8e8432d5df4 100644 --- a/homeassistant/components/fireservicerota/translations/hu.json +++ b/homeassistant/components/fireservicerota/translations/hu.json @@ -1,9 +1,26 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "create_entry": { + "default": "Sikeres hiteles\u00edt\u00e9s" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, "step": { + "reauth": { + "data": { + "password": "Jelsz\u00f3" + } + }, "user": { "data": { - "url": "Weboldal" + "password": "Jelsz\u00f3", + "url": "Weboldal", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } } } diff --git a/homeassistant/components/fireservicerota/translations/id.json b/homeassistant/components/fireservicerota/translations/id.json new file mode 100644 index 00000000000..0c4462a1ea7 --- /dev/null +++ b/homeassistant/components/fireservicerota/translations/id.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "reauth": { + "data": { + "password": "Kata Sandi" + }, + "description": "Token autentikasi menjadi tidak valid, masuk untuk membuat token lagi." + }, + "user": { + "data": { + "password": "Kata Sandi", + "url": "Situs Web", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fireservicerota/translations/ko.json b/homeassistant/components/fireservicerota/translations/ko.json index f705fd9873c..843371ed035 100644 --- a/homeassistant/components/fireservicerota/translations/ko.json +++ b/homeassistant/components/fireservicerota/translations/ko.json @@ -14,11 +14,13 @@ "reauth": { "data": { "password": "\ube44\ubc00\ubc88\ud638" - } + }, + "description": "\uc778\uc99d \ud1a0\ud070\uc774 \ub354 \uc774\uc0c1 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc0dd\uc131\ud558\ub824\uba74 \ub85c\uadf8\uc778\ud574\uc8fc\uc138\uc694." }, "user": { "data": { "password": "\ube44\ubc00\ubc88\ud638", + "url": "\uc6f9\uc0ac\uc774\ud2b8", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" } } diff --git a/homeassistant/components/firmata/translations/fr.json b/homeassistant/components/firmata/translations/fr.json index a66d58dce87..b3509c9126c 100644 --- a/homeassistant/components/firmata/translations/fr.json +++ b/homeassistant/components/firmata/translations/fr.json @@ -2,6 +2,10 @@ "config": { "abort": { "cannot_connect": "Impossible de se connecter \u00e0 la carte Firmata pendant la configuration" + }, + "step": { + "one": "Vide ", + "other": "Vide" } } } \ No newline at end of file diff --git a/homeassistant/components/firmata/translations/hu.json b/homeassistant/components/firmata/translations/hu.json new file mode 100644 index 00000000000..563ede56155 --- /dev/null +++ b/homeassistant/components/firmata/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/firmata/translations/id.json b/homeassistant/components/firmata/translations/id.json new file mode 100644 index 00000000000..3f10b4aa77c --- /dev/null +++ b/homeassistant/components/firmata/translations/id.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cannot_connect": "Gagal terhubung" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flick_electric/translations/de.json b/homeassistant/components/flick_electric/translations/de.json index 3e3568c45f8..5cca1386ffb 100644 --- a/homeassistant/components/flick_electric/translations/de.json +++ b/homeassistant/components/flick_electric/translations/de.json @@ -12,6 +12,7 @@ "user": { "data": { "client_id": "Client-ID (optional)", + "client_secret": "Client Secret (optional)", "password": "Passwort", "username": "Benutzername" } diff --git a/homeassistant/components/flick_electric/translations/hu.json b/homeassistant/components/flick_electric/translations/hu.json index dee4ed9ee0f..f7ed726e433 100644 --- a/homeassistant/components/flick_electric/translations/hu.json +++ b/homeassistant/components/flick_electric/translations/hu.json @@ -1,11 +1,20 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - } + }, + "title": "Flick Bejelentkez\u00e9si Adatok" } } } diff --git a/homeassistant/components/flick_electric/translations/id.json b/homeassistant/components/flick_electric/translations/id.json new file mode 100644 index 00000000000..8c283cfd56e --- /dev/null +++ b/homeassistant/components/flick_electric/translations/id.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "client_id": "ID Klien (Opsional)", + "client_secret": "Kode Rahasia Klien (Opsional)", + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Kredensial Masuk Flick" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flo/translations/hu.json b/homeassistant/components/flo/translations/hu.json index 3b2d79a34a7..0abcc301f0c 100644 --- a/homeassistant/components/flo/translations/hu.json +++ b/homeassistant/components/flo/translations/hu.json @@ -2,6 +2,20 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/flo/translations/id.json b/homeassistant/components/flo/translations/id.json new file mode 100644 index 00000000000..ed8fde32106 --- /dev/null +++ b/homeassistant/components/flo/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flume/translations/he.json b/homeassistant/components/flume/translations/he.json new file mode 100644 index 00000000000..ac90b3264ea --- /dev/null +++ b/homeassistant/components/flume/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flume/translations/hu.json b/homeassistant/components/flume/translations/hu.json index dee4ed9ee0f..cc0c820facf 100644 --- a/homeassistant/components/flume/translations/hu.json +++ b/homeassistant/components/flume/translations/hu.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/flume/translations/id.json b/homeassistant/components/flume/translations/id.json new file mode 100644 index 00000000000..333afb167e6 --- /dev/null +++ b/homeassistant/components/flume/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "client_id": "ID Klien", + "client_secret": "Kode Rahasia Klien", + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Untuk mengakses API Flume Personal, Anda harus meminta 'ID Klien' dan 'Kode Rahasia Klien' di https://portal.flumetech.com/settings#token", + "title": "Hubungkan ke Akun Flume Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flume/translations/nl.json b/homeassistant/components/flume/translations/nl.json index d176eb13365..97daf42d11e 100644 --- a/homeassistant/components/flume/translations/nl.json +++ b/homeassistant/components/flume/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Dit account is al geconfigureerd." + "already_configured": "Account is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/flunearyou/translations/he.json b/homeassistant/components/flunearyou/translations/he.json new file mode 100644 index 00000000000..4c49313d977 --- /dev/null +++ b/homeassistant/components/flunearyou/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/hu.json b/homeassistant/components/flunearyou/translations/hu.json new file mode 100644 index 00000000000..4f8cca2a939 --- /dev/null +++ b/homeassistant/components/flunearyou/translations/hu.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/id.json b/homeassistant/components/flunearyou/translations/id.json new file mode 100644 index 00000000000..86afc7bb5fd --- /dev/null +++ b/homeassistant/components/flunearyou/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi" + }, + "error": { + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "latitude": "Lintang", + "longitude": "Bujur" + }, + "description": "Pantau laporan berbasis pengguna dan CDC berdasarkan data koordinat.", + "title": "Konfigurasikan Flu Near You" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/ko.json b/homeassistant/components/flunearyou/translations/ko.json index f6528e85cca..bfe1945fa67 100644 --- a/homeassistant/components/flunearyou/translations/ko.json +++ b/homeassistant/components/flunearyou/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/flunearyou/translations/nl.json b/homeassistant/components/flunearyou/translations/nl.json index 0ff044abc5e..bca6e2a8c8b 100644 --- a/homeassistant/components/flunearyou/translations/nl.json +++ b/homeassistant/components/flunearyou/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Deze co\u00f6rdinaten zijn al geregistreerd." + "already_configured": "Locatie is al geconfigureerd." }, "error": { "unknown": "Onverwachte fout" diff --git a/homeassistant/components/flunearyou/translations/zh-Hans.json b/homeassistant/components/flunearyou/translations/zh-Hans.json new file mode 100644 index 00000000000..f55159dc235 --- /dev/null +++ b/homeassistant/components/flunearyou/translations/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "\u4f4d\u7f6e\u5b8c\u6210\u914d\u7f6e" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/de.json b/homeassistant/components/forked_daapd/translations/de.json index e90ffc71f90..fcbcf6b0df0 100644 --- a/homeassistant/components/forked_daapd/translations/de.json +++ b/homeassistant/components/forked_daapd/translations/de.json @@ -4,6 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { + "forbidden": "Verbindung kann nicht hergestellt werden. Bitte \u00fcberpr\u00fcfen Sie Ihre forked-daapd-Netzwerkberechtigungen.", "unknown_error": "Unbekannter Fehler", "wrong_host_or_port": "Verbindung konnte nicht hergestellt werden. Bitte Host und Port pr\u00fcfen.", "wrong_password": "Ung\u00fcltiges Passwort", diff --git a/homeassistant/components/forked_daapd/translations/hu.json b/homeassistant/components/forked_daapd/translations/hu.json index a9c13f1ee68..ca90fad3048 100644 --- a/homeassistant/components/forked_daapd/translations/hu.json +++ b/homeassistant/components/forked_daapd/translations/hu.json @@ -1,7 +1,18 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { - "forbidden": "Nem tud csatlakozni. K\u00e9rj\u00fck, ellen\u0151rizze a forked-daapd h\u00e1l\u00f3zati enged\u00e9lyeket." + "forbidden": "Nem tud csatlakozni. K\u00e9rj\u00fck, ellen\u0151rizze a forked-daapd h\u00e1l\u00f3zati enged\u00e9lyeket.", + "unknown_error": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/id.json b/homeassistant/components/forked_daapd/translations/id.json new file mode 100644 index 00000000000..76787e2a19b --- /dev/null +++ b/homeassistant/components/forked_daapd/translations/id.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "not_forked_daapd": "Perangkat bukan server forked-daapd." + }, + "error": { + "forbidden": "Tidak dapat terhubung. Periksa izin jaringan forked-daapd Anda.", + "unknown_error": "Kesalahan yang tidak diharapkan", + "websocket_not_enabled": "Websocket server forked-daapd tidak diaktifkan.", + "wrong_host_or_port": "Tidak dapat terhubung. Periksa nilai host dan port.", + "wrong_password": "Kata sandi salah.", + "wrong_server_type": "Integrasi forked-daapd membutuhkan server forked-daapd dengan versi >= 27.0." + }, + "flow_title": "forked-daapd server: {name} ({host})", + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama alias", + "password": "Kata sandi API (kosongkan jika tidak ada kata sandi)", + "port": "Port API" + }, + "title": "Siapkan perangkat forked-daapd" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "librespot_java_port": "Port untuk kontrol pipa librespot-java (jika digunakan)", + "max_playlists": "Jumlah maksimum daftar putar yang digunakan sebagai sumber", + "tts_pause_time": "Tenggang waktu dalam detik sebelum dan setelah TTS", + "tts_volume": "Volume TTS (bilangan float dalam rentang [0,1])" + }, + "description": "Tentukan berbagai opsi untuk integrasi forked-daapd.", + "title": "Konfigurasikan opsi forked-daapd" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/ko.json b/homeassistant/components/forked_daapd/translations/ko.json index 5ae487a4096..3eaaf15be2c 100644 --- a/homeassistant/components/forked_daapd/translations/ko.json +++ b/homeassistant/components/forked_daapd/translations/ko.json @@ -5,6 +5,7 @@ "not_forked_daapd": "\uae30\uae30\uac00 forked-daapd \uc11c\ubc84\uac00 \uc544\ub2d9\ub2c8\ub2e4." }, "error": { + "forbidden": "\uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. fork-daapd \ub124\ud2b8\uc6cc\ud06c \uc0ac\uc6a9 \uad8c\ud55c\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694", "unknown_error": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4", "websocket_not_enabled": "forked-daapd \uc11c\ubc84 \uc6f9\uc18c\ucf13\uc774 \ube44\ud65c\uc131\ud654 \ub418\uc5b4\uc788\uc2b5\ub2c8\ub2e4.", "wrong_host_or_port": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ud638\uc2a4\ud2b8\uc640 \ud3ec\ud2b8\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694.", @@ -34,7 +35,7 @@ "tts_volume": "TTS \ubcfc\ub968 (0~1 \uc758 \uc2e4\uc218\uac12)" }, "description": "forked-daapd \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0 \ub300\ud55c \ub2e4\uc591\ud55c \uc635\uc158\uc744 \uc124\uc815\ud574\uc8fc\uc138\uc694.", - "title": "forked-daapd \uc635\uc158 \uc124\uc815\ud558\uae30" + "title": "forked-daapd \uc635\uc158 \uad6c\uc131\ud558\uae30" } } } diff --git a/homeassistant/components/forked_daapd/translations/nl.json b/homeassistant/components/forked_daapd/translations/nl.json index 5391615fcb1..903a716d16e 100644 --- a/homeassistant/components/forked_daapd/translations/nl.json +++ b/homeassistant/components/forked_daapd/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd.", + "already_configured": "Apparaat is al geconfigureerd", "not_forked_daapd": "Apparaat is geen forked-daapd-server." }, "error": { - "unknown_error": "Onbekende fout.", + "unknown_error": "Onverwachte fout", "websocket_not_enabled": "forked-daapd server websocket niet ingeschakeld.", "wrong_host_or_port": "Verbinding mislukt, controleer het host-adres en poort.", "wrong_password": "Onjuist wachtwoord.", diff --git a/homeassistant/components/foscam/translations/bg.json b/homeassistant/components/foscam/translations/bg.json new file mode 100644 index 00000000000..5c41e03c838 --- /dev/null +++ b/homeassistant/components/foscam/translations/bg.json @@ -0,0 +1,14 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "port": "\u041f\u043e\u0440\u0442", + "rtsp_port": "RTSP \u043f\u043e\u0440\u0442", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/hu.json b/homeassistant/components/foscam/translations/hu.json new file mode 100644 index 00000000000..63ea95210ff --- /dev/null +++ b/homeassistant/components/foscam/translations/hu.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "invalid_response": "\u00c9rv\u00e9nytelen v\u00e1lasz az eszk\u00f6zt\u0151l", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "password": "Jelsz\u00f3", + "port": "Port", + "rtsp_port": "RTSP port", + "stream": "Stream", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/id.json b/homeassistant/components/foscam/translations/id.json new file mode 100644 index 00000000000..21a7682b92c --- /dev/null +++ b/homeassistant/components/foscam/translations/id.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "invalid_response": "Response tidak valid dari perangkat", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "port": "Port", + "rtsp_port": "Port RTSP", + "stream": "Stream", + "username": "Nama Pengguna" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/ko.json b/homeassistant/components/foscam/translations/ko.json index bfd8e952671..ba743f6b27a 100644 --- a/homeassistant/components/foscam/translations/ko.json +++ b/homeassistant/components/foscam/translations/ko.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_response": "\uae30\uae30\uc5d0\uc11c \uc798\ubabb\ub41c \uc751\ub2f5\uc744 \ubcf4\ub0c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { @@ -14,9 +15,12 @@ "host": "\ud638\uc2a4\ud2b8", "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8", + "rtsp_port": "RTSP \ud3ec\ud2b8", + "stream": "\uc2a4\ud2b8\ub9bc", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" } } } - } + }, + "title": "Foscam" } \ No newline at end of file diff --git a/homeassistant/components/freebox/translations/hu.json b/homeassistant/components/freebox/translations/hu.json index d13f5fa17c8..1f0b848d3b6 100644 --- a/homeassistant/components/freebox/translations/hu.json +++ b/homeassistant/components/freebox/translations/hu.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { - "register_failed": "Regisztr\u00e1ci\u00f3 nem siker\u00fclt, k\u00e9rem pr\u00f3b\u00e1lja \u00fajra" + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "register_failed": "Regisztr\u00e1ci\u00f3 nem siker\u00fclt, k\u00e9rem pr\u00f3b\u00e1lja \u00fajra", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "user": { diff --git a/homeassistant/components/freebox/translations/id.json b/homeassistant/components/freebox/translations/id.json new file mode 100644 index 00000000000..b03ec248edb --- /dev/null +++ b/homeassistant/components/freebox/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "register_failed": "Gagal mendaftar, coba lagi.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "link": { + "description": "Klik \"Kirim\", lalu sentuh panah kanan di router untuk mendaftarkan Freebox dengan Home Assistant. \n\n![Lokasi tombol di router](/static/images/config_freebox.png)", + "title": "Tautkan router Freebox" + }, + "user": { + "data": { + "host": "Host", + "port": "Port" + }, + "title": "Freebox" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/freebox/translations/ko.json b/homeassistant/components/freebox/translations/ko.json index a8b9a1edc7a..c3bb5a9bd40 100644 --- a/homeassistant/components/freebox/translations/ko.json +++ b/homeassistant/components/freebox/translations/ko.json @@ -10,7 +10,7 @@ }, "step": { "link": { - "description": "\ud655\uc778\uc744 \ud074\ub9ad\ud55c \ub2e4\uc74c \ub77c\uc6b0\ud130\uc758 \uc624\ub978\ucabd \ud654\uc0b4\ud45c\ub97c \ud130\uce58\ud558\uc5ec Home Assistant \uc5d0 Freebox \ub97c \ub4f1\ub85d\ud574\uc8fc\uc138\uc694.\n\n![\ub77c\uc6b0\ud130\uc758 \ubc84\ud2bc \uc704\uce58](/static/images/config_freebox.png)", + "description": "\ud655\uc778\uc744 \ud074\ub9ad\ud55c \ub2e4\uc74c \ub77c\uc6b0\ud130\uc758 \uc624\ub978\ucabd \ud654\uc0b4\ud45c\ub97c \ud130\uce58\ud558\uc5ec Home Assistant\uc5d0 Freebox\ub97c \ub4f1\ub85d\ud574\uc8fc\uc138\uc694.\n\n![\ub77c\uc6b0\ud130\uc758 \ubc84\ud2bc \uc704\uce58](/static/images/config_freebox.png)", "title": "Freebox \ub77c\uc6b0\ud130 \uc5f0\uacb0\ud558\uae30" }, "user": { diff --git a/homeassistant/components/freebox/translations/nl.json b/homeassistant/components/freebox/translations/nl.json index ea41fcfcd6a..7fbd57dd6ff 100644 --- a/homeassistant/components/freebox/translations/nl.json +++ b/homeassistant/components/freebox/translations/nl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Host is al geconfigureerd." + "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "register_failed": "Registratie is mislukt, probeer het opnieuw", - "unknown": "Onbekende fout: probeer het later nog eens" + "unknown": "Onverwachte fout" }, "step": { "link": { diff --git a/homeassistant/components/fritzbox/translations/bg.json b/homeassistant/components/fritzbox/translations/bg.json new file mode 100644 index 00000000000..ec678d2d76c --- /dev/null +++ b/homeassistant/components/fritzbox/translations/bg.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/he.json b/homeassistant/components/fritzbox/translations/he.json new file mode 100644 index 00000000000..035cb07a170 --- /dev/null +++ b/homeassistant/components/fritzbox/translations/he.json @@ -0,0 +1,18 @@ +{ + "config": { + "step": { + "confirm": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + }, + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/hu.json b/homeassistant/components/fritzbox/translations/hu.json index 6a08b68d863..44b68d5f540 100644 --- a/homeassistant/components/fritzbox/translations/hu.json +++ b/homeassistant/components/fritzbox/translations/hu.json @@ -1,7 +1,24 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "flow_title": "AVM FRITZ!Box: {name}", "step": { "confirm": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a {name}-t?" + }, + "reauth_confirm": { "data": { "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" diff --git a/homeassistant/components/fritzbox/translations/id.json b/homeassistant/components/fritzbox/translations/id.json new file mode 100644 index 00000000000..8dbd1f71534 --- /dev/null +++ b/homeassistant/components/fritzbox/translations/id.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "not_supported": "Tersambung ke AVM FRITZ!Box tetapi tidak dapat mengontrol perangkat Smart Home.", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "flow_title": "AVM FRITZ!Box: {name}", + "step": { + "confirm": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Ingin menyiapkan {name}?" + }, + "reauth_confirm": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Perbarui informasi masuk Anda untuk {name}." + }, + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Masukkan informasi AVM FRITZ!Box Anda." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/ko.json b/homeassistant/components/fritzbox/translations/ko.json index dfdcc0ad4eb..21fd6b6afdf 100644 --- a/homeassistant/components/fritzbox/translations/ko.json +++ b/homeassistant/components/fritzbox/translations/ko.json @@ -4,7 +4,7 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "not_supported": "AVM FRITZ!Box \uc5d0 \uc5f0\uacb0\ub418\uc5c8\uc9c0\ub9cc \uc2a4\ub9c8\ud2b8 \ud648 \uae30\uae30\ub97c \uc81c\uc5b4\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", + "not_supported": "AVM FRITZ!Box\uc5d0 \uc5f0\uacb0\ub418\uc5c8\uc9c0\ub9cc \uc2a4\ub9c8\ud2b8 \ud648 \uae30\uae30\ub97c \uc81c\uc5b4\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { @@ -17,13 +17,14 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "{name} \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "{name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" }, "reauth_confirm": { "data": { "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - } + }, + "description": "{name}\uc5d0 \ub300\ud55c \ub85c\uadf8\uc778 \uc815\ubcf4\ub97c \uc5c5\ub370\uc774\ud2b8\ud574\uc8fc\uc138\uc694." }, "user": { "data": { diff --git a/homeassistant/components/fritzbox/translations/nl.json b/homeassistant/components/fritzbox/translations/nl.json index 9bfe2ef6be6..aa4f796f44c 100644 --- a/homeassistant/components/fritzbox/translations/nl.json +++ b/homeassistant/components/fritzbox/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Deze AVM FRITZ!Box is al geconfigureerd.", - "already_in_progress": "AVM FRITZ!Box configuratie is al bezig.", + "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "no_devices_found": "Geen apparaten gevonden op het netwerk", "not_supported": "Verbonden met AVM FRITZ! Box, maar het kan geen Smart Home-apparaten bedienen.", "reauth_successful": "Herauthenticatie was succesvol" @@ -23,11 +23,12 @@ "data": { "password": "Wachtwoord", "username": "Gebruikersnaam" - } + }, + "description": "Gelieve uw logingegevens voor {name} te actualiseren." }, "user": { "data": { - "host": "Host of IP-adres", + "host": "Host", "password": "Wachtwoord", "username": "Gebruikersnaam" }, diff --git a/homeassistant/components/fritzbox_callmonitor/translations/bg.json b/homeassistant/components/fritzbox_callmonitor/translations/bg.json new file mode 100644 index 00000000000..fc2115d9ca0 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/bg.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/hu.json b/homeassistant/components/fritzbox_callmonitor/translations/hu.json new file mode 100644 index 00000000000..8c2c34775e5 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/hu.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "phonebook": { + "data": { + "phonebook": "Telefonk\u00f6nyv" + } + }, + "user": { + "data": { + "host": "Hoszt", + "password": "Jelsz\u00f3", + "port": "Port", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/id.json b/homeassistant/components/fritzbox_callmonitor/translations/id.json new file mode 100644 index 00000000000..43bb4a16b47 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/id.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "insufficient_permissions": "Izin pengguna tidak memadai untuk mengakses pengaturan dan buku telepon AVM FRITZ!Box.", + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "flow_title": "Pantau panggilan AVM FRITZ!Box: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "Buku telepon" + } + }, + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "Format prefiks salah, periksa formatnya." + }, + "step": { + "init": { + "data": { + "prefixes": "Prefiks (daftar yang dipisahkan koma)" + }, + "title": "Konfigurasikan Prefiks" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/ko.json b/homeassistant/components/fritzbox_callmonitor/translations/ko.json index b8fd442cd03..a9bfff9a5e5 100644 --- a/homeassistant/components/fritzbox_callmonitor/translations/ko.json +++ b/homeassistant/components/fritzbox_callmonitor/translations/ko.json @@ -2,12 +2,19 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "insufficient_permissions": "AVM FRIZ!Box \uc124\uc815 \ubc0f \uc804\ud654\ubc88\ud638\ubd80\uc5d0 \uc561\uc138\uc2a4\uc5d0 \ud544\uc694\ud55c \uc0ac\uc6a9\uc790\uc758 \uad8c\ud55c\uc774 \ubd80\uc871\ud569\ub2c8\ub2e4.", "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" }, "error": { "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, + "flow_title": "AVM FRITZ!Box \uc804\ud654 \ubaa8\ub2c8\ud130\ub9c1: {name}", "step": { + "phonebook": { + "data": { + "phonebook": "\uc804\ud654\ubc88\ud638\ubd80" + } + }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8", @@ -17,5 +24,18 @@ } } } + }, + "options": { + "error": { + "malformed_prefixes": "\uc811\ub450\uc0ac\uc758 \ud615\uc2dd\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud615\uc2dd\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694." + }, + "step": { + "init": { + "data": { + "prefixes": "\uc811\ub450\uc0ac (\uc27c\ud45c\ub85c \uad6c\ubd84\ub41c \ubaa9\ub85d)" + }, + "title": "\uc811\ub450\uc0ac \uad6c\uc131\ud558\uae30" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/nl.json b/homeassistant/components/fritzbox_callmonitor/translations/nl.json index 3381ed0d9b2..bc706861313 100644 --- a/homeassistant/components/fritzbox_callmonitor/translations/nl.json +++ b/homeassistant/components/fritzbox_callmonitor/translations/nl.json @@ -2,12 +2,19 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", + "insufficient_permissions": "Gebruiker heeft onvoldoende rechten voor toegang tot AVM FRITZ!Box instellingen en de telefoonboeken.", "no_devices_found": "Geen apparaten gevonden op het netwerk" }, "error": { "invalid_auth": "Ongeldige authenticatie" }, + "flow_title": "AVM FRITZ!Box oproepmonitor: {name}", "step": { + "phonebook": { + "data": { + "phonebook": "Telefoonboek" + } + }, "user": { "data": { "host": "Host", @@ -17,5 +24,18 @@ } } } + }, + "options": { + "error": { + "malformed_prefixes": "Voorvoegsels hebben een onjuiste indeling, controleer hun indeling." + }, + "step": { + "init": { + "data": { + "prefixes": "Voorvoegsels (door komma's gescheiden lijst)" + }, + "title": "Configureer voorvoegsels" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/garmin_connect/translations/he.json b/homeassistant/components/garmin_connect/translations/he.json new file mode 100644 index 00000000000..ac90b3264ea --- /dev/null +++ b/homeassistant/components/garmin_connect/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/garmin_connect/translations/hu.json b/homeassistant/components/garmin_connect/translations/hu.json index 2ada884847f..ae518acf001 100644 --- a/homeassistant/components/garmin_connect/translations/hu.json +++ b/homeassistant/components/garmin_connect/translations/hu.json @@ -4,10 +4,10 @@ "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" }, "error": { - "cannot_connect": "Nem siker\u00fclt csatlakozni, pr\u00f3b\u00e1lkozzon \u00fajra.", - "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "too_many_requests": "T\u00fal sok k\u00e9r\u00e9s, pr\u00f3b\u00e1lkozzon k\u00e9s\u0151bb \u00fajra.", - "unknown": "V\u00e1ratlan hiba." + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "user": { diff --git a/homeassistant/components/garmin_connect/translations/id.json b/homeassistant/components/garmin_connect/translations/id.json new file mode 100644 index 00000000000..27460757234 --- /dev/null +++ b/homeassistant/components/garmin_connect/translations/id.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "too_many_requests": "Terlalu banyak permintaan, coba lagi nanti.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Masukkan kredensial Anda.", + "title": "Garmin Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gdacs/translations/hu.json b/homeassistant/components/gdacs/translations/hu.json index 47eca9a7fac..fefcabae802 100644 --- a/homeassistant/components/gdacs/translations/hu.json +++ b/homeassistant/components/gdacs/translations/hu.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "A hely m\u00e1r konfigur\u00e1lva van." + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" }, "step": { "user": { diff --git a/homeassistant/components/gdacs/translations/id.json b/homeassistant/components/gdacs/translations/id.json new file mode 100644 index 00000000000..55e1db686a2 --- /dev/null +++ b/homeassistant/components/gdacs/translations/id.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "step": { + "user": { + "data": { + "radius": "Radius" + }, + "title": "Isi detail filter Anda." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/translations/hu.json b/homeassistant/components/geofency/translations/hu.json index 99dc0fecead..826b943e2f8 100644 --- a/homeassistant/components/geofency/translations/hu.json +++ b/homeassistant/components/geofency/translations/hu.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "webhook_not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." + }, "create_entry": { - "default": "Az esem\u00e9ny Home Assistantnek val\u00f3 k\u00fcld\u00e9s\u00e9hez be kell \u00e1ll\u00edtanod a webhook funkci\u00f3t a Geofencyben. \n\n Az al\u00e1bbi inform\u00e1ci\u00f3kat haszn\u00e1ld: \n\n - URL: ` {webhook_url} ` \n - Method: POST \n\n Tov\u00e1bbi r\u00e9szletek a [dokument\u00e1ci\u00f3] ( {docs_url} ) linken tal\u00e1lhat\u00f3k." + "default": "Az esem\u00e9ny Home Assistantnek val\u00f3 k\u00fcld\u00e9s\u00e9hez be kell \u00e1ll\u00edtanod a webhook funkci\u00f3t a Geofencyben. \n\n Az al\u00e1bbi inform\u00e1ci\u00f3kat haszn\u00e1ld: \n\n - URL: `{webhook_url}` \n - Method: POST \n\n Tov\u00e1bbi r\u00e9szletek a [dokument\u00e1ci\u00f3]({docs_url}) linken tal\u00e1lhat\u00f3k." }, "step": { "user": { diff --git a/homeassistant/components/geofency/translations/id.json b/homeassistant/components/geofency/translations/id.json new file mode 100644 index 00000000000..0e5163b96cd --- /dev/null +++ b/homeassistant/components/geofency/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." + }, + "create_entry": { + "default": "Untuk mengirim event ke Home Assistant, Anda harus menyiapkan fitur webhook di Geofency.\n\nIsi info berikut:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nBaca [dokumentasi]({docs_url}) tentang detail lebih lanjut." + }, + "step": { + "user": { + "description": "Yakin ingin menyiapkan Geofency Webhook?", + "title": "Siapkan Geofency Webhook" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/translations/ko.json b/homeassistant/components/geofency/translations/ko.json index fd49c57e249..2482ccca454 100644 --- a/homeassistant/components/geofency/translations/ko.json +++ b/homeassistant/components/geofency/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Geofency \uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Geofency\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/geonetnz_quakes/translations/hu.json b/homeassistant/components/geonetnz_quakes/translations/hu.json index 4a163d24b75..21a38c18e28 100644 --- a/homeassistant/components/geonetnz_quakes/translations/hu.json +++ b/homeassistant/components/geonetnz_quakes/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/geonetnz_quakes/translations/id.json b/homeassistant/components/geonetnz_quakes/translations/id.json new file mode 100644 index 00000000000..7a4e340e230 --- /dev/null +++ b/homeassistant/components/geonetnz_quakes/translations/id.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "step": { + "user": { + "data": { + "mmi": "MMI", + "radius": "Radius" + }, + "title": "Isi detail filter Anda." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geonetnz_quakes/translations/nl.json b/homeassistant/components/geonetnz_quakes/translations/nl.json index 865860a5adf..74766300e11 100644 --- a/homeassistant/components/geonetnz_quakes/translations/nl.json +++ b/homeassistant/components/geonetnz_quakes/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Locatie is al geconfigureerd." + "already_configured": "Service is al geconfigureerd" }, "step": { "user": { diff --git a/homeassistant/components/geonetnz_volcano/translations/hu.json b/homeassistant/components/geonetnz_volcano/translations/hu.json index 42de5a13142..dadc8142d7e 100644 --- a/homeassistant/components/geonetnz_volcano/translations/hu.json +++ b/homeassistant/components/geonetnz_volcano/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/geonetnz_volcano/translations/id.json b/homeassistant/components/geonetnz_volcano/translations/id.json new file mode 100644 index 00000000000..5dd4414ca62 --- /dev/null +++ b/homeassistant/components/geonetnz_volcano/translations/id.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi" + }, + "step": { + "user": { + "data": { + "radius": "Radius" + }, + "title": "Isi detail filter Anda." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geonetnz_volcano/translations/ko.json b/homeassistant/components/geonetnz_volcano/translations/ko.json index 1aeaf219288..6d743c3a18d 100644 --- a/homeassistant/components/geonetnz_volcano/translations/ko.json +++ b/homeassistant/components/geonetnz_volcano/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/gios/translations/hu.json b/homeassistant/components/gios/translations/hu.json index 5702d3b33d2..b35904e9d76 100644 --- a/homeassistant/components/gios/translations/hu.json +++ b/homeassistant/components/gios/translations/hu.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "already_configured": "A GIO\u015a integr\u00e1ci\u00f3 ehhez a m\u00e9r\u0151\u00e1llom\u00e1shoz m\u00e1r konfigur\u00e1lva van." + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van" }, "error": { - "cannot_connect": "Nem lehet csatlakozni a GIO\u015a szerverhez.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_sensors_data": "\u00c9rv\u00e9nytelen \u00e9rz\u00e9kel\u0151k adatai ehhez a m\u00e9r\u0151\u00e1llom\u00e1shoz.", "wrong_station_id": "A m\u00e9r\u0151\u00e1llom\u00e1s azonos\u00edt\u00f3ja nem megfelel\u0151." }, "step": { "user": { "data": { - "name": "Az integr\u00e1ci\u00f3 neve", + "name": "N\u00e9v", "station_id": "A m\u00e9r\u0151\u00e1llom\u00e1s azonos\u00edt\u00f3ja" }, "description": "A GIO\u015a (lengyel k\u00f6rnyezetv\u00e9delmi f\u0151fel\u00fcgyel\u0151) leveg\u0151min\u0151s\u00e9gi integr\u00e1ci\u00f3j\u00e1nak be\u00e1ll\u00edt\u00e1sa. Ha seg\u00edts\u00e9gre van sz\u00fcks\u00e9ged a konfigur\u00e1ci\u00f3val kapcsolatban, l\u00e1togass ide: https://www.home-assistant.io/integrations/gios", diff --git a/homeassistant/components/gios/translations/id.json b/homeassistant/components/gios/translations/id.json new file mode 100644 index 00000000000..b32210c30d5 --- /dev/null +++ b/homeassistant/components/gios/translations/id.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_sensors_data": "Data sensor tidak valid untuk stasiun pengukuran ini.", + "wrong_station_id": "ID stasiun pengukuran salah." + }, + "step": { + "user": { + "data": { + "name": "Nama", + "station_id": "ID stasiun pengukuran" + }, + "description": "Siapkan integrasi kualitas udara GIO\u015a (Inspektorat Jenderal Perlindungan Lingkungan Polandia). Jika Anda memerlukan bantuan tentang konfigurasi, baca di sini: https://www.home-assistant.io/integrations/gios", + "title": "GIO\u015a (Inspektorat Jenderal Perlindungan Lingkungan Polandia)" + } + } + }, + "system_health": { + "info": { + "can_reach_server": "Keterjangkauan server GIO\u015a" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gios/translations/ko.json b/homeassistant/components/gios/translations/ko.json index 7895dafe8ce..e462ef4e3b6 100644 --- a/homeassistant/components/gios/translations/ko.json +++ b/homeassistant/components/gios/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", @@ -18,5 +18,10 @@ "title": "\ud3f4\ub780\ub4dc \ud658\uacbd\uccad (GIO\u015a)" } } + }, + "system_health": { + "info": { + "can_reach_server": "GIO\u015a \uc11c\ubc84 \uc5f0\uacb0" + } } } \ No newline at end of file diff --git a/homeassistant/components/gios/translations/nl.json b/homeassistant/components/gios/translations/nl.json index 09fddb56225..f6ac11f8724 100644 --- a/homeassistant/components/gios/translations/nl.json +++ b/homeassistant/components/gios/translations/nl.json @@ -4,14 +4,14 @@ "already_configured": "GIO\u015a-integratie voor dit meetstation is al geconfigureerd." }, "error": { - "cannot_connect": "Kan geen verbinding maken met de GIO\u015a-server.", + "cannot_connect": "Kan geen verbinding maken", "invalid_sensors_data": "Ongeldige sensorgegevens voor dit meetstation.", "wrong_station_id": "ID van het meetstation is niet correct." }, "step": { "user": { "data": { - "name": "Naam van de integratie", + "name": "Naam", "station_id": "ID van het meetstation" }, "description": "GIO\u015a (Poolse hoofdinspectie van milieubescherming) luchtkwaliteitintegratie instellen. Als u hulp nodig hebt bij de configuratie, kijk dan hier: https://www.home-assistant.io/integrations/gios", diff --git a/homeassistant/components/glances/translations/he.json b/homeassistant/components/glances/translations/he.json new file mode 100644 index 00000000000..6f4191da70d --- /dev/null +++ b/homeassistant/components/glances/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/glances/translations/hu.json b/homeassistant/components/glances/translations/hu.json index 0958efee4ae..d85baecb5ca 100644 --- a/homeassistant/components/glances/translations/hu.json +++ b/homeassistant/components/glances/translations/hu.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Kiszolg\u00e1l\u00f3 m\u00e1r konfigur\u00e1lva van." + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, "error": { - "cannot_connect": "Nem lehet csatlakozni a kiszolg\u00e1l\u00f3hoz", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", "wrong_version": "Nem t\u00e1mogatott verzi\u00f3 (2 vagy 3 csak)" }, "step": { @@ -14,9 +14,9 @@ "name": "N\u00e9v", "password": "Jelsz\u00f3", "port": "Port", - "ssl": "Az SSL / TLS haszn\u00e1lat\u00e1val csatlakozzon a Glances rendszerhez", + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", "username": "Felhaszn\u00e1l\u00f3n\u00e9v", - "verify_ssl": "A rendszer tan\u00fas\u00edt\u00e1s\u00e1nak ellen\u0151rz\u00e9se", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se", "version": "Glances API-verzi\u00f3 (2 vagy 3)" }, "title": "Glances Be\u00e1ll\u00edt\u00e1sa" diff --git a/homeassistant/components/glances/translations/id.json b/homeassistant/components/glances/translations/id.json new file mode 100644 index 00000000000..13127e74322 --- /dev/null +++ b/homeassistant/components/glances/translations/id.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "wrong_version": "Versi tidak didukung (hanya versi 2 atau versi 3)" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama", + "password": "Kata Sandi", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL", + "username": "Nama Pengguna", + "verify_ssl": "Verifikasi sertifikat SSL", + "version": "Versi API Glances (2 atau 3)" + }, + "title": "Siapkan Glances" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Frekuensi pembaruan" + }, + "description": "Konfigurasikan opsi untuk Glances" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/glances/translations/ko.json b/homeassistant/components/glances/translations/ko.json index 47f24d2edf1..e50206fade5 100644 --- a/homeassistant/components/glances/translations/ko.json +++ b/homeassistant/components/glances/translations/ko.json @@ -29,7 +29,7 @@ "data": { "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \ube48\ub3c4" }, - "description": "Glances \uc635\uc158 \uc124\uc815\ud558\uae30" + "description": "Glances\uc5d0 \ub300\ud55c \uc635\uc158 \uad6c\uc131\ud558\uae30" } } } diff --git a/homeassistant/components/goalzero/translations/hu.json b/homeassistant/components/goalzero/translations/hu.json new file mode 100644 index 00000000000..c876a55301f --- /dev/null +++ b/homeassistant/components/goalzero/translations/hu.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_host": "\u00c9rv\u00e9nytelen hosztn\u00e9v vagy IP-c\u00edm", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "name": "N\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/id.json b/homeassistant/components/goalzero/translations/id.json new file mode 100644 index 00000000000..63fddf13a8e --- /dev/null +++ b/homeassistant/components/goalzero/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_host": "Nama host atau alamat IP tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama" + }, + "description": "Pertama, Anda perlu mengunduh aplikasi Goal Zero: https://www.goalzero.com/product-features/yeti-app/\n\nIkuti petunjuk untuk menghubungkan Yeti Anda ke jaringan Wi-Fi Anda. Kemudian dapatkan IP host dari router Anda. DHCP harus disetel di pengaturan router Anda untuk perangkat host agar IP host tidak berubah. Lihat manual pengguna router Anda.", + "title": "Goal Zero Yeti" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/ko.json b/homeassistant/components/goalzero/translations/ko.json index f15f5827448..d5119363002 100644 --- a/homeassistant/components/goalzero/translations/ko.json +++ b/homeassistant/components/goalzero/translations/ko.json @@ -5,7 +5,7 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { @@ -13,7 +13,9 @@ "data": { "host": "\ud638\uc2a4\ud2b8", "name": "\uc774\ub984" - } + }, + "description": "\uba3c\uc800 Goal Zero \uc571\uc744 \ub2e4\uc6b4\ub85c\ub4dc\ud574\uc57c \ud569\ub2c8\ub2e4. https://www.goalzero.com/product-features/yeti-app/\n\n\uc9c0\uce68\uc5d0 \ub530\ub77c Yeti\ub97c Wifi \ub124\ud2b8\uc6cc\ud06c\uc5d0 \uc5f0\uacb0\ud55c \ub2e4\uc74c \ub77c\uc6b0\ud130\uc5d0\uc11c \ud638\uc2a4\ud2b8 IP\ub97c \uac00\uc838\uc640\uc8fc\uc138\uc694. \ud638\uc2a4\ud2b8 IP\uac00 \ubcc0\uacbd\ub418\uc9c0 \uc54a\ub3c4\ub85d \ud558\ub824\uba74 \uae30\uae30\uc5d0 \ub300\ud574 \ub77c\uc6b0\ud130\uc5d0\uc11c DHCP\ub97c \uc54c\ub9de\uac8c \uc124\uc815\ud574\uc8fc\uc5b4\uc57c \ud569\ub2c8\ub2e4. \ud574\ub2f9 \ub0b4\uc6a9\uc5d0 \ub300\ud574\uc11c\ub294 \ub77c\uc6b0\ud130\uc758 \uc0ac\uc6a9\uc790 \uc124\uba85\uc11c\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "title": "Goal Zero Yeti" } } } diff --git a/homeassistant/components/gogogate2/translations/hu.json b/homeassistant/components/gogogate2/translations/hu.json index 952a502a72d..cdc76a4145a 100644 --- a/homeassistant/components/gogogate2/translations/hu.json +++ b/homeassistant/components/gogogate2/translations/hu.json @@ -10,9 +10,11 @@ "step": { "user": { "data": { + "ip_address": "IP c\u00edm", "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - } + }, + "title": "A GogoGate2 vagy az iSmartGate be\u00e1ll\u00edt\u00e1sa" } } } diff --git a/homeassistant/components/gogogate2/translations/id.json b/homeassistant/components/gogogate2/translations/id.json new file mode 100644 index 00000000000..9de61641d41 --- /dev/null +++ b/homeassistant/components/gogogate2/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "cannot_connect": "Gagal terhubung" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "ip_address": "Alamat IP", + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Berikan informasi yang diperlukan di bawah ini.", + "title": "Siapkan GogoGate2 atau iSmartGate" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/translations/hu.json b/homeassistant/components/gpslogger/translations/hu.json index 4fa3678043e..fe459ca3164 100644 --- a/homeassistant/components/gpslogger/translations/hu.json +++ b/homeassistant/components/gpslogger/translations/hu.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "webhook_not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." + }, "create_entry": { - "default": "Az esem\u00e9ny Home Assistantnek val\u00f3 k\u00fcld\u00e9s\u00e9hez be kell \u00e1ll\u00edtanod a webhook funkci\u00f3t a GPSLoggerben. \n\n Az al\u00e1bbi inform\u00e1ci\u00f3kat haszn\u00e1ld: \n\n - URL: ` {webhook_url} ` \n - Method: POST \n\n Tov\u00e1bbi r\u00e9szletek a [dokument\u00e1ci\u00f3] ( {docs_url} ) linken tal\u00e1lhat\u00f3k." + "default": "Az esem\u00e9ny Home Assistantnek val\u00f3 k\u00fcld\u00e9s\u00e9hez be kell \u00e1ll\u00edtanod a webhook funkci\u00f3t a GPSLoggerben. \n\n Az al\u00e1bbi inform\u00e1ci\u00f3kat haszn\u00e1ld: \n\n - URL: `{webhook_url}` \n - Method: POST \n\n Tov\u00e1bbi r\u00e9szletek a [dokument\u00e1ci\u00f3]({docs_url}) linken tal\u00e1lhat\u00f3k." }, "step": { "user": { diff --git a/homeassistant/components/gpslogger/translations/id.json b/homeassistant/components/gpslogger/translations/id.json new file mode 100644 index 00000000000..3be2d91f1f3 --- /dev/null +++ b/homeassistant/components/gpslogger/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." + }, + "create_entry": { + "default": "Untuk mengirim event ke Home Assistant, Anda harus menyiapkan fitur webhook di GPSLogger.\n\nIsi info berikut:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nBaca [dokumentasi]({docs_url}) tentang detail lebih lanjut." + }, + "step": { + "user": { + "description": "Yakin ingin menyiapkan GPSLogger Webhook?", + "title": "Siapkan GPSLogger Webhook" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/translations/ko.json b/homeassistant/components/gpslogger/translations/ko.json index e73d72c06b7..7a4e8b37c2e 100644 --- a/homeassistant/components/gpslogger/translations/ko.json +++ b/homeassistant/components/gpslogger/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 GPSLogger \uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 GPSLogger\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/gree/translations/de.json b/homeassistant/components/gree/translations/de.json index 96ed09a974f..86bc8e36730 100644 --- a/homeassistant/components/gree/translations/de.json +++ b/homeassistant/components/gree/translations/de.json @@ -6,7 +6,7 @@ }, "step": { "confirm": { - "description": "M\u00f6chtest du mit der Einrichtung beginnen?" + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?" } } } diff --git a/homeassistant/components/gree/translations/hu.json b/homeassistant/components/gree/translations/hu.json new file mode 100644 index 00000000000..6c61530acbe --- /dev/null +++ b/homeassistant/components/gree/translations/hu.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "step": { + "confirm": { + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gree/translations/id.json b/homeassistant/components/gree/translations/id.json new file mode 100644 index 00000000000..223836a8b40 --- /dev/null +++ b/homeassistant/components/gree/translations/id.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "confirm": { + "description": "Ingin memulai penyiapan?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gree/translations/ko.json b/homeassistant/components/gree/translations/ko.json index 7011a61f757..e5ae04d6e5c 100644 --- a/homeassistant/components/gree/translations/ko.json +++ b/homeassistant/components/gree/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/group/translations/id.json b/homeassistant/components/group/translations/id.json index 9a38f0f2de3..553cbce0550 100644 --- a/homeassistant/components/group/translations/id.json +++ b/homeassistant/components/group/translations/id.json @@ -2,14 +2,14 @@ "state": { "_": { "closed": "Tertutup", - "home": "Rumah", + "home": "Di Rumah", "locked": "Terkunci", "not_home": "Keluar", - "off": "Off", - "ok": "OK", - "on": "On", + "off": "Mati", + "ok": "Oke", + "on": "Nyala", "open": "Terbuka", - "problem": "Masalah", + "problem": "Bermasalah", "unlocked": "Terbuka" } }, diff --git a/homeassistant/components/guardian/translations/hu.json b/homeassistant/components/guardian/translations/hu.json index 563ede56155..bd43ce7672c 100644 --- a/homeassistant/components/guardian/translations/hu.json +++ b/homeassistant/components/guardian/translations/hu.json @@ -1,7 +1,17 @@ { "config": { "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "user": { + "data": { + "ip_address": "IP c\u00edm", + "port": "Port" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/id.json b/homeassistant/components/guardian/translations/id.json new file mode 100644 index 00000000000..b5b75321037 --- /dev/null +++ b/homeassistant/components/guardian/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "ip_address": "Alamat IP", + "port": "Port" + }, + "description": "Konfigurasikan perangkat Elexa Guardian lokal." + }, + "zeroconf_confirm": { + "description": "Ingin menyiapkan perangkat Guardian ini?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/nl.json b/homeassistant/components/guardian/translations/nl.json index 959a847a7cf..a33cb9357a9 100644 --- a/homeassistant/components/guardian/translations/nl.json +++ b/homeassistant/components/guardian/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Dit Guardian-apparaat is al geconfigureerd.", - "already_in_progress": "De configuratie van het Guardian-apparaat is al bezig.", + "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kan geen verbinding maken" }, "step": { diff --git a/homeassistant/components/habitica/translations/bg.json b/homeassistant/components/habitica/translations/bg.json new file mode 100644 index 00000000000..02c83a6e916 --- /dev/null +++ b/homeassistant/components/habitica/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/hu.json b/homeassistant/components/habitica/translations/hu.json new file mode 100644 index 00000000000..4914a1bd27a --- /dev/null +++ b/homeassistant/components/habitica/translations/hu.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_credentials": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "api_user": "Habitica API felhaszn\u00e1l\u00f3i azonos\u00edt\u00f3ja", + "url": "URL" + } + } + } + }, + "title": "Habitica" +} \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/id.json b/homeassistant/components/habitica/translations/id.json new file mode 100644 index 00000000000..c7e4c549206 --- /dev/null +++ b/homeassistant/components/habitica/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "invalid_credentials": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "api_user": "ID pengguna API Habitica", + "name": "Ganti nama pengguna Habitica. Nama ini akan digunakan untuk panggilan layanan", + "url": "URL" + }, + "description": "Hubungkan profil Habitica Anda untuk memungkinkan pemantauan profil dan tugas pengguna Anda. Perhatikan bahwa api_id dan api_key harus diperoleh dari https://habitica.com/user/settings/api" + } + } + }, + "title": "Habitica" +} \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/ko.json b/homeassistant/components/habitica/translations/ko.json index 3fd04a4477b..6b890a320df 100644 --- a/homeassistant/components/habitica/translations/ko.json +++ b/homeassistant/components/habitica/translations/ko.json @@ -8,8 +8,11 @@ "user": { "data": { "api_key": "API \ud0a4", + "api_user": "Habitica\uc758 API \uc0ac\uc6a9\uc790 ID", + "name": "Habitica\uc758 \uc0ac\uc6a9\uc790 \uc774\ub984\uc744 \uc7ac\uc815\uc758\ud574\uc8fc\uc138\uc694. \uc11c\ube44\uc2a4 \ud638\ucd9c\uc5d0 \uc0ac\uc6a9\ub429\ub2c8\ub2e4", "url": "URL \uc8fc\uc18c" - } + }, + "description": "\uc0ac\uc6a9\uc790\uc758 \ud504\ub85c\ud544 \ubc0f \uc791\uc5c5\uc744 \ubaa8\ub2c8\ud130\ub9c1\ud560 \uc218 \uc788\ub3c4\ub85d \ud558\ub824\uba74 Habitica \ud504\ub85c\ud544\uc744 \uc5f0\uacb0\ud574\uc8fc\uc138\uc694.\n\ucc38\uace0\ub85c api_id \ubc0f api_key\ub294 https://habitica.com/user/settings/api \uc5d0\uc11c \uac00\uc838\uc640\uc57c \ud569\ub2c8\ub2e4." } } }, diff --git a/homeassistant/components/habitica/translations/nl.json b/homeassistant/components/habitica/translations/nl.json index 13a4fd6c729..817ffd8c616 100644 --- a/homeassistant/components/habitica/translations/nl.json +++ b/homeassistant/components/habitica/translations/nl.json @@ -8,8 +8,11 @@ "user": { "data": { "api_key": "API-sleutel", + "api_user": "Habitica's API-gebruikers-ID", + "name": "Vervanging voor de gebruikersnaam van Habitica. Wordt gebruikt voor serviceoproepen", "url": "URL" - } + }, + "description": "Verbind uw Habitica-profiel om het profiel en de taken van uw gebruiker te bewaken. Houd er rekening mee dat api_id en api_key van https://habitica.com/user/settings/api moeten worden gehaald" } } }, diff --git a/homeassistant/components/habitica/translations/pt.json b/homeassistant/components/habitica/translations/pt.json new file mode 100644 index 00000000000..034099e4828 --- /dev/null +++ b/homeassistant/components/habitica/translations/pt.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "invalid_credentials": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "API Key", + "url": "" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hangouts/translations/ca.json b/homeassistant/components/hangouts/translations/ca.json index 2d1d81bb08d..b4114312f3e 100644 --- a/homeassistant/components/hangouts/translations/ca.json +++ b/homeassistant/components/hangouts/translations/ca.json @@ -6,13 +6,13 @@ }, "error": { "invalid_2fa": "La verificaci\u00f3 en dos passos no \u00e9s v\u00e0lida, torna-ho a provar.", - "invalid_2fa_method": "El m\u00e8tode de verificaci\u00f3 en dos passos no \u00e9s v\u00e0lid (verifica-ho al m\u00f2bil).", + "invalid_2fa_method": "M\u00e8tode 2FA inv\u00e0lid (verifica-ho al m\u00f2bil).", "invalid_login": "L'inici de sessi\u00f3 no \u00e9s v\u00e0lid, torna-ho a provar." }, "step": { "2fa": { "data": { - "2fa": "Pin 2FA" + "2fa": "PIN 2FA" }, "description": "Buit", "title": "Verificaci\u00f3 en dos passos" diff --git a/homeassistant/components/hangouts/translations/en.json b/homeassistant/components/hangouts/translations/en.json index 5de8ac24970..b2d7076bd75 100644 --- a/homeassistant/components/hangouts/translations/en.json +++ b/homeassistant/components/hangouts/translations/en.json @@ -6,13 +6,13 @@ }, "error": { "invalid_2fa": "Invalid 2 Factor Authentication, please try again.", - "invalid_2fa_method": "Invalid 2FA Method (Verify on Phone).", + "invalid_2fa_method": "Invalid 2FA Method (verify on Phone).", "invalid_login": "Invalid Login, please try again." }, "step": { "2fa": { "data": { - "2fa": "2FA Pin" + "2fa": "2FA PIN" }, "title": "2-Factor-Authentication" }, diff --git a/homeassistant/components/hangouts/translations/et.json b/homeassistant/components/hangouts/translations/et.json index 6bcc19d2043..7d6deb2ef53 100644 --- a/homeassistant/components/hangouts/translations/et.json +++ b/homeassistant/components/hangouts/translations/et.json @@ -6,13 +6,13 @@ }, "error": { "invalid_2fa": "Vale 2-teguriline autentimine, proovi uuesti.", - "invalid_2fa_method": "Kehtetu 2FA meetod (kontrolli telefoni teel).", + "invalid_2fa_method": "Kehtetu kaheastmelise tuvastuse meetod (kontrolli telefonistl).", "invalid_login": "Vale kasutajanimi, palun proovi uuesti." }, "step": { "2fa": { "data": { - "2fa": "2FA PIN" + "2fa": "Kaheastmelise tuvastuse PIN" }, "description": "", "title": "Kaheastmeline autentimine" diff --git a/homeassistant/components/hangouts/translations/fr.json b/homeassistant/components/hangouts/translations/fr.json index 2e8bec54c34..68e652db309 100644 --- a/homeassistant/components/hangouts/translations/fr.json +++ b/homeassistant/components/hangouts/translations/fr.json @@ -12,7 +12,7 @@ "step": { "2fa": { "data": { - "2fa": "Code PIN d'authentification \u00e0 2 facteurs" + "2fa": "Code NIP d'authentification \u00e0 2 facteurs" }, "description": "Vide", "title": "Authentification \u00e0 2 facteurs" diff --git a/homeassistant/components/hangouts/translations/hu.json b/homeassistant/components/hangouts/translations/hu.json index 9a9f5b41598..b81e3fcf0dd 100644 --- a/homeassistant/components/hangouts/translations/hu.json +++ b/homeassistant/components/hangouts/translations/hu.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "A Google Hangouts m\u00e1r konfigur\u00e1lva van", - "unknown": "Ismeretlen hiba t\u00f6rt\u00e9nt." + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { "invalid_2fa": "\u00c9rv\u00e9nytelen K\u00e9tfaktoros hiteles\u00edt\u00e9s, pr\u00f3b\u00e1ld \u00fajra.", @@ -14,7 +14,6 @@ "data": { "2fa": "2FA Pin" }, - "description": "\u00dcres", "title": "K\u00e9tfaktoros Hiteles\u00edt\u00e9s" }, "user": { @@ -22,7 +21,6 @@ "email": "E-mail", "password": "Jelsz\u00f3" }, - "description": "\u00dcres", "title": "Google Hangouts Bejelentkez\u00e9s" } } diff --git a/homeassistant/components/hangouts/translations/id.json b/homeassistant/components/hangouts/translations/id.json index 1bcfeaeba50..39c68dda211 100644 --- a/homeassistant/components/hangouts/translations/id.json +++ b/homeassistant/components/hangouts/translations/id.json @@ -1,29 +1,30 @@ { "config": { "abort": { - "already_configured": "Google Hangouts sudah dikonfigurasikan", - "unknown": "Kesalahan tidak dikenal terjadi." + "already_configured": "Layanan sudah dikonfigurasi", + "unknown": "Kesalahan yang tidak diharapkan" }, "error": { - "invalid_2fa": "Autentikasi 2 Faktor Tidak Valid, silakan coba lagi.", - "invalid_2fa_method": "Metode 2FA Tidak Sah (Verifikasi di Ponsel).", - "invalid_login": "Login tidak valid, silahkan coba lagi." + "invalid_2fa": "Autentikasi 2 Faktor Tidak Valid, coba lagi.", + "invalid_2fa_method": "Metode 2FA Tidak Valid (Verifikasikan di Ponsel).", + "invalid_login": "Info Masuk Tidak Valid, coba lagi." }, "step": { "2fa": { "data": { - "2fa": "Pin 2FA" + "2fa": "PIN 2FA" }, "description": "Kosong", - "title": "2-Faktor-Otentikasi" + "title": "Autentikasi Dua Faktor" }, "user": { "data": { - "email": "Alamat email", - "password": "Kata sandi" + "authorization_code": "Kode Otorisasi (diperlukan untuk autentikasi manual)", + "email": "Email", + "password": "Kata Sandi" }, "description": "Kosong", - "title": "Google Hangouts Login" + "title": "Info Masuk Google Hangouts" } } } diff --git a/homeassistant/components/hangouts/translations/ko.json b/homeassistant/components/hangouts/translations/ko.json index 3c23effaf4f..56c3c577a89 100644 --- a/homeassistant/components/hangouts/translations/ko.json +++ b/homeassistant/components/hangouts/translations/ko.json @@ -7,7 +7,7 @@ "error": { "invalid_2fa": "2\ub2e8\uacc4 \uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "invalid_2fa_method": "2\ub2e8\uacc4 \uc778\uc99d \ubc29\ubc95\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. (\uc804\ud654\uae30\uc5d0\uc11c \ud655\uc778)", - "invalid_login": "\uc798\ubabb\ub41c \ub85c\uadf8\uc778\uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." + "invalid_login": "\ub85c\uadf8\uc778\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." }, "step": { "2fa": { diff --git a/homeassistant/components/hangouts/translations/nl.json b/homeassistant/components/hangouts/translations/nl.json index fac77660251..456d2193922 100644 --- a/homeassistant/components/hangouts/translations/nl.json +++ b/homeassistant/components/hangouts/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Google Hangouts is al geconfigureerd", - "unknown": "Onbekende fout opgetreden." + "already_configured": "Service is al geconfigureerd", + "unknown": "Onverwachte fout" }, "error": { "invalid_2fa": "Ongeldige twee-factor-authenticatie, probeer het opnieuw.", @@ -20,7 +20,7 @@ "user": { "data": { "authorization_code": "Autorisatiecode (vereist voor handmatige authenticatie)", - "email": "E-mailadres", + "email": "E-mail", "password": "Wachtwoord" }, "description": "Leeg", diff --git a/homeassistant/components/hangouts/translations/no.json b/homeassistant/components/hangouts/translations/no.json index fa341509634..d4fe4dbb5a6 100644 --- a/homeassistant/components/hangouts/translations/no.json +++ b/homeassistant/components/hangouts/translations/no.json @@ -6,13 +6,13 @@ }, "error": { "invalid_2fa": "Ugyldig totrinnsbekreftelse, vennligst pr\u00f8v igjen.", - "invalid_2fa_method": "Ugyldig totrinnsbekreftelse-metode (Bekreft p\u00e5 telefon)", + "invalid_2fa_method": "Ugyldig 2FA-metode (bekreft p\u00e5 telefon).", "invalid_login": "Ugyldig innlogging, vennligst pr\u00f8v igjen." }, "step": { "2fa": { "data": { - "2fa": "Totrinnsbekreftelse Pin" + "2fa": "2FA PIN" }, "description": "", "title": "Totrinnsbekreftelse" diff --git a/homeassistant/components/hangouts/translations/pl.json b/homeassistant/components/hangouts/translations/pl.json index ff60deeece2..8fb7e9e64d9 100644 --- a/homeassistant/components/hangouts/translations/pl.json +++ b/homeassistant/components/hangouts/translations/pl.json @@ -12,7 +12,7 @@ "step": { "2fa": { "data": { - "2fa": "PIN" + "2fa": "Kod uwierzytelniania dwusk\u0142adnikowego" }, "description": "Pusty", "title": "Uwierzytelnianie dwusk\u0142adnikowe" diff --git a/homeassistant/components/hangouts/translations/zh-Hant.json b/homeassistant/components/hangouts/translations/zh-Hant.json index 62a220eaa94..678aacc5b62 100644 --- a/homeassistant/components/hangouts/translations/zh-Hant.json +++ b/homeassistant/components/hangouts/translations/zh-Hant.json @@ -5,17 +5,17 @@ "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "error": { - "invalid_2fa": "\u96d9\u91cd\u9a57\u8b49\u7121\u6548\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002", - "invalid_2fa_method": "\u8a8d\u8b49\u65b9\u5f0f\u7121\u6548\uff08\u65bc\u96fb\u8a71\u4e0a\u9a57\u8b49\uff09\u3002", + "invalid_2fa": "\u96d9\u91cd\u8a8d\u8b49\u7121\u6548\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002", + "invalid_2fa_method": "\u5169\u968e\u6bb5\u8a8d\u8b49\u65b9\u5f0f\u7121\u6548\uff08\u65bc\u96fb\u8a71\u4e0a\u9a57\u8b49\uff09\u3002", "invalid_login": "\u767b\u5165\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002" }, "step": { "2fa": { "data": { - "2fa": "\u8a8d\u8b49\u78bc" + "2fa": "\u5169\u968e\u6bb5\u8a8d\u8b49\u78bc" }, "description": "\u7a7a\u767d", - "title": "\u96d9\u91cd\u9a57\u8b49" + "title": "\u96d9\u91cd\u8a8d\u8b49" }, "user": { "data": { diff --git a/homeassistant/components/harmony/translations/hu.json b/homeassistant/components/harmony/translations/hu.json index cbf055e2fba..a9cb6ccecee 100644 --- a/homeassistant/components/harmony/translations/hu.json +++ b/homeassistant/components/harmony/translations/hu.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/harmony/translations/id.json b/homeassistant/components/harmony/translations/id.json new file mode 100644 index 00000000000..0d2991b1feb --- /dev/null +++ b/homeassistant/components/harmony/translations/id.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Logitech Harmony Hub {name}", + "step": { + "link": { + "description": "Ingin menyiapkan {name} ({host})?", + "title": "Siapkan Logitech Harmony Hub" + }, + "user": { + "data": { + "host": "Host", + "name": "Nama Hub" + }, + "title": "Siapkan Logitech Harmony Hub" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "activity": "Aktivitas default yang akan dijalankan jika tidak ada yang ditentukan.", + "delay_secs": "Penundaan antara mengirim perintah." + }, + "description": "Sesuaikan Opsi Hub Harmony" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/harmony/translations/nl.json b/homeassistant/components/harmony/translations/nl.json index 63d8026d9c2..33cbeca8893 100644 --- a/homeassistant/components/harmony/translations/nl.json +++ b/homeassistant/components/harmony/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "unknown": "Onverwachte fout" }, "flow_title": "Logitech Harmony Hub {name}", @@ -15,7 +15,7 @@ }, "user": { "data": { - "host": "Hostnaam of IP-adres", + "host": "Host", "name": "Naam van hub" }, "title": "Logitech Harmony Hub instellen" diff --git a/homeassistant/components/hassio/translations/af.json b/homeassistant/components/hassio/translations/af.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/af.json +++ b/homeassistant/components/hassio/translations/af.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/bg.json b/homeassistant/components/hassio/translations/bg.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/bg.json +++ b/homeassistant/components/hassio/translations/bg.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/ca.json b/homeassistant/components/hassio/translations/ca.json index 19b4316c9ce..d2e712c230d 100644 --- a/homeassistant/components/hassio/translations/ca.json +++ b/homeassistant/components/hassio/translations/ca.json @@ -15,5 +15,5 @@ "version_api": "Versi\u00f3 d'APIs" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/cs.json b/homeassistant/components/hassio/translations/cs.json index cf1a28c3cc2..eb64ed58baa 100644 --- a/homeassistant/components/hassio/translations/cs.json +++ b/homeassistant/components/hassio/translations/cs.json @@ -15,5 +15,5 @@ "version_api": "Verze API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/cy.json b/homeassistant/components/hassio/translations/cy.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/cy.json +++ b/homeassistant/components/hassio/translations/cy.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/da.json b/homeassistant/components/hassio/translations/da.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/da.json +++ b/homeassistant/components/hassio/translations/da.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/de.json b/homeassistant/components/hassio/translations/de.json index 939821edb54..a0d02f4a7b9 100644 --- a/homeassistant/components/hassio/translations/de.json +++ b/homeassistant/components/hassio/translations/de.json @@ -14,5 +14,5 @@ "version_api": "Versions-API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/el.json b/homeassistant/components/hassio/translations/el.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/el.json +++ b/homeassistant/components/hassio/translations/el.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/en.json b/homeassistant/components/hassio/translations/en.json index 230e0c11fea..16911be4110 100644 --- a/homeassistant/components/hassio/translations/en.json +++ b/homeassistant/components/hassio/translations/en.json @@ -15,5 +15,5 @@ "version_api": "Version API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/es-419.json b/homeassistant/components/hassio/translations/es-419.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/es-419.json +++ b/homeassistant/components/hassio/translations/es-419.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/es.json b/homeassistant/components/hassio/translations/es.json index 5faf32e515f..f3bdf14c446 100644 --- a/homeassistant/components/hassio/translations/es.json +++ b/homeassistant/components/hassio/translations/es.json @@ -15,5 +15,5 @@ "version_api": "Versi\u00f3n del API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/et.json b/homeassistant/components/hassio/translations/et.json index 9e5e776013f..9d3ef08afbe 100644 --- a/homeassistant/components/hassio/translations/et.json +++ b/homeassistant/components/hassio/translations/et.json @@ -15,5 +15,5 @@ "version_api": "API versioon" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/eu.json b/homeassistant/components/hassio/translations/eu.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/eu.json +++ b/homeassistant/components/hassio/translations/eu.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/fa.json b/homeassistant/components/hassio/translations/fa.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/fa.json +++ b/homeassistant/components/hassio/translations/fa.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/fi.json b/homeassistant/components/hassio/translations/fi.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/fi.json +++ b/homeassistant/components/hassio/translations/fi.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/fr.json b/homeassistant/components/hassio/translations/fr.json index cef14b258c4..e4fe8a63bba 100644 --- a/homeassistant/components/hassio/translations/fr.json +++ b/homeassistant/components/hassio/translations/fr.json @@ -7,13 +7,13 @@ "docker_version": "Version de Docker", "healthy": "Sain", "host_os": "Syst\u00e8me d'exploitation h\u00f4te", - "installed_addons": "Add-ons install\u00e9s", - "supervisor_api": "API du superviseur", - "supervisor_version": "Version du supervisor", + "installed_addons": "Modules compl\u00e9mentaires install\u00e9s", + "supervisor_api": "API du Supervisor", + "supervisor_version": "Version du Supervisor", "supported": "Prise en charge", "update_channel": "Mise \u00e0 jour", "version_api": "Version API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/he.json b/homeassistant/components/hassio/translations/he.json index 981cb51c83a..80c1a0c48ee 100644 --- a/homeassistant/components/hassio/translations/he.json +++ b/homeassistant/components/hassio/translations/he.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/hr.json b/homeassistant/components/hassio/translations/hr.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/hr.json +++ b/homeassistant/components/hassio/translations/hr.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/hu.json b/homeassistant/components/hassio/translations/hu.json index 216e8d391b6..e0fc98408d4 100644 --- a/homeassistant/components/hassio/translations/hu.json +++ b/homeassistant/components/hassio/translations/hu.json @@ -7,12 +7,12 @@ "healthy": "Eg\u00e9szs\u00e9ges", "host_os": "Gazdag\u00e9p oper\u00e1ci\u00f3s rendszer", "installed_addons": "Telep\u00edtett kieg\u00e9sz\u00edt\u0151k", - "supervisor_api": "Adminisztr\u00e1tor API", - "supervisor_version": "Adminisztr\u00e1tor verzi\u00f3", + "supervisor_api": "Supervisor API", + "supervisor_version": "Supervisor verzi\u00f3", "supported": "T\u00e1mogatott", "update_channel": "Friss\u00edt\u00e9si csatorna", "version_api": "API verzi\u00f3" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/hy.json b/homeassistant/components/hassio/translations/hy.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/hy.json +++ b/homeassistant/components/hassio/translations/hy.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/id.json b/homeassistant/components/hassio/translations/id.json new file mode 100644 index 00000000000..b95ffb35d81 --- /dev/null +++ b/homeassistant/components/hassio/translations/id.json @@ -0,0 +1,19 @@ +{ + "system_health": { + "info": { + "board": "Board", + "disk_total": "Total Disk", + "disk_used": "Disk Digunakan", + "docker_version": "Versi Docker", + "healthy": "Kesehatan", + "host_os": "Sistem Operasi Host", + "installed_addons": "Add-on yang Diinstal", + "supervisor_api": "API Supervisor", + "supervisor_version": "Versi Supervisor", + "supported": "Didukung", + "update_channel": "Kanal Pembaruan", + "version_api": "API Versi" + } + }, + "title": "Home Assistant Supervisor" +} \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/is.json b/homeassistant/components/hassio/translations/is.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/is.json +++ b/homeassistant/components/hassio/translations/is.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/it.json b/homeassistant/components/hassio/translations/it.json index 385a0eedff2..7a375d3d78d 100644 --- a/homeassistant/components/hassio/translations/it.json +++ b/homeassistant/components/hassio/translations/it.json @@ -15,5 +15,5 @@ "version_api": "Versione API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/ja.json b/homeassistant/components/hassio/translations/ja.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/ja.json +++ b/homeassistant/components/hassio/translations/ja.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/ko.json b/homeassistant/components/hassio/translations/ko.json index 981cb51c83a..0acdd0c77a1 100644 --- a/homeassistant/components/hassio/translations/ko.json +++ b/homeassistant/components/hassio/translations/ko.json @@ -1,3 +1,19 @@ { - "title": "Hass.io" + "system_health": { + "info": { + "board": "\ubcf4\ub4dc \uc720\ud615", + "disk_total": "\ub514\uc2a4\ud06c \ucd1d \uc6a9\ub7c9", + "disk_used": "\ub514\uc2a4\ud06c \uc0ac\uc6a9\ub7c9", + "docker_version": "Docker \ubc84\uc804", + "healthy": "\uc2dc\uc2a4\ud15c \uc0c1\ud0dc", + "host_os": "\ud638\uc2a4\ud2b8 \uc6b4\uc601 \uccb4\uc81c", + "installed_addons": "\uc124\uce58\ub41c \ucd94\uac00 \uae30\ub2a5", + "supervisor_api": "Supervisor API", + "supervisor_version": "Supervisor \ubc84\uc804", + "supported": "\uc9c0\uc6d0 \uc5ec\ubd80", + "update_channel": "\uc5c5\ub370\uc774\ud2b8 \ucc44\ub110", + "version_api": "\ubc84\uc804 API" + } + }, + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/lb.json b/homeassistant/components/hassio/translations/lb.json index 54aae5a2c59..c0d0f42ed94 100644 --- a/homeassistant/components/hassio/translations/lb.json +++ b/homeassistant/components/hassio/translations/lb.json @@ -15,5 +15,5 @@ "version_api": "API Versioun" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/lt.json b/homeassistant/components/hassio/translations/lt.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/lt.json +++ b/homeassistant/components/hassio/translations/lt.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/lv.json b/homeassistant/components/hassio/translations/lv.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/lv.json +++ b/homeassistant/components/hassio/translations/lv.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/nb.json b/homeassistant/components/hassio/translations/nb.json deleted file mode 100644 index d8a4c453015..00000000000 --- a/homeassistant/components/hassio/translations/nb.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "" -} \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/nl.json b/homeassistant/components/hassio/translations/nl.json index fca08d49d7c..7224857a10c 100644 --- a/homeassistant/components/hassio/translations/nl.json +++ b/homeassistant/components/hassio/translations/nl.json @@ -1,6 +1,7 @@ { "system_health": { "info": { + "board": "Bord", "disk_total": "Totale schijfruimte", "disk_used": "Gebruikte schijfruimte", "docker_version": "Docker versie", @@ -14,5 +15,5 @@ "version_api": "API Versie" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/nn.json b/homeassistant/components/hassio/translations/nn.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/nn.json +++ b/homeassistant/components/hassio/translations/nn.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/no.json b/homeassistant/components/hassio/translations/no.json index e652f76a12c..9f0c5ba89b2 100644 --- a/homeassistant/components/hassio/translations/no.json +++ b/homeassistant/components/hassio/translations/no.json @@ -15,5 +15,5 @@ "version_api": "Versjon API" } }, - "title": "" + "title": "Home Assistant veileder" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/pl.json b/homeassistant/components/hassio/translations/pl.json index 10ee7c9d16c..5266d640d7c 100644 --- a/homeassistant/components/hassio/translations/pl.json +++ b/homeassistant/components/hassio/translations/pl.json @@ -15,5 +15,5 @@ "version_api": "Wersja API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/pt-BR.json b/homeassistant/components/hassio/translations/pt-BR.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/pt-BR.json +++ b/homeassistant/components/hassio/translations/pt-BR.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/pt.json b/homeassistant/components/hassio/translations/pt.json index 06083bae759..326560409e4 100644 --- a/homeassistant/components/hassio/translations/pt.json +++ b/homeassistant/components/hassio/translations/pt.json @@ -13,5 +13,5 @@ "supported": "Suportado" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/ro.json b/homeassistant/components/hassio/translations/ro.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/ro.json +++ b/homeassistant/components/hassio/translations/ro.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/ru.json b/homeassistant/components/hassio/translations/ru.json index 4f9b16621fc..56c3522ba3c 100644 --- a/homeassistant/components/hassio/translations/ru.json +++ b/homeassistant/components/hassio/translations/ru.json @@ -15,5 +15,5 @@ "version_api": "\u0412\u0435\u0440\u0441\u0438\u044f API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/sk.json b/homeassistant/components/hassio/translations/sk.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/sk.json +++ b/homeassistant/components/hassio/translations/sk.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/sl.json b/homeassistant/components/hassio/translations/sl.json index eb2f5f7ca8b..cfc71ce0832 100644 --- a/homeassistant/components/hassio/translations/sl.json +++ b/homeassistant/components/hassio/translations/sl.json @@ -14,5 +14,5 @@ "version_api": "API razli\u010dica" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/sv.json b/homeassistant/components/hassio/translations/sv.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/sv.json +++ b/homeassistant/components/hassio/translations/sv.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/th.json b/homeassistant/components/hassio/translations/th.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/th.json +++ b/homeassistant/components/hassio/translations/th.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/tr.json b/homeassistant/components/hassio/translations/tr.json index f2c2d52f60d..06a8d3fd661 100644 --- a/homeassistant/components/hassio/translations/tr.json +++ b/homeassistant/components/hassio/translations/tr.json @@ -15,5 +15,5 @@ "version_api": "S\u00fcr\u00fcm API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/uk.json b/homeassistant/components/hassio/translations/uk.json index 19a40730897..d25ad6e7979 100644 --- a/homeassistant/components/hassio/translations/uk.json +++ b/homeassistant/components/hassio/translations/uk.json @@ -15,5 +15,5 @@ "version_api": "\u0412\u0435\u0440\u0441\u0456\u044f API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/vi.json b/homeassistant/components/hassio/translations/vi.json index 981cb51c83a..91588c5529a 100644 --- a/homeassistant/components/hassio/translations/vi.json +++ b/homeassistant/components/hassio/translations/vi.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/zh-Hans.json b/homeassistant/components/hassio/translations/zh-Hans.json index 0d74360b8f3..a48cbeb95a8 100644 --- a/homeassistant/components/hassio/translations/zh-Hans.json +++ b/homeassistant/components/hassio/translations/zh-Hans.json @@ -15,5 +15,5 @@ "version_api": "API \u7248\u672c" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/zh-Hant.json b/homeassistant/components/hassio/translations/zh-Hant.json index 574b82358d6..b8b3a1e7b93 100644 --- a/homeassistant/components/hassio/translations/zh-Hant.json +++ b/homeassistant/components/hassio/translations/zh-Hant.json @@ -7,7 +7,7 @@ "docker_version": "Docker \u7248\u672c", "healthy": "\u5065\u5eb7\u5ea6", "host_os": "\u4e3b\u6a5f\u4f5c\u696d\u7cfb\u7d71", - "installed_addons": "\u5df2\u5b89\u88dd Add-on", + "installed_addons": "\u5df2\u5b89\u88dd\u9644\u52a0\u5143\u4ef6", "supervisor_api": "Supervisor API", "supervisor_version": "Supervisor \u7248\u672c", "supported": "\u652f\u63f4", @@ -15,5 +15,5 @@ "version_api": "\u7248\u672c API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/heos/translations/hu.json b/homeassistant/components/heos/translations/hu.json index cf688d6fdeb..2fbce1993cd 100644 --- a/homeassistant/components/heos/translations/hu.json +++ b/homeassistant/components/heos/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, diff --git a/homeassistant/components/heos/translations/id.json b/homeassistant/components/heos/translations/id.json new file mode 100644 index 00000000000..b7c57b46f66 --- /dev/null +++ b/homeassistant/components/heos/translations/id.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "host": "Host" + }, + "description": "Masukkan nama host atau alamat IP perangkat Heos (sebaiknya yang terhubung melalui kabel ke jaringan).", + "title": "Hubungkan ke Heos" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/heos/translations/ko.json b/homeassistant/components/heos/translations/ko.json index d17cbd0e4b7..5e4057ae482 100644 --- a/homeassistant/components/heos/translations/ko.json +++ b/homeassistant/components/heos/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" @@ -12,7 +12,7 @@ "host": "\ud638\uc2a4\ud2b8" }, "description": "Heos \uae30\uae30\uc758 \ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. (\uc720\uc120 \ub124\ud2b8\uc6cc\ud06c\ub85c \uc5f0\uacb0\ud558\ub294 \uac83\uc774 \uc88b\uc2b5\ub2c8\ub2e4)", - "title": "Heos \uc5d0 \uc5f0\uacb0\ud558\uae30" + "title": "Heos\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/hu.json b/homeassistant/components/hisense_aehw4a1/translations/hu.json index 0b21d7c4c32..c71972772eb 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/hu.json +++ b/homeassistant/components/hisense_aehw4a1/translations/hu.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "A h\u00e1l\u00f3zaton nem tal\u00e1lhat\u00f3 Hisense AEH-W4A1 eszk\u00f6z.", - "single_instance_allowed": "Csak egy konfigur\u00e1ci\u00f3 lehet Hisense AEH-W4A1 eset\u00e9n." + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "step": { "confirm": { diff --git a/homeassistant/components/hisense_aehw4a1/translations/id.json b/homeassistant/components/hisense_aehw4a1/translations/id.json new file mode 100644 index 00000000000..eb31eb979c4 --- /dev/null +++ b/homeassistant/components/hisense_aehw4a1/translations/id.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "confirm": { + "description": "Ingin menyiapkan Hisense AEH-W4A1?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hisense_aehw4a1/translations/ko.json b/homeassistant/components/hisense_aehw4a1/translations/ko.json index 491887280c0..dadb6cc7948 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/ko.json +++ b/homeassistant/components/hisense_aehw4a1/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/hisense_aehw4a1/translations/nl.json b/homeassistant/components/hisense_aehw4a1/translations/nl.json index 14f2445f63e..c1f353558b6 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/nl.json +++ b/homeassistant/components/hisense_aehw4a1/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Geen Hisense AEH-W4A1-apparaten gevonden op het netwerk.", - "single_instance_allowed": "Slechts een enkele configuratie van Hisense AEH-W4A1 is mogelijk." + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "step": { "confirm": { diff --git a/homeassistant/components/hive/translations/ca.json b/homeassistant/components/hive/translations/ca.json new file mode 100644 index 00000000000..eacccda82e7 --- /dev/null +++ b/homeassistant/components/hive/translations/ca.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "El compte ja ha estat configurat", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament", + "unknown_entry": "No s'ha pogut trobar l'entrada existent." + }, + "error": { + "invalid_code": "No s'ha pogut iniciar sessi\u00f3 a Hive. El codi de verificaci\u00f3 en dos passos no \u00e9s correcte.", + "invalid_password": "No s'ha pogut iniciar sessi\u00f3 a Hive. Contrasenya incorrecta, torna-ho a provar.", + "invalid_username": "No s'ha pogut iniciar sessi\u00f3 a Hive. L'adre\u00e7a de correu electr\u00f2nic no s'ha reconegut.", + "no_internet_available": "Cal una connexi\u00f3 a Internet per connectar-se al Hive.", + "unknown": "Error inesperat" + }, + "step": { + "2fa": { + "data": { + "2fa": "Codi de verificaci\u00f3 en dos passos" + }, + "description": "Introdueix codi d'autenticaci\u00f3 Hive. \n\n Introdueix el codi 0000 per demanar un altre codi.", + "title": "Verificaci\u00f3 en dos passos de Hive." + }, + "reauth": { + "data": { + "password": "Contrasenya", + "username": "Nom d'usuari" + }, + "description": "Torna a introduir la informaci\u00f3 d'inici de sessi\u00f3 del Hive.", + "title": "Inici de sessi\u00f3 Hive" + }, + "user": { + "data": { + "password": "Contrasenya", + "scan_interval": "Interval d'escaneig (segons)", + "username": "Nom d'usuari" + }, + "description": "Actualitza la informaci\u00f3 i configuraci\u00f3 d'inici de sessi\u00f3.", + "title": "Inici de sessi\u00f3 Hive" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Interval d'escaneig (segons)" + }, + "description": "Actualitza l'interval d'escaneig per sondejar les dades m\u00e9s sovint.", + "title": "Opcions de Hive" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/de.json b/homeassistant/components/hive/translations/de.json new file mode 100644 index 00000000000..1bd84f7f61c --- /dev/null +++ b/homeassistant/components/hive/translations/de.json @@ -0,0 +1,17 @@ +{ + "config": { + "step": { + "reauth": { + "data": { + "username": "Benutzername" + } + }, + "user": { + "data": { + "password": "PAsswort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/en.json b/homeassistant/components/hive/translations/en.json index 1d491d64ebf..32453da0a0c 100644 --- a/homeassistant/components/hive/translations/en.json +++ b/homeassistant/components/hive/translations/en.json @@ -1,53 +1,53 @@ { - "config": { - "step": { - "user": { - "title": "Hive Login", - "description": "Enter your Hive login information and configuration.", - "data": { - "username": "Username", - "password": "Password", - "scan_interval": "Scan Interval (seconds)" + "config": { + "abort": { + "already_configured": "Account is already configured", + "reauth_successful": "Re-authentication was successful", + "unknown_entry": "Unable to find existing entry." + }, + "error": { + "invalid_code": "Failed to sign into Hive. Your two-factor authentication code was incorrect.", + "invalid_password": "Failed to sign into Hive. Incorrect password please try again.", + "invalid_username": "Failed to sign into Hive. Your email address is not recognised.", + "no_internet_available": "An internet connection is required to connect to Hive.", + "unknown": "Unexpected error" + }, + "step": { + "2fa": { + "data": { + "2fa": "Two-factor code" + }, + "description": "Enter your Hive authentication code. \n \n Please enter code 0000 to request another code.", + "title": "Hive Two-factor Authentication." + }, + "reauth": { + "data": { + "password": "Password", + "username": "Username" + }, + "description": "Re-enter your Hive login information.", + "title": "Hive Login" + }, + "user": { + "data": { + "password": "Password", + "scan_interval": "Scan Interval (seconds)", + "username": "Username" + }, + "description": "Enter your Hive login information and configuration.", + "title": "Hive Login" + } } - }, - "2fa": { - "title": "Hive Two-factor Authentication.", - "description": "Enter your Hive authentication code. \n \n Please enter code 0000 to request another code.", - "data": { - "2fa": "Two-factor code" - } - }, - "reauth": { - "title": "Hive Login", - "description": "Re-enter your Hive login information.", - "data": { - "username": "Username", - "password": "Password" - } - } }, - "error": { - "invalid_username": "Failed to sign into Hive. Your email address is not recognised.", - "invalid_password": "Failed to sign into Hive. Incorrect password please try again.", - "invalid_code": "Failed to sign into Hive. Your two-factor authentication code was incorrect.", - "no_internet_available": "An internet connection is required to connect to Hive.", - "unknown": "Unexpected error" - }, - "abort": { - "already_configured": "Account is already configured", - "unknown_entry": "Unable to find existing entry.", - "reauth_successful": "Re-authentication was successful" - } - }, - "options": { - "step": { - "user": { - "title": "Options for Hive", - "description": "Update the scan interval to poll for data more often.", - "data": { - "scan_interval": "Scan Interval (seconds)" + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Scan Interval (seconds)" + }, + "description": "Update the scan interval to poll for data more often.", + "title": "Options for Hive" + } } - } } - } } \ No newline at end of file diff --git a/homeassistant/components/hive/translations/et.json b/homeassistant/components/hive/translations/et.json new file mode 100644 index 00000000000..5cffcb036d3 --- /dev/null +++ b/homeassistant/components/hive/translations/et.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Kasutaja on juba seadistatud", + "reauth_successful": "Taastuvastamine \u00f5nnestus", + "unknown_entry": "Olemasolevat kirjet ei leitud." + }, + "error": { + "invalid_code": "Hive sisselogimine nurjus. Kaheastmeline autentimiskood oli vale.", + "invalid_password": "Hive sisselogimine nurjus. Vale parool, proovi uuesti.", + "invalid_username": "Hive sisselogimine nurjus. E-posti aadressi ei tuvastatud.", + "no_internet_available": "Hive-ga \u00fchenduse loomiseks on vajalik Interneti\u00fchendus.", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "2fa": { + "data": { + "2fa": "Kaheastmelise tuvastuse kood" + }, + "description": "Sisesta oma Hive autentimiskood. \n\n Uue koodi taotlemiseks sisesta kood 0000.", + "title": "Hive kaheastmeline autentimine." + }, + "reauth": { + "data": { + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + }, + "description": "Sisesta oma Hive sisselogimisandmed uuesti.", + "title": "Hive sisselogimine" + }, + "user": { + "data": { + "password": "Salas\u00f5na", + "scan_interval": "P\u00e4ringute intervall (sekundites)", + "username": "Kasutajanimi" + }, + "description": "Sisesta oma Hive sisselogimisteave ja s\u00e4tted.", + "title": "Hive sisselogimine" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "P\u00e4ringute intervall (sekundites)" + }, + "description": "Muuda k\u00fcsitlemise intervalli p\u00e4ringute tihendamiseks.", + "title": "Hive s\u00e4tted" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/fr.json b/homeassistant/components/hive/translations/fr.json new file mode 100644 index 00000000000..5868a2bf175 --- /dev/null +++ b/homeassistant/components/hive/translations/fr.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi", + "unknown_entry": "Impossible de trouver l'entr\u00e9e existante." + }, + "error": { + "invalid_code": "\u00c9chec de la connexion \u00e0 Hive. Votre code d'authentification \u00e0 deux facteurs \u00e9tait incorrect.", + "invalid_password": "\u00c9chec de la connexion \u00e0 Hive. Mot de passe incorrect, veuillez r\u00e9essayer.", + "invalid_username": "\u00c9chec de la connexion \u00e0 Hive. Votre adresse e-mail n'est pas reconnue.", + "no_internet_available": "Une connexion Internet est requise pour se connecter \u00e0 Hive.", + "unknown": "Erreur inattendue" + }, + "step": { + "2fa": { + "data": { + "2fa": "Code \u00e0 deux facteurs" + }, + "description": "Entrez votre code d\u2019authentification Hive. \n \nVeuillez entrer le code 0000 pour demander un autre code.", + "title": "Authentification \u00e0 deux facteurs Hive." + }, + "reauth": { + "data": { + "password": "Mot de passe", + "username": "Nom d'utilisateur" + }, + "description": "Entrez \u00e0 nouveau vos informations de connexion Hive.", + "title": "Connexion \u00e0 Hive" + }, + "user": { + "data": { + "password": "Mot de passe", + "scan_interval": "Intervalle de scan (secondes)", + "username": "Nom d'utilisateur" + }, + "description": "Entrez vos informations de connexion et votre configuration Hive.", + "title": "Connexion \u00e0 Hive" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Intervalle de scan (secondes)" + }, + "description": "Mettez \u00e0 jour l\u2019intervalle d\u2019analyse pour obtenir des donn\u00e9es plus souvent.", + "title": "Options pour Hive" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/ko.json b/homeassistant/components/hive/translations/ko.json new file mode 100644 index 00000000000..99c8c132229 --- /dev/null +++ b/homeassistant/components/hive/translations/ko.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4", + "unknown_entry": "\uae30\uc874 \ud56d\ubaa9\uc744 \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." + }, + "error": { + "invalid_code": "Hive\uc5d0 \ub85c\uadf8\uc778\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. 2\ub2e8\uacc4 \uc778\uc99d \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "invalid_password": "Hive\uc5d0 \ub85c\uadf8\uc778\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ube44\ubc00\ubc88\ud638\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "invalid_username": "Hive\uc5d0 \ub85c\uadf8\uc778\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uc774\uba54\uc77c \uc8fc\uc18c\ub97c \uc778\uc2dd\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", + "no_internet_available": "Hive\uc5d0 \uc5f0\uacb0\ud558\ub824\uba74 \uc778\ud130\ub137 \uc5f0\uacb0\uc774 \ud544\uc694\ud569\ub2c8\ub2e4.", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "2fa": { + "data": { + "2fa": "2\ub2e8\uacc4 \uc778\uc99d \ucf54\ub4dc" + }, + "description": "Hive \uc778\uc99d \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\n\ub2e4\ub978 \ucf54\ub4dc\ub97c \uc694\uccad\ud558\ub824\uba74 \ucf54\ub4dc 0000\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "Hive 2\ub2e8\uacc4 \uc778\uc99d." + }, + "reauth": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "Hive \ub85c\uadf8\uc778 \uc815\ubcf4\ub97c \ub2e4\uc2dc \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "Hive \ub85c\uadf8\uc778" + }, + "user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "scan_interval": "\uc2a4\uce94 \uac04\uaca9 (\ucd08)", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "Hive \ub85c\uadf8\uc778 \uc815\ubcf4 \ubc0f \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "Hive \ub85c\uadf8\uc778" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Scan Interval (seconds)" + }, + "description": "\ub370\uc774\ud130\ub97c \ub354 \uc790\uc8fc \ud3f4\ub9c1\ud558\ub824\uba74 \uac80\uc0c9 \uac04\uaca9\uc744 \uc5c5\ub370\uc774\ud2b8\ud574\uc8fc\uc138\uc694.", + "title": "Hive \uc635\uc158" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/pt.json b/homeassistant/components/hive/translations/pt.json new file mode 100644 index 00000000000..46b7da5620a --- /dev/null +++ b/homeassistant/components/hive/translations/pt.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Conta j\u00e1 configurada", + "reauth_successful": "Reautentica\u00e7\u00e3o bem sucedida" + }, + "step": { + "reauth": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + }, + "user": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/ru.json b/homeassistant/components/hive/translations/ru.json new file mode 100644 index 00000000000..42df1bf9a24 --- /dev/null +++ b/homeassistant/components/hive/translations/ru.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant.", + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e.", + "unknown_entry": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0439\u0442\u0438 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c." + }, + "error": { + "invalid_code": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u043e\u0439\u0442\u0438 \u0432 Hive. \u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043e\u0434 \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", + "invalid_password": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u043e\u0439\u0442\u0438 \u0432 Hive. \u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043f\u0430\u0440\u043e\u043b\u044c.", + "invalid_username": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u043e\u0439\u0442\u0438 \u0432 Hive. \u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441 \u043d\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d.", + "no_internet_available": "\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "2fa": { + "data": { + "2fa": "\u041a\u043e\u0434 \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u0434 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 Hive \u0438\u043b\u0438 \u0432\u0432\u0435\u0434\u0438\u0442\u0435 0000, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0434\u0440\u0443\u0433\u043e\u0439 \u043a\u043e\u0434.", + "title": "\u0414\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f" + }, + "reauth": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u041b\u043e\u0433\u0438\u043d" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0432 Hive.", + "title": "Hive" + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0440\u043e\u0441\u0430 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", + "username": "\u041b\u043e\u0433\u0438\u043d" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u0430 \u0432 Hive.", + "title": "Hive" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0440\u043e\u0441\u0430 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + }, + "description": "\u0427\u0442\u043e\u0431\u044b \u0447\u0430\u0449\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435, \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u0435 \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0440\u043e\u0441\u0430.", + "title": "Hive" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/zh-Hant.json b/homeassistant/components/hive/translations/zh-Hant.json new file mode 100644 index 00000000000..0af7e218f6e --- /dev/null +++ b/homeassistant/components/hive/translations/zh-Hant.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f", + "unknown_entry": "\u7121\u6cd5\u627e\u5230\u73fe\u6709\u5be6\u9ad4\u3002" + }, + "error": { + "invalid_code": "Hive \u767b\u5165\u5931\u6557\u3002\u96d9\u91cd\u8a8d\u8b49\u78bc\u4e0d\u6b63\u78ba\u3002", + "invalid_password": "Hive \u767b\u5165\u5931\u6557\u3002\u5bc6\u78bc\u932f\u8aa4\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002", + "invalid_username": "Hive \u767b\u5165\u5931\u6557\u3002\u627e\u4e0d\u5230\u96fb\u5b50\u90f5\u4ef6\u3002", + "no_internet_available": "\u9700\u8981\u7db2\u969b\u7db2\u8def\u9023\u7dda\u4ee5\u9023\u7dda\u81f3 Hive\u3002", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "2fa": { + "data": { + "2fa": "\u96d9\u91cd\u8a8d\u8b49\u78bc" + }, + "description": "\u8f38\u5165 Hive \u8a8d\u8b49\u78bc\u3002\n \n \u8acb\u8f38\u5165 0000 \u4ee5\u7372\u53d6\u5176\u4ed6\u8a8d\u8b49\u78bc\u3002", + "title": "\u96d9\u91cd\u8a8d\u8b49" + }, + "reauth": { + "data": { + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "description": "\u91cd\u65b0\u8f38\u5165 Hive \u767b\u5165\u8cc7\u8a0a\u3002", + "title": "Hive \u767b\u5165\u8cc7\u8a0a" + }, + "user": { + "data": { + "password": "\u5bc6\u78bc", + "scan_interval": "\u6383\u63cf\u9593\u8ddd\uff08\u79d2\uff09", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "description": "\u8f38\u5165 Hive \u767b\u5165\u8cc7\u8a0a\u8207\u8a2d\u5b9a\u3002", + "title": "Hive \u767b\u5165\u8cc7\u8a0a" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "\u6383\u63cf\u9593\u8ddd\uff08\u79d2\uff09" + }, + "description": "\u66f4\u65b0\u6383\u63cf\u9593\u8ddd\u4ee5\u66f4\u983b\u7e41\u7372\u53d6\u66f4\u65b0\u3002", + "title": "Hive \u9078\u9805" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hlk_sw16/translations/hu.json b/homeassistant/components/hlk_sw16/translations/hu.json index 3b2d79a34a7..0abcc301f0c 100644 --- a/homeassistant/components/hlk_sw16/translations/hu.json +++ b/homeassistant/components/hlk_sw16/translations/hu.json @@ -2,6 +2,20 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/hlk_sw16/translations/id.json b/homeassistant/components/hlk_sw16/translations/id.json new file mode 100644 index 00000000000..ed8fde32106 --- /dev/null +++ b/homeassistant/components/hlk_sw16/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/home_connect/translations/hu.json b/homeassistant/components/home_connect/translations/hu.json index f02fb97b9df..aa43f65b520 100644 --- a/homeassistant/components/home_connect/translations/hu.json +++ b/homeassistant/components/home_connect/translations/hu.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz." + }, "create_entry": { - "default": "Sikeres autentik\u00e1ci\u00f3" + "default": "Sikeres hiteles\u00edt\u00e9s" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/home_connect/translations/id.json b/homeassistant/components/home_connect/translations/id.json new file mode 100644 index 00000000000..bc6089beb2b --- /dev/null +++ b/homeassistant/components/home_connect/translations/id.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})" + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant/translations/fr.json b/homeassistant/components/homeassistant/translations/fr.json index 194254a0384..8d76ff76b79 100644 --- a/homeassistant/components/homeassistant/translations/fr.json +++ b/homeassistant/components/homeassistant/translations/fr.json @@ -6,7 +6,7 @@ "dev": "D\u00e9veloppement", "docker": "Docker", "docker_version": "Docker", - "hassio": "Superviseur", + "hassio": "Supervisor", "host_os": "Home Assistant OS", "installation_type": "Type d'installation", "os_name": "Famille du syst\u00e8me d'exploitation", diff --git a/homeassistant/components/homeassistant/translations/he.json b/homeassistant/components/homeassistant/translations/he.json new file mode 100644 index 00000000000..f45b17b1a13 --- /dev/null +++ b/homeassistant/components/homeassistant/translations/he.json @@ -0,0 +1,7 @@ +{ + "system_health": { + "info": { + "os_name": "\u05de\u05e9\u05e4\u05d7\u05ea \u05de\u05e2\u05e8\u05db\u05ea \u05d4\u05e4\u05e2\u05dc\u05d4" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant/translations/hu.json b/homeassistant/components/homeassistant/translations/hu.json index e202a747ac7..f6bfe03321e 100644 --- a/homeassistant/components/homeassistant/translations/hu.json +++ b/homeassistant/components/homeassistant/translations/hu.json @@ -6,13 +6,13 @@ "dev": "Fejleszt\u00e9s", "docker": "Docker", "docker_version": "Docker", - "hassio": "Adminisztr\u00e1tor", + "hassio": "Supervisor", "host_os": "Home Assistant OS", "installation_type": "Telep\u00edt\u00e9s t\u00edpusa", "os_name": "Oper\u00e1ci\u00f3s rendszer csal\u00e1d", "os_version": "Oper\u00e1ci\u00f3s rendszer verzi\u00f3ja", "python_version": "Python verzi\u00f3", - "supervisor": "Adminisztr\u00e1tor", + "supervisor": "Supervisor", "timezone": "Id\u0151z\u00f3na", "version": "Verzi\u00f3", "virtualenv": "Virtu\u00e1lis k\u00f6rnyezet" diff --git a/homeassistant/components/homeassistant/translations/id.json b/homeassistant/components/homeassistant/translations/id.json new file mode 100644 index 00000000000..2ee86bba815 --- /dev/null +++ b/homeassistant/components/homeassistant/translations/id.json @@ -0,0 +1,21 @@ +{ + "system_health": { + "info": { + "arch": "Arsitektur CPU", + "chassis": "Kerangka", + "dev": "Pengembangan", + "docker": "Docker", + "docker_version": "Docker", + "hassio": "Supervisor", + "host_os": "Home Assistant OS", + "installation_type": "Jenis Instalasi", + "os_name": "Keluarga Sistem Operasi", + "os_version": "Versi Sistem Operasi", + "python_version": "Versi Python", + "supervisor": "Supervisor", + "timezone": "Zona Waktu", + "version": "Versi", + "virtualenv": "Lingkungan Virtual" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant/translations/ko.json b/homeassistant/components/homeassistant/translations/ko.json new file mode 100644 index 00000000000..801d63fd449 --- /dev/null +++ b/homeassistant/components/homeassistant/translations/ko.json @@ -0,0 +1,21 @@ +{ + "system_health": { + "info": { + "arch": "CPU \uc544\ud0a4\ud14d\ucc98", + "chassis": "\uc100\uc2dc", + "dev": "\uac1c\ubc1c\uc790 \ubaa8\ub4dc", + "docker": "Docker", + "docker_version": "Docker", + "hassio": "Supervisor", + "host_os": "Home Assistant OS", + "installation_type": "\uc124\uce58 \uc720\ud615", + "os_name": "\uc6b4\uc601 \uccb4\uc81c \uc81c\ud488\uad70", + "os_version": "\uc6b4\uc601 \uccb4\uc81c \ubc84\uc804", + "python_version": "Python \ubc84\uc804", + "supervisor": "Supervisor", + "timezone": "\uc2dc\uac04\ub300", + "version": "\ubc84\uc804", + "virtualenv": "\uac00\uc0c1 \ud658\uacbd" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant/translations/nl.json b/homeassistant/components/homeassistant/translations/nl.json index 47b69068ea3..b4e33dcd7aa 100644 --- a/homeassistant/components/homeassistant/translations/nl.json +++ b/homeassistant/components/homeassistant/translations/nl.json @@ -11,7 +11,7 @@ "installation_type": "Type installatie", "os_name": "Besturingssysteemfamilie", "os_version": "Versie van het besturingssysteem", - "python_version": "Python versie", + "python_version": "Python-versie", "supervisor": "Supervisor", "timezone": "Tijdzone", "version": "Versie", diff --git a/homeassistant/components/homekit/translations/he.json b/homeassistant/components/homekit/translations/he.json index 6acebca0ca4..cb5a530b739 100644 --- a/homeassistant/components/homekit/translations/he.json +++ b/homeassistant/components/homekit/translations/he.json @@ -4,7 +4,8 @@ "include_exclude": { "data": { "mode": "\u05de\u05e6\u05d1" - } + }, + "title": "\u05d1\u05d7\u05e8 \u05d9\u05e9\u05d5\u05d9\u05d5\u05ea \u05e9\u05d9\u05d9\u05db\u05dc\u05dc\u05d5" }, "init": { "data": { diff --git a/homeassistant/components/homekit/translations/hu.json b/homeassistant/components/homekit/translations/hu.json index 4cf1f44c439..7cc2577cb31 100644 --- a/homeassistant/components/homekit/translations/hu.json +++ b/homeassistant/components/homekit/translations/hu.json @@ -1,16 +1,52 @@ { + "config": { + "step": { + "accessory_mode": { + "data": { + "entity_id": "Entit\u00e1s" + }, + "title": "V\u00e1laszd ki a felvenni k\u00edv\u00e1nt entit\u00e1st" + }, + "pairing": { + "title": "HomeKit p\u00e1ros\u00edt\u00e1s" + }, + "user": { + "data": { + "include_domains": "Felvenni k\u00edv\u00e1nt domainek", + "mode": "M\u00f3d" + }, + "title": "Felvenni k\u00edv\u00e1nt domainek kiv\u00e1laszt\u00e1sa" + } + } + }, "options": { "step": { + "advanced": { + "title": "Halad\u00f3 be\u00e1ll\u00edt\u00e1sok" + }, + "cameras": { + "data": { + "camera_copy": "A nat\u00edv H.264 streameket t\u00e1mogat\u00f3 kamer\u00e1k" + }, + "title": "V\u00e1laszd ki a kamera vide\u00f3 kodekj\u00e9t." + }, "include_exclude": { "data": { "entities": "Entit\u00e1sok", "mode": "M\u00f3d" - } + }, + "title": "V\u00e1laszd ki a felvenni k\u00edv\u00e1nt entit\u00e1sokat" }, "init": { "data": { + "include_domains": "Felvenni k\u00edv\u00e1nt domainek", "mode": "M\u00f3d" - } + }, + "title": "V\u00e1laszd ki a felvenni k\u00edv\u00e1nt domaineket." + }, + "yaml": { + "description": "Ez a bejegyz\u00e9s YAML-en kereszt\u00fcl vez\u00e9relhet\u0151", + "title": "HomeKit be\u00e1ll\u00edt\u00e1sok m\u00f3dos\u00edt\u00e1sa" } } } diff --git a/homeassistant/components/homekit/translations/id.json b/homeassistant/components/homekit/translations/id.json new file mode 100644 index 00000000000..588631a5215 --- /dev/null +++ b/homeassistant/components/homekit/translations/id.json @@ -0,0 +1,75 @@ +{ + "config": { + "abort": { + "port_name_in_use": "Aksesori atau bridge dengan nama atau port yang sama telah dikonfigurasi." + }, + "step": { + "accessory_mode": { + "data": { + "entity_id": "Entitas" + }, + "description": "Pilih entitas yang akan disertakan. Dalam mode aksesori, hanya satu entitas yang disertakan.", + "title": "Pilih entitas yang akan disertakan" + }, + "bridge_mode": { + "data": { + "include_domains": "Domain yang disertakan" + }, + "description": "Pilih domain yang akan disertakan. Semua entitas yang didukung di domain akan disertakan.", + "title": "Pilih domain yang akan disertakan" + }, + "pairing": { + "description": "Untuk menyelesaikan pemasangan ikuti petunjuk di \"Notifikasi\" di bawah \"Pemasangan HomeKit\".", + "title": "Pasangkan HomeKit" + }, + "user": { + "data": { + "auto_start": "Mulai otomatis (nonaktifkan jika menggunakan Z-Wave atau sistem mulai tertunda lainnya)", + "include_domains": "Domain yang disertakan", + "mode": "Mode" + }, + "description": "Pilih domain yang akan disertakan. Semua entitas yang didukung di domain akan disertakan. Instans HomeKit terpisah dalam mode aksesori akan dibuat untuk setiap pemutar media TV dan kamera.", + "title": "Pilih domain yang akan disertakan" + } + } + }, + "options": { + "step": { + "advanced": { + "data": { + "auto_start": "Mulai otomatis (nonaktifkan jika Anda memanggil layanan homekit.start secara manual)", + "safe_mode": "Mode Aman (aktifkan hanya jika pemasangan gagal)" + }, + "description": "Pengaturan ini hanya perlu disesuaikan jika HomeKit tidak berfungsi.", + "title": "Konfigurasi Tingkat Lanjut" + }, + "cameras": { + "data": { + "camera_copy": "Kamera yang mendukung aliran H.264 asli" + }, + "description": "Periksa semua kamera yang mendukung streaming H.264 asli. Jika kamera tidak mengeluarkan aliran H.264, sistem akan mentranskode video ke H.264 untuk HomeKit. Proses transcoding membutuhkan CPU kinerja tinggi dan tidak mungkin bekerja pada komputer papan tunggal.", + "title": "Pilih codec video kamera." + }, + "include_exclude": { + "data": { + "entities": "Entitas", + "mode": "Mode" + }, + "description": "Pilih entitas yang akan disertakan. Dalam mode aksesori, hanya satu entitas yang disertakan. Dalam mode \"bridge include\", semua entitas di domain akan disertakan, kecuali entitas tertentu dipilih. Dalam mode \"bridge exclude\", semua entitas di domain akan disertakan, kecuali untuk entitas tertentu yang dipilih. Untuk kinerja terbaik, aksesori HomeKit terpisah diperlukan untuk masing-masing pemutar media, TV, dan kamera.", + "title": "Pilih entitas untuk disertakan" + }, + "init": { + "data": { + "include_domains": "Domain yang disertakan", + "mode": "Mode" + }, + "description": "HomeKit dapat dikonfigurasi untuk memaparkakan sebuah bridge atau sebuah aksesori. Dalam mode aksesori, hanya satu entitas yang dapat digunakan. Mode aksesori diperlukan agar pemutar media dengan kelas perangkat TV berfungsi dengan baik. Entitas di \"Domain yang akan disertakan\" akan disertakan ke HomeKit. Anda akan dapat memilih entitas mana yang akan disertakan atau dikecualikan dari daftar ini pada layar berikutnya.", + "title": "Pilih domain yang akan disertakan." + }, + "yaml": { + "description": "Entri ini dikontrol melalui YAML", + "title": "Sesuaikan Opsi HomeKit" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/ko.json b/homeassistant/components/homekit/translations/ko.json index bc8e138fbaf..274898425cb 100644 --- a/homeassistant/components/homekit/translations/ko.json +++ b/homeassistant/components/homekit/translations/ko.json @@ -1,16 +1,25 @@ { "config": { "abort": { - "port_name_in_use": "\uc774\ub984\uc774\ub098 \ud3ec\ud2b8\uac00 \uac19\uc740 \ube0c\ub9ac\uc9c0\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "port_name_in_use": "\uc774\ub984\uc774\ub098 \ud3ec\ud2b8\uac00 \uac19\uc740 \ube0c\ub9ac\uc9c0 \ub610\ub294 \uc561\uc138\uc11c\ub9ac\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "\uad6c\uc131\uc694\uc18c" + }, + "description": "\ud3ec\ud568\ud560 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uc5d0\uc11c\ub294 \ub2e8\uc77c \uad6c\uc131\uc694\uc18c\ub9cc \ud3ec\ud568\ub429\ub2c8\ub2e4.", + "title": "\ud3ec\ud568\ud560 \uad6c\uc131\uc694\uc18c \uc120\ud0dd\ud558\uae30" + }, "bridge_mode": { "data": { "include_domains": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778" - } + }, + "description": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \uc9c0\uc6d0\ub418\ub294 \ub3c4\uba54\uc778\uc758 \ubaa8\ub4e0 \uad6c\uc131\uc694\uc18c\uac00 \ud3ec\ud568\ub429\ub2c8\ub2e4.", + "title": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778 \uc120\ud0dd\ud558\uae30" }, "pairing": { - "description": "{name} \uc774(\uac00) \uc900\ube44\ub418\uba74 \"\uc54c\ub9bc\"\uc5d0\uc11c \"HomeKit \ube0c\ub9ac\uc9c0 \uc124\uc815\"\uc73c\ub85c \ud398\uc5b4\ub9c1\uc744 \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "\"\uc54c\ub9bc\"\uc5d0\uc11c \"HomeKit Pairing\"\uc5d0 \uc788\ub294 \uc548\ub0b4\uc5d0 \ub530\ub77c \ud398\uc5b4\ub9c1\uc744 \uc644\ub8cc\ud574\uc8fc\uc138\uc694.", "title": "HomeKit \ud398\uc5b4\ub9c1\ud558\uae30" }, "user": { @@ -19,8 +28,8 @@ "include_domains": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778", "mode": "\ubaa8\ub4dc" }, - "description": "HomeKit \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \ud1b5\ud574 HomeKit \uc758 Home Assistant \uad6c\uc131\uc694\uc18c\uc5d0 \uc561\uc138\uc2a4\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ube0c\ub9ac\uc9c0 \ubaa8\ub4dc\uc5d0\uc11c HomeKit \ube0c\ub9ac\uc9c0\ub294 \ube0c\ub9ac\uc9c0 \uc790\uccb4\ub97c \ud3ec\ud568\ud558\uc5ec \uc778\uc2a4\ud134\uc2a4\ub2f9 150 \uac1c\uc758 \uc561\uc138\uc11c\ub9ac\ub85c \uc81c\ud55c\ub429\ub2c8\ub2e4. \ucd5c\ub300 \uc561\uc138\uc11c\ub9ac \uac1c\uc218\ubcf4\ub2e4 \ub9ce\uc740 \uc218\uc758 \ube0c\ub9ac\uc9c0\ub97c \uc0ac\uc6a9\ud558\ub824\ub294 \uacbd\uc6b0, \uc11c\ub85c \ub2e4\ub978 \ub3c4\uba54\uc778\uc5d0 \ub300\ud574 \uc5ec\ub7ec\uac1c\uc758 HomeKit \ube0c\ub9ac\uc9c0\ub97c \uc0ac\uc6a9\ud558\ub294 \uac83\uc774 \uc88b\uc2b5\ub2c8\ub2e4. \uad6c\uc131\uc694\uc18c\uc758 \uc790\uc138\ud55c \uad6c\uc131\uc740 YAML \uc744 \ud1b5\ud574\uc11c\ub9cc \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ucd5c\uc0c1\uc758 \uc131\ub2a5\uacfc \uc608\uae30\uce58 \uc54a\uc740 \uc0ac\uc6a9 \ubd88\uac00\ub2a5\ud55c \uc0c1\ud0dc\ub97c \ubc29\uc9c0\ud558\ub824\uba74 \uac01\uac01\uc758 TV \ubbf8\ub514\uc5b4 \ud50c\ub808\uc774\uc5b4\uc640 \uce74\uba54\ub77c\uc5d0 \ub300\ud574 \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uc5d0\uc11c \ubcc4\ub3c4\uc758 HomeKit \uc778\uc2a4\ud134\uc2a4\ub97c \uc0dd\uc131\ud558\uace0 \ud398\uc5b4\ub9c1\ud574\uc8fc\uc138\uc694.", - "title": "HomeKit \ud65c\uc131\ud654\ud558\uae30" + "description": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \ub3c4\uba54\uc778\uc5d0\uc11c \uc9c0\uc6d0\ub418\ub294 \ubaa8\ub4e0 \uad6c\uc131\uc694\uc18c\uac00 \ud3ec\ud568\ub429\ub2c8\ub2e4. \uac01 TV \ubbf8\ub514\uc5b4 \ud50c\ub808\uc774\uc5b4\uc640 \uce74\uba54\ub77c\uc5d0 \ub300\ud574 \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uc758 \uac1c\ubcc4 HomeKit \uc778\uc2a4\ud134\uc2a4\uac00 \uc0dd\uc131\ub429\ub2c8\ub2e4.", + "title": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778 \uc120\ud0dd\ud558\uae30" } } }, @@ -31,31 +40,34 @@ "auto_start": "\uc790\ub3d9 \uc2dc\uc791 (homekit.start \uc11c\ube44\uc2a4\ub97c \uc218\ub3d9\uc73c\ub85c \ud638\ucd9c\ud558\ub824\uba74 \ube44\ud65c\uc131\ud654\ud574\uc8fc\uc138\uc694)", "safe_mode": "\uc548\uc804 \ubaa8\ub4dc (\ud398\uc5b4\ub9c1\uc774 \uc2e4\ud328\ud55c \uacbd\uc6b0\uc5d0\ub9cc \ud65c\uc131\ud654\ud574\uc8fc\uc138\uc694)" }, - "description": "\uc774 \uc124\uc815\uc740 HomeKit \uac00 \uc791\ub3d9\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0\uc5d0\ub9cc \uc124\uc815\ud574\uc8fc\uc138\uc694.", - "title": "\uace0\uae09 \uad6c\uc131\ud558\uae30" + "description": "\uc774 \uc124\uc815\uc740 HomeKit\uac00 \uc791\ub3d9\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0\uc5d0\ub9cc \uc124\uc815\ud574\uc8fc\uc138\uc694.", + "title": "\uace0\uae09 \uad6c\uc131" }, "cameras": { "data": { "camera_copy": "\ub124\uc774\ud2f0\ube0c H.264 \uc2a4\ud2b8\ub9bc\uc744 \uc9c0\uc6d0\ud558\ub294 \uce74\uba54\ub77c" }, - "description": "\ub124\uc774\ud2f0\ube0c H.264 \uc2a4\ud2b8\ub9bc\uc744 \uc9c0\uc6d0\ud558\ub294 \ubaa8\ub4e0 \uce74\uba54\ub77c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694. \uce74\uba54\ub77c\uac00 H.264 \uc2a4\ud2b8\ub9bc\uc744 \ucd9c\ub825\ud558\uc9c0 \uc54a\uc73c\uba74 \uc2dc\uc2a4\ud15c\uc740 \ube44\ub514\uc624\ub97c HomeKit \uc6a9 H.264 \ud3ec\ub9f7\uc73c\ub85c \ubcc0\ud658\uc2dc\ud0b5\ub2c8\ub2e4. \ud2b8\ub79c\uc2a4\ucf54\ub529 \ubcc0\ud658\uc5d0\ub294 \ub192\uc740 CPU \uc131\ub2a5\uc774 \ud544\uc694\ud558\uba70 Raspberry Pi \uc640 \uac19\uc740 \ub2e8\uc77c \ubcf4\ub4dc \ucef4\ud4e8\ud130\uc5d0\uc11c\ub294 \uc791\ub3d9\ud558\uc9c0 \uc54a\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "\ub124\uc774\ud2f0\ube0c H.264 \uc2a4\ud2b8\ub9bc\uc744 \uc9c0\uc6d0\ud558\ub294 \ubaa8\ub4e0 \uce74\uba54\ub77c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694. \uce74\uba54\ub77c\uac00 H.264 \uc2a4\ud2b8\ub9bc\uc744 \ucd9c\ub825\ud558\uc9c0 \uc54a\uc73c\uba74 \uc2dc\uc2a4\ud15c\uc740 \ube44\ub514\uc624\ub97c HomeKit\uc6a9 H.264 \ud3ec\ub9f7\uc73c\ub85c \ubcc0\ud658\uc2dc\ud0b5\ub2c8\ub2e4. \ud2b8\ub79c\uc2a4\ucf54\ub529 \ubcc0\ud658\uc5d0\ub294 \ub192\uc740 CPU \uc131\ub2a5\uc774 \ud544\uc694\ud558\uba70 Raspberry Pi\uc640 \uac19\uc740 \ub2e8\uc77c \ubcf4\ub4dc \ucef4\ud4e8\ud130\uc5d0\uc11c\ub294 \uc791\ub3d9\ud558\uc9c0 \uc54a\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "\uce74\uba54\ub77c \ube44\ub514\uc624 \ucf54\ub371 \uc120\ud0dd\ud558\uae30" }, "include_exclude": { "data": { + "entities": "\uad6c\uc131\uc694\uc18c", "mode": "\ubaa8\ub4dc" - } + }, + "description": "\ud3ec\ud568\ud560 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uc5d0\uc11c\ub294 \ub2e8\uc77c \uad6c\uc131\uc694\uc18c\ub9cc \ud3ec\ud568\ub429\ub2c8\ub2e4. \ube0c\ub9ac\uc9c0 \ud3ec\ud568 \ubaa8\ub4dc\uc5d0\uc11c\ub294 \ud2b9\uc815 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud558\uc9c0 \uc54a\uc73c\uba74 \ub3c4\uba54\uc778\uc758 \ubaa8\ub4e0 \uad6c\uc131\uc694\uc18c\uac00 \ud3ec\ud568\ub429\ub2c8\ub2e4. \ube0c\ub9ac\uc9c0 \uc81c\uc678 \ubaa8\ub4dc\uc5d0\uc11c\ub294 \uc81c\uc678\ub41c \uad6c\uc131\uc694\uc18c\ub97c \ube80 \ub3c4\uba54\uc778\uc758 \ub098\uba38\uc9c0 \uad6c\uc131\uc694\uc18c\uac00 \ud3ec\ud568\ub429\ub2c8\ub2e4. \ucd5c\uc0c1\uc758 \uc131\ub2a5\uc744 \uc704\ud574 \uac01 TV \ubbf8\ub514\uc5b4 \ud50c\ub808\uc774\uc5b4\uc640 \uce74\uba54\ub77c\ub294 \ubcc4\ub3c4\uc758 HomeKit \uc561\uc138\uc11c\ub9ac\ub85c \uc0dd\uc131\ub429\ub2c8\ub2e4.", + "title": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778 \uc120\ud0dd\ud558\uae30" }, "init": { "data": { "include_domains": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778", "mode": "\ubaa8\ub4dc" }, - "description": "HomeKit \ub294 \ube0c\ub9ac\uc9c0 \ub610\ub294 \uc561\uc138\uc11c\ub9ac\ub97c \ub178\ucd9c\ud558\ub3c4\ub85d \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uc5d0\uc11c\ub294 \ub2e8\uc77c \uad6c\uc131\uc694\uc18c\ub9cc \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. TV \uae30\uae30 \ud074\ub798\uc2a4\uac00 \uc788\ub294 \ubbf8\ub514\uc5b4 \ud50c\ub808\uc774\uc5b4\uac00 \uc81c\ub300\ub85c \uc791\ub3d9\ud558\ub824\uba74 \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. \"\ud3ec\ud568\ud560 \ub3c4\uba54\uc778\"\uc758 \uad6c\uc131\uc694\uc18c\ub294 HomeKit \uc5d0 \ud3ec\ud568\ub429\ub2c8\ub2e4. \ub2e4\uc74c \ud654\uba74\uc5d0\uc11c \uc774 \ubaa9\ub85d\uc5d0 \ud3ec\ud568\ud558\uac70\ub098 \uc81c\uc678\ud560 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "\ube0c\ub9ac\uc9c0 \ub610\ub294 \ub2e8\uc77c \uc561\uc138\uc11c\ub9ac\ub97c \ub178\ucd9c\ud558\uc5ec HomeKit\ub97c \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uc5d0\uc11c\ub294 \ub2e8\uc77c \uad6c\uc131\uc694\uc18c\ub9cc \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. TV \uae30\uae30 \ud074\ub798\uc2a4\uac00 \uc788\ub294 \ubbf8\ub514\uc5b4 \ud50c\ub808\uc774\uc5b4\uac00 \uc81c\ub300\ub85c \uc791\ub3d9\ud558\ub824\uba74 \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. \"\ud3ec\ud568\ud560 \ub3c4\uba54\uc778\"\uc758 \uad6c\uc131\uc694\uc18c\uac00 HomeKit\uc5d0 \ud3ec\ud568\ub418\uba70, \ub2e4\uc74c \ud654\uba74\uc5d0\uc11c \uc774 \ubaa9\ub85d\uc5d0 \ud3ec\ud568\ud558\uac70\ub098 \uc81c\uc678\ud560 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694." }, "yaml": { - "description": "\uc774 \ud56d\ubaa9\uc740 YAML \uc744 \ud1b5\ud574 \uc81c\uc5b4\ub429\ub2c8\ub2e4", + "description": "\uc774 \ud56d\ubaa9\uc740 YAML\uc744 \ud1b5\ud574 \uc81c\uc5b4\ub429\ub2c8\ub2e4", "title": "HomeKit \uc635\uc158 \uc870\uc815\ud558\uae30" } } diff --git a/homeassistant/components/homekit/translations/nl.json b/homeassistant/components/homekit/translations/nl.json index 9013723ac6c..dce21a4b880 100644 --- a/homeassistant/components/homekit/translations/nl.json +++ b/homeassistant/components/homekit/translations/nl.json @@ -7,10 +7,19 @@ "accessory_mode": { "data": { "entity_id": "Entiteit" - } + }, + "description": "Kies de entiteit die moet worden opgenomen. In de accessoiremodus wordt slechts \u00e9\u00e9n entiteit opgenomen.", + "title": "Selecteer de entiteit die u wilt opnemen" + }, + "bridge_mode": { + "data": { + "include_domains": "Domeinen om op te nemen" + }, + "description": "Kies de domeinen die moeten worden opgenomen. Alle ondersteunde entiteiten in het domein zullen worden opgenomen.", + "title": "Selecteer domeinen die u wilt opnemen" }, "pairing": { - "description": "Zodra de {name} klaar is, is het koppelen beschikbaar in \"Meldingen\" als \"HomeKit Bridge Setup\".", + "description": "Volg de instructies in \"Meldingen\" onder \"HomeKit-koppeling\" om het koppelen te voltooien.", "title": "Koppel HomeKit" }, "user": { @@ -45,11 +54,12 @@ "data": { "entities": "Entiteiten", "mode": "Mode" - } + }, + "title": "Selecteer de entiteiten die u wilt opnemen" }, "init": { "data": { - "include_domains": "Op te nemen domeinen", + "include_domains": "Domeinen om op te nemen", "mode": "modus" }, "description": "HomeKit kan worden geconfigureerd om een brug of een enkel accessoire te tonen. In de accessoiremodus kan slechts \u00e9\u00e9n entiteit worden gebruikt. De accessoiremodus is vereist om mediaspelers met de tv-apparaatklasse correct te laten werken. Entiteiten in de \"Op te nemen domeinen\" zullen worden blootgesteld aan HomeKit. U kunt op het volgende scherm selecteren welke entiteiten u wilt opnemen of uitsluiten van deze lijst.", diff --git a/homeassistant/components/homekit_controller/translations/bg.json b/homeassistant/components/homekit_controller/translations/bg.json index 8e2762f9f32..01439889734 100644 --- a/homeassistant/components/homekit_controller/translations/bg.json +++ b/homeassistant/components/homekit_controller/translations/bg.json @@ -34,5 +34,19 @@ } } }, + "device_automation": { + "trigger_subtype": { + "button1": "\u0411\u0443\u0442\u043e\u043d 1", + "button10": "\u0411\u0443\u0442\u043e\u043d 10", + "button2": "\u0411\u0443\u0442\u043e\u043d 2", + "button3": "\u0411\u0443\u0442\u043e\u043d 3", + "button4": "\u0411\u0443\u0442\u043e\u043d 4", + "button5": "\u0411\u0443\u0442\u043e\u043d 5", + "button6": "\u0411\u0443\u0442\u043e\u043d 6", + "button7": "\u0411\u0443\u0442\u043e\u043d 7", + "button8": "\u0411\u0443\u0442\u043e\u043d 8", + "button9": "\u0411\u0443\u0442\u043e\u043d 9" + } + }, "title": "HomeKit \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/hu.json b/homeassistant/components/homekit_controller/translations/hu.json index 49e6fc53231..90e2405ed64 100644 --- a/homeassistant/components/homekit_controller/translations/hu.json +++ b/homeassistant/components/homekit_controller/translations/hu.json @@ -3,7 +3,7 @@ "abort": { "accessory_not_found_error": "Nem adhat\u00f3 hozz\u00e1 p\u00e1ros\u00edt\u00e1s, mert az eszk\u00f6z m\u00e1r nem tal\u00e1lhat\u00f3.", "already_configured": "A tartoz\u00e9k m\u00e1r konfigur\u00e1lva van ezzel a vez\u00e9rl\u0151vel.", - "already_in_progress": "Az eszk\u00f6z konfigur\u00e1ci\u00f3ja m\u00e1r folyamatban van.", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", "already_paired": "Ez a tartoz\u00e9k m\u00e1r p\u00e1ros\u00edtva van egy m\u00e1sik eszk\u00f6zzel. \u00c1ll\u00edtsa alaphelyzetbe a tartoz\u00e9kot, majd pr\u00f3b\u00e1lkozzon \u00fajra.", "ignored_model": "A HomeKit t\u00e1mogat\u00e1sa e modelln\u00e9l blokkolva van, mivel a szolg\u00e1ltat\u00e1shoz teljes nat\u00edv integr\u00e1ci\u00f3 \u00e9rhet\u0151 el.", "invalid_config_entry": "Ez az eszk\u00f6z k\u00e9szen \u00e1ll a p\u00e1ros\u00edt\u00e1sra, de m\u00e1r van egy \u00fctk\u00f6z\u0151 konfigur\u00e1ci\u00f3s bejegyz\u00e9s a Home Assistant-ben, amelyet el\u0151sz\u00f6r el kell t\u00e1vol\u00edtani.", @@ -30,9 +30,29 @@ "device": "Eszk\u00f6z" }, "description": "V\u00e1lassza ki azt az eszk\u00f6zt, amelyet p\u00e1ros\u00edtani szeretne", - "title": "HomeKit tartoz\u00e9k p\u00e1ros\u00edt\u00e1sa" + "title": "Eszk\u00f6z kiv\u00e1laszt\u00e1sa" } } }, + "device_automation": { + "trigger_subtype": { + "button1": "Gomb 1", + "button10": "Gomb 10", + "button2": "Gomb 2", + "button3": "Gomb 3", + "button4": "Gomb 4", + "button5": "Gomb 5", + "button6": "Gomb 6", + "button7": "Gomb 7", + "button8": "Gomb 8", + "button9": "Gomb 9", + "doorbell": "Cseng\u0151" + }, + "trigger_type": { + "double_press": "\"{subtype}\" k\u00e9tszer lenyomva", + "long_press": "\"{subtype}\" lenyomva \u00e9s nyomva tartva", + "single_press": "\"{subtype}\" lenyomva" + } + }, "title": "HomeKit Vez\u00e9rl\u0151" } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/id.json b/homeassistant/components/homekit_controller/translations/id.json new file mode 100644 index 00000000000..49a37d3b3fb --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/id.json @@ -0,0 +1,71 @@ +{ + "config": { + "abort": { + "accessory_not_found_error": "Tidak dapat menambahkan pemasangan karena perangkat tidak dapat ditemukan lagi.", + "already_configured": "Aksesori sudah dikonfigurasi dengan pengontrol ini.", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "already_paired": "Aksesori ini sudah dipasangkan ke perangkat lain. Setel ulang aksesori dan coba lagi.", + "ignored_model": "Dukungan HomeKit untuk model ini diblokir karena integrasi asli dengan fitur lebih lengkap telah tersedia.", + "invalid_config_entry": "Perangkat ini ditampilkan sebagai siap untuk dipasangkan tetapi sudah ada entri konfigurasi yang bertentangan untuk perangkat tersebut dalam Home Assistant, yang harus dihapus terlebih dulu.", + "invalid_properties": "Properti tidak valid diumumkan oleh perangkat.", + "no_devices": "Tidak ada perangkat yang belum dipasangkan yang dapat ditemukan" + }, + "error": { + "authentication_error": "Kode HomeKit salah. Periksa dan coba lagi.", + "max_peers_error": "Perangkat menolak untuk menambahkan pemasangan karena tidak memiliki penyimpanan pemasangan yang tersedia.", + "pairing_failed": "Terjadi kesalahan yang tidak tertangani saat mencoba memasangkan dengan perangkat ini. Ini mungkin kegagalan sementara atau perangkat Anda mungkin tidak didukung saat ini.", + "unable_to_pair": "Gagal memasangkan, coba lagi.", + "unknown_error": "Perangkat melaporkan kesalahan yang tidak diketahui. Pemasangan gagal." + }, + "flow_title": "{name} lewat HomeKit Accessory Protocol", + "step": { + "busy_error": { + "description": "Batalkan pemasangan di semua pengontrol, atau coba mulai ulang perangkat, lalu lanjutkan untuk melanjutkan pemasangan.", + "title": "Perangkat sudah dipasangkan dengan pengontrol lain" + }, + "max_tries_error": { + "description": "Perangkat telah menerima lebih dari 100 upaya autentikasi yang gagal. Coba mulai ulang perangkat, lalu lanjutkan pemasangan.", + "title": "Upaya autentikasi maksimum terlampaui" + }, + "pair": { + "data": { + "pairing_code": "Kode Pemasangan" + }, + "description": "Pengontrol HomeKit berkomunikasi dengan {name} melalui jaringan area lokal menggunakan koneksi terenkripsi yang aman tanpa pengontrol HomeKit atau iCloud terpisah. Masukkan kode pemasangan HomeKit Anda (dalam format XXX-XX-XXX) untuk menggunakan aksesori ini. Kode ini biasanya ditemukan pada perangkat itu sendiri atau dalam kemasan.", + "title": "Pasangkan dengan perangkat melalui HomeKit Accessory Protocol" + }, + "protocol_error": { + "description": "Perangkat mungkin tidak dalam mode pemasangan dan mungkin memerlukan tombol fisik atau virtual. Pastikan perangkat dalam mode pemasangan atau coba mulai ulang perangkat, lalu lanjutkan pemasangan.", + "title": "Terjadi kesalahan saat berkomunikasi dengan aksesori" + }, + "user": { + "data": { + "device": "Perangkat" + }, + "description": "Pengontrol HomeKit berkomunikasi melalui jaringan area lokal menggunakan koneksi terenkripsi yang aman tanpa pengontrol HomeKit atau iCloud terpisah. Pilih perangkat yang ingin Anda pasangkan:", + "title": "Pemilihan perangkat" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button1": "Tombol 1", + "button10": "Tombol 10", + "button2": "Tombol 2", + "button3": "Tombol 3", + "button4": "Tombol 4", + "button5": "Tombol 5", + "button6": "Tombol 6", + "button7": "Tombol 7", + "button8": "Tombol 8", + "button9": "Tombol 9", + "doorbell": "Bel pintu" + }, + "trigger_type": { + "double_press": "\"{subtype}\" ditekan dua kali", + "long_press": "\"{subtype}\" ditekan dan ditahan", + "single_press": "\"{subtype}\" ditekan" + } + }, + "title": "Pengontrol HomeKit" +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/ko.json b/homeassistant/components/homekit_controller/translations/ko.json index 7314f43545e..c28573ee6ac 100644 --- a/homeassistant/components/homekit_controller/translations/ko.json +++ b/homeassistant/components/homekit_controller/translations/ko.json @@ -1,18 +1,18 @@ { "config": { "abort": { - "accessory_not_found_error": "\uae30\uae30\ub97c \ub354 \uc774\uc0c1 \ucc3e\uc744 \uc218 \uc5c6\uc73c\ubbc0\ub85c \ud398\uc5b4\ub9c1\uc744 \ucd94\uac00 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", + "accessory_not_found_error": "\uae30\uae30\ub97c \ub354 \uc774\uc0c1 \ucc3e\uc744 \uc218 \uc5c6\uc5b4 \ud398\uc5b4\ub9c1\uc744 \ucd94\uac00\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "already_configured": "\uc561\uc138\uc11c\ub9ac\uac00 \ucee8\ud2b8\ub864\ub7ec\uc5d0 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "already_paired": "\uc774 \uc561\uc138\uc11c\ub9ac\ub294 \uc774\ubbf8 \ub2e4\ub978 \uae30\uae30\uc640 \ud398\uc5b4\ub9c1\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4. \uc561\uc138\uc11c\ub9ac\ub97c \uc7ac\uc124\uc815\ud558\uace0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "ignored_model": "\uc774 \ubaa8\ub378\uc5d0 \ub300\ud55c HomeKit \uc9c0\uc6d0\uc740 \ub354 \ub9ce\uc740 \uae30\ub2a5\uc744 \uc81c\uacf5\ud558\ub294 \uae30\ubcf8 \uad6c\uc131\uc694\uc18c\ub85c \uc778\ud574 \ucc28\ub2e8\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "invalid_config_entry": "\uc774 \uae30\uae30\ub294 \ud398\uc5b4\ub9c1 \ud560 \uc900\ube44\uac00 \ub418\uc5c8\uc9c0\ub9cc Home Assistant \uc5d0 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \ucda9\ub3cc\ud558\ub294 \uad6c\uc131\uc694\uc18c\uac00 \uc788\uc2b5\ub2c8\ub2e4. \uba3c\uc800 \ud574\ub2f9 \uad6c\uc131\uc694\uc18c\ub97c \uc81c\uac70\ud574\uc8fc\uc138\uc694.", - "invalid_properties": "\uc7a5\uce58\uc5d0\uc11c\uc120\uc5b8\ud55c \uc798\ubabb\ub41c \uc18d\uc131\uc785\ub2c8\ub2e4.", + "invalid_config_entry": "\uc774 \uae30\uae30\ub294 \ud398\uc5b4\ub9c1 \ud560 \uc900\ube44\uac00 \ub418\uc5c8\uc9c0\ub9cc Home Assistant\uc5d0 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \ucda9\ub3cc\ud558\ub294 \uad6c\uc131\uc694\uc18c\uac00 \uc788\uc2b5\ub2c8\ub2e4. \uba3c\uc800 \ud574\ub2f9 \uad6c\uc131\uc694\uc18c\ub97c \uc81c\uac70\ud574\uc8fc\uc138\uc694.", + "invalid_properties": "\uae30\uae30\uc5d0\uc11c \uc798\ubabb\ub41c \uc18d\uc131\uc744 \ubcf4\ub0c8\uc2b5\ub2c8\ub2e4.", "no_devices": "\ud398\uc5b4\ub9c1\uc774 \ud544\uc694\ud55c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" }, "error": { "authentication_error": "HomeKit \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud655\uc778 \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", - "max_peers_error": "\uae30\uae30\uc5d0 \ube44\uc5b4\uc788\ub294 \ud398\uc5b4\ub9c1 \uc7a5\uc18c\uac00 \uc5c6\uc5b4 \ud398\uc5b4\ub9c1 \ucd94\uac00\ub97c \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", + "max_peers_error": "\uae30\uae30\uc5d0 \ube44\uc5b4\uc788\ub294 \ud398\uc5b4\ub9c1 \uc7a5\uc18c\uac00 \uc5c6\uc5b4 \ud398\uc5b4\ub9c1\uc744 \ucd94\uac00\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "pairing_failed": "\uc774 \uae30\uae30\uc640 \ud398\uc5b4\ub9c1\uc744 \uc2dc\ub3c4\ud558\ub294 \uc911 \ucc98\ub9ac\ub418\uc9c0 \uc54a\uc740 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4. \uc77c\uc2dc\uc801\uc778 \uc624\ub958\uc774\uac70\ub098 \ud604\uc7ac \uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \uae30\uae30 \uc77c \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "unable_to_pair": "\ud398\uc5b4\ub9c1 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "unknown_error": "\uae30\uae30\uc5d0\uc11c \uc54c \uc218\uc5c6\ub294 \uc624\ub958\ub97c \ubcf4\uace0\ud588\uc2b5\ub2c8\ub2e4. \ud398\uc5b4\ub9c1\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4." @@ -21,7 +21,7 @@ "step": { "busy_error": { "description": "\ubaa8\ub4e0 \ucee8\ud2b8\ub864\ub7ec\uc5d0\uc11c \ud398\uc5b4\ub9c1\uc744 \uc911\ub2e8\ud558\uac70\ub098 \uae30\uae30\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ub2e4\uc74c \ud398\uc5b4\ub9c1\uc744 \uacc4\uc18d\ud574\uc8fc\uc138\uc694.", - "title": "\uae30\uae30\uac00 \uc774\ubbf8 \ub2e4\ub978 \ucee8\ud2b8\ub864\ub7ec\uc640 \ud398\uc774\ub9c1\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4" + "title": "\uae30\uae30\uac00 \uc774\ubbf8 \ub2e4\ub978 \ucee8\ud2b8\ub864\ub7ec\uc640 \ud398\uc774\ub9c1 \ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4" }, "max_tries_error": { "description": "\uae30\uae30\uac00 100\ud68c \uc774\uc0c1\uc758 \uc2e4\ud328\ud55c \uc778\uc99d \uc2dc\ub3c4\ub97c \ubc1b\uc558\uc2b5\ub2c8\ub2e4. \uae30\uae30\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ub2e4\uc74c \ud398\uc5b4\ub9c1\uc744 \uacc4\uc18d\ud574\uc8fc\uc138\uc694.", @@ -31,11 +31,11 @@ "data": { "pairing_code": "\ud398\uc5b4\ub9c1 \ucf54\ub4dc" }, - "description": "HomeKit \ucee8\ud2b8\ub864\ub7ec\ub294 \ubcc4\ub3c4\uc758 HomeKit \ucee8\ud2b8\ub864\ub7ec \ub610\ub294 iCloud \uc5c6\uc774 \uc554\ud638\ud654\ub41c \ubcf4\uc548 \uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uc5ec \ub85c\uceec \uc601\uc5ed \ub124\ud2b8\uc6cc\ud06c \uc0c1\uc5d0\uc11c {name} \uacfc(\uc640) \ud1b5\uc2e0\ud569\ub2c8\ub2e4. \uc774 \uc561\uc138\uc11c\ub9ac\ub97c \uc0ac\uc6a9\ud558\ub824\uba74 HomeKit \ud398\uc5b4\ub9c1 \ucf54\ub4dc (XX-XX-XXX \ud615\uc2dd) \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc774 \ucf54\ub4dc\ub294 \uc77c\ubc18\uc801\uc73c\ub85c \uae30\uae30\ub098 \ud3ec\uc7a5 \ubc15\uc2a4\uc5d0 \ud45c\uc2dc\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4.", - "title": "HomeKit \uc561\uc138\uc11c\ub9ac \ud504\ub85c\ud1a0\ucf5c\uc744 \ud1b5\ud574 \uae30\uae30\uc640 \ud398\uc5b4\ub9c1 \ud558\uae30" + "description": "HomeKit \ucee8\ud2b8\ub864\ub7ec\ub294 \ubcc4\ub3c4\uc758 HomeKit \ucee8\ud2b8\ub864\ub7ec \ub610\ub294 iCloud \uc5c6\uc774 \uc554\ud638\ud654\ub41c \ubcf4\uc548 \uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uc5ec \ub85c\uceec \uc601\uc5ed \ub124\ud2b8\uc6cc\ud06c \uc0c1\uc5d0\uc11c {name}\uacfc(\uc640) \ud1b5\uc2e0\ud569\ub2c8\ub2e4. \uc774 \uc561\uc138\uc11c\ub9ac\ub97c \uc0ac\uc6a9\ud558\ub824\uba74 HomeKit \ud398\uc5b4\ub9c1 \ucf54\ub4dc(XX-XX-XXX \ud615\uc2dd)\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc774 \ucf54\ub4dc\ub294 \uc77c\ubc18\uc801\uc73c\ub85c \uae30\uae30\ub098 \ud3ec\uc7a5 \ubc15\uc2a4\uc5d0 \ud45c\uc2dc\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4.", + "title": "HomeKit \uc561\uc138\uc11c\ub9ac \ud504\ub85c\ud1a0\ucf5c\ub85c \uae30\uae30\uc640 \ud398\uc5b4\ub9c1 \ud558\uae30" }, "protocol_error": { - "description": "\uae30\uae30\uac00 \ud398\uc5b4\ub9c1 \ubaa8\ub4dc\uc5d0 \uc788\uc9c0 \uc54a\uc744 \uc218 \uc788\uc73c\uba70 \ubb3c\ub9ac\uc801 \ub610\ub294 \uac00\uc0c1 \uc758 \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc57c \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uae30\uae30\uac00 \ud398\uc5b4\ub9c1 \ubaa8\ub4dc\uc5d0 \uc788\ub294\uc9c0 \ud655\uc778\ud558\uac70\ub098 \uae30\uae30\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ub2e4\uc74c \ud398\uc5b4\ub9c1\uc744 \uacc4\uc18d\ud574\uc8fc\uc138\uc694.", + "description": "\uae30\uae30\uac00 \ud398\uc5b4\ub9c1 \ubaa8\ub4dc\uc5d0 \uc788\uc9c0 \uc54a\uc744 \uc218 \uc788\uc73c\uba70 \ubb3c\ub9ac\uc801 \ub610\ub294 \uac00\uc0c1\uc758 \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc57c \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uae30\uae30\uac00 \ud398\uc5b4\ub9c1 \ubaa8\ub4dc\uc5d0 \uc788\ub294\uc9c0 \ud655\uc778\ud558\uac70\ub098 \uae30\uae30\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ub2e4\uc74c \ud398\uc5b4\ub9c1\uc744 \uacc4\uc18d\ud574\uc8fc\uc138\uc694.", "title": "\uc561\uc138\uc11c\ub9ac\uc640 \ud1b5\uc2e0 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "user": { @@ -62,9 +62,9 @@ "doorbell": "\ucd08\uc778\uc885" }, "trigger_type": { - "double_press": "\" {subtype} \"\uc744 \ub450\ubc88 \ub204\ub984", - "long_press": "\" {subtype} \"\uc744 \uae38\uac8c \ub204\ub984", - "single_press": "\"{subtype}\" \uc744 \ud55c\ubc88 \ub204\ub984" + "double_press": "\"{subtype}\"\uc774(\uac00) \ub450 \ubc88 \ub20c\ub838\uc744 \ub54c", + "long_press": "\"{subtype}\"\uc774(\uac00) \ub20c\ub824\uc9c4 \ucc44\ub85c \uc788\uc744 \ub54c", + "single_press": "\"{subtype}\"\uc774(\uac00) \ub20c\ub838\uc744 \ub54c" } }, "title": "HomeKit \ucee8\ud2b8\ub864\ub7ec" diff --git a/homeassistant/components/homekit_controller/translations/nl.json b/homeassistant/components/homekit_controller/translations/nl.json index ce4279229ab..46167e9da98 100644 --- a/homeassistant/components/homekit_controller/translations/nl.json +++ b/homeassistant/components/homekit_controller/translations/nl.json @@ -19,19 +19,25 @@ }, "flow_title": "HomeKit-accessoire: {name}", "step": { + "max_tries_error": { + "title": "Maximum aantal authenticatiepogingen overschreden" + }, "pair": { "data": { "pairing_code": "Koppelingscode" }, - "description": "Voer uw HomeKit pairing code (in het formaat XXX-XX-XXX) om dit accessoire te gebruiken", + "description": "HomeKit Controller communiceert met {name} via het lokale netwerk met behulp van een beveiligde versleutelde verbinding zonder een aparte HomeKit-controller of iCloud. Voer uw HomeKit-koppelcode in (in de indeling XXX-XX-XXX) om dit accessoire te gebruiken. Deze code is meestal te vinden op het apparaat zelf of in de verpakking.", "title": "Koppel met HomeKit accessoire" }, + "protocol_error": { + "title": "Fout bij het communiceren met de accessoire" + }, "user": { "data": { "device": "Apparaat" }, - "description": "Selecteer het apparaat waarmee u wilt koppelen", - "title": "Koppel met HomeKit accessoire" + "description": "HomeKit Controller communiceert via het lokale netwerk met behulp van een veilige versleutelde verbinding zonder een aparte HomeKit-controller of iCloud. Selecteer het apparaat dat u wilt koppelen:", + "title": "Apparaat selectie" } } }, @@ -55,5 +61,5 @@ "single_press": "\" {subtype} \" ingedrukt" } }, - "title": "HomeKit Accessoires" + "title": "HomeKit Controller" } \ No newline at end of file diff --git a/homeassistant/components/homematicip_cloud/translations/hu.json b/homeassistant/components/homematicip_cloud/translations/hu.json index 1ae318c45ad..eaa8d8834c3 100644 --- a/homeassistant/components/homematicip_cloud/translations/hu.json +++ b/homeassistant/components/homematicip_cloud/translations/hu.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "A hozz\u00e1f\u00e9r\u00e9si pontot m\u00e1r konfigur\u00e1ltuk", - "connection_aborted": "Nem siker\u00fclt csatlakozni a HMIP szerverhez", - "unknown": "Unknown error occurred." + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "connection_aborted": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { - "invalid_sgtin_or_pin": "\u00c9rv\u00e9nytelen PIN, pr\u00f3b\u00e1lkozz \u00fajra.", + "invalid_sgtin_or_pin": "\u00c9rv\u00e9nytelen PIN-k\u00f3d, pr\u00f3b\u00e1lkozz \u00fajra.", "press_the_button": "Nyomd meg a k\u00e9k gombot.", "register_failed": "Regisztr\u00e1ci\u00f3 nem siker\u00fclt, pr\u00f3b\u00e1ld \u00fajra.", "timeout_button": "K\u00e9k gomb megnyom\u00e1s\u00e1nak id\u0151t\u00fall\u00e9p\u00e9se, pr\u00f3b\u00e1lkozz \u00fajra." @@ -16,7 +16,7 @@ "data": { "hapid": "Hozz\u00e1f\u00e9r\u00e9si pont azonos\u00edt\u00f3ja (SGTIN)", "name": "N\u00e9v (opcion\u00e1lis, minden eszk\u00f6z n\u00e9vel\u0151tagjak\u00e9nt haszn\u00e1latos)", - "pin": "Pin k\u00f3d (opcion\u00e1lis)" + "pin": "PIN-k\u00f3d" }, "title": "V\u00e1lassz HomematicIP hozz\u00e1f\u00e9r\u00e9si pontot" }, diff --git a/homeassistant/components/homematicip_cloud/translations/id.json b/homeassistant/components/homematicip_cloud/translations/id.json index 43525955c36..26679d3b37f 100644 --- a/homeassistant/components/homematicip_cloud/translations/id.json +++ b/homeassistant/components/homematicip_cloud/translations/id.json @@ -1,28 +1,28 @@ { "config": { "abort": { - "already_configured": "Jalur akses sudah dikonfigurasi", - "connection_aborted": "Tidak dapat terhubung ke server HMIP", - "unknown": "Kesalahan tidak dikenal terjadi." + "already_configured": "Perangkat sudah dikonfigurasi", + "connection_aborted": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" }, "error": { - "invalid_sgtin_or_pin": "PIN tidak valid, silakan coba lagi.", - "press_the_button": "Silakan tekan tombol biru.", - "register_failed": "Gagal mendaftar, silakan coba lagi.", - "timeout_button": "Batas waktu tekan tombol biru berakhir, silakan coba lagi." + "invalid_sgtin_or_pin": "SGTIN atau Kode PIN tidak valid, coba lagi.", + "press_the_button": "Tekan tombol biru.", + "register_failed": "Gagal mendaftar, coba lagi.", + "timeout_button": "Tenggang waktu penekanan tombol biru berakhir, coba lagi." }, "step": { "init": { "data": { "hapid": "Titik akses ID (SGTIN)", - "name": "Nama (opsional, digunakan sebagai awalan nama untuk semua perangkat)", - "pin": "Kode Pin (opsional)" + "name": "Nama (opsional, digunakan sebagai prefiks nama untuk semua perangkat)", + "pin": "Kode PIN" }, - "title": "Pilih HomematicIP Access point" + "title": "Pilih Access Point HomematicIP" }, "link": { - "description": "Tekan tombol biru pada access point dan tombol submit untuk mendaftarkan HomematicIP dengan rumah asisten.\n\n! [Lokasi tombol di bridge] (/ static/images/config_flows/config_homematicip_cloud.png)", - "title": "Tautkan jalur akses" + "description": "Tekan tombol biru pada access point dan tombol sukirimbmit untuk mendaftarkan HomematicIP dengan Home Assistant.\n\n![Lokasi tombol di bridge](/static/images/config_flows/config_homematicip_cloud.png)", + "title": "Tautkan Titik akses" } } } diff --git a/homeassistant/components/homematicip_cloud/translations/ko.json b/homeassistant/components/homematicip_cloud/translations/ko.json index 6a15b21de84..07f6c5c70fc 100644 --- a/homeassistant/components/homematicip_cloud/translations/ko.json +++ b/homeassistant/components/homematicip_cloud/translations/ko.json @@ -6,7 +6,7 @@ "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "invalid_sgtin_or_pin": "\uc798\ubabb\ub41c SGTIN \uc774\uac70\ub098 PIN \ucf54\ub4dc \uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "invalid_sgtin_or_pin": "SGTIN \ub610\ub294 PIN \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "press_the_button": "\ud30c\ub780\uc0c9 \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694.", "register_failed": "\ub4f1\ub85d\uc5d0 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "timeout_button": "\uc815\ud574\uc9c4 \uc2dc\uac04\ub0b4\uc5d0 \ud30c\ub780\uc0c9 \ubc84\ud2bc\uc744 \ub20c\ub974\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." @@ -15,13 +15,13 @@ "init": { "data": { "hapid": "\uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8 ID (SGTIN)", - "name": "\uc774\ub984 (\uc120\ud0dd \uc0ac\ud56d, \ubaa8\ub4e0 \uae30\uae30 \uc774\ub984\uc758 \uc811\ub450\uc5b4\ub85c \uc0ac\uc6a9)", + "name": "\uc774\ub984 (\uc120\ud0dd \uc0ac\ud56d, \ubaa8\ub4e0 \uae30\uae30 \uc774\ub984\uc758 \uc811\ub450\uc0ac\ub85c \uc0ac\uc6a9)", "pin": "PIN \ucf54\ub4dc" }, "title": "HomematicIP \uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8 \uc120\ud0dd\ud558\uae30" }, "link": { - "description": "Home Assistant \uc5d0 HomematicIP \ub97c \ub4f1\ub85d\ud558\ub824\uba74 \uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8\uc758 \ud30c\ub780\uc0c9 \ubc84\ud2bc\uacfc \ud655\uc778\uc744 \ud074\ub9ad\ud574\uc8fc\uc138\uc694.\n\n![\ube0c\ub9ac\uc9c0\uc758 \ubc84\ud2bc \uc704\uce58 \ubcf4\uae30](/static/images/config_flows/config_homematicip_cloud.png)", + "description": "Home Assistant\uc5d0 HomematicIP\ub97c \ub4f1\ub85d\ud558\ub824\uba74 \uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8\uc758 \ud30c\ub780\uc0c9 \ubc84\ud2bc\uacfc \ud655\uc778\uc744 \ud074\ub9ad\ud574\uc8fc\uc138\uc694.\n\n![\ube0c\ub9ac\uc9c0\uc758 \ubc84\ud2bc \uc704\uce58 \ubcf4\uae30](/static/images/config_flows/config_homematicip_cloud.png)", "title": "\uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/homematicip_cloud/translations/nl.json b/homeassistant/components/homematicip_cloud/translations/nl.json index 7127b5c5aae..cb65dee7bd1 100644 --- a/homeassistant/components/homematicip_cloud/translations/nl.json +++ b/homeassistant/components/homematicip_cloud/translations/nl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Accesspoint is al geconfigureerd", - "connection_aborted": "Kon geen verbinding maken met de HMIP-server", - "unknown": "Er is een onbekende fout opgetreden." + "already_configured": "Apparaat is al geconfigureerd", + "connection_aborted": "Kan geen verbinding maken", + "unknown": "Onverwachte fout" }, "error": { - "invalid_sgtin_or_pin": "Ongeldige PIN-code, probeer het nogmaals.", + "invalid_sgtin_or_pin": "Ongeldige SGTIN of PIN-code, probeer het opnieuw.", "press_the_button": "Druk op de blauwe knop.", "register_failed": "Kan niet registreren, gelieve opnieuw te proberen.", "timeout_button": "Blauwe knop druk op timeout, probeer het opnieuw." @@ -15,8 +15,8 @@ "init": { "data": { "hapid": "Accesspoint ID (SGTIN)", - "name": "Naam (optioneel, gebruikt als naamprefix voor alle apparaten)", - "pin": "Pin-Code (optioneel)" + "name": "Naam (optioneel, gebruikt als naamvoorvoegsel voor alle apparaten)", + "pin": "PIN-code" }, "title": "Kies HomematicIP accesspoint" }, diff --git a/homeassistant/components/huawei_lte/translations/he.json b/homeassistant/components/huawei_lte/translations/he.json new file mode 100644 index 00000000000..6f4191da70d --- /dev/null +++ b/homeassistant/components/huawei_lte/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/translations/hu.json b/homeassistant/components/huawei_lte/translations/hu.json index 90814fea5a5..815794133d2 100644 --- a/homeassistant/components/huawei_lte/translations/hu.json +++ b/homeassistant/components/huawei_lte/translations/hu.json @@ -1,20 +1,24 @@ { "config": { "abort": { - "already_configured": "Ez az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "Ez az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", "not_huawei_lte": "Nem Huawei LTE eszk\u00f6z" }, "error": { "connection_timeout": "Kapcsolat id\u0151t\u00fall\u00e9p\u00e9se", "incorrect_password": "Hib\u00e1s jelsz\u00f3", "incorrect_username": "Helytelen felhaszn\u00e1l\u00f3n\u00e9v", - "invalid_url": "\u00c9rv\u00e9nytelen URL" + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "invalid_url": "\u00c9rv\u00e9nytelen URL", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, + "flow_title": "Huawei LTE: {name}", "step": { "user": { "data": { "password": "Jelsz\u00f3", + "url": "URL", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" }, "title": "Huawei LTE konfigur\u00e1l\u00e1sa" diff --git a/homeassistant/components/huawei_lte/translations/id.json b/homeassistant/components/huawei_lte/translations/id.json new file mode 100644 index 00000000000..2077b31ccd7 --- /dev/null +++ b/homeassistant/components/huawei_lte/translations/id.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "not_huawei_lte": "Bukan perangkat Huawei LTE" + }, + "error": { + "connection_timeout": "Tenggang waktu terhubung habis", + "incorrect_password": "Kata sandi salah", + "incorrect_username": "Nama pengguna salah", + "invalid_auth": "Autentikasi tidak valid", + "invalid_url": "URL tidak valid", + "login_attempts_exceeded": "Upaya login maksimum telah terlampaui, coba lagi nanti", + "response_error": "Kesalahan tidak dikenal dari perangkat", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Huawei LTE: {name}", + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "url": "URL", + "username": "Nama Pengguna" + }, + "description": "Masukkan detail akses perangkat. Menentukan nama pengguna dan kata sandi bersifat opsional, tetapi memungkinkan dukungan untuk fitur integrasi lainnya. Selain itu, penggunaan koneksi resmi dapat menyebabkan masalah mengakses antarmuka web perangkat dari luar Home Assistant saat integrasi aktif, dan sebaliknya.", + "title": "Konfigurasikan Huawei LTE" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Nama layanan notifikasi (perubahan harus dimulai ulang)", + "recipient": "Penerima notifikasi SMS", + "track_new_devices": "Lacak perangkat baru" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/translations/ko.json b/homeassistant/components/huawei_lte/translations/ko.json index 73274d15bfb..1713b81cf7e 100644 --- a/homeassistant/components/huawei_lte/translations/ko.json +++ b/homeassistant/components/huawei_lte/translations/ko.json @@ -23,7 +23,7 @@ "url": "URL \uc8fc\uc18c", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "\uae30\uae30 \uc561\uc138\uc2a4 \uc138\ubd80 \uc0ac\ud56d\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc124\uc815\ud558\ub294 \uac83\uc740 \uc120\ud0dd \uc0ac\ud56d\uc774\uc9c0\ub9cc \ub354 \ub9ce\uc740 \uae30\ub2a5\uc744 \uc9c0\uc6d0\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ubc18\uba74, \uc778\uc99d\ub41c \uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uba74, \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uac00 \ud65c\uc131\ud654\ub41c \uc0c1\ud0dc\uc5d0\uc11c \ub2e4\ub978 \ubc29\ubc95\uc73c\ub85c Home Assistant \uc758 \uc678\ubd80\uc5d0\uc11c \uae30\uae30\uc758 \uc6f9 \uc778\ud130\ud398\uc774\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud558\ub294 \ub370 \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "\uae30\uae30 \uc561\uc138\uc2a4 \uc138\ubd80 \uc0ac\ud56d\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc124\uc815\ud558\ub294 \uac83\uc740 \uc120\ud0dd \uc0ac\ud56d\uc774\uc9c0\ub9cc \ub354 \ub9ce\uc740 \uae30\ub2a5\uc744 \uc9c0\uc6d0\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ubc18\uba74, \uc778\uc99d\ub41c \uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uba74, \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uac00 \ud65c\uc131\ud654\ub41c \uc0c1\ud0dc\uc5d0\uc11c \ub2e4\ub978 \ubc29\ubc95\uc73c\ub85c Home Assistant\uc758 \uc678\ubd80\uc5d0\uc11c \uae30\uae30\uc758 \uc6f9 \uc778\ud130\ud398\uc774\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud558\ub294 \ub370 \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "Huawei LTE \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/hue/translations/hu.json b/homeassistant/components/hue/translations/hu.json index c3e3203a66c..d0aa043b10b 100644 --- a/homeassistant/components/hue/translations/hu.json +++ b/homeassistant/components/hue/translations/hu.json @@ -2,14 +2,16 @@ "config": { "abort": { "all_configured": "M\u00e1r minden Philips Hue bridge konfigur\u00e1lt", - "already_configured": "A bridge m\u00e1r konfigur\u00e1lva van", - "cannot_connect": "Nem siker\u00fclt csatlakozni a bridge-hez.", + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", "discover_timeout": "Nem tal\u00e1ltam a Hue bridget", "no_bridges": "Nem tal\u00e1ltam Philips Hue bridget", + "not_hue_bridge": "Nem egy Hue Bridge", "unknown": "Ismeretlen hiba t\u00f6rt\u00e9nt" }, "error": { - "linking": "Ismeretlen \u00f6sszekapcsol\u00e1si hiba t\u00f6rt\u00e9nt.", + "linking": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt", "register_failed": "Regisztr\u00e1ci\u00f3 nem siker\u00fclt, k\u00e9rem pr\u00f3b\u00e1lja \u00fajra" }, "step": { @@ -22,6 +24,12 @@ "link": { "description": "Nyomja meg a gombot a bridge-en a Philips Hue Home Assistant-ben val\u00f3 regisztr\u00e1l\u00e1s\u00e1hoz.\n\n![Location of button on bridge](/static/images/config_philips_hue.jpg)", "title": "Kapcsol\u00f3d\u00e1s a hubhoz" + }, + "manual": { + "data": { + "host": "Hoszt" + }, + "title": "A Hue bridge manu\u00e1lis konfigur\u00e1l\u00e1sa" } } }, @@ -29,6 +37,10 @@ "trigger_subtype": { "turn_off": "Kikapcsol\u00e1s", "turn_on": "Bekapcsol\u00e1s" + }, + "trigger_type": { + "remote_button_short_press": "\"{subtype}\" gomb lenyomva", + "remote_button_short_release": "\"{subtype}\" gomb elengedve" } } } \ No newline at end of file diff --git a/homeassistant/components/hue/translations/id.json b/homeassistant/components/hue/translations/id.json index 2af5753f654..c9e0bcd75d4 100644 --- a/homeassistant/components/hue/translations/id.json +++ b/homeassistant/components/hue/translations/id.json @@ -1,27 +1,66 @@ { "config": { "abort": { - "all_configured": "Semua Philips Hue bridges sudah dikonfigurasi", - "already_configured": "Bridge sudah dikonfigurasi", - "cannot_connect": "Tidak dapat terhubung ke bridge", - "discover_timeout": "Tidak dapat menemukan Hue Bridges.", + "all_configured": "Semua bridge Philips Hue sudah dikonfigurasi", + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "cannot_connect": "Gagal terhubung", + "discover_timeout": "Tidak dapat menemukan bridge Hue", "no_bridges": "Bridge Philips Hue tidak ditemukan", - "unknown": "Kesalahan tidak dikenal terjadi." + "not_hue_bridge": "Bukan bridge Hue", + "unknown": "Kesalahan yang tidak diharapkan" }, "error": { - "linking": "Terjadi kesalahan tautan tidak dikenal.", - "register_failed": "Gagal mendaftar, silakan coba lagi." + "linking": "Kesalahan yang tidak diharapkan", + "register_failed": "Gagal mendaftar, coba lagi." }, "step": { "init": { "data": { "host": "Host" }, - "title": "Pilih Hue bridge" + "title": "Pilih bridge Hue" }, "link": { - "description": "Tekan tombol di bridge untuk mendaftar Philips Hue dengan Home Assistant.\n\n![Lokasi tombol di bridge](/static/images/config_philips_hue.jpg)", - "title": "Tautan Hub" + "description": "Tekan tombol di bridge untuk mendaftarkan Philips Hue dengan Home Assistant.\n\n![Lokasi tombol di bridge](/static/images/config_philips_hue.jpg)", + "title": "Tautkan Hub" + }, + "manual": { + "data": { + "host": "Host" + }, + "title": "Konfigurasi bridge Hue secara manual" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Tombol pertama", + "button_2": "Tombol kedua", + "button_3": "Tombol ketiga", + "button_4": "Tombol keempat", + "dim_down": "Redupkan", + "dim_up": "Terangkan", + "double_buttons_1_3": "Tombol Pertama dan Ketiga", + "double_buttons_2_4": "Tombol Kedua dan Keempat", + "turn_off": "Matikan", + "turn_on": "Nyalakan" + }, + "trigger_type": { + "remote_button_long_release": "Tombol \"{subtype}\" dilepaskan setelah ditekan lama", + "remote_button_short_press": "Tombol \"{subtype}\" ditekan", + "remote_button_short_release": "Tombol \"{subtype}\" dilepaskan", + "remote_double_button_long_press": "Kedua \"{subtype}\" dilepaskan setelah ditekan lama", + "remote_double_button_short_press": "Kedua \"{subtype}\" dilepas" + } + }, + "options": { + "step": { + "init": { + "data": { + "allow_hue_groups": "Izinkan grup Hue", + "allow_unreachable": "Izinkan bohlam yang tidak dapat dijangkau untuk melaporkan statusnya dengan benar" + } } } } diff --git a/homeassistant/components/hue/translations/ko.json b/homeassistant/components/hue/translations/ko.json index 846ea937515..30da5cee484 100644 --- a/homeassistant/components/hue/translations/ko.json +++ b/homeassistant/components/hue/translations/ko.json @@ -47,11 +47,11 @@ "turn_on": "\ucf1c\uae30" }, "trigger_type": { - "remote_button_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc190\uc744 \ub5c4 \ub54c", - "remote_button_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub9b4 \ub54c", - "remote_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5c4 \ub54c", - "remote_double_button_long_press": "\"{subtype}\"\uc5d0\uc11c \ubaa8\ub450 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc190\uc744 \ub5c4 \ub54c", - "remote_double_button_short_press": "\"{subtype}\"\uc5d0\uc11c \ubaa8\ub450 \uc190\uc744 \ub5c4 \ub54c" + "remote_button_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \ub5bc\uc600\uc744 \ub54c", + "remote_button_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub838\uc744 \ub54c", + "remote_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5bc\uc5c8\uc744 \ub54c", + "remote_double_button_long_press": "\ub450 \"{subtype}\"\uc774(\uac00) \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \ub5bc\uc600\uc744 \ub54c", + "remote_double_button_short_press": "\ub450 \"{subtype}\"\uc5d0\uc11c \ubaa8\ub450 \uc190\uc744 \ub5bc\uc5c8\uc744 \ub54c" } }, "options": { diff --git a/homeassistant/components/hue/translations/nl.json b/homeassistant/components/hue/translations/nl.json index cead9dd21c6..5cc6a3572e2 100644 --- a/homeassistant/components/hue/translations/nl.json +++ b/homeassistant/components/hue/translations/nl.json @@ -2,16 +2,16 @@ "config": { "abort": { "all_configured": "Alle Philips Hue bridges zijn al geconfigureerd", - "already_configured": "Bridge is al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom voor het apparaat is al in volle gang.", - "cannot_connect": "Kan geen verbinding maken met bridge", + "cannot_connect": "Kan geen verbinding maken", "discover_timeout": "Hue bridges kunnen niet worden gevonden", "no_bridges": "Geen Philips Hue bridges ontdekt", "not_hue_bridge": "Dit is geen Hue bridge", "unknown": "Onverwachte fout" }, "error": { - "linking": "Er is een onbekende verbindingsfout opgetreden.", + "linking": "Onverwachte fout", "register_failed": "Registratie is mislukt, probeer het opnieuw" }, "step": { diff --git a/homeassistant/components/hue/translations/zh-Hans.json b/homeassistant/components/hue/translations/zh-Hans.json index c34bd68aad8..1dcc6b8f59b 100644 --- a/homeassistant/components/hue/translations/zh-Hans.json +++ b/homeassistant/components/hue/translations/zh-Hans.json @@ -29,6 +29,13 @@ "device_automation": { "trigger_subtype": { "turn_off": "\u5173\u95ed" + }, + "trigger_type": { + "remote_button_long_release": "\"{subtype}\" \u957f\u6309\u540e\u677e\u5f00", + "remote_button_short_press": "\"{subtype}\" \u5355\u51fb", + "remote_button_short_release": "\"{subtype}\" \u677e\u5f00", + "remote_double_button_long_press": "\"{subtype}\" \u4e24\u952e\u540c\u65f6\u957f\u6309\u540e\u677e\u5f00", + "remote_double_button_short_press": "\"{subtype}\" \u4e24\u952e\u540c\u65f6\u677e\u5f00" } } } \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/bg.json b/homeassistant/components/huisbaasje/translations/bg.json new file mode 100644 index 00000000000..67a484573aa --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/bg.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/hu.json b/homeassistant/components/huisbaasje/translations/hu.json new file mode 100644 index 00000000000..a80b4b9b093 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/hu.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "connection_exception": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unauthenticated_exception": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/id.json b/homeassistant/components/huisbaasje/translations/id.json new file mode 100644 index 00000000000..76e8805524e --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "connection_exception": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unauthenticated_exception": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/humidifier/translations/hu.json b/homeassistant/components/humidifier/translations/hu.json new file mode 100644 index 00000000000..7dd723df738 --- /dev/null +++ b/homeassistant/components/humidifier/translations/hu.json @@ -0,0 +1,28 @@ +{ + "device_automation": { + "action_type": { + "set_humidity": "{entity_name} p\u00e1ratartalom be\u00e1ll\u00edt\u00e1sa", + "set_mode": "{entity_name} m\u00f3d m\u00f3dos\u00edt\u00e1sa", + "toggle": "{entity_name} be/kikapcsol\u00e1sa", + "turn_off": "{entity_name} kikapcsol\u00e1sa", + "turn_on": "{entity_name} bekapcsol\u00e1sa" + }, + "condition_type": { + "is_mode": "A(z) {entity_name} egy adott m\u00f3dra van \u00e1ll\u00edtva", + "is_off": "{entity_name} ki van kapcsolva", + "is_on": "{entity_name} be van kapcsolva" + }, + "trigger_type": { + "target_humidity_changed": "{name} k\u00edv\u00e1nt p\u00e1ratartalom megv\u00e1ltozott", + "turned_off": "{entity_name} ki lett kapcsolva", + "turned_on": "{entity_name} be lett kapcsolva" + } + }, + "state": { + "_": { + "off": "Ki", + "on": "Be" + } + }, + "title": "P\u00e1r\u00e1s\u00edt\u00f3" +} \ No newline at end of file diff --git a/homeassistant/components/humidifier/translations/id.json b/homeassistant/components/humidifier/translations/id.json new file mode 100644 index 00000000000..b06b2bfee45 --- /dev/null +++ b/homeassistant/components/humidifier/translations/id.json @@ -0,0 +1,28 @@ +{ + "device_automation": { + "action_type": { + "set_humidity": "Setel kelembaban untuk {entity_name}", + "set_mode": "Ubah mode di {entity_name}", + "toggle": "Nyala/matikan {entity_name}", + "turn_off": "Matikan {entity_name}", + "turn_on": "Nyalakan {entity_name}" + }, + "condition_type": { + "is_mode": "{entity_name} disetel ke mode tertentu", + "is_off": "{entity_name} mati", + "is_on": "{entity_name} nyala" + }, + "trigger_type": { + "target_humidity_changed": "Kelembapan target {entity_name} berubah", + "turned_off": "{entity_name} dimatikan", + "turned_on": "{entity_name} dinyalakan" + } + }, + "state": { + "_": { + "off": "Mati", + "on": "Nyala" + } + }, + "title": "Pelembab" +} \ No newline at end of file diff --git a/homeassistant/components/humidifier/translations/ko.json b/homeassistant/components/humidifier/translations/ko.json index c484a532156..9c556f246e3 100644 --- a/homeassistant/components/humidifier/translations/ko.json +++ b/homeassistant/components/humidifier/translations/ko.json @@ -1,21 +1,21 @@ { "device_automation": { "action_type": { - "set_humidity": "{entity_name} \uc2b5\ub3c4 \uc124\uc815\ud558\uae30", - "set_mode": "{entity_name} \uc758 \uc6b4\uc804 \ubaa8\ub4dc \ubcc0\uacbd", - "toggle": "{entity_name} \ud1a0\uae00", - "turn_off": "{entity_name} \ub044\uae30", - "turn_on": "{entity_name} \ucf1c\uae30" + "set_humidity": "{entity_name}\uc758 \uc2b5\ub3c4 \uc124\uc815\ud558\uae30", + "set_mode": "{entity_name}\uc758 \uc6b4\uc804 \ubaa8\ub4dc \ubcc0\uacbd", + "toggle": "{entity_name}\uc744(\ub97c) \ud1a0\uae00\ud558\uae30", + "turn_off": "{entity_name}\uc744(\ub97c) \ub044\uae30", + "turn_on": "{entity_name}\uc744(\ub97c) \ucf1c\uae30" }, "condition_type": { - "is_mode": "{entity_name} \uc774(\uac00) \ud2b9\uc815 \ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5b4\uc788\uc73c\uba74", - "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", - "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" + "is_mode": "{entity_name}\uc774(\uac00) \ud2b9\uc815 \ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5b4\uc788\uc73c\uba74", + "is_off": "{entity_name}\uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name}\uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" }, "trigger_type": { - "target_humidity_changed": "{entity_name} \ubaa9\ud45c \uc2b5\ub3c4\uac00 \ubcc0\uacbd\ub420 \ub54c", - "turned_off": "{entity_name} \uc774(\uac00) \uaebc\uc9c8 \ub54c", - "turned_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c8 \ub54c" + "target_humidity_changed": "{entity_name}\uc758 \ubaa9\ud45c \uc2b5\ub3c4\uac00 \ubcc0\uacbd\ub418\uc5c8\uc744 \ub54c", + "turned_off": "{entity_name}\uc774(\uac00) \uaebc\uc84c\uc744 \ub54c", + "turned_on": "{entity_name}\uc774(\uac00) \ucf1c\uc84c\uc744 \ub54c" } }, "state": { diff --git a/homeassistant/components/humidifier/translations/nl.json b/homeassistant/components/humidifier/translations/nl.json index 311943bbd23..915b6f477d4 100644 --- a/homeassistant/components/humidifier/translations/nl.json +++ b/homeassistant/components/humidifier/translations/nl.json @@ -1,9 +1,16 @@ { "device_automation": { "action_type": { + "set_humidity": "Luchtvochtigheid instellen voor {entity_name}", + "set_mode": "Wijzig modus van {entity_name}", "turn_off": "{entity_name} uitschakelen", "turn_on": "{entity_name} inschakelen" }, + "condition_type": { + "is_mode": "{entity_name} is ingesteld op een specifieke modus", + "is_off": "{entity_name} is uitgeschakeld", + "is_on": "{entity_name} staat aan" + }, "trigger_type": { "target_humidity_changed": "{entity_name} doel luchtvochtigheid gewijzigd", "turned_off": "{entity_name} is uitgeschakeld", diff --git a/homeassistant/components/humidifier/translations/zh-Hans.json b/homeassistant/components/humidifier/translations/zh-Hans.json index 8fa6b0be0da..d21c7bf61f7 100644 --- a/homeassistant/components/humidifier/translations/zh-Hans.json +++ b/homeassistant/components/humidifier/translations/zh-Hans.json @@ -8,9 +8,12 @@ "turn_on": "\u6253\u5f00 {entity_name}" }, "condition_type": { - "is_off": "{entity_name} \u5df2\u5173\u95ed" + "is_mode": "{entity_name} \u5904\u4e8e\u6307\u5b9a\u6a21\u5f0f", + "is_off": "{entity_name} \u5df2\u5173\u95ed", + "is_on": "{entity_name} \u5df2\u6253\u5f00" }, "trigger_type": { + "target_humidity_changed": "{entity_name} \u7684\u8bbe\u5b9a\u6e7f\u5ea6\u53d8\u5316", "turned_off": "{entity_name} \u88ab\u5173\u95ed", "turned_on": "{entity_name} \u88ab\u6253\u5f00" } diff --git a/homeassistant/components/hunterdouglas_powerview/translations/hu.json b/homeassistant/components/hunterdouglas_powerview/translations/hu.json index 61461d1796c..063e0dad3c4 100644 --- a/homeassistant/components/hunterdouglas_powerview/translations/hu.json +++ b/homeassistant/components/hunterdouglas_powerview/translations/hu.json @@ -4,8 +4,8 @@ "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, "error": { - "cannot_connect": "Nem siker\u00fclt csatlakozni, pr\u00f3b\u00e1lkozzon \u00fajra.", - "unknown": "V\u00e1ratlan hiba" + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "user": { diff --git a/homeassistant/components/hunterdouglas_powerview/translations/id.json b/homeassistant/components/hunterdouglas_powerview/translations/id.json new file mode 100644 index 00000000000..2d21f87bf67 --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/id.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "link": { + "description": "Ingin menyiapkan {name} ({host})?", + "title": "Hubungkan ke PowerView Hub" + }, + "user": { + "data": { + "host": "Alamat IP" + }, + "title": "Hubungkan ke PowerView Hub" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hvv_departures/translations/he.json b/homeassistant/components/hvv_departures/translations/he.json new file mode 100644 index 00000000000..ac90b3264ea --- /dev/null +++ b/homeassistant/components/hvv_departures/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hvv_departures/translations/hu.json b/homeassistant/components/hvv_departures/translations/hu.json new file mode 100644 index 00000000000..91da2d13a7c --- /dev/null +++ b/homeassistant/components/hvv_departures/translations/hu.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Be\u00e1ll\u00edt\u00e1sok" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hvv_departures/translations/id.json b/homeassistant/components/hvv_departures/translations/id.json new file mode 100644 index 00000000000..d43b306d292 --- /dev/null +++ b/homeassistant/components/hvv_departures/translations/id.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "no_results": "Tidak ada hasil. Coba stasiun/alamat lainnya" + }, + "step": { + "station": { + "data": { + "station": "Stasiun/Alamat" + }, + "title": "Masukkan Stasiun/Alamat" + }, + "station_select": { + "data": { + "station": "Stasiun/Alamat" + }, + "title": "Pilih Stasiun/Alamat" + }, + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Hubungkan ke API HVV" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "filter": "Pilih jalur", + "offset": "Tenggang (menit)", + "real_time": "Gunakan data waktu nyata" + }, + "description": "Ubah opsi untuk sensor keberangkatan ini", + "title": "Opsi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hvv_departures/translations/ko.json b/homeassistant/components/hvv_departures/translations/ko.json index ea6ef8bc23a..ca8de2ff5cd 100644 --- a/homeassistant/components/hvv_departures/translations/ko.json +++ b/homeassistant/components/hvv_departures/translations/ko.json @@ -13,7 +13,7 @@ "data": { "station": "\uc2a4\ud14c\uc774\uc158 / \uc8fc\uc18c" }, - "title": "\uc2a4\ud14c\uc774\uc158 / \uc8fc\uc18c \uc785\ub825\ud558\uae30" + "title": "\uc2a4\ud14c\uc774\uc158 / \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" }, "station_select": { "data": { @@ -27,7 +27,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "title": "HVV API \uc5d0 \uc5f0\uacb0\ud558\uae30" + "title": "HVV API\uc5d0 \uc5f0\uacb0\ud558\uae30" } } }, diff --git a/homeassistant/components/hvv_departures/translations/nl.json b/homeassistant/components/hvv_departures/translations/nl.json index 8c80ae5b942..09c8b5b60e7 100644 --- a/homeassistant/components/hvv_departures/translations/nl.json +++ b/homeassistant/components/hvv_departures/translations/nl.json @@ -5,9 +5,22 @@ }, "error": { "cannot_connect": "Kon niet verbinden", - "invalid_auth": "Ongeldige authenticatie" + "invalid_auth": "Ongeldige authenticatie", + "no_results": "Geen resultaten. Probeer het met een ander station/adres" }, "step": { + "station": { + "data": { + "station": "Station/Adres" + }, + "title": "Voer station/adres in" + }, + "station_select": { + "data": { + "station": "Station/Adres" + }, + "title": "Selecteer Station/Adres" + }, "user": { "data": { "host": "Host", @@ -17,5 +30,18 @@ "title": "Maak verbinding met de HVV API" } } + }, + "options": { + "step": { + "init": { + "data": { + "filter": "Selecteer lijnen", + "offset": "Offset (minuten)", + "real_time": "Gebruik realtime gegevens" + }, + "description": "Wijzig opties voor deze vertreksensor", + "title": "Opties" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/hu.json b/homeassistant/components/hyperion/translations/hu.json index 50ccd9f3b63..cfe649d9d5e 100644 --- a/homeassistant/components/hyperion/translations/hu.json +++ b/homeassistant/components/hyperion/translations/hu.json @@ -1,13 +1,26 @@ { "config": { "abort": { - "reauth_successful": "Az \u00fajb\u00f3li azonos\u00edt\u00e1s sikeres" + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_access_token": "\u00c9rv\u00e9nytelen hozz\u00e1f\u00e9r\u00e9si token" }, "step": { "auth": { "data": { "create_token": "\u00daj token automatikus l\u00e9trehoz\u00e1sa" } + }, + "user": { + "data": { + "host": "Hoszt", + "port": "Port" + } } } } diff --git a/homeassistant/components/hyperion/translations/id.json b/homeassistant/components/hyperion/translations/id.json new file mode 100644 index 00000000000..c1c2a62e0d9 --- /dev/null +++ b/homeassistant/components/hyperion/translations/id.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "auth_new_token_not_granted_error": "Token yang baru dibuat tidak disetujui di antarmuka Hyperion", + "auth_new_token_not_work_error": "Gagal mengautentikasi menggunakan token yang baru dibuat", + "auth_required_error": "Gagal menentukan apakah otorisasi diperlukan", + "cannot_connect": "Gagal terhubung", + "no_id": "Instans Hyperion Ambilight tidak melaporkan ID-nya", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_access_token": "Token akses tidak valid" + }, + "step": { + "auth": { + "data": { + "create_token": "Buat token baru secara otomatis", + "token": "Atau berikan token yang sudah ada sebelumnya" + }, + "description": "Konfigurasikan otorisasi ke server Hyperion Ambilight Anda" + }, + "confirm": { + "description": "Apakah Anda ingin menambahkan Hyperion Ambilight ini ke Home Assistant?\n\n**Host:** {host}\n**Port:** {port}\n**ID**: {id}", + "title": "Konfirmasikan penambahan layanan Hyperion Ambilight" + }, + "create_token": { + "description": "Pilih **Kirim** di bawah ini untuk meminta token autentikasi baru. Anda akan diarahkan ke antarmuka Hyperion untuk menyetujui permintaan. Pastikan ID yang ditampilkan adalah \"{auth_id}\"", + "title": "Buat token autentikasi baru secara otomatis" + }, + "create_token_external": { + "title": "Terima token baru di antarmuka Hyperion" + }, + "user": { + "data": { + "host": "Host", + "port": "Port" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "priority": "Prioritas hyperion digunakan untuk warna dan efek" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/ko.json b/homeassistant/components/hyperion/translations/ko.json index 295d418da12..94dc9d48a58 100644 --- a/homeassistant/components/hyperion/translations/ko.json +++ b/homeassistant/components/hyperion/translations/ko.json @@ -3,7 +3,11 @@ "abort": { "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "auth_new_token_not_granted_error": "\uc0c8\ub85c \uc0dd\uc131\ub41c \ud1a0\ud070\uc774 Hyperion UI\uc5d0\uc11c \uc2b9\uc778\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4", + "auth_new_token_not_work_error": "\uc0c8\ub85c \uc0dd\uc131\ub41c \ud1a0\ud070\uc744 \uc0ac\uc6a9\ud558\uc5ec \uc778\uc99d\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "auth_required_error": "\uc778\uc99d\uc774 \ud544\uc694\ud55c\uc9c0 \ud655\uc778\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "no_id": "Hyperion Amblight \uc778\uc2a4\ud134\uc2a4\uac00 \ud574\ub2f9 ID\ub97c \ubcf4\uace0\ud558\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4", "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { @@ -11,6 +15,24 @@ "invalid_access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { + "auth": { + "data": { + "create_token": "\uc0c8\ub85c\uc6b4 \ud1a0\ud070\uc744 \uc790\ub3d9\uc73c\ub85c \uc0dd\uc131\ud558\uae30", + "token": "\ub610\ub294 \uae30\uc874 \ud1a0\ud070 \uc81c\uacf5\ud558\uae30" + }, + "description": "Hyperion Amblight \uc11c\ubc84\uc5d0 \ub300\ud55c \uad8c\ud55c \uad6c\uc131\ud558\uae30" + }, + "confirm": { + "description": "\uc774 Hyperion Amblight\ub97c Home Assistant\uc5d0 \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\n\n**\ud638\uc2a4\ud2b8**: {host}\n**\ud3ec\ud2b8**: {port}\n**ID**: {id}", + "title": "Hyperion Amblight \uc11c\ube44\uc2a4 \ucd94\uac00 \ud655\uc778\ud558\uae30" + }, + "create_token": { + "description": "\uc544\ub798 **\ud655\uc778**\uc744 \uc120\ud0dd\ud558\uc5ec \uc0c8\ub85c\uc6b4 \uc778\uc99d \ud1a0\ud070\uc744 \uc694\uccad\ud574\uc8fc\uc138\uc694. \uc694\uccad\uc744 \uc2b9\uc778\ud558\ub3c4\ub85d Hyperion UI\ub85c \ub9ac\ub514\ub809\uc158\ub429\ub2c8\ub2e4. \ud45c\uc2dc\ub41c ID\uac00 \"{auth_id}\"\uc778\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694", + "title": "\uc0c8\ub85c\uc6b4 \uc778\uc99d \ud1a0\ud070\uc744 \uc790\ub3d9\uc73c\ub85c \uc0dd\uc131\ud558\uae30" + }, + "create_token_external": { + "title": "Hyperion UI\uc5d0\uc11c \uc0c8\ub85c\uc6b4 \ud1a0\ud070 \uc218\ub77d\ud558\uae30" + }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8", @@ -18,5 +40,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "priority": "\uc0c9\uc0c1 \ubc0f \ud6a8\uacfc\uc5d0 \uc0ac\uc6a9\ud560 Hyperion \uc6b0\uc120 \uc21c\uc704" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/nl.json b/homeassistant/components/hyperion/translations/nl.json index 0898272e4a2..cb2de7e845d 100644 --- a/homeassistant/components/hyperion/translations/nl.json +++ b/homeassistant/components/hyperion/translations/nl.json @@ -7,6 +7,7 @@ "auth_new_token_not_work_error": "Verificatie met nieuw aangemaakt token mislukt", "auth_required_error": "Kan niet bepalen of autorisatie vereist is", "cannot_connect": "Kan geen verbinding maken", + "no_id": "De Hyperion Ambilight instantie heeft zijn id niet gerapporteerd", "reauth_successful": "Herauthenticatie was succesvol" }, "error": { @@ -16,8 +17,14 @@ "step": { "auth": { "data": { - "create_token": "Maak automatisch een nieuw token aan" - } + "create_token": "Maak automatisch een nieuw token aan", + "token": "Of geef een reeds bestaand token op" + }, + "description": "Configureer autorisatie voor uw Hyperion Ambilight-server" + }, + "confirm": { + "description": "Wilt u deze Hyperion Ambilight toevoegen aan Home Assistant? \n\n ** Host: ** {host}\n ** Poort: ** {port}\n ** ID **: {id}", + "title": "Bevestig de toevoeging van Hyperion Ambilight-service" }, "create_token_external": { "title": "Accepteer nieuwe token in Hyperion UI" diff --git a/homeassistant/components/iaqualink/translations/he.json b/homeassistant/components/iaqualink/translations/he.json new file mode 100644 index 00000000000..6f4191da70d --- /dev/null +++ b/homeassistant/components/iaqualink/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iaqualink/translations/hu.json b/homeassistant/components/iaqualink/translations/hu.json index 149fee90583..dcb7b906ee3 100644 --- a/homeassistant/components/iaqualink/translations/hu.json +++ b/homeassistant/components/iaqualink/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, diff --git a/homeassistant/components/iaqualink/translations/id.json b/homeassistant/components/iaqualink/translations/id.json new file mode 100644 index 00000000000..4591cf11e05 --- /dev/null +++ b/homeassistant/components/iaqualink/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Masukkan nama pengguna dan kata sandi untuk akun iAqualink Anda.", + "title": "Hubungkan ke iAqualink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iaqualink/translations/ko.json b/homeassistant/components/iaqualink/translations/ko.json index 1386480fca4..ef77daf978b 100644 --- a/homeassistant/components/iaqualink/translations/ko.json +++ b/homeassistant/components/iaqualink/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" @@ -13,7 +13,7 @@ "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, "description": "iAqualink \uacc4\uc815\uc758 \uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", - "title": "iAqualink \uc5d0 \uc5f0\uacb0\ud558\uae30" + "title": "iAqualink\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/icloud/translations/de.json b/homeassistant/components/icloud/translations/de.json index 64a6bcd885c..7baf9dc3917 100644 --- a/homeassistant/components/icloud/translations/de.json +++ b/homeassistant/components/icloud/translations/de.json @@ -15,6 +15,7 @@ "data": { "password": "Passwort" }, + "description": "Ihr zuvor eingegebenes Passwort f\u00fcr {username} funktioniert nicht mehr. Aktualisieren Sie Ihr Passwort, um diese Integration weiterhin zu verwenden.", "title": "Integration erneut authentifizieren" }, "trusted_device": { diff --git a/homeassistant/components/icloud/translations/he.json b/homeassistant/components/icloud/translations/he.json index 71466dddc39..139d7a1e399 100644 --- a/homeassistant/components/icloud/translations/he.json +++ b/homeassistant/components/icloud/translations/he.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", "username": "\u05d3\u05d5\u05d0\u05e8 \u05d0\u05dc\u05e7\u05d8\u05e8\u05d5\u05e0\u05d9" } } diff --git a/homeassistant/components/icloud/translations/hu.json b/homeassistant/components/icloud/translations/hu.json index 2e820418e94..bb47cdd879b 100644 --- a/homeassistant/components/icloud/translations/hu.json +++ b/homeassistant/components/icloud/translations/hu.json @@ -1,6 +1,11 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "send_verification_code": "Nem siker\u00fclt elk\u00fcldeni az ellen\u0151rz\u0151 k\u00f3dot", "validate_verification_code": "Nem siker\u00fclt ellen\u0151rizni az ellen\u0151rz\u0151 k\u00f3dot, ki kell v\u00e1lasztania egy megb\u00edzhat\u00f3s\u00e1gi eszk\u00f6zt, \u00e9s \u00fajra kell ind\u00edtania az ellen\u0151rz\u00e9st" }, @@ -8,7 +13,8 @@ "reauth": { "data": { "password": "Jelsz\u00f3" - } + }, + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" }, "trusted_device": { "data": { diff --git a/homeassistant/components/icloud/translations/id.json b/homeassistant/components/icloud/translations/id.json new file mode 100644 index 00000000000..cd7abc1945d --- /dev/null +++ b/homeassistant/components/icloud/translations/id.json @@ -0,0 +1,46 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "no_device": "Tidak ada perangkat Anda yang mengaktifkan \"Temukan iPhone saya\"", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid", + "send_verification_code": "Gagal mengirim kode verifikasi", + "validate_verification_code": "Gagal memverifikasi kode verifikasi Anda, coba lagi" + }, + "step": { + "reauth": { + "data": { + "password": "Kata Sandi" + }, + "description": "Kata sandi yang Anda masukkan sebelumnya untuk {username} tidak lagi berfungsi. Perbarui kata sandi Anda untuk tetap menggunakan integrasi ini.", + "title": "Autentikasi Ulang Integrasi" + }, + "trusted_device": { + "data": { + "trusted_device": "Perangkat tepercaya" + }, + "description": "Pilih perangkat tepercaya Anda", + "title": "Perangkat tepercaya iCloud" + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Email", + "with_family": "Dengan keluarga" + }, + "description": "Masukkan kredensial Anda", + "title": "Kredensial iCloud" + }, + "verification_code": { + "data": { + "verification_code": "Kode verifikasi" + }, + "description": "Masukkan kode verifikasi yang baru saja diterima dari iCloud", + "title": "Kode verifikasi iCloud" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/translations/ko.json b/homeassistant/components/icloud/translations/ko.json index 5e02fb02993..52319b888ca 100644 --- a/homeassistant/components/icloud/translations/ko.json +++ b/homeassistant/components/icloud/translations/ko.json @@ -15,7 +15,8 @@ "data": { "password": "\ube44\ubc00\ubc88\ud638" }, - "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d" + "description": "\uc774\uc804\uc5d0 \uc785\ub825\ud55c {username}\uc5d0 \ub300\ud55c \ube44\ubc00\ubc88\ud638\uac00 \ub354 \uc774\uc0c1 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \uc774 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uacc4\uc18d \uc0ac\uc6a9\ud558\ub824\uba74 \ube44\ubc00\ubc88\ud638\ub97c \uc5c5\ub370\uc774\ud2b8\ud574\uc8fc\uc138\uc694.", + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" }, "trusted_device": { "data": { diff --git a/homeassistant/components/icloud/translations/nl.json b/homeassistant/components/icloud/translations/nl.json index b150c8d5b16..7260f954c38 100644 --- a/homeassistant/components/icloud/translations/nl.json +++ b/homeassistant/components/icloud/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Account reeds geconfigureerd", + "already_configured": "Account is al geconfigureerd", "no_device": "Op geen van uw apparaten is \"Find my iPhone\" geactiveerd", "reauth_successful": "Herauthenticatie was succesvol" }, diff --git a/homeassistant/components/ifttt/translations/hu.json b/homeassistant/components/ifttt/translations/hu.json index c3e7007b1a0..9898beb3e92 100644 --- a/homeassistant/components/ifttt/translations/hu.json +++ b/homeassistant/components/ifttt/translations/hu.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "webhook_not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." + }, "create_entry": { - "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, akkor az \u201eIFTTT Webhook kisalkalmaz\u00e1s\u201d ( {applet_url} ) \"Webk\u00e9r\u00e9s k\u00e9sz\u00edt\u00e9se\" m\u0171velet\u00e9t kell haszn\u00e1lnia. \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: \" {webhook_url} \"\n - M\u00f3dszer: POST\n - Tartalom t\u00edpusa: application/json \n\n L\u00e1sd [a dokument\u00e1ci\u00f3] ( {docs_url} ), hogyan konfigur\u00e1lhatja az automatiz\u00e1l\u00e1sokat a bej\u00f6v\u0151 adatok kezel\u00e9s\u00e9re." + "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, akkor az [IFTTT Webhook applet]({applet_url}) \"Make a web request\" m\u0171velet\u00e9t kell haszn\u00e1lnia. \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: `{webhook_url}`\n - M\u00f3dszer: POST\n - Tartalom t\u00edpusa: application/json \n\n L\u00e1sd [a dokument\u00e1ci\u00f3]({docs_url}), hogyan konfigur\u00e1lhatja az automatiz\u00e1l\u00e1sokat a bej\u00f6v\u0151 adatok kezel\u00e9s\u00e9re." }, "step": { "user": { diff --git a/homeassistant/components/ifttt/translations/id.json b/homeassistant/components/ifttt/translations/id.json new file mode 100644 index 00000000000..f997f39a54e --- /dev/null +++ b/homeassistant/components/ifttt/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." + }, + "create_entry": { + "default": "Untuk mengirim event ke Home Assistant, Anda harus menggunakan tindakan \"Make a web request\" dari [applet IFTTT Webhook]({applet_url}).\n\nIsi info berikut:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nBaca [dokumentasi]({docs_url}) tentang cara mengonfigurasi otomasi untuk menangani data masuk." + }, + "step": { + "user": { + "description": "Yakin ingin menyiapkan IFTTT?", + "title": "Siapkan Applet IFTTT Webhook" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ifttt/translations/ko.json b/homeassistant/components/ifttt/translations/ko.json index bc561027fc3..826a03c7339 100644 --- a/homeassistant/components/ifttt/translations/ko.json +++ b/homeassistant/components/ifttt/translations/ko.json @@ -1,15 +1,15 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\uae30 \uc704\ud574\uc11c\ub294 [IFTTT \uc6f9 \ud6c5 \uc560\ud50c\ub9bf]({applet_url}) \uc5d0\uc11c \"Make a web request\" \ub97c \uc0ac\uc6a9\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c\uc758 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\nHome Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\uae30 \uc704\ud574\uc11c\ub294 [IFTTT \uc6f9 \ud6c5 \uc560\ud50c\ub9bf]({applet_url})\uc5d0\uc11c \"Make a web request\"\ub97c \uc0ac\uc6a9\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c\uc758 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\nHome Assistant\ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { - "description": "IFTTT \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "IFTTT\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "IFTTT \uc6f9 \ud6c5 \uc560\ud50c\ub9bf \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/image_processing/translations/id.json b/homeassistant/components/image_processing/translations/id.json index 19e3a64dcba..c186898fd07 100644 --- a/homeassistant/components/image_processing/translations/id.json +++ b/homeassistant/components/image_processing/translations/id.json @@ -1,3 +1,3 @@ { - "title": "Pengolahan gambar" + "title": "Pengolahan citra" } \ No newline at end of file diff --git a/homeassistant/components/input_boolean/translations/id.json b/homeassistant/components/input_boolean/translations/id.json index 4401df1f453..df890baae4a 100644 --- a/homeassistant/components/input_boolean/translations/id.json +++ b/homeassistant/components/input_boolean/translations/id.json @@ -1,8 +1,8 @@ { "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" } }, "title": "Input boolean" diff --git a/homeassistant/components/insteon/translations/he.json b/homeassistant/components/insteon/translations/he.json new file mode 100644 index 00000000000..8aabed0bfce --- /dev/null +++ b/homeassistant/components/insteon/translations/he.json @@ -0,0 +1,12 @@ +{ + "options": { + "step": { + "change_hub_config": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/insteon/translations/hu.json b/homeassistant/components/insteon/translations/hu.json new file mode 100644 index 00000000000..06033fb1321 --- /dev/null +++ b/homeassistant/components/insteon/translations/hu.json @@ -0,0 +1,72 @@ +{ + "config": { + "abort": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "select_single": "V\u00e1lassz egy lehet\u0151s\u00e9get" + }, + "step": { + "hubv1": { + "data": { + "host": "IP c\u00edm", + "port": "Port" + } + }, + "hubv2": { + "data": { + "host": "IP c\u00edm", + "password": "Jelsz\u00f3", + "port": "Port", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "title": "Insteon Hub 2. verzi\u00f3" + }, + "plm": { + "data": { + "device": "USB eszk\u00f6z el\u00e9r\u00e9si \u00fat" + } + }, + "user": { + "data": { + "modem_type": "Modem t\u00edpusa." + }, + "description": "V\u00e1laszd ki az Insteon modem t\u00edpus\u00e1t.", + "title": "Insteon" + } + } + }, + "options": { + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "add_override": { + "title": "Insteon" + }, + "add_x10": { + "title": "Insteon" + }, + "change_hub_config": { + "data": { + "host": "IP c\u00edm", + "password": "Jelsz\u00f3", + "port": "Port", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "title": "Insteon" + }, + "init": { + "title": "Insteon" + }, + "remove_override": { + "title": "Insteon" + }, + "remove_x10": { + "title": "Insteon" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/insteon/translations/id.json b/homeassistant/components/insteon/translations/id.json new file mode 100644 index 00000000000..efae5284150 --- /dev/null +++ b/homeassistant/components/insteon/translations/id.json @@ -0,0 +1,109 @@ +{ + "config": { + "abort": { + "cannot_connect": "Gagal terhubung", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "select_single": "Pilih satu opsi." + }, + "step": { + "hubv1": { + "data": { + "host": "Alamat IP", + "port": "Port" + }, + "description": "Konfigurasikan Insteon Hub Versio 1 (pra-2014).", + "title": "Insteon Hub Versi 1" + }, + "hubv2": { + "data": { + "host": "Alamat IP", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + }, + "description": "Konfigurasikan Insteon Hub Versi 2.", + "title": "Insteon Hub Versi 2" + }, + "plm": { + "data": { + "device": "Jalur Perangkat USB" + }, + "description": "Konfigurasikan Insteon PowerLink Modem (PLM).", + "title": "Insteon PLM" + }, + "user": { + "data": { + "modem_type": "Jenis modem." + }, + "description": "Pilih jenis modem Insteon.", + "title": "Insteon" + } + } + }, + "options": { + "error": { + "cannot_connect": "Gagal terhubung", + "input_error": "Entri tidak valid, periksa nilai Anda.", + "select_single": "Pilih satu opsi." + }, + "step": { + "add_override": { + "data": { + "address": "Alamat perangkat (yaitu 1a2b3c)", + "cat": "Kategori perangkat (mis. 0x10)", + "subcat": "Subkategori perangkat (mis. 0x0a)" + }, + "description": "Tambahkan penimpaan nilai perangkat.", + "title": "Insteon" + }, + "add_x10": { + "data": { + "housecode": "Kode rumah (a - p)", + "platform": "Platform", + "steps": "Langkah peredup (hanya untuk perangkat ringan, nilai bawaan adalah 22)", + "unitcode": "Unitcode (1 - 16)" + }, + "description": "Ubah kata sandi Insteon Hub.", + "title": "Insteon" + }, + "change_hub_config": { + "data": { + "host": "Alamat IP", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + }, + "description": "Ubah informasi koneksi Insteon Hub. Anda harus memulai ulang Home Assistant setelah melakukan perubahan ini. Ini tidak mengubah konfigurasi Hub itu sendiri. Untuk mengubah konfigurasi di Hub, gunakan aplikasi Hub.", + "title": "Insteon" + }, + "init": { + "data": { + "add_override": "Tambahkan penimpaan nilai perangkat.", + "add_x10": "Tambahkan perangkat X10.", + "change_hub_config": "Ubah konfigurasi Hub.", + "remove_override": "Hapus penimpaan nilai perangkat.", + "remove_x10": "Hapus perangkat X10." + }, + "description": "Pilih opsi untuk dikonfigurasi.", + "title": "Insteon" + }, + "remove_override": { + "data": { + "address": "Pilih alamat perangkat untuk dihapus" + }, + "description": "Hapus penimpaan nilai perangkat", + "title": "Insteon" + }, + "remove_x10": { + "data": { + "address": "Pilih alamat perangkat untuk dihapus" + }, + "description": "Hapus perangkat X10", + "title": "Insteon" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/insteon/translations/ko.json b/homeassistant/components/insteon/translations/ko.json index 1cd65afc9e9..559e3351932 100644 --- a/homeassistant/components/insteon/translations/ko.json +++ b/homeassistant/components/insteon/translations/ko.json @@ -2,11 +2,11 @@ "config": { "abort": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "select_single": "\ud558\ub098\uc758 \uc635\uc158\uc744 \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624." + "select_single": "\ud558\ub098\uc758 \uc635\uc158\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694." }, "step": { "hubv1": { @@ -14,7 +14,7 @@ "host": "IP \uc8fc\uc18c", "port": "\ud3ec\ud2b8" }, - "description": "Insteon Hub \ubc84\uc804 1 (2014 \ub144 \uc774\uc804)\uc744 \uad6c\uc131\ud569\ub2c8\ub2e4.", + "description": "Insteon Hub \ubc84\uc804 1\uc744 \uad6c\uc131\ud569\ub2c8\ub2e4. (2014\ub144 \uc774\uc804)", "title": "Insteon Hub \ubc84\uc804 1" }, "hubv2": { @@ -30,13 +30,15 @@ "plm": { "data": { "device": "USB \uc7a5\uce58 \uacbd\ub85c" - } + }, + "description": "Insteon PowerLinc Modem (PLM)\uc744 \uad6c\uc131\ud569\ub2c8\ub2e4.", + "title": "Insteon PLM" }, "user": { "data": { "modem_type": "\ubaa8\ub380 \uc720\ud615." }, - "description": "Insteon \ubaa8\ub380 \uc720\ud615\uc744 \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624.", + "description": "Insteon \ubaa8\ub380 \uc720\ud615\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", "title": "Insteon" } } @@ -44,20 +46,28 @@ "options": { "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "select_single": "\uc635\uc158 \uc120\ud0dd" + "input_error": "\ud56d\ubaa9\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uac12\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "select_single": "\uc635\uc158\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694." }, "step": { "add_override": { "data": { - "cat": "\uc7a5\uce58 \ubc94\uc8fc(\uc608: 0x10)" - } + "address": "\uae30\uae30 \uc8fc\uc18c (\uc608: 1a2b3c)", + "cat": "\uae30\uae30 \ubc94\uc8fc (\uc608: 0x10)", + "subcat": "\uae30\uae30 \ud558\uc704 \ubc94\uc8fc (\uc608: 0x0a)" + }, + "description": "\uae30\uae30 \uc7ac\uc815\uc758\ub97c \ucd94\uac00\ud569\ub2c8\ub2e4.", + "title": "Insteon" }, "add_x10": { "data": { - "steps": "\ub514\uba38 \ub2e8\uacc4(\ub77c\uc774\ud2b8 \uc7a5\uce58\uc5d0\ub9cc, \uae30\ubcf8\uac12 22)", - "unitcode": "\ub2e8\uc704 \ucf54\ub4dc (1-16)" + "housecode": "\ud558\uc6b0\uc2a4 \ucf54\ub4dc (a - p)", + "platform": "\ud50c\ub7ab\ud3fc", + "steps": "\ubc1d\uae30 \uc870\uc808 \ub2e8\uacc4 (\uc870\uba85 \uae30\uae30 \uc804\uc6a9, \uae30\ubcf8\uac12 22)", + "unitcode": "\uc720\ub2db \ucf54\ub4dc (1-16)" }, - "description": "Insteon Hub \ube44\ubc00\ubc88\ud638\ub97c \ubcc0\uacbd\ud569\ub2c8\ub2e4." + "description": "Insteon Hub \ube44\ubc00\ubc88\ud638\ub97c \ubcc0\uacbd\ud569\ub2c8\ub2e4.", + "title": "Insteon" }, "change_hub_config": { "data": { @@ -65,29 +75,34 @@ "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - } + }, + "description": "Insteon \ud5c8\ube0c\uc758 \uc5f0\uacb0 \uc815\ubcf4\ub97c \ubcc0\uacbd\ud569\ub2c8\ub2e4. \ubcc0\uacbd\ud55c \ud6c4\uc5d0\ub294 Home Assistant\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud574\uc57c \ud569\ub2c8\ub2e4. \ud5c8\ube0c \uc790\uccb4\uc758 \uad6c\uc131\uc740 \ubcc0\uacbd\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \ud5c8\ube0c\uc758 \uad6c\uc131\uc744 \ubcc0\uacbd\ud558\ub824\uba74 \ud5c8\ube0c \uc571\uc744 \uc0ac\uc6a9\ud574\uc8fc\uc138\uc694.", + "title": "Insteon" }, "init": { "data": { - "add_override": "\uc7a5\uce58 Override \ucd94\uac00", - "add_x10": "X10 \uc7a5\uce58\ub97c \ucd94\uac00\ud569\ub2c8\ub2e4.", + "add_override": "\uae30\uae30 \uc7ac\uc815\uc758\ub97c \ucd94\uac00\ud569\ub2c8\ub2e4.", + "add_x10": "X10 \uae30\uae30\ub97c \ucd94\uac00\ud569\ub2c8\ub2e4.", "change_hub_config": "\ud5c8\ube0c \uad6c\uc131\uc744 \ubcc0\uacbd\ud569\ub2c8\ub2e4.", - "remove_override": "\uc7a5\uce58 Override \uc81c\uac70", - "remove_x10": "X10 \uc7a5\uce58\ub97c \uc81c\uac70\ud569\ub2c8\ub2e4." + "remove_override": "\uae30\uae30 \uc7ac\uc815\uc758\ub97c \uc81c\uac70\ud569\ub2c8\ub2e4.", + "remove_x10": "X10 \uae30\uae30\ub97c \uc81c\uac70\ud569\ub2c8\ub2e4." }, - "description": "\uad6c\uc131 \ud560 \uc635\uc158\uc744 \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624." + "description": "\uad6c\uc131\ud560 \uc635\uc158\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", + "title": "Insteon" }, "remove_override": { "data": { - "address": "\uc81c\uac70 \ud560 \uc7a5\uce58 \uc8fc\uc18c\ub97c \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624." + "address": "\uc81c\uac70\ud560 \uae30\uae30\uc758 \uc8fc\uc18c\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694" }, - "description": "\uc7a5\uce58 Override \uc81c\uac70" + "description": "\uae30\uae30 \uc7ac\uc815\uc758 \uc81c\uac70\ud558\uae30", + "title": "Insteon" }, "remove_x10": { "data": { - "address": "\uc81c\uac70 \ud560 \uc7a5\uce58 \uc8fc\uc18c\ub97c \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624." + "address": "\uc81c\uac70\ud560 \uae30\uae30\uc758 \uc8fc\uc18c\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694" }, - "description": "X10 \uc7a5\uce58 \uc81c\uac70" + "description": "X10 \uae30\uae30 \uc81c\uac70\ud558\uae30", + "title": "Insteon" } } } diff --git a/homeassistant/components/insteon/translations/nl.json b/homeassistant/components/insteon/translations/nl.json index 98a27fb1139..20b29287862 100644 --- a/homeassistant/components/insteon/translations/nl.json +++ b/homeassistant/components/insteon/translations/nl.json @@ -86,7 +86,11 @@ "change_hub_config": "Wijzig de Hub-configuratie.", "remove_override": "Verwijder een apparaatoverschrijving.", "remove_x10": "Verwijder een X10-apparaat." - } + }, + "title": "Insteon" + }, + "remove_override": { + "title": "Insteon" }, "remove_x10": { "title": "Insteon" diff --git a/homeassistant/components/ios/translations/hu.json b/homeassistant/components/ios/translations/hu.json index f716fd36e9a..dda7af8c541 100644 --- a/homeassistant/components/ios/translations/hu.json +++ b/homeassistant/components/ios/translations/hu.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "Csak egyetlen Home Assistant iOS konfigur\u00e1ci\u00f3 sz\u00fcks\u00e9ges." + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "step": { "confirm": { - "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a Home Assistant iOS komponenst?" + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?" } } } diff --git a/homeassistant/components/ios/translations/id.json b/homeassistant/components/ios/translations/id.json index 46449f1c044..c8003cc0d3d 100644 --- a/homeassistant/components/ios/translations/id.json +++ b/homeassistant/components/ios/translations/id.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "Hanya satu konfigurasi Home Assistant iOS yang diperlukan." + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." }, "step": { "confirm": { - "description": "Apakah Anda ingin mengatur komponen iOS Home Assistant?" + "description": "Ingin memulai penyiapan?" } } } diff --git a/homeassistant/components/ios/translations/ko.json b/homeassistant/components/ios/translations/ko.json index f5da462c1ab..d7eaa77481f 100644 --- a/homeassistant/components/ios/translations/ko.json +++ b/homeassistant/components/ios/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/ios/translations/nl.json b/homeassistant/components/ios/translations/nl.json index 0575b4558af..78757f9f715 100644 --- a/homeassistant/components/ios/translations/nl.json +++ b/homeassistant/components/ios/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "Er is slechts \u00e9\u00e9n configuratie van Home Assistant iOS nodig." + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "step": { "confirm": { - "description": "Wilt u het Home Assistant iOS component instellen?" + "description": "Wil je beginnen met instellen?" } } } diff --git a/homeassistant/components/ipma/translations/hu.json b/homeassistant/components/ipma/translations/hu.json index 0aeb36b0442..00ba66d2dd7 100644 --- a/homeassistant/components/ipma/translations/hu.json +++ b/homeassistant/components/ipma/translations/hu.json @@ -12,8 +12,13 @@ "name": "N\u00e9v" }, "description": "Portug\u00e1l Atmoszf\u00e9ra Int\u00e9zet", - "title": "Hely" + "title": "Elhelyezked\u00e9s" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "IPMA API v\u00e9gpont el\u00e9rhet\u0151" + } } } \ No newline at end of file diff --git a/homeassistant/components/ipma/translations/id.json b/homeassistant/components/ipma/translations/id.json new file mode 100644 index 00000000000..2f7e8324fdf --- /dev/null +++ b/homeassistant/components/ipma/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "error": { + "name_exists": "Nama sudah ada" + }, + "step": { + "user": { + "data": { + "latitude": "Lintang", + "longitude": "Bujur", + "mode": "Mode", + "name": "Nama" + }, + "description": "Instituto Portugu\u00eas do Mar e Atmosfera", + "title": "Lokasi" + } + } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "Titik akhir API IPMA dapat dijangkau" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/translations/ko.json b/homeassistant/components/ipma/translations/ko.json index 3fd1a8d8ea9..826309a4ee4 100644 --- a/homeassistant/components/ipma/translations/ko.json +++ b/homeassistant/components/ipma/translations/ko.json @@ -15,5 +15,10 @@ "title": "\uc704\uce58" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "IPMA API \uc5d4\ub4dc\ud3ec\uc778\ud2b8 \uc5f0\uacb0" + } } } \ No newline at end of file diff --git a/homeassistant/components/ipma/translations/nl.json b/homeassistant/components/ipma/translations/nl.json index 79248e0d064..154aafe1033 100644 --- a/homeassistant/components/ipma/translations/nl.json +++ b/homeassistant/components/ipma/translations/nl.json @@ -6,8 +6,8 @@ "step": { "user": { "data": { - "latitude": "Latitude", - "longitude": "Longitude", + "latitude": "Breedtegraad", + "longitude": "Lengtegraad", "mode": "Mode", "name": "Naam" }, @@ -15,5 +15,10 @@ "title": "Locatie" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "IPMA API-eindpunt bereikbaar" + } } } \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/hu.json b/homeassistant/components/ipp/translations/hu.json index 396992156c0..8c988eff551 100644 --- a/homeassistant/components/ipp/translations/hu.json +++ b/homeassistant/components/ipp/translations/hu.json @@ -14,8 +14,13 @@ "user": { "data": { "host": "Hoszt", - "port": "Port" + "port": "Port", + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" } + }, + "zeroconf_confirm": { + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a {name}-t?" } } } diff --git a/homeassistant/components/ipp/translations/id.json b/homeassistant/components/ipp/translations/id.json new file mode 100644 index 00000000000..c2b95751d4b --- /dev/null +++ b/homeassistant/components/ipp/translations/id.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "connection_upgrade": "Gagal terhubung ke printer karena peningkatan koneksi diperlukan.", + "ipp_error": "Terjadi kesalahan IPP.", + "ipp_version_error": "Versi IPP tidak didukung oleh printer.", + "parse_error": "Gagal mengurai respons dari printer.", + "unique_id_required": "Perangkat tidak memiliki identifikasi unik yang diperlukan untuk ditemukan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "connection_upgrade": "Gagal terhubung ke printer. Coba lagi dengan mencentang opsi SSL/TLS." + }, + "flow_title": "Printer: {name}", + "step": { + "user": { + "data": { + "base_path": "Jalur relatif ke printer", + "host": "Host", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL", + "verify_ssl": "Verifikasi sertifikat SSL" + }, + "description": "Siapkan printer Anda melalui Internet Printing Protocol (IPP) untuk diintegrasikan dengan Home Assistant.", + "title": "Tautkan printer Anda" + }, + "zeroconf_confirm": { + "description": "Ingin menyiapkan {name}?", + "title": "Printer yang ditemukan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/ko.json b/homeassistant/components/ipp/translations/ko.json index 28e79ffe281..2ae4e08beaf 100644 --- a/homeassistant/components/ipp/translations/ko.json +++ b/homeassistant/components/ipp/translations/ko.json @@ -11,7 +11,7 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "connection_upgrade": "\ud504\ub9b0\ud130\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. SSL/TLS \uc635\uc158\uc744 \ud655\uc778\ud558\uace0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." + "connection_upgrade": "\ud504\ub9b0\ud130\uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. SSL/TLS \uc635\uc158\uc744 \ud655\uc778\ud558\uace0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." }, "flow_title": "\ud504\ub9b0\ud130: {name}", "step": { @@ -27,7 +27,7 @@ "title": "\ud504\ub9b0\ud130 \uc5f0\uacb0\ud558\uae30" }, "zeroconf_confirm": { - "description": "{name} \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "{name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "\ubc1c\uacac\ub41c \ud504\ub9b0\ud130" } } diff --git a/homeassistant/components/ipp/translations/nl.json b/homeassistant/components/ipp/translations/nl.json index bcbd495be91..f3d2ee60797 100644 --- a/homeassistant/components/ipp/translations/nl.json +++ b/homeassistant/components/ipp/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Deze printer is al geconfigureerd.", + "already_configured": "Apparaat is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken", "connection_upgrade": "Kan geen verbinding maken met de printer omdat een upgrade van de verbinding vereist is.", "ipp_error": "Er is een IPP-fout opgetreden.", @@ -18,10 +18,10 @@ "user": { "data": { "base_path": "Relatief pad naar de printer", - "host": "Host- of IP-adres", + "host": "Host", "port": "Poort", "ssl": "Printer ondersteunt communicatie via SSL / TLS", - "verify_ssl": "Printer gebruikt een correct SSL-certificaat" + "verify_ssl": "SSL-certificaat verifi\u00ebren" }, "description": "Stel uw printer in via Internet Printing Protocol (IPP) om te integreren met Home Assistant.", "title": "Koppel uw printer" diff --git a/homeassistant/components/iqvia/translations/hu.json b/homeassistant/components/iqvia/translations/hu.json new file mode 100644 index 00000000000..f5301e874ea --- /dev/null +++ b/homeassistant/components/iqvia/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iqvia/translations/id.json b/homeassistant/components/iqvia/translations/id.json index a93f9aac26f..32f9b77135b 100644 --- a/homeassistant/components/iqvia/translations/id.json +++ b/homeassistant/components/iqvia/translations/id.json @@ -1,10 +1,18 @@ { "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "invalid_zip_code": "Kode pos tidak valid" + }, "step": { "user": { "data": { "zip_code": "Kode Pos" - } + }, + "description": "Isi kode pos AS atau Kanada.", + "title": "IQVIA" } } } diff --git a/homeassistant/components/islamic_prayer_times/translations/hu.json b/homeassistant/components/islamic_prayer_times/translations/hu.json new file mode 100644 index 00000000000..065747fb39d --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/islamic_prayer_times/translations/id.json b/homeassistant/components/islamic_prayer_times/translations/id.json new file mode 100644 index 00000000000..30eb3497847 --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/translations/id.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "user": { + "description": "Ingin menyiapkan Jadwal Sholat Islam?", + "title": "Siapkan Jadwal Sholat Islam" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "calculation_method": "Metode perhitungan waktu sholat" + } + } + } + }, + "title": "Jadwal Sholat Islami" +} \ No newline at end of file diff --git a/homeassistant/components/islamic_prayer_times/translations/ko.json b/homeassistant/components/islamic_prayer_times/translations/ko.json index 240ad6f57dc..aa0905b3612 100644 --- a/homeassistant/components/islamic_prayer_times/translations/ko.json +++ b/homeassistant/components/islamic_prayer_times/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "user": { diff --git a/homeassistant/components/isy994/translations/hu.json b/homeassistant/components/isy994/translations/hu.json index 6177c39b231..427a51157bf 100644 --- a/homeassistant/components/isy994/translations/hu.json +++ b/homeassistant/components/isy994/translations/hu.json @@ -6,15 +6,24 @@ "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", - "unknown": "V\u00e1ratlan hiba" + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, + "flow_title": "Universal Devices ISY994 {name} ({host})", "step": { "user": { "data": { + "host": "URL", "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } } } + }, + "options": { + "step": { + "init": { + "title": "ISY994 Be\u00e1ll\u00edt\u00e1sok" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/id.json b/homeassistant/components/isy994/translations/id.json new file mode 100644 index 00000000000..fec6d1090b0 --- /dev/null +++ b/homeassistant/components/isy994/translations/id.json @@ -0,0 +1,40 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "invalid_host": "Entri host tidak dalam format URL lengkap, misalnya, http://192.168.10.100:80", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Universal Devices ISY994 {name} ({host})", + "step": { + "user": { + "data": { + "host": "URL", + "password": "Kata Sandi", + "tls": "Versi TLS dari pengontrol ISY.", + "username": "Nama Pengguna" + }, + "description": "Entri host harus dalam format URL lengkap, misalnya, http://192.168.10.100:80", + "title": "Hubungkan ke ISY994 Anda" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ignore_string": "Abaikan String", + "restore_light_state": "Pulihkan Kecerahan Cahaya", + "sensor_string": "String Sensor Node", + "variable_sensor_string": "String Sensor Variabel" + }, + "description": "Mengatur opsi untuk Integrasi ISY: \n \u2022 String Sensor Node: Setiap perangkat atau folder yang berisi 'String Sensor Node' dalam nama akan diperlakukan sebagai sensor atau sensor biner. \n \u2022 Abaikan String: Setiap perangkat dengan 'Abaikan String' dalam nama akan diabaikan. \n \u2022 String Sensor Variabel: Variabel apa pun yang berisi 'String Sensor Variabel' akan ditambahkan sebagai sensor. \n \u2022 Pulihkan Kecerahan Cahaya: Jika diaktifkan, kecerahan sebelumnya akan dipulihkan saat menyalakan lampu alih-alih bawaan perangkat On-Level.", + "title": "Opsi ISY994" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/ko.json b/homeassistant/components/isy994/translations/ko.json index b8c80f1ee74..29829e066b9 100644 --- a/homeassistant/components/isy994/translations/ko.json +++ b/homeassistant/components/isy994/translations/ko.json @@ -19,7 +19,7 @@ "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, "description": "\ud638\uc2a4\ud2b8 \ud56d\ubaa9\uc740 \uc644\uc804\ud55c URL \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc608: http://192.168.10.100:80", - "title": "ISY994 \uc5d0 \uc5f0\uacb0\ud558\uae30" + "title": "ISY994\uc5d0 \uc5f0\uacb0\ud558\uae30" } } }, diff --git a/homeassistant/components/isy994/translations/nl.json b/homeassistant/components/isy994/translations/nl.json index d263815158a..9fed7b8c99c 100644 --- a/homeassistant/components/isy994/translations/nl.json +++ b/homeassistant/components/isy994/translations/nl.json @@ -29,7 +29,8 @@ "data": { "ignore_string": "Tekenreeks negeren", "restore_light_state": "Herstel lichthelderheid", - "sensor_string": "Node Sensor String" + "sensor_string": "Node Sensor String", + "variable_sensor_string": "Variabele sensor string" }, "description": "Stel de opties in voor de ISY-integratie:\n \u2022 Node Sensor String: elk apparaat of elke map die 'Node Sensor String' in de naam bevat, wordt behandeld als een sensor of binaire sensor.\n \u2022 Ignore String: elk apparaat met 'Ignore String' in de naam wordt genegeerd.\n \u2022 Variabele sensorreeks: elke variabele die 'Variabele sensorreeks' bevat, wordt als sensor toegevoegd.\n \u2022 Lichthelderheid herstellen: indien ingeschakeld, wordt de vorige helderheid hersteld wanneer u een lamp inschakelt in plaats van het ingebouwde Aan-niveau van het apparaat.", "title": "ISY994-opties" diff --git a/homeassistant/components/izone/translations/hu.json b/homeassistant/components/izone/translations/hu.json index 026093232a3..2d474986415 100644 --- a/homeassistant/components/izone/translations/hu.json +++ b/homeassistant/components/izone/translations/hu.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "step": { "confirm": { "description": "Szeretn\u00e9 be\u00e1ll\u00edtani az iZone-t?" diff --git a/homeassistant/components/izone/translations/id.json b/homeassistant/components/izone/translations/id.json new file mode 100644 index 00000000000..208b59dc9ac --- /dev/null +++ b/homeassistant/components/izone/translations/id.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "confirm": { + "description": "Ingin menyiapkan iZone?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/izone/translations/ko.json b/homeassistant/components/izone/translations/ko.json index b6eae170bec..d7173fc2db4 100644 --- a/homeassistant/components/izone/translations/ko.json +++ b/homeassistant/components/izone/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/juicenet/translations/de.json b/homeassistant/components/juicenet/translations/de.json index 7a6b5cff541..fbdea4c321f 100644 --- a/homeassistant/components/juicenet/translations/de.json +++ b/homeassistant/components/juicenet/translations/de.json @@ -13,7 +13,7 @@ "data": { "api_token": "API-Token" }, - "description": "Du ben\u00f6tigst das API-Token von https://home.juice.net/Manage.", + "description": "Sie ben\u00f6tigen das API-Token von https://home.juice.net/Manage.", "title": "Stelle eine Verbindung zu JuiceNet her" } } diff --git a/homeassistant/components/juicenet/translations/hu.json b/homeassistant/components/juicenet/translations/hu.json new file mode 100644 index 00000000000..f04a8c1e6ca --- /dev/null +++ b/homeassistant/components/juicenet/translations/hu.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "api_token": "API Token" + }, + "title": "Csatlakoz\u00e1s a JuiceNethez" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/juicenet/translations/id.json b/homeassistant/components/juicenet/translations/id.json new file mode 100644 index 00000000000..a150b7b7bbf --- /dev/null +++ b/homeassistant/components/juicenet/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_token": "Token API" + }, + "description": "Anda akan memerlukan Token API dari https://home.juice.net/Manage.", + "title": "Hubungkan ke JuiceNet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/juicenet/translations/ko.json b/homeassistant/components/juicenet/translations/ko.json index 1e1ae6aaa88..b38ddbd1f69 100644 --- a/homeassistant/components/juicenet/translations/ko.json +++ b/homeassistant/components/juicenet/translations/ko.json @@ -14,7 +14,7 @@ "api_token": "API \ud1a0\ud070" }, "description": "https://home.juice.net/Manage \uc758 API \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4.", - "title": "JuiceNet \uc5d0 \uc5f0\uacb0\ud558\uae30" + "title": "JuiceNet\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/keenetic_ndms2/translations/bg.json b/homeassistant/components/keenetic_ndms2/translations/bg.json new file mode 100644 index 00000000000..db122ec078b --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/bg.json @@ -0,0 +1,14 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "\u0418\u043c\u0435", + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/en.json b/homeassistant/components/keenetic_ndms2/translations/en.json index 5a946751ff4..e95f2f740ef 100644 --- a/homeassistant/components/keenetic_ndms2/translations/en.json +++ b/homeassistant/components/keenetic_ndms2/translations/en.json @@ -10,6 +10,7 @@ "user": { "data": { "host": "Host", + "name": "Name", "password": "Password", "port": "Port", "username": "Username" diff --git a/homeassistant/components/keenetic_ndms2/translations/hu.json b/homeassistant/components/keenetic_ndms2/translations/hu.json new file mode 100644 index 00000000000..72482de8604 --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/hu.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "name": "N\u00e9v", + "password": "Jelsz\u00f3", + "port": "Port", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/id.json b/homeassistant/components/keenetic_ndms2/translations/id.json new file mode 100644 index 00000000000..6a427a875a0 --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/id.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + }, + "title": "Siapkan Router NDMS2 Keenetik" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "consider_home": "Pertimbangkan interval rumah", + "include_arp": "Gunakan data ARP (diabaikan jika data hotspot digunakan)", + "include_associated": "Gunakan data asosiasi Wi-Fi AP (diabaikan jika data hotspot digunakan)", + "interfaces": "Pilih antarmuka untuk dipindai", + "scan_interval": "Interval pindai", + "try_hotspot": "Gunakan data 'ip hotspot' (paling akurat)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/ko.json b/homeassistant/components/keenetic_ndms2/translations/ko.json index 3281ddbe3d4..8968d9e7709 100644 --- a/homeassistant/components/keenetic_ndms2/translations/ko.json +++ b/homeassistant/components/keenetic_ndms2/translations/ko.json @@ -14,7 +14,8 @@ "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - } + }, + "title": "Keenetic NDMS2 \ub77c\uc6b0\ud130 \uc124\uc815\ud558\uae30" } } }, @@ -22,7 +23,12 @@ "step": { "user": { "data": { - "scan_interval": "\uc2a4\uce94 \uac04\uaca9" + "consider_home": "\uc7ac\uc2e4 \ucd94\uce21 \uac04\uaca9", + "include_arp": "ARP \ub370\uc774\ud130 \uc0ac\uc6a9\ud558\uae30 (\ud56b\uc2a4\ud31f \ub370\uc774\ud130\uac00 \uc0ac\uc6a9\ub418\ub294 \uacbd\uc6b0 \ubb34\uc2dc\ub428)", + "include_associated": "WiFi AP \uc5f0\uacb0 \ub370\uc774\ud130 \uc0ac\uc6a9\ud558\uae30 (\ud56b\uc2a4\ud31f \ub370\uc774\ud130\uac00 \uc0ac\uc6a9\ub418\ub294 \uacbd\uc6b0 \ubb34\uc2dc\ub428)", + "interfaces": "\uc2a4\uce94\ud560 \uc778\ud130\ud398\uc774\uc2a4\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694", + "scan_interval": "\uc2a4\uce94 \uac04\uaca9", + "try_hotspot": "'ip \ud56b\uc2a4\ud31f' \ub370\uc774\ud130 \uc0ac\uc6a9\ud558\uae30 (\uac00\uc7a5 \uc815\ud655\ud568)" } } } diff --git a/homeassistant/components/keenetic_ndms2/translations/nl.json b/homeassistant/components/keenetic_ndms2/translations/nl.json index c3c08575052..b7c89bb65e9 100644 --- a/homeassistant/components/keenetic_ndms2/translations/nl.json +++ b/homeassistant/components/keenetic_ndms2/translations/nl.json @@ -10,10 +10,12 @@ "user": { "data": { "host": "Host", + "name": "Naam", "password": "Wachtwoord", "port": "Poort", "username": "Gebruikersnaam" - } + }, + "title": "Keenetic NDMS2 Router instellen" } } }, @@ -21,8 +23,12 @@ "step": { "user": { "data": { + "consider_home": "Overweeg thuisinterval", + "include_arp": "Gebruik ARP-gegevens (genegeerd als hotspot-gegevens worden gebruikt)", + "include_associated": "Gebruik WiFi AP-koppelingsgegevens (genegeerd als hotspot-gegevens worden gebruikt)", "interfaces": "Kies interfaces om te scannen", - "scan_interval": "Scaninterval" + "scan_interval": "Scaninterval", + "try_hotspot": "Gebruik 'ip hotspot'-gegevens (meest nauwkeurig)" } } } diff --git a/homeassistant/components/kmtronic/translations/bg.json b/homeassistant/components/kmtronic/translations/bg.json new file mode 100644 index 00000000000..a84e1c3bfdf --- /dev/null +++ b/homeassistant/components/kmtronic/translations/bg.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/ca.json b/homeassistant/components/kmtronic/translations/ca.json index df8218bab3e..0847a86a41b 100644 --- a/homeassistant/components/kmtronic/translations/ca.json +++ b/homeassistant/components/kmtronic/translations/ca.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "L\u00f2gica de commutaci\u00f3 inversa (utilitza NC)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/en.json b/homeassistant/components/kmtronic/translations/en.json index 0a1bde9fb19..61da788028e 100644 --- a/homeassistant/components/kmtronic/translations/en.json +++ b/homeassistant/components/kmtronic/translations/en.json @@ -13,8 +13,7 @@ "data": { "host": "Host", "password": "Password", - "username": "Username", - "reverse": "Reverse switch logic (use NC)" + "username": "Username" } } } diff --git a/homeassistant/components/kmtronic/translations/es.json b/homeassistant/components/kmtronic/translations/es.json new file mode 100644 index 00000000000..4fb0fc0e0a5 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/es.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Fallo al conectar", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Usuario" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/et.json b/homeassistant/components/kmtronic/translations/et.json index 0c1715b4932..d501e1d1962 100644 --- a/homeassistant/components/kmtronic/translations/et.json +++ b/homeassistant/components/kmtronic/translations/et.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "P\u00f6\u00f6rdl\u00fcliti loogika (kasuta NC-d)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/fr.json b/homeassistant/components/kmtronic/translations/fr.json index 45620fe7795..7c0050bfad8 100644 --- a/homeassistant/components/kmtronic/translations/fr.json +++ b/homeassistant/components/kmtronic/translations/fr.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "Logique de commutation inverse (utiliser NC)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/hu.json b/homeassistant/components/kmtronic/translations/hu.json new file mode 100644 index 00000000000..0abcc301f0c --- /dev/null +++ b/homeassistant/components/kmtronic/translations/hu.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/id.json b/homeassistant/components/kmtronic/translations/id.json new file mode 100644 index 00000000000..ed8fde32106 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/it.json b/homeassistant/components/kmtronic/translations/it.json index e9356485e08..59aeaa25f6f 100644 --- a/homeassistant/components/kmtronic/translations/it.json +++ b/homeassistant/components/kmtronic/translations/it.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "Invertire la logica dell'interruttore (usare NC)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/ko.json b/homeassistant/components/kmtronic/translations/ko.json new file mode 100644 index 00000000000..cf62f34c755 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/ko.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "\uc2a4\uc704\uce58 \ubc29\uc2dd \ubcc0\uacbd (\uc0c1\uc2dc\ud3d0\ub85c(NC)\ub85c \uc0ac\uc6a9)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/nl.json b/homeassistant/components/kmtronic/translations/nl.json index 8ad15260b0d..9c32e48b20a 100644 --- a/homeassistant/components/kmtronic/translations/nl.json +++ b/homeassistant/components/kmtronic/translations/nl.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "Omgekeerde schakelaarlogica (gebruik NC)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/no.json b/homeassistant/components/kmtronic/translations/no.json index 249711bb912..1b192f7895e 100644 --- a/homeassistant/components/kmtronic/translations/no.json +++ b/homeassistant/components/kmtronic/translations/no.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "Omvendt bryterlogikk (bruk NC)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/pl.json b/homeassistant/components/kmtronic/translations/pl.json index 25dab56796c..6b0d06df11b 100644 --- a/homeassistant/components/kmtronic/translations/pl.json +++ b/homeassistant/components/kmtronic/translations/pl.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "Odwr\u00f3\u0107 logik\u0119 prze\u0142\u0105cznika (u\u017cyj NC)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/pt.json b/homeassistant/components/kmtronic/translations/pt.json new file mode 100644 index 00000000000..6ff15c6c8d7 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/pt.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Servidor", + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "Inverter l\u00f3gica do interruptor (usar NC)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/ru.json b/homeassistant/components/kmtronic/translations/ru.json index 9e0db9fcf94..7401068638d 100644 --- a/homeassistant/components/kmtronic/translations/ru.json +++ b/homeassistant/components/kmtronic/translations/ru.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "\u041b\u043e\u0433\u0438\u043a\u0430 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f (\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c NC)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/zh-Hant.json b/homeassistant/components/kmtronic/translations/zh-Hant.json index cad7d736a9d..5027bc2f5b2 100644 --- a/homeassistant/components/kmtronic/translations/zh-Hant.json +++ b/homeassistant/components/kmtronic/translations/zh-Hant.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "\u53cd\u5411\u958b\u95dc\u908f\u8f2f\uff08\u4f7f\u7528 NC\uff09" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/hu.json b/homeassistant/components/kodi/translations/hu.json index 3b2d79a34a7..64dbfac0c8b 100644 --- a/homeassistant/components/kodi/translations/hu.json +++ b/homeassistant/components/kodi/translations/hu.json @@ -1,7 +1,41 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "Kodi: {name}", + "step": { + "credentials": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "description": "Add meg a Kodi felhaszn\u00e1l\u00f3nevet \u00e9s jelsz\u00f3t. Ezek megtal\u00e1lhat\u00f3k a Rendszer/Be\u00e1ll\u00edt\u00e1sok/H\u00e1l\u00f3zat/Szolg\u00e1ltat\u00e1sok r\u00e9szben." + }, + "discovery_confirm": { + "description": "Szeretn\u00e9d hozz\u00e1adni a Kodi (`{name}`)-t a Home Assistant-hoz?", + "title": "Felfedezett Kodi" + }, + "user": { + "data": { + "host": "Hoszt", + "port": "Port", + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata" + } + }, + "ws_port": { + "data": { + "ws_port": "Port" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/id.json b/homeassistant/components/kodi/translations/id.json new file mode 100644 index 00000000000..1a81ab72fab --- /dev/null +++ b/homeassistant/components/kodi/translations/id.json @@ -0,0 +1,50 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "no_uuid": "Instans Kodi tidak memiliki ID yang unik. Ini kemungkinan besar karena versi Kodi lama (17.x atau sebelumnya). Anda dapat mengonfigurasi integrasi secara manual atau meningkatkan ke versi Kodi yang lebih baru.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Kodi: {name}", + "step": { + "credentials": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Masukkan nama pengguna dan kata sandi Kodi Anda. Ini dapat ditemukan di dalam Sistem/Setelan/Jaringan/Layanan." + }, + "discovery_confirm": { + "description": "Ingin menambahkan Kodi (`{name}`) to Home Assistant?", + "title": "Kodi yang ditemukan" + }, + "user": { + "data": { + "host": "Host", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL" + }, + "description": "Informasi koneksi Kodi. Pastikan untuk mengaktifkan \"Izinkan kontrol Kodi melalui HTTP\" di Sistem/Pengaturan/Jaringan/Layanan." + }, + "ws_port": { + "data": { + "ws_port": "Port" + }, + "description": "Port WebSocket (kadang-kadang disebut port TCP di Kodi). Untuk terhubung melalui WebSocket, Anda perlu mengaktifkan \"Izinkan program... untuk mengontrol Kodi\" dalam Sistem/Pengaturan/Jaringan/Layanan. Jika WebSocket tidak diaktifkan, hapus port dan biarkan kosong." + } + } + }, + "device_automation": { + "trigger_type": { + "turn_off": "{entity_name} diminta untuk dimatikan", + "turn_on": "{entity_name} diminta untuk dinyalakan" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/ko.json b/homeassistant/components/kodi/translations/ko.json index 233cd068a1e..3c5a1a38e96 100644 --- a/homeassistant/components/kodi/translations/ko.json +++ b/homeassistant/components/kodi/translations/ko.json @@ -4,6 +4,7 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "no_uuid": "Kodi \uc778\uc2a4\ud134\uc2a4\uc5d0 \uace0\uc720\ud55c ID\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774\ub294 \uc624\ub798\ub41c Kodi \ubc84\uc804(17.x \uc774\ud558) \ub54c\ubb38\uc77c \uac00\ub2a5\uc131\uc774 \ub192\uc2b5\ub2c8\ub2e4. \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc218\ub3d9\uc73c\ub85c \uad6c\uc131\ud558\uac70\ub098 \ucd5c\uc2e0 Kodi \ubc84\uc804\uc73c\ub85c \uc5c5\uadf8\ub808\uc774\ub4dc\ud574\ubcf4\uc138\uc694.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { @@ -18,11 +19,11 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "Kodi \uc0ac\uc6a9\uc790\uba85\uacfc \uc554\ud638\ub97c \uc785\ub825\ud558\uc2ed\uc2dc\uc624. \uc774\ub7ec\ud55c \ub0b4\uc6a9\uc740 \uc2dc\uc2a4\ud15c/\uc124\uc815/\ub124\ud2b8\uc6cc\ud06c/\uc11c\ube44\uc2a4\uc5d0\uc11c \ucc3e\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "description": "Kodi \uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc124\uc815/\uc11c\ube44\uc2a4/\ucee8\ud2b8\ub864/\uc6f9 \uc11c\ubc84\uc5d0\uc11c \ucc3e\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "discovery_confirm": { - "description": "Kodi (` {name} `)\ub97c Home Assistant\uc5d0 \ucd94\uac00 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Kodi \ubc1c\uacac" + "description": "Home Assistant\uc5d0 Kodi (`{name}`)\uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\ubc1c\uacac\ub41c Kodi" }, "user": { "data": { @@ -30,14 +31,20 @@ "port": "\ud3ec\ud2b8", "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9" }, - "description": "Kodi \uc5f0\uacb0 \uc815\ubcf4. \uc2dc\uc2a4\ud15c / \uc124\uc815 / \ub124\ud2b8\uc6cc\ud06c / \uc11c\ube44\uc2a4\uc5d0\uc11c \"HTTP\ub97c \ud1b5\ud55c Kodi \uc81c\uc5b4 \ud5c8\uc6a9\"\uc744 \ud65c\uc131\ud654\ud588\ub294\uc9c0 \ud655\uc778\ud558\uc2ed\uc2dc\uc624." + "description": "Kodi \uc5f0\uacb0 \uc815\ubcf4\uc785\ub2c8\ub2e4. \uc124\uc815/\uc11c\ube44\uc2a4/\ucee8\ud2b8\ub864/\uc6f9 \uc11c\ubc84\uc5d0\uc11c \"HTTP\ub97c \ud1b5\ud55c \uc6d0\uaca9 \uc81c\uc5b4 \ud5c8\uc6a9\"\uc744 \ud65c\uc131\ud654\ud588\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694." }, "ws_port": { "data": { "ws_port": "\ud3ec\ud2b8" }, - "description": "WebSocket \ud3ec\ud2b8 (Kodi\uc5d0\uc11c TCP \ud3ec\ud2b8\ub77c\uace0\ub3c4 \ud568). WebSocket\uc744 \ud1b5\ud574 \uc5f0\uacb0\ud558\ub824\uba74 \uc2dc\uc2a4\ud15c / \uc124\uc815 / \ub124\ud2b8\uc6cc\ud06c / \uc11c\ube44\uc2a4\uc5d0\uc11c \"\ud504\ub85c\uadf8\ub7a8\uc774 Kodi\ub97c \uc81c\uc5b4\ud558\ub3c4\ub85d \ud5c8\uc6a9\"\uc744 \ud65c\uc131\ud654\ud574\uc57c\ud569\ub2c8\ub2e4. WebSocket\uc774 \ud65c\uc131\ud654\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \ud3ec\ud2b8\ub97c \uc81c\uac70\ud558\uace0 \ube44\uc6cc \ub461\ub2c8\ub2e4." + "description": "\uc6f9 \uc18c\ucf13 \ud3ec\ud2b8(Kodi\uc5d0\uc11c\ub294 \ud3ec\ud2b8\ub77c\uace0\ub3c4 \ud568)\uc785\ub2c8\ub2e4. \uc6f9 \uc18c\ucf13\uc744 \ud1b5\ud574 \uc5f0\uacb0\ud558\ub824\uba74 \uc124\uc815/\uc11c\ube44\uc2a4/\ucee8\ud2b8\ub864/\uc560\ud50c\ub9ac\ucf00\uc774\uc158 \ucee8\ud2b8\ub864\uc5d0\uc11c \"\uc774 \uc2dc\uc2a4\ud15c\uc758/\ub2e4\ub978 \uc2dc\uc2a4\ud15c\uc758 \ud504\ub85c\uadf8\ub7a8\uc5d0 \uc758\ud55c \uc6d0\uaca9 \uc81c\uc5b4 \ud5c8\uc6a9\"\uc744 \ud65c\uc131\ud654\ud574\uc57c \ud569\ub2c8\ub2e4. \uc6f9 \uc18c\ucf13\uc744 \uc0ac\uc6a9\ud560 \uc218 \uc5c6\ub294 \uacbd\uc6b0 \ud3ec\ud2b8\ub97c \uc81c\uac70\ud558\uace0 \ube44\uc6cc\ub450\uc138\uc694." } } + }, + "device_automation": { + "trigger_type": { + "turn_off": "{entity_name}\uc774(\uac00) \uaebc\uc9c0\ub3c4\ub85d \uc694\uccad\ub418\uc5c8\uc744 \ub54c", + "turn_on": "{entity_name}\uc774(\uac00) \ucf1c\uc9c0\ub3c4\ub85d \uc694\uccad\ub418\uc5c8\uc744 \ub54c" + } } } \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/nl.json b/homeassistant/components/kodi/translations/nl.json index 57476791b8f..95d8ea995d5 100644 --- a/homeassistant/components/kodi/translations/nl.json +++ b/homeassistant/components/kodi/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd", "cannot_connect": "Kon niet verbinden", "invalid_auth": "Ongeldige authenticatie", + "no_uuid": "Kodi-instantie heeft geen unieke ID. Dit komt waarschijnlijk door een oude Kodi-versie (17.x of lager). U kunt de integratie handmatig configureren of upgraden naar een recentere Kodi-versie.", "unknown": "Onverwachte fout" }, "error": { diff --git a/homeassistant/components/konnected/translations/hu.json b/homeassistant/components/konnected/translations/hu.json index 1cc44a02646..507e5d258f2 100644 --- a/homeassistant/components/konnected/translations/hu.json +++ b/homeassistant/components/konnected/translations/hu.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, "step": { "user": { "data": { @@ -11,6 +19,11 @@ }, "options": { "step": { + "options_binary": { + "data": { + "name": "N\u00e9v (nem k\u00f6telez\u0151)" + } + }, "options_digital": { "data": { "name": "N\u00e9v (nem k\u00f6telez\u0151)", diff --git a/homeassistant/components/konnected/translations/id.json b/homeassistant/components/konnected/translations/id.json new file mode 100644 index 00000000000..633e6bba2df --- /dev/null +++ b/homeassistant/components/konnected/translations/id.json @@ -0,0 +1,108 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "not_konn_panel": "Bukan perangkat Konnected.io yang dikenali", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "confirm": { + "description": "Model: {model}\nID: {id}\nHost: {host}\nPort: {port}\n\nAnda dapat mengonfigurasi IO dan perilaku panel di pengaturan Panel Alarm Konnected.", + "title": "Perangkat Konnected Siap" + }, + "import_confirm": { + "description": "Panel Alarm Konnected dengan ID {id} telah ditemukan di configuration.yaml. Aliran ini akan memungkinkan Anda mengimpornya menjadi entri konfigurasi.", + "title": "Impor Perangkat Konnected" + }, + "user": { + "data": { + "host": "Alamat IP", + "port": "Port" + }, + "description": "Masukkan informasi host untuk Panel Konnected Anda." + } + } + }, + "options": { + "abort": { + "not_konn_panel": "Bukan perangkat Konnected.io yang dikenali" + }, + "error": { + "bad_host": "URL host API yang Menimpa Tidak Valid" + }, + "step": { + "options_binary": { + "data": { + "inverse": "Balikkan status buka/tutup", + "name": "Nama (opsional)", + "type": "Jenis Sensor Biner" + }, + "description": "Opsi {zone}", + "title": "Konfigurasikan Sensor Biner" + }, + "options_digital": { + "data": { + "name": "Nama (opsional)", + "poll_interval": "Interval Polling (dalam menit) (opsional)", + "type": "Jenis Sensor" + }, + "description": "Opsi {zone}", + "title": "Konfigurasi Sensor Digital" + }, + "options_io": { + "data": { + "1": "Zona 1", + "2": "Zona 2", + "3": "Zona 3", + "4": "Zona 4", + "5": "Zona 5", + "6": "Zona 6", + "7": "Zona 7", + "out": "OUT" + }, + "description": "Ditemukan {model} di {host}. Pilih konfigurasi dasar setiap I/O di bawah ini - tergantung pada I/O yang mungkin memungkinkan sensor biner (kontak terbuka/tutup), sensor digital (dht dan ds18b20), atau sakelar output. Anda dapat mengonfigurasi opsi terperinci dalam langkah berikutnya.", + "title": "Konfigurasikan I/O" + }, + "options_io_ext": { + "data": { + "10": "Zona 10", + "11": "Zona 11", + "12": "Zona 12", + "8": "Zona 8", + "9": "Zona 9", + "alarm1": "ALARM1", + "alarm2_out2": "OUT2/ALARM2", + "out1": "OUT1" + }, + "description": "Pilih konfigurasi I/O lainnya di bawah ini. Anda dapat mengonfigurasi detail opsi pada langkah berikutnya.", + "title": "Konfigurasikan I/O yang Diperluas" + }, + "options_misc": { + "data": { + "api_host": "Ganti URL host API (opsional)", + "blink": "Kedipkan panel LED saat mengirim perubahan status", + "discovery": "Tanggapi permintaan penemuan di jaringan Anda", + "override_api_host": "Timpa URL panel host API Home Assistant bawaan" + }, + "description": "Pilih perilaku yang diinginkan untuk panel Anda", + "title": "Konfigurasikan Lainnya" + }, + "options_switch": { + "data": { + "activation": "Keluaran saat nyala", + "momentary": "Durasi pulsa (milidetik) (opsional)", + "more_states": "Konfigurasikan status tambahan untuk zona ini", + "name": "Nama (opsional)", + "pause": "Jeda di antara pulsa (milidetik) (opsional)", + "repeat": "Waktu pengulangan (-1 = tak terbatas) (opsional)" + }, + "description": "Opsi {zone}: status {state}", + "title": "Konfigurasikan Output yang Dapat Dialihkan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/konnected/translations/ko.json b/homeassistant/components/konnected/translations/ko.json index fe5b9a0347a..eb7fa3de4e0 100644 --- a/homeassistant/components/konnected/translations/ko.json +++ b/homeassistant/components/konnected/translations/ko.json @@ -15,7 +15,7 @@ "title": "Konnected \uae30\uae30 \uc900\ube44" }, "import_confirm": { - "description": "Konnected \uc54c\ub78c \ud328\ub110 ID {id} \uac00 configuration.yaml \uc5d0\uc11c \ubc1c\uacac\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc774 \uacfc\uc815\uc744 \ud1b5\ud574 \uad6c\uc131 \ud56d\ubaa9\uc73c\ub85c \uac00\uc838\uc62c \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "Konnected \uc54c\ub78c \ud328\ub110 ID {id}\uac00 configuration.yaml\uc5d0\uc11c \ubc1c\uacac\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc774 \uacfc\uc815\uc744 \ud1b5\ud574 \uad6c\uc131 \ud56d\ubaa9\uc73c\ub85c \uac00\uc838\uc62c \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "Konnected \uae30\uae30 \uac00\uc838\uc624\uae30" }, "user": { @@ -85,7 +85,7 @@ "data": { "api_host": "API \ud638\uc2a4\ud2b8 URL \uc7ac\uc815\uc758 (\uc120\ud0dd \uc0ac\ud56d)", "blink": "\uc0c1\ud0dc \ubcc0\uacbd\uc744 \ubcf4\ub0bc \ub54c \uae5c\ubc15\uc784 \ud328\ub110 LED \ub97c \ucf2d\ub2c8\ub2e4", - "discovery": "\ub124\ud2b8\uc6cc\ud06c\uc758 \uac80\uc0c9 \uc694\uccad\uc5d0 \uc751\ub2f5", + "discovery": "\ub124\ud2b8\uc6cc\ud06c\uc758 \uac80\uc0c9 \uc694\uccad\uc5d0 \uc751\ub2f5\ud558\uae30", "override_api_host": "\uae30\ubcf8 Home Assistant API \ud638\uc2a4\ud2b8 \ud328\ub110 URL \uc7ac\uc815\uc758" }, "description": "\ud328\ub110\uc5d0 \uc6d0\ud558\ub294 \ub3d9\uc791\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694", diff --git a/homeassistant/components/konnected/translations/nl.json b/homeassistant/components/konnected/translations/nl.json index 9a7f20ac1e1..75b11338016 100644 --- a/homeassistant/components/konnected/translations/nl.json +++ b/homeassistant/components/konnected/translations/nl.json @@ -48,7 +48,7 @@ }, "options_digital": { "data": { - "name": "Naam (optioneel)", + "name": "Naam (optional)", "poll_interval": "Poll interval (minuten) (optioneel)", "type": "Type sensor" }, diff --git a/homeassistant/components/kulersky/translations/de.json b/homeassistant/components/kulersky/translations/de.json index 96ed09a974f..86bc8e36730 100644 --- a/homeassistant/components/kulersky/translations/de.json +++ b/homeassistant/components/kulersky/translations/de.json @@ -6,7 +6,7 @@ }, "step": { "confirm": { - "description": "M\u00f6chtest du mit der Einrichtung beginnen?" + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?" } } } diff --git a/homeassistant/components/kulersky/translations/hu.json b/homeassistant/components/kulersky/translations/hu.json index 3d5be90042e..6c61530acbe 100644 --- a/homeassistant/components/kulersky/translations/hu.json +++ b/homeassistant/components/kulersky/translations/hu.json @@ -1,8 +1,12 @@ { "config": { + "abort": { + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "step": { "confirm": { - "description": "El akarod kezdeni a be\u00e1ll\u00edt\u00e1st?" + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?" } } } diff --git a/homeassistant/components/kulersky/translations/id.json b/homeassistant/components/kulersky/translations/id.json new file mode 100644 index 00000000000..223836a8b40 --- /dev/null +++ b/homeassistant/components/kulersky/translations/id.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "confirm": { + "description": "Ingin memulai penyiapan?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kulersky/translations/ko.json b/homeassistant/components/kulersky/translations/ko.json index 7011a61f757..e5ae04d6e5c 100644 --- a/homeassistant/components/kulersky/translations/ko.json +++ b/homeassistant/components/kulersky/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/life360/translations/he.json b/homeassistant/components/life360/translations/he.json new file mode 100644 index 00000000000..3007c0e968c --- /dev/null +++ b/homeassistant/components/life360/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/life360/translations/hu.json b/homeassistant/components/life360/translations/hu.json index 086e3ebf7d2..603efee6d9d 100644 --- a/homeassistant/components/life360/translations/hu.json +++ b/homeassistant/components/life360/translations/hu.json @@ -1,15 +1,17 @@ { "config": { "abort": { - "unknown": "V\u00e1ratlan hiba" + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "create_entry": { - "default": "A speci\u00e1lis be\u00e1ll\u00edt\u00e1sok megad\u00e1s\u00e1hoz l\u00e1sd: [Life360 dokument\u00e1ci\u00f3] ( {docs_url} )." + "default": "A speci\u00e1lis be\u00e1ll\u00edt\u00e1sok megad\u00e1s\u00e1hoz l\u00e1sd: [Life360 dokument\u00e1ci\u00f3]({docs_url})." }, "error": { "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "invalid_username": "\u00c9rv\u00e9nytelen felhaszn\u00e1l\u00f3n\u00e9v", - "unknown": "V\u00e1ratlan hiba" + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "user": { diff --git a/homeassistant/components/life360/translations/id.json b/homeassistant/components/life360/translations/id.json index 2bb7a1cca68..21a93366c44 100644 --- a/homeassistant/components/life360/translations/id.json +++ b/homeassistant/components/life360/translations/id.json @@ -1,7 +1,27 @@ { "config": { + "abort": { + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "create_entry": { + "default": "Untuk mengatur opsi tingkat lanjut, baca [dokumentasi Life360]({docs_url})." + }, "error": { - "invalid_username": "Nama pengguna tidak valid" + "already_configured": "Akun sudah dikonfigurasi", + "invalid_auth": "Autentikasi tidak valid", + "invalid_username": "Nama pengguna tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Untuk mengatur opsi tingkat lanjut, baca [dokumentasi Life360]({docs_url}).\nAnda mungkin ingin melakukannya sebelum menambahkan akun.", + "title": "Info Akun Life360" + } } } } \ No newline at end of file diff --git a/homeassistant/components/lifx/translations/hu.json b/homeassistant/components/lifx/translations/hu.json index 0b6cdb39fd4..f706dcefa96 100644 --- a/homeassistant/components/lifx/translations/hu.json +++ b/homeassistant/components/lifx/translations/hu.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Nem tal\u00e1lhat\u00f3k LIFX eszk\u00f6z\u00f6k a h\u00e1l\u00f3zaton.", - "single_instance_allowed": "Csak egyetlen LIFX konfigur\u00e1ci\u00f3 lehets\u00e9ges." + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "step": { "confirm": { diff --git a/homeassistant/components/lifx/translations/id.json b/homeassistant/components/lifx/translations/id.json new file mode 100644 index 00000000000..03b2b387c6f --- /dev/null +++ b/homeassistant/components/lifx/translations/id.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "confirm": { + "description": "Ingin menyiapkan LIFX?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lifx/translations/ko.json b/homeassistant/components/lifx/translations/ko.json index 34bec9c3aee..4d388cbeda2 100644 --- a/homeassistant/components/lifx/translations/ko.json +++ b/homeassistant/components/lifx/translations/ko.json @@ -2,11 +2,11 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { - "description": "LIFX \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "LIFX\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/lifx/translations/nl.json b/homeassistant/components/lifx/translations/nl.json index 60efcdffa46..0e0a6190f0d 100644 --- a/homeassistant/components/lifx/translations/nl.json +++ b/homeassistant/components/lifx/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Geen LIFX-apparaten gevonden op het netwerk.", - "single_instance_allowed": "Slechts een enkele configuratie van LIFX is mogelijk." + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "step": { "confirm": { diff --git a/homeassistant/components/light/translations/id.json b/homeassistant/components/light/translations/id.json index bc2ba732df2..25c636ac1c7 100644 --- a/homeassistant/components/light/translations/id.json +++ b/homeassistant/components/light/translations/id.json @@ -1,8 +1,26 @@ { + "device_automation": { + "action_type": { + "brightness_decrease": "Kurangi kecerahan {entity_name}", + "brightness_increase": "Tingkatkan kecerahan {entity_name}", + "flash": "Lampu kilat {entity_name}", + "toggle": "Nyala/matikan {entity_name}", + "turn_off": "Matikan {entity_name}", + "turn_on": "Nyalakan {entity_name}" + }, + "condition_type": { + "is_off": "{entity_name} mati", + "is_on": "{entity_name} nyala" + }, + "trigger_type": { + "turned_off": "{entity_name} dimatikan", + "turned_on": "{entity_name} dinyalakan" + } + }, "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" } }, "title": "Lampu" diff --git a/homeassistant/components/light/translations/ko.json b/homeassistant/components/light/translations/ko.json index 33afda2d06d..5413ecd567e 100644 --- a/homeassistant/components/light/translations/ko.json +++ b/homeassistant/components/light/translations/ko.json @@ -1,20 +1,20 @@ { "device_automation": { "action_type": { - "brightness_decrease": "{entity_name} \uc744(\ub97c) \uc5b4\ub461\uac8c \ud558\uae30", - "brightness_increase": "{entity_name} \uc744(\ub97c) \ubc1d\uac8c \ud558\uae30", - "flash": "{entity_name} \ud50c\ub798\uc2dc", - "toggle": "{entity_name} \ud1a0\uae00", - "turn_off": "{entity_name} \ub044\uae30", - "turn_on": "{entity_name} \ucf1c\uae30" + "brightness_decrease": "{entity_name}\uc744(\ub97c) \uc5b4\ub461\uac8c \ud558\uae30", + "brightness_increase": "{entity_name}\uc744(\ub97c) \ubc1d\uac8c \ud558\uae30", + "flash": "{entity_name}\uc744(\ub97c) \uae5c\ube61\uc774\uae30", + "toggle": "{entity_name}\uc744(\ub97c) \ud1a0\uae00\ud558\uae30", + "turn_off": "{entity_name}\uc744(\ub97c) \ub044\uae30", + "turn_on": "{entity_name}\uc744(\ub97c) \ucf1c\uae30" }, "condition_type": { - "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", - "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" + "is_off": "{entity_name}\uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name}\uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" }, "trigger_type": { - "turned_off": "{entity_name} \uc774(\uac00) \uaebc\uc9c8 \ub54c", - "turned_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c8 \ub54c" + "turned_off": "{entity_name}\uc774(\uac00) \uaebc\uc84c\uc744 \ub54c", + "turned_on": "{entity_name}\uc774(\uac00) \ucf1c\uc84c\uc744 \ub54c" } }, "state": { diff --git a/homeassistant/components/litejet/translations/bg.json b/homeassistant/components/litejet/translations/bg.json new file mode 100644 index 00000000000..4983c9a14b2 --- /dev/null +++ b/homeassistant/components/litejet/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "\u041f\u043e\u0440\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/hu.json b/homeassistant/components/litejet/translations/hu.json new file mode 100644 index 00000000000..6d895624c30 --- /dev/null +++ b/homeassistant/components/litejet/translations/hu.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "title": "Csatlakoz\u00e1s a LiteJet-hez" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/id.json b/homeassistant/components/litejet/translations/id.json new file mode 100644 index 00000000000..690692ca4cc --- /dev/null +++ b/homeassistant/components/litejet/translations/id.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "open_failed": "Tidak dapat membuka port serial yang ditentukan." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "description": "Hubungkan port RS232-2 LiteJet ke komputer Anda dan masukkan jalur ke perangkat port serial.\n\nLiteJet MCP harus dikonfigurasi untuk baud 19,2 K, bit data 8,bit stop 1, tanpa paritas, dan untuk mengirimkan 'CR' setelah setiap respons.", + "title": "Hubungkan ke LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/ko.json b/homeassistant/components/litejet/translations/ko.json new file mode 100644 index 00000000000..829918dc557 --- /dev/null +++ b/homeassistant/components/litejet/translations/ko.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + }, + "error": { + "open_failed": "\uc9c0\uc815\ud55c \uc2dc\ub9ac\uc5bc \ud3ec\ud2b8\ub97c \uc5f4 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." + }, + "step": { + "user": { + "data": { + "port": "\ud3ec\ud2b8" + }, + "description": "LiteJet\uc758 RS232-2 \ud3ec\ud2b8\ub97c \ucef4\ud4e8\ud130\uc5d0 \uc5f0\uacb0\ud558\uace0 \uc2dc\ub9ac\uc5bc \ud3ec\ud2b8 \uc7a5\uce58\uc758 \uacbd\ub85c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\nLiteJet MCP\ub294 19.2 K \uc804\uc1a1\uc18d\ub3c4, 8 \ub370\uc774\ud130 \ube44\ud2b8, 1 \uc815\uc9c0 \ube44\ud2b8, \ud328\ub9ac\ud2f0 \uc5c6\uc74c\uc73c\ub85c \uad6c\uc131\ub418\uc5b4\uc57c \ud558\uba70 \uac01 \uc751\ub2f5 \ud6c4\uc5d0 'CR'\uc744 \uc804\uc1a1\ud574\uc57c \ud569\ub2c8\ub2e4.", + "title": "Litejet\uc5d0 \uc5f0\uacb0\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/nl.json b/homeassistant/components/litejet/translations/nl.json index f16f25a3987..a96f8de6171 100644 --- a/homeassistant/components/litejet/translations/nl.json +++ b/homeassistant/components/litejet/translations/nl.json @@ -11,6 +11,7 @@ "data": { "port": "Poort" }, + "description": "Verbind de RS232-2 poort van de LiteJet met uw computer en voer het pad naar het seri\u00eble poortapparaat in.\n\nDe LiteJet MCP moet worden geconfigureerd voor 19,2 K baud, 8 databits, 1 stopbit, geen pariteit, en om een 'CR' uit te zenden na elk antwoord.", "title": "Maak verbinding met LiteJet" } } diff --git a/homeassistant/components/litejet/translations/pt.json b/homeassistant/components/litejet/translations/pt.json new file mode 100644 index 00000000000..8b09f9a245f --- /dev/null +++ b/homeassistant/components/litejet/translations/pt.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma \u00fanica configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "user": { + "data": { + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/bg.json b/homeassistant/components/litterrobot/translations/bg.json new file mode 100644 index 00000000000..67a484573aa --- /dev/null +++ b/homeassistant/components/litterrobot/translations/bg.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/es.json b/homeassistant/components/litterrobot/translations/es.json new file mode 100644 index 00000000000..12a48f17c32 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/es.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Fallo al conectar", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Usuario" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/hu.json b/homeassistant/components/litterrobot/translations/hu.json new file mode 100644 index 00000000000..fd8db27da5e --- /dev/null +++ b/homeassistant/components/litterrobot/translations/hu.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/id.json b/homeassistant/components/litterrobot/translations/id.json new file mode 100644 index 00000000000..4a84db42a14 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/ko.json b/homeassistant/components/litterrobot/translations/ko.json new file mode 100644 index 00000000000..94261de9637 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/ko.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/pt.json b/homeassistant/components/litterrobot/translations/pt.json new file mode 100644 index 00000000000..565b9f6c0e8 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/pt.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/local_ip/translations/de.json b/homeassistant/components/local_ip/translations/de.json index 9e2a6eda5c6..2d31f90139d 100644 --- a/homeassistant/components/local_ip/translations/de.json +++ b/homeassistant/components/local_ip/translations/de.json @@ -8,7 +8,7 @@ "data": { "name": "Sensorname" }, - "description": "M\u00f6chtest du mit der Einrichtung beginnen?", + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?", "title": "Lokale IP-Adresse" } } diff --git a/homeassistant/components/local_ip/translations/hu.json b/homeassistant/components/local_ip/translations/hu.json index 09b894742a6..b692e87f922 100644 --- a/homeassistant/components/local_ip/translations/hu.json +++ b/homeassistant/components/local_ip/translations/hu.json @@ -1,10 +1,14 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "step": { "user": { "data": { "name": "\u00c9rz\u00e9kel\u0151 neve" }, + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?", "title": "Helyi IP c\u00edm" } } diff --git a/homeassistant/components/local_ip/translations/id.json b/homeassistant/components/local_ip/translations/id.json new file mode 100644 index 00000000000..a7d8993baa6 --- /dev/null +++ b/homeassistant/components/local_ip/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "user": { + "data": { + "name": "Nama Sensor" + }, + "description": "Ingin memulai penyiapan?", + "title": "Alamat IP Lokal" + } + } + }, + "title": "Alamat IP Lokal" +} \ No newline at end of file diff --git a/homeassistant/components/local_ip/translations/ko.json b/homeassistant/components/local_ip/translations/ko.json index 3b543f87f79..f7248ab0f6f 100644 --- a/homeassistant/components/local_ip/translations/ko.json +++ b/homeassistant/components/local_ip/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "user": { diff --git a/homeassistant/components/local_ip/translations/nl.json b/homeassistant/components/local_ip/translations/nl.json index 57547adedd8..b8b033d2e73 100644 --- a/homeassistant/components/local_ip/translations/nl.json +++ b/homeassistant/components/local_ip/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Er is slechts \u00e9\u00e9n configuratie van lokaal IP toegestaan." + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "step": { "user": { diff --git a/homeassistant/components/locative/translations/de.json b/homeassistant/components/locative/translations/de.json index a6dcf4150d0..2df9f889e85 100644 --- a/homeassistant/components/locative/translations/de.json +++ b/homeassistant/components/locative/translations/de.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "M\u00f6chtest du mit der Einrichtung beginnen?", + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?", "title": "Locative Webhook einrichten" } } diff --git a/homeassistant/components/locative/translations/hu.json b/homeassistant/components/locative/translations/hu.json index 983ffacfa7d..8dc03e9c37a 100644 --- a/homeassistant/components/locative/translations/hu.json +++ b/homeassistant/components/locative/translations/hu.json @@ -1,11 +1,15 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "webhook_not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." + }, "create_entry": { - "default": "Ha helyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, be kell \u00e1ll\u00edtania a webhook funkci\u00f3t a Locative alkalmaz\u00e1sban. \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: \" {webhook_url} \"\n - M\u00f3dszer: POST \n\n Tov\u00e1bbi r\u00e9szletek\u00e9rt l\u00e1sd a [dokument\u00e1ci\u00f3t] ( {docs_url} )." + "default": "Ha helyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, be kell \u00e1ll\u00edtania a webhook funkci\u00f3t a Locative alkalmaz\u00e1sban. \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: `{webhook_url}`\n - M\u00f3dszer: POST \n\n Tov\u00e1bbi r\u00e9szletek\u00e9rt l\u00e1sd a [dokument\u00e1ci\u00f3t]({docs_url})." }, "step": { "user": { - "description": "Biztosan be szeretn\u00e9d \u00e1ll\u00edtani a Locative Webhook-ot?", + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?", "title": "Locative Webhook be\u00e1ll\u00edt\u00e1sa" } } diff --git a/homeassistant/components/locative/translations/id.json b/homeassistant/components/locative/translations/id.json new file mode 100644 index 00000000000..71aea5cc63c --- /dev/null +++ b/homeassistant/components/locative/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." + }, + "create_entry": { + "default": "Untuk mengirim lokasi ke Home Assistant, Anda harus mengatur fitur webhook di aplikasi Locative.\n\nIsi info berikut:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nBaca [dokumentasi]({docs_url}) untuk detail lebih lanjut." + }, + "step": { + "user": { + "description": "Ingin memulai penyiapan?", + "title": "Siapkan Locative Webhook" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/translations/ko.json b/homeassistant/components/locative/translations/ko.json index 5930e7edf1b..da063335ff8 100644 --- a/homeassistant/components/locative/translations/ko.json +++ b/homeassistant/components/locative/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Locative \uc571\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Locative\uc571\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/locative/translations/nl.json b/homeassistant/components/locative/translations/nl.json index 16cbbc77277..d66a1262b5d 100644 --- a/homeassistant/components/locative/translations/nl.json +++ b/homeassistant/components/locative/translations/nl.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "Weet u zeker dat u de Locative Webhook wilt instellen?", + "description": "Wil je beginnen met instellen?", "title": "Stel de Locative Webhook in" } } diff --git a/homeassistant/components/lock/translations/id.json b/homeassistant/components/lock/translations/id.json index da11e3422f1..d8778868651 100644 --- a/homeassistant/components/lock/translations/id.json +++ b/homeassistant/components/lock/translations/id.json @@ -1,8 +1,23 @@ { + "device_automation": { + "action_type": { + "lock": "Kunci {entity_name}", + "open": "Buka {entity_name}", + "unlock": "Buka kunci {entity_name}" + }, + "condition_type": { + "is_locked": "{entity_name} terkunci", + "is_unlocked": "{entity_name} tidak terkunci" + }, + "trigger_type": { + "locked": "{entity_name} terkunci", + "unlocked": "{entity_name} tidak terkunci" + } + }, "state": { "_": { "locked": "Terkunci", - "unlocked": "Terbuka" + "unlocked": "Tidak Terkunci" } }, "title": "Kunci" diff --git a/homeassistant/components/lock/translations/ko.json b/homeassistant/components/lock/translations/ko.json index 6f04e2b4110..f89a1d1b54f 100644 --- a/homeassistant/components/lock/translations/ko.json +++ b/homeassistant/components/lock/translations/ko.json @@ -1,17 +1,17 @@ { "device_automation": { "action_type": { - "lock": "{entity_name} \uc7a0\uae08", - "open": "{entity_name} \uc5f4\uae30", - "unlock": "{entity_name} \uc7a0\uae08 \ud574\uc81c" + "lock": "{entity_name}\uc744(\ub97c) \uc7a0\uadf8\uae30", + "open": "{entity_name}\uc744(\ub97c) \uc5f4\uae30", + "unlock": "{entity_name}\uc744(\ub97c) \uc7a0\uae08 \ud574\uc81c\ud558\uae30" }, "condition_type": { - "is_locked": "{entity_name} \uc774(\uac00) \uc7a0\uaca8\uc788\uc73c\uba74", - "is_unlocked": "{entity_name} \uc774(\uac00) \uc7a0\uaca8\uc788\uc9c0 \uc54a\uc73c\uba74" + "is_locked": "{entity_name}\uc774(\uac00) \uc7a0\uaca8\uc788\uc73c\uba74", + "is_unlocked": "{entity_name}\uc774(\uac00) \uc7a0\uaca8\uc788\uc9c0 \uc54a\uc73c\uba74" }, "trigger_type": { - "locked": "{entity_name} \uc774(\uac00) \uc7a0\uae38 \ub54c", - "unlocked": "{entity_name} \uc774(\uac00) \uc7a0\uae08\uc774 \ud574\uc81c\ub420 \ub54c" + "locked": "{entity_name}\uc774(\uac00) \uc7a0\uacbc\uc744 \ub54c", + "unlocked": "{entity_name}\uc774(\uac00) \uc7a0\uae08\uc774 \ud574\uc81c\ub418\uc5c8\uc744 \ub54c" } }, "state": { diff --git a/homeassistant/components/lock/translations/zh-Hans.json b/homeassistant/components/lock/translations/zh-Hans.json index 4999c52f8e0..7e2a7719718 100644 --- a/homeassistant/components/lock/translations/zh-Hans.json +++ b/homeassistant/components/lock/translations/zh-Hans.json @@ -1,13 +1,13 @@ { "device_automation": { "action_type": { - "lock": "\u4e0a\u9501{entity_name}", - "open": "\u5f00\u542f{entity_name}", - "unlock": "\u89e3\u9501{entity_name}" + "lock": "\u9501\u5b9a {entity_name}", + "open": "\u5f00\u542f {entity_name}", + "unlock": "\u89e3\u9501 {entity_name}" }, "condition_type": { - "is_locked": "{entity_name}\u5df2\u4e0a\u9501", - "is_unlocked": "{entity_name}\u5df2\u89e3\u9501" + "is_locked": "{entity_name} \u5df2\u9501\u5b9a", + "is_unlocked": "{entity_name} \u5df2\u89e3\u9501" }, "trigger_type": { "locked": "{entity_name} \u88ab\u9501\u5b9a", diff --git a/homeassistant/components/logi_circle/translations/hu.json b/homeassistant/components/logi_circle/translations/hu.json index 04c8229f5ff..9c788350de4 100644 --- a/homeassistant/components/logi_circle/translations/hu.json +++ b/homeassistant/components/logi_circle/translations/hu.json @@ -1,7 +1,13 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t." + }, "error": { - "follow_link": "K\u00e9rlek, k\u00f6vesd a hivatkoz\u00e1st \u00e9s hiteles\u00edtsd magad miel\u0151tt megnyomod a K\u00fcld\u00e9s gombot" + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "follow_link": "K\u00e9rlek, k\u00f6vesd a hivatkoz\u00e1st \u00e9s hiteles\u00edtsd magad miel\u0151tt megnyomod a K\u00fcld\u00e9s gombot", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, "step": { "user": { diff --git a/homeassistant/components/logi_circle/translations/id.json b/homeassistant/components/logi_circle/translations/id.json new file mode 100644 index 00000000000..3cbfdd03978 --- /dev/null +++ b/homeassistant/components/logi_circle/translations/id.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "external_error": "Eksepsi terjadi dari alur lain.", + "external_setup": "Logi Circle berhasil dikonfigurasi dari alur lain.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi." + }, + "error": { + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "follow_link": "Ikuti tautan dan autentikasi sebelum menekan Kirim.", + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "auth": { + "description": "Buka tautan di bawah ini dan **Terima** akses ke akun Logi Circle Anda, lalu kembali dan tekan **Kirim** di bawah ini.\n\n[Tautan] ({authorization_url})", + "title": "Autentikasi dengan Logi Circle" + }, + "user": { + "data": { + "flow_impl": "Penyedia" + }, + "description": "Pilih melalui penyedia autentikasi mana yang ingin Anda autentikasi dengan Logi Circle.", + "title": "Penyedia Autentikasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/logi_circle/translations/ko.json b/homeassistant/components/logi_circle/translations/ko.json index 2300bbb27c6..733c95f95e0 100644 --- a/homeassistant/components/logi_circle/translations/ko.json +++ b/homeassistant/components/logi_circle/translations/ko.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "external_error": "\ub2e4\ub978 \uad6c\uc131 \ub2e8\uacc4\uc5d0\uc11c \uc608\uc678\uc0ac\ud56d\uc774 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", - "external_setup": "Logi Circle \uc774 \ub2e4\ub978 \uad6c\uc131 \ub2e8\uacc4\uc5d0\uc11c \uc131\uacf5\uc801\uc73c\ub85c \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "external_setup": "Logi Circle\uc774 \ub2e4\ub978 \uad6c\uc131 \ub2e8\uacc4\uc5d0\uc11c \uc131\uacf5\uc801\uc73c\ub85c \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694." }, "error": { diff --git a/homeassistant/components/logi_circle/translations/nl.json b/homeassistant/components/logi_circle/translations/nl.json index 36970feb48b..8c4d81d120e 100644 --- a/homeassistant/components/logi_circle/translations/nl.json +++ b/homeassistant/components/logi_circle/translations/nl.json @@ -13,7 +13,7 @@ }, "step": { "auth": { - "description": "Volg de onderstaande link en Accepteer toegang tot uw Logi Circle-account, kom dan terug en druk hieronder op Verzenden . \n\n [Link] ({authorization_url})", + "description": "Volg de onderstaande link en **Accepteer** toegang tot uw Logi Circle-account, kom dan terug en druk hieronder op **Verzenden** . \n\n [Link] ({authorization_url})", "title": "Authenticeren met Logi Circle" }, "user": { diff --git a/homeassistant/components/lovelace/translations/id.json b/homeassistant/components/lovelace/translations/id.json new file mode 100644 index 00000000000..d945bc04f22 --- /dev/null +++ b/homeassistant/components/lovelace/translations/id.json @@ -0,0 +1,10 @@ +{ + "system_health": { + "info": { + "dashboards": "Dasbor", + "mode": "Mode", + "resources": "Sumber Daya", + "views": "Tampilan" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lovelace/translations/ko.json b/homeassistant/components/lovelace/translations/ko.json new file mode 100644 index 00000000000..48a26a1371c --- /dev/null +++ b/homeassistant/components/lovelace/translations/ko.json @@ -0,0 +1,10 @@ +{ + "system_health": { + "info": { + "dashboards": "\ub300\uc2dc\ubcf4\ub4dc", + "mode": "\ubaa8\ub4dc", + "resources": "\ub9ac\uc18c\uc2a4", + "views": "\ubdf0" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/luftdaten/translations/hu.json b/homeassistant/components/luftdaten/translations/hu.json index 4385c09d6ef..2fa90c23ca8 100644 --- a/homeassistant/components/luftdaten/translations/hu.json +++ b/homeassistant/components/luftdaten/translations/hu.json @@ -1,6 +1,8 @@ { "config": { "error": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_sensor": "Az \u00e9rz\u00e9kel\u0151 nem el\u00e9rhet\u0151 vagy \u00e9rv\u00e9nytelen" }, "step": { diff --git a/homeassistant/components/luftdaten/translations/id.json b/homeassistant/components/luftdaten/translations/id.json new file mode 100644 index 00000000000..96ec6d5f20f --- /dev/null +++ b/homeassistant/components/luftdaten/translations/id.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "already_configured": "Layanan sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "invalid_sensor": "Sensor tidak tersedia atau tidak valid" + }, + "step": { + "user": { + "data": { + "show_on_map": "Tampilkan di peta", + "station_id": "ID Sensor Luftdaten" + }, + "title": "Konfigurasikan Luftdaten" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/bg.json b/homeassistant/components/lutron_caseta/translations/bg.json new file mode 100644 index 00000000000..ba9f144cb0a --- /dev/null +++ b/homeassistant/components/lutron_caseta/translations/bg.json @@ -0,0 +1,12 @@ +{ + "device_automation": { + "trigger_subtype": { + "button_1": "\u041f\u044a\u0440\u0432\u0438 \u0431\u0443\u0442\u043e\u043d", + "button_2": "\u0412\u0442\u043e\u0440\u0438 \u0431\u0443\u0442\u043e\u043d", + "button_3": "\u0422\u0440\u0435\u0442\u0438 \u0431\u0443\u0442\u043e\u043d", + "button_4": "\u0427\u0435\u0442\u0432\u044a\u0440\u0442\u0438 \u0431\u0443\u0442\u043e\u043d", + "off": "\u0418\u0437\u043a\u043b.", + "on": "\u0412\u043a\u043b." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/en.json b/homeassistant/components/lutron_caseta/translations/en.json index 48b305ac7fa..2088cd52327 100644 --- a/homeassistant/components/lutron_caseta/translations/en.json +++ b/homeassistant/components/lutron_caseta/translations/en.json @@ -73,4 +73,4 @@ "release": "\"{subtype}\" released" } } -} +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/et.json b/homeassistant/components/lutron_caseta/translations/et.json index 81fee6d5b4a..5e57dd63b8b 100644 --- a/homeassistant/components/lutron_caseta/translations/et.json +++ b/homeassistant/components/lutron_caseta/translations/et.json @@ -23,7 +23,7 @@ "host": "" }, "description": "Sisesta seadme IP-aadress.", - "title": "\u00dchendu sillaga automaatselt" + "title": "\u00dchenda sillaga automaatselt" } } }, diff --git a/homeassistant/components/lutron_caseta/translations/he.json b/homeassistant/components/lutron_caseta/translations/he.json new file mode 100644 index 00000000000..7b55b0743fb --- /dev/null +++ b/homeassistant/components/lutron_caseta/translations/he.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "description": "\u05d4\u05d6\u05df \u05d0\u05ea \u05db\u05ea\u05d5\u05d1\u05ea \u05d4- IP \u05e9\u05dc \u05d4\u05de\u05db\u05e9\u05d9\u05e8." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/hu.json b/homeassistant/components/lutron_caseta/translations/hu.json new file mode 100644 index 00000000000..bf949ddf21c --- /dev/null +++ b/homeassistant/components/lutron_caseta/translations/hu.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "user": { + "data": { + "host": "Hoszt" + }, + "description": "Add meg az eszk\u00f6z IP-c\u00edm\u00e9t." + } + } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Els\u0151 gomb", + "button_2": "M\u00e1sodik gomb", + "button_3": "Harmadik gomb", + "button_4": "Negyedik gomb", + "off": "Ki", + "on": "Be" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/id.json b/homeassistant/components/lutron_caseta/translations/id.json new file mode 100644 index 00000000000..b14e9ad1c23 --- /dev/null +++ b/homeassistant/components/lutron_caseta/translations/id.json @@ -0,0 +1,70 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "not_lutron_device": "Perangkat yang ditemukan bukan perangkat Lutron" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "Lutron Cas\u00e9ta {name} ({host})", + "step": { + "import_failed": { + "description": "Tidak dapat menyiapkan bridge (host: {host} ) yang diimpor dari configuration.yaml.", + "title": "Gagal mengimpor konfigurasi bridge Cas\u00e9ta." + }, + "link": { + "description": "Untuk memasangkan dengan {name} ({host}), setelah mengirimkan formulir ini, tekan tombol hitam di bagian belakang bridge.", + "title": "Pasangkan dengan bridge" + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Masukkan alamat IP perangkat.", + "title": "Sambungkan secara otomatis ke bridge" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Tombol pertama", + "button_2": "Tombol kedua", + "button_3": "Tombol ketiga", + "button_4": "Tombol keempat", + "close_1": "Tutup 1", + "close_2": "Tutup 2", + "close_3": "Tutup 3", + "close_4": "Tutup 4", + "close_all": "Tutup semua", + "group_1_button_1": "Tombol pertama Grup Pertama", + "group_1_button_2": "Tombol kedua Grup Pertama", + "group_2_button_1": "Tombol pertama Grup Kedua", + "group_2_button_2": "Tombol kedua Grup Kedua", + "lower": "Rendah", + "lower_1": "Rendah 1", + "lower_2": "Rendah 2", + "lower_3": "Rendah 3", + "lower_4": "Rendah 4", + "lower_all": "Rendahkan semua", + "off": "Mati", + "on": "Nyala", + "open_1": "Buka 1", + "open_2": "Buka 2", + "open_3": "Buka 3", + "open_4": "Buka 4", + "open_all": "Buka semua", + "stop": "Hentikan (favorit)", + "stop_1": "Hentikan 1", + "stop_2": "Hentikan 2", + "stop_3": "Hentikan 3", + "stop_4": "Hentikan 4", + "stop_all": "Hentikan semuanya" + }, + "trigger_type": { + "press": "\"{subtype}\" ditekan", + "release": "\"{subtype}\" dilepas" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/ko.json b/homeassistant/components/lutron_caseta/translations/ko.json index af7fed5829c..862bfab8cfd 100644 --- a/homeassistant/components/lutron_caseta/translations/ko.json +++ b/homeassistant/components/lutron_caseta/translations/ko.json @@ -2,21 +2,75 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "not_lutron_device": "\ubc1c\uacac\ub41c \uae30\uae30\ub294 Lutron \uae30\uae30\uac00 \uc544\ub2d9\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, + "flow_title": "Lutron Cas\u00e9ta: {name} ({host})", "step": { "import_failed": { "description": "configuration.yaml \uc5d0\uc11c \uac00\uc838\uc628 \ube0c\ub9ac\uc9c0 (\ud638\uc2a4\ud2b8:{host}) \ub97c \uc124\uc815\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "title": "Cas\u00e9ta \ube0c\ub9ac\uc9c0 \uad6c\uc131\uc744 \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4." }, + "link": { + "description": "{name} ({host})\uacfc(\uc640) \ud398\uc5b4\ub9c1\ud558\ub824\uba74 \uc774 \uc591\uc2dd\uc744 \uc81c\ucd9c\ud55c \ud6c4 \ube0c\ub9ac\uc9c0 \ub4a4\ucabd\uc5d0 \uc788\ub294 \uac80\uc740\uc0c9 \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694.", + "title": "\ube0c\ub9ac\uc9c0\uc640 \ud398\uc5b4\ub9c1\ud558\uae30" + }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8" - } + }, + "description": "\uae30\uae30\uc758 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "\ube0c\ub9ac\uc9c0\uc5d0 \uc790\ub3d9\uc73c\ub85c \uc5f0\uacb0\ud558\uae30" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "\uccab \ubc88\uc9f8 \ubc84\ud2bc", + "button_2": "\ub450 \ubc88\uc9f8 \ubc84\ud2bc", + "button_3": "\uc138 \ubc88\uc9f8 \ubc84\ud2bc", + "button_4": "\ub124 \ubc88\uc9f8 \ubc84\ud2bc", + "close_1": "1 \ub2eb\uae30", + "close_2": "2 \ub2eb\uae30", + "close_3": "3 \ub2eb\uae30", + "close_4": "4 \ub2eb\uae30", + "close_all": "\ubaa8\ub450 \ub2eb\uae30", + "group_1_button_1": "\uccab \ubc88\uc9f8 \uadf8\ub8f9 \uccab \ubc88\uc9f8 \ubc84\ud2bc", + "group_1_button_2": "\uccab \ubc88\uc9f8 \uadf8\ub8f9 \ub450 \ubc88\uc9f8 \ubc84\ud2bc", + "group_2_button_1": "\ub450 \ubc88\uc9f8 \uadf8\ub8f9 \uccab \ubc88\uc9f8 \ubc84\ud2bc", + "group_2_button_2": "\ub450 \ubc88\uc9f8 \uadf8\ub8f9 \ub450 \ubc88\uc9f8 \ubc84\ud2bc", + "lower": "\ub0ae\ucd94\uae30", + "lower_1": "1 \ub0ae\ucd94\uae30", + "lower_2": "2 \ub0ae\ucd94\uae30", + "lower_3": "3 \ub0ae\ucd94\uae30", + "lower_4": "4 \ub0ae\ucd94\uae30", + "lower_all": "\ubaa8\ub450 \ub0ae\ucd94\uae30", + "off": "\ub044\uae30", + "on": "\ucf1c\uae30", + "open_1": "1 \uc5f4\uae30", + "open_2": "2 \uc5f4\uae30", + "open_3": "3 \uc5f4\uae30", + "open_4": "4 \uc5f4\uae30", + "open_all": "\ubaa8\ub450 \uc5f4\uae30", + "raise": "\ub192\uc774\uae30", + "raise_1": "1 \uc62c\ub9ac\uae30", + "raise_2": "2 \uc62c\ub9ac\uae30", + "raise_3": "3 \uc62c\ub9ac\uae30", + "raise_4": "4 \uc62c\ub9ac\uae30", + "raise_all": "\ubaa8\ub450 \uc62c\ub9ac\uae30", + "stop": "\uc911\uc9c0 (\uc990\uaca8 \ucc3e\uae30)", + "stop_1": "1 \uc911\uc9c0", + "stop_2": "2 \uc911\uc9c0", + "stop_3": "3 \uc911\uc9c0", + "stop_4": "4 \uc911\uc9c0", + "stop_all": "\ubaa8\ub450 \uc911\uc9c0" + }, + "trigger_type": { + "press": "\"{subtype}\"\uc774(\uac00) \ub20c\ub838\uc744 \ub54c", + "release": "\"{subtype}\"\uc5d0\uc11c \uc190\uc744 \ub5bc\uc5c8\uc744 \ub54c" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/lb.json b/homeassistant/components/lutron_caseta/translations/lb.json index e78f8839797..6a390bc5a1f 100644 --- a/homeassistant/components/lutron_caseta/translations/lb.json +++ b/homeassistant/components/lutron_caseta/translations/lb.json @@ -13,5 +13,37 @@ "title": "Feeler beim import vun der Cas\u00e9ta Bridge Konfiguratioun" } } + }, + "device_automation": { + "trigger_subtype": { + "lower": "Erofsetzen", + "lower_1": "1 erofsetzen", + "lower_2": "2 erofsetzen", + "lower_3": "3 erofsetzen", + "lower_4": "4 erofsetzen", + "lower_all": "All erofsetzen", + "off": "Aus", + "on": "Un", + "open_1": "1 opmaachen", + "open_2": "2 opmaachen", + "open_3": "3 opmaachen", + "open_4": "4 opmaachen", + "open_all": "All opmaachen", + "raise": "Unhiewen", + "raise_1": "1 unhiewen", + "raise_2": "2 unhiewen", + "raise_3": "3 unhiewen", + "raise_4": "4 unhiewen", + "raise_all": "All unhiewen", + "stop_1": "Stop 1", + "stop_2": "Stop 2", + "stop_3": "Stop 3", + "stop_4": "Stop 4", + "stop_all": "All stoppen" + }, + "trigger_type": { + "press": "\"{subtype}\" gedr\u00e9ckt", + "release": "\"{subtype}\" lassgelooss" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/nl.json b/homeassistant/components/lutron_caseta/translations/nl.json index 17e6fc47fd8..d74a18622d0 100644 --- a/homeassistant/components/lutron_caseta/translations/nl.json +++ b/homeassistant/components/lutron_caseta/translations/nl.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "cannot_connect": "Kon niet verbinden" + "cannot_connect": "Kon niet verbinden", + "not_lutron_device": "Ontdekt apparaat is geen Lutron-apparaat" }, "error": { "cannot_connect": "Kon niet verbinden" @@ -10,13 +11,18 @@ "flow_title": "Lutron Cas\u00e9ta {name} ({host})", "step": { "import_failed": { - "description": "Kan bridge (host: {host} ) niet instellen, ge\u00efmporteerd uit configuration.yaml." + "description": "Kan bridge (host: {host} ) niet instellen, ge\u00efmporteerd uit configuration.yaml.", + "title": "Het importeren van de Cas\u00e9ta bridge configuratie is mislukt." + }, + "link": { + "title": "Koppel met de bridge" }, "user": { "data": { "host": "Host" }, - "description": "Voer het IP-adres van het apparaat in." + "description": "Voer het IP-adres van het apparaat in.", + "title": "Automatisch verbinding maken met de bridge" } } }, @@ -35,17 +41,31 @@ "group_1_button_2": "Eerste Groep tweede knop", "group_2_button_1": "Tweede Groep eerste knop", "group_2_button_2": "Tweede Groep tweede knop", + "lower": "Verlagen", + "lower_1": "Verlagen 1", + "lower_2": "Verlagen 2", + "lower_3": "Verlagen 3", + "lower_4": "Verlaag 4", + "lower_all": "Verlaag alles", "off": "Uit", "on": "Aan", "open_1": "Open 1", "open_2": "Open 2", "open_3": "Open 3", "open_4": "Open 4", + "open_all": "Open alle", + "raise": "Verhogen", + "raise_1": "Verhogen 1", + "raise_2": "Verhogen 2", + "raise_3": "Verhogen 3", + "raise_4": "Verhogen 4", + "raise_all": "Verhoog alles", "stop": "Stop (favoriet)", "stop_1": "Stop 1", "stop_2": "Stop 2", "stop_3": "Stop 3", - "stop_4": "Stop 4" + "stop_4": "Stop 4", + "stop_all": "Stop alles" }, "trigger_type": { "press": "\" {subtype} \" ingedrukt", diff --git a/homeassistant/components/lyric/translations/hu.json b/homeassistant/components/lyric/translations/hu.json new file mode 100644 index 00000000000..cae1f6d20c0 --- /dev/null +++ b/homeassistant/components/lyric/translations/hu.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t." + }, + "create_entry": { + "default": "Sikeres hiteles\u00edt\u00e9s" + }, + "step": { + "pick_implementation": { + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/id.json b/homeassistant/components/lyric/translations/id.json new file mode 100644 index 00000000000..876fe2f8c39 --- /dev/null +++ b/homeassistant/components/lyric/translations/id.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi." + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/hu.json b/homeassistant/components/mailgun/translations/hu.json index 51bbe6ef04c..14c2293734c 100644 --- a/homeassistant/components/mailgun/translations/hu.json +++ b/homeassistant/components/mailgun/translations/hu.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "webhook_not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." + }, "create_entry": { - "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant programnak, be kell \u00e1ll\u00edtania a [Webhooks Mailgun-al] ( {mailgun_url} ) alkalmaz\u00e1st. \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: \" {webhook_url} \"\n - M\u00f3dszer: POST\n - Tartalom t\u00edpusa: application/json \n\n L\u00e1sd [a dokument\u00e1ci\u00f3] ( {docs_url} ), hogyan konfigur\u00e1lhatja az automatiz\u00e1l\u00e1sokat a bej\u00f6v\u0151 adatok kezel\u00e9s\u00e9re." + "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant programnak, be kell \u00e1ll\u00edtania a [Webhooks with Mailgun]({mailgun_url}) alkalmaz\u00e1st. \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: `{webhook_url}`\n - M\u00f3dszer: POST\n - Tartalom t\u00edpusa: application/json \n\n L\u00e1sd [a dokument\u00e1ci\u00f3]({docs_url}), hogyan konfigur\u00e1lhatja az automatiz\u00e1l\u00e1sokat a bej\u00f6v\u0151 adatok kezel\u00e9s\u00e9re." }, "step": { "user": { diff --git a/homeassistant/components/mailgun/translations/id.json b/homeassistant/components/mailgun/translations/id.json new file mode 100644 index 00000000000..b58deb171be --- /dev/null +++ b/homeassistant/components/mailgun/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." + }, + "create_entry": { + "default": "Untuk mengirim event ke Home Assistant, Anda harus menyiapkan [Webhooks dengan Mailgun]({mailgun_url}).\n\nIsi info berikut:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nBaca [dokumentasi]({docs_url}) tentang cara mengonfigurasi otomasi untuk menangani data masuk." + }, + "step": { + "user": { + "description": "Yakin ingin menyiapkan Mailgun?", + "title": "Siapkan Mailgun Webhook" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/ko.json b/homeassistant/components/mailgun/translations/ko.json index b757a27f4a0..1be1721d78f 100644 --- a/homeassistant/components/mailgun/translations/ko.json +++ b/homeassistant/components/mailgun/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Mailgun \uc6f9 \ud6c5]({mailgun_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \nHome Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Mailgun \uc6f9 \ud6c5]({mailgun_url})\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \nHome Assistant\ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/mazda/translations/bg.json b/homeassistant/components/mazda/translations/bg.json new file mode 100644 index 00000000000..6f3c5a54f3f --- /dev/null +++ b/homeassistant/components/mazda/translations/bg.json @@ -0,0 +1,21 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "reauth": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "region": "\u0420\u0435\u0433\u0438\u043e\u043d" + } + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "region": "\u0420\u0435\u0433\u0438\u043e\u043d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/de.json b/homeassistant/components/mazda/translations/de.json index 4e23becb8af..9050ee9f00c 100644 --- a/homeassistant/components/mazda/translations/de.json +++ b/homeassistant/components/mazda/translations/de.json @@ -5,6 +5,7 @@ "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { + "account_locked": "Konto gesperrt. Bitte versuchen Sie es sp\u00e4ter erneut.", "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" @@ -15,15 +16,20 @@ "email": "E-Mail", "password": "Passwort", "region": "Region" - } + }, + "description": "Die Authentifizierung f\u00fcr Mazda Connected Services ist fehlgeschlagen. Bitte geben Sie Ihre aktuellen Anmeldedaten ein.", + "title": "Mazda Connected Services - Authentifizierung fehlgeschlagen" }, "user": { "data": { "email": "E-Mail", "password": "Passwort", "region": "Region" - } + }, + "description": "Bitte geben Sie die E-Mail-Adresse und das Passwort ein, die Sie f\u00fcr die Anmeldung bei der MyMazda Mobile App verwenden.", + "title": "Mazda Connected Services - Konto hinzuf\u00fcgen" } } - } + }, + "title": "Mazda Connected Services" } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/hu.json b/homeassistant/components/mazda/translations/hu.json new file mode 100644 index 00000000000..1b9c6893ed5 --- /dev/null +++ b/homeassistant/components/mazda/translations/hu.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "account_locked": "Fi\u00f3k z\u00e1rolva. K\u00e9rlek, pr\u00f3b\u00e1ld \u00fajra k\u00e9s\u0151bb.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "reauth": { + "data": { + "email": "E-mail", + "password": "Jelsz\u00f3", + "region": "R\u00e9gi\u00f3" + }, + "title": "Mazda Connected Services - A hiteles\u00edt\u00e9s sikertelen" + }, + "user": { + "data": { + "email": "E-mail", + "password": "Jelsz\u00f3", + "region": "R\u00e9gi\u00f3" + }, + "title": "Mazda Connected Services - Fi\u00f3k hozz\u00e1ad\u00e1sa" + } + } + }, + "title": "Mazda Connected Services" +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/id.json b/homeassistant/components/mazda/translations/id.json new file mode 100644 index 00000000000..0a6e81e8454 --- /dev/null +++ b/homeassistant/components/mazda/translations/id.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "account_locked": "Akun terkunci. Coba lagi nanti.", + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "reauth": { + "data": { + "email": "Email", + "password": "Kata Sandi", + "region": "Wilayah" + }, + "description": "Autentikasi gagal untuk Mazda Connected Services. Masukkan kredensial Anda saat ini.", + "title": "Mazda Connected Services - Autentikasi Gagal" + }, + "user": { + "data": { + "email": "Email", + "password": "Kata Sandi", + "region": "Wilayah" + }, + "description": "Masukkan alamat email dan kata sandi yang digunakan untuk masuk ke aplikasi seluler MyMazda.", + "title": "Mazda Connected Services - Tambahkan Akun" + } + } + }, + "title": "Mazda Connected Services" +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/ko.json b/homeassistant/components/mazda/translations/ko.json index 31495b0d8e3..aa9dcf99d14 100644 --- a/homeassistant/components/mazda/translations/ko.json +++ b/homeassistant/components/mazda/translations/ko.json @@ -5,6 +5,7 @@ "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { + "account_locked": "\uacc4\uc815\uc774 \uc7a0\uacbc\uc2b5\ub2c8\ub2e4. \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574 \uc8fc\uc138\uc694.", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" @@ -13,15 +14,22 @@ "reauth": { "data": { "email": "\uc774\uba54\uc77c", - "password": "\ube44\ubc00\ubc88\ud638" - } + "password": "\ube44\ubc00\ubc88\ud638", + "region": "\uc9c0\uc5ed" + }, + "description": "Mazda Connected Services\uc5d0 \ub300\ud55c \uc778\uc99d\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. \ud604\uc7ac \uc790\uaca9 \uc99d\uba85\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "Mazda Connected Services - \uc778\uc99d \uc2e4\ud328" }, "user": { "data": { "email": "\uc774\uba54\uc77c", - "password": "\ube44\ubc00\ubc88\ud638" - } + "password": "\ube44\ubc00\ubc88\ud638", + "region": "\uc9c0\uc5ed" + }, + "description": "MyMazda \ubaa8\ubc14\uc77c \uc571\uc5d0 \ub85c\uadf8\uc778\ud558\uae30 \uc704\ud574 \uc0ac\uc6a9\ud558\ub294 \uc774\uba54\uc77c \uc8fc\uc18c\uc640 \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "Mazda Connected Services - \uacc4\uc815 \ucd94\uac00\ud558\uae30" } } - } + }, + "title": "Mazda Connected Services" } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/nl.json b/homeassistant/components/mazda/translations/nl.json index 3198bfb4192..64975c9b14b 100644 --- a/homeassistant/components/mazda/translations/nl.json +++ b/homeassistant/components/mazda/translations/nl.json @@ -14,15 +14,20 @@ "reauth": { "data": { "email": "E-mail", - "password": "Wachtwoord" - } + "password": "Wachtwoord", + "region": "Regio" + }, + "description": "Verificatie mislukt voor Mazda Connected Services. Voer uw huidige inloggegevens in.", + "title": "Mazda Connected Services - Authenticatie mislukt" }, "user": { "data": { "email": "E-mail", "password": "Wachtwoord", "region": "Regio" - } + }, + "description": "Voer het e-mailadres en wachtwoord in dat u gebruikt om in te loggen op de MyMazda mobiele app.", + "title": "Mazda Connected Services - Account toevoegen" } } }, diff --git a/homeassistant/components/media_player/translations/hu.json b/homeassistant/components/media_player/translations/hu.json index 0eae14fdd98..83b5dc4e122 100644 --- a/homeassistant/components/media_player/translations/hu.json +++ b/homeassistant/components/media_player/translations/hu.json @@ -6,6 +6,13 @@ "is_on": "{entity_name} be van kapcsolva", "is_paused": "{entity_name} sz\u00fcneteltetve van", "is_playing": "{entity_name} lej\u00e1tszik" + }, + "trigger_type": { + "idle": "{entity_name} t\u00e9tlenn\u00e9 v\u00e1lik", + "paused": "{entity_name} sz\u00fcneteltetve van", + "playing": "{entity_name} megkezdi a lej\u00e1tsz\u00e1st", + "turned_off": "{entity_name} ki lett kapcsolva", + "turned_on": "{entity_name} be lett kapcsolva" } }, "state": { diff --git a/homeassistant/components/media_player/translations/id.json b/homeassistant/components/media_player/translations/id.json index bcf12d72542..e759f88a15a 100644 --- a/homeassistant/components/media_player/translations/id.json +++ b/homeassistant/components/media_player/translations/id.json @@ -1,11 +1,27 @@ { + "device_automation": { + "condition_type": { + "is_idle": "{entity_name} siaga", + "is_off": "{entity_name} mati", + "is_on": "{entity_name} nyala", + "is_paused": "{entity_name} dijeda", + "is_playing": "{entity_name} sedang memutar" + }, + "trigger_type": { + "idle": "{entity_name} menjadi siaga", + "paused": "{entity_name} dijeda", + "playing": "{entity_name} mulai memutar", + "turned_off": "{entity_name} dimatikan", + "turned_on": "{entity_name} dinyalakan" + } + }, "state": { "_": { - "idle": "Diam", - "off": "Off", - "on": "On", + "idle": "Siaga", + "off": "Mati", + "on": "Nyala", "paused": "Jeda", - "playing": "Memainkan", + "playing": "Memutar", "standby": "Siaga" } }, diff --git a/homeassistant/components/media_player/translations/ko.json b/homeassistant/components/media_player/translations/ko.json index e727e744d73..213b61ef6b9 100644 --- a/homeassistant/components/media_player/translations/ko.json +++ b/homeassistant/components/media_player/translations/ko.json @@ -1,11 +1,18 @@ { "device_automation": { "condition_type": { - "is_idle": "{entity_name} \uc774(\uac00) \uc720\ud734 \uc0c1\ud0dc\uc774\uba74", - "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", - "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74", - "is_paused": "{entity_name} \uc774(\uac00) \uc77c\uc2dc\uc911\uc9c0\ub418\uc5b4 \uc788\uc73c\uba74", - "is_playing": "{entity_name} \uc774(\uac00) \uc7ac\uc0dd \uc911\uc774\uba74" + "is_idle": "{entity_name}\uc774(\uac00) \ub300\uae30 \uc0c1\ud0dc\uc774\uba74", + "is_off": "{entity_name}\uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name}\uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74", + "is_paused": "{entity_name}\uc774(\uac00) \uc77c\uc2dc\uc911\uc9c0\ub418\uc5b4 \uc788\uc73c\uba74", + "is_playing": "{entity_name}\uc774(\uac00) \uc7ac\uc0dd \uc911\uc774\uba74" + }, + "trigger_type": { + "idle": "{entity_name}\uc774(\uac00) \ub300\uae30 \uc0c1\ud0dc\uac00 \ub420 \ub54c", + "paused": "{entity_name}\uc774(\uac00) \uc77c\uc2dc\uc911\uc9c0\ub420 \ub54c", + "playing": "{entity_name}\uc774(\uac00) \uc7ac\uc0dd\uc744 \uc2dc\uc791\ud560 \ub54c", + "turned_off": "{entity_name}\uc774(\uac00) \uaebc\uc84c\uc744 \ub54c", + "turned_on": "{entity_name}\uc774(\uac00) \ucf1c\uc84c\uc744 \ub54c" } }, "state": { diff --git a/homeassistant/components/media_player/translations/zh-Hans.json b/homeassistant/components/media_player/translations/zh-Hans.json index af8579075be..0fa034898c3 100644 --- a/homeassistant/components/media_player/translations/zh-Hans.json +++ b/homeassistant/components/media_player/translations/zh-Hans.json @@ -6,6 +6,13 @@ "is_on": "{entity_name} \u5df2\u5f00\u542f", "is_paused": "{entity_name} \u5df2\u6682\u505c", "is_playing": "{entity_name} \u6b63\u5728\u64ad\u653e" + }, + "trigger_type": { + "idle": "{entity_name} \u7a7a\u95f2", + "paused": "{entity_name} \u6682\u505c", + "playing": "{entity_name} \u5f00\u59cb\u64ad\u653e", + "turned_off": "{entity_name} \u88ab\u5173\u95ed", + "turned_on": "{entity_name} \u88ab\u6253\u5f00" } }, "state": { diff --git a/homeassistant/components/melcloud/translations/he.json b/homeassistant/components/melcloud/translations/he.json new file mode 100644 index 00000000000..ac90b3264ea --- /dev/null +++ b/homeassistant/components/melcloud/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/melcloud/translations/hu.json b/homeassistant/components/melcloud/translations/hu.json index 62699ecb468..7f81269c700 100644 --- a/homeassistant/components/melcloud/translations/hu.json +++ b/homeassistant/components/melcloud/translations/hu.json @@ -1,5 +1,10 @@ { "config": { + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/melcloud/translations/id.json b/homeassistant/components/melcloud/translations/id.json new file mode 100644 index 00000000000..d2847541537 --- /dev/null +++ b/homeassistant/components/melcloud/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Integrasi MELCloud sudah dikonfigurasi untuk email ini. Token akses telah disegarkan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Email" + }, + "description": "Hubungkan menggunakan akun MELCloud Anda.", + "title": "Hubungkan ke MELCloud" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/melcloud/translations/ko.json b/homeassistant/components/melcloud/translations/ko.json index 2e1f1b535e1..ce984e26906 100644 --- a/homeassistant/components/melcloud/translations/ko.json +++ b/homeassistant/components/melcloud/translations/ko.json @@ -15,7 +15,7 @@ "username": "\uc774\uba54\uc77c" }, "description": "MELCloud \uacc4\uc815\uc73c\ub85c \uc5f0\uacb0\ud558\uc138\uc694.", - "title": "MELCloud \uc5d0 \uc5f0\uacb0\ud558\uae30" + "title": "MELCloud\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/met/translations/he.json b/homeassistant/components/met/translations/he.json new file mode 100644 index 00000000000..4c49313d977 --- /dev/null +++ b/homeassistant/components/met/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/met/translations/hu.json b/homeassistant/components/met/translations/hu.json index 4e3ccf87ab7..b9141541a93 100644 --- a/homeassistant/components/met/translations/hu.json +++ b/homeassistant/components/met/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/met/translations/id.json b/homeassistant/components/met/translations/id.json index 12854e4ed61..639ed5086ce 100644 --- a/homeassistant/components/met/translations/id.json +++ b/homeassistant/components/met/translations/id.json @@ -1,11 +1,17 @@ { "config": { + "error": { + "already_configured": "Layanan sudah dikonfigurasi" + }, "step": { "user": { "data": { "elevation": "Ketinggian", + "latitude": "Lintang", + "longitude": "Bujur", "name": "Nama" }, + "description": "Meteorologisk institutt", "title": "Lokasi" } } diff --git a/homeassistant/components/meteo_france/translations/hu.json b/homeassistant/components/meteo_france/translations/hu.json index dc74eafa409..112f70b6ea6 100644 --- a/homeassistant/components/meteo_france/translations/hu.json +++ b/homeassistant/components/meteo_france/translations/hu.json @@ -1,13 +1,20 @@ { "config": { "abort": { - "already_configured": "A v\u00e1ros m\u00e1r konfigur\u00e1lva van", - "unknown": "Ismeretlen hiba: k\u00e9rj\u00fck, pr\u00f3b\u00e1lja \u00fajra k\u00e9s\u0151bb" + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { "empty": "Nincs eredm\u00e9ny a v\u00e1roskeres\u00e9sben: ellen\u0151rizze a v\u00e1ros mez\u0151t" }, "step": { + "cities": { + "data": { + "city": "V\u00e1ros" + }, + "description": "V\u00e1laszd ki a v\u00e1rost a list\u00e1b\u00f3l", + "title": "M\u00e9t\u00e9o-France" + }, "user": { "data": { "city": "V\u00e1ros" @@ -16,5 +23,14 @@ "title": "M\u00e9t\u00e9o-France" } } + }, + "options": { + "step": { + "init": { + "data": { + "mode": "El\u0151rejelz\u00e9si m\u00f3d" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/meteo_france/translations/id.json b/homeassistant/components/meteo_france/translations/id.json new file mode 100644 index 00000000000..07d8450e873 --- /dev/null +++ b/homeassistant/components/meteo_france/translations/id.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "empty": "Tidak ada hasil dalam penelusuran kota: periksa bidang isian kota" + }, + "step": { + "cities": { + "data": { + "city": "Kota" + }, + "description": "Pilih kota Anda dari daftar", + "title": "M\u00e9t\u00e9o-France" + }, + "user": { + "data": { + "city": "Kota" + }, + "description": "Masukkan kode pos (hanya untuk Prancis, disarankan) atau nama kota", + "title": "M\u00e9t\u00e9o-France" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "mode": "Mode prakiraan" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meteo_france/translations/ko.json b/homeassistant/components/meteo_france/translations/ko.json index ec48103bbff..83cda0e4dcf 100644 --- a/homeassistant/components/meteo_france/translations/ko.json +++ b/homeassistant/components/meteo_france/translations/ko.json @@ -1,14 +1,18 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "empty": "\ub3c4\uc2dc \uac80\uc0c9 \uacb0\uacfc \uc5c6\uc74c: \ub3c4\uc2dc \ud544\ub4dc\ub97c \ud655\uc778\ud558\uc2ed\uc2dc\uc624." + "empty": "\ub3c4\uc2dc \uac80\uc0c9 \uacb0\uacfc\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \ub3c4\uc2dc \ud544\ub4dc\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694." }, "step": { "cities": { + "data": { + "city": "\ub3c4\uc2dc" + }, + "description": "\ubaa9\ub85d\uc5d0\uc11c \ub3c4\uc2dc\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694", "title": "\ud504\ub791\uc2a4 \uae30\uc0c1\uccad (M\u00e9t\u00e9o-France)" }, "user": { diff --git a/homeassistant/components/meteo_france/translations/nl.json b/homeassistant/components/meteo_france/translations/nl.json index 61925da4cd3..e828fa6e09f 100644 --- a/homeassistant/components/meteo_france/translations/nl.json +++ b/homeassistant/components/meteo_france/translations/nl.json @@ -6,6 +6,9 @@ }, "step": { "cities": { + "data": { + "city": "Stad" + }, "title": "M\u00e9t\u00e9o-France" }, "user": { @@ -16,5 +19,14 @@ "title": "M\u00e9t\u00e9o-France" } } + }, + "options": { + "step": { + "init": { + "data": { + "mode": "Voorspellingsmodus" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/metoffice/translations/he.json b/homeassistant/components/metoffice/translations/he.json new file mode 100644 index 00000000000..4c49313d977 --- /dev/null +++ b/homeassistant/components/metoffice/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/metoffice/translations/hu.json b/homeassistant/components/metoffice/translations/hu.json index 3b2d79a34a7..350e6f92f32 100644 --- a/homeassistant/components/metoffice/translations/hu.json +++ b/homeassistant/components/metoffice/translations/hu.json @@ -1,7 +1,21 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g" + }, + "title": "Csatlakoz\u00e1s a UK Met Office-hoz" + } } } } \ No newline at end of file diff --git a/homeassistant/components/metoffice/translations/id.json b/homeassistant/components/metoffice/translations/id.json new file mode 100644 index 00000000000..d9bb784a99f --- /dev/null +++ b/homeassistant/components/metoffice/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "longitude": "Bujur" + }, + "description": "Lintang dan bujur akan digunakan untuk menemukan stasiun cuaca terdekat.", + "title": "Hubungkan ke the UK Met Office" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/metoffice/translations/nl.json b/homeassistant/components/metoffice/translations/nl.json index 1b3063459c4..a6ba36f07af 100644 --- a/homeassistant/components/metoffice/translations/nl.json +++ b/homeassistant/components/metoffice/translations/nl.json @@ -14,6 +14,7 @@ "latitude": "Breedtegraad", "longitude": "Lengtegraad" }, + "description": "De lengte- en breedtegraad worden gebruikt om het dichtstbijzijnde weerstation te vinden.", "title": "Maak verbinding met het UK Met Office" } } diff --git a/homeassistant/components/mikrotik/translations/he.json b/homeassistant/components/mikrotik/translations/he.json new file mode 100644 index 00000000000..ac90b3264ea --- /dev/null +++ b/homeassistant/components/mikrotik/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mikrotik/translations/hu.json b/homeassistant/components/mikrotik/translations/hu.json index 67a2e8d8fc3..248884f9687 100644 --- a/homeassistant/components/mikrotik/translations/hu.json +++ b/homeassistant/components/mikrotik/translations/hu.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "A Mikrotik m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, "error": { - "cannot_connect": "A kapcsolat sikertelen", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "name_exists": "A n\u00e9v m\u00e1r l\u00e9tezik" }, "step": { diff --git a/homeassistant/components/mikrotik/translations/id.json b/homeassistant/components/mikrotik/translations/id.json new file mode 100644 index 00000000000..3ef0dacb763 --- /dev/null +++ b/homeassistant/components/mikrotik/translations/id.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "name_exists": "Nama sudah ada" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna", + "verify_ssl": "Gunakan SSL" + }, + "title": "Siapkan Router Mikrotik" + } + } + }, + "options": { + "step": { + "device_tracker": { + "data": { + "arp_ping": "Aktifkan ping ARP", + "detection_time": "Pertimbangkan interval rumah", + "force_dhcp": "Paksa pemindaian menggunakan DHCP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mill/translations/hu.json b/homeassistant/components/mill/translations/hu.json index 387a73041d9..74a6f9abbac 100644 --- a/homeassistant/components/mill/translations/hu.json +++ b/homeassistant/components/mill/translations/hu.json @@ -3,6 +3,9 @@ "abort": { "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/mill/translations/id.json b/homeassistant/components/mill/translations/id.json new file mode 100644 index 00000000000..ab929d3d7c8 --- /dev/null +++ b/homeassistant/components/mill/translations/id.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/minecraft_server/translations/hu.json b/homeassistant/components/minecraft_server/translations/hu.json index 7a8958bd7c6..247c1ffc1c3 100644 --- a/homeassistant/components/minecraft_server/translations/hu.json +++ b/homeassistant/components/minecraft_server/translations/hu.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Kiszolg\u00e1l\u00f3 m\u00e1r konfigur\u00e1lva van." + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" }, "error": { "cannot_connect": "Nem siker\u00fclt csatlakozni a szerverhez. K\u00e9rj\u00fck, ellen\u0151rizze a gazdag\u00e9pet \u00e9s a portot, majd pr\u00f3b\u00e1lkozzon \u00fajra. Gondoskodjon arr\u00f3l, hogy a szerveren legal\u00e1bb a Minecraft 1.7-es verzi\u00f3j\u00e1t futtassa." diff --git a/homeassistant/components/minecraft_server/translations/id.json b/homeassistant/components/minecraft_server/translations/id.json new file mode 100644 index 00000000000..fffb0865b24 --- /dev/null +++ b/homeassistant/components/minecraft_server/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung ke server. Periksa host dan port lalu coba lagi. Pastikan juga Anda menjalankan Minecraft dengan versi minimal 1.7 di server Anda.", + "invalid_ip": "Alamat IP tidak valid (alamat MAC tidak dapat ditentukan). Perbaiki, lalu coba lagi.", + "invalid_port": "Port harus berada dalam rentang dari 1024 hingga 65535. Perbaiki, lalu coba lagi." + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama" + }, + "description": "Siapkan instans Minecraft Server Anda untuk pemantauan.", + "title": "Tautkan Server Minecraft Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/ca.json b/homeassistant/components/mobile_app/translations/ca.json index a36fd1ca13a..70709d1be64 100644 --- a/homeassistant/components/mobile_app/translations/ca.json +++ b/homeassistant/components/mobile_app/translations/ca.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Envia una notificaci\u00f3" } - } + }, + "title": "Aplicaci\u00f3 m\u00f2bil" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/en.json b/homeassistant/components/mobile_app/translations/en.json index 34631f86afa..0b564a38174 100644 --- a/homeassistant/components/mobile_app/translations/en.json +++ b/homeassistant/components/mobile_app/translations/en.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Send a notification" } - } + }, + "title": "Mobile App" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/et.json b/homeassistant/components/mobile_app/translations/et.json index 27f5fce2bdf..e5b4ea8009c 100644 --- a/homeassistant/components/mobile_app/translations/et.json +++ b/homeassistant/components/mobile_app/translations/et.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Saada teavitus" } - } + }, + "title": "Mobiilirakendus" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/fr.json b/homeassistant/components/mobile_app/translations/fr.json index f4b0f590e48..f888b8062f8 100644 --- a/homeassistant/components/mobile_app/translations/fr.json +++ b/homeassistant/components/mobile_app/translations/fr.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Envoyer une notification" } - } + }, + "title": "Application mobile" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/hu.json b/homeassistant/components/mobile_app/translations/hu.json index 301075e0ad4..90690e2545b 100644 --- a/homeassistant/components/mobile_app/translations/hu.json +++ b/homeassistant/components/mobile_app/translations/hu.json @@ -13,5 +13,6 @@ "action_type": { "notify": "\u00c9rtes\u00edt\u00e9s k\u00fcld\u00e9se" } - } + }, + "title": "Mobil alkalmaz\u00e1s" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/id.json b/homeassistant/components/mobile_app/translations/id.json new file mode 100644 index 00000000000..d346ca76eab --- /dev/null +++ b/homeassistant/components/mobile_app/translations/id.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "install_app": "Buka aplikasi seluler untuk menyiapkan integrasi dengan Home Assistant. Baca [dokumentasi]({apps_url}) tentang daftar aplikasi yang kompatibel." + }, + "step": { + "confirm": { + "description": "Ingin menyiapkan komponen Aplikasi Seluler?" + } + } + }, + "device_automation": { + "action_type": { + "notify": "Kirim notifikasi" + } + }, + "title": "Aplikasi Seluler" +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/it.json b/homeassistant/components/mobile_app/translations/it.json index f5ba52b1a53..3784a158930 100644 --- a/homeassistant/components/mobile_app/translations/it.json +++ b/homeassistant/components/mobile_app/translations/it.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Invia una notifica" } - } + }, + "title": "App per dispositivi mobili" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/ko.json b/homeassistant/components/mobile_app/translations/ko.json index 03478e1cf2a..7f99e1f2100 100644 --- a/homeassistant/components/mobile_app/translations/ko.json +++ b/homeassistant/components/mobile_app/translations/ko.json @@ -1,12 +1,18 @@ { "config": { "abort": { - "install_app": "\ubaa8\ubc14\uc77c \uc571\uc744 \uc5f4\uc5b4 Home Assistant \uc640 \uc5f0\ub3d9\uc744 \uc124\uc815\ud574\uc8fc\uc138\uc694. \ud638\ud658\ub418\ub294 \uc571 \ubaa9\ub85d\uc740 [\uc548\ub0b4]({apps_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "install_app": "Mobile App\uc744 \uc5f4\uc5b4 Home Assistant\uc640 \uc5f0\ub3d9\uc744 \uc124\uc815\ud574\uc8fc\uc138\uc694. \ud638\ud658\ub418\ub294 \uc571 \ubaa9\ub85d\uc740 [\uad00\ub828 \ubb38\uc11c]({apps_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "confirm": { - "description": "\ubaa8\ubc14\uc77c \uc571 \ucef4\ud3ec\ub10c\ud2b8\uc758 \uc124\uc815\uc744 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "Mobile App \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } - } + }, + "device_automation": { + "action_type": { + "notify": "\uc54c\ub9bc \ubcf4\ub0b4\uae30" + } + }, + "title": "Mobile App" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/nl.json b/homeassistant/components/mobile_app/translations/nl.json index 17a20705cd4..e7bfb6150a5 100644 --- a/homeassistant/components/mobile_app/translations/nl.json +++ b/homeassistant/components/mobile_app/translations/nl.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Stuur een notificatie" } - } + }, + "title": "Mobiele app" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/no.json b/homeassistant/components/mobile_app/translations/no.json index 65d465723b1..0f6f6dbb0fd 100644 --- a/homeassistant/components/mobile_app/translations/no.json +++ b/homeassistant/components/mobile_app/translations/no.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Send et varsel" } - } + }, + "title": "Mobilapp" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/pl.json b/homeassistant/components/mobile_app/translations/pl.json index cd083447634..00a4631684c 100644 --- a/homeassistant/components/mobile_app/translations/pl.json +++ b/homeassistant/components/mobile_app/translations/pl.json @@ -13,5 +13,6 @@ "action_type": { "notify": "wy\u015blij powiadomienie" } - } + }, + "title": "Aplikacja mobilna" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/ru.json b/homeassistant/components/mobile_app/translations/ru.json index fc4496ba1d8..65b6cc15c65 100644 --- a/homeassistant/components/mobile_app/translations/ru.json +++ b/homeassistant/components/mobile_app/translations/ru.json @@ -13,5 +13,6 @@ "action_type": { "notify": "\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435" } - } + }, + "title": "\u041c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/zh-Hans.json b/homeassistant/components/mobile_app/translations/zh-Hans.json index b48ca1e4263..6a884b156bc 100644 --- a/homeassistant/components/mobile_app/translations/zh-Hans.json +++ b/homeassistant/components/mobile_app/translations/zh-Hans.json @@ -8,5 +8,11 @@ "description": "\u60a8\u60f3\u8981\u914d\u7f6e\u79fb\u52a8\u5e94\u7528\u7a0b\u5e8f\u7ec4\u4ef6\u5417\uff1f" } } - } + }, + "device_automation": { + "action_type": { + "notify": "\u63a8\u9001\u901a\u77e5" + } + }, + "title": "\u79fb\u52a8\u5e94\u7528" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/zh-Hant.json b/homeassistant/components/mobile_app/translations/zh-Hant.json index d54afd94f54..2793794b8b6 100644 --- a/homeassistant/components/mobile_app/translations/zh-Hant.json +++ b/homeassistant/components/mobile_app/translations/zh-Hant.json @@ -13,5 +13,6 @@ "action_type": { "notify": "\u50b3\u9001\u901a\u77e5" } - } + }, + "title": "\u624b\u6a5f App" } \ No newline at end of file diff --git a/homeassistant/components/monoprice/translations/hu.json b/homeassistant/components/monoprice/translations/hu.json index 892b8b2cd91..a845f862160 100644 --- a/homeassistant/components/monoprice/translations/hu.json +++ b/homeassistant/components/monoprice/translations/hu.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/monoprice/translations/id.json b/homeassistant/components/monoprice/translations/id.json new file mode 100644 index 00000000000..bf4269c492e --- /dev/null +++ b/homeassistant/components/monoprice/translations/id.json @@ -0,0 +1,40 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "port": "Port", + "source_1": "Nama sumber #1", + "source_2": "Nama sumber #2", + "source_3": "Nama sumber #3", + "source_4": "Nama sumber #4", + "source_5": "Nama sumber #5", + "source_6": "Nama sumber #6" + }, + "title": "Hubungkan ke perangkat" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Nama sumber #1", + "source_2": "Nama sumber #2", + "source_3": "Nama sumber #3", + "source_4": "Nama sumber #4", + "source_5": "Nama sumber #5", + "source_6": "Nama sumber #6" + }, + "title": "Konfigurasikan sumber" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/monoprice/translations/nl.json b/homeassistant/components/monoprice/translations/nl.json index 74bc677dbe8..28ecedab3d3 100644 --- a/homeassistant/components/monoprice/translations/nl.json +++ b/homeassistant/components/monoprice/translations/nl.json @@ -4,13 +4,13 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "unknown": "Onverwachte fout" }, "step": { "user": { "data": { - "port": "Seri\u00eble poort", + "port": "Poort", "source_1": "Naam van bron #1", "source_2": "Naam van bron #2", "source_3": "Naam van bron #3", diff --git a/homeassistant/components/moon/translations/sensor.id.json b/homeassistant/components/moon/translations/sensor.id.json new file mode 100644 index 00000000000..197bc609813 --- /dev/null +++ b/homeassistant/components/moon/translations/sensor.id.json @@ -0,0 +1,14 @@ +{ + "state": { + "moon__phase": { + "first_quarter": "Seperempat pertama", + "full_moon": "Bulan purnama", + "last_quarter": "Seperempat ketiga", + "new_moon": "Bulan baru", + "waning_crescent": "Bulan sabit tua", + "waning_gibbous": "Bulan cembung tua", + "waxing_crescent": "Bulan sabit muda", + "waxing_gibbous": "Bulan cembung muda" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/bg.json b/homeassistant/components/motion_blinds/translations/bg.json new file mode 100644 index 00000000000..39f706036fd --- /dev/null +++ b/homeassistant/components/motion_blinds/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "select": { + "data": { + "select_ip": "IP \u0430\u0434\u0440\u0435\u0441" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/hu.json b/homeassistant/components/motion_blinds/translations/hu.json new file mode 100644 index 00000000000..541cefd2110 --- /dev/null +++ b/homeassistant/components/motion_blinds/translations/hu.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "connection_error": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "connect": { + "data": { + "api_key": "API kulcs" + } + }, + "select": { + "data": { + "select_ip": "IP c\u00edm" + } + }, + "user": { + "data": { + "api_key": "API kulcs", + "host": "IP c\u00edm" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/id.json b/homeassistant/components/motion_blinds/translations/id.json new file mode 100644 index 00000000000..9248531a751 --- /dev/null +++ b/homeassistant/components/motion_blinds/translations/id.json @@ -0,0 +1,37 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "connection_error": "Gagal terhubung" + }, + "error": { + "discovery_error": "Gagal menemukan Motion Gateway" + }, + "flow_title": "Motion Blinds", + "step": { + "connect": { + "data": { + "api_key": "Kunci API" + }, + "description": "Anda akan memerlukan Kunci API 16 karakter, baca petunjuknya di https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", + "title": "Motion Blinds" + }, + "select": { + "data": { + "select_ip": "Alamat IP" + }, + "description": "Jalankan penyiapan lagi jika ingin menghubungkan Motion Gateway lainnya", + "title": "Pilih Motion Gateway yang ingin dihubungkan" + }, + "user": { + "data": { + "api_key": "Kunci API", + "host": "Alamat IP" + }, + "description": "Hubungkan ke Motion Gateway Anda, jika alamat IP tidak disetel, penemuan otomatis akan digunakan", + "title": "Motion Blinds" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/ko.json b/homeassistant/components/motion_blinds/translations/ko.json index 9d2f0eead3d..69ed2cd7b35 100644 --- a/homeassistant/components/motion_blinds/translations/ko.json +++ b/homeassistant/components/motion_blinds/translations/ko.json @@ -5,22 +5,32 @@ "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "connection_error": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, + "error": { + "discovery_error": "Motion \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \ucc3e\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "flow_title": "Motion Blinds", "step": { "connect": { "data": { "api_key": "API \ud0a4" - } + }, + "description": "16\uac1c\uc758 \ubb38\uc790\uc5f4\ub85c \uad6c\uc131\ub41c API Key\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "title": "Motion Blinds" }, "select": { "data": { "select_ip": "IP \uc8fc\uc18c" - } + }, + "description": "Motion \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \ucd94\uac00 \uc5f0\uacb0\ud558\ub824\uba74 \uc124\uc815\uc744 \ub2e4\uc2dc \uc2e4\ud589\ud574\uc8fc\uc138\uc694", + "title": "\uc5f0\uacb0\ud560 Motion \uac8c\uc774\ud2b8\uc6e8\uc774 \uc120\ud0dd\ud558\uae30" }, "user": { "data": { "api_key": "API \ud0a4", "host": "IP \uc8fc\uc18c" - } + }, + "description": "Motion \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud569\ub2c8\ub2e4. IP \uc8fc\uc18c\uac00 \uc124\uc815\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \uc790\ub3d9 \uac80\uc0c9\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4", + "title": "Motion Blinds" } } } diff --git a/homeassistant/components/mqtt/translations/bg.json b/homeassistant/components/mqtt/translations/bg.json index 7a470b74272..96343a7f87a 100644 --- a/homeassistant/components/mqtt/translations/bg.json +++ b/homeassistant/components/mqtt/translations/bg.json @@ -21,8 +21,8 @@ "data": { "discovery": "\u0410\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e\u0442\u043e \u043e\u0442\u043a\u0440\u0438\u0432\u0430\u043d\u0435 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" }, - "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 Home Assistant \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0436\u0435 \u0441 MQTT \u0431\u0440\u043e\u043a\u0435\u0440\u0430 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d \u043e\u0442 hass.io \u0434\u043e\u0431\u0430\u0432\u043a\u0430\u0442\u0430 {addon}?", - "title": "MQTT \u0431\u0440\u043e\u043a\u0435\u0440 \u0447\u0440\u0435\u0437 Hass.io \u0434\u043e\u0431\u0430\u0432\u043a\u0430" + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 Home Assistant \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0436\u0435 \u0441 MQTT \u0431\u0440\u043e\u043a\u0435\u0440\u0430 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d \u043e\u0442 Supervisor \u0434\u043e\u0431\u0430\u0432\u043a\u0430\u0442\u0430 {addon}?", + "title": "MQTT \u0431\u0440\u043e\u043a\u0435\u0440 \u0447\u0440\u0435\u0437 Supervisor \u0434\u043e\u0431\u0430\u0432\u043a\u0430" } } } diff --git a/homeassistant/components/mqtt/translations/ca.json b/homeassistant/components/mqtt/translations/ca.json index f72ee30cdcf..23b7cd5dfa9 100644 --- a/homeassistant/components/mqtt/translations/ca.json +++ b/homeassistant/components/mqtt/translations/ca.json @@ -21,8 +21,8 @@ "data": { "discovery": "Habilitar descobriment autom\u00e0tic" }, - "description": "Vols configurar Home Assistant perqu\u00e8 es connecti amb el broker MQTT proporcionat pel complement de Hass.io: {addon}?", - "title": "Broker MQTT a trav\u00e9s del complement de Hass.io" + "description": "Vols configurar Home Assistant perqu\u00e8 es connecti amb el broker MQTT proporcionat pel complement de Hass.io {addon}?", + "title": "Broker MQTT via complement de Hass.io" } } }, diff --git a/homeassistant/components/mqtt/translations/cs.json b/homeassistant/components/mqtt/translations/cs.json index 60c323d9051..9876e2509b3 100644 --- a/homeassistant/components/mqtt/translations/cs.json +++ b/homeassistant/components/mqtt/translations/cs.json @@ -21,8 +21,8 @@ "data": { "discovery": "Povolit automatick\u00e9 vyhled\u00e1v\u00e1n\u00ed za\u0159\u00edzen\u00ed" }, - "description": "Chcete nakonfigurovat Home Assistant pro p\u0159ipojen\u00ed k MQTT poskytovan\u00e9mu dopl\u0148kem {addon} z Hass.io?", - "title": "MQTT Broker prost\u0159ednictv\u00edm dopl\u0148ku Hass.io" + "description": "Chcete nakonfigurovat Home Assistant pro p\u0159ipojen\u00ed k MQTT poskytovan\u00e9mu dopl\u0148kem {addon} z Supervisor?", + "title": "MQTT Broker prost\u0159ednictv\u00edm dopl\u0148ku Supervisor" } } }, diff --git a/homeassistant/components/mqtt/translations/da.json b/homeassistant/components/mqtt/translations/da.json index 7ff0f2b0a70..9b853a2dae2 100644 --- a/homeassistant/components/mqtt/translations/da.json +++ b/homeassistant/components/mqtt/translations/da.json @@ -21,8 +21,8 @@ "data": { "discovery": "Aktiv\u00e9r opdagelse" }, - "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til MQTT-brokeren, der leveres af hass.io-tilf\u00f8jelsen {addon}?", - "title": "MQTT-broker via Hass.io-tilf\u00f8jelse" + "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til MQTT-brokeren, der leveres af Supervisor-tilf\u00f8jelsen {addon}?", + "title": "MQTT-broker via Supervisor-tilf\u00f8jelse" } } }, diff --git a/homeassistant/components/mqtt/translations/de.json b/homeassistant/components/mqtt/translations/de.json index 3346abfd53e..4b57249eb38 100644 --- a/homeassistant/components/mqtt/translations/de.json +++ b/homeassistant/components/mqtt/translations/de.json @@ -21,8 +21,8 @@ "data": { "discovery": "Suche aktivieren" }, - "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass er eine Verbindung mit dem MQTT-Broker herstellt, der vom Hass.io Add-on {addon} bereitgestellt wird?", - "title": "MQTT Broker per Hass.io add-on" + "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass er eine Verbindung mit dem MQTT-Broker herstellt, der vom Supervisor Add-on {addon} bereitgestellt wird?", + "title": "MQTT Broker per Supervisor add-on" } } }, diff --git a/homeassistant/components/mqtt/translations/es-419.json b/homeassistant/components/mqtt/translations/es-419.json index d2ddc6691d1..a69be795f77 100644 --- a/homeassistant/components/mqtt/translations/es-419.json +++ b/homeassistant/components/mqtt/translations/es-419.json @@ -21,8 +21,8 @@ "data": { "discovery": "Habilitar descubrimiento" }, - "description": "\u00bfDesea configurar el Asistente del Hogar para que se conecte al broker MQTT proporcionado por el complemento hass.io {addon}?", - "title": "MQTT Broker a trav\u00e9s del complemento Hass.io" + "description": "\u00bfDesea configurar el Asistente del Hogar para que se conecte al broker MQTT proporcionado por el complemento Supervisor {addon}?", + "title": "MQTT Broker a trav\u00e9s del complemento Supervisor" } } }, diff --git a/homeassistant/components/mqtt/translations/es.json b/homeassistant/components/mqtt/translations/es.json index d36cfbc9694..70107efa269 100644 --- a/homeassistant/components/mqtt/translations/es.json +++ b/homeassistant/components/mqtt/translations/es.json @@ -21,8 +21,8 @@ "data": { "discovery": "Habilitar descubrimiento" }, - "description": "\u00bfQuieres configurar Home Assistant para conectar con el broker de MQTT proporcionado por el complemento Hass.io {addon}?", - "title": "MQTT Broker a trav\u00e9s del complemento Hass.io" + "description": "\u00bfQuieres configurar Home Assistant para conectar con el broker de MQTT proporcionado por el complemento Supervisor {addon}?", + "title": "MQTT Broker a trav\u00e9s del complemento Supervisor" } } }, diff --git a/homeassistant/components/mqtt/translations/et.json b/homeassistant/components/mqtt/translations/et.json index d2b863cab46..f28b1f4f94e 100644 --- a/homeassistant/components/mqtt/translations/et.json +++ b/homeassistant/components/mqtt/translations/et.json @@ -21,8 +21,8 @@ "data": { "discovery": "Luba automaatne avastamine" }, - "description": "Kas soovite seadistada Home Assistanti \u00fchenduse loomiseks Hass.io lisandmooduli {addon} pakutava MQTT vahendajaga?", - "title": "MQTT vahendaja Hass.io pistikprogrammi kaudu" + "description": "Kas soovid seadistada Home Assistanti \u00fchenduse loomiseks Hass.io lisandmooduli {addon} pakutava MQTT vahendajaga?", + "title": "MQTT vahendaja Hass.io lisandmooduli abil" } } }, diff --git a/homeassistant/components/mqtt/translations/fr.json b/homeassistant/components/mqtt/translations/fr.json index 574db2d2faf..6ee3788725d 100644 --- a/homeassistant/components/mqtt/translations/fr.json +++ b/homeassistant/components/mqtt/translations/fr.json @@ -21,8 +21,8 @@ "data": { "discovery": "Activer la d\u00e9couverte" }, - "description": "Vous voulez configurer Home Assistant pour vous connecter au broker MQTT fourni par l\u2019Add-on hass.io {addon} ?", - "title": "MQTT Broker via le module compl\u00e9mentaire Hass.io" + "description": "Voulez-vous configurer Home Assistant pour qu'il se connecte au courtier MQTT fourni par le module compl\u00e9mentaire Hass.io {addon} ?", + "title": "Courtier MQTT via le module compl\u00e9mentaire Hass.io" } } }, diff --git a/homeassistant/components/mqtt/translations/hu.json b/homeassistant/components/mqtt/translations/hu.json index 8cc6aa0857f..f265789d777 100644 --- a/homeassistant/components/mqtt/translations/hu.json +++ b/homeassistant/components/mqtt/translations/hu.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Csak egyetlen MQTT konfigur\u00e1ci\u00f3 megengedett." + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "error": { - "cannot_connect": "Nem siker\u00fclt csatlakozni a br\u00f3kerhez." + "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { "broker": { @@ -21,8 +21,8 @@ "data": { "discovery": "Felfedez\u00e9s enged\u00e9lyez\u00e9se" }, - "description": "Be szeretn\u00e9d konfigru\u00e1lni, hogy a Home Assistant a(z) {addon} Hass.io add-on \u00e1ltal biztos\u00edtott MQTT br\u00f3kerhez csatlakozzon?", - "title": "MQTT Br\u00f3ker a Hass.io b\u0151v\u00edtm\u00e9nnyel" + "description": "Be szeretn\u00e9d konfigru\u00e1lni, hogy a Home Assistant a(z) {addon} Supervisor add-on \u00e1ltal biztos\u00edtott MQTT br\u00f3kerhez csatlakozzon?", + "title": "MQTT Br\u00f3ker a Supervisor b\u0151v\u00edtm\u00e9nnyel" } } }, @@ -47,5 +47,20 @@ "button_short_release": "\"{subtype}\" felengedve", "button_triple_press": "\"{subtype}\" tripla kattint\u00e1s" } + }, + "options": { + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "broker": { + "data": { + "broker": "Br\u00f3ker", + "password": "Jelsz\u00f3", + "port": "Port", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/id.json b/homeassistant/components/mqtt/translations/id.json index e21052a501f..2a3171456c8 100644 --- a/homeassistant/components/mqtt/translations/id.json +++ b/homeassistant/components/mqtt/translations/id.json @@ -1,20 +1,72 @@ { "config": { "abort": { - "single_instance_allowed": "Hanya satu konfigurasi MQTT yang diizinkan." + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." }, "error": { - "cannot_connect": "Tidak dapat terhubung ke broker." + "cannot_connect": "Gagal terhubung" }, "step": { "broker": { "data": { "broker": "Broker", - "password": "Kata sandi", + "discovery": "Aktifkan penemuan", + "password": "Kata Sandi", "port": "Port", - "username": "Nama pengguna" + "username": "Nama Pengguna" }, - "description": "Harap masukkan informasi koneksi dari broker MQTT Anda." + "description": "Masukkan informasi koneksi broker MQTT Anda." + }, + "hassio_confirm": { + "data": { + "discovery": "Aktifkan penemuan" + }, + "description": "Ingin mengonfigurasi Home Assistant untuk terhubung ke broker MQTT yang disediakan oleh add-on Supervisor {addon}?", + "title": "MQTT Broker via add-on Supervisor" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Tombol pertama", + "button_2": "Tombol kedua", + "button_3": "Tombol ketiga", + "button_4": "Tombol keempat", + "button_5": "Tombol kelima", + "button_6": "Tombol keenam", + "turn_off": "Matikan", + "turn_on": "Nyalakan" + }, + "trigger_type": { + "button_double_press": "\"{subtype}\" diklik dua kali", + "button_long_press": "\"{subtype}\" terus ditekan", + "button_long_release": "\"{subtype}\" dilepaskan setelah ditekan lama", + "button_quadruple_press": "\"{subtype}\" diklik empat kali", + "button_quintuple_press": "\"{subtype}\" diklik lima kali", + "button_short_press": "\"{subtype}\" ditekan", + "button_short_release": "\"{subtype}\" dilepas", + "button_triple_press": "\"{subtype}\" diklik tiga kali" + } + }, + "options": { + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "broker": { + "data": { + "broker": "Broker", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + }, + "description": "Masukkan informasi koneksi broker MQTT Anda." + }, + "options": { + "data": { + "discovery": "Aktifkan penemuan" + }, + "description": "Pilih opsi MQTT." } } } diff --git a/homeassistant/components/mqtt/translations/ko.json b/homeassistant/components/mqtt/translations/ko.json index fd79863fadf..f10c9bbf7e9 100644 --- a/homeassistant/components/mqtt/translations/ko.json +++ b/homeassistant/components/mqtt/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" @@ -21,8 +21,8 @@ "data": { "discovery": "\uae30\uae30 \uac80\uc0c9 \ud65c\uc131\ud654" }, - "description": "Hass.io {addon} \uc560\ub4dc\uc628\uc5d0\uc11c \uc81c\uacf5\ub41c MQTT \ube0c\ub85c\ucee4\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant \ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Hass.io \uc560\ub4dc\uc628\uc758 MQTT \ube0c\ub85c\ucee4" + "description": "Hass.io {addon} \ucd94\uac00 \uae30\ub2a5\uc5d0\uc11c \uc81c\uacf5\ub41c MQTT \ube0c\ub85c\ucee4\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Hass.io \ucd94\uac00 \uae30\ub2a5\uc758 MQTT \ube0c\ub85c\ucee4" } } }, @@ -38,14 +38,14 @@ "turn_on": "\ucf1c\uae30" }, "trigger_type": { - "button_double_press": "\"{subtype}\" \uc774 \ub450 \ubc88 \ub20c\ub9b4 \ub54c", - "button_long_press": "\"{subtype}\" \uc774 \uacc4\uc18d \ub20c\ub824\uc9c8 \ub54c", - "button_long_release": "\"{subtype}\" \uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc190\uc744 \ub5c4 \ub54c", - "button_quadruple_press": "\"{subtype}\" \uc774 \ub124 \ubc88 \ub20c\ub9b4 \ub54c", - "button_quintuple_press": "\"{subtype}\" \uc774 \ub2e4\uc12f \ubc88 \ub20c\ub9b4 \ub54c", - "button_short_press": "\"{subtype}\" \uc774 \ub20c\ub9b4 \ub54c", - "button_short_release": "\"{subtype}\" \uc5d0\uc11c \uc190\uc744 \ub5c4 \ub54c", - "button_triple_press": "\"{subtype}\" \uc774 \uc138 \ubc88 \ub20c\ub9b4 \ub54c" + "button_double_press": "\"{subtype}\"\uc774(\uac00) \ub450 \ubc88 \ub20c\ub838\uc744 \ub54c", + "button_long_press": "\"{subtype}\"\uc774(\uac00) \uacc4\uc18d \ub20c\ub838\uc744 \ub54c", + "button_long_release": "\"{subtype}\"\uc774(\uac00) \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \ub5bc\uc600\uc744 \ub54c", + "button_quadruple_press": "\"{subtype}\"\uc774(\uac00) \ub124 \ubc88 \ub20c\ub838\uc744 \ub54c", + "button_quintuple_press": "\"{subtype}\"\uc774(\uac00) \ub2e4\uc12f \ubc88 \ub20c\ub838\uc744 \ub54c", + "button_short_press": "\"{subtype}\"\uc774(\uac00) \ub20c\ub838\uc744 \ub54c", + "button_short_release": "\"{subtype}\"\uc5d0\uc11c \uc190\uc744 \ub5bc\uc5c8\uc744 \ub54c", + "button_triple_press": "\"{subtype}\"\uc774(\uac00) \uc138 \ubc88 \ub20c\ub838\uc744 \ub54c" } }, "options": { @@ -66,12 +66,13 @@ }, "options": { "data": { + "birth_enable": "Birth \uba54\uc2dc\uc9c0 \ud65c\uc131\ud654\ud558\uae30", "birth_payload": "Birth \uba54\uc2dc\uc9c0 \ud398\uc774\ub85c\ub4dc", "birth_qos": "Birth \uba54\uc2dc\uc9c0 QoS", "birth_retain": "Birth \uba54\uc2dc\uc9c0 \ub9ac\ud14c\uc778", "birth_topic": "Birth \uba54\uc2dc\uc9c0 \ud1a0\ud53d", - "discovery": "\uc7a5\uce58 \uac80\uc0c9 \ud65c\uc131\ud654", - "will_enable": "Will \uba54\uc2dc\uc9c0 \ud65c\uc131\ud654", + "discovery": "\uae30\uae30 \uac80\uc0c9 \ud65c\uc131\ud654\ud558\uae30", + "will_enable": "Will \uba54\uc2dc\uc9c0 \ud65c\uc131\ud654\ud558\uae30", "will_payload": "Will \uba54\uc2dc\uc9c0 \ud398\uc774\ub85c\ub4dc", "will_qos": "Will \uba54\uc2dc\uc9c0 QoS", "will_retain": "Will \uba54\uc2dc\uc9c0 \ub9ac\ud14c\uc778", diff --git a/homeassistant/components/mqtt/translations/lb.json b/homeassistant/components/mqtt/translations/lb.json index 88a2664cd37..fd9cd351858 100644 --- a/homeassistant/components/mqtt/translations/lb.json +++ b/homeassistant/components/mqtt/translations/lb.json @@ -21,8 +21,8 @@ "data": { "discovery": "Entdeckung aktiv\u00e9ieren" }, - "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mam MQTT broker ze verbannen dee vum hass.io add-on {addon} bereet gestallt g\u00ebtt?", - "title": "MQTT Broker via Hass.io add-on" + "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mam MQTT broker ze verbannen dee vum Supervisor add-on {addon} bereet gestallt g\u00ebtt?", + "title": "MQTT Broker via Supervisor add-on" } } }, diff --git a/homeassistant/components/mqtt/translations/nl.json b/homeassistant/components/mqtt/translations/nl.json index 3b3ebf9fe3b..8395b1db7e9 100644 --- a/homeassistant/components/mqtt/translations/nl.json +++ b/homeassistant/components/mqtt/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Slechts \u00e9\u00e9n configuratie van MQTT is toegestaan." + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "error": { - "cannot_connect": "Kan geen verbinding maken met de broker." + "cannot_connect": "Kan geen verbinding maken" }, "step": { "broker": { @@ -21,8 +21,8 @@ "data": { "discovery": "Detectie inschakelen" }, - "description": "Wilt u Home Assistant configureren om verbinding te maken met de MQTT-broker die wordt aangeboden door de hass.io add-on {addon} ?", - "title": "MQTTT Broker via Hass.io add-on" + "description": "Wilt u Home Assistant configureren om verbinding te maken met de MQTT-broker die wordt aangeboden door de Supervisor add-on {addon} ?", + "title": "MQTT Broker via Supervisor add-on" } } }, @@ -66,7 +66,8 @@ "birth_enable": "Geboortebericht inschakelen", "birth_payload": "Birth message payload", "birth_topic": "Birth message onderwerp" - } + }, + "description": "Selecteer MQTT-opties." } } } diff --git a/homeassistant/components/mqtt/translations/no.json b/homeassistant/components/mqtt/translations/no.json index 12c72603a1f..586c62dac6a 100644 --- a/homeassistant/components/mqtt/translations/no.json +++ b/homeassistant/components/mqtt/translations/no.json @@ -21,8 +21,8 @@ "data": { "discovery": "Aktiver oppdagelse" }, - "description": "Vil du konfigurere Home Assistant til \u00e5 koble til en MQTT megler som er levert av Hass.io-tillegg {addon}?", - "title": "MQTT megler via Hass.io-tillegg" + "description": "Vil du konfigurere Home Assistant til \u00e5 koble til MQTT-megleren levert av Hass.io-tillegget {addon} ?", + "title": "MQTT Megler via Hass.io-tillegg" } } }, diff --git a/homeassistant/components/mqtt/translations/pl.json b/homeassistant/components/mqtt/translations/pl.json index 08b1d2f1974..287f0165d96 100644 --- a/homeassistant/components/mqtt/translations/pl.json +++ b/homeassistant/components/mqtt/translations/pl.json @@ -22,7 +22,7 @@ "discovery": "W\u0142\u0105cz wykrywanie" }, "description": "Czy chcesz skonfigurowa\u0107 Home Assistanta, aby po\u0142\u0105czy\u0142 si\u0119 z po\u015brednikiem MQTT dostarczonym przez dodatek Hass.io {addon}?", - "title": "Po\u015brednik MQTT za po\u015brednictwem dodatku Hass.io" + "title": "Po\u015brednik MQTT przez dodatek Hass.io" } } }, diff --git a/homeassistant/components/mqtt/translations/pt-BR.json b/homeassistant/components/mqtt/translations/pt-BR.json index de739963cae..ef9fad14440 100644 --- a/homeassistant/components/mqtt/translations/pt-BR.json +++ b/homeassistant/components/mqtt/translations/pt-BR.json @@ -21,8 +21,8 @@ "data": { "discovery": "Ativar descoberta" }, - "description": "Deseja configurar o Home Assistant para se conectar ao broker MQTT fornecido pelo complemento hass.io {addon}?", - "title": "MQTT Broker via add-on Hass.io" + "description": "Deseja configurar o Home Assistant para se conectar ao broker MQTT fornecido pelo complemento Supervisor {addon}?", + "title": "MQTT Broker via add-on Supervisor" } } }, diff --git a/homeassistant/components/mqtt/translations/pt.json b/homeassistant/components/mqtt/translations/pt.json index 606997038b2..209c33cf165 100644 --- a/homeassistant/components/mqtt/translations/pt.json +++ b/homeassistant/components/mqtt/translations/pt.json @@ -21,8 +21,8 @@ "data": { "discovery": "Ativar descoberta" }, - "description": "Deseja configurar o Home Assistant para se ligar ao broker MQTT fornecido pelo add-on hass.io {addon}?", - "title": "MQTT Broker atrav\u00e9s do add-on Hass.io" + "description": "Deseja configurar o Home Assistant para se ligar ao broker MQTT fornecido pelo add-on Supervisor {addon}?", + "title": "MQTT Broker atrav\u00e9s do add-on Supervisor" } } }, diff --git a/homeassistant/components/mqtt/translations/ro.json b/homeassistant/components/mqtt/translations/ro.json index 2292b58d01d..a98818be937 100644 --- a/homeassistant/components/mqtt/translations/ro.json +++ b/homeassistant/components/mqtt/translations/ro.json @@ -22,7 +22,7 @@ "discovery": "Activa\u021bi descoperirea" }, "description": "Dori\u021bi s\u0103 configura\u021bi Home Assistant pentru a se conecta la brokerul MQTT furnizat de addon-ul {addon} ?", - "title": "MQTT Broker, prin intermediul Hass.io add-on" + "title": "MQTT Broker, prin intermediul Supervisor add-on" } } } diff --git a/homeassistant/components/mqtt/translations/sl.json b/homeassistant/components/mqtt/translations/sl.json index afd2f3b8000..9f16209d524 100644 --- a/homeassistant/components/mqtt/translations/sl.json +++ b/homeassistant/components/mqtt/translations/sl.json @@ -21,8 +21,8 @@ "data": { "discovery": "Omogo\u010di odkrivanje" }, - "description": "Ali \u017eelite konfigurirati Home Assistant za povezavo s posrednikom MQTT, ki ga ponuja dodatek Hass.io {addon} ?", - "title": "MQTT Broker prek dodatka Hass.io" + "description": "Ali \u017eelite konfigurirati Home Assistant za povezavo s posrednikom MQTT, ki ga ponuja dodatek Supervisor {addon} ?", + "title": "MQTT Broker prek dodatka Supervisor" } } }, diff --git a/homeassistant/components/mqtt/translations/sv.json b/homeassistant/components/mqtt/translations/sv.json index c74979bb6bb..b3088ca49a9 100644 --- a/homeassistant/components/mqtt/translations/sv.json +++ b/homeassistant/components/mqtt/translations/sv.json @@ -21,8 +21,8 @@ "data": { "discovery": "Aktivera uppt\u00e4ckt" }, - "description": "Vill du konfigurera Home Assistant att ansluta till den MQTT-broker som tillhandah\u00e5lls av Hass.io-till\u00e4gget \"{addon}\"?", - "title": "MQTT Broker via Hass.io till\u00e4gg" + "description": "Vill du konfigurera Home Assistant att ansluta till den MQTT-broker som tillhandah\u00e5lls av Supervisor-till\u00e4gget \"{addon}\"?", + "title": "MQTT Broker via Supervisor till\u00e4gg" } } }, diff --git a/homeassistant/components/mqtt/translations/uk.json b/homeassistant/components/mqtt/translations/uk.json index f871db4aa9d..b8cbab32b14 100644 --- a/homeassistant/components/mqtt/translations/uk.json +++ b/homeassistant/components/mqtt/translations/uk.json @@ -21,8 +21,8 @@ "data": { "discovery": "\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u0410\u0432\u0442\u043e\u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432" }, - "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0431\u0440\u043e\u043a\u0435\u0440\u0430 MQTT (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io \"{addon}\")?", - "title": "\u0411\u0440\u043e\u043a\u0435\u0440 MQTT (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io)" + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0431\u0440\u043e\u043a\u0435\u0440\u0430 MQTT (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Supervisor \"{addon}\")?", + "title": "\u0411\u0440\u043e\u043a\u0435\u0440 MQTT (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Supervisor)" } } }, diff --git a/homeassistant/components/mqtt/translations/zh-Hans.json b/homeassistant/components/mqtt/translations/zh-Hans.json index 63ceded5654..97356ed44d4 100644 --- a/homeassistant/components/mqtt/translations/zh-Hans.json +++ b/homeassistant/components/mqtt/translations/zh-Hans.json @@ -21,8 +21,8 @@ "data": { "discovery": "\u542f\u7528\u53d1\u73b0" }, - "description": "\u662f\u5426\u8981\u914d\u7f6e Home Assistant \u8fde\u63a5\u5230 Hass.io \u52a0\u8f7d\u9879 {addon} \u63d0\u4f9b\u7684 MQTT \u670d\u52a1\u5668\uff1f", - "title": "\u6765\u81ea Hass.io \u52a0\u8f7d\u9879\u7684 MQTT \u670d\u52a1\u5668" + "description": "\u662f\u5426\u8981\u914d\u7f6e Home Assistant \u8fde\u63a5\u5230 Supervisor \u52a0\u8f7d\u9879 {addon} \u63d0\u4f9b\u7684 MQTT \u670d\u52a1\u5668\uff1f", + "title": "\u6765\u81ea Supervisor \u52a0\u8f7d\u9879\u7684 MQTT \u670d\u52a1\u5668" } } }, diff --git a/homeassistant/components/mqtt/translations/zh-Hant.json b/homeassistant/components/mqtt/translations/zh-Hant.json index bfb27361889..807de2e2c09 100644 --- a/homeassistant/components/mqtt/translations/zh-Hant.json +++ b/homeassistant/components/mqtt/translations/zh-Hant.json @@ -21,8 +21,8 @@ "data": { "discovery": "\u958b\u555f\u641c\u5c0b" }, - "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u9023\u7dda\u81f3 Hass.io \u9644\u52a0\u6574\u5408 {addon} \u4e4b MQTT broker\uff1f", - "title": "\u4f7f\u7528 Hass.io \u9644\u52a0\u7d44\u4ef6 MQTT Broker" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u9023\u7dda\u81f3 Hass.io \u9644\u52a0\u5143\u4ef6 {addon} \u4e4b MQTT broker\uff1f", + "title": "\u4f7f\u7528 Hass.io \u9644\u52a0\u5143\u4ef6 MQTT Broker" } } }, diff --git a/homeassistant/components/mullvad/translations/bg.json b/homeassistant/components/mullvad/translations/bg.json new file mode 100644 index 00000000000..a84e1c3bfdf --- /dev/null +++ b/homeassistant/components/mullvad/translations/bg.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/es.json b/homeassistant/components/mullvad/translations/es.json index d6a17561c3d..579726b061e 100644 --- a/homeassistant/components/mullvad/translations/es.json +++ b/homeassistant/components/mullvad/translations/es.json @@ -1,7 +1,19 @@ { "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Fallo al conectar", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", + "unknown": "Error inesperado" + }, "step": { "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Usuario" + }, "description": "\u00bfConfigurar la integraci\u00f3n VPN de Mullvad?" } } diff --git a/homeassistant/components/mullvad/translations/hu.json b/homeassistant/components/mullvad/translations/hu.json new file mode 100644 index 00000000000..0abcc301f0c --- /dev/null +++ b/homeassistant/components/mullvad/translations/hu.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/id.json b/homeassistant/components/mullvad/translations/id.json new file mode 100644 index 00000000000..a5409549f19 --- /dev/null +++ b/homeassistant/components/mullvad/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Siapkan integrasi VPN Mullvad?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/ko.json b/homeassistant/components/mullvad/translations/ko.json new file mode 100644 index 00000000000..fd9134b977c --- /dev/null +++ b/homeassistant/components/mullvad/translations/ko.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "Mullvad VPN \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/pl.json b/homeassistant/components/mullvad/translations/pl.json new file mode 100644 index 00000000000..f5aca4e092c --- /dev/null +++ b/homeassistant/components/mullvad/translations/pl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Skonfigurowa\u0107 integracj\u0119 Mullvad VPN?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/pt.json b/homeassistant/components/mullvad/translations/pt.json new file mode 100644 index 00000000000..561c8d77287 --- /dev/null +++ b/homeassistant/components/mullvad/translations/pt.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Servidor", + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/zh-Hans.json b/homeassistant/components/mullvad/translations/zh-Hans.json new file mode 100644 index 00000000000..acb02a7d0f6 --- /dev/null +++ b/homeassistant/components/mullvad/translations/zh-Hans.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u8bbe\u5907\u5df2\u7ecf\u5b8c\u6210\u914d\u7f6e" + }, + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25", + "invalid_auth": "\u9a8c\u8bc1\u5931\u8d25", + "unknown": "\u9884\u671f\u5916\u7684\u9519\u8bef" + }, + "step": { + "user": { + "data": { + "password": "\u5bc6\u7801", + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/myq/translations/he.json b/homeassistant/components/myq/translations/he.json new file mode 100644 index 00000000000..ac90b3264ea --- /dev/null +++ b/homeassistant/components/myq/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/myq/translations/hu.json b/homeassistant/components/myq/translations/hu.json index dee4ed9ee0f..9c5b90e7447 100644 --- a/homeassistant/components/myq/translations/hu.json +++ b/homeassistant/components/myq/translations/hu.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/myq/translations/id.json b/homeassistant/components/myq/translations/id.json new file mode 100644 index 00000000000..2cc790d15e0 --- /dev/null +++ b/homeassistant/components/myq/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Hubungkan ke MyQ Gateway" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/bg.json b/homeassistant/components/mysensors/translations/bg.json new file mode 100644 index 00000000000..854e88b38b9 --- /dev/null +++ b/homeassistant/components/mysensors/translations/bg.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "gw_serial": { + "data": { + "device": "\u0421\u0435\u0440\u0438\u0435\u043d \u043f\u043e\u0440\u0442" + } + }, + "gw_tcp": { + "data": { + "tcp_port": "\u043f\u043e\u0440\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/hu.json b/homeassistant/components/mysensors/translations/hu.json new file mode 100644 index 00000000000..5faaf7aea56 --- /dev/null +++ b/homeassistant/components/mysensors/translations/hu.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "invalid_device": "\u00c9rv\u00e9nytelen eszk\u00f6z", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "error": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "gw_serial": { + "data": { + "version": "MySensors verzi\u00f3" + } + }, + "gw_tcp": { + "data": { + "tcp_port": "port", + "version": "MySensors verzi\u00f3" + } + } + } + }, + "title": "MySensors" +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/id.json b/homeassistant/components/mysensors/translations/id.json new file mode 100644 index 00000000000..e982250b09c --- /dev/null +++ b/homeassistant/components/mysensors/translations/id.json @@ -0,0 +1,75 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "duplicate_persistence_file": "File persistensi sudah digunakan", + "duplicate_topic": "Topik sudah digunakan", + "invalid_auth": "Autentikasi tidak valid", + "invalid_device": "Perangkat tidak valid", + "invalid_ip": "Alamat IP tidak valid", + "invalid_persistence_file": "File persistensi tidak valid", + "invalid_port": "Nomor port tidak valid", + "invalid_serial": "Port serial tidak valid", + "invalid_subscribe_topic": "Topik langganan tidak valid", + "invalid_version": "Versi MySensors tidak valid", + "not_a_number": "Masukkan angka", + "port_out_of_range": "Nilai port minimal 1 dan maksimal 65535", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "duplicate_persistence_file": "File persistensi sudah digunakan", + "duplicate_topic": "Topik sudah digunakan", + "invalid_auth": "Autentikasi tidak valid", + "invalid_device": "Perangkat tidak valid", + "invalid_ip": "Alamat IP tidak valid", + "invalid_persistence_file": "File persistensi tidak valid", + "invalid_port": "Nomor port tidak valid", + "invalid_serial": "Port serial tidak valid", + "invalid_subscribe_topic": "Topik langganan tidak valid", + "invalid_version": "Versi MySensors tidak valid", + "not_a_number": "Masukkan angka", + "port_out_of_range": "Nilai port minimal 1 dan maksimal 65535", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "gw_mqtt": { + "data": { + "persistence_file": "file persistensi (kosongkan untuk dihasilkan otomatis)", + "retain": "mqtt retain", + "topic_in_prefix": "prefiks untuk topik input (topic_in_prefix)", + "topic_out_prefix": "prefiks untuk topik output (topic_out_prefix)", + "version": "Versi MySensors" + }, + "description": "Penyiapan gateway MQTT" + }, + "gw_serial": { + "data": { + "baud_rate": "tingkat baud", + "device": "Port serial", + "persistence_file": "file persistensi (kosongkan untuk dihasilkan otomatis)", + "version": "Versi MySensors" + }, + "description": "Penyiapan gateway serial" + }, + "gw_tcp": { + "data": { + "device": "Alamat IP gateway", + "persistence_file": "file persistensi (kosongkan untuk dihasilkan otomatis)", + "tcp_port": "port", + "version": "Versi MySensors" + }, + "description": "Pengaturan gateway Ethernet" + }, + "user": { + "data": { + "gateway_type": "Jenis gateway" + }, + "description": "Pilih metode koneksi ke gateway" + } + } + }, + "title": "MySensors" +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/ko.json b/homeassistant/components/mysensors/translations/ko.json index bb38f94bc92..e57f60aafbf 100644 --- a/homeassistant/components/mysensors/translations/ko.json +++ b/homeassistant/components/mysensors/translations/ko.json @@ -3,14 +3,77 @@ "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "duplicate_persistence_file": "\uc9c0\uc18d\uc131 \ud30c\uc77c\uc774 \uc774\ubbf8 \uc0ac\uc6a9 \uc911\uc785\ub2c8\ub2e4", + "duplicate_topic": "\ud1a0\ud53d\uc774 \uc774\ubbf8 \uc0ac\uc6a9 \uc911\uc785\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_device": "\uae30\uae30\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_ip": "IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_persistence_file": "\uc9c0\uc18d\uc131 \ud30c\uc77c\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_port": "\ud3ec\ud2b8 \ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_publish_topic": "\ubc1c\ud589 \ud1a0\ud53d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_serial": "\uc2dc\ub9ac\uc5bc \ud3ec\ud2b8\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_subscribe_topic": "\uad6c\ub3c5 \ud1a0\ud53d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_version": "MySensors \ubc84\uc804\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "not_a_number": "\uc22b\uc790\ub85c \uc785\ub825\ud574\uc8fc\uc138\uc694", + "port_out_of_range": "\ud3ec\ud2b8 \ubc88\ud638\ub294 1 \uc774\uc0c1 65535 \uc774\ud558\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4", + "same_topic": "\uad6c\ub3c5 \ubc0f \ubc1c\ud589 \ud1a0\ud53d\uc740 \ub3d9\uc77c\ud569\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "duplicate_persistence_file": "\uc9c0\uc18d\uc131 \ud30c\uc77c\uc774 \uc774\ubbf8 \uc0ac\uc6a9 \uc911\uc785\ub2c8\ub2e4", + "duplicate_topic": "\ud1a0\ud53d\uc774 \uc774\ubbf8 \uc0ac\uc6a9 \uc911\uc785\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_device": "\uae30\uae30\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_ip": "IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_persistence_file": "\uc9c0\uc18d\uc131 \ud30c\uc77c\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_port": "\ud3ec\ud2b8 \ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_publish_topic": "\ubc1c\ud589 \ud1a0\ud53d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_serial": "\uc2dc\ub9ac\uc5bc \ud3ec\ud2b8\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_subscribe_topic": "\uad6c\ub3c5 \ud1a0\ud53d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_version": "MySensors \ubc84\uc804\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "not_a_number": "\uc22b\uc790\ub85c \uc785\ub825\ud574\uc8fc\uc138\uc694", + "port_out_of_range": "\ud3ec\ud2b8 \ubc88\ud638\ub294 1 \uc774\uc0c1 65535 \uc774\ud558\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4", + "same_topic": "\uad6c\ub3c5 \ubc0f \ubc1c\ud589 \ud1a0\ud53d\uc740 \ub3d9\uc77c\ud569\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "gw_mqtt": { + "data": { + "persistence_file": "\uc9c0\uc18d\uc131 \ud30c\uc77c(\uc790\ub3d9\uc73c\ub85c \uc0dd\uc131\ud558\ub824\uba74 \ube44\uc6cc\ub450\uc138\uc694)", + "retain": "mqtt retain", + "topic_in_prefix": "\uc785\ub825 \ud1a0\ud53d\uc758 \uc811\ub450\uc0ac (topic_in_prefix)", + "topic_out_prefix": "\ucd9c\ub825 \ud1a0\ud53d\uc758 \uc811\ub450\uc0ac (topic_out_prefix)", + "version": "MySensors \ubc84\uc804" + }, + "description": "MQTT \uac8c\uc774\ud2b8\uc6e8\uc774 \uc124\uc815" + }, + "gw_serial": { + "data": { + "baud_rate": "\uc804\uc1a1 \uc18d\ub3c4", + "device": "\uc2dc\ub9ac\uc5bc \ud3ec\ud2b8", + "persistence_file": "\uc9c0\uc18d\uc131 \ud30c\uc77c(\uc790\ub3d9\uc73c\ub85c \uc0dd\uc131\ud558\ub824\uba74 \ube44\uc6cc\ub450\uc138\uc694)", + "version": "MySensors \ubc84\uc804" + }, + "description": "\uc2dc\ub9ac\uc5bc \uac8c\uc774\ud2b8\uc6e8\uc774 \uc124\uc815" + }, + "gw_tcp": { + "data": { + "device": "\uac8c\uc774\ud2b8\uc6e8\uc774\uc758 IP \uc8fc\uc18c", + "persistence_file": "\uc9c0\uc18d\uc131 \ud30c\uc77c(\uc790\ub3d9\uc73c\ub85c \uc0dd\uc131\ud558\ub824\uba74 \ube44\uc6cc\ub450\uc138\uc694)", + "tcp_port": "\ud3ec\ud2b8", + "version": "MySensors \ubc84\uc804" + }, + "description": "\uc774\ub354\ub137 \uac8c\uc774\ud2b8\uc6e8\uc774 \uc124\uc815" + }, + "user": { + "data": { + "gateway_type": "\uac8c\uc774\ud2b8\uc6e8\uc774 \uc720\ud615" + }, + "description": "\uac8c\uc774\ud2b8\uc6e8\uc774 \uc5f0\uacb0 \ubc29\ubc95\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694" + } } - } + }, + "title": "MySensors" } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/nl.json b/homeassistant/components/mysensors/translations/nl.json index e41f67c7730..49ddf987cef 100644 --- a/homeassistant/components/mysensors/translations/nl.json +++ b/homeassistant/components/mysensors/translations/nl.json @@ -3,13 +3,16 @@ "abort": { "already_configured": "Apparaat is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken", + "duplicate_persistence_file": "Persistentiebestand al in gebruik", "duplicate_topic": "Topic is al in gebruik", "invalid_auth": "Ongeldige authenticatie", "invalid_device": "Ongeldig apparaat", "invalid_ip": "Ongeldig IP-adres", + "invalid_persistence_file": "Ongeldig persistentiebestand", "invalid_port": "Ongeldig poortnummer", "invalid_publish_topic": "Ongeldig publiceer topic", "invalid_serial": "Ongeldige seri\u00eble poort", + "invalid_subscribe_topic": "Ongeldig abonneeronderwerp", "invalid_version": "Ongeldige MySensors-versie", "not_a_number": "Voer een nummer in", "port_out_of_range": "Poortnummer moet minimaal 1 en maximaal 65535 zijn", diff --git a/homeassistant/components/neato/translations/de.json b/homeassistant/components/neato/translations/de.json index 4c2fc456873..272191a4221 100644 --- a/homeassistant/components/neato/translations/de.json +++ b/homeassistant/components/neato/translations/de.json @@ -20,7 +20,7 @@ "title": "W\u00e4hle die Authentifizierungsmethode" }, "reauth_confirm": { - "title": "M\u00f6chtest du mit der Einrichtung beginnen?" + "title": "M\u00f6chten Sie mit der Einrichtung beginnen?" }, "user": { "data": { diff --git a/homeassistant/components/neato/translations/he.json b/homeassistant/components/neato/translations/he.json new file mode 100644 index 00000000000..6f4191da70d --- /dev/null +++ b/homeassistant/components/neato/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/neato/translations/hu.json b/homeassistant/components/neato/translations/hu.json index f2fd30f323e..3cb6ffd3364 100644 --- a/homeassistant/components/neato/translations/hu.json +++ b/homeassistant/components/neato/translations/hu.json @@ -1,15 +1,26 @@ { "config": { "abort": { - "already_configured": "M\u00e1r konfigur\u00e1lva van", - "reauth_successful": "Az \u00fajb\u00f3li azonos\u00edt\u00e1s sikeres" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" }, "create_entry": { - "default": "L\u00e1sd: [Neato dokument\u00e1ci\u00f3] ( {docs_url} )." + "default": "Sikeres hiteles\u00edt\u00e9s" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { + "pick_implementation": { + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + }, "reauth_confirm": { - "title": "El akarja kezdeni a be\u00e1ll\u00edt\u00e1st?" + "title": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?" }, "user": { "data": { @@ -17,9 +28,10 @@ "username": "Felhaszn\u00e1l\u00f3n\u00e9v", "vendor": "Sz\u00e1ll\u00edt\u00f3" }, - "description": "L\u00e1sd: [Neato dokument\u00e1ci\u00f3] ( {docs_url} ).", + "description": "L\u00e1sd: [Neato dokument\u00e1ci\u00f3]({docs_url}).", "title": "Neato Fi\u00f3kinform\u00e1ci\u00f3" } } - } + }, + "title": "Neato Botvac" } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/id.json b/homeassistant/components/neato/translations/id.json new file mode 100644 index 00000000000..17eee515787 --- /dev/null +++ b/homeassistant/components/neato/translations/id.json @@ -0,0 +1,37 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "invalid_auth": "Autentikasi tidak valid", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + }, + "reauth_confirm": { + "title": "Ingin memulai penyiapan?" + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna", + "vendor": "Vendor" + }, + "description": "Baca [dokumentasi Neato]({docs_url}).", + "title": "Info Akun Neato" + } + } + }, + "title": "Neato Botvac" +} \ No newline at end of file diff --git a/homeassistant/components/neato/translations/ko.json b/homeassistant/components/neato/translations/ko.json index 359aeefcc78..d08000871ea 100644 --- a/homeassistant/components/neato/translations/ko.json +++ b/homeassistant/components/neato/translations/ko.json @@ -32,5 +32,6 @@ "title": "Neato \uacc4\uc815 \uc815\ubcf4" } } - } + }, + "title": "Neato Botvac" } \ No newline at end of file diff --git a/homeassistant/components/nest/translations/ca.json b/homeassistant/components/nest/translations/ca.json index 08d8cb97454..bc19d5c2c7c 100644 --- a/homeassistant/components/nest/translations/ca.json +++ b/homeassistant/components/nest/translations/ca.json @@ -30,7 +30,7 @@ "data": { "code": "Codi PIN" }, - "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.", + "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": { diff --git a/homeassistant/components/nest/translations/en.json b/homeassistant/components/nest/translations/en.json index f45c9ea489f..d7b000d921f 100644 --- a/homeassistant/components/nest/translations/en.json +++ b/homeassistant/components/nest/translations/en.json @@ -30,7 +30,7 @@ "data": { "code": "PIN Code" }, - "description": "To link your Nest account, [authorize your account]({url}).\n\nAfter authorization, copy-paste the provided pin code below.", + "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": { diff --git a/homeassistant/components/nest/translations/et.json b/homeassistant/components/nest/translations/et.json index 7d22dfd96bf..1319bc2ca4b 100644 --- a/homeassistant/components/nest/translations/et.json +++ b/homeassistant/components/nest/translations/et.json @@ -30,7 +30,7 @@ "data": { "code": "PIN kood" }, - "description": "Nest-i konto linkimiseks [authorize your account] ({url}).\n\nP\u00e4rast autoriseerimist kopeeri allolev PIN kood.", + "description": "Nest-i konto linkimiseks [authorize your account] ({url}).\n\nP\u00e4rast autoriseerimist kopeeri ja kleebi allolev PIN kood.", "title": "Lingi Nesti konto" }, "pick_implementation": { diff --git a/homeassistant/components/nest/translations/fr.json b/homeassistant/components/nest/translations/fr.json index 2830bf5da87..ce716fb3083 100644 --- a/homeassistant/components/nest/translations/fr.json +++ b/homeassistant/components/nest/translations/fr.json @@ -30,7 +30,7 @@ "data": { "code": "Code PIN" }, - "description": "Pour associer votre compte Nest, [autorisez votre compte]({url}). \n\n Apr\u00e8s autorisation, copiez-collez le code PIN fourni ci-dessous.", + "description": "Pour associer votre compte Nest, [autorisez votre compte]({url}). \n\n Apr\u00e8s autorisation, copiez-collez le code NIP fourni ci-dessous.", "title": "Lier un compte Nest" }, "pick_implementation": { diff --git a/homeassistant/components/nest/translations/hu.json b/homeassistant/components/nest/translations/hu.json index 47334c4aa62..9400ea7875c 100644 --- a/homeassistant/components/nest/translations/hu.json +++ b/homeassistant/components/nest/translations/hu.json @@ -3,34 +3,42 @@ "abort": { "authorize_url_fail": "Ismeretlen hiba t\u00f6rt\u00e9nt a hiteles\u00edt\u00e9si link gener\u00e1l\u00e1sa sor\u00e1n.", "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s az \u00e9rv\u00e9nyes\u00edt\u00e9si url gener\u00e1l\u00e1sa sor\u00e1n.", - "reauth_successful": "Az \u00fajb\u00f3li azonos\u00edt\u00e1s sikeres" + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "unknown_authorize_url_generation": "Ismeretlen hiba t\u00f6rt\u00e9nt a hiteles\u00edt\u00e9si link gener\u00e1l\u00e1sa sor\u00e1n." }, "create_entry": { - "default": "Sikeres autentik\u00e1ci\u00f3" + "default": "Sikeres hiteles\u00edt\u00e9s" }, "error": { "internal_error": "Bels\u0151 hiba t\u00f6rt\u00e9nt a k\u00f3d valid\u00e1l\u00e1s\u00e1n\u00e1l", - "invalid_pin": "\u00c9rv\u00e9nytelen ", + "invalid_pin": "\u00c9rv\u00e9nytelen PIN-k\u00f3d", "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a k\u00f3d \u00e9rv\u00e9nyes\u00edt\u00e9se sor\u00e1n.", - "unknown": "Ismeretlen hiba t\u00f6rt\u00e9nt a k\u00f3d \u00e9rv\u00e9nyes\u00edt\u00e9se sor\u00e1n" + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "init": { "data": { "flow_impl": "Szolg\u00e1ltat\u00f3" }, - "description": "V\u00e1laszd ki, hogy melyik hiteles\u00edt\u00e9si szolg\u00e1ltat\u00f3n\u00e1l szeretn\u00e9d hiteles\u00edteni a Nestet.", + "description": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert", "title": "Hiteles\u00edt\u00e9si Szolg\u00e1ltat\u00f3" }, "link": { "data": { "code": "PIN-k\u00f3d" }, - "description": "A Nest-fi\u00f3k \u00f6sszekapcsol\u00e1s\u00e1hoz [enged\u00e9lyezze fi\u00f3kj\u00e1t] ( {url} ). \n\n Az enged\u00e9lyez\u00e9s ut\u00e1n m\u00e1solja be az al\u00e1bbi PIN k\u00f3dot.", + "description": "A Nest-fi\u00f3k \u00f6sszekapcsol\u00e1s\u00e1hoz [enged\u00e9lyezze fi\u00f3kj\u00e1t]({url}). \n\nAz enged\u00e9lyez\u00e9s ut\u00e1n m\u00e1solja be az PIN k\u00f3dot.", "title": "Nest fi\u00f3k \u00f6sszekapcsol\u00e1sa" }, + "pick_implementation": { + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + }, "reauth_confirm": { - "title": "Integr\u00e1ci\u00f3 \u00fajb\u00f3li azonos\u00edt\u00e1sa" + "description": "A Nest integr\u00e1ci\u00f3nak \u00fajra kell hiteles\u00edtenie a fi\u00f3kodat", + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" } } }, diff --git a/homeassistant/components/nest/translations/id.json b/homeassistant/components/nest/translations/id.json index 4fc229c0d53..757c53e2866 100644 --- a/homeassistant/components/nest/translations/id.json +++ b/homeassistant/components/nest/translations/id.json @@ -2,28 +2,52 @@ "config": { "abort": { "authorize_url_fail": "Kesalahan tidak dikenal terjadi ketika menghasilkan URL otorisasi.", - "authorize_url_timeout": "Waktu tunggu menghasilkan otorisasi url telah habis." + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})", + "reauth_successful": "Autentikasi ulang berhasil", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "unknown_authorize_url_generation": "Kesalahan tidak dikenal ketika menghasilkan URL otorisasi." + }, + "create_entry": { + "default": "Berhasil diautentikasi" }, "error": { - "internal_error": "Kesalahan Internal memvalidasi kode", - "timeout": "Waktu tunggu memvalidasi kode telah habis.", - "unknown": "Error tidak diketahui saat memvalidasi kode" + "internal_error": "Kesalahan internal saat memvalidasi kode", + "invalid_pin": "Invalid Kode PIN", + "timeout": "Tenggang waktu memvalidasi kode telah habis.", + "unknown": "Kesalahan yang tidak diharapkan" }, "step": { "init": { "data": { "flow_impl": "Penyedia" }, - "description": "Pilih melalui penyedia autentikasi mana yang ingin Anda autentikasi dengan Nest.", - "title": "Penyedia Otentikasi" + "description": "Pilih Metode Autentikasi", + "title": "Penyedia Autentikasi" }, "link": { "data": { "code": "Kode PIN" }, - "description": "Untuk menautkan akun Nest Anda, [beri kuasa akun Anda] ( {url} ). \n\n Setelah otorisasi, salin-tempel kode pin yang disediakan di bawah ini.", - "title": "Hubungkan Akun Nest" + "description": "Untuk menautkan akun Nest Anda, [otorisasi akun Anda]({url}).\n\nSetelah otorisasi, salin dan tempel kode PIN yang disediakan di bawah ini.", + "title": "Tautkan Akun Nest" + }, + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + }, + "reauth_confirm": { + "description": "Integrasi Nest perlu mengautentikasi ulang akun Anda", + "title": "Autentikasi Ulang Integrasi" } } + }, + "device_automation": { + "trigger_type": { + "camera_motion": "Gerakan terdeteksi", + "camera_person": "Orang terdeteksi", + "camera_sound": "Suara terdeteksi", + "doorbell_chime": "Bel pintu ditekan" + } } } \ No newline at end of file diff --git a/homeassistant/components/nest/translations/ko.json b/homeassistant/components/nest/translations/ko.json index f5a0fcf39d1..f8d6de2244a 100644 --- a/homeassistant/components/nest/translations/ko.json +++ b/homeassistant/components/nest/translations/ko.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "authorize_url_fail": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", + "authorize_url_fail": "\uc778\uc99d URL\uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "unknown_authorize_url_generation": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4." }, "create_entry": { @@ -30,15 +30,24 @@ "data": { "code": "PIN \ucf54\ub4dc" }, - "description": "Nest \uacc4\uc815\uc744 \uc5f0\uacb0\ud558\ub824\uba74, [\uacc4\uc815 \uc5f0\uacb0 \uc2b9\uc778]({url}) \uc744 \ud574\uc8fc\uc138\uc694.\n\n\uc2b9\uc778 \ud6c4, \uc544\ub798\uc758 PIN \ucf54\ub4dc\ub97c \ubcf5\uc0ac\ud558\uc5ec \ubd99\uc5ec\ub123\uc73c\uc138\uc694.", + "description": "Nest \uacc4\uc815\uc744 \uc5f0\uacb0\ud558\ub824\uba74 [\uacc4\uc815 \uc5f0\uacb0 \uc2b9\uc778]({url})\uc744 \ud574\uc8fc\uc138\uc694.\n\n\uc2b9\uc778 \ud6c4 \uc544\ub798\uc5d0 \uc81c\uacf5\ub41c PIN \ucf54\ub4dc\ub97c \ubcf5\uc0ac\ud558\uc5ec \ubd99\uc5ec \ub123\uc5b4\uc8fc\uc138\uc694.", "title": "Nest \uacc4\uc815 \uc5f0\uacb0\ud558\uae30" }, "pick_implementation": { "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" }, "reauth_confirm": { - "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d" + "description": "Nest \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub294 \uacc4\uc815\uc744 \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4", + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" } } + }, + "device_automation": { + "trigger_type": { + "camera_motion": "\uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud588\uc744 \ub54c", + "camera_person": "\uc0ac\ub78c\uc744 \uac10\uc9c0\ud588\uc744 \ub54c", + "camera_sound": "\uc18c\ub9ac\ub97c \uac10\uc9c0\ud588\uc744 \ub54c", + "doorbell_chime": "\ucd08\uc778\uc885\uc774 \ub20c\ub838\uc744 \ub54c" + } } } \ No newline at end of file diff --git a/homeassistant/components/nest/translations/nl.json b/homeassistant/components/nest/translations/nl.json index 387b1effcb0..b4a965f4955 100644 --- a/homeassistant/components/nest/translations/nl.json +++ b/homeassistant/components/nest/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "authorize_url_fail": "Onbekende fout bij het genereren van een autoriseer-URL.", - "authorize_url_timeout": "Toestemming voor het genereren van autoriseer-url.", + "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", "reauth_successful": "Herauthenticatie was succesvol", @@ -16,19 +16,19 @@ "internal_error": "Interne foutvalidatiecode", "invalid_pin": "Ongeldige PIN-code", "timeout": "Time-out validatie van code", - "unknown": "Onbekende foutvalidatiecode" + "unknown": "Onverwachte fout" }, "step": { "init": { "data": { "flow_impl": "Leverancier" }, - "description": "Kies met welke authenticatieleverancier u wilt verifi\u00ebren met Nest.", + "description": "Kies een authenticatie methode", "title": "Authenticatieleverancier" }, "link": { "data": { - "code": "Pincode" + "code": "PIN-code" }, "description": "Als je je Nest-account wilt koppelen, [autoriseer je account] ( {url} ). \n\nNa autorisatie, kopieer en plak de voorziene pincode hieronder.", "title": "Koppel Nest-account" diff --git a/homeassistant/components/nest/translations/no.json b/homeassistant/components/nest/translations/no.json index 14166f7d9a6..d6b6c89bcaa 100644 --- a/homeassistant/components/nest/translations/no.json +++ b/homeassistant/components/nest/translations/no.json @@ -30,7 +30,7 @@ "data": { "code": "PIN kode" }, - "description": "For \u00e5 koble din Nest-konto m\u00e5 du [bekrefte kontoen din]({url}). \n\nEtter bekreftelse, kopier og lim inn den oppgitte PIN koden nedenfor.", + "description": "For \u00e5 koble til Nest-kontoen din, [autoriser kontoen din] ( {url} ). \n\n Etter autorisasjon, kopier og lim inn den angitte PIN-koden nedenfor.", "title": "Koble til Nest konto" }, "pick_implementation": { diff --git a/homeassistant/components/nest/translations/ru.json b/homeassistant/components/nest/translations/ru.json index 4f2e8952566..07ac5246cbf 100644 --- a/homeassistant/components/nest/translations/ru.json +++ b/homeassistant/components/nest/translations/ru.json @@ -30,7 +30,7 @@ "data": { "code": "PIN-\u043a\u043e\u0434" }, - "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.", + "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 PIN-\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": { diff --git a/homeassistant/components/nest/translations/sv.json b/homeassistant/components/nest/translations/sv.json index 0abe4d75fac..cddb9e2fe79 100644 --- a/homeassistant/components/nest/translations/sv.json +++ b/homeassistant/components/nest/translations/sv.json @@ -25,5 +25,10 @@ "title": "L\u00e4nka Nest-konto" } } + }, + "device_automation": { + "trigger_type": { + "camera_motion": "R\u00f6relse uppt\u00e4ckt" + } } } \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/de.json b/homeassistant/components/netatmo/translations/de.json index 0be425d1e31..247e9e8b931 100644 --- a/homeassistant/components/netatmo/translations/de.json +++ b/homeassistant/components/netatmo/translations/de.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "Abwesenheit", + "hg": "Frostw\u00e4chter", + "schedule": "Zeitplan" + }, + "trigger_type": { + "alarm_started": "{entity_name} hat einen Alarm erkannt", + "animal": "{entity_name} hat ein Tier erkannt", + "cancel_set_point": "{entity_name} hat seinen Zeitplan wieder aufgenommen", + "human": "{entity_name} hat einen Menschen erkannt", + "movement": "{entity_name} hat eine Bewegung erkannt", + "outdoor": "{entity_name} hat ein Ereignis im Freien erkannt", + "person": "{entity_name} hat eine Person erkannt", + "person_away": "{entity_name} hat erkannt, dass eine Person gegangen ist", + "set_point": "Solltemperatur {entity_name} manuell eingestellt", + "therm_mode": "{entity_name} wechselte zu \"{subtype}\"", + "turned_off": "{entity_name} ausgeschaltet", + "turned_on": "{entity_name} eingeschaltet", + "vehicle": "{entity_name} hat ein Fahrzeug erkannt" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/hu.json b/homeassistant/components/netatmo/translations/hu.json index cae1f6d20c0..b3140a6f115 100644 --- a/homeassistant/components/netatmo/translations/hu.json +++ b/homeassistant/components/netatmo/translations/hu.json @@ -2,7 +2,9 @@ "config": { "abort": { "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", - "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t." + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "create_entry": { "default": "Sikeres hiteles\u00edt\u00e9s" @@ -12,5 +14,29 @@ "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" } } + }, + "device_automation": { + "trigger_subtype": { + "away": "t\u00e1vol" + }, + "trigger_type": { + "turned_off": "{entity_name} ki lett kapcsolva", + "turned_on": "{entity_name} be lett kapcsolva" + } + }, + "options": { + "step": { + "public_weather": { + "data": { + "area_name": "A ter\u00fclet neve" + } + }, + "public_weather_areas": { + "data": { + "new_area": "Ter\u00fclet neve", + "weather_areas": "Id\u0151j\u00e1r\u00e1si ter\u00fcletek" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/id.json b/homeassistant/components/netatmo/translations/id.json new file mode 100644 index 00000000000..6812d45816b --- /dev/null +++ b/homeassistant/components/netatmo/translations/id.json @@ -0,0 +1,64 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + } + } + }, + "device_automation": { + "trigger_subtype": { + "away": "keluar", + "schedule": "jadwal" + }, + "trigger_type": { + "alarm_started": "{entity_name} mendeteksi alarm", + "animal": "{entity_name} mendeteksi binatang", + "cancel_set_point": "{entity_name} telah melanjutkan jadwalnya", + "human": "{entity_name} mendeteksi manusia", + "movement": "{entity_name} mendeteksi gerakan", + "outdoor": "{entity_name} mendeteksi peristiwa luar ruangan", + "person": "{entity_name} mendeteksi seseorang", + "person_away": "{entity_name} mendeteksi seseorang telah pergi", + "set_point": "Suhu target {entity_name} disetel secara manual", + "therm_mode": "{entity_name} beralih ke \"{subtype}\"", + "turned_off": "{entity_name} dimatikan", + "turned_on": "{entity_name} dinyalakan", + "vehicle": "{entity_name} mendeteksi kendaraan" + } + }, + "options": { + "step": { + "public_weather": { + "data": { + "area_name": "Nama area", + "lat_ne": "Lintang Pojok Timur Laut", + "lat_sw": "Lintang Pojok Barat Daya", + "lon_ne": "Bujur Pojok Timur Laut", + "lon_sw": "Bujur Pojok Barat Daya", + "mode": "Perhitungan", + "show_on_map": "Tampilkan di peta" + }, + "description": "Konfigurasikan sensor cuaca publik untuk suatu area.", + "title": "Sensor cuaca publik Netatmo" + }, + "public_weather_areas": { + "data": { + "new_area": "Nama area", + "weather_areas": "Area cuaca" + }, + "description": "Konfigurasikan sensor cuaca publik.", + "title": "Sensor cuaca publik Netatmo" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/ko.json b/homeassistant/components/netatmo/translations/ko.json index 320df466515..fee370ce219 100644 --- a/homeassistant/components/netatmo/translations/ko.json +++ b/homeassistant/components/netatmo/translations/ko.json @@ -4,7 +4,7 @@ "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "create_entry": { "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "\uc678\ucd9c", + "hg": "\ub3d9\ud30c \ubc29\uc9c0", + "schedule": "\uc77c\uc815" + }, + "trigger_type": { + "alarm_started": "{entity_name}\uc774(\uac00) \uc54c\ub78c\uc744 \uac10\uc9c0\ud588\uc744 \ub54c", + "animal": "{entity_name}\uc774(\uac00) \ub3d9\ubb3c\uc744 \uac10\uc9c0\ud588\uc744 \ub54c", + "cancel_set_point": "{entity_name}\uc774(\uac00) \uc77c\uc815\uc744 \uc7ac\uac1c\ud560 \ub54c", + "human": "{entity_name}\uc774(\uac00) \uc0ac\ub78c\uc744 \uac10\uc9c0\ud588\uc744 \ub54c", + "movement": "{entity_name}\uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud588\uc744 \ub54c", + "outdoor": "{entity_name}\uc774(\uac00) \uc2e4\uc678\uc758 \uc774\ubca4\ud2b8\ub97c \uac10\uc9c0\ud588\uc744 \ub54c", + "person": "{entity_name}\uc774(\uac00) \uc0ac\ub78c\uc744 \uac10\uc9c0\ud588\uc744 \ub54c", + "person_away": "{entity_name}\uc774(\uac00) \uc0ac\ub78c\uc774 \ub5a0\ub0ac\uc74c\uc744 \uac10\uc9c0\ud588\uc744 \ub54c", + "set_point": "{entity_name}\uc758 \ubaa9\ud45c \uc628\ub3c4\uac00 \uc218\ub3d9\uc73c\ub85c \uc124\uc815\ub418\uc5c8\uc744 \ub54c", + "therm_mode": "{entity_name}\uc774(\uac00) {subtype}(\uc73c)\ub85c \uc804\ud658\ub418\uc5c8\uc744 \ub54c", + "turned_off": "{entity_name}\uc774(\uac00) \uaebc\uc84c\uc744 \ub54c", + "turned_on": "{entity_name}\uc774(\uac00) \ucf1c\uc84c\uc744 \ub54c", + "vehicle": "{entity_name}\uc774(\uac00) \ucc28\ub7c9\uc744 \uac10\uc9c0\ud588\uc744 \ub54c" + } + }, "options": { "step": { "public_weather": { @@ -27,16 +49,16 @@ "mode": "\uacc4\uc0b0\ud558\uae30", "show_on_map": "\uc9c0\ub3c4\uc5d0 \ud45c\uc2dc\ud558\uae30" }, - "description": "\uc9c0\uc5ed\uc5d0 \ub300\ud55c \uacf5\uc6a9 \ub0a0\uc528 \uc13c\uc11c\ub97c \uad6c\uc131\ud569\ub2c8\ub2e4.", - "title": "Netamo \uacf5\uc6a9 \ub0a0\uc528 \uc13c\uc11c" + "description": "\uc9c0\uc5ed\uc5d0 \ub300\ud55c \uacf5\uacf5 \uae30\uc0c1 \uc13c\uc11c\ub97c \uad6c\uc131\ud569\ub2c8\ub2e4.", + "title": "Netamo \uacf5\uacf5 \uae30\uc0c1 \uc13c\uc11c" }, "public_weather_areas": { "data": { "new_area": "\uc9c0\uc5ed \uc774\ub984", "weather_areas": "\ub0a0\uc528 \uc9c0\uc5ed" }, - "description": "\uacf5\uc6a9 \ub0a0\uc528 \uc13c\uc11c\ub97c \uad6c\uc131\ud569\ub2c8\ub2e4.", - "title": "Netamo \uacf5\uc6a9 \ub0a0\uc528 \uc13c\uc11c" + "description": "\uacf5\uacf5 \uae30\uc0c1 \uc13c\uc11c\ub97c \uad6c\uc131\ud569\ub2c8\ub2e4.", + "title": "Netamo \uacf5\uacf5 \uae30\uc0c1 \uc13c\uc11c" } } } diff --git a/homeassistant/components/netatmo/translations/nl.json b/homeassistant/components/netatmo/translations/nl.json index 0bdc3170a5a..53ff8bb5cb5 100644 --- a/homeassistant/components/netatmo/translations/nl.json +++ b/homeassistant/components/netatmo/translations/nl.json @@ -48,10 +48,13 @@ "lon_sw": "Lengtegraad Zuidwestelijke hoek", "mode": "Berekening", "show_on_map": "Toon op kaart" - } + }, + "description": "Configureer een openbare weersensor voor een gebied.", + "title": "Netatmo openbare weersensor" }, "public_weather_areas": { - "description": "Configureer openbare weersensoren." + "description": "Configureer openbare weersensoren.", + "title": "Netatmo openbare weersensor" } } } diff --git a/homeassistant/components/netatmo/translations/pl.json b/homeassistant/components/netatmo/translations/pl.json index b41689a9f8c..449e09bfa3a 100644 --- a/homeassistant/components/netatmo/translations/pl.json +++ b/homeassistant/components/netatmo/translations/pl.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "poza domem", + "hg": "ochrona przed mrozem", + "schedule": "harmonogram" + }, + "trigger_type": { + "alarm_started": "{entity_name} wykryje alarm", + "animal": "{entity_name} wykryje zwierz\u0119", + "cancel_set_point": "{entity_name} wznowi sw\u00f3j harmonogram", + "human": "{entity_name} wykryje cz\u0142owieka", + "movement": "{entity_name} wykryje ruch", + "outdoor": "{entity_name} wykryje zdarzenie zewn\u0119trzne", + "person": "{entity_name} wykryje osob\u0119", + "person_away": "{entity_name} wykryje, \u017ce osoba wysz\u0142a", + "set_point": "temperatura docelowa {entity_name} zosta\u0142a ustawiona r\u0119cznie", + "therm_mode": "{entity_name} prze\u0142\u0105czy\u0142(a) si\u0119 na \u201e{subtype}\u201d", + "turned_off": "{entity_name} zostanie wy\u0142\u0105czony", + "turned_on": "{entity_name} zostanie w\u0142\u0105czony", + "vehicle": "{entity_name} wykryje pojazd" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/zh-Hans.json b/homeassistant/components/netatmo/translations/zh-Hans.json new file mode 100644 index 00000000000..0e8f01ed8f7 --- /dev/null +++ b/homeassistant/components/netatmo/translations/zh-Hans.json @@ -0,0 +1,16 @@ +{ + "device_automation": { + "trigger_subtype": { + "away": "\u79bb\u5f00", + "schedule": "\u65e5\u7a0b" + }, + "trigger_type": { + "alarm_started": "{entity_name} \u68c0\u6d4b\u5230\u4e00\u4e2a\u95f9\u949f", + "animal": "{entity_name} \u68c0\u6d4b\u5230\u4e00\u4e2a\u52a8\u7269", + "human": "{entity_name} \u68c0\u6d4b\u5230\u4e00\u4e2a\u4eba", + "outdoor": "{entity_name} \u68c0\u6d4b\u5230\u4e00\u4e2a\u51fa\u95e8\u4e8b\u4ef6", + "person": "{entity_name} \u68c0\u6d4b\u5230\u4e00\u4e2a\u4eba", + "person_away": "{entity_name} \u68c0\u6d4b\u5230\u4e00\u4e2a\u4eba\u79bb\u5f00\u4e86" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/he.json b/homeassistant/components/nexia/translations/he.json new file mode 100644 index 00000000000..ac90b3264ea --- /dev/null +++ b/homeassistant/components/nexia/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/hu.json b/homeassistant/components/nexia/translations/hu.json index dee4ed9ee0f..7dedf459484 100644 --- a/homeassistant/components/nexia/translations/hu.json +++ b/homeassistant/components/nexia/translations/hu.json @@ -1,11 +1,20 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - } + }, + "title": "Csatlakoz\u00e1s a mynexia.com-hoz" } } } diff --git a/homeassistant/components/nexia/translations/id.json b/homeassistant/components/nexia/translations/id.json new file mode 100644 index 00000000000..e6900bdffa1 --- /dev/null +++ b/homeassistant/components/nexia/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Hubungkan ke mynexia.com" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/ko.json b/homeassistant/components/nexia/translations/ko.json index 170411f5f73..4bd1589e4e8 100644 --- a/homeassistant/components/nexia/translations/ko.json +++ b/homeassistant/components/nexia/translations/ko.json @@ -14,7 +14,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "title": "mynexia.com \uc5d0 \uc5f0\uacb0\ud558\uae30" + "title": "mynexia.com\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/nexia/translations/nl.json b/homeassistant/components/nexia/translations/nl.json index d718c78d7af..a9b4d99883e 100644 --- a/homeassistant/components/nexia/translations/nl.json +++ b/homeassistant/components/nexia/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Deze nexia-woning is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd" }, "error": { "cannot_connect": "Verbinding mislukt, probeer het opnieuw", diff --git a/homeassistant/components/nightscout/translations/hu.json b/homeassistant/components/nightscout/translations/hu.json index 3b2d79a34a7..459a879e82c 100644 --- a/homeassistant/components/nightscout/translations/hu.json +++ b/homeassistant/components/nightscout/translations/hu.json @@ -2,6 +2,20 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "Nightscout", + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "url": "URL" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/nightscout/translations/id.json b/homeassistant/components/nightscout/translations/id.json new file mode 100644 index 00000000000..75496084bc4 --- /dev/null +++ b/homeassistant/components/nightscout/translations/id.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Nightscout", + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "url": "URL" + }, + "description": "- URL: alamat instans nightscout Anda, misalnya https://myhomeassistant.duckdns.org:5423\n- Kunci API (Opsional): Hanya gunakan jika instans Anda dilindungi (auth_default_roles != dapat dibaca).", + "title": "Masukkan informasi server Nightscout Anda." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nightscout/translations/ko.json b/homeassistant/components/nightscout/translations/ko.json index 0408a1f61ab..1146e6926e3 100644 --- a/homeassistant/components/nightscout/translations/ko.json +++ b/homeassistant/components/nightscout/translations/ko.json @@ -8,12 +8,15 @@ "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, + "flow_title": "Nightscout", "step": { "user": { "data": { "api_key": "API \ud0a4", "url": "URL \uc8fc\uc18c" - } + }, + "description": "- URL: Nightscout \uc778\uc2a4\ud134\uc2a4\uc758 \uc8fc\uc18c. \uc608: https://myhomeassistant.duckdns.org:5423\n- API \ud0a4 (\uc120\ud0dd \uc0ac\ud56d): \uc778\uc2a4\ud134\uc2a4\uac00 \ubcf4\ud638\ub41c \uacbd\uc6b0\uc5d0\ub9cc \uc0ac\uc6a9\ud574\uc8fc\uc138\uc694 (auth_default_roles != readable).", + "title": "Nightscout \uc11c\ubc84 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." } } } diff --git a/homeassistant/components/notify/translations/hu.json b/homeassistant/components/notify/translations/hu.json index b5c88047f66..18413724b53 100644 --- a/homeassistant/components/notify/translations/hu.json +++ b/homeassistant/components/notify/translations/hu.json @@ -1,3 +1,3 @@ { - "title": "\u00c9rtes\u00edt" + "title": "\u00c9rtes\u00edt\u00e9sek" } \ No newline at end of file diff --git a/homeassistant/components/notify/translations/id.json b/homeassistant/components/notify/translations/id.json index 723b49fe6af..0ee3a77f9c1 100644 --- a/homeassistant/components/notify/translations/id.json +++ b/homeassistant/components/notify/translations/id.json @@ -1,3 +1,3 @@ { - "title": "Pemberitahuan" + "title": "Notifikasi" } \ No newline at end of file diff --git a/homeassistant/components/notion/translations/he.json b/homeassistant/components/notion/translations/he.json new file mode 100644 index 00000000000..3007c0e968c --- /dev/null +++ b/homeassistant/components/notion/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/notion/translations/hu.json b/homeassistant/components/notion/translations/hu.json index 6bf20925535..b4d57f83bb3 100644 --- a/homeassistant/components/notion/translations/hu.json +++ b/homeassistant/components/notion/translations/hu.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "no_devices": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a fi\u00f3kban" }, "step": { diff --git a/homeassistant/components/notion/translations/id.json b/homeassistant/components/notion/translations/id.json new file mode 100644 index 00000000000..35ee7a29544 --- /dev/null +++ b/homeassistant/components/notion/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid", + "no_devices": "Tidak ada perangkat yang ditemukan di akun" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Isi informasi Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/notion/translations/nl.json b/homeassistant/components/notion/translations/nl.json index 4b6597725ef..64894303cdf 100644 --- a/homeassistant/components/notion/translations/nl.json +++ b/homeassistant/components/notion/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Deze gebruikersnaam is al in gebruik." + "already_configured": "Account is al geconfigureerd" }, "error": { "invalid_auth": "Ongeldige authenticatie", diff --git a/homeassistant/components/nuheat/translations/he.json b/homeassistant/components/nuheat/translations/he.json new file mode 100644 index 00000000000..ac90b3264ea --- /dev/null +++ b/homeassistant/components/nuheat/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuheat/translations/hu.json b/homeassistant/components/nuheat/translations/hu.json index 8c523b72e04..e6e7174e325 100644 --- a/homeassistant/components/nuheat/translations/hu.json +++ b/homeassistant/components/nuheat/translations/hu.json @@ -1,8 +1,13 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { - "cannot_connect": "A csatlakoz\u00e1s nem siker\u00fclt, pr\u00f3b\u00e1lkozzon \u00fajra", - "invalid_thermostat": "A termoszt\u00e1t sorozatsz\u00e1ma \u00e9rv\u00e9nytelen." + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "invalid_thermostat": "A termoszt\u00e1t sorozatsz\u00e1ma \u00e9rv\u00e9nytelen.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "user": { diff --git a/homeassistant/components/nuheat/translations/id.json b/homeassistant/components/nuheat/translations/id.json new file mode 100644 index 00000000000..69041c7755d --- /dev/null +++ b/homeassistant/components/nuheat/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "invalid_thermostat": "Nomor seri termostat tidak valid.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "serial_number": "Nomor seri termostat.", + "username": "Nama Pengguna" + }, + "description": "Anda harus mendapatkan nomor seri atau ID numerik termostat Anda dengan masuk ke https://MyNuHeat.com dan memilih termostat Anda.", + "title": "Hubungkan ke NuHeat" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuheat/translations/ko.json b/homeassistant/components/nuheat/translations/ko.json index a533cd69093..b926d1f5269 100644 --- a/homeassistant/components/nuheat/translations/ko.json +++ b/homeassistant/components/nuheat/translations/ko.json @@ -16,8 +16,8 @@ "serial_number": "\uc628\ub3c4 \uc870\uc808\uae30\uc758 \uc2dc\ub9ac\uc5bc \ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "https://MyNuHeat.com \uc5d0 \ub85c\uadf8\uc778\ud558\uace0 \uc628\ub3c4 \uc870\uc808\uae30\ub97c \uc120\ud0dd\ud558\uc5ec \uc628\ub3c4 \uc870\uc808\uae30\uc758 \uc2dc\ub9ac\uc5bc \ubc88\ud638 \ub610\ub294 \ub610\ub294 ID \ub97c \uc5bb\uc5b4\uc57c \ud569\ub2c8\ub2e4.", - "title": "NuHeat \uc5d0 \uc5f0\uacb0\ud558\uae30" + "description": "https://MyNuHeat.com \uc5d0 \ub85c\uadf8\uc778\ud558\uace0 \uc628\ub3c4 \uc870\uc808\uae30\ub97c \uc120\ud0dd\ud558\uc5ec \uc628\ub3c4 \uc870\uc808\uae30\uc758 \uc2dc\ub9ac\uc5bc \ubc88\ud638 \ub610\ub294 \ub610\ub294 ID\ub97c \uc5bb\uc5b4\uc57c \ud569\ub2c8\ub2e4.", + "title": "NuHeat\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/nuheat/translations/nl.json b/homeassistant/components/nuheat/translations/nl.json index edf3ad17ff4..d7672832db0 100644 --- a/homeassistant/components/nuheat/translations/nl.json +++ b/homeassistant/components/nuheat/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "De thermostaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "invalid_thermostat": "Het serienummer van de thermostaat is ongeldig.", "unknown": "Onverwachte fout" diff --git a/homeassistant/components/nuki/translations/bg.json b/homeassistant/components/nuki/translations/bg.json new file mode 100644 index 00000000000..4983c9a14b2 --- /dev/null +++ b/homeassistant/components/nuki/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "\u041f\u043e\u0440\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/hu.json b/homeassistant/components/nuki/translations/hu.json new file mode 100644 index 00000000000..4f0b1a29738 --- /dev/null +++ b/homeassistant/components/nuki/translations/hu.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "port": "Port", + "token": "Hozz\u00e1f\u00e9r\u00e9si token" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/id.json b/homeassistant/components/nuki/translations/id.json new file mode 100644 index 00000000000..d9e5e1de2c3 --- /dev/null +++ b/homeassistant/components/nuki/translations/id.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "token": "Token Akses" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/de.json b/homeassistant/components/number/translations/de.json new file mode 100644 index 00000000000..5005eb5a010 --- /dev/null +++ b/homeassistant/components/number/translations/de.json @@ -0,0 +1,3 @@ +{ + "title": "Nummer" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/hu.json b/homeassistant/components/number/translations/hu.json new file mode 100644 index 00000000000..296b9b750a7 --- /dev/null +++ b/homeassistant/components/number/translations/hu.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "{entity_name} \u00e9rt\u00e9k\u00e9nek be\u00e1ll\u00edt\u00e1sa" + } + }, + "title": "Sz\u00e1m" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/id.json b/homeassistant/components/number/translations/id.json new file mode 100644 index 00000000000..6e928cb51cf --- /dev/null +++ b/homeassistant/components/number/translations/id.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "Tetapkan nilai untuk {entity_name}" + } + }, + "title": "Bilangan" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/ko.json b/homeassistant/components/number/translations/ko.json new file mode 100644 index 00000000000..9c642931408 --- /dev/null +++ b/homeassistant/components/number/translations/ko.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "{entity_name}\uc758 \uac12 \uc124\uc815\ud558\uae30" + } + }, + "title": "\uc22b\uc790" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/zh-Hans.json b/homeassistant/components/number/translations/zh-Hans.json new file mode 100644 index 00000000000..de9720ed77a --- /dev/null +++ b/homeassistant/components/number/translations/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "device_automation": { + "action_type": { + "set_value": "\u8bbe\u7f6e {entity_name} \u7684\u503c" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/ca.json b/homeassistant/components/nut/translations/ca.json index fa7b728e115..fc8b5b719a0 100644 --- a/homeassistant/components/nut/translations/ca.json +++ b/homeassistant/components/nut/translations/ca.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "unknown": "Error inesperat" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/de.json b/homeassistant/components/nut/translations/de.json index 50d37fa8ec4..990f21523b6 100644 --- a/homeassistant/components/nut/translations/de.json +++ b/homeassistant/components/nut/translations/de.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/et.json b/homeassistant/components/nut/translations/et.json index 27745bfeeae..83d793eb22c 100644 --- a/homeassistant/components/nut/translations/et.json +++ b/homeassistant/components/nut/translations/et.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "\u00dchendus nurjus", + "unknown": "Tundmatu viga" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/fr.json b/homeassistant/components/nut/translations/fr.json index d91bd1e4070..35739689425 100644 --- a/homeassistant/components/nut/translations/fr.json +++ b/homeassistant/components/nut/translations/fr.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "\u00c9chec de connexion", + "unknown": "Erreur inattendue" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/he.json b/homeassistant/components/nut/translations/he.json new file mode 100644 index 00000000000..ac90b3264ea --- /dev/null +++ b/homeassistant/components/nut/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/hu.json b/homeassistant/components/nut/translations/hu.json index 1ca56c7684f..a7bad455dc3 100644 --- a/homeassistant/components/nut/translations/hu.json +++ b/homeassistant/components/nut/translations/hu.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { @@ -10,5 +17,11 @@ } } } + }, + "options": { + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + } } } \ No newline at end of file diff --git a/homeassistant/components/nut/translations/id.json b/homeassistant/components/nut/translations/id.json new file mode 100644 index 00000000000..fc23e34fe8e --- /dev/null +++ b/homeassistant/components/nut/translations/id.json @@ -0,0 +1,50 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "resources": { + "data": { + "resources": "Sumber Daya" + }, + "title": "Pilih Sumber Daya untuk Dipantau" + }, + "ups": { + "data": { + "alias": "Alias", + "resources": "Sumber Daya" + }, + "title": "Pilih UPS untuk Dipantau" + }, + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + }, + "title": "Hubungkan ke server NUT" + } + } + }, + "options": { + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "init": { + "data": { + "resources": "Sumber Daya", + "scan_interval": "Interval Pindai (detik)" + }, + "description": "Pilih Sumber Daya Sensor." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/it.json b/homeassistant/components/nut/translations/it.json index 440cb421504..cb8949fca64 100644 --- a/homeassistant/components/nut/translations/it.json +++ b/homeassistant/components/nut/translations/it.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "Impossibile connettersi", + "unknown": "Errore imprevisto" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/ko.json b/homeassistant/components/nut/translations/ko.json index 0fb8339ddfc..a5680f4ed48 100644 --- a/homeassistant/components/nut/translations/ko.json +++ b/homeassistant/components/nut/translations/ko.json @@ -33,13 +33,17 @@ } }, "options": { + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, "step": { "init": { "data": { "resources": "\ub9ac\uc18c\uc2a4", "scan_interval": "\uc2a4\uce94 \uac04\uaca9 (\ucd08)" }, - "description": "\uc13c\uc11c \ub9ac\uc18c\uc2a4 \uc120\ud0dd" + "description": "\uc13c\uc11c \ub9ac\uc18c\uc2a4\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694." } } } diff --git a/homeassistant/components/nut/translations/nl.json b/homeassistant/components/nut/translations/nl.json index 5e4acf3574d..d90b75b4bcc 100644 --- a/homeassistant/components/nut/translations/nl.json +++ b/homeassistant/components/nut/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "unknown": "Onverwachte fout" }, "step": { @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "Verbinden mislukt", + "unknown": "Onverwachte fout" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/no.json b/homeassistant/components/nut/translations/no.json index 0af64ad4fa4..887d3b6d30a 100644 --- a/homeassistant/components/nut/translations/no.json +++ b/homeassistant/components/nut/translations/no.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "Tilkobling mislyktes", + "unknown": "Uventet feil" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/pl.json b/homeassistant/components/nut/translations/pl.json index 240b32bfe19..2686a98e697 100644 --- a/homeassistant/components/nut/translations/pl.json +++ b/homeassistant/components/nut/translations/pl.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/ru.json b/homeassistant/components/nut/translations/ru.json index 7a3f1c9b47e..5422704c96d 100644 --- a/homeassistant/components/nut/translations/ru.json +++ b/homeassistant/components/nut/translations/ru.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/zh-Hans.json b/homeassistant/components/nut/translations/zh-Hans.json index a5f4ff11f09..91522c7f609 100644 --- a/homeassistant/components/nut/translations/zh-Hans.json +++ b/homeassistant/components/nut/translations/zh-Hans.json @@ -1,11 +1,22 @@ { "config": { "step": { + "resources": { + "data": { + "resources": "\u8d44\u6e90" + } + }, "user": { "data": { "username": "\u7528\u6237\u540d" } } } + }, + "options": { + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25", + "unknown": "\u4e0d\u5728\u9884\u671f\u5185\u7684\u9519\u8bef" + } } } \ No newline at end of file diff --git a/homeassistant/components/nut/translations/zh-Hant.json b/homeassistant/components/nut/translations/zh-Hant.json index 7c65e836f9e..822d2e785f2 100644 --- a/homeassistant/components/nut/translations/zh-Hant.json +++ b/homeassistant/components/nut/translations/zh-Hant.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nws/translations/he.json b/homeassistant/components/nws/translations/he.json new file mode 100644 index 00000000000..4c49313d977 --- /dev/null +++ b/homeassistant/components/nws/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nws/translations/hu.json b/homeassistant/components/nws/translations/hu.json index 5f4f7bb8bee..1d674cacc7e 100644 --- a/homeassistant/components/nws/translations/hu.json +++ b/homeassistant/components/nws/translations/hu.json @@ -1,9 +1,18 @@ { "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { - "api_key": "API kulcs" + "api_key": "API kulcs", + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g" } } } diff --git a/homeassistant/components/nws/translations/id.json b/homeassistant/components/nws/translations/id.json new file mode 100644 index 00000000000..3c92ebaed64 --- /dev/null +++ b/homeassistant/components/nws/translations/id.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "longitude": "Bujur", + "station": "Kode stasiun METAR" + }, + "description": "Jika kode stasiun METAR tidak ditentukan, informasi lintang dan bujur akan digunakan untuk menemukan stasiun terdekat. Untuk saat ini, Kunci API bisa berupa nilai sebarang. Disarankan untuk menggunakan alamat email yang valid.", + "title": "Hubungkan ke National Weather Service" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nws/translations/ko.json b/homeassistant/components/nws/translations/ko.json index 9fbdf026558..6cf4ca0c9c5 100644 --- a/homeassistant/components/nws/translations/ko.json +++ b/homeassistant/components/nws/translations/ko.json @@ -15,7 +15,7 @@ "longitude": "\uacbd\ub3c4", "station": "METAR \uc2a4\ud14c\uc774\uc158 \ucf54\ub4dc" }, - "description": "METAR \uc2a4\ud14c\uc774\uc158 \ucf54\ub4dc\uac00 \uc9c0\uc815\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \uc704\ub3c4 \ubc0f \uacbd\ub3c4\uac00 \uac00\uc7a5 \uac00\uae4c\uc6b4 \uc2a4\ud14c\uc774\uc158\uc744 \ucc3e\ub294 \ub370 \uc0ac\uc6a9\ub429\ub2c8\ub2e4. \ud604\uc7ac API Key \ub294 \uc544\ubb34 \ud0a4\ub098 \ub123\uc5b4\ub3c4 \uc0c1\uad00 \uc5c6\uc2b5\ub2c8\ub2e4\ub9cc, \uc62c\ubc14\ub978 \uc774\uba54\uc77c \uc8fc\uc18c\ub97c \uc0ac\uc6a9\ud558\ub294 \uac83\uc774 \uad8c\uc7a5\ud569\ub2c8\ub2e4.", + "description": "METAR \uc2a4\ud14c\uc774\uc158 \ucf54\ub4dc\uac00 \uc9c0\uc815\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \uc704\ub3c4 \ubc0f \uacbd\ub3c4\uac00 \uac00\uc7a5 \uac00\uae4c\uc6b4 \uc2a4\ud14c\uc774\uc158\uc744 \ucc3e\ub294 \ub370 \uc0ac\uc6a9\ub429\ub2c8\ub2e4. \ud604\uc7ac API Key\ub294 \uc544\ubb34 \ud0a4\ub098 \ub123\uc5b4\ub3c4 \uc0c1\uad00\uc5c6\uc2b5\ub2c8\ub2e4\ub9cc, \uc62c\ubc14\ub978 \uc774\uba54\uc77c \uc8fc\uc18c\ub97c \uc0ac\uc6a9\ud558\ub294 \uac83\uc774 \uc88b\uc2b5\ub2c8\ub2e4.", "title": "\ubbf8\uad6d \uae30\uc0c1\uccad\uc5d0 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/nws/translations/nl.json b/homeassistant/components/nws/translations/nl.json index b74e6db96a2..c8e10dfba10 100644 --- a/homeassistant/components/nws/translations/nl.json +++ b/homeassistant/components/nws/translations/nl.json @@ -1,16 +1,16 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Service is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "unknown": "Onverwachte fout" }, "step": { "user": { "data": { - "api_key": "API-sleutel (e-mail)", + "api_key": "API-sleutel", "latitude": "Breedtegraad", "longitude": "Lengtegraad", "station": "METAR-zendercode" diff --git a/homeassistant/components/nzbget/translations/hu.json b/homeassistant/components/nzbget/translations/hu.json new file mode 100644 index 00000000000..9eee6cc3be6 --- /dev/null +++ b/homeassistant/components/nzbget/translations/hu.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "flow_title": "NZBGet: {name}", + "step": { + "user": { + "data": { + "host": "Hoszt", + "name": "N\u00e9v", + "password": "Jelsz\u00f3", + "port": "Port", + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" + }, + "title": "Csatlakoz\u00e1s az NZBGet-hez" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Friss\u00edt\u00e9si gyakoris\u00e1g (m\u00e1sodpercben)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nzbget/translations/id.json b/homeassistant/components/nzbget/translations/id.json new file mode 100644 index 00000000000..af096f4ef5f --- /dev/null +++ b/homeassistant/components/nzbget/translations/id.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "NZBGet: {name}", + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama", + "password": "Kata Sandi", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL", + "username": "Nama Pengguna", + "verify_ssl": "Verifikasi sertifikat SSL" + }, + "title": "Hubungkan ke NZBGet" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Frekuensi pembaruan (dalam detik)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nzbget/translations/ko.json b/homeassistant/components/nzbget/translations/ko.json index 58d38b66361..0de5bbb3dd8 100644 --- a/homeassistant/components/nzbget/translations/ko.json +++ b/homeassistant/components/nzbget/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { @@ -19,7 +19,7 @@ "username": "\uc0ac\uc6a9\uc790 \uc774\ub984", "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" }, - "title": "NZBGet\uc5d0 \uc5f0\uacb0" + "title": "NZBGet\uc5d0 \uc5f0\uacb0\ud558\uae30" } } }, @@ -27,7 +27,7 @@ "step": { "init": { "data": { - "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ucd08)" + "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \ube48\ub3c4 (\ucd08)" } } } diff --git a/homeassistant/components/omnilogic/translations/de.json b/homeassistant/components/omnilogic/translations/de.json index 4378d39912d..85de80d3dfa 100644 --- a/homeassistant/components/omnilogic/translations/de.json +++ b/homeassistant/components/omnilogic/translations/de.json @@ -16,5 +16,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "Abfrageintervall (in Sekunden)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/hu.json b/homeassistant/components/omnilogic/translations/hu.json new file mode 100644 index 00000000000..129bb041b42 --- /dev/null +++ b/homeassistant/components/omnilogic/translations/hu.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "Lek\u00e9rdez\u00e9si id\u0151k\u00f6z (m\u00e1sodpercben)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/id.json b/homeassistant/components/omnilogic/translations/id.json new file mode 100644 index 00000000000..ed19cc68cf8 --- /dev/null +++ b/homeassistant/components/omnilogic/translations/id.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "Interval polling (dalam detik)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/ko.json b/homeassistant/components/omnilogic/translations/ko.json index 74786104624..d2a5f58abeb 100644 --- a/homeassistant/components/omnilogic/translations/ko.json +++ b/homeassistant/components/omnilogic/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", diff --git a/homeassistant/components/onboarding/translations/id.json b/homeassistant/components/onboarding/translations/id.json index 33e8a88a9ae..472630cacf6 100644 --- a/homeassistant/components/onboarding/translations/id.json +++ b/homeassistant/components/onboarding/translations/id.json @@ -1,5 +1,7 @@ { "area": { - "kitchen": "Dapur" + "bedroom": "Kamar Tidur", + "kitchen": "Dapur", + "living_room": "Ruang Keluarga" } } \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/de.json b/homeassistant/components/ondilo_ico/translations/de.json index 5bab6ed132b..ad11cefde66 100644 --- a/homeassistant/components/ondilo_ico/translations/de.json +++ b/homeassistant/components/ondilo_ico/translations/de.json @@ -12,5 +12,6 @@ "title": "W\u00e4hle die Authentifizierungsmethode" } } - } + }, + "title": "Ondilo ICO" } \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/hu.json b/homeassistant/components/ondilo_ico/translations/hu.json new file mode 100644 index 00000000000..cae1f6d20c0 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/hu.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t." + }, + "create_entry": { + "default": "Sikeres hiteles\u00edt\u00e9s" + }, + "step": { + "pick_implementation": { + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/id.json b/homeassistant/components/ondilo_ico/translations/id.json new file mode 100644 index 00000000000..1227a6d6689 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi." + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/ko.json b/homeassistant/components/ondilo_ico/translations/ko.json index fa000ea1c06..88f3d678171 100644 --- a/homeassistant/components/ondilo_ico/translations/ko.json +++ b/homeassistant/components/ondilo_ico/translations/ko.json @@ -12,5 +12,6 @@ "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" } } - } + }, + "title": "Ondilo ICO" } \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/de.json b/homeassistant/components/onewire/translations/de.json index d3ed8137da3..2b0630db22c 100644 --- a/homeassistant/components/onewire/translations/de.json +++ b/homeassistant/components/onewire/translations/de.json @@ -12,12 +12,14 @@ "data": { "host": "Host", "port": "Port" - } + }, + "title": "owserver-Details einstellen" }, "user": { "data": { "type": "Verbindungstyp" - } + }, + "title": "1-Wire einrichten" } } } diff --git a/homeassistant/components/onewire/translations/hu.json b/homeassistant/components/onewire/translations/hu.json index 8ac8f6d3b03..662475dde2c 100644 --- a/homeassistant/components/onewire/translations/hu.json +++ b/homeassistant/components/onewire/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_path": "A k\u00f6nyvt\u00e1r nem tal\u00e1lhat\u00f3." @@ -7,14 +10,15 @@ "step": { "owserver": { "data": { - "host": "Gazdag\u00e9p", + "host": "Hoszt", "port": "Port" } }, "user": { "data": { "type": "Kapcsolat t\u00edpusa" - } + }, + "title": "A 1-Wire be\u00e1ll\u00edt\u00e1sa" } } } diff --git a/homeassistant/components/onewire/translations/id.json b/homeassistant/components/onewire/translations/id.json new file mode 100644 index 00000000000..5de8e2eee3e --- /dev/null +++ b/homeassistant/components/onewire/translations/id.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_path": "Direktori tidak ditemukan." + }, + "step": { + "owserver": { + "data": { + "host": "Host", + "port": "Port" + }, + "title": "Tetapkan detail owserver" + }, + "user": { + "data": { + "type": "Jenis koneksi" + }, + "title": "Siapkan 1-Wire" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/ko.json b/homeassistant/components/onewire/translations/ko.json index 871482b766b..038be16108a 100644 --- a/homeassistant/components/onewire/translations/ko.json +++ b/homeassistant/components/onewire/translations/ko.json @@ -4,14 +4,22 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_path": "\ub514\ub809\ud130\ub9ac\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" }, "step": { "owserver": { "data": { "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8" - } + }, + "title": "owserver \uc138\ubd80 \uc815\ubcf4 \uc124\uc815\ud558\uae30" + }, + "user": { + "data": { + "type": "\uc5f0\uacb0 \uc720\ud615" + }, + "title": "1-Wire \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/onvif/translations/he.json b/homeassistant/components/onvif/translations/he.json index a3203bfedb3..ecfa1afaab2 100644 --- a/homeassistant/components/onvif/translations/he.json +++ b/homeassistant/components/onvif/translations/he.json @@ -1,6 +1,12 @@ { "config": { "step": { + "auth": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + }, "configure_profile": { "data": { "include": "\u05e6\u05d5\u05e8 \u05d9\u05e9\u05d5\u05ea \u05de\u05e6\u05dc\u05de\u05d4" diff --git a/homeassistant/components/onvif/translations/hu.json b/homeassistant/components/onvif/translations/hu.json index c9c3f137984..61a6cfb056e 100644 --- a/homeassistant/components/onvif/translations/hu.json +++ b/homeassistant/components/onvif/translations/hu.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "no_h264": "Nem voltak el\u00e9rhet\u0151 H264 streamek. Ellen\u0151rizd a profil konfigur\u00e1ci\u00f3j\u00e1t a k\u00e9sz\u00fcl\u00e9ken.", + "no_mac": "Nem siker\u00fclt konfigur\u00e1lni az egyedi azonos\u00edt\u00f3t az ONVIF eszk\u00f6zh\u00f6z.", + "onvif_error": "Hiba t\u00f6rt\u00e9nt az ONVIF eszk\u00f6z be\u00e1ll\u00edt\u00e1sakor. Tov\u00e1bbi inform\u00e1ci\u00f3k\u00e9rt ellen\u0151rizd a napl\u00f3kat." + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, @@ -8,7 +15,8 @@ "data": { "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - } + }, + "title": "Hiteles\u00edt\u00e9s konfigur\u00e1l\u00e1sa" }, "configure_profile": { "data": { @@ -17,11 +25,19 @@ "description": "L\u00e9trehozza a(z) {profile} f\u00e9nyk\u00e9pez\u0151g\u00e9p entit\u00e1s\u00e1t {resolution} felbont\u00e1ssal?", "title": "Profilok konfigur\u00e1l\u00e1sa" }, + "device": { + "data": { + "host": "V\u00e1laszd ki a felfedezett ONVIF eszk\u00f6zt" + }, + "title": "ONVIF eszk\u00f6z kiv\u00e1laszt\u00e1sa" + }, "manual_input": { "data": { "host": "Hoszt", + "name": "N\u00e9v", "port": "Port" - } + }, + "title": "ONVIF eszk\u00f6z konfigur\u00e1l\u00e1sa" }, "user": { "description": "A k\u00fcld\u00e9s gombra kattintva olyan ONVIF-eszk\u00f6z\u00f6ket keres\u00fcnk a h\u00e1l\u00f3zat\u00e1ban, amelyek t\u00e1mogatj\u00e1k az S profilt.\n\nEgyes gy\u00e1rt\u00f3k alap\u00e9rtelmez\u00e9s szerint elkezdt\u00e9k letiltani az ONVIF-et. Ellen\u0151rizze, hogy az ONVIF enged\u00e9lyezve van-e a kamera konfigur\u00e1ci\u00f3j\u00e1ban." diff --git a/homeassistant/components/onvif/translations/id.json b/homeassistant/components/onvif/translations/id.json new file mode 100644 index 00000000000..3ed50ae63c4 --- /dev/null +++ b/homeassistant/components/onvif/translations/id.json @@ -0,0 +1,59 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "no_h264": "Tidak ada aliran H264 yang tersedia. Periksa konfigurasi profil di perangkat Anda.", + "no_mac": "Tidak dapat mengonfigurasi ID unik untuk perangkat ONVIF.", + "onvif_error": "Terjadi kesalahan saat menyiapkan perangkat ONVIF. Periksa log untuk informasi lebih lanjut." + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "auth": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Konfigurasikan autentikasi" + }, + "configure_profile": { + "data": { + "include": "Buat entitas kamera" + }, + "description": "Buat entitas kamera untuk {profile} dengan {resolution}?", + "title": "Konfigurasikan Profil" + }, + "device": { + "data": { + "host": "Pilih perangkat ONVIF yang ditemukan" + }, + "title": "Pilih perangkat ONVIF" + }, + "manual_input": { + "data": { + "host": "Host", + "name": "Nama", + "port": "Port" + }, + "title": "Konfigurasikan perangkat ONVIF" + }, + "user": { + "description": "Dengan mengklik kirim, kami akan mencari perangkat ONVIF pada jaringan Anda yang mendukung Profil S.\n\nBeberapa produsen mulai menonaktifkan ONVIF secara default. Pastikan ONVIF diaktifkan dalam konfigurasi kamera Anda.", + "title": "Penyiapan perangkat ONVIF" + } + } + }, + "options": { + "step": { + "onvif_devices": { + "data": { + "extra_arguments": "Argumen FFMPEG ekstra", + "rtsp_transport": "Mekanisme transport RTSP" + }, + "title": "Opsi Perangkat ONVIF" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/opentherm_gw/translations/hu.json b/homeassistant/components/opentherm_gw/translations/hu.json index c3dd3f10206..b8f51f4bb20 100644 --- a/homeassistant/components/opentherm_gw/translations/hu.json +++ b/homeassistant/components/opentherm_gw/translations/hu.json @@ -1,7 +1,8 @@ { "config": { "error": { - "already_configured": "Az \u00e1tj\u00e1r\u00f3 m\u00e1r konfigur\u00e1lva van", + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", "id_exists": "Az \u00e1tj\u00e1r\u00f3 azonos\u00edt\u00f3ja m\u00e1r l\u00e9tezik" }, "step": { diff --git a/homeassistant/components/opentherm_gw/translations/id.json b/homeassistant/components/opentherm_gw/translations/id.json new file mode 100644 index 00000000000..7c7624c3dfe --- /dev/null +++ b/homeassistant/components/opentherm_gw/translations/id.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "id_exists": "ID gateway sudah ada" + }, + "step": { + "init": { + "data": { + "device": "Jalur atau URL", + "id": "ID", + "name": "Nama" + }, + "title": "Gateway OpenTherm" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "floor_temperature": "Suhu Lantai", + "precision": "Tingkat Presisi" + }, + "description": "Pilihan untuk Gateway OpenTherm" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openuv/translations/hu.json b/homeassistant/components/openuv/translations/hu.json index 3a6f6a8ae91..b5c0e5ec608 100644 --- a/homeassistant/components/openuv/translations/hu.json +++ b/homeassistant/components/openuv/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van" + }, "error": { "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs" }, diff --git a/homeassistant/components/openuv/translations/id.json b/homeassistant/components/openuv/translations/id.json index 4d0edd93ea9..5075ec2c965 100644 --- a/homeassistant/components/openuv/translations/id.json +++ b/homeassistant/components/openuv/translations/id.json @@ -1,15 +1,18 @@ { "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi" + }, "error": { "invalid_api_key": "Kunci API tidak valid" }, "step": { "user": { "data": { - "api_key": "Kunci API OpenUV", + "api_key": "Kunci API", "elevation": "Ketinggian", "latitude": "Lintang", - "longitude": "Garis bujur" + "longitude": "Bujur" }, "title": "Isi informasi Anda" } diff --git a/homeassistant/components/openuv/translations/ko.json b/homeassistant/components/openuv/translations/ko.json index ee211d3cbd5..114be1df692 100644 --- a/homeassistant/components/openuv/translations/ko.json +++ b/homeassistant/components/openuv/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/openuv/translations/nl.json b/homeassistant/components/openuv/translations/nl.json index 118e8f05141..d7287b99ddf 100644 --- a/homeassistant/components/openuv/translations/nl.json +++ b/homeassistant/components/openuv/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Deze co\u00f6rdinaten zijn al geregistreerd." + "already_configured": "Locatie is al geconfigureerd." }, "error": { "invalid_api_key": "Ongeldige API-sleutel" @@ -9,7 +9,7 @@ "step": { "user": { "data": { - "api_key": "OpenUV API-Sleutel", + "api_key": "API-sleutel", "elevation": "Hoogte", "latitude": "Breedtegraad", "longitude": "Lengtegraad" diff --git a/homeassistant/components/openweathermap/translations/he.json b/homeassistant/components/openweathermap/translations/he.json new file mode 100644 index 00000000000..4c49313d977 --- /dev/null +++ b/homeassistant/components/openweathermap/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openweathermap/translations/hu.json b/homeassistant/components/openweathermap/translations/hu.json new file mode 100644 index 00000000000..2fd2f0acc7a --- /dev/null +++ b/homeassistant/components/openweathermap/translations/hu.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs" + }, + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "language": "Nyelv", + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g", + "mode": "M\u00f3d", + "name": "Az integr\u00e1ci\u00f3 neve" + }, + "description": "Az OpenWeatherMap integr\u00e1ci\u00f3 be\u00e1ll\u00edt\u00e1sa. Az API kulcs l\u00e9trehoz\u00e1s\u00e1hoz menj az https://openweathermap.org/appid oldalra", + "title": "OpenWeatherMap" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "language": "Nyelv", + "mode": "M\u00f3d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openweathermap/translations/id.json b/homeassistant/components/openweathermap/translations/id.json new file mode 100644 index 00000000000..61d2713f42a --- /dev/null +++ b/homeassistant/components/openweathermap/translations/id.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_api_key": "Kunci API tidak valid" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "language": "Bahasa", + "latitude": "Lintang", + "longitude": "Bujur", + "mode": "Mode", + "name": "Nama integrasi" + }, + "description": "Siapkan integrasi OpenWeatherMap. Untuk membuat kunci API, buka https://openweathermap.org/appid", + "title": "OpenWeatherMap" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "language": "Bahasa", + "mode": "Mode" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openweathermap/translations/ko.json b/homeassistant/components/openweathermap/translations/ko.json index 1514ada24ec..d2f5d9aa123 100644 --- a/homeassistant/components/openweathermap/translations/ko.json +++ b/homeassistant/components/openweathermap/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", @@ -17,7 +17,7 @@ "mode": "\ubaa8\ub4dc", "name": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc774\ub984" }, - "description": "OpenWeatherMap \ud1b5\ud569\uc744 \uc124\uc815\ud558\uc138\uc694. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://openweathermap.org/appid\ub85c \uc774\ub3d9\ud558\uc2ed\uc2dc\uc624.", + "description": "OpenWeatherMap \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://openweathermap.org/appid \ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/ovo_energy/translations/de.json b/homeassistant/components/ovo_energy/translations/de.json index 761f6a7d247..a86f39a614c 100644 --- a/homeassistant/components/ovo_energy/translations/de.json +++ b/homeassistant/components/ovo_energy/translations/de.json @@ -10,7 +10,9 @@ "reauth": { "data": { "password": "Passwort" - } + }, + "description": "Die Authentifizierung f\u00fcr OVO Energy ist fehlgeschlagen. Bitte geben Sie Ihre aktuellen Anmeldedaten ein.", + "title": "Erneute Authentifizierung" }, "user": { "data": { diff --git a/homeassistant/components/ovo_energy/translations/hu.json b/homeassistant/components/ovo_energy/translations/hu.json index c4091388b35..7bfd337cce5 100644 --- a/homeassistant/components/ovo_energy/translations/hu.json +++ b/homeassistant/components/ovo_energy/translations/hu.json @@ -2,11 +2,23 @@ "config": { "error": { "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", - "cannot_connect": "Sikertelen csatlakoz\u00e1s" + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, + "flow_title": "OVO Energy: {username}", "step": { "reauth": { + "data": { + "password": "Jelsz\u00f3" + }, "title": "\u00dajrahiteles\u00edt\u00e9s" + }, + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "title": "OVO Energy azonos\u00edt\u00f3 megad\u00e1sa" } } } diff --git a/homeassistant/components/ovo_energy/translations/id.json b/homeassistant/components/ovo_energy/translations/id.json new file mode 100644 index 00000000000..05c38f244e7 --- /dev/null +++ b/homeassistant/components/ovo_energy/translations/id.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "already_configured": "Akun sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "flow_title": "OVO Energy: {username}", + "step": { + "reauth": { + "data": { + "password": "Kata Sandi" + }, + "description": "Autentikasi gagal untuk OVO Energy. Masukkan kredensial Anda saat ini.", + "title": "Autentikasi ulang" + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Siapkan instans OVO Energy untuk mengakses data penggunaan energi Anda.", + "title": "Tambahkan Akun OVO Energy" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ovo_energy/translations/ko.json b/homeassistant/components/ovo_energy/translations/ko.json index 07ef8d8e166..273c146e805 100644 --- a/homeassistant/components/ovo_energy/translations/ko.json +++ b/homeassistant/components/ovo_energy/translations/ko.json @@ -5,17 +5,21 @@ "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, + "flow_title": "OVO Energy: {username}", "step": { "reauth": { "data": { "password": "\ube44\ubc00\ubc88\ud638" - } + }, + "description": "OVO Energy\uc5d0 \ub300\ud55c \uc778\uc99d\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. \ud604\uc7ac \uc790\uaca9 \uc99d\uba85\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "\uc7ac\uc778\uc99d" }, "user": { "data": { "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, + "description": "\uc5d0\ub108\uc9c0 \uc0ac\uc6a9\ub7c9\uc5d0 \uc561\uc138\uc2a4 \ud558\ub824\uba74 OVO Energy \uc778\uc2a4\ud134\uc2a4\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694.", "title": "OVO Energy \uacc4\uc815 \ucd94\uac00\ud558\uae30" } } diff --git a/homeassistant/components/ovo_energy/translations/nl.json b/homeassistant/components/ovo_energy/translations/nl.json index 7a2b5b757bb..d3909b0e44e 100644 --- a/homeassistant/components/ovo_energy/translations/nl.json +++ b/homeassistant/components/ovo_energy/translations/nl.json @@ -18,7 +18,8 @@ "data": { "password": "Wachtwoord", "username": "Gebruikersnaam" - } + }, + "title": "Voeg OVO Energie Account toe" } } } diff --git a/homeassistant/components/owntracks/translations/hu.json b/homeassistant/components/owntracks/translations/hu.json index b6bb7593906..f103fc9bbe1 100644 --- a/homeassistant/components/owntracks/translations/hu.json +++ b/homeassistant/components/owntracks/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "create_entry": { "default": "\n\nAndroidon, nyisd meg [az OwnTracks appot]({android_url}), menj a preferences -> connectionre. V\u00e1ltoztasd meg a al\u00e1bbi be\u00e1ll\u00edt\u00e1sokat:\n - Mode: Private HTTP\n - Host: {webhook_url}\n - Identification:\n - Username: ``\n - Device ID: ``\n\niOS-en, nyisd meg [az OwnTracks appot]({ios_url}), kattints az (i) ikonra bal oldalon fel\u00fcl -> settings. V\u00e1ltoztasd meg az al\u00e1bbi be\u00e1ll\u00edt\u00e1sokat:\n - Mode: HTTP\n - URL: {webhook_url}\n - Turn on authentication\n - UserID: ``\n\n{secret}\n\nN\u00e9zd meg [a dokument\u00e1ci\u00f3t]({docs_url}) tov\u00e1bbi inform\u00e1ci\u00f3k\u00e9rt." }, diff --git a/homeassistant/components/owntracks/translations/id.json b/homeassistant/components/owntracks/translations/id.json new file mode 100644 index 00000000000..890afaa099c --- /dev/null +++ b/homeassistant/components/owntracks/translations/id.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "create_entry": { + "default": "\n\nDi Android, buka [aplikasi OwnTracks]({android_url}), buka preferensi -> koneksi. Ubah setelan berikut ini:\n - Mode: HTTP Pribadi\n - Host: {webhook_url}\n - Identifikasi:\n - Nama pengguna: `''`\n - ID Perangkat: `''`\n\nDi iOS, buka [aplikasi OwnTracks]({ios_url}), ketuk ikon (i) di pojok kiri atas -> pengaturan. Ubah setelan berikut ini:\n - Mode: HTTP\n - URL: {webhook_url}\n - Aktifkan autentikasi\n - UserID: `''`\n\n{secret}\n\nLihat [dokumentasi]({docs_url}) untuk informasi lebih lanjut." + }, + "step": { + "user": { + "description": "Yakin ingin menyiapkan OwnTracks?", + "title": "Siapkan OwnTracks" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/owntracks/translations/ko.json b/homeassistant/components/owntracks/translations/ko.json index 6e558e54627..a8dd94b7e16 100644 --- a/homeassistant/components/owntracks/translations/ko.json +++ b/homeassistant/components/owntracks/translations/ko.json @@ -1,14 +1,14 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "create_entry": { - "default": "\n\nAndroid \uc778 \uacbd\uc6b0, [OwnTracks \uc571]({android_url}) \uc744 \uc5f4\uace0 preferences -> connection \uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \ub2e4\uc74c\uacfc \uac19\uc774 \uc124\uc815\ud574\uc8fc\uc138\uc694:\n - Mode: Private HTTP\n - Host: {webhook_url}\n - Identification:\n - Username: `''`\n - Device ID: `''`\n\niOS \uc778 \uacbd\uc6b0, [OwnTracks \uc571]({ios_url}) \uc744 \uc5f4\uace0 \uc67c\ucabd \uc0c1\ub2e8\uc758 (i) \uc544\uc774\ucf58\uc744 \ud0ed\ud558\uc5ec \uc124\uc815\uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \ub2e4\uc74c\uacfc \uac19\uc774 \uc124\uc815\ud574\uc8fc\uc138\uc694:\n - Mode: HTTP\n - URL: {webhook_url}\n - Turn on authentication\n - UserID: `''`\n\n{secret} \n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "\n\nAndroid\uc778 \uacbd\uc6b0, [OwnTracks \uc571]({android_url})\uc744 \uc5f4\uace0 preferences -> connection\uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \ub2e4\uc74c\uacfc \uac19\uc774 \uc124\uc815\ud574\uc8fc\uc138\uc694:\n - Mode: Private HTTP\n - Host: {webhook_url}\n - Identification:\n - Username: `''`\n - Device ID: `''`\n\niOS \uc778 \uacbd\uc6b0, [OwnTracks \uc571]({ios_url})\uc744 \uc5f4\uace0 \uc67c\ucabd \uc0c1\ub2e8\uc758 (i) \uc544\uc774\ucf58\uc744 \ud0ed\ud558\uc5ec \uc124\uc815\uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \ub2e4\uc74c\uacfc \uac19\uc774 \uc124\uc815\ud574\uc8fc\uc138\uc694:\n - Mode: HTTP\n - URL: {webhook_url}\n - Turn on authentication\n - UserID: `''`\n\n{secret} \n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { - "description": "OwnTracks \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "OwnTracks\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "OwnTracks \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/ozw/translations/fr.json b/homeassistant/components/ozw/translations/fr.json index bf4ba5c6995..5e408b7b807 100644 --- a/homeassistant/components/ozw/translations/fr.json +++ b/homeassistant/components/ozw/translations/fr.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "addon_info_failed": "Impossible d\u2019obtenir des informations de l'add-on OpenZWave.", - "addon_install_failed": "\u00c9chec de l\u2019installation de l'add-on OpenZWave.", + "addon_info_failed": "Impossible d'obtenir les informations sur le module compl\u00e9mentaire OpenZWave.", + "addon_install_failed": "\u00c9chec de l'installation du module compl\u00e9mentaire OpenZWave.", "addon_set_config_failed": "\u00c9chec de la configuration OpenZWave.", "already_configured": "Cet appareil est d\u00e9j\u00e0 configur\u00e9", "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", @@ -10,23 +10,23 @@ "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "error": { - "addon_start_failed": "\u00c9chec du d\u00e9marrage de l'add-on OpenZWave. V\u00e9rifiez la configuration." + "addon_start_failed": "\u00c9chec du d\u00e9marrage du module compl\u00e9mentaire OpenZWave. V\u00e9rifiez la configuration." }, "progress": { "install_addon": "Veuillez patienter pendant que l'installation du module OpenZWave se termine. Cela peut prendre plusieurs minutes." }, "step": { "hassio_confirm": { - "title": "Configurer l\u2019int\u00e9gration OpenZWave avec l\u2019add-on OpenZWave" + "title": "Configurer l'int\u00e9gration d'OpenZWave avec le module compl\u00e9mentaire OpenZWave" }, "install_addon": { "title": "L'installation du module compl\u00e9mentaire OpenZWave a commenc\u00e9" }, "on_supervisor": { "data": { - "use_addon": "Utiliser l'add-on OpenZWave Supervisor" + "use_addon": "Utilisez le module compl\u00e9mentaire OpenZWave du Supervisor" }, - "description": "Souhaitez-vous utiliser l'add-on OpenZWave Supervisor ?", + "description": "Voulez-vous utiliser le module compl\u00e9mentaire OpenZWave du Supervisor?", "title": "S\u00e9lectionner la m\u00e9thode de connexion" }, "start_addon": { @@ -34,7 +34,7 @@ "network_key": "Cl\u00e9 r\u00e9seau", "usb_path": "Chemin du p\u00e9riph\u00e9rique USB" }, - "title": "Entrez dans la configuration de l'add-on OpenZWave" + "title": "Entrez dans la configuration du module compl\u00e9mentaire OpenZWave" } } } diff --git a/homeassistant/components/ozw/translations/hu.json b/homeassistant/components/ozw/translations/hu.json index e4c864d9fd3..6c2c6c22f55 100644 --- a/homeassistant/components/ozw/translations/hu.json +++ b/homeassistant/components/ozw/translations/hu.json @@ -4,7 +4,9 @@ "addon_info_failed": "Nem siker\u00fclt bet\u00f6lteni az OpenZWave kieg\u00e9sz\u00edt\u0151 inform\u00e1ci\u00f3kat.", "addon_install_failed": "Nem siker\u00fclt telep\u00edteni az OpenZWave b\u0151v\u00edtm\u00e9nyt.", "addon_set_config_failed": "Nem siker\u00fclt be\u00e1ll\u00edtani az OpenZWave konfigur\u00e1ci\u00f3t.", - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "error": { "addon_start_failed": "Nem siker\u00fclt elind\u00edtani az OpenZWave b\u0151v\u00edtm\u00e9nyt. Ellen\u0151rizze a konfigur\u00e1ci\u00f3t." @@ -12,14 +14,15 @@ "step": { "on_supervisor": { "data": { - "use_addon": "Haszn\u00e1lja az OpenZWave adminisztr\u00e1tori b\u0151v\u00edtm\u00e9nyt" + "use_addon": "Haszn\u00e1ld az OpenZWave Supervisor b\u0151v\u00edtm\u00e9nyt" }, - "description": "Szeretn\u00e9 haszn\u00e1lni az OpenZWave adminisztr\u00e1tori b\u0151v\u00edtm\u00e9nyt?", - "title": "V\u00e1lassza ki a csatlakoz\u00e1si m\u00f3dot" + "description": "Szeretn\u00e9d haszn\u00e1lni az OpenZWave Supervisor b\u0151v\u00edtm\u00e9nyt?", + "title": "V\u00e1laszd ki a csatlakoz\u00e1si m\u00f3dot" }, "start_addon": { "data": { - "network_key": "H\u00e1l\u00f3zati kulcs" + "network_key": "H\u00e1l\u00f3zati kulcs", + "usb_path": "USB eszk\u00f6z el\u00e9r\u00e9si \u00fat" } } } diff --git a/homeassistant/components/ozw/translations/id.json b/homeassistant/components/ozw/translations/id.json new file mode 100644 index 00000000000..ef47e12f12d --- /dev/null +++ b/homeassistant/components/ozw/translations/id.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "addon_info_failed": "Gagal mendapatkan info add-on OpenZWave.", + "addon_install_failed": "Gagal menginstal add-on OpenZWave.", + "addon_set_config_failed": "Gagal menyetel konfigurasi OpenZWave.", + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "mqtt_required": "Integrasi MQTT belum disiapkan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "addon_start_failed": "Gagal memulai add-on OpenZWave. Periksa konfigurasi." + }, + "progress": { + "install_addon": "Harap tunggu hingga penginstalan add-on OpenZWave selesai. Ini bisa memakan waktu beberapa saat." + }, + "step": { + "hassio_confirm": { + "title": "Siapkan integrasi OpenZWave dengan add-on OpenZWave" + }, + "install_addon": { + "title": "Instalasi add-on OpenZWave telah dimulai" + }, + "on_supervisor": { + "data": { + "use_addon": "Gunakan add-on Supervisor OpenZWave" + }, + "description": "Ingin menggunakan add-on Supervisor OpenZWave?", + "title": "Pilih metode koneksi" + }, + "start_addon": { + "data": { + "network_key": "Kunci Jaringan", + "usb_path": "Jalur Perangkat USB" + }, + "title": "Masukkan konfigurasi add-on OpenZWave" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/ko.json b/homeassistant/components/ozw/translations/ko.json index ba37dccdd68..f118db7f72c 100644 --- a/homeassistant/components/ozw/translations/ko.json +++ b/homeassistant/components/ozw/translations/ko.json @@ -1,16 +1,40 @@ { "config": { "abort": { + "addon_info_failed": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc758 \uc815\ubcf4\ub97c \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_install_failed": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc744 \uc124\uce58\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_set_config_failed": "OpenZWave \uad6c\uc131\uc744 \uc124\uc815\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "mqtt_required": "MQTT \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uac00 \uc124\uc815\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + }, + "error": { + "addon_start_failed": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uad6c\uc131 \ub0b4\uc6a9\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694." + }, + "progress": { + "install_addon": "Openzwave \ucd94\uac00 \uae30\ub2a5\uc758 \uc124\uce58\uac00 \uc644\ub8cc\ub418\ub294 \ub3d9\uc548 \uc7a0\uc2dc \uae30\ub2e4\ub824\uc8fc\uc138\uc694. \uba87 \ubd84 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { + "hassio_confirm": { + "title": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc73c\ub85c OpenZWave \ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc124\uc815\ud558\uae30" + }, + "install_addon": { + "title": "Openzwave \ucd94\uac00 \uae30\ub2a5 \uc124\uce58\uac00 \uc2dc\uc791\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "on_supervisor": { + "data": { + "use_addon": "OpenZWave Supervisor \ucd94\uac00 \uae30\ub2a5\uc744 \uc0ac\uc6a9\ud558\uae30" + }, + "description": "OpenZWave Supervisor \ucd94\uac00 \uae30\ub2a5\uc744 \uc0ac\uc6a9\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\uc5f0\uacb0 \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" + }, "start_addon": { "data": { + "network_key": "\ub124\ud2b8\uc6cc\ud06c \ud0a4", "usb_path": "USB \uc7a5\uce58 \uacbd\ub85c" - } + }, + "title": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc758 \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" } } } diff --git a/homeassistant/components/ozw/translations/nl.json b/homeassistant/components/ozw/translations/nl.json index 80ef72a061e..3026427e8f1 100644 --- a/homeassistant/components/ozw/translations/nl.json +++ b/homeassistant/components/ozw/translations/nl.json @@ -13,10 +13,15 @@ "install_addon": { "title": "De OpenZWave add-on installatie is gestart" }, + "on_supervisor": { + "title": "Selecteer een verbindingsmethode" + }, "start_addon": { "data": { + "network_key": "Netwerksleutel", "usb_path": "USB-apparaatpad" - } + }, + "title": "Voer de OpenZWave add-on configuratie in" } } } diff --git a/homeassistant/components/ozw/translations/zh-Hant.json b/homeassistant/components/ozw/translations/zh-Hant.json index f9ed5469da0..37ab2ea9c9e 100644 --- a/homeassistant/components/ozw/translations/zh-Hant.json +++ b/homeassistant/components/ozw/translations/zh-Hant.json @@ -1,32 +1,32 @@ { "config": { "abort": { - "addon_info_failed": "\u53d6\u5f97 OpenZWave add-on \u8cc7\u8a0a\u5931\u6557\u3002", - "addon_install_failed": "OpenZWave add-on \u5b89\u88dd\u5931\u6557\u3002", - "addon_set_config_failed": "OpenZWave add-on \u8a2d\u5b9a\u5931\u6557\u3002", + "addon_info_failed": "\u53d6\u5f97 OpenZWave \u9644\u52a0\u5143\u4ef6\u8cc7\u8a0a\u5931\u6557\u3002", + "addon_install_failed": "OpenZWave \u9644\u52a0\u5143\u4ef6\u5b89\u88dd\u5931\u6557\u3002", + "addon_set_config_failed": "OpenZWave a\u9644\u52a0\u5143\u4ef6\u8a2d\u5b9a\u5931\u6557\u3002", "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "mqtt_required": "MQTT \u6574\u5408\u5c1a\u672a\u8a2d\u5b9a", "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { - "addon_start_failed": "OpenZWave add-on \u555f\u52d5\u5931\u6557\uff0c\u8acb\u6aa2\u67e5\u8a2d\u5b9a\u3002" + "addon_start_failed": "OpenZWave \u9644\u52a0\u5143\u4ef6\u555f\u52d5\u5931\u6557\uff0c\u8acb\u6aa2\u67e5\u8a2d\u5b9a\u3002" }, "progress": { - "install_addon": "\u8acb\u7a0d\u7b49 OpenZWave add-on \u5b89\u88dd\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002" + "install_addon": "\u8acb\u7a0d\u7b49 OpenZWave \u9644\u52a0\u5143\u4ef6\u5b89\u88dd\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002" }, "step": { "hassio_confirm": { - "title": "\u4ee5 OpenZWave add-on \u8a2d\u5b9a OpenZwave \u6574\u5408" + "title": "\u4ee5 OpenZWave \u9644\u52a0\u5143\u4ef6\u8a2d\u5b9a OpenZwave \u6574\u5408" }, "install_addon": { - "title": "OpenZWave add-on \u5b89\u88dd\u5df2\u555f\u52d5" + "title": "OpenZWave \u9644\u52a0\u5143\u4ef6\u5b89\u88dd\u5df2\u555f\u52d5" }, "on_supervisor": { "data": { - "use_addon": "\u4f7f\u7528 OpenZWave Supervisor add-on" + "use_addon": "\u4f7f\u7528 OpenZWave Supervisor \u9644\u52a0\u5143\u4ef6" }, - "description": "\u662f\u5426\u8981\u4f7f\u7528 OpenZWave Supervisor add-on\uff1f", + "description": "\u662f\u5426\u8981\u4f7f\u7528 OpenZWave Supervisor \u9644\u52a0\u5143\u4ef6\uff1f", "title": "\u9078\u64c7\u9023\u7dda\u985e\u578b" }, "start_addon": { @@ -34,7 +34,7 @@ "network_key": "\u7db2\u8def\u5bc6\u9470", "usb_path": "USB \u88dd\u7f6e\u8def\u5f91" }, - "title": "\u8acb\u8f38\u5165 OpenZWave \u8a2d\u5b9a\u3002" + "title": "\u8acb\u8f38\u5165 OpenZWave \u9644\u52a0\u5143\u4ef6\u8a2d\u5b9a\u3002" } } } diff --git a/homeassistant/components/panasonic_viera/translations/hu.json b/homeassistant/components/panasonic_viera/translations/hu.json index fbf7f49be6a..cfc0be387d0 100644 --- a/homeassistant/components/panasonic_viera/translations/hu.json +++ b/homeassistant/components/panasonic_viera/translations/hu.json @@ -1,16 +1,28 @@ { "config": { "abort": { - "cannot_connect": "Sikertelen csatlakoz\u00e1s" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { - "cannot_connect": "Sikertelen csatlakoz\u00e1s" + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_pin_code": "A megadott PIN-k\u00f3d \u00e9rv\u00e9nytelen volt" }, "step": { + "pairing": { + "data": { + "pin": "PIN-k\u00f3d" + }, + "description": "Add meg a TV-k\u00e9sz\u00fcl\u00e9ken megjelen\u0151 PIN-k\u00f3dot", + "title": "P\u00e1ros\u00edt\u00e1s" + }, "user": { "data": { - "host": "IP c\u00edm" - } + "host": "IP c\u00edm", + "name": "N\u00e9v" + }, + "description": "Add meg a Panasonic Viera TV-hez tartoz\u00f3 IP c\u00edmet" } } } diff --git a/homeassistant/components/panasonic_viera/translations/id.json b/homeassistant/components/panasonic_viera/translations/id.json new file mode 100644 index 00000000000..4f9c6e3d432 --- /dev/null +++ b/homeassistant/components/panasonic_viera/translations/id.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_pin_code": "Kode PIN Anda masukkan tidak valid" + }, + "step": { + "pairing": { + "data": { + "pin": "Kode PIN" + }, + "description": "Masukkan Kode PIN yang ditampilkan di TV Anda", + "title": "Memasangkan" + }, + "user": { + "data": { + "host": "Alamat IP", + "name": "Nama" + }, + "description": "Masukkan Alamat IP TV Panasonic Viera Anda", + "title": "Siapkan TV Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/nl.json b/homeassistant/components/panasonic_viera/translations/nl.json index 96757370bed..fa63892730e 100644 --- a/homeassistant/components/panasonic_viera/translations/nl.json +++ b/homeassistant/components/panasonic_viera/translations/nl.json @@ -1,18 +1,18 @@ { "config": { "abort": { - "already_configured": "Deze Panasonic Viera TV is al geconfigureerd.", + "already_configured": "Apparaat is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken", - "unknown": "Er is een onbekende fout opgetreden. Controleer de logs voor meer informatie." + "unknown": "Onverwachte fout" }, "error": { "cannot_connect": "Kan geen verbinding maken", - "invalid_pin_code": "De ingevoerde pincode is ongeldig" + "invalid_pin_code": "De PIN-code die u hebt ingevoerd is ongeldig" }, "step": { "pairing": { "data": { - "pin": "PIN" + "pin": "PIN-code" }, "description": "Voer de PIN-code in die op uw TV wordt weergegeven", "title": "Koppelen" @@ -22,7 +22,7 @@ "host": "IP-adres", "name": "Naam" }, - "description": "Voer het IP-adres van uw Panasonic Viera TV in", + "description": "Voer de IP-adres van uw Panasonic Viera TV in.", "title": "Uw tv instellen" } } diff --git a/homeassistant/components/person/translations/id.json b/homeassistant/components/person/translations/id.json index 2be3be8476a..1535bb2bd50 100644 --- a/homeassistant/components/person/translations/id.json +++ b/homeassistant/components/person/translations/id.json @@ -1,7 +1,7 @@ { "state": { "_": { - "home": "Di rumah", + "home": "Di Rumah", "not_home": "Keluar" } }, diff --git a/homeassistant/components/philips_js/translations/bg.json b/homeassistant/components/philips_js/translations/bg.json new file mode 100644 index 00000000000..eb502f5a135 --- /dev/null +++ b/homeassistant/components/philips_js/translations/bg.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "invalid_pin": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d \u041f\u0418\u041d", + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/de.json b/homeassistant/components/philips_js/translations/de.json index f59a17bce49..8b5d376ead7 100644 --- a/homeassistant/components/philips_js/translations/de.json +++ b/homeassistant/components/philips_js/translations/de.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Verbindung fehlgeschlagen", "invalid_pin": "Ung\u00fcltige PIN", + "pairing_failure": "Fehler beim Koppeln: {error_id}", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/philips_js/translations/es.json b/homeassistant/components/philips_js/translations/es.json index d4476f29981..66d3b85c3bd 100644 --- a/homeassistant/components/philips_js/translations/es.json +++ b/homeassistant/components/philips_js/translations/es.json @@ -1,5 +1,9 @@ { "config": { + "error": { + "invalid_pin": "PIN no v\u00e1lido", + "pairing_failure": "No se ha podido emparejar: {error_id}" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/philips_js/translations/hu.json b/homeassistant/components/philips_js/translations/hu.json new file mode 100644 index 00000000000..0065a78ae0c --- /dev/null +++ b/homeassistant/components/philips_js/translations/hu.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_pin": "\u00c9rv\u00e9nytelen PIN", + "pairing_failure": "Nem lehet p\u00e1ros\u00edtani: {error_id}", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "api_version": "API Verzi\u00f3", + "host": "Hoszt" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/id.json b/homeassistant/components/philips_js/translations/id.json new file mode 100644 index 00000000000..633cfdd633e --- /dev/null +++ b/homeassistant/components/philips_js/translations/id.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_pin": "PIN tidak valid", + "pairing_failure": "Tidak dapat memasangkan: {error_id}", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_version": "Versi API", + "host": "Host" + } + } + } + }, + "device_automation": { + "trigger_type": { + "turn_on": "Perangkat diminta untuk dinyalakan" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/it.json b/homeassistant/components/philips_js/translations/it.json index 216248d8eac..03b5340a6bc 100644 --- a/homeassistant/components/philips_js/translations/it.json +++ b/homeassistant/components/philips_js/translations/it.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "Impossibile connettersi", + "invalid_pin": "PIN non valido", + "pairing_failure": "Impossibile eseguire l'associazione: {error_id}", "unknown": "Errore imprevisto" }, "step": { diff --git a/homeassistant/components/philips_js/translations/ko.json b/homeassistant/components/philips_js/translations/ko.json index 85281856809..d698b01af1a 100644 --- a/homeassistant/components/philips_js/translations/ko.json +++ b/homeassistant/components/philips_js/translations/ko.json @@ -5,14 +5,22 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_pin": "PIN\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "pairing_failure": "\ud398\uc5b4\ub9c1\uc744 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4: {error_id}", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "user": { "data": { + "api_version": "API \ubc84\uc804", "host": "\ud638\uc2a4\ud2b8" } } } + }, + "device_automation": { + "trigger_type": { + "turn_on": "\uae30\uae30\uac00 \ucf1c\uc9c0\ub3c4\ub85d \uc694\uccad\ub418\uc5c8\uc744 \ub54c" + } } } \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/nl.json b/homeassistant/components/philips_js/translations/nl.json index 23cd7e47043..108d158f1c5 100644 --- a/homeassistant/components/philips_js/translations/nl.json +++ b/homeassistant/components/philips_js/translations/nl.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "Kan geen verbinding maken", + "invalid_pin": "Ongeldige pincode", + "pairing_failure": "Kan niet koppelen: {error_id}", "unknown": "Onverwachte fout" }, "step": { @@ -15,5 +17,10 @@ } } } + }, + "device_automation": { + "trigger_type": { + "turn_on": "Apparaat wordt gevraagd om in te schakelen" + } } } \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/pl.json b/homeassistant/components/philips_js/translations/pl.json index 27c088350c4..4fc1fbc5269 100644 --- a/homeassistant/components/philips_js/translations/pl.json +++ b/homeassistant/components/philips_js/translations/pl.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_pin": "Nieprawid\u0142owy kod PIN", + "pairing_failure": "Nie mo\u017cna sparowa\u0107: {error_id}", "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { diff --git a/homeassistant/components/philips_js/translations/zh-Hans.json b/homeassistant/components/philips_js/translations/zh-Hans.json new file mode 100644 index 00000000000..1353d8d1225 --- /dev/null +++ b/homeassistant/components/philips_js/translations/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_pin": "\u65e0\u6548\u7684PIN\u7801" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/hu.json b/homeassistant/components/pi_hole/translations/hu.json index f1bd9a106bc..104f711d8d5 100644 --- a/homeassistant/components/pi_hole/translations/hu.json +++ b/homeassistant/components/pi_hole/translations/hu.json @@ -7,10 +7,20 @@ "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { + "api_key": { + "data": { + "api_key": "API kulcs" + } + }, "user": { "data": { + "api_key": "API kulcs", "host": "Hoszt", - "port": "Port" + "location": "Elhelyezked\u00e9s", + "name": "N\u00e9v", + "port": "Port", + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" } } } diff --git a/homeassistant/components/pi_hole/translations/id.json b/homeassistant/components/pi_hole/translations/id.json new file mode 100644 index 00000000000..c38c0f5c9bb --- /dev/null +++ b/homeassistant/components/pi_hole/translations/id.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "api_key": { + "data": { + "api_key": "Kunci API" + } + }, + "user": { + "data": { + "api_key": "Kunci API", + "host": "Host", + "location": "Lokasi", + "name": "Nama", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL", + "statistics_only": "Hanya Statistik", + "verify_ssl": "Verifikasi sertifikat SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/ko.json b/homeassistant/components/pi_hole/translations/ko.json index 7261742b2a6..d79878d8a42 100644 --- a/homeassistant/components/pi_hole/translations/ko.json +++ b/homeassistant/components/pi_hole/translations/ko.json @@ -20,6 +20,7 @@ "name": "\uc774\ub984", "port": "\ud3ec\ud2b8", "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9", + "statistics_only": "\ud1b5\uacc4 \uc804\uc6a9", "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" } } diff --git a/homeassistant/components/plaato/translations/de.json b/homeassistant/components/plaato/translations/de.json index eaf68b507f9..20cba7e9ebc 100644 --- a/homeassistant/components/plaato/translations/de.json +++ b/homeassistant/components/plaato/translations/de.json @@ -10,7 +10,7 @@ }, "step": { "user": { - "description": "M\u00f6chtest du mit der Einrichtung beginnen?", + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?", "title": "Plaato Webhook einrichten" } } diff --git a/homeassistant/components/plaato/translations/en.json b/homeassistant/components/plaato/translations/en.json index 64d41d0091e..1217eb53d6e 100644 --- a/homeassistant/components/plaato/translations/en.json +++ b/homeassistant/components/plaato/translations/en.json @@ -9,7 +9,7 @@ "default": "Your Plaato {device_type} with name **{device_name}** was successfully setup!" }, "error": { - "invalid_webhook_device": "You have selected a device that doesn't not support sending data to a webhook. It is only available for the Airlock", + "invalid_webhook_device": "You have selected a device that does not support sending data to a webhook. It is only available for the Airlock", "no_api_method": "You need to add an auth token or select webhook", "no_auth_token": "You need to add an auth token" }, diff --git a/homeassistant/components/plaato/translations/et.json b/homeassistant/components/plaato/translations/et.json index 66a7b252a87..fb4325581dd 100644 --- a/homeassistant/components/plaato/translations/et.json +++ b/homeassistant/components/plaato/translations/et.json @@ -9,7 +9,7 @@ "default": "{device_type} Plaato seade nimega **{device_name}** on edukalt seadistatud!" }, "error": { - "invalid_webhook_device": "Oled valinud seadme, mis ei toeta andmete saatmist veebihaagile. See on saadaval ainult Airlocki jaoks", + "invalid_webhook_device": "Oled valinud seadme mis ei toeta andmete saatmist veebihaagile. See on saadaval ainult Airlocki jaoks", "no_api_method": "Pead lisama autentimisloa v\u00f5i valima veebihaagi", "no_auth_token": "Pead lisama autentimisloa" }, diff --git a/homeassistant/components/plaato/translations/hu.json b/homeassistant/components/plaato/translations/hu.json index 76229e86224..aee8ceb07cd 100644 --- a/homeassistant/components/plaato/translations/hu.json +++ b/homeassistant/components/plaato/translations/hu.json @@ -1,7 +1,18 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "webhook_not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." + }, "create_entry": { - "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, be kell \u00e1ll\u00edtania a webhook funkci\u00f3t a Plaato Airlock-ban. \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: \" {webhook_url} \"\n - M\u00f3dszer: POST \n\n Tov\u00e1bbi r\u00e9szletek\u00e9rt l\u00e1sd a [dokument\u00e1ci\u00f3t] ( {docs_url} )." + "default": "A Plaato {device_type} **{device_name}** n\u00e9vvel sikeresen telep\u00edtve lett!" + }, + "step": { + "user": { + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?", + "title": "A Plaato eszk\u00f6z\u00f6k be\u00e1ll\u00edt\u00e1sa" + } } } } \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/id.json b/homeassistant/components/plaato/translations/id.json new file mode 100644 index 00000000000..989bb38bcaf --- /dev/null +++ b/homeassistant/components/plaato/translations/id.json @@ -0,0 +1,54 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." + }, + "create_entry": { + "default": "Plaato {device_type} dengan nama **{device_name}** berhasil disiapkan!" + }, + "error": { + "invalid_webhook_device": "Anda telah memilih perangkat yang tidak mendukung pengiriman data ke webhook. Ini hanya tersedia untuk Airlock", + "no_api_method": "Anda perlu menambahkan token auth atau memilih webhook", + "no_auth_token": "Anda perlu menambahkan token autentikasi" + }, + "step": { + "api_method": { + "data": { + "token": "Tempel Token Auth di sini", + "use_webhook": "Gunakan webhook" + }, + "description": "Untuk dapat melakukan kueri API diperlukan 'auth_token'. Nilai token dapat diperoleh dengan mengikuti [petunjuk ini](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token)\n\nPerangkat yang dipilih: **{device_type}** \n\nJika Anda lebih memilih untuk menggunakan metode webhook bawaan (hanya Airlock), centang centang kotak di bawah ini dan kosongkan nilai Auth Token'", + "title": "Pilih metode API" + }, + "user": { + "data": { + "device_name": "Nama perangkat Anda", + "device_type": "Jenis perangkat Plaato" + }, + "description": "Ingin memulai penyiapan?", + "title": "Siapkan perangkat Plaato" + }, + "webhook": { + "description": "Untuk mengirim event ke Home Assistant, Anda harus menyiapkan fitur webhook di Plaato Airlock.\n\nIsi info berikut:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nBaca [dokumentasi]({docs_url}) tentang detail lebih lanjut.", + "title": "Webhook untuk digunakan" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "Interval pembaruan (menit)" + }, + "description": "Atur interval pembaruan (menit)", + "title": "Opsi untuk Plaato" + }, + "webhook": { + "description": "Info webhook:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n", + "title": "Opsi untuk Plaato Airlock" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/ko.json b/homeassistant/components/plaato/translations/ko.json index 753653c88b2..1cb999c4729 100644 --- a/homeassistant/components/plaato/translations/ko.json +++ b/homeassistant/components/plaato/translations/ko.json @@ -2,16 +2,52 @@ "config": { "abort": { "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "**{device_name}** \uc758 Plaato {device_type} \uc774(\uac00) \uc131\uacf5\uc801\uc73c\ub85c \uc124\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4!" + "default": "**{device_name}**\uc758 Plaato {device_type}\uc774(\uac00) \uc131\uacf5\uc801\uc73c\ub85c \uc124\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4!" + }, + "error": { + "invalid_webhook_device": "\uc6f9 \ud6c5\uc73c\ub85c \ub370\uc774\ud130 \uc804\uc1a1\uc744 \uc9c0\uc6d0\ud558\uc9c0 \uc54a\ub294 \uae30\uae30\ub97c \uc120\ud0dd\ud588\uc2b5\ub2c8\ub2e4. Airlock\uc5d0\uc11c\ub9cc \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4", + "no_api_method": "\uc778\uc99d \ud1a0\ud070\uc744 \ucd94\uac00\ud558\uac70\ub098 \uc6f9 \ud6c5\uc744 \uc120\ud0dd\ud574\uc57c \ud569\ub2c8\ub2e4", + "no_auth_token": "\uc778\uc99d \ud1a0\ud070\uc744 \ucd94\uac00\ud574\uc57c \ud569\ub2c8\ub2e4" }, "step": { + "api_method": { + "data": { + "token": "\uc5ec\uae30\uc5d0 \uc778\uc99d \ud1a0\ud070\uc744 \ubd99\uc5ec \ub123\uc5b4\uc8fc\uc138\uc694", + "use_webhook": "\uc6f9 \ud6c5 \uc0ac\uc6a9\ud558\uae30" + }, + "description": "API\ub97c \ucffc\ub9ac \ud558\ub824\uba74 [\uc548\ub0b4 \uc9c0\uce68](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token)\uc5d0 \ub530\ub77c \uc5bb\uc744 \uc218 \uc788\ub294 'auth_token'\uc774 \ud544\uc694\ud569\ub2c8\ub2e4\n\n\uc120\ud0dd\ud55c \uae30\uae30: **{device_type}**\n\n\ub0b4\uc7a5\ub41c \uc6f9 \ud6c5 \ubc29\uc2dd(Airlock \uc804\uc6a9)\uc744 \uc0ac\uc6a9\ud558\ub824\ub294 \uacbd\uc6b0 \uc544\ub798 \ud655\uc778\ub780\uc744 \uc120\ud0dd\ud558\uace0 Auth Token\uc744 \ube44\uc6cc\ub450\uc138\uc694", + "title": "API \ubc29\uc2dd \uc120\ud0dd\ud558\uae30" + }, "user": { + "data": { + "device_name": "\uae30\uae30 \uc774\ub984", + "device_type": "Plaato \uae30\uae30 \uc720\ud615" + }, "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Plaato \uae30\uae30 \uc124\uc815\ud558\uae30" + }, + "webhook": { + "description": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Plaato Airlock\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4.\n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "title": "\uc0ac\uc6a9\ud560 \uc6f9 \ud6c5" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ubd84)" + }, + "description": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 \uc124\uc815\ud558\uae30 (\ubd84)", + "title": "Plaato \uc635\uc158" + }, + "webhook": { + "description": "\uc6f9 \ud6c5 \uc815\ubcf4:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n", + "title": "Plaato Airlock \uc635\uc158" } } } diff --git a/homeassistant/components/plaato/translations/nl.json b/homeassistant/components/plaato/translations/nl.json index c0a9d1e04fb..6453668680a 100644 --- a/homeassistant/components/plaato/translations/nl.json +++ b/homeassistant/components/plaato/translations/nl.json @@ -9,6 +9,8 @@ "default": "Om evenementen naar de Home Assistant te sturen, moet u de webhook-functie instellen in Plaato Airlock. \n\n Vul de volgende info in: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n\n Zie [de documentatie] ( {docs_url} ) voor meer informatie." }, "error": { + "invalid_webhook_device": "U heeft een apparaat geselecteerd dat het verzenden van gegevens naar een webhook niet ondersteunt. Het is alleen beschikbaar voor de Airlock", + "no_api_method": "U moet een verificatie token toevoegen of selecteer een webhook", "no_auth_token": "U moet een verificatie token toevoegen" }, "step": { @@ -26,15 +28,24 @@ }, "description": "Weet u zeker dat u de Plaato-airlock wilt instellen?", "title": "Stel de Plaato Webhook in" + }, + "webhook": { + "description": "Om evenementen naar de Home Assistant te sturen, moet u de webhook-functie instellen in Plaato Airlock. \n\n Vul de volgende info in: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n\n Zie [de documentatie] ({docs_url}) voor meer informatie.", + "title": "Webhook om te gebruiken" } } }, "options": { "step": { "user": { + "data": { + "update_interval": "Update-interval (minuten)" + }, + "description": "Stel het update-interval in (minuten)", "title": "Opties voor Plaato" }, "webhook": { + "description": "Webhook-informatie: \n\n - URL: ' {webhook_url} '\n - Methode: POST\n\n", "title": "Opties voor Plaato Airlock" } } diff --git a/homeassistant/components/plaato/translations/no.json b/homeassistant/components/plaato/translations/no.json index 8efbf07945f..7039662468e 100644 --- a/homeassistant/components/plaato/translations/no.json +++ b/homeassistant/components/plaato/translations/no.json @@ -9,7 +9,7 @@ "default": "Plaato {device_type} med navnet **{device_name}** ble konfigurert!" }, "error": { - "invalid_webhook_device": "Du har valgt en enhet som ikke st\u00f8tter sending av data til en webhook. Den er kun tilgjengelig for Airlock", + "invalid_webhook_device": "Du har valgt en enhet som ikke st\u00f8tter sending av data til en webhook. Den er bare tilgjengelig for luftsluse", "no_api_method": "Du m\u00e5 legge til et godkjenningstoken eller velge webhook", "no_auth_token": "Du m\u00e5 legge til et godkjenningstoken" }, diff --git a/homeassistant/components/plaato/translations/pl.json b/homeassistant/components/plaato/translations/pl.json index 57df32c3f4e..ddfb779ea2e 100644 --- a/homeassistant/components/plaato/translations/pl.json +++ b/homeassistant/components/plaato/translations/pl.json @@ -6,7 +6,7 @@ "webhook_not_internet_accessible": "Tw\u00f3j Home Assistant musi by\u0107 dost\u0119pny z Internetu, aby odbiera\u0107 komunikaty webhook" }, "create_entry": { - "default": "Tw\u00f3j {device_type} Plaato o nazwie *{device_name}* zosta\u0142o pomy\u015blnie skonfigurowane!" + "default": "Tw\u00f3j {device_type} Plaato o nazwie **{device_name}** zosta\u0142o pomy\u015blnie skonfigurowane!" }, "error": { "invalid_webhook_device": "Wybra\u0142e\u015b urz\u0105dzenie, kt\u00f3re nie obs\u0142uguje wysy\u0142ania danych do webhooka. Opcja dost\u0119pna tylko w areometrze Airlock", @@ -19,7 +19,7 @@ "token": "Wklej token autoryzacji", "use_webhook": "U\u017cyj webhook" }, - "description": "Aby m\u00f3c przesy\u0142a\u0107 zapytania do API, wymagany jest \u201etoken autoryzacji\u201d, kt\u00f3ry mo\u017cna uzyska\u0107, post\u0119puj\u0105c zgodnie z [t\u0105] (https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) instrukcj\u0105\n\nWybrane urz\u0105dzenie: *{device_type}* \n\nJe\u015bli wolisz u\u017cywa\u0107 wbudowanej metody webhook (tylko areomierz Airlock), zaznacz poni\u017csze pole i pozostaw token autoryzacji pusty", + "description": "Aby m\u00f3c przesy\u0142a\u0107 zapytania do API, wymagany jest \u201etoken autoryzacji\u201d, kt\u00f3ry mo\u017cna uzyska\u0107, post\u0119puj\u0105c zgodnie z [t\u0105] (https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) instrukcj\u0105\n\nWybrane urz\u0105dzenie: **{device_type}** \n\nJe\u015bli wolisz u\u017cywa\u0107 wbudowanej metody webhook (tylko areomierz Airlock), zaznacz poni\u017csze pole i pozostaw token autoryzacji pusty", "title": "Wybierz metod\u0119 API" }, "user": { diff --git a/homeassistant/components/plant/translations/id.json b/homeassistant/components/plant/translations/id.json index 519964be278..5378edc405f 100644 --- a/homeassistant/components/plant/translations/id.json +++ b/homeassistant/components/plant/translations/id.json @@ -1,9 +1,9 @@ { "state": { "_": { - "ok": "OK", - "problem": "Masalah" + "ok": "Oke", + "problem": "Bermasalah" } }, - "title": "Tanaman" + "title": "Monitor Tanaman" } \ No newline at end of file diff --git a/homeassistant/components/plex/translations/hu.json b/homeassistant/components/plex/translations/hu.json index cfccf5c83e6..9168f070609 100644 --- a/homeassistant/components/plex/translations/hu.json +++ b/homeassistant/components/plex/translations/hu.json @@ -3,26 +3,30 @@ "abort": { "all_configured": "Az \u00f6sszes \u00f6sszekapcsolt szerver m\u00e1r konfigur\u00e1lva van", "already_configured": "Ez a Plex szerver m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A Plex konfigur\u00e1l\u00e1sa folyamatban van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt", "token_request_timeout": "Token k\u00e9r\u00e9sre sz\u00e1nt id\u0151 lej\u00e1rt", - "unknown": "Ismeretlen okb\u00f3l nem siker\u00fclt" + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { "faulty_credentials": "A hiteles\u00edt\u00e9s sikertelen", "no_servers": "Nincs szerver csatlakoztatva a fi\u00f3khoz", "not_found": "A Plex szerver nem tal\u00e1lhat\u00f3" }, + "flow_title": "{name} ({host})", "step": { "manual_setup": { "data": { "host": "Hoszt", "port": "Port", - "ssl": "Haszn\u00e1ljon SSL-t" + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "token": "Token (opcion\u00e1lis)", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" } }, "select_server": { "data": { - "server": "szerver" + "server": "Szerver" }, "description": "T\u00f6bb szerver el\u00e9rhet\u0151, v\u00e1lasszon egyet:", "title": "Plex-kiszolg\u00e1l\u00f3 kiv\u00e1laszt\u00e1sa" @@ -39,6 +43,7 @@ "step": { "plex_mp_settings": { "data": { + "ignore_plex_web_clients": "Plex Web kliensek figyelmen k\u00edv\u00fcl hagy\u00e1sa", "use_episode_art": "Haszn\u00e1lja az epiz\u00f3d bor\u00edt\u00f3j\u00e1t" }, "description": "Plex media lej\u00e1tsz\u00f3k be\u00e1ll\u00edt\u00e1sai" diff --git a/homeassistant/components/plex/translations/id.json b/homeassistant/components/plex/translations/id.json new file mode 100644 index 00000000000..7c596835e03 --- /dev/null +++ b/homeassistant/components/plex/translations/id.json @@ -0,0 +1,62 @@ +{ + "config": { + "abort": { + "all_configured": "Semua server tertaut sudah dikonfigurasi", + "already_configured": "Server Plex ini sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "reauth_successful": "Autentikasi ulang berhasil", + "token_request_timeout": "Tenggang waktu pengambilan token habis", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "faulty_credentials": "Otorisasi gagal, verifikasi Token", + "host_or_token": "Harus menyediakan setidaknya satu Host atau Token", + "no_servers": "Tidak ada server yang ditautkan ke akun Plex", + "not_found": "Server Plex tidak ditemukan", + "ssl_error": "Masalah sertifikat SSL" + }, + "flow_title": "{name} ({host})", + "step": { + "manual_setup": { + "data": { + "host": "Host", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL", + "token": "Token (Opsional)", + "verify_ssl": "Verifikasi sertifikat SSL" + }, + "title": "Konfigurasi Plex Manual" + }, + "select_server": { + "data": { + "server": "Server" + }, + "description": "Beberapa server tersedia, pilih satu:", + "title": "Pilih server Plex" + }, + "user": { + "description": "Lanjutkan [plex.tv](https://plex.tv) untuk menautkan server Plex.", + "title": "Server Media Plex" + }, + "user_advanced": { + "data": { + "setup_method": "Metode penyiapan" + }, + "title": "Server Media Plex" + } + } + }, + "options": { + "step": { + "plex_mp_settings": { + "data": { + "ignore_new_shared_users": "Abaikan pengguna baru yang dikelola/berbagi", + "ignore_plex_web_clients": "Abaikan klien Plex Web", + "monitored_users": "Pengguna yang dipantau", + "use_episode_art": "Gunakan sampul episode" + }, + "description": "Opsi untuk Pemutar Media Plex" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/hu.json b/homeassistant/components/plugwise/translations/hu.json index 1dcdb7fe5af..d6d9012c21a 100644 --- a/homeassistant/components/plugwise/translations/hu.json +++ b/homeassistant/components/plugwise/translations/hu.json @@ -1,13 +1,31 @@ { "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "Smile: {name}", "step": { "user": { "data": { "flow_type": "Kapcsolat t\u00edpusa" - } + }, + "description": "Term\u00e9k:", + "title": "Plugwise t\u00edpus" }, "user_gateway": { - "description": "K\u00e9rj\u00fck, adja meg" + "data": { + "host": "IP c\u00edm", + "password": "Smile azonos\u00edt\u00f3", + "port": "Port", + "username": "Smile Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "description": "K\u00e9rj\u00fck, adja meg", + "title": "Csatlakoz\u00e1s a Smile-hoz" } } } diff --git a/homeassistant/components/plugwise/translations/id.json b/homeassistant/components/plugwise/translations/id.json new file mode 100644 index 00000000000..9047bf477bd --- /dev/null +++ b/homeassistant/components/plugwise/translations/id.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Smile: {name}", + "step": { + "user": { + "data": { + "flow_type": "Jenis koneksi" + }, + "description": "Produk:", + "title": "Jenis Plugwise" + }, + "user_gateway": { + "data": { + "host": "Alamat IP", + "password": "ID Smile", + "port": "Port", + "username": "Nama Pengguna Smile" + }, + "description": "Masukkan", + "title": "Hubungkan ke Smile" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Interval Pindai (detik)" + }, + "description": "Sesuaikan Opsi Plugwise" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/ko.json b/homeassistant/components/plugwise/translations/ko.json index 7af503f0a66..7d856d1fe28 100644 --- a/homeassistant/components/plugwise/translations/ko.json +++ b/homeassistant/components/plugwise/translations/ko.json @@ -11,14 +11,21 @@ "flow_title": "Smile: {name}", "step": { "user": { + "data": { + "flow_type": "\uc5f0\uacb0 \uc720\ud615" + }, "description": "\uc81c\ud488:", "title": "Plugwise \uc720\ud615" }, "user_gateway": { "data": { "host": "IP \uc8fc\uc18c", - "port": "\ud3ec\ud2b8" - } + "password": "Smile ID", + "port": "\ud3ec\ud2b8", + "username": "Smile \uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "\uc785\ub825\ud574\uc8fc\uc138\uc694", + "title": "Smile\uc5d0 \uc5f0\uacb0\ud558\uae30" } } }, @@ -28,7 +35,7 @@ "data": { "scan_interval": "\uc2a4\uce94 \uac04\uaca9 (\ucd08)" }, - "description": "Plugwise \uc635\uc158 \uc870\uc815" + "description": "Plugwise \uc635\uc158 \uc870\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/plugwise/translations/nl.json b/homeassistant/components/plugwise/translations/nl.json index 001bbbe6d5e..a432b8bbfd8 100644 --- a/homeassistant/components/plugwise/translations/nl.json +++ b/homeassistant/components/plugwise/translations/nl.json @@ -4,8 +4,8 @@ "already_configured": "De service is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", - "invalid_auth": "Ongeldige authenticatie, controleer de 8 karakters van uw Smile-ID", + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, "step": { @@ -20,8 +20,10 @@ "data": { "host": "IP-adres", "password": "Smile-ID", - "port": "Poort" + "port": "Poort", + "username": "Smile Gebruikersnaam" }, + "description": "Voer in", "title": "Maak verbinding met de Smile" } } diff --git a/homeassistant/components/plum_lightpad/translations/hu.json b/homeassistant/components/plum_lightpad/translations/hu.json index 436e8b1fb7d..3a82e0a241e 100644 --- a/homeassistant/components/plum_lightpad/translations/hu.json +++ b/homeassistant/components/plum_lightpad/translations/hu.json @@ -2,6 +2,17 @@ "config": { "abort": { "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "E-mail" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/plum_lightpad/translations/id.json b/homeassistant/components/plum_lightpad/translations/id.json new file mode 100644 index 00000000000..0d2f98d1faa --- /dev/null +++ b/homeassistant/components/plum_lightpad/translations/id.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/point/translations/de.json b/homeassistant/components/point/translations/de.json index 343c02055d5..c36c7a44b51 100644 --- a/homeassistant/components/point/translations/de.json +++ b/homeassistant/components/point/translations/de.json @@ -24,7 +24,7 @@ "data": { "flow_impl": "Anbieter" }, - "description": "M\u00f6chtest du mit der Einrichtung beginnen?", + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?", "title": "W\u00e4hle die Authentifizierungsmethode" } } diff --git a/homeassistant/components/point/translations/hu.json b/homeassistant/components/point/translations/hu.json index c31e2c55e6a..7f4346a6ea9 100644 --- a/homeassistant/components/point/translations/hu.json +++ b/homeassistant/components/point/translations/hu.json @@ -4,7 +4,8 @@ "already_setup": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", "authorize_url_fail": "Ismeretlen hiba t\u00f6rt\u00e9nt a hiteles\u00edt\u00e9si link gener\u00e1l\u00e1sa sor\u00e1n.", "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", - "no_flows": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t." + "no_flows": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "unknown_authorize_url_generation": "Ismeretlen hiba t\u00f6rt\u00e9nt a hiteles\u00edt\u00e9si link gener\u00e1l\u00e1sa sor\u00e1n." }, "create_entry": { "default": "Sikeres hiteles\u00edt\u00e9s" @@ -15,7 +16,7 @@ }, "step": { "auth": { - "description": "K\u00e9rlek k\u00f6vesd az al\u00e1bbi linket \u00e9s a Fogadd el a hozz\u00e1f\u00e9r\u00e9st a Minut fi\u00f3kj\u00e1hoz, majd t\u00e9rj vissza \u00e9s nyomd meg a K\u00fcld\u00e9s gombot. \n\n [Link] ( {authorization_url} )", + "description": "K\u00e9rlek k\u00f6vesd az al\u00e1bbi linket \u00e9s a **Fogadd el** a hozz\u00e1f\u00e9r\u00e9st a Minut fi\u00f3kj\u00e1hoz, majd t\u00e9rj vissza \u00e9s nyomd meg a **K\u00fcld\u00e9s ** gombot. \n\n [Link]({authorization_url})", "title": "Point hiteles\u00edt\u00e9se" }, "user": { diff --git a/homeassistant/components/point/translations/id.json b/homeassistant/components/point/translations/id.json new file mode 100644 index 00000000000..868321d7469 --- /dev/null +++ b/homeassistant/components/point/translations/id.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_setup": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "authorize_url_fail": "Kesalahan tidak dikenal terjadi ketika menghasilkan URL otorisasi.", + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "external_setup": "Point berhasil dikonfigurasi dari alur konfigurasi lainnya.", + "no_flows": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "unknown_authorize_url_generation": "Kesalahan tidak dikenal ketika menghasilkan URL otorisasi." + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "error": { + "follow_link": "Buka tautan dan autentikasi sebelum menekan Kirim", + "no_token": "Token akses tidak valid" + }, + "step": { + "auth": { + "description": "Buka tautan di bawah ini dan **Terima** akses ke akun Minut Anda, lalu kembali dan tekan tombol **Kirim** di bawah ini.\n\n[Tautan] ({authorization_url})", + "title": "Autentikasi Point" + }, + "user": { + "data": { + "flow_impl": "Penyedia" + }, + "description": "Ingin memulai penyiapan?", + "title": "Pilih Metode Autentikasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/point/translations/ko.json b/homeassistant/components/point/translations/ko.json index f4ca6002036..5813dbba137 100644 --- a/homeassistant/components/point/translations/ko.json +++ b/homeassistant/components/point/translations/ko.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_setup": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", - "authorize_url_fail": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", + "already_setup": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "authorize_url_fail": "\uc778\uc99d URL\uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "external_setup": "Point \uac00 \ub2e4\ub978 \uad6c\uc131 \ub2e8\uacc4\uc5d0\uc11c \uc131\uacf5\uc801\uc73c\ub85c \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "external_setup": "Point\uac00 \ub2e4\ub978 \uad6c\uc131 \ub2e8\uacc4\uc5d0\uc11c \uc131\uacf5\uc801\uc73c\ub85c \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "no_flows": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "unknown_authorize_url_generation": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4." }, diff --git a/homeassistant/components/point/translations/nl.json b/homeassistant/components/point/translations/nl.json index 94763a412a0..8447ac6bbb2 100644 --- a/homeassistant/components/point/translations/nl.json +++ b/homeassistant/components/point/translations/nl.json @@ -1,31 +1,31 @@ { "config": { "abort": { - "already_setup": "U kunt alleen een Point-account configureren.", + "already_setup": "Al geconfigureerd. Slechts een enkele configuratie mogelijk.", "authorize_url_fail": "Onbekende fout bij het genereren van een autoriseer-URL.", "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", "external_setup": "Punt succesvol geconfigureerd vanuit een andere stroom.", - "no_flows": "U moet Point configureren voordat u zich ermee kunt verifi\u00ebren. [Gelieve de instructies te lezen](https://www.home-assistant.io/components/nest/).", + "no_flows": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", "unknown_authorize_url_generation": "Onbekende fout bij het genereren van een autorisatie-URL." }, "create_entry": { - "default": "Succesvol geverifieerd met Minut voor uw Point appara(a)t(en)" + "default": "Succesvol geauthenticeerd" }, "error": { "follow_link": "Volg de link en verifieer voordat je op Verzenden klikt", - "no_token": "Niet geverifieerd met Minut" + "no_token": "Ongeldig toegangstoken" }, "step": { "auth": { - "description": "Ga naar onderstaande link en Accepteer toegang tot je Minut account, kom dan hier terug en klik op Verzenden hier onder.\n\n[Link]({authorization_url})", + "description": "Ga naar onderstaande link en **Accepteer** toegang tot je Minut account, kom dan hier terug en klik op **Verzenden** hier onder.\n\n[Link]({authorization_url})", "title": "Verificatie Point" }, "user": { "data": { "flow_impl": "Leverancier" }, - "description": "Kies met welke authenticatieprovider u wilt authenticeren met Point.", - "title": "Authenticatieleverancier" + "description": "Wil je beginnen met instellen?", + "title": "Kies een authenticatie methode" } } } diff --git a/homeassistant/components/poolsense/translations/de.json b/homeassistant/components/poolsense/translations/de.json index 5869da61c9c..6b64ec2eef1 100644 --- a/homeassistant/components/poolsense/translations/de.json +++ b/homeassistant/components/poolsense/translations/de.json @@ -12,7 +12,7 @@ "email": "E-Mail", "password": "Passwort" }, - "description": "M\u00f6chtest du mit der Einrichtung beginnen?" + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?" } } } diff --git a/homeassistant/components/poolsense/translations/hu.json b/homeassistant/components/poolsense/translations/hu.json index 3b2d79a34a7..80562b34e28 100644 --- a/homeassistant/components/poolsense/translations/hu.json +++ b/homeassistant/components/poolsense/translations/hu.json @@ -2,6 +2,19 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "user": { + "data": { + "email": "E-mail", + "password": "Jelsz\u00f3" + }, + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?", + "title": "PoolSense" + } } } } \ No newline at end of file diff --git a/homeassistant/components/poolsense/translations/id.json b/homeassistant/components/poolsense/translations/id.json new file mode 100644 index 00000000000..6e40f5f0925 --- /dev/null +++ b/homeassistant/components/poolsense/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Kata Sandi" + }, + "description": "Ingin memulai penyiapan?", + "title": "PoolSense" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/bg.json b/homeassistant/components/powerwall/translations/bg.json new file mode 100644 index 00000000000..cef3726d759 --- /dev/null +++ b/homeassistant/components/powerwall/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/hu.json b/homeassistant/components/powerwall/translations/hu.json index 7cc0ceafac1..a2b8af13370 100644 --- a/homeassistant/components/powerwall/translations/hu.json +++ b/homeassistant/components/powerwall/translations/hu.json @@ -1,9 +1,20 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "Tesla Powerwall ({ip_address})", "step": { "user": { "data": { - "ip_address": "IP-c\u00edm" + "ip_address": "IP c\u00edm", + "password": "Jelsz\u00f3" } } } diff --git a/homeassistant/components/powerwall/translations/id.json b/homeassistant/components/powerwall/translations/id.json new file mode 100644 index 00000000000..a5ae5f5e979 --- /dev/null +++ b/homeassistant/components/powerwall/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan", + "wrong_version": "Powerwall Anda menggunakan versi perangkat lunak yang tidak didukung. Pertimbangkan untuk memutakhirkan atau melaporkan masalah ini agar dapat diatasi." + }, + "flow_title": "Tesla Powerwall ({ip_address})", + "step": { + "user": { + "data": { + "ip_address": "Alamat IP", + "password": "Kata Sandi" + }, + "description": "Kata sandi umumnya adalah 5 karakter terakhir dari nomor seri untuk Backup Gateway dan dapat ditemukan di aplikasi Tesla atau 5 karakter terakhir kata sandi yang ditemukan di dalam pintu untuk Backup Gateway 2.", + "title": "Hubungkan ke powerwall" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/ko.json b/homeassistant/components/powerwall/translations/ko.json index 11c638fa9bd..d1dd99bbb7a 100644 --- a/homeassistant/components/powerwall/translations/ko.json +++ b/homeassistant/components/powerwall/translations/ko.json @@ -10,13 +10,15 @@ "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4", "wrong_version": "Powerwall \uc774 \ud604\uc7ac \uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \ubc84\uc804\uc758 \uc18c\ud504\ud2b8\uc6e8\uc5b4\ub97c \uc0ac\uc6a9 \uc911\uc785\ub2c8\ub2e4. \uc774 \ubb38\uc81c\ub97c \ud574\uacb0\ud558\ub824\uba74 \uc5c5\uadf8\ub808\uc774\ub4dc\ud558\uac70\ub098 \uc774 \ub0b4\uc6a9\uc744 \uc54c\ub824\uc8fc\uc138\uc694." }, + "flow_title": "Tesla Powerwall ({ip_address})", "step": { "user": { "data": { "ip_address": "IP \uc8fc\uc18c", "password": "\ube44\ubc00\ubc88\ud638" }, - "title": "powerwall \uc5d0 \uc5f0\uacb0\ud558\uae30" + "description": "\ube44\ubc00\ubc88\ud638\ub294 \uc77c\ubc18\uc801\uc73c\ub85c \ubc31\uc5c5 \uac8c\uc774\ud2b8\uc6e8\uc774 \uc2dc\ub9ac\uc5bc \ubc88\ud638\uc758 \ub9c8\uc9c0\ub9c9 5\uc790\ub9ac\uc774\uba70 Tesla \uc571 \ub610\ub294 \ubc31\uc5c5 \uac8c\uc774\ud2b8\uc6e8\uc774 2\uc758 \ub3c4\uc5b4 \ub0b4\ubd80\uc5d0 \uc788\ub294 \ub9c8\uc9c0\ub9c9 5\uc790\ub9ac \ube44\ubc00\ubc88\ud638\uc5d0\uc11c \ucc3e\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "title": "powerwall\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/powerwall/translations/nl.json b/homeassistant/components/powerwall/translations/nl.json index c4ae2616f46..5149f391669 100644 --- a/homeassistant/components/powerwall/translations/nl.json +++ b/homeassistant/components/powerwall/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "De powerwall is al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "reauth_successful": "Herauthenticatie was succesvol" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout", "wrong_version": "Uw powerwall gebruikt een softwareversie die niet wordt ondersteund. Overweeg om dit probleem te upgraden of te melden, zodat het kan worden opgelost." @@ -17,6 +17,7 @@ "ip_address": "IP-adres", "password": "Wachtwoord" }, + "description": "Het wachtwoord is meestal de laatste 5 tekens van het serienummer voor Backup Gateway en is te vinden in de Tesla-app of de laatste 5 tekens van het wachtwoord aan de binnenkant van de deur voor Backup Gateway 2.", "title": "Maak verbinding met de powerwall" } } diff --git a/homeassistant/components/profiler/translations/hu.json b/homeassistant/components/profiler/translations/hu.json index bbdd2e5b536..c5d28903888 100644 --- a/homeassistant/components/profiler/translations/hu.json +++ b/homeassistant/components/profiler/translations/hu.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "M\u00e1r konfigur\u00e1lva. Csak egyetlen konfigur\u00e1ci\u00f3 lehets\u00e9ges." + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "step": { "user": { - "description": "El akarja kezdeni a be\u00e1ll\u00edt\u00e1st?" + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?" } } } diff --git a/homeassistant/components/profiler/translations/id.json b/homeassistant/components/profiler/translations/id.json new file mode 100644 index 00000000000..3521bb95412 --- /dev/null +++ b/homeassistant/components/profiler/translations/id.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "user": { + "description": "Ingin memulai penyiapan?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/profiler/translations/ko.json b/homeassistant/components/profiler/translations/ko.json index 0c38052b826..251c9777b6c 100644 --- a/homeassistant/components/profiler/translations/ko.json +++ b/homeassistant/components/profiler/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "user": { diff --git a/homeassistant/components/progettihwsw/translations/hu.json b/homeassistant/components/progettihwsw/translations/hu.json index 3b2d79a34a7..f6f6e2c15b7 100644 --- a/homeassistant/components/progettihwsw/translations/hu.json +++ b/homeassistant/components/progettihwsw/translations/hu.json @@ -2,6 +2,32 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "relay_modes": { + "data": { + "relay_1": "Rel\u00e9 1", + "relay_10": "Rel\u00e9 10", + "relay_11": "Rel\u00e9 11", + "relay_12": "Rel\u00e9 12", + "relay_13": "Rel\u00e9 13", + "relay_14": "Rel\u00e9 14", + "relay_15": "Rel\u00e9 15", + "relay_16": "Rel\u00e9 16", + "relay_2": "Rel\u00e9 2", + "relay_3": "Rel\u00e9 3" + } + }, + "user": { + "data": { + "host": "Hoszt", + "port": "Port" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/id.json b/homeassistant/components/progettihwsw/translations/id.json new file mode 100644 index 00000000000..0aa69cad958 --- /dev/null +++ b/homeassistant/components/progettihwsw/translations/id.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "relay_modes": { + "data": { + "relay_1": "Relai 1", + "relay_10": "Relai 10", + "relay_11": "Relai 11", + "relay_12": "Relai 12", + "relay_13": "Relai 13", + "relay_14": "Relai 14", + "relay_15": "Relai 15", + "relay_16": "Relai 16", + "relay_2": "Relai 2", + "relay_3": "Relai 3", + "relay_4": "Relai 4", + "relay_5": "Relai 5", + "relay_6": "Relai 6", + "relay_7": "Relai 7", + "relay_8": "Relai 8", + "relay_9": "Relai 9" + }, + "title": "Siapkan relai" + }, + "user": { + "data": { + "host": "Host", + "port": "Port" + }, + "title": "Siapkan papan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/ko.json b/homeassistant/components/progettihwsw/translations/ko.json index ab21d8427bd..9324783f5cb 100644 --- a/homeassistant/components/progettihwsw/translations/ko.json +++ b/homeassistant/components/progettihwsw/translations/ko.json @@ -27,14 +27,14 @@ "relay_8": "\ub9b4\ub808\uc774 8", "relay_9": "\ub9b4\ub808\uc774 9" }, - "title": "\ub9b4\ub808\uc774 \uc124\uc815" + "title": "\ub9b4\ub808\uc774 \uc124\uc815\ud558\uae30" }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8" }, - "title": "\ubcf4\ub4dc \uc124\uc815" + "title": "\ubcf4\ub4dc \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/ps4/translations/hu.json b/homeassistant/components/ps4/translations/hu.json index c3bcabb0d3a..bb677b21700 100644 --- a/homeassistant/components/ps4/translations/hu.json +++ b/homeassistant/components/ps4/translations/hu.json @@ -1,8 +1,12 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton" + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "login_failed": "Nem siker\u00fclt p\u00e1ros\u00edtani a PlayStation 4-gyel. Ellen\u0151rizze, hogy a helyes-e." + "login_failed": "Nem siker\u00fclt p\u00e1ros\u00edtani a PlayStation 4-gyel. Ellen\u0151rizze, hogy a PIN-k\u00f3d helyes-e." }, "step": { "creds": { @@ -10,14 +14,15 @@ }, "link": { "data": { - "code": "PIN", - "ip_address": "IP-c\u00edm", + "code": "PIN-k\u00f3d", + "ip_address": "IP c\u00edm", "name": "N\u00e9v", "region": "R\u00e9gi\u00f3" } }, "mode": { "data": { + "ip_address": "IP c\u00edm (Hagyd \u00fcresen az Automatikus Felder\u00edt\u00e9s haszn\u00e1lat\u00e1hoz).", "mode": "Konfigur\u00e1ci\u00f3s m\u00f3d" }, "title": "PlayStation 4" diff --git a/homeassistant/components/ps4/translations/id.json b/homeassistant/components/ps4/translations/id.json new file mode 100644 index 00000000000..aab31564e16 --- /dev/null +++ b/homeassistant/components/ps4/translations/id.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "credential_error": "Terjadi kesalahan saat mengambil kredensial.", + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "port_987_bind_error": "Tidak dapat mengaitkan ke port 987. Baca [dokumentasi] (https://www.home-assistant.io/components/ps4/) untuk info lebih lanjut.", + "port_997_bind_error": "Tidak dapat mengaitkan ke port 997. Baca [dokumentasi] (https://www.home-assistant.io/components/ps4/) untuk info lebih lanjut." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "credential_timeout": "Tenggang waktu layanan kredensial habis. Tekan kirim untuk memulai kembali.", + "login_failed": "Gagal memasangkan dengan PlayStation 4. Pastikan Kode PIN sudah benar.", + "no_ipaddress": "Masukkan Alamat IP perangkat PlayStation 4 yang dikonfigurasi." + }, + "step": { + "creds": { + "description": "Kredensial diperlukan. Tekan 'Kirim' dan kemudian di Aplikasi Layar ke-2 PS4, segarkan perangkat dan pilih perangkat 'Home-Assistant' untuk melanjutkan.", + "title": "PlayStation 4" + }, + "link": { + "data": { + "code": "Kode PIN", + "ip_address": "Alamat IP", + "name": "Nama", + "region": "Wilayah" + }, + "description": "Masukkan informasi PlayStation 4 Anda. Untuk Kode PIN, buka 'Pengaturan' di konsol PlayStation 4 Anda. Kemudian buka 'Pengaturan Koneksi Aplikasi Seluler' dan pilih 'Tambah Perangkat'. Masukkan Kode PIN yang ditampilkan. Lihat [dokumentasi] (https://www.home-assistant.io/components/ps4/) untuk info lebih lanjut.", + "title": "PlayStation 4" + }, + "mode": { + "data": { + "ip_address": "Alamat IP (Kosongkan jika menggunakan Penemuan Otomatis).", + "mode": "Mode Konfigurasi" + }, + "description": "Pilih mode untuk konfigurasi. Alamat IP dapat dikosongkan jika memilih Penemuan Otomatis karena perangkat akan ditemukan secara otomatis.", + "title": "PlayStation 4" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ps4/translations/ko.json b/homeassistant/components/ps4/translations/ko.json index 76762a0bec6..129927a9bab 100644 --- a/homeassistant/components/ps4/translations/ko.json +++ b/homeassistant/components/ps4/translations/ko.json @@ -4,8 +4,8 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "credential_error": "\uc790\uaca9 \uc99d\uba85\uc744 \uac00\uc838\uc624\ub294 \uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "port_987_bind_error": "\ud3ec\ud2b8 987 \uc5d0 \ubc14\uc778\ub529 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucd94\uac00 \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/components/ps4/) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", - "port_997_bind_error": "\ud3ec\ud2b8 997 \uc5d0 \ubc14\uc778\ub529 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucd94\uac00 \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/components/ps4/) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "port_987_bind_error": "987 \ud3ec\ud2b8\uc5d0 \ubc14\uc778\ub529\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucd94\uac00 \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c](https://www.home-assistant.io/components/ps4/)\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "port_997_bind_error": "997 \ud3ec\ud2b8\uc5d0 \ubc14\uc778\ub529\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucd94\uac00 \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c](https://www.home-assistant.io/components/ps4/)\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", @@ -15,7 +15,7 @@ }, "step": { "creds": { - "description": "\uc790\uaca9 \uc99d\uba85\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. '\ud655\uc778'\uc744 \ud074\ub9ad\ud55c \ub2e4\uc74c PS4 \uc138\ucee8\ub4dc \uc2a4\ud06c\ub9b0 \uc571\uc5d0\uc11c \uae30\uae30\ub97c \uc0c8\ub85c \uace0\uce68\ud558\uace0 'Home-Assistant' \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", + "description": "\uc790\uaca9 \uc99d\uba85\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. '\ud655\uc778'\uc744 \ud074\ub9ad\ud55c \ub2e4\uc74c PS4 \uc138\ucee8\ub4dc \uc2a4\ud06c\ub9b0 \uc571\uc5d0\uc11c \uae30\uae30\ub97c \uc0c8\ub85c\uace0\uce68 \ud558\uace0 'Home-Assistant' \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", "title": "PlayStation 4" }, "link": { @@ -25,7 +25,7 @@ "name": "\uc774\ub984", "region": "\uc9c0\uc5ed" }, - "description": "PlayStation 4 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. PIN \ucf54\ub4dc\ub97c \ud655\uc778\ud558\ub824\uba74, PlayStation 4 \ucf58\uc194\uc5d0\uc11c '\uc124\uc815' \uc73c\ub85c \uc774\ub3d9\ud55c \ub4a4 '\ubaa8\ubc14\uc77c \uc571 \uc811\uc18d \uc124\uc815' \uc73c\ub85c \uc774\ub3d9\ud558\uc5ec '\uae30\uae30 \ub4f1\ub85d\ud558\uae30' \ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \ud654\uba74\uc5d0 \ud45c\uc2dc\ub41c PIN \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \ucd94\uac00 \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/components/ps4/) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "description": "PlayStation 4 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. PIN \ucf54\ub4dc\ub97c \ud655\uc778\ud558\ub824\uba74, PlayStation 4 \ucf58\uc194\uc5d0\uc11c '\uc124\uc815'\uc73c\ub85c \uc774\ub3d9\ud55c \ub4a4 '\ubaa8\ubc14\uc77c \uc571 \uc811\uc18d \uc124\uc815'\uc73c\ub85c \uc774\ub3d9\ud558\uc5ec '\uae30\uae30 \ub4f1\ub85d\ud558\uae30'\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \ud654\uba74\uc5d0 \ud45c\uc2dc\ub41c PIN \ucf54\ub4dc(\uc744)\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \ucd94\uac00 \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c](https://www.home-assistant.io/components/ps4/)\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", "title": "PlayStation 4" }, "mode": { diff --git a/homeassistant/components/ps4/translations/nl.json b/homeassistant/components/ps4/translations/nl.json index 326917e4960..db59c7ab30a 100644 --- a/homeassistant/components/ps4/translations/nl.json +++ b/homeassistant/components/ps4/translations/nl.json @@ -3,15 +3,15 @@ "abort": { "already_configured": "Apparaat is al geconfigureerd", "credential_error": "Fout bij ophalen van inloggegevens.", - "no_devices_found": "Geen PlayStation 4 apparaten gevonden op het netwerk.", + "no_devices_found": "Geen apparaten gevonden op het netwerk", "port_987_bind_error": "Kon niet binden aan poort 987. Raadpleeg de [documentatie] (https://www.home-assistant.io/components/ps4/) voor meer informatie.", "port_997_bind_error": "Kon niet binden aan poort 997. Raadpleeg de [documentatie] (https://www.home-assistant.io/components/ps4/) voor aanvullende informatie." }, "error": { "cannot_connect": "Kan geen verbinding maken", "credential_timeout": "Time-out van inlog service. Druk op Submit om opnieuw te starten.", - "login_failed": "Kan niet koppelen met PlayStation 4. Controleer of de pincode juist is.", - "no_ipaddress": "Voer het IP-adres in van de PlayStation 4 die je wilt configureren." + "login_failed": "Kan Playstation 4 niet koppelen. Controleer of de PIN-code correct is.", + "no_ipaddress": "Voer het IP-adres in van de PlayStation 4 die u wilt configureren." }, "step": { "creds": { @@ -20,20 +20,20 @@ }, "link": { "data": { - "code": "PIN", + "code": "PIN-code", "ip_address": "IP-adres", "name": "Naam", "region": "Regio" }, - "description": "Voer je PlayStation 4-informatie in. Ga voor 'PIN' naar 'Instellingen' op je PlayStation 4-console. Navigeer vervolgens naar 'Verbindingsinstellingen mobiele app' en selecteer 'Apparaat toevoegen'. Voer de pincode in die wordt weergegeven. Raadpleeg de [documentatie] (https://www.home-assistant.io/components/ps4/) voor meer informatie.", + "description": "Voer je PlayStation 4-informatie in. Voor PIN-code navigeer je naar 'Instellingen' op je PlayStation 4-console. Ga vervolgens naar 'Verbindingsinstellingen mobiele app' en selecteer 'Apparaat toevoegen'. Voer de PIN-code in die wordt weergegeven. Raadpleeg de [documentatie](https://www.home-assistant.io/components/ps4/) voor meer informatie.", "title": "PlayStation 4" }, "mode": { "data": { - "ip_address": "IP-adres (leeg laten als u Auto Discovery gebruikt).", + "ip_address": "IP-adres (leeg laten indien Auto Discovery wordt gebruikt).", "mode": "Configuratiemodus" }, - "description": "Selecteer modus voor configuratie. Het veld IP-adres kan leeg blijven als Auto Discovery wordt geselecteerd, omdat apparaten automatisch worden gedetecteerd.", + "description": "Selecteer modus voor configuratie. Het veld IP-adres kan leeg worden gelaten als Auto Discovery wordt geselecteerd, omdat apparaten dan automatisch worden ontdekt.", "title": "PlayStation 4" } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/hu.json b/homeassistant/components/pvpc_hourly_pricing/translations/hu.json new file mode 100644 index 00000000000..f5301e874ea --- /dev/null +++ b/homeassistant/components/pvpc_hourly_pricing/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/id.json b/homeassistant/components/pvpc_hourly_pricing/translations/id.json new file mode 100644 index 00000000000..8601c31fda0 --- /dev/null +++ b/homeassistant/components/pvpc_hourly_pricing/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "step": { + "user": { + "data": { + "name": "Nama Sensor", + "tariff": "Tarif kontrak (1, 2, atau 3 periode)" + }, + "description": "Sensor ini menggunakan API resmi untuk mendapatkan [harga listrik per jam (PVPC)](https://www.esios.ree.es/es/pvpc) di Spanyol.\nUntuk penjelasan yang lebih tepat, kunjungi [dokumen integrasi](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).\n\nPilih tarif kontrak berdasarkan jumlah periode penagihan per hari:\n- 1 periode: normal\n- 2 periode: diskriminasi (tarif per malam)\n- 3 periode: mobil listrik (tarif per malam 3 periode)", + "title": "Pemilihan tarif" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/ko.json b/homeassistant/components/pvpc_hourly_pricing/translations/ko.json index f1f225ae525..c44d1217961 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/ko.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/ko.json @@ -9,7 +9,7 @@ "name": "\uc13c\uc11c \uc774\ub984", "tariff": "\uacc4\uc57d \uc694\uae08\uc81c (1, 2 \ub610\ub294 3 \uad6c\uac04)" }, - "description": "\uc774 \uc13c\uc11c\ub294 \uacf5\uc2dd API \ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2a4\ud398\uc778\uc758 [\uc2dc\uac04\ub2f9 \uc804\uae30 \uc694\uae08 (PVPC)](https://www.esios.ree.es/es/pvpc) \uc744 \uac00\uc838\uc635\ub2c8\ub2e4.\n\ubcf4\ub2e4 \uc790\uc138\ud55c \uc124\uba85\uc740 [\uc548\ub0b4](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/)\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.\n\n1\uc77c\ub2f9 \uccad\uad6c \uad6c\uac04\uc5d0 \ub530\ub77c \uacc4\uc57d \uc694\uae08\uc81c\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.\n - 1 \uad6c\uac04: \uc77c\ubc18 \uc694\uae08\uc81c\n - 2 \uad6c\uac04: \ucc28\ub4f1 \uc694\uae08\uc81c (\uc57c\uac04 \uc694\uae08) \n - 3 \uad6c\uac04: \uc804\uae30\uc790\ub3d9\ucc28 (3 \uad6c\uac04 \uc57c\uac04 \uc694\uae08)", + "description": "\uc774 \uc13c\uc11c\ub294 \uacf5\uc2dd API\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2a4\ud398\uc778\uc758 [\uc2dc\uac04\ub2f9 \uc804\uae30 \uc694\uae08 (PVPC)](https://www.esios.ree.es/es/pvpc) \uc744 \uac00\uc838\uc635\ub2c8\ub2e4.\n\ubcf4\ub2e4 \uc790\uc138\ud55c \uc124\uba85\uc740 [\uad00\ub828 \ubb38\uc11c](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/)\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.\n\n1\uc77c\ub2f9 \uccad\uad6c \uad6c\uac04\uc5d0 \ub530\ub77c \uacc4\uc57d \uc694\uae08\uc81c\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.\n - 1 \uad6c\uac04: \uc77c\ubc18 \uc694\uae08\uc81c\n - 2 \uad6c\uac04: \ucc28\ub4f1 \uc694\uae08\uc81c (\uc57c\uac04 \uc694\uae08) \n - 3 \uad6c\uac04: \uc804\uae30\uc790\ub3d9\ucc28 (3 \uad6c\uac04 \uc57c\uac04 \uc694\uae08)", "title": "\uc694\uae08\uc81c \uc120\ud0dd" } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/nl.json b/homeassistant/components/pvpc_hourly_pricing/translations/nl.json index 3abffdf5bc0..5048ed498df 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/nl.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Integratie is al geconfigureerd met een bestaande sensor met dat tarief" + "already_configured": "Service is al geconfigureerd" }, "step": { "user": { diff --git a/homeassistant/components/rachio/translations/hu.json b/homeassistant/components/rachio/translations/hu.json index 5f4f7bb8bee..570dd27b5d9 100644 --- a/homeassistant/components/rachio/translations/hu.json +++ b/homeassistant/components/rachio/translations/hu.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/rachio/translations/id.json b/homeassistant/components/rachio/translations/id.json new file mode 100644 index 00000000000..66302734248 --- /dev/null +++ b/homeassistant/components/rachio/translations/id.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API" + }, + "description": "Anda akan memerlukan Kunci API dari https://app.rach.io/. Buka Settings, lalu klik 'GET API KEY'.", + "title": "Hubungkan ke perangkat Rachio Anda" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "manual_run_mins": "Durasi dalam menit yang akan dijalankan saat mengaktifkan sakelar zona" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rachio/translations/ko.json b/homeassistant/components/rachio/translations/ko.json index 298ef476745..203dba17303 100644 --- a/homeassistant/components/rachio/translations/ko.json +++ b/homeassistant/components/rachio/translations/ko.json @@ -13,7 +13,7 @@ "data": { "api_key": "API \ud0a4" }, - "description": "https://app.rach.io/ \uc758 API \ud0a4\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. Settings \ub85c \uc774\ub3d9\ud55c \ub2e4\uc74c 'GET API KEY ' \ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694.", + "description": "https://app.rach.io/ \uc758 API \ud0a4\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. Settings\ub85c \uc774\ub3d9\ud55c \ub2e4\uc74c 'GET API KEY '\ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694.", "title": "Rachio \uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/rachio/translations/nl.json b/homeassistant/components/rachio/translations/nl.json index 2173c768185..6a94ac2dcd4 100644 --- a/homeassistant/components/rachio/translations/nl.json +++ b/homeassistant/components/rachio/translations/nl.json @@ -4,14 +4,14 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, "step": { "user": { "data": { - "api_key": "De API-sleutel voor het Rachio-account." + "api_key": "API-sleutel" }, "description": "U heeft de API-sleutel nodig van https://app.rach.io/. Selecteer 'Accountinstellingen en klik vervolgens op' GET API KEY '.", "title": "Maak verbinding met uw Rachio-apparaat" diff --git a/homeassistant/components/rainmachine/translations/he.json b/homeassistant/components/rainmachine/translations/he.json new file mode 100644 index 00000000000..3007c0e968c --- /dev/null +++ b/homeassistant/components/rainmachine/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rainmachine/translations/hu.json b/homeassistant/components/rainmachine/translations/hu.json index 44e24519ca2..48718980e2e 100644 --- a/homeassistant/components/rainmachine/translations/hu.json +++ b/homeassistant/components/rainmachine/translations/hu.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, "step": { "user": { "data": { @@ -16,7 +22,8 @@ "init": { "data": { "zone_run_time": "Alap\u00e9rtelmezett z\u00f3nafut\u00e1si id\u0151 (m\u00e1sodpercben)" - } + }, + "title": "RainMachine konfigur\u00e1l\u00e1sa" } } } diff --git a/homeassistant/components/rainmachine/translations/id.json b/homeassistant/components/rainmachine/translations/id.json new file mode 100644 index 00000000000..482ffb75278 --- /dev/null +++ b/homeassistant/components/rainmachine/translations/id.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "ip_address": "Nama Host atau Alamat IP", + "password": "Kata Sandi", + "port": "Port" + }, + "title": "Isi informasi Anda" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "zone_run_time": "Waktu berjalan zona default (dalam detik)" + }, + "title": "Konfigurasikan RainMachine" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rainmachine/translations/ko.json b/homeassistant/components/rainmachine/translations/ko.json index 08ccfd1f5b9..0e38f4c4dfa 100644 --- a/homeassistant/components/rainmachine/translations/ko.json +++ b/homeassistant/components/rainmachine/translations/ko.json @@ -16,5 +16,15 @@ "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4 \uc785\ub825\ud558\uae30" } } + }, + "options": { + "step": { + "init": { + "data": { + "zone_run_time": "\uae30\ubcf8 \uc9c0\uc5ed \uc2e4\ud589 \uc2dc\uac04 (\ucd08)" + }, + "title": "RainMachine \uad6c\uc131\ud558\uae30" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/recollect_waste/translations/hu.json b/homeassistant/components/recollect_waste/translations/hu.json index 112c8cb8385..3222f50be02 100644 --- a/homeassistant/components/recollect_waste/translations/hu.json +++ b/homeassistant/components/recollect_waste/translations/hu.json @@ -14,5 +14,12 @@ } } } + }, + "options": { + "step": { + "init": { + "title": "Recollect Waste konfigur\u00e1l\u00e1sa" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/recollect_waste/translations/id.json b/homeassistant/components/recollect_waste/translations/id.json new file mode 100644 index 00000000000..1c0656810dd --- /dev/null +++ b/homeassistant/components/recollect_waste/translations/id.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "invalid_place_or_service_id": "Place atau Service ID tidak valid" + }, + "step": { + "user": { + "data": { + "place_id": "Place ID", + "service_id": "Service ID" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "friendly_name": "Gunakan nama alias untuk jenis pengambilan (jika memungkinkan)" + }, + "title": "Konfigurasikan Recollect Waste" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recollect_waste/translations/ko.json b/homeassistant/components/recollect_waste/translations/ko.json index 17dee71d640..422843c67f2 100644 --- a/homeassistant/components/recollect_waste/translations/ko.json +++ b/homeassistant/components/recollect_waste/translations/ko.json @@ -2,6 +2,27 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "invalid_place_or_service_id": "\uc7a5\uc18c \ub610\ub294 \uc11c\ube44\uc2a4 ID\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "place_id": "\uc7a5\uc18c ID", + "service_id": "\uc11c\ube44\uc2a4 ID" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "friendly_name": "\uc218\uac70 \uc720\ud615\uc5d0 \uce5c\uc219\ud55c \uc774\ub984\uc744 \uc0ac\uc6a9\ud558\uae30 (\uac00\ub2a5\ud55c \uacbd\uc6b0)" + }, + "title": "Recollect Waste \uad6c\uc131\ud558\uae30" + } } } } \ No newline at end of file diff --git a/homeassistant/components/remote/translations/hu.json b/homeassistant/components/remote/translations/hu.json index fa0bf3fee90..39ce5f17a12 100644 --- a/homeassistant/components/remote/translations/hu.json +++ b/homeassistant/components/remote/translations/hu.json @@ -1,4 +1,19 @@ { + "device_automation": { + "action_type": { + "toggle": "{entity_name} be/kikapcsol\u00e1sa", + "turn_off": "{entity_name} kikapcsol\u00e1sa", + "turn_on": "{entity_name} bekapcsol\u00e1sa" + }, + "condition_type": { + "is_off": "{entity_name} ki van kapcsolva", + "is_on": "{entity_name} be van kapcsolva" + }, + "trigger_type": { + "turned_off": "{entity_name} ki lett kapcsolva", + "turned_on": "{entity_name} be lett kapcsolva" + } + }, "state": { "_": { "off": "Ki", diff --git a/homeassistant/components/remote/translations/id.json b/homeassistant/components/remote/translations/id.json index e824cafff4e..09552be40d4 100644 --- a/homeassistant/components/remote/translations/id.json +++ b/homeassistant/components/remote/translations/id.json @@ -1,8 +1,23 @@ { + "device_automation": { + "action_type": { + "toggle": "Nyala/matikan {entity_name}", + "turn_off": "Matikan {entity_name}", + "turn_on": "Nyalakan {entity_name}" + }, + "condition_type": { + "is_off": "{entity_name} mati", + "is_on": "{entity_name} nyala" + }, + "trigger_type": { + "turned_off": "{entity_name} dimatikan", + "turned_on": "{entity_name} dinyalakan" + } + }, "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" } }, "title": "Daring" diff --git a/homeassistant/components/remote/translations/ko.json b/homeassistant/components/remote/translations/ko.json index bd055e21f5b..a89c4b36f35 100644 --- a/homeassistant/components/remote/translations/ko.json +++ b/homeassistant/components/remote/translations/ko.json @@ -1,17 +1,17 @@ { "device_automation": { "action_type": { - "toggle": "{entity_name} \ud1a0\uae00", - "turn_off": "{entity_name} \ub044\uae30", - "turn_on": "{entity_name} \ucf1c\uae30" + "toggle": "{entity_name}\uc744(\ub97c) \ud1a0\uae00\ud558\uae30", + "turn_off": "{entity_name}\uc744(\ub97c) \ub044\uae30", + "turn_on": "{entity_name}\uc744(\ub97c) \ucf1c\uae30" }, "condition_type": { - "is_off": "{entity_name} \uc774 \uaebc\uc838 \uc788\uc73c\uba74", - "is_on": "{entity_name} \uc774 \ucf1c\uc838 \uc788\uc73c\uba74" + "is_off": "{entity_name}\uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name}\uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" }, "trigger_type": { - "turned_off": "{entity_name} \uaebc\uc9d0", - "turned_on": "{entity_name} \ucf1c\uc9d0" + "turned_off": "{entity_name}\uc774(\uac00) \uaebc\uc84c\uc744 \ub54c", + "turned_on": "{entity_name}\uc774(\uac00) \ucf1c\uc84c\uc744 \ub54c" } }, "state": { diff --git a/homeassistant/components/remote/translations/zh-Hans.json b/homeassistant/components/remote/translations/zh-Hans.json index ba1344fbb74..f6c509d4a08 100644 --- a/homeassistant/components/remote/translations/zh-Hans.json +++ b/homeassistant/components/remote/translations/zh-Hans.json @@ -1,7 +1,9 @@ { "device_automation": { "action_type": { - "turn_off": "\u5173\u95ed {entity_name}" + "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", diff --git a/homeassistant/components/rfxtrx/translations/de.json b/homeassistant/components/rfxtrx/translations/de.json index b1e4197c0f1..2315e739909 100644 --- a/homeassistant/components/rfxtrx/translations/de.json +++ b/homeassistant/components/rfxtrx/translations/de.json @@ -50,7 +50,8 @@ "data": { "off_delay": "Ausschaltverz\u00f6gerung", "off_delay_enabled": "Ausschaltverz\u00f6gerung aktivieren", - "replace_device": "W\u00e4hle ein Ger\u00e4t aus, das ersetzt werden soll" + "replace_device": "W\u00e4hle ein Ger\u00e4t aus, das ersetzt werden soll", + "venetian_blind_mode": "Jalousie-Modus" } } } diff --git a/homeassistant/components/rfxtrx/translations/fr.json b/homeassistant/components/rfxtrx/translations/fr.json index c0df7233458..8794b3913f1 100644 --- a/homeassistant/components/rfxtrx/translations/fr.json +++ b/homeassistant/components/rfxtrx/translations/fr.json @@ -5,9 +5,13 @@ "cannot_connect": "\u00c9chec de connexion" }, "error": { - "cannot_connect": "\u00c9chec de connexion" + "cannot_connect": "\u00c9chec de connexion", + "one": "Vide", + "other": "Vide" }, "step": { + "one": "Vide", + "other": "Vide", "setup_network": { "data": { "host": "H\u00f4te", @@ -35,6 +39,7 @@ } } }, + "one": "Vide", "options": { "error": { "already_configured_device": "L'appareil est d\u00e9j\u00e0 configur\u00e9", @@ -70,5 +75,6 @@ "title": "Configurer les options de l'appareil" } } - } + }, + "other": "Vide" } \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/hu.json b/homeassistant/components/rfxtrx/translations/hu.json index 4e04ab16ce2..20ef3db6171 100644 --- a/homeassistant/components/rfxtrx/translations/hu.json +++ b/homeassistant/components/rfxtrx/translations/hu.json @@ -1,9 +1,19 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { + "setup_network": { + "data": { + "host": "Hoszt", + "port": "Port" + } + }, "setup_serial": { "data": { "device": "Eszk\u00f6z kiv\u00e1laszt\u00e1sa" @@ -11,13 +21,18 @@ "title": "Eszk\u00f6z" }, "setup_serial_manual_path": { + "data": { + "device": "USB eszk\u00f6z el\u00e9r\u00e9si \u00fat" + }, "title": "El\u00e9r\u00e9si \u00fat" } } }, "options": { "error": { - "invalid_event_code": "\u00c9rv\u00e9nytelen esem\u00e9nyk\u00f3d" + "already_configured_device": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "invalid_event_code": "\u00c9rv\u00e9nytelen esem\u00e9nyk\u00f3d", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "prompt_options": { diff --git a/homeassistant/components/rfxtrx/translations/id.json b/homeassistant/components/rfxtrx/translations/id.json new file mode 100644 index 00000000000..9836d252c68 --- /dev/null +++ b/homeassistant/components/rfxtrx/translations/id.json @@ -0,0 +1,73 @@ +{ + "config": { + "abort": { + "already_configured": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "cannot_connect": "Gagal terhubung" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "setup_network": { + "data": { + "host": "Host", + "port": "Port" + }, + "title": "Pilih alamat koneksi" + }, + "setup_serial": { + "data": { + "device": "Pilih perangkat" + }, + "title": "Perangkat" + }, + "setup_serial_manual_path": { + "data": { + "device": "Jalur Perangkat USB" + }, + "title": "Jalur" + }, + "user": { + "data": { + "type": "Jenis koneksi" + }, + "title": "Pilih jenis koneksi" + } + } + }, + "options": { + "error": { + "already_configured_device": "Perangkat sudah dikonfigurasi", + "invalid_event_code": "Kode event tidak valid", + "invalid_input_2262_off": "Masukan tidak valid untuk perintah mematikan", + "invalid_input_2262_on": "Masukan tidak valid untuk perintah menyalakan", + "invalid_input_off_delay": "Input tidak valid untuk penundaan mematikan", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "prompt_options": { + "data": { + "automatic_add": "Aktifkan penambahan otomatis", + "debug": "Aktifkan debugging", + "device": "Pilih perangkat untuk dikonfigurasi", + "event_code": "Masukkan kode event untuk ditambahkan", + "remove_device": "Pilih perangkat yang akan dihapus" + }, + "title": "Opsi Rfxtrx" + }, + "set_device_options": { + "data": { + "command_off": "Nilai bit data untuk perintah mematikan", + "command_on": "Nilai bit data untuk perintah menyalakan", + "data_bit": "Jumlah bit data", + "fire_event": "Aktifkan event perangkat", + "off_delay": "Penundaan mematikan", + "off_delay_enabled": "Aktifkan penundaan mematikan", + "replace_device": "Pilih perangkat yang akan diganti", + "signal_repetitions": "Jumlah pengulangan sinyal" + }, + "title": "Konfigurasi opsi perangkat" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/ko.json b/homeassistant/components/rfxtrx/translations/ko.json index e8c83a7bfd7..891926083dd 100644 --- a/homeassistant/components/rfxtrx/translations/ko.json +++ b/homeassistant/components/rfxtrx/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "already_configured": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "error": { @@ -12,19 +12,63 @@ "data": { "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8" - } + }, + "title": "\uc5f0\uacb0 \uc8fc\uc18c \uc120\ud0dd\ud558\uae30" + }, + "setup_serial": { + "data": { + "device": "\uae30\uae30 \uc120\ud0dd\ud558\uae30" + }, + "title": "\uae30\uae30" }, "setup_serial_manual_path": { "data": { "device": "USB \uc7a5\uce58 \uacbd\ub85c" - } + }, + "title": "\uacbd\ub85c" + }, + "user": { + "data": { + "type": "\uc5f0\uacb0 \uc720\ud615" + }, + "title": "\uc5f0\uacb0 \uc720\ud615 \uc120\ud0dd\ud558\uae30" } } }, "options": { "error": { "already_configured_device": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_event_code": "\uc774\ubca4\ud2b8 \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_input_2262_off": "\ub044\uae30 \uba85\ub839\uc5d0 \ub300\ud55c \uc785\ub825\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_input_2262_on": "\ucf1c\uae30 \uba85\ub839\uc5d0 \ub300\ud55c \uc785\ub825\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_input_off_delay": "\uc790\ub3d9 \uaebc\uc9c4 \uc0c1\ud0dc\uc5d0 \ub300\ud55c \uc785\ub825\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "prompt_options": { + "data": { + "automatic_add": "\uc790\ub3d9 \ucd94\uac00 \ud65c\uc131\ud654\ud558\uae30", + "debug": "\ub514\ubc84\uae45 \ud65c\uc131\ud654\ud558\uae30", + "device": "\uad6c\uc131\ud560 \uae30\uae30 \uc120\ud0dd\ud558\uae30", + "event_code": "\ucd94\uac00\ud560 \uc774\ubca4\ud2b8 \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", + "remove_device": "\uc0ad\uc81c\ud560 \uae30\uae30 \uc120\ud0dd\ud558\uae30" + }, + "title": "Rfxtrx \uc635\uc158" + }, + "set_device_options": { + "data": { + "command_off": "\ub044\uae30 \uba85\ub839\uc5d0 \ub300\ud55c \ub370\uc774\ud130 \ube44\ud2b8 \uac12", + "command_on": "\ucf1c\uae30 \uba85\ub839\uc5d0 \ub300\ud55c \ub370\uc774\ud130 \ube44\ud2b8 \uac12", + "data_bit": "\ub370\uc774\ud130 \ube44\ud2b8 \uc218", + "fire_event": "\uae30\uae30 \uc774\ubca4\ud2b8 \ud65c\uc131\ud654\ud558\uae30", + "off_delay": "\uc790\ub3d9 \uaebc\uc9c4 \uc0c1\ud0dc", + "off_delay_enabled": "\uc790\ub3d9 \uaebc\uc9c4 \uc0c1\ud0dc(Off Delay) \ud65c\uc131\ud654\ud558\uae30", + "replace_device": "\uad50\uccb4\ud560 \uae30\uae30 \uc120\ud0dd\ud558\uae30", + "signal_repetitions": "\uc2e0\ud638 \ubc18\ubcf5 \ud69f\uc218", + "venetian_blind_mode": "\ubca0\ub124\uc2dc\uc548 \ube14\ub77c\uc778\ub4dc \ubaa8\ub4dc" + }, + "title": "\uae30\uae30 \uc635\uc158 \uad6c\uc131\ud558\uae30" + } } } } \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/nl.json b/homeassistant/components/rfxtrx/translations/nl.json index 0b6e8997b18..81b77f5ced5 100644 --- a/homeassistant/components/rfxtrx/translations/nl.json +++ b/homeassistant/components/rfxtrx/translations/nl.json @@ -49,6 +49,12 @@ } }, "set_device_options": { + "data": { + "data_bit": "Aantal databits", + "fire_event": "Schakel apparaatgebeurtenis in", + "off_delay": "Uitschakelvertraging", + "off_delay_enabled": "Schakel uitschakelvertraging in" + }, "title": "Configureer apparaatopties" } } diff --git a/homeassistant/components/ring/translations/he.json b/homeassistant/components/ring/translations/he.json new file mode 100644 index 00000000000..ac90b3264ea --- /dev/null +++ b/homeassistant/components/ring/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ring/translations/hu.json b/homeassistant/components/ring/translations/hu.json index fba6b944222..8e6e94eb2d6 100644 --- a/homeassistant/components/ring/translations/hu.json +++ b/homeassistant/components/ring/translations/hu.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6zt m\u00e1r konfigur\u00e1ltuk" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, "error": { - "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s.", - "unknown": "V\u00e1ratlan hiba" + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "2fa": { diff --git a/homeassistant/components/ring/translations/id.json b/homeassistant/components/ring/translations/id.json new file mode 100644 index 00000000000..19883329938 --- /dev/null +++ b/homeassistant/components/ring/translations/id.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "2fa": { + "data": { + "2fa": "Kode autentikasi dua faktor" + }, + "title": "Autentikasi dua faktor" + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Masuk dengan akun Ring" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ring/translations/zh-Hant.json b/homeassistant/components/ring/translations/zh-Hant.json index 5eb31bfa792..9f3c91e2a7c 100644 --- a/homeassistant/components/ring/translations/zh-Hant.json +++ b/homeassistant/components/ring/translations/zh-Hant.json @@ -10,9 +10,9 @@ "step": { "2fa": { "data": { - "2fa": "\u96d9\u91cd\u9a57\u8b49\u78bc" + "2fa": "\u96d9\u91cd\u8a8d\u8b49\u78bc" }, - "title": "\u96d9\u91cd\u9a57\u8b49" + "title": "\u96d9\u91cd\u8a8d\u8b49" }, "user": { "data": { diff --git a/homeassistant/components/risco/translations/hu.json b/homeassistant/components/risco/translations/hu.json index 3b2d79a34a7..ee57b9488dc 100644 --- a/homeassistant/components/risco/translations/hu.json +++ b/homeassistant/components/risco/translations/hu.json @@ -2,6 +2,27 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "pin": "PIN-k\u00f3d", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Be\u00e1ll\u00edt\u00e1sok konfigur\u00e1l\u00e1sa" + } } } } \ No newline at end of file diff --git a/homeassistant/components/risco/translations/id.json b/homeassistant/components/risco/translations/id.json new file mode 100644 index 00000000000..eef1f00ffa6 --- /dev/null +++ b/homeassistant/components/risco/translations/id.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "pin": "Kode PIN", + "username": "Nama Pengguna" + } + } + } + }, + "options": { + "step": { + "ha_to_risco": { + "data": { + "armed_away": "Diaktifkan untuk Keluar", + "armed_custom_bypass": "Diaktifkan Khusus", + "armed_home": "Diaktifkan untuk Di Rumah", + "armed_night": "Diaktifkan untuk Malam" + }, + "description": "Pilih status mana yang akan disetel pada Risco ketika mengaktifkan alarm Home Assistant", + "title": "Petakan status Home Assistant ke status Risco" + }, + "init": { + "data": { + "code_arm_required": "Membutuhkan Kode PIN untuk mengaktifkan", + "code_disarm_required": "Membutuhkan Kode PIN untuk menonaktifkan", + "scan_interval": "Seberapa sering untuk mengambil dari Risco (dalam detik)" + }, + "title": "Konfigurasi opsi" + }, + "risco_to_ha": { + "data": { + "A": "Grup A", + "B": "Grup B", + "C": "Grup C", + "D": "Grup D", + "arm": "Diaktifkan (Keluar)", + "partial_arm": "Diaktifkan Sebagian (Di Rumah)" + }, + "description": "Pilih status mana untuk masing-masing status yang akan dilaporkan oleh Home Assistant ke Risco", + "title": "Petakan status Risco ke status Home Assistant" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/risco/translations/ko.json b/homeassistant/components/risco/translations/ko.json index f3065256e7f..6ceaedbc6dc 100644 --- a/homeassistant/components/risco/translations/ko.json +++ b/homeassistant/components/risco/translations/ko.json @@ -22,20 +22,21 @@ "step": { "ha_to_risco": { "data": { - "armed_away": "\uacbd\ube44\uc911(\uc678\ucd9c)", - "armed_custom_bypass": "\uacbd\ube44\uc911(\uc0ac\uc6a9\uc790 \uc6b0\ud68c)", - "armed_home": "\uc9d1\uc548 \uacbd\ube44\uc911", - "armed_night": "\uc57c\uac04 \uacbd\ube44\uc911" + "armed_away": "\uacbd\ube44 \uc911(\uc678\ucd9c)", + "armed_custom_bypass": "\uacbd\ube44 \uc911 (\uc0ac\uc6a9\uc790 \uc6b0\ud68c)", + "armed_home": "\uacbd\ube44 \uc911 (\uc7ac\uc2e4)", + "armed_night": "\uc57c\uac04 \uacbd\ube44 \uc911" }, - "description": "Home Assistant \uc54c\ub78c\uc744 \ud65c\uc131\ud654 \ud560 \ub54c Risco \uc54c\ub78c\uc758 \uc0c1\ud0dc\ub97c \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624.", - "title": "Home Assistant \uc0c1\ud0dc\ub97c Risco \uc0c1\ud0dc\ub85c \ub9e4\ud551" + "description": "Home Assistant \uc54c\ub78c\uc744 \uc124\uc815\ud560 \ub54c Risco \uc54c\ub78c\uc744 \uc124\uc815\ud560 \uc0c1\ud0dc\ub97c \uc120\ud0dd\ud558\uae30", + "title": "Home Assistant \uc0c1\ud0dc\ub97c Risco \uc0c1\ud0dc\uc5d0 \ub9e4\ud551\ud558\uae30" }, "init": { "data": { "code_arm_required": "\uc124\uc815\ud558\ub824\uba74 PIN \ucf54\ub4dc\uac00 \ud544\uc694\ud569\ub2c8\ub2e4", "code_disarm_required": "\ud574\uc81c\ud558\ub824\uba74 PIN \ucf54\ub4dc\uac00 \ud544\uc694\ud569\ub2c8\ub2e4", "scan_interval": "Risco\ub97c \ud3f4\ub9c1\ud558\ub294 \ube48\ub3c4 (\ucd08)" - } + }, + "title": "\uc635\uc158 \uad6c\uc131\ud558\uae30" }, "risco_to_ha": { "data": { @@ -43,11 +44,11 @@ "B": "\uadf8\ub8f9 B", "C": "\uadf8\ub8f9 C", "D": "\uadf8\ub8f9 D", - "arm": "\uacbd\ube44\uc911(\uc678\ucd9c)", - "partial_arm": "\ubd80\ubd84 \uacbd\ube44 \uc124\uc815 (\uc7ac\uc2e4)" + "arm": "\uacbd\ube44 \uc911 (\uc678\ucd9c)", + "partial_arm": "\ubd80\ubd84 \uacbd\ube44 \uc911 (\uc7ac\uc2e4)" }, - "description": "Risco\uc5d0\uc11c\ubcf4\uace0\ud558\ub294 \ubaa8\ub4e0 \uc0c1\ud0dc\uc5d0 \ub300\ud574 Home Assistant \uc54c\ub78c\uc774 \ubcf4\uace0 \ud560 \uc0c1\ud0dc\ub97c \uc120\ud0dd\ud569\ub2c8\ub2e4.", - "title": "Risco \uc0c1\ud0dc\ub97c \ud648 \uc5b4\uc2dc\uc2a4\ud134\ud2b8 \uc0c1\ud0dc\uc5d0 \ub9e4\ud551" + "description": "Risco\uac00 \ubcf4\uace0\ud558\ub294 \ubaa8\ub4e0 \uc0c1\ud0dc\uc5d0 \ub300\ud574 Home Assistant \uc54c\ub78c\uc774 \ubcf4\uace0\ud560 \uc0c1\ud0dc\ub97c \uc120\ud0dd\ud558\uae30", + "title": "Risco \uc0c1\ud0dc\ub97c Home Assistant \uc0c1\ud0dc\uc5d0 \ub9e4\ud551\ud558\uae30" } } } diff --git a/homeassistant/components/rituals_perfume_genie/translations/bg.json b/homeassistant/components/rituals_perfume_genie/translations/bg.json new file mode 100644 index 00000000000..cef3726d759 --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rituals_perfume_genie/translations/es.json b/homeassistant/components/rituals_perfume_genie/translations/es.json index bc74ecfd7ea..bdb1933eaf7 100644 --- a/homeassistant/components/rituals_perfume_genie/translations/es.json +++ b/homeassistant/components/rituals_perfume_genie/translations/es.json @@ -1,7 +1,19 @@ { "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "unknown": "Error inesperado" + }, "step": { "user": { + "data": { + "email": "email", + "password": "Contrase\u00f1a" + }, "title": "Con\u00e9ctese a su cuenta de Rituals" } } diff --git a/homeassistant/components/rituals_perfume_genie/translations/hu.json b/homeassistant/components/rituals_perfume_genie/translations/hu.json new file mode 100644 index 00000000000..4ecaf2ba0d0 --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/hu.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "email": "E-mail", + "password": "Jelsz\u00f3" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rituals_perfume_genie/translations/id.json b/homeassistant/components/rituals_perfume_genie/translations/id.json new file mode 100644 index 00000000000..91d931005cf --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Kata Sandi" + }, + "title": "Hubungkan ke akun Ritual Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rituals_perfume_genie/translations/ko.json b/homeassistant/components/rituals_perfume_genie/translations/ko.json new file mode 100644 index 00000000000..489e4f5b806 --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/ko.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "email": "\uc774\uba54\uc77c", + "password": "\ube44\ubc00\ubc88\ud638" + }, + "title": "Rituals \uacc4\uc815\uc5d0 \uc5f0\uacb0\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/hu.json b/homeassistant/components/roku/translations/hu.json index 8b4e9f9d54d..5485d9e00ce 100644 --- a/homeassistant/components/roku/translations/hu.json +++ b/homeassistant/components/roku/translations/hu.json @@ -2,12 +2,26 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "unknown": "V\u00e1ratlan hiba" + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, + "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "data": { + "one": "Egy", + "other": "Egy\u00e9b" + }, + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a(z) {name}-t?", + "title": "Roku" + }, + "ssdp_confirm": { + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a {name}-t?", + "title": "Roku" + }, "user": { "data": { "host": "Hoszt" diff --git a/homeassistant/components/roku/translations/id.json b/homeassistant/components/roku/translations/id.json new file mode 100644 index 00000000000..0e60de9b61f --- /dev/null +++ b/homeassistant/components/roku/translations/id.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "Roku: {name}", + "step": { + "discovery_confirm": { + "description": "Ingin menyiapkan {name}?", + "title": "Roku" + }, + "ssdp_confirm": { + "description": "Ingin menyiapkan {name}?", + "title": "Roku" + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Masukkan informasi Roku Anda." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/ko.json b/homeassistant/components/roku/translations/ko.json index 19f4c16785f..cb127234601 100644 --- a/homeassistant/components/roku/translations/ko.json +++ b/homeassistant/components/roku/translations/ko.json @@ -10,8 +10,12 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "description": "{name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Roku" + }, "ssdp_confirm": { - "description": "{name} \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "{name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Roku" }, "user": { diff --git a/homeassistant/components/roku/translations/nl.json b/homeassistant/components/roku/translations/nl.json index d892d2c78d2..daecee2f1dc 100644 --- a/homeassistant/components/roku/translations/nl.json +++ b/homeassistant/components/roku/translations/nl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Roku-apparaat is al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang", "unknown": "Onverwachte fout" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw" + "cannot_connect": "Kan geen verbinding maken" }, "flow_title": "Roku: {name}", "step": { @@ -15,7 +15,7 @@ "one": "Een", "other": "Ander" }, - "description": "Wilt u {naam} instellen?", + "description": "Wilt u {name} instellen?", "title": "Roku" }, "ssdp_confirm": { @@ -28,7 +28,7 @@ }, "user": { "data": { - "host": "Host- of IP-adres" + "host": "Host" }, "description": "Voer uw Roku-informatie in." } diff --git a/homeassistant/components/roomba/translations/bg.json b/homeassistant/components/roomba/translations/bg.json new file mode 100644 index 00000000000..10f778472e6 --- /dev/null +++ b/homeassistant/components/roomba/translations/bg.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "link_manual": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430" + }, + "title": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u0430\u0442\u0430" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/ca.json b/homeassistant/components/roomba/translations/ca.json index b2fe68c876c..d5c81db15ca 100644 --- a/homeassistant/components/roomba/translations/ca.json +++ b/homeassistant/components/roomba/translations/ca.json @@ -15,7 +15,7 @@ "host": "Amfitri\u00f3" }, "description": "Selecciona un/a Roomba o Braava.", - "title": "Connecta't al dispositiu autom\u00e0ticament" + "title": "Connexi\u00f3 autom\u00e0tica amb el dispositiu" }, "link": { "description": "Mant\u00e9 premut el bot\u00f3 d'inici a {name} fins que el dispositiu emeti un so (aproximadament dos segons).", diff --git a/homeassistant/components/roomba/translations/en.json b/homeassistant/components/roomba/translations/en.json index 11c9c6e27fd..ddbf5a10a78 100644 --- a/homeassistant/components/roomba/translations/en.json +++ b/homeassistant/components/roomba/translations/en.json @@ -59,4 +59,4 @@ } } } -} +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/et.json b/homeassistant/components/roomba/translations/et.json index e038257c12d..5718e8b6a53 100644 --- a/homeassistant/components/roomba/translations/et.json +++ b/homeassistant/components/roomba/translations/et.json @@ -15,7 +15,7 @@ "host": "Host" }, "description": "Vali Roomba v\u00f5i Braava seade.", - "title": "\u00dchendu seadmega automaatselt" + "title": "\u00dchenda seadmega automaatselt" }, "link": { "description": "Vajuta ja hoia all seadme {name} nuppu Home kuni seade teeb piiksu (umbes kaks sekundit).", diff --git a/homeassistant/components/roomba/translations/he.json b/homeassistant/components/roomba/translations/he.json new file mode 100644 index 00000000000..3007c0e968c --- /dev/null +++ b/homeassistant/components/roomba/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/hu.json b/homeassistant/components/roomba/translations/hu.json index 357ca74746d..8f7c2c97884 100644 --- a/homeassistant/components/roomba/translations/hu.json +++ b/homeassistant/components/roomba/translations/hu.json @@ -1,10 +1,51 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "not_irobot_device": "A felfedezett eszk\u00f6z nem iRobot eszk\u00f6z" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "flow_title": "iRobot {name} ({host})", "step": { + "init": { + "data": { + "host": "Hoszt" + }, + "title": "Automatikus csatlakoz\u00e1s az eszk\u00f6zh\u00f6z" + }, + "link_manual": { + "data": { + "password": "Jelsz\u00f3" + }, + "title": "Jelsz\u00f3 megad\u00e1sa" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Hoszt" + }, + "title": "Manu\u00e1lis csatlakoz\u00e1s az eszk\u00f6zh\u00f6z" + }, "user": { "data": { + "blid": "BLID", + "delay": "K\u00e9sleltet\u00e9s", "host": "Hoszt", "password": "Jelsz\u00f3" + }, + "title": "Csatlakoz\u00e1s az eszk\u00f6zh\u00f6z" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "continuous": "Folyamatos", + "delay": "K\u00e9sleltet\u00e9s" } } } diff --git a/homeassistant/components/roomba/translations/id.json b/homeassistant/components/roomba/translations/id.json new file mode 100644 index 00000000000..3afe75ae09d --- /dev/null +++ b/homeassistant/components/roomba/translations/id.json @@ -0,0 +1,62 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "not_irobot_device": "Perangkat yang ditemukan bukan perangkat iRobot" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "iRobot {name} ({host})", + "step": { + "init": { + "data": { + "host": "Host" + }, + "description": "Pilih Roomba atau Braava.", + "title": "Sambungkan secara otomatis ke perangkat" + }, + "link": { + "description": "Tekan dan tahan tombol Home pada {name} hingga perangkat mengeluarkan suara (sekitar dua detik).", + "title": "Ambil Kata Sandi" + }, + "link_manual": { + "data": { + "password": "Kata Sandi" + }, + "description": "Kata sandi tidak dapat diambil dari perangkat secara otomatis. Ikuti langkah-langkah yang diuraikan dalam dokumentasi di: {auth_help_url}", + "title": "Masukkan Kata Sandi" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Host" + }, + "description": "Tidak ada Roomba atau Braava yang ditemukan di jaringan Anda. BLID adalah bagian dari nama host perangkat setelah `iRobot-`. Ikuti langkah-langkah yang diuraikan dalam dokumentasi di: {auth_help_url}", + "title": "Hubungkan ke perangkat secara manual" + }, + "user": { + "data": { + "blid": "BLID", + "continuous": "Terus menerus", + "delay": "Tunda", + "host": "Host", + "password": "Kata Sandi" + }, + "description": "Saat ini proses mengambil BLID dan kata sandi merupakan proses manual. Iikuti langkah-langkah yang diuraikan dalam dokumentasi di: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", + "title": "Hubungkan ke perangkat" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "continuous": "Terus menerus", + "delay": "Tunda" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/ko.json b/homeassistant/components/roomba/translations/ko.json index d9c661a20dd..5066225100b 100644 --- a/homeassistant/components/roomba/translations/ko.json +++ b/homeassistant/components/roomba/translations/ko.json @@ -2,26 +2,39 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "not_irobot_device": "\ubc1c\uacac\ub41c \uae30\uae30\ub294 \uc544\uc774\ub85c\ubd07 \uae30\uae30\uac00 \uc544\ub2d9\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, + "flow_title": "\uc544\uc774\ub85c\ubd07: {name} ({host})", "step": { "init": { "data": { "host": "\ud638\uc2a4\ud2b8" - } + }, + "description": "\ub8f8\ubc14 \ub610\ub294 \ube0c\ub77c\ubc14\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", + "title": "\uae30\uae30\uc5d0 \uc790\ub3d9\uc73c\ub85c \uc5f0\uacb0\ud558\uae30" + }, + "link": { + "description": "\uae30\uae30\uc5d0\uc11c \uc18c\ub9ac\uac00 \ub0a0 \ub54c\uae4c\uc9c0 {name}\uc758 \ud648 \ubc84\ud2bc\uc744 \uae38\uac8c \ub20c\ub7ec\uc8fc\uc138\uc694 (\uc57d 2\ucd08).", + "title": "\ube44\ubc00\ubc88\ud638 \uac00\uc838\uc624\uae30" }, "link_manual": { "data": { "password": "\ube44\ubc00\ubc88\ud638" - } + }, + "description": "\uae30\uae30\uc5d0\uc11c \ube44\ubc00\ubc88\ud638\ub97c \uc790\ub3d9\uc73c\ub85c \uac00\uc838\uc62c \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uad00\ub828 \ubb38\uc11c\uc5d0 \ub098\uc640 \uc788\ub294 {auth_help_url} \ub2e8\uacc4\ub97c \ub530\ub77c\uc8fc\uc138\uc694.", + "title": "\ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" }, "manual": { "data": { + "blid": "BLID", "host": "\ud638\uc2a4\ud2b8" - } + }, + "description": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ub8f8\ubc14 \ub610\ub294 \ube0c\ub77c\ubc14\uac00 \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. BLID\ub294 `iRobot-` \ub4a4\uc758 \uae30\uae30 \ud638\uc2a4\ud2b8 \uc774\ub984 \ubd80\ubd84\uc785\ub2c8\ub2e4. \uad00\ub828 \ubb38\uc11c\uc5d0 \ub098\uc640 \uc788\ub294 {auth_help_url} \ub2e8\uacc4\ub97c \ub530\ub77c\uc8fc\uc138\uc694.", + "title": "\uae30\uae30\uc5d0 \uc218\ub3d9\uc73c\ub85c \uc5f0\uacb0\ud558\uae30" }, "user": { "data": { @@ -31,7 +44,7 @@ "host": "\ud638\uc2a4\ud2b8", "password": "\ube44\ubc00\ubc88\ud638" }, - "description": "\ud604\uc7ac BLID \ubc0f \ube44\ubc00\ubc88\ud638\ub294 \uc218\ub3d9\uc73c\ub85c \uac00\uc838\uc640\uc57c\ud569\ub2c8\ub2e4. \ub2e4\uc74c \ubb38\uc11c\uc5d0 \uc124\uba85\ub41c \uc808\ucc28\ub97c \ub530\ub77c \uc124\uc815\ud574\uc8fc\uc138\uc694: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", + "description": "\ud604\uc7ac BLID \ubc0f \ube44\ubc00\ubc88\ud638\ub294 \uc218\ub3d9\uc73c\ub85c \uac00\uc838\uc640\uc57c\ud569\ub2c8\ub2e4. \ub2e4\uc74c \uad00\ub828 \ubb38\uc11c\uc5d0 \ub098\uc640 \uc788\ub294 \ub2e8\uacc4\ub97c \ub530\ub77c\uc8fc\uc138\uc694: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", "title": "\uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/roomba/translations/lb.json b/homeassistant/components/roomba/translations/lb.json index 500aa4fbee6..5578a7710db 100644 --- a/homeassistant/components/roomba/translations/lb.json +++ b/homeassistant/components/roomba/translations/lb.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Feeler beim verbannen" }, + "flow_title": "iRobot {name} ({host})", "step": { "init": { "data": { diff --git a/homeassistant/components/roomba/translations/nl.json b/homeassistant/components/roomba/translations/nl.json index 0177adaea1f..f26b28d2248 100644 --- a/homeassistant/components/roomba/translations/nl.json +++ b/homeassistant/components/roomba/translations/nl.json @@ -6,9 +6,9 @@ "not_irobot_device": "Het gevonden apparaat is geen iRobot-apparaat" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw" + "cannot_connect": "Kan geen verbinding maken" }, - "flow_title": "iRobot {naam} ({host})", + "flow_title": "iRobot {name} ({host})", "step": { "init": { "data": { @@ -18,7 +18,7 @@ "title": "Automatisch verbinding maken met het apparaat" }, "link": { - "description": "Houd de Home-knop op {naam} ingedrukt totdat het apparaat een geluid genereert (ongeveer twee seconden).", + "description": "Houd de Home-knop op {name} ingedrukt totdat het apparaat een geluid genereert (ongeveer twee seconden).", "title": "Wachtwoord opvragen" }, "link_manual": { @@ -32,14 +32,16 @@ "data": { "blid": "BLID", "host": "Host" - } + }, + "description": "Er is geen Roomba of Braava ontdekt op uw netwerk. De BLID is het gedeelte van de hostnaam van het apparaat na `iRobot-`. Volg de stappen die worden beschreven in de documentatie op: {auth_help_url}", + "title": "Handmatig verbinding maken met het apparaat" }, "user": { "data": { "blid": "BLID", "continuous": "Doorlopend", "delay": "Vertraging", - "host": "Hostnaam of IP-adres", + "host": "Host", "password": "Wachtwoord" }, "description": "Het ophalen van de BLID en het wachtwoord is momenteel een handmatig proces. Volg de stappen in de documentatie op: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", diff --git a/homeassistant/components/roon/translations/hu.json b/homeassistant/components/roon/translations/hu.json index 3b2d79a34a7..6ea0aa2b25c 100644 --- a/homeassistant/components/roon/translations/hu.json +++ b/homeassistant/components/roon/translations/hu.json @@ -2,6 +2,18 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "duplicate_entry": "Ez a hoszt m\u00e1r konfigur\u00e1lva van.", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/roon/translations/id.json b/homeassistant/components/roon/translations/id.json new file mode 100644 index 00000000000..bfd70955ac8 --- /dev/null +++ b/homeassistant/components/roon/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "duplicate_entry": "Host ini telah ditambahkan.", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "link": { + "description": "Anda harus mengotorisasi Home Assistant di Roon. Setelah Anda mengeklik kirim, buka aplikasi Roon Core, buka Pengaturan dan aktifkan HomeAssistant pada tab Ekstensi.", + "title": "Otorisasi HomeAssistant di Roon" + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Tidak dapat menemukan server Roon, masukkan Nama Host atau IP Anda." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roon/translations/ko.json b/homeassistant/components/roon/translations/ko.json index 7c051d49dc7..b1e0fee4089 100644 --- a/homeassistant/components/roon/translations/ko.json +++ b/homeassistant/components/roon/translations/ko.json @@ -10,14 +10,14 @@ }, "step": { "link": { - "description": "Roon\uc5d0\uc11c \ud648 \uc5b4\uc2dc\uc2a4\ud134\ud2b8\ub97c \uc778\uc99d\ud574\uc57c\ud569\ub2c8\ub2e4. \uc81c\ucd9c\uc744 \ud074\ub9ad \ud55c \ud6c4 Roon Core \uc560\ud50c\ub9ac\ucf00\uc774\uc158\uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \uc124\uc815\uc744 \uc5f4\uace0 \ud655\uc7a5 \ud0ed\uc5d0\uc11c HomeAssistant\ub97c \ud65c\uc131\ud654\ud569\ub2c8\ub2e4.", - "title": "Roon\uc5d0\uc11c HomeAssistant \uc778\uc99d" + "description": "Roon\uc5d0\uc11c Home Assistant\ub97c \uc778\uc99d\ud574\uc8fc\uc5b4\uc57c \ud569\ub2c8\ub2e4. \ud655\uc778\uc744 \ud074\ub9ad\ud55c \ud6c4 Roon Core \uc560\ud50c\ub9ac\ucf00\uc774\uc158\uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \uc124\uc815\uc744 \uc5f4\uace0 \ud655\uc7a5 \ud0ed\uc5d0\uc11c Home Assistant\ub97c \ud65c\uc131\ud654\ud574\uc8fc\uc138\uc694.", + "title": "Roon\uc5d0\uc11c HomeAssistant \uc778\uc99d\ud558\uae30" }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8" }, - "description": "Roon \uc11c\ubc84\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ud638\uc2a4\ud2b8\uba85\uc774\ub098 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." + "description": "Roon \uc11c\ubc84\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." } } } diff --git a/homeassistant/components/rpi_power/translations/de.json b/homeassistant/components/rpi_power/translations/de.json index 9f3851f0c2b..1a00c87b985 100644 --- a/homeassistant/components/rpi_power/translations/de.json +++ b/homeassistant/components/rpi_power/translations/de.json @@ -1,11 +1,12 @@ { "config": { "abort": { + "no_devices_found": "Die f\u00fcr diese Komponente ben\u00f6tigte Systemklasse konnte nicht gefunden werden. Stellen Sie sicher, dass Ihr Kernel aktuell ist und die Hardware unterst\u00fctzt wird", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "confirm": { - "description": "M\u00f6chtest du mit der Einrichtung beginnen?" + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?" } } }, diff --git a/homeassistant/components/rpi_power/translations/hu.json b/homeassistant/components/rpi_power/translations/hu.json new file mode 100644 index 00000000000..2d1c0811286 --- /dev/null +++ b/homeassistant/components/rpi_power/translations/hu.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "step": { + "confirm": { + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?" + } + } + }, + "title": "Raspberry Pi Power Supply Checker" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/id.json b/homeassistant/components/rpi_power/translations/id.json new file mode 100644 index 00000000000..f9fcfa6c97a --- /dev/null +++ b/homeassistant/components/rpi_power/translations/id.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak dapat menemukan kelas sistem yang diperlukan untuk komponen ini, pastikan kernel Anda cukup terbaru dan perangkat kerasnya didukung", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "confirm": { + "description": "Ingin memulai penyiapan?" + } + } + }, + "title": "Pemeriksa Catu Daya Raspberry Pi" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/ko.json b/homeassistant/components/rpi_power/translations/ko.json index 445c0c34e68..e8423b7d733 100644 --- a/homeassistant/components/rpi_power/translations/ko.json +++ b/homeassistant/components/rpi_power/translations/ko.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "\uc774 \uad6c\uc131 \uc694\uc18c\uc5d0 \ud544\uc694\ud55c \uc2dc\uc2a4\ud15c \ud074\ub798\uc2a4\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucee4\ub110\uc774 \ucd5c\uc2e0\uc774\uace0 \ud558\ub4dc\uc6e8\uc5b4\uac00 \uc9c0\uc6d0\ub418\ub294\uc9c0 \ud655\uc778\ud558\uc2ed\uc2dc\uc624.", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "no_devices_found": "\uc774 \uad6c\uc131 \uc694\uc18c\uc5d0 \ud544\uc694\ud55c \uc2dc\uc2a4\ud15c \ud074\ub798\uc2a4\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucee4\ub110\uc774 \ucd5c\uc2e0 \uc0c1\ud0dc\uc774\uace0 \ud558\ub4dc\uc6e8\uc5b4\uac00 \uc9c0\uc6d0\ub418\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/ruckus_unleashed/translations/he.json b/homeassistant/components/ruckus_unleashed/translations/he.json new file mode 100644 index 00000000000..6ef580c7d8d --- /dev/null +++ b/homeassistant/components/ruckus_unleashed/translations/he.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "user": { + "data": { + "host": "\u05de\u05d0\u05e8\u05d7", + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ruckus_unleashed/translations/hu.json b/homeassistant/components/ruckus_unleashed/translations/hu.json index c1a23478ac4..0abcc301f0c 100644 --- a/homeassistant/components/ruckus_unleashed/translations/hu.json +++ b/homeassistant/components/ruckus_unleashed/translations/hu.json @@ -1,13 +1,17 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "unknown": "V\u00e1ratlan hiba" + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "user": { "data": { - "host": "Gazdag\u00e9p", + "host": "Hoszt", "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } diff --git a/homeassistant/components/ruckus_unleashed/translations/id.json b/homeassistant/components/ruckus_unleashed/translations/id.json new file mode 100644 index 00000000000..ed8fde32106 --- /dev/null +++ b/homeassistant/components/ruckus_unleashed/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/samsungtv/translations/hu.json b/homeassistant/components/samsungtv/translations/hu.json index ca42aff331a..5c517c78d69 100644 --- a/homeassistant/components/samsungtv/translations/hu.json +++ b/homeassistant/components/samsungtv/translations/hu.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Ez a Samsung TV m\u00e1r konfigur\u00e1lva van.", - "already_in_progress": "A Samsung TV konfigur\u00e1l\u00e1sa m\u00e1r folyamatban van.", + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", "auth_missing": "A Home Assistant nem jogosult csatlakozni ehhez a Samsung TV-hez. Ellen\u0151rizd a TV be\u00e1ll\u00edt\u00e1sait a Home Assistant enged\u00e9lyez\u00e9s\u00e9hez.", "cannot_connect": "Sikertelen csatlakoz\u00e1s", "not_supported": "Ez a Samsung TV k\u00e9sz\u00fcl\u00e9k jelenleg nem t\u00e1mogatott." diff --git a/homeassistant/components/samsungtv/translations/id.json b/homeassistant/components/samsungtv/translations/id.json new file mode 100644 index 00000000000..7d0f5982a65 --- /dev/null +++ b/homeassistant/components/samsungtv/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "auth_missing": "Home Assistant tidak diizinkan untuk tersambung ke TV Samsung ini. Periksa setelan TV Anda untuk mengotorisasi Home Assistant.", + "cannot_connect": "Gagal terhubung", + "not_supported": "Perangkat TV Samsung ini saat ini tidak didukung." + }, + "flow_title": "TV Samsung: {model}", + "step": { + "confirm": { + "description": "Apakah Anda ingin menyiapkan TV Samsung {model}? Jika Anda belum pernah menyambungkan Home Assistant sebelumnya, Anda akan melihat dialog di TV yang meminta otorisasi. Konfigurasi manual untuk TV ini akan ditimpa.", + "title": "TV Samsung" + }, + "user": { + "data": { + "host": "Host", + "name": "Nama" + }, + "description": "Masukkan informasi TV Samsung Anda. Jika Anda belum pernah menyambungkan Home Assistant sebelumnya, Anda akan melihat dialog di TV yang meminta otorisasi." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/samsungtv/translations/ko.json b/homeassistant/components/samsungtv/translations/ko.json index 14e35a7ff2e..5686c684e1f 100644 --- a/homeassistant/components/samsungtv/translations/ko.json +++ b/homeassistant/components/samsungtv/translations/ko.json @@ -3,14 +3,14 @@ "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", - "auth_missing": "Home Assistant \uac00 \ud574\ub2f9 \uc0bc\uc131 TV \uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc788\ub294 \uad8c\ud55c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. TV \uc124\uc815\uc744 \ud655\uc778\ud558\uc5ec Home Assistant \ub97c \uc2b9\uc778\ud574\uc8fc\uc138\uc694.", + "auth_missing": "Home Assistant\uac00 \ud574\ub2f9 \uc0bc\uc131 TV\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc788\ub294 \uad8c\ud55c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. TV \uc124\uc815\uc744 \ud655\uc778\ud558\uc5ec Home Assistant\ub97c \uc2b9\uc778\ud574\uc8fc\uc138\uc694.", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "not_supported": "\uc774 \uc0bc\uc131 TV \ubaa8\ub378\uc740 \ud604\uc7ac \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "flow_title": "\uc0bc\uc131 TV: {model}", "step": { "confirm": { - "description": "\uc0bc\uc131 TV {model} \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? Home Assistant \ub97c \uc5f0\uacb0 \ud55c \uc801\uc774 \uc5c6\ub2e4\uba74 TV \uc5d0\uc11c \uc778\uc99d\uc744 \uc694\uccad\ud558\ub294 \ud31d\uc5c5\uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4. \uc774 TV \uc758 \uc218\ub3d9\uc73c\ub85c \uad6c\uc131\ub41c \ub0b4\uc6a9\uc744 \ub36e\uc5b4\uc501\ub2c8\ub2e4.", + "description": "{model} \uc0bc\uc131 TV\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? Home Assistant\ub97c \uc5f0\uacb0\ud55c \uc801\uc774 \uc5c6\ub2e4\uba74 TV\uc5d0\uc11c \uc778\uc99d\uc744 \uc694\uccad\ud558\ub294 \ud31d\uc5c5\uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4. \uc774 TV\uc758 \uc218\ub3d9\uc73c\ub85c \uad6c\uc131\ub41c \ub0b4\uc6a9\uc740 \ub36e\uc5b4\uc50c\uc6cc\uc9d1\ub2c8\ub2e4.", "title": "\uc0bc\uc131 TV" }, "user": { diff --git a/homeassistant/components/scene/translations/id.json b/homeassistant/components/scene/translations/id.json index 65f2adf7325..827c0c81f38 100644 --- a/homeassistant/components/scene/translations/id.json +++ b/homeassistant/components/scene/translations/id.json @@ -1,3 +1,3 @@ { - "title": "Adegan" + "title": "Scene" } \ No newline at end of file diff --git a/homeassistant/components/script/translations/id.json b/homeassistant/components/script/translations/id.json index 8b23be94861..cac38736ea7 100644 --- a/homeassistant/components/script/translations/id.json +++ b/homeassistant/components/script/translations/id.json @@ -1,8 +1,8 @@ { "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" } }, "title": "Skrip" diff --git a/homeassistant/components/season/translations/sensor.id.json b/homeassistant/components/season/translations/sensor.id.json new file mode 100644 index 00000000000..fd28dca5901 --- /dev/null +++ b/homeassistant/components/season/translations/sensor.id.json @@ -0,0 +1,16 @@ +{ + "state": { + "season__season": { + "autumn": "Musim gugur", + "spring": "Musim semi", + "summer": "Musim panas", + "winter": "Musim dingin" + }, + "season__season__": { + "autumn": "Musim gugur", + "spring": "Musim semi", + "summer": "Musim panas", + "winter": "Musim dingin" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sense/translations/he.json b/homeassistant/components/sense/translations/he.json new file mode 100644 index 00000000000..3007c0e968c --- /dev/null +++ b/homeassistant/components/sense/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sense/translations/hu.json b/homeassistant/components/sense/translations/hu.json index 0085d9ea9c4..4ecaf2ba0d0 100644 --- a/homeassistant/components/sense/translations/hu.json +++ b/homeassistant/components/sense/translations/hu.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/sense/translations/id.json b/homeassistant/components/sense/translations/id.json new file mode 100644 index 00000000000..8d0d996e510 --- /dev/null +++ b/homeassistant/components/sense/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Kata Sandi" + }, + "title": "Hubungkan ke Sense Energy Monitor Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sense/translations/ko.json b/homeassistant/components/sense/translations/ko.json index 517ad7af17d..269d8a76fea 100644 --- a/homeassistant/components/sense/translations/ko.json +++ b/homeassistant/components/sense/translations/ko.json @@ -14,7 +14,7 @@ "email": "\uc774\uba54\uc77c", "password": "\ube44\ubc00\ubc88\ud638" }, - "title": "Sense Energy Monitor \uc5d0 \uc5f0\uacb0\ud558\uae30" + "title": "Sense Energy Monitor\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/sense/translations/nl.json b/homeassistant/components/sense/translations/nl.json index ee9e61b5a38..df64e83da16 100644 --- a/homeassistant/components/sense/translations/nl.json +++ b/homeassistant/components/sense/translations/nl.json @@ -4,14 +4,14 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, "step": { "user": { "data": { - "email": "E-mailadres", + "email": "E-mail", "password": "Wachtwoord" }, "title": "Maak verbinding met uw Sense Energy Monitor" diff --git a/homeassistant/components/sensor/translations/ca.json b/homeassistant/components/sensor/translations/ca.json index b351aed3049..e0cfb5faa50 100644 --- a/homeassistant/components/sensor/translations/ca.json +++ b/homeassistant/components/sensor/translations/ca.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "Nivell de bateria actual de {entity_name}", + "is_carbon_dioxide": "Concentraci\u00f3 actual de di\u00f2xid de carboni de {entity_name}", + "is_carbon_monoxide": "Concentraci\u00f3 actual de mon\u00f2xid de carboni de {entity_name}", "is_current": "Intensitat actual de {entity_name}", "is_energy": "Energia actual de {entity_name}", "is_humidity": "Humitat actual de {entity_name}", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "Canvia el nivell de bateria de {entity_name}", + "carbon_dioxide": "Canvia la concentraci\u00f3 de di\u00f2xid de carboni de {entity_name}", + "carbon_monoxide": "Canvia la concentraci\u00f3 de mon\u00f2xid de carboni de {entity_name}", "current": "Canvia la intensitat de {entity_name}", "energy": "Canvia l'energia de {entity_name}", "humidity": "Canvia la humitat de {entity_name}", diff --git a/homeassistant/components/sensor/translations/en.json b/homeassistant/components/sensor/translations/en.json index ae0c32df574..e32ae845c1c 100644 --- a/homeassistant/components/sensor/translations/en.json +++ b/homeassistant/components/sensor/translations/en.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "Current {entity_name} battery level", + "is_carbon_dioxide": "Current {entity_name} carbon dioxide concentration level", + "is_carbon_monoxide": "Current {entity_name} carbon monoxide concentration level", "is_current": "Current {entity_name} current", "is_energy": "Current {entity_name} energy", "is_humidity": "Current {entity_name} humidity", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "{entity_name} battery level changes", + "carbon_dioxide": "{entity_name} carbon dioxide concentration changes", + "carbon_monoxide": "{entity_name} carbon monoxide concentration changes", "current": "{entity_name} current changes", "energy": "{entity_name} energy changes", "humidity": "{entity_name} humidity changes", diff --git a/homeassistant/components/sensor/translations/et.json b/homeassistant/components/sensor/translations/et.json index 450f5b60537..55b1fa48a8f 100644 --- a/homeassistant/components/sensor/translations/et.json +++ b/homeassistant/components/sensor/translations/et.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "Praegune {entity_name} aku tase", + "is_carbon_dioxide": "{entity_name} praegune s\u00fcsihappegaasi tase", + "is_carbon_monoxide": "{entity_name} praegune vingugaasi tase", "is_current": "Praegune {entity_name} voolutugevus", "is_energy": "Praegune {entity_name} v\u00f5imsus", "is_humidity": "Praegune {entity_name} niiskus", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "{entity_name} aku tase muutub", + "carbon_dioxide": "{entity_name} s\u00fcsihappegaasi tase muutus", + "carbon_monoxide": "{entity_name} vingugaasi tase muutus", "current": "{entity_name} voolutugevus muutub", "energy": "{entity_name} v\u00f5imsus muutub", "humidity": "{entity_name} niiskus muutub", diff --git a/homeassistant/components/sensor/translations/fr.json b/homeassistant/components/sensor/translations/fr.json index 4705d28b5c3..ef88b7acecc 100644 --- a/homeassistant/components/sensor/translations/fr.json +++ b/homeassistant/components/sensor/translations/fr.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "Niveau de la batterie de {entity_name}", + "is_carbon_dioxide": "Niveau actuel de concentration de dioxyde de carbone {entity_name}", + "is_carbon_monoxide": "Niveau actuel de concentration de monoxyde de carbone {entity_name}", "is_current": "Courant actuel pour {entity_name}", "is_energy": "\u00c9nergie actuelle pour {entity_name}", "is_humidity": "Humidit\u00e9 de {entity_name}", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "{entity_name} modification du niveau de batterie", + "carbon_dioxide": "{entity_name} changements de concentration de dioxyde de carbone", + "carbon_monoxide": "{entity_name} changements de concentration de monoxyde de carbone", "current": "{entity_name} changement de courant", "energy": "{entity_name} changement d'\u00e9nergie", "humidity": "{entity_name} modification de l'humidit\u00e9", diff --git a/homeassistant/components/sensor/translations/hu.json b/homeassistant/components/sensor/translations/hu.json index 7be96984451..9c49de27a7b 100644 --- a/homeassistant/components/sensor/translations/hu.json +++ b/homeassistant/components/sensor/translations/hu.json @@ -20,7 +20,8 @@ "signal_strength": "{entity_name} jeler\u0151ss\u00e9ge v\u00e1ltozik", "temperature": "{entity_name} h\u0151m\u00e9rs\u00e9klete v\u00e1ltozik", "timestamp": "{entity_name} id\u0151b\u00e9lyege v\u00e1ltozik", - "value": "{entity_name} \u00e9rt\u00e9ke v\u00e1ltozik" + "value": "{entity_name} \u00e9rt\u00e9ke v\u00e1ltozik", + "voltage": "{entity_name} fesz\u00fclts\u00e9ge v\u00e1ltozik" } }, "state": { diff --git a/homeassistant/components/sensor/translations/id.json b/homeassistant/components/sensor/translations/id.json index e2d0cdb057d..d43c2304428 100644 --- a/homeassistant/components/sensor/translations/id.json +++ b/homeassistant/components/sensor/translations/id.json @@ -1,8 +1,44 @@ { + "device_automation": { + "condition_type": { + "is_battery_level": "Level baterai {entity_name} saat ini", + "is_carbon_dioxide": "Level konsentasi karbondioksida {entity_name} saat ini", + "is_carbon_monoxide": "Level konsentasi karbonmonoksida {entity_name} saat ini", + "is_current": "Arus {entity_name} saat ini", + "is_energy": "Energi {entity_name} saat ini", + "is_humidity": "Kelembaban {entity_name} saat ini", + "is_illuminance": "Pencahayaan {entity_name} saat ini", + "is_power": "Daya {entity_name} saat ini", + "is_power_factor": "Faktor daya {entity_name} saat ini", + "is_pressure": "Tekanan {entity_name} saat ini", + "is_signal_strength": "Kekuatan sinyal {entity_name} saat ini", + "is_temperature": "Suhu {entity_name} saat ini", + "is_timestamp": "Stempel waktu {entity_name} saat ini", + "is_value": "Nilai {entity_name} saat ini", + "is_voltage": "Tegangan {entity_name} saat ini" + }, + "trigger_type": { + "battery_level": "Perubahan level baterai {entity_name}", + "carbon_dioxide": "Perubahan konsentrasi karbondioksida {entity_name}", + "carbon_monoxide": "Perubahan konsentrasi karbonmonoksida {entity_name}", + "current": "Perubahan arus {entity_name}", + "energy": "Perubahan energi {entity_name}", + "humidity": "Perubahan kelembaban {entity_name}", + "illuminance": "Perubahan pencahayaan {entity_name}", + "power": "Perubahan daya {entity_name}", + "power_factor": "Perubahan faktor daya {entity_name}", + "pressure": "Perubahan tekanan {entity_name}", + "signal_strength": "Perubahan kekuatan sinyal {entity_name}", + "temperature": "Perubahan suhu {entity_name}", + "timestamp": "Perubahan stempel waktu {entity_name}", + "value": "Perubahan nilai {entity_name}", + "voltage": "Perubahan tegangan {entity_name}" + } + }, "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" } }, "title": "Sensor" diff --git a/homeassistant/components/sensor/translations/it.json b/homeassistant/components/sensor/translations/it.json index 84a8b2773a5..c8b4a2f9b9c 100644 --- a/homeassistant/components/sensor/translations/it.json +++ b/homeassistant/components/sensor/translations/it.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "Livello della batteria attuale di {entity_name}", + "is_carbon_dioxide": "Livello di concentrazione di anidride carbonica attuale in {entity_name}", + "is_carbon_monoxide": "Livello attuale di concentrazione di monossido di carbonio in {entity_name}", "is_current": "Corrente attuale di {entity_name}", "is_energy": "Energia attuale di {entity_name}", "is_humidity": "Umidit\u00e0 attuale di {entity_name}", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "variazioni del livello di batteria di {entity_name} ", + "carbon_dioxide": "Variazioni della concentrazione di anidride carbonica di {entity_name}", + "carbon_monoxide": "Variazioni nella concentrazione di monossido di carbonio di {entity_name}", "current": "variazioni di corrente di {entity_name}", "energy": "variazioni di energia di {entity_name}", "humidity": "variazioni di umidit\u00e0 di {entity_name} ", diff --git a/homeassistant/components/sensor/translations/ko.json b/homeassistant/components/sensor/translations/ko.json index 92fcd5d37a2..d8e99874822 100644 --- a/homeassistant/components/sensor/translations/ko.json +++ b/homeassistant/components/sensor/translations/ko.json @@ -1,26 +1,38 @@ { "device_automation": { "condition_type": { - "is_battery_level": "\ud604\uc7ac {entity_name} \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 ~ \uc774\uba74", - "is_humidity": "\ud604\uc7ac {entity_name} \uc2b5\ub3c4\uac00 ~ \uc774\uba74", - "is_illuminance": "\ud604\uc7ac {entity_name} \uc870\ub3c4\uac00 ~ \uc774\uba74", - "is_power": "\ud604\uc7ac {entity_name} \uc18c\ube44 \uc804\ub825\uc774 ~ \uc774\uba74", - "is_pressure": "\ud604\uc7ac {entity_name} \uc555\ub825\uc774 ~ \uc774\uba74", - "is_signal_strength": "\ud604\uc7ac {entity_name} \uc2e0\ud638 \uac15\ub3c4\uac00 ~ \uc774\uba74", - "is_temperature": "\ud604\uc7ac {entity_name} \uc628\ub3c4\uac00 ~ \uc774\uba74", - "is_timestamp": "\ud604\uc7ac {entity_name} \uc2dc\uac01\uc774 ~ \uc774\uba74", - "is_value": "\ud604\uc7ac {entity_name} \uac12\uc774 ~ \uc774\uba74" + "is_battery_level": "\ud604\uc7ac {entity_name}\uc758 \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 ~ \uc774\uba74", + "is_carbon_dioxide": "\ud604\uc7ac {entity_name}\uc758 \uc774\uc0b0\ud654\ud0c4\uc18c \ub18d\ub3c4 \uc218\uc900\uc774 ~ \uc774\uba74", + "is_carbon_monoxide": "\ud604\uc7ac {entity_name}\uc758 \uc77c\uc0b0\ud654\ud0c4\uc18c \ub18d\ub3c4 \uc218\uc900\uc774 ~ \uc774\uba74", + "is_current": "\ud604\uc7ac {entity_name}\uc758 \uc804\ub958\uac00 ~ \uc774\uba74", + "is_energy": "\ud604\uc7ac {entity_name}\uc758 \uc5d0\ub108\uc9c0\uac00 ~ \uc774\uba74", + "is_humidity": "\ud604\uc7ac {entity_name}\uc758 \uc2b5\ub3c4\uac00 ~ \uc774\uba74", + "is_illuminance": "\ud604\uc7ac {entity_name}\uc758 \uc870\ub3c4\uac00 ~ \uc774\uba74", + "is_power": "\ud604\uc7ac {entity_name}\uc758 \uc18c\ube44 \uc804\ub825\uc774 ~ \uc774\uba74", + "is_power_factor": "\ud604\uc7ac {entity_name}\uc758 \uc5ed\ub960\uc774 ~ \uc774\uba74", + "is_pressure": "\ud604\uc7ac {entity_name}\uc758 \uc555\ub825\uc774 ~ \uc774\uba74", + "is_signal_strength": "\ud604\uc7ac {entity_name}\uc758 \uc2e0\ud638 \uac15\ub3c4\uac00 ~ \uc774\uba74", + "is_temperature": "\ud604\uc7ac {entity_name}\uc758 \uc628\ub3c4\uac00 ~ \uc774\uba74", + "is_timestamp": "\ud604\uc7ac {entity_name}\uc758 \uc2dc\uac01\uc774 ~ \uc774\uba74", + "is_value": "\ud604\uc7ac {entity_name}\uc758 \uac12\uc774 ~ \uc774\uba74", + "is_voltage": "\ud604\uc7ac {entity_name}\uc758 \uc804\uc555\uc774 ~ \uc774\uba74" }, "trigger_type": { - "battery_level": "{entity_name} \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 \ubc14\ub014 \ub54c", - "humidity": "{entity_name} \uc2b5\ub3c4\uac00 \ubc14\ub014 \ub54c", - "illuminance": "{entity_name} \uc870\ub3c4\uac00 \ubc14\ub014 \ub54c", - "power": "{entity_name} \uc18c\ube44 \uc804\ub825\uc774 \ubc14\ub014 \ub54c", - "pressure": "{entity_name} \uc555\ub825\uc774 \ubc14\ub014 \ub54c", - "signal_strength": "{entity_name} \uc2e0\ud638 \uac15\ub3c4\uac00 \ubc14\ub014 \ub54c", - "temperature": "{entity_name} \uc628\ub3c4\uac00 \ubc14\ub014 \ub54c", - "timestamp": "{entity_name} \uc2dc\uac01\uc774 \ubc14\ub014 \ub54c", - "value": "{entity_name} \uac12\uc774 \ubc14\ub014 \ub54c" + "battery_level": "{entity_name}\uc758 \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 \ubcc0\ud560 \ub54c", + "carbon_dioxide": "{entity_name}\uc758 \uc774\uc0b0\ud654\ud0c4\uc18c \ub18d\ub3c4\uac00 \ubcc0\ud560 \ub54c", + "carbon_monoxide": "{entity_name}\uc758 \uc77c\uc0b0\ud654\ud0c4\uc18c \ub18d\ub3c4\uac00 \ubcc0\ud560 \ub54c", + "current": "{entity_name}\uc758 \uc804\ub958\uac00 \ubcc0\ud560 \ub54c", + "energy": "{entity_name}\uc758 \uc5d0\ub108\uc9c0\uac00 \ubcc0\ud560 \ub54c", + "humidity": "{entity_name}\uc758 \uc2b5\ub3c4\uac00 \ubcc0\ud560 \ub54c", + "illuminance": "{entity_name}\uc758 \uc870\ub3c4\uac00 \ubcc0\ud560 \ub54c", + "power": "{entity_name}\uc758 \uc18c\ube44 \uc804\ub825\uc774 \ubcc0\ud560 \ub54c", + "power_factor": "{entity_name}\uc758 \uc5ed\ub960\uc774 \ubcc0\ud560 \ub54c", + "pressure": "{entity_name}\uc758 \uc555\ub825\uc774 \ubcc0\ud560 \ub54c", + "signal_strength": "{entity_name}\uc758 \uc2e0\ud638 \uac15\ub3c4\uac00 \ubcc0\ud560 \ub54c", + "temperature": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ubcc0\ud560 \ub54c", + "timestamp": "{entity_name}\uc758 \uc2dc\uac01\uc774 \ubcc0\ud560 \ub54c", + "value": "{entity_name}\uc758 \uac12\uc774 \ubcc0\ud560 \ub54c", + "voltage": "{entity_name}\uc758 \uc804\uc555\uc774 \ubcc0\ud560 \ub54c" } }, "state": { diff --git a/homeassistant/components/sensor/translations/nl.json b/homeassistant/components/sensor/translations/nl.json index 869599296d5..94eb0374adf 100644 --- a/homeassistant/components/sensor/translations/nl.json +++ b/homeassistant/components/sensor/translations/nl.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "Huidige batterijniveau {entity_name}", + "is_carbon_dioxide": "Huidig niveau {entity_name} kooldioxideconcentratie", + "is_carbon_monoxide": "Huidig niveau {entity_name} koolmonoxideconcentratie", "is_current": "Huidige {entity_name} stroom", "is_energy": "Huidige {entity_name} energie", "is_humidity": "Huidige {entity_name} vochtigheidsgraad", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "{entity_name} batterijniveau gewijzigd", + "carbon_dioxide": "{entity_name} kooldioxideconcentratie gewijzigd", + "carbon_monoxide": "{entity_name} koolmonoxideconcentratie gewijzigd", "current": "{entity_name} huidige wijzigingen", "energy": "{entity_name} energieveranderingen", "humidity": "{entity_name} vochtigheidsgraad gewijzigd", diff --git a/homeassistant/components/sensor/translations/no.json b/homeassistant/components/sensor/translations/no.json index b3e8dc199f6..3662356d15e 100644 --- a/homeassistant/components/sensor/translations/no.json +++ b/homeassistant/components/sensor/translations/no.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "Gjeldende {entity_name} batteriniv\u00e5", + "is_carbon_dioxide": "Gjeldende {entity_name} karbondioksidkonsentrasjonsniv\u00e5", + "is_carbon_monoxide": "Gjeldende {entity_name} karbonmonoksid konsentrasjonsniv\u00e5", "is_current": "Gjeldende {entity_name} str\u00f8m", "is_energy": "Gjeldende {entity_name} effekt", "is_humidity": "Gjeldende {entity_name} fuktighet", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "{entity_name} batteriniv\u00e5 endres", + "carbon_dioxide": "{entity_name} endringer i konsentrasjonen av karbondioksid", + "carbon_monoxide": "{entity_name} endringer i konsentrasjonen av karbonmonoksid", "current": "{entity_name} gjeldende endringer", "energy": "{entity_name} effektendringer", "humidity": "{entity_name} fuktighets endringer", diff --git a/homeassistant/components/sensor/translations/pl.json b/homeassistant/components/sensor/translations/pl.json index a8fb604cc53..a2baa380174 100644 --- a/homeassistant/components/sensor/translations/pl.json +++ b/homeassistant/components/sensor/translations/pl.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "obecny poziom na\u0142adowania baterii {entity_name}", + "is_carbon_dioxide": "Bie\u017c\u0105cy poziom st\u0119\u017cenia dwutlenku w\u0119gla w {entity_name}", + "is_carbon_monoxide": "Bie\u017c\u0105cy poziom st\u0119\u017cenia tlenku w\u0119gla w {entity_name}", "is_current": "obecne nat\u0119\u017cenie pr\u0105du {entity_name}", "is_energy": "obecna energia {entity_name}", "is_humidity": "obecna wilgotno\u015b\u0107 {entity_name}", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "zmieni si\u0119 poziom baterii {entity_name}", + "carbon_dioxide": "Zmiana st\u0119\u017cenie dwutlenku w\u0119gla w {entity_name}", + "carbon_monoxide": "Zmiana st\u0119\u017cenia tlenku w\u0119gla w {entity_name}", "current": "zmieni si\u0119 nat\u0119\u017cenie pr\u0105du w {entity_name}", "energy": "zmieni si\u0119 energia {entity_name}", "humidity": "zmieni si\u0119 wilgotno\u015b\u0107 {entity_name}", diff --git a/homeassistant/components/sensor/translations/ru.json b/homeassistant/components/sensor/translations/ru.json index ae84c843bc3..ae0a0997dd6 100644 --- a/homeassistant/components/sensor/translations/ru.json +++ b/homeassistant/components/sensor/translations/ru.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "{entity_name} \u0438\u043c\u0435\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435", + "is_carbon_dioxide": "{entity_name} \u0438\u043c\u0435\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0443\u0440\u043e\u0432\u043d\u044f \u043a\u043e\u043d\u0446\u0435\u043d\u0442\u0440\u0430\u0446\u0438\u0438 \u0443\u0433\u043b\u0435\u043a\u0438\u0441\u043b\u043e\u0433\u043e \u0433\u0430\u0437\u0430", + "is_carbon_monoxide": "{entity_name} \u0438\u043c\u0435\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0443\u0440\u043e\u0432\u043d\u044f \u043a\u043e\u043d\u0446\u0435\u043d\u0442\u0440\u0430\u0446\u0438\u0438 \u0443\u0433\u0430\u0440\u043d\u043e\u0433\u043e \u0433\u0430\u0437\u0430", "is_current": "{entity_name} \u0438\u043c\u0435\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0441\u0438\u043b\u044b \u0442\u043e\u043a\u0430", "is_energy": "{entity_name} \u0438\u043c\u0435\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043c\u043e\u0449\u043d\u043e\u0441\u0442\u0438", "is_humidity": "{entity_name} \u0438\u043c\u0435\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "{entity_name} \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435", + "carbon_dioxide": "{entity_name} \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435", + "carbon_monoxide": "{entity_name} \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435", "current": "{entity_name} \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0441\u0438\u043b\u044b \u0442\u043e\u043a\u0430", "energy": "{entity_name} \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043c\u043e\u0449\u043d\u043e\u0441\u0442\u0438", "humidity": "{entity_name} \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435", diff --git a/homeassistant/components/sensor/translations/zh-Hant.json b/homeassistant/components/sensor/translations/zh-Hant.json index 56350e501ef..ff84d8b2790 100644 --- a/homeassistant/components/sensor/translations/zh-Hant.json +++ b/homeassistant/components/sensor/translations/zh-Hant.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "\u76ee\u524d{entity_name}\u96fb\u91cf", + "is_carbon_dioxide": "\u76ee\u524d {entity_name} \u4e8c\u6c27\u5316\u78b3\u6fc3\u5ea6\u72c0\u614b", + "is_carbon_monoxide": "\u76ee\u524d {entity_name} \u4e00\u6c27\u5316\u78b3\u6fc3\u5ea6\u72c0\u614b", "is_current": "\u76ee\u524d{entity_name}\u96fb\u6d41", "is_energy": "\u76ee\u524d{entity_name}\u96fb\u529b", "is_humidity": "\u76ee\u524d{entity_name}\u6fd5\u5ea6", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "{entity_name}\u96fb\u91cf\u8b8a\u66f4", + "carbon_dioxide": "{entity_name} \u4e8c\u6c27\u5316\u78b3\u6fc3\u5ea6\u8b8a\u5316", + "carbon_monoxide": "{entity_name} \u4e00\u6c27\u5316\u78b3\u6fc3\u5ea6\u8b8a\u5316", "current": "\u76ee\u524d{entity_name}\u96fb\u6d41\u8b8a\u66f4", "energy": "\u76ee\u524d{entity_name}\u96fb\u529b\u8b8a\u66f4", "humidity": "{entity_name}\u6fd5\u5ea6\u8b8a\u66f4", diff --git a/homeassistant/components/sentry/translations/hu.json b/homeassistant/components/sentry/translations/hu.json index 64ee672a02f..055c8817177 100644 --- a/homeassistant/components/sentry/translations/hu.json +++ b/homeassistant/components/sentry/translations/hu.json @@ -1,8 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "error": { "bad_dsn": "\u00c9rv\u00e9nytelen DSN", - "unknown": "V\u00e1ratlan hiba" + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "user": { diff --git a/homeassistant/components/sentry/translations/id.json b/homeassistant/components/sentry/translations/id.json new file mode 100644 index 00000000000..fbd94fa6565 --- /dev/null +++ b/homeassistant/components/sentry/translations/id.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "bad_dsn": "DSN tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "dsn": "DSN" + }, + "description": "Masukkan DSN Sentry Anda", + "title": "Sentry" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "environment": "Nama opsional untuk lingkungan.", + "event_custom_components": "Kirim event dari komponen khusus", + "event_handled": "Kirim event yang ditangani", + "event_third_party_packages": "Kirim event dari paket pihak ketiga", + "tracing": "Aktifkan pelacakan kinerja", + "tracing_sample_rate": "Laju sampel pelacakan; antara 0,0 dan 1,0 (1,0 = 100%)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sentry/translations/ko.json b/homeassistant/components/sentry/translations/ko.json index 6c00ffea2ef..92e26b30c42 100644 --- a/homeassistant/components/sentry/translations/ko.json +++ b/homeassistant/components/sentry/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "bad_dsn": "DSN \uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", @@ -9,6 +9,9 @@ }, "step": { "user": { + "data": { + "dsn": "DSN" + }, "description": "Sentry DSN \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694", "title": "Sentry" } @@ -18,14 +21,14 @@ "step": { "init": { "data": { - "environment": "\ud658\uacbd\uc758 \uc120\ud0dd\uc801 \uba85\uce6d", + "environment": "\ud658\uacbd\uc5d0 \ub300\ud55c \uc774\ub984 (\uc120\ud0dd \uc0ac\ud56d)", "event_custom_components": "\uc0ac\uc6a9\uc790 \uc9c0\uc815 \uad6c\uc131 \uc694\uc18c\uc5d0\uc11c \uc774\ubca4\ud2b8 \ubcf4\ub0b4\uae30", "event_handled": "\ucc98\ub9ac\ub41c \uc774\ubca4\ud2b8 \ubcf4\ub0b4\uae30", - "event_third_party_packages": "\uc368\ub4dc\ud30c\ud2f0 \ud328\ud0a4\uc9c0\uc5d0\uc11c \uc774\ubca4\ud2b8 \ubcf4\ub0b4\uae30", - "logging_event_level": "Log level Sentry\ub294 \ub2e4\uc74c\uc5d0 \ub300\ud55c \uc774\ubca4\ud2b8\ub97c \ub4f1\ub85d\ud569\ub2c8\ub2e4.", - "logging_level": "Log level sentry\ub294 \ub2e4\uc74c\uc5d0 \ub300\ud55c \ub85c\uadf8\ub97c \ube0c\ub808\ub4dc \ud06c\ub7fc\uc73c\ub85c \uae30\ub85d\ud569\ub2c8\ub2e4.", - "tracing": "\uc131\ub2a5 \ucd94\uc801 \ud65c\uc131\ud654", - "tracing_sample_rate": "\uc0d8\ud50c\ub9c1 \uc18d\ub3c4 \ucd94\uc801; 0.0\uc5d0\uc11c 1.0 \uc0ac\uc774 (1.0 = 100 %)" + "event_third_party_packages": "\uc11c\ub4dc \ud30c\ud2f0 \ud328\ud0a4\uc9c0\uc5d0\uc11c \uc774\ubca4\ud2b8 \ubcf4\ub0b4\uae30", + "logging_event_level": "\ub85c\uadf8 \ub808\ubca8 Sentry\ub294 \ub2e4\uc74c \ub85c\uadf8 \uc218\uc900\uc5d0 \ub300\ud55c \uc774\ubca4\ud2b8\ub97c \ub4f1\ub85d\ud569\ub2c8\ub2e4", + "logging_level": "\ub85c\uadf8 \ub808\ubca8 Sentry\ub294 \ub2e4\uc74c \ub85c\uadf8 \uc218\uc900\uc5d0 \ub300\ud55c \ub85c\uadf8\ub97c \ube0c\ub808\ub4dc\ud06c\ub7fc\uc73c\ub85c \uae30\ub85d\ud569\ub2c8\ub2e4", + "tracing": "\uc131\ub2a5 \ucd94\uc801 \ud65c\uc131\ud654\ud558\uae30", + "tracing_sample_rate": "\ucd94\uc801 \uc0d8\ud50c \uc18d\ub3c4; 0.0\uc5d0\uc11c 1.0 \uc0ac\uc774 (1.0 = 100%)" } } } diff --git a/homeassistant/components/sharkiq/translations/hu.json b/homeassistant/components/sharkiq/translations/hu.json new file mode 100644 index 00000000000..b765ad68a3f --- /dev/null +++ b/homeassistant/components/sharkiq/translations/hu.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "reauth": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + }, + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sharkiq/translations/id.json b/homeassistant/components/sharkiq/translations/id.json new file mode 100644 index 00000000000..e3d8b4a8ed2 --- /dev/null +++ b/homeassistant/components/sharkiq/translations/id.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "reauth_successful": "Autentikasi ulang berhasil", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "reauth": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/bg.json b/homeassistant/components/shelly/translations/bg.json new file mode 100644 index 00000000000..c856929a5e1 --- /dev/null +++ b/homeassistant/components/shelly/translations/bg.json @@ -0,0 +1,10 @@ +{ + "device_automation": { + "trigger_subtype": { + "button": "\u0411\u0443\u0442\u043e\u043d", + "button1": "\u041f\u044a\u0440\u0432\u0438 \u0431\u0443\u0442\u043e\u043d", + "button2": "\u0412\u0442\u043e\u0440\u0438 \u0431\u0443\u0442\u043e\u043d", + "button3": "\u0422\u0440\u0435\u0442\u0438 \u0431\u0443\u0442\u043e\u043d" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/de.json b/homeassistant/components/shelly/translations/de.json index 4764936a41b..9d78d362c99 100644 --- a/homeassistant/components/shelly/translations/de.json +++ b/homeassistant/components/shelly/translations/de.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "unsupported_firmware": "Das Ger\u00e4t verwendet eine nicht unterst\u00fctzte Firmware-Version." }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", @@ -23,5 +24,21 @@ "description": "Vor der Einrichtung m\u00fcssen batteriebetriebene Ger\u00e4te durch Dr\u00fccken der Taste am Ger\u00e4t aufgeweckt werden." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Taste", + "button1": "Erste Taste", + "button2": "Zweite Taste", + "button3": "Dritte Taste" + }, + "trigger_type": { + "double": "{subtype} zweifach bet\u00e4tigt", + "long": "{subtype} gehalten", + "long_single": "{subtype} gehalten und dann einfach bet\u00e4tigt", + "single": "{subtype} einfach bet\u00e4tigt", + "single_long": "{subtype} einfach bet\u00e4tigt und dann gehalten", + "triple": "{subtype} dreifach bet\u00e4tigt" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/hu.json b/homeassistant/components/shelly/translations/hu.json index 3b2d79a34a7..2c8f468aaed 100644 --- a/homeassistant/components/shelly/translations/hu.json +++ b/homeassistant/components/shelly/translations/hu.json @@ -1,7 +1,44 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "unsupported_firmware": "Az eszk\u00f6z nem t\u00e1mogatott firmware verzi\u00f3t haszn\u00e1l." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "{name}", + "step": { + "credentials": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + }, + "user": { + "data": { + "host": "Hoszt" + }, + "description": "A be\u00e1ll\u00edt\u00e1s el\u0151tt az akkumul\u00e1toros eszk\u00f6z\u00f6ket fel kell \u00e9breszteni, most egy rajta l\u00e9v\u0151 gombbal fel\u00e9bresztheted az eszk\u00f6zt." + } + } + }, + "device_automation": { + "trigger_subtype": { + "button": "Gomb", + "button1": "Els\u0151 gomb", + "button2": "M\u00e1sodik gomb", + "button3": "Harmadik gomb" + }, + "trigger_type": { + "double": "{subtype} dupla kattint\u00e1s", + "long": "{subtype} hosszan nyomva", + "long_single": "{subtype} hosszan nyomva, majd egy kattint\u00e1s", + "single": "{subtype} egy kattint\u00e1s", + "single_long": "{subtype} egy kattint\u00e1s, majd hosszan nyomva", + "triple": "{subtype} tripla kattint\u00e1s" } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/id.json b/homeassistant/components/shelly/translations/id.json new file mode 100644 index 00000000000..606ee473805 --- /dev/null +++ b/homeassistant/components/shelly/translations/id.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "unsupported_firmware": "Perangkat menggunakan versi firmware yang tidak didukung." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "{name}", + "step": { + "confirm_discovery": { + "description": "Ingin menyiapkan {model} di {host}?\n\nPerangkat bertenaga baterai yang dilindungi kata sandi harus dibangunkan sebelum melanjutkan penyiapan.\nPerangkat bertenaga baterai yang tidak dilindungi kata sandi akan ditambahkan ketika perangkat bangun. Anda dapat membangunkan perangkat secara manual menggunakan tombol di atasnya sekarang juga atau menunggu pembaruan data berikutnya dari perangkat." + }, + "credentials": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Sebelum menyiapkan, perangkat bertenaga baterai harus dibangunkan. Anda dapat membangunkan perangkat menggunakan tombol di atasnya sekarang." + } + } + }, + "device_automation": { + "trigger_subtype": { + "button": "Tombol", + "button1": "Tombol pertama", + "button2": "Tombol kedua", + "button3": "Tombol ketiga" + }, + "trigger_type": { + "double": "{subtype} diklik dua kali", + "long": "{subtype} diklik lama", + "long_single": "{subtype} diklik lama kemudian diklik sekali", + "single": "{subtype} diklik sekali", + "single_long": "{subtype} diklik sekali kemudian diklik lama", + "triple": "{subtype} diklik tiga kali" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/ko.json b/homeassistant/components/shelly/translations/ko.json index 914c9a46bd8..d422b6de86b 100644 --- a/homeassistant/components/shelly/translations/ko.json +++ b/homeassistant/components/shelly/translations/ko.json @@ -2,14 +2,18 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "unsupported_firmware": "\uc774 \uc7a5\uce58\ub294 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \ud38c\uc6e8\uc5b4 \ubc84\uc804\uc744 \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4." + "unsupported_firmware": "\uae30\uae30\uac00 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \ud38c\uc6e8\uc5b4 \ubc84\uc804\uc744 \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, + "flow_title": "{name}", "step": { + "confirm_discovery": { + "description": "{host}\uc5d0\uc11c {model}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\n\n\ube44\ubc00\ubc88\ud638\ub85c \ubcf4\ud638\ub41c \ubc30\ud130\ub9ac \uad6c\ub3d9 \uae30\uae30\ub294 \uc124\uc815\ud558\uae30 \uc804\uc5d0 \uc808\uc804 \ubaa8\ub4dc\ub97c \ud574\uc81c\ud574\uc57c \ud569\ub2c8\ub2e4.\n\ube44\ubc00\ubc88\ud638\ub85c \ubcf4\ud638\ub418\uc9c0 \uc54a\ub294 \ubc30\ud130\ub9ac \uad6c\ub3d9 \uae30\uae30\ub294 \uae30\uae30\uc758 \uc808\uc804 \ubaa8\ub4dc\uac00 \ud574\uc81c\ub420 \ub54c \ucd94\uac00\ub418\uba70, \uae30\uae30\uc758 \ubc84\ud2bc\uc744 \uc0ac\uc6a9\ud558\uc5ec \uc218\ub3d9\uc73c\ub85c \uae30\uae30\ub97c \uc808\uc804 \ud574\uc81c\uc2dc\ud0a4\uac70\ub098 \uae30\uae30\uc5d0\uc11c \ub2e4\uc74c \ub370\uc774\ud130\ub97c \uc5c5\ub370\uc774\ud2b8\ud560 \ub54c\uae4c\uc9c0 \uae30\ub2e4\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + }, "credentials": { "data": { "password": "\ube44\ubc00\ubc88\ud638", @@ -19,8 +23,25 @@ "user": { "data": { "host": "\ud638\uc2a4\ud2b8" - } + }, + "description": "\uc124\uc815\ud558\uae30 \uc804\uc5d0 \ubc30\ud130\ub9ac\ub85c \uc791\ub3d9\ub418\ub294 \uae30\uae30\ub294 \uc808\uc804 \ubaa8\ub4dc\uac00 \ud574\uc81c\ub418\uc5b4 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uae30\uae30\uc758 \ubc84\ud2bc\uc744 \uc0ac\uc6a9\ud558\uc5ec \uc808\uc804 \ubaa8\ub4dc\ub97c \ud574\uc81c\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "\ubc84\ud2bc", + "button1": "\uccab \ubc88\uc9f8 \ubc84\ud2bc", + "button2": "\ub450 \ubc88\uc9f8 \ubc84\ud2bc", + "button3": "\uc138 \ubc88\uc9f8 \ubc84\ud2bc" + }, + "trigger_type": { + "double": "\"{subtype}\"\uc774(\uac00) \ub450 \ubc88 \ub20c\ub838\uc744 \ub54c", + "long": "\"{subtype}\"\uc774(\uac00) \uae38\uac8c \ub20c\ub838\uc744 \ub54c", + "long_single": "\"{subtype}\"\uc774(\uac00) \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc9e7\uac8c \ub20c\ub838\uc744 \ub54c", + "single": "\"{subtype}\"\uc774(\uac00) \uc9e7\uac8c \ub20c\ub838\uc744 \ub54c", + "single_long": "\"{subtype}\"\uc774(\uac00) \uc9e7\uac8c \ub20c\ub838\ub2e4\uac00 \uae38\uac8c \ub20c\ub838\uc744 \ub54c", + "triple": "\"{subtype}\"\uc774(\uac00) \uc138 \ubc88 \ub20c\ub838\uc744 \ub54c" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/nl.json b/homeassistant/components/shelly/translations/nl.json index c486b9c6bfe..571fb9b4339 100644 --- a/homeassistant/components/shelly/translations/nl.json +++ b/homeassistant/components/shelly/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "unsupported_firmware": "Het apparaat gebruikt een niet-ondersteunde firmwareversie." }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -22,7 +23,8 @@ "user": { "data": { "host": "Host" - } + }, + "description": "Slapende (op batterij werkende) apparaten moeten wakker zijn wanneer deze apparaten opgezet worden. Je kunt deze apparaten nu wakker maken door op de knop erop te drukken" } } }, @@ -35,6 +37,9 @@ }, "trigger_type": { "double": "{subtype} dubbel geklikt", + "long": "{subtype} lang geklikt", + "long_single": "{subtype} lang geklikt en daarna \u00e9\u00e9n keer geklikt", + "single": "{subtype} enkel geklikt", "single_long": "{subtype} een keer geklikt en daarna lang geklikt", "triple": "{subtype} driemaal geklikt" } diff --git a/homeassistant/components/shopping_list/translations/hu.json b/homeassistant/components/shopping_list/translations/hu.json index 4a093bea379..5f092963da3 100644 --- a/homeassistant/components/shopping_list/translations/hu.json +++ b/homeassistant/components/shopping_list/translations/hu.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "A bev\u00e1s\u00e1rl\u00f3lista m\u00e1r konfigur\u00e1lva van." + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" }, "step": { "user": { diff --git a/homeassistant/components/shopping_list/translations/id.json b/homeassistant/components/shopping_list/translations/id.json new file mode 100644 index 00000000000..0efa42d0782 --- /dev/null +++ b/homeassistant/components/shopping_list/translations/id.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "step": { + "user": { + "description": "Ingin mengonfigurasi daftar belanja?", + "title": "Daftar Belanja" + } + } + }, + "title": "Daftar Belanja" +} \ No newline at end of file diff --git a/homeassistant/components/shopping_list/translations/nl.json b/homeassistant/components/shopping_list/translations/nl.json index de6045dd81b..e8de5fbae1d 100644 --- a/homeassistant/components/shopping_list/translations/nl.json +++ b/homeassistant/components/shopping_list/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "De Shopping List is al geconfigureerd." + "already_configured": "Service is al geconfigureerd" }, "step": { "user": { diff --git a/homeassistant/components/simplisafe/translations/he.json b/homeassistant/components/simplisafe/translations/he.json new file mode 100644 index 00000000000..3007c0e968c --- /dev/null +++ b/homeassistant/components/simplisafe/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/hu.json b/homeassistant/components/simplisafe/translations/hu.json index 7b989246de1..8a2deedc534 100644 --- a/homeassistant/components/simplisafe/translations/hu.json +++ b/homeassistant/components/simplisafe/translations/hu.json @@ -1,11 +1,23 @@ { "config": { + "abort": { + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, "error": { - "identifier_exists": "Fi\u00f3k m\u00e1r regisztr\u00e1lva van" + "identifier_exists": "Fi\u00f3k m\u00e1r regisztr\u00e1lva van", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { + "reauth_confirm": { + "data": { + "password": "Jelsz\u00f3" + }, + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" + }, "user": { "data": { + "code": "K\u00f3d (a Home Assistant felhaszn\u00e1l\u00f3i fel\u00fclet\u00e9n haszn\u00e1latos)", "password": "Jelsz\u00f3", "username": "E-mail" }, diff --git a/homeassistant/components/simplisafe/translations/id.json b/homeassistant/components/simplisafe/translations/id.json new file mode 100644 index 00000000000..512d6a38405 --- /dev/null +++ b/homeassistant/components/simplisafe/translations/id.json @@ -0,0 +1,45 @@ +{ + "config": { + "abort": { + "already_configured": "Akun SimpliSafe ini sudah digunakan.", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "identifier_exists": "Akun sudah terdaftar", + "invalid_auth": "Autentikasi tidak valid", + "still_awaiting_mfa": "Masih menunggu pengeklikan dari email MFA", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "mfa": { + "description": "Periksa email Anda untuk mendapatkan tautan dari SimpliSafe. Setelah memverifikasi tautan, kembali ke sini untuk menyelesaikan instalasi integrasi.", + "title": "Autentikasi Multi-Faktor SimpliSafe" + }, + "reauth_confirm": { + "data": { + "password": "Kata Sandi" + }, + "description": "Token akses Anda telah kedaluwarsa atau dicabut. Masukkan kata sandi Anda untuk menautkan kembali akun Anda.", + "title": "Autentikasi Ulang Integrasi" + }, + "user": { + "data": { + "code": "Kode (digunakan di antarmuka Home Assistant)", + "password": "Kata Sandi", + "username": "Email" + }, + "title": "Isi informasi Anda." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "code": "Kode (digunakan di antarmuka Home Assistant)" + }, + "title": "Konfigurasikan SimpliSafe" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/ko.json b/homeassistant/components/simplisafe/translations/ko.json index c5c1b057ea8..194fa6cecf4 100644 --- a/homeassistant/components/simplisafe/translations/ko.json +++ b/homeassistant/components/simplisafe/translations/ko.json @@ -7,14 +7,20 @@ "error": { "identifier_exists": "\uacc4\uc815\uc774 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "still_awaiting_mfa": "\uc544\uc9c1 \ub2e4\ub2e8\uacc4 \uc778\uc99d(MFA) \uc774\uba54\uc77c\uc758 \ub9c1\ud06c \ud074\ub9ad\uc744 \uae30\ub2e4\ub9ac\uace0\uc788\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { + "mfa": { + "description": "\uc774\uba54\uc77c\uc5d0\uc11c SimpliSafe\uc758 \ub9c1\ud06c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694. \ub9c1\ud06c\ub97c \ud655\uc778\ud55c \ud6c4 \uc5ec\uae30\ub85c \ub3cc\uc544\uc640 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc758 \uc124\uce58\ub97c \uc644\ub8cc\ud574\uc8fc\uc138\uc694.", + "title": "SimpliSafe \ub2e4\ub2e8\uacc4 \uc778\uc99d" + }, "reauth_confirm": { "data": { "password": "\ube44\ubc00\ubc88\ud638" }, - "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d" + "description": "\uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \ub9cc\ub8cc\ub418\uc5c8\uac70\ub098 \ud574\uc9c0\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uacc4\uc815\uc744 \ub2e4\uc2dc \uc5f0\uacb0\ud558\ub824\uba74 \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" }, "user": { "data": { diff --git a/homeassistant/components/simplisafe/translations/nl.json b/homeassistant/components/simplisafe/translations/nl.json index d3196c591cb..29b9d9ab70b 100644 --- a/homeassistant/components/simplisafe/translations/nl.json +++ b/homeassistant/components/simplisafe/translations/nl.json @@ -21,7 +21,7 @@ "data": { "code": "Code (gebruikt in Home Assistant)", "password": "Wachtwoord", - "username": "E-mailadres" + "username": "E-mail" }, "title": "Vul uw gegevens in" } diff --git a/homeassistant/components/smappee/translations/hu.json b/homeassistant/components/smappee/translations/hu.json index 4258cfb0912..15bfd4dc5d2 100644 --- a/homeassistant/components/smappee/translations/hu.json +++ b/homeassistant/components/smappee/translations/hu.json @@ -2,7 +2,21 @@ "config": { "abort": { "already_configured_device": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "cannot_connect": "Sikertelen csatlakoz\u00e1s" + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz." + }, + "flow_title": "Smappee: {name}", + "step": { + "local": { + "data": { + "host": "Hoszt" + } + }, + "pick_implementation": { + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + } } } } \ No newline at end of file diff --git a/homeassistant/components/smappee/translations/id.json b/homeassistant/components/smappee/translations/id.json new file mode 100644 index 00000000000..b72200c34ca --- /dev/null +++ b/homeassistant/components/smappee/translations/id.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured_device": "Perangkat sudah dikonfigurasi", + "already_configured_local_device": "Perangkat lokal sudah dikonfigurasi. Hapus perangkat tersebut terlebih dahulu sebelum mengonfigurasi perangkat awan.", + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "cannot_connect": "Gagal terhubung", + "invalid_mdns": "Perangkat tidak didukung untuk integrasi Smappee.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})" + }, + "flow_title": "Smappee: {name}", + "step": { + "environment": { + "data": { + "environment": "Lingkungan" + }, + "description": "Siapkan Smappee Anda untuk diintegrasikan dengan Home Assistant." + }, + "local": { + "data": { + "host": "Host" + }, + "description": "Masukkan host untuk memulai integrasi lokal Smappee" + }, + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + }, + "zeroconf_confirm": { + "description": "Ingin menambahkan perangkat Smappee dengan nomor seri `{serialnumber}` ke Home Assistant?", + "title": "Peranti Smappee yang ditemukan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smappee/translations/ko.json b/homeassistant/components/smappee/translations/ko.json index 8509b65ca09..b2f0e5880c5 100644 --- a/homeassistant/components/smappee/translations/ko.json +++ b/homeassistant/components/smappee/translations/ko.json @@ -2,19 +2,33 @@ "config": { "abort": { "already_configured_device": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_configured_local_device": "\ub85c\uceec \uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4. \ud074\ub77c\uc6b0\ub4dc \uae30\uae30\ub97c \uad6c\uc131\ud558\uae30 \uc804\uc5d0 \uc774\ub7ec\ud55c \uae30\uae30\ub97c \uba3c\uc800 \uc81c\uac70\ud574\uc8fc\uc138\uc694.", "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_mdns": "Smappee \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0\uc11c \uc9c0\uc6d0\ud558\uc9c0 \uc54a\ub294 \uae30\uae30\uc785\ub2c8\ub2e4.", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, + "flow_title": "Smappee: {name}", "step": { + "environment": { + "data": { + "environment": "\ud658\uacbd" + }, + "description": "Home Assistant\uc5d0 Smappee \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4." + }, "local": { "data": { "host": "\ud638\uc2a4\ud2b8" - } + }, + "description": "Smappee \ub85c\uceec \uc5f0\ub3d9\uc744 \uc2dc\uc791\ud560 \ud638\uc2a4\ud2b8\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" }, "pick_implementation": { "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" + }, + "zeroconf_confirm": { + "description": "\uc2dc\ub9ac\uc5bc \ubc88\ud638 `{serialnumber}`\uc758 Smappee \uae30\uae30\ub97c Home Assistant\uc5d0 \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\ubc1c\uacac\ub41c Smpappee \uae30\uae30" } } } diff --git a/homeassistant/components/smappee/translations/nl.json b/homeassistant/components/smappee/translations/nl.json index 10a4fe2efab..373ff6ecd2e 100644 --- a/homeassistant/components/smappee/translations/nl.json +++ b/homeassistant/components/smappee/translations/nl.json @@ -9,6 +9,7 @@ "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" }, + "flow_title": "Smappee: {name}", "step": { "local": { "data": { diff --git a/homeassistant/components/smart_meter_texas/translations/hu.json b/homeassistant/components/smart_meter_texas/translations/hu.json index 3b2d79a34a7..fd8db27da5e 100644 --- a/homeassistant/components/smart_meter_texas/translations/hu.json +++ b/homeassistant/components/smart_meter_texas/translations/hu.json @@ -2,6 +2,19 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/smart_meter_texas/translations/id.json b/homeassistant/components/smart_meter_texas/translations/id.json new file mode 100644 index 00000000000..4a84db42a14 --- /dev/null +++ b/homeassistant/components/smart_meter_texas/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarthab/translations/hu.json b/homeassistant/components/smarthab/translations/hu.json index b40828cc764..222c95bba16 100644 --- a/homeassistant/components/smarthab/translations/hu.json +++ b/homeassistant/components/smarthab/translations/hu.json @@ -1,7 +1,15 @@ { "config": { + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { + "data": { + "email": "E-mail", + "password": "Jelsz\u00f3" + }, "description": "Technikai okokb\u00f3l ne felejtsen el m\u00e1sodlagos fi\u00f3kot haszn\u00e1lni a Home Assistant be\u00e1ll\u00edt\u00e1s\u00e1hoz. A SmartHab alkalmaz\u00e1sb\u00f3l l\u00e9trehozhat egyet." } } diff --git a/homeassistant/components/smarthab/translations/id.json b/homeassistant/components/smarthab/translations/id.json new file mode 100644 index 00000000000..7a776eac304 --- /dev/null +++ b/homeassistant/components/smarthab/translations/id.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "invalid_auth": "Autentikasi tidak valid", + "service": "Terjadi kesalahan saat mencoba menjangkau SmartHab. Layanan mungkin sedang mengalami gangguan. Periksa koneksi Anda.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Kata Sandi" + }, + "description": "Untuk alasan teknis, pastikan untuk menggunakan akun sekunder khusus untuk penyiapan Home Assistant Anda. Anda dapat membuatnya dari aplikasi SmartHab.", + "title": "Siapkan SmartHab" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarthab/translations/ko.json b/homeassistant/components/smarthab/translations/ko.json index d39931cbc03..1641555b412 100644 --- a/homeassistant/components/smarthab/translations/ko.json +++ b/homeassistant/components/smarthab/translations/ko.json @@ -11,7 +11,7 @@ "email": "\uc774\uba54\uc77c", "password": "\ube44\ubc00\ubc88\ud638" }, - "description": "\uae30\uc220\uc801\uc778 \uc774\uc720\ub85c Home Assistant \uc124\uc815\uacfc \uad00\ub828\ub41c \ubcf4\uc870 \uacc4\uc815\uc744 \uc0ac\uc6a9\ud574\uc57c \ud569\ub2c8\ub2e4. SmartHab \uc751\uc6a9 \ud504\ub85c\uadf8\ub7a8\uc5d0\uc11c \uc0dd\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "\uae30\uc220\uc801\uc778 \uc774\uc720\ub85c Home Assistant \uc124\uc815\uacfc \uad00\ub828\ub41c \ubcf4\uc870 \uacc4\uc815\uc744 \uc0ac\uc6a9\ud574\uc57c \ud569\ub2c8\ub2e4. SmartHab \uc560\ud50c\ub9ac\ucf00\uc774\uc158\uc5d0\uc11c \uc0dd\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "SmartHab \uc124\uce58\ud558\uae30" } } diff --git a/homeassistant/components/smartthings/translations/hu.json b/homeassistant/components/smartthings/translations/hu.json index 5cbe1d086bc..17c0a1a1b04 100644 --- a/homeassistant/components/smartthings/translations/hu.json +++ b/homeassistant/components/smartthings/translations/hu.json @@ -12,11 +12,15 @@ "data": { "access_token": "Hozz\u00e1f\u00e9r\u00e9si token" }, - "description": "K\u00e9rj\u00fck, adjon meg egy SmartThings [Szem\u00e9lyes hozz\u00e1f\u00e9r\u00e9si tokent] ( {token_url} ), amelyet az [utas\u00edt\u00e1sok] ( {component_url} ) alapj\u00e1n hoztak l\u00e9tre. Ezt haszn\u00e1ljuk a Home Assistant integr\u00e1ci\u00f3j\u00e1nak l\u00e9trehoz\u00e1s\u00e1hoz a SmartThings-fi\u00f3kban." + "description": "K\u00e9rj\u00fck, adjon meg egy SmartThings [Szem\u00e9lyes hozz\u00e1f\u00e9r\u00e9si tokent]({token_url}), amelyet az [utas\u00edt\u00e1sok]({component_url}) alapj\u00e1n hoztak l\u00e9tre. Ezt haszn\u00e1ljuk a Home Assistant integr\u00e1ci\u00f3j\u00e1nak l\u00e9trehoz\u00e1s\u00e1hoz a SmartThings-fi\u00f3kban." + }, + "select_location": { + "data": { + "location_id": "Elhelyezked\u00e9s" + } }, "user": { - "description": "K\u00e9rlek add meg a SmartThings [Personal Access Tokent]({token_url}), amit az [instrukci\u00f3k] ({component_url}) alapj\u00e1n hozt\u00e1l l\u00e9tre.", - "title": "Adja meg a szem\u00e9lyes hozz\u00e1f\u00e9r\u00e9si Tokent" + "title": "Callback URL meger\u0151s\u00edt\u00e9se" } } } diff --git a/homeassistant/components/smartthings/translations/id.json b/homeassistant/components/smartthings/translations/id.json new file mode 100644 index 00000000000..77846487d85 --- /dev/null +++ b/homeassistant/components/smartthings/translations/id.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "invalid_webhook_url": "Home Assistant tidak dikonfigurasi dengan benar untuk menerima pembaruan dari SmartThings. URL webhook tidak valid:\n> {webhook_url}\n\nPerbarui konfigurasi Anda sesuai [petunjuk]({component_url}), kemudian mulai ulang Home Assistant, dan coba kembali.", + "no_available_locations": "Tidak ada SmartThings Location untuk disiapkan di Home Assistant." + }, + "error": { + "app_setup_error": "Tidak dapat menyiapkan SmartApp. Coba lagi.", + "token_forbidden": "Token tidak memiliki cakupan OAuth yang diperlukan.", + "token_invalid_format": "Token harus dalam format UID/GUID", + "token_unauthorized": "Token tidak valid atau tidak lagi diotorisasi.", + "webhook_error": "SmartThings tidak dapat memvalidasi URL webhook. Pastikan URL webhook dapat dijangkau dari internet, lalu coba lagi." + }, + "step": { + "authorize": { + "title": "Otorisasi Home Assistant" + }, + "pat": { + "data": { + "access_token": "Token Akses" + }, + "description": "Masukkan [Token Akses Pribadi]({token_url}) SmartThings yang telah dibuat sesuai [petunjuk]({component_url}). Ini akan digunakan untuk membuat integrasi Home Assistant dalam akun SmartThings Anda.", + "title": "Masukkan Token Akses Pribadi" + }, + "select_location": { + "data": { + "location_id": "Lokasi" + }, + "description": "Pilih SmartThings Location yang ingin ditambahkan ke Home Assistant. Kami akan membuka jendela baru dan meminta Anda untuk masuk dan mengotorisasi instalasi integrasi Home Assistant ke Location yang dipilih.", + "title": "Pilih Location" + }, + "user": { + "description": "SmartThings akan dikonfigurasi untuk mengirim pembaruan push ke Home Assistant di:\n > {webhook_url} \n\nJika ini tidak benar, perbarui konfigurasi Anda, mulai ulang Home Assistant, dan coba lagi.", + "title": "Konfirmasikan URL Panggilan Balik" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/translations/ko.json b/homeassistant/components/smartthings/translations/ko.json index 0ee28136651..dc02f25451b 100644 --- a/homeassistant/components/smartthings/translations/ko.json +++ b/homeassistant/components/smartthings/translations/ko.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "invalid_webhook_url": "Home Assistant \uac00 SmartThings \uc5d0\uc11c \uc5c5\ub370\uc774\ud2b8\ub97c \uc218\uc2e0\ud558\ub3c4\ub85d \uc62c\ubc14\ub974\uac8c \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc6f9 \ud6c5 URL \uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4:\n> {webhook_url} \n\n[\uc548\ub0b4]({component_url}) \ub97c \ucc38\uace0\ud558\uc5ec \uad6c\uc131\uc744 \uc5c5\ub370\uc774\ud2b8\ud558\uace0 Home Assistant \ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", - "no_available_locations": "Home Assistant \uc5d0\uc11c \uc124\uc815\ud560 \uc218 \uc788\ub294 SmartThings \uc704\uce58\uac00 \uc5c6\uc2b5\ub2c8\ub2e4." + "invalid_webhook_url": "Home Assistant\uac00 SmartThings\uc5d0\uc11c \uc5c5\ub370\uc774\ud2b8\ub97c \uc218\uc2e0\ud558\ub3c4\ub85d \uc62c\ubc14\ub974\uac8c \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc6f9 \ud6c5 URL\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4:\n> {webhook_url} \n\n[\uc548\ub0b4]({component_url})\ub97c \ucc38\uace0\ud558\uc5ec \uad6c\uc131\uc744 \uc5c5\ub370\uc774\ud2b8\ud558\uace0 Home Assistant\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "no_available_locations": "Home Assistant\uc5d0\uc11c \uc124\uc815\ud560 \uc218 \uc788\ub294 SmartThings \uc704\uce58\uac00 \uc5c6\uc2b5\ub2c8\ub2e4." }, "error": { "app_setup_error": "SmartApp \uc744 \uc124\uc815\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", @@ -19,18 +19,18 @@ "data": { "access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070" }, - "description": "[\uc548\ub0b4]({component_url}) \uc5d0 \ub530\ub77c \uc0dd\uc131\ub41c SmartThings [\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070]({token_url}) \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. SmartThings \uacc4\uc815\uc5d0\uc11c Home Assistant \uc5f0\ub3d9\uc744 \ub9cc\ub4dc\ub294\ub370 \uc0ac\uc6a9\ub429\ub2c8\ub2e4.", - "title": "\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070 \uc785\ub825\ud558\uae30" + "description": "[\uc548\ub0b4]({component_url})\uc5d0 \ub530\ub77c \uc0dd\uc131\ub41c SmartThings [\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070]({token_url})\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. SmartThings \uacc4\uc815\uc5d0\uc11c Home Assistant \uc5f0\ub3d9\uc744 \ub9cc\ub4dc\ub294\ub370 \uc0ac\uc6a9\ub429\ub2c8\ub2e4.", + "title": "\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" }, "select_location": { "data": { "location_id": "\uc704\uce58" }, - "description": "Home Assistant \uc5d0 \ucd94\uac00\ud558\ub824\ub294 SmartThings \uc704\uce58\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \uc0c8\ub86d\uac8c \uc5f4\ub9b0 \ub85c\uadf8\uc778 \ucc3d\uc5d0\uc11c \ub85c\uadf8\uc778\uc744 \ud558\uba74 \uc120\ud0dd\ud55c \uc704\uce58\uc5d0 Home Assistant \uc5f0\ub3d9\uc744 \uc2b9\uc778\ud558\ub77c\ub294 \uba54\uc2dc\uc9c0\uac00 \ud45c\uc2dc\ub429\ub2c8\ub2e4.", + "description": "Home Assistant\uc5d0 \ucd94\uac00\ud558\ub824\ub294 SmartThings \uc704\uce58\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \uc0c8\ub86d\uac8c \uc5f4\ub9b0 \ub85c\uadf8\uc778 \ucc3d\uc5d0\uc11c \ub85c\uadf8\uc778\uc744 \ud558\uba74 \uc120\ud0dd\ud55c \uc704\uce58\uc5d0 Home Assistant \uc5f0\ub3d9\uc744 \uc2b9\uc778\ud558\ub77c\ub294 \uba54\uc2dc\uc9c0\uac00 \ud45c\uc2dc\ub429\ub2c8\ub2e4.", "title": "\uc704\uce58 \uc120\ud0dd\ud558\uae30" }, "user": { - "description": "SmartThings \ub294 \uc544\ub798\uc758 \uc6f9 \ud6c5 \uc8fc\uc18c\ub85c Home Assistant \uc5d0 \ud478\uc2dc \uc5c5\ub370\uc774\ud2b8\ub97c \ubcf4\ub0b4\ub3c4\ub85d \uad6c\uc131\ub429\ub2c8\ub2e4. \n > {webhook_url} \n\n\uc774 \uad6c\uc131\uc774 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc73c\uba74 \uad6c\uc131\uc744 \uc5c5\ub370\uc774\ud2b8\ud558\uace0 Home Assistant \ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "description": "SmartThings\ub294 \uc544\ub798\uc758 \uc6f9 \ud6c5 \uc8fc\uc18c\ub85c Home Assistant\uc5d0 \ud478\uc2dc \uc5c5\ub370\uc774\ud2b8\ub97c \ubcf4\ub0b4\ub3c4\ub85d \uad6c\uc131\ub429\ub2c8\ub2e4. \n > {webhook_url} \n\n\uc774 \uad6c\uc131\uc774 \uc62c\ubc14\ub974\uc9c0 \uc54a\ub2e4\uba74 \uad6c\uc131\uc744 \uc5c5\ub370\uc774\ud2b8\ud558\uace0 Home Assistant\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "title": "\ucf5c\ubc31 URL \ud655\uc778\ud558\uae30" } } diff --git a/homeassistant/components/smartthings/translations/nl.json b/homeassistant/components/smartthings/translations/nl.json index a77ad40f0ca..6c141b624d2 100644 --- a/homeassistant/components/smartthings/translations/nl.json +++ b/homeassistant/components/smartthings/translations/nl.json @@ -9,7 +9,7 @@ "token_forbidden": "Het token heeft niet de vereiste OAuth-scopes.", "token_invalid_format": "Het token moet de UID/GUID-indeling hebben", "token_unauthorized": "Het token is ongeldig of niet langer geautoriseerd.", - "webhook_error": "SmartThings kon het in 'base_url` geconfigureerde endpoint niet goedkeuren. Lees de componentvereisten door." + "webhook_error": "SmartThings kan de webhook URL niet valideren. Zorg ervoor dat de webhook URL bereikbaar is vanaf het internet en probeer het opnieuw." }, "step": { "authorize": { @@ -30,8 +30,8 @@ "title": "Locatie selecteren" }, "user": { - "description": "Voer een SmartThings [Personal Access Token]({token_url}) in die is aangemaakt volgens de [instructies]({component_url}).", - "title": "Persoonlijk toegangstoken invoeren" + "description": "SmartThings zal worden geconfigureerd om push updates te sturen naar Home Assistant op:\n> {webhook_url}\n\nAls dit niet correct is, werk dan uw configuratie bij, start Home Assistant opnieuw op en probeer het opnieuw.", + "title": "Bevestig Callback URL" } } } diff --git a/homeassistant/components/smarttub/translations/bg.json b/homeassistant/components/smarttub/translations/bg.json new file mode 100644 index 00000000000..05ef3ed780e --- /dev/null +++ b/homeassistant/components/smarttub/translations/bg.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarttub/translations/hu.json b/homeassistant/components/smarttub/translations/hu.json new file mode 100644 index 00000000000..666ff85e321 --- /dev/null +++ b/homeassistant/components/smarttub/translations/hu.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "email": "E-mail", + "password": "Jelsz\u00f3" + }, + "description": "Add meg SmartTub e-mail c\u00edmet \u00e9s jelsz\u00f3t a bejelentkez\u00e9shez", + "title": "Bejelentkez\u00e9s" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarttub/translations/id.json b/homeassistant/components/smarttub/translations/id.json new file mode 100644 index 00000000000..c1de3aa0453 --- /dev/null +++ b/homeassistant/components/smarttub/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Kata Sandi" + }, + "description": "Masukkan alamat email dan kata sandi SmartTub Anda untuk masuk", + "title": "Masuk" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarttub/translations/ko.json b/homeassistant/components/smarttub/translations/ko.json index fab7e511034..2ab844cd967 100644 --- a/homeassistant/components/smarttub/translations/ko.json +++ b/homeassistant/components/smarttub/translations/ko.json @@ -13,7 +13,9 @@ "data": { "email": "\uc774\uba54\uc77c", "password": "\ube44\ubc00\ubc88\ud638" - } + }, + "description": "\ub85c\uadf8\uc778\ud558\ub824\uba74 SmartTub \uc774\uba54\uc77c \uc8fc\uc18c\uc640 \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", + "title": "\ub85c\uadf8\uc778" } } } diff --git a/homeassistant/components/smarttub/translations/nl.json b/homeassistant/components/smarttub/translations/nl.json index a5f20db8a32..7ef935d8cee 100644 --- a/homeassistant/components/smarttub/translations/nl.json +++ b/homeassistant/components/smarttub/translations/nl.json @@ -14,6 +14,7 @@ "email": "E-mail", "password": "Wachtwoord" }, + "description": "Voer uw SmartTub-e-mailadres en wachtwoord in om in te loggen", "title": "Inloggen" } } diff --git a/homeassistant/components/smarttub/translations/pt.json b/homeassistant/components/smarttub/translations/pt.json new file mode 100644 index 00000000000..414ca7ddf82 --- /dev/null +++ b/homeassistant/components/smarttub/translations/pt.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", + "reauth_successful": "Reautentica\u00e7\u00e3o bem sucedida" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Palavra-passe" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smhi/translations/he.json b/homeassistant/components/smhi/translations/he.json new file mode 100644 index 00000000000..4c49313d977 --- /dev/null +++ b/homeassistant/components/smhi/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smhi/translations/id.json b/homeassistant/components/smhi/translations/id.json new file mode 100644 index 00000000000..8d5d95f183e --- /dev/null +++ b/homeassistant/components/smhi/translations/id.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "name_exists": "Nama sudah ada", + "wrong_location": "Hanya untuk lokasi di Swedia" + }, + "step": { + "user": { + "data": { + "latitude": "Lintang", + "longitude": "Bujur", + "name": "Nama" + }, + "title": "Lokasi di Swedia" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sms/translations/hu.json b/homeassistant/components/sms/translations/hu.json index 3b2d79a34a7..6fa524b18ab 100644 --- a/homeassistant/components/sms/translations/hu.json +++ b/homeassistant/components/sms/translations/hu.json @@ -1,7 +1,20 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "device": "Eszk\u00f6z" + }, + "title": "Csatlakoz\u00e1s a modemhez" + } } } } \ No newline at end of file diff --git a/homeassistant/components/sms/translations/id.json b/homeassistant/components/sms/translations/id.json new file mode 100644 index 00000000000..63ebb088521 --- /dev/null +++ b/homeassistant/components/sms/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "device": "Perangkat" + }, + "title": "Hubungkan ke modem" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sms/translations/ko.json b/homeassistant/components/sms/translations/ko.json index 13c043ef635..5ead95c1a27 100644 --- a/homeassistant/components/sms/translations/ko.json +++ b/homeassistant/components/sms/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", diff --git a/homeassistant/components/solaredge/translations/hu.json b/homeassistant/components/solaredge/translations/hu.json index 31890269925..8479c90f595 100644 --- a/homeassistant/components/solaredge/translations/hu.json +++ b/homeassistant/components/solaredge/translations/hu.json @@ -1,6 +1,11 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs", "site_not_active": "Az oldal nem akt\u00edv" }, "step": { diff --git a/homeassistant/components/solaredge/translations/id.json b/homeassistant/components/solaredge/translations/id.json new file mode 100644 index 00000000000..41c94755af7 --- /dev/null +++ b/homeassistant/components/solaredge/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "site_exists": "Nilai site_id ini sudah dikonfigurasi" + }, + "error": { + "already_configured": "Perangkat sudah dikonfigurasi", + "could_not_connect": "Tidak dapat terhubung ke API solaredge", + "invalid_api_key": "Kunci API tidak valid", + "site_exists": "Nilai site_id ini sudah dikonfigurasi", + "site_not_active": "Situs tidak aktif" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "name": "Nama instalasi ini", + "site_id": "Nilai site_id SolarEdge" + }, + "title": "Tentukan parameter API untuk instalasi ini" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solaredge/translations/ko.json b/homeassistant/components/solaredge/translations/ko.json index 8544cdd143d..ce3ed2a767d 100644 --- a/homeassistant/components/solaredge/translations/ko.json +++ b/homeassistant/components/solaredge/translations/ko.json @@ -6,8 +6,10 @@ }, "error": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "could_not_connect": "SolarEdge API\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "site_exists": "\uc774 site_id \ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "site_exists": "\uc774 site_id \ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "site_not_active": "\uc0ac\uc774\ud2b8\uac00 \ud65c\uc131\ud654\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/solarlog/translations/hu.json b/homeassistant/components/solarlog/translations/hu.json index 3fa8a9620a0..dd0ea8033ae 100644 --- a/homeassistant/components/solarlog/translations/hu.json +++ b/homeassistant/components/solarlog/translations/hu.json @@ -1,7 +1,11 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6zt m\u00e1r konfigur\u00e1ltuk" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { "user": { diff --git a/homeassistant/components/solarlog/translations/id.json b/homeassistant/components/solarlog/translations/id.json new file mode 100644 index 00000000000..3ce222f9c2a --- /dev/null +++ b/homeassistant/components/solarlog/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Prefiks yang akan digunakan untuk sensor Solar-Log Anda" + }, + "title": "Tentukan koneksi Solar-Log Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/soma/translations/hu.json b/homeassistant/components/soma/translations/hu.json index 82ec28ff4d7..d013cb49fdf 100644 --- a/homeassistant/components/soma/translations/hu.json +++ b/homeassistant/components/soma/translations/hu.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", "connection_error": "Nem siker\u00fclt csatlakozni a SOMA Connecthez.", "missing_configuration": "A Soma \u00f6sszetev\u0151 nincs konfigur\u00e1lva. K\u00e9rj\u00fck, k\u00f6vesse a dokument\u00e1ci\u00f3t.", "result_error": "A SOMA Connect hiba\u00e1llapottal v\u00e1laszolt." @@ -15,7 +16,7 @@ "port": "Port" }, "description": "K\u00e9rj\u00fck, adja meg a SOMA Connect csatlakoz\u00e1si be\u00e1ll\u00edt\u00e1sait.", - "title": "SOMA csatlakoz\u00e1s" + "title": "SOMA Connect" } } } diff --git a/homeassistant/components/soma/translations/id.json b/homeassistant/components/soma/translations/id.json new file mode 100644 index 00000000000..d512bd46797 --- /dev/null +++ b/homeassistant/components/soma/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_setup": "Anda hanya dapat mengonfigurasi satu akun Soma.", + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "connection_error": "Gagal menyambungkan ke SOMA Connect.", + "missing_configuration": "Komponen Soma tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "result_error": "SOMA Connect merespons dengan status kesalahan." + }, + "create_entry": { + "default": "Berhasil mengautentikasi dengan Soma." + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port" + }, + "description": "Masukkan pengaturan koneksi SOMA Connect Anda.", + "title": "SOMA Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/soma/translations/ko.json b/homeassistant/components/soma/translations/ko.json index 83c2f01ff8b..13a6fb03e83 100644 --- a/homeassistant/components/soma/translations/ko.json +++ b/homeassistant/components/soma/translations/ko.json @@ -3,12 +3,12 @@ "abort": { "already_setup": "\ud558\ub098\uc758 Soma \uacc4\uc815\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "connection_error": "SOMA Connect \uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "connection_error": "SOMA Connect\uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", "missing_configuration": "Soma \uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "result_error": "SOMA Connect \uac00 \uc624\ub958 \uc0c1\ud0dc\ub85c \uc751\ub2f5\ud588\uc2b5\ub2c8\ub2e4." }, "create_entry": { - "default": "Soma \ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "default": "Soma\ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "step": { "user": { diff --git a/homeassistant/components/somfy/translations/hu.json b/homeassistant/components/somfy/translations/hu.json index 86927570c85..ce4e94b3399 100644 --- a/homeassistant/components/somfy/translations/hu.json +++ b/homeassistant/components/somfy/translations/hu.json @@ -1,11 +1,17 @@ { "config": { + "abort": { + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "create_entry": { - "default": "Sikeres autentik\u00e1ci\u00f3" + "default": "Sikeres hiteles\u00edt\u00e9s" }, "step": { "pick_implementation": { - "title": "V\u00e1lassza ki a hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" } } } diff --git a/homeassistant/components/somfy/translations/id.json b/homeassistant/components/somfy/translations/id.json new file mode 100644 index 00000000000..2d229de00d5 --- /dev/null +++ b/homeassistant/components/somfy/translations/id.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy/translations/ko.json b/homeassistant/components/somfy/translations/ko.json index 8b4f4ff752f..568c8d05116 100644 --- a/homeassistant/components/somfy/translations/ko.json +++ b/homeassistant/components/somfy/translations/ko.json @@ -4,7 +4,7 @@ "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "create_entry": { "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/somfy_mylink/translations/bg.json b/homeassistant/components/somfy_mylink/translations/bg.json new file mode 100644 index 00000000000..4983c9a14b2 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "\u041f\u043e\u0440\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/he.json b/homeassistant/components/somfy_mylink/translations/he.json new file mode 100644 index 00000000000..9af5985ac45 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/he.json @@ -0,0 +1,9 @@ +{ + "options": { + "step": { + "init": { + "title": "\u05e7\u05d1\u05d9\u05e2\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4 \u05e9\u05dc MyLink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/hu.json b/homeassistant/components/somfy_mylink/translations/hu.json new file mode 100644 index 00000000000..1ef6d25d34c --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/hu.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "Hoszt", + "port": "Port" + } + } + } + }, + "options": { + "abort": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "init": { + "title": "Mylink be\u00e1ll\u00edt\u00e1sok konfigur\u00e1l\u00e1sa" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/id.json b/homeassistant/components/somfy_mylink/translations/id.json new file mode 100644 index 00000000000..0203ae421e2 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/id.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "system_id": "ID Sistem" + }, + "description": "ID Sistem dapat diperoleh di aplikasi MyLink di bawah bagian Integrasi dengan memilih layanan non-Cloud." + } + } + }, + "options": { + "abort": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "entity_config": { + "data": { + "reverse": "Penutup dibalik" + }, + "description": "Konfigurasikan opsi untuk `{entity_id}`", + "title": "Konfigurasikan Entitas" + }, + "init": { + "data": { + "default_reverse": "Status pembalikan baku untuk penutup yang belum dikonfigurasi", + "entity_id": "Konfigurasikan entitas tertentu.", + "target_id": "Konfigurasikan opsi untuk penutup." + }, + "title": "Konfigurasikan Opsi MyLink" + }, + "target_config": { + "data": { + "reverse": "Penutup dibalik" + }, + "description": "Konfigurasikan opsi untuk `{target_name}`", + "title": "Konfigurasikan Cover MyLink" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/ko.json b/homeassistant/components/somfy_mylink/translations/ko.json index 4d4a78ee1f0..b099d3d6ed8 100644 --- a/homeassistant/components/somfy_mylink/translations/ko.json +++ b/homeassistant/components/somfy_mylink/translations/ko.json @@ -8,18 +8,46 @@ "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, + "flow_title": "Somfy MyLink: {mac} ({ip})", "step": { "user": { "data": { "host": "\ud638\uc2a4\ud2b8", - "port": "\ud3ec\ud2b8" - } + "port": "\ud3ec\ud2b8", + "system_id": "\uc2dc\uc2a4\ud15c ID" + }, + "description": "\uc2dc\uc2a4\ud15c ID\ub294 MyLink \uc571\uc758 Integration\uc5d0\uc11c non-Cloud service\ub97c \uc120\ud0dd\ud558\uc5ec \uc5bb\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4." } } }, "options": { "abort": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "entity_config": { + "data": { + "reverse": "\uc5ec\ub2eb\uc774\uac00 \ubc18\uc804\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "description": "`{entity_id}`\uc5d0 \ub300\ud55c \uc635\uc158 \uad6c\uc131\ud558\uae30", + "title": "\uad6c\uc131\uc694\uc18c \uad6c\uc131\ud558\uae30" + }, + "init": { + "data": { + "default_reverse": "\uad6c\uc131\ub418\uc9c0 \uc54a\uc740 \uc5ec\ub2eb\uc774\uc5d0 \ub300\ud55c \uae30\ubcf8 \ubc18\uc804 \uc0c1\ud0dc", + "entity_id": "\ud2b9\uc815 \uad6c\uc131\uc694\uc18c\ub97c \uad6c\uc131\ud569\ub2c8\ub2e4.", + "target_id": "\uc5ec\ub2eb\uc774\uc5d0 \ub300\ud55c \uc635\uc158 \uad6c\uc131\ud558\uae30" + }, + "title": "MyLink \uc635\uc158 \uad6c\uc131\ud558\uae30" + }, + "target_config": { + "data": { + "reverse": "\uc5ec\ub2eb\uc774\uac00 \ubc18\uc804\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "description": "`{target_name}`\uc5d0 \ub300\ud55c \uc635\uc158 \uad6c\uc131\ud558\uae30", + "title": "MyLink \uc5ec\ub2eb\uc774 \uad6c\uc131\ud558\uae30" + } } - } + }, + "title": "Somfy MyLink" } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/nl.json b/homeassistant/components/somfy_mylink/translations/nl.json index b0ae5c9d3ad..1e9d5a58f89 100644 --- a/homeassistant/components/somfy_mylink/translations/nl.json +++ b/homeassistant/components/somfy_mylink/translations/nl.json @@ -33,6 +33,10 @@ "entity_id": "Configureer een specifieke entiteit." }, "title": "Configureer MyLink-opties" + }, + "target_config": { + "description": "Configureer opties voor ' {target_name} '", + "title": "Configureer MyLink Cover" } } }, diff --git a/homeassistant/components/sonarr/translations/hu.json b/homeassistant/components/sonarr/translations/hu.json index f5301e874ea..e3fa0b5ff21 100644 --- a/homeassistant/components/sonarr/translations/hu.json +++ b/homeassistant/components/sonarr/translations/hu.json @@ -1,7 +1,39 @@ { "config": { "abort": { - "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "flow_title": "Sonarr: {name}", + "step": { + "reauth_confirm": { + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" + }, + "user": { + "data": { + "api_key": "API kulcs", + "base_path": "El\u00e9r\u00e9si \u00fat az API-hoz", + "host": "Hoszt", + "port": "Port", + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "upcoming_days": "A megjelen\u00edteni k\u00edv\u00e1nt k\u00f6vetkez\u0151 napok sz\u00e1ma", + "wanted_max_items": "A megjelen\u00edteni k\u00edv\u00e1nt elemek maxim\u00e1lis sz\u00e1ma" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/id.json b/homeassistant/components/sonarr/translations/id.json new file mode 100644 index 00000000000..ffaf1d22604 --- /dev/null +++ b/homeassistant/components/sonarr/translations/id.json @@ -0,0 +1,40 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "flow_title": "Sonarr: {name}", + "step": { + "reauth_confirm": { + "description": "Integrasi Sonarr perlu diautentikasi ulang secara manual dengan API Sonarr yang dihosting di: {host}", + "title": "Autentikasi Ulang Integrasi" + }, + "user": { + "data": { + "api_key": "Kunci API", + "base_path": "Jalur ke API", + "host": "Host", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL", + "verify_ssl": "Verifikasi sertifikat SSL" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "upcoming_days": "Jumlah hari mendatang untuk ditampilkan", + "wanted_max_items": "Jumlah maksimal item yang diinginkan untuk ditampilkan" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/ko.json b/homeassistant/components/sonarr/translations/ko.json index 17e3592d509..fbff72e46f2 100644 --- a/homeassistant/components/sonarr/translations/ko.json +++ b/homeassistant/components/sonarr/translations/ko.json @@ -12,7 +12,8 @@ "flow_title": "Sonarr: {name}", "step": { "reauth_confirm": { - "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d" + "description": "Sonarr \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub294 {host}\uc5d0\uc11c \ud638\uc2a4\ud305\ub418\ub294 Sonarr API\ub85c \uc218\ub3d9\uc73c\ub85c \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4", + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" }, "user": { "data": { diff --git a/homeassistant/components/sonarr/translations/nl.json b/homeassistant/components/sonarr/translations/nl.json index 08ef9bb2ece..3203f3ac128 100644 --- a/homeassistant/components/sonarr/translations/nl.json +++ b/homeassistant/components/sonarr/translations/nl.json @@ -9,6 +9,7 @@ "cannot_connect": "Kon niet verbinden", "invalid_auth": "Ongeldige authenticatie" }, + "flow_title": "Sonarr: {name}", "step": { "reauth_confirm": { "description": "De Sonarr-integratie moet handmatig opnieuw worden geverifieerd met de Sonarr-API die wordt gehost op: {host}", @@ -17,6 +18,7 @@ "user": { "data": { "api_key": "API-sleutel", + "base_path": "Pad naar API", "host": "Host", "port": "Poort", "ssl": "Maakt gebruik van een SSL-certificaat", @@ -24,5 +26,15 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "upcoming_days": "Aantal komende dagen om weer te geven", + "wanted_max_items": "Maximaal aantal gewenste items dat moet worden weergegeven" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/songpal/translations/hu.json b/homeassistant/components/songpal/translations/hu.json index cd4c501ecf7..aa55862c0aa 100644 --- a/homeassistant/components/songpal/translations/hu.json +++ b/homeassistant/components/songpal/translations/hu.json @@ -1,12 +1,17 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "not_songpal_device": "Nem Songpal eszk\u00f6z" }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, + "flow_title": "Sony Songpal {name} ({host})", "step": { + "init": { + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a {name}({host})-t?" + }, "user": { "data": { "endpoint": "V\u00e9gpont" diff --git a/homeassistant/components/songpal/translations/id.json b/homeassistant/components/songpal/translations/id.json new file mode 100644 index 00000000000..2b8149661bc --- /dev/null +++ b/homeassistant/components/songpal/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "not_songpal_device": "Bukan perangkat Songpal" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "Sony Songpal {name} ({host})", + "step": { + "init": { + "description": "Ingin menyiapkan {name} ({host})?" + }, + "user": { + "data": { + "endpoint": "Titik Akhir" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonos/translations/hu.json b/homeassistant/components/sonos/translations/hu.json index aa10087a884..2123ec520f7 100644 --- a/homeassistant/components/sonos/translations/hu.json +++ b/homeassistant/components/sonos/translations/hu.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Nem tal\u00e1lhat\u00f3k Sonos eszk\u00f6z\u00f6k a h\u00e1l\u00f3zaton.", - "single_instance_allowed": "Csak egyetlen Sonos konfigur\u00e1ci\u00f3 sz\u00fcks\u00e9ges." + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "step": { "confirm": { diff --git a/homeassistant/components/sonos/translations/id.json b/homeassistant/components/sonos/translations/id.json index ef88cab5814..145e2775e4a 100644 --- a/homeassistant/components/sonos/translations/id.json +++ b/homeassistant/components/sonos/translations/id.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "no_devices_found": "Tidak ada perangkat Sonos yang ditemukan pada jaringan.", - "single_instance_allowed": "Hanya satu konfigurasi Sonos yang diperlukan." + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." }, "step": { "confirm": { - "description": "Apakah Anda ingin mengatur Sonos?" + "description": "Ingin menyiapkan Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/ko.json b/homeassistant/components/sonos/translations/ko.json index ba85f8df170..f85f3c5cab4 100644 --- a/homeassistant/components/sonos/translations/ko.json +++ b/homeassistant/components/sonos/translations/ko.json @@ -2,11 +2,11 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { - "description": "Sonos \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "Sonos\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/sonos/translations/nl.json b/homeassistant/components/sonos/translations/nl.json index e52111fc50f..42298f0b4f7 100644 --- a/homeassistant/components/sonos/translations/nl.json +++ b/homeassistant/components/sonos/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Geen Sonos-apparaten gevonden op het netwerk.", - "single_instance_allowed": "Er is slechts \u00e9\u00e9n configuratie van Sonos nodig." + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "step": { "confirm": { diff --git a/homeassistant/components/speedtestdotnet/translations/de.json b/homeassistant/components/speedtestdotnet/translations/de.json index 3b5ef0b26e1..f2635c19f03 100644 --- a/homeassistant/components/speedtestdotnet/translations/de.json +++ b/homeassistant/components/speedtestdotnet/translations/de.json @@ -6,7 +6,7 @@ }, "step": { "user": { - "description": "M\u00f6chtest du mit der Einrichtung beginnen?" + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?" } } }, diff --git a/homeassistant/components/speedtestdotnet/translations/hu.json b/homeassistant/components/speedtestdotnet/translations/hu.json new file mode 100644 index 00000000000..ec08c711e1d --- /dev/null +++ b/homeassistant/components/speedtestdotnet/translations/hu.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "wrong_server_id": "A szerver azonos\u00edt\u00f3 \u00e9rv\u00e9nytelen" + }, + "step": { + "user": { + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "server_name": "V\u00e1laszd ki a teszt szervert" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/speedtestdotnet/translations/id.json b/homeassistant/components/speedtestdotnet/translations/id.json new file mode 100644 index 00000000000..24e78609380 --- /dev/null +++ b/homeassistant/components/speedtestdotnet/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "wrong_server_id": "ID server tidak valid" + }, + "step": { + "user": { + "description": "Ingin memulai penyiapan?" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "manual": "Nonaktifkan pembaruan otomatis", + "scan_interval": "Frekuensi pembaruan (menit)", + "server_name": "Pilih server uji" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/speedtestdotnet/translations/ko.json b/homeassistant/components/speedtestdotnet/translations/ko.json index 2951d72d201..e3b65208317 100644 --- a/homeassistant/components/speedtestdotnet/translations/ko.json +++ b/homeassistant/components/speedtestdotnet/translations/ko.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", - "wrong_server_id": "\uc11c\ubc84 ID \uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "wrong_server_id": "\uc11c\ubc84 ID\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/nl.json b/homeassistant/components/speedtestdotnet/translations/nl.json index 1fe99195f7a..5de8460fd77 100644 --- a/homeassistant/components/speedtestdotnet/translations/nl.json +++ b/homeassistant/components/speedtestdotnet/translations/nl.json @@ -9,5 +9,16 @@ "description": "Wil je beginnen met instellen?" } } + }, + "options": { + "step": { + "init": { + "data": { + "manual": "Automatische updaten uitschakelen", + "scan_interval": "Update frequentie (minuten)", + "server_name": "Selecteer testserver" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/spider/translations/hu.json b/homeassistant/components/spider/translations/hu.json new file mode 100644 index 00000000000..9639cfe6367 --- /dev/null +++ b/homeassistant/components/spider/translations/hu.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "title": "Bejelentkez\u00e9s mijn.ithodaalderop.nl fi\u00f3kkal" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spider/translations/id.json b/homeassistant/components/spider/translations/id.json new file mode 100644 index 00000000000..2ea038fdcdd --- /dev/null +++ b/homeassistant/components/spider/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Masuk dengan akun mijn.ithodaalderop.nl" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spider/translations/ko.json b/homeassistant/components/spider/translations/ko.json index 9e9ed5b0f30..5c72b30726a 100644 --- a/homeassistant/components/spider/translations/ko.json +++ b/homeassistant/components/spider/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", @@ -12,7 +12,8 @@ "data": { "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - } + }, + "title": "mijn.ithodaalderop.nl \uacc4\uc815\uc73c\ub85c \ub85c\uadf8\uc778\ud558\uae30" } } } diff --git a/homeassistant/components/spider/translations/nl.json b/homeassistant/components/spider/translations/nl.json index bc7683ac0a4..373d203aed7 100644 --- a/homeassistant/components/spider/translations/nl.json +++ b/homeassistant/components/spider/translations/nl.json @@ -12,7 +12,8 @@ "data": { "password": "Wachtwoord", "username": "Gebruikersnaam" - } + }, + "title": "Aanmelden met mijn.ithodaalderop.nl account" } } } diff --git a/homeassistant/components/spotify/translations/hu.json b/homeassistant/components/spotify/translations/hu.json index fb0dc0f8a1f..060aeffe8bd 100644 --- a/homeassistant/components/spotify/translations/hu.json +++ b/homeassistant/components/spotify/translations/hu.json @@ -2,15 +2,24 @@ "config": { "abort": { "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s az \u00e9rv\u00e9nyes\u00edt\u00e9si url gener\u00e1l\u00e1sa sor\u00e1n.", - "missing_configuration": "A Spotify integr\u00e1ci\u00f3 nincs konfigur\u00e1lva. K\u00e9rj\u00fck, k\u00f6vesse a dokument\u00e1ci\u00f3t." + "missing_configuration": "A Spotify integr\u00e1ci\u00f3 nincs konfigur\u00e1lva. K\u00e9rj\u00fck, k\u00f6vesse a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz." }, "create_entry": { "default": "A Spotify sikeresen hiteles\u00edtett." }, "step": { "pick_implementation": { - "title": "V\u00e1lassza ki a hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + }, + "reauth_confirm": { + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "A Spotify API v\u00e9gpont el\u00e9rhet\u0151" + } } } \ No newline at end of file diff --git a/homeassistant/components/spotify/translations/id.json b/homeassistant/components/spotify/translations/id.json new file mode 100644 index 00000000000..f75f4159a96 --- /dev/null +++ b/homeassistant/components/spotify/translations/id.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Integrasi Spotify tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})", + "reauth_account_mismatch": "Akun Spotify yang digunakan untuk autentikasi tidak cocok dengan akun yang memerlukan autentikasi ulang." + }, + "create_entry": { + "default": "Berhasil mengautentikasi dengan Spotify." + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + }, + "reauth_confirm": { + "description": "Integrasi Spotify perlu diautentikasi ulang ke Spotify untuk akun: {account}", + "title": "Autentikasi Ulang Integrasi" + } + } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "Titik akhir API Spotify dapat dijangkau" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spotify/translations/ko.json b/homeassistant/components/spotify/translations/ko.json index 22a338a8d7c..926231a1de3 100644 --- a/homeassistant/components/spotify/translations/ko.json +++ b/homeassistant/components/spotify/translations/ko.json @@ -4,19 +4,24 @@ "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "missing_configuration": "Spotify \uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", - "reauth_account_mismatch": "\uc778\uc99d\ub41c Spotify \uacc4\uc815\uc740 \uc7ac\uc778\uc99d\uc774 \ud544\uc694\ud55c \uacc4\uc815\uacfc \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." + "reauth_account_mismatch": "\uc778\uc99d \ubc1b\uc740 Spotify \uacc4\uc815\uc774 \uc7ac\uc778\uc99d\ud574\uc57c \ud558\ub294 \uacc4\uc815\uacfc \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "create_entry": { - "default": "Spotify \ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "default": "Spotify\ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "step": { "pick_implementation": { "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" }, "reauth_confirm": { - "description": "Spotify \ud1b5\ud569\uc740 \uacc4\uc815 {account} \ub300\ud574 Spotify\ub85c \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c\ud569\ub2c8\ub2e4.", - "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d" + "description": "Spotify \uad6c\uc131\uc694\uc18c\ub294 {account} \uacc4\uc815\uc5d0 \ub300\ud574 Spotify\ub97c \uc0ac\uc6a9\ud558\uc5ec \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4.", + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "Spotify API \uc5d4\ub4dc \ud3ec\uc778\ud2b8 \uc5f0\uacb0" + } } } \ No newline at end of file diff --git a/homeassistant/components/spotify/translations/nl.json b/homeassistant/components/spotify/translations/nl.json index 46b18857fe8..afccb637145 100644 --- a/homeassistant/components/spotify/translations/nl.json +++ b/homeassistant/components/spotify/translations/nl.json @@ -18,5 +18,10 @@ "title": "Verifieer de integratie opnieuw" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "Spotify API-eindpunt is bereikbaar" + } } } \ No newline at end of file diff --git a/homeassistant/components/squeezebox/translations/hu.json b/homeassistant/components/squeezebox/translations/hu.json index 3b2d79a34a7..216badd15c6 100644 --- a/homeassistant/components/squeezebox/translations/hu.json +++ b/homeassistant/components/squeezebox/translations/hu.json @@ -1,7 +1,30 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "no_server_found": "Nem tal\u00e1lhat\u00f3 LMS szerver." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "no_server_found": "Nem siker\u00fclt automatikusan felfedezni a szervert.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "Logitech Squeezebox: {host}", + "step": { + "edit": { + "data": { + "host": "Hoszt", + "password": "Jelsz\u00f3", + "port": "Port", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + }, + "user": { + "data": { + "host": "Hoszt" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/squeezebox/translations/id.json b/homeassistant/components/squeezebox/translations/id.json new file mode 100644 index 00000000000..764c356ba84 --- /dev/null +++ b/homeassistant/components/squeezebox/translations/id.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "no_server_found": "Tidak ada server LMS yang ditemukan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "no_server_found": "Tidak dapat menemukan server secara otomatis.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Logitech Squeezebox: {host}", + "step": { + "edit": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + }, + "title": "Edit informasi koneksi" + }, + "user": { + "data": { + "host": "Host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/hu.json b/homeassistant/components/srp_energy/translations/hu.json index f46e17923ad..0c3bdf29389 100644 --- a/homeassistant/components/srp_energy/translations/hu.json +++ b/homeassistant/components/srp_energy/translations/hu.json @@ -1,11 +1,22 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { - "id": "A fi\u00f3k azonos\u00edt\u00f3ja" + "id": "A fi\u00f3k azonos\u00edt\u00f3ja", + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } } } - } + }, + "title": "SRP Energy" } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/id.json b/homeassistant/components/srp_energy/translations/id.json new file mode 100644 index 00000000000..fefcbff2ecb --- /dev/null +++ b/homeassistant/components/srp_energy/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_account": "ID akun harus terdiri dari 9 angka", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "id": "ID akun", + "is_tou": "Dalam Paket Time of Use", + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + }, + "title": "SRP Energy" +} \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/ko.json b/homeassistant/components/srp_energy/translations/ko.json index 4b6af62638a..b329cf1f2b1 100644 --- a/homeassistant/components/srp_energy/translations/ko.json +++ b/homeassistant/components/srp_energy/translations/ko.json @@ -1,20 +1,24 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_account": "\uacc4\uc815 ID\ub294 9\uc790\ub9ac \uc22b\uc790\uc5ec\uc57c \ud569\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "user": { "data": { + "id": "\uacc4\uc815 ID", + "is_tou": "\uacc4\uc2dc\ubcc4 \uc694\uae08\uc81c", "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" } } } - } + }, + "title": "SRP Energy" } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/nl.json b/homeassistant/components/srp_energy/translations/nl.json index cd06c36b661..d1df1796f3a 100644 --- a/homeassistant/components/srp_energy/translations/nl.json +++ b/homeassistant/components/srp_energy/translations/nl.json @@ -12,6 +12,7 @@ "step": { "user": { "data": { + "id": "Account ID", "password": "Wachtwoord", "username": "Gebruikersnaam" } diff --git a/homeassistant/components/starline/translations/he.json b/homeassistant/components/starline/translations/he.json new file mode 100644 index 00000000000..53542798232 --- /dev/null +++ b/homeassistant/components/starline/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "auth_user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/starline/translations/hu.json b/homeassistant/components/starline/translations/hu.json index 9d544eb0337..71895e80ad5 100644 --- a/homeassistant/components/starline/translations/hu.json +++ b/homeassistant/components/starline/translations/hu.json @@ -11,7 +11,7 @@ "app_id": "App ID", "app_secret": "Titok" }, - "description": "Alkalmaz\u00e1s azonos\u00edt\u00f3ja \u00e9s titkos k\u00f3dja a StarLine fejleszt\u0151i fi\u00f3kb\u00f3l ", + "description": "Alkalmaz\u00e1s azonos\u00edt\u00f3ja \u00e9s titkos k\u00f3dja a [StarLine fejleszt\u0151i fi\u00f3kb\u00f3l](https://my.starline.ru/developer)", "title": "Alkalmaz\u00e1si hiteles\u00edt\u0151 adatok" }, "auth_captcha": { diff --git a/homeassistant/components/starline/translations/id.json b/homeassistant/components/starline/translations/id.json new file mode 100644 index 00000000000..5a0afdba7ef --- /dev/null +++ b/homeassistant/components/starline/translations/id.json @@ -0,0 +1,41 @@ +{ + "config": { + "error": { + "error_auth_app": "ID aplikasi atau kode rahasia salah", + "error_auth_mfa": "Kode salah", + "error_auth_user": "Nama pengguna atau kata sandi salah" + }, + "step": { + "auth_app": { + "data": { + "app_id": "ID Aplikasi", + "app_secret": "Kode Rahasia" + }, + "description": "ID Aplikasi dan kode rahasia dari [akun pengembang StarLine] (https://my.starline.ru/developer)", + "title": "Kredensial aplikasi" + }, + "auth_captcha": { + "data": { + "captcha_code": "Kode dari gambar" + }, + "description": "{captcha_img}", + "title": "Captcha" + }, + "auth_mfa": { + "data": { + "mfa_code": "Kode SMS" + }, + "description": "Masukkan kode yang dikirimkan ke ponsel {phone_number}", + "title": "Autentikasi Dua Faktor" + }, + "auth_user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Email dan kata sandi akun StarLine", + "title": "Kredensial pengguna" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/starline/translations/zh-Hant.json b/homeassistant/components/starline/translations/zh-Hant.json index 81a65ac0405..722c5daaad4 100644 --- a/homeassistant/components/starline/translations/zh-Hant.json +++ b/homeassistant/components/starline/translations/zh-Hant.json @@ -26,7 +26,7 @@ "mfa_code": "\u7c21\u8a0a\u5bc6\u78bc" }, "description": "\u8f38\u5165\u50b3\u9001\u81f3 {phone_number} \u7684\u9a57\u8b49\u78bc", - "title": "\u96d9\u91cd\u9a57\u8b49" + "title": "\u96d9\u91cd\u8a8d\u8b49" }, "auth_user": { "data": { diff --git a/homeassistant/components/subaru/translations/bg.json b/homeassistant/components/subaru/translations/bg.json new file mode 100644 index 00000000000..c3dc8345ecd --- /dev/null +++ b/homeassistant/components/subaru/translations/bg.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "user": { + "data": { + "country": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0434\u044a\u0440\u0436\u0430\u0432\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/hu.json b/homeassistant/components/subaru/translations/hu.json new file mode 100644 index 00000000000..d92ca24b7a1 --- /dev/null +++ b/homeassistant/components/subaru/translations/hu.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "error": { + "bad_pin_format": "A PIN-nek 4 sz\u00e1mjegy\u0171nek kell lennie", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "incorrect_pin": "Helytelen PIN", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "title": "Subaru Starlink konfigur\u00e1ci\u00f3" + }, + "user": { + "data": { + "country": "V\u00e1lassz orsz\u00e1got", + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "title": "Subaru Starlink konfigur\u00e1ci\u00f3" + } + } + }, + "options": { + "step": { + "init": { + "title": "Subaru Starlink be\u00e1ll\u00edt\u00e1sok" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/id.json b/homeassistant/components/subaru/translations/id.json new file mode 100644 index 00000000000..1ae1506fe09 --- /dev/null +++ b/homeassistant/components/subaru/translations/id.json @@ -0,0 +1,44 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung" + }, + "error": { + "bad_pin_format": "PIN harus terdiri dari 4 angka", + "cannot_connect": "Gagal terhubung", + "incorrect_pin": "PIN salah", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "Masukkan PIN MySubaru Anda\nCATATAN: Semua kendaraan dalam akun harus memiliki PIN yang sama", + "title": "Konfigurasi Subaru Starlink" + }, + "user": { + "data": { + "country": "Pilih negara", + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Masukkan kredensial MySubaru Anda\nCATATAN: Penyiapan awal mungkin memerlukan waktu hingga 30 detik", + "title": "Konfigurasi Subaru Starlink" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "Aktifkan polling kendaraan" + }, + "description": "Ketika diaktifkan, polling kendaraan akan mengirim perintah jarak jauh ke kendaraan Anda setiap 2 jam untuk mendapatkan data sensor baru. Tanpa polling kendaraan, data sensor baru hanya diterima ketika kendaraan mengirimkan data secara otomatis (umumnya setelah mesin dimatikan).", + "title": "Opsi Subaru Starlink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/ko.json b/homeassistant/components/subaru/translations/ko.json new file mode 100644 index 00000000000..8fe12309812 --- /dev/null +++ b/homeassistant/components/subaru/translations/ko.json @@ -0,0 +1,44 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "bad_pin_format": "PIN\uc740 4\uc790\ub9ac\uc5ec\uc57c \ud569\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "incorrect_pin": "PIN\uc774 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "MySubaru PIN\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694\n\ucc38\uace0: \uacc4\uc815\uc758 \ubaa8\ub4e0 \ucc28\ub7c9\uc740 \ub3d9\uc77c\ud55c PIN \uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4", + "title": "Subaru Starlink \uad6c\uc131" + }, + "user": { + "data": { + "country": "\uad6d\uac00 \uc120\ud0dd\ud558\uae30", + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "MySubaru \uc790\uaca9 \uc99d\uba85\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694\n\ucc38\uace0: \ucd08\uae30 \uc124\uc815\uc5d0\ub294 \ucd5c\ub300 30\ucd08 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4", + "title": "Subaru Starlink \uad6c\uc131" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "\ucc28\ub7c9 \ud3f4\ub9c1 \ud65c\uc131\ud654\ud558\uae30" + }, + "description": "\ud65c\uc131\ud654\ub418\uba74 \ucc28\ub7c9 \ud3f4\ub9c1\uc740 \ucc28\ub7c9\uc5d0 2\uc2dc\uac04\ub9c8\ub2e4 \uc6d0\uaca9 \uba85\ub839\uc744 \uc804\uc1a1\ud558\uc5ec \uc0c8\ub85c\uc6b4 \uc13c\uc11c \ub370\uc774\ud130\ub97c \ubc1b\uc544\uc635\ub2c8\ub2e4. \ucc28\ub7c9 \ud3f4\ub9c1\uc774 \uc5c6\uc73c\uba74 \uc0c8\ub85c\uc6b4 \uc13c\uc11c \ub370\uc774\ud130\ub294 \ucc28\ub7c9\uc774 \uc790\ub3d9\uc73c\ub85c \ub370\uc774\ud130\ub97c \ubcf4\ub0bc \ub54c\ub9cc \uc218\uc2e0\ub429\ub2c8\ub2e4(\uc77c\ubc18\uc801\uc73c\ub85c \uc5d4\uc9c4\uc774 \uaebc\uc9c4 \ud6c4).", + "title": "Subaru Starlink \uc635\uc158" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/nl.json b/homeassistant/components/subaru/translations/nl.json index 5a9bd4119ff..339c990ca0b 100644 --- a/homeassistant/components/subaru/translations/nl.json +++ b/homeassistant/components/subaru/translations/nl.json @@ -16,6 +16,7 @@ "data": { "pin": "PIN" }, + "description": "Voer uw MySubaru-pincode in\n OPMERKING: Alle voertuigen in een account moeten dezelfde pincode hebben", "title": "Subaru Starlink Configuratie" }, "user": { @@ -24,6 +25,7 @@ "password": "Wachtwoord", "username": "Gebruikersnaam" }, + "description": "Voer uw MySubaru inloggegevens in\nOPMERKING: De eerste installatie kan tot 30 seconden duren", "title": "Subaru Starlink-configuratie" } } @@ -31,6 +33,10 @@ "options": { "step": { "init": { + "data": { + "update_enabled": "Voertuigpeiling inschakelen" + }, + "description": "Wanneer deze optie is ingeschakeld, zal voertuigpeiling om de 2 uur een opdracht op afstand naar uw voertuig sturen om nieuwe sensorgegevens te verkrijgen. Zonder voertuigpeiling worden nieuwe sensorgegevens alleen ontvangen wanneer het voertuig automatisch gegevens doorstuurt (normaal gesproken na het uitschakelen van de motor).", "title": "Subaru Starlink-opties" } } diff --git a/homeassistant/components/sun/translations/id.json b/homeassistant/components/sun/translations/id.json index 374da4e0db9..df6c960e67d 100644 --- a/homeassistant/components/sun/translations/id.json +++ b/homeassistant/components/sun/translations/id.json @@ -2,7 +2,7 @@ "state": { "_": { "above_horizon": "Terbit", - "below_horizon": "Tenggelam" + "below_horizon": "Terbenam" } }, "title": "Matahari" diff --git a/homeassistant/components/switch/translations/id.json b/homeassistant/components/switch/translations/id.json index 891b1b00681..070d272aa43 100644 --- a/homeassistant/components/switch/translations/id.json +++ b/homeassistant/components/switch/translations/id.json @@ -1,8 +1,23 @@ { + "device_automation": { + "action_type": { + "toggle": "Nyala/matikan {entity_name}", + "turn_off": "Matikan {entity_name}", + "turn_on": "Nyalakan {entity_name}" + }, + "condition_type": { + "is_off": "{entity_name} mati", + "is_on": "{entity_name} nyala" + }, + "trigger_type": { + "turned_off": "{entity_name} dimatikan", + "turned_on": "{entity_name} dinyalakan" + } + }, "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" } }, "title": "Sakelar" diff --git a/homeassistant/components/switch/translations/ko.json b/homeassistant/components/switch/translations/ko.json index 1779f3e1f64..6a3417efeb6 100644 --- a/homeassistant/components/switch/translations/ko.json +++ b/homeassistant/components/switch/translations/ko.json @@ -1,17 +1,17 @@ { "device_automation": { "action_type": { - "toggle": "{entity_name} \ud1a0\uae00", - "turn_off": "{entity_name} \ub044\uae30", - "turn_on": "{entity_name} \ucf1c\uae30" + "toggle": "{entity_name}\uc744(\ub97c) \ud1a0\uae00\ud558\uae30", + "turn_off": "{entity_name}\uc744(\ub97c) \ub044\uae30", + "turn_on": "{entity_name}\uc744(\ub97c) \ucf1c\uae30" }, "condition_type": { - "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", - "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" + "is_off": "{entity_name}\uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name}\uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" }, "trigger_type": { - "turned_off": "{entity_name} \uc774(\uac00) \uaebc\uc9c8 \ub54c", - "turned_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c8 \ub54c" + "turned_off": "{entity_name}\uc774(\uac00) \uaebc\uc84c\uc744 \ub54c", + "turned_on": "{entity_name}\uc774(\uac00) \ucf1c\uc84c\uc744 \ub54c" } }, "state": { diff --git a/homeassistant/components/switch/translations/zh-Hans.json b/homeassistant/components/switch/translations/zh-Hans.json index a18455aec6a..afe9f58db8f 100644 --- a/homeassistant/components/switch/translations/zh-Hans.json +++ b/homeassistant/components/switch/translations/zh-Hans.json @@ -7,7 +7,7 @@ }, "condition_type": { "is_off": "{entity_name} \u5df2\u5173\u95ed", - "is_on": "{entity_name} \u5df2\u5f00\u542f" + "is_on": "{entity_name} \u5df2\u6253\u5f00" }, "trigger_type": { "turned_off": "{entity_name} \u88ab\u5173\u95ed", diff --git a/homeassistant/components/syncthru/translations/hu.json b/homeassistant/components/syncthru/translations/hu.json index 3b2d79a34a7..227b759ffaf 100644 --- a/homeassistant/components/syncthru/translations/hu.json +++ b/homeassistant/components/syncthru/translations/hu.json @@ -2,6 +2,23 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "invalid_url": "\u00c9rv\u00e9nytelen URL" + }, + "step": { + "confirm": { + "data": { + "name": "N\u00e9v", + "url": "Webes fel\u00fclet URL-je" + } + }, + "user": { + "data": { + "name": "N\u00e9v", + "url": "Webes fel\u00fclet URL-je" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/id.json b/homeassistant/components/syncthru/translations/id.json new file mode 100644 index 00000000000..54d5e6f5c96 --- /dev/null +++ b/homeassistant/components/syncthru/translations/id.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "invalid_url": "URL tidak valid", + "syncthru_not_supported": "Perangkat tidak mendukung SyncThru", + "unknown_state": "Status printer tidak diketahui, verifikasi URL dan konektivitas jaringan" + }, + "flow_title": "Printer Samsung SyncThru: {name}", + "step": { + "confirm": { + "data": { + "name": "Nama", + "url": "URL antarmuka web" + } + }, + "user": { + "data": { + "name": "Nama", + "url": "URL antarmuka web" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/nl.json b/homeassistant/components/syncthru/translations/nl.json index 799e19ea371..e569d1d6322 100644 --- a/homeassistant/components/syncthru/translations/nl.json +++ b/homeassistant/components/syncthru/translations/nl.json @@ -4,13 +4,15 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "invalid_url": "Ongeldige URL", "unknown_state": "Printerstatus onbekend, controleer URL en netwerkconnectiviteit" }, "flow_title": "Samsung SyncThru Printer: {name}", "step": { "confirm": { "data": { - "name": "Naam" + "name": "Naam", + "url": "Webinterface URL" } }, "user": { diff --git a/homeassistant/components/synology_dsm/translations/he.json b/homeassistant/components/synology_dsm/translations/he.json index 98b3a2214d7..8135fba13e0 100644 --- a/homeassistant/components/synology_dsm/translations/he.json +++ b/homeassistant/components/synology_dsm/translations/he.json @@ -1,4 +1,20 @@ { + "config": { + "step": { + "link": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + }, + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + }, "options": { "step": { "init": { diff --git a/homeassistant/components/synology_dsm/translations/hu.json b/homeassistant/components/synology_dsm/translations/hu.json index 29e520de432..c26fd349f06 100644 --- a/homeassistant/components/synology_dsm/translations/hu.json +++ b/homeassistant/components/synology_dsm/translations/hu.json @@ -1,23 +1,40 @@ { "config": { - "error": { - "cannot_connect": "Sikertelen csatlakoz\u00e1s" + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "Synology DSM {name} ({host})", "step": { + "2sa": { + "data": { + "otp_code": "K\u00f3d" + } + }, "link": { "data": { "password": "Jelsz\u00f3", "port": "Port", - "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - } + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" + }, + "title": "Synology DSM" }, "user": { "data": { "host": "Hoszt", "password": "Jelsz\u00f3", "port": "Port", - "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - } + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" + }, + "title": "Synology DSM" } } } diff --git a/homeassistant/components/synology_dsm/translations/id.json b/homeassistant/components/synology_dsm/translations/id.json new file mode 100644 index 00000000000..e614c2578d4 --- /dev/null +++ b/homeassistant/components/synology_dsm/translations/id.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "missing_data": "Data tidak tersedia: coba lagi nanti atau konfigurasikan lainnya", + "otp_failed": "Autentikasi dua langkah gagal, coba lagi dengan kode sandi baru", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Synology DSM {name} ({host})", + "step": { + "2sa": { + "data": { + "otp_code": "Kode" + }, + "title": "Synology DSM: autentikasi dua langkah" + }, + "link": { + "data": { + "password": "Kata Sandi", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL", + "username": "Nama Pengguna", + "verify_ssl": "Verifikasi sertifikat SSL" + }, + "description": "Ingin menyiapkan {name} ({host})?", + "title": "Synology DSM" + }, + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL", + "username": "Nama Pengguna", + "verify_ssl": "Verifikasi sertifikat SSL" + }, + "title": "Synology DSM" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Interval pemindaian dalam menit", + "timeout": "Tenggang waktu (detik)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/nl.json b/homeassistant/components/synology_dsm/translations/nl.json index d4932064a60..be8d7d45348 100644 --- a/homeassistant/components/synology_dsm/translations/nl.json +++ b/homeassistant/components/synology_dsm/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Host is al geconfigureerd." + "already_configured": "Apparaat is al geconfigureerd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -21,8 +21,8 @@ "link": { "data": { "password": "Wachtwoord", - "port": "Poort (optioneel)", - "ssl": "Gebruik SSL/TLS om verbinding te maken met uw NAS", + "port": "Poort", + "ssl": "Gebruik een SSL-certificaat", "username": "Gebruikersnaam", "verify_ssl": "Controleer het SSL-certificaat" }, @@ -33,8 +33,8 @@ "data": { "host": "Host", "password": "Wachtwoord", - "port": "Poort (optioneel)", - "ssl": "Gebruik SSL/TLS om verbinding te maken met uw NAS", + "port": "Poort", + "ssl": "Gebruik een SSL-certificaat", "username": "Gebruikersnaam", "verify_ssl": "Controleer het SSL-certificaat" }, diff --git a/homeassistant/components/synology_dsm/translations/zh-Hant.json b/homeassistant/components/synology_dsm/translations/zh-Hant.json index d5e78faf91c..19a231ccd60 100644 --- a/homeassistant/components/synology_dsm/translations/zh-Hant.json +++ b/homeassistant/components/synology_dsm/translations/zh-Hant.json @@ -7,7 +7,7 @@ "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "missing_data": "\u7f3a\u5c11\u8cc7\u6599\uff1a\u8acb\u7a0d\u5f8c\u91cd\u8a66\u6216\u4f7f\u7528\u5176\u4ed6\u8a2d\u5b9a", - "otp_failed": "\u5169\u6b65\u9a5f\u9a57\u8b49\u5931\u6557\uff0c\u8acb\u91cd\u65b0\u53d6\u5f97\u4ee3\u78bc\u5f8c\u91cd\u8a66", + "otp_failed": "\u96d9\u91cd\u8a8d\u8b49\u5931\u6557\uff0c\u8acb\u91cd\u65b0\u53d6\u5f97\u4ee3\u78bc\u5f8c\u91cd\u8a66", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "flow_title": "\u7fa4\u6689 DSM {name} ({host})", @@ -16,7 +16,7 @@ "data": { "otp_code": "\u4ee3\u78bc" }, - "title": "Synology DSM\uff1a\u96d9\u91cd\u9a57\u8b49" + "title": "Synology DSM\uff1a\u96d9\u91cd\u8a8d\u8b49" }, "link": { "data": { diff --git a/homeassistant/components/system_health/translations/id.json b/homeassistant/components/system_health/translations/id.json new file mode 100644 index 00000000000..309a6dd38d0 --- /dev/null +++ b/homeassistant/components/system_health/translations/id.json @@ -0,0 +1,3 @@ +{ + "title": "Kesehatan Sistem" +} \ No newline at end of file diff --git a/homeassistant/components/tado/translations/he.json b/homeassistant/components/tado/translations/he.json new file mode 100644 index 00000000000..ac90b3264ea --- /dev/null +++ b/homeassistant/components/tado/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tado/translations/hu.json b/homeassistant/components/tado/translations/hu.json index dee4ed9ee0f..fd8db27da5e 100644 --- a/homeassistant/components/tado/translations/hu.json +++ b/homeassistant/components/tado/translations/hu.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/tado/translations/id.json b/homeassistant/components/tado/translations/id.json new file mode 100644 index 00000000000..bdbfa19cb6d --- /dev/null +++ b/homeassistant/components/tado/translations/id.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "no_homes": "Tidak ada rumah yang ditautkan ke akun Tado ini.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Hubungkan ke akun Tado Anda" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "fallback": "Aktifkan mode alternatif." + }, + "title": "Sesuaikan opsi Tado." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tado/translations/ko.json b/homeassistant/components/tado/translations/ko.json index 8290e32c4c8..29603d8f6d2 100644 --- a/homeassistant/components/tado/translations/ko.json +++ b/homeassistant/components/tado/translations/ko.json @@ -25,7 +25,7 @@ "data": { "fallback": "\ub300\uccb4 \ubaa8\ub4dc\ub97c \ud65c\uc131\ud654\ud569\ub2c8\ub2e4." }, - "description": "\uc601\uc5ed\uc744 \uc218\ub3d9\uc73c\ub85c \uc804\ud658\ud558\uba74 \ub300\uccb4 \ubaa8\ub4dc\ub294 \ub2e4\uc74c \uc77c\uc815\uc744 \uc2a4\ub9c8\ud2b8 \uc77c\uc815\uc73c\ub85c \uc804\ud658\ud569\ub2c8\ub2e4.", + "description": "\ub300\uccb4 \ubaa8\ub4dc\ub294 \uc9c0\uc5ed\uc744 \uc218\ub3d9\uc73c\ub85c \uc870\uc815\ud55c \ud6c4 \ub2e4\uc74c \uc77c\uc815 \uc804\ud658\uc2dc \uc2a4\ub9c8\ud2b8 \uc77c\uc815\uc73c\ub85c \uc804\ud658\ub429\ub2c8\ub2e4.", "title": "Tado \uc635\uc158 \uc870\uc815\ud558\uae30" } } diff --git a/homeassistant/components/tado/translations/nl.json b/homeassistant/components/tado/translations/nl.json index 3cdadf0f54e..3b6d914b71c 100644 --- a/homeassistant/components/tado/translations/nl.json +++ b/homeassistant/components/tado/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "no_homes": "Er zijn geen huizen gekoppeld aan dit tado-account.", "unknown": "Onverwachte fout" diff --git a/homeassistant/components/tag/translations/hu.json b/homeassistant/components/tag/translations/hu.json new file mode 100644 index 00000000000..edea7ba32ef --- /dev/null +++ b/homeassistant/components/tag/translations/hu.json @@ -0,0 +1,3 @@ +{ + "title": "C\u00edmke" +} \ No newline at end of file diff --git a/homeassistant/components/tag/translations/id.json b/homeassistant/components/tag/translations/id.json new file mode 100644 index 00000000000..fdac700612d --- /dev/null +++ b/homeassistant/components/tag/translations/id.json @@ -0,0 +1,3 @@ +{ + "title": "Tag" +} \ No newline at end of file diff --git a/homeassistant/components/tasmota/translations/hu.json b/homeassistant/components/tasmota/translations/hu.json index c76efd0e898..4461f2a2b71 100644 --- a/homeassistant/components/tasmota/translations/hu.json +++ b/homeassistant/components/tasmota/translations/hu.json @@ -1,8 +1,15 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "step": { "config": { + "description": "Add meg a Tasmota konfigur\u00e1ci\u00f3t.", "title": "Tasmota" + }, + "confirm": { + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a Tasmota-t?" } } } diff --git a/homeassistant/components/tasmota/translations/id.json b/homeassistant/components/tasmota/translations/id.json new file mode 100644 index 00000000000..a11acf50390 --- /dev/null +++ b/homeassistant/components/tasmota/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "invalid_discovery_topic": "Prefiks topik penemuan tidak valid." + }, + "step": { + "config": { + "data": { + "discovery_prefix": "Prefiks topik penemuan" + }, + "description": "Masukkan konfigurasi Tasmota.", + "title": "Tasmota" + }, + "confirm": { + "description": "Ingin menyiapkan Tasmota?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tasmota/translations/ko.json b/homeassistant/components/tasmota/translations/ko.json index c6e52d209e7..45cac13f622 100644 --- a/homeassistant/components/tasmota/translations/ko.json +++ b/homeassistant/components/tasmota/translations/ko.json @@ -1,7 +1,22 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + }, + "error": { + "invalid_discovery_topic": "\uac80\uc0c9 \ud1a0\ud53d \uc811\ub450\uc0ac\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "config": { + "data": { + "discovery_prefix": "\uac80\uc0c9 \ud1a0\ud53d \uc811\ub450\uc0ac" + }, + "description": "Tasmota \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "Tasmota" + }, + "confirm": { + "description": "Tasmota\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/tellduslive/translations/hu.json b/homeassistant/components/tellduslive/translations/hu.json index 748189e6427..2e59375369d 100644 --- a/homeassistant/components/tellduslive/translations/hu.json +++ b/homeassistant/components/tellduslive/translations/hu.json @@ -1,16 +1,20 @@ { "config": { "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van", "authorize_url_fail": "Ismeretlen hiba t\u00f6rt\u00e9nt a hiteles\u00edt\u00e9si link gener\u00e1l\u00e1sa sor\u00e1n.", - "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s az \u00e9rv\u00e9nyes\u00edt\u00e9si url gener\u00e1l\u00e1sa sor\u00e1n.", - "unknown": "Ismeretlen hiba t\u00f6rt\u00e9nt" + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt", + "unknown_authorize_url_generation": "Ismeretlen hiba t\u00f6rt\u00e9nt a hiteles\u00edt\u00e9si link gener\u00e1l\u00e1sa sor\u00e1n." + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, "step": { "user": { "data": { "host": "Hoszt" }, - "description": "\u00dcres", "title": "V\u00e1lassz v\u00e9gpontot." } } diff --git a/homeassistant/components/tellduslive/translations/id.json b/homeassistant/components/tellduslive/translations/id.json new file mode 100644 index 00000000000..1a405e794fe --- /dev/null +++ b/homeassistant/components/tellduslive/translations/id.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi", + "authorize_url_fail": "Kesalahan tidak dikenal terjadi ketika menghasilkan URL otorisasi.", + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "unknown": "Kesalahan yang tidak diharapkan", + "unknown_authorize_url_generation": "Kesalahan tidak dikenal ketika menghasilkan URL otorisasi." + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "auth": { + "description": "Untuk menautkan akun TelldusLive Anda:\n 1. Klik tautan di bawah ini\n 2. Masuk ke Telldus Live\n 3. Otorisasi **{app_name}** (klik **Yes**).\n 4. Kembali ke sini dan klik **KIRIM**.\n\n[Akun Link TelldusLive] ({auth_url})", + "title": "Autentikasi ke TelldusLive" + }, + "user": { + "data": { + "host": "Host" + }, + "title": "Pilih titik akhir." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/translations/ko.json b/homeassistant/components/tellduslive/translations/ko.json index d29dd504844..6107245c088 100644 --- a/homeassistant/components/tellduslive/translations/ko.json +++ b/homeassistant/components/tellduslive/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "authorize_url_fail": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", + "authorize_url_fail": "\uc778\uc99d URL\uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4", "unknown_authorize_url_generation": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4." @@ -12,7 +12,7 @@ }, "step": { "auth": { - "description": "TelldusLive \uacc4\uc815\uc744 \uc5f0\uacb0\ud558\ub824\uba74:\n 1. \ud558\ub2e8\uc758 \ub9c1\ud06c\ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694\n 2. Telldus Live \uc5d0 \ub85c\uadf8\uc778 \ud558\uc138\uc694\n 3. Authorize **{app_name}** (**Yes** \ub97c \ud074\ub9ad\ud558\uc138\uc694).\n 4. \ub2e4\uc2dc \uc5ec\uae30\ub85c \ub3cc\uc544\uc640\uc11c **\ud655\uc778**\uc744 \ud074\ub9ad\ud558\uc138\uc694.\n\n [TelldusLive \uacc4\uc815 \uc5f0\uacb0\ud558\uae30]({auth_url})", + "description": "TelldusLive \uacc4\uc815\uc744 \uc5f0\uacb0\ud558\ub824\uba74:\n 1. \ud558\ub2e8\uc758 \ub9c1\ud06c\ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694\n 2. Telldus Live\uc5d0 \ub85c\uadf8\uc778\ud574\uc8fc\uc138\uc694\n 3. **{app_name}**\uc744(\ub97c) \uc778\uc99d\ud574\uc8fc\uc138\uc694 (**Yes**\ub97c \ud074\ub9ad\ud558\uc138\uc694).\n 4. \ub2e4\uc2dc \uc5ec\uae30\ub85c \ub3cc\uc544\uc640\uc11c **\ud655\uc778**\uc744 \ud074\ub9ad\ud558\uc138\uc694.\n\n [TelldusLive \uacc4\uc815 \uc5f0\uacb0\ud558\uae30]({auth_url})", "title": "TelldusLive \uc778\uc99d\ud558\uae30" }, "user": { diff --git a/homeassistant/components/tellduslive/translations/nl.json b/homeassistant/components/tellduslive/translations/nl.json index 4eb6d40a142..c34911553ab 100644 --- a/homeassistant/components/tellduslive/translations/nl.json +++ b/homeassistant/components/tellduslive/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Service is al geconfigureerd", "authorize_url_fail": "Onbekende fout bij het genereren van een autorisatie url.", "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", - "unknown": "Onbekende fout opgetreden", + "unknown": "Onverwachte fout", "unknown_authorize_url_generation": "Onbekende fout bij het genereren van een autorisatie-URL." }, "error": { diff --git a/homeassistant/components/tesla/translations/he.json b/homeassistant/components/tesla/translations/he.json new file mode 100644 index 00000000000..ac90b3264ea --- /dev/null +++ b/homeassistant/components/tesla/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/translations/hu.json b/homeassistant/components/tesla/translations/hu.json index c6553d4a595..a4622ce7efa 100644 --- a/homeassistant/components/tesla/translations/hu.json +++ b/homeassistant/components/tesla/translations/hu.json @@ -1,5 +1,14 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/tesla/translations/id.json b/homeassistant/components/tesla/translations/id.json new file mode 100644 index 00000000000..681504d0d42 --- /dev/null +++ b/homeassistant/components/tesla/translations/id.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "already_configured": "Akun sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Email" + }, + "description": "Masukkan informasi Anda.", + "title": "Tesla - Konfigurasi" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "enable_wake_on_start": "Paksa mobil bangun saat dinyalakan", + "scan_interval": "Interval pemindaian dalam detik" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/translations/nl.json b/homeassistant/components/tesla/translations/nl.json index f6289de6d9d..5655a641f96 100644 --- a/homeassistant/components/tesla/translations/nl.json +++ b/homeassistant/components/tesla/translations/nl.json @@ -13,7 +13,7 @@ "user": { "data": { "password": "Wachtwoord", - "username": "E-mailadres" + "username": "E-mail" }, "description": "Vul alstublieft uw gegevens in.", "title": "Tesla - Configuratie" diff --git a/homeassistant/components/tibber/translations/hu.json b/homeassistant/components/tibber/translations/hu.json index 08a622fd238..6ad59022845 100644 --- a/homeassistant/components/tibber/translations/hu.json +++ b/homeassistant/components/tibber/translations/hu.json @@ -1,14 +1,20 @@ { "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "invalid_access_token": "\u00c9rv\u00e9nytelen hozz\u00e1f\u00e9r\u00e9si token" + "invalid_access_token": "\u00c9rv\u00e9nytelen hozz\u00e1f\u00e9r\u00e9si token", + "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a Tibberhez val\u00f3 csatlakoz\u00e1skor" }, "step": { "user": { "data": { "access_token": "Hozz\u00e1f\u00e9r\u00e9si token" - } + }, + "description": "Add meg a hozz\u00e1f\u00e9r\u00e9si tokent a https://developer.tibber.com/settings/accesstoken c\u00edmr\u0151l", + "title": "Tibber" } } } diff --git a/homeassistant/components/tibber/translations/id.json b/homeassistant/components/tibber/translations/id.json new file mode 100644 index 00000000000..479cf83f8c7 --- /dev/null +++ b/homeassistant/components/tibber/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_access_token": "Token akses tidak valid", + "timeout": "Tenggang waktu terhubung ke Tibber habis" + }, + "step": { + "user": { + "data": { + "access_token": "Token Akses" + }, + "description": "Masukkan token akses Anda dari https://developer.tibber.com/settings/accesstoken", + "title": "Tibber" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tile/translations/hu.json b/homeassistant/components/tile/translations/hu.json new file mode 100644 index 00000000000..c4a6e63030c --- /dev/null +++ b/homeassistant/components/tile/translations/hu.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "E-mail" + }, + "title": "Tile konfigur\u00e1l\u00e1sa" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_inactive": "Inakt\u00edv Tile-ok megjelen\u00edt\u00e9se" + }, + "title": "Tile konfigur\u00e1l\u00e1sa" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tile/translations/id.json b/homeassistant/components/tile/translations/id.json new file mode 100644 index 00000000000..5b5c710594d --- /dev/null +++ b/homeassistant/components/tile/translations/id.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Email" + }, + "title": "Konfigurasi Tile" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_inactive": "Tampilkan Tile yang tidak aktif" + }, + "title": "Konfigurasi Tile" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/toon/translations/hu.json b/homeassistant/components/toon/translations/hu.json new file mode 100644 index 00000000000..cd832522870 --- /dev/null +++ b/homeassistant/components/toon/translations/hu.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "unknown_authorize_url_generation": "Ismeretlen hiba t\u00f6rt\u00e9nt a hiteles\u00edt\u00e9si link gener\u00e1l\u00e1sa sor\u00e1n." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/toon/translations/id.json b/homeassistant/components/toon/translations/id.json new file mode 100644 index 00000000000..6e9d4a76683 --- /dev/null +++ b/homeassistant/components/toon/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Perjanjian yang dipilih sudah dikonfigurasi.", + "authorize_url_fail": "Kesalahan tidak dikenal terjadi ketika menghasilkan URL otorisasi.", + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_agreements": "Akun ini tidak memiliki tampilan Toon.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})", + "unknown_authorize_url_generation": "Kesalahan tidak dikenal ketika menghasilkan URL otorisasi." + }, + "step": { + "agreement": { + "data": { + "agreement": "Persetujuan" + }, + "description": "Pilih alamat persetujuan yang ingin ditambahkan.", + "title": "Pilih persetujuan Anda" + }, + "pick_implementation": { + "title": "Pilih penyewa Anda untuk diautentikasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/toon/translations/ko.json b/homeassistant/components/toon/translations/ko.json index faed1fe74d7..e36adba2ffb 100644 --- a/homeassistant/components/toon/translations/ko.json +++ b/homeassistant/components/toon/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\uc120\ud0dd\ub41c \uc57d\uc815\uc740 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "authorize_url_fail": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", + "authorize_url_fail": "\uc778\uc99d URL\uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "no_agreements": "\uc774 \uacc4\uc815\uc5d0\ub294 Toon \ub514\uc2a4\ud50c\ub808\uc774\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.", diff --git a/homeassistant/components/totalconnect/translations/es.json b/homeassistant/components/totalconnect/translations/es.json index 090d9271dee..85797fa901e 100644 --- a/homeassistant/components/totalconnect/translations/es.json +++ b/homeassistant/components/totalconnect/translations/es.json @@ -9,6 +9,9 @@ }, "step": { "locations": { + "data": { + "location": "Localizaci\u00f3n" + }, "description": "Ingrese el c\u00f3digo de usuario para este usuario en esta ubicaci\u00f3n", "title": "C\u00f3digos de usuario de ubicaci\u00f3n" }, diff --git a/homeassistant/components/totalconnect/translations/he.json b/homeassistant/components/totalconnect/translations/he.json new file mode 100644 index 00000000000..ed07845a182 --- /dev/null +++ b/homeassistant/components/totalconnect/translations/he.json @@ -0,0 +1,17 @@ +{ + "config": { + "step": { + "locations": { + "data": { + "location": "\u05de\u05d9\u05e7\u05d5\u05dd" + } + }, + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/hu.json b/homeassistant/components/totalconnect/translations/hu.json index dee4ed9ee0f..6002f056635 100644 --- a/homeassistant/components/totalconnect/translations/hu.json +++ b/homeassistant/components/totalconnect/translations/hu.json @@ -1,6 +1,21 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, "step": { + "locations": { + "data": { + "location": "Elhelyezked\u00e9s" + } + }, + "reauth_confirm": { + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" + }, "user": { "data": { "password": "Jelsz\u00f3", diff --git a/homeassistant/components/totalconnect/translations/id.json b/homeassistant/components/totalconnect/translations/id.json new file mode 100644 index 00000000000..c1bdf664994 --- /dev/null +++ b/homeassistant/components/totalconnect/translations/id.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid", + "usercode": "Kode pengguna tidak valid untuk pengguna ini di lokasi ini" + }, + "step": { + "locations": { + "data": { + "location": "Lokasi" + }, + "description": "Masukkan kode pengguna untuk pengguna ini di lokasi ini", + "title": "Lokasi Kode Pengguna" + }, + "reauth_confirm": { + "description": "Total Connect perlu mengautentikasi ulang akun Anda", + "title": "Autentikasi Ulang Integrasi" + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Total Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/ko.json b/homeassistant/components/totalconnect/translations/ko.json index c074472b8f4..354522154b5 100644 --- a/homeassistant/components/totalconnect/translations/ko.json +++ b/homeassistant/components/totalconnect/translations/ko.json @@ -1,12 +1,25 @@ { "config": { "abort": { - "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "usercode": "\uc774 \uc704\uce58\uc758 \ud574\ub2f9 \uc0ac\uc6a9\uc790\uc5d0 \ub300\ud55c \uc0ac\uc6a9\uc790 \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { + "locations": { + "data": { + "location": "\uc704\uce58" + }, + "description": "\uc774 \uc704\uce58\uc758 \ud574\ub2f9 \uc0ac\uc6a9\uc790\uc5d0 \ub300\ud55c \uc0ac\uc6a9\uc790 \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", + "title": "\uc704\uce58 \uc0ac\uc6a9\uc790 \ucf54\ub4dc" + }, + "reauth_confirm": { + "description": "Total Connect\ub294 \uacc4\uc815\uc744 \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4", + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" + }, "user": { "data": { "password": "\ube44\ubc00\ubc88\ud638", diff --git a/homeassistant/components/totalconnect/translations/nl.json b/homeassistant/components/totalconnect/translations/nl.json index 94d8e3ac01e..de20d40bee6 100644 --- a/homeassistant/components/totalconnect/translations/nl.json +++ b/homeassistant/components/totalconnect/translations/nl.json @@ -1,19 +1,23 @@ { "config": { "abort": { - "already_configured": "Account al geconfigureerd", + "already_configured": "Account is al geconfigureerd", "reauth_successful": "Herauthenticatie was succesvol" }, "error": { - "invalid_auth": "Ongeldige authenticatie" + "invalid_auth": "Ongeldige authenticatie", + "usercode": "Gebruikerscode niet geldig voor deze gebruiker op deze locatie" }, "step": { "locations": { "data": { "location": "Locatie" - } + }, + "description": "Voer de gebruikerscode voor deze gebruiker op deze locatie in", + "title": "Locatie gebruikerscodes" }, "reauth_confirm": { + "description": "Total Connect moet uw account opnieuw verifi\u00ebren", "title": "Verifieer de integratie opnieuw" }, "user": { diff --git a/homeassistant/components/tplink/translations/hu.json b/homeassistant/components/tplink/translations/hu.json new file mode 100644 index 00000000000..ab799e90c74 --- /dev/null +++ b/homeassistant/components/tplink/translations/hu.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/id.json b/homeassistant/components/tplink/translations/id.json new file mode 100644 index 00000000000..66d510de4ed --- /dev/null +++ b/homeassistant/components/tplink/translations/id.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "confirm": { + "description": "Ingin menyiapkan perangkat cerdas TP-Link?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/ko.json b/homeassistant/components/tplink/translations/ko.json index e1ff7eff372..a1bfb59ca07 100644 --- a/homeassistant/components/tplink/translations/ko.json +++ b/homeassistant/components/tplink/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/tplink/translations/nl.json b/homeassistant/components/tplink/translations/nl.json index f6a8dbbe02a..362645d9f19 100644 --- a/homeassistant/components/tplink/translations/nl.json +++ b/homeassistant/components/tplink/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Geen TP-Link apparaten gevonden op het netwerk.", - "single_instance_allowed": "Slechts \u00e9\u00e9n configuratie is nodig." + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "step": { "confirm": { diff --git a/homeassistant/components/traccar/translations/hu.json b/homeassistant/components/traccar/translations/hu.json index a14c446e673..c4fc027d059 100644 --- a/homeassistant/components/traccar/translations/hu.json +++ b/homeassistant/components/traccar/translations/hu.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "webhook_not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." + }, "create_entry": { - "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, be kell \u00e1ll\u00edtania a webhook funkci\u00f3t a Traccar-ban. \n\n Haszn\u00e1lja a k\u00f6vetkez\u0151 URL-t: \" {webhook_url} \" \n\n Tov\u00e1bbi r\u00e9szletek\u00e9rt l\u00e1sd a [dokument\u00e1ci\u00f3t] ( {docs_url} )." + "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, be kell \u00e1ll\u00edtania a webhook funkci\u00f3t a Traccar-ban. \n\n Haszn\u00e1lja a k\u00f6vetkez\u0151 URL-t: `{webhook_url}`\n\n Tov\u00e1bbi r\u00e9szletek\u00e9rt l\u00e1sd a [dokument\u00e1ci\u00f3t]({docs_url})." } } } \ No newline at end of file diff --git a/homeassistant/components/traccar/translations/id.json b/homeassistant/components/traccar/translations/id.json new file mode 100644 index 00000000000..573b73570c2 --- /dev/null +++ b/homeassistant/components/traccar/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." + }, + "create_entry": { + "default": "Untuk mengirim event ke Home Assistant, Anda harus menyiapkan fitur webhook di Traccar.\n\nGunakan URL berikut: {webhook_url}`\n\nBaca [dokumentasi]({docs_url}) untuk detail lebih lanjut." + }, + "step": { + "user": { + "description": "Yakin ingin menyiapkan Traccar?", + "title": "Siapkan Traccar" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/traccar/translations/ko.json b/homeassistant/components/traccar/translations/ko.json index 04e13a9aa6f..2af5079ae2d 100644 --- a/homeassistant/components/traccar/translations/ko.json +++ b/homeassistant/components/traccar/translations/ko.json @@ -1,15 +1,15 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Traccar \uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c\uc758 URL \uc8fc\uc18c\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4: `{webhook_url}`\n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Traccar\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c\uc758 URL \uc8fc\uc18c\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4: `{webhook_url}`\n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { - "description": "Traccar \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Traccar\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Traccar \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/tradfri/translations/hu.json b/homeassistant/components/tradfri/translations/hu.json index 8be065fe797..3bc4ec90e77 100644 --- a/homeassistant/components/tradfri/translations/hu.json +++ b/homeassistant/components/tradfri/translations/hu.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "A bridge m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van." }, "error": { - "cannot_connect": "Nem siker\u00fclt csatlakozni a gatewayhez.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_key": "Nem siker\u00fclt regisztr\u00e1lni a megadott kulcs seg\u00edts\u00e9g\u00e9vel. Ha ez t\u00f6bbsz\u00f6r megt\u00f6rt\u00e9nik, pr\u00f3b\u00e1lja meg \u00fajraind\u00edtani a gatewayt.", "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a k\u00f3d \u00e9rv\u00e9nyes\u00edt\u00e9se sor\u00e1n." }, diff --git a/homeassistant/components/tradfri/translations/id.json b/homeassistant/components/tradfri/translations/id.json index 0671b162e1c..8ff5fe257eb 100644 --- a/homeassistant/components/tradfri/translations/id.json +++ b/homeassistant/components/tradfri/translations/id.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Bridge sudah dikonfigurasi" + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung" }, "error": { - "cannot_connect": "Tidak dapat terhubung ke gateway.", + "cannot_connect": "Gagal terhubung", "invalid_key": "Gagal mendaftar dengan kunci yang disediakan. Jika ini terus terjadi, coba mulai ulang gateway.", "timeout": "Waktu tunggu memvalidasi kode telah habis." }, @@ -12,7 +13,7 @@ "auth": { "data": { "host": "Host", - "security_code": "Kode keamanan" + "security_code": "Kode Keamanan" }, "description": "Anda dapat menemukan kode keamanan di belakang gateway Anda.", "title": "Masukkan kode keamanan" diff --git a/homeassistant/components/tradfri/translations/ko.json b/homeassistant/components/tradfri/translations/ko.json index 067a10c6490..307ba39ceaf 100644 --- a/homeassistant/components/tradfri/translations/ko.json +++ b/homeassistant/components/tradfri/translations/ko.json @@ -16,7 +16,7 @@ "security_code": "\ubcf4\uc548 \ucf54\ub4dc" }, "description": "\uac8c\uc774\ud2b8\uc6e8\uc774 \ub4b7\uba74\uc5d0\uc11c \ubcf4\uc548 \ucf54\ub4dc\ub97c \ucc3e\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "title": "\ubcf4\uc548 \ucf54\ub4dc \uc785\ub825\ud558\uae30" + "title": "\ubcf4\uc548 \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" } } } diff --git a/homeassistant/components/tradfri/translations/nl.json b/homeassistant/components/tradfri/translations/nl.json index 1d0453704d0..874b6cff81c 100644 --- a/homeassistant/components/tradfri/translations/nl.json +++ b/homeassistant/components/tradfri/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Bridge is al geconfigureerd.", + "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "Bridge configuratie is al in volle gang." }, "error": { - "cannot_connect": "Kan geen verbinding maken met bridge", + "cannot_connect": "Kan geen verbinding maken", "invalid_key": "Mislukt om te registreren met de meegeleverde sleutel. Als dit blijft gebeuren, probeer dan de gateway opnieuw op te starten.", "timeout": "Time-out bij validatie van code" }, diff --git a/homeassistant/components/transmission/translations/he.json b/homeassistant/components/transmission/translations/he.json new file mode 100644 index 00000000000..6f4191da70d --- /dev/null +++ b/homeassistant/components/transmission/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/transmission/translations/hu.json b/homeassistant/components/transmission/translations/hu.json index f2fd2ca79e5..22d4e18df5e 100644 --- a/homeassistant/components/transmission/translations/hu.json +++ b/homeassistant/components/transmission/translations/hu.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { - "cannot_connect": "Nem lehet csatlakozni az \u00e1llom\u00e1shoz", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "name_exists": "A n\u00e9v m\u00e1r l\u00e9tezik" }, "step": { @@ -21,6 +25,8 @@ "step": { "init": { "data": { + "limit": "Limit", + "order": "Sorrend", "scan_interval": "Friss\u00edt\u00e9si gyakoris\u00e1g" } } diff --git a/homeassistant/components/transmission/translations/id.json b/homeassistant/components/transmission/translations/id.json new file mode 100644 index 00000000000..a96524f5165 --- /dev/null +++ b/homeassistant/components/transmission/translations/id.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "name_exists": "Nama sudah ada" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + }, + "title": "Siapkan Klien Transmission" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "limit": "Batas", + "order": "Urutan", + "scan_interval": "Frekuensi pembaruan" + }, + "title": "Konfigurasikan opsi untuk Transmission" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/transmission/translations/ko.json b/homeassistant/components/transmission/translations/ko.json index 002e374e54d..898a3710350 100644 --- a/homeassistant/components/transmission/translations/ko.json +++ b/homeassistant/components/transmission/translations/ko.json @@ -29,7 +29,7 @@ "order": "\uc21c\uc11c", "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \ube48\ub3c4" }, - "title": "Transmission \uc635\uc158 \uc124\uc815\ud558\uae30" + "title": "Transmission\uc5d0 \ub300\ud55c \uc635\uc158 \uad6c\uc131\ud558\uae30" } } } diff --git a/homeassistant/components/tuya/translations/de.json b/homeassistant/components/tuya/translations/de.json index 67a61f81a1c..cf5d3dfffe8 100644 --- a/homeassistant/components/tuya/translations/de.json +++ b/homeassistant/components/tuya/translations/de.json @@ -12,6 +12,7 @@ "step": { "user": { "data": { + "country_code": "L\u00e4ndercode Ihres Kontos (z. B. 1 f\u00fcr USA oder 86 f\u00fcr China)", "password": "Passwort", "username": "Benutzername" }, diff --git a/homeassistant/components/tuya/translations/hu.json b/homeassistant/components/tuya/translations/hu.json index b128be67087..b45148f80b8 100644 --- a/homeassistant/components/tuya/translations/hu.json +++ b/homeassistant/components/tuya/translations/hu.json @@ -2,11 +2,11 @@ "config": { "abort": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "invalid_auth": "\u00c9rv\u00e9nytelen autentik\u00e1ci\u00f3", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "error": { - "invalid_auth": "\u00c9rv\u00e9nytelen autentik\u00e1ci\u00f3" + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, "flow_title": "Tuya konfigur\u00e1ci\u00f3", "step": { @@ -27,7 +27,7 @@ "cannot_connect": "A kapcsol\u00f3d\u00e1s nem siker\u00fclt" }, "error": { - "dev_multi_type": "A konfigur\u00e1land\u00f3 eszk\u00f6z\u00f6knek azonos t\u00edpus\u00faaknak kell lennie", + "dev_multi_type": "T\u00f6bb kiv\u00e1lasztott konfigur\u00e1land\u00f3 eszk\u00f6znek azonos t\u00edpus\u00fanak kell lennie", "dev_not_config": "Ez az eszk\u00f6zt\u00edpus nem konfigur\u00e1lhat\u00f3", "dev_not_found": "Eszk\u00f6z nem tal\u00e1lhat\u00f3" }, @@ -45,18 +45,18 @@ "tuya_max_coltemp": "Az eszk\u00f6z \u00e1ltal megadott maxim\u00e1lis sz\u00ednh\u0151m\u00e9rs\u00e9klet", "unit_of_measurement": "Az eszk\u00f6z \u00e1ltal haszn\u00e1lt h\u0151m\u00e9rs\u00e9kleti egys\u00e9g" }, - "description": "Konfigur\u00e1lja a(z) {device_type} eszk\u00f6zt \" {device_name} {device_type} \" megjelen\u00edtett inform\u00e1ci\u00f3inak be\u00e1ll\u00edt\u00e1s\u00e1hoz", - "title": "Konfigur\u00e1lja a Tuya eszk\u00f6zt" + "description": "Konfigur\u00e1l\u00e1si lehet\u0151s\u00e9gek a(z) {device_type} t\u00edpus\u00fa `{device_name}` eszk\u00f6z megjelen\u00edtett inform\u00e1ci\u00f3inak be\u00e1ll\u00edt\u00e1s\u00e1hoz", + "title": "Tuya eszk\u00f6z konfigur\u00e1l\u00e1sa" }, "init": { "data": { "discovery_interval": "Felfedez\u0151 eszk\u00f6z lek\u00e9rdez\u00e9si intervalluma m\u00e1sodpercben", - "list_devices": "V\u00e1lassza ki a konfigur\u00e1lni k\u00edv\u00e1nt eszk\u00f6z\u00f6ket, vagy hagyja \u00fcresen a konfigur\u00e1ci\u00f3 ment\u00e9s\u00e9hez", - "query_device": "V\u00e1lassza ki azt az eszk\u00f6zt, amely a lek\u00e9rdez\u00e9si m\u00f3dszert haszn\u00e1lja a gyorsabb \u00e1llapotfriss\u00edt\u00e9shez", + "list_devices": "V\u00e1laszd ki a konfigur\u00e1lni k\u00edv\u00e1nt eszk\u00f6z\u00f6ket, vagy hagyd \u00fcresen a konfigur\u00e1ci\u00f3 ment\u00e9s\u00e9hez", + "query_device": "V\u00e1laszd ki azt az eszk\u00f6zt, amely a lek\u00e9rdez\u00e9si m\u00f3dszert haszn\u00e1lja a gyorsabb \u00e1llapotfriss\u00edt\u00e9shez", "query_interval": "Eszk\u00f6z lek\u00e9rdez\u00e9si id\u0151k\u00f6ze m\u00e1sodpercben" }, - "description": "Ne \u00e1ll\u00edtsa t\u00fal alacsonyra a lek\u00e9rdez\u00e9si intervallum \u00e9rt\u00e9keit, k\u00fcl\u00f6nben a h\u00edv\u00e1sok nem fognak hiba\u00fczenetet gener\u00e1lni a napl\u00f3ban", - "title": "Konfigur\u00e1lja a Tuya be\u00e1ll\u00edt\u00e1sokat" + "description": "Ne \u00e1ll\u00edtsd t\u00fal alacsonyra a lek\u00e9rdez\u00e9si intervallum \u00e9rt\u00e9keit, k\u00fcl\u00f6nben a h\u00edv\u00e1sok nem fognak hiba\u00fczenetet gener\u00e1lni a napl\u00f3ban", + "title": "Tuya be\u00e1ll\u00edt\u00e1sok konfigur\u00e1l\u00e1sa" } } } diff --git a/homeassistant/components/tuya/translations/id.json b/homeassistant/components/tuya/translations/id.json new file mode 100644 index 00000000000..bb338e12752 --- /dev/null +++ b/homeassistant/components/tuya/translations/id.json @@ -0,0 +1,65 @@ +{ + "config": { + "abort": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "flow_title": "Konfigurasi Tuya", + "step": { + "user": { + "data": { + "country_code": "Kode negara akun Anda (mis., 1 untuk AS atau 86 untuk China)", + "password": "Kata Sandi", + "platform": "Aplikasi tempat akun Anda mendaftar", + "username": "Nama Pengguna" + }, + "description": "Masukkan kredensial Tuya Anda.", + "title": "Tuya" + } + } + }, + "options": { + "abort": { + "cannot_connect": "Gagal terhubung" + }, + "error": { + "dev_multi_type": "Untuk konfigurasi sekaligus, beberapa perangkat yang dipilih harus berjenis sama", + "dev_not_config": "Jenis perangkat tidak dapat dikonfigurasi", + "dev_not_found": "Perangkat tidak ditemukan" + }, + "step": { + "device": { + "data": { + "brightness_range_mode": "Rentang kecerahan yang digunakan oleh perangkat", + "curr_temp_divider": "Pembagi nilai suhu saat ini (0 = gunakan bawaan)", + "max_kelvin": "Suhu warna maksimal yang didukung dalam Kelvin", + "max_temp": "Suhu target maksimal (gunakan min dan maks = 0 untuk bawaan)", + "min_kelvin": "Suhu warna minimal yang didukung dalam Kelvin", + "min_temp": "Suhu target minimal (gunakan min dan maks = 0 untuk bawaan)", + "set_temp_divided": "Gunakan nilai suhu terbagi untuk mengirimkan perintah mengatur suhu", + "support_color": "Paksa dukungan warna", + "temp_divider": "Pembagi nilai suhu (0 = gunakan bawaan)", + "temp_step_override": "Langkah Suhu Target", + "tuya_max_coltemp": "Suhu warna maksimal yang dilaporkan oleh perangkat", + "unit_of_measurement": "Satuan suhu yang digunakan oleh perangkat" + }, + "description": "Konfigurasikan opsi untuk menyesuaikan informasi yang ditampilkan untuk perangkat {device_type} `{device_name}`", + "title": "Konfigurasi Perangkat Tuya" + }, + "init": { + "data": { + "discovery_interval": "Interval polling penemuan perangkat dalam detik", + "list_devices": "Pilih perangkat yang akan dikonfigurasi atau biarkan kosong untuk menyimpan konfigurasi", + "query_device": "Pilih perangkat yang akan menggunakan metode kueri untuk pembaruan status lebih cepat", + "query_interval": "Interval polling perangkat kueri dalam detik" + }, + "description": "Jangan atur nilai interval polling terlalu rendah karena panggilan akan gagal menghasilkan pesan kesalahan dalam log", + "title": "Konfigurasikan Opsi Tuya" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/ko.json b/homeassistant/components/tuya/translations/ko.json index 81dd2689b0c..3e3fbc68bee 100644 --- a/homeassistant/components/tuya/translations/ko.json +++ b/homeassistant/components/tuya/translations/ko.json @@ -3,7 +3,7 @@ "abort": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" @@ -25,6 +25,41 @@ "options": { "abort": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "dev_multi_type": "\uc120\ud0dd\ud55c \uc5ec\ub7ec \uae30\uae30\ub97c \uad6c\uc131\ud558\ub824\uba74 \uc720\ud615\uc774 \ub3d9\uc77c\ud574\uc57c \ud569\ub2c8\ub2e4", + "dev_not_config": "\uae30\uae30 \uc720\ud615\uc744 \uad6c\uc131\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "dev_not_found": "\uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" + }, + "step": { + "device": { + "data": { + "brightness_range_mode": "\uae30\uae30\uc5d0\uc11c \uc0ac\uc6a9\ud558\ub294 \ubc1d\uae30 \ubc94\uc704", + "curr_temp_divider": "\ud604\uc7ac \uc628\ub3c4 \uac12 \ubd84\ud560 (0 = \uae30\ubcf8\uac12 \uc0ac\uc6a9)", + "max_kelvin": "\uce98\ube48 \ub2e8\uc704\uc758 \ucd5c\ub300 \uc0c9\uc628\ub3c4", + "max_temp": "\ucd5c\ub300 \ubaa9\ud45c \uc628\ub3c4 (\uae30\ubcf8\uac12\uc758 \uacbd\uc6b0 \ucd5c\uc18c\uac12 \ubc0f \ucd5c\ub300\uac12 = 0)", + "min_kelvin": "\uce98\ube48 \ub2e8\uc704\uc758 \ucd5c\uc18c \uc0c9\uc628\ub3c4", + "min_temp": "\ucd5c\uc18c \ubaa9\ud45c \uc628\ub3c4 (\uae30\ubcf8\uac12\uc758 \uacbd\uc6b0 \ucd5c\uc18c\uac12 \ubc0f \ucd5c\ub300\uac12 = 0)", + "set_temp_divided": "\uc124\uc815 \uc628\ub3c4 \uba85\ub839\uc5d0 \ubd84\ud560\ub41c \uc628\ub3c4 \uac12 \uc0ac\uc6a9\ud558\uae30", + "support_color": "\uc0c9\uc0c1 \uc9c0\uc6d0 \uac15\uc81c \uc801\uc6a9\ud558\uae30", + "temp_divider": "\uc628\ub3c4 \uac12 \ubd84\ud560 (0 = \uae30\ubcf8\uac12 \uc0ac\uc6a9)", + "temp_step_override": "\ud76c\ub9dd \uc628\ub3c4 \ub2e8\uacc4", + "tuya_max_coltemp": "\uae30\uae30\uc5d0\uc11c \ubcf4\uace0\ud55c \ucd5c\ub300 \uc0c9\uc628\ub3c4", + "unit_of_measurement": "\uae30\uae30\uc5d0\uc11c \uc0ac\uc6a9\ud558\ub294 \uc628\ub3c4 \ub2e8\uc704" + }, + "description": "{device_type} `{device_name}` \uae30\uae30\uc5d0 \ub300\ud574 \ud45c\uc2dc\ub418\ub294 \uc815\ubcf4\ub97c \uc870\uc815\ud558\ub294 \uc635\uc158 \uad6c\uc131\ud558\uae30", + "title": "Tuya \uae30\uae30 \uad6c\uc131\ud558\uae30" + }, + "init": { + "data": { + "discovery_interval": "\uae30\uae30 \uac80\uc0c9 \ud3f4\ub9c1 \uac04\uaca9 (\ucd08)", + "list_devices": "\uad6c\uc131\uc744 \uc800\uc7a5\ud558\ub824\uba74 \uad6c\uc131\ud560 \uae30\uae30\ub97c \uc120\ud0dd\ud558\uac70\ub098 \ube44\uc6cc \ub450\uc138\uc694", + "query_device": "\ube60\ub978 \uc0c1\ud0dc \uc5c5\ub370\uc774\ud2b8\ub97c \uc704\ud574 \ucffc\ub9ac \ubc29\ubc95\uc744 \uc0ac\uc6a9\ud560 \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694", + "query_interval": "\uae30\uae30 \ucffc\ub9ac \ud3f4\ub9c1 \uac04\uaca9 (\ucd08)" + }, + "description": "\ud3f4\ub9c1 \uac04\uaca9 \uac12\uc744 \ub108\ubb34 \ub0ae\uac8c \uc124\uc815\ud558\uc9c0 \ub9d0\uc544 \uc8fc\uc138\uc694. \uadf8\ub807\uc9c0 \uc54a\uc73c\uba74 \ud638\ucd9c\uc5d0 \uc2e4\ud328\ud558\uace0 \ub85c\uadf8\uc5d0 \uc624\ub958 \uba54\uc2dc\uc9c0\uac00 \uc0dd\uc131\ub429\ub2c8\ub2e4.", + "title": "Tuya \uc635\uc158 \uad6c\uc131\ud558\uae30" + } } } } \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/nl.json b/homeassistant/components/tuya/translations/nl.json index 46a0228843b..55fb320c17e 100644 --- a/homeassistant/components/tuya/translations/nl.json +++ b/homeassistant/components/tuya/translations/nl.json @@ -31,6 +31,9 @@ }, "step": { "device": { + "data": { + "temp_step_override": "Doeltemperatuur stap" + }, "title": "Configureer Tuya Apparaat" }, "init": { diff --git a/homeassistant/components/tuya/translations/pl.json b/homeassistant/components/tuya/translations/pl.json index 742ac600c62..92ced00e733 100644 --- a/homeassistant/components/tuya/translations/pl.json +++ b/homeassistant/components/tuya/translations/pl.json @@ -43,6 +43,7 @@ "set_temp_divided": "U\u017cyj podzielonej warto\u015bci temperatury dla polecenia ustawienia temperatury", "support_color": "Wymu\u015b obs\u0142ug\u0119 kolor\u00f3w", "temp_divider": "Dzielnik warto\u015bci temperatury (0 = u\u017cyj warto\u015bci domy\u015blnej)", + "temp_step_override": "Krok docelowej temperatury", "tuya_max_coltemp": "Maksymalna temperatura barwy raportowana przez urz\u0105dzenie", "unit_of_measurement": "Jednostka temperatury u\u017cywana przez urz\u0105dzenie" }, diff --git a/homeassistant/components/twentemilieu/translations/hu.json b/homeassistant/components/twentemilieu/translations/hu.json index 8f88f82f2e5..df83a29ec22 100644 --- a/homeassistant/components/twentemilieu/translations/hu.json +++ b/homeassistant/components/twentemilieu/translations/hu.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/twentemilieu/translations/id.json b/homeassistant/components/twentemilieu/translations/id.json new file mode 100644 index 00000000000..38746dfd12f --- /dev/null +++ b/homeassistant/components/twentemilieu/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_address": "Alamat tidak ditemukan di area layanan Twente Milieu." + }, + "step": { + "user": { + "data": { + "house_letter": "Abjad rumah/tambahan", + "house_number": "Nomor rumah", + "post_code": "Kode pos" + }, + "description": "Siapkan Twente Milieu untuk memberikan informasi pengumpulan sampah di alamat Anda.", + "title": "Twente Milieu" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twentemilieu/translations/ko.json b/homeassistant/components/twentemilieu/translations/ko.json index e6c19d40d06..a27df565f1b 100644 --- a/homeassistant/components/twentemilieu/translations/ko.json +++ b/homeassistant/components/twentemilieu/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", diff --git a/homeassistant/components/twilio/translations/de.json b/homeassistant/components/twilio/translations/de.json index 61df22c10f8..d9f071e8ff7 100644 --- a/homeassistant/components/twilio/translations/de.json +++ b/homeassistant/components/twilio/translations/de.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "M\u00f6chtest du mit der Einrichtung beginnen?", + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?", "title": "Twilio-Webhook einrichten" } } diff --git a/homeassistant/components/twilio/translations/hu.json b/homeassistant/components/twilio/translations/hu.json index 913e3d2a7a2..cd60890dab3 100644 --- a/homeassistant/components/twilio/translations/hu.json +++ b/homeassistant/components/twilio/translations/hu.json @@ -1,11 +1,15 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "webhook_not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." + }, "create_entry": { - "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, be kell \u00e1ll\u00edtania a [Webhooks Twilio-val] ( {twilio_url} ) alkalmaz\u00e1st. \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: \" {webhook_url} \"\n - M\u00f3dszer: POST\n - Tartalom t\u00edpusa: application/x-www-form-urlencoded \n\n L\u00e1sd [a dokument\u00e1ci\u00f3] ( {docs_url} ), hogyan konfigur\u00e1lhatja az automatiz\u00e1l\u00e1sokat a bej\u00f6v\u0151 adatok kezel\u00e9s\u00e9re." + "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, be kell \u00e1ll\u00edtania a [Webhooks Twilio-val]({twilio_url}) alkalmaz\u00e1st. \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: `{webhook_url}`\n - M\u00f3dszer: POST\n - Tartalom t\u00edpusa: application/x-www-form-urlencoded \n\n L\u00e1sd [a dokument\u00e1ci\u00f3]({docs_url}), hogyan konfigur\u00e1lhatja az automatiz\u00e1l\u00e1sokat a bej\u00f6v\u0151 adatok kezel\u00e9s\u00e9re." }, "step": { "user": { - "description": "Biztosan be szeretn\u00e9d \u00e1ll\u00edtani a Twilio-t?", + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?", "title": "A Twilio Webhook be\u00e1ll\u00edt\u00e1sa" } } diff --git a/homeassistant/components/twilio/translations/id.json b/homeassistant/components/twilio/translations/id.json new file mode 100644 index 00000000000..be16b1d4802 --- /dev/null +++ b/homeassistant/components/twilio/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." + }, + "create_entry": { + "default": "Untuk mengirim event ke Home Assistant, Anda harus menyiapkan [Webhooks dengan Twilio]({twilio_url}).\n\nIsikan info berikut:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content-Type: application/x-www-form-urlencoded\n\nBaca [dokumentasi]({docs_url}) tentang cara mengonfigurasi otomasi untuk menangani data masuk." + }, + "step": { + "user": { + "description": "Ingin memulai penyiapan?", + "title": "Siapkan Twilio Webhook" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/translations/ko.json b/homeassistant/components/twilio/translations/ko.json index 72165dfb798..4feeacf1a20 100644 --- a/homeassistant/components/twilio/translations/ko.json +++ b/homeassistant/components/twilio/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Twilio \uc6f9 \ud6c5]({twilio_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/x-www-form-urlencoded\n \nHome Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Twilio \uc6f9 \ud6c5]({twilio_url})\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/x-www-form-urlencoded\n \nHome Assistant\ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/twilio/translations/nl.json b/homeassistant/components/twilio/translations/nl.json index 55db4ef5e48..0d5d33a727e 100644 --- a/homeassistant/components/twilio/translations/nl.json +++ b/homeassistant/components/twilio/translations/nl.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "Weet u zeker dat u Twilio wilt instellen?", + "description": "Wil je beginnen met instellen?", "title": "Stel de Twilio Webhook in" } } diff --git a/homeassistant/components/twinkly/translations/hu.json b/homeassistant/components/twinkly/translations/hu.json new file mode 100644 index 00000000000..190d7e469d5 --- /dev/null +++ b/homeassistant/components/twinkly/translations/hu.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "device_exists": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "user": { + "title": "Twinkly" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twinkly/translations/id.json b/homeassistant/components/twinkly/translations/id.json new file mode 100644 index 00000000000..b4a5ba6cbfa --- /dev/null +++ b/homeassistant/components/twinkly/translations/id.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "device_exists": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "host": "Host (atau alamat IP) perangkat twinkly Anda" + }, + "description": "Siapkan string led Twinkly Anda", + "title": "Twinkly" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twinkly/translations/ko.json b/homeassistant/components/twinkly/translations/ko.json index 207037cba60..b3b23991e3d 100644 --- a/homeassistant/components/twinkly/translations/ko.json +++ b/homeassistant/components/twinkly/translations/ko.json @@ -5,6 +5,15 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "Twinkly \uae30\uae30\uc758 \ud638\uc2a4\ud2b8 (\ub610\ub294 IP \uc8fc\uc18c)" + }, + "description": "Twinkly LED \uc904 \uc870\uba85 \uc124\uc815\ud558\uae30", + "title": "Twinkly" + } } } } \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/he.json b/homeassistant/components/unifi/translations/he.json new file mode 100644 index 00000000000..3007c0e968c --- /dev/null +++ b/homeassistant/components/unifi/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/hu.json b/homeassistant/components/unifi/translations/hu.json index 2a7a43d42e9..4602193850f 100644 --- a/homeassistant/components/unifi/translations/hu.json +++ b/homeassistant/components/unifi/translations/hu.json @@ -1,9 +1,14 @@ { "config": { + "abort": { + "configuration_updated": "A konfigur\u00e1ci\u00f3 friss\u00edtve.", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, "error": { "faulty_credentials": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "service_unavailable": "Sikertelen csatlakoz\u00e1s" }, + "flow_title": "UniFi Network {site} ({host})", "step": { "user": { "data": { @@ -12,7 +17,7 @@ "port": "Port", "site": "Site azonos\u00edt\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v", - "verify_ssl": "Vez\u00e9rl\u0151 megfelel\u0151 tan\u00fas\u00edtv\u00e1nnyal" + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" }, "title": "UniFi vez\u00e9rl\u0151 be\u00e1ll\u00edt\u00e1sa" } @@ -23,6 +28,9 @@ "client_control": { "description": "Konfigur\u00e1lja a klienseket\n\n Hozzon l\u00e9tre kapcsol\u00f3kat azokhoz a sorsz\u00e1mokhoz, amelyeknek vez\u00e9relni k\u00edv\u00e1nja a h\u00e1l\u00f3zati hozz\u00e1f\u00e9r\u00e9st." }, + "simple_options": { + "description": "UniFi integr\u00e1ci\u00f3 konfigur\u00e1l\u00e1sa" + }, "statistics_sensors": { "data": { "allow_bandwidth_sensors": "S\u00e1vsz\u00e9less\u00e9g-haszn\u00e1lati \u00e9rz\u00e9kel\u0151k l\u00e9trehoz\u00e1sa a h\u00e1l\u00f3zati \u00fcgyfelek sz\u00e1m\u00e1ra" diff --git a/homeassistant/components/unifi/translations/id.json b/homeassistant/components/unifi/translations/id.json new file mode 100644 index 00000000000..7a707b28aa0 --- /dev/null +++ b/homeassistant/components/unifi/translations/id.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "already_configured": "Situs Controller sudah dikonfigurasi", + "configuration_updated": "Konfigurasi diperbarui.", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "faulty_credentials": "Autentikasi tidak valid", + "service_unavailable": "Gagal terhubung", + "unknown_client_mac": "Tidak ada klien yang tersedia di alamat MAC tersebut" + }, + "flow_title": "UniFi Network {site} ({host})", + "step": { + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "port": "Port", + "site": "ID Site", + "username": "Nama Pengguna", + "verify_ssl": "Verifikasi sertifikat SSL" + }, + "title": "Siapkan UniFi Controller" + } + } + }, + "options": { + "step": { + "client_control": { + "data": { + "block_client": "Klien yang dikontrol akses jaringan", + "dpi_restrictions": "Izinkan kontrol grup pembatasan DPI", + "poe_clients": "Izinkan kontrol POE klien" + }, + "description": "Konfigurasikan kontrol klien \n\nBuat sakelar untuk nomor seri yang ingin dikontrol akses jaringannya.", + "title": "Opsi UniFi 2/3" + }, + "device_tracker": { + "data": { + "detection_time": "Tenggang waktu dalam detik dari terakhir terlihat hingga dianggap sebagai keluar", + "ignore_wired_bug": "Nonaktifkan bug logika kabel UniFi", + "ssid_filter": "Pilih SSID untuk melacak klien nirkabel", + "track_clients": "Lacak klien jaringan", + "track_devices": "Lacak perangkat jaringan (perangkat Ubiquiti)", + "track_wired_clients": "Sertakan klien jaringan berkabel" + }, + "description": "Konfigurasikan pelacakan perangkat", + "title": "Opsi UniFi 1/3" + }, + "simple_options": { + "data": { + "block_client": "Klien yang dikontrol akses jaringan", + "track_clients": "Lacak klien jaringan", + "track_devices": "Lacak perangkat jaringan (perangkat Ubiquiti)" + }, + "description": "Konfigurasikan integrasi UniFi" + }, + "statistics_sensors": { + "data": { + "allow_bandwidth_sensors": "Sensor penggunaan bandwidth untuk klien jaringan", + "allow_uptime_sensors": "Sensor waktu kerja untuk klien jaringan" + }, + "description": "Konfigurasikan sensor statistik", + "title": "Opsi UniFi 3/3" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/ko.json b/homeassistant/components/unifi/translations/ko.json index 454feec0922..f5ada3df99a 100644 --- a/homeassistant/components/unifi/translations/ko.json +++ b/homeassistant/components/unifi/translations/ko.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\ucee8\ud2b8\ub864\ub7ec \uc0ac\uc774\ud2b8\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "configuration_updated": "\uad6c\uc131\uc774 \uc5c5\ub370\uc774\ud2b8\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { @@ -9,6 +10,7 @@ "service_unavailable": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "unknown_client_mac": "\ud574\ub2f9 MAC \uc8fc\uc18c\uc5d0\uc11c \uc0ac\uc6a9 \uac00\ub2a5\ud55c \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \uc5c6\uc2b5\ub2c8\ub2e4." }, + "flow_title": "UniFi \ub124\ud2b8\uc6cc\ud06c: {site} ({host})", "step": { "user": { "data": { @@ -28,7 +30,8 @@ "client_control": { "data": { "block_client": "\ub124\ud2b8\uc6cc\ud06c \uc561\uc138\uc2a4 \uc81c\uc5b4 \ud074\ub77c\uc774\uc5b8\ud2b8", - "poe_clients": "\ud074\ub77c\uc774\uc5b8\ud2b8\uc758 POE \uc81c\uc5b4 \ud5c8\uc6a9" + "dpi_restrictions": "DPI \uc81c\ud55c \uadf8\ub8f9\uc758 \uc81c\uc5b4 \ud5c8\uc6a9\ud558\uae30", + "poe_clients": "\ud074\ub77c\uc774\uc5b8\ud2b8\uc758 POE \uc81c\uc5b4 \ud5c8\uc6a9\ud558\uae30" }, "description": "\ud074\ub77c\uc774\uc5b8\ud2b8 \ucee8\ud2b8\ub864 \uad6c\uc131 \n\n\ub124\ud2b8\uc6cc\ud06c \uc561\uc138\uc2a4\ub97c \uc81c\uc5b4\ud558\ub824\ub294 \uc2dc\ub9ac\uc5bc \ubc88\ud638\uc5d0 \ub300\ud55c \uc2a4\uc704\uce58\ub97c \ub9cc\ub4ed\ub2c8\ub2e4.", "title": "UniFi \uc635\uc158 2/3" @@ -56,7 +59,7 @@ "statistics_sensors": { "data": { "allow_bandwidth_sensors": "\ub124\ud2b8\uc6cc\ud06c \ud074\ub77c\uc774\uc5b8\ud2b8 \ub300\uc5ed\ud3ed \uc0ac\uc6a9\ub7c9 \uc13c\uc11c", - "allow_uptime_sensors": "\ub124\ud2b8\uc6cc\ud06c \ud074\ub77c\uc774\uc5b8\ud2b8\ub97c \uc704\ud55c \uac00\ub3d9 \uc2dc\uac04 \uc13c\uc11c" + "allow_uptime_sensors": "\ub124\ud2b8\uc6cc\ud06c \ud074\ub77c\uc774\uc5b8\ud2b8\uc5d0 \ub300\ud55c \uac00\ub3d9 \uc2dc\uac04 \uc13c\uc11c" }, "description": "\ud1b5\uacc4 \uc13c\uc11c \uad6c\uc131", "title": "UniFi \uc635\uc158 3/3" diff --git a/homeassistant/components/unifi/translations/lb.json b/homeassistant/components/unifi/translations/lb.json index 1e4870e6ab8..5c939a0105a 100644 --- a/homeassistant/components/unifi/translations/lb.json +++ b/homeassistant/components/unifi/translations/lb.json @@ -8,6 +8,7 @@ "service_unavailable": "Feeler beim verbannen", "unknown_client_mac": "Kee Cliwent mat der MAC Adress disponibel" }, + "flow_title": "UniFi Network {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/nl.json b/homeassistant/components/unifi/translations/nl.json index 7f0baf4a3be..e5e5e3a1dfb 100644 --- a/homeassistant/components/unifi/translations/nl.json +++ b/homeassistant/components/unifi/translations/nl.json @@ -6,8 +6,8 @@ "reauth_successful": "Herauthenticatie was succesvol" }, "error": { - "faulty_credentials": "Foutieve gebruikersgegevens", - "service_unavailable": "Geen service beschikbaar", + "faulty_credentials": "Ongeldige authenticatie", + "service_unavailable": "Kan geen verbinding maken", "unknown_client_mac": "Geen client beschikbaar op dat MAC-adres" }, "flow_title": "UniFi Netwerk {site} ({host})", @@ -19,7 +19,7 @@ "port": "Poort", "site": "Site ID", "username": "Gebruikersnaam", - "verify_ssl": "Controller gebruik van het juiste certificaat" + "verify_ssl": "SSL-certificaat verifi\u00ebren" }, "title": "Stel de UniFi-controller in" } @@ -30,6 +30,7 @@ "client_control": { "data": { "block_client": "Cli\u00ebnten met netwerktoegang", + "dpi_restrictions": "Sta controle van DPI-beperkingsgroepen toe", "poe_clients": "Sta POE-controle van gebruikers toe" }, "description": "Configureer clientbesturingen \n\n Maak schakelaars voor serienummers waarvoor u de netwerktoegang wilt beheren.", diff --git a/homeassistant/components/upb/translations/hu.json b/homeassistant/components/upb/translations/hu.json new file mode 100644 index 00000000000..b09f497a0e4 --- /dev/null +++ b/homeassistant/components/upb/translations/hu.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "protocol": "Protokoll" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upb/translations/id.json b/homeassistant/components/upb/translations/id.json new file mode 100644 index 00000000000..3e875de7b19 --- /dev/null +++ b/homeassistant/components/upb/translations/id.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_upb_file": "File ekspor UPB UPStart tidak ada atau tidak valid, periksa nama dan jalur berkas.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "address": "Alamat (lihat deskripsi di atas)", + "file_path": "Jalur dan nama file ekspor UPStart UPB.", + "protocol": "Protokol" + }, + "description": "Hubungkan Universal Powerline Bus Powerline Interface Module (UPB PIM). String alamat harus dalam format 'address[:port]' untuk 'tcp'. Nilai port ini opsional dan nilai bakunya adalah 2101. Contoh: '192.168.1.42'. Untuk protokol serial, alamat harus dalam format 'tty[:baud]'. Nilai baud ini opsional dan nilai bakunya adalah 4800. Contoh: '/dev/ttyS1'.", + "title": "Hubungkan ke UPB PIM" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upb/translations/ko.json b/homeassistant/components/upb/translations/ko.json index da357f7a136..aedb6e06c41 100644 --- a/homeassistant/components/upb/translations/ko.json +++ b/homeassistant/components/upb/translations/ko.json @@ -15,8 +15,8 @@ "file_path": "UPStart UPB \ub0b4\ubcf4\ub0b4\uae30 \ud30c\uc77c\uc758 \uacbd\ub85c \ubc0f \uc774\ub984", "protocol": "\ud504\ub85c\ud1a0\ucf5c" }, - "description": "\ubc94\uc6a9 \ud30c\uc6cc\ub77c\uc778 \ubc84\uc2a4 \ud30c\uc6cc\ub77c\uc778 \uc778\ud130\ud398\uc774\uc2a4 \ubaa8\ub4c8 (UPB PIM) \uc744 \uc5f0\uacb0\ud574\uc8fc\uc138\uc694. \uc8fc\uc18c \ubb38\uc790\uc5f4\uc740 'tcp' \ud504\ub85c\ud1a0\ucf5c\uc758 \uacbd\uc6b0 'address[:port]' \ud615\uc2dd\uc785\ub2c8\ub2e4. \ud3ec\ud2b8\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 2101 \uc785\ub2c8\ub2e4. \uc608: '192.168.1.42'. \uc2dc\ub9ac\uc5bc \ud504\ub85c\ud1a0\ucf5c\uc758 \uacbd\uc6b0 \uc8fc\uc18c \ubb38\uc790\uc5f4\uc740 'tty[:baud]' \ud615\uc2dd\uc785\ub2c8\ub2e4. \ubcf4(baud)\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 4800 \uc785\ub2c8\ub2e4. \uc608: '/dev/ttyS1'.", - "title": "UPB PIM \uc5d0 \uc5f0\uacb0\ud558\uae30" + "description": "\ubc94\uc6a9 \ud30c\uc6cc\ub77c\uc778 \ubc84\uc2a4 \ud30c\uc6cc\ub77c\uc778 \uc778\ud130\ud398\uc774\uc2a4 \ubaa8\ub4c8 (UPB PIM) \uc744 \uc5f0\uacb0\ud574\uc8fc\uc138\uc694. \uc8fc\uc18c \ubb38\uc790\uc5f4\uc740 'tcp' \ud504\ub85c\ud1a0\ucf5c\uc758 \uacbd\uc6b0 'address[:port]' \ud615\uc2dd\uc785\ub2c8\ub2e4. \ud3ec\ud2b8\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 2101 \uc785\ub2c8\ub2e4. \uc608: '192.168.1.42'. \uc2dc\ub9ac\uc5bc \ud504\ub85c\ud1a0\ucf5c\uc758 \uacbd\uc6b0 \uc8fc\uc18c \ubb38\uc790\uc5f4\uc740 'tty[:baud]' \ud615\uc2dd\uc785\ub2c8\ub2e4. \uc804\uc1a1 \uc18d\ub3c4\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 4800 \uc785\ub2c8\ub2e4. \uc608: '/dev/ttyS1'.", + "title": "UPB PIM\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/upb/translations/pt.json b/homeassistant/components/upb/translations/pt.json index ae100e45845..657ce03e544 100644 --- a/homeassistant/components/upb/translations/pt.json +++ b/homeassistant/components/upb/translations/pt.json @@ -4,6 +4,7 @@ "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" }, "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", "unknown": "Erro inesperado" } } diff --git a/homeassistant/components/upcloud/translations/hu.json b/homeassistant/components/upcloud/translations/hu.json index 7a7de0633a7..2bb28c6c3bc 100644 --- a/homeassistant/components/upcloud/translations/hu.json +++ b/homeassistant/components/upcloud/translations/hu.json @@ -2,7 +2,7 @@ "config": { "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "invalid_auth": "\u00c9rv\u00e9nytelen autentik\u00e1ci\u00f3" + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, "step": { "user": { diff --git a/homeassistant/components/upcloud/translations/id.json b/homeassistant/components/upcloud/translations/id.json new file mode 100644 index 00000000000..4ff6a8c7d92 --- /dev/null +++ b/homeassistant/components/upcloud/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Interval pembaruan (dalam detik, minimal 30)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upcloud/translations/ko.json b/homeassistant/components/upcloud/translations/ko.json index 04360a9a8f7..073236bd80b 100644 --- a/homeassistant/components/upcloud/translations/ko.json +++ b/homeassistant/components/upcloud/translations/ko.json @@ -12,5 +12,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ucd08, \ucd5c\uc18c\uac12 30)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/updater/translations/zh-Hant.json b/homeassistant/components/updater/translations/zh-Hant.json index 31188faa135..23c1b069fc1 100644 --- a/homeassistant/components/updater/translations/zh-Hant.json +++ b/homeassistant/components/updater/translations/zh-Hant.json @@ -1,3 +1,3 @@ { - "title": "\u66f4\u65b0\u7248\u672c" + "title": "\u66f4\u65b0\u5668" } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/de.json b/homeassistant/components/upnp/translations/de.json index 972f8da4075..8a5f4fefadf 100644 --- a/homeassistant/components/upnp/translations/de.json +++ b/homeassistant/components/upnp/translations/de.json @@ -16,6 +16,7 @@ }, "user": { "data": { + "scan_interval": "Aktualisierungsintervall (Sekunden, mindestens 30)", "usn": "Ger\u00e4t" } } diff --git a/homeassistant/components/upnp/translations/hu.json b/homeassistant/components/upnp/translations/hu.json index 66320386a89..8b50de71f74 100644 --- a/homeassistant/components/upnp/translations/hu.json +++ b/homeassistant/components/upnp/translations/hu.json @@ -1,12 +1,20 @@ { "config": { "abort": { - "already_configured": "Az UPnP / IGD m\u00e1r konfigur\u00e1l\u00e1sra ker\u00fclt", - "no_devices_found": "Nincsenek UPnPIGD eszk\u00f6z\u00f6k a h\u00e1l\u00f3zaton." + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton" }, "error": { "one": "hiba", "other": "" + }, + "flow_title": "UPnP/IGD: {name}", + "step": { + "user": { + "data": { + "usn": "Eszk\u00f6z" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/id.json b/homeassistant/components/upnp/translations/id.json new file mode 100644 index 00000000000..463e61f271c --- /dev/null +++ b/homeassistant/components/upnp/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "incomplete_discovery": "Proses penemuan tidak selesai", + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan" + }, + "flow_title": "UPnP/IGD: {name}", + "step": { + "ssdp_confirm": { + "description": "Ingin menyiapkan perangkat UPnP/IGD ini?" + }, + "user": { + "data": { + "scan_interval": "Interval pembaruan (dalam detik, minimal 30)", + "usn": "Perangkat" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/nl.json b/homeassistant/components/upnp/translations/nl.json index 3d2c628fcbb..cd4192e257c 100644 --- a/homeassistant/components/upnp/translations/nl.json +++ b/homeassistant/components/upnp/translations/nl.json @@ -1,9 +1,9 @@ { "config": { "abort": { - "already_configured": "UPnP/IGD is al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "incomplete_discovery": "Onvolledige ontdekking", - "no_devices_found": "Geen UPnP/IGD apparaten gevonden op het netwerk." + "no_devices_found": "Geen apparaten gevonden op het netwerk" }, "error": { "one": "Een", @@ -11,6 +11,10 @@ }, "flow_title": "UPnP/IGD: {name}", "step": { + "init": { + "one": "Leeg", + "other": "Leeg" + }, "ssdp_confirm": { "description": "Wilt u [%%] instellen?" }, diff --git a/homeassistant/components/vacuum/translations/id.json b/homeassistant/components/vacuum/translations/id.json index a9827363d5e..5fc888515fe 100644 --- a/homeassistant/components/vacuum/translations/id.json +++ b/homeassistant/components/vacuum/translations/id.json @@ -1,14 +1,28 @@ { + "device_automation": { + "action_type": { + "clean": "Perintahkan {entity_name} untuk bersih-bersih", + "dock": "Kembalikan {entity_name} ke dok" + }, + "condition_type": { + "is_cleaning": "{entity_name} sedang membersihkan", + "is_docked": "{entity_name} berlabuh di dok" + }, + "trigger_type": { + "cleaning": "{entity_name} mulai membersihkan", + "docked": "{entity_name} berlabuh" + } + }, "state": { "_": { "cleaning": "Membersihkan", "docked": "Berlabuh", "error": "Kesalahan", "idle": "Siaga", - "off": "Padam", + "off": "Mati", "on": "Nyala", - "paused": "Dijeda", - "returning": "Kembali ke dock" + "paused": "Jeda", + "returning": "Kembali ke dok" } }, "title": "Vakum" diff --git a/homeassistant/components/vacuum/translations/ko.json b/homeassistant/components/vacuum/translations/ko.json index 0e59d5157eb..52e1e1bd36b 100644 --- a/homeassistant/components/vacuum/translations/ko.json +++ b/homeassistant/components/vacuum/translations/ko.json @@ -1,16 +1,16 @@ { "device_automation": { "action_type": { - "clean": "{entity_name} \uc744(\ub97c) \uccad\uc18c\uc2dc\ud0a4\uae30", - "dock": "{entity_name} \uc744(\ub97c) \ucda9\uc804\uc2a4\ud14c\uc774\uc158\uc73c\ub85c \ubcf5\uadc0\uc2dc\ud0a4\uae30" + "clean": "{entity_name}\uc744(\ub97c) \uccad\uc18c\uc2dc\ud0a4\uae30", + "dock": "{entity_name}\uc744(\ub97c) \ucda9\uc804\uc2a4\ud14c\uc774\uc158\uc73c\ub85c \ubcf5\uadc0\uc2dc\ud0a4\uae30" }, "condition_type": { - "is_cleaning": "{entity_name} \uc774(\uac00) \uccad\uc18c \uc911\uc774\uba74", - "is_docked": "{entity_name} \uc774(\uac00) \ub3c4\ud0b9\ub418\uc5b4\uc788\uc73c\uba74" + "is_cleaning": "{entity_name}\uc774(\uac00) \uccad\uc18c \uc911\uc774\uba74", + "is_docked": "{entity_name}\uc774(\uac00) \ucda9\uc804 \uc2a4\ud14c\uc774\uc158\uc5d0 \uc788\uc73c\uba74" }, "trigger_type": { - "cleaning": "{entity_name} \uc774(\uac00) \uccad\uc18c\ub97c \uc2dc\uc791\ud560 \ub54c", - "docked": "{entity_name} \uc774(\uac00) \ub3c4\ud0b9\ub420 \ub54c" + "cleaning": "{entity_name}\uc774(\uac00) \uccad\uc18c\ud558\uae30 \uc2dc\uc791\ud588\uc744 \ub54c", + "docked": "{entity_name}\uc774(\uac00) \ucda9\uc804 \uc2a4\ud14c\uc774\uc158\uc5d0 \ubcf5\uadc0\ud588\uc744 \ub54c" } }, "state": { diff --git a/homeassistant/components/vacuum/translations/nl.json b/homeassistant/components/vacuum/translations/nl.json index 3fbb0ae50be..1de347f9875 100644 --- a/homeassistant/components/vacuum/translations/nl.json +++ b/homeassistant/components/vacuum/translations/nl.json @@ -25,5 +25,5 @@ "returning": "Terugkeren naar dock" } }, - "title": "Stofzuigen" + "title": "Stofzuiger" } \ No newline at end of file diff --git a/homeassistant/components/vacuum/translations/zh-Hans.json b/homeassistant/components/vacuum/translations/zh-Hans.json index 9e252236d0a..1e4be0ebe0b 100644 --- a/homeassistant/components/vacuum/translations/zh-Hans.json +++ b/homeassistant/components/vacuum/translations/zh-Hans.json @@ -1,7 +1,8 @@ { "device_automation": { "action_type": { - "clean": "\u4f7f {entity_name} \u5f00\u59cb\u6e05\u626b" + "clean": "\u4f7f {entity_name} \u5f00\u59cb\u6e05\u626b", + "dock": "\u4f7f {entity_name} \u8fd4\u56de\u5e95\u5ea7" }, "condition_type": { "is_cleaning": "{entity_name} \u6b63\u5728\u6e05\u626b", diff --git a/homeassistant/components/velbus/translations/de.json b/homeassistant/components/velbus/translations/de.json index 9bbb23b1bcd..c6c452953ca 100644 --- a/homeassistant/components/velbus/translations/de.json +++ b/homeassistant/components/velbus/translations/de.json @@ -11,7 +11,7 @@ "user": { "data": { "name": "Der Name f\u00fcr diese Velbus-Verbindung", - "port": "Verbindungs details" + "port": "Verbindungsdetails" }, "title": "Definieren des Velbus-Verbindungstyps" } diff --git a/homeassistant/components/velbus/translations/hu.json b/homeassistant/components/velbus/translations/hu.json new file mode 100644 index 00000000000..414ee7e60c6 --- /dev/null +++ b/homeassistant/components/velbus/translations/hu.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/velbus/translations/id.json b/homeassistant/components/velbus/translations/id.json new file mode 100644 index 00000000000..69a05411dc4 --- /dev/null +++ b/homeassistant/components/velbus/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "name": "Nama untuk koneksi velbus ini", + "port": "String koneksi" + }, + "title": "Tentukan jenis koneksi velbus" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vera/translations/id.json b/homeassistant/components/vera/translations/id.json new file mode 100644 index 00000000000..435fc722dba --- /dev/null +++ b/homeassistant/components/vera/translations/id.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "cannot_connect": "Tidak dapat terhubung ke pengontrol dengan URL {base_url}" + }, + "step": { + "user": { + "data": { + "exclude": "ID perangkat Vera yang akan dikecualikan dari Home Assistant.", + "lights": "ID perangkat sakelar Vera yang diperlakukan sebagai lampu di Home Assistant", + "vera_controller_url": "URL Pengontrol" + }, + "description": "Tentukan URL pengontrol Vera di bawah. Ini akan terlihat seperti ini: http://192.168.1.161:3480.", + "title": "Siapkan pengontrol Vera" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "exclude": "ID perangkat Vera yang akan dikecualikan dari Home Assistant.", + "lights": "ID perangkat sakelar Vera yang diperlakukan sebagai lampu di Home Assistant" + }, + "description": "Lihat dokumentasi Vera untuk informasi tentang parameter opsional: https://www.home-assistant.io/integrations/vera/. Catatan: Setiap perubahan yang dilakukan di sini membutuhkan memulai ulang Home Assistant. Untuk menghapus nilai, ketikkan karakter spasi.", + "title": "Opsi pengontrol Vera" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vera/translations/ko.json b/homeassistant/components/vera/translations/ko.json index e8658528488..0990435cb4a 100644 --- a/homeassistant/components/vera/translations/ko.json +++ b/homeassistant/components/vera/translations/ko.json @@ -6,8 +6,8 @@ "step": { "user": { "data": { - "exclude": "Home Assistant \uc5d0\uc11c \uc81c\uc678\ud560 Vera \uae30\uae30 ID.", - "lights": "Vera \uc2a4\uc704\uce58 \uae30\uae30 ID \ub294 Home Assistant \uc5d0\uc11c \uc870\uba85\uc73c\ub85c \ucde8\uae09\ub429\ub2c8\ub2e4.", + "exclude": "Home Assistant\uc5d0\uc11c \uc81c\uc678\ud560 Vera \uae30\uae30 ID.", + "lights": "Vera \uc2a4\uc704\uce58 \uae30\uae30 ID \ub294 Home Assistant\uc5d0\uc11c \uc870\uba85\uc73c\ub85c \ucde8\uae09\ub429\ub2c8\ub2e4.", "vera_controller_url": "\ucee8\ud2b8\ub864\ub7ec URL" }, "description": "\uc544\ub798\uc5d0 Vera \ucee8\ud2b8\ub864\ub7ec URL \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. http://192.168.1.161:3480 \uacfc \uac19\uc740 \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4.", @@ -19,8 +19,8 @@ "step": { "init": { "data": { - "exclude": "Home Assistant \uc5d0\uc11c \uc81c\uc678\ud560 Vera \uae30\uae30 ID.", - "lights": "Vera \uc2a4\uc704\uce58 \uae30\uae30 ID \ub294 Home Assistant \uc5d0\uc11c \uc870\uba85\uc73c\ub85c \ucde8\uae09\ub429\ub2c8\ub2e4." + "exclude": "Home Assistant\uc5d0\uc11c \uc81c\uc678\ud560 Vera \uae30\uae30 ID.", + "lights": "Vera \uc2a4\uc704\uce58 \uae30\uae30 ID \ub294 Home Assistant\uc5d0\uc11c \uc870\uba85\uc73c\ub85c \ucde8\uae09\ub429\ub2c8\ub2e4." }, "description": "\ub9e4\uac1c \ubcc0\uc218 \uc120\ud0dd\uc0ac\ud56d\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 vera \uc124\uba85\uc11c\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694: https://www.home-assistant.io/integrations/vera/. \ucc38\uace0: \uc5ec\uae30\uc5d0\uc11c \ubcc0\uacbd\ud558\uba74 Home Assistant \uc11c\ubc84\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud574\uc57c \ud569\ub2c8\ub2e4. \uac12\uc744 \uc9c0\uc6b0\ub824\uba74 \uc785\ub825\ub780\uc744 \uacf5\ubc31\uc73c\ub85c \ub450\uc138\uc694.", "title": "Vera \ucee8\ud2b8\ub864\ub7ec \uc635\uc158" diff --git a/homeassistant/components/verisure/translations/ca.json b/homeassistant/components/verisure/translations/ca.json new file mode 100644 index 00000000000..4df484912ce --- /dev/null +++ b/homeassistant/components/verisure/translations/ca.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "El compte ja ha estat configurat" + }, + "error": { + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "installation": { + "data": { + "giid": "Instal\u00b7laci\u00f3" + }, + "description": "Home Assistant ha trobat diverses instal\u00b7lacions Verisure al teu compte de My Pages. Selecciona la instal\u00b7laci\u00f3 a afegir a Home Assistant." + }, + "user": { + "data": { + "description": "Inicia sessi\u00f3 amb el compte de Verisure My Pages.", + "email": "Correu electr\u00f2nic", + "password": "Contrasenya" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "El codi PIN predeterminat no coincideix amb el nombre de d\u00edgits correcte" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Nombre de d\u00edgits del codi PIN dels panys", + "lock_default_code": "Codi PIN dels panys predeterminat, s'utilitza si no se n'indica cap" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/et.json b/homeassistant/components/verisure/translations/et.json new file mode 100644 index 00000000000..d893f470a9a --- /dev/null +++ b/homeassistant/components/verisure/translations/et.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Kasutaja on juba seadistatud" + }, + "error": { + "invalid_auth": "Vigane autentimine", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "installation": { + "data": { + "giid": "Paigaldus" + }, + "description": "Home Assistant leidis kontolt Minu lehed mitu Verisure paigaldust. Vali Home Assistantile lisatav paigaldus." + }, + "user": { + "data": { + "description": "Logi sisse oma Verisure My Pages kontoga.", + "email": "E-posti aadress", + "password": "Salas\u00f5na" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "Vaikimisi PIN-koodi numbrite arv on vale" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Lukkude PIN-koodi numbrite arv", + "lock_default_code": "Lukkude VAIKIMISI PIN-kood, mida kasutatakse juhul kui seda polem\u00e4\u00e4ratud" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/fr.json b/homeassistant/components/verisure/translations/fr.json new file mode 100644 index 00000000000..df2640e10ba --- /dev/null +++ b/homeassistant/components/verisure/translations/fr.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "installation": { + "data": { + "giid": "Installation" + }, + "description": "Home Assistant a trouv\u00e9 plusieurs installations Verisure dans votre compte My Pages. Veuillez s\u00e9lectionner l'installation \u00e0 ajouter \u00e0 Home Assistant." + }, + "user": { + "data": { + "description": "Connectez-vous avec votre compte Verisure My Pages.", + "email": "Email", + "password": "Mot de passe" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "Le code NIP par d\u00e9faut ne correspond pas au nombre de chiffres requis" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Nombre de chiffres du code NIP pour les serrures", + "lock_default_code": "Code NIP par d\u00e9faut pour les serrures, utilis\u00e9 si aucun n'est indiqu\u00e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/pt.json b/homeassistant/components/verisure/translations/pt.json new file mode 100644 index 00000000000..b6634945535 --- /dev/null +++ b/homeassistant/components/verisure/translations/pt.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "already_configured": "Conta j\u00e1 configurada" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vesync/translations/he.json b/homeassistant/components/vesync/translations/he.json new file mode 100644 index 00000000000..6f4191da70d --- /dev/null +++ b/homeassistant/components/vesync/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vesync/translations/hu.json b/homeassistant/components/vesync/translations/hu.json index 10607c5a136..91956aff452 100644 --- a/homeassistant/components/vesync/translations/hu.json +++ b/homeassistant/components/vesync/translations/hu.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/vesync/translations/id.json b/homeassistant/components/vesync/translations/id.json new file mode 100644 index 00000000000..968f8541849 --- /dev/null +++ b/homeassistant/components/vesync/translations/id.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Email" + }, + "title": "Masukkan Nama Pengguna dan Kata Sandi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vesync/translations/ko.json b/homeassistant/components/vesync/translations/ko.json index d11e9f9459d..a3f1fc1316a 100644 --- a/homeassistant/components/vesync/translations/ko.json +++ b/homeassistant/components/vesync/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" @@ -12,7 +12,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc774\uba54\uc77c" }, - "title": "\uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud558\uae30" + "title": "\uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" } } } diff --git a/homeassistant/components/vilfo/translations/hu.json b/homeassistant/components/vilfo/translations/hu.json index a75149507fc..34db9cf7cc9 100644 --- a/homeassistant/components/vilfo/translations/hu.json +++ b/homeassistant/components/vilfo/translations/hu.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/vilfo/translations/id.json b/homeassistant/components/vilfo/translations/id.json new file mode 100644 index 00000000000..3871670a1cd --- /dev/null +++ b/homeassistant/components/vilfo/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "access_token": "Token Akses", + "host": "Host" + }, + "description": "Siapkan integrasi Router Vilfo. Anda memerlukan nama host/IP Router Vilfo dan token akses API. Untuk informasi lebih lanjut tentang integrasi ini dan cara mendapatkan data tersebut, kunjungi: https://www.home-assistant.io/integrations/vilfo", + "title": "Hubungkan ke Router Vilfo" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vilfo/translations/ko.json b/homeassistant/components/vilfo/translations/ko.json index 4b79130c750..980ee36d7a5 100644 --- a/homeassistant/components/vilfo/translations/ko.json +++ b/homeassistant/components/vilfo/translations/ko.json @@ -14,7 +14,7 @@ "access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070", "host": "\ud638\uc2a4\ud2b8" }, - "description": "Vilfo \ub77c\uc6b0\ud130 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. Vilfo \ub77c\uc6b0\ud130 \ud638\uc2a4\ud2b8 \uc774\ub984 / IP \uc640 API \uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \uc774 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0 \ub300\ud55c \ucd94\uac00 \uc815\ubcf4\uc640 \uc138\ubd80 \uc0ac\ud56d\uc740 https://www.home-assistant.io/integrations/vilfo \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694", + "description": "Vilfo \ub77c\uc6b0\ud130 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. Vilfo \ub77c\uc6b0\ud130 \ud638\uc2a4\ud2b8 \uc774\ub984/IP \uc640 API \uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \uc774 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0 \ub300\ud55c \ucd94\uac00 \uc815\ubcf4\uc640 \uc138\ubd80 \uc0ac\ud56d\uc740 https://www.home-assistant.io/integrations/vilfo \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694", "title": "Vilfo \ub77c\uc6b0\ud130\uc5d0 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/vilfo/translations/nl.json b/homeassistant/components/vilfo/translations/nl.json index d4b117e2b70..304871cc145 100644 --- a/homeassistant/components/vilfo/translations/nl.json +++ b/homeassistant/components/vilfo/translations/nl.json @@ -1,18 +1,18 @@ { "config": { "abort": { - "already_configured": "Deze Vilfo Router is al geconfigureerd." + "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Kon niet verbinden. Controleer de door u verstrekte informatie en probeer het opnieuw.", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", - "unknown": "Er is een onverwachte fout opgetreden tijdens het instellen van de integratie." + "unknown": "Onverwachte fout" }, "step": { "user": { "data": { - "access_token": "Toegangstoken voor de Vilfo Router API", - "host": "Router hostnaam of IP-adres" + "access_token": "Toegangstoken", + "host": "Host" }, "description": "Stel de Vilfo Router-integratie in. U heeft de hostnaam/IP van uw Vilfo Router en een API-toegangstoken nodig. Voor meer informatie over deze integratie en hoe u die details kunt verkrijgen, gaat u naar: https://www.home-assistant.io/integrations/vilfo", "title": "Maak verbinding met de Vilfo Router" diff --git a/homeassistant/components/vizio/translations/hu.json b/homeassistant/components/vizio/translations/hu.json index 7401b751d27..6f0962509f5 100644 --- a/homeassistant/components/vizio/translations/hu.json +++ b/homeassistant/components/vizio/translations/hu.json @@ -2,9 +2,21 @@ "config": { "abort": { "already_configured_device": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", "updated_entry": "Ez a bejegyz\u00e9s m\u00e1r be van \u00e1ll\u00edtva, de a konfigur\u00e1ci\u00f3ban defini\u00e1lt n\u00e9v, appok \u00e9s/vagy be\u00e1ll\u00edt\u00e1sok nem egyeznek meg a kor\u00e1bban import\u00e1lt konfigur\u00e1ci\u00f3val, \u00edgy a konfigur\u00e1ci\u00f3s bejegyz\u00e9s ennek megfelel\u0151en friss\u00fclt." }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, "step": { + "pair_tv": { + "data": { + "pin": "PIN-k\u00f3d" + } + }, + "pairing_complete": { + "description": "A VIZIO SmartCast Eszk\u00f6z mostant\u00f3l csatlakozik a Home Assistant-hoz." + }, "user": { "data": { "access_token": "Hozz\u00e1f\u00e9r\u00e9si token", @@ -12,7 +24,7 @@ "host": "Hoszt", "name": "N\u00e9v" }, - "title": "A Vizio SmartCast Client be\u00e1ll\u00edt\u00e1sa" + "title": "VIZIO SmartCast Eszk\u00f6z" } } }, @@ -22,7 +34,7 @@ "data": { "volume_step": "Hanger\u0151 l\u00e9p\u00e9s nagys\u00e1ga" }, - "title": "Friss\u00edtse a Vizo SmartCast be\u00e1ll\u00edt\u00e1sokat" + "title": "VIZIO SmartCast Eszk\u00f6z be\u00e1ll\u00edt\u00e1sok friss\u00edt\u00e9se" } } } diff --git a/homeassistant/components/vizio/translations/id.json b/homeassistant/components/vizio/translations/id.json new file mode 100644 index 00000000000..19f9b41449c --- /dev/null +++ b/homeassistant/components/vizio/translations/id.json @@ -0,0 +1,54 @@ +{ + "config": { + "abort": { + "already_configured_device": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "updated_entry": "Entri ini telah disiapkan tetapi nama, aplikasi, dan/atau opsi yang ditentukan dalam konfigurasi tidak cocok dengan konfigurasi yang diimpor sebelumnya, oleh karena itu entri konfigurasi telah diperbarui." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "complete_pairing_failed": "Tidak dapat menyelesaikan pemasangan. Pastikan PIN yang diberikan benar dan TV masih menyala dan terhubung ke jaringan sebelum mengirim ulang.", + "existing_config_entry_found": "Entri konfigurasi Perangkat VIZIO SmartCast yang ada dengan nomor seri yang sama telah dikonfigurasi. Anda harus menghapus entri yang ada untuk mengonfigurasi entri ini." + }, + "step": { + "pair_tv": { + "data": { + "pin": "Kode PIN" + }, + "description": "TV Anda harus menampilkan kode. Masukkan kode tersebut ke dalam formulir dan kemudian lanjutkan ke langkah berikutnya untuk menyelesaikan pemasangan.", + "title": "Selesaikan Proses Pemasangan" + }, + "pairing_complete": { + "description": "Perangkat VIZIO SmartCast sekarang terhubung ke Home Assistant.", + "title": "Pemasangan Selesai" + }, + "pairing_complete_import": { + "description": "Perangkat VIZIO SmartCast sekarang terhubung ke Home Assistant. \n\nToken Akses adalah '**{access_token}**'.", + "title": "Pemasangan Selesai" + }, + "user": { + "data": { + "access_token": "Token Akses", + "device_class": "Jenis Perangkat", + "host": "Host", + "name": "Nama" + }, + "description": "Token Akses hanya diperlukan untuk TV. Jika Anda mengkonfigurasi TV dan belum memiliki Token Akses , biarkan kosong untuk melakukan proses pemasangan.", + "title": "Perangkat VIZIO SmartCast" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "apps_to_include_or_exclude": "Aplikasi untuk Disertakan atau Dikecualikan", + "include_or_exclude": "Sertakan atau Kecualikan Aplikasi?", + "volume_step": "Langkah Volume" + }, + "description": "Jika memiliki Smart TV, Anda dapat memfilter daftar sumber secara opsional dengan memilih aplikasi mana yang akan disertakan atau dikecualikan dalam daftar sumber Anda.", + "title": "Perbarui Opsi Perangkat VIZIO SmartCast" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vizio/translations/ko.json b/homeassistant/components/vizio/translations/ko.json index 037c85d7c4e..9310adfa4b9 100644 --- a/homeassistant/components/vizio/translations/ko.json +++ b/homeassistant/components/vizio/translations/ko.json @@ -7,8 +7,8 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "complete_pairing_failed": "\ud398\uc5b4\ub9c1\uc744 \uc644\ub8cc\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc81c\ucd9c\ud558\uae30 \uc804\uc5d0 \uc785\ub825\ud55c PIN \uc774 \uc62c\ubc14\ub978\uc9c0, TV \uc758 \uc804\uc6d0\uc774 \ucf1c\uc838 \uc788\uace0 \ub124\ud2b8\uc6cc\ud06c\uc5d0 \uc5f0\uacb0\ub418\uc5b4 \uc788\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", - "existing_config_entry_found": "\uc77c\ub828 \ubc88\ud638\uac00 \ub3d9\uc77c\ud55c \uae30\uc874 VIZIO SmartCast \uae30\uae30 \uad6c\uc131 \ud56d\ubaa9\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc774 \ud56d\ubaa9\uc744 \uad6c\uc131\ud558\ub824\uba74 \uae30\uc874 \ud56d\ubaa9\uc744 \uc0ad\uc81c\ud574\uc57c\ud569\ub2c8\ub2e4." + "complete_pairing_failed": "\ud398\uc5b4\ub9c1\uc744 \uc644\ub8cc\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud558\uae30 \uc804\uc5d0 \uc785\ub825\ud55c PIN\uc774 \uc62c\ubc14\ub978\uc9c0, TV\uc758 \uc804\uc6d0\uc774 \ucf1c\uc838 \uc788\uace0 \ub124\ud2b8\uc6cc\ud06c\uc5d0 \uc5f0\uacb0\ub418\uc5b4 \uc788\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "existing_config_entry_found": "\uc2dc\ub9ac\uc5bc \ubc88\ud638\uac00 \ub3d9\uc77c\ud55c VIZIO SmartCast \uae30\uae30 \uad6c\uc131 \ud56d\ubaa9\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4. \uc774 \ud56d\ubaa9\uc744 \uad6c\uc131\ud558\ub824\uba74 \uae30\uc874 \ud56d\ubaa9\uc744 \uc0ad\uc81c\ud574\uc57c \ud569\ub2c8\ub2e4." }, "step": { "pair_tv": { @@ -19,11 +19,11 @@ "title": "\ud398\uc5b4\ub9c1 \uacfc\uc815 \ub05d\ub0b4\uae30" }, "pairing_complete": { - "description": "VIZIO SmartCast \uae30\uae30\uac00 Home Assistant \uc5d0 \uc5f0\uacb0\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "description": "VIZIO SmartCast \uae30\uae30\uac00 Home Assistant\uc5d0 \uc5f0\uacb0\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "title": "\ud398\uc5b4\ub9c1 \uc644\ub8cc" }, "pairing_complete_import": { - "description": "VIZIO SmartCast \uae30\uae30\uac00 Home Assistant \uc5d0 \uc5f0\uacb0\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \n\n\uc561\uc138\uc2a4 \ud1a0\ud070\uc740 '**{access_token}**' \uc785\ub2c8\ub2e4.", + "description": "VIZIO SmartCast \uae30\uae30\uac00 Home Assistant\uc5d0 \uc5f0\uacb0\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \n\n\uc561\uc138\uc2a4 \ud1a0\ud070\uc740 '**{access_token}**' \uc785\ub2c8\ub2e4.", "title": "\ud398\uc5b4\ub9c1 \uc644\ub8cc" }, "user": { @@ -46,7 +46,7 @@ "include_or_exclude": "\uc571\uc744 \ud3ec\ud568 \ub610\ub294 \uc81c\uc678\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "volume_step": "\ubcfc\ub968 \ub2e8\uacc4 \ud06c\uae30" }, - "description": "\uc2a4\ub9c8\ud2b8 TV \uac00 \uc788\ub294 \uacbd\uc6b0 \uc120\ud0dd\uc0ac\ud56d\uc73c\ub85c \uc18c\uc2a4 \ubaa9\ub85d\uc5d0 \ud3ec\ud568 \ub610\ub294 \uc81c\uc678\ud560 \uc571\uc744 \uc120\ud0dd\ud558\uc5ec \uc18c\uc2a4 \ubaa9\ub85d\uc744 \ud544\ud130\ub9c1\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "\uc2a4\ub9c8\ud2b8 TV\uac00 \uc788\ub294 \uacbd\uc6b0 \uc120\ud0dd\uc0ac\ud56d\uc73c\ub85c \uc18c\uc2a4 \ubaa9\ub85d\uc5d0 \ud3ec\ud568 \ub610\ub294 \uc81c\uc678\ud560 \uc571\uc744 \uc120\ud0dd\ud558\uc5ec \uc18c\uc2a4 \ubaa9\ub85d\uc744 \ud544\ud130\ub9c1\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "VIZIO SmartCast \uae30\uae30 \uc635\uc158 \uc5c5\ub370\uc774\ud2b8\ud558\uae30" } } diff --git a/homeassistant/components/vizio/translations/nl.json b/homeassistant/components/vizio/translations/nl.json index 48fd831d61c..4472a9cb9c0 100644 --- a/homeassistant/components/vizio/translations/nl.json +++ b/homeassistant/components/vizio/translations/nl.json @@ -12,7 +12,7 @@ "step": { "pair_tv": { "data": { - "pin": "PIN" + "pin": "PIN-code" }, "description": "Uw TV zou een code moeten weergeven. Voer die code in het formulier in en ga dan door naar de volgende stap om de koppeling te voltooien.", "title": "Voltooi het koppelingsproces" diff --git a/homeassistant/components/volumio/translations/hu.json b/homeassistant/components/volumio/translations/hu.json index 3b2d79a34a7..e58f0666039 100644 --- a/homeassistant/components/volumio/translations/hu.json +++ b/homeassistant/components/volumio/translations/hu.json @@ -1,7 +1,24 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Nem lehet csatlakozni a felfedezett Volumi\u00f3hoz" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "discovery_confirm": { + "description": "Szeretn\u00e9d hozz\u00e1adni a Volumio (`{name}`)-t a Home Assistant-hoz?", + "title": "Felfedezett Volumio" + }, + "user": { + "data": { + "host": "Hoszt", + "port": "Port" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/volumio/translations/id.json b/homeassistant/components/volumio/translations/id.json new file mode 100644 index 00000000000..210c6eca87d --- /dev/null +++ b/homeassistant/components/volumio/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Tidak dapat terhubung ke Volumio yang ditemukan" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "discovery_confirm": { + "description": "Ingin menambahkan Volumio (`{name}`) ke Home Assistant?", + "title": "Volumio yang ditemukan" + }, + "user": { + "data": { + "host": "Host", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/volumio/translations/ko.json b/homeassistant/components/volumio/translations/ko.json index 2c630e533ff..75ff87b41c8 100644 --- a/homeassistant/components/volumio/translations/ko.json +++ b/homeassistant/components/volumio/translations/ko.json @@ -1,13 +1,18 @@ { "config": { "abort": { - "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\ubc1c\uacac\ub41c Volumio\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { + "discovery_confirm": { + "description": "Home Assistant\uc5d0 Volumio (`{name}`)\uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\ubc1c\uacac\ub41c Volumio" + }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8", diff --git a/homeassistant/components/volumio/translations/nl.json b/homeassistant/components/volumio/translations/nl.json index 9e11dbad82b..96538422fe0 100644 --- a/homeassistant/components/volumio/translations/nl.json +++ b/homeassistant/components/volumio/translations/nl.json @@ -1,13 +1,18 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken met Volumio" }, "error": { "cannot_connect": "Kan geen verbinding maken", "unknown": "Onverwachte fout" }, "step": { + "discovery_confirm": { + "description": "Wilt u Volumio (`{name}`) toevoegen aan Home Assistant?", + "title": "Volumio ontdekt" + }, "user": { "data": { "host": "Host", diff --git a/homeassistant/components/water_heater/translations/ca.json b/homeassistant/components/water_heater/translations/ca.json index 8c1de7124f5..022b5e887d8 100644 --- a/homeassistant/components/water_heater/translations/ca.json +++ b/homeassistant/components/water_heater/translations/ca.json @@ -4,5 +4,16 @@ "turn_off": "Apaga {entity_name}", "turn_on": "Engega {entity_name}" } + }, + "state": { + "_": { + "eco": "Eco", + "electric": "El\u00e8ctric", + "gas": "Gas", + "heat_pump": "Bomba de calor", + "high_demand": "Alta demanda", + "off": "OFF", + "performance": "Rendiment" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/et.json b/homeassistant/components/water_heater/translations/et.json index fbb3d11424f..cba6b8274d9 100644 --- a/homeassistant/components/water_heater/translations/et.json +++ b/homeassistant/components/water_heater/translations/et.json @@ -4,5 +4,16 @@ "turn_off": "L\u00fclita {entity_name} v\u00e4lja", "turn_on": "L\u00fclita {entity_name} sisse" } + }, + "state": { + "_": { + "eco": "\u00d6ko", + "electric": "Elektriline", + "gas": "Gaas", + "heat_pump": "Soojuspump", + "high_demand": "Suur n\u00f5udlus", + "off": "V\u00e4lja l\u00fclitatud", + "performance": "J\u00f5udlus" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/fr.json b/homeassistant/components/water_heater/translations/fr.json index ac72ffab883..84c9b730b22 100644 --- a/homeassistant/components/water_heater/translations/fr.json +++ b/homeassistant/components/water_heater/translations/fr.json @@ -4,5 +4,16 @@ "turn_off": "\u00c9teindre {entity_name}", "turn_on": "Allumer {entity_name}" } + }, + "state": { + "_": { + "eco": "\u00c9co", + "electric": "\u00c9lectrique", + "gas": "Gaz", + "heat_pump": "Pompe \u00e0 chaleur", + "high_demand": "Demande \u00e9lev\u00e9e", + "off": "Inactif", + "performance": "Performance" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/id.json b/homeassistant/components/water_heater/translations/id.json new file mode 100644 index 00000000000..591f96ffc4f --- /dev/null +++ b/homeassistant/components/water_heater/translations/id.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "turn_off": "Matikan {entity_name}", + "turn_on": "Nyalakan {entity_name}" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/ko.json b/homeassistant/components/water_heater/translations/ko.json new file mode 100644 index 00000000000..8c591c19918 --- /dev/null +++ b/homeassistant/components/water_heater/translations/ko.json @@ -0,0 +1,19 @@ +{ + "device_automation": { + "action_type": { + "turn_off": "{entity_name}\uc744(\ub97c) \ub044\uae30", + "turn_on": "{entity_name}\uc744(\ub97c) \ucf1c\uae30" + } + }, + "state": { + "_": { + "eco": "\uc808\uc57d", + "electric": "\uc804\uae30", + "gas": "\uac00\uc2a4", + "heat_pump": "\ud788\ud2b8 \ud38c\ud504", + "high_demand": "\uace0\uc131\ub2a5", + "off": "\uaebc\uc9d0", + "performance": "\uace0\ud6a8\uc728" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/pt.json b/homeassistant/components/water_heater/translations/pt.json index 2278e7701aa..072a81b682c 100644 --- a/homeassistant/components/water_heater/translations/pt.json +++ b/homeassistant/components/water_heater/translations/pt.json @@ -4,5 +4,10 @@ "turn_off": "Desligar {entity_name}", "turn_on": "Ligar {entity_name}" } + }, + "state": { + "_": { + "off": "Desligado" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/ru.json b/homeassistant/components/water_heater/translations/ru.json index 7c703da39d5..b3491f82e5e 100644 --- a/homeassistant/components/water_heater/translations/ru.json +++ b/homeassistant/components/water_heater/translations/ru.json @@ -4,5 +4,16 @@ "turn_off": "{entity_name}: \u0432\u044b\u043a\u043b\u044e\u0447\u0438\u0442\u044c", "turn_on": "{entity_name}: \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c" } + }, + "state": { + "_": { + "eco": "\u042d\u043a\u043e", + "electric": "\u042d\u043b\u0435\u043a\u0442\u0440\u0438\u0447\u0435\u0441\u043a\u0438\u0439", + "gas": "\u0413\u0430\u0437", + "heat_pump": "\u0422\u0435\u043f\u043b\u043e\u0432\u043e\u0439 \u043d\u0430\u0441\u043e\u0441", + "high_demand": "\u0411\u043e\u043b\u044c\u0448\u0430\u044f \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0430", + "off": "\u0412\u044b\u043a\u043b", + "performance": "\u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c" + } } } \ No newline at end of file diff --git a/homeassistant/components/wemo/translations/hu.json b/homeassistant/components/wemo/translations/hu.json new file mode 100644 index 00000000000..ab799e90c74 --- /dev/null +++ b/homeassistant/components/wemo/translations/hu.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wemo/translations/id.json b/homeassistant/components/wemo/translations/id.json new file mode 100644 index 00000000000..af0b3128cb9 --- /dev/null +++ b/homeassistant/components/wemo/translations/id.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "confirm": { + "description": "Ingin menyiapkan Wemo?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wemo/translations/ko.json b/homeassistant/components/wemo/translations/ko.json index 5673c049422..704b1126261 100644 --- a/homeassistant/components/wemo/translations/ko.json +++ b/homeassistant/components/wemo/translations/ko.json @@ -2,11 +2,11 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { - "description": "Wemo \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "Wemo\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/wiffi/translations/hu.json b/homeassistant/components/wiffi/translations/hu.json index 21320afea78..c623f6ddaba 100644 --- a/homeassistant/components/wiffi/translations/hu.json +++ b/homeassistant/components/wiffi/translations/hu.json @@ -2,6 +2,22 @@ "config": { "abort": { "start_server_failed": "A szerver ind\u00edt\u00e1sa nem siker\u00fclt." + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s (perc)" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/id.json b/homeassistant/components/wiffi/translations/id.json new file mode 100644 index 00000000000..0022f83b0a1 --- /dev/null +++ b/homeassistant/components/wiffi/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "addr_in_use": "Port server sudah digunakan.", + "start_server_failed": "Gagal memulai server." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "title": "Siapkan server TCP untuk perangkat WIFFI" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Tenggang waktu (menit)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/nl.json b/homeassistant/components/wiffi/translations/nl.json index af14d1942a7..966f9a18e41 100644 --- a/homeassistant/components/wiffi/translations/nl.json +++ b/homeassistant/components/wiffi/translations/nl.json @@ -7,7 +7,7 @@ "step": { "user": { "data": { - "port": "Server poort" + "port": "Poort" }, "title": "TCP-server instellen voor WIFFI-apparaten" } diff --git a/homeassistant/components/wilight/translations/hu.json b/homeassistant/components/wilight/translations/hu.json index 3b2d79a34a7..26cdaf6a025 100644 --- a/homeassistant/components/wilight/translations/hu.json +++ b/homeassistant/components/wilight/translations/hu.json @@ -2,6 +2,11 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "step": { + "confirm": { + "title": "WiLight" + } } } } \ No newline at end of file diff --git a/homeassistant/components/wilight/translations/id.json b/homeassistant/components/wilight/translations/id.json new file mode 100644 index 00000000000..dae7b0bd16a --- /dev/null +++ b/homeassistant/components/wilight/translations/id.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "not_supported_device": "Perangkat WiLight ini saat ini tidak didukung.", + "not_wilight_device": "Perangkat ini bukan perangkat WiLight" + }, + "flow_title": "WiLight: {name}", + "step": { + "confirm": { + "description": "Apakah Anda ingin menyiapkan WiLight {name}?\n\nIni mendukung: {components}", + "title": "WiLight" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wilight/translations/ko.json b/homeassistant/components/wilight/translations/ko.json index e18250811fc..1b53a1ba544 100644 --- a/homeassistant/components/wilight/translations/ko.json +++ b/homeassistant/components/wilight/translations/ko.json @@ -2,11 +2,14 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "not_wilight_device": "\uc774 \uc7a5\uce58\ub294 WiLight\uac00 \uc544\ub2d9\ub2c8\ub2e4." + "not_supported_device": "\uc774 WiLight\ub294 \ud604\uc7ac \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", + "not_wilight_device": "\uc774 \uae30\uae30\ub294 WiLight\uac00 \uc544\ub2d9\ub2c8\ub2e4" }, + "flow_title": "WiLight: {name}", "step": { "confirm": { - "description": "WiLight {name} \uc744 \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? \n\n \uc9c0\uc6d0 : {components}" + "description": "WiLight {name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\n\n\uc9c0\uc6d0 \uae30\uae30: {components}", + "title": "WiLight" } } } diff --git a/homeassistant/components/withings/translations/hu.json b/homeassistant/components/withings/translations/hu.json index 1486048adfc..d39410b6d17 100644 --- a/homeassistant/components/withings/translations/hu.json +++ b/homeassistant/components/withings/translations/hu.json @@ -2,15 +2,19 @@ "config": { "abort": { "already_configured": "A profil konfigur\u00e1ci\u00f3ja friss\u00edtve.", - "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s az \u00e9rv\u00e9nyes\u00edt\u00e9si url gener\u00e1l\u00e1sa sor\u00e1n.", - "missing_configuration": "A Withings integr\u00e1ci\u00f3 nincs konfigur\u00e1lva. K\u00e9rj\u00fck, k\u00f6vesse a dokument\u00e1ci\u00f3t." + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz." }, "create_entry": { "default": "A Withings sikeresen hiteles\u00edtett." }, + "error": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, "step": { "pick_implementation": { - "title": "V\u00e1lassza ki a hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" }, "profile": { "data": { @@ -18,6 +22,9 @@ }, "description": "Melyik profilt v\u00e1lasztottad ki a Withings weboldalon? Fontos, hogy a profilok egyeznek, k\u00fcl\u00f6nben az adatok helytelen c\u00edmk\u00e9vel lesznek ell\u00e1tva.", "title": "Felhaszn\u00e1l\u00f3i profil." + }, + "reauth": { + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" } } } diff --git a/homeassistant/components/withings/translations/id.json b/homeassistant/components/withings/translations/id.json new file mode 100644 index 00000000000..e254e61d91e --- /dev/null +++ b/homeassistant/components/withings/translations/id.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "already_configured": "Konfigurasi diperbarui untuk profil.", + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})" + }, + "create_entry": { + "default": "Berhasil mengautentikasi dengan Withings." + }, + "error": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "flow_title": "Withings: {profile}", + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + }, + "profile": { + "data": { + "profile": "Nama Profil" + }, + "description": "Berikan nama profil unik untuk data ini. Umumnya namanya sama dengan nama profil yang Anda pilih di langkah sebelumnya.", + "title": "Profil Pengguna." + }, + "reauth": { + "description": "Profil \"{profile}\" perlu diautentikasi ulang untuk terus menerima data Withings.", + "title": "Autentikasi Ulang Integrasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/withings/translations/ko.json b/homeassistant/components/withings/translations/ko.json index 38ed96dca67..4823061de41 100644 --- a/homeassistant/components/withings/translations/ko.json +++ b/homeassistant/components/withings/translations/ko.json @@ -7,7 +7,7 @@ "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "create_entry": { - "default": "Withings \ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "default": "Withings\ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "error": { "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" @@ -26,7 +26,7 @@ }, "reauth": { "description": "Withings \ub370\uc774\ud130\ub97c \uacc4\uc18d \uc218\uc2e0\ud558\ub824\uba74 \"{profile}\" \ud504\ub85c\ud544\uc744 \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4.", - "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d" + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" } } } diff --git a/homeassistant/components/withings/translations/nl.json b/homeassistant/components/withings/translations/nl.json index 21b2d6b11e9..5b099b952e5 100644 --- a/homeassistant/components/withings/translations/nl.json +++ b/homeassistant/components/withings/translations/nl.json @@ -12,6 +12,7 @@ "error": { "already_configured": "Account is al geconfigureerd" }, + "flow_title": "Withings: {profile}", "step": { "pick_implementation": { "title": "Kies Authenticatiemethode" @@ -24,7 +25,7 @@ "title": "Gebruikersprofiel." }, "reauth": { - "title": "Profiel opnieuw verifi\u00ebren" + "title": "Verifieer de integratie opnieuw" } } } diff --git a/homeassistant/components/wled/translations/hu.json b/homeassistant/components/wled/translations/hu.json index b89bd72f704..0d2c85e477d 100644 --- a/homeassistant/components/wled/translations/hu.json +++ b/homeassistant/components/wled/translations/hu.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Ez a WLED eszk\u00f6z m\u00e1r konfigur\u00e1lva van.", + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "error": { diff --git a/homeassistant/components/wled/translations/id.json b/homeassistant/components/wled/translations/id.json new file mode 100644 index 00000000000..6437dfaf83e --- /dev/null +++ b/homeassistant/components/wled/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "WLED: {name}", + "step": { + "user": { + "data": { + "host": "Host" + }, + "description": "Siapkan WLED Anda untuk diintegrasikan dengan Home Assistant." + }, + "zeroconf_confirm": { + "description": "Ingin menambahkan WLED `{name}` ke Home Assistant?", + "title": "Peranti WLED yang ditemukan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wled/translations/ko.json b/homeassistant/components/wled/translations/ko.json index d1945707b6d..69f30eb7516 100644 --- a/homeassistant/components/wled/translations/ko.json +++ b/homeassistant/components/wled/translations/ko.json @@ -13,10 +13,10 @@ "data": { "host": "\ud638\uc2a4\ud2b8" }, - "description": "Home Assistant \uc5d0 WLED \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4." + "description": "Home Assistant\uc5d0 WLED \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4." }, "zeroconf_confirm": { - "description": "Home Assistant \uc5d0 WLED `{name}` \uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Home Assistant\uc5d0 WLED `{name}`\uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "\ubc1c\uacac\ub41c WLED \uae30\uae30" } } diff --git a/homeassistant/components/wled/translations/nl.json b/homeassistant/components/wled/translations/nl.json index 329716e2cd5..3e7b16a7f4a 100644 --- a/homeassistant/components/wled/translations/nl.json +++ b/homeassistant/components/wled/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Dit WLED-apparaat is al geconfigureerd.", + "already_configured": "Apparaat is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken" }, "error": { @@ -11,7 +11,7 @@ "step": { "user": { "data": { - "host": "Hostnaam of IP-adres" + "host": "Host" }, "description": "Stel uw WLED-integratie in met Home Assistant." }, diff --git a/homeassistant/components/wolflink/translations/hu.json b/homeassistant/components/wolflink/translations/hu.json index 3b2d79a34a7..c7bb483155d 100644 --- a/homeassistant/components/wolflink/translations/hu.json +++ b/homeassistant/components/wolflink/translations/hu.json @@ -2,6 +2,24 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "device": { + "data": { + "device_name": "Eszk\u00f6z" + } + }, + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/id.json b/homeassistant/components/wolflink/translations/id.json new file mode 100644 index 00000000000..64d692efa7d --- /dev/null +++ b/homeassistant/components/wolflink/translations/id.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "device": { + "data": { + "device_name": "Perangkat" + }, + "title": "Pilih perangkat WOLF" + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Koneksi WOLF SmartSet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/nl.json b/homeassistant/components/wolflink/translations/nl.json index 7fb1b867cdd..d3cde252467 100644 --- a/homeassistant/components/wolflink/translations/nl.json +++ b/homeassistant/components/wolflink/translations/nl.json @@ -4,10 +4,16 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, "step": { + "device": { + "data": { + "device_name": "Apparaat" + } + }, "user": { "data": { "password": "Wachtwoord", diff --git a/homeassistant/components/wolflink/translations/sensor.de.json b/homeassistant/components/wolflink/translations/sensor.de.json index 9680716cd19..17c365e88c4 100644 --- a/homeassistant/components/wolflink/translations/sensor.de.json +++ b/homeassistant/components/wolflink/translations/sensor.de.json @@ -1,6 +1,7 @@ { "state": { "wolflink__state": { + "partymodus": "Party-Modus", "permanent": "Permanent", "solarbetrieb": "Solarmodus", "sparbetrieb": "Sparmodus", diff --git a/homeassistant/components/wolflink/translations/sensor.hu.json b/homeassistant/components/wolflink/translations/sensor.hu.json new file mode 100644 index 00000000000..2d8cdda9315 --- /dev/null +++ b/homeassistant/components/wolflink/translations/sensor.hu.json @@ -0,0 +1,7 @@ +{ + "state": { + "wolflink__state": { + "permanent": "\u00c1lland\u00f3" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/sensor.id.json b/homeassistant/components/wolflink/translations/sensor.id.json new file mode 100644 index 00000000000..12b755a9c24 --- /dev/null +++ b/homeassistant/components/wolflink/translations/sensor.id.json @@ -0,0 +1,47 @@ +{ + "state": { + "wolflink__state": { + "1_x_warmwasser": "1 x DHW", + "aktiviert": "Diaktifkan", + "antilegionellenfunktion": "Fungsi Anti-legionella", + "aus": "Dinonaktifkan", + "auto": "Otomatis", + "auto_off_cool": "AutoOffCool", + "auto_on_cool": "AutoOnCool", + "automatik_aus": "Otomatis MATI", + "automatik_ein": "Otomatis NYALA", + "bereit_keine_ladung": "Siap, tidak memuat", + "betrieb_ohne_brenner": "Bekerja tanpa pembakar", + "cooling": "Mendinginkan", + "deaktiviert": "Tidak aktif", + "dhw_prior": "DHWPrior", + "eco": "Eco", + "ein": "Diaktifkan", + "externe_deaktivierung": "Penonaktifan eksternal", + "fernschalter_ein": "Kontrol jarak jauh diaktifkan", + "heizung": "Memanaskan", + "initialisierung": "Inisialisasi", + "kalibration": "Kalibrasi", + "kalibration_heizbetrieb": "Kalibrasi mode pemanasan", + "kalibration_kombibetrieb": "Kalibrasi mode kombi", + "kalibration_warmwasserbetrieb": "Kalibrasi DHW", + "kaskadenbetrieb": "Operasi bertingkat", + "kombibetrieb": "Mode kombi", + "mindest_kombizeit": "Waktu kombi minimum", + "nur_heizgerat": "Hanya boiler", + "parallelbetrieb": "Mode paralel", + "partymodus": "Mode pesta", + "permanent": "Permanen", + "permanentbetrieb": "Mode permanen", + "reduzierter_betrieb": "Mode terbatas", + "schornsteinfeger": "Uji emisi", + "smart_grid": "SmartGrid", + "smart_home": "SmartHome", + "solarbetrieb": "Mode surya", + "sparbetrieb": "Mode ekonomi", + "sparen": "Ekonomi", + "urlaubsmodus": "Mode liburan", + "ventilprufung": "Uji katup" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/sensor.ko.json b/homeassistant/components/wolflink/translations/sensor.ko.json index 71fde05a07a..2597560be1b 100644 --- a/homeassistant/components/wolflink/translations/sensor.ko.json +++ b/homeassistant/components/wolflink/translations/sensor.ko.json @@ -8,14 +8,80 @@ "aktiviert": "\ud65c\uc131\ud654", "antilegionellenfunktion": "\ud56d \ub808\uc9c0\uc624\ub12c\ub77c\uade0 \uae30\ub2a5", "at_abschaltung": "OT \ub044\uae30", - "at_frostschutz": "OT \ube59\uacb0 \ubcf4\ud638", + "at_frostschutz": "OT \ub3d9\ud30c \ubc29\uc9c0", "aus": "\ube44\ud65c\uc131\ud654", "auto": "\uc790\ub3d9", "auto_off_cool": "\ub0c9\ubc29 \uc790\ub3d9 \uaebc\uc9d0", "auto_on_cool": "\ub0c9\ubc29 \uc790\ub3d9 \ucf1c\uc9d0", "automatik_aus": "\uc790\ub3d9 \uaebc\uc9d0", "automatik_ein": "\uc790\ub3d9 \ucf1c\uc9d0", - "permanent": "\uc601\uad6c\uc801" + "bereit_keine_ladung": "\uc900\ube44\ub428, \ub85c\ub4dc\ub418\uc9c0\ub294 \uc54a\uc74c", + "betrieb_ohne_brenner": "\ubc84\ub108 \uc5c6\uc774 \uc791\ub3d9", + "cooling": "\ub0c9\ubc29", + "deaktiviert": "\ube44\ud65c\uc131", + "dhw_prior": "DHW \uc6b0\uc120", + "eco": "\uc808\uc57d", + "ein": "\ud65c\uc131\ud654", + "estrichtrocknung": "\uc7a5\uae30\uac04 \uc81c\uc2b5", + "externe_deaktivierung": "\uc678\ubd80 \ube44\ud65c\uc131\ud654", + "fernschalter_ein": "\uc6d0\uaca9 \uc81c\uc5b4 \ud65c\uc131\ud654", + "frost_heizkreis": "\ub09c\ubc29 \ud68c\ub85c \ub3d9\ud30c", + "frost_warmwasser": "DHW \ub3d9\ud30c", + "frostschutz": "\ub3d9\ud30c \ubc29\uc9c0", + "gasdruck": "\uac00\uc2a4 \uc555\ub825", + "glt_betrieb": "BMS \ubaa8\ub4dc", + "gradienten_uberwachung": "\uae30\uc6b8\uae30 \ubaa8\ub2c8\ud130\ub9c1", + "heizbetrieb": "\ub09c\ubc29 \ubaa8\ub4dc", + "heizgerat_mit_speicher": "\uc2e4\ub9b0\ub354 \ubcf4\uc77c\ub7ec", + "heizung": "\ub09c\ubc29", + "initialisierung": "\ucd08\uae30\ud654", + "kalibration": "\ubcf4\uc815", + "kalibration_heizbetrieb": "\ub09c\ubc29 \ubaa8\ub4dc \ubcf4\uc815", + "kalibration_kombibetrieb": "\ucf64\ube44 \ubaa8\ub4dc \ubcf4\uc815", + "kalibration_warmwasserbetrieb": "DHW \ubcf4\uc815", + "kaskadenbetrieb": "\uce90\uc2a4\ucf00\uc774\ub4dc \uc6b4\uc804", + "kombibetrieb": "\ucf64\ube44 \ubaa8\ub4dc", + "kombigerat": "\ucf64\ube44 \ubcf4\uc77c\ub7ec", + "kombigerat_mit_solareinbindung": "\ud0dc\uc591\uc5f4 \ud1b5\ud569\ud615 \ucf64\ube44 \ubcf4\uc77c\ub7ec", + "mindest_kombizeit": "\ucd5c\uc18c \ucf64\ube44 \uc2dc\uac04", + "nachlauf_heizkreispumpe": "\ub09c\ubc29 \ud68c\ub85c \ud38c\ud504 \uc6b4\uc804", + "nachspulen": "\ud50c\ub7ec\uc2dc \ud6c4", + "nur_heizgerat": "\ubcf4\uc77c\ub7ec\ub9cc", + "parallelbetrieb": "\ubcd1\ub82c \ubaa8\ub4dc", + "partymodus": "\ud30c\ud2f0 \ubaa8\ub4dc", + "perm_cooling": "\uc601\uad6c \ub0c9\ubc29", + "permanent": "\uc601\uad6c", + "permanentbetrieb": "\uc601\uad6c \ubaa8\ub4dc", + "reduzierter_betrieb": "\uc81c\ud55c \ubaa8\ub4dc", + "rt_abschaltung": "RT \ub044\uae30", + "rt_frostschutz": "RT \ub3d9\ud30c \ubcf4\ud638", + "ruhekontakt": "\uc0c1\uc2dc \ud3d0\uc1c4(NC)", + "schornsteinfeger": "\ubc30\uae30 \ud14c\uc2a4\ud2b8", + "smart_grid": "\uc2a4\ub9c8\ud2b8\uadf8\ub9ac\ub4dc", + "smart_home": "\uc2a4\ub9c8\ud2b8\ud648", + "softstart": "\uc18c\ud504\ud2b8 \uc2a4\ud0c0\ud2b8", + "solarbetrieb": "\ud0dc\uc591\uc5f4 \ubaa8\ub4dc", + "sparbetrieb": "\uc808\uc57d \ubaa8\ub4dc", + "sparen": "\uc808\uc57d", + "spreizung_hoch": "\ub108\ubb34 \ub113\uc740 dT", + "spreizung_kf": "KF \ud655\uc0b0", + "stabilisierung": "\uc548\uc815\ud654", + "standby": "\uc900\ube44\uc911", + "start": "\uc2dc\uc791", + "storung": "\uc624\ub958", + "taktsperre": "\uc21c\ud658 \ubc29\uc9c0", + "telefonfernschalter": "\uc804\ud654 \uc6d0\uaca9 \uc2a4\uc704\uce58", + "test": "\ud14c\uc2a4\ud2b8", + "tpw": "TPW", + "urlaubsmodus": "\ud734\uc77c \ubaa8\ub4dc", + "ventilprufung": "\ubc38\ube0c \ud14c\uc2a4\ud2b8", + "vorspulen": "\uc785\uc218\uad6c \uccad\uc18c", + "warmwasser": "DHW", + "warmwasser_schnellstart": "DHW \ube60\ub978 \uc2dc\uc791", + "warmwasserbetrieb": "DHW \ubaa8\ub4dc", + "warmwassernachlauf": "DHW \uc6b4\uc804", + "warmwasservorrang": "DHW \uc6b0\uc120\uc21c\uc704", + "zunden": "\uc810\ud654" } } } \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/sensor.nl.json b/homeassistant/components/wolflink/translations/sensor.nl.json index ae205d79aef..02e8e3f8c2e 100644 --- a/homeassistant/components/wolflink/translations/sensor.nl.json +++ b/homeassistant/components/wolflink/translations/sensor.nl.json @@ -1,6 +1,15 @@ { "state": { "wolflink__state": { + "1_x_warmwasser": "1 x DHW", + "auto": "Auto", + "automatik_aus": "Automatisch UIT", + "automatik_ein": "Automatisch AAN", + "cooling": "Koelen", + "deaktiviert": "Inactief", + "dhw_prior": "DHWPrior", + "eco": "Eco", + "ein": "Ingeschakeld", "frost_warmwasser": "DHW vorst", "frostschutz": "Vorstbescherming", "gasdruck": "Gasdruk", @@ -11,7 +20,18 @@ "initialisierung": "Initialisatie", "kalibration": "Kalibratie", "kalibration_heizbetrieb": "Kalibratie verwarmingsmodus", + "kalibration_warmwasserbetrieb": "DHW-kalibratie", + "kombibetrieb": "Combi-modus", + "kombigerat": "Combiketel", + "nur_heizgerat": "Alleen ketel", + "partymodus": "Feestmodus", "permanent": "Permanent", + "permanentbetrieb": "Permanente modus", + "reduzierter_betrieb": "Beperkte modus", + "schornsteinfeger": "Emissietest", + "smart_grid": "SmartGrid", + "smart_home": "SmartHome", + "sparbetrieb": "Spaarstand", "standby": "Stand-by", "start": "Start", "storung": "Fout", @@ -19,7 +39,9 @@ "tpw": "TPW", "urlaubsmodus": "Vakantiemodus", "ventilprufung": "Kleptest", - "warmwasser": "DHW" + "warmwasser": "DHW", + "warmwasserbetrieb": "DHW-modus", + "zunden": "Ontsteking" } } } \ No newline at end of file diff --git a/homeassistant/components/xbox/translations/hu.json b/homeassistant/components/xbox/translations/hu.json index 19f706be1c8..b35b1b8e2fc 100644 --- a/homeassistant/components/xbox/translations/hu.json +++ b/homeassistant/components/xbox/translations/hu.json @@ -1,7 +1,17 @@ { "config": { + "abort": { + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "create_entry": { - "default": "Sikeres autentik\u00e1ci\u00f3" + "default": "Sikeres hiteles\u00edt\u00e9s" + }, + "step": { + "pick_implementation": { + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + } } } } \ No newline at end of file diff --git a/homeassistant/components/xbox/translations/id.json b/homeassistant/components/xbox/translations/id.json new file mode 100644 index 00000000000..ed8106b0144 --- /dev/null +++ b/homeassistant/components/xbox/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xbox/translations/ko.json b/homeassistant/components/xbox/translations/ko.json index 7314d9e3c5c..0765928f8c9 100644 --- a/homeassistant/components/xbox/translations/ko.json +++ b/homeassistant/components/xbox/translations/ko.json @@ -3,7 +3,7 @@ "abort": { "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "create_entry": { "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/xiaomi_aqara/translations/de.json b/homeassistant/components/xiaomi_aqara/translations/de.json index 6b0e25dfcd5..61e865ef016 100644 --- a/homeassistant/components/xiaomi_aqara/translations/de.json +++ b/homeassistant/components/xiaomi_aqara/translations/de.json @@ -14,7 +14,8 @@ "select": { "data": { "select_ip": "IP-Adresse" - } + }, + "description": "F\u00fchre das Setup erneut aus, wenn du zus\u00e4tzliche Gateways verbinden m\u00f6chtest" }, "user": { "data": { diff --git a/homeassistant/components/xiaomi_aqara/translations/hu.json b/homeassistant/components/xiaomi_aqara/translations/hu.json index 1a69e20c6b1..295fcef83fe 100644 --- a/homeassistant/components/xiaomi_aqara/translations/hu.json +++ b/homeassistant/components/xiaomi_aqara/translations/hu.json @@ -2,11 +2,12 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1ci\u00f3s folyamat m\u00e1r fut" + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "not_xiaomi_aqara": "Nem egy Xiaomi Aqara Gateway, a felfedezett eszk\u00f6z nem egyezett az ismert \u00e1tj\u00e1r\u00f3kkal" }, "error": { "discovery_error": "Nem siker\u00fclt felfedezni a Xiaomi Aqara K\u00f6zponti egys\u00e9get, pr\u00f3b\u00e1lja meg interf\u00e9szk\u00e9nt haszn\u00e1lni a HomeAssistant futtat\u00f3 eszk\u00f6z IP-j\u00e9t", - "invalid_host": " , l\u00e1sd: https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem", + "invalid_host": "\u00c9rv\u00e9nytelen hosztn\u00e9v vagy IP-c\u00edm, l\u00e1sd: https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem", "invalid_interface": "\u00c9rv\u00e9nytelen h\u00e1l\u00f3zati interf\u00e9sz", "invalid_key": "\u00c9rv\u00e9nytelen kulcs", "invalid_mac": "\u00c9rv\u00e9nytelen Mac-c\u00edm" @@ -17,7 +18,7 @@ "data": { "select_ip": "IP c\u00edm" }, - "description": "Futtassa \u00fajra a be\u00e1ll\u00edt\u00e1st, ha egy m\u00e1sik K\u00f6zponti egys\u00e9get szeretne csatlakoztatni", + "description": "Futtassa \u00fajra a be\u00e1ll\u00edt\u00e1st, ha egy m\u00e1sik k\u00f6zponti egys\u00e9get szeretne csatlakoztatni", "title": "V\u00e1lassza ki a csatlakoztatni k\u00edv\u00e1nt Xiaomi Aqara K\u00f6zponti egys\u00e9get" }, "settings": { @@ -25,7 +26,7 @@ "key": "K\u00f6zponti egys\u00e9g kulcsa", "name": "K\u00f6zponti egys\u00e9g neve" }, - "description": "A kulcs (jelsz\u00f3) az al\u00e1bbi oktat\u00f3anyag seg\u00edts\u00e9g\u00e9vel t\u00f6lthet\u0151 le: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Ha a kulcs nincs megadva, csak az \u00e9rz\u00e9kel\u0151k f\u00e9rhetnek hozz\u00e1", + "description": "A kulcs (jelsz\u00f3) az al\u00e1bbi oktat\u00f3anyag seg\u00edts\u00e9g\u00e9vel t\u00f6lthet\u0151 le: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Ha a kulcs nincs megadva, csak az \u00e9rz\u00e9kel\u0151k lesznek hozz\u00e1f\u00e9rhet\u0151k", "title": "Xiaomi Aqara k\u00f6zponti egys\u00e9g, opcion\u00e1lis be\u00e1ll\u00edt\u00e1sok" }, "user": { @@ -34,7 +35,7 @@ "interface": "A haszn\u00e1lni k\u00edv\u00e1nt h\u00e1l\u00f3zati interf\u00e9sz", "mac": "Mac-c\u00edm (opcion\u00e1lis)" }, - "description": "Csatlakozzon a Xiaomi Aqara k\u00f6zponti egys\u00e9ghez, ha az IP- \u00e9s a mac-c\u00edm \u00fcresen marad, akkor az automatikus felder\u00edt\u00e9st haszn\u00e1lja", + "description": "Csatlakozzon a Xiaomi Aqara k\u00f6zponti egys\u00e9ghez, ha az IP- \u00e9s a MAC-c\u00edm \u00fcresen marad, akkor az automatikus felder\u00edt\u00e9st haszn\u00e1lja", "title": "Xiaomi Aqara k\u00f6zponti egys\u00e9g" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/id.json b/homeassistant/components/xiaomi_aqara/translations/id.json new file mode 100644 index 00000000000..5a2acfa330a --- /dev/null +++ b/homeassistant/components/xiaomi_aqara/translations/id.json @@ -0,0 +1,43 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "not_xiaomi_aqara": "Bukan Gateway Xiaomi Aqara, perangkat yang ditemukan tidak sesuai dengan gateway yang dikenal" + }, + "error": { + "discovery_error": "Gagal menemukan Xiaomi Aqara Gateway, coba gunakan IP perangkat yang menjalankan HomeAssistant sebagai antarmuka", + "invalid_host": "Nama host atau alamat IP tidak valid, lihat https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem", + "invalid_interface": "Antarmuka jaringan tidak valid", + "invalid_key": "Kunci gateway tidak valid", + "invalid_mac": "Alamat MAC Tidak Valid" + }, + "flow_title": "Xiaomi Aqara Gateway: {name}", + "step": { + "select": { + "data": { + "select_ip": "Alamat IP" + }, + "description": "Jalankan penyiapan lagi jika Anda ingin menghubungkan gateway lainnya", + "title": "Pilih Gateway Xiaomi Aqara yang ingin dihubungkan" + }, + "settings": { + "data": { + "key": "Kunci gateway Anda", + "name": "Nama Gateway" + }, + "description": "Kunci (kata sandi) dapat diambil menggunakan tutorial ini: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Jika kunci tidak disediakan, hanya sensor yang akan dapat diakses", + "title": "Xiaomi Aqara Gateway, pengaturan opsional" + }, + "user": { + "data": { + "host": "Alamat IP (opsional)", + "interface": "Antarmuka jaringan yang akan digunakan", + "mac": "Alamat MAC (opsional)" + }, + "description": "Hubungkan ke Xiaomi Aqara Gateway Anda, jika alamat IP dan MAC dibiarkan kosong, penemuan otomatis digunakan", + "title": "Xiaomi Aqara Gateway" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_aqara/translations/ko.json b/homeassistant/components/xiaomi_aqara/translations/ko.json index 7c15bc572e5..131975c1d8c 100644 --- a/homeassistant/components/xiaomi_aqara/translations/ko.json +++ b/homeassistant/components/xiaomi_aqara/translations/ko.json @@ -7,9 +7,10 @@ }, "error": { "discovery_error": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \ubc1c\uacac\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. HomeAssistant \ub97c \uc778\ud130\ud398\uc774\uc2a4\ub85c \uc0ac\uc6a9\ud558\ub294 \uae30\uae30\uc758 IP \ub85c \uc2dc\ub3c4\ud574\ubcf4\uc138\uc694.", - "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem \uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694", + "invalid_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem \uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694", "invalid_interface": "\ub124\ud2b8\uc6cc\ud06c \uc778\ud130\ud398\uc774\uc2a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "invalid_key": "\uac8c\uc774\ud2b8\uc6e8\uc774 \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "invalid_key": "\uac8c\uc774\ud2b8\uc6e8\uc774 \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_mac": "Mac \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "flow_title": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774: {name}", "step": { @@ -25,14 +26,14 @@ "key": "\uac8c\uc774\ud2b8\uc6e8\uc774 \ud0a4", "name": "\uac8c\uc774\ud2b8\uc6e8\uc774 \uc774\ub984" }, - "description": "\ud0a4(\ube44\ubc00\ubc88\ud638)\ub97c \uc5bb\uc740 \ubc29\ubc95\uc740 \ub2e4\uc74c\uc758 \uc548\ub0b4\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. \ud0a4\uac00 \uc81c\uacf5\ub418\uc9c0 \uc54a\uc73c\uba74 \uc13c\uc11c\uc5d0\ub9cc \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "\ud0a4\uac00 \uc81c\uacf5\ub418\uc9c0 \uc54a\uc73c\uba74 \uc13c\uc11c\uc5d0\ub9cc \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ud0a4(\ube44\ubc00\ubc88\ud638)\ub97c \uc5bb\ub294 \ubc29\ubc95\uc740 \ub2e4\uc74c\uc758 \uc548\ub0b4\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz", "title": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774 \ucd94\uac00 \uc124\uc815\ud558\uae30" }, "user": { "data": { "host": "IP \uc8fc\uc18c (\uc120\ud0dd \uc0ac\ud56d)", "interface": "\uc0ac\uc6a9\ud560 \ub124\ud2b8\uc6cc\ud06c \uc778\ud130\ud398\uc774\uc2a4", - "mac": "Mac \uc8fc\uc18c(\uc120\ud0dd \uc0ac\ud56d)" + "mac": "Mac \uc8fc\uc18c (\uc120\ud0dd \uc0ac\ud56d)" }, "description": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud569\ub2c8\ub2e4. IP \uc8fc\uc18c \ubc0f MAC \uc8fc\uc18c\ub97c \ube44\uc6cc\ub450\uba74 \uc790\ub3d9 \uac80\uc0c9\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4", "title": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774" diff --git a/homeassistant/components/xiaomi_aqara/translations/nl.json b/homeassistant/components/xiaomi_aqara/translations/nl.json index 81f984a5a05..0e5283c485e 100644 --- a/homeassistant/components/xiaomi_aqara/translations/nl.json +++ b/homeassistant/components/xiaomi_aqara/translations/nl.json @@ -2,10 +2,13 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang" + "already_in_progress": "De configuratiestroom is al aan de gang", + "not_xiaomi_aqara": "Geen Xiaomi Aqara Gateway, ontdekt apparaat kwam niet overeen met bekende gateways" }, "error": { "invalid_host": "Ongeldige hostnaam of IP-adres, zie https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem", + "invalid_interface": "Ongeldige netwerkinterface", + "invalid_key": "Ongeldige gatewaysleutel", "invalid_mac": "Ongeldig MAC-adres" }, "flow_title": "Xiaomi Aqara Gateway: {name}", @@ -18,11 +21,16 @@ "title": "Selecteer de Xiaomi Aqara Gateway waarmee u verbinding wilt maken" }, "settings": { + "data": { + "key": "De sleutel van uw gateway", + "name": "Naam van de Gateway" + }, "description": "De sleutel (wachtwoord) kan worden opgehaald met behulp van deze tutorial: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Als de sleutel niet wordt meegeleverd, zijn alleen sensoren toegankelijk" }, "user": { "data": { "host": "IP-adres (optioneel)", + "interface": "De netwerkinterface die moet worden gebruikt", "mac": "MAC-adres (optioneel)" }, "description": "Maak verbinding met uw Xiaomi Aqara Gateway, als de IP- en mac-adressen leeg worden gelaten, wordt automatische detectie gebruikt", diff --git a/homeassistant/components/xiaomi_miio/translations/bg.json b/homeassistant/components/xiaomi_miio/translations/bg.json new file mode 100644 index 00000000000..bd5387fe8f9 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "device": { + "data": { + "host": "IP \u0430\u0434\u0440\u0435\u0441" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/ca.json b/homeassistant/components/xiaomi_miio/translations/ca.json index 170d14fc6dc..f8dee0efe69 100644 --- a/homeassistant/components/xiaomi_miio/translations/ca.json +++ b/homeassistant/components/xiaomi_miio/translations/ca.json @@ -18,7 +18,7 @@ "name": "Nom del dispositiu", "token": "Token d'API" }, - "description": "Necessitar\u00e0s el Token d'API de 32 car\u00e0cters, consulta les instruccions a https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token. Tingues en compte que aquest Token d'API \u00e9s diferent a la clau utilitzada per la integraci\u00f3 Xiaomi Aqara.", + "description": "Necessitar\u00e0s el Token d'API de 32 car\u00e0cters, consulta les instruccions a https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token. Tingues en compte que aquest Token d'API \u00e9s diferent a la clau utilitzada per la integraci\u00f3 Xiaomi Aqara.", "title": "Connexi\u00f3 amb un dispositiu Xiaomi Miio o una passarel\u00b7la de Xiaomi" }, "gateway": { diff --git a/homeassistant/components/xiaomi_miio/translations/de.json b/homeassistant/components/xiaomi_miio/translations/de.json index 7cf11a1085e..2817d18b578 100644 --- a/homeassistant/components/xiaomi_miio/translations/de.json +++ b/homeassistant/components/xiaomi_miio/translations/de.json @@ -6,16 +6,20 @@ }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", - "no_device_selected": "Kein Ger\u00e4t ausgew\u00e4hlt, bitte w\u00e4hle ein Ger\u00e4t aus." + "no_device_selected": "Kein Ger\u00e4t ausgew\u00e4hlt, bitte w\u00e4hle ein Ger\u00e4t aus.", + "unknown_device": "Das Ger\u00e4temodell ist nicht bekannt und das Ger\u00e4t kann nicht mithilfe des Assistenten eingerichtet werden." }, "flow_title": "Xiaomi Miio: {name}", "step": { "device": { "data": { "host": "IP-Adresse", + "model": "Ger\u00e4temodell (optional)", "name": "Name des Ger\u00e4ts", "token": "API-Token" - } + }, + "description": "Sie ben\u00f6tigen den 32 Zeichen langen API-Token, siehe https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token f\u00fcr eine Anleitung. Dieser unterscheidet sich vom API-Token, den die Xiaomi Aqara-Integration nutzt.", + "title": "Herstellen einer Verbindung mit einem Xiaomi Miio-Ger\u00e4t oder Xiaomi Gateway" }, "gateway": { "data": { @@ -23,14 +27,14 @@ "name": "Name des Gateways", "token": "API-Token" }, - "description": "Du ben\u00f6tigst den 32 Zeichen langen API-Token. Anweisungen findest du unter https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token.", + "description": "Sie ben\u00f6tigen den 32 Zeichen langen API-Token. Anweisungen finden Sie unter https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token.", "title": "Stelle eine Verbindung zu einem Xiaomi Gateway her" }, "user": { "data": { "gateway": "Stelle eine Verbindung zu einem Xiaomi Gateway her" }, - "description": "W\u00e4hle aus, mit welchem Ger\u00e4t du eine Verbindung herstellen m\u00f6chtest.", + "description": "W\u00e4hlen Sie aus, mit welchem Ger\u00e4t Sie eine Verbindung herstellen m\u00f6chten.", "title": "Xiaomi Miio" } } diff --git a/homeassistant/components/xiaomi_miio/translations/hu.json b/homeassistant/components/xiaomi_miio/translations/hu.json index beb5c06c098..e5cf4501608 100644 --- a/homeassistant/components/xiaomi_miio/translations/hu.json +++ b/homeassistant/components/xiaomi_miio/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1ci\u00f3s folyamat m\u00e1r fut" + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van." }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", @@ -10,20 +10,30 @@ }, "flow_title": "Xiaomi Miio: {name}", "step": { + "device": { + "data": { + "host": "IP c\u00edm", + "model": "Eszk\u00f6z modell (opcion\u00e1lis)", + "name": "Eszk\u00f6z neve", + "token": "API Token" + }, + "description": "Sz\u00fcks\u00e9ged lesz a 32 karakteres API Tokenre, k\u00f6vesd a https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token oldal instrukci\u00f3it. Vedd figyelembe, hogy ez az API Token k\u00fcl\u00f6nb\u00f6zik a Xiaomi Aqara integr\u00e1ci\u00f3 \u00e1ltal haszn\u00e1lt kulcst\u00f3l.", + "title": "Csatlakoz\u00e1s Xiaomi Miio eszk\u00f6zh\u00f6z vagy Xiaomi Gateway-hez" + }, "gateway": { "data": { "host": "IP c\u00edm", "name": "K\u00f6zponti egys\u00e9g neve", "token": "API Token" }, - "description": "Sz\u00fcks\u00e9ge lesz az API Tokenre, tov\u00e1bbi inforaciok: https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token", + "description": "Sz\u00fcks\u00e9ge lesz az API Tokenre, tov\u00e1bbi inforaciok: https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token. K\u00e9rj\u00fck, vegye figyelembe, hogy ez az API Token k\u00fcl\u00f6nb\u00f6zik a Xiaomi Aqara integr\u00e1ci\u00f3 \u00e1ltal haszn\u00e1lt kulcst\u00f3l.", "title": "Csatlakozzon egy Xiaomi K\u00f6zponti egys\u00e9ghez" }, "user": { "data": { "gateway": "Csatlakozzon egy Xiaomi K\u00f6zponti egys\u00e9ghez" }, - "description": "V\u00e1lassza ki, melyik k\u00e9sz\u00fcl\u00e9khez szeretne csatlakozni. ", + "description": "V\u00e1lassza ki, melyik k\u00e9sz\u00fcl\u00e9khez szeretne csatlakozni.", "title": "Xiaomi Miio" } } diff --git a/homeassistant/components/xiaomi_miio/translations/id.json b/homeassistant/components/xiaomi_miio/translations/id.json new file mode 100644 index 00000000000..d55e19980a7 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/id.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "no_device_selected": "Tidak ada perangkat yang dipilih, pilih satu perangkat.", + "unknown_device": "Model perangkat tidak diketahui, tidak dapat menyiapkan perangkat menggunakan alur konfigurasi." + }, + "flow_title": "Xiaomi Miio: {name}", + "step": { + "device": { + "data": { + "host": "Alamat IP", + "model": "Model perangkat (Opsional)", + "name": "Nama perangkat", + "token": "Token API" + }, + "description": "Anda akan membutuhkan Token API 32 karakter, lihat https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token untuk mendapatkan petunjuknya. Perhatikan bahwa Token API ini berbeda dari kunci yang digunakan oleh integrasi Xiaomi Aqara.", + "title": "Hubungkan ke Perangkat Xiaomi Miio atau Xiaomi Gateway" + }, + "gateway": { + "data": { + "host": "Alamat IP", + "name": "Nama Gateway", + "token": "Token API" + }, + "description": "Anda akan membutuhkan Token API 32 karakter, lihat https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token untuk mendapatkan petunjuknya. Perhatikan bahwa Token API ini berbeda dari kunci yang digunakan oleh integrasi Xiaomi Aqara.", + "title": "Hubungkan ke Xiaomi Gateway" + }, + "user": { + "data": { + "gateway": "Hubungkan ke Xiaomi Gateway" + }, + "description": "Pilih perangkat mana yang ingin disambungkan.", + "title": "Xiaomi Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/it.json b/homeassistant/components/xiaomi_miio/translations/it.json index aa48ba7cfa8..7eec7d7e424 100644 --- a/homeassistant/components/xiaomi_miio/translations/it.json +++ b/homeassistant/components/xiaomi_miio/translations/it.json @@ -18,7 +18,7 @@ "name": "Nome del dispositivo", "token": "Token API" }, - "description": "Avrai bisogno dei 32 caratteri Token API , vedi https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token per istruzioni. Tieni presente che questa Token API \u00e8 diversa dalla chiave utilizzata dall'integrazione Xiaomi Aqara.", + "description": "Avrai bisogno dei 32 caratteri della Token API, vedi https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token per istruzioni. Tieni presente che questa Token API \u00e8 diversa dalla chiave utilizzata dall'integrazione Xiaomi Aqara.", "title": "Connettiti a un dispositivo Xiaomi Miio o Xiaomi Gateway" }, "gateway": { diff --git a/homeassistant/components/xiaomi_miio/translations/ko.json b/homeassistant/components/xiaomi_miio/translations/ko.json index 7e594fde247..03043f92957 100644 --- a/homeassistant/components/xiaomi_miio/translations/ko.json +++ b/homeassistant/components/xiaomi_miio/translations/ko.json @@ -6,15 +6,20 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "no_device_selected": "\uc120\ud0dd\ub41c \uae30\uae30\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694." + "no_device_selected": "\uc120\ud0dd\ub41c \uae30\uae30\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", + "unknown_device": "\uae30\uae30\uc758 \ubaa8\ub378\uc744 \uc54c \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uad6c\uc131 \ud750\ub984\uc5d0\uc11c \uae30\uae30\ub97c \uc124\uc815\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." }, "flow_title": "Xiaomi Miio: {name}", "step": { "device": { "data": { "host": "IP \uc8fc\uc18c", + "model": "\uae30\uae30 \ubaa8\ub378 (\uc120\ud0dd \uc0ac\ud56d)", + "name": "\uae30\uae30 \uc774\ub984", "token": "API \ud1a0\ud070" - } + }, + "description": "32\uac1c\uc758 \ubb38\uc790\uc5f4\ub85c \uad6c\uc131\ub41c API \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694. \ucc38\uace0\ub85c \uc774 API \ud1a0\ud070\uc740 Xiaomi Aqara \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0\uc11c \uc0ac\uc6a9\ub418\ub294 \ud0a4\uc640 \ub2e4\ub985\ub2c8\ub2e4.", + "title": "Xiaomi Miio \uae30\uae30 \ub610\ub294 Xiaomi \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\uae30" }, "gateway": { "data": { @@ -22,7 +27,7 @@ "name": "\uac8c\uc774\ud2b8\uc6e8\uc774 \uc774\ub984", "token": "API \ud1a0\ud070" }, - "description": "32 \uac1c\uc758 \ubb38\uc790\uc5f4\ub85c \uad6c\uc131\ub41c API \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694. \ucc38\uace0\ub85c \uc774 API \ud1a0\ud070\uc740 Xiaomi Aqara \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0\uc11c \uc0ac\uc6a9\ub418\ub294 \ud0a4\uc640 \ub2e4\ub985\ub2c8\ub2e4.", + "description": "32\uac1c\uc758 \ubb38\uc790\uc5f4\ub85c \uad6c\uc131\ub41c API \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694. \ucc38\uace0\ub85c \uc774 API \ud1a0\ud070\uc740 Xiaomi Aqara \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0\uc11c \uc0ac\uc6a9\ub418\ub294 \ud0a4\uc640 \ub2e4\ub985\ub2c8\ub2e4.", "title": "Xiaomi \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\uae30" }, "user": { diff --git a/homeassistant/components/xiaomi_miio/translations/nl.json b/homeassistant/components/xiaomi_miio/translations/nl.json index 66209e61ee6..394d43fc261 100644 --- a/homeassistant/components/xiaomi_miio/translations/nl.json +++ b/homeassistant/components/xiaomi_miio/translations/nl.json @@ -2,11 +2,12 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom voor dit Xiaomi Miio-apparaat is al bezig." + "already_in_progress": "De configuratiestroom is al aan de gang" }, "error": { "cannot_connect": "Kan geen verbinding maken", - "no_device_selected": "Geen apparaat geselecteerd, selecteer 1 apparaat alstublieft" + "no_device_selected": "Geen apparaat geselecteerd, selecteer 1 apparaat alstublieft", + "unknown_device": "Het apparaatmodel is niet bekend, niet in staat om het apparaat in te stellen met config flow." }, "flow_title": "Xiaomi Miio: {name}", "step": { @@ -16,7 +17,9 @@ "model": "Apparaatmodel (Optioneel)", "name": "Naam van het apparaat", "token": "API-token" - } + }, + "description": "U hebt de 32 karakter API-token nodig, zie https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token voor instructies. Let op, deze API-token is anders dan de sleutel die wordt gebruikt door de Xiaomi Aqara integratie.", + "title": "Verbinding maken met een Xiaomi Miio-apparaat of Xiaomi Gateway" }, "gateway": { "data": { diff --git a/homeassistant/components/xiaomi_miio/translations/no.json b/homeassistant/components/xiaomi_miio/translations/no.json index 0a6cf433d87..74a398a9ba6 100644 --- a/homeassistant/components/xiaomi_miio/translations/no.json +++ b/homeassistant/components/xiaomi_miio/translations/no.json @@ -18,7 +18,7 @@ "name": "Navnet p\u00e5 enheten", "token": "API-token" }, - "description": "Du trenger 32 tegn API-token , se https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instruksjoner. V\u00e6r oppmerksom p\u00e5 at denne API-token er forskjellig fra n\u00f8kkelen som brukes av Xiaomi Aqara-integrasjonen.", + "description": "Du trenger 32 tegn API-token , se https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instruksjoner. V\u00e6r oppmerksom p\u00e5 at denne API-token er forskjellig fra n\u00f8kkelen som brukes av Xiaomi Aqara-integrasjonen.", "title": "Koble til en Xiaomi Miio-enhet eller Xiaomi Gateway" }, "gateway": { diff --git a/homeassistant/components/xiaomi_miio/translations/pl.json b/homeassistant/components/xiaomi_miio/translations/pl.json index 80528b71370..8b7105b6736 100644 --- a/homeassistant/components/xiaomi_miio/translations/pl.json +++ b/homeassistant/components/xiaomi_miio/translations/pl.json @@ -18,7 +18,7 @@ "name": "Nazwa urz\u0105dzenia", "token": "Token API" }, - "description": "B\u0119dziesz potrzebowa\u0107 tokenu API (32 znaki), odwied\u017a https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token, aby uzyska\u0107 instrukcje. Zauwa\u017c i\u017c jest to inny token ni\u017c w integracji Xiaomi Aqara.", + "description": "B\u0119dziesz potrzebowa\u0107 tokenu API (32 znaki), odwied\u017a https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token, aby uzyska\u0107 instrukcje. Zauwa\u017c i\u017c jest to inny token ni\u017c w integracji Xiaomi Aqara.", "title": "Po\u0142\u0105czenie z bramk\u0105 Xiaomi b\u0105d\u017a innym urz\u0105dzeniem Xiaomi Miio" }, "gateway": { diff --git a/homeassistant/components/xiaomi_miio/translations/ru.json b/homeassistant/components/xiaomi_miio/translations/ru.json index 5c5064ac347..b17291746b3 100644 --- a/homeassistant/components/xiaomi_miio/translations/ru.json +++ b/homeassistant/components/xiaomi_miio/translations/ru.json @@ -18,7 +18,7 @@ "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", "token": "\u0422\u043e\u043a\u0435\u043d API" }, - "description": "\u0414\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f 32-\u0445 \u0437\u043d\u0430\u0447\u043d\u044b\u0439 \u0422\u043e\u043a\u0435\u043d API. \u041e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d, \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0437\u043d\u0430\u0442\u044c \u0437\u0434\u0435\u0441\u044c: \nhttps://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token.\n\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u044d\u0442\u043e\u0442 \u0442\u043e\u043a\u0435\u043d \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043a\u043b\u044e\u0447\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0433\u043e \u043f\u0440\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 Xiaomi Aqara.", + "description": "\u0414\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f 32-\u0445 \u0437\u043d\u0430\u0447\u043d\u044b\u0439 \u0422\u043e\u043a\u0435\u043d API. \u041e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d, \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0437\u043d\u0430\u0442\u044c \u0437\u0434\u0435\u0441\u044c: \nhttps://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token.\n\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u044d\u0442\u043e\u0442 \u0442\u043e\u043a\u0435\u043d \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043a\u043b\u044e\u0447\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0433\u043e \u043f\u0440\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 Xiaomi Aqara.", "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Xiaomi Miio \u0438\u043b\u0438 \u0448\u043b\u044e\u0437\u0443 Xiaomi" }, "gateway": { diff --git a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json index 3b0a89b7485..db1d825cea8 100644 --- a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json +++ b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json @@ -18,7 +18,7 @@ "name": "\u88dd\u7f6e\u540d\u7a31", "token": "API \u6b0a\u6756" }, - "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u6b0a\u6756\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u6b0a\u6756\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64API \u6b0a\u6756\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u6b0a\u6756\u4e0d\u540c\u3002", + "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u6b0a\u6756\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u6b0a\u6756\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64 API \u6b0a\u6756\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u6b0a\u6756\u4e0d\u540c\u3002", "title": "\u9023\u7dda\u81f3\u5c0f\u7c73 MIIO \u88dd\u7f6e\u6216\u5c0f\u7c73\u7db2\u95dc" }, "gateway": { diff --git a/homeassistant/components/yeelight/translations/hu.json b/homeassistant/components/yeelight/translations/hu.json index 10a03cebd21..ac463142359 100644 --- a/homeassistant/components/yeelight/translations/hu.json +++ b/homeassistant/components/yeelight/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "no_devices_found": "Nincs eszk\u00f6z a h\u00e1l\u00f3zaton" + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton" }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" @@ -15,7 +15,7 @@ }, "user": { "data": { - "host": "Gazdag\u00e9p" + "host": "Hoszt" }, "description": "Ha a gazdag\u00e9pet \u00fcresen hagyja, felder\u00edt\u00e9sre ker\u00fcl automatikusan." } diff --git a/homeassistant/components/yeelight/translations/id.json b/homeassistant/components/yeelight/translations/id.json new file mode 100644 index 00000000000..0c81739095d --- /dev/null +++ b/homeassistant/components/yeelight/translations/id.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "pick_device": { + "data": { + "device": "Perangkat" + } + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Jika host dibiarkan kosong, proses penemuan akan digunakan untuk menemukan perangkat." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "model": "Model (Opsional)", + "nightlight_switch": "Gunakan Sakelar Lampu Malam", + "save_on_change": "Simpan Status Saat Berubah", + "transition": "Waktu Transisi (milidetik)", + "use_music_mode": "Aktifkan Mode Musik" + }, + "description": "Jika model dibiarkan kosong, model akan dideteksi secara otomatis." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yeelight/translations/ko.json b/homeassistant/components/yeelight/translations/ko.json index 1d6974aaa61..4abb8fcbbff 100644 --- a/homeassistant/components/yeelight/translations/ko.json +++ b/homeassistant/components/yeelight/translations/ko.json @@ -10,14 +10,14 @@ "step": { "pick_device": { "data": { - "device": "\uc7a5\uce58" + "device": "\uae30\uae30" } }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8" }, - "description": "\ud638\uc2a4\ud2b8\ub97c \ube44\uc6cc\ub450\uba74 \uc7a5\uce58\ub97c \ucc3e\ub294 \ub370 \uac80\uc0c9\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4." + "description": "\ud638\uc2a4\ud2b8\ub97c \ube44\uc6cc \ub450\uba74 \uae30\uae30\ub97c \ucc3e\ub294 \ub370 \uac80\uc0c9\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4" } } }, @@ -25,11 +25,11 @@ "step": { "init": { "data": { - "model": "\ubaa8\ub378(\uc120\ud0dd \uc0ac\ud56d)", - "nightlight_switch": "\uc57c\uac04 \uc870\uba85 \uc2a4\uc704\uce58 \uc0ac\uc6a9", - "save_on_change": "\ubcc0\uacbd\uc2dc \uc0c1\ud0dc \uc800\uc7a5", + "model": "\ubaa8\ub378 (\uc120\ud0dd \uc0ac\ud56d)", + "nightlight_switch": "\uc57c\uac04 \uc870\uba85 \uc804\ud658 \uc0ac\uc6a9\ud558\uae30", + "save_on_change": "\ubcc0\uacbd \uc2dc \uc0c1\ud0dc\ub97c \uc800\uc7a5\ud558\uae30", "transition": "\uc804\ud658 \uc2dc\uac04(ms)", - "use_music_mode": "\uc74c\uc545 \ubaa8\ub4dc \ud65c\uc131\ud654" + "use_music_mode": "\uc74c\uc545 \ubaa8\ub4dc \ud65c\uc131\ud654\ud558\uae30" }, "description": "\ubaa8\ub378\uc744 \ube44\uc6cc \ub450\uba74 \uc790\ub3d9\uc73c\ub85c \uac80\uc0c9\ub429\ub2c8\ub2e4." } diff --git a/homeassistant/components/zerproc/translations/id.json b/homeassistant/components/zerproc/translations/id.json new file mode 100644 index 00000000000..223836a8b40 --- /dev/null +++ b/homeassistant/components/zerproc/translations/id.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "confirm": { + "description": "Ingin memulai penyiapan?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zerproc/translations/ko.json b/homeassistant/components/zerproc/translations/ko.json index 7011a61f757..e5ae04d6e5c 100644 --- a/homeassistant/components/zerproc/translations/ko.json +++ b/homeassistant/components/zerproc/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/zha/translations/hu.json b/homeassistant/components/zha/translations/hu.json index 935663ed9e4..844f2dd7191 100644 --- a/homeassistant/components/zha/translations/hu.json +++ b/homeassistant/components/zha/translations/hu.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Csak egyetlen ZHA konfigur\u00e1ci\u00f3 megengedett." + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "error": { - "cannot_connect": "Nem lehet csatlakozni a ZHA eszk\u00f6zh\u00f6z." + "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { "port_config": { @@ -17,5 +17,10 @@ "title": "ZHA" } } + }, + "device_automation": { + "trigger_type": { + "device_offline": "Eszk\u00f6z offline" + } } } \ No newline at end of file diff --git a/homeassistant/components/zha/translations/id.json b/homeassistant/components/zha/translations/id.json new file mode 100644 index 00000000000..5baf04e1314 --- /dev/null +++ b/homeassistant/components/zha/translations/id.json @@ -0,0 +1,91 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "pick_radio": { + "data": { + "radio_type": "Jenis Radio" + }, + "description": "Pilih jenis radio Zigbee Anda", + "title": "Jenis Radio" + }, + "port_config": { + "data": { + "baudrate": "kecepatan port", + "flow_control": "kontrol data flow", + "path": "Jalur perangkat serial" + }, + "description": "Masukkan pengaturan khusus port", + "title": "Setelan" + }, + "user": { + "data": { + "path": "Jalur Perangkat Serial" + }, + "description": "Pilih port serial untuk radio Zigbee", + "title": "ZHA" + } + } + }, + "device_automation": { + "action_type": { + "squawk": "Squawk", + "warn": "Peringatkan" + }, + "trigger_subtype": { + "both_buttons": "Kedua tombol", + "button_1": "Tombol pertama", + "button_2": "Tombol kedua", + "button_3": "Tombol ketiga", + "button_4": "Tombol keempat", + "button_5": "Tombol kelima", + "button_6": "Tombol keenam", + "close": "Tutup", + "dim_down": "Redupkan", + "dim_up": "Terangkan", + "face_1": "dengan wajah 1 diaktifkan", + "face_2": "dengan wajah 2 diaktifkan", + "face_3": "dengan wajah 3 diaktifkan", + "face_4": "dengan wajah 4 diaktifkan", + "face_5": "dengan wajah 5 diaktifkan", + "face_6": "dengan wajah 6 diaktifkan", + "face_any": "Dengan wajah apa pun/yang ditentukan diaktifkan", + "left": "Kiri", + "open": "Buka", + "right": "Kanan", + "turn_off": "Matikan", + "turn_on": "Nyalakan" + }, + "trigger_type": { + "device_dropped": "Perangkat dijatuhkan", + "device_flipped": "Perangkat dibalik \"{subtype}\"", + "device_knocked": "Perangkat diketuk \"{subtype}\"", + "device_offline": "Perangkat offline", + "device_rotated": "Perangkat diputar \"{subtype}\"", + "device_shaken": "Perangkat diguncangkan", + "device_slid": "Perangkat diluncurkan \"{subtype}\"", + "device_tilted": "Perangkat dimiringkan", + "remote_button_alt_double_press": "Tombol \"{subtype}\" diklik dua kali (Mode alternatif)", + "remote_button_alt_long_press": "Tombol \"{subtype}\" terus ditekan (Mode alternatif)", + "remote_button_alt_long_release": "Tombol \"{subtype}\" dilepaskan setelah ditekan lama (Mode alternatif)", + "remote_button_alt_quadruple_press": "Tombol \"{subtype}\" diklik empat kali (Mode alternatif)", + "remote_button_alt_quintuple_press": "Tombol \"{subtype}\" diklik lima kali (Mode alternatif)", + "remote_button_alt_short_press": "Tombol \"{subtype}\" ditekan (Mode alternatif)", + "remote_button_alt_short_release": "Tombol \"{subtype}\" dilepaskan (Mode alternatif)", + "remote_button_alt_triple_press": "Tombol \"{subtype}\" diklik tiga kali (Mode alternatif)", + "remote_button_double_press": "Tombol \"{subtype}\" diklik dua kali", + "remote_button_long_press": "Tombol \"{subtype}\" terus ditekan", + "remote_button_long_release": "Tombol \"{subtype}\" dilepaskan setelah ditekan lama", + "remote_button_quadruple_press": "Tombol \"{subtype}\" diklik empat kali", + "remote_button_quintuple_press": "Tombol \"{subtype}\" diklik lima kali", + "remote_button_short_press": "Tombol \"{subtype}\" ditekan", + "remote_button_short_release": "Tombol \"{subtype}\" dilepaskan", + "remote_button_triple_press": "Tombol \"{subtype}\" diklik tiga kali" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zha/translations/ko.json b/homeassistant/components/zha/translations/ko.json index 639cc84d86f..4ec9790b357 100644 --- a/homeassistant/components/zha/translations/ko.json +++ b/homeassistant/components/zha/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" @@ -34,8 +34,8 @@ }, "device_automation": { "action_type": { - "squawk": "\ube44\uc0c1", - "warn": "\uacbd\uace0" + "squawk": "\uc2a4\ucffc\ud06c \ud558\uae30", + "warn": "\uacbd\uace0\ud558\uae30" }, "trigger_subtype": { "both_buttons": "\ub450 \uac1c", @@ -63,28 +63,29 @@ }, "trigger_type": { "device_dropped": "\uae30\uae30\uac00 \ub5a8\uc5b4\uc84c\uc744 \ub54c", - "device_flipped": "\"{subtype}\" \uae30\uae30\uac00 \ub4a4\uc9d1\uc5b4\uc9c8 \ub54c", - "device_knocked": "\"{subtype}\" \uae30\uae30\uac00 \ub450\ub4dc\ub824\uc9c8 \ub54c", - "device_rotated": "\"{subtype}\" \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", - "device_shaken": "\uae30\uae30\uac00 \ud754\ub4e4\ub9b4 \ub54c", - "device_slid": "\"{subtype}\" \uae30\uae30\uac00 \ubbf8\ub044\ub7ec\uc9c8 \ub54c", - "device_tilted": "\uae30\uae30\uac00 \uae30\uc6b8\uc5b4\uc9c8 \ub54c", - "remote_button_alt_double_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub450 \ubc88 \ub20c\ub9b4 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", - "remote_button_alt_long_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uacc4\uc18d \ub20c\ub824\uc9c8 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", - "remote_button_alt_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc190\uc744 \ub5c4 \ub54c (\ub300\uccb4\ubaa8\ub4dc)", - "remote_button_alt_quadruple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub124 \ubc88 \ub20c\ub9b4 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", - "remote_button_alt_quintuple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub2e4\uc12f \ubc88 \ub20c\ub9b4 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", - "remote_button_alt_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub9b4 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", - "remote_button_alt_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5c4 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", - "remote_button_alt_triple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uc138 \ubc88 \ub20c\ub9b4 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", - "remote_button_double_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub450 \ubc88 \ub20c\ub9b4 \ub54c", - "remote_button_long_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uacc4\uc18d \ub20c\ub824\uc9c8 \ub54c", - "remote_button_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc190\uc744 \ub5c4 \ub54c", - "remote_button_quadruple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub124 \ubc88 \ub20c\ub9b4 \ub54c", - "remote_button_quintuple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub2e4\uc12f \ubc88 \ub20c\ub9b4 \ub54c", - "remote_button_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub9b4 \ub54c", - "remote_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5c4 \ub54c", - "remote_button_triple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uc138 \ubc88 \ub20c\ub9b4 \ub54c" + "device_flipped": "\"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ub4a4\uc9d1\uc5b4\uc84c\uc744 \ub54c", + "device_knocked": "\"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ub450\ub4dc\ub824\uc84c\uc744 \ub54c", + "device_offline": "\uae30\uae30\uac00 \uc624\ud504\ub77c\uc778\uc774 \ub418\uc5c8\uc744 \ub54c", + "device_rotated": "\"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "device_shaken": "\uae30\uae30\uac00 \ud754\ub4e4\ub838\uc744 \ub54c", + "device_slid": "\"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ubbf8\ub044\ub7ec\uc84c\uc744 \ub54c", + "device_tilted": "\uae30\uae30\uac00 \uae30\uc6b8\uc5b4\uc84c\uc744 \ub54c", + "remote_button_alt_double_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub450 \ubc88 \ub20c\ub838\uc744 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", + "remote_button_alt_long_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uacc4\uc18d \ub20c\ub838\uc744 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", + "remote_button_alt_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \ub5bc\uc600\uc744 \ub54c (\ub300\uccb4\ubaa8\ub4dc)", + "remote_button_alt_quadruple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub124 \ubc88 \ub20c\ub838\uc744 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", + "remote_button_alt_quintuple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub2e4\uc12f \ubc88 \ub20c\ub838\uc744 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", + "remote_button_alt_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub838\uc744 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", + "remote_button_alt_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5bc\uc5c8\uc744 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", + "remote_button_alt_triple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uc138 \ubc88 \ub20c\ub838\uc744 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", + "remote_button_double_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub450 \ubc88 \ub20c\ub838\uc744 \ub54c", + "remote_button_long_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uacc4\uc18d \ub20c\ub838\uc744 \ub54c", + "remote_button_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \ub5bc\uc600\uc744 \ub54c", + "remote_button_quadruple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub124 \ubc88 \ub20c\ub838\uc744 \ub54c", + "remote_button_quintuple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub2e4\uc12f \ubc88 \ub20c\ub838\uc744 \ub54c", + "remote_button_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub838\uc744 \ub54c", + "remote_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5bc\uc5c8\uc744 \ub54c", + "remote_button_triple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uc138 \ubc88 \ub20c\ub838\uc744 \ub54c" } } } \ No newline at end of file diff --git a/homeassistant/components/zha/translations/nl.json b/homeassistant/components/zha/translations/nl.json index 9a7a83e19d3..ddf208c8577 100644 --- a/homeassistant/components/zha/translations/nl.json +++ b/homeassistant/components/zha/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Slechts \u00e9\u00e9n configuratie van ZHA is toegestaan." + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "error": { - "cannot_connect": "Kan geen verbinding maken met ZHA apparaat." + "cannot_connect": "Kan geen verbinding maken" }, "step": { "pick_radio": { @@ -65,6 +65,7 @@ "device_dropped": "Apparaat gevallen", "device_flipped": "Apparaat omgedraaid \"{subtype}\"", "device_knocked": "Apparaat klopte \"{subtype}\"", + "device_offline": "Apparaat offline", "device_rotated": "Apparaat gedraaid \" {subtype} \"", "device_shaken": "Apparaat geschud", "device_slid": "Apparaat geschoven \"{subtype}\"\".", diff --git a/homeassistant/components/zodiac/translations/sensor.hu.json b/homeassistant/components/zodiac/translations/sensor.hu.json new file mode 100644 index 00000000000..8897339b74f --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.hu.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "V\u00edz\u00f6nt\u0151", + "aries": "Kos", + "cancer": "R\u00e1k", + "capricorn": "Bak", + "gemini": "Ikrek", + "leo": "Oroszl\u00e1n", + "libra": "M\u00e9rleg", + "pisces": "Halak", + "sagittarius": "Nyilas", + "scorpio": "Skorpi\u00f3", + "taurus": "Bika", + "virgo": "Sz\u0171z" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.id.json b/homeassistant/components/zodiac/translations/sensor.id.json new file mode 100644 index 00000000000..cd671e146ed --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.id.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "Aquarius", + "aries": "Aries", + "cancer": "Cancer", + "capricorn": "Capricorn", + "gemini": "Gemini", + "leo": "Leo", + "libra": "Libra", + "pisces": "Pisces", + "sagittarius": "Sagittarius", + "scorpio": "Scorpio", + "taurus": "Taurus", + "virgo": "Virgo" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.ko.json b/homeassistant/components/zodiac/translations/sensor.ko.json index 0a9fc83cdea..88221b2f34e 100644 --- a/homeassistant/components/zodiac/translations/sensor.ko.json +++ b/homeassistant/components/zodiac/translations/sensor.ko.json @@ -1,18 +1,18 @@ { "state": { "zodiac__sign": { - "aquarius": "\ubb3c\ubcd1 \uc790\ub9ac", - "aries": "\uc591 \uc790\ub9ac", - "cancer": "\uac8c \uc790\ub9ac", - "capricorn": "\uc5fc\uc18c \uc790\ub9ac", - "gemini": "\uc30d\ub465\uc774 \uc790\ub9ac", - "leo": "\uc0ac\uc790 \uc790\ub9ac", - "libra": "\ucc9c\uce6d \uc790\ub9ac", - "pisces": "\ubb3c\uace0\uae30 \uc790\ub9ac", - "sagittarius": "\uad81\uc218 \uc790\ub9ac", - "scorpio": "\uc804\uac08 \uc790\ub9ac", - "taurus": "\ud669\uc18c \uc790\ub9ac", - "virgo": "\ucc98\ub140 \uc790\ub9ac" + "aquarius": "\ubb3c\ubcd1\uc790\ub9ac", + "aries": "\uc591\uc790\ub9ac", + "cancer": "\uac8c\uc790\ub9ac", + "capricorn": "\uc5fc\uc18c\uc790\ub9ac", + "gemini": "\uc30d\ub465\uc774\uc790\ub9ac", + "leo": "\uc0ac\uc790\uc790\ub9ac", + "libra": "\ucc9c\uce6d\uc790\ub9ac", + "pisces": "\ubb3c\uace0\uae30\uc790\ub9ac", + "sagittarius": "\uad81\uc218\uc790\ub9ac", + "scorpio": "\uc804\uac08\uc790\ub9ac", + "taurus": "\ud669\uc18c\uc790\ub9ac", + "virgo": "\ucc98\ub140\uc790\ub9ac" } } } \ No newline at end of file diff --git a/homeassistant/components/zone/translations/id.json b/homeassistant/components/zone/translations/id.json index b84710dc408..aa05923b561 100644 --- a/homeassistant/components/zone/translations/id.json +++ b/homeassistant/components/zone/translations/id.json @@ -8,7 +8,7 @@ "data": { "icon": "Ikon", "latitude": "Lintang", - "longitude": "Garis bujur", + "longitude": "Bujur", "name": "Nama", "passive": "Pasif", "radius": "Radius" diff --git a/homeassistant/components/zoneminder/translations/hu.json b/homeassistant/components/zoneminder/translations/hu.json index f1f99fa2f7c..a40d9299251 100644 --- a/homeassistant/components/zoneminder/translations/hu.json +++ b/homeassistant/components/zoneminder/translations/hu.json @@ -1,13 +1,28 @@ { "config": { "abort": { - "connection_error": "Nem siker\u00fclt csatlakozni a ZoneMinder szerverhez." + "auth_fail": "\u00c9rv\u00e9nytelen felhaszn\u00e1l\u00f3n\u00e9v vagy jelsz\u00f3", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "connection_error": "Nem siker\u00fclt csatlakozni a ZoneMinder szerverhez.", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, "create_entry": { "default": "ZoneMinder szerver hozz\u00e1adva." }, "error": { - "connection_error": "Nem siker\u00fclt csatlakozni a ZoneMinder szerverhez." + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "connection_error": "Nem siker\u00fclt csatlakozni a ZoneMinder szerverhez.", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/id.json b/homeassistant/components/zoneminder/translations/id.json new file mode 100644 index 00000000000..25f1d98fc26 --- /dev/null +++ b/homeassistant/components/zoneminder/translations/id.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "auth_fail": "Nama pengguna atau kata sandi salah.", + "cannot_connect": "Gagal terhubung", + "connection_error": "Gagal terhubung ke server ZoneMinder.", + "invalid_auth": "Autentikasi tidak valid" + }, + "create_entry": { + "default": "Server ZoneMinder ditambahkan." + }, + "error": { + "auth_fail": "Nama pengguna atau kata sandi salah.", + "cannot_connect": "Gagal terhubung", + "connection_error": "Gagal terhubung ke server ZoneMinder.", + "invalid_auth": "Autentikasi tidak valid" + }, + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "host": "Host dan Port (mis. 10.10.0.4:8010)", + "password": "Kata Sandi", + "path": "Jalur ZM", + "path_zms": "Jalur ZMS", + "ssl": "Menggunakan sertifikat SSL", + "username": "Nama Pengguna", + "verify_ssl": "Verifikasi sertifikat SSL" + }, + "title": "Tambahkan Server ZoneMinder." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/ko.json b/homeassistant/components/zoneminder/translations/ko.json index e03da9ed8fa..bee0b7b6465 100644 --- a/homeassistant/components/zoneminder/translations/ko.json +++ b/homeassistant/components/zoneminder/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "auth_fail": "\uc0ac\uc6a9\uc790\uba85\uacfc \uc554\ud638\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", + "auth_fail": "\uc0ac\uc6a9\uc790 \uc774\ub984 \ub610\ub294 \ube44\ubc00\ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "connection_error": "ZoneMinder \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" @@ -10,7 +10,7 @@ "default": "ZoneMinder \uc11c\ubc84\uac00 \ucd94\uac00\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "error": { - "auth_fail": "\uc0ac\uc6a9\uc790\uba85\uacfc \uc554\ud638\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", + "auth_fail": "\uc0ac\uc6a9\uc790 \uc774\ub984 \ub610\ub294 \ube44\ubc00\ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "connection_error": "ZoneMinder \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" @@ -19,15 +19,15 @@ "step": { "user": { "data": { - "host": "\ud638\uc2a4\ud2b8 \ubc0f \ud3ec\ud2b8(\uc608: 10.10.0.4:8010)", + "host": "\ud638\uc2a4\ud2b8 \ubc0f \ud3ec\ud2b8 (\uc608: 10.10.0.4:8010)", "password": "\ube44\ubc00\ubc88\ud638", - "path": "ZMS \uacbd\ub85c", + "path": "ZM \uacbd\ub85c", "path_zms": "ZMS \uacbd\ub85c", "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984", "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" }, - "title": "ZoneMinder \uc11c\ubc84\ub97c \ucd94\uac00\ud558\uc138\uc694." + "title": "ZoneMinder \uc11c\ubc84\ub97c \ucd94\uac00\ud574\uc8fc\uc138\uc694." } } } diff --git a/homeassistant/components/zwave/translations/hu.json b/homeassistant/components/zwave/translations/hu.json index 240e7fe776c..68a19863b53 100644 --- a/homeassistant/components/zwave/translations/hu.json +++ b/homeassistant/components/zwave/translations/hu.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "A Z-Wave m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "error": { "option_error": "A Z-Wave \u00e9rv\u00e9nyes\u00edt\u00e9s sikertelen. Az USB-meghajt\u00f3 el\u00e9r\u00e9si \u00fatj\u00e1t helyesen adtad meg?" diff --git a/homeassistant/components/zwave/translations/id.json b/homeassistant/components/zwave/translations/id.json index 76c9c148b1e..99bd6270326 100644 --- a/homeassistant/components/zwave/translations/id.json +++ b/homeassistant/components/zwave/translations/id.json @@ -1,4 +1,23 @@ { + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "option_error": "Validasi Z-Wave gagal. Apakah jalur ke stik USB sudah benar?" + }, + "step": { + "user": { + "data": { + "network_key": "Kunci Jaringan (biarkan kosong untuk dibuat secara otomatis)", + "usb_path": "Jalur Perangkat USB" + }, + "description": "Integrasi ini tidak lagi dipertahankan. Untuk instalasi baru, gunakan Z-Wave JS sebagai gantinya.\n\nBaca https://www.home-assistant.io/docs/z-wave/installation/ untuk informasi tentang variabel konfigurasi", + "title": "Siapkan Z-Wave" + } + } + }, "state": { "_": { "dead": "Mati", @@ -7,8 +26,8 @@ "sleeping": "Tidur" }, "query_stage": { - "dead": "Mati ({query_stage})", - "initializing": "Inisialisasi ( {query_stage} )" + "dead": "Mati", + "initializing": "Inisialisasi" } } } \ No newline at end of file diff --git a/homeassistant/components/zwave/translations/ko.json b/homeassistant/components/zwave/translations/ko.json index 84c7b4ee4e3..674476ac759 100644 --- a/homeassistant/components/zwave/translations/ko.json +++ b/homeassistant/components/zwave/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "option_error": "Z-Wave \uc720\ud6a8\uc131 \uac80\uc0ac\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. USB \uc2a4\ud2f1\uc758 \uacbd\ub85c\uac00 \uc815\ud655\ud569\ub2c8\uae4c?" diff --git a/homeassistant/components/zwave/translations/nl.json b/homeassistant/components/zwave/translations/nl.json index 50e81003d27..a366d1d50df 100644 --- a/homeassistant/components/zwave/translations/nl.json +++ b/homeassistant/components/zwave/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Z-Wave is al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { @@ -11,9 +11,9 @@ "user": { "data": { "network_key": "Netwerksleutel (laat leeg om automatisch te genereren)", - "usb_path": "USB-pad" + "usb_path": "USB-apparaatpad" }, - "description": "Zie https://www.home-assistant.io/docs/z-wave/installation/ voor informatie over de configuratievariabelen", + "description": "Deze integratie wordt niet langer onderhouden. Voor nieuwe installaties, gebruik Z-Wave JS in plaats daarvan.\n\nZie https://www.home-assistant.io/docs/z-wave/installation/ voor informatie over de configuratievariabelen", "title": "Stel Z-Wave in" } } diff --git a/homeassistant/components/zwave_js/translations/bg.json b/homeassistant/components/zwave_js/translations/bg.json new file mode 100644 index 00000000000..abf89f00513 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/bg.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "manual": { + "data": { + "url": "URL" + } + }, + "user": { + "data": { + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/en.json b/homeassistant/components/zwave_js/translations/en.json index 101942dc717..5be980d52cb 100644 --- a/homeassistant/components/zwave_js/translations/en.json +++ b/homeassistant/components/zwave_js/translations/en.json @@ -4,6 +4,7 @@ "addon_get_discovery_info_failed": "Failed to get Z-Wave JS add-on discovery info.", "addon_info_failed": "Failed to get Z-Wave JS add-on info.", "addon_install_failed": "Failed to install the Z-Wave JS add-on.", + "addon_missing_discovery_info": "Missing Z-Wave JS add-on discovery info.", "addon_set_config_failed": "Failed to set Z-Wave JS configuration.", "addon_start_failed": "Failed to start the Z-Wave JS add-on.", "already_configured": "Device is already configured", @@ -48,6 +49,11 @@ }, "start_addon": { "title": "The Z-Wave JS add-on is starting." + }, + "user": { + "data": { + "url": "URL" + } } } }, diff --git a/homeassistant/components/zwave_js/translations/fr.json b/homeassistant/components/zwave_js/translations/fr.json index 9cc8bf822b8..ce9f2f8b501 100644 --- a/homeassistant/components/zwave_js/translations/fr.json +++ b/homeassistant/components/zwave_js/translations/fr.json @@ -42,9 +42,9 @@ }, "on_supervisor": { "data": { - "use_addon": "Utiliser le module compl\u00e9mentaire Z-Wave JS Supervisor" + "use_addon": "Utiliser le module compl\u00e9mentaire Z-Wave JS du Supervisor" }, - "description": "Voulez-vous utiliser le module compl\u00e9mentaire Z-Wave JS Supervisor?", + "description": "Voulez-vous utiliser le module compl\u00e9mentaire Z-Wave JS du Supervisor?", "title": "S\u00e9lectionner la m\u00e9thode de connexion" }, "start_addon": { diff --git a/homeassistant/components/zwave_js/translations/hu.json b/homeassistant/components/zwave_js/translations/hu.json new file mode 100644 index 00000000000..6732251f3a0 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/hu.json @@ -0,0 +1,49 @@ +{ + "config": { + "abort": { + "addon_start_failed": "Nem siker\u00fclt elind\u00edtani a Z-Wave JS b\u0151v\u00edtm\u00e9nyt.", + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_ws_url": "\u00c9rv\u00e9nytelen websocket URL", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "progress": { + "start_addon": "V\u00e1rj am\u00edg a Z-Wave JS b\u0151v\u00edtm\u00e9ny elindul. Ez eltarthat n\u00e9h\u00e1ny m\u00e1sodpercig." + }, + "step": { + "configure_addon": { + "data": { + "usb_path": "USB eszk\u00f6z el\u00e9r\u00e9si \u00fat" + } + }, + "install_addon": { + "title": "Elkezd\u0151d\u00f6tt a Z-Wave JS b\u0151v\u00edtm\u00e9ny telep\u00edt\u00e9se" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "Haszn\u00e1ld a Z-Wave JS Supervisor b\u0151v\u00edtm\u00e9nyt" + }, + "description": "Szeretn\u00e9d haszn\u00e1lni az Z-Wave JS Supervisor b\u0151v\u00edtm\u00e9nyt?", + "title": "V\u00e1laszd ki a csatlakoz\u00e1si m\u00f3dot" + }, + "start_addon": { + "title": "Indul a Z-Wave JS b\u0151v\u00edtm\u00e9ny." + }, + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/id.json b/homeassistant/components/zwave_js/translations/id.json new file mode 100644 index 00000000000..e8ea9381544 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/id.json @@ -0,0 +1,61 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "Gagal mendapatkan info penemuan add-on Z-Wave JS.", + "addon_info_failed": "Gagal mendapatkan info add-on Z-Wave JS.", + "addon_install_failed": "Gagal menginstal add-on Z-Wave JS.", + "addon_missing_discovery_info": "Info penemuan add-on Z-Wave JS tidak ada.", + "addon_set_config_failed": "Gagal menyetel konfigurasi Z-Wave JS.", + "addon_start_failed": "Gagal memulai add-on Z-Wave JS.", + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "cannot_connect": "Gagal terhubung" + }, + "error": { + "addon_start_failed": "Gagal memulai add-on Z-Wave JS. Periksa konfigurasi.", + "cannot_connect": "Gagal terhubung", + "invalid_ws_url": "URL websocket tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "progress": { + "install_addon": "Harap tunggu hingga penginstalan add-on Z-Wave JS selesai. Ini bisa memakan waktu beberapa saat.", + "start_addon": "Harap tunggu hingga add-on Z-Wave JS selesai. Ini mungkin perlu waktu beberapa saat." + }, + "step": { + "configure_addon": { + "data": { + "network_key": "Kunci Jaringan", + "usb_path": "Jalur Perangkat USB" + }, + "title": "Masukkan konfigurasi add-on Z-Wave JS" + }, + "hassio_confirm": { + "title": "Siapkan integrasi Z-Wave JS dengan add-on Z-Wave JS" + }, + "install_addon": { + "title": "Instalasi add-on Z-Wave JS telah dimulai" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "Gunakan add-on Supervisor Z-Wave JS" + }, + "description": "Ingin menggunakan add-on Supervisor Z-Wave JS?", + "title": "Pilih metode koneksi" + }, + "start_addon": { + "title": "Add-on Z-Wave JS sedang dimulai." + }, + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/ko.json b/homeassistant/components/zwave_js/translations/ko.json index 9c86a064151..72d81531551 100644 --- a/homeassistant/components/zwave_js/translations/ko.json +++ b/homeassistant/components/zwave_js/translations/ko.json @@ -1,30 +1,61 @@ { "config": { "abort": { + "addon_get_discovery_info_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uac80\uc0c9 \uc815\ubcf4\ub97c \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_info_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uc815\ubcf4\ub97c \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_install_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc744 \uc124\uce58\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_missing_discovery_info": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uac80\uc0c9 \uc815\ubcf4\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.", + "addon_set_config_failed": "Z-Wave JS \uad6c\uc131\uc744 \uc124\uc815\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_start_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "error": { + "addon_start_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uad6c\uc131 \ub0b4\uc6a9\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694.", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_ws_url": "\uc6f9 \uc18c\ucf13 URL \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, + "progress": { + "install_addon": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uc124\uce58\uac00 \uc644\ub8cc\ub418\ub294 \ub3d9\uc548 \uc7a0\uc2dc \uae30\ub2e4\ub824\uc8fc\uc138\uc694. \uba87 \ubd84 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "start_addon": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5 \uc2dc\uc791\uc774 \uc644\ub8cc\ub418\ub294 \ub3d9\uc548 \uc7a0\uc2dc \uae30\ub2e4\ub824\uc8fc\uc138\uc694. \uba87 \ucd08 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + }, "step": { "configure_addon": { "data": { + "network_key": "\ub124\ud2b8\uc6cc\ud06c \ud0a4", "usb_path": "USB \uc7a5\uce58 \uacbd\ub85c" - } + }, + "title": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" + }, + "hassio_confirm": { + "title": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc73c\ub85c Z-Wave JS \ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc124\uc815\ud558\uae30" + }, + "install_addon": { + "title": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5 \uc124\uce58\uac00 \uc2dc\uc791\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "manual": { "data": { "url": "URL \uc8fc\uc18c" } }, + "on_supervisor": { + "data": { + "use_addon": "Z-Wave JS Supervisor \ucd94\uac00 \uae30\ub2a5\uc744 \uc0ac\uc6a9\ud558\uae30" + }, + "description": "Z-Wave JS Supervisor \ucd94\uac00 \uae30\ub2a5\uc744 \uc0ac\uc6a9\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\uc5f0\uacb0 \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" + }, + "start_addon": { + "title": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc774 \uc2dc\uc791\ud558\ub294 \uc911\uc785\ub2c8\ub2e4." + }, "user": { "data": { "url": "URL \uc8fc\uc18c" } } } - } + }, + "title": "Z-Wave JS" } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/nl.json b/homeassistant/components/zwave_js/translations/nl.json index c15cfd26f31..f50c9c8ceba 100644 --- a/homeassistant/components/zwave_js/translations/nl.json +++ b/homeassistant/components/zwave_js/translations/nl.json @@ -6,6 +6,7 @@ "addon_install_failed": "Kan de Z-Wave JS add-on niet installeren.", "addon_missing_discovery_info": "De Z-Wave JS addon mist ontdekkings informatie", "addon_set_config_failed": "Instellen van de Z-Wave JS configuratie is mislukt.", + "addon_start_failed": "Kan de Z-Wave JS add-on niet starten.", "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kan geen verbinding maken" @@ -17,7 +18,8 @@ "unknown": "Onverwachte fout" }, "progress": { - "install_addon": "Een ogenblik geduld terwijl de installatie van de Z-Wave JS add-on is voltooid. Dit kan enkele minuten duren." + "install_addon": "Een ogenblik geduld terwijl de installatie van de Z-Wave JS add-on is voltooid. Dit kan enkele minuten duren.", + "start_addon": "Wacht alstublieft terwijl de Z-Wave JS add-on start voltooid is. Dit kan enkele seconden duren." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Wilt u de Z-Wave JS Supervisor add-on gebruiken?", "title": "Selecteer verbindingsmethode" }, + "start_addon": { + "title": "De add-on Z-Wave JS wordt gestart." + }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/zh-Hant.json b/homeassistant/components/zwave_js/translations/zh-Hant.json index f1495b1aeda..10b003f71e8 100644 --- a/homeassistant/components/zwave_js/translations/zh-Hant.json +++ b/homeassistant/components/zwave_js/translations/zh-Hant.json @@ -1,25 +1,25 @@ { "config": { "abort": { - "addon_get_discovery_info_failed": "\u53d6\u5f97 Z-Wave JS add-on \u63a2\u7d22\u8cc7\u8a0a\u5931\u6557\u3002", - "addon_info_failed": "\u53d6\u5f97 Z-Wave JS add-on \u8cc7\u8a0a\u5931\u6557\u3002", - "addon_install_failed": "Z-Wave JS add-on \u5b89\u88dd\u5931\u6557\u3002", - "addon_missing_discovery_info": "\u7f3a\u5c11 Z-Wave JS add-on \u63a2\u7d22\u8cc7\u8a0a\u3002", - "addon_set_config_failed": "Z-Wave JS add-on \u8a2d\u5b9a\u5931\u6557\u3002", - "addon_start_failed": "Z-Wave JS add-on \u555f\u59cb\u5931\u6557\u3002", + "addon_get_discovery_info_failed": "\u53d6\u5f97 Z-Wave JS \u9644\u52a0\u5143\u4ef6\u63a2\u7d22\u8cc7\u8a0a\u5931\u6557\u3002", + "addon_info_failed": "\u53d6\u5f97 Z-Wave JS \u9644\u52a0\u5143\u4ef6\u8cc7\u8a0a\u5931\u6557\u3002", + "addon_install_failed": "Z-Wave JS \u9644\u52a0\u5143\u4ef6\u5b89\u88dd\u5931\u6557\u3002", + "addon_missing_discovery_info": "\u7f3a\u5c11 Z-Wave JS \u9644\u52a0\u5143\u4ef6\u63a2\u7d22\u8cc7\u8a0a\u3002", + "addon_set_config_failed": "Z-Wave JS \u9644\u52a0\u5143\u4ef6\u8a2d\u5b9a\u5931\u6557\u3002", + "addon_start_failed": "Z-Wave JS \u9644\u52a0\u5143\u4ef6\u555f\u59cb\u5931\u6557\u3002", "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "error": { - "addon_start_failed": "Z-Wave JS add-on \u555f\u52d5\u5931\u6557\uff0c\u8acb\u6aa2\u67e5\u8a2d\u5b9a\u3002", + "addon_start_failed": "Z-Wave JS \u9644\u52a0\u5143\u4ef6\u555f\u52d5\u5931\u6557\uff0c\u8acb\u6aa2\u67e5\u8a2d\u5b9a\u3002", "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_ws_url": "Websocket URL \u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "progress": { - "install_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS add-on \u5b89\u88dd\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002", - "start_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS add-on \u555f\u59cb\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002" + "install_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS \u9644\u52a0\u5143\u4ef6\u5b89\u88dd\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002", + "start_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS \u9644\u52a0\u5143\u4ef6\u555f\u59cb\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002" }, "step": { "configure_addon": { @@ -27,13 +27,13 @@ "network_key": "\u7db2\u8def\u5bc6\u9470", "usb_path": "USB \u88dd\u7f6e\u8def\u5f91" }, - "title": "\u8f38\u5165 Z-Wave JS \u9644\u52a0\u8a2d\u5b9a" + "title": "\u8f38\u5165 Z-Wave JS \u9644\u52a0\u5143\u4ef6\u8a2d\u5b9a" }, "hassio_confirm": { - "title": "\u4ee5 Z-Wave JS add-on \u8a2d\u5b9a Z-Wave JS \u6574\u5408" + "title": "\u4ee5 Z-Wave JS \u9644\u52a0\u5143\u4ef6\u8a2d\u5b9a Z-Wave JS \u6574\u5408" }, "install_addon": { - "title": "Z-Wave JS add-on \u5b89\u88dd\u5df2\u555f\u52d5" + "title": "Z-Wave JS \u9644\u52a0\u5143\u4ef6\u5b89\u88dd\u5df2\u555f\u52d5" }, "manual": { "data": { @@ -42,13 +42,13 @@ }, "on_supervisor": { "data": { - "use_addon": "\u4f7f\u7528 Z-Wave JS Supervisor add-on" + "use_addon": "\u4f7f\u7528 Z-Wave JS Supervisor \u9644\u52a0\u5143\u4ef6" }, - "description": "\u662f\u5426\u8981\u4f7f\u7528 Z-Wave JS Supervisor add-on\uff1f", + "description": "\u662f\u5426\u8981\u4f7f\u7528 Z-Wave JS Supervisor \u9644\u52a0\u5143\u4ef6\uff1f", "title": "\u9078\u64c7\u9023\u7dda\u985e\u578b" }, "start_addon": { - "title": "Z-Wave JS add-on \u555f\u59cb\u4e2d\u3002" + "title": "Z-Wave JS \u9644\u52a0\u5143\u4ef6\u555f\u59cb\u4e2d\u3002" }, "user": { "data": { From 0e368df02381587c12120b490c27284f008eca87 Mon Sep 17 00:00:00 2001 From: SoCalix <48040807+Socalix@users.noreply.github.com> Date: Mon, 15 Mar 2021 20:07:54 -0500 Subject: [PATCH 390/831] Fix xmpp notify for muc rooms (#46715) --- homeassistant/components/xmpp/notify.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/xmpp/notify.py b/homeassistant/components/xmpp/notify.py index 68a041a2887..2abd3ffa245 100644 --- a/homeassistant/components/xmpp/notify.py +++ b/homeassistant/components/xmpp/notify.py @@ -165,7 +165,7 @@ async def async_send_message( if message: self.send_text_message() - self.disconnect(wait=True) + self.disconnect() async def send_file(self, timeout=None): """Send file via XMPP. @@ -174,7 +174,7 @@ async def async_send_message( HTTP Upload (XEP_0363) """ if room: - self.plugin["xep_0045"].join_muc(room, sender, wait=True) + self.plugin["xep_0045"].join_muc(room, sender) try: # Uploading with XEP_0363 @@ -335,7 +335,7 @@ async def async_send_message( try: if room: _LOGGER.debug("Joining room %s", room) - self.plugin["xep_0045"].join_muc(room, sender, wait=True) + self.plugin["xep_0045"].join_muc(room, sender) self.send_message(mto=room, mbody=message, mtype="groupchat") else: for recipient in recipients: From 2230b03888a3d71d280c983f49d12e6a875088a5 Mon Sep 17 00:00:00 2001 From: Guido Schmitz Date: Tue, 16 Mar 2021 08:13:03 +0100 Subject: [PATCH 391/831] Add voltage device class to devolo Home Control (#47967) --- homeassistant/components/devolo_home_control/sensor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/devolo_home_control/sensor.py b/homeassistant/components/devolo_home_control/sensor.py index e78b4eabeac..9fa3bdcf809 100644 --- a/homeassistant/components/devolo_home_control/sensor.py +++ b/homeassistant/components/devolo_home_control/sensor.py @@ -5,6 +5,7 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE @@ -20,6 +21,7 @@ DEVICE_CLASS_MAPPING = { "humidity": DEVICE_CLASS_HUMIDITY, "current": DEVICE_CLASS_POWER, "total": DEVICE_CLASS_POWER, + "voltage": DEVICE_CLASS_VOLTAGE, } From 354c0a7fd119c20120653e4244fcddc67c6ca706 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 16 Mar 2021 08:41:41 +0100 Subject: [PATCH 392/831] Add reauthentication to Verisure (#47972) * Add reauthentication to Verisure * Update translations * Correct translation step key * Address pylint warning * = is not : --- homeassistant/components/verisure/__init__.py | 9 +- .../components/verisure/config_flow.py | 52 +++++++- .../components/verisure/strings.json | 10 +- .../components/verisure/translations/en.json | 10 +- tests/components/verisure/test_config_flow.py | 121 ++++++++++++++++++ 5 files changed, 196 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index 5f8b2119310..1a727daac73 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -15,7 +15,7 @@ from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_REAUTH, ConfigEntry from homeassistant.const import ( CONF_EMAIL, CONF_PASSWORD, @@ -35,7 +35,6 @@ from .const import ( CONF_LOCK_DEFAULT_CODE, DEFAULT_LOCK_CODE_DIGITS, DOMAIN, - LOGGER, ) from .coordinator import VerisureDataUpdateCoordinator @@ -125,7 +124,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: coordinator = VerisureDataUpdateCoordinator(hass, entry=entry) if not await coordinator.async_login(): - LOGGER.error("Could not login to Verisure, aborting setting up integration") + await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_REAUTH}, + data={"entry": entry}, + ) return False hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, coordinator.async_logout) diff --git a/homeassistant/components/verisure/config_flow.py b/homeassistant/components/verisure/config_flow.py index ce9c76874f1..f05571d338c 100644 --- a/homeassistant/components/verisure/config_flow.py +++ b/homeassistant/components/verisure/config_flow.py @@ -36,8 +36,9 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = CONN_CLASS_CLOUD_POLL - installations: dict[str, str] email: str + entry: ConfigEntry + installations: dict[str, str] password: str # These can be removed after YAML import has been removed. @@ -123,6 +124,55 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN): }, ) + async def async_step_reauth(self, data: dict[str, Any]) -> dict[str, Any]: + """Handle initiation of re-authentication with Verisure.""" + self.entry = data["entry"] + return await self.async_step_reauth_confirm() + + async def async_step_reauth_confirm( + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: + """Handle re-authentication with Verisure.""" + errors: dict[str, str] = {} + + if user_input is not None: + verisure = Verisure( + username=user_input[CONF_EMAIL], password=user_input[CONF_PASSWORD] + ) + try: + await self.hass.async_add_executor_job(verisure.login) + except VerisureLoginError as ex: + LOGGER.debug("Could not log in to Verisure, %s", ex) + errors["base"] = "invalid_auth" + except (VerisureError, VerisureResponseError) as ex: + LOGGER.debug("Unexpected response from Verisure, %s", ex) + errors["base"] = "unknown" + else: + data = self.entry.data.copy() + self.hass.config_entries.async_update_entry( + self.entry, + data={ + **data, + CONF_EMAIL: user_input[CONF_EMAIL], + CONF_PASSWORD: user_input[CONF_PASSWORD], + }, + ) + self.hass.async_create_task( + self.hass.config_entries.async_reload(self.entry.entry_id) + ) + return self.async_abort(reason="reauth_successful") + + return self.async_show_form( + step_id="reauth_confirm", + data_schema=vol.Schema( + { + vol.Required(CONF_EMAIL, default=self.entry.data[CONF_EMAIL]): str, + vol.Required(CONF_PASSWORD): str, + } + ), + errors=errors, + ) + async def async_step_import(self, user_input: dict[str, Any]) -> dict[str, Any]: """Import Verisure YAML configuration.""" if user_input[CONF_GIID]: diff --git a/homeassistant/components/verisure/strings.json b/homeassistant/components/verisure/strings.json index 0c7f513f8ee..5170bff5faa 100644 --- a/homeassistant/components/verisure/strings.json +++ b/homeassistant/components/verisure/strings.json @@ -13,6 +13,13 @@ "data": { "giid": "Installation" } + }, + "reauth_confirm": { + "data": { + "description": "Re-authenticate with your Verisure My Pages account.", + "email": "[%key:common::config_flow::data::email%]", + "password": "[%key:common::config_flow::data::password%]" + } } }, "error": { @@ -20,7 +27,8 @@ "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } }, "options": { diff --git a/homeassistant/components/verisure/translations/en.json b/homeassistant/components/verisure/translations/en.json index 85c7acc167e..57f73c3772b 100644 --- a/homeassistant/components/verisure/translations/en.json +++ b/homeassistant/components/verisure/translations/en.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Account is already configured" + "already_configured": "Account is already configured", + "reauth_successful": "Re-authentication was successful" }, "error": { "invalid_auth": "Invalid authentication", @@ -14,6 +15,13 @@ }, "description": "Home Assistant found multiple Verisure installations in your My Pages account. Please, select the installation to add to Home Assistant." }, + "reauth_confirm": { + "data": { + "description": "Re-authenticate with your Verisure My Pages account.", + "email": "Email", + "password": "Password" + } + }, "user": { "data": { "description": "Sign-in with your Verisure My Pages account.", diff --git a/tests/components/verisure/test_config_flow.py b/tests/components/verisure/test_config_flow.py index 97f0f9731b1..c53c418c72b 100644 --- a/tests/components/verisure/test_config_flow.py +++ b/tests/components/verisure/test_config_flow.py @@ -190,6 +190,127 @@ async def test_dhcp(hass: HomeAssistant) -> None: assert result["step_id"] == "user" +async def test_reauth_flow(hass: HomeAssistant) -> None: + """Test a reauthentication flow.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="12345", + data={ + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_GIID: "12345", + CONF_PASSWORD: "SuperS3cr3t!", + }, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data={"entry": entry} + ) + assert result["step_id"] == "reauth_confirm" + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.verisure.config_flow.Verisure.login", + return_value=True, + ) as mock_verisure, patch( + "homeassistant.components.verisure.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.verisure.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "email": "verisure_my_pages@example.com", + "password": "correct horse battery staple", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_ABORT + assert result2["reason"] == "reauth_successful" + assert entry.data == { + CONF_GIID: "12345", + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_PASSWORD: "correct horse battery staple", + } + + assert len(mock_verisure.mock_calls) == 1 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_reauth_flow_invalid_login(hass: HomeAssistant) -> None: + """Test a reauthentication flow.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="12345", + data={ + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_GIID: "12345", + CONF_PASSWORD: "SuperS3cr3t!", + }, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data={"entry": entry} + ) + + with patch( + "homeassistant.components.verisure.config_flow.Verisure.login", + side_effect=VerisureLoginError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "email": "verisure_my_pages@example.com", + "password": "WrOngP4ssw0rd!", + }, + ) + await hass.async_block_till_done() + + assert result2["step_id"] == "reauth_confirm" + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "invalid_auth"} + + +async def test_reauth_flow_unknown_error(hass: HomeAssistant) -> None: + """Test a reauthentication flow, with an unknown error happening.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="12345", + data={ + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_GIID: "12345", + CONF_PASSWORD: "SuperS3cr3t!", + }, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data={"entry": entry} + ) + + with patch( + "homeassistant.components.verisure.config_flow.Verisure.login", + side_effect=VerisureError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "email": "verisure_my_pages@example.com", + "password": "WrOngP4ssw0rd!", + }, + ) + await hass.async_block_till_done() + + assert result2["step_id"] == "reauth_confirm" + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "unknown"} + + @pytest.mark.parametrize( "input,output", [ From 1cde1074c91aaf9bb3c1dffcb4d711de5338b919 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 16 Mar 2021 08:49:16 +0100 Subject: [PATCH 393/831] Correct trace for choose and repeat script actions (#47973) * Correct trace for choose and repeat script actions * only choose-wrap the choices * Update tests Co-authored-by: Paulus Schoutsen --- homeassistant/helpers/script.py | 25 ++++++++++++++----------- tests/helpers/test_script.py | 21 ++++++++++++++++++++- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index f1732aef316..51bf7b14927 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -640,6 +640,7 @@ class _ScriptRun: result = traced_test_conditions(self._hass, self._variables) return result + @trace_path("repeat") async def _async_repeat_step(self): """Repeat a sequence.""" description = self._action.get(CONF_ALIAS, "sequence") @@ -658,7 +659,7 @@ class _ScriptRun: async def async_run_sequence(iteration, extra_msg=""): self._log("Repeating %s: Iteration %i%s", description, iteration, extra_msg) - with trace_path(str(self._step)): + with trace_path("sequence"): await self._async_run_script(script) if CONF_COUNT in repeat: @@ -724,19 +725,21 @@ class _ScriptRun: # pylint: disable=protected-access choose_data = await self._script._async_get_choose_data(self._step) - for idx, (conditions, script) in enumerate(choose_data["choices"]): - with trace_path(str(idx)): - try: - if self._test_conditions(conditions, "choose"): - trace_set_result(choice=idx) - await self._async_run_script(script) - return - except exceptions.ConditionError as ex: - _LOGGER.warning("Error in 'choose' evaluation:\n%s", ex) + with trace_path("choose"): + for idx, (conditions, script) in enumerate(choose_data["choices"]): + with trace_path(str(idx)): + try: + if self._test_conditions(conditions, "choose"): + trace_set_result(choice=idx) + with trace_path("sequence"): + await self._async_run_script(script) + return + except exceptions.ConditionError as ex: + _LOGGER.warning("Error in 'choose' evaluation:\n%s", ex) if choose_data["default"]: trace_set_result(choice="default") - with trace_path("default"): + with trace_path(["default", "sequence"]): await self._async_run_script(choose_data["default"]) async def _async_wait_for_trigger_step(self): diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 7cb4b627a94..f6246299074 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -1236,7 +1236,7 @@ async def test_repeat_count(hass, caplog, count): assert_action_trace( { "0": [{}], - "0/0/0": [{}] * min(count, script.ACTION_TRACE_NODE_MAX_LEN), + "0/sequence/0": [{}] * min(count, script.ACTION_TRACE_NODE_MAX_LEN), } ) @@ -1578,6 +1578,10 @@ async def test_choose(hass, caplog, var, result): }, } ) + + # Prepare tracing + trace.trace_get() + script_obj = script.Script(hass, sequence, "Test Name", "test_domain") await script_obj.async_run(MappingProxyType({"var": var}), Context()) @@ -1590,6 +1594,21 @@ async def test_choose(hass, caplog, var, result): expected_choice = "default" assert f"{alias}: {expected_choice}: Executing step {aliases[var]}" in caplog.text + expected_trace = {"0": [{}]} + if var >= 1: + expected_trace["0/choose/0"] = [{}] + expected_trace["0/choose/0/conditions/0"] = [{}] + if var >= 2: + expected_trace["0/choose/1"] = [{}] + expected_trace["0/choose/1/conditions/0"] = [{}] + if var == 1: + expected_trace["0/choose/0/sequence/0"] = [{}] + if var == 2: + expected_trace["0/choose/1/sequence/0"] = [{}] + if var == 3: + expected_trace["0/default/sequence/0"] = [{}] + assert_action_trace(expected_trace) + @pytest.mark.parametrize( "action", From 333e6a215a021c87dea472cee756c6931f08de3f Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 16 Mar 2021 08:51:00 +0100 Subject: [PATCH 394/831] Add execute_script WS API (#47964) * Add execute_script WS API * Improve tests --- .../components/websocket_api/commands.py | 35 +++++++++++++---- .../components/websocket_api/test_commands.py | 39 +++++++++++++++++++ 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 53531cf9ba9..f85281d10c1 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -26,19 +26,20 @@ from . import const, decorators, messages @callback def async_register_commands(hass, async_reg): """Register commands.""" - async_reg(hass, handle_subscribe_events) - async_reg(hass, handle_unsubscribe_events) async_reg(hass, handle_call_service) - async_reg(hass, handle_get_states) - async_reg(hass, handle_get_services) + async_reg(hass, handle_entity_source) + async_reg(hass, handle_execute_script) async_reg(hass, handle_get_config) + async_reg(hass, handle_get_services) + async_reg(hass, handle_get_states) + async_reg(hass, handle_manifest_get) + async_reg(hass, handle_manifest_list) async_reg(hass, handle_ping) async_reg(hass, handle_render_template) - async_reg(hass, handle_manifest_list) - async_reg(hass, handle_manifest_get) - async_reg(hass, handle_entity_source) + async_reg(hass, handle_subscribe_events) async_reg(hass, handle_subscribe_trigger) async_reg(hass, handle_test_condition) + async_reg(hass, handle_unsubscribe_events) def pong_message(iden): @@ -420,3 +421,23 @@ async def handle_test_condition(hass, connection, msg): connection.send_result( msg["id"], {"result": check_condition(hass, msg.get("variables"))} ) + + +@decorators.websocket_command( + { + vol.Required("type"): "execute_script", + vol.Required("sequence"): cv.SCRIPT_SCHEMA, + } +) +@decorators.require_admin +@decorators.async_response +async def handle_execute_script(hass, connection, msg): + """Handle execute script command.""" + # Circular dep + # pylint: disable=import-outside-toplevel + from homeassistant.helpers.script import Script + + context = connection.context(msg) + script_obj = Script(hass, msg["sequence"], f"{const.DOMAIN} script", const.DOMAIN) + await script_obj.async_run(context=context) + connection.send_message(messages.result_message(msg["id"], {"context": context})) diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index f596db63c5e..a83f9509d01 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -44,6 +44,7 @@ async def test_call_service(hass, websocket_client): assert call.domain == "domain_test" assert call.service == "test_service" assert call.data == {"hello": "world"} + assert call.context.as_dict() == msg["result"]["context"] async def test_call_service_target(hass, websocket_client): @@ -79,6 +80,7 @@ async def test_call_service_target(hass, websocket_client): "entity_id": ["entity.one", "entity.two"], "device_id": ["deviceid"], } + assert call.context.as_dict() == msg["result"]["context"] async def test_call_service_target_template(hass, websocket_client): @@ -985,3 +987,40 @@ async def test_test_condition(hass, websocket_client): assert msg["type"] == const.TYPE_RESULT assert msg["success"] assert msg["result"]["result"] is True + + +async def test_execute_script(hass, websocket_client): + """Test testing a condition.""" + calls = async_mock_service(hass, "domain_test", "test_service") + + await websocket_client.send_json( + { + "id": 5, + "type": "execute_script", + "sequence": [ + { + "service": "domain_test.test_service", + "data": {"hello": "world"}, + } + ], + } + ) + + await hass.async_block_till_done() + await hass.async_block_till_done() + + msg = await websocket_client.receive_json() + assert msg["id"] == 5 + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + + await hass.async_block_till_done() + await hass.async_block_till_done() + + assert len(calls) == 1 + call = calls[0] + + assert call.domain == "domain_test" + assert call.service == "test_service" + assert call.data == {"hello": "world"} + assert call.context.as_dict() == msg["result"]["context"] From 5f2326fb5756b85255cec27d3b2172c44bc66058 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 16 Mar 2021 12:51:39 +0100 Subject: [PATCH 395/831] Add support for light color modes (#47720) * Add support for light color modes * Update tests * Update comments * Fix bugs, add tests * Suppress lint errors * Don't suppress brightness when state is ambiguous * Improve reproduce_state + add tests * Add comment * Change COLOR_MODE_* constants, rename COLOR_MODE_DIMMER to COLOR_MODE_BRIGHTNESS * Fix tests * Tweaks --- homeassistant/components/light/__init__.py | 271 +++++++++- .../components/light/reproduce_state.py | 47 +- tests/components/kulersky/test_light.py | 11 + tests/components/light/test_init.py | 479 ++++++++++++++++++ .../components/light/test_reproduce_state.py | 82 ++- tests/components/yeelight/test_light.py | 22 +- tests/components/zerproc/test_light.py | 11 + .../custom_components/test/light.py | 13 + 8 files changed, 897 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 693eb1a573a..56fd5841388 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -6,7 +6,7 @@ import dataclasses from datetime import timedelta import logging import os -from typing import Dict, List, Optional, Tuple, cast +from typing import Dict, List, Optional, Set, Tuple, cast import voluptuous as vol @@ -38,19 +38,49 @@ DATA_PROFILES = "light_profiles" ENTITY_ID_FORMAT = DOMAIN + ".{}" # Bitfield of features supported by the light entity -SUPPORT_BRIGHTNESS = 1 -SUPPORT_COLOR_TEMP = 2 +SUPPORT_BRIGHTNESS = 1 # Deprecated, replaced by color modes +SUPPORT_COLOR_TEMP = 2 # Deprecated, replaced by color modes SUPPORT_EFFECT = 4 SUPPORT_FLASH = 8 -SUPPORT_COLOR = 16 +SUPPORT_COLOR = 16 # Deprecated, replaced by color modes SUPPORT_TRANSITION = 32 -SUPPORT_WHITE_VALUE = 128 +SUPPORT_WHITE_VALUE = 128 # Deprecated, replaced by color modes + +# Color mode of the light +ATTR_COLOR_MODE = "color_mode" +# List of color modes supported by the light +ATTR_SUPPORTED_COLOR_MODES = "supported_color_modes" +# Possible color modes +COLOR_MODE_UNKNOWN = "unknown" # Ambiguous color mode +COLOR_MODE_ONOFF = "onoff" # Must be the only supported mode +COLOR_MODE_BRIGHTNESS = "brightness" # Must be the only supported mode +COLOR_MODE_COLOR_TEMP = "color_temp" +COLOR_MODE_HS = "hs" +COLOR_MODE_XY = "xy" +COLOR_MODE_RGB = "rgb" +COLOR_MODE_RGBW = "rgbw" +COLOR_MODE_RGBWW = "rgbww" + +VALID_COLOR_MODES = { + COLOR_MODE_ONOFF, + COLOR_MODE_BRIGHTNESS, + COLOR_MODE_COLOR_TEMP, + COLOR_MODE_HS, + COLOR_MODE_XY, + COLOR_MODE_RGB, + COLOR_MODE_RGBW, + COLOR_MODE_RGBWW, +} +COLOR_MODES_BRIGHTNESS = VALID_COLOR_MODES - {COLOR_MODE_ONOFF} +COLOR_MODES_COLOR = {COLOR_MODE_HS, COLOR_MODE_RGB, COLOR_MODE_XY} # Float that represents transition time in seconds to make change. ATTR_TRANSITION = "transition" # Lists holding color values ATTR_RGB_COLOR = "rgb_color" +ATTR_RGBW_COLOR = "rgbw_color" +ATTR_RGBWW_COLOR = "rgbww_color" ATTR_XY_COLOR = "xy_color" ATTR_HS_COLOR = "hs_color" ATTR_COLOR_TEMP = "color_temp" @@ -104,7 +134,13 @@ LIGHT_TURN_ON_SCHEMA = { vol.Exclusive(ATTR_BRIGHTNESS_STEP_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_STEP_PCT, vol.Exclusive(ATTR_COLOR_NAME, COLOR_GROUP): cv.string, vol.Exclusive(ATTR_RGB_COLOR, COLOR_GROUP): vol.All( - vol.ExactSequence((cv.byte, cv.byte, cv.byte)), vol.Coerce(tuple) + vol.ExactSequence((cv.byte,) * 3), vol.Coerce(tuple) + ), + vol.Exclusive(ATTR_RGBW_COLOR, COLOR_GROUP): vol.All( + vol.ExactSequence((cv.byte,) * 4), vol.Coerce(tuple) + ), + vol.Exclusive(ATTR_RGBWW_COLOR, COLOR_GROUP): vol.All( + vol.ExactSequence((cv.byte,) * 5), vol.Coerce(tuple) ), vol.Exclusive(ATTR_XY_COLOR, COLOR_GROUP): vol.All( vol.ExactSequence((cv.small_float, cv.small_float)), vol.Coerce(tuple) @@ -166,14 +202,6 @@ def preprocess_turn_on_alternatives(hass, params): if brightness_pct is not None: params[ATTR_BRIGHTNESS] = round(255 * brightness_pct / 100) - xy_color = params.pop(ATTR_XY_COLOR, None) - if xy_color is not None: - params[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color) - - rgb_color = params.pop(ATTR_RGB_COLOR, None) - if rgb_color is not None: - params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) - def filter_turn_off_params(params): """Filter out params not used in turn off.""" @@ -228,6 +256,52 @@ async def async_setup(hass, config): if ATTR_PROFILE not in params: profiles.apply_default(light.entity_id, params) + supported_color_modes = light.supported_color_modes + # Backwards compatibility: if an RGBWW color is specified, convert to RGB + W + # for legacy lights + if ATTR_RGBW_COLOR in params: + legacy_supported_color_modes = ( + light._light_internal_supported_color_modes # pylint: disable=protected-access + ) + if ( + COLOR_MODE_RGBW in legacy_supported_color_modes + and not supported_color_modes + ): + rgbw_color = params.pop(ATTR_RGBW_COLOR) + params[ATTR_RGB_COLOR] = rgbw_color[0:3] + params[ATTR_WHITE_VALUE] = rgbw_color[3] + + # If a color is specified, convert to the color space supported by the light + # Backwards compatibility: Fall back to hs color if light.supported_color_modes + # is not implemented + if not supported_color_modes: + if (rgb_color := params.pop(ATTR_RGB_COLOR, None)) is not None: + params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) + elif (xy_color := params.pop(ATTR_XY_COLOR, None)) is not None: + params[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color) + elif ATTR_HS_COLOR in params and COLOR_MODE_HS not in supported_color_modes: + hs_color = params.pop(ATTR_HS_COLOR) + if COLOR_MODE_RGB in supported_color_modes: + params[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color) + elif COLOR_MODE_XY in supported_color_modes: + params[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color) + elif ATTR_RGB_COLOR in params and COLOR_MODE_RGB not in supported_color_modes: + rgb_color = params.pop(ATTR_RGB_COLOR) + if COLOR_MODE_HS in supported_color_modes: + params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) + elif COLOR_MODE_XY in supported_color_modes: + params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color) + elif ATTR_XY_COLOR in params and COLOR_MODE_XY not in supported_color_modes: + xy_color = params.pop(ATTR_XY_COLOR) + if COLOR_MODE_HS in supported_color_modes: + params[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color) + elif COLOR_MODE_RGB in supported_color_modes: + params[ATTR_RGB_COLOR] = color_util.color_xy_to_RGB(*xy_color) + + # Remove deprecated white value if the light supports color mode + if supported_color_modes: + params.pop(ATTR_WHITE_VALUE, None) + # Zero brightness: Light will be turned off if params.get(ATTR_BRIGHTNESS) == 0: await light.async_turn_off(**filter_turn_off_params(params)) @@ -411,11 +485,83 @@ class LightEntity(ToggleEntity): """Return the brightness of this light between 0..255.""" return None + @property + def color_mode(self) -> Optional[str]: + """Return the color mode of the light.""" + return None + + @property + def _light_internal_color_mode(self) -> str: + """Return the color mode of the light with backwards compatibility.""" + color_mode = self.color_mode + + if color_mode is None: + # Backwards compatibility for color_mode added in 2021.4 + # Add warning in 2021.6, remove in 2021.10 + supported = self._light_internal_supported_color_modes + + if ( + COLOR_MODE_RGBW in supported + and self.white_value is not None + and self.hs_color is not None + ): + return COLOR_MODE_RGBW + if COLOR_MODE_HS in supported and self.hs_color is not None: + return COLOR_MODE_HS + if COLOR_MODE_COLOR_TEMP in supported and self.color_temp is not None: + return COLOR_MODE_COLOR_TEMP + if COLOR_MODE_BRIGHTNESS in supported and self.brightness is not None: + return COLOR_MODE_BRIGHTNESS + if COLOR_MODE_ONOFF in supported: + return COLOR_MODE_ONOFF + return COLOR_MODE_UNKNOWN + + return color_mode + @property def hs_color(self) -> Optional[Tuple[float, float]]: """Return the hue and saturation color value [float, float].""" return None + @property + def xy_color(self) -> Optional[Tuple[float, float]]: + """Return the xy color value [float, float].""" + return None + + @property + def rgb_color(self) -> Optional[Tuple[int, int, int]]: + """Return the rgb color value [int, int, int].""" + return None + + @property + def rgbw_color(self) -> Optional[Tuple[int, int, int, int]]: + """Return the rgbw color value [int, int, int, int].""" + return None + + @property + def _light_internal_rgbw_color(self) -> Optional[Tuple[int, int, int, int]]: + """Return the rgbw color value [int, int, int, int].""" + rgbw_color = self.rgbw_color + if ( + rgbw_color is None + and self.hs_color is not None + and self.white_value is not None + ): + # Backwards compatibility for rgbw_color added in 2021.4 + # Add warning in 2021.6, remove in 2021.10 + r, g, b = color_util.color_hs_to_RGB( # pylint: disable=invalid-name + *self.hs_color + ) + w = self.white_value # pylint: disable=invalid-name + rgbw_color = (r, g, b, w) + + return rgbw_color + + @property + def rgbww_color(self) -> Optional[Tuple[int, int, int, int, int]]: + """Return the rgbww color value [int, int, int, int, int].""" + return None + @property def color_temp(self) -> Optional[int]: """Return the CT color value in mireds.""" @@ -463,6 +609,29 @@ class LightEntity(ToggleEntity): if supported_features & SUPPORT_EFFECT: data[ATTR_EFFECT_LIST] = self.effect_list + data[ATTR_SUPPORTED_COLOR_MODES] = sorted( + list(self._light_internal_supported_color_modes) + ) + + return data + + def _light_internal_convert_color(self, color_mode: str) -> dict: + data: Dict[str, Tuple] = {} + if color_mode == COLOR_MODE_HS and self.hs_color: + hs_color = self.hs_color + data[ATTR_HS_COLOR] = (round(hs_color[0], 3), round(hs_color[1], 3)) + data[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color) + data[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color) + elif color_mode == COLOR_MODE_XY and self.xy_color: + xy_color = self.xy_color + data[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color) + data[ATTR_RGB_COLOR] = color_util.color_xy_to_RGB(*xy_color) + data[ATTR_XY_COLOR] = (round(xy_color[0], 6), round(xy_color[1], 6)) + elif color_mode == COLOR_MODE_RGB and self.rgb_color: + rgb_color = self.rgb_color + data[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) + data[ATTR_RGB_COLOR] = tuple(int(x) for x in rgb_color[0:3]) + data[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color) return data @property @@ -473,27 +642,85 @@ class LightEntity(ToggleEntity): data = {} supported_features = self.supported_features + color_mode = self._light_internal_color_mode - if supported_features & SUPPORT_BRIGHTNESS: + if color_mode not in self._light_internal_supported_color_modes: + # Increase severity to warning in 2021.6, reject in 2021.10 + _LOGGER.debug( + "%s: set to unsupported color_mode: %s, supported_color_modes: %s", + self.entity_id, + color_mode, + self._light_internal_supported_color_modes, + ) + + data[ATTR_COLOR_MODE] = color_mode + + if color_mode in COLOR_MODES_BRIGHTNESS: + data[ATTR_BRIGHTNESS] = self.brightness + elif supported_features & SUPPORT_BRIGHTNESS: + # Backwards compatibility for ambiguous / incomplete states + # Add warning in 2021.6, remove in 2021.10 data[ATTR_BRIGHTNESS] = self.brightness - if supported_features & SUPPORT_COLOR_TEMP: + if color_mode == COLOR_MODE_COLOR_TEMP: data[ATTR_COLOR_TEMP] = self.color_temp - if supported_features & SUPPORT_COLOR and self.hs_color: - hs_color = self.hs_color - data[ATTR_HS_COLOR] = (round(hs_color[0], 3), round(hs_color[1], 3)) - data[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color) - data[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color) + if color_mode in COLOR_MODES_COLOR: + data.update(self._light_internal_convert_color(color_mode)) - if supported_features & SUPPORT_WHITE_VALUE: + if color_mode == COLOR_MODE_RGBW: + data[ATTR_RGBW_COLOR] = self._light_internal_rgbw_color + + if color_mode == COLOR_MODE_RGBWW: + data[ATTR_RGBWW_COLOR] = self.rgbww_color + + if supported_features & SUPPORT_COLOR_TEMP and not self.supported_color_modes: + # Backwards compatibility + # Add warning in 2021.6, remove in 2021.10 + data[ATTR_COLOR_TEMP] = self.color_temp + + if supported_features & SUPPORT_WHITE_VALUE and not self.supported_color_modes: + # Backwards compatibility + # Add warning in 2021.6, remove in 2021.10 data[ATTR_WHITE_VALUE] = self.white_value + if self.hs_color is not None: + data.update(self._light_internal_convert_color(COLOR_MODE_HS)) if supported_features & SUPPORT_EFFECT: data[ATTR_EFFECT] = self.effect return {key: val for key, val in data.items() if val is not None} + @property + def _light_internal_supported_color_modes(self) -> Set: + """Calculate supported color modes with backwards compatibility.""" + supported_color_modes = self.supported_color_modes + + if supported_color_modes is None: + # Backwards compatibility for supported_color_modes added in 2021.4 + # Add warning in 2021.6, remove in 2021.10 + supported_features = self.supported_features + supported_color_modes = set() + + if supported_features & SUPPORT_COLOR_TEMP: + supported_color_modes.add(COLOR_MODE_COLOR_TEMP) + if supported_features & SUPPORT_COLOR: + supported_color_modes.add(COLOR_MODE_HS) + if supported_features & SUPPORT_WHITE_VALUE: + supported_color_modes.add(COLOR_MODE_RGBW) + if supported_features & SUPPORT_BRIGHTNESS and not supported_color_modes: + supported_color_modes = {COLOR_MODE_BRIGHTNESS} + + if not supported_color_modes: + supported_color_modes = {COLOR_MODE_ONOFF} + + return supported_color_modes + + @property + def supported_color_modes(self) -> Optional[Set]: + """Flag supported color modes.""" + return None + @property def supported_features(self) -> int: """Flag supported features.""" diff --git a/homeassistant/components/light/reproduce_state.py b/homeassistant/components/light/reproduce_state.py index a7939beb91e..863790cff71 100644 --- a/homeassistant/components/light/reproduce_state.py +++ b/homeassistant/components/light/reproduce_state.py @@ -17,6 +17,7 @@ from homeassistant.helpers.typing import HomeAssistantType from . import ( ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, + ATTR_COLOR_MODE, ATTR_COLOR_NAME, ATTR_COLOR_TEMP, ATTR_EFFECT, @@ -25,9 +26,18 @@ from . import ( ATTR_KELVIN, ATTR_PROFILE, ATTR_RGB_COLOR, + ATTR_RGBW_COLOR, + ATTR_RGBWW_COLOR, ATTR_TRANSITION, ATTR_WHITE_VALUE, ATTR_XY_COLOR, + COLOR_MODE_COLOR_TEMP, + COLOR_MODE_HS, + COLOR_MODE_RGB, + COLOR_MODE_RGBW, + COLOR_MODE_RGBWW, + COLOR_MODE_UNKNOWN, + COLOR_MODE_XY, DOMAIN, ) @@ -48,6 +58,8 @@ COLOR_GROUP = [ ATTR_HS_COLOR, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, + ATTR_RGBW_COLOR, + ATTR_RGBWW_COLOR, ATTR_XY_COLOR, # The following color attributes are deprecated ATTR_PROFILE, @@ -55,6 +67,15 @@ COLOR_GROUP = [ ATTR_KELVIN, ] +COLOR_MODE_TO_ATTRIBUTE = { + COLOR_MODE_COLOR_TEMP: ATTR_COLOR_TEMP, + COLOR_MODE_HS: ATTR_HS_COLOR, + COLOR_MODE_RGB: ATTR_RGB_COLOR, + COLOR_MODE_RGBW: ATTR_RGBW_COLOR, + COLOR_MODE_RGBWW: ATTR_RGBWW_COLOR, + COLOR_MODE_XY: ATTR_XY_COLOR, +} + DEPRECATED_GROUP = [ ATTR_BRIGHTNESS_PCT, ATTR_COLOR_NAME, @@ -114,11 +135,29 @@ async def _async_reproduce_state( if attr in state.attributes: service_data[attr] = state.attributes[attr] - for color_attr in COLOR_GROUP: - # Choose the first color that is specified - if color_attr in state.attributes: + if ( + state.attributes.get(ATTR_COLOR_MODE, COLOR_MODE_UNKNOWN) + != COLOR_MODE_UNKNOWN + ): + # Remove deprecated white value if we got a valid color mode + service_data.pop(ATTR_WHITE_VALUE, None) + color_mode = state.attributes[ATTR_COLOR_MODE] + if color_attr := COLOR_MODE_TO_ATTRIBUTE.get(color_mode): + if color_attr not in state.attributes: + _LOGGER.warning( + "Color mode %s specified but attribute %s missing for: %s", + color_mode, + color_attr, + state.entity_id, + ) + return service_data[color_attr] = state.attributes[color_attr] - break + else: + # Fall back to Choosing the first color that is specified + for color_attr in COLOR_GROUP: + if color_attr in state.attributes: + service_data[color_attr] = state.attributes[color_attr] + break elif state.state == STATE_OFF: service = SERVICE_TURN_OFF diff --git a/tests/components/kulersky/test_light.py b/tests/components/kulersky/test_light.py index 9dd13fad18b..c08dd10d597 100644 --- a/tests/components/kulersky/test_light.py +++ b/tests/components/kulersky/test_light.py @@ -8,10 +8,15 @@ from homeassistant import setup from homeassistant.components.kulersky.light import DOMAIN from homeassistant.components.light import ( ATTR_BRIGHTNESS, + ATTR_COLOR_MODE, ATTR_HS_COLOR, ATTR_RGB_COLOR, + ATTR_RGBW_COLOR, + ATTR_SUPPORTED_COLOR_MODES, ATTR_WHITE_VALUE, ATTR_XY_COLOR, + COLOR_MODE_HS, + COLOR_MODE_RGBW, SCAN_INTERVAL, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, @@ -65,6 +70,7 @@ async def test_init(hass, mock_light): assert state.state == STATE_OFF assert state.attributes == { ATTR_FRIENDLY_NAME: "Bedroom", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS, COLOR_MODE_RGBW], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_WHITE_VALUE, @@ -168,6 +174,7 @@ async def test_light_update(hass, mock_light): assert state.state == STATE_OFF assert state.attributes == { ATTR_FRIENDLY_NAME: "Bedroom", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS, COLOR_MODE_RGBW], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_WHITE_VALUE, @@ -183,6 +190,7 @@ async def test_light_update(hass, mock_light): assert state.state == STATE_UNAVAILABLE assert state.attributes == { ATTR_FRIENDLY_NAME: "Bedroom", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS, COLOR_MODE_RGBW], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_WHITE_VALUE, @@ -198,12 +206,15 @@ async def test_light_update(hass, mock_light): assert state.state == STATE_ON assert state.attributes == { ATTR_FRIENDLY_NAME: "Bedroom", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS, COLOR_MODE_RGBW], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_WHITE_VALUE, + ATTR_COLOR_MODE: COLOR_MODE_RGBW, ATTR_BRIGHTNESS: 200, ATTR_HS_COLOR: (200, 60), ATTR_RGB_COLOR: (102, 203, 255), + ATTR_RGBW_COLOR: (102, 203, 255, 240), ATTR_WHITE_VALUE: 240, ATTR_XY_COLOR: (0.184, 0.261), } diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 10f475a580d..3adb146a225 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -915,3 +915,482 @@ invalid_no_brightness_no_color_no_transition,,, "invalid_no_brightness_no_color_no_transition", ): assert invalid_profile_name not in profiles.data + + +@pytest.mark.parametrize("light_state", (STATE_ON, STATE_OFF)) +async def test_light_backwards_compatibility_supported_color_modes(hass, light_state): + """Test supported_color_modes if not implemented by the entity.""" + platform = getattr(hass.components, "test.light") + platform.init(empty=True) + + platform.ENTITIES.append(platform.MockLight("Test_0", light_state)) + platform.ENTITIES.append(platform.MockLight("Test_1", light_state)) + platform.ENTITIES.append(platform.MockLight("Test_2", light_state)) + platform.ENTITIES.append(platform.MockLight("Test_3", light_state)) + platform.ENTITIES.append(platform.MockLight("Test_4", light_state)) + platform.ENTITIES.append(platform.MockLight("Test_5", light_state)) + platform.ENTITIES.append(platform.MockLight("Test_6", light_state)) + + entity0 = platform.ENTITIES[0] + + entity1 = platform.ENTITIES[1] + entity1.supported_features = light.SUPPORT_BRIGHTNESS + + entity2 = platform.ENTITIES[2] + entity2.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR_TEMP + + entity3 = platform.ENTITIES[3] + entity3.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR + + entity4 = platform.ENTITIES[4] + entity4.supported_features = ( + light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_WHITE_VALUE + ) + + entity5 = platform.ENTITIES[5] + entity5.supported_features = ( + light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_COLOR_TEMP + ) + + entity6 = platform.ENTITIES[6] + entity6.supported_features = ( + light.SUPPORT_BRIGHTNESS + | light.SUPPORT_COLOR + | light.SUPPORT_COLOR_TEMP + | light.SUPPORT_WHITE_VALUE + ) + + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) + await hass.async_block_till_done() + + state = hass.states.get(entity0.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_ONOFF] + if light_state == STATE_OFF: + assert "color_mode" not in state.attributes + else: + assert state.attributes["color_mode"] == light.COLOR_MODE_ONOFF + + state = hass.states.get(entity1.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_BRIGHTNESS] + if light_state == STATE_OFF: + assert "color_mode" not in state.attributes + else: + assert state.attributes["color_mode"] == light.COLOR_MODE_UNKNOWN + + state = hass.states.get(entity2.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_COLOR_TEMP] + if light_state == STATE_OFF: + assert "color_mode" not in state.attributes + else: + assert state.attributes["color_mode"] == light.COLOR_MODE_UNKNOWN + + state = hass.states.get(entity3.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_HS] + if light_state == STATE_OFF: + assert "color_mode" not in state.attributes + else: + assert state.attributes["color_mode"] == light.COLOR_MODE_UNKNOWN + + state = hass.states.get(entity4.entity_id) + assert state.attributes["supported_color_modes"] == [ + light.COLOR_MODE_HS, + light.COLOR_MODE_RGBW, + ] + if light_state == STATE_OFF: + assert "color_mode" not in state.attributes + else: + assert state.attributes["color_mode"] == light.COLOR_MODE_UNKNOWN + + state = hass.states.get(entity5.entity_id) + assert state.attributes["supported_color_modes"] == [ + light.COLOR_MODE_COLOR_TEMP, + light.COLOR_MODE_HS, + ] + if light_state == STATE_OFF: + assert "color_mode" not in state.attributes + else: + assert state.attributes["color_mode"] == light.COLOR_MODE_UNKNOWN + + state = hass.states.get(entity6.entity_id) + assert state.attributes["supported_color_modes"] == [ + light.COLOR_MODE_COLOR_TEMP, + light.COLOR_MODE_HS, + light.COLOR_MODE_RGBW, + ] + if light_state == STATE_OFF: + assert "color_mode" not in state.attributes + else: + assert state.attributes["color_mode"] == light.COLOR_MODE_UNKNOWN + + +async def test_light_backwards_compatibility_color_mode(hass): + """Test color_mode if not implemented by the entity.""" + platform = getattr(hass.components, "test.light") + platform.init(empty=True) + + platform.ENTITIES.append(platform.MockLight("Test_0", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_1", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_2", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_3", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_4", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_5", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_6", STATE_ON)) + + entity0 = platform.ENTITIES[0] + + entity1 = platform.ENTITIES[1] + entity1.supported_features = light.SUPPORT_BRIGHTNESS + entity1.brightness = 100 + + entity2 = platform.ENTITIES[2] + entity2.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR_TEMP + entity2.color_temp = 100 + + entity3 = platform.ENTITIES[3] + entity3.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR + entity3.hs_color = (240, 100) + + entity4 = platform.ENTITIES[4] + entity4.supported_features = ( + light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_WHITE_VALUE + ) + entity4.hs_color = (240, 100) + entity4.white_value = 100 + + entity5 = platform.ENTITIES[5] + entity5.supported_features = ( + light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_COLOR_TEMP + ) + entity5.hs_color = (240, 100) + entity5.color_temp = 100 + + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) + await hass.async_block_till_done() + + state = hass.states.get(entity0.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_ONOFF] + assert state.attributes["color_mode"] == light.COLOR_MODE_ONOFF + + state = hass.states.get(entity1.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_BRIGHTNESS] + assert state.attributes["color_mode"] == light.COLOR_MODE_BRIGHTNESS + + state = hass.states.get(entity2.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_COLOR_TEMP] + assert state.attributes["color_mode"] == light.COLOR_MODE_COLOR_TEMP + + state = hass.states.get(entity3.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_HS] + assert state.attributes["color_mode"] == light.COLOR_MODE_HS + + state = hass.states.get(entity4.entity_id) + assert state.attributes["supported_color_modes"] == [ + light.COLOR_MODE_HS, + light.COLOR_MODE_RGBW, + ] + assert state.attributes["color_mode"] == light.COLOR_MODE_RGBW + + state = hass.states.get(entity5.entity_id) + assert state.attributes["supported_color_modes"] == [ + light.COLOR_MODE_COLOR_TEMP, + light.COLOR_MODE_HS, + ] + # hs color prioritized over color_temp, light should report mode COLOR_MODE_HS + assert state.attributes["color_mode"] == light.COLOR_MODE_HS + + +async def test_light_service_call_rgbw(hass): + """Test backwards compatibility for rgbw functionality in service calls.""" + platform = getattr(hass.components, "test.light") + platform.init(empty=True) + + platform.ENTITIES.append(platform.MockLight("Test_legacy_white_value", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_rgbw", STATE_ON)) + + entity0 = platform.ENTITIES[0] + entity0.supported_features = ( + light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_WHITE_VALUE + ) + + entity1 = platform.ENTITIES[1] + entity1.supported_color_modes = {light.COLOR_MODE_RGBW} + + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) + await hass.async_block_till_done() + + state = hass.states.get(entity0.entity_id) + assert state.attributes["supported_color_modes"] == [ + light.COLOR_MODE_HS, + light.COLOR_MODE_RGBW, + ] + + state = hass.states.get(entity1.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_RGBW] + + await hass.services.async_call( + "light", + "turn_on", + { + "entity_id": [entity0.entity_id, entity1.entity_id], + "brightness_pct": 100, + "rgbw_color": (10, 20, 30, 40), + }, + blocking=True, + ) + + _, data = entity0.last_call("turn_on") + assert data == {"brightness": 255, "hs_color": (210.0, 66.667), "white_value": 40} + _, data = entity1.last_call("turn_on") + assert data == {"brightness": 255, "rgbw_color": (10, 20, 30, 40)} + + +async def test_light_state_rgbw(hass): + """Test rgbw color conversion in state updates.""" + platform = getattr(hass.components, "test.light") + platform.init(empty=True) + + platform.ENTITIES.append(platform.MockLight("Test_legacy_white_value", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_rgbw", STATE_ON)) + + entity0 = platform.ENTITIES[0] + legacy_supported_features = ( + light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_WHITE_VALUE + ) + entity0.supported_features = legacy_supported_features + entity0.hs_color = (210.0, 66.667) + entity0.rgb_color = "Invalid" # Should be ignored + entity0.rgbww_color = "Invalid" # Should be ignored + entity0.white_value = 40 + entity0.xy_color = "Invalid" # Should be ignored + + entity1 = platform.ENTITIES[1] + entity1.supported_color_modes = {light.COLOR_MODE_RGBW} + entity1.color_mode = light.COLOR_MODE_RGBW + entity1.hs_color = "Invalid" # Should be ignored + entity1.rgb_color = "Invalid" # Should be ignored + entity1.rgbw_color = (1, 2, 3, 4) + entity1.rgbww_color = "Invalid" # Should be ignored + entity1.white_value = "Invalid" # Should be ignored + entity1.xy_color = "Invalid" # Should be ignored + + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) + await hass.async_block_till_done() + + state = hass.states.get(entity0.entity_id) + assert state.attributes == { + "color_mode": light.COLOR_MODE_RGBW, + "friendly_name": "Test_legacy_white_value", + "supported_color_modes": [light.COLOR_MODE_HS, light.COLOR_MODE_RGBW], + "supported_features": legacy_supported_features, + "hs_color": (210.0, 66.667), + "rgb_color": (84, 169, 255), + "rgbw_color": (84, 169, 255, 40), + "white_value": 40, + "xy_color": (0.173, 0.207), + } + + state = hass.states.get(entity1.entity_id) + assert state.attributes == { + "color_mode": light.COLOR_MODE_RGBW, + "friendly_name": "Test_rgbw", + "supported_color_modes": [light.COLOR_MODE_RGBW], + "supported_features": 0, + "rgbw_color": (1, 2, 3, 4), + } + + +async def test_light_service_call_color_conversion(hass): + """Test color conversion in service calls.""" + platform = getattr(hass.components, "test.light") + platform.init(empty=True) + + platform.ENTITIES.append(platform.MockLight("Test_hs", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_rgb", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_xy", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_all", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_legacy", STATE_ON)) + + entity0 = platform.ENTITIES[0] + entity0.supported_color_modes = {light.COLOR_MODE_HS} + + entity1 = platform.ENTITIES[1] + entity1.supported_color_modes = {light.COLOR_MODE_RGB} + + entity2 = platform.ENTITIES[2] + entity2.supported_color_modes = {light.COLOR_MODE_XY} + + entity3 = platform.ENTITIES[3] + entity3.supported_color_modes = { + light.COLOR_MODE_HS, + light.COLOR_MODE_RGB, + light.COLOR_MODE_XY, + } + + entity4 = platform.ENTITIES[4] + entity4.supported_features = light.SUPPORT_COLOR + + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) + await hass.async_block_till_done() + + state = hass.states.get(entity0.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_HS] + + state = hass.states.get(entity1.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_RGB] + + state = hass.states.get(entity2.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_XY] + + state = hass.states.get(entity3.entity_id) + assert state.attributes["supported_color_modes"] == [ + light.COLOR_MODE_HS, + light.COLOR_MODE_RGB, + light.COLOR_MODE_XY, + ] + + state = hass.states.get(entity4.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_HS] + + await hass.services.async_call( + "light", + "turn_on", + { + "entity_id": [ + entity0.entity_id, + entity1.entity_id, + entity2.entity_id, + entity3.entity_id, + entity4.entity_id, + ], + "brightness_pct": 100, + "hs_color": (240, 100), + }, + blocking=True, + ) + _, data = entity0.last_call("turn_on") + assert data == {"brightness": 255, "hs_color": (240.0, 100.0)} + _, data = entity1.last_call("turn_on") + assert data == {"brightness": 255, "rgb_color": (0, 0, 255)} + _, data = entity2.last_call("turn_on") + assert data == {"brightness": 255, "xy_color": (0.136, 0.04)} + _, data = entity3.last_call("turn_on") + assert data == {"brightness": 255, "hs_color": (240.0, 100.0)} + _, data = entity4.last_call("turn_on") + assert data == {"brightness": 255, "hs_color": (240.0, 100.0)} + + await hass.services.async_call( + "light", + "turn_on", + { + "entity_id": [ + entity0.entity_id, + entity1.entity_id, + entity2.entity_id, + entity3.entity_id, + entity4.entity_id, + ], + "brightness_pct": 50, + "rgb_color": (128, 0, 0), + }, + blocking=True, + ) + _, data = entity0.last_call("turn_on") + assert data == {"brightness": 128, "hs_color": (0.0, 100.0)} + _, data = entity1.last_call("turn_on") + assert data == {"brightness": 128, "rgb_color": (128, 0, 0)} + _, data = entity2.last_call("turn_on") + assert data == {"brightness": 128, "xy_color": (0.701, 0.299)} + _, data = entity3.last_call("turn_on") + assert data == {"brightness": 128, "rgb_color": (128, 0, 0)} + _, data = entity4.last_call("turn_on") + assert data == {"brightness": 128, "hs_color": (0.0, 100.0)} + + await hass.services.async_call( + "light", + "turn_on", + { + "entity_id": [ + entity0.entity_id, + entity1.entity_id, + entity2.entity_id, + entity3.entity_id, + entity4.entity_id, + ], + "brightness_pct": 50, + "xy_color": (0.1, 0.8), + }, + blocking=True, + ) + _, data = entity0.last_call("turn_on") + assert data == {"brightness": 128, "hs_color": (125.176, 100.0)} + _, data = entity1.last_call("turn_on") + assert data == {"brightness": 128, "rgb_color": (0, 255, 22)} + _, data = entity2.last_call("turn_on") + assert data == {"brightness": 128, "xy_color": (0.1, 0.8)} + _, data = entity3.last_call("turn_on") + assert data == {"brightness": 128, "xy_color": (0.1, 0.8)} + _, data = entity4.last_call("turn_on") + assert data == {"brightness": 128, "hs_color": (125.176, 100.0)} + + +async def test_light_state_color_conversion(hass): + """Test color conversion in state updates.""" + platform = getattr(hass.components, "test.light") + platform.init(empty=True) + + platform.ENTITIES.append(platform.MockLight("Test_hs", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_rgb", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_xy", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_legacy", STATE_ON)) + + entity0 = platform.ENTITIES[0] + entity0.supported_color_modes = {light.COLOR_MODE_HS} + entity0.color_mode = light.COLOR_MODE_HS + entity0.hs_color = (240, 100) + entity0.rgb_color = "Invalid" # Should be ignored + entity0.xy_color = "Invalid" # Should be ignored + + entity1 = platform.ENTITIES[1] + entity1.supported_color_modes = {light.COLOR_MODE_RGB} + entity1.color_mode = light.COLOR_MODE_RGB + entity1.hs_color = "Invalid" # Should be ignored + entity1.rgb_color = (128, 0, 0) + entity1.xy_color = "Invalid" # Should be ignored + + entity2 = platform.ENTITIES[2] + entity2.supported_color_modes = {light.COLOR_MODE_XY} + entity2.color_mode = light.COLOR_MODE_XY + entity2.hs_color = "Invalid" # Should be ignored + entity2.rgb_color = "Invalid" # Should be ignored + entity2.xy_color = (0.1, 0.8) + + entity3 = platform.ENTITIES[3] + entity3.hs_color = (240, 100) + entity3.supported_features = light.SUPPORT_COLOR + + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) + await hass.async_block_till_done() + + state = hass.states.get(entity0.entity_id) + assert state.attributes["color_mode"] == light.COLOR_MODE_HS + assert state.attributes["hs_color"] == (240, 100) + assert state.attributes["rgb_color"] == (0, 0, 255) + assert state.attributes["xy_color"] == (0.136, 0.04) + + state = hass.states.get(entity1.entity_id) + assert state.attributes["color_mode"] == light.COLOR_MODE_RGB + assert state.attributes["hs_color"] == (0.0, 100.0) + assert state.attributes["rgb_color"] == (128, 0, 0) + assert state.attributes["xy_color"] == (0.701, 0.299) + + state = hass.states.get(entity2.entity_id) + assert state.attributes["color_mode"] == light.COLOR_MODE_XY + assert state.attributes["hs_color"] == (125.176, 100.0) + assert state.attributes["rgb_color"] == (0, 255, 22) + assert state.attributes["xy_color"] == (0.1, 0.8) + + state = hass.states.get(entity3.entity_id) + assert state.attributes["color_mode"] == light.COLOR_MODE_HS + assert state.attributes["hs_color"] == (240, 100) + assert state.attributes["rgb_color"] == (0, 0, 255) + assert state.attributes["xy_color"] == (0.136, 0.04) diff --git a/tests/components/light/test_reproduce_state.py b/tests/components/light/test_reproduce_state.py index e96f4ff4528..815b8831d37 100644 --- a/tests/components/light/test_reproduce_state.py +++ b/tests/components/light/test_reproduce_state.py @@ -1,4 +1,7 @@ """Test reproduce state for Light.""" +import pytest + +from homeassistant.components import light from homeassistant.components.light.reproduce_state import DEPRECATION_WARNING from homeassistant.core import State @@ -15,6 +18,8 @@ VALID_HS_COLOR = {"hs_color": (345, 75)} VALID_KELVIN = {"kelvin": 4000} VALID_PROFILE = {"profile": "relax"} VALID_RGB_COLOR = {"rgb_color": (255, 63, 111)} +VALID_RGBW_COLOR = {"rgbw_color": (255, 63, 111, 10)} +VALID_RGBWW_COLOR = {"rgbww_color": (255, 63, 111, 10, 20)} VALID_XY_COLOR = {"xy_color": (0.59, 0.274)} @@ -91,51 +96,51 @@ async def test_reproducing_states(hass, caplog): expected_calls = [] - expected_off = VALID_BRIGHTNESS + expected_off = dict(VALID_BRIGHTNESS) expected_off["entity_id"] = "light.entity_off" expected_calls.append(expected_off) - expected_bright = VALID_WHITE_VALUE + expected_bright = dict(VALID_WHITE_VALUE) expected_bright["entity_id"] = "light.entity_bright" expected_calls.append(expected_bright) - expected_white = VALID_FLASH + expected_white = dict(VALID_FLASH) expected_white["entity_id"] = "light.entity_white" expected_calls.append(expected_white) - expected_flash = VALID_EFFECT + expected_flash = dict(VALID_EFFECT) expected_flash["entity_id"] = "light.entity_flash" expected_calls.append(expected_flash) - expected_effect = VALID_TRANSITION + expected_effect = dict(VALID_TRANSITION) expected_effect["entity_id"] = "light.entity_effect" expected_calls.append(expected_effect) - expected_trans = VALID_COLOR_NAME + expected_trans = dict(VALID_COLOR_NAME) expected_trans["entity_id"] = "light.entity_trans" expected_calls.append(expected_trans) - expected_name = VALID_COLOR_TEMP + expected_name = dict(VALID_COLOR_TEMP) expected_name["entity_id"] = "light.entity_name" expected_calls.append(expected_name) - expected_temp = VALID_HS_COLOR + expected_temp = dict(VALID_HS_COLOR) expected_temp["entity_id"] = "light.entity_temp" expected_calls.append(expected_temp) - expected_hs = VALID_KELVIN + expected_hs = dict(VALID_KELVIN) expected_hs["entity_id"] = "light.entity_hs" expected_calls.append(expected_hs) - expected_kelvin = VALID_PROFILE + expected_kelvin = dict(VALID_PROFILE) expected_kelvin["entity_id"] = "light.entity_kelvin" expected_calls.append(expected_kelvin) - expected_profile = VALID_RGB_COLOR + expected_profile = dict(VALID_RGB_COLOR) expected_profile["entity_id"] = "light.entity_profile" expected_calls.append(expected_profile) - expected_rgb = VALID_XY_COLOR + expected_rgb = dict(VALID_XY_COLOR) expected_rgb["entity_id"] = "light.entity_rgb" expected_calls.append(expected_rgb) @@ -156,6 +161,59 @@ async def test_reproducing_states(hass, caplog): assert turn_off_calls[0].data == {"entity_id": "light.entity_xy"} +@pytest.mark.parametrize( + "color_mode", + ( + light.COLOR_MODE_COLOR_TEMP, + light.COLOR_MODE_BRIGHTNESS, + light.COLOR_MODE_HS, + light.COLOR_MODE_ONOFF, + light.COLOR_MODE_RGB, + light.COLOR_MODE_RGBW, + light.COLOR_MODE_RGBWW, + light.COLOR_MODE_UNKNOWN, + light.COLOR_MODE_XY, + ), +) +async def test_filter_color_modes(hass, caplog, color_mode): + """Test filtering of parameters according to color mode.""" + hass.states.async_set("light.entity", "off", {}) + all_colors = { + **VALID_WHITE_VALUE, + **VALID_COLOR_NAME, + **VALID_COLOR_TEMP, + **VALID_HS_COLOR, + **VALID_KELVIN, + **VALID_RGB_COLOR, + **VALID_RGBW_COLOR, + **VALID_RGBWW_COLOR, + **VALID_XY_COLOR, + } + + turn_on_calls = async_mock_service(hass, "light", "turn_on") + + await hass.helpers.state.async_reproduce_state( + [State("light.entity", "on", {**all_colors, "color_mode": color_mode})] + ) + + expected_map = { + light.COLOR_MODE_COLOR_TEMP: VALID_COLOR_TEMP, + light.COLOR_MODE_BRIGHTNESS: {}, + light.COLOR_MODE_HS: VALID_HS_COLOR, + light.COLOR_MODE_ONOFF: {}, + light.COLOR_MODE_RGB: VALID_RGB_COLOR, + light.COLOR_MODE_RGBW: VALID_RGBW_COLOR, + light.COLOR_MODE_RGBWW: VALID_RGBWW_COLOR, + light.COLOR_MODE_UNKNOWN: {**VALID_HS_COLOR, **VALID_WHITE_VALUE}, + light.COLOR_MODE_XY: VALID_XY_COLOR, + } + expected = expected_map[color_mode] + + assert len(turn_on_calls) == 1 + assert turn_on_calls[0].domain == "light" + assert dict(turn_on_calls[0].data) == {"entity_id": "light.entity", **expected} + + async def test_deprecation_warning(hass, caplog): """Test deprecation warning.""" hass.states.async_set("light.entity_off", "off", {}) diff --git a/tests/components/yeelight/test_light.py b/tests/components/yeelight/test_light.py index 90a8d1f9e6e..b6ce2fa4faf 100644 --- a/tests/components/yeelight/test_light.py +++ b/tests/components/yeelight/test_light.py @@ -430,6 +430,8 @@ async def test_device_types(hass: HomeAssistant): "effect_list": YEELIGHT_MONO_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, "brightness": bright, + "color_mode": "brightness", + "supported_color_modes": ["brightness"], }, ) @@ -441,6 +443,8 @@ async def test_device_types(hass: HomeAssistant): "effect_list": YEELIGHT_MONO_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, "brightness": bright, + "color_mode": "brightness", + "supported_color_modes": ["brightness"], }, ) @@ -463,8 +467,14 @@ async def test_device_types(hass: HomeAssistant): "hs_color": hs_color, "rgb_color": rgb_color, "xy_color": xy_color, + "color_mode": "hs", + "supported_color_modes": ["color_temp", "hs"], + }, + { + "supported_features": 0, + "color_mode": "onoff", + "supported_color_modes": ["onoff"], }, - {"supported_features": 0}, ) # WhiteTemp @@ -483,11 +493,15 @@ async def test_device_types(hass: HomeAssistant): ), "brightness": current_brightness, "color_temp": ct, + "color_mode": "color_temp", + "supported_color_modes": ["color_temp"], }, { "effect_list": YEELIGHT_TEMP_ONLY_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, "brightness": nl_br, + "color_mode": "brightness", + "supported_color_modes": ["brightness"], }, ) @@ -512,11 +526,15 @@ async def test_device_types(hass: HomeAssistant): ), "brightness": current_brightness, "color_temp": ct, + "color_mode": "color_temp", + "supported_color_modes": ["color_temp"], }, { "effect_list": YEELIGHT_TEMP_ONLY_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, "brightness": nl_br, + "color_mode": "brightness", + "supported_color_modes": ["brightness"], }, ) await _async_test( @@ -532,6 +550,8 @@ async def test_device_types(hass: HomeAssistant): "hs_color": bg_hs_color, "rgb_color": bg_rgb_color, "xy_color": bg_xy_color, + "color_mode": "hs", + "supported_color_modes": ["color_temp", "hs"], }, name=f"{UNIQUE_NAME} ambilight", entity_id=f"{ENTITY_LIGHT}_ambilight", diff --git a/tests/components/zerproc/test_light.py b/tests/components/zerproc/test_light.py index 1dc608e18df..9fac5085986 100644 --- a/tests/components/zerproc/test_light.py +++ b/tests/components/zerproc/test_light.py @@ -7,9 +7,12 @@ import pyzerproc from homeassistant import setup from homeassistant.components.light import ( ATTR_BRIGHTNESS, + ATTR_COLOR_MODE, ATTR_HS_COLOR, ATTR_RGB_COLOR, + ATTR_SUPPORTED_COLOR_MODES, ATTR_XY_COLOR, + COLOR_MODE_HS, SCAN_INTERVAL, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, @@ -96,6 +99,7 @@ async def test_init(hass, mock_entry): assert state.state == STATE_OFF assert state.attributes == { ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR, ATTR_ICON: "mdi:string-lights", } @@ -104,8 +108,10 @@ async def test_init(hass, mock_entry): assert state.state == STATE_ON assert state.attributes == { ATTR_FRIENDLY_NAME: "LEDBlue-33445566", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR, ATTR_ICON: "mdi:string-lights", + ATTR_COLOR_MODE: COLOR_MODE_HS, ATTR_BRIGHTNESS: 255, ATTR_HS_COLOR: (221.176, 100.0), ATTR_RGB_COLOR: (0, 80, 255), @@ -272,6 +278,7 @@ async def test_light_update(hass, mock_light): assert state.state == STATE_OFF assert state.attributes == { ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR, ATTR_ICON: "mdi:string-lights", } @@ -290,6 +297,7 @@ async def test_light_update(hass, mock_light): assert state.state == STATE_UNAVAILABLE assert state.attributes == { ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR, ATTR_ICON: "mdi:string-lights", } @@ -307,6 +315,7 @@ async def test_light_update(hass, mock_light): assert state.state == STATE_OFF assert state.attributes == { ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR, ATTR_ICON: "mdi:string-lights", } @@ -324,8 +333,10 @@ async def test_light_update(hass, mock_light): assert state.state == STATE_ON assert state.attributes == { ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR, ATTR_ICON: "mdi:string-lights", + ATTR_COLOR_MODE: COLOR_MODE_HS, ATTR_BRIGHTNESS: 220, ATTR_HS_COLOR: (261.429, 31.818), ATTR_RGB_COLOR: (202, 173, 255), diff --git a/tests/testing_config/custom_components/test/light.py b/tests/testing_config/custom_components/test/light.py index 863412fe747..84008d90c27 100644 --- a/tests/testing_config/custom_components/test/light.py +++ b/tests/testing_config/custom_components/test/light.py @@ -37,4 +37,17 @@ class MockLight(MockToggleEntity, LightEntity): """Mock light class.""" brightness = None + supported_color_modes = None supported_features = 0 + + color_mode = None + + hs_color = None + xy_color = None + rgb_color = None + rgbw_color = None + rgbww_color = None + + color_temp = None + + white_value = None From 9647eeb2e08822da190e47208e57b4133facd5cf Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 16 Mar 2021 14:21:05 +0100 Subject: [PATCH 396/831] Add custom JSONEncoder for automation traces (#47942) * Add custom JSONEncoder for automation traces * Add tests * Update default case to include type * Tweak * Refactor * Tweak * Lint * Update websocket_api.py --- homeassistant/components/automation/trace.py | 18 ++++++++ .../components/automation/websocket_api.py | 12 +++++- tests/components/automation/test_trace.py | 42 +++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 tests/components/automation/test_trace.py diff --git a/homeassistant/components/automation/trace.py b/homeassistant/components/automation/trace.py index 351ca1ed979..4aac3d327b8 100644 --- a/homeassistant/components/automation/trace.py +++ b/homeassistant/components/automation/trace.py @@ -2,11 +2,13 @@ from collections import OrderedDict from contextlib import contextmanager import datetime as dt +from datetime import timedelta from itertools import count import logging from typing import Any, Awaitable, Callable, Deque, Dict, Optional from homeassistant.core import Context, HomeAssistant, callback +from homeassistant.helpers.json import JSONEncoder as HAJSONEncoder from homeassistant.helpers.trace import TraceElement, trace_id_set from homeassistant.helpers.typing import TemplateVarsType from homeassistant.util import dt as dt_util @@ -203,3 +205,19 @@ def get_debug_traces(hass, summary=False): traces.extend(get_debug_traces_for_automation(hass, automation_id, summary)) return traces + + +class TraceJSONEncoder(HAJSONEncoder): + """JSONEncoder that supports Home Assistant objects and falls back to repr(o).""" + + def default(self, o: Any) -> Any: + """Convert certain objects. + + Fall back to repr(o). + """ + if isinstance(o, timedelta): + return {"__type": str(type(o)), "total_seconds": o.total_seconds()} + try: + return super().default(o) + except TypeError: + return {"__type": str(type(o)), "repr": repr(o)} diff --git a/homeassistant/components/automation/websocket_api.py b/homeassistant/components/automation/websocket_api.py index bb47dd58ff9..eba56f94e7d 100644 --- a/homeassistant/components/automation/websocket_api.py +++ b/homeassistant/components/automation/websocket_api.py @@ -1,4 +1,6 @@ """Websocket API for automation.""" +import json + import voluptuous as vol from homeassistant.components import websocket_api @@ -21,7 +23,12 @@ from homeassistant.helpers.script import ( debug_stop, ) -from .trace import get_debug_trace, get_debug_traces, get_debug_traces_for_automation +from .trace import ( + TraceJSONEncoder, + get_debug_trace, + get_debug_traces, + get_debug_traces_for_automation, +) # mypy: allow-untyped-calls, allow-untyped-defs @@ -55,8 +62,9 @@ def websocket_automation_trace_get(hass, connection, msg): run_id = msg["run_id"] trace = get_debug_trace(hass, automation_id, run_id) + message = websocket_api.messages.result_message(msg["id"], trace) - connection.send_result(msg["id"], trace) + connection.send_message(json.dumps(message, cls=TraceJSONEncoder, allow_nan=False)) @callback diff --git a/tests/components/automation/test_trace.py b/tests/components/automation/test_trace.py new file mode 100644 index 00000000000..818f1ee1768 --- /dev/null +++ b/tests/components/automation/test_trace.py @@ -0,0 +1,42 @@ +"""Test Automation trace helpers.""" +from datetime import timedelta + +from homeassistant import core +from homeassistant.components import automation +from homeassistant.util import dt as dt_util + + +def test_json_encoder(hass): + """Test the Trace JSON Encoder.""" + ha_json_enc = automation.trace.TraceJSONEncoder() + state = core.State("test.test", "hello") + + # Test serializing a datetime + now = dt_util.utcnow() + assert ha_json_enc.default(now) == now.isoformat() + + # Test serializing a timedelta + data = timedelta( + days=50, + seconds=27, + microseconds=10, + milliseconds=29000, + minutes=5, + hours=8, + weeks=2, + ) + assert ha_json_enc.default(data) == { + "__type": str(type(data)), + "total_seconds": data.total_seconds(), + } + + # Test serializing a set() + data = {"milk", "beer"} + assert sorted(ha_json_enc.default(data)) == sorted(list(data)) + + # Test serializong object which implements as_dict + assert ha_json_enc.default(state) == state.as_dict() + + # Default method falls back to repr(o) + o = object() + assert ha_json_enc.default(o) == {"__type": str(type(o)), "repr": repr(o)} From 673ebe291192c0d6ad2de82ab30897b8b535694a Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Tue, 16 Mar 2021 10:02:26 -0400 Subject: [PATCH 397/831] Guard extra call in ZHA lights (#47832) * add flag to prevent sending an on command * fix condition * add constant for default transition * make groups work with new force on flag * reorder light entity creation * rearrange logic * update test * remove failed attempt at group light flag * fix flag --- homeassistant/components/zha/light.py | 19 +++++++++++++++++-- tests/components/zha/test_light.py | 8 +++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index e7d9be62374..f81b931a49d 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -65,6 +65,8 @@ CAPABILITIES_COLOR_LOOP = 0x4 CAPABILITIES_COLOR_XY = 0x08 CAPABILITIES_COLOR_TEMP = 0x10 +DEFAULT_TRANSITION = 1 + UPDATE_COLORLOOP_ACTION = 0x1 UPDATE_COLORLOOP_DIRECTION = 0x2 UPDATE_COLORLOOP_TIME = 0x4 @@ -114,6 +116,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class BaseLight(LogMixin, light.LightEntity): """Operations common to all light entities.""" + _FORCE_ON = False + def __init__(self, *args, **kwargs): """Initialize the light.""" super().__init__(*args, **kwargs) @@ -201,7 +205,7 @@ class BaseLight(LogMixin, light.LightEntity): async def async_turn_on(self, **kwargs): """Turn the entity on.""" transition = kwargs.get(light.ATTR_TRANSITION) - duration = transition * 10 if transition else 1 + duration = transition * 10 if transition else DEFAULT_TRANSITION brightness = kwargs.get(light.ATTR_BRIGHTNESS) effect = kwargs.get(light.ATTR_EFFECT) flash = kwargs.get(light.ATTR_FLASH) @@ -228,7 +232,7 @@ class BaseLight(LogMixin, light.LightEntity): if level: self._brightness = level - if brightness is None or brightness: + if brightness is None or (self._FORCE_ON and brightness): # since some lights don't always turn on with move_to_level_with_on_off, # we should call the on command on the on_off cluster if brightness is not 0. result = await self._on_off_channel.on() @@ -512,6 +516,17 @@ class HueLight(Light): _REFRESH_INTERVAL = (3, 5) +@STRICT_MATCH( + channel_names=CHANNEL_ON_OFF, + aux_channels={CHANNEL_COLOR, CHANNEL_LEVEL}, + manufacturers="Jasco", +) +class ForceOnLight(Light): + """Representation of a light which does not respect move_to_level_with_on_off.""" + + _FORCE_ON = True + + @GROUP_MATCH() class LightGroup(BaseLight, ZhaGroupEntity): """Representation of a light group.""" diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py index 0a9a492a148..021f4f09dd9 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -392,13 +392,11 @@ async def async_test_level_on_off_from_hass( await hass.services.async_call( DOMAIN, "turn_on", {"entity_id": entity_id, "brightness": 10}, blocking=True ) - assert on_off_cluster.request.call_count == 1 - assert on_off_cluster.request.await_count == 1 + # the onoff cluster is now not used when brightness is present by default + assert on_off_cluster.request.call_count == 0 + assert on_off_cluster.request.await_count == 0 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, tries=1, tsn=None - ) assert level_cluster.request.call_args == call( False, 4, From 4dc0cdbb5fee2660fb89ecc143c25f5c3aa94802 Mon Sep 17 00:00:00 2001 From: Antoine Meillet Date: Tue, 16 Mar 2021 16:11:51 +0100 Subject: [PATCH 398/831] Ignore STATE_UNKNOWN in prometheus (#47840) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Giving a value of 0 by default can lead to erroneous data being exported. For example, if a MQTT temperature sensor is in `STATE_UNKNOWN` (which can happen after a HASS restart), a temperature of 0°C will be exported. Some user might prefer no value rather than a wrong one. --- homeassistant/components/prometheus/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py index bbab9af83e8..b253daf559e 100644 --- a/homeassistant/components/prometheus/__init__.py +++ b/homeassistant/components/prometheus/__init__.py @@ -29,6 +29,7 @@ from homeassistant.const import ( PERCENTAGE, STATE_ON, STATE_UNAVAILABLE, + STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT, ) @@ -154,9 +155,11 @@ class PrometheusMetrics: if not self._filter(state.entity_id): return + ignored_states = (STATE_UNAVAILABLE, STATE_UNKNOWN) + handler = f"_handle_{domain}" - if hasattr(self, handler) and state.state != STATE_UNAVAILABLE: + if hasattr(self, handler) and state.state not in ignored_states: getattr(self, handler)(state) labels = self._labels(state) @@ -168,9 +171,9 @@ class PrometheusMetrics: entity_available = self._metric( "entity_available", self.prometheus_cli.Gauge, - "Entity is available (not in the unavailable state)", + "Entity is available (not in the unavailable or unknown state)", ) - entity_available.labels(**labels).set(float(state.state != STATE_UNAVAILABLE)) + entity_available.labels(**labels).set(float(state.state not in ignored_states)) last_updated_time_seconds = self._metric( "last_updated_time_seconds", From f695155af58cbe615fc234e0029fd276dbcb9922 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 16 Mar 2021 16:30:04 +0100 Subject: [PATCH 399/831] Add device classes to Verisure sensors (#47990) --- .../components/verisure/alarm_control_panel.py | 6 +++--- .../components/verisure/binary_sensor.py | 14 +++++++------- homeassistant/components/verisure/camera.py | 4 ++-- homeassistant/components/verisure/lock.py | 2 +- homeassistant/components/verisure/sensor.py | 16 +++++++++++++++- homeassistant/components/verisure/switch.py | 2 +- 6 files changed, 29 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index 54e36ac65de..761feb0d2cb 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -48,12 +48,12 @@ class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity): @property def name(self) -> str: - """Return the name of the device.""" + """Return the name of the entity.""" return "Verisure Alarm" @property def unique_id(self) -> str: - """Return the unique ID for this alarm control panel.""" + """Return the unique ID for this entity.""" return self.coordinator.entry.data[CONF_GIID] @property @@ -68,7 +68,7 @@ class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity): @property def state(self) -> str | None: - """Return the state of the device.""" + """Return the state of the entity.""" status = self.coordinator.data["alarm"]["statusType"] if status == "DISARMED": self._state = STATE_ALARM_DISARMED diff --git a/homeassistant/components/verisure/binary_sensor.py b/homeassistant/components/verisure/binary_sensor.py index 864785c7cd0..3363178efe2 100644 --- a/homeassistant/components/verisure/binary_sensor.py +++ b/homeassistant/components/verisure/binary_sensor.py @@ -22,7 +22,7 @@ async def async_setup_entry( entry: ConfigEntry, async_add_entities: Callable[[Iterable[Entity]], None], ) -> None: - """Set up Verisure sensors based on a config entry.""" + """Set up Verisure binary sensors based on a config entry.""" coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] sensors: list[Entity] = [VerisureEthernetStatus(coordinator)] @@ -49,12 +49,12 @@ class VerisureDoorWindowSensor(CoordinatorEntity, BinarySensorEntity): @property def name(self) -> str: - """Return the name of the binary sensor.""" + """Return the name of this entity.""" return self.coordinator.data["door_window"][self.serial_number]["area"] @property def unique_id(self) -> str: - """Return the unique ID for this alarm control panel.""" + """Return the unique ID for this entity.""" return f"{self.serial_number}_door_window" @property @@ -72,7 +72,7 @@ class VerisureDoorWindowSensor(CoordinatorEntity, BinarySensorEntity): @property def device_class(self) -> str: - """Return the class of this device, from component DEVICE_CLASSES.""" + """Return the class of this entity.""" return DEVICE_CLASS_OPENING @property @@ -98,12 +98,12 @@ class VerisureEthernetStatus(CoordinatorEntity, BinarySensorEntity): @property def name(self) -> str: - """Return the name of the binary sensor.""" + """Return the name of this entity.""" return "Verisure Ethernet status" @property def unique_id(self) -> str: - """Return the unique ID for this binary sensor.""" + """Return the unique ID for this entity.""" return f"{self.coordinator.entry.data[CONF_GIID]}_ethernet" @property @@ -128,5 +128,5 @@ class VerisureEthernetStatus(CoordinatorEntity, BinarySensorEntity): @property def device_class(self) -> str: - """Return the class of this device, from component DEVICE_CLASSES.""" + """Return the class of this entity.""" return DEVICE_CLASS_CONNECTIVITY diff --git a/homeassistant/components/verisure/camera.py b/homeassistant/components/verisure/camera.py index 006f9c28ef5..c97d7f8c76c 100644 --- a/homeassistant/components/verisure/camera.py +++ b/homeassistant/components/verisure/camera.py @@ -64,12 +64,12 @@ class VerisureSmartcam(CoordinatorEntity, Camera): @property def name(self) -> str: - """Return the name of this camera.""" + """Return the name of this entity.""" return self.coordinator.data["cameras"][self.serial_number]["area"] @property def unique_id(self) -> str: - """Return the unique ID for this camera.""" + """Return the unique ID for this entity.""" return self.serial_number @property diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index d4708c68e88..eeec7e53a0a 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -71,7 +71,7 @@ class VerisureDoorlock(CoordinatorEntity, LockEntity): @property def name(self) -> str: - """Return the name of the lock.""" + """Return the name of this entity.""" return self.coordinator.data["locks"][self.serial_number]["area"] @property diff --git a/homeassistant/components/verisure/sensor.py b/homeassistant/components/verisure/sensor.py index 614bd0a386d..f7f2212149b 100644 --- a/homeassistant/components/verisure/sensor.py +++ b/homeassistant/components/verisure/sensor.py @@ -3,6 +3,10 @@ from __future__ import annotations from typing import Any, Callable, Iterable +from homeassistant.components.sensor import ( + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE, TEMP_CELSIUS from homeassistant.core import HomeAssistant @@ -64,6 +68,11 @@ class VerisureThermometer(CoordinatorEntity, Entity): """Return the unique ID for this entity.""" return f"{self.serial_number}_temperature" + @property + def device_class(self) -> str: + """Return the class of this entity.""" + return DEVICE_CLASS_TEMPERATURE + @property def device_info(self) -> dict[str, Any]: """Return device information about this entity.""" @@ -123,6 +132,11 @@ class VerisureHygrometer(CoordinatorEntity, Entity): """Return the unique ID for this entity.""" return f"{self.serial_number}_humidity" + @property + def device_class(self) -> str: + """Return the class of this entity.""" + return DEVICE_CLASS_HUMIDITY + @property def device_info(self) -> dict[str, Any]: """Return device information about this entity.""" @@ -197,7 +211,7 @@ class VerisureMouseDetection(CoordinatorEntity, Entity): @property def state(self) -> str | None: - """Return the state of the device.""" + """Return the state of the entity.""" return self.coordinator.data["mice"][self.serial_number]["detections"] @property diff --git a/homeassistant/components/verisure/switch.py b/homeassistant/components/verisure/switch.py index 534db9c7a50..f55db8ce428 100644 --- a/homeassistant/components/verisure/switch.py +++ b/homeassistant/components/verisure/switch.py @@ -43,7 +43,7 @@ class VerisureSmartplug(CoordinatorEntity, SwitchEntity): @property def name(self) -> str: - """Return the name or location of the smartplug.""" + """Return the name of this entity.""" return self.coordinator.data["smart_plugs"][self.serial_number]["area"] @property From 7ed9e5b2c203b8968c32983c342d34e07cdea059 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Tue, 16 Mar 2021 16:43:11 +0100 Subject: [PATCH 400/831] Update xknx to 0.17.3 (#47996) --- homeassistant/components/knx/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index f8ed51f364a..7f14c17839c 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -2,7 +2,7 @@ "domain": "knx", "name": "KNX", "documentation": "https://www.home-assistant.io/integrations/knx", - "requirements": ["xknx==0.17.2"], + "requirements": ["xknx==0.17.3"], "codeowners": ["@Julius2342", "@farmio", "@marvin-w"], "quality_scale": "silver" } diff --git a/requirements_all.txt b/requirements_all.txt index d1e27d28076..c07215ade32 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2329,7 +2329,7 @@ xbox-webapi==2.0.8 xboxapi==2.0.1 # homeassistant.components.knx -xknx==0.17.2 +xknx==0.17.3 # homeassistant.components.bluesound # homeassistant.components.rest From 389891d13d18cd69e34b819b4bb6fa06f52d3067 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 16 Mar 2021 17:12:51 +0100 Subject: [PATCH 401/831] Improve JSONEncoder test coverage (#47935) --- tests/helpers/test_json.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/helpers/test_json.py b/tests/helpers/test_json.py index 8ae516b2bbe..55d566f5edd 100644 --- a/tests/helpers/test_json.py +++ b/tests/helpers/test_json.py @@ -11,11 +11,17 @@ def test_json_encoder(hass): ha_json_enc = JSONEncoder() state = core.State("test.test", "hello") + # Test serializing a datetime + now = dt_util.utcnow() + assert ha_json_enc.default(now) == now.isoformat() + + # Test serializing a set() + data = {"milk", "beer"} + assert sorted(ha_json_enc.default(data)) == sorted(list(data)) + + # Test serializing an object which implements as_dict assert ha_json_enc.default(state) == state.as_dict() # Default method raises TypeError if non HA object with pytest.raises(TypeError): ha_json_enc.default(1) - - now = dt_util.utcnow() - assert ha_json_enc.default(now) == now.isoformat() From 0097169dd36dde9471062c0f0cfb523b7276ec52 Mon Sep 17 00:00:00 2001 From: Klaas Schoute Date: Tue, 16 Mar 2021 17:14:07 +0100 Subject: [PATCH 402/831] Add aliases to actions in automation blueprints (#47940) --- .../automation/blueprints/motion_light.yaml | 12 ++++++++---- .../automation/blueprints/notify_leaving_zone.yaml | 9 +++++---- tests/components/automation/test_blueprint.py | 1 + 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/automation/blueprints/motion_light.yaml b/homeassistant/components/automation/blueprints/motion_light.yaml index c11d22d974e..54a4a4f0643 100644 --- a/homeassistant/components/automation/blueprints/motion_light.yaml +++ b/homeassistant/components/automation/blueprints/motion_light.yaml @@ -38,13 +38,17 @@ trigger: to: "on" action: - - service: light.turn_on + - alias: "Turn on the light" + service: light.turn_on target: !input light_target - - wait_for_trigger: + - alias: "Wait until there is no motion from device" + wait_for_trigger: platform: state entity_id: !input motion_entity from: "on" to: "off" - - delay: !input no_motion_wait - - service: light.turn_off + - alias: "Wait the number of seconds that has been set" + delay: !input no_motion_wait + - alias: "Turn off the light" + service: light.turn_off target: !input light_target diff --git a/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml b/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml index d3a70d773ee..71abf8f865c 100644 --- a/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml +++ b/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml @@ -37,7 +37,8 @@ condition: value_template: "{{ trigger.from_state.state == zone_state and trigger.to_state.state != zone_state }}" action: - domain: mobile_app - type: notify - device_id: !input notify_device - message: "{{ person_name }} has left {{ zone_state }}" + - alias: "Notify that a person has left the zone" + domain: mobile_app + type: notify + device_id: !input notify_device + message: "{{ person_name }} has left {{ zone_state }}" diff --git a/tests/components/automation/test_blueprint.py b/tests/components/automation/test_blueprint.py index 1a6ccec7a8e..747e162fe46 100644 --- a/tests/components/automation/test_blueprint.py +++ b/tests/components/automation/test_blueprint.py @@ -84,6 +84,7 @@ async def test_notify_leaving_zone(hass): _hass, config, variables, _context = mock_call_action.mock_calls[0][1] message_tpl = config.pop("message") assert config == { + "alias": "Notify that a person has left the zone", "domain": "mobile_app", "type": "notify", "device_id": "abcdefgh", From bbd98e196b70a08800a23b954ed81b62a7eabf75 Mon Sep 17 00:00:00 2001 From: Ron Heft Date: Tue, 16 Mar 2021 12:15:22 -0400 Subject: [PATCH 403/831] Fix withings InvalidParamsException (#47975) * Bump withings_api to 2.3.1 (fixes #47329) * Fix NotifyAppli calls to be compatible with withings_api 2.3.1 * Fix errors with withings_api 2.2+ using pydantic * Bump withings_api to 2.3.2 --- homeassistant/components/withings/__init__.py | 8 +++----- homeassistant/components/withings/common.py | 4 ++-- homeassistant/components/withings/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/withings/__init__.py b/homeassistant/components/withings/__init__.py index c6f420d172a..5efa22e6a86 100644 --- a/homeassistant/components/withings/__init__.py +++ b/homeassistant/components/withings/__init__.py @@ -4,12 +4,12 @@ Support for the Withings API. For more details about this platform, please refer to the documentation at """ import asyncio -from typing import Optional, cast +from typing import Optional from aiohttp.web import Request, Response import voluptuous as vol from withings_api import WithingsAuth -from withings_api.common import NotifyAppli, enum_or_raise +from withings_api.common import NotifyAppli from homeassistant.components import webhook from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN @@ -195,9 +195,7 @@ async def async_webhook_handler( return json_message_response("Parameter appli not provided", message_code=20) try: - appli = cast( - NotifyAppli, enum_or_raise(int(params.getone("appli")), NotifyAppli) - ) + appli = NotifyAppli(int(params.getone("appli"))) except ValueError: return json_message_response("Invalid appli provided", message_code=21) diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index e2e0b06e342..54a0a3c94ee 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -681,7 +681,7 @@ class DataManager: ) # Determine what subscriptions need to be created. - ignored_applis = frozenset({NotifyAppli.USER}) + ignored_applis = frozenset({NotifyAppli.USER, NotifyAppli.UNKNOWN}) to_add_applis = frozenset( [ appli @@ -846,7 +846,7 @@ class DataManager: data = serie.data for field in GetSleepSummaryField: - raw_values[field].append(data._asdict()[field.value]) + raw_values[field].append(dict(data)[field.value]) values: Dict[GetSleepSummaryField, float] = {} diff --git a/homeassistant/components/withings/manifest.json b/homeassistant/components/withings/manifest.json index ec981ff691c..6b2918722ba 100644 --- a/homeassistant/components/withings/manifest.json +++ b/homeassistant/components/withings/manifest.json @@ -3,7 +3,7 @@ "name": "Withings", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/withings", - "requirements": ["withings-api==2.1.6"], + "requirements": ["withings-api==2.3.2"], "dependencies": ["http", "webhook"], "codeowners": ["@vangorra"] } diff --git a/requirements_all.txt b/requirements_all.txt index c07215ade32..ca653fb6e1b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2311,7 +2311,7 @@ wiffi==1.0.1 wirelesstagpy==0.4.1 # homeassistant.components.withings -withings-api==2.1.6 +withings-api==2.3.2 # homeassistant.components.wled wled==0.4.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ed005d34d03..7210c651177 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1187,7 +1187,7 @@ watchdog==1.0.2 wiffi==1.0.1 # homeassistant.components.withings -withings-api==2.1.6 +withings-api==2.3.2 # homeassistant.components.wled wled==0.4.4 From 6a66ccef1b5036409e612facbf508c0a8160321c Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Tue, 16 Mar 2021 12:21:08 -0400 Subject: [PATCH 404/831] Bump up ZHA dependencies (#47997) --- 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 5c66e2cf605..5eee54a5c71 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zha", "requirements": [ - "bellows==0.23.0", + "bellows==0.23.1", "pyserial==3.5", "pyserial-asyncio==0.5", "zha-quirks==0.0.54", diff --git a/requirements_all.txt b/requirements_all.txt index ca653fb6e1b..a626f812fb8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -340,7 +340,7 @@ beautifulsoup4==4.9.3 # beewi_smartclim==0.0.10 # homeassistant.components.zha -bellows==0.23.0 +bellows==0.23.1 # homeassistant.components.bmw_connected_drive bimmer_connected==0.7.15 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7210c651177..7889a5af96b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -193,7 +193,7 @@ azure-eventhub==5.1.0 base36==0.1.1 # homeassistant.components.zha -bellows==0.23.0 +bellows==0.23.1 # homeassistant.components.bmw_connected_drive bimmer_connected==0.7.15 From 73c6728e98e2e853afa9bd80b3a689c2d56e49e3 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Tue, 16 Mar 2021 09:38:09 -0700 Subject: [PATCH 405/831] Add binary_sensor entities for SmartTub reminders (#47583) --- .../components/smarttub/binary_sensor.py | 63 ++++++++++++++++++- homeassistant/components/smarttub/climate.py | 2 +- homeassistant/components/smarttub/const.py | 5 +- .../components/smarttub/controller.py | 5 +- tests/components/smarttub/conftest.py | 8 +++ .../components/smarttub/test_binary_sensor.py | 22 ++++++- 6 files changed, 96 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/smarttub/binary_sensor.py b/homeassistant/components/smarttub/binary_sensor.py index 52dbfd71a37..04a6bb9b72a 100644 --- a/homeassistant/components/smarttub/binary_sensor.py +++ b/homeassistant/components/smarttub/binary_sensor.py @@ -1,23 +1,38 @@ """Platform for binary sensor integration.""" +from datetime import datetime, timedelta import logging +from smarttub import SpaReminder + from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_PROBLEM, BinarySensorEntity, ) -from .const import DOMAIN, SMARTTUB_CONTROLLER -from .entity import SmartTubSensorBase +from .const import ATTR_REMINDERS, DOMAIN, SMARTTUB_CONTROLLER +from .entity import SmartTubEntity, SmartTubSensorBase _LOGGER = logging.getLogger(__name__) +# whether the reminder has been snoozed (bool) +ATTR_REMINDER_SNOOZED = "snoozed" +# the date at which the reminder will be activated +ATTR_REMINDER_DATE = "date" + async def async_setup_entry(hass, entry, async_add_entities): """Set up binary sensor entities for the binary sensors in the tub.""" controller = hass.data[DOMAIN][entry.entry_id][SMARTTUB_CONTROLLER] - entities = [SmartTubOnline(controller.coordinator, spa) for spa in controller.spas] + entities = [] + for spa in controller.spas: + entities.append(SmartTubOnline(controller.coordinator, spa)) + entities.extend( + SmartTubReminder(controller.coordinator, spa, reminder) + for reminder in controller.coordinator.data[spa.id][ATTR_REMINDERS].values() + ) async_add_entities(entities) @@ -38,3 +53,45 @@ class SmartTubOnline(SmartTubSensorBase, BinarySensorEntity): def device_class(self) -> str: """Return the device class for this entity.""" return DEVICE_CLASS_CONNECTIVITY + + +class SmartTubReminder(SmartTubEntity, BinarySensorEntity): + """Reminders for maintenance actions.""" + + def __init__(self, coordinator, spa, reminder): + """Initialize the entity.""" + super().__init__( + coordinator, + spa, + f"{reminder.name.title()} Reminder", + ) + self.reminder_id = reminder.id + + @property + def unique_id(self): + """Return a unique id for this sensor.""" + return f"{self.spa.id}-reminder-{self.reminder_id}" + + @property + def reminder(self) -> SpaReminder: + """Return the underlying SpaReminder object for this entity.""" + return self.coordinator.data[self.spa.id]["reminders"][self.reminder_id] + + @property + def is_on(self) -> bool: + """Return whether the specified maintenance action needs to be taken.""" + return self.reminder.remaining_days == 0 + + @property + def device_state_attributes(self): + """Return the state attributes.""" + when = datetime.now() + timedelta(days=self.reminder.remaining_days) + return { + ATTR_REMINDER_SNOOZED: self.reminder.snoozed, + ATTR_REMINDER_DATE: when.date().isoformat(), + } + + @property + def device_class(self) -> str: + """Return the device class for this entity.""" + return DEVICE_CLASS_PROBLEM diff --git a/homeassistant/components/smarttub/climate.py b/homeassistant/components/smarttub/climate.py index 3e18bc12672..be564c84a94 100644 --- a/homeassistant/components/smarttub/climate.py +++ b/homeassistant/components/smarttub/climate.py @@ -54,7 +54,7 @@ class SmartTubThermostat(SmartTubEntity, ClimateEntity): def __init__(self, coordinator, spa): """Initialize the entity.""" - super().__init__(coordinator, spa, "thermostat") + super().__init__(coordinator, spa, "Thermostat") @property def temperature_unit(self): diff --git a/homeassistant/components/smarttub/const.py b/homeassistant/components/smarttub/const.py index 082d46cb26a..23bd8bd8ec0 100644 --- a/homeassistant/components/smarttub/const.py +++ b/homeassistant/components/smarttub/const.py @@ -21,6 +21,7 @@ DEFAULT_LIGHT_EFFECT = "purple" # default to 50% brightness DEFAULT_LIGHT_BRIGHTNESS = 128 -ATTR_STATUS = "status" -ATTR_PUMPS = "pumps" ATTR_LIGHTS = "lights" +ATTR_PUMPS = "pumps" +ATTR_REMINDERS = "reminders" +ATTR_STATUS = "status" diff --git a/homeassistant/components/smarttub/controller.py b/homeassistant/components/smarttub/controller.py index e5246446665..8139c72ab6e 100644 --- a/homeassistant/components/smarttub/controller.py +++ b/homeassistant/components/smarttub/controller.py @@ -18,6 +18,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda from .const import ( ATTR_LIGHTS, ATTR_PUMPS, + ATTR_REMINDERS, ATTR_STATUS, DOMAIN, POLLING_TIMEOUT, @@ -93,15 +94,17 @@ class SmartTubController: return data async def _get_spa_data(self, spa): - status, pumps, lights = await asyncio.gather( + status, pumps, lights, reminders = await asyncio.gather( spa.get_status(), spa.get_pumps(), spa.get_lights(), + spa.get_reminders(), ) return { ATTR_STATUS: status, ATTR_PUMPS: {pump.id: pump for pump in pumps}, ATTR_LIGHTS: {light.zone: light for light in lights}, + ATTR_REMINDERS: {reminder.id: reminder for reminder in reminders}, } async def async_register_devices(self, entry): diff --git a/tests/components/smarttub/conftest.py b/tests/components/smarttub/conftest.py index b7c90b5ad3e..7f23c2355bc 100644 --- a/tests/components/smarttub/conftest.py +++ b/tests/components/smarttub/conftest.py @@ -105,6 +105,14 @@ def mock_spa(): mock_spa.get_lights.return_value = [mock_light_off, mock_light_on] + mock_filter_reminder = create_autospec(smarttub.SpaReminder, instance=True) + mock_filter_reminder.id = "FILTER01" + mock_filter_reminder.name = "MyFilter" + mock_filter_reminder.remaining_days = 2 + mock_filter_reminder.snoozed = False + + mock_spa.get_reminders.return_value = [mock_filter_reminder] + return mock_spa diff --git a/tests/components/smarttub/test_binary_sensor.py b/tests/components/smarttub/test_binary_sensor.py index b2624369e96..8229372e904 100644 --- a/tests/components/smarttub/test_binary_sensor.py +++ b/tests/components/smarttub/test_binary_sensor.py @@ -1,13 +1,31 @@ """Test the SmartTub binary sensor platform.""" +from datetime import date, timedelta -from homeassistant.components.binary_sensor import DEVICE_CLASS_CONNECTIVITY, STATE_ON +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + STATE_OFF, + STATE_ON, +) async def test_binary_sensors(spa, setup_entry, hass): - """Test the binary sensors.""" + """Test simple binary sensors.""" entity_id = f"binary_sensor.{spa.brand}_{spa.model}_online" state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_ON assert state.attributes.get("device_class") == DEVICE_CLASS_CONNECTIVITY + + +async def test_reminders(spa, setup_entry, hass): + """Test the reminder sensor.""" + + entity_id = f"binary_sensor.{spa.brand}_{spa.model}_myfilter_reminder" + state = hass.states.get(entity_id) + assert state is not None + assert state.state == STATE_OFF + assert date.fromisoformat(state.attributes["date"]) <= date.today() + timedelta( + days=2 + ) + assert state.attributes["snoozed"] is False From 55db855f91270ce8e8ebfa98cc287e7902fa90a4 Mon Sep 17 00:00:00 2001 From: billsq Date: Tue, 16 Mar 2021 12:54:13 -0700 Subject: [PATCH 406/831] Add support for Xiaomi Air Purifier Pro H (#47601) --- homeassistant/components/xiaomi_miio/const.py | 7 ++++++- homeassistant/components/xiaomi_miio/fan.py | 4 +--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index ddde0c77229..e4084542e13 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -27,6 +27,7 @@ MODEL_AIRPURIFIER_SA2 = "zhimi.airpurifier.sa2" MODEL_AIRPURIFIER_2S = "zhimi.airpurifier.mc1" MODEL_AIRPURIFIER_3 = "zhimi.airpurifier.ma4" MODEL_AIRPURIFIER_3H = "zhimi.airpurifier.mb3" +MODEL_AIRPURIFIER_PROH = "zhimi.airpurifier.va1" MODEL_AIRHUMIDIFIER_V1 = "zhimi.humidifier.v1" MODEL_AIRHUMIDIFIER_CA1 = "zhimi.humidifier.ca1" @@ -35,7 +36,11 @@ MODEL_AIRHUMIDIFIER_CB1 = "zhimi.humidifier.cb1" MODEL_AIRFRESH_VA2 = "zhimi.airfresh.va2" -MODELS_PURIFIER_MIOT = [MODEL_AIRPURIFIER_3, MODEL_AIRPURIFIER_3H] +MODELS_PURIFIER_MIOT = [ + MODEL_AIRPURIFIER_3, + MODEL_AIRPURIFIER_3H, + MODEL_AIRPURIFIER_PROH, +] MODELS_HUMIDIFIER_MIOT = [MODEL_AIRHUMIDIFIER_CA4] MODELS_FAN_MIIO = [ MODEL_AIRPURIFIER_V1, diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index cc8ee74feb0..e20b1429bc6 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -62,8 +62,6 @@ from .const import ( MODEL_AIRHUMIDIFIER_CA4, MODEL_AIRHUMIDIFIER_CB1, MODEL_AIRPURIFIER_2S, - MODEL_AIRPURIFIER_3, - MODEL_AIRPURIFIER_3H, MODEL_AIRPURIFIER_PRO, MODEL_AIRPURIFIER_PRO_V7, MODEL_AIRPURIFIER_V3, @@ -786,7 +784,7 @@ class XiaomiAirPurifier(XiaomiGenericDevice): self._device_features = FEATURE_FLAGS_AIRPURIFIER_2S self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_2S self._speed_list = OPERATION_MODES_AIRPURIFIER_2S - elif self._model == MODEL_AIRPURIFIER_3 or self._model == MODEL_AIRPURIFIER_3H: + elif self._model in MODELS_PURIFIER_MIOT: self._device_features = FEATURE_FLAGS_AIRPURIFIER_3 self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_3 self._speed_list = OPERATION_MODES_AIRPURIFIER_3 From 14d3e29e649cc9a923154e31f6e45259bd1d1e96 Mon Sep 17 00:00:00 2001 From: Kevin Eifinger Date: Tue, 16 Mar 2021 20:56:49 +0100 Subject: [PATCH 407/831] Add missing "pin" field in step "pair" for philips_js (#47802) --- homeassistant/components/philips_js/strings.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/components/philips_js/strings.json b/homeassistant/components/philips_js/strings.json index df65d453f2b..5c8f08eff6a 100644 --- a/homeassistant/components/philips_js/strings.json +++ b/homeassistant/components/philips_js/strings.json @@ -6,6 +6,13 @@ "host": "[%key:common::config_flow::data::host%]", "api_version": "API Version" } + }, + "pair": { + "title": "Pair", + "description": "Enter the PIN displayed on your TV", + "data":{ + "pin": "[%key:common::config_flow::data::pin%]" + } } }, "error": { From f3c74948c3a4d2b1048fd980eba9d240db472504 Mon Sep 17 00:00:00 2001 From: chpego <38792705+chpego@users.noreply.github.com> Date: Tue, 16 Mar 2021 21:02:05 +0100 Subject: [PATCH 408/831] Upgrade youtube_dl to version 2021.03.14 (#48000) --- homeassistant/components/media_extractor/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_extractor/manifest.json b/homeassistant/components/media_extractor/manifest.json index 48589b37bd2..35a5b098184 100644 --- a/homeassistant/components/media_extractor/manifest.json +++ b/homeassistant/components/media_extractor/manifest.json @@ -2,7 +2,7 @@ "domain": "media_extractor", "name": "Media Extractor", "documentation": "https://www.home-assistant.io/integrations/media_extractor", - "requirements": ["youtube_dl==2021.02.22"], + "requirements": ["youtube_dl==2021.03.14"], "dependencies": ["media_player"], "codeowners": [], "quality_scale": "internal" diff --git a/requirements_all.txt b/requirements_all.txt index a626f812fb8..5a92aebdd51 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2351,7 +2351,7 @@ yeelight==0.5.4 yeelightsunflower==0.0.10 # homeassistant.components.media_extractor -youtube_dl==2021.02.22 +youtube_dl==2021.03.14 # homeassistant.components.onvif zeep[async]==4.0.0 From f86e7535e0dbb37159f13f08c1cc0e39ce6976ad Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Tue, 16 Mar 2021 13:16:07 -0700 Subject: [PATCH 409/831] Add location details to deprecation warning (#47155) --- homeassistant/helpers/config_validation.py | 21 +++++-- tests/helpers/test_config_validation.py | 72 ++++++++++++++++++++++ 2 files changed, 87 insertions(+), 6 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 422f940e98e..b06e9974125 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -746,12 +746,21 @@ def deprecated( def validator(config: Dict) -> Dict: """Check if key is in config and log warning.""" if key in config: - KeywordStyleAdapter(logging.getLogger(module_name)).warning( - warning, - key=key, - replacement_key=replacement_key, - ) - + try: + KeywordStyleAdapter(logging.getLogger(module_name)).warning( + warning.replace( + "'{key}' option", + f"'{key}' option near {config.__config_file__}:{config.__line__}", # type: ignore + ), + key=key, + replacement_key=replacement_key, + ) + except AttributeError: + KeywordStyleAdapter(logging.getLogger(module_name)).warning( + warning, + key=key, + replacement_key=replacement_key, + ) value = config[key] if replacement_key: config.pop(key) diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index d0ae86f8f7e..f58fd1b22f9 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -1,4 +1,5 @@ """Test config validators.""" +from collections import OrderedDict from datetime import date, datetime, timedelta import enum import os @@ -799,6 +800,77 @@ def test_deprecated_cant_find_module(): ) +def test_deprecated_logger_with_config_attributes(caplog): + """Test if the logger outputs the correct message if the line and file attribute is available in config.""" + file: str = "configuration.yaml" + line: int = 54 + replacement = f"'mars' option near {file}:{line} is deprecated" + config = OrderedDict([("mars", "blah")]) + setattr(config, "__config_file__", file) + setattr(config, "__line__", line) + + cv.deprecated("mars", replacement_key="jupiter", default=False)(config) + + assert len(caplog.records) == 1 + assert replacement in caplog.text + + caplog.clear() + assert len(caplog.records) == 0 + + +def test_deprecated_logger_with_one_config_attribute(caplog): + """Test if the logger outputs the correct message if only one of line and file attribute is available in config.""" + file: str = "configuration.yaml" + line: int = 54 + replacement = f"'mars' option near {file}:{line} is deprecated" + config = OrderedDict([("mars", "blah")]) + setattr(config, "__config_file__", file) + + cv.deprecated("mars", replacement_key="jupiter", default=False)(config) + + assert len(caplog.records) == 1 + assert replacement not in caplog.text + assert ( + "The 'mars' option is deprecated, please replace it with 'jupiter'" + ) in caplog.text + + caplog.clear() + assert len(caplog.records) == 0 + + config = OrderedDict([("mars", "blah")]) + setattr(config, "__line__", line) + + cv.deprecated("mars", replacement_key="jupiter", default=False)(config) + + assert len(caplog.records) == 1 + assert replacement not in caplog.text + assert ( + "The 'mars' option is deprecated, please replace it with 'jupiter'" + ) in caplog.text + + caplog.clear() + assert len(caplog.records) == 0 + + +def test_deprecated_logger_without_config_attributes(caplog): + """Test if the logger outputs the correct message if the line and file attribute is not available in config.""" + file: str = "configuration.yaml" + line: int = 54 + replacement = f"'mars' option near {file}:{line} is deprecated" + config = OrderedDict([("mars", "blah")]) + + cv.deprecated("mars", replacement_key="jupiter", default=False)(config) + + assert len(caplog.records) == 1 + assert replacement not in caplog.text + assert ( + "The 'mars' option is deprecated, please replace it with 'jupiter'" + ) in caplog.text + + caplog.clear() + assert len(caplog.records) == 0 + + def test_key_dependency(): """Test key_dependency validator.""" schema = vol.Schema(cv.key_dependency("beer", "soda")) From d39aa9f80b82bc7137e71dab8cc6957cd3c6a714 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Tue, 16 Mar 2021 21:24:01 +0100 Subject: [PATCH 410/831] Bump philips_js with backported fixes (#47959) --- homeassistant/components/philips_js/manifest.json | 2 +- homeassistant/components/philips_js/media_player.py | 6 ------ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/philips_js/manifest.json b/homeassistant/components/philips_js/manifest.json index 9ed1cedbf05..ad591ad330b 100644 --- a/homeassistant/components/philips_js/manifest.json +++ b/homeassistant/components/philips_js/manifest.json @@ -3,7 +3,7 @@ "name": "Philips TV", "documentation": "https://www.home-assistant.io/integrations/philips_js", "requirements": [ - "ha-philipsjs==2.3.1" + "ha-philipsjs==2.3.2" ], "codeowners": [ "@elupus" diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index 38521989567..f3b2fe651e2 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -370,9 +370,6 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): media_content_type=MEDIA_TYPE_CHANNEL, can_play=True, can_expand=False, - thumbnail=self.get_browse_image_url( - MEDIA_TYPE_APP, channel_id, media_image_id=None - ), ) for channel_id, channel in self._tv.channels.items() ] @@ -410,9 +407,6 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): media_content_type=MEDIA_TYPE_CHANNEL, can_play=True, can_expand=False, - thumbnail=self.get_browse_image_url( - MEDIA_TYPE_APP, channel, media_image_id=None - ), ) for channel in favorites ] diff --git a/requirements_all.txt b/requirements_all.txt index 5a92aebdd51..1f0cc6ab7ca 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -718,7 +718,7 @@ guppy3==3.1.0 ha-ffmpeg==3.0.2 # homeassistant.components.philips_js -ha-philipsjs==2.3.1 +ha-philipsjs==2.3.2 # homeassistant.components.habitica habitipy==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7889a5af96b..7dd403f6bd1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -379,7 +379,7 @@ guppy3==3.1.0 ha-ffmpeg==3.0.2 # homeassistant.components.philips_js -ha-philipsjs==2.3.1 +ha-philipsjs==2.3.2 # homeassistant.components.habitica habitipy==0.2.0 From a4075d9e1144041a3d45801d1dd9902666a69915 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Tue, 16 Mar 2021 21:33:56 +0100 Subject: [PATCH 411/831] KNX sensor: float no longer valid for `type` (#48005) --- homeassistant/components/knx/__init__.py | 3 ++- homeassistant/components/knx/schema.py | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 99198fbaa99..e05c18e5d5c 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -49,6 +49,7 @@ from .schema import ( WeatherSchema, ga_validator, ia_validator, + sensor_type_validator, ) _LOGGER = logging.getLogger(__name__) @@ -156,7 +157,7 @@ SERVICE_KNX_SEND_SCHEMA = vol.Any( [ga_validator], ), vol.Required(SERVICE_KNX_ATTR_PAYLOAD): cv.match_all, - vol.Required(SERVICE_KNX_ATTR_TYPE): vol.Any(int, float, str), + vol.Required(SERVICE_KNX_ATTR_TYPE): sensor_type_validator, } ), vol.Schema( diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index c57539b501f..376e86cf90c 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -47,6 +47,8 @@ sync_state_validator = vol.Any( cv.matches_regex(r"^(init|expire|every)( \d*)?$"), ) +sensor_type_validator = vol.Any(int, str) + ############## # CONNECTION @@ -255,7 +257,7 @@ class ExposeSchema: SCHEMA = vol.Schema( { - vol.Required(CONF_KNX_EXPOSE_TYPE): vol.Any(int, float, str), + vol.Required(CONF_KNX_EXPOSE_TYPE): sensor_type_validator, vol.Required(KNX_ADDRESS): ga_validator, vol.Optional(CONF_ENTITY_ID): cv.entity_id, vol.Optional(CONF_KNX_EXPOSE_ATTRIBUTE): cv.string, @@ -417,7 +419,7 @@ class SensorSchema: vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator, vol.Optional(CONF_ALWAYS_CALLBACK, default=False): cv.boolean, vol.Required(CONF_STATE_ADDRESS): ga_validator, - vol.Required(CONF_TYPE): vol.Any(int, float, str), + vol.Required(CONF_TYPE): sensor_type_validator, } ) From f1c274b3dd1bdb328acf9bf36168bdb34c139345 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 16 Mar 2021 14:37:26 -0700 Subject: [PATCH 412/831] Add run_id to automation logbook event (#47980) --- .../components/automation/__init__.py | 10 +--- .../components/automation/logbook.py | 9 ++-- homeassistant/components/automation/trace.py | 4 +- .../components/automation/websocket_api.py | 30 +++++++++++ tests/components/automation/test_logbook.py | 54 +++++++++++++++++++ .../automation/test_websocket_api.py | 23 ++++++++ 6 files changed, 117 insertions(+), 13 deletions(-) create mode 100644 tests/components/automation/test_logbook.py diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 4555a336cc3..b769b0329cc 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -281,6 +281,8 @@ class AutomationEntity(ToggleEntity, RestoreEntity): } if self.action_script.supports_max: attrs[ATTR_MAX] = self.action_script.max_runs + if self._id is not None: + attrs[CONF_ID] = self._id return attrs @property @@ -534,14 +536,6 @@ class AutomationEntity(ToggleEntity, RestoreEntity): variables, ) - @property - def extra_state_attributes(self): - """Return automation attributes.""" - if self._id is None: - return None - - return {CONF_ID: self._id} - async def _async_process_config( hass: HomeAssistant, diff --git a/homeassistant/components/automation/logbook.py b/homeassistant/components/automation/logbook.py index 3c9671af18f..b5dbada1b7a 100644 --- a/homeassistant/components/automation/logbook.py +++ b/homeassistant/components/automation/logbook.py @@ -1,26 +1,29 @@ """Describe logbook events.""" +from homeassistant.components.logbook import LazyEventPartialState from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from . import ATTR_SOURCE, DOMAIN, EVENT_AUTOMATION_TRIGGERED @callback -def async_describe_events(hass, async_describe_event): # type: ignore +def async_describe_events(hass: HomeAssistant, async_describe_event): # type: ignore """Describe logbook events.""" @callback - def async_describe_logbook_event(event): # type: ignore + def async_describe_logbook_event(event: LazyEventPartialState): # type: ignore """Describe a logbook event.""" data = event.data message = "has been triggered" if ATTR_SOURCE in data: message = f"{message} by {data[ATTR_SOURCE]}" + return { "name": data.get(ATTR_NAME), "message": message, "source": data.get(ATTR_SOURCE), "entity_id": data.get(ATTR_ENTITY_ID), + "context_id": event.context_id, } async_describe_event( diff --git a/homeassistant/components/automation/trace.py b/homeassistant/components/automation/trace.py index 4aac3d327b8..68cca5a4a41 100644 --- a/homeassistant/components/automation/trace.py +++ b/homeassistant/components/automation/trace.py @@ -38,7 +38,7 @@ class AutomationTrace: self._action_trace: Optional[Dict[str, Deque[TraceElement]]] = None self._condition_trace: Optional[Dict[str, Deque[TraceElement]]] = None self._config: Dict[str, Any] = config - self._context: Context = context + self.context: Context = context self._error: Optional[Exception] = None self._state: str = "running" self.run_id: str = str(next(self._run_ids)) @@ -88,7 +88,7 @@ class AutomationTrace: "action_trace": action_traces, "condition_trace": condition_traces, "config": self._config, - "context": self._context, + "context": self.context, "variables": self._variables, } ) diff --git a/homeassistant/components/automation/websocket_api.py b/homeassistant/components/automation/websocket_api.py index eba56f94e7d..7bd1b57d064 100644 --- a/homeassistant/components/automation/websocket_api.py +++ b/homeassistant/components/automation/websocket_api.py @@ -24,6 +24,7 @@ from homeassistant.helpers.script import ( ) from .trace import ( + DATA_AUTOMATION_TRACE, TraceJSONEncoder, get_debug_trace, get_debug_traces, @@ -38,6 +39,7 @@ def async_setup(hass: HomeAssistant) -> None: """Set up the websocket API.""" websocket_api.async_register_command(hass, websocket_automation_trace_get) websocket_api.async_register_command(hass, websocket_automation_trace_list) + websocket_api.async_register_command(hass, websocket_automation_trace_contexts) websocket_api.async_register_command(hass, websocket_automation_breakpoint_clear) websocket_api.async_register_command(hass, websocket_automation_breakpoint_list) websocket_api.async_register_command(hass, websocket_automation_breakpoint_set) @@ -86,6 +88,34 @@ def websocket_automation_trace_list(hass, connection, msg): connection.send_result(msg["id"], automation_traces) +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/trace/contexts", + vol.Optional("automation_id"): str, + } +) +def websocket_automation_trace_contexts(hass, connection, msg): + """Retrieve contexts we have traces for.""" + automation_id = msg.get("automation_id") + + if automation_id is not None: + values = { + automation_id: hass.data[DATA_AUTOMATION_TRACE].get(automation_id, {}) + } + else: + values = hass.data[DATA_AUTOMATION_TRACE] + + contexts = { + trace.context.id: {"run_id": trace.run_id, "automation_id": automation_id} + for automation_id, traces in values.items() + for trace in traces.values() + } + + connection.send_result(msg["id"], contexts) + + @callback @websocket_api.require_admin @websocket_api.websocket_command( diff --git a/tests/components/automation/test_logbook.py b/tests/components/automation/test_logbook.py new file mode 100644 index 00000000000..e13ebdc17a1 --- /dev/null +++ b/tests/components/automation/test_logbook.py @@ -0,0 +1,54 @@ +"""Test automation logbook.""" +from homeassistant.components import automation, logbook +from homeassistant.core import Context +from homeassistant.setup import async_setup_component + +from tests.components.logbook.test_init import MockLazyEventPartialState + + +async def test_humanify_automation_trigger_event(hass): + """Test humanifying Shelly click event.""" + hass.config.components.add("recorder") + assert await async_setup_component(hass, "automation", {}) + assert await async_setup_component(hass, "logbook", {}) + entity_attr_cache = logbook.EntityAttributeCache(hass) + context = Context() + + event1, event2 = list( + logbook.humanify( + hass, + [ + MockLazyEventPartialState( + automation.EVENT_AUTOMATION_TRIGGERED, + { + "name": "Bla", + "entity_id": "automation.bla", + "source": "state change of input_boolean.yo", + }, + context=context, + ), + MockLazyEventPartialState( + automation.EVENT_AUTOMATION_TRIGGERED, + { + "name": "Bla", + "entity_id": "automation.bla", + }, + context=context, + ), + ], + entity_attr_cache, + {}, + ) + ) + + assert event1["name"] == "Bla" + assert event1["message"] == "has been triggered by state change of input_boolean.yo" + assert event1["source"] == "state change of input_boolean.yo" + assert event1["context_id"] == context.id + assert event1["entity_id"] == "automation.bla" + + assert event2["name"] == "Bla" + assert event2["message"] == "has been triggered" + assert event2["source"] is None + assert event2["context_id"] == context.id + assert event2["entity_id"] == "automation.bla" diff --git a/tests/components/automation/test_websocket_api.py b/tests/components/automation/test_websocket_api.py index 84c85e224e5..ab69e89416a 100644 --- a/tests/components/automation/test_websocket_api.py +++ b/tests/components/automation/test_websocket_api.py @@ -65,6 +65,7 @@ async def test_get_automation_trace(hass, hass_ws_client): await async_setup_component(hass, "config", {}) client = await hass_ws_client() + contexts = {} # Trigger "sun" automation context = Context() @@ -102,6 +103,10 @@ async def test_get_automation_trace(hass, hass_ws_client): assert trace["trigger"] == "event 'test_event'" assert trace["unique_id"] == "sun" assert trace["variables"] + contexts[trace["context"]["id"]] = { + "run_id": trace["run_id"], + "automation_id": trace["automation_id"], + } # Trigger "moon" automation, with passing condition hass.bus.async_fire("test_event2") @@ -139,6 +144,10 @@ async def test_get_automation_trace(hass, hass_ws_client): assert trace["trigger"] == "event 'test_event2'" assert trace["unique_id"] == "moon" assert trace["variables"] + contexts[trace["context"]["id"]] = { + "run_id": trace["run_id"], + "automation_id": trace["automation_id"], + } # Trigger "moon" automation, with failing condition hass.bus.async_fire("test_event3") @@ -173,6 +182,10 @@ async def test_get_automation_trace(hass, hass_ws_client): assert trace["trigger"] == "event 'test_event3'" assert trace["unique_id"] == "moon" assert trace["variables"] + contexts[trace["context"]["id"]] = { + "run_id": trace["run_id"], + "automation_id": trace["automation_id"], + } # Trigger "moon" automation, with passing condition hass.bus.async_fire("test_event2") @@ -210,6 +223,16 @@ async def test_get_automation_trace(hass, hass_ws_client): assert trace["trigger"] == "event 'test_event2'" assert trace["unique_id"] == "moon" assert trace["variables"] + contexts[trace["context"]["id"]] = { + "run_id": trace["run_id"], + "automation_id": trace["automation_id"], + } + + # Check contexts + await client.send_json({"id": next_id(), "type": "automation/trace/contexts"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == contexts async def test_automation_trace_overflow(hass, hass_ws_client): From d49a4365735fbb6af09a6f0a02fe9f7655985484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Tue, 16 Mar 2021 21:38:16 +0000 Subject: [PATCH 413/831] Delay ZHA group updates to ensure all members are updated first (#46861) * Delay ZHA group updates to ensure all members are updated first After turning off a group, when the first device reports "off", the other devices may still be "on". If HA processes the group state update quickly enough, the group will see that some devices are on, so the state of the group will revert back to "on", and then "off" when the remaining devices all report "off". That would cause the UI toggle to go back and forward quickly, and automations that trigger with "state: on" to fire when the user turns the group off. This PR fixes that by delaying the group state update, giving time for all the devices to report their states first. * Fix zha group tests * Reorder sleeping. * Update tests/components/zha/common.py Co-authored-by: Alexei Chetroi --- homeassistant/components/zha/entity.py | 7 ++++++- tests/components/zha/common.py | 9 +++++++++ tests/components/zha/test_fan.py | 22 ++++++++++++++++------ tests/components/zha/test_light.py | 25 +++++++++++++++---------- tests/components/zha/test_switch.py | 17 +++++++++++------ 5 files changed, 57 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index c19bad21455..9a573e92aa4 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -32,6 +32,7 @@ from .core.typing import CALLABLE_T, ChannelType, ZhaDeviceType _LOGGER = logging.getLogger(__name__) ENTITY_SUFFIX = "entity_suffix" +UPDATE_GROUP_FROM_CHILD_DELAY = 0.2 class BaseZhaEntity(LogMixin, entity.Entity): @@ -267,7 +268,11 @@ class ZhaGroupEntity(BaseZhaEntity): @callback def async_state_changed_listener(self, event: Event): """Handle child updates.""" - self.async_schedule_update_ha_state(True) + # Delay to ensure that we get updates from all members before updating the group + self.hass.loop.call_later( + UPDATE_GROUP_FROM_CHILD_DELAY, + lambda: self.async_schedule_update_ha_state(True), + ) async def async_will_remove_from_hass(self) -> None: """Handle removal from Home Assistant.""" diff --git a/tests/components/zha/common.py b/tests/components/zha/common.py index 234ca0c9ba5..eeffa3fb911 100644 --- a/tests/components/zha/common.py +++ b/tests/components/zha/common.py @@ -1,4 +1,5 @@ """Common test objects.""" +import asyncio import time from unittest.mock import AsyncMock, Mock @@ -237,3 +238,11 @@ async def async_test_rejoin(hass, zigpy_device, clusters, report_counts, ep_id=1 assert cluster.bind.await_count == 1 assert cluster.configure_reporting.call_count == reports assert cluster.configure_reporting.await_count == reports + + +async def async_wait_for_updates(hass): + """Wait until all scheduled updates are executed.""" + await hass.async_block_till_done() + await asyncio.sleep(0) + await asyncio.sleep(0) + await hass.async_block_till_done() diff --git a/tests/components/zha/test_fan.py b/tests/components/zha/test_fan.py index 81a441a4101..eed0e0b691e 100644 --- a/tests/components/zha/test_fan.py +++ b/tests/components/zha/test_fan.py @@ -50,6 +50,8 @@ from .common import ( send_attributes_report, ) +from tests.components.zha.common import async_wait_for_updates + IEEE_GROUPABLE_DEVICE = "01:2d:6f:00:0a:90:69:e8" IEEE_GROUPABLE_DEVICE2 = "02:2d:6f:00:0a:90:69:e8" @@ -246,6 +248,10 @@ async def async_set_preset_mode(hass, entity_id, preset_mode=None): "zigpy.zcl.clusters.hvac.Fan.write_attributes", new=AsyncMock(return_value=zcl_f.WriteAttributesResponse.deserialize(b"\x00")[0]), ) +@patch( + "homeassistant.components.zha.entity.UPDATE_GROUP_FROM_CHILD_DELAY", + new=0, +) async def test_zha_group_fan_entity(hass, device_fan_1, device_fan_2, coordinator): """Test the fan entity for a ZHA group.""" zha_gateway = get_zha_gateway(hass) @@ -283,13 +289,13 @@ async def test_zha_group_fan_entity(hass, device_fan_1, device_fan_2, coordinato dev2_fan_cluster = device_fan_2.device.endpoints[1].fan await async_enable_traffic(hass, [device_fan_1, device_fan_2], enabled=False) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that the fans were created and that they are unavailable assert hass.states.get(entity_id).state == STATE_UNAVAILABLE # allow traffic to flow through the gateway and device await async_enable_traffic(hass, [device_fan_1, device_fan_2]) - + await async_wait_for_updates(hass) # test that the fan group entity was created and is off assert hass.states.get(entity_id).state == STATE_OFF @@ -338,13 +344,13 @@ async def test_zha_group_fan_entity(hass, device_fan_1, device_fan_2, coordinato assert hass.states.get(entity_id).state == STATE_OFF await send_attributes_report(hass, dev2_fan_cluster, {0: 2}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that group fan is speed medium assert hass.states.get(entity_id).state == STATE_ON await send_attributes_report(hass, dev2_fan_cluster, {0: 0}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that group fan is now off assert hass.states.get(entity_id).state == STATE_OFF @@ -354,6 +360,10 @@ async def test_zha_group_fan_entity(hass, device_fan_1, device_fan_2, coordinato "zigpy.zcl.clusters.hvac.Fan.write_attributes", new=AsyncMock(side_effect=ZigbeeException), ) +@patch( + "homeassistant.components.zha.entity.UPDATE_GROUP_FROM_CHILD_DELAY", + new=0, +) async def test_zha_group_fan_entity_failure_state( hass, device_fan_1, device_fan_2, coordinator, caplog ): @@ -390,13 +400,13 @@ async def test_zha_group_fan_entity_failure_state( group_fan_cluster = zha_group.endpoint[hvac.Fan.cluster_id] await async_enable_traffic(hass, [device_fan_1, device_fan_2], enabled=False) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that the fans were created and that they are unavailable assert hass.states.get(entity_id).state == STATE_UNAVAILABLE # allow traffic to flow through the gateway and device await async_enable_traffic(hass, [device_fan_1, device_fan_2]) - + await async_wait_for_updates(hass) # test that the fan group entity was created and is off assert hass.states.get(entity_id).state == STATE_OFF diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py index 021f4f09dd9..fe367a3969b 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -25,6 +25,7 @@ from .common import ( ) from tests.common import async_fire_time_changed +from tests.components.zha.common import async_wait_for_updates ON = 1 OFF = 0 @@ -309,7 +310,7 @@ async def async_test_on_from_light(hass, cluster, entity_id): """Test on off functionality from the light.""" # turn on at light await send_attributes_report(hass, cluster, {1: -1, 0: 1, 2: 2}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) assert hass.states.get(entity_id).state == STATE_ON @@ -466,6 +467,10 @@ async def async_test_flash_from_hass(hass, cluster, entity_id, flash): "zigpy.zcl.clusters.general.OnOff.request", new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]), ) +@patch( + "homeassistant.components.zha.entity.UPDATE_GROUP_FROM_CHILD_DELAY", + new=0, +) async def test_zha_group_light_entity( hass, device_light_1, device_light_2, device_light_3, coordinator ): @@ -522,13 +527,13 @@ async def test_zha_group_light_entity( await async_enable_traffic( hass, [device_light_1, device_light_2, device_light_3], enabled=False ) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that the lights were created and that they are unavailable assert hass.states.get(group_entity_id).state == STATE_UNAVAILABLE # allow traffic to flow through the gateway and device await async_enable_traffic(hass, [device_light_1, device_light_2, device_light_3]) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that the lights were created and are off assert hass.states.get(group_entity_id).state == STATE_OFF @@ -580,7 +585,7 @@ async def test_zha_group_light_entity( assert hass.states.get(group_entity_id).state == STATE_ON await send_attributes_report(hass, dev2_cluster_on_off, {0: 0}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that group light is now off assert hass.states.get(device_1_entity_id).state == STATE_OFF @@ -588,7 +593,7 @@ async def test_zha_group_light_entity( assert hass.states.get(group_entity_id).state == STATE_OFF await send_attributes_report(hass, dev1_cluster_on_off, {0: 1}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that group light is now back on assert hass.states.get(device_1_entity_id).state == STATE_ON @@ -597,7 +602,7 @@ async def test_zha_group_light_entity( # turn it off to test a new member add being tracked await send_attributes_report(hass, dev1_cluster_on_off, {0: 0}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) assert hass.states.get(device_1_entity_id).state == STATE_OFF assert hass.states.get(device_2_entity_id).state == STATE_OFF assert hass.states.get(group_entity_id).state == STATE_OFF @@ -605,7 +610,7 @@ async def test_zha_group_light_entity( # add a new member and test that his state is also tracked await zha_group.async_add_members([GroupMember(device_light_3.ieee, 1)]) await send_attributes_report(hass, dev3_cluster_on_off, {0: 1}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) assert device_3_entity_id in zha_group.member_entity_ids assert len(zha_group.members) == 3 @@ -629,14 +634,14 @@ async def test_zha_group_light_entity( # add a member back and ensure that the group entity was created again await zha_group.async_add_members([GroupMember(device_light_3.ieee, 1)]) await send_attributes_report(hass, dev3_cluster_on_off, {0: 1}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) assert len(zha_group.members) == 2 assert hass.states.get(group_entity_id).state == STATE_ON # add a 3rd member and ensure we still have an entity and we track the new one await send_attributes_report(hass, dev1_cluster_on_off, {0: 0}) await send_attributes_report(hass, dev3_cluster_on_off, {0: 0}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) assert hass.states.get(group_entity_id).state == STATE_OFF # this will test that _reprobe_group is used correctly @@ -644,7 +649,7 @@ async def test_zha_group_light_entity( [GroupMember(device_light_2.ieee, 1), GroupMember(coordinator.ieee, 1)] ) await send_attributes_report(hass, dev2_cluster_on_off, {0: 1}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) assert len(zha_group.members) == 4 assert hass.states.get(group_entity_id).state == STATE_ON diff --git a/tests/components/zha/test_switch.py b/tests/components/zha/test_switch.py index da3037f720d..4cec0753c68 100644 --- a/tests/components/zha/test_switch.py +++ b/tests/components/zha/test_switch.py @@ -20,6 +20,7 @@ from .common import ( ) from tests.common import mock_coro +from tests.components.zha.common import async_wait_for_updates ON = 1 OFF = 0 @@ -160,6 +161,10 @@ async def test_switch(hass, zha_device_joined_restored, zigpy_device): await async_test_rejoin(hass, zigpy_device, [cluster], (1,)) +@patch( + "homeassistant.components.zha.entity.UPDATE_GROUP_FROM_CHILD_DELAY", + new=0, +) async def test_zha_group_switch_entity( hass, device_switch_1, device_switch_2, coordinator ): @@ -195,14 +200,14 @@ async def test_zha_group_switch_entity( dev2_cluster_on_off = device_switch_2.device.endpoints[1].on_off await async_enable_traffic(hass, [device_switch_1, device_switch_2], enabled=False) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that the lights were created and that they are off assert hass.states.get(entity_id).state == STATE_UNAVAILABLE # allow traffic to flow through the gateway and device await async_enable_traffic(hass, [device_switch_1, device_switch_2]) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that the lights were created and are off assert hass.states.get(entity_id).state == STATE_OFF @@ -240,25 +245,25 @@ async def test_zha_group_switch_entity( # test some of the group logic to make sure we key off states correctly await send_attributes_report(hass, dev1_cluster_on_off, {0: 1}) await send_attributes_report(hass, dev2_cluster_on_off, {0: 1}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that group light is on assert hass.states.get(entity_id).state == STATE_ON await send_attributes_report(hass, dev1_cluster_on_off, {0: 0}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that group light is still on assert hass.states.get(entity_id).state == STATE_ON await send_attributes_report(hass, dev2_cluster_on_off, {0: 0}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that group light is now off assert hass.states.get(entity_id).state == STATE_OFF await send_attributes_report(hass, dev1_cluster_on_off, {0: 1}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that group light is now back on assert hass.states.get(entity_id).state == STATE_ON From f605a3c149af0d61695b62058a380d908f733089 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 16 Mar 2021 13:22:07 -1000 Subject: [PATCH 414/831] Remove YAML support from August (#47615) --- homeassistant/components/august/__init__.py | 179 +++--------------- .../components/august/config_flow.py | 162 +++++++++------- homeassistant/components/august/gateway.py | 31 +-- homeassistant/components/august/manifest.json | 1 - homeassistant/components/august/strings.json | 10 +- .../components/august/translations/en.json | 10 +- tests/components/august/mocks.py | 19 +- tests/components/august/test_config_flow.py | 76 +++++++- tests/components/august/test_init.py | 50 +---- 9 files changed, 239 insertions(+), 299 deletions(-) diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index 23419f4df2d..73f1cc6a1b5 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -4,169 +4,25 @@ import itertools import logging from aiohttp import ClientError, ClientResponseError -from august.authenticator import ValidationResult from august.exceptions import AugustApiAIOHTTPError -import voluptuous as vol -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import ( - CONF_PASSWORD, - CONF_TIMEOUT, - CONF_USERNAME, - HTTP_UNAUTHORIZED, -) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PASSWORD, HTTP_UNAUTHORIZED from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError -import homeassistant.helpers.config_validation as cv from .activity import ActivityStream -from .const import ( - CONF_ACCESS_TOKEN_CACHE_FILE, - CONF_INSTALL_ID, - CONF_LOGIN_METHOD, - DATA_AUGUST, - DEFAULT_AUGUST_CONFIG_FILE, - DEFAULT_NAME, - DEFAULT_TIMEOUT, - DOMAIN, - LOGIN_METHODS, - MIN_TIME_BETWEEN_DETAIL_UPDATES, - PLATFORMS, - VERIFICATION_CODE_KEY, -) +from .const import DATA_AUGUST, DOMAIN, MIN_TIME_BETWEEN_DETAIL_UPDATES, PLATFORMS from .exceptions import CannotConnect, InvalidAuth, RequireValidation from .gateway import AugustGateway from .subscriber import AugustSubscriberMixin _LOGGER = logging.getLogger(__name__) -TWO_FA_REVALIDATE = "verify_configurator" - -CONFIG_SCHEMA = vol.Schema( - vol.All( - cv.deprecated(DOMAIN), - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_LOGIN_METHOD): vol.In(LOGIN_METHODS), - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_INSTALL_ID): cv.string, - vol.Optional( - CONF_TIMEOUT, default=DEFAULT_TIMEOUT - ): cv.positive_int, - } - ) - }, - ), - extra=vol.ALLOW_EXTRA, -) - - -async def async_request_validation(hass, config_entry, august_gateway): - """Request a new verification code from the user.""" - - # - # In the future this should start a new config flow - # instead of using the legacy configurator - # - _LOGGER.error("Access token is no longer valid") - configurator = hass.components.configurator - entry_id = config_entry.entry_id - - async def async_august_configuration_validation_callback(data): - code = data.get(VERIFICATION_CODE_KEY) - result = await august_gateway.authenticator.async_validate_verification_code( - code - ) - - if result == ValidationResult.INVALID_VERIFICATION_CODE: - configurator.async_notify_errors( - hass.data[DOMAIN][entry_id][TWO_FA_REVALIDATE], - "Invalid verification code, please make sure you are using the latest code and try again.", - ) - elif result == ValidationResult.VALIDATED: - return await async_setup_august(hass, config_entry, august_gateway) - - return False - - if TWO_FA_REVALIDATE not in hass.data[DOMAIN][entry_id]: - await august_gateway.authenticator.async_send_verification_code() - - entry_data = config_entry.data - login_method = entry_data.get(CONF_LOGIN_METHOD) - username = entry_data.get(CONF_USERNAME) - - hass.data[DOMAIN][entry_id][TWO_FA_REVALIDATE] = configurator.async_request_config( - f"{DEFAULT_NAME} ({username})", - async_august_configuration_validation_callback, - description=( - "August must be re-verified. " - f"Please check your {login_method} ({username}) " - "and enter the verification code below" - ), - submit_caption="Verify", - fields=[ - {"id": VERIFICATION_CODE_KEY, "name": "Verification code", "type": "string"} - ], - ) - return - - -async def async_setup_august(hass, config_entry, august_gateway): - """Set up the August component.""" - - entry_id = config_entry.entry_id - hass.data[DOMAIN].setdefault(entry_id, {}) - - try: - await august_gateway.async_authenticate() - except RequireValidation: - await async_request_validation(hass, config_entry, august_gateway) - raise - - # We still use the configurator to get a new 2fa code - # when needed since config_flow doesn't have a way - # to re-request if it expires - if TWO_FA_REVALIDATE in hass.data[DOMAIN][entry_id]: - hass.components.configurator.async_request_done( - hass.data[DOMAIN][entry_id].pop(TWO_FA_REVALIDATE) - ) - - hass.data[DOMAIN][entry_id][DATA_AUGUST] = AugustData(hass, august_gateway) - - await hass.data[DOMAIN][entry_id][DATA_AUGUST].async_setup() - - for platform in PLATFORMS: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, platform) - ) - - return True - async def async_setup(hass: HomeAssistant, config: dict): """Set up the August component from YAML.""" - - conf = config.get(DOMAIN) hass.data.setdefault(DOMAIN, {}) - - if not conf: - return True - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={ - CONF_LOGIN_METHOD: conf.get(CONF_LOGIN_METHOD), - CONF_USERNAME: conf.get(CONF_USERNAME), - CONF_PASSWORD: conf.get(CONF_PASSWORD), - CONF_INSTALL_ID: conf.get(CONF_INSTALL_ID), - CONF_ACCESS_TOKEN_CACHE_FILE: DEFAULT_AUGUST_CONFIG_FILE, - }, - ) - ) return True @@ -184,11 +40,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): return False raise ConfigEntryNotReady from err - except InvalidAuth: + except (RequireValidation, InvalidAuth): _async_start_reauth(hass, entry) return False - except RequireValidation: - return False except (CannotConnect, asyncio.TimeoutError) as err: raise ConfigEntryNotReady from err @@ -221,6 +75,31 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): return unload_ok +async def async_setup_august(hass, config_entry, august_gateway): + """Set up the August component.""" + + if CONF_PASSWORD in config_entry.data: + # We no longer need to store passwords since we do not + # support YAML anymore + config_data = config_entry.data.copy() + del config_data[CONF_PASSWORD] + hass.config_entries.async_update_entry(config_entry, data=config_data) + + await august_gateway.async_authenticate() + + data = hass.data[DOMAIN][config_entry.entry_id] = { + DATA_AUGUST: AugustData(hass, august_gateway) + } + await data[DATA_AUGUST].async_setup() + + for platform in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, platform) + ) + + return True + + class AugustData(AugustSubscriberMixin): """August data object.""" diff --git a/homeassistant/components/august/config_flow.py b/homeassistant/components/august/config_flow.py index f595479c0cf..29bb41947a0 100644 --- a/homeassistant/components/august/config_flow.py +++ b/homeassistant/components/august/config_flow.py @@ -5,14 +5,9 @@ from august.authenticator import ValidationResult import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from .const import ( - CONF_LOGIN_METHOD, - DEFAULT_TIMEOUT, - LOGIN_METHODS, - VERIFICATION_CODE_KEY, -) +from .const import CONF_LOGIN_METHOD, LOGIN_METHODS, VERIFICATION_CODE_KEY from .const import DOMAIN # pylint:disable=unused-import from .exceptions import CannotConnect, InvalidAuth, RequireValidation from .gateway import AugustGateway @@ -68,61 +63,48 @@ class AugustConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Store an AugustGateway().""" self._august_gateway = None - self.user_auth_details = {} + self._user_auth_details = {} self._needs_reset = False + self._mode = None super().__init__() async def async_step_user(self, user_input=None): """Handle the initial step.""" - if self._august_gateway is None: - self._august_gateway = AugustGateway(self.hass) + self._august_gateway = AugustGateway(self.hass) + return await self.async_step_user_validate() + + async def async_step_user_validate(self, user_input=None): + """Handle authentication.""" errors = {} if user_input is not None: - combined_inputs = {**self.user_auth_details, **user_input} - await self._august_gateway.async_setup(combined_inputs) - if self._needs_reset: - self._needs_reset = False - await self._august_gateway.async_reset_authentication() - - try: - info = await async_validate_input( - combined_inputs, - self._august_gateway, - ) - except CannotConnect: - errors["base"] = "cannot_connect" - except InvalidAuth: - errors["base"] = "invalid_auth" - except RequireValidation: - self.user_auth_details.update(user_input) - - return await self.async_step_validation() - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" - - if not errors: - self.user_auth_details.update(user_input) - - existing_entry = await self.async_set_unique_id( - combined_inputs[CONF_USERNAME] - ) - if existing_entry: - self.hass.config_entries.async_update_entry( - existing_entry, data=info["data"] - ) - await self.hass.config_entries.async_reload(existing_entry.entry_id) - return self.async_abort(reason="reauth_successful") - return self.async_create_entry(title=info["title"], data=info["data"]) + result = await self._async_auth_or_validate(user_input, errors) + if result is not None: + return result return self.async_show_form( - step_id="user", data_schema=self._async_build_schema(), errors=errors + step_id="user_validate", + data_schema=vol.Schema( + { + vol.Required( + CONF_LOGIN_METHOD, + default=self._user_auth_details.get(CONF_LOGIN_METHOD, "phone"), + ): vol.In(LOGIN_METHODS), + vol.Required( + CONF_USERNAME, + default=self._user_auth_details.get(CONF_USERNAME), + ): str, + vol.Required(CONF_PASSWORD): str, + } + ), + errors=errors, ) async def async_step_validation(self, user_input=None): """Handle validation (2fa) step.""" if user_input: - return await self.async_step_user({**self.user_auth_details, **user_input}) + if self._mode == "reauth": + return await self.async_step_reauth_validate(user_input) + return await self.async_step_user_validate(user_input) return self.async_show_form( step_id="validation", @@ -130,34 +112,70 @@ class AugustConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): {vol.Required(VERIFICATION_CODE_KEY): vol.All(str, vol.Strip)} ), description_placeholders={ - CONF_USERNAME: self.user_auth_details.get(CONF_USERNAME), - CONF_LOGIN_METHOD: self.user_auth_details.get(CONF_LOGIN_METHOD), + CONF_USERNAME: self._user_auth_details[CONF_USERNAME], + CONF_LOGIN_METHOD: self._user_auth_details[CONF_LOGIN_METHOD], }, ) - async def async_step_import(self, user_input): - """Handle import.""" - await self.async_set_unique_id(user_input[CONF_USERNAME]) - self._abort_if_unique_id_configured() - - return await self.async_step_user(user_input) - async def async_step_reauth(self, data): """Handle configuration by re-auth.""" - self.user_auth_details = dict(data) + self._user_auth_details = dict(data) + self._mode = "reauth" self._needs_reset = True - return await self.async_step_user() + self._august_gateway = AugustGateway(self.hass) + return await self.async_step_reauth_validate() - def _async_build_schema(self): - """Generate the config flow schema.""" - base_schema = { - vol.Required(CONF_LOGIN_METHOD, default="phone"): vol.In(LOGIN_METHODS), - vol.Required(CONF_USERNAME): str, - vol.Required(CONF_PASSWORD): str, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): vol.Coerce(int), - } - for key in self.user_auth_details: - if key == CONF_PASSWORD or key not in base_schema: - continue - del base_schema[key] - return vol.Schema(base_schema) + async def async_step_reauth_validate(self, user_input=None): + """Handle reauth and validation.""" + errors = {} + if user_input is not None: + result = await self._async_auth_or_validate(user_input, errors) + if result is not None: + return result + + return self.async_show_form( + step_id="reauth_validate", + data_schema=vol.Schema( + { + vol.Required(CONF_PASSWORD): str, + } + ), + errors=errors, + description_placeholders={ + CONF_USERNAME: self._user_auth_details[CONF_USERNAME], + }, + ) + + async def _async_auth_or_validate(self, user_input, errors): + self._user_auth_details.update(user_input) + await self._august_gateway.async_setup(self._user_auth_details) + if self._needs_reset: + self._needs_reset = False + await self._august_gateway.async_reset_authentication() + try: + info = await async_validate_input( + self._user_auth_details, + self._august_gateway, + ) + except CannotConnect: + errors["base"] = "cannot_connect" + except InvalidAuth: + errors["base"] = "invalid_auth" + except RequireValidation: + return await self.async_step_validation() + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + + if errors: + return None + + existing_entry = await self.async_set_unique_id( + self._user_auth_details[CONF_USERNAME] + ) + if not existing_entry: + return self.async_create_entry(title=info["title"], data=info["data"]) + + self.hass.config_entries.async_update_entry(existing_entry, data=info["data"]) + await self.hass.config_entries.async_reload(existing_entry.entry_id) + return self.async_abort(reason="reauth_successful") diff --git a/homeassistant/components/august/gateway.py b/homeassistant/components/august/gateway.py index b72bb52e710..f71541e82fc 100644 --- a/homeassistant/components/august/gateway.py +++ b/homeassistant/components/august/gateway.py @@ -21,6 +21,7 @@ from .const import ( CONF_INSTALL_ID, CONF_LOGIN_METHOD, DEFAULT_AUGUST_CONFIG_FILE, + DEFAULT_TIMEOUT, VERIFICATION_CODE_KEY, ) from .exceptions import CannotConnect, InvalidAuth, RequireValidation @@ -52,9 +53,7 @@ class AugustGateway: return { CONF_LOGIN_METHOD: self._config[CONF_LOGIN_METHOD], CONF_USERNAME: self._config[CONF_USERNAME], - CONF_PASSWORD: self._config[CONF_PASSWORD], CONF_INSTALL_ID: self._config.get(CONF_INSTALL_ID), - CONF_TIMEOUT: self._config.get(CONF_TIMEOUT), CONF_ACCESS_TOKEN_CACHE_FILE: self._access_token_cache_file, } @@ -70,14 +69,15 @@ class AugustGateway: self._config = conf self.api = ApiAsync( - self._aiohttp_session, timeout=self._config.get(CONF_TIMEOUT) + self._aiohttp_session, + timeout=self._config.get(CONF_TIMEOUT, DEFAULT_TIMEOUT), ) self.authenticator = AuthenticatorAsync( self.api, self._config[CONF_LOGIN_METHOD], self._config[CONF_USERNAME], - self._config[CONF_PASSWORD], + self._config.get(CONF_PASSWORD, ""), install_id=self._config.get(CONF_INSTALL_ID), access_token_cache_file=self._hass.config.path( self._access_token_cache_file @@ -128,14 +128,15 @@ class AugustGateway: async def async_refresh_access_token_if_needed(self): """Refresh the august access token if needed.""" - if self.authenticator.should_refresh(): - async with self._token_refresh_lock: - refreshed_authentication = ( - await self.authenticator.async_refresh_access_token(force=False) - ) - _LOGGER.info( - "Refreshed august access token. The old token expired at %s, and the new token expires at %s", - self.authentication.access_token_expires, - refreshed_authentication.access_token_expires, - ) - self.authentication = refreshed_authentication + if not self.authenticator.should_refresh(): + return + async with self._token_refresh_lock: + refreshed_authentication = ( + await self.authenticator.async_refresh_access_token(force=False) + ) + _LOGGER.info( + "Refreshed august access token. The old token expired at %s, and the new token expires at %s", + self.authentication.access_token_expires, + refreshed_authentication.access_token_expires, + ) + self.authentication = refreshed_authentication diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index dcdfb0a0497..91733b6822e 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -3,7 +3,6 @@ "name": "August", "documentation": "https://www.home-assistant.io/integrations/august", "requirements": ["py-august==0.25.2"], - "dependencies": ["configurator"], "codeowners": ["@bdraco"], "dhcp": [ {"hostname":"connect","macaddress":"D86162*"}, diff --git a/homeassistant/components/august/strings.json b/homeassistant/components/august/strings.json index 998d870e629..7939fb1d25f 100644 --- a/homeassistant/components/august/strings.json +++ b/homeassistant/components/august/strings.json @@ -17,15 +17,21 @@ }, "description": "Please check your {login_method} ({username}) and enter the verification code below" }, - "user": { + "user_validate": { "description": "If the Login Method is 'email', Username is the email address. If the Login Method is 'phone', Username is the phone number in the format '+NNNNNNNNN'.", "data": { - "timeout": "Timeout (seconds)", "password": "[%key:common::config_flow::data::password%]", "username": "[%key:common::config_flow::data::username%]", "login_method": "Login Method" }, "title": "Setup an August account" + }, + "reauth_validate": { + "description": "Enter the password for {username}.", + "data": { + "password": "[%key:common::config_flow::data::password%]" + }, + "title": "Reauthenticate an August account" } } } diff --git a/homeassistant/components/august/translations/en.json b/homeassistant/components/august/translations/en.json index c6c19321d8a..f2ceef78d48 100644 --- a/homeassistant/components/august/translations/en.json +++ b/homeassistant/components/august/translations/en.json @@ -10,11 +10,17 @@ "unknown": "Unexpected error" }, "step": { - "user": { + "reauth_validate": { + "data": { + "password": "Password" + }, + "description": "Enter the password for {username}.", + "title": "Reauthenticate an August account" + }, + "user_validate": { "data": { "login_method": "Login Method", "password": "Password", - "timeout": "Timeout (seconds)", "username": "Username" }, "description": "If the Login Method is 'email', Username is the email address. If the Login Method is 'phone', Username is the phone number in the format '+NNNNNNNNN'.", diff --git a/tests/components/august/mocks.py b/tests/components/august/mocks.py index e02e1ec59dd..928b753af52 100644 --- a/tests/components/august/mocks.py +++ b/tests/components/august/mocks.py @@ -22,15 +22,10 @@ from august.authenticator import AuthenticationState from august.doorbell import Doorbell, DoorbellDetail from august.lock import Lock, LockDetail -from homeassistant.components.august import ( - CONF_LOGIN_METHOD, - CONF_PASSWORD, - CONF_USERNAME, - DOMAIN, -) -from homeassistant.setup import async_setup_component +from homeassistant.components.august.const import CONF_LOGIN_METHOD, DOMAIN +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from tests.common import load_fixture +from tests.common import MockConfigEntry, load_fixture def _mock_get_config(): @@ -61,7 +56,13 @@ async def _mock_setup_august(hass, api_instance, authenticate_mock, api_mock): ) ) api_mock.return_value = api_instance - assert await async_setup_component(hass, DOMAIN, _mock_get_config()) + entry = MockConfigEntry( + domain=DOMAIN, + data=_mock_get_config()[DOMAIN], + options={}, + ) + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() return True diff --git a/tests/components/august/test_config_flow.py b/tests/components/august/test_config_flow.py index c1e7c9bb3c5..205c5c70689 100644 --- a/tests/components/august/test_config_flow.py +++ b/tests/components/august/test_config_flow.py @@ -54,9 +54,7 @@ async def test_form(hass): assert result2["data"] == { CONF_LOGIN_METHOD: "email", CONF_USERNAME: "my@email.tld", - CONF_PASSWORD: "test-password", CONF_INSTALL_ID: None, - CONF_TIMEOUT: 10, CONF_ACCESS_TOKEN_CACHE_FILE: ".my@email.tld.august.conf", } assert len(mock_setup.mock_calls) == 1 @@ -215,9 +213,7 @@ async def test_form_needs_validate(hass): assert result4["data"] == { CONF_LOGIN_METHOD: "email", CONF_USERNAME: "my@email.tld", - CONF_PASSWORD: "test-password", CONF_INSTALL_ID: None, - CONF_TIMEOUT: 10, CONF_ACCESS_TOKEN_CACHE_FILE: ".my@email.tld.august.conf", } assert len(mock_setup.mock_calls) == 1 @@ -268,3 +264,75 @@ async def test_form_reauth(hass): assert result2["reason"] == "reauth_successful" assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_reauth_with_2fa(hass): + """Test reauthenticate with 2fa.""" + + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_LOGIN_METHOD: "email", + CONF_USERNAME: "my@email.tld", + CONF_PASSWORD: "test-password", + CONF_INSTALL_ID: None, + CONF_TIMEOUT: 10, + CONF_ACCESS_TOKEN_CACHE_FILE: ".my@email.tld.august.conf", + }, + unique_id="my@email.tld", + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "reauth"}, data=entry.data + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch( + "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", + side_effect=RequireValidation, + ), patch( + "homeassistant.components.august.gateway.AuthenticatorAsync.async_send_verification_code", + return_value=True, + ) as mock_send_verification_code: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_PASSWORD: "new-test-password", + }, + ) + await hass.async_block_till_done() + + assert len(mock_send_verification_code.mock_calls) == 1 + assert result2["type"] == "form" + assert result2["errors"] is None + assert result2["step_id"] == "validation" + + # Try with the CORRECT verification code and we setup + with patch( + "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", + return_value=True, + ), patch( + "homeassistant.components.august.gateway.AuthenticatorAsync.async_validate_verification_code", + return_value=ValidationResult.VALIDATED, + ) as mock_validate_verification_code, patch( + "homeassistant.components.august.gateway.AuthenticatorAsync.async_send_verification_code", + return_value=True, + ) as mock_send_verification_code, patch( + "homeassistant.components.august.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.august.async_setup_entry", return_value=True + ) as mock_setup_entry: + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + {VERIFICATION_CODE_KEY: "correct"}, + ) + await hass.async_block_till_done() + + assert len(mock_validate_verification_code.mock_calls) == 1 + assert len(mock_send_verification_code.mock_calls) == 0 + assert result3["type"] == "abort" + assert result3["reason"] == "reauth_successful" + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/components/august/test_init.py b/tests/components/august/test_init.py index e881ac09c97..d0116d2c586 100644 --- a/tests/components/august/test_init.py +++ b/tests/components/august/test_init.py @@ -7,13 +7,7 @@ from august.authenticator_common import AuthenticationState from august.exceptions import AugustApiAIOHTTPError from homeassistant import setup -from homeassistant.components.august.const import ( - CONF_ACCESS_TOKEN_CACHE_FILE, - CONF_INSTALL_ID, - CONF_LOGIN_METHOD, - DEFAULT_AUGUST_CONFIG_FILE, - DOMAIN, -) +from homeassistant.components.august.const import DOMAIN from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.config_entries import ( ENTRY_STATE_SETUP_ERROR, @@ -21,16 +15,12 @@ from homeassistant.config_entries import ( ) from homeassistant.const import ( ATTR_ENTITY_ID, - CONF_PASSWORD, - CONF_TIMEOUT, - CONF_USERNAME, SERVICE_LOCK, SERVICE_UNLOCK, STATE_LOCKED, STATE_ON, ) from homeassistant.exceptions import HomeAssistantError -from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry from tests.components.august.mocks import ( @@ -149,35 +139,6 @@ async def test_lock_has_doorsense(hass): assert binary_sensor_missing_doorsense_id_name_open is None -async def test_set_up_from_yaml(hass): - """Test to make sure config is imported from yaml.""" - - await setup.async_setup_component(hass, "persistent_notification", {}) - with patch( - "homeassistant.components.august.async_setup_august", - return_value=True, - ) as mock_setup_august, patch( - "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", - return_value=True, - ): - assert await async_setup_component(hass, DOMAIN, _mock_get_config()) - await hass.async_block_till_done() - assert len(mock_setup_august.mock_calls) == 1 - call = mock_setup_august.call_args - args, _ = call - imported_config_entry = args[1] - # The import must use DEFAULT_AUGUST_CONFIG_FILE so they - # do not loose their token when config is migrated - assert imported_config_entry.data == { - CONF_ACCESS_TOKEN_CACHE_FILE: DEFAULT_AUGUST_CONFIG_FILE, - CONF_INSTALL_ID: None, - CONF_LOGIN_METHOD: "email", - CONF_PASSWORD: "mocked_password", - CONF_TIMEOUT: None, - CONF_USERNAME: "mocked_username", - } - - async def test_auth_fails(hass): """Config entry state is ENTRY_STATE_SETUP_ERROR when auth fails.""" @@ -201,7 +162,7 @@ async def test_auth_fails(hass): flows = hass.config_entries.flow.async_progress() - assert flows[0]["step_id"] == "user" + assert flows[0]["step_id"] == "reauth_validate" async def test_bad_password(hass): @@ -229,7 +190,7 @@ async def test_bad_password(hass): flows = hass.config_entries.flow.async_progress() - assert flows[0]["step_id"] == "user" + assert flows[0]["step_id"] == "reauth_validate" async def test_http_failure(hass): @@ -279,7 +240,7 @@ async def test_unknown_auth_state(hass): flows = hass.config_entries.flow.async_progress() - assert flows[0]["step_id"] == "user" + assert flows[0]["step_id"] == "reauth_validate" async def test_requires_validation_state(hass): @@ -305,4 +266,5 @@ async def test_requires_validation_state(hass): assert config_entry.state == ENTRY_STATE_SETUP_ERROR - assert hass.config_entries.flow.async_progress() == [] + assert len(hass.config_entries.flow.async_progress()) == 1 + assert hass.config_entries.flow.async_progress()[0]["context"]["source"] == "reauth" From d21d9951ba1663905325f80ea8c9ef2019eaaa6e Mon Sep 17 00:00:00 2001 From: Kevin Worrel <37058192+dieselrabbit@users.noreply.github.com> Date: Tue, 16 Mar 2021 16:32:02 -0700 Subject: [PATCH 415/831] Add Pentair ScreenLogic integration (#47933) Co-authored-by: J. Nick Koston --- .coveragerc | 5 + CODEOWNERS | 1 + .../components/screenlogic/__init__.py | 202 ++++++++++++++ .../components/screenlogic/binary_sensor.py | 54 ++++ .../components/screenlogic/config_flow.py | 218 +++++++++++++++ homeassistant/components/screenlogic/const.py | 7 + .../components/screenlogic/manifest.json | 11 + .../components/screenlogic/sensor.py | 107 ++++++++ .../components/screenlogic/strings.json | 39 +++ .../components/screenlogic/switch.py | 63 +++++ .../screenlogic/translations/en.json | 39 +++ .../components/screenlogic/water_heater.py | 128 +++++++++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/dhcp.py | 5 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/screenlogic/__init__.py | 1 + .../screenlogic/test_config_flow.py | 249 ++++++++++++++++++ 18 files changed, 1136 insertions(+) create mode 100644 homeassistant/components/screenlogic/__init__.py create mode 100644 homeassistant/components/screenlogic/binary_sensor.py create mode 100644 homeassistant/components/screenlogic/config_flow.py create mode 100644 homeassistant/components/screenlogic/const.py create mode 100644 homeassistant/components/screenlogic/manifest.json create mode 100644 homeassistant/components/screenlogic/sensor.py create mode 100644 homeassistant/components/screenlogic/strings.json create mode 100644 homeassistant/components/screenlogic/switch.py create mode 100644 homeassistant/components/screenlogic/translations/en.json create mode 100644 homeassistant/components/screenlogic/water_heater.py create mode 100644 tests/components/screenlogic/__init__.py create mode 100644 tests/components/screenlogic/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index d6bdfb9b091..8f609ec5b22 100644 --- a/.coveragerc +++ b/.coveragerc @@ -842,6 +842,11 @@ omit = homeassistant/components/satel_integra/* homeassistant/components/schluter/* homeassistant/components/scrape/sensor.py + homeassistant/components/screenlogic/__init__.py + homeassistant/components/screenlogic/binary_sensor.py + homeassistant/components/screenlogic/sensor.py + homeassistant/components/screenlogic/switch.py + homeassistant/components/screenlogic/water_heater.py homeassistant/components/scsgate/* homeassistant/components/scsgate/cover.py homeassistant/components/sendgrid/notify.py diff --git a/CODEOWNERS b/CODEOWNERS index 263e5337c58..1b0f2747dee 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -401,6 +401,7 @@ homeassistant/components/samsungtv/* @escoand homeassistant/components/scene/* @home-assistant/core homeassistant/components/schluter/* @prairieapps homeassistant/components/scrape/* @fabaff +homeassistant/components/screenlogic/* @dieselrabbit homeassistant/components/script/* @home-assistant/core homeassistant/components/search/* @home-assistant/core homeassistant/components/sense/* @kbickar diff --git a/homeassistant/components/screenlogic/__init__.py b/homeassistant/components/screenlogic/__init__.py new file mode 100644 index 00000000000..720b59f80b9 --- /dev/null +++ b/homeassistant/components/screenlogic/__init__.py @@ -0,0 +1,202 @@ +"""The Screenlogic integration.""" +import asyncio +from collections import defaultdict +from datetime import timedelta +import logging + +from screenlogicpy import ScreenLogicError, ScreenLogicGateway +from screenlogicpy.const import ( + CONTROLLER_HARDWARE, + SL_GATEWAY_IP, + SL_GATEWAY_NAME, + SL_GATEWAY_PORT, +) + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, CONF_SCAN_INTERVAL +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) + +from .config_flow import async_discover_gateways_by_unique_id, name_for_mac +from .const import DEFAULT_SCAN_INTERVAL, DISCOVERED_GATEWAYS, DOMAIN + +_LOGGER = logging.getLogger(__name__) + +PLATFORMS = ["switch", "sensor", "binary_sensor", "water_heater"] + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the Screenlogic component.""" + domain_data = hass.data[DOMAIN] = {} + domain_data[DISCOVERED_GATEWAYS] = await async_discover_gateways_by_unique_id(hass) + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up Screenlogic from a config entry.""" + mac = entry.unique_id + # Attempt to re-discover named gateway to follow IP changes + discovered_gateways = hass.data[DOMAIN][DISCOVERED_GATEWAYS] + if mac in discovered_gateways: + connect_info = discovered_gateways[mac] + else: + _LOGGER.warning("Gateway rediscovery failed.") + # Static connection defined or fallback from discovery + connect_info = { + SL_GATEWAY_NAME: name_for_mac(mac), + SL_GATEWAY_IP: entry.data[CONF_IP_ADDRESS], + SL_GATEWAY_PORT: entry.data[CONF_PORT], + } + + try: + gateway = ScreenLogicGateway(**connect_info) + except ScreenLogicError as ex: + _LOGGER.error("Error while connecting to the gateway %s: %s", connect_info, ex) + raise ConfigEntryNotReady from ex + except AttributeError as ex: + _LOGGER.exception( + "Unexpected error while connecting to the gateway %s", connect_info + ) + raise ConfigEntryNotReady from ex + + coordinator = ScreenlogicDataUpdateCoordinator( + hass, config_entry=entry, gateway=gateway + ) + + device_data = defaultdict(list) + + await coordinator.async_refresh() + + for circuit in coordinator.data["circuits"]: + device_data["switch"].append(circuit) + + for sensor in coordinator.data["sensors"]: + if sensor == "chem_alarm": + device_data["binary_sensor"].append(sensor) + else: + if coordinator.data["sensors"][sensor]["value"] != 0: + device_data["sensor"].append(sensor) + + for pump in coordinator.data["pumps"]: + if ( + coordinator.data["pumps"][pump]["data"] != 0 + and "currentWatts" in coordinator.data["pumps"][pump] + ): + device_data["pump"].append(pump) + + for body in coordinator.data["bodies"]: + device_data["water_heater"].append(body) + + hass.data[DOMAIN][entry.entry_id] = { + "coordinator": coordinator, + "devices": device_data, + "listener": entry.add_update_listener(async_update_listener), + } + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + hass.data[DOMAIN][entry.entry_id]["listener"]() + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok + + +async def async_update_listener(hass: HomeAssistant, entry: ConfigEntry): + """Handle options update.""" + await hass.config_entries.async_reload(entry.entry_id) + + +class ScreenlogicDataUpdateCoordinator(DataUpdateCoordinator): + """Class to manage the data update for the Screenlogic component.""" + + def __init__(self, hass, *, config_entry, gateway): + """Initialize the Screenlogic Data Update Coordinator.""" + self.config_entry = config_entry + self.gateway = gateway + self.screenlogic_data = {} + interval = timedelta( + seconds=config_entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) + ) + super().__init__( + hass, + _LOGGER, + name=DOMAIN, + update_interval=interval, + ) + + async def _async_update_data(self): + """Fetch data from the Screenlogic gateway.""" + try: + await self.hass.async_add_executor_job(self.gateway.update) + return self.gateway.get_data() + except ScreenLogicError as error: + raise UpdateFailed(error) from error + + +class ScreenlogicEntity(CoordinatorEntity): + """Base class for all ScreenLogic entities.""" + + def __init__(self, coordinator, datakey): + """Initialize of the entity.""" + super().__init__(coordinator) + self._data_key = datakey + + @property + def mac(self): + """Mac address.""" + return self.coordinator.config_entry.unique_id + + @property + def unique_id(self): + """Entity Unique ID.""" + return f"{self.mac}_{self._data_key}" + + @property + def config_data(self): + """Shortcut for config data.""" + return self.coordinator.data["config"] + + @property + def gateway(self): + """Return the gateway.""" + return self.coordinator.gateway + + @property + def gateway_name(self): + """Return the configured name of the gateway.""" + return self.gateway.name + + @property + def device_info(self): + """Return device information for the controller.""" + controller_type = self.config_data["controler_type"] + hardware_type = self.config_data["hardware_type"] + return { + "connections": {(dr.CONNECTION_NETWORK_MAC, self.mac)}, + "name": self.gateway_name, + "manufacturer": "Pentair", + "model": CONTROLLER_HARDWARE[controller_type][hardware_type], + } diff --git a/homeassistant/components/screenlogic/binary_sensor.py b/homeassistant/components/screenlogic/binary_sensor.py new file mode 100644 index 00000000000..fa8d63ee5e6 --- /dev/null +++ b/homeassistant/components/screenlogic/binary_sensor.py @@ -0,0 +1,54 @@ +"""Support for a ScreenLogic Binary Sensor.""" +import logging + +from screenlogicpy.const import ON_OFF + +from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorEntity + +from . import ScreenlogicEntity +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up entry.""" + entities = [] + data = hass.data[DOMAIN][config_entry.entry_id] + coordinator = data["coordinator"] + + for binary_sensor in data["devices"]["binary_sensor"]: + entities.append(ScreenLogicBinarySensor(coordinator, binary_sensor)) + async_add_entities(entities, True) + + +class ScreenLogicBinarySensor(ScreenlogicEntity, BinarySensorEntity): + """Representation of a ScreenLogic binary sensor entity.""" + + @property + def name(self): + """Return the sensor name.""" + return f"{self.gateway_name} {self.sensor['name']}" + + @property + def device_class(self): + """Return the device class.""" + device_class = self.sensor.get("hass_type") + if device_class in DEVICE_CLASSES: + return device_class + return None + + @property + def is_on(self) -> bool: + """Determine if the sensor is on.""" + return self.sensor["value"] == ON_OFF.ON + + @property + def sensor(self): + """Shortcut to access the sensor data.""" + return self.sensor_data[self._data_key] + + @property + def sensor_data(self): + """Shortcut to access the sensors data.""" + return self.coordinator.data["sensors"] diff --git a/homeassistant/components/screenlogic/config_flow.py b/homeassistant/components/screenlogic/config_flow.py new file mode 100644 index 00000000000..865c0fdbbf4 --- /dev/null +++ b/homeassistant/components/screenlogic/config_flow.py @@ -0,0 +1,218 @@ +"""Config flow for ScreenLogic.""" +import logging + +from screenlogicpy import ScreenLogicError, discover +from screenlogicpy.const import SL_GATEWAY_IP, SL_GATEWAY_NAME, SL_GATEWAY_PORT +from screenlogicpy.requests import login +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.components.dhcp import HOSTNAME, IP_ADDRESS +from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, CONF_SCAN_INTERVAL +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.device_registry import format_mac + +from .const import DEFAULT_SCAN_INTERVAL, MIN_SCAN_INTERVAL +from .const import DOMAIN # pylint: disable=unused-import + +_LOGGER = logging.getLogger(__name__) + +GATEWAY_SELECT_KEY = "selected_gateway" +GATEWAY_MANUAL_ENTRY = "manual" + +PENTAIR_OUI = "00-C0-33" + + +async def async_discover_gateways_by_unique_id(hass): + """Discover gateways and return a dict of them by unique id.""" + discovered_gateways = {} + try: + hosts = await hass.async_add_executor_job(discover) + _LOGGER.debug("Discovered hosts: %s", hosts) + except ScreenLogicError as ex: + _LOGGER.debug(ex) + return discovered_gateways + + for host in hosts: + mac = _extract_mac_from_name(host[SL_GATEWAY_NAME]) + discovered_gateways[mac] = host + + _LOGGER.debug("Discovered gateways: %s", discovered_gateways) + return discovered_gateways + + +def _extract_mac_from_name(name): + return format_mac(f"{PENTAIR_OUI}-{name.split(':')[1].strip()}") + + +def short_mac(mac): + """Short version of the mac as seen in the app.""" + return "-".join(mac.split(":")[3:]).upper() + + +def name_for_mac(mac): + """Derive the gateway name from the mac.""" + return f"Pentair: {short_mac(mac)}" + + +async def async_get_mac_address(hass, ip_address, port): + """Connect to a screenlogic gateway and return the mac address.""" + connected_socket = await hass.async_add_executor_job( + login.create_socket, + ip_address, + port, + ) + if not connected_socket: + raise ScreenLogicError("Unknown socket error") + return await hass.async_add_executor_job(login.gateway_connect, connected_socket) + + +class ScreenlogicConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Config flow to setup screen logic devices.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + def __init__(self): + """Initialize ScreenLogic ConfigFlow.""" + self.discovered_gateways = {} + self.discovered_ip = None + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for ScreenLogic.""" + return ScreenLogicOptionsFlowHandler(config_entry) + + async def async_step_user(self, user_input=None): + """Handle the start of the config flow.""" + self.discovered_gateways = await async_discover_gateways_by_unique_id(self.hass) + return await self.async_step_gateway_select() + + async def async_step_dhcp(self, dhcp_discovery): + """Handle dhcp discovery.""" + mac = _extract_mac_from_name(dhcp_discovery[HOSTNAME]) + await self.async_set_unique_id(mac) + self._abort_if_unique_id_configured( + updates={CONF_IP_ADDRESS: dhcp_discovery[IP_ADDRESS]} + ) + self.discovered_ip = dhcp_discovery[IP_ADDRESS] + self.context["title_placeholders"] = {"name": dhcp_discovery[HOSTNAME]} + return await self.async_step_gateway_entry() + + async def async_step_gateway_select(self, user_input=None): + """Handle the selection of a discovered ScreenLogic gateway.""" + existing = self._async_current_ids() + unconfigured_gateways = { + mac: gateway[SL_GATEWAY_NAME] + for mac, gateway in self.discovered_gateways.items() + if mac not in existing + } + + if not unconfigured_gateways: + return await self.async_step_gateway_entry() + + errors = {} + if user_input is not None: + if user_input[GATEWAY_SELECT_KEY] == GATEWAY_MANUAL_ENTRY: + return await self.async_step_gateway_entry() + + mac = user_input[GATEWAY_SELECT_KEY] + selected_gateway = self.discovered_gateways[mac] + await self.async_set_unique_id(mac) + self._abort_if_unique_id_configured() + return self.async_create_entry( + title=name_for_mac(mac), + data={ + CONF_IP_ADDRESS: selected_gateway[SL_GATEWAY_IP], + CONF_PORT: selected_gateway[SL_GATEWAY_PORT], + }, + ) + + return self.async_show_form( + step_id="gateway_select", + data_schema=vol.Schema( + { + vol.Required(GATEWAY_SELECT_KEY): vol.In( + { + **unconfigured_gateways, + GATEWAY_MANUAL_ENTRY: "Manually configure a ScreenLogic gateway", + } + ) + } + ), + errors=errors, + description_placeholders={}, + ) + + async def async_step_gateway_entry(self, user_input=None): + """Handle the manual entry of a ScreenLogic gateway.""" + errors = {} + ip_address = self.discovered_ip + port = 80 + + if user_input is not None: + ip_address = user_input[CONF_IP_ADDRESS] + port = user_input[CONF_PORT] + try: + mac = format_mac( + await async_get_mac_address(self.hass, ip_address, port) + ) + except ScreenLogicError as ex: + _LOGGER.debug(ex) + errors[CONF_IP_ADDRESS] = "cannot_connect" + + if not errors: + await self.async_set_unique_id(mac) + self._abort_if_unique_id_configured() + return self.async_create_entry( + title=name_for_mac(mac), + data={ + CONF_IP_ADDRESS: ip_address, + CONF_PORT: port, + }, + ) + + return self.async_show_form( + step_id="gateway_entry", + data_schema=vol.Schema( + { + vol.Required(CONF_IP_ADDRESS, default=ip_address): str, + vol.Required(CONF_PORT, default=port): int, + } + ), + errors=errors, + description_placeholders={}, + ) + + +class ScreenLogicOptionsFlowHandler(config_entries.OptionsFlow): + """Handles the options for the ScreenLogic integration.""" + + def __init__(self, config_entry: config_entries.ConfigEntry): + """Init the screen logic options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Manage the options.""" + if user_input is not None: + return self.async_create_entry( + title=self.config_entry.title, data=user_input + ) + + current_interval = self.config_entry.options.get( + CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL + ) + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Required( + CONF_SCAN_INTERVAL, + default=current_interval, + ): vol.All(cv.positive_int, vol.Clamp(min=MIN_SCAN_INTERVAL)) + } + ), + description_placeholders={"gateway_name": self.config_entry.title}, + ) diff --git a/homeassistant/components/screenlogic/const.py b/homeassistant/components/screenlogic/const.py new file mode 100644 index 00000000000..d777dc6ddc5 --- /dev/null +++ b/homeassistant/components/screenlogic/const.py @@ -0,0 +1,7 @@ +"""Constants for the ScreenLogic integration.""" + +DOMAIN = "screenlogic" +DEFAULT_SCAN_INTERVAL = 30 +MIN_SCAN_INTERVAL = 10 + +DISCOVERED_GATEWAYS = "_discovered_gateways" diff --git a/homeassistant/components/screenlogic/manifest.json b/homeassistant/components/screenlogic/manifest.json new file mode 100644 index 00000000000..2ff3f2c683d --- /dev/null +++ b/homeassistant/components/screenlogic/manifest.json @@ -0,0 +1,11 @@ +{ + "domain": "screenlogic", + "name": "Pentair ScreenLogic", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/screenlogic", + "requirements": ["screenlogicpy==0.1.2"], + "codeowners": [ + "@dieselrabbit" + ], + "dhcp": [{"hostname":"pentair: *","macaddress":"00C033*"}] +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/sensor.py b/homeassistant/components/screenlogic/sensor.py new file mode 100644 index 00000000000..f4f86cdd2aa --- /dev/null +++ b/homeassistant/components/screenlogic/sensor.py @@ -0,0 +1,107 @@ +"""Support for a ScreenLogic Sensor.""" +import logging + +from homeassistant.components.sensor import DEVICE_CLASSES + +from . import ScreenlogicEntity +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +PUMP_SENSORS = ("currentWatts", "currentRPM", "currentGPM") + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up entry.""" + entities = [] + data = hass.data[DOMAIN][config_entry.entry_id] + coordinator = data["coordinator"] + # Generic sensors + for sensor in data["devices"]["sensor"]: + entities.append(ScreenLogicSensor(coordinator, sensor)) + for pump in data["devices"]["pump"]: + for pump_key in PUMP_SENSORS: + entities.append(ScreenLogicPumpSensor(coordinator, pump, pump_key)) + + async_add_entities(entities, True) + + +class ScreenLogicSensor(ScreenlogicEntity): + """Representation of a ScreenLogic sensor entity.""" + + @property + def name(self): + """Name of the sensor.""" + return f"{self.gateway_name} {self.sensor['name']}" + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return self.sensor.get("unit") + + @property + def device_class(self): + """Device class of the sensor.""" + device_class = self.sensor.get("hass_type") + if device_class in DEVICE_CLASSES: + return device_class + return None + + @property + def state(self): + """State of the sensor.""" + value = self.sensor["value"] + return (value - 1) if "supply" in self._data_key else value + + @property + def sensor(self): + """Shortcut to access the sensor data.""" + return self.sensor_data[self._data_key] + + @property + def sensor_data(self): + """Shortcut to access the sensors data.""" + return self.coordinator.data["sensors"] + + +class ScreenLogicPumpSensor(ScreenlogicEntity): + """Representation of a ScreenLogic pump sensor entity.""" + + def __init__(self, coordinator, pump, key): + """Initialize of the pump sensor.""" + super().__init__(coordinator, f"{key}_{pump}") + self._pump_id = pump + self._key = key + + @property + def name(self): + """Return the pump sensor name.""" + return f"{self.gateway_name} {self.pump_sensor['name']}" + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return self.pump_sensor.get("unit") + + @property + def device_class(self): + """Return the device class.""" + device_class = self.pump_sensor.get("hass_type") + if device_class in DEVICE_CLASSES: + return device_class + return None + + @property + def state(self): + """State of the pump sensor.""" + return self.pump_sensor["value"] + + @property + def pump_sensor(self): + """Shortcut to access the pump sensor data.""" + return self.pumps_data[self._pump_id][self._key] + + @property + def pumps_data(self): + """Shortcut to access the pump data.""" + return self.coordinator.data["pumps"] diff --git a/homeassistant/components/screenlogic/strings.json b/homeassistant/components/screenlogic/strings.json new file mode 100644 index 00000000000..155eeb3043e --- /dev/null +++ b/homeassistant/components/screenlogic/strings.json @@ -0,0 +1,39 @@ +{ + "config": { + "flow_title": "ScreenLogic {name}", + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + }, + "step": { + "gateway_entry": { + "title": "ScreenLogic", + "description": "Enter your ScreenLogic Gateway information.", + "data": { + "ip_address": "[%key:common::config_flow::data::ip%]", + "port": "[%key:common::config_flow::data::port%]" + } + }, + "gateway_select": { + "title": "ScreenLogic", + "description": "The following ScreenLogic gateways were discovered. Please select one to configure, or choose to manually configure a ScreenLogic gateway.", + "data": { + "selected_gateway": "Gateway" + } + } + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + }, + "options":{ + "step": { + "init": { + "title": "ScreenLogic", + "description": "Specify settings for {gateway_name}", + "data": { + "scan_interval": "Seconds between scans" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/switch.py b/homeassistant/components/screenlogic/switch.py new file mode 100644 index 00000000000..aa1e643681e --- /dev/null +++ b/homeassistant/components/screenlogic/switch.py @@ -0,0 +1,63 @@ +"""Support for a ScreenLogic 'circuit' switch.""" +import logging + +from screenlogicpy.const import ON_OFF + +from homeassistant.components.switch import SwitchEntity + +from . import ScreenlogicEntity +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up entry.""" + entities = [] + data = hass.data[DOMAIN][config_entry.entry_id] + coordinator = data["coordinator"] + + for switch in data["devices"]["switch"]: + entities.append(ScreenLogicSwitch(coordinator, switch)) + async_add_entities(entities, True) + + +class ScreenLogicSwitch(ScreenlogicEntity, SwitchEntity): + """ScreenLogic switch entity.""" + + @property + def name(self): + """Get the name of the switch.""" + return f"{self.gateway_name} {self.circuit['name']}" + + @property + def is_on(self) -> bool: + """Get whether the switch is in on state.""" + return self.circuit["value"] == 1 + + async def async_turn_on(self, **kwargs) -> None: + """Send the ON command.""" + return await self._async_set_circuit(ON_OFF.ON) + + async def async_turn_off(self, **kwargs) -> None: + """Send the OFF command.""" + return await self._async_set_circuit(ON_OFF.OFF) + + async def _async_set_circuit(self, circuit_value) -> None: + if await self.hass.async_add_executor_job( + self.gateway.set_circuit, self._data_key, circuit_value + ): + _LOGGER.info("screenlogic turn %s %s", circuit_value, self._data_key) + await self.coordinator.async_request_refresh() + else: + _LOGGER.info("screenlogic turn %s %s error", circuit_value, self._data_key) + + @property + def circuit(self): + """Shortcut to access the circuit.""" + return self.circuits_data[self._data_key] + + @property + def circuits_data(self): + """Shortcut to access the circuits data.""" + return self.coordinator.data["circuits"] diff --git a/homeassistant/components/screenlogic/translations/en.json b/homeassistant/components/screenlogic/translations/en.json new file mode 100644 index 00000000000..2572fdf38fa --- /dev/null +++ b/homeassistant/components/screenlogic/translations/en.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "IP Address", + "port": "Port" + }, + "description": "Enter your ScreenLogic Gateway information.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "Gateway" + }, + "description": "The following ScreenLogic gateways were discovered. Please select one to configure, or choose to manually configure a ScreenLogic gateway.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Seconds between scans" + }, + "description": "Specify settings for {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/water_heater.py b/homeassistant/components/screenlogic/water_heater.py new file mode 100644 index 00000000000..2a0a8e82c80 --- /dev/null +++ b/homeassistant/components/screenlogic/water_heater.py @@ -0,0 +1,128 @@ +"""Support for a ScreenLogic Water Heater.""" +import logging + +from screenlogicpy.const import HEAT_MODE + +from homeassistant.components.water_heater import ( + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, + WaterHeaterEntity, +) +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT + +from . import ScreenlogicEntity +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +SUPPORTED_FEATURES = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE + +HEAT_MODE_NAMES = HEAT_MODE.Names + +MODE_NAME_TO_MODE_NUM = { + HEAT_MODE_NAMES[num]: num for num in range(len(HEAT_MODE_NAMES)) +} + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up entry.""" + entities = [] + data = hass.data[DOMAIN][config_entry.entry_id] + coordinator = data["coordinator"] + + for body in data["devices"]["water_heater"]: + entities.append(ScreenLogicWaterHeater(coordinator, body)) + async_add_entities(entities, True) + + +class ScreenLogicWaterHeater(ScreenlogicEntity, WaterHeaterEntity): + """Represents the heating functions for a body of water.""" + + @property + def name(self) -> str: + """Name of the water heater.""" + ent_name = self.body["heat_status"]["name"] + return f"{self.gateway_name} {ent_name}" + + @property + def state(self) -> str: + """State of the water heater.""" + return HEAT_MODE.GetFriendlyName(self.body["heat_status"]["value"]) + + @property + def min_temp(self) -> float: + """Minimum allowed temperature.""" + return self.body["min_set_point"]["value"] + + @property + def max_temp(self) -> float: + """Maximum allowed temperature.""" + return self.body["max_set_point"]["value"] + + @property + def current_temperature(self) -> float: + """Return water temperature.""" + return self.body["last_temperature"]["value"] + + @property + def target_temperature(self) -> float: + """Target temperature.""" + return self.body["heat_set_point"]["value"] + + @property + def temperature_unit(self) -> str: + """Return the unit of measurement.""" + if self.config_data["is_celcius"]["value"] == 1: + return TEMP_CELSIUS + return TEMP_FAHRENHEIT + + @property + def current_operation(self) -> str: + """Return operation.""" + return HEAT_MODE_NAMES[self.body["heat_mode"]["value"]] + + @property + def operation_list(self): + """All available operations.""" + supported_heat_modes = [HEAT_MODE.OFF] + # Is solar listed as available equipment? + if self.coordinator.data["config"]["equipment_flags"] & 0x1: + supported_heat_modes.extend([HEAT_MODE.SOLAR, HEAT_MODE.SOLAR_PREFERED]) + supported_heat_modes.append(HEAT_MODE.HEATER) + + return [HEAT_MODE_NAMES[mode_num] for mode_num in supported_heat_modes] + + @property + def supported_features(self): + """Supported features of the water heater.""" + return SUPPORTED_FEATURES + + async def async_set_temperature(self, **kwargs) -> None: + """Change the setpoint of the heater.""" + temperature = kwargs.get(ATTR_TEMPERATURE) + if await self.hass.async_add_executor_job( + self.gateway.set_heat_temp, int(self._data_key), int(temperature) + ): + await self.coordinator.async_request_refresh() + else: + _LOGGER.error("screenlogic set_temperature error") + + async def async_set_operation_mode(self, operation_mode) -> None: + """Set the operation mode.""" + mode = MODE_NAME_TO_MODE_NUM[operation_mode] + if await self.hass.async_add_executor_job( + self.gateway.set_heat_mode, int(self._data_key), int(mode) + ): + await self.coordinator.async_request_refresh() + else: + _LOGGER.error("screenlogic set_operation_mode error") + + @property + def body(self): + """Shortcut to access body data.""" + return self.bodies_data[self._data_key] + + @property + def bodies_data(self): + """Shortcut to access bodies data.""" + return self.coordinator.data["bodies"] diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index a3bcef9047f..b6799f59a04 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -195,6 +195,7 @@ FLOWS = [ "rpi_power", "ruckus_unleashed", "samsungtv", + "screenlogic", "sense", "sentry", "sharkiq", diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index b3e10c90621..b5d419662ff 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -109,6 +109,11 @@ DHCP = [ "hostname": "irobot-*", "macaddress": "501479*" }, + { + "domain": "screenlogic", + "hostname": "pentair: *", + "macaddress": "00C033*" + }, { "domain": "sense", "hostname": "sense-*", diff --git a/requirements_all.txt b/requirements_all.txt index 1f0cc6ab7ca..a3788d7493a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2008,6 +2008,9 @@ scapy==2.4.4 # homeassistant.components.deutsche_bahn schiene==0.23 +# homeassistant.components.screenlogic +screenlogicpy==0.1.2 + # homeassistant.components.scsgate scsgate==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7dd403f6bd1..216a7e0c0b2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1039,6 +1039,9 @@ samsungtvws==1.6.0 # homeassistant.components.dhcp scapy==2.4.4 +# homeassistant.components.screenlogic +screenlogicpy==0.1.2 + # homeassistant.components.emulated_kasa # homeassistant.components.sense sense_energy==0.9.0 diff --git a/tests/components/screenlogic/__init__.py b/tests/components/screenlogic/__init__.py new file mode 100644 index 00000000000..ad2b82960f0 --- /dev/null +++ b/tests/components/screenlogic/__init__.py @@ -0,0 +1 @@ +"""Tests for the Screenlogic integration.""" diff --git a/tests/components/screenlogic/test_config_flow.py b/tests/components/screenlogic/test_config_flow.py new file mode 100644 index 00000000000..6d2c1ee2595 --- /dev/null +++ b/tests/components/screenlogic/test_config_flow.py @@ -0,0 +1,249 @@ +"""Test the Pentair ScreenLogic config flow.""" +from unittest.mock import patch + +from screenlogicpy import ScreenLogicError +from screenlogicpy.const import ( + SL_GATEWAY_IP, + SL_GATEWAY_NAME, + SL_GATEWAY_PORT, + SL_GATEWAY_SUBTYPE, + SL_GATEWAY_TYPE, +) + +from homeassistant import config_entries, setup +from homeassistant.components.dhcp import HOSTNAME, IP_ADDRESS +from homeassistant.components.screenlogic.config_flow import ( + GATEWAY_MANUAL_ENTRY, + GATEWAY_SELECT_KEY, +) +from homeassistant.components.screenlogic.const import ( + DEFAULT_SCAN_INTERVAL, + DOMAIN, + MIN_SCAN_INTERVAL, +) +from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, CONF_SCAN_INTERVAL + +from tests.common import MockConfigEntry + + +async def test_flow_discovery(hass): + """Test the flow works with basic discovery.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch( + "homeassistant.components.screenlogic.config_flow.discover", + return_value=[ + { + SL_GATEWAY_IP: "1.1.1.1", + SL_GATEWAY_PORT: 80, + SL_GATEWAY_TYPE: 12, + SL_GATEWAY_SUBTYPE: 2, + SL_GATEWAY_NAME: "Pentair: 01-01-01", + }, + ], + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["errors"] == {} + assert result["step_id"] == "gateway_select" + + with patch( + "homeassistant.components.screenlogic.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.screenlogic.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={GATEWAY_SELECT_KEY: "00:c0:33:01:01:01"} + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "Pentair: 01-01-01" + assert result2["data"] == { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_PORT: 80, + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_flow_discover_none(hass): + """Test when nothing is discovered.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch( + "homeassistant.components.screenlogic.config_flow.discover", + return_value=[], + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["errors"] == {} + assert result["step_id"] == "gateway_entry" + + +async def test_flow_discover_error(hass): + """Test when discovery errors.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch( + "homeassistant.components.screenlogic.config_flow.discover", + side_effect=ScreenLogicError("Fake error"), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["errors"] == {} + assert result["step_id"] == "gateway_entry" + + +async def test_dhcp(hass): + """Test DHCP discovery flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": "dhcp"}, + data={ + HOSTNAME: "Pentair: 01-01-01", + IP_ADDRESS: "1.1.1.1", + }, + ) + + assert result["type"] == "form" + assert result["step_id"] == "gateway_entry" + + +async def test_form_manual_entry(hass): + """Test we get the form.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch( + "homeassistant.components.screenlogic.config_flow.discover", + return_value=[ + { + SL_GATEWAY_IP: "1.1.1.1", + SL_GATEWAY_PORT: 80, + SL_GATEWAY_TYPE: 12, + SL_GATEWAY_SUBTYPE: 2, + SL_GATEWAY_NAME: "Pentair: 01-01-01", + }, + ], + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + assert result["step_id"] == "gateway_select" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={GATEWAY_SELECT_KEY: GATEWAY_MANUAL_ENTRY} + ) + + assert result2["type"] == "form" + assert result2["errors"] == {} + assert result2["step_id"] == "gateway_entry" + + with patch( + "homeassistant.components.screenlogic.config_flow.login.create_socket", + return_value=True, + ), patch( + "homeassistant.components.screenlogic.config_flow.login.gateway_connect", + return_value="00-C0-33-01-01-01", + ): + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_PORT: 80, + }, + ) + await hass.async_block_till_done() + + assert result3["type"] == "create_entry" + assert result3["title"] == "Pentair: 01-01-01" + assert result3["data"] == { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_PORT: 80, + } + + +async def test_form_cannot_connect(hass): + """Test we handle cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.screenlogic.config_flow.login.create_socket", + return_value=None, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_PORT: 80, + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {CONF_IP_ADDRESS: "cannot_connect"} + + +async def test_option_flow(hass): + """Test config flow options.""" + entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) + entry.add_to_hass(hass) + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == "form" + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_SCAN_INTERVAL: 15}, + ) + assert result["type"] == "create_entry" + assert result["data"] == {CONF_SCAN_INTERVAL: 15} + + +async def test_option_flow_defaults(hass): + """Test config flow options.""" + entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) + entry.add_to_hass(hass) + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == "form" + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={} + ) + assert result["type"] == "create_entry" + assert result["data"] == { + CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL, + } + + +async def test_option_flow_input_floor(hass): + """Test config flow options.""" + entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) + entry.add_to_hass(hass) + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == "form" + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={CONF_SCAN_INTERVAL: 1} + ) + assert result["type"] == "create_entry" + assert result["data"] == { + CONF_SCAN_INTERVAL: MIN_SCAN_INTERVAL, + } From 529b23d8afe6cd79952232de5d858c249256cb1a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 16 Mar 2021 23:09:35 +0000 Subject: [PATCH 416/831] Bump frontend to 20210316.0 --- 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 9d6bf462a48..ed97d0718e7 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20210314.0" + "home-assistant-frontend==20210316.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 8bb7d1cbad5..a8ad4ff85f9 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.42.0 -home-assistant-frontend==20210314.0 +home-assistant-frontend==20210316.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index a3788d7493a..e685876c09d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210314.0 +home-assistant-frontend==20210316.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 216a7e0c0b2..00e6121b485 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210314.0 +home-assistant-frontend==20210316.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 4306c8fbb44de71f3870a3bb4327d9f486a8a18d Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Wed, 17 Mar 2021 00:03:55 +0000 Subject: [PATCH 417/831] [ci skip] Translation update --- .../components/adguard/translations/ru.json | 2 +- .../components/aemet/translations/de.json | 7 ++- .../components/airly/translations/nl.json | 4 +- .../components/almond/translations/no.json | 2 +- .../ambiclimate/translations/nl.json | 2 +- .../components/asuswrt/translations/de.json | 19 ++++++- .../components/asuswrt/translations/ru.json | 2 +- .../components/august/translations/en.json | 10 ++++ .../components/august/translations/ru.json | 2 +- .../components/awair/translations/nl.json | 3 +- .../components/axis/translations/nl.json | 4 +- .../components/axis/translations/ru.json | 2 +- .../components/blink/translations/it.json | 2 +- .../components/blink/translations/ru.json | 2 +- .../bmw_connected_drive/translations/ru.json | 2 +- .../components/bond/translations/de.json | 5 ++ .../components/bsblan/translations/ru.json | 2 +- .../components/canary/translations/ru.json | 2 +- .../components/cast/translations/pt.json | 3 ++ .../cert_expiry/translations/nl.json | 4 +- .../components/climacell/translations/de.json | 16 +++++- .../cloudflare/translations/nl.json | 1 + .../components/control4/translations/de.json | 10 ++++ .../components/control4/translations/ru.json | 2 +- .../components/deconz/translations/nl.json | 4 +- .../components/denonavr/translations/nl.json | 11 +++- .../components/dexcom/translations/de.json | 1 + .../components/dexcom/translations/ru.json | 2 +- .../components/doorbird/translations/ru.json | 2 +- .../components/elkm1/translations/ru.json | 2 +- .../components/enocean/translations/nl.json | 17 ++++++ .../fireservicerota/translations/ru.json | 2 +- .../flick_electric/translations/ru.json | 2 +- .../components/flo/translations/ru.json | 2 +- .../components/flume/translations/ru.json | 2 +- .../forked_daapd/translations/nl.json | 1 + .../components/foscam/translations/de.json | 1 + .../components/foscam/translations/ru.json | 2 +- .../components/fritzbox/translations/ru.json | 6 +-- .../fritzbox_callmonitor/translations/ru.json | 2 +- .../garmin_connect/translations/ru.json | 2 +- .../components/glances/translations/nl.json | 8 +-- .../components/glances/translations/ru.json | 2 +- .../components/gogogate2/translations/ru.json | 2 +- .../components/hangouts/translations/it.json | 2 +- .../components/hassio/translations/it.json | 4 +- .../components/hive/translations/de.json | 42 +++++++++++++-- .../components/hive/translations/it.json | 53 +++++++++++++++++++ .../components/hive/translations/nl.json | 49 +++++++++++++++++ .../components/hive/translations/no.json | 53 +++++++++++++++++++ .../components/hive/translations/pl.json | 53 +++++++++++++++++++ .../components/hive/translations/pt.json | 3 ++ .../components/hive/translations/ru.json | 4 +- .../components/hlk_sw16/translations/ru.json | 2 +- .../homeassistant/translations/it.json | 4 +- .../components/homekit/translations/nl.json | 1 + .../homekit_controller/translations/nl.json | 4 +- .../huawei_lte/translations/nl.json | 3 +- .../huawei_lte/translations/ru.json | 6 +-- .../components/hue/translations/nl.json | 2 +- .../huisbaasje/translations/ru.json | 2 +- .../humidifier/translations/de.json | 7 +++ .../humidifier/translations/nl.json | 4 +- .../hvv_departures/translations/ru.json | 2 +- .../components/iaqualink/translations/nl.json | 2 +- .../components/iaqualink/translations/ru.json | 4 +- .../components/insteon/translations/ru.json | 4 +- .../components/isy994/translations/ru.json | 2 +- .../components/izone/translations/nl.json | 4 +- .../keenetic_ndms2/translations/de.json | 9 ++++ .../keenetic_ndms2/translations/ru.json | 2 +- .../components/kmtronic/translations/ru.json | 2 +- .../components/kodi/translations/ru.json | 2 +- .../components/life360/translations/ru.json | 4 +- .../components/litejet/translations/de.json | 7 ++- .../litterrobot/translations/pt.json | 3 ++ .../litterrobot/translations/ru.json | 2 +- .../components/mikrotik/translations/ru.json | 2 +- .../components/mill/translations/ru.json | 2 +- .../components/mqtt/translations/ru.json | 4 +- .../components/mullvad/translations/de.json | 3 +- .../components/mullvad/translations/ru.json | 2 +- .../components/myq/translations/ru.json | 2 +- .../components/mysensors/translations/de.json | 40 +++++++++++++- .../components/neato/translations/ru.json | 2 +- .../components/nest/translations/it.json | 2 +- .../components/netatmo/translations/de.json | 1 + .../components/nexia/translations/ru.json | 2 +- .../components/notion/translations/nl.json | 2 +- .../components/notion/translations/ru.json | 2 +- .../components/nuheat/translations/ru.json | 2 +- .../components/number/translations/de.json | 5 ++ .../components/nut/translations/pt.json | 6 +++ .../components/nut/translations/ru.json | 2 +- .../components/nzbget/translations/ru.json | 2 +- .../components/omnilogic/translations/ru.json | 2 +- .../components/onvif/translations/ru.json | 2 +- .../ovo_energy/translations/ru.json | 2 +- .../philips_js/translations/ca.json | 7 +++ .../philips_js/translations/de.json | 10 ++++ .../philips_js/translations/en.json | 7 +++ .../philips_js/translations/ko.json | 5 ++ .../philips_js/translations/pt.json | 14 +++++ .../components/plaato/translations/de.json | 28 ++++++++++ .../components/plaato/translations/nl.json | 4 +- .../components/plex/translations/nl.json | 8 +-- .../components/plugwise/translations/ru.json | 2 +- .../components/rfxtrx/translations/nl.json | 13 +++-- .../components/ring/translations/ru.json | 2 +- .../components/risco/translations/ru.json | 2 +- .../ruckus_unleashed/translations/ru.json | 2 +- .../components/sensor/translations/de.json | 4 ++ .../components/sharkiq/translations/ru.json | 4 +- .../components/shelly/translations/ru.json | 2 +- .../smart_meter_texas/translations/ru.json | 2 +- .../components/smarttub/translations/de.json | 4 +- .../components/solaredge/translations/nl.json | 2 +- .../components/solarlog/translations/nl.json | 4 +- .../components/somfy/translations/nl.json | 4 +- .../components/spider/translations/ru.json | 2 +- .../squeezebox/translations/nl.json | 4 +- .../squeezebox/translations/ru.json | 2 +- .../srp_energy/translations/ru.json | 2 +- .../components/starline/translations/ru.json | 4 +- .../components/subaru/translations/de.json | 7 ++- .../components/subaru/translations/it.json | 4 +- .../components/subaru/translations/pt.json | 21 ++++++++ .../components/subaru/translations/ru.json | 2 +- .../components/syncthru/translations/nl.json | 1 + .../synology_dsm/translations/ru.json | 4 +- .../components/tado/translations/ru.json | 2 +- .../components/tasmota/translations/nl.json | 3 ++ .../components/toon/translations/nl.json | 12 +++++ .../totalconnect/translations/de.json | 1 + .../totalconnect/translations/pt.json | 11 +++- .../totalconnect/translations/ru.json | 2 +- .../components/tradfri/translations/nl.json | 2 +- .../transmission/translations/nl.json | 6 ++- .../transmission/translations/ru.json | 2 +- .../components/tuya/translations/nl.json | 3 ++ .../components/tuya/translations/ru.json | 2 +- .../components/unifi/translations/ru.json | 2 +- .../components/upcloud/translations/nl.json | 9 ++++ .../components/upcloud/translations/ru.json | 2 +- .../components/verisure/translations/ca.json | 12 ++++- .../components/verisure/translations/de.json | 44 +++++++++++++++ .../components/verisure/translations/et.json | 10 +++- .../components/verisure/translations/it.json | 47 ++++++++++++++++ .../components/verisure/translations/ko.json | 47 ++++++++++++++++ .../components/verisure/translations/nl.json | 46 ++++++++++++++++ .../components/verisure/translations/no.json | 47 ++++++++++++++++ .../components/verisure/translations/pl.json | 47 ++++++++++++++++ .../components/verisure/translations/pt.json | 17 +++++- .../components/verisure/translations/ru.json | 47 ++++++++++++++++ .../verisure/translations/zh-Hant.json | 47 ++++++++++++++++ .../components/vesync/translations/nl.json | 2 +- .../water_heater/translations/de.json | 11 ++++ .../water_heater/translations/it.json | 11 ++++ .../water_heater/translations/nl.json | 10 ++++ .../water_heater/translations/no.json | 11 ++++ .../water_heater/translations/pl.json | 11 ++++ .../water_heater/translations/pt.json | 5 ++ .../water_heater/translations/zh-Hant.json | 11 ++++ .../components/wemo/translations/nl.json | 4 +- .../components/withings/translations/nl.json | 1 + .../components/wolflink/translations/ru.json | 2 +- .../xiaomi_aqara/translations/de.json | 6 +++ .../xiaomi_aqara/translations/nl.json | 4 +- .../zoneminder/translations/ru.json | 6 +-- .../components/zwave_js/translations/de.json | 26 ++++++++- 170 files changed, 1233 insertions(+), 160 deletions(-) create mode 100644 homeassistant/components/hive/translations/it.json create mode 100644 homeassistant/components/hive/translations/nl.json create mode 100644 homeassistant/components/hive/translations/no.json create mode 100644 homeassistant/components/hive/translations/pl.json create mode 100644 homeassistant/components/philips_js/translations/pt.json create mode 100644 homeassistant/components/subaru/translations/pt.json create mode 100644 homeassistant/components/verisure/translations/de.json create mode 100644 homeassistant/components/verisure/translations/it.json create mode 100644 homeassistant/components/verisure/translations/ko.json create mode 100644 homeassistant/components/verisure/translations/nl.json create mode 100644 homeassistant/components/verisure/translations/no.json create mode 100644 homeassistant/components/verisure/translations/pl.json create mode 100644 homeassistant/components/verisure/translations/ru.json create mode 100644 homeassistant/components/verisure/translations/zh-Hant.json diff --git a/homeassistant/components/adguard/translations/ru.json b/homeassistant/components/adguard/translations/ru.json index 5e8483047f8..97dc6505c3b 100644 --- a/homeassistant/components/adguard/translations/ru.json +++ b/homeassistant/components/adguard/translations/ru.json @@ -18,7 +18,7 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", - "username": "\u041b\u043e\u0433\u0438\u043d", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f AdGuard Home." diff --git a/homeassistant/components/aemet/translations/de.json b/homeassistant/components/aemet/translations/de.json index d7254aea92f..d5312805722 100644 --- a/homeassistant/components/aemet/translations/de.json +++ b/homeassistant/components/aemet/translations/de.json @@ -11,8 +11,11 @@ "data": { "api_key": "API-Schl\u00fcssel", "latitude": "Breitengrad", - "longitude": "L\u00e4ngengrad" - } + "longitude": "L\u00e4ngengrad", + "name": "Name der Integration" + }, + "description": "Richte die AEMET OpenData Integration ein. Um den API-Schl\u00fcssel zu generieren, besuche https://opendata.aemet.es/centrodedescargas/altaUsuario", + "title": "[void]" } } } diff --git a/homeassistant/components/airly/translations/nl.json b/homeassistant/components/airly/translations/nl.json index 5ea975dfaef..e89f769cd83 100644 --- a/homeassistant/components/airly/translations/nl.json +++ b/homeassistant/components/airly/translations/nl.json @@ -10,10 +10,10 @@ "step": { "user": { "data": { - "api_key": "Airly API-sleutel", + "api_key": "API-sleutel", "latitude": "Breedtegraad", "longitude": "Lengtegraad", - "name": "Naam van de integratie" + "name": "Naam" }, "description": "Airly-integratie van luchtkwaliteit instellen. Ga naar https://developer.airly.eu/register om de API-sleutel te genereren", "title": "Airly" diff --git a/homeassistant/components/almond/translations/no.json b/homeassistant/components/almond/translations/no.json index e6ca8f16589..84a57a42ff7 100644 --- a/homeassistant/components/almond/translations/no.json +++ b/homeassistant/components/almond/translations/no.json @@ -8,7 +8,7 @@ }, "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant for \u00e5 koble til Mandel levert av Hass.io-tillegget: {addon} ?", + "description": "Vil du konfigurere Home Assistant til \u00e5 koble til Almond levert av Supervisor-tillegg: {addon}?", "title": "Almond via Hass.io-tillegg" }, "pick_implementation": { diff --git a/homeassistant/components/ambiclimate/translations/nl.json b/homeassistant/components/ambiclimate/translations/nl.json index 1d7652a370e..4e6c5ebb202 100644 --- a/homeassistant/components/ambiclimate/translations/nl.json +++ b/homeassistant/components/ambiclimate/translations/nl.json @@ -6,7 +6,7 @@ "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen." }, "create_entry": { - "default": "Succesvol geverifieerd met Ambiclimate" + "default": "Succesvol geauthenticeerd" }, "error": { "follow_link": "Gelieve de link te volgen en te verifi\u00ebren voordat u op Verzenden drukt.", diff --git a/homeassistant/components/asuswrt/translations/de.json b/homeassistant/components/asuswrt/translations/de.json index 433bf17b814..36699d95753 100644 --- a/homeassistant/components/asuswrt/translations/de.json +++ b/homeassistant/components/asuswrt/translations/de.json @@ -6,6 +6,9 @@ "error": { "cannot_connect": "Verbindung fehlgeschlagen", "invalid_host": "Ung\u00fcltiger Hostname oder IP-Adresse", + "pwd_and_ssh": "Nur Passwort oder SSH-Schl\u00fcsseldatei angeben", + "pwd_or_ssh": "Bitte Passwort oder SSH-Schl\u00fcsseldatei angeben", + "ssh_not_file": "SSH-Schl\u00fcsseldatei nicht gefunden", "unknown": "Unerwarteter Fehler" }, "step": { @@ -16,8 +19,22 @@ "name": "Name", "password": "Passwort", "port": "Port", + "protocol": "Zu verwendendes Kommunikationsprotokoll", + "ssh_key": "Pfad zu deiner SSH-Schl\u00fcsseldatei (anstelle des Passworts)", "username": "Benutzername" - } + }, + "title": "" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "interface": "Schnittstelle, von der du Statistiken haben m\u00f6chtest (z.B. eth0, eth1 usw.)", + "require_ip": "Ger\u00e4te m\u00fcssen IP haben (f\u00fcr Zugangspunkt-Modus)" + }, + "title": "AsusWRT Optionen" } } } diff --git a/homeassistant/components/asuswrt/translations/ru.json b/homeassistant/components/asuswrt/translations/ru.json index 236f7642c12..a2090b1faf6 100644 --- a/homeassistant/components/asuswrt/translations/ru.json +++ b/homeassistant/components/asuswrt/translations/ru.json @@ -21,7 +21,7 @@ "port": "\u041f\u043e\u0440\u0442", "protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0441\u0432\u044f\u0437\u0438", "ssh_key": "\u041f\u0443\u0442\u044c \u0444\u0430\u0439\u043b\u0443 \u043a\u043b\u044e\u0447\u0435\u0439 SSH (\u0432\u043c\u0435\u0441\u0442\u043e \u043f\u0430\u0440\u043e\u043b\u044f)", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0412\u0430\u0448\u0435\u043c\u0443 \u0440\u043e\u0443\u0442\u0435\u0440\u0443.", "title": "AsusWRT" diff --git a/homeassistant/components/august/translations/en.json b/homeassistant/components/august/translations/en.json index f2ceef78d48..0b8d1511244 100644 --- a/homeassistant/components/august/translations/en.json +++ b/homeassistant/components/august/translations/en.json @@ -17,6 +17,16 @@ "description": "Enter the password for {username}.", "title": "Reauthenticate an August account" }, + "user": { + "data": { + "login_method": "Login Method", + "password": "Password", + "timeout": "Timeout (seconds)", + "username": "Username" + }, + "description": "If the Login Method is 'email', Username is the email address. If the Login Method is 'phone', Username is the phone number in the format '+NNNNNNNNN'.", + "title": "Setup an August account" + }, "user_validate": { "data": { "login_method": "Login Method", diff --git a/homeassistant/components/august/translations/ru.json b/homeassistant/components/august/translations/ru.json index 97dba8fc758..277fd6abec2 100644 --- a/homeassistant/components/august/translations/ru.json +++ b/homeassistant/components/august/translations/ru.json @@ -15,7 +15,7 @@ "login_method": "\u0421\u043f\u043e\u0441\u043e\u0431 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "timeout": "\u0422\u0430\u0439\u043c-\u0430\u0443\u0442 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0415\u0441\u043b\u0438 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043e 'email', \u0442\u043e \u043b\u043e\u0433\u0438\u043d\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0430\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b. \u0415\u0441\u043b\u0438 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043e 'phone', \u0442\u043e \u043b\u043e\u0433\u0438\u043d\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 '+NNNNNNNNN'.", "title": "August" diff --git a/homeassistant/components/awair/translations/nl.json b/homeassistant/components/awair/translations/nl.json index 5d20aed2fdb..d41b85cc09b 100644 --- a/homeassistant/components/awair/translations/nl.json +++ b/homeassistant/components/awair/translations/nl.json @@ -21,7 +21,8 @@ "data": { "access_token": "Toegangstoken", "email": "E-mail" - } + }, + "description": "U moet zich registreren voor een Awair-toegangstoken voor ontwikkelaars op: https://developer.getawair.com/onboard/login" } } } diff --git a/homeassistant/components/axis/translations/nl.json b/homeassistant/components/axis/translations/nl.json index 8ba305366f9..3b41c1184ba 100644 --- a/homeassistant/components/axis/translations/nl.json +++ b/homeassistant/components/axis/translations/nl.json @@ -7,11 +7,11 @@ }, "error": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom voor het apparaat is al in volle gang.", + "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie" }, - "flow_title": "Axis apparaat: {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/axis/translations/ru.json b/homeassistant/components/axis/translations/ru.json index 1bf3e369b65..f5c5e79a32f 100644 --- a/homeassistant/components/axis/translations/ru.json +++ b/homeassistant/components/axis/translations/ru.json @@ -18,7 +18,7 @@ "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "Axis" } diff --git a/homeassistant/components/blink/translations/it.json b/homeassistant/components/blink/translations/it.json index bdb0ba3f6b4..6dee5d9c02f 100644 --- a/homeassistant/components/blink/translations/it.json +++ b/homeassistant/components/blink/translations/it.json @@ -14,7 +14,7 @@ "data": { "2fa": "Codice a due fattori" }, - "description": "Inserisci il pin inviato alla tua email", + "description": "Inserisci il PIN inviato alla tua email", "title": "Autenticazione a due fattori" }, "user": { diff --git a/homeassistant/components/blink/translations/ru.json b/homeassistant/components/blink/translations/ru.json index 0835ab5ac0a..fa68ee2dad4 100644 --- a/homeassistant/components/blink/translations/ru.json +++ b/homeassistant/components/blink/translations/ru.json @@ -20,7 +20,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "Blink" } diff --git a/homeassistant/components/bmw_connected_drive/translations/ru.json b/homeassistant/components/bmw_connected_drive/translations/ru.json index 9ac76bbea9e..8ab4e4e1207 100644 --- a/homeassistant/components/bmw_connected_drive/translations/ru.json +++ b/homeassistant/components/bmw_connected_drive/translations/ru.json @@ -12,7 +12,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "region": "\u0420\u0435\u0433\u0438\u043e\u043d ConnectedDrive", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/bond/translations/de.json b/homeassistant/components/bond/translations/de.json index 14f86a30bb2..1c1c7375a28 100644 --- a/homeassistant/components/bond/translations/de.json +++ b/homeassistant/components/bond/translations/de.json @@ -9,6 +9,11 @@ "unknown": "Unerwarteter Fehler" }, "step": { + "confirm": { + "data": { + "access_token": "Zugangstoken" + } + }, "user": { "data": { "access_token": "Zugriffstoken", diff --git a/homeassistant/components/bsblan/translations/ru.json b/homeassistant/components/bsblan/translations/ru.json index 76aa715a9de..8291a20d307 100644 --- a/homeassistant/components/bsblan/translations/ru.json +++ b/homeassistant/components/bsblan/translations/ru.json @@ -14,7 +14,7 @@ "passkey": "\u041f\u0430\u0440\u043e\u043b\u044c", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 BSB-Lan.", "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" diff --git a/homeassistant/components/canary/translations/ru.json b/homeassistant/components/canary/translations/ru.json index 146863cf768..51052d0d68d 100644 --- a/homeassistant/components/canary/translations/ru.json +++ b/homeassistant/components/canary/translations/ru.json @@ -12,7 +12,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a Canary" } diff --git a/homeassistant/components/cast/translations/pt.json b/homeassistant/components/cast/translations/pt.json index 32da71ec389..2a5b62a9de1 100644 --- a/homeassistant/components/cast/translations/pt.json +++ b/homeassistant/components/cast/translations/pt.json @@ -5,6 +5,9 @@ "single_instance_allowed": "Apenas uma \u00fanica configura\u00e7\u00e3o do Google Cast \u00e9 necess\u00e1ria." }, "step": { + "config": { + "title": "Google Cast" + }, "confirm": { "description": "Deseja configurar o Google Cast?" } diff --git a/homeassistant/components/cert_expiry/translations/nl.json b/homeassistant/components/cert_expiry/translations/nl.json index b29cdcaad71..e3cd3d7983b 100644 --- a/homeassistant/components/cert_expiry/translations/nl.json +++ b/homeassistant/components/cert_expiry/translations/nl.json @@ -12,9 +12,9 @@ "step": { "user": { "data": { - "host": "De hostnaam van het certificaat", + "host": "Host", "name": "De naam van het certificaat", - "port": "De poort van het certificaat" + "port": "Poort" }, "title": "Het certificaat defini\u00ebren dat moet worden getest" } diff --git a/homeassistant/components/climacell/translations/de.json b/homeassistant/components/climacell/translations/de.json index a91e222b3cd..7ec41d01733 100644 --- a/homeassistant/components/climacell/translations/de.json +++ b/homeassistant/components/climacell/translations/de.json @@ -3,6 +3,7 @@ "error": { "cannot_connect": "Verbindung fehlgeschlagen", "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel", + "rate_limited": "Aktuelle Aktualisierungsrate gedrosselt, bitte versuche es sp\u00e4ter erneut.", "unknown": "Unerwarteter Fehler" }, "step": { @@ -12,7 +13,20 @@ "latitude": "Breitengrad", "longitude": "L\u00e4ngengrad", "name": "Name" - } + }, + "description": "Wenn Breitengrad und L\u00e4ngengrad nicht angegeben werden, werden die Standardwerte in der Home Assistant-Konfiguration verwendet. F\u00fcr jeden Vorhersagetyp wird eine Entit\u00e4t erstellt, aber nur die von Ihnen ausgew\u00e4hlten werden standardm\u00e4\u00dfig aktiviert." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Vorhersage Arten", + "timestep": "Minuten zwischen den Kurzvorhersagen" + }, + "description": "Wenn du die Vorhersage-Entitit\u00e4t \"Kurzvorhersage\" aktivierst, kannst du die Anzahl der Minuten zwischen den einzelnen Vorhersagen konfigurieren. Die Anzahl der bereitgestellten Vorhersagen h\u00e4ngt von der Anzahl der zwischen den Vorhersagen gew\u00e4hlten Minuten ab.", + "title": "Aktualisiere ClimaCell-Optionen" } } }, diff --git a/homeassistant/components/cloudflare/translations/nl.json b/homeassistant/components/cloudflare/translations/nl.json index e4f6c1180e2..94697419ff1 100644 --- a/homeassistant/components/cloudflare/translations/nl.json +++ b/homeassistant/components/cloudflare/translations/nl.json @@ -21,6 +21,7 @@ "data": { "api_token": "API-token" }, + "description": "Voor deze integratie is een API-token vereist dat is gemaakt met Zone:Zone:Lezen en Zone:DNS:Bewerk machtigingen voor alle zones in uw account.", "title": "Verbinden met Cloudflare" }, "zone": { diff --git a/homeassistant/components/control4/translations/de.json b/homeassistant/components/control4/translations/de.json index 399b8d42491..e50e2499320 100644 --- a/homeassistant/components/control4/translations/de.json +++ b/homeassistant/components/control4/translations/de.json @@ -14,6 +14,16 @@ "host": "IP-Addresse", "password": "Passwort", "username": "Benutzername" + }, + "description": "Bitte gib deine Control4-Kontodaten und die IP-Adresse deiner lokalen Steuerung ein." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Sekunden zwischen Updates" } } } diff --git a/homeassistant/components/control4/translations/ru.json b/homeassistant/components/control4/translations/ru.json index 3882f03cb32..41a033c0376 100644 --- a/homeassistant/components/control4/translations/ru.json +++ b/homeassistant/components/control4/translations/ru.json @@ -13,7 +13,7 @@ "data": { "host": "IP-\u0430\u0434\u0440\u0435\u0441", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Control4 \u0438 IP-\u0430\u0434\u0440\u0435\u0441 \u0412\u0430\u0448\u0435\u0433\u043e \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430." } diff --git a/homeassistant/components/deconz/translations/nl.json b/homeassistant/components/deconz/translations/nl.json index 37833352f91..833050eaf92 100644 --- a/homeassistant/components/deconz/translations/nl.json +++ b/homeassistant/components/deconz/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Bridge is al geconfigureerd", - "already_in_progress": "Configuratiestroom voor bridge wordt al ingesteld.", + "already_in_progress": "De configuratiestroom is al aan de gang", "no_bridges": "Geen deCONZ apparaten ontdekt", "no_hardware_available": "Geen radiohardware aangesloten op deCONZ", "not_deconz_bridge": "Dit is geen deCONZ bridge", @@ -14,7 +14,7 @@ "flow_title": "deCONZ Zigbee gateway ( {host} )", "step": { "hassio_confirm": { - "description": "Wilt u de Home Assistant configureren om verbinding te maken met de deCONZ gateway van de Supervisor add-on {addon}?", + "description": "Wilt u Home Assistant configureren om verbinding te maken met de deCONZ gateway van de Supervisor add-on {addon}?", "title": "deCONZ Zigbee Gateway via Supervisor add-on" }, "link": { diff --git a/homeassistant/components/denonavr/translations/nl.json b/homeassistant/components/denonavr/translations/nl.json index d96e81f2322..a109df0f125 100644 --- a/homeassistant/components/denonavr/translations/nl.json +++ b/homeassistant/components/denonavr/translations/nl.json @@ -3,11 +3,16 @@ "abort": { "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang", - "not_denonavr_manufacturer": "Geen Denon AVR Netwerk Receiver, ontdekte fabrikant komt niet overeen" + "not_denonavr_manufacturer": "Geen Denon AVR Netwerk Receiver, ontdekte fabrikant komt niet overeen", + "not_denonavr_missing": "Geen Denon AVR netwerkontvanger, zoekinformatie niet compleet" + }, + "error": { + "discovery_error": "Kan een Denon AVR netwerkontvanger niet vinden" }, "flow_title": "Denon AVR Network Receiver: {name}", "step": { "confirm": { + "description": "Bevestig het toevoegen van de ontvanger", "title": "Denon AVR Network Receivers" }, "select": { @@ -21,7 +26,8 @@ "data": { "host": "IP-adres" }, - "description": "Maak verbinding met uw ontvanger. Als het IP-adres niet is ingesteld, wordt automatische detectie gebruikt" + "description": "Maak verbinding met uw ontvanger. Als het IP-adres niet is ingesteld, wordt automatische detectie gebruikt", + "title": "Denon AVR Netwerk Ontvangers" } } }, @@ -33,6 +39,7 @@ "zone2": "Stel Zone 2 in", "zone3": "Stel Zone 3 in" }, + "description": "Optionele instellingen opgeven", "title": "Denon AVR Network Receivers" } } diff --git a/homeassistant/components/dexcom/translations/de.json b/homeassistant/components/dexcom/translations/de.json index d567dd6b611..64cdc05a081 100644 --- a/homeassistant/components/dexcom/translations/de.json +++ b/homeassistant/components/dexcom/translations/de.json @@ -12,6 +12,7 @@ "user": { "data": { "password": "Passwort", + "server": "Server", "username": "Benutzername" } } diff --git a/homeassistant/components/dexcom/translations/ru.json b/homeassistant/components/dexcom/translations/ru.json index aa90d6d998d..08543cf103e 100644 --- a/homeassistant/components/dexcom/translations/ru.json +++ b/homeassistant/components/dexcom/translations/ru.json @@ -13,7 +13,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "server": "\u0421\u0435\u0440\u0432\u0435\u0440", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0412\u0430\u0448\u0438 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.", "title": "Dexcom" diff --git a/homeassistant/components/doorbird/translations/ru.json b/homeassistant/components/doorbird/translations/ru.json index 5e376ee56d3..4d5695a3ab2 100644 --- a/homeassistant/components/doorbird/translations/ru.json +++ b/homeassistant/components/doorbird/translations/ru.json @@ -17,7 +17,7 @@ "host": "\u0425\u043e\u0441\u0442", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a DoorBird" } diff --git a/homeassistant/components/elkm1/translations/ru.json b/homeassistant/components/elkm1/translations/ru.json index 48a950f1cca..954722ecf52 100644 --- a/homeassistant/components/elkm1/translations/ru.json +++ b/homeassistant/components/elkm1/translations/ru.json @@ -17,7 +17,7 @@ "prefix": "\u0423\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u0440\u0435\u0444\u0438\u043a\u0441 (\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0435\u0441\u043b\u0438 \u0443 \u0412\u0430\u0441 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d ElkM1)", "protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b", "temperature_unit": "\u0415\u0434\u0438\u043d\u0438\u0446\u0430 \u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0421\u0442\u0440\u043e\u043a\u0430 \u0430\u0434\u0440\u0435\u0441\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 'addres[:port]' \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u043e\u0432 'secure' \u0438 'non-secure' (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: '192.168.1.1'). \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 'port' \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e, \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u043e\u043d \u0440\u0430\u0432\u0435\u043d 2101 \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430 'non-secure' \u0438 2601 \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430 'secure'. \u0414\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430 'serial' \u0430\u0434\u0440\u0435\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 'tty[:baud]' (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: '/dev/ttyS1'). \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 'baud' \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e, \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u043e\u043d \u0440\u0430\u0432\u0435\u043d 115200.", "title": "Elk-M1 Control" diff --git a/homeassistant/components/enocean/translations/nl.json b/homeassistant/components/enocean/translations/nl.json index 79aaec23123..27e4091552a 100644 --- a/homeassistant/components/enocean/translations/nl.json +++ b/homeassistant/components/enocean/translations/nl.json @@ -1,7 +1,24 @@ { "config": { "abort": { + "invalid_dongle_path": "Ongeldig dongle-pad", "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, + "error": { + "invalid_dongle_path": "Geen geldige dongle gevonden voor dit pad" + }, + "step": { + "detect": { + "data": { + "path": "USB dongle pad" + }, + "title": "Selecteer het pad naar uw ENOcean-dongle" + }, + "manual": { + "data": { + "path": "USB-dongle-pad" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/fireservicerota/translations/ru.json b/homeassistant/components/fireservicerota/translations/ru.json index 3955172e02d..046a65081ec 100644 --- a/homeassistant/components/fireservicerota/translations/ru.json +++ b/homeassistant/components/fireservicerota/translations/ru.json @@ -21,7 +21,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "url": "\u0412\u0435\u0431-\u0441\u0430\u0439\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/flick_electric/translations/ru.json b/homeassistant/components/flick_electric/translations/ru.json index bcabe2f2157..08bfc3ffb02 100644 --- a/homeassistant/components/flick_electric/translations/ru.json +++ b/homeassistant/components/flick_electric/translations/ru.json @@ -14,7 +14,7 @@ "client_id": "ID \u043a\u043b\u0438\u0435\u043d\u0442\u0430 (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", "client_secret": "\u0421\u0435\u043a\u0440\u0435\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "Flick Electric" } diff --git a/homeassistant/components/flo/translations/ru.json b/homeassistant/components/flo/translations/ru.json index 9e0db9fcf94..9b02cafd466 100644 --- a/homeassistant/components/flo/translations/ru.json +++ b/homeassistant/components/flo/translations/ru.json @@ -13,7 +13,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/flume/translations/ru.json b/homeassistant/components/flume/translations/ru.json index e4be913abcd..757ec6e5226 100644 --- a/homeassistant/components/flume/translations/ru.json +++ b/homeassistant/components/flume/translations/ru.json @@ -14,7 +14,7 @@ "client_id": "ID \u043a\u043b\u0438\u0435\u043d\u0442\u0430", "client_secret": "\u0421\u0435\u043a\u0440\u0435\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0430", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u043c\u0443 API Flume, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c 'ID \u043a\u043b\u0438\u0435\u043d\u0442\u0430' \u0438 '\u0421\u0435\u043a\u0440\u0435\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0430' \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 https://portal.flumetech.com/settings#token.", "title": "Flume" diff --git a/homeassistant/components/forked_daapd/translations/nl.json b/homeassistant/components/forked_daapd/translations/nl.json index 903a716d16e..7eec6a34571 100644 --- a/homeassistant/components/forked_daapd/translations/nl.json +++ b/homeassistant/components/forked_daapd/translations/nl.json @@ -5,6 +5,7 @@ "not_forked_daapd": "Apparaat is geen forked-daapd-server." }, "error": { + "forbidden": "Niet in staat te verbinden. Controleer alstublieft uw forked-daapd netwerkrechten.", "unknown_error": "Onverwachte fout", "websocket_not_enabled": "forked-daapd server websocket niet ingeschakeld.", "wrong_host_or_port": "Verbinding mislukt, controleer het host-adres en poort.", diff --git a/homeassistant/components/foscam/translations/de.json b/homeassistant/components/foscam/translations/de.json index 603be1847cc..e3011f61615 100644 --- a/homeassistant/components/foscam/translations/de.json +++ b/homeassistant/components/foscam/translations/de.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", + "invalid_response": "Ung\u00fcltige Antwort vom Ger\u00e4t", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/foscam/translations/ru.json b/homeassistant/components/foscam/translations/ru.json index f78f64af69a..8e8404c501e 100644 --- a/homeassistant/components/foscam/translations/ru.json +++ b/homeassistant/components/foscam/translations/ru.json @@ -17,7 +17,7 @@ "port": "\u041f\u043e\u0440\u0442", "rtsp_port": "\u041f\u043e\u0440\u0442 RTSP", "stream": "\u041f\u043e\u0442\u043e\u043a", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/fritzbox/translations/ru.json b/homeassistant/components/fritzbox/translations/ru.json index 8cd77671bd8..adbdfa13d6b 100644 --- a/homeassistant/components/fritzbox/translations/ru.json +++ b/homeassistant/components/fritzbox/translations/ru.json @@ -15,14 +15,14 @@ "confirm": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?" }, "reauth_confirm": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0434\u043b\u044f {name}." }, @@ -30,7 +30,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 AVM FRITZ!Box." } diff --git a/homeassistant/components/fritzbox_callmonitor/translations/ru.json b/homeassistant/components/fritzbox_callmonitor/translations/ru.json index f1bcb18a2f6..38448ac8c59 100644 --- a/homeassistant/components/fritzbox_callmonitor/translations/ru.json +++ b/homeassistant/components/fritzbox_callmonitor/translations/ru.json @@ -20,7 +20,7 @@ "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/garmin_connect/translations/ru.json b/homeassistant/components/garmin_connect/translations/ru.json index 49dd5c5b3bc..066c337309f 100644 --- a/homeassistant/components/garmin_connect/translations/ru.json +++ b/homeassistant/components/garmin_connect/translations/ru.json @@ -13,7 +13,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0412\u0430\u0448\u0438 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.", "title": "Garmin Connect" diff --git a/homeassistant/components/glances/translations/nl.json b/homeassistant/components/glances/translations/nl.json index c2f2b9d473a..6cb7fb445bb 100644 --- a/homeassistant/components/glances/translations/nl.json +++ b/homeassistant/components/glances/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Host is al geconfigureerd." + "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Kan geen verbinding maken met host", + "cannot_connect": "Kan geen verbinding maken", "wrong_version": "Versie niet ondersteund (alleen 2 of 3)" }, "step": { @@ -14,9 +14,9 @@ "name": "Naam", "password": "Wachtwoord", "port": "Poort", - "ssl": "Gebruik SSL / TLS om verbinding te maken met het Glances-systeem", + "ssl": "Gebruik een SSL-certificaat", "username": "Gebruikersnaam", - "verify_ssl": "Controleer de certificering van het systeem", + "verify_ssl": "SSL-certificaat verifi\u00ebren", "version": "Glances API-versie (2 of 3)" }, "title": "Glances instellen" diff --git a/homeassistant/components/glances/translations/ru.json b/homeassistant/components/glances/translations/ru.json index 0dc8c72dc9f..aecffe204c8 100644 --- a/homeassistant/components/glances/translations/ru.json +++ b/homeassistant/components/glances/translations/ru.json @@ -15,7 +15,7 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", - "username": "\u041b\u043e\u0433\u0438\u043d", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", "version": "\u0412\u0435\u0440\u0441\u0438\u044f API Glances (2 \u0438\u043b\u0438 3)" }, diff --git a/homeassistant/components/gogogate2/translations/ru.json b/homeassistant/components/gogogate2/translations/ru.json index 43e9f7a1b2f..4efa554fc91 100644 --- a/homeassistant/components/gogogate2/translations/ru.json +++ b/homeassistant/components/gogogate2/translations/ru.json @@ -12,7 +12,7 @@ "data": { "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 GogoGate2.", "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 GogoGate2 \u0438\u043b\u0438 iSmartGate" diff --git a/homeassistant/components/hangouts/translations/it.json b/homeassistant/components/hangouts/translations/it.json index 4831d51ef12..3e89327ca30 100644 --- a/homeassistant/components/hangouts/translations/it.json +++ b/homeassistant/components/hangouts/translations/it.json @@ -12,7 +12,7 @@ "step": { "2fa": { "data": { - "2fa": "2FA Pin" + "2fa": "2FA PIN" }, "description": "Vuoto", "title": "Autenticazione a due fattori" diff --git a/homeassistant/components/hassio/translations/it.json b/homeassistant/components/hassio/translations/it.json index 7a375d3d78d..86d573cba40 100644 --- a/homeassistant/components/hassio/translations/it.json +++ b/homeassistant/components/hassio/translations/it.json @@ -8,8 +8,8 @@ "healthy": "Integrit\u00e0", "host_os": "Sistema Operativo Host", "installed_addons": "Componenti aggiuntivi installati", - "supervisor_api": "API Supervisore", - "supervisor_version": "Versione Supervisore", + "supervisor_api": "API Supervisor", + "supervisor_version": "Versione Supervisor", "supported": "Supportato", "update_channel": "Canale di aggiornamento", "version_api": "Versione API" diff --git a/homeassistant/components/hive/translations/de.json b/homeassistant/components/hive/translations/de.json index 1bd84f7f61c..bd5876bb023 100644 --- a/homeassistant/components/hive/translations/de.json +++ b/homeassistant/components/hive/translations/de.json @@ -1,16 +1,52 @@ { "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich", + "unknown_entry": "Vorhandener Eintrag kann nicht gefunden werden." + }, + "error": { + "invalid_code": "Anmeldung bei Hive fehlgeschlagen. Dein Zwei-Faktor-Authentifizierungscode war falsch.", + "invalid_password": "Anmeldung bei Hive fehlgeschlagen. Falsches Passwort, bitte versuche es erneut.", + "invalid_username": "Die Anmeldung bei Hive ist fehlgeschlagen. Deine E-Mail-Adresse wird nicht erkannt.", + "no_internet_available": "F\u00fcr die Verbindung mit Hive ist eine Internetverbindung erforderlich.", + "unknown": "Unerwarteter Fehler" + }, "step": { + "2fa": { + "data": { + "2fa": "Zwei-Faktor Authentifizierungscode" + }, + "description": "Gib deinen Hive-Authentifizierungscode ein. \n \nBitte gib den Code 0000 ein, um einen anderen Code anzufordern.", + "title": "Hive Zwei-Faktor-Authentifizierung." + }, "reauth": { "data": { + "password": "Passwort", "username": "Benutzername" - } + }, + "description": "Gebe deine Hive Anmeldeinformationen erneut ein.", + "title": "Hive Anmeldung" }, "user": { "data": { - "password": "PAsswort", + "password": "Passwort", + "scan_interval": "Scanintervall (Sekunden)", "username": "Benutzername" - } + }, + "description": "Gebe deine Anmeldeinformationen und -konfiguration f\u00fcr Hive ein", + "title": "Hive Anmeldung" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Scanintervall (Sekunden)" + }, + "description": "Aktualisiere den Scanintervall, um Daten \u00f6fters abzufragen.", + "title": "Optionen f\u00fcr Hive" } } } diff --git a/homeassistant/components/hive/translations/it.json b/homeassistant/components/hive/translations/it.json new file mode 100644 index 00000000000..fd79ca35b79 --- /dev/null +++ b/homeassistant/components/hive/translations/it.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente", + "unknown_entry": "Impossibile trovare la voce esistente." + }, + "error": { + "invalid_code": "Impossibile accedere a Hive. Il codice di autenticazione a due fattori non era corretto.", + "invalid_password": "Impossibile accedere a Hive. Password errata, riprova.", + "invalid_username": "Impossibile accedere a Hive. Il tuo indirizzo email non \u00e8 riconosciuto.", + "no_internet_available": "\u00c8 necessaria una connessione Internet per connettersi a Hive.", + "unknown": "Errore imprevisto" + }, + "step": { + "2fa": { + "data": { + "2fa": "Codice a due fattori" + }, + "description": "Inserisci il tuo codice di autenticazione Hive. \n\n Inserisci il codice 0000 per richiedere un altro codice.", + "title": "Autenticazione a due fattori di Hive." + }, + "reauth": { + "data": { + "password": "Password", + "username": "Nome utente" + }, + "description": "Inserisci nuovamente le tue informazioni di accesso a Hive.", + "title": "Accesso Hive" + }, + "user": { + "data": { + "password": "Password", + "scan_interval": "Intervallo di scansione (secondi)", + "username": "Nome utente" + }, + "description": "Immettere le informazioni di accesso e la configurazione di Hive.", + "title": "Accesso Hive" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Intervallo di scansione (secondi)" + }, + "description": "Aggiorna l'intervallo di scansione per eseguire la verifica ciclica dei dati pi\u00f9 spesso.", + "title": "Opzioni per Hive" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/nl.json b/homeassistant/components/hive/translations/nl.json new file mode 100644 index 00000000000..96ea799c0fe --- /dev/null +++ b/homeassistant/components/hive/translations/nl.json @@ -0,0 +1,49 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd", + "unknown_entry": "Kan bestaand item niet vinden." + }, + "error": { + "invalid_code": "Aanmelden bij Hive is mislukt. Uw tweefactorauthenticatiecode was onjuist.", + "invalid_password": "Aanmelden bij Hive is mislukt. Onjuist wachtwoord, probeer het opnieuw.", + "invalid_username": "Aanmelden bij Hive is mislukt. Uw e-mailadres wordt niet herkend.", + "no_internet_available": "Een internetverbinding is vereist om verbinding te maken met Hive.", + "unknown": "Onverwachte fout" + }, + "step": { + "2fa": { + "data": { + "2fa": "Tweefactorauthenticatiecode" + }, + "title": "Hive tweefactorauthenticatie" + }, + "reauth": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + }, + "description": "Voer uw Hive-aanmeldingsgegevens opnieuw in.", + "title": "Hive-aanmelding" + }, + "user": { + "data": { + "password": "Wachtwoord", + "scan_interval": "Scaninterval (seconden)", + "username": "Gebruikersnaam" + }, + "title": "Hive-aanmelding" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Scaninterval (seconden)" + }, + "title": "Opties voor Hive" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/no.json b/homeassistant/components/hive/translations/no.json new file mode 100644 index 00000000000..c5213aafeee --- /dev/null +++ b/homeassistant/components/hive/translations/no.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Kontoen er allerede konfigurert", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket", + "unknown_entry": "Kunne ikke finne eksisterende oppf\u00f8ring." + }, + "error": { + "invalid_code": "Kunne ikke logge p\u00e5 Hive. Tofaktorautentiseringskoden din var feil.", + "invalid_password": "Kunne ikke logge p\u00e5 Hive. Feil passord. Vennligst pr\u00f8v igjen.", + "invalid_username": "Kunne ikke logge p\u00e5 Hive. E-postadressen din blir ikke gjenkjent.", + "no_internet_available": "Det kreves en internettforbindelse for \u00e5 koble til Hive.", + "unknown": "Uventet feil" + }, + "step": { + "2fa": { + "data": { + "2fa": "Totrinnsbekreftelse kode" + }, + "description": "Skriv inn din Hive-godkjenningskode. \n\n Vennligst skriv inn kode 0000 for \u00e5 be om en annen kode.", + "title": "Hive Totrinnsbekreftelse autentisering." + }, + "reauth": { + "data": { + "password": "Passord", + "username": "Brukernavn" + }, + "description": "Skriv innloggingsinformasjonen for Hive p\u00e5 nytt.", + "title": "Hive-p\u00e5logging" + }, + "user": { + "data": { + "password": "Passord", + "scan_interval": "Skanneintervall (sekunder)", + "username": "Brukernavn" + }, + "description": "Skriv inn inn innloggingsinformasjonen og konfigurasjonen for Hive.", + "title": "Hive-p\u00e5logging" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Skanneintervall (sekunder)" + }, + "description": "Oppdater skanneintervallet for \u00e5 avstemme etter data oftere.", + "title": "Alternativer for Hive" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/pl.json b/homeassistant/components/hive/translations/pl.json new file mode 100644 index 00000000000..0c61fa74feb --- /dev/null +++ b/homeassistant/components/hive/translations/pl.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119", + "unknown_entry": "Nie mo\u017cna znale\u017a\u0107 istniej\u0105cego wpisu." + }, + "error": { + "invalid_code": "Nie uda\u0142o si\u0119 zalogowa\u0107 do Hive. Tw\u00f3j kod uwierzytelniania dwusk\u0142adnikowego by\u0142 nieprawid\u0142owy.", + "invalid_password": "Nie uda\u0142o si\u0119 zalogowa\u0107 do Hive. Nieprawid\u0142owe has\u0142o, spr\u00f3buj ponownie.", + "invalid_username": "Nie uda\u0142o si\u0119 zalogowa\u0107 do Hive. Tw\u00f3j adres e-mail nie zosta\u0142 rozpoznany.", + "no_internet_available": "Do po\u0142\u0105czenia z Hive wymagane jest po\u0142\u0105czenie z internetem.", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "2fa": { + "data": { + "2fa": "Kod uwierzytelniania dwusk\u0142adnikowego" + }, + "description": "Wprowad\u017a sw\u00f3j kod uwierzytelniaj\u0105cy Hive. \n\nWprowad\u017a kod 0000, aby poprosi\u0107 o kolejny kod.", + "title": "Uwierzytelnianie dwusk\u0142adnikowe Hive" + }, + "reauth": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Wprowad\u017a ponownie swoje dane logowania do Hive.", + "title": "Login Hive" + }, + "user": { + "data": { + "password": "Has\u0142o", + "scan_interval": "Cz\u0119stotliwo\u015b\u0107 skanowania (w sekundach)", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Wprowad\u017a dane logowania i konfiguracj\u0119 Hive.", + "title": "Login Hive" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Cz\u0119stotliwo\u015b\u0107 skanowania (w sekundach)" + }, + "description": "Zaktualizuj cz\u0119stotliwo\u015b\u0107 skanowania, aby cz\u0119\u015bciej sondowa\u0107 dane.", + "title": "Opcje Hive" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/pt.json b/homeassistant/components/hive/translations/pt.json index 46b7da5620a..8397b5cb0a4 100644 --- a/homeassistant/components/hive/translations/pt.json +++ b/homeassistant/components/hive/translations/pt.json @@ -4,6 +4,9 @@ "already_configured": "Conta j\u00e1 configurada", "reauth_successful": "Reautentica\u00e7\u00e3o bem sucedida" }, + "error": { + "unknown": "Erro inesperado" + }, "step": { "reauth": { "data": { diff --git a/homeassistant/components/hive/translations/ru.json b/homeassistant/components/hive/translations/ru.json index 42df1bf9a24..02736871d24 100644 --- a/homeassistant/components/hive/translations/ru.json +++ b/homeassistant/components/hive/translations/ru.json @@ -23,7 +23,7 @@ "reauth": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0432 Hive.", "title": "Hive" @@ -32,7 +32,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0440\u043e\u0441\u0430 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u0430 \u0432 Hive.", "title": "Hive" diff --git a/homeassistant/components/hlk_sw16/translations/ru.json b/homeassistant/components/hlk_sw16/translations/ru.json index 9e0db9fcf94..9b02cafd466 100644 --- a/homeassistant/components/hlk_sw16/translations/ru.json +++ b/homeassistant/components/hlk_sw16/translations/ru.json @@ -13,7 +13,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/homeassistant/translations/it.json b/homeassistant/components/homeassistant/translations/it.json index f3168807715..66b8f8a1d14 100644 --- a/homeassistant/components/homeassistant/translations/it.json +++ b/homeassistant/components/homeassistant/translations/it.json @@ -6,13 +6,13 @@ "dev": "Sviluppo", "docker": "Docker", "docker_version": "Docker", - "hassio": "Supervisore", + "hassio": "Supervisor", "host_os": "Sistema Operativo di Home Assistant", "installation_type": "Tipo di installazione", "os_name": "Famiglia del Sistema Operativo", "os_version": "Versione del Sistema Operativo", "python_version": "Versione Python", - "supervisor": "Supervisore", + "supervisor": "Supervisor", "timezone": "Fuso orario", "version": "Versione", "virtualenv": "Ambiente virtuale" diff --git a/homeassistant/components/homekit/translations/nl.json b/homeassistant/components/homekit/translations/nl.json index dce21a4b880..3ba77f095a5 100644 --- a/homeassistant/components/homekit/translations/nl.json +++ b/homeassistant/components/homekit/translations/nl.json @@ -55,6 +55,7 @@ "entities": "Entiteiten", "mode": "Mode" }, + "description": "Kies de entiteiten die u wilt opnemen. In de accessoiremodus is slechts een enkele entiteit inbegrepen. In de bridge-include-modus worden alle entiteiten in het domein opgenomen, tenzij specifieke entiteiten zijn geselecteerd. In de bridge-uitsluitingsmodus worden alle entiteiten in het domein opgenomen, behalve de uitgesloten entiteiten. Voor de beste prestaties is elke tv-mediaspeler en camera een apart HomeKit-accessoire.", "title": "Selecteer de entiteiten die u wilt opnemen" }, "init": { diff --git a/homeassistant/components/homekit_controller/translations/nl.json b/homeassistant/components/homekit_controller/translations/nl.json index 46167e9da98..5c23159e21f 100644 --- a/homeassistant/components/homekit_controller/translations/nl.json +++ b/homeassistant/components/homekit_controller/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "accessory_not_found_error": "Kan geen koppeling toevoegen omdat het apparaat niet langer kan worden gevonden.", "already_configured": "Accessoire is al geconfigureerd met deze controller.", - "already_in_progress": "De configuratiestroom voor het apparaat is al in volle gang.", + "already_in_progress": "De configuratiestroom is al aan de gang", "already_paired": "Dit accessoire is al gekoppeld aan een ander apparaat. Reset het accessoire en probeer het opnieuw.", "ignored_model": "HomeKit-ondersteuning voor dit model is geblokkeerd omdat er een meer functie volledige native integratie beschikbaar is.", "invalid_config_entry": "Dit apparaat geeft aan dat het gereed is om te koppelen, maar er is al een conflicterend configuratie-item voor in de Home Assistant dat eerst moet worden verwijderd.", @@ -17,7 +17,7 @@ "unable_to_pair": "Kan niet koppelen, probeer het opnieuw.", "unknown_error": "Apparaat meldde een onbekende fout. Koppelen mislukt." }, - "flow_title": "HomeKit-accessoire: {name}", + "flow_title": "{name} via HomeKit-accessoireprotocol", "step": { "max_tries_error": { "title": "Maximum aantal authenticatiepogingen overschreden" diff --git a/homeassistant/components/huawei_lte/translations/nl.json b/homeassistant/components/huawei_lte/translations/nl.json index d420093996c..c1584b56330 100644 --- a/homeassistant/components/huawei_lte/translations/nl.json +++ b/homeassistant/components/huawei_lte/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Dit apparaat is reeds geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "Dit apparaat wordt al geconfigureerd", "not_huawei_lte": "Geen Huawei LTE-apparaat" }, @@ -15,6 +15,7 @@ "response_error": "Onbekende fout van het apparaat", "unknown": "Onverwachte fout" }, + "flow_title": "Huawei LTE: {name}", "step": { "user": { "data": { diff --git a/homeassistant/components/huawei_lte/translations/ru.json b/homeassistant/components/huawei_lte/translations/ru.json index c2ec20fb259..d3f95e3fbf1 100644 --- a/homeassistant/components/huawei_lte/translations/ru.json +++ b/homeassistant/components/huawei_lte/translations/ru.json @@ -8,7 +8,7 @@ "error": { "connection_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.", "incorrect_password": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043f\u0430\u0440\u043e\u043b\u044c.", - "incorrect_username": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d.", + "incorrect_username": "\u041d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.", "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "invalid_url": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 URL-\u0430\u0434\u0440\u0435\u0441.", "login_attempts_exceeded": "\u041f\u0440\u0435\u0432\u044b\u0448\u0435\u043d\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u043f\u044b\u0442\u043e\u043a \u0432\u0445\u043e\u0434\u0430, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435.", @@ -21,9 +21,9 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "url": "URL-\u0430\u0434\u0440\u0435\u0441", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443. \u0423\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043b\u043e\u0433\u0438\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e, \u043d\u043e \u044d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438. \u0421 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u043a \u0432\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0443 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u0438\u0437 Home Assistant, \u043a\u043e\u0433\u0434\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u0430, \u0438 \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442.", + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443. \u0423\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e, \u043d\u043e \u044d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438. \u0421 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u043a \u0432\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0443 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u0438\u0437 Home Assistant, \u043a\u043e\u0433\u0434\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u0430, \u0438 \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442.", "title": "Huawei LTE" } } diff --git a/homeassistant/components/hue/translations/nl.json b/homeassistant/components/hue/translations/nl.json index 5cc6a3572e2..c2ed895dc8c 100644 --- a/homeassistant/components/hue/translations/nl.json +++ b/homeassistant/components/hue/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "all_configured": "Alle Philips Hue bridges zijn al geconfigureerd", "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom voor het apparaat is al in volle gang.", + "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kan geen verbinding maken", "discover_timeout": "Hue bridges kunnen niet worden gevonden", "no_bridges": "Geen Philips Hue bridges ontdekt", diff --git a/homeassistant/components/huisbaasje/translations/ru.json b/homeassistant/components/huisbaasje/translations/ru.json index a598320115d..995e340c396 100644 --- a/homeassistant/components/huisbaasje/translations/ru.json +++ b/homeassistant/components/huisbaasje/translations/ru.json @@ -13,7 +13,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/humidifier/translations/de.json b/homeassistant/components/humidifier/translations/de.json index 24d8b01353e..a9bd89055ef 100644 --- a/homeassistant/components/humidifier/translations/de.json +++ b/homeassistant/components/humidifier/translations/de.json @@ -1,12 +1,19 @@ { "device_automation": { "action_type": { + "set_humidity": "Luftfeuchtigkeit f\u00fcr {entity_name} einstellen", "set_mode": "Wechsele Modus auf {entity_name}", "toggle": "{entity_name} umschalten", "turn_off": "Schalte {entity_name} aus", "turn_on": "Schalte {entity_name} an" }, + "condition_type": { + "is_mode": "{entity_name} ist auf einen bestimmten Modus festgelegt", + "is_off": "{entity_name} ist ausgeschaltet", + "is_on": "{entity_name} ist eingeschaltet" + }, "trigger_type": { + "target_humidity_changed": "{entity_name} Soll-Luftfeuchtigkeit ge\u00e4ndert", "turned_off": "{entity_name} ausgeschaltet", "turned_on": "{entity_name} eingeschaltet" } diff --git a/homeassistant/components/humidifier/translations/nl.json b/homeassistant/components/humidifier/translations/nl.json index 915b6f477d4..9505a6a0838 100644 --- a/homeassistant/components/humidifier/translations/nl.json +++ b/homeassistant/components/humidifier/translations/nl.json @@ -3,6 +3,7 @@ "action_type": { "set_humidity": "Luchtvochtigheid instellen voor {entity_name}", "set_mode": "Wijzig modus van {entity_name}", + "toggle": "Schakel {entity_name}", "turn_off": "{entity_name} uitschakelen", "turn_on": "{entity_name} inschakelen" }, @@ -22,5 +23,6 @@ "off": "Uit", "on": "Aan" } - } + }, + "title": "Luchtbevochtiger" } \ No newline at end of file diff --git a/homeassistant/components/hvv_departures/translations/ru.json b/homeassistant/components/hvv_departures/translations/ru.json index 6ae27715033..55e60b23c32 100644 --- a/homeassistant/components/hvv_departures/translations/ru.json +++ b/homeassistant/components/hvv_departures/translations/ru.json @@ -25,7 +25,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a API HVV" } diff --git a/homeassistant/components/iaqualink/translations/nl.json b/homeassistant/components/iaqualink/translations/nl.json index fae8693ce4c..fc5b00694a1 100644 --- a/homeassistant/components/iaqualink/translations/nl.json +++ b/homeassistant/components/iaqualink/translations/nl.json @@ -10,7 +10,7 @@ "user": { "data": { "password": "Wachtwoord", - "username": "Gebruikersnaam/E-mailadres" + "username": "Gebruikersnaam" }, "description": "Voer de gebruikersnaam en het wachtwoord voor uw iAqualink-account in.", "title": "Verbinding maken met iAqualink" diff --git a/homeassistant/components/iaqualink/translations/ru.json b/homeassistant/components/iaqualink/translations/ru.json index 27531c65d9e..b7c9779e11a 100644 --- a/homeassistant/components/iaqualink/translations/ru.json +++ b/homeassistant/components/iaqualink/translations/ru.json @@ -10,9 +10,9 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043b\u043e\u0433\u0438\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 iAqualink.", + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 iAqualink.", "title": "Jandy iAqualink" } } diff --git a/homeassistant/components/insteon/translations/ru.json b/homeassistant/components/insteon/translations/ru.json index dec25f1fe4b..69b5354fe87 100644 --- a/homeassistant/components/insteon/translations/ru.json +++ b/homeassistant/components/insteon/translations/ru.json @@ -22,7 +22,7 @@ "host": "IP-\u0430\u0434\u0440\u0435\u0441", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Insteon Hub \u0432\u0435\u0440\u0441\u0438\u0438 2", "title": "Insteon Hub. \u0412\u0435\u0440\u0441\u0438\u044f 2" @@ -74,7 +74,7 @@ "host": "IP-\u0430\u0434\u0440\u0435\u0441", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 Insteon Hub. \u041f\u043e\u0441\u043b\u0435 \u0432\u043d\u0435\u0441\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c Home Assistant. \u042d\u0442\u043e \u043d\u0435 \u043c\u0435\u043d\u044f\u0435\u0442 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0441\u0430\u043c\u043e\u0433\u043e \u0445\u0430\u0431\u0430. \u0427\u0442\u043e\u0431\u044b \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0445\u0430\u0431\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 Hub.", "title": "Insteon" diff --git a/homeassistant/components/isy994/translations/ru.json b/homeassistant/components/isy994/translations/ru.json index cbf88574e1e..50aa75cab37 100644 --- a/homeassistant/components/isy994/translations/ru.json +++ b/homeassistant/components/isy994/translations/ru.json @@ -16,7 +16,7 @@ "host": "URL-\u0430\u0434\u0440\u0435\u0441", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "tls": "\u0412\u0435\u0440\u0441\u0438\u044f TLS \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0423\u043a\u0430\u0436\u0438\u0442\u0435 URL-\u0430\u0434\u0440\u0435\u0441 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 'address[:port]' (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: 'http://192.168.10.100:80').", "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" diff --git a/homeassistant/components/izone/translations/nl.json b/homeassistant/components/izone/translations/nl.json index 22d1a3c4963..b70bb738df0 100644 --- a/homeassistant/components/izone/translations/nl.json +++ b/homeassistant/components/izone/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Geen iZone-apparaten gevonden op het netwerk.", - "single_instance_allowed": "Er is slechts \u00e9\u00e9n configuratie van iZone nodig." + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "step": { "confirm": { diff --git a/homeassistant/components/keenetic_ndms2/translations/de.json b/homeassistant/components/keenetic_ndms2/translations/de.json index 71ce0154639..5994df16940 100644 --- a/homeassistant/components/keenetic_ndms2/translations/de.json +++ b/homeassistant/components/keenetic_ndms2/translations/de.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Scanintervall" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/ru.json b/homeassistant/components/keenetic_ndms2/translations/ru.json index bfd7f6407e7..6f99453888e 100644 --- a/homeassistant/components/keenetic_ndms2/translations/ru.json +++ b/homeassistant/components/keenetic_ndms2/translations/ru.json @@ -13,7 +13,7 @@ "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430 Keenetic NDMS2" } diff --git a/homeassistant/components/kmtronic/translations/ru.json b/homeassistant/components/kmtronic/translations/ru.json index 7401068638d..219af38fae9 100644 --- a/homeassistant/components/kmtronic/translations/ru.json +++ b/homeassistant/components/kmtronic/translations/ru.json @@ -13,7 +13,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/kodi/translations/ru.json b/homeassistant/components/kodi/translations/ru.json index 50742417f28..b6f7443f061 100644 --- a/homeassistant/components/kodi/translations/ru.json +++ b/homeassistant/components/kodi/translations/ru.json @@ -17,7 +17,7 @@ "credentials": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u043f\u0430\u0440\u043e\u043b\u044c Kodi. \u0418\u0445 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438, \u043f\u0435\u0440\u0435\u0439\u0434\u044f \u0432 \"\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\" - \"\u0421\u043b\u0443\u0436\u0431\u044b\" - \"\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\"." }, diff --git a/homeassistant/components/life360/translations/ru.json b/homeassistant/components/life360/translations/ru.json index 5b5934fbb42..b7bc7198987 100644 --- a/homeassistant/components/life360/translations/ru.json +++ b/homeassistant/components/life360/translations/ru.json @@ -10,14 +10,14 @@ "error": { "already_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant.", "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", - "invalid_username": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d.", + "invalid_username": "\u041d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "step": { "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438]({docs_url}) \u0434\u043b\u044f \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u043e\u0439 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u043f\u0435\u0440\u0435\u0434 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430.", "title": "Life360" diff --git a/homeassistant/components/litejet/translations/de.json b/homeassistant/components/litejet/translations/de.json index 492314e5cc6..ff528dd79b2 100644 --- a/homeassistant/components/litejet/translations/de.json +++ b/homeassistant/components/litejet/translations/de.json @@ -3,11 +3,16 @@ "abort": { "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, + "error": { + "open_failed": "Die angegebene serielle Schnittstelle kann nicht ge\u00f6ffnet werden." + }, "step": { "user": { "data": { "port": "Port" - } + }, + "description": "Verbinde den RS232-2-Anschluss des LiteJet mit deinen Computer und gib den Pfad zum Ger\u00e4t der seriellen Schnittstelle ein.\n\nDer LiteJet MCP muss f\u00fcr 19,2 K Baud, 8 Datenbits, 1 Stoppbit, keine Parit\u00e4t und zur \u00dcbertragung eines 'CR' nach jeder Antwort konfiguriert werden.", + "title": "Verbinde zu LiteJet" } } } diff --git a/homeassistant/components/litterrobot/translations/pt.json b/homeassistant/components/litterrobot/translations/pt.json index 565b9f6c0e8..7953cf5625c 100644 --- a/homeassistant/components/litterrobot/translations/pt.json +++ b/homeassistant/components/litterrobot/translations/pt.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + }, "error": { "cannot_connect": "Falha na liga\u00e7\u00e3o", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", diff --git a/homeassistant/components/litterrobot/translations/ru.json b/homeassistant/components/litterrobot/translations/ru.json index 3f4677a050e..aef0fdff54e 100644 --- a/homeassistant/components/litterrobot/translations/ru.json +++ b/homeassistant/components/litterrobot/translations/ru.json @@ -12,7 +12,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/mikrotik/translations/ru.json b/homeassistant/components/mikrotik/translations/ru.json index 21391f12b1c..06e9d647545 100644 --- a/homeassistant/components/mikrotik/translations/ru.json +++ b/homeassistant/components/mikrotik/translations/ru.json @@ -15,7 +15,7 @@ "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", "verify_ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c SSL" }, "title": "MikroTik" diff --git a/homeassistant/components/mill/translations/ru.json b/homeassistant/components/mill/translations/ru.json index db2d4651c2d..eac6c63c559 100644 --- a/homeassistant/components/mill/translations/ru.json +++ b/homeassistant/components/mill/translations/ru.json @@ -10,7 +10,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/mqtt/translations/ru.json b/homeassistant/components/mqtt/translations/ru.json index 7cc7a84b28c..4357a0902c6 100644 --- a/homeassistant/components/mqtt/translations/ru.json +++ b/homeassistant/components/mqtt/translations/ru.json @@ -13,7 +13,7 @@ "discovery": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0430\u0432\u0442\u043e\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043a \u0412\u0430\u0448\u0435\u043c\u0443 \u0431\u0440\u043e\u043a\u0435\u0440\u0443 MQTT." }, @@ -60,7 +60,7 @@ "broker": "\u0411\u0440\u043e\u043a\u0435\u0440", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043a \u0412\u0430\u0448\u0435\u043c\u0443 \u0431\u0440\u043e\u043a\u0435\u0440\u0443 MQTT." }, diff --git a/homeassistant/components/mullvad/translations/de.json b/homeassistant/components/mullvad/translations/de.json index 625c7372347..6014a9155c8 100644 --- a/homeassistant/components/mullvad/translations/de.json +++ b/homeassistant/components/mullvad/translations/de.json @@ -14,7 +14,8 @@ "host": "Host", "password": "Passwort", "username": "Benutzername" - } + }, + "description": "Mullvad VPN Integration einrichten?" } } } diff --git a/homeassistant/components/mullvad/translations/ru.json b/homeassistant/components/mullvad/translations/ru.json index ff34530e4a9..af2ecd321f0 100644 --- a/homeassistant/components/mullvad/translations/ru.json +++ b/homeassistant/components/mullvad/translations/ru.json @@ -13,7 +13,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Mullvad VPN." } diff --git a/homeassistant/components/myq/translations/ru.json b/homeassistant/components/myq/translations/ru.json index c3b113f148f..c88db7d6960 100644 --- a/homeassistant/components/myq/translations/ru.json +++ b/homeassistant/components/myq/translations/ru.json @@ -12,7 +12,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "MyQ" } diff --git a/homeassistant/components/mysensors/translations/de.json b/homeassistant/components/mysensors/translations/de.json index 189226f29d5..98a1ca9cd32 100644 --- a/homeassistant/components/mysensors/translations/de.json +++ b/homeassistant/components/mysensors/translations/de.json @@ -4,13 +4,51 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert", "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", + "invalid_device": "Ung\u00fcltiges Ger\u00e4t", + "invalid_ip": "Ung\u00fcltige IP-Adresse", + "invalid_version": "Ung\u00fcltige MySensors Version", + "not_a_number": "Bitte eine Nummer eingeben", "unknown": "Unerwarteter Fehler" }, "error": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", + "invalid_device": "Ung\u00fcltiges Ger\u00e4t", + "invalid_ip": "Ung\u00fcltige IP-Adresse", + "invalid_version": "Ung\u00fcltige MySensors Version", + "not_a_number": "Bitte eine Nummer eingeben", "unknown": "Unerwarteter Fehler" + }, + "step": { + "gw_mqtt": { + "data": { + "version": "MySensors Version" + }, + "description": "MQTT-Gateway einrichten" + }, + "gw_serial": { + "data": { + "baud_rate": "Baudrate", + "device": "Serielle Schnittstelle", + "version": "MySensors Version" + }, + "description": "Einrichtung des seriellen Gateways" + }, + "gw_tcp": { + "data": { + "device": "IP-Adresse des Gateways", + "version": "MySensors Version" + }, + "description": "Einrichtung des Ethernet-Gateways" + }, + "user": { + "data": { + "gateway_type": "Gateway-Typ" + }, + "description": "Verbindungsmethode zum Gateway w\u00e4hlen" + } } - } + }, + "title": "" } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/ru.json b/homeassistant/components/neato/translations/ru.json index ea1be16d7ac..25bb616a638 100644 --- a/homeassistant/components/neato/translations/ru.json +++ b/homeassistant/components/neato/translations/ru.json @@ -25,7 +25,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", "vendor": "\u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c" }, "description": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438]({docs_url}) \u0434\u043b\u044f \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u043e\u0439 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438.", diff --git a/homeassistant/components/nest/translations/it.json b/homeassistant/components/nest/translations/it.json index 84c04049946..c6e62db314d 100644 --- a/homeassistant/components/nest/translations/it.json +++ b/homeassistant/components/nest/translations/it.json @@ -30,7 +30,7 @@ "data": { "code": "Codice PIN" }, - "description": "Per collegare l'account Nido, [autorizzare l'account]({url}).\n\nDopo l'autorizzazione, copia-incolla il codice PIN fornito di seguito.", + "description": "Per collegare il tuo account Nest, [autorizza il tuo account] ({url}). \n\nDopo l'autorizzazione, copia e incolla il codice PIN fornito di seguito.", "title": "Collega un account Nest" }, "pick_implementation": { diff --git a/homeassistant/components/netatmo/translations/de.json b/homeassistant/components/netatmo/translations/de.json index 247e9e8b931..dccb5857748 100644 --- a/homeassistant/components/netatmo/translations/de.json +++ b/homeassistant/components/netatmo/translations/de.json @@ -52,6 +52,7 @@ }, "public_weather_areas": { "data": { + "new_area": "Bereichsname", "weather_areas": "Wettergebiete" }, "description": "Konfiguriere \u00f6ffentliche Wettersensoren.", diff --git a/homeassistant/components/nexia/translations/ru.json b/homeassistant/components/nexia/translations/ru.json index f1c7b5b8ced..74ec08ec2cf 100644 --- a/homeassistant/components/nexia/translations/ru.json +++ b/homeassistant/components/nexia/translations/ru.json @@ -12,7 +12,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a mynexia.com" } diff --git a/homeassistant/components/notion/translations/nl.json b/homeassistant/components/notion/translations/nl.json index 64894303cdf..acb42046c90 100644 --- a/homeassistant/components/notion/translations/nl.json +++ b/homeassistant/components/notion/translations/nl.json @@ -11,7 +11,7 @@ "user": { "data": { "password": "Wachtwoord", - "username": "Gebruikersnaam/E-mailadres" + "username": "Gebruikersnaam" }, "title": "Vul uw gegevens informatie" } diff --git a/homeassistant/components/notion/translations/ru.json b/homeassistant/components/notion/translations/ru.json index 737539424b0..4b9a45bbf3f 100644 --- a/homeassistant/components/notion/translations/ru.json +++ b/homeassistant/components/notion/translations/ru.json @@ -11,7 +11,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "Notion" } diff --git a/homeassistant/components/nuheat/translations/ru.json b/homeassistant/components/nuheat/translations/ru.json index 099f6c3f1fc..7c90b861564 100644 --- a/homeassistant/components/nuheat/translations/ru.json +++ b/homeassistant/components/nuheat/translations/ru.json @@ -14,7 +14,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "serial_number": "\u0421\u0435\u0440\u0438\u0439\u043d\u044b\u0439 \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u0440\u043c\u043e\u0441\u0442\u0430\u0442\u0430", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0441\u0435\u0440\u0438\u0439\u043d\u044b\u0439 \u043d\u043e\u043c\u0435\u0440 \u0438\u043b\u0438 ID \u0412\u0430\u0448\u0435\u0433\u043e \u0442\u0435\u0440\u043c\u043e\u0441\u0442\u0430\u0442\u0430, \u043d\u0430 \u0441\u0430\u0439\u0442\u0435 https://MyNuHeat.com.", "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" diff --git a/homeassistant/components/number/translations/de.json b/homeassistant/components/number/translations/de.json index 5005eb5a010..3ef9a0358a4 100644 --- a/homeassistant/components/number/translations/de.json +++ b/homeassistant/components/number/translations/de.json @@ -1,3 +1,8 @@ { + "device_automation": { + "action_type": { + "set_value": "Wert f\u00fcr {entity_name} setzen" + } + }, "title": "Nummer" } \ No newline at end of file diff --git a/homeassistant/components/nut/translations/pt.json b/homeassistant/components/nut/translations/pt.json index a856ef0aeed..f5e8690e383 100644 --- a/homeassistant/components/nut/translations/pt.json +++ b/homeassistant/components/nut/translations/pt.json @@ -22,5 +22,11 @@ } } } + }, + "options": { + "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", + "unknown": "Erro inesperado" + } } } \ No newline at end of file diff --git a/homeassistant/components/nut/translations/ru.json b/homeassistant/components/nut/translations/ru.json index 5422704c96d..071a7d0f09c 100644 --- a/homeassistant/components/nut/translations/ru.json +++ b/homeassistant/components/nut/translations/ru.json @@ -26,7 +26,7 @@ "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 NUT" } diff --git a/homeassistant/components/nzbget/translations/ru.json b/homeassistant/components/nzbget/translations/ru.json index e4f0a44fbcc..4c5d7379527 100644 --- a/homeassistant/components/nzbget/translations/ru.json +++ b/homeassistant/components/nzbget/translations/ru.json @@ -16,7 +16,7 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", - "username": "\u041b\u043e\u0433\u0438\u043d", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" }, "title": "NZBGet" diff --git a/homeassistant/components/omnilogic/translations/ru.json b/homeassistant/components/omnilogic/translations/ru.json index 828f0530830..5b00efefa1a 100644 --- a/homeassistant/components/omnilogic/translations/ru.json +++ b/homeassistant/components/omnilogic/translations/ru.json @@ -12,7 +12,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/onvif/translations/ru.json b/homeassistant/components/onvif/translations/ru.json index 823dd8eb7fd..6b486bb62e2 100644 --- a/homeassistant/components/onvif/translations/ru.json +++ b/homeassistant/components/onvif/translations/ru.json @@ -14,7 +14,7 @@ "auth": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f" }, diff --git a/homeassistant/components/ovo_energy/translations/ru.json b/homeassistant/components/ovo_energy/translations/ru.json index 47a94f6a24a..89eb632102f 100644 --- a/homeassistant/components/ovo_energy/translations/ru.json +++ b/homeassistant/components/ovo_energy/translations/ru.json @@ -17,7 +17,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 OVO Energy.", "title": "OVO Energy" diff --git a/homeassistant/components/philips_js/translations/ca.json b/homeassistant/components/philips_js/translations/ca.json index 980bb6800e1..b12d8e7a848 100644 --- a/homeassistant/components/philips_js/translations/ca.json +++ b/homeassistant/components/philips_js/translations/ca.json @@ -10,6 +10,13 @@ "unknown": "Error inesperat" }, "step": { + "pair": { + "data": { + "pin": "Codi PIN" + }, + "description": "Introdueix el PIN que apareix a la pantalla del televisor", + "title": "Emparellar" + }, "user": { "data": { "api_version": "Versi\u00f3 de l'API", diff --git a/homeassistant/components/philips_js/translations/de.json b/homeassistant/components/philips_js/translations/de.json index 8b5d376ead7..d0c9932b3cd 100644 --- a/homeassistant/components/philips_js/translations/de.json +++ b/homeassistant/components/philips_js/translations/de.json @@ -10,6 +10,11 @@ "unknown": "Unerwarteter Fehler" }, "step": { + "pair": { + "data": { + "pin": "PIN-Code" + } + }, "user": { "data": { "api_version": "API-Version", @@ -17,5 +22,10 @@ } } } + }, + "device_automation": { + "trigger_type": { + "turn_on": "Ger\u00e4t wird zum Einschalten aufgefordert" + } } } \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/en.json b/homeassistant/components/philips_js/translations/en.json index 65d4f417b9f..ea254a3873d 100644 --- a/homeassistant/components/philips_js/translations/en.json +++ b/homeassistant/components/philips_js/translations/en.json @@ -10,6 +10,13 @@ "unknown": "Unexpected error" }, "step": { + "pair": { + "data": { + "pin": "PIN Code" + }, + "description": "Enter the PIN displayed on your TV", + "title": "Pair" + }, "user": { "data": { "api_version": "API Version", diff --git a/homeassistant/components/philips_js/translations/ko.json b/homeassistant/components/philips_js/translations/ko.json index d698b01af1a..9a7c530ce52 100644 --- a/homeassistant/components/philips_js/translations/ko.json +++ b/homeassistant/components/philips_js/translations/ko.json @@ -10,6 +10,11 @@ "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { + "pair": { + "data": { + "pin": "PIN \ucf54\ub4dc" + } + }, "user": { "data": { "api_version": "API \ubc84\uc804", diff --git a/homeassistant/components/philips_js/translations/pt.json b/homeassistant/components/philips_js/translations/pt.json new file mode 100644 index 00000000000..4646fcae7dc --- /dev/null +++ b/homeassistant/components/philips_js/translations/pt.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_pin": "PIN inv\u00e1lido" + }, + "step": { + "pair": { + "data": { + "pin": "C\u00f3digo PIN" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/de.json b/homeassistant/components/plaato/translations/de.json index 20cba7e9ebc..e3e94ddf848 100644 --- a/homeassistant/components/plaato/translations/de.json +++ b/homeassistant/components/plaato/translations/de.json @@ -9,9 +9,37 @@ "default": "Um Ereignisse an Home Assistant zu senden, muss das Webhook Feature in Plaato Airlock konfiguriert werden.\n\n F\u00fcge die folgenden Informationen ein: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n \n Weitere Informationen finden sich in der [Dokumentation]({docs_url})." }, "step": { + "api_method": { + "data": { + "use_webhook": "Webhook verwenden" + }, + "title": "API-Methode ausw\u00e4hlen" + }, "user": { + "data": { + "device_name": "Benenne dein Ger\u00e4t", + "device_type": "Art des Plaato-Ger\u00e4ts" + }, "description": "M\u00f6chten Sie mit der Einrichtung beginnen?", "title": "Plaato Webhook einrichten" + }, + "webhook": { + "description": "Um Ereignisse an Home Assistant zu senden, muss das Webhook Feature in Plaato Airlock konfiguriert werden.\n\n F\u00fcge die folgenden Informationen ein: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n \n Weitere Informationen finden sich in der [Dokumentation]({docs_url})." + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "Aktualisierungsintervall (Minuten)" + }, + "description": "Aktualisierungsintervall einrichten (Minuten)", + "title": "Optionen f\u00fcr Plaato" + }, + "webhook": { + "description": "Webhook-Informationen:\n\n- URL: `{webhook_url}`\n- Methode: POST\n\n", + "title": "Optionen f\u00fcr Plaato Airlock" } } } diff --git a/homeassistant/components/plaato/translations/nl.json b/homeassistant/components/plaato/translations/nl.json index 6453668680a..6c09818594c 100644 --- a/homeassistant/components/plaato/translations/nl.json +++ b/homeassistant/components/plaato/translations/nl.json @@ -6,7 +6,7 @@ "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." }, "create_entry": { - "default": "Om evenementen naar de Home Assistant te sturen, moet u de webhook-functie instellen in Plaato Airlock. \n\n Vul de volgende info in: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n\n Zie [de documentatie] ( {docs_url} ) voor meer informatie." + "default": "Uw Plaato {device_type} met naam **{device_name}** is succesvol ingesteld!" }, "error": { "invalid_webhook_device": "U heeft een apparaat geselecteerd dat het verzenden van gegevens naar een webhook niet ondersteunt. Het is alleen beschikbaar voor de Airlock", @@ -26,7 +26,7 @@ "device_name": "Geef uw apparaat een naam", "device_type": "Type Plaato-apparaat" }, - "description": "Weet u zeker dat u de Plaato-airlock wilt instellen?", + "description": "Wil je beginnen met instellen?", "title": "Stel de Plaato Webhook in" }, "webhook": { diff --git a/homeassistant/components/plex/translations/nl.json b/homeassistant/components/plex/translations/nl.json index 6c89b0b8d5f..a196555e7ea 100644 --- a/homeassistant/components/plex/translations/nl.json +++ b/homeassistant/components/plex/translations/nl.json @@ -3,15 +3,15 @@ "abort": { "all_configured": "Alle gekoppelde servers zijn al geconfigureerd", "already_configured": "Deze Plex-server is al geconfigureerd", - "already_in_progress": "Plex wordt geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "reauth_successful": "Herauthenticatie was succesvol", "token_request_timeout": "Time-out verkrijgen van token", - "unknown": "Mislukt om onbekende reden" + "unknown": "Onverwachte fout" }, "error": { - "faulty_credentials": "Autorisatie mislukt", + "faulty_credentials": "Autorisatie mislukt, controleer token", "host_or_token": "Moet ten minste \u00e9\u00e9n host of token verstrekken.", - "no_servers": "Geen servers gekoppeld aan account", + "no_servers": "Geen servers gekoppeld aan Plex account", "not_found": "Plex-server niet gevonden", "ssl_error": "SSL-certificaatprobleem" }, diff --git a/homeassistant/components/plugwise/translations/ru.json b/homeassistant/components/plugwise/translations/ru.json index 9df460e8919..f027ebfc772 100644 --- a/homeassistant/components/plugwise/translations/ru.json +++ b/homeassistant/components/plugwise/translations/ru.json @@ -22,7 +22,7 @@ "host": "IP-\u0430\u0434\u0440\u0435\u0441", "password": "Smile ID", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d Smile" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f Smile" }, "description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435:", "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a Smile" diff --git a/homeassistant/components/rfxtrx/translations/nl.json b/homeassistant/components/rfxtrx/translations/nl.json index 81b77f5ced5..d727fc9c1b8 100644 --- a/homeassistant/components/rfxtrx/translations/nl.json +++ b/homeassistant/components/rfxtrx/translations/nl.json @@ -39,21 +39,28 @@ "error": { "already_configured_device": "Apparaat is al geconfigureerd", "invalid_event_code": "Ongeldige gebeurteniscode", + "invalid_input_off_delay": "Ongeldige invoer voor uitschakelvertraging", "unknown": "Onverwachte fout" }, "step": { "prompt_options": { "data": { "automatic_add": "Schakel automatisch toevoegen in", - "debug": "Foutopsporing inschakelen" - } + "debug": "Foutopsporing inschakelen", + "device": "Selecteer het apparaat om te configureren", + "event_code": "Voer de gebeurteniscode in om toe te voegen", + "remove_device": "Apparaat selecteren dat u wilt verwijderen" + }, + "title": "Rfxtrx-opties" }, "set_device_options": { "data": { "data_bit": "Aantal databits", "fire_event": "Schakel apparaatgebeurtenis in", "off_delay": "Uitschakelvertraging", - "off_delay_enabled": "Schakel uitschakelvertraging in" + "off_delay_enabled": "Schakel uitschakelvertraging in", + "replace_device": "Selecteer apparaat dat u wilt vervangen", + "signal_repetitions": "Aantal signaalherhalingen" }, "title": "Configureer apparaatopties" } diff --git a/homeassistant/components/ring/translations/ru.json b/homeassistant/components/ring/translations/ru.json index 636d83f2e02..bc07db24007 100644 --- a/homeassistant/components/ring/translations/ru.json +++ b/homeassistant/components/ring/translations/ru.json @@ -17,7 +17,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "Ring" } diff --git a/homeassistant/components/risco/translations/ru.json b/homeassistant/components/risco/translations/ru.json index a507bb84e53..200c38fb213 100644 --- a/homeassistant/components/risco/translations/ru.json +++ b/homeassistant/components/risco/translations/ru.json @@ -13,7 +13,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "pin": "PIN-\u043a\u043e\u0434", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/ruckus_unleashed/translations/ru.json b/homeassistant/components/ruckus_unleashed/translations/ru.json index 9e0db9fcf94..9b02cafd466 100644 --- a/homeassistant/components/ruckus_unleashed/translations/ru.json +++ b/homeassistant/components/ruckus_unleashed/translations/ru.json @@ -13,7 +13,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/sensor/translations/de.json b/homeassistant/components/sensor/translations/de.json index 0f72b344982..bb7c197f0e8 100644 --- a/homeassistant/components/sensor/translations/de.json +++ b/homeassistant/components/sensor/translations/de.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "{entity_name} Batteriestand", + "is_carbon_dioxide": "Aktuelle {entity_name} Kohlenstoffdioxid-Konzentration", + "is_carbon_monoxide": "Aktuelle {entity_name} Kohlenstoffmonoxid-Konzentration", "is_humidity": "{entity_name} Feuchtigkeit", "is_illuminance": "Aktuelle {entity_name} Helligkeit", "is_power": "Aktuelle {entity_name} Leistung", @@ -13,6 +15,8 @@ }, "trigger_type": { "battery_level": "{entity_name} Batteriestatus\u00e4nderungen", + "carbon_dioxide": "{entity_name} Kohlenstoffdioxid-Konzentrations\u00e4nderung", + "carbon_monoxide": "{entity_name} Kohlenstoffmonoxid-Konzentrations\u00e4nderung", "humidity": "{entity_name} Feuchtigkeits\u00e4nderungen", "illuminance": "{entity_name} Helligkeits\u00e4nderungen", "power": "{entity_name} Leistungs\u00e4nderungen", diff --git a/homeassistant/components/sharkiq/translations/ru.json b/homeassistant/components/sharkiq/translations/ru.json index 60ce7d454d6..0e91c64c7d4 100644 --- a/homeassistant/components/sharkiq/translations/ru.json +++ b/homeassistant/components/sharkiq/translations/ru.json @@ -15,13 +15,13 @@ "reauth": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } }, "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/shelly/translations/ru.json b/homeassistant/components/shelly/translations/ru.json index a570cb7f9fb..9996e347e96 100644 --- a/homeassistant/components/shelly/translations/ru.json +++ b/homeassistant/components/shelly/translations/ru.json @@ -17,7 +17,7 @@ "credentials": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } }, "user": { diff --git a/homeassistant/components/smart_meter_texas/translations/ru.json b/homeassistant/components/smart_meter_texas/translations/ru.json index 3f4677a050e..aef0fdff54e 100644 --- a/homeassistant/components/smart_meter_texas/translations/ru.json +++ b/homeassistant/components/smart_meter_texas/translations/ru.json @@ -12,7 +12,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/smarttub/translations/de.json b/homeassistant/components/smarttub/translations/de.json index fbb3411a6c5..8e608193b81 100644 --- a/homeassistant/components/smarttub/translations/de.json +++ b/homeassistant/components/smarttub/translations/de.json @@ -13,7 +13,9 @@ "data": { "email": "E-Mail", "password": "Passwort" - } + }, + "description": "Gib deine SmartTub E-Mail-Adresse und Passwort f\u00fcr die Anmeldung ein", + "title": "Anmeldung" } } } diff --git a/homeassistant/components/solaredge/translations/nl.json b/homeassistant/components/solaredge/translations/nl.json index 3fe28971f29..24e1716dd57 100644 --- a/homeassistant/components/solaredge/translations/nl.json +++ b/homeassistant/components/solaredge/translations/nl.json @@ -14,7 +14,7 @@ "step": { "user": { "data": { - "api_key": "De API-sleutel voor deze site", + "api_key": "API-sleutel", "name": "De naam van deze installatie", "site_id": "De SolarEdge site-id" }, diff --git a/homeassistant/components/solarlog/translations/nl.json b/homeassistant/components/solarlog/translations/nl.json index 8ccf5e626c7..6e526802d8a 100644 --- a/homeassistant/components/solarlog/translations/nl.json +++ b/homeassistant/components/solarlog/translations/nl.json @@ -5,12 +5,12 @@ }, "error": { "already_configured": "Apparaat is al geconfigureerd", - "cannot_connect": "Verbinding mislukt, controleer het host-adres" + "cannot_connect": "Kan geen verbinding maken" }, "step": { "user": { "data": { - "host": "De hostnaam of het IP-adres van uw Solar-Log apparaat", + "host": "Host", "name": "Het voorvoegsel dat moet worden gebruikt voor uw Solar-Log sensoren" }, "title": "Definieer uw Solar-Log verbinding" diff --git a/homeassistant/components/somfy/translations/nl.json b/homeassistant/components/somfy/translations/nl.json index b7f077f2c73..3fe61e5eaab 100644 --- a/homeassistant/components/somfy/translations/nl.json +++ b/homeassistant/components/somfy/translations/nl.json @@ -2,12 +2,12 @@ "config": { "abort": { "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", - "missing_configuration": "Het Somfy-component is niet geconfigureerd. Gelieve de documentatie te volgen.", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "create_entry": { - "default": "Succesvol geverifieerd met Somfy." + "default": "Succesvol geauthenticeerd" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/spider/translations/ru.json b/homeassistant/components/spider/translations/ru.json index 1b1a175cce5..08d70e44065 100644 --- a/homeassistant/components/spider/translations/ru.json +++ b/homeassistant/components/spider/translations/ru.json @@ -11,7 +11,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "\u0412\u0445\u043e\u0434 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 mijn.ithodaalderop.nl" } diff --git a/homeassistant/components/squeezebox/translations/nl.json b/homeassistant/components/squeezebox/translations/nl.json index d023a3e96d1..f60fc640db2 100644 --- a/homeassistant/components/squeezebox/translations/nl.json +++ b/homeassistant/components/squeezebox/translations/nl.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "no_server_found": "Geen LMS server gevonden." }, "error": { "cannot_connect": "Kon niet verbinden", "invalid_auth": "Ongeldige authenticatie", + "no_server_found": "Kan server niet automatisch vinden.", "unknown": "Onverwachte fout" }, "flow_title": "Logitech Squeezebox: {host}", diff --git a/homeassistant/components/squeezebox/translations/ru.json b/homeassistant/components/squeezebox/translations/ru.json index fb07471d116..3b144adc04e 100644 --- a/homeassistant/components/squeezebox/translations/ru.json +++ b/homeassistant/components/squeezebox/translations/ru.json @@ -17,7 +17,7 @@ "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438" }, diff --git a/homeassistant/components/srp_energy/translations/ru.json b/homeassistant/components/srp_energy/translations/ru.json index 125f3a5addc..a492fa7dfb2 100644 --- a/homeassistant/components/srp_energy/translations/ru.json +++ b/homeassistant/components/srp_energy/translations/ru.json @@ -15,7 +15,7 @@ "id": "ID \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430", "is_tou": "\u041f\u043b\u0430\u043d \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/starline/translations/ru.json b/homeassistant/components/starline/translations/ru.json index ea6833f5842..a89fc3b15d5 100644 --- a/homeassistant/components/starline/translations/ru.json +++ b/homeassistant/components/starline/translations/ru.json @@ -3,7 +3,7 @@ "error": { "error_auth_app": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438\u043b\u0438 \u0441\u0435\u043a\u0440\u0435\u0442\u043d\u044b\u0439 \u043a\u043e\u0434.", "error_auth_mfa": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043e\u0434.", - "error_auth_user": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c." + "error_auth_user": "\u041d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c." }, "step": { "auth_app": { @@ -31,7 +31,7 @@ "auth_user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 StarLine", "title": "\u0423\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" diff --git a/homeassistant/components/subaru/translations/de.json b/homeassistant/components/subaru/translations/de.json index 1c162d61e99..9c4a0bdf535 100644 --- a/homeassistant/components/subaru/translations/de.json +++ b/homeassistant/components/subaru/translations/de.json @@ -15,13 +15,16 @@ "pin": { "data": { "pin": "PIN" - } + }, + "description": "Bitte gib deinen MySubaru-PIN ein\nHINWEIS: Alle Fahrzeuge im Konto m\u00fcssen dieselbe PIN haben" }, "user": { "data": { + "country": "Land ausw\u00e4hlen", "password": "Passwort", "username": "Benutzername" - } + }, + "description": "Bitte gib deine MySubaru-Anmeldedaten ein\nHINWEIS: Die Ersteinrichtung kann bis zu 30 Sekunden dauern" } } } diff --git a/homeassistant/components/subaru/translations/it.json b/homeassistant/components/subaru/translations/it.json index 6dbb0702f46..c585834f12b 100644 --- a/homeassistant/components/subaru/translations/it.json +++ b/homeassistant/components/subaru/translations/it.json @@ -34,9 +34,9 @@ "step": { "init": { "data": { - "update_enabled": "Abilita l'interrogazione del veicolo" + "update_enabled": "Abilita la verifica ciclica del veicolo" }, - "description": "Quando abilitata, l'interrogazione del veicolo invier\u00e0 un comando remoto al tuo veicolo ogni 2 ore per ottenere nuovi dati del sensore. Senza l'interrogazione del veicolo, i nuovi dati del sensore verranno ricevuti solo quando il veicolo invier\u00e0 automaticamente i dati (normalmente dopo lo spegnimento del motore).", + "description": "Quando abilitata, la verifica ciclica del veicolo invier\u00e0 un comando remoto d'interrogazione al tuo veicolo ogni 2 ore per ottenere nuovi dati del sensore. Senza l'interrogazione del veicolo i nuovi dati del sensore verranno ricevuti solo quando il veicolo invier\u00e0 automaticamente i dati (normalmente dopo lo spegnimento del motore).", "title": "Opzioni Subaru Starlink" } } diff --git a/homeassistant/components/subaru/translations/pt.json b/homeassistant/components/subaru/translations/pt.json new file mode 100644 index 00000000000..7f3e1ec8e3b --- /dev/null +++ b/homeassistant/components/subaru/translations/pt.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Conta j\u00e1 configurada", + "cannot_connect": "Falha na liga\u00e7\u00e3o" + }, + "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/ru.json b/homeassistant/components/subaru/translations/ru.json index 7e3fbce6e38..c414ebb385f 100644 --- a/homeassistant/components/subaru/translations/ru.json +++ b/homeassistant/components/subaru/translations/ru.json @@ -23,7 +23,7 @@ "data": { "country": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0442\u0440\u0430\u043d\u0443", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 MySubaru.\n\u041f\u0435\u0440\u0432\u043e\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u0434\u043e 30 \u0441\u0435\u043a\u0443\u043d\u0434.", "title": "Subaru Starlink" diff --git a/homeassistant/components/syncthru/translations/nl.json b/homeassistant/components/syncthru/translations/nl.json index e569d1d6322..d86e9fa2087 100644 --- a/homeassistant/components/syncthru/translations/nl.json +++ b/homeassistant/components/syncthru/translations/nl.json @@ -5,6 +5,7 @@ }, "error": { "invalid_url": "Ongeldige URL", + "syncthru_not_supported": "Apparaat ondersteunt SyncThru niet", "unknown_state": "Printerstatus onbekend, controleer URL en netwerkconnectiviteit" }, "flow_title": "Samsung SyncThru Printer: {name}", diff --git a/homeassistant/components/synology_dsm/translations/ru.json b/homeassistant/components/synology_dsm/translations/ru.json index 8c48b8c3fc7..98a7522b27d 100644 --- a/homeassistant/components/synology_dsm/translations/ru.json +++ b/homeassistant/components/synology_dsm/translations/ru.json @@ -23,7 +23,7 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", - "username": "\u041b\u043e\u0433\u0438\u043d", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" }, "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name} ({host})?", @@ -35,7 +35,7 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", - "username": "\u041b\u043e\u0433\u0438\u043d", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" }, "title": "Synology DSM" diff --git a/homeassistant/components/tado/translations/ru.json b/homeassistant/components/tado/translations/ru.json index 75c83e8582b..18e9ddff67b 100644 --- a/homeassistant/components/tado/translations/ru.json +++ b/homeassistant/components/tado/translations/ru.json @@ -13,7 +13,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "Tado" } diff --git a/homeassistant/components/tasmota/translations/nl.json b/homeassistant/components/tasmota/translations/nl.json index 3b0cc5c1ca8..561439362c7 100644 --- a/homeassistant/components/tasmota/translations/nl.json +++ b/homeassistant/components/tasmota/translations/nl.json @@ -5,6 +5,9 @@ }, "step": { "config": { + "data": { + "discovery_prefix": "Discovery-onderwerpvoorvoegsel" + }, "description": "Vul de Tasmota gegevens in", "title": "Tasmota" }, diff --git a/homeassistant/components/toon/translations/nl.json b/homeassistant/components/toon/translations/nl.json index a0cda915172..69ae8aa127f 100644 --- a/homeassistant/components/toon/translations/nl.json +++ b/homeassistant/components/toon/translations/nl.json @@ -8,6 +8,18 @@ "no_agreements": "Dit account heeft geen Toon schermen.", "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout [check the help section] ( {docs_url} )", "unknown_authorize_url_generation": "Onbekende fout bij het genereren van een autorisatie-URL." + }, + "step": { + "agreement": { + "data": { + "agreement": "Overeenkomst" + }, + "description": "Selecteer het overeenkomstadres dat u wilt toevoegen.", + "title": "Kies uw overeenkomst" + }, + "pick_implementation": { + "title": "Kies uw tenant om mee te authenticeren" + } } } } \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/de.json b/homeassistant/components/totalconnect/translations/de.json index 3fb5bb8f3e1..57dbaf77364 100644 --- a/homeassistant/components/totalconnect/translations/de.json +++ b/homeassistant/components/totalconnect/translations/de.json @@ -14,6 +14,7 @@ } }, "reauth_confirm": { + "description": "Total Connect muss dein Konto neu authentifizieren", "title": "Integration erneut authentifizieren" }, "user": { diff --git a/homeassistant/components/totalconnect/translations/pt.json b/homeassistant/components/totalconnect/translations/pt.json index 3c17682089a..11ac8262267 100644 --- a/homeassistant/components/totalconnect/translations/pt.json +++ b/homeassistant/components/totalconnect/translations/pt.json @@ -1,12 +1,21 @@ { "config": { "abort": { - "already_configured": "Conta j\u00e1 configurada" + "already_configured": "Conta j\u00e1 configurada", + "reauth_successful": "Reautentica\u00e7\u00e3o bem sucedida" }, "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, "step": { + "locations": { + "data": { + "location": "Localiza\u00e7\u00e3o" + } + }, + "reauth_confirm": { + "title": "Reautenticar integra\u00e7\u00e3o" + }, "user": { "data": { "password": "Palavra-passe", diff --git a/homeassistant/components/totalconnect/translations/ru.json b/homeassistant/components/totalconnect/translations/ru.json index 0f067541dec..a32f92b7b58 100644 --- a/homeassistant/components/totalconnect/translations/ru.json +++ b/homeassistant/components/totalconnect/translations/ru.json @@ -23,7 +23,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "Total Connect" } diff --git a/homeassistant/components/tradfri/translations/nl.json b/homeassistant/components/tradfri/translations/nl.json index 874b6cff81c..e70e515c4d2 100644 --- a/homeassistant/components/tradfri/translations/nl.json +++ b/homeassistant/components/tradfri/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "Bridge configuratie is al in volle gang." + "already_in_progress": "De configuratiestroom is al aan de gang" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/transmission/translations/nl.json b/homeassistant/components/transmission/translations/nl.json index df9a4590e66..fcc1e05e7ab 100644 --- a/homeassistant/components/transmission/translations/nl.json +++ b/homeassistant/components/transmission/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Host is al geconfigureerd." + "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Kan geen verbinding maken met host", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "name_exists": "Naam bestaat al" }, @@ -25,6 +25,8 @@ "step": { "init": { "data": { + "limit": "Limiet", + "order": "Bestel", "scan_interval": "Update frequentie" }, "title": "Configureer de opties voor Transmission" diff --git a/homeassistant/components/transmission/translations/ru.json b/homeassistant/components/transmission/translations/ru.json index 6b326bc123c..a83e19da400 100644 --- a/homeassistant/components/transmission/translations/ru.json +++ b/homeassistant/components/transmission/translations/ru.json @@ -15,7 +15,7 @@ "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "Transmission" } diff --git a/homeassistant/components/tuya/translations/nl.json b/homeassistant/components/tuya/translations/nl.json index 55fb320c17e..c9f681c5ecb 100644 --- a/homeassistant/components/tuya/translations/nl.json +++ b/homeassistant/components/tuya/translations/nl.json @@ -27,11 +27,14 @@ "cannot_connect": "Kan geen verbinding maken" }, "error": { + "dev_multi_type": "Meerdere geselecteerde apparaten om te configureren moeten van hetzelfde type zijn", + "dev_not_config": "Apparaattype kan niet worden geconfigureerd", "dev_not_found": "Apparaat niet gevonden" }, "step": { "device": { "data": { + "brightness_range_mode": "Helderheidsbereik gebruikt door apparaat", "temp_step_override": "Doeltemperatuur stap" }, "title": "Configureer Tuya Apparaat" diff --git a/homeassistant/components/tuya/translations/ru.json b/homeassistant/components/tuya/translations/ru.json index 4babc23f2ec..f40071ba400 100644 --- a/homeassistant/components/tuya/translations/ru.json +++ b/homeassistant/components/tuya/translations/ru.json @@ -15,7 +15,7 @@ "country_code": "\u041a\u043e\u0434 \u0441\u0442\u0440\u0430\u043d\u044b \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430 (1 \u0434\u043b\u044f \u0421\u0428\u0410 \u0438\u043b\u0438 86 \u0434\u043b\u044f \u041a\u0438\u0442\u0430\u044f)", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "platform": "\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d \u0430\u043a\u043a\u0430\u0443\u043d\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Tuya.", "title": "Tuya" diff --git a/homeassistant/components/unifi/translations/ru.json b/homeassistant/components/unifi/translations/ru.json index 5810204db41..769287bb975 100644 --- a/homeassistant/components/unifi/translations/ru.json +++ b/homeassistant/components/unifi/translations/ru.json @@ -18,7 +18,7 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", "site": "ID \u0441\u0430\u0439\u0442\u0430", - "username": "\u041b\u043e\u0433\u0438\u043d", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" }, "title": "UniFi Controller" diff --git a/homeassistant/components/upcloud/translations/nl.json b/homeassistant/components/upcloud/translations/nl.json index 312b117208c..f9ba7d2f696 100644 --- a/homeassistant/components/upcloud/translations/nl.json +++ b/homeassistant/components/upcloud/translations/nl.json @@ -12,5 +12,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Update-interval in seconden, minimaal 30" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/upcloud/translations/ru.json b/homeassistant/components/upcloud/translations/ru.json index c64a69965b2..8b0377e9c34 100644 --- a/homeassistant/components/upcloud/translations/ru.json +++ b/homeassistant/components/upcloud/translations/ru.json @@ -8,7 +8,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/verisure/translations/ca.json b/homeassistant/components/verisure/translations/ca.json index 4df484912ce..0ddcf9513f4 100644 --- a/homeassistant/components/verisure/translations/ca.json +++ b/homeassistant/components/verisure/translations/ca.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "El compte ja ha estat configurat" + "already_configured": "El compte ja ha estat configurat", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" }, "error": { "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", @@ -12,7 +13,14 @@ "data": { "giid": "Instal\u00b7laci\u00f3" }, - "description": "Home Assistant ha trobat diverses instal\u00b7lacions Verisure al teu compte de My Pages. Selecciona la instal\u00b7laci\u00f3 a afegir a Home Assistant." + "description": "Home Assistant ha trobat diverses instal\u00b7lacions Verisure al compte de My Pages. Selecciona la instal\u00b7laci\u00f3 a afegir a Home Assistant." + }, + "reauth_confirm": { + "data": { + "description": "Torna a autenticar-te amb el compte de Verisure My Pages.", + "email": "Correu electr\u00f2nic", + "password": "Contrasenya" + } }, "user": { "data": { diff --git a/homeassistant/components/verisure/translations/de.json b/homeassistant/components/verisure/translations/de.json new file mode 100644 index 00000000000..f9edd2e7b16 --- /dev/null +++ b/homeassistant/components/verisure/translations/de.json @@ -0,0 +1,44 @@ +{ + "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "installation": { + "data": { + "giid": "Installation" + } + }, + "reauth_confirm": { + "data": { + "email": "E-Mail", + "password": "Passwort" + } + }, + "user": { + "data": { + "email": "E-Mail", + "password": "Passwort" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "Der Standard-PIN-Code stimmt nicht mit der erforderlichen Anzahl von Ziffern \u00fcberein" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Anzahl der Ziffern im PIN-Code f\u00fcr Schl\u00f6sser", + "lock_default_code": "Standard-PIN-Code f\u00fcr Schl\u00f6sser, wird verwendet wenn keiner angegeben wird" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/et.json b/homeassistant/components/verisure/translations/et.json index d893f470a9a..78a2c987ef2 100644 --- a/homeassistant/components/verisure/translations/et.json +++ b/homeassistant/components/verisure/translations/et.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Kasutaja on juba seadistatud" + "already_configured": "Kasutaja on juba seadistatud", + "reauth_successful": "Taastuvastamine \u00f5nnestus" }, "error": { "invalid_auth": "Vigane autentimine", @@ -14,6 +15,13 @@ }, "description": "Home Assistant leidis kontolt Minu lehed mitu Verisure paigaldust. Vali Home Assistantile lisatav paigaldus." }, + "reauth_confirm": { + "data": { + "description": "Taastuvasta oma Verisure My Pages'i kontoga.", + "email": "E-posti aadress", + "password": "Salas\u00f5na" + } + }, "user": { "data": { "description": "Logi sisse oma Verisure My Pages kontoga.", diff --git a/homeassistant/components/verisure/translations/it.json b/homeassistant/components/verisure/translations/it.json new file mode 100644 index 00000000000..1c90871db71 --- /dev/null +++ b/homeassistant/components/verisure/translations/it.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" + }, + "error": { + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "installation": { + "data": { + "giid": "Installazione" + }, + "description": "Home Assistant ha trovato pi\u00f9 installazioni Verisure nel tuo account My Pages. Per favore, seleziona l'installazione da aggiungere a Home Assistant." + }, + "reauth_confirm": { + "data": { + "description": "Autenticati nuovamente con il tuo account Verisure My Pages.", + "email": "E-mail", + "password": "Password" + } + }, + "user": { + "data": { + "description": "Accedi con il tuo account Verisure My Pages.", + "email": "E-mail", + "password": "Password" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "Il codice PIN predefinito non corrisponde al numero di cifre richiesto" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Numero di cifre nel codice PIN per le serrature", + "lock_default_code": "Codice PIN predefinito per le serrature, utilizzato se non ne viene fornito nessuno" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/ko.json b/homeassistant/components/verisure/translations/ko.json new file mode 100644 index 00000000000..470aed32b19 --- /dev/null +++ b/homeassistant/components/verisure/translations/ko.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "installation": { + "data": { + "giid": "\uc124\uce58" + }, + "description": "Home Assistant\uac00 My Pages \uacc4\uc815\uc5d0\uc11c \uc5ec\ub7ec \uac00\uc9c0 Verisure \uc124\uce58\ub97c \ubc1c\uacac\ud588\uc2b5\ub2c8\ub2e4. Home Assistant\uc5d0 \ucd94\uac00\ud560 \uc124\uce58\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694." + }, + "reauth_confirm": { + "data": { + "description": "Verisure My Pages \uacc4\uc815\uc73c\ub85c \ub2e4\uc2dc \uc778\uc99d\ud574\uc8fc\uc138\uc694.", + "email": "\uc774\uba54\uc77c", + "password": "\ube44\ubc00\ubc88\ud638" + } + }, + "user": { + "data": { + "description": "Verisure My Pages \uacc4\uc815\uc73c\ub85c \ub85c\uadf8\uc778\ud558\uae30.", + "email": "\uc774\uba54\uc77c", + "password": "\ube44\ubc00\ubc88\ud638" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "\uae30\ubcf8 PIN \ucf54\ub4dc\uac00 \ud544\uc694\ud55c \uc790\ub9bf\uc218\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "\uc7a0\uae08\uc7a5\uce58\uc6a9 PIN \ucf54\ub4dc\uc758 \uc790\ub9bf\uc218", + "lock_default_code": "\uc7a0\uae08\uc7a5\uce58\uc5d0 \ub300\ud55c \uae30\ubcf8 PIN \ucf54\ub4dc (\uc81c\uacf5\ub41c PIN\uc774 \uc5c6\ub294 \uacbd\uc6b0 \uc0ac\uc6a9)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/nl.json b/homeassistant/components/verisure/translations/nl.json new file mode 100644 index 00000000000..806231dbe4a --- /dev/null +++ b/homeassistant/components/verisure/translations/nl.json @@ -0,0 +1,46 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd" + }, + "error": { + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "installation": { + "data": { + "giid": "Installatie" + }, + "description": "Home Assistant heeft meerdere Verisure-installaties gevonden in uw My Pages-account. Selecteer de installatie om toe te voegen aan Home Assistant." + }, + "reauth_confirm": { + "data": { + "description": "Verifieer opnieuw met uw Verisure My Pages-account.", + "email": "E-mail", + "password": "Wachtwoord" + } + }, + "user": { + "data": { + "description": "Aanmelden met Verisure My Pages-account.", + "email": "E-mail", + "password": "Wachtwoord" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "De standaard pincode komt niet overeen met het vereiste aantal cijfers" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Aantal cijfers in pincode voor vergrendelingen", + "lock_default_code": "Standaard pincode voor vergrendelingen, gebruikt als er geen wordt gegeven" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/no.json b/homeassistant/components/verisure/translations/no.json new file mode 100644 index 00000000000..0bd5529c51d --- /dev/null +++ b/homeassistant/components/verisure/translations/no.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "Kontoen er allerede konfigurert", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" + }, + "error": { + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "installation": { + "data": { + "giid": "Installasjon" + }, + "description": "Home Assistant fant flere Verisure-installasjoner i Mine sider-kontoen din. Velg installasjonen du vil legge til i Home Assistant." + }, + "reauth_confirm": { + "data": { + "description": "Autentiser p\u00e5 nytt med Verisure Mine sider-kontoen din.", + "email": "E-post", + "password": "Passord" + } + }, + "user": { + "data": { + "description": "Logg p\u00e5 med Verisure Mine sider-kontoen din.", + "email": "E-post", + "password": "Passord" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "Standard PIN-kode samsvarer ikke med det n\u00f8dvendige antallet sifre" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Antall sifre i PIN-kode for l\u00e5ser", + "lock_default_code": "Standard PIN-kode for l\u00e5ser, brukes hvis ingen er oppgitt" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/pl.json b/homeassistant/components/verisure/translations/pl.json new file mode 100644 index 00000000000..baaf61f60c3 --- /dev/null +++ b/homeassistant/components/verisure/translations/pl.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" + }, + "error": { + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "installation": { + "data": { + "giid": "Instalacja" + }, + "description": "Home Assistant znalaz\u0142 wiele instalacji Verisure na Twoim koncie. Wybierz instalacj\u0119, kt\u00f3r\u0105 chcesz doda\u0107 do Home Assistanta." + }, + "reauth_confirm": { + "data": { + "description": "Ponownie uwierzytelnij za pomoc\u0105 konta Verisure.", + "email": "Adres e-mail", + "password": "Has\u0142o" + } + }, + "user": { + "data": { + "description": "Zaloguj si\u0119 na swoje konto Verisure.", + "email": "Adres e-mail", + "password": "Has\u0142o" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "Domy\u015blny kod PIN nie odpowiada wymaganej liczbie cyfr" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Liczba cyfr w kodzie PIN dla zamk\u00f3w", + "lock_default_code": "Domy\u015blny kod PIN dla zamk\u00f3w. U\u017cywany, je\u015bli nie podano \u017cadnego." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/pt.json b/homeassistant/components/verisure/translations/pt.json index b6634945535..a60db2b4ea0 100644 --- a/homeassistant/components/verisure/translations/pt.json +++ b/homeassistant/components/verisure/translations/pt.json @@ -1,11 +1,26 @@ { "config": { "abort": { - "already_configured": "Conta j\u00e1 configurada" + "already_configured": "Conta j\u00e1 configurada", + "reauth_successful": "Reautentica\u00e7\u00e3o bem sucedida" }, "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" + }, + "step": { + "reauth_confirm": { + "data": { + "email": "Email", + "password": "Palavra-passe" + } + }, + "user": { + "data": { + "email": "Email", + "password": "Palavra-passe" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/ru.json b/homeassistant/components/verisure/translations/ru.json new file mode 100644 index 00000000000..430b8d773d0 --- /dev/null +++ b/homeassistant/components/verisure/translations/ru.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant.", + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." + }, + "error": { + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "installation": { + "data": { + "giid": "\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430" + }, + "description": "Home Assistant \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u043b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043e\u043a Verisure \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 \u00ab\u041c\u043e\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b\u00bb \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 Home Assistant." + }, + "reauth_confirm": { + "data": { + "description": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Verisure.", + "email": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + } + }, + "user": { + "data": { + "description": "\u0412\u0445\u043e\u0434 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Verisure.", + "email": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "PIN-\u043a\u043e\u0434 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u043d\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0442\u0440\u0435\u0431\u0443\u0435\u043c\u043e\u043c\u0443 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0443 \u0446\u0438\u0444\u0440." + }, + "step": { + "init": { + "data": { + "lock_code_digits": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0446\u0438\u0444\u0440 \u0432 PIN-\u043a\u043e\u0434\u0435 \u0434\u043b\u044f \u0437\u0430\u043c\u043a\u043e\u0432", + "lock_default_code": "PIN-\u043a\u043e\u0434 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0434\u043b\u044f \u0437\u0430\u043c\u043a\u043e\u0432, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0439, \u0435\u0441\u043b\u0438 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u043e" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/zh-Hant.json b/homeassistant/components/verisure/translations/zh-Hant.json new file mode 100644 index 00000000000..29410e390fe --- /dev/null +++ b/homeassistant/components/verisure/translations/zh-Hant.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" + }, + "error": { + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "installation": { + "data": { + "giid": "\u5b89\u88dd" + }, + "description": "Home Assistant \u65bc My Pages \u5e33\u865f\u4e2d\u627e\u5230\u591a\u500b Verisure \u5b89\u88dd\u3002\u8acb\u9078\u64c7\u6240\u8981\u65b0\u589e\u81f3 Home Assistant \u7684\u9805\u76ee\u3002" + }, + "reauth_confirm": { + "data": { + "description": "\u91cd\u65b0\u8a8d\u8b49 Verisure My Pages \u5e33\u865f\u3002", + "email": "\u96fb\u5b50\u90f5\u4ef6", + "password": "\u5bc6\u78bc" + } + }, + "user": { + "data": { + "description": "\u4ee5 Verisure My Pages \u5e33\u865f\u767b\u5165", + "email": "\u96fb\u5b50\u90f5\u4ef6", + "password": "\u5bc6\u78bc" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "\u9810\u8a2d PIN \u78bc\u8207\u6240\u9700\u6578\u5b57\u6578\u4e0d\u7b26\u5408" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "\u9580\u9396 PIN \u78bc\u6578\u5b57\u6578", + "lock_default_code": "\u9810\u8a2d\u9580\u9396 PIN \u78bc\uff0c\u65bc\u672a\u63d0\u4f9b\u6642\u4f7f\u7528" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vesync/translations/nl.json b/homeassistant/components/vesync/translations/nl.json index 36c7f315bcc..ab330237afd 100644 --- a/homeassistant/components/vesync/translations/nl.json +++ b/homeassistant/components/vesync/translations/nl.json @@ -10,7 +10,7 @@ "user": { "data": { "password": "Wachtwoord", - "username": "E-mailadres" + "username": "E-mail" }, "title": "Voer gebruikersnaam en wachtwoord in" } diff --git a/homeassistant/components/water_heater/translations/de.json b/homeassistant/components/water_heater/translations/de.json index 9169018a5d4..86f61158351 100644 --- a/homeassistant/components/water_heater/translations/de.json +++ b/homeassistant/components/water_heater/translations/de.json @@ -4,5 +4,16 @@ "turn_off": "{entity_name} ausschalten", "turn_on": "{entity_name} einschalten" } + }, + "state": { + "_": { + "eco": "Sparmodus", + "electric": "Elektrisch", + "gas": "Gas", + "heat_pump": "W\u00e4rmepumpe", + "high_demand": "Hoher Bedarf", + "off": "Aus", + "performance": "Leistung" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/it.json b/homeassistant/components/water_heater/translations/it.json index 86458a54272..88c53f67e3a 100644 --- a/homeassistant/components/water_heater/translations/it.json +++ b/homeassistant/components/water_heater/translations/it.json @@ -4,5 +4,16 @@ "turn_off": "Disattiva {entity_name}", "turn_on": "Attiva {entity_name}" } + }, + "state": { + "_": { + "eco": "Eco", + "electric": "Elettrico", + "gas": "Gas", + "heat_pump": "Pompa di calore", + "high_demand": "Forte richiesta", + "off": "Spento", + "performance": "Prestazione" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/nl.json b/homeassistant/components/water_heater/translations/nl.json index 8b832b52c1c..f70a5efc3e7 100644 --- a/homeassistant/components/water_heater/translations/nl.json +++ b/homeassistant/components/water_heater/translations/nl.json @@ -4,5 +4,15 @@ "turn_off": "Schakel {entity_name} uit", "turn_on": "Schakel {entity_name} in" } + }, + "state": { + "_": { + "eco": "Eco", + "electric": "Elektriciteit", + "gas": "Gas", + "heat_pump": "Warmtepomp", + "off": "Uit", + "performance": "Prestaties" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/no.json b/homeassistant/components/water_heater/translations/no.json index 6455919518f..2cbb96b23e7 100644 --- a/homeassistant/components/water_heater/translations/no.json +++ b/homeassistant/components/water_heater/translations/no.json @@ -4,5 +4,16 @@ "turn_off": "Sl\u00e5 av {entity_name}", "turn_on": "Sl\u00e5 p\u00e5 {entity_name}" } + }, + "state": { + "_": { + "eco": "\u00d8ko", + "electric": "Elektrisk", + "gas": "Gass", + "heat_pump": "Varmepumpe", + "high_demand": "H\u00f8y ettersp\u00f8rsel", + "off": "Av", + "performance": "Ytelse" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/pl.json b/homeassistant/components/water_heater/translations/pl.json index d6deb99da02..bb314c85510 100644 --- a/homeassistant/components/water_heater/translations/pl.json +++ b/homeassistant/components/water_heater/translations/pl.json @@ -4,5 +4,16 @@ "turn_off": "wy\u0142\u0105cz {entity_name}", "turn_on": "w\u0142\u0105cz {entity_name}" } + }, + "state": { + "_": { + "eco": "ekonomiczny", + "electric": "elektryczny", + "gas": "gaz", + "heat_pump": "pompa ciep\u0142a", + "high_demand": "du\u017ce zapotrzebowanie", + "off": "wy\u0142.", + "performance": "wydajno\u015b\u0107" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/pt.json b/homeassistant/components/water_heater/translations/pt.json index 072a81b682c..97cc30fe135 100644 --- a/homeassistant/components/water_heater/translations/pt.json +++ b/homeassistant/components/water_heater/translations/pt.json @@ -7,6 +7,11 @@ }, "state": { "_": { + "eco": "Eco", + "electric": "El\u00e9trico", + "gas": "G\u00e1s", + "heat_pump": "Bomba de calor", + "high_demand": "Necessidade alta", "off": "Desligado" } } diff --git a/homeassistant/components/water_heater/translations/zh-Hant.json b/homeassistant/components/water_heater/translations/zh-Hant.json index 11e88cb1fae..5bb7aa129b5 100644 --- a/homeassistant/components/water_heater/translations/zh-Hant.json +++ b/homeassistant/components/water_heater/translations/zh-Hant.json @@ -4,5 +4,16 @@ "turn_off": "\u95dc\u9589{entity_name}", "turn_on": "\u958b\u555f{entity_name}" } + }, + "state": { + "_": { + "eco": "\u7bc0\u80fd", + "electric": "\u96fb\u529b", + "gas": "\u74e6\u65af", + "heat_pump": "\u6696\u6c23", + "high_demand": "\u9ad8\u7528\u91cf", + "off": "\u95dc\u9589", + "performance": "\u6548\u80fd" + } } } \ No newline at end of file diff --git a/homeassistant/components/wemo/translations/nl.json b/homeassistant/components/wemo/translations/nl.json index 6146072623a..e4087863726 100644 --- a/homeassistant/components/wemo/translations/nl.json +++ b/homeassistant/components/wemo/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Geen Wemo-apparaten gevonden op het netwerk.", - "single_instance_allowed": "Slechts een enkele configuratie van Wemo is mogelijk." + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "step": { "confirm": { diff --git a/homeassistant/components/withings/translations/nl.json b/homeassistant/components/withings/translations/nl.json index 5b099b952e5..9f7810a8378 100644 --- a/homeassistant/components/withings/translations/nl.json +++ b/homeassistant/components/withings/translations/nl.json @@ -25,6 +25,7 @@ "title": "Gebruikersprofiel." }, "reauth": { + "description": "Het \"{profile}\" profiel moet opnieuw worden geverifieerd om Withings gegevens te kunnen blijven ontvangen.", "title": "Verifieer de integratie opnieuw" } } diff --git a/homeassistant/components/wolflink/translations/ru.json b/homeassistant/components/wolflink/translations/ru.json index 0b105ad922a..8a430c9a63d 100644 --- a/homeassistant/components/wolflink/translations/ru.json +++ b/homeassistant/components/wolflink/translations/ru.json @@ -18,7 +18,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "WOLF SmartSet" } diff --git a/homeassistant/components/xiaomi_aqara/translations/de.json b/homeassistant/components/xiaomi_aqara/translations/de.json index 61e865ef016..1effe228de6 100644 --- a/homeassistant/components/xiaomi_aqara/translations/de.json +++ b/homeassistant/components/xiaomi_aqara/translations/de.json @@ -17,6 +17,12 @@ }, "description": "F\u00fchre das Setup erneut aus, wenn du zus\u00e4tzliche Gateways verbinden m\u00f6chtest" }, + "settings": { + "data": { + "name": "Name des Gateways" + }, + "title": "Xiaomi Aqara Gateway, optionale Einstellungen" + }, "user": { "data": { "host": "IP-Adresse", diff --git a/homeassistant/components/xiaomi_aqara/translations/nl.json b/homeassistant/components/xiaomi_aqara/translations/nl.json index 0e5283c485e..a356ed36e1b 100644 --- a/homeassistant/components/xiaomi_aqara/translations/nl.json +++ b/homeassistant/components/xiaomi_aqara/translations/nl.json @@ -6,6 +6,7 @@ "not_xiaomi_aqara": "Geen Xiaomi Aqara Gateway, ontdekt apparaat kwam niet overeen met bekende gateways" }, "error": { + "discovery_error": "Het is niet gelukt om een Xiaomi Aqara Gateway te vinden, probeer het IP van het apparaat waarop HomeAssistant draait als interface te gebruiken", "invalid_host": "Ongeldige hostnaam of IP-adres, zie https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem", "invalid_interface": "Ongeldige netwerkinterface", "invalid_key": "Ongeldige gatewaysleutel", @@ -25,7 +26,8 @@ "key": "De sleutel van uw gateway", "name": "Naam van de Gateway" }, - "description": "De sleutel (wachtwoord) kan worden opgehaald met behulp van deze tutorial: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Als de sleutel niet wordt meegeleverd, zijn alleen sensoren toegankelijk" + "description": "De sleutel (wachtwoord) kan worden opgehaald met behulp van deze tutorial: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Als de sleutel niet wordt meegeleverd, zijn alleen sensoren toegankelijk", + "title": "Xiaomi Aqara Gateway, optionele instellingen" }, "user": { "data": { diff --git a/homeassistant/components/zoneminder/translations/ru.json b/homeassistant/components/zoneminder/translations/ru.json index bee720ee09a..f520f0e29bd 100644 --- a/homeassistant/components/zoneminder/translations/ru.json +++ b/homeassistant/components/zoneminder/translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "auth_fail": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", + "auth_fail": "\u041d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 ZoneMinder.", "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." @@ -10,7 +10,7 @@ "default": "\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u0441\u0435\u0440\u0432\u0435\u0440 ZoneMinder." }, "error": { - "auth_fail": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", + "auth_fail": "\u041d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 ZoneMinder.", "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." @@ -24,7 +24,7 @@ "path": "\u041f\u0443\u0442\u044c \u043a ZM", "path_zms": "\u041f\u0443\u0442\u044c \u043a ZMS", "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", - "username": "\u041b\u043e\u0433\u0438\u043d", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" }, "title": "ZoneMinder" diff --git a/homeassistant/components/zwave_js/translations/de.json b/homeassistant/components/zwave_js/translations/de.json index 9ff130605ef..30254e2a523 100644 --- a/homeassistant/components/zwave_js/translations/de.json +++ b/homeassistant/components/zwave_js/translations/de.json @@ -1,25 +1,49 @@ { "config": { "abort": { + "addon_install_failed": "Installation des Z-Wave JS Add-Ons fehlgeschlagen.", + "addon_start_failed": "Starten des Z-Wave JS Add-ons fehlgeschlagen.", "already_configured": "Ger\u00e4t ist bereits konfiguriert", "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "cannot_connect": "Verbindung fehlgeschlagen" }, "error": { + "addon_start_failed": "Fehler beim Starten des Z-Wave JS Add-Ons. \u00dcberpr\u00fcfe die Konfiguration.", "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, + "progress": { + "install_addon": "Bitte warte, w\u00e4hrend die Installation des Z-Wave JS Add-ons abgeschlossen wird. Dies kann einige Minuten dauern.", + "start_addon": "Bitte warte, w\u00e4hrend der Start des Z-Wave JS Add-ons abgeschlossen wird. Dies kann einige Sekunden dauern." + }, "step": { "configure_addon": { "data": { "usb_path": "USB-Ger\u00e4te-Pfad" - } + }, + "title": "Gib die Konfiguration des Z-Wave JS Add-ons ein" + }, + "hassio_confirm": { + "title": "Einrichten der Z-Wave JS Integration mit dem Z-Wave JS Add-on" + }, + "install_addon": { + "title": "Die Installation des Z-Wave-JS-Add-ons hat begonnen" }, "manual": { "data": { "url": "URL" } }, + "on_supervisor": { + "data": { + "use_addon": "Verwende das Z-Wave JS Supervisor Add-on" + }, + "description": "M\u00f6chtest du das Z-Wave JS Supervisor Add-on verwenden?", + "title": "Verbindungstyp ausw\u00e4hlen" + }, + "start_addon": { + "title": "Z-Wave JS Add-on wird gestartet." + }, "user": { "data": { "url": "URL" From 470b0b8b876f71955148103ff5bf2d5fe603d1a9 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Wed, 17 Mar 2021 06:40:20 +0100 Subject: [PATCH 418/831] Fix historic attributes for input_datetime (#45208) --- homeassistant/components/history/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index 1e22e45a892..dd98ba4ef25 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -71,6 +71,7 @@ IGNORE_DOMAINS = ("zone", "scene") NEED_ATTRIBUTE_DOMAINS = { "climate", "humidifier", + "input_datetime", "thermostat", "water_heater", } From bdc8a2878f2afba60df683c91a8db93de94ed7d1 Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Wed, 17 Mar 2021 01:23:54 -0600 Subject: [PATCH 419/831] Fix issue with setting sleep mode during DST (#48001) --- homeassistant/components/litterrobot/hub.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/litterrobot/hub.py b/homeassistant/components/litterrobot/hub.py index 943ef5bfe37..1847f37ed64 100644 --- a/homeassistant/components/litterrobot/hub.py +++ b/homeassistant/components/litterrobot/hub.py @@ -113,9 +113,12 @@ class LitterRobotEntity(CoordinatorEntity): if parsed_time is None: return None - return time( - hour=parsed_time.hour, - minute=parsed_time.minute, - second=parsed_time.second, - tzinfo=dt_util.DEFAULT_TIME_ZONE, + return ( + dt_util.start_of_local_day() + .replace( + hour=parsed_time.hour, + minute=parsed_time.minute, + second=parsed_time.second, + ) + .timetz() ) From ecf93e09e8f6bb8a0a8898243ef0091402700aa4 Mon Sep 17 00:00:00 2001 From: Jim Ekman Date: Wed, 17 Mar 2021 08:50:34 +0100 Subject: [PATCH 420/831] Add support for percentage based fan model in esphome (#46712) --- homeassistant/components/esphome/fan.py | 37 +++++++++++++++---- .../components/esphome/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/esphome/fan.py b/homeassistant/components/esphome/fan.py index 8cf28d6b2aa..bac35fdb93f 100644 --- a/homeassistant/components/esphome/fan.py +++ b/homeassistant/components/esphome/fan.py @@ -1,4 +1,5 @@ """Support for ESPHome fans.""" +import math from typing import Optional from aioesphomeapi import FanDirection, FanInfo, FanSpeed, FanState @@ -16,6 +17,8 @@ from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util.percentage import ( ordered_list_item_to_percentage, percentage_to_ordered_list_item, + percentage_to_ranged_value, + ranged_value_to_percentage, ) from . import ( @@ -62,6 +65,11 @@ class EsphomeFan(EsphomeEntity, FanEntity): def _state(self) -> Optional[FanState]: return super()._state + @property + def _supports_speed_levels(self) -> bool: + api_version = self._client.api_version + return api_version.major == 1 and api_version.minor > 3 + async def async_set_percentage(self, percentage: int) -> None: """Set the speed percentage of the fan.""" if percentage == 0: @@ -70,10 +78,17 @@ class EsphomeFan(EsphomeEntity, FanEntity): data = {"key": self._static_info.key, "state": True} if percentage is not None: - named_speed = percentage_to_ordered_list_item( - ORDERED_NAMED_FAN_SPEEDS, percentage - ) - data["speed"] = named_speed + if self._supports_speed_levels: + data["speed_level"] = math.ceil( + percentage_to_ranged_value( + (1, self._static_info.supported_speed_levels), percentage + ) + ) + else: + named_speed = percentage_to_ordered_list_item( + ORDERED_NAMED_FAN_SPEEDS, percentage + ) + data["speed"] = named_speed await self._client.fan_command(**data) async def async_turn_on( @@ -115,14 +130,22 @@ class EsphomeFan(EsphomeEntity, FanEntity): """Return the current speed percentage.""" if not self._static_info.supports_speed: return None - return ordered_list_item_to_percentage( - ORDERED_NAMED_FAN_SPEEDS, self._state.speed + + if not self._supports_speed_levels: + return ordered_list_item_to_percentage( + ORDERED_NAMED_FAN_SPEEDS, self._state.speed + ) + + return ranged_value_to_percentage( + (1, self._static_info.supported_speed_levels), self._state.speed_level ) @property def speed_count(self) -> int: """Return the number of speeds the fan supports.""" - return len(ORDERED_NAMED_FAN_SPEEDS) + if not self._supports_speed_levels: + return len(ORDERED_NAMED_FAN_SPEEDS) + return self._static_info.supported_speed_levels @esphome_state_property def oscillating(self) -> None: diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index 17ff2ca96ba..e3c609c9fad 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -3,7 +3,7 @@ "name": "ESPHome", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/esphome", - "requirements": ["aioesphomeapi==2.6.5"], + "requirements": ["aioesphomeapi==2.6.6"], "zeroconf": ["_esphomelib._tcp.local."], "codeowners": ["@OttoWinter"], "after_dependencies": ["zeroconf", "tag"] diff --git a/requirements_all.txt b/requirements_all.txt index e685876c09d..0398f053ec3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -154,7 +154,7 @@ aiodns==2.0.0 aioeafm==0.1.2 # homeassistant.components.esphome -aioesphomeapi==2.6.5 +aioesphomeapi==2.6.6 # homeassistant.components.flo aioflo==0.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 00e6121b485..47c10b5700c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -91,7 +91,7 @@ aiodns==2.0.0 aioeafm==0.1.2 # homeassistant.components.esphome -aioesphomeapi==2.6.5 +aioesphomeapi==2.6.6 # homeassistant.components.flo aioflo==0.4.1 From 4f3b7f917ff47a7d9c01a1d0ffe3ed51027b9b34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Mar 2021 09:52:17 +0100 Subject: [PATCH 421/831] Bump codecov/codecov-action from v1.2.2 to v1.3.1 (#48020) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from v1.2.2 to v1.3.1. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v1.2.2...fcebab03f26c7530a22baa63f06b3e0515f0c7cd) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f395a4b3c42..afee814b432 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -739,4 +739,4 @@ jobs: coverage report --fail-under=94 coverage xml - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1.2.2 + uses: codecov/codecov-action@v1.3.1 From 6f7f4955a359e5c711d505366ca58f8ecb58435f Mon Sep 17 00:00:00 2001 From: Nathan Tilley Date: Wed, 17 Mar 2021 06:11:39 -0400 Subject: [PATCH 422/831] Add Logger Check Before Adding Another (#47954) --- script/setup | 2 ++ 1 file changed, 2 insertions(+) diff --git a/script/setup b/script/setup index 46865ecfcb6..f827c3a373f 100755 --- a/script/setup +++ b/script/setup @@ -28,9 +28,11 @@ python3 -m pip install -e . --constraint homeassistant/package_constraints.txt hass --script ensure_config -c config +if ! grep -R "logger" config/configuration.yaml >> /dev/null;then echo " logger: default: info logs: homeassistant.components.cloud: debug " >> config/configuration.yaml +fi \ No newline at end of file From 009e44ed9b7e2c2fad662cfea23e6490d0e24a42 Mon Sep 17 00:00:00 2001 From: sycx2 Date: Wed, 17 Mar 2021 11:30:44 +0100 Subject: [PATCH 423/831] Rewrite tests for Template Light (#41163) --- tests/components/template/test_light.py | 1825 ++++++++++++----------- 1 file changed, 934 insertions(+), 891 deletions(-) diff --git a/tests/components/template/test_light.py b/tests/components/template/test_light.py index 1b99e1cec0a..b3a4b2a1aa4 100644 --- a/tests/components/template/test_light.py +++ b/tests/components/template/test_light.py @@ -4,17 +4,23 @@ import logging import pytest from homeassistant import setup +import homeassistant.components.light as light from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_WHITE_VALUE, ) -from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE -from homeassistant.core import callback +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, + STATE_UNAVAILABLE, +) -from tests.common import assert_setup_component, get_test_home_assistant -from tests.components.light import common +from tests.common import assert_setup_component, async_mock_service _LOGGER = logging.getLogger(__name__) @@ -22,288 +28,319 @@ _LOGGER = logging.getLogger(__name__) _STATE_AVAILABILITY_BOOLEAN = "availability_boolean.state" -class TestTemplateLight: - """Test the Template light.""" +@pytest.fixture(name="calls") +def fixture_calls(hass): + """Track calls to a mock service.""" + return async_mock_service(hass, "test", "automation") - hass = None - calls = None - # pylint: disable=invalid-name - def setup_method(self, method): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.calls = [] - - @callback - def record_call(service): - """Track function calls.""" - self.calls.append(service) - - self.hass.services.register("test", "automation", record_call) - - def teardown_method(self, method): - """Stop everything that was started.""" - self.hass.stop() - - def test_template_state_invalid(self): - """Test template state with render error.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{states.test['big.fat...']}}", - "turn_on": { - "service": "light.turn_on", +async def test_template_state_invalid(hass): + """Test template state with render error.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{states.test['big.fat...']}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { "entity_id": "light.test_state", + "brightness": "{{brightness}}", }, - "turn_off": { - "service": "light.turn_off", + }, + } + }, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.state == STATE_OFF + + +async def test_template_state_text(hass): + """Test the state text of a template.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{ states.light.test_state.state }}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { "entity_id": "light.test_state", + "brightness": "{{brightness}}", }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, + }, + } + }, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.async_set("light.test_state", STATE_ON) + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.state == STATE_ON + + state = hass.states.async_set("light.test_state", STATE_OFF) + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.state == STATE_OFF + + +@pytest.mark.parametrize( + "expected_state,template", + [(STATE_ON, "{{ 1 == 1 }}"), (STATE_OFF, "{{ 1 == 2 }}")], +) +async def test_template_state_boolean(hass, expected_state, template): + """Test the setting of the state with boolean on.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": template, + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", }, - } + }, + } + }, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.state == expected_state + + +async def test_template_syntax_error(hass): + """Test templating syntax error.""" + with assert_setup_component(0, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{%- if false -%}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", + }, + }, + } + }, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert hass.states.async_all() == [] + + +async def test_invalid_name_does_not_create(hass): + """Test invalid name.""" + with assert_setup_component(0, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "bad name here": { + "value_template": "{{ 1== 1}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", + }, + }, + } + }, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert hass.states.async_all() == [] + + +async def test_invalid_light_does_not_create(hass): + """Test invalid light.""" + with assert_setup_component(0, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "switches": {"test_template_light": "Invalid"}, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert hass.states.async_all() == [] + + +async def test_no_lights_does_not_create(hass): + """Test if there are no lights no creation.""" + with assert_setup_component(0, light.DOMAIN): + assert await setup.async_setup_component( + hass, "light", {"light": {"platform": "template"}} + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert hass.states.async_all() == [] + + +@pytest.mark.parametrize( + "missing_key, count", [("value_template", 1), ("turn_on", 0), ("turn_off", 0)] +) +async def test_missing_key(hass, missing_key, count): + """Test missing template.""" + light_config = { + "light": { + "platform": "template", + "lights": { + "light_one": { + "value_template": "{{ 1== 1}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", }, - } - }, - ) + }, + } + }, + } + } - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() + del light_config["light"]["lights"]["light_one"][missing_key] + with assert_setup_component(count, light.DOMAIN): + assert await setup.async_setup_component(hass, "light", light_config) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() - state = self.hass.states.get("light.test_template_light") - assert state.state == STATE_OFF + if count: + assert hass.states.async_all() != [] + else: + assert hass.states.async_all() == [] - def test_template_state_text(self): - """Test the state text of a template.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{ states.light.test_state.state }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, - } - }, - ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.set("light.test_state", STATE_ON) - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state.state == STATE_ON - - state = self.hass.states.set("light.test_state", STATE_OFF) - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state.state == STATE_OFF - - @pytest.mark.parametrize( - "expected_state,template", - [(STATE_ON, "{{ 1 == 1 }}"), (STATE_OFF, "{{ 1 == 2 }}")], - ) - def test_template_state_boolean(self, expected_state, template): - """Test the setting of the state with boolean on.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": template, - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, - } - }, - ) - - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state.state == expected_state - - def test_template_syntax_error(self): - """Test templating syntax error.""" - with assert_setup_component(0, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{%- if false -%}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, - } - }, - ) - - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - assert self.hass.states.all() == [] - - def test_invalid_name_does_not_create(self): - """Test invalid name.""" - with assert_setup_component(0, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "bad name here": { - "value_template": "{{ 1== 1}}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, - } - }, - ) - - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - assert self.hass.states.all() == [] - - def test_invalid_light_does_not_create(self): - """Test invalid light.""" - with assert_setup_component(0, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "switches": {"test_template_light": "Invalid"}, - } - }, - ) - - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - assert self.hass.states.all() == [] - - def test_no_lights_does_not_create(self): - """Test if there are no lights no creation.""" - with assert_setup_component(0, "light"): - assert setup.setup_component( - self.hass, "light", {"light": {"platform": "template"}} - ) - - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - assert self.hass.states.all() == [] - - @pytest.mark.parametrize( - "missing_key, count", [("value_template", 1), ("turn_on", 0), ("turn_off", 0)] - ) - def test_missing_key(self, missing_key, count): - """Test missing template.""" - light = { +async def test_on_action(hass, calls): + """Test on action.""" + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { "light": { "platform": "template", "lights": { - "light_one": { - "value_template": "{{ 1== 1}}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, + "test_template_light": { + "value_template": "{{states.light.test_state.state}}", + "turn_on": {"service": "test.automation"}, "turn_off": { "service": "light.turn_off", "entity_id": "light.test_state", @@ -318,206 +355,250 @@ class TestTemplateLight: } }, } - } + }, + ) - del light["light"]["lights"]["light_one"][missing_key] - with assert_setup_component(count, "light"): - assert setup.setup_component(self.hass, "light", light) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() - if count: - assert self.hass.states.all() != [] - else: - assert self.hass.states.all() == [] + hass.states.async_set("light.test_state", STATE_OFF) + await hass.async_block_till_done() - def test_on_action(self): - """Test on action.""" - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{states.light.test_state.state}}", - "turn_on": {"service": "test.automation"}, - "turn_off": { - "service": "light.turn_off", + state = hass.states.get("light.test_template_light") + assert state.state == STATE_OFF + + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.test_template_light"}, + blocking=True, + ) + + assert len(calls) == 1 + + +async def test_on_action_optimistic(hass, calls): + """Test on action with optimistic state.""" + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "turn_on": {"service": "test.automation"}, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { "entity_id": "light.test_state", + "brightness": "{{brightness}}", }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, - } - }, - ) + }, + } + }, + } + }, + ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() - self.hass.states.set("light.test_state", STATE_OFF) - self.hass.block_till_done() + hass.states.async_set("light.test_state", STATE_OFF) + await hass.async_block_till_done() - state = self.hass.states.get("light.test_template_light") - assert state.state == STATE_OFF + state = hass.states.get("light.test_template_light") + assert state.state == STATE_OFF - common.turn_on(self.hass, "light.test_template_light") - self.hass.block_till_done() + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.test_template_light"}, + blocking=True, + ) - assert len(self.calls) == 1 + state = hass.states.get("light.test_template_light") + assert len(calls) == 1 + assert state.state == STATE_ON - def test_on_action_optimistic(self): - """Test on action with optimistic state.""" - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "turn_on": {"service": "test.automation"}, - "turn_off": { - "service": "light.turn_off", + +async def test_off_action(hass, calls): + """Test off action.""" + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{states.light.test_state.state}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": {"service": "test.automation"}, + "set_level": { + "service": "light.turn_on", + "data_template": { "entity_id": "light.test_state", + "brightness": "{{brightness}}", }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, - } - }, - ) + }, + } + }, + } + }, + ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() - self.hass.states.set("light.test_state", STATE_OFF) - self.hass.block_till_done() + hass.states.async_set("light.test_state", STATE_ON) + await hass.async_block_till_done() - state = self.hass.states.get("light.test_template_light") - assert state.state == STATE_OFF + state = hass.states.get("light.test_template_light") + assert state.state == STATE_ON - common.turn_on(self.hass, "light.test_template_light") - self.hass.block_till_done() + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.test_template_light"}, + blocking=True, + ) - state = self.hass.states.get("light.test_template_light") - assert len(self.calls) == 1 - assert state.state == STATE_ON + assert len(calls) == 1 - def test_off_action(self): - """Test off action.""" - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{states.light.test_state.state}}", - "turn_on": { - "service": "light.turn_on", + +async def test_off_action_optimistic(hass, calls): + """Test off action with optimistic state.""" + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": {"service": "test.automation"}, + "set_level": { + "service": "light.turn_on", + "data_template": { "entity_id": "light.test_state", + "brightness": "{{brightness}}", }, - "turn_off": {"service": "test.automation"}, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, + }, + } + }, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.state == STATE_OFF + + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.test_template_light"}, + blocking=True, + ) + + assert len(calls) == 1 + state = hass.states.get("light.test_template_light") + assert state.state == STATE_OFF + + +async def test_white_value_action_no_template(hass, calls): + """Test setting white value with optimistic template.""" + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{1 == 1}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_white_value": { + "service": "test.automation", + "data_template": { + "entity_id": "test.test_state", + "white_value": "{{white_value}}", }, - } - }, - } - }, - ) + }, + } + }, + } + }, + ) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() + state = hass.states.get("light.test_template_light") + assert state.attributes.get("white_value") is None - self.hass.states.set("light.test_state", STATE_ON) - self.hass.block_till_done() + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.test_template_light", ATTR_WHITE_VALUE: 124}, + blocking=True, + ) - state = self.hass.states.get("light.test_template_light") - assert state.state == STATE_ON + assert len(calls) == 1 + assert calls[0].data["white_value"] == 124 - common.turn_off(self.hass, "light.test_template_light") - self.hass.block_till_done() + state = hass.states.get("light.test_template_light") + assert state is not None + assert state.attributes.get("white_value") == 124 - assert len(self.calls) == 1 - def test_off_action_optimistic(self): - """Test off action with optimistic state.""" - assert setup.setup_component( - self.hass, - "light", +@pytest.mark.parametrize( + "expected_white_value,template", + [ + (255, "{{255}}"), + (None, "{{256}}"), + (None, "{{x - 12}}"), + (None, "{{ none }}"), + (None, ""), + ], +) +async def test_white_value_template(hass, expected_white_value, template): + """Test the template for the white value.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, { "light": { "platform": "template", "lights": { "test_template_light": { - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": {"service": "test.automation"}, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, - } - }, - ) - - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state.state == STATE_OFF - - common.turn_off(self.hass, "light.test_template_light") - self.hass.block_till_done() - - assert len(self.calls) == 1 - state = self.hass.states.get("light.test_template_light") - assert state.state == STATE_OFF - - def test_white_value_action_no_template(self): - """Test setting white value with optimistic template.""" - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{1 == 1}}", + "value_template": "{{ 1 == 1 }}", "turn_on": { "service": "light.turn_on", "entity_id": "light.test_state", @@ -527,98 +608,104 @@ class TestTemplateLight: "entity_id": "light.test_state", }, "set_white_value": { - "service": "test.automation", + "service": "light.turn_on", "data_template": { - "entity_id": "test.test_state", + "entity_id": "light.test_state", "white_value": "{{white_value}}", }, }, + "white_value_template": template, } }, } }, ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - state = self.hass.states.get("light.test_template_light") - assert state.attributes.get("white_value") is None + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() - common.turn_on( - self.hass, "light.test_template_light", **{ATTR_WHITE_VALUE: 124} - ) - self.hass.block_till_done() - assert len(self.calls) == 1 - assert self.calls[0].data["white_value"] == 124 + state = hass.states.get("light.test_template_light") + assert state is not None + assert state.attributes.get("white_value") == expected_white_value - state = self.hass.states.get("light.test_template_light") - assert state is not None - assert state.attributes.get("white_value") == 124 - @pytest.mark.parametrize( - "expected_white_value,template", - [ - (255, "{{255}}"), - (None, "{{256}}"), - (None, "{{x - 12}}"), - (None, "{{ none }}"), - (None, ""), - ], - ) - def test_white_value_template(self, expected_white_value, template): - """Test the template for the white value.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_white_value": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "white_value": "{{white_value}}", - }, - }, - "white_value_template": template, - } +async def test_level_action_no_template(hass, calls): + """Test setting brightness with optimistic template.""" + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{1 == 1}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "test.automation", + "data_template": { + "entity_id": "test.test_state", + "brightness": "{{brightness}}", + }, }, } }, - ) + } + }, + ) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() + state = hass.states.get("light.test_template_light") + assert state.attributes.get("brightness") is None - state = self.hass.states.get("light.test_template_light") - assert state is not None - assert state.attributes.get("white_value") == expected_white_value + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.test_template_light", ATTR_BRIGHTNESS: 124}, + blocking=True, + ) - def test_level_action_no_template(self): - """Test setting brightness with optimistic template.""" - assert setup.setup_component( - self.hass, - "light", + assert len(calls) == 1 + assert calls[0].data["brightness"] == 124 + + state = hass.states.get("light.test_template_light") + _LOGGER.info(str(state.attributes)) + assert state is not None + assert state.attributes.get("brightness") == 124 + + +@pytest.mark.parametrize( + "expected_level,template", + [ + (255, "{{255}}"), + (None, "{{256}}"), + (None, "{{x - 12}}"), + (None, "{{ none }}"), + (None, ""), + ], +) +async def test_level_template(hass, expected_level, template): + """Test the template for the level.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, { "light": { "platform": "template", "lights": { "test_template_light": { - "value_template": "{{1 == 1}}", + "value_template": "{{ 1 == 1 }}", "turn_on": { "service": "light.turn_on", "entity_id": "light.test_state", @@ -628,150 +715,51 @@ class TestTemplateLight: "entity_id": "light.test_state", }, "set_level": { - "service": "test.automation", + "service": "light.turn_on", "data_template": { - "entity_id": "test.test_state", + "entity_id": "light.test_state", "brightness": "{{brightness}}", }, }, + "level_template": template, } }, } }, ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - state = self.hass.states.get("light.test_template_light") - assert state.attributes.get("brightness") is None + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() - common.turn_on(self.hass, "light.test_template_light", **{ATTR_BRIGHTNESS: 124}) - self.hass.block_till_done() - assert len(self.calls) == 1 - assert self.calls[0].data["brightness"] == 124 + state = hass.states.get("light.test_template_light") + assert state is not None + assert state.attributes.get("brightness") == expected_level - state = self.hass.states.get("light.test_template_light") - _LOGGER.info(str(state.attributes)) - assert state is not None - assert state.attributes.get("brightness") == 124 - @pytest.mark.parametrize( - "expected_level,template", - [ - (255, "{{255}}"), - (None, "{{256}}"), - (None, "{{x - 12}}"), - (None, "{{ none }}"), - (None, ""), - ], - ) - def test_level_template(self, expected_level, template): - """Test the template for the level.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - "level_template": template, - } - }, - } - }, - ) - - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state is not None - assert state.attributes.get("brightness") == expected_level - - @pytest.mark.parametrize( - "expected_temp,template", - [ - (500, "{{500}}"), - (None, "{{501}}"), - (None, "{{x - 12}}"), - (None, "None"), - (None, "{{ none }}"), - (None, ""), - ], - ) - def test_temperature_template(self, expected_temp, template): - """Test the template for the temperature.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_temperature": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "color_temp": "{{color_temp}}", - }, - }, - "temperature_template": template, - } - }, - } - }, - ) - - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state is not None - assert state.attributes.get("color_temp") == expected_temp - - def test_temperature_action_no_template(self): - """Test setting temperature with optimistic template.""" - assert setup.setup_component( - self.hass, - "light", +@pytest.mark.parametrize( + "expected_temp,template", + [ + (500, "{{500}}"), + (None, "{{501}}"), + (None, "{{x - 12}}"), + (None, "None"), + (None, "{{ none }}"), + (None, ""), + ], +) +async def test_temperature_template(hass, expected_temp, template): + """Test the template for the temperature.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, { "light": { "platform": "template", "lights": { "test_template_light": { - "value_template": "{{1 == 1}}", + "value_template": "{{ 1 == 1 }}", "turn_on": { "service": "light.turn_on", "entity_id": "light.test_state", @@ -781,190 +769,325 @@ class TestTemplateLight: "entity_id": "light.test_state", }, "set_temperature": { - "service": "test.automation", + "service": "light.turn_on", "data_template": { - "entity_id": "test.test_state", + "entity_id": "light.test_state", "color_temp": "{{color_temp}}", }, }, + "temperature_template": template, + } + }, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state is not None + assert state.attributes.get("color_temp") == expected_temp + + +async def test_temperature_action_no_template(hass, calls): + """Test setting temperature with optimistic template.""" + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{1 == 1}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_temperature": { + "service": "test.automation", + "data_template": { + "entity_id": "test.test_state", + "color_temp": "{{color_temp}}", + }, + }, + } + }, + } + }, + ) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.attributes.get("color_template") is None + + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.test_template_light", ATTR_COLOR_TEMP: 345}, + blocking=True, + ) + + assert len(calls) == 1 + assert calls[0].data["color_temp"] == 345 + + state = hass.states.get("light.test_template_light") + _LOGGER.info(str(state.attributes)) + assert state is not None + assert state.attributes.get("color_temp") == 345 + + +async def test_friendly_name(hass): + """Test the accessibility of the friendly_name attribute.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "friendly_name": "Template light", + "value_template": "{{ 1 == 1 }}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", + }, + }, } }, } }, ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - state = self.hass.states.get("light.test_template_light") - assert state.attributes.get("color_template") is None + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() - common.turn_on(self.hass, "light.test_template_light", **{ATTR_COLOR_TEMP: 345}) - self.hass.block_till_done() - assert len(self.calls) == 1 - assert self.calls[0].data["color_temp"] == 345 + state = hass.states.get("light.test_template_light") + assert state is not None - state = self.hass.states.get("light.test_template_light") - _LOGGER.info(str(state.attributes)) - assert state is not None - assert state.attributes.get("color_temp") == 345 + assert state.attributes.get("friendly_name") == "Template light" - def test_friendly_name(self): - """Test the accessibility of the friendly_name attribute.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "friendly_name": "Template light", - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, - } - }, - ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state is not None - - assert state.attributes.get("friendly_name") == "Template light" - - def test_icon_template(self): - """Test icon template.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "friendly_name": "Template light", - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - "icon_template": "{% if states.light.test_state.state %}" - "mdi:check" - "{% endif %}", - } - }, - } - }, - ) - - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state.attributes.get("icon") == "" - - state = self.hass.states.set("light.test_state", STATE_ON) - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - - assert state.attributes["icon"] == "mdi:check" - - def test_entity_picture_template(self): - """Test entity_picture template.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "friendly_name": "Template light", - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - "entity_picture_template": "{% if states.light.test_state.state %}" - "/local/light.png" - "{% endif %}", - } - }, - } - }, - ) - - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state.attributes.get("entity_picture") == "" - - state = self.hass.states.set("light.test_state", STATE_ON) - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - - assert state.attributes["entity_picture"] == "/local/light.png" - - def test_color_action_no_template(self): - """Test setting color with optimistic template.""" - assert setup.setup_component( - self.hass, - "light", +async def test_icon_template(hass): + """Test icon template.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, { "light": { "platform": "template", "lights": { "test_template_light": { - "value_template": "{{1 == 1}}", + "friendly_name": "Template light", + "value_template": "{{ 1 == 1 }}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", + }, + }, + "icon_template": "{% if states.light.test_state.state %}" + "mdi:check" + "{% endif %}", + } + }, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.attributes.get("icon") == "" + + state = hass.states.async_set("light.test_state", STATE_ON) + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + + assert state.attributes["icon"] == "mdi:check" + + +async def test_entity_picture_template(hass): + """Test entity_picture template.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "friendly_name": "Template light", + "value_template": "{{ 1 == 1 }}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", + }, + }, + "entity_picture_template": "{% if states.light.test_state.state %}" + "/local/light.png" + "{% endif %}", + } + }, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.attributes.get("entity_picture") == "" + + state = hass.states.async_set("light.test_state", STATE_ON) + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + + assert state.attributes["entity_picture"] == "/local/light.png" + + +async def test_color_action_no_template(hass, calls): + """Test setting color with optimistic template.""" + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{1 == 1}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_color": [ + { + "service": "test.automation", + "data_template": { + "entity_id": "test.test_state", + "h": "{{h}}", + "s": "{{s}}", + }, + }, + { + "service": "test.automation", + "data_template": { + "entity_id": "test.test_state", + "s": "{{s}}", + "h": "{{h}}", + }, + }, + ], + } + }, + } + }, + ) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.attributes.get("hs_color") is None + + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.test_template_light", ATTR_HS_COLOR: (40, 50)}, + blocking=True, + ) + + assert len(calls) == 2 + assert calls[0].data["h"] == 40 + assert calls[0].data["s"] == 50 + assert calls[1].data["h"] == 40 + assert calls[1].data["s"] == 50 + + state = hass.states.get("light.test_template_light") + _LOGGER.info(str(state.attributes)) + assert state is not None + assert calls[0].data["h"] == 40 + assert calls[0].data["s"] == 50 + assert calls[1].data["h"] == 40 + assert calls[1].data["s"] == 50 + + +@pytest.mark.parametrize( + "expected_hs,template", + [ + ((360, 100), "{{(360, 100)}}"), + ((359.9, 99.9), "{{(359.9, 99.9)}}"), + (None, "{{(361, 100)}}"), + (None, "{{(360, 101)}}"), + (None, "{{x - 12}}"), + (None, ""), + (None, "{{ none }}"), + ], +) +async def test_color_template(hass, expected_hs, template): + """Test the template for the color.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{ 1 == 1 }}", "turn_on": { "service": "light.turn_on", "entity_id": "light.test_state", @@ -975,112 +1098,32 @@ class TestTemplateLight: }, "set_color": [ { - "service": "test.automation", + "service": "input_number.set_value", "data_template": { - "entity_id": "test.test_state", - "h": "{{h}}", - "s": "{{s}}", + "entity_id": "input_number.h", + "color_temp": "{{h}}", }, - }, - { - "service": "test.automation", - "data_template": { - "entity_id": "test.test_state", - "s": "{{s}}", - "h": "{{h}}", - }, - }, + } ], + "color_template": template, } }, } }, ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state.attributes.get("hs_color") is None - - common.turn_on( - self.hass, "light.test_template_light", **{ATTR_HS_COLOR: (40, 50)} - ) - self.hass.block_till_done() - assert len(self.calls) == 2 - assert self.calls[0].data["h"] == 40 - assert self.calls[0].data["s"] == 50 - assert self.calls[1].data["h"] == 40 - assert self.calls[1].data["s"] == 50 - - state = self.hass.states.get("light.test_template_light") - _LOGGER.info(str(state.attributes)) - assert state is not None - assert self.calls[0].data["h"] == 40 - assert self.calls[0].data["s"] == 50 - assert self.calls[1].data["h"] == 40 - assert self.calls[1].data["s"] == 50 - - @pytest.mark.parametrize( - "expected_hs,template", - [ - ((360, 100), "{{(360, 100)}}"), - ((359.9, 99.9), "{{(359.9, 99.9)}}"), - (None, "{{(361, 100)}}"), - (None, "{{(360, 101)}}"), - (None, "{{x - 12}}"), - (None, ""), - (None, "{{ none }}"), - ], - ) - def test_color_template(self, expected_hs, template): - """Test the template for the color.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_color": [ - { - "service": "input_number.set_value", - "data_template": { - "entity_id": "input_number.h", - "color_temp": "{{h}}", - }, - } - ], - "color_template": template, - } - }, - } - }, - ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - state = self.hass.states.get("light.test_template_light") - assert state is not None - assert state.attributes.get("hs_color") == expected_hs + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + state = hass.states.get("light.test_template_light") + assert state is not None + assert state.attributes.get("hs_color") == expected_hs async def test_available_template_with_entities(hass): """Test availability templates with values from other entities.""" await setup.async_setup_component( hass, - "light", + light.DOMAIN, { "light": { "platform": "template", @@ -1130,7 +1173,7 @@ async def test_invalid_availability_template_keeps_component_available(hass, cap """Test that an invalid availability keeps the device available.""" await setup.async_setup_component( hass, - "light", + light.DOMAIN, { "light": { "platform": "template", @@ -1170,7 +1213,7 @@ async def test_unique_id(hass): """Test unique_id option only creates one light per id.""" await setup.async_setup_component( hass, - "light", + light.DOMAIN, { "light": { "platform": "template", From a1ff45cbf29abefd283d86bdf870663757a3b28e Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 17 Mar 2021 14:32:13 +0100 Subject: [PATCH 424/831] Update metadata license string (#46899) --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index dbdb61b5fcf..d5f8b9da16b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -license = Apache License 2.0 +license = Apache-2.0 license_file = LICENSE.md platforms = any description = Open-source home automation platform running on Python 3. From 6a24ec7a3097213024cc3fd4d377430980e9ed69 Mon Sep 17 00:00:00 2001 From: schiermi Date: Wed, 17 Mar 2021 15:32:18 +0100 Subject: [PATCH 425/831] Fix workday sensor to honor timezone (#47927) --- homeassistant/components/workday/binary_sensor.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/workday/binary_sensor.py b/homeassistant/components/workday/binary_sensor.py index 44f30cf9538..7a414035900 100644 --- a/homeassistant/components/workday/binary_sensor.py +++ b/homeassistant/components/workday/binary_sensor.py @@ -1,5 +1,5 @@ """Sensor to indicate whether the current day is a workday.""" -from datetime import datetime, timedelta +from datetime import timedelta import logging from typing import Any @@ -9,6 +9,7 @@ import voluptuous as vol from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import CONF_NAME, WEEKDAYS import homeassistant.helpers.config_validation as cv +import homeassistant.util.dt as dt _LOGGER = logging.getLogger(__name__) @@ -77,7 +78,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): sensor_name = config[CONF_NAME] workdays = config[CONF_WORKDAYS] - year = (get_date(datetime.today()) + timedelta(days=days_offset)).year + year = (get_date(dt.now()) + timedelta(days=days_offset)).year obj_holidays = getattr(holidays, country)(years=year) if province: @@ -185,7 +186,7 @@ class IsWorkdaySensor(BinarySensorEntity): self._state = False # Get ISO day of the week (1 = Monday, 7 = Sunday) - date = get_date(datetime.today()) + timedelta(days=self._days_offset) + date = get_date(dt.now()) + timedelta(days=self._days_offset) day = date.isoweekday() - 1 day_of_week = day_to_string(day) From 9011a54e7f0c8b3a10fc648b9a2bdd8660efb42b Mon Sep 17 00:00:00 2001 From: Matthew Donoughe Date: Wed, 17 Mar 2021 10:32:44 -0400 Subject: [PATCH 426/831] Switch history tests to pytest (#42318) Co-authored-by: Martin Hjelmare --- tests/components/history/conftest.py | 49 + tests/components/history/test_init.py | 1361 ++++++++++++------------- 2 files changed, 711 insertions(+), 699 deletions(-) create mode 100644 tests/components/history/conftest.py diff --git a/tests/components/history/conftest.py b/tests/components/history/conftest.py new file mode 100644 index 00000000000..35829ece721 --- /dev/null +++ b/tests/components/history/conftest.py @@ -0,0 +1,49 @@ +"""Fixtures for history tests.""" +import pytest + +from homeassistant.components import history +from homeassistant.components.recorder.const import DATA_INSTANCE +from homeassistant.setup import setup_component + +from tests.common import get_test_home_assistant, init_recorder_component + + +@pytest.fixture +def hass_recorder(): + """Home Assistant fixture with in-memory recorder.""" + hass = get_test_home_assistant() + + def setup_recorder(config=None): + """Set up with params.""" + init_recorder_component(hass, config) + hass.start() + hass.block_till_done() + hass.data[DATA_INSTANCE].block_till_done() + return hass + + yield setup_recorder + hass.stop() + + +@pytest.fixture +def hass_history(hass_recorder): + """Home Assistant fixture with history.""" + hass = hass_recorder() + + config = history.CONFIG_SCHEMA( + { + history.DOMAIN: { + history.CONF_INCLUDE: { + history.CONF_DOMAINS: ["media_player"], + history.CONF_ENTITIES: ["thermostat.test"], + }, + history.CONF_EXCLUDE: { + history.CONF_DOMAINS: ["thermostat"], + history.CONF_ENTITIES: ["media_player.test"], + }, + } + } + ) + assert setup_component(hass, history.DOMAIN, config) + + yield hass diff --git a/tests/components/history/test_init.py b/tests/components/history/test_init.py index 87e18305b8a..f4d1c817858 100644 --- a/tests/components/history/test_init.py +++ b/tests/components/history/test_init.py @@ -3,760 +3,723 @@ from copy import copy from datetime import timedelta import json -import unittest from unittest.mock import patch, sentinel +import pytest + from homeassistant.components import history, recorder from homeassistant.components.recorder.models import process_timestamp import homeassistant.core as ha from homeassistant.helpers.json import JSONEncoder -from homeassistant.setup import async_setup_component, setup_component +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from tests.common import ( - get_test_home_assistant, - init_recorder_component, - mock_state_change_event, -) +from tests.common import init_recorder_component, mock_state_change_event from tests.components.recorder.common import trigger_db_commit, wait_recording_done -class TestComponentHistory(unittest.TestCase): - """Test History component.""" +@pytest.mark.usefixtures("hass_history") +def test_setup(): + """Test setup method of history.""" + # Verification occurs in the fixture + pass - def setUp(self): # pylint: disable=invalid-name - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.addCleanup(self.tear_down_cleanup) - def tear_down_cleanup(self): - """Stop everything that was started.""" - self.hass.stop() +def test_get_states(hass_history): + """Test getting states at a specific point in time.""" + hass = hass_history + states = [] - def init_recorder(self): - """Initialize the recorder.""" - init_recorder_component(self.hass) - self.hass.start() - wait_recording_done(self.hass) + now = dt_util.utcnow() + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=now): + for i in range(5): + state = ha.State( + "test.point_in_time_{}".format(i % 5), + f"State {i}", + {"attribute_test": i}, + ) - def test_setup(self): - """Test setup method of history.""" - config = history.CONFIG_SCHEMA( - { - # ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_INCLUDE: { - history.CONF_DOMAINS: ["media_player"], - history.CONF_ENTITIES: ["thermostat.test"], - }, - history.CONF_EXCLUDE: { - history.CONF_DOMAINS: ["thermostat"], - history.CONF_ENTITIES: ["media_player.test"], - }, - } - } + mock_state_change_event(hass, state) + + states.append(state) + + wait_recording_done(hass) + + future = now + timedelta(seconds=1) + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=future): + for i in range(5): + state = ha.State( + "test.point_in_time_{}".format(i % 5), + f"State {i}", + {"attribute_test": i}, + ) + + mock_state_change_event(hass, state) + + wait_recording_done(hass) + + # Get states returns everything before POINT + for state1, state2 in zip( + states, + sorted(history.get_states(hass, future), key=lambda state: state.entity_id), + ): + assert state1 == state2 + + # Test get_state here because we have a DB setup + assert states[0] == history.get_state(hass, future, states[0].entity_id) + + time_before_recorder_ran = now - timedelta(days=1000) + assert history.get_states(hass, time_before_recorder_ran) == [] + + assert history.get_state(hass, time_before_recorder_ran, "demo.id") is None + + +def test_state_changes_during_period(hass_history): + """Test state change during period.""" + hass = hass_history + entity_id = "media_player.test" + + def set_state(state): + """Set the state.""" + hass.states.set(entity_id, state) + wait_recording_done(hass) + return hass.states.get(entity_id) + + start = dt_util.utcnow() + point = start + timedelta(seconds=1) + end = point + timedelta(seconds=1) + + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=start): + set_state("idle") + set_state("YouTube") + + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=point): + states = [ + set_state("idle"), + set_state("Netflix"), + set_state("Plex"), + set_state("YouTube"), + ] + + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=end): + set_state("Netflix") + set_state("Plex") + + hist = history.state_changes_during_period(hass, start, end, entity_id) + + assert states == hist[entity_id] + + +def test_get_last_state_changes(hass_history): + """Test number of state changes.""" + hass = hass_history + entity_id = "sensor.test" + + def set_state(state): + """Set the state.""" + hass.states.set(entity_id, state) + wait_recording_done(hass) + return hass.states.get(entity_id) + + start = dt_util.utcnow() - timedelta(minutes=2) + point = start + timedelta(minutes=1) + point2 = point + timedelta(minutes=1) + + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=start): + set_state("1") + + states = [] + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=point): + states.append(set_state("2")) + + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=point2): + states.append(set_state("3")) + + hist = history.get_last_state_changes(hass, 2, entity_id) + + assert states == hist[entity_id] + + +def test_ensure_state_can_be_copied(hass_history): + """Ensure a state can pass though copy(). + + The filter integration uses copy() on states + from history. + """ + hass = hass_history + entity_id = "sensor.test" + + def set_state(state): + """Set the state.""" + hass.states.set(entity_id, state) + wait_recording_done(hass) + return hass.states.get(entity_id) + + start = dt_util.utcnow() - timedelta(minutes=2) + point = start + timedelta(minutes=1) + + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=start): + set_state("1") + + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=point): + set_state("2") + + hist = history.get_last_state_changes(hass, 2, entity_id) + + assert copy(hist[entity_id][0]) == hist[entity_id][0] + assert copy(hist[entity_id][1]) == hist[entity_id][1] + + +def test_get_significant_states(hass_history): + """Test that only significant states are returned. + + We should get back every thermostat change that + includes an attribute change, but only the state updates for + media player (attribute changes are not significant and not returned). + """ + hass = hass_history + zero, four, states = record_states(hass) + hist = history.get_significant_states(hass, zero, four, filters=history.Filters()) + assert states == hist + + +def test_get_significant_states_minimal_response(hass_history): + """Test that only significant states are returned. + + When minimal responses is set only the first and + last states return a complete state. + + We should get back every thermostat change that + includes an attribute change, but only the state updates for + media player (attribute changes are not significant and not returned). + """ + hass = hass_history + zero, four, states = record_states(hass) + hist = history.get_significant_states( + hass, zero, four, filters=history.Filters(), minimal_response=True + ) + + # The second media_player.test state is reduced + # down to last_changed and state when minimal_response + # is set. We use JSONEncoder to make sure that are + # pre-encoded last_changed is always the same as what + # will happen with encoding a native state + input_state = states["media_player.test"][1] + orig_last_changed = json.dumps( + process_timestamp(input_state.last_changed), + cls=JSONEncoder, + ).replace('"', "") + orig_state = input_state.state + states["media_player.test"][1] = { + "last_changed": orig_last_changed, + "state": orig_state, + } + + assert states == hist + + +def test_get_significant_states_with_initial(hass_history): + """Test that only significant states are returned. + + We should get back every thermostat change that + includes an attribute change, but only the state updates for + media player (attribute changes are not significant and not returned). + """ + hass = hass_history + zero, four, states = record_states(hass) + one = zero + timedelta(seconds=1) + one_and_half = zero + timedelta(seconds=1.5) + for entity_id in states: + if entity_id == "media_player.test": + states[entity_id] = states[entity_id][1:] + for state in states[entity_id]: + if state.last_changed == one: + state.last_changed = one_and_half + + hist = history.get_significant_states( + hass, + one_and_half, + four, + filters=history.Filters(), + include_start_time_state=True, + ) + assert states == hist + + +def test_get_significant_states_without_initial(hass_history): + """Test that only significant states are returned. + + We should get back every thermostat change that + includes an attribute change, but only the state updates for + media player (attribute changes are not significant and not returned). + """ + hass = hass_history + zero, four, states = record_states(hass) + one = zero + timedelta(seconds=1) + one_and_half = zero + timedelta(seconds=1.5) + for entity_id in states: + states[entity_id] = list( + filter(lambda s: s.last_changed != one, states[entity_id]) ) - self.init_recorder() - assert setup_component(self.hass, history.DOMAIN, config) + del states["media_player.test2"] - def test_get_states(self): - """Test getting states at a specific point in time.""" - self.test_setup() - states = [] + hist = history.get_significant_states( + hass, + one_and_half, + four, + filters=history.Filters(), + include_start_time_state=False, + ) + assert states == hist - now = dt_util.utcnow() - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=now - ): - for i in range(5): - state = ha.State( - "test.point_in_time_{}".format(i % 5), - f"State {i}", - {"attribute_test": i}, - ) - mock_state_change_event(self.hass, state) +def test_get_significant_states_entity_id(hass_history): + """Test that only significant states are returned for one entity.""" + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test2"] + del states["media_player.test3"] + del states["thermostat.test"] + del states["thermostat.test2"] + del states["script.can_cancel_this_one"] - states.append(state) + hist = history.get_significant_states( + hass, zero, four, ["media_player.test"], filters=history.Filters() + ) + assert states == hist - wait_recording_done(self.hass) - future = now + timedelta(seconds=1) - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=future - ): - for i in range(5): - state = ha.State( - "test.point_in_time_{}".format(i % 5), - f"State {i}", - {"attribute_test": i}, - ) +def test_get_significant_states_multiple_entity_ids(hass_history): + """Test that only significant states are returned for one entity.""" + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test2"] + del states["media_player.test3"] + del states["thermostat.test2"] + del states["script.can_cancel_this_one"] - mock_state_change_event(self.hass, state) + hist = history.get_significant_states( + hass, + zero, + four, + ["media_player.test", "thermostat.test"], + filters=history.Filters(), + ) + assert states == hist - wait_recording_done(self.hass) - # Get states returns everything before POINT - for state1, state2 in zip( - states, - sorted( - history.get_states(self.hass, future), key=lambda state: state.entity_id - ), - ): - assert state1 == state2 +def test_get_significant_states_exclude_domain(hass_history): + """Test if significant states are returned when excluding domains. - # Test get_state here because we have a DB setup - assert states[0] == history.get_state(self.hass, future, states[0].entity_id) + We should get back every thermostat change that includes an attribute + change, but no media player changes. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test"] + del states["media_player.test2"] + del states["media_player.test3"] - time_before_recorder_ran = now - timedelta(days=1000) - assert history.get_states(self.hass, time_before_recorder_ran) == [] - - assert history.get_state(self.hass, time_before_recorder_ran, "demo.id") is None - - def test_state_changes_during_period(self): - """Test state change during period.""" - self.test_setup() - entity_id = "media_player.test" - - def set_state(state): - """Set the state.""" - self.hass.states.set(entity_id, state) - wait_recording_done(self.hass) - return self.hass.states.get(entity_id) - - start = dt_util.utcnow() - point = start + timedelta(seconds=1) - end = point + timedelta(seconds=1) - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=start - ): - set_state("idle") - set_state("YouTube") - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=point - ): - states = [ - set_state("idle"), - set_state("Netflix"), - set_state("Plex"), - set_state("YouTube"), - ] - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=end - ): - set_state("Netflix") - set_state("Plex") - - hist = history.state_changes_during_period(self.hass, start, end, entity_id) - - assert states == hist[entity_id] - - def test_get_last_state_changes(self): - """Test number of state changes.""" - self.test_setup() - entity_id = "sensor.test" - - def set_state(state): - """Set the state.""" - self.hass.states.set(entity_id, state) - wait_recording_done(self.hass) - return self.hass.states.get(entity_id) - - start = dt_util.utcnow() - timedelta(minutes=2) - point = start + timedelta(minutes=1) - point2 = point + timedelta(minutes=1) - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=start - ): - set_state("1") - - states = [] - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=point - ): - states.append(set_state("2")) - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=point2 - ): - states.append(set_state("3")) - - hist = history.get_last_state_changes(self.hass, 2, entity_id) - - assert states == hist[entity_id] - - def test_ensure_state_can_be_copied(self): - """Ensure a state can pass though copy(). - - The filter integration uses copy() on states - from history. - """ - self.test_setup() - entity_id = "sensor.test" - - def set_state(state): - """Set the state.""" - self.hass.states.set(entity_id, state) - wait_recording_done(self.hass) - return self.hass.states.get(entity_id) - - start = dt_util.utcnow() - timedelta(minutes=2) - point = start + timedelta(minutes=1) - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=start - ): - set_state("1") - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=point - ): - set_state("2") - - hist = history.get_last_state_changes(self.hass, 2, entity_id) - - assert copy(hist[entity_id][0]) == hist[entity_id][0] - assert copy(hist[entity_id][1]) == hist[entity_id][1] - - def test_get_significant_states(self): - """Test that only significant states are returned. - - We should get back every thermostat change that - includes an attribute change, but only the state updates for - media player (attribute changes are not significant and not returned). - """ - zero, four, states = self.record_states() - hist = history.get_significant_states( - self.hass, zero, four, filters=history.Filters() - ) - assert states == hist - - def test_get_significant_states_minimal_response(self): - """Test that only significant states are returned. - - When minimal responses is set only the first and - last states return a complete state. - - We should get back every thermostat change that - includes an attribute change, but only the state updates for - media player (attribute changes are not significant and not returned). - """ - zero, four, states = self.record_states() - hist = history.get_significant_states( - self.hass, zero, four, filters=history.Filters(), minimal_response=True - ) - - # The second media_player.test state is reduced - # down to last_changed and state when minimal_response - # is set. We use JSONEncoder to make sure that are - # pre-encoded last_changed is always the same as what - # will happen with encoding a native state - input_state = states["media_player.test"][1] - orig_last_changed = json.dumps( - process_timestamp(input_state.last_changed), - cls=JSONEncoder, - ).replace('"', "") - orig_state = input_state.state - states["media_player.test"][1] = { - "last_changed": orig_last_changed, - "state": orig_state, + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_EXCLUDE: {history.CONF_DOMAINS: ["media_player"]} + }, } + ) + check_significant_states(hass, zero, four, states, config) - assert states == hist - def test_get_significant_states_with_initial(self): - """Test that only significant states are returned. +def test_get_significant_states_exclude_entity(hass_history): + """Test if significant states are returned when excluding entities. - We should get back every thermostat change that - includes an attribute change, but only the state updates for - media player (attribute changes are not significant and not returned). - """ - zero, four, states = self.record_states() - one = zero + timedelta(seconds=1) - one_and_half = zero + timedelta(seconds=1.5) - for entity_id in states: - if entity_id == "media_player.test": - states[entity_id] = states[entity_id][1:] - for state in states[entity_id]: - if state.last_changed == one: - state.last_changed = one_and_half + We should get back every thermostat and script changes, but no media + player changes. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test"] - hist = history.get_significant_states( - self.hass, - one_and_half, - four, - filters=history.Filters(), - include_start_time_state=True, - ) - assert states == hist + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_EXCLUDE: {history.CONF_ENTITIES: ["media_player.test"]} + }, + } + ) + check_significant_states(hass, zero, four, states, config) - def test_get_significant_states_without_initial(self): - """Test that only significant states are returned. - We should get back every thermostat change that - includes an attribute change, but only the state updates for - media player (attribute changes are not significant and not returned). - """ - zero, four, states = self.record_states() - one = zero + timedelta(seconds=1) - one_and_half = zero + timedelta(seconds=1.5) - for entity_id in states: - states[entity_id] = list( - filter(lambda s: s.last_changed != one, states[entity_id]) - ) - del states["media_player.test2"] +def test_get_significant_states_exclude(hass_history): + """Test significant states when excluding entities and domains. - hist = history.get_significant_states( - self.hass, - one_and_half, - four, - filters=history.Filters(), - include_start_time_state=False, - ) - assert states == hist + We should not get back every thermostat and media player test changes. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test"] + del states["thermostat.test"] + del states["thermostat.test2"] - def test_get_significant_states_entity_id(self): - """Test that only significant states are returned for one entity.""" - zero, four, states = self.record_states() - del states["media_player.test2"] - del states["media_player.test3"] - del states["thermostat.test"] - del states["thermostat.test2"] - del states["script.can_cancel_this_one"] + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_EXCLUDE: { + history.CONF_DOMAINS: ["thermostat"], + history.CONF_ENTITIES: ["media_player.test"], + } + }, + } + ) + check_significant_states(hass, zero, four, states, config) - hist = history.get_significant_states( - self.hass, zero, four, ["media_player.test"], filters=history.Filters() - ) - assert states == hist - def test_get_significant_states_multiple_entity_ids(self): - """Test that only significant states are returned for one entity.""" - zero, four, states = self.record_states() - del states["media_player.test2"] - del states["media_player.test3"] - del states["thermostat.test2"] - del states["script.can_cancel_this_one"] +def test_get_significant_states_exclude_include_entity(hass_history): + """Test significant states when excluding domains and include entities. - hist = history.get_significant_states( - self.hass, - zero, - four, - ["media_player.test", "thermostat.test"], - filters=history.Filters(), - ) - assert states == hist + We should not get back every thermostat and media player test changes. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test2"] + del states["media_player.test3"] + del states["thermostat.test"] + del states["thermostat.test2"] + del states["script.can_cancel_this_one"] - def test_get_significant_states_exclude_domain(self): - """Test if significant states are returned when excluding domains. - - We should get back every thermostat change that includes an attribute - change, but no media player changes. - """ - zero, four, states = self.record_states() - del states["media_player.test"] - del states["media_player.test2"] - del states["media_player.test3"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_EXCLUDE: {history.CONF_DOMAINS: ["media_player"]} + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_INCLUDE: { + history.CONF_ENTITIES: ["media_player.test", "thermostat.test"] }, - } - ) - self.check_significant_states(zero, four, states, config) + history.CONF_EXCLUDE: {history.CONF_DOMAINS: ["thermostat"]}, + }, + } + ) + check_significant_states(hass, zero, four, states, config) - def test_get_significant_states_exclude_entity(self): - """Test if significant states are returned when excluding entities. - We should get back every thermostat and script changes, but no media - player changes. - """ - zero, four, states = self.record_states() - del states["media_player.test"] +def test_get_significant_states_include_domain(hass_history): + """Test if significant states are returned when including domains. - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_EXCLUDE: {history.CONF_ENTITIES: ["media_player.test"]} + We should get back every thermostat and script changes, but no media + player changes. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test"] + del states["media_player.test2"] + del states["media_player.test3"] + + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_INCLUDE: {history.CONF_DOMAINS: ["thermostat", "script"]} + }, + } + ) + check_significant_states(hass, zero, four, states, config) + + +def test_get_significant_states_include_entity(hass_history): + """Test if significant states are returned when including entities. + + We should only get back changes of the media_player.test entity. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test2"] + del states["media_player.test3"] + del states["thermostat.test"] + del states["thermostat.test2"] + del states["script.can_cancel_this_one"] + + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_INCLUDE: {history.CONF_ENTITIES: ["media_player.test"]} + }, + } + ) + check_significant_states(hass, zero, four, states, config) + + +def test_get_significant_states_include(hass_history): + """Test significant states when including domains and entities. + + We should only get back changes of the media_player.test entity and the + thermostat domain. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test2"] + del states["media_player.test3"] + del states["script.can_cancel_this_one"] + + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_INCLUDE: { + history.CONF_DOMAINS: ["thermostat"], + history.CONF_ENTITIES: ["media_player.test"], + } + }, + } + ) + check_significant_states(hass, zero, four, states, config) + + +def test_get_significant_states_include_exclude_domain(hass_history): + """Test if significant states when excluding and including domains. + + We should not get back any changes since we include only the + media_player domain but also exclude it. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test"] + del states["media_player.test2"] + del states["media_player.test3"] + del states["thermostat.test"] + del states["thermostat.test2"] + del states["script.can_cancel_this_one"] + + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_INCLUDE: {history.CONF_DOMAINS: ["media_player"]}, + history.CONF_EXCLUDE: {history.CONF_DOMAINS: ["media_player"]}, + }, + } + ) + check_significant_states(hass, zero, four, states, config) + + +def test_get_significant_states_include_exclude_entity(hass_history): + """Test if significant states when excluding and including domains. + + We should not get back any changes since we include only + media_player.test but also exclude it. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test"] + del states["media_player.test2"] + del states["media_player.test3"] + del states["thermostat.test"] + del states["thermostat.test2"] + del states["script.can_cancel_this_one"] + + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_INCLUDE: {history.CONF_ENTITIES: ["media_player.test"]}, + history.CONF_EXCLUDE: {history.CONF_ENTITIES: ["media_player.test"]}, + }, + } + ) + check_significant_states(hass, zero, four, states, config) + + +def test_get_significant_states_include_exclude(hass_history): + """Test if significant states when in/excluding domains and entities. + + We should only get back changes of the media_player.test2 entity. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test"] + del states["thermostat.test"] + del states["thermostat.test2"] + del states["script.can_cancel_this_one"] + + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_INCLUDE: { + history.CONF_DOMAINS: ["media_player"], + history.CONF_ENTITIES: ["thermostat.test"], }, - } - ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_exclude(self): - """Test significant states when excluding entities and domains. - - We should not get back every thermostat and media player test changes. - """ - zero, four, states = self.record_states() - del states["media_player.test"] - del states["thermostat.test"] - del states["thermostat.test2"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_EXCLUDE: { - history.CONF_DOMAINS: ["thermostat"], - history.CONF_ENTITIES: ["media_player.test"], - } + history.CONF_EXCLUDE: { + history.CONF_DOMAINS: ["thermostat"], + history.CONF_ENTITIES: ["media_player.test"], }, - } + }, + } + ) + check_significant_states(hass, zero, four, states, config) + + +def test_get_significant_states_are_ordered(hass_history): + """Test order of results from get_significant_states. + + When entity ids are given, the results should be returned with the data + in the same order. + """ + hass = hass_history + zero, four, _states = record_states(hass) + entity_ids = ["media_player.test", "media_player.test2"] + hist = history.get_significant_states( + hass, zero, four, entity_ids, filters=history.Filters() + ) + assert list(hist.keys()) == entity_ids + entity_ids = ["media_player.test2", "media_player.test"] + hist = history.get_significant_states( + hass, zero, four, entity_ids, filters=history.Filters() + ) + assert list(hist.keys()) == entity_ids + + +def test_get_significant_states_only(hass_history): + """Test significant states when significant_states_only is set.""" + hass = hass_history + entity_id = "sensor.test" + + def set_state(state, **kwargs): + """Set the state.""" + hass.states.set(entity_id, state, **kwargs) + wait_recording_done(hass) + return hass.states.get(entity_id) + + start = dt_util.utcnow() - timedelta(minutes=4) + points = [] + for i in range(1, 4): + points.append(start + timedelta(minutes=i)) + + states = [] + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=start): + set_state("123", attributes={"attribute": 10.64}) + + with patch( + "homeassistant.components.recorder.dt_util.utcnow", return_value=points[0] + ): + # Attributes are different, state not + states.append(set_state("123", attributes={"attribute": 21.42})) + + with patch( + "homeassistant.components.recorder.dt_util.utcnow", return_value=points[1] + ): + # state is different, attributes not + states.append(set_state("32", attributes={"attribute": 21.42})) + + with patch( + "homeassistant.components.recorder.dt_util.utcnow", return_value=points[2] + ): + # everything is different + states.append(set_state("412", attributes={"attribute": 54.23})) + + hist = history.get_significant_states(hass, start, significant_changes_only=True) + + assert len(hist[entity_id]) == 2 + assert states[0] not in hist[entity_id] + assert states[1] in hist[entity_id] + assert states[2] in hist[entity_id] + + hist = history.get_significant_states(hass, start, significant_changes_only=False) + + assert len(hist[entity_id]) == 3 + assert states == hist[entity_id] + + +def check_significant_states(hass, zero, four, states, config): + """Check if significant states are retrieved.""" + filters = history.Filters() + exclude = config[history.DOMAIN].get(history.CONF_EXCLUDE) + if exclude: + filters.excluded_entities = exclude.get(history.CONF_ENTITIES, []) + filters.excluded_domains = exclude.get(history.CONF_DOMAINS, []) + include = config[history.DOMAIN].get(history.CONF_INCLUDE) + if include: + filters.included_entities = include.get(history.CONF_ENTITIES, []) + filters.included_domains = include.get(history.CONF_DOMAINS, []) + + hist = history.get_significant_states(hass, zero, four, filters=filters) + assert states == hist + + +def record_states(hass): + """Record some test states. + + We inject a bunch of state updates from media player, zone and + thermostat. + """ + mp = "media_player.test" + mp2 = "media_player.test2" + mp3 = "media_player.test3" + therm = "thermostat.test" + therm2 = "thermostat.test2" + zone = "zone.home" + script_c = "script.can_cancel_this_one" + + def set_state(entity_id, state, **kwargs): + """Set the state.""" + hass.states.set(entity_id, state, **kwargs) + wait_recording_done(hass) + return hass.states.get(entity_id) + + zero = dt_util.utcnow() + one = zero + timedelta(seconds=1) + two = one + timedelta(seconds=1) + three = two + timedelta(seconds=1) + four = three + timedelta(seconds=1) + + states = {therm: [], therm2: [], mp: [], mp2: [], mp3: [], script_c: []} + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=one): + states[mp].append( + set_state(mp, "idle", attributes={"media_title": str(sentinel.mt1)}) ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_exclude_include_entity(self): - """Test significant states when excluding domains and include entities. - - We should not get back every thermostat and media player test changes. - """ - zero, four, states = self.record_states() - del states["media_player.test2"] - del states["media_player.test3"] - del states["thermostat.test"] - del states["thermostat.test2"] - del states["script.can_cancel_this_one"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_INCLUDE: { - history.CONF_ENTITIES: ["media_player.test", "thermostat.test"] - }, - history.CONF_EXCLUDE: {history.CONF_DOMAINS: ["thermostat"]}, - }, - } + states[mp].append( + set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt2)}) ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_include_domain(self): - """Test if significant states are returned when including domains. - - We should get back every thermostat and script changes, but no media - player changes. - """ - zero, four, states = self.record_states() - del states["media_player.test"] - del states["media_player.test2"] - del states["media_player.test3"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_INCLUDE: { - history.CONF_DOMAINS: ["thermostat", "script"] - } - }, - } + states[mp2].append( + set_state(mp2, "YouTube", attributes={"media_title": str(sentinel.mt2)}) ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_include_entity(self): - """Test if significant states are returned when including entities. - - We should only get back changes of the media_player.test entity. - """ - zero, four, states = self.record_states() - del states["media_player.test2"] - del states["media_player.test3"] - del states["thermostat.test"] - del states["thermostat.test2"] - del states["script.can_cancel_this_one"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_INCLUDE: {history.CONF_ENTITIES: ["media_player.test"]} - }, - } + states[mp3].append( + set_state(mp3, "idle", attributes={"media_title": str(sentinel.mt1)}) ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_include(self): - """Test significant states when including domains and entities. - - We should only get back changes of the media_player.test entity and the - thermostat domain. - """ - zero, four, states = self.record_states() - del states["media_player.test2"] - del states["media_player.test3"] - del states["script.can_cancel_this_one"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_INCLUDE: { - history.CONF_DOMAINS: ["thermostat"], - history.CONF_ENTITIES: ["media_player.test"], - } - }, - } - ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_include_exclude_domain(self): - """Test if significant states when excluding and including domains. - - We should not get back any changes since we include only the - media_player domain but also exclude it. - """ - zero, four, states = self.record_states() - del states["media_player.test"] - del states["media_player.test2"] - del states["media_player.test3"] - del states["thermostat.test"] - del states["thermostat.test2"] - del states["script.can_cancel_this_one"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_INCLUDE: {history.CONF_DOMAINS: ["media_player"]}, - history.CONF_EXCLUDE: {history.CONF_DOMAINS: ["media_player"]}, - }, - } - ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_include_exclude_entity(self): - """Test if significant states when excluding and including domains. - - We should not get back any changes since we include only - media_player.test but also exclude it. - """ - zero, four, states = self.record_states() - del states["media_player.test"] - del states["media_player.test2"] - del states["media_player.test3"] - del states["thermostat.test"] - del states["thermostat.test2"] - del states["script.can_cancel_this_one"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_INCLUDE: { - history.CONF_ENTITIES: ["media_player.test"] - }, - history.CONF_EXCLUDE: { - history.CONF_ENTITIES: ["media_player.test"] - }, - }, - } - ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_include_exclude(self): - """Test if significant states when in/excluding domains and entities. - - We should only get back changes of the media_player.test2 entity. - """ - zero, four, states = self.record_states() - del states["media_player.test"] - del states["thermostat.test"] - del states["thermostat.test2"] - del states["script.can_cancel_this_one"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_INCLUDE: { - history.CONF_DOMAINS: ["media_player"], - history.CONF_ENTITIES: ["thermostat.test"], - }, - history.CONF_EXCLUDE: { - history.CONF_DOMAINS: ["thermostat"], - history.CONF_ENTITIES: ["media_player.test"], - }, - }, - } - ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_are_ordered(self): - """Test order of results from get_significant_states. - - When entity ids are given, the results should be returned with the data - in the same order. - """ - zero, four, states = self.record_states() - entity_ids = ["media_player.test", "media_player.test2"] - hist = history.get_significant_states( - self.hass, zero, four, entity_ids, filters=history.Filters() - ) - assert list(hist.keys()) == entity_ids - entity_ids = ["media_player.test2", "media_player.test"] - hist = history.get_significant_states( - self.hass, zero, four, entity_ids, filters=history.Filters() - ) - assert list(hist.keys()) == entity_ids - - def test_get_significant_states_only(self): - """Test significant states when significant_states_only is set.""" - self.test_setup() - entity_id = "sensor.test" - - def set_state(state, **kwargs): - """Set the state.""" - self.hass.states.set(entity_id, state, **kwargs) - wait_recording_done(self.hass) - return self.hass.states.get(entity_id) - - start = dt_util.utcnow() - timedelta(minutes=4) - points = [] - for i in range(1, 4): - points.append(start + timedelta(minutes=i)) - - states = [] - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=start - ): - set_state("123", attributes={"attribute": 10.64}) - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=points[0] - ): - # Attributes are different, state not - states.append(set_state("123", attributes={"attribute": 21.42})) - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=points[1] - ): - # state is different, attributes not - states.append(set_state("32", attributes={"attribute": 21.42})) - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=points[2] - ): - # everything is different - states.append(set_state("412", attributes={"attribute": 54.23})) - - hist = history.get_significant_states( - self.hass, start, significant_changes_only=True + states[therm].append( + set_state(therm, 20, attributes={"current_temperature": 19.5}) ) - assert len(hist[entity_id]) == 2 - assert states[0] not in hist[entity_id] - assert states[1] in hist[entity_id] - assert states[2] in hist[entity_id] - - hist = history.get_significant_states( - self.hass, start, significant_changes_only=False + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=two): + # This state will be skipped only different in time + set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt3)}) + # This state will be skipped because domain is excluded + set_state(zone, "zoning") + states[script_c].append( + set_state(script_c, "off", attributes={"can_cancel": True}) + ) + states[therm].append( + set_state(therm, 21, attributes={"current_temperature": 19.8}) + ) + states[therm2].append( + set_state(therm2, 20, attributes={"current_temperature": 19}) ) - assert len(hist[entity_id]) == 3 - assert states == hist[entity_id] + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=three): + states[mp].append( + set_state(mp, "Netflix", attributes={"media_title": str(sentinel.mt4)}) + ) + states[mp3].append( + set_state(mp3, "Netflix", attributes={"media_title": str(sentinel.mt3)}) + ) + # Attributes changed even though state is the same + states[therm].append( + set_state(therm, 21, attributes={"current_temperature": 20}) + ) - def check_significant_states(self, zero, four, states, config): - """Check if significant states are retrieved.""" - filters = history.Filters() - exclude = config[history.DOMAIN].get(history.CONF_EXCLUDE) - if exclude: - filters.excluded_entities = exclude.get(history.CONF_ENTITIES, []) - filters.excluded_domains = exclude.get(history.CONF_DOMAINS, []) - include = config[history.DOMAIN].get(history.CONF_INCLUDE) - if include: - filters.included_entities = include.get(history.CONF_ENTITIES, []) - filters.included_domains = include.get(history.CONF_DOMAINS, []) - - hist = history.get_significant_states(self.hass, zero, four, filters=filters) - assert states == hist - - def record_states(self): - """Record some test states. - - We inject a bunch of state updates from media player, zone and - thermostat. - """ - self.test_setup() - mp = "media_player.test" - mp2 = "media_player.test2" - mp3 = "media_player.test3" - therm = "thermostat.test" - therm2 = "thermostat.test2" - zone = "zone.home" - script_c = "script.can_cancel_this_one" - - def set_state(entity_id, state, **kwargs): - """Set the state.""" - self.hass.states.set(entity_id, state, **kwargs) - wait_recording_done(self.hass) - return self.hass.states.get(entity_id) - - zero = dt_util.utcnow() - one = zero + timedelta(seconds=1) - two = one + timedelta(seconds=1) - three = two + timedelta(seconds=1) - four = three + timedelta(seconds=1) - - states = {therm: [], therm2: [], mp: [], mp2: [], mp3: [], script_c: []} - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=one - ): - states[mp].append( - set_state(mp, "idle", attributes={"media_title": str(sentinel.mt1)}) - ) - states[mp].append( - set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt2)}) - ) - states[mp2].append( - set_state(mp2, "YouTube", attributes={"media_title": str(sentinel.mt2)}) - ) - states[mp3].append( - set_state(mp3, "idle", attributes={"media_title": str(sentinel.mt1)}) - ) - states[therm].append( - set_state(therm, 20, attributes={"current_temperature": 19.5}) - ) - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=two - ): - # This state will be skipped only different in time - set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt3)}) - # This state will be skipped because domain is excluded - set_state(zone, "zoning") - states[script_c].append( - set_state(script_c, "off", attributes={"can_cancel": True}) - ) - states[therm].append( - set_state(therm, 21, attributes={"current_temperature": 19.8}) - ) - states[therm2].append( - set_state(therm2, 20, attributes={"current_temperature": 19}) - ) - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=three - ): - states[mp].append( - set_state(mp, "Netflix", attributes={"media_title": str(sentinel.mt4)}) - ) - states[mp3].append( - set_state(mp3, "Netflix", attributes={"media_title": str(sentinel.mt3)}) - ) - # Attributes changed even though state is the same - states[therm].append( - set_state(therm, 21, attributes={"current_temperature": 20}) - ) - - return zero, four, states + return zero, four, states async def test_fetch_period_api(hass, hass_client): From e55702d63528f52d5324cf24d233399b47607c2b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 17 Mar 2021 17:34:55 +0100 Subject: [PATCH 427/831] Update typing 01 (#48013) --- homeassistant/__main__.py | 5 +- homeassistant/bootstrap.py | 26 +++-- homeassistant/config.py | 54 ++++----- homeassistant/config_entries.py | 104 ++++++++--------- homeassistant/core.py | 185 +++++++++++++++---------------- homeassistant/data_entry_flow.py | 72 ++++++------ homeassistant/exceptions.py | 16 +-- homeassistant/loader.py | 116 +++++++++---------- homeassistant/requirements.py | 18 +-- homeassistant/runner.py | 10 +- homeassistant/setup.py | 10 +- 11 files changed, 303 insertions(+), 313 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 840e0bed24d..d8256e2ef92 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -1,11 +1,12 @@ """Start Home Assistant.""" +from __future__ import annotations + import argparse import os import platform import subprocess import sys import threading -from typing import List from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__ @@ -206,7 +207,7 @@ def closefds_osx(min_fd: int, max_fd: int) -> None: pass -def cmdline() -> List[str]: +def cmdline() -> list[str]: """Collect path and arguments to re-execute the current hass instance.""" if os.path.basename(sys.argv[0]) == "__main__.py": modulepath = os.path.dirname(sys.argv[0]) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 6d334ac8953..8b191ae3a47 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -1,4 +1,6 @@ """Provide methods to bootstrap a Home Assistant instance.""" +from __future__ import annotations + import asyncio import contextlib from datetime import datetime @@ -8,7 +10,7 @@ import os import sys import threading from time import monotonic -from typing import TYPE_CHECKING, Any, Dict, Optional, Set +from typing import TYPE_CHECKING, Any import voluptuous as vol import yarl @@ -75,7 +77,7 @@ STAGE_1_INTEGRATIONS = { async def async_setup_hass( runtime_config: "RuntimeConfig", -) -> Optional[core.HomeAssistant]: +) -> core.HomeAssistant | None: """Set up Home Assistant.""" hass = core.HomeAssistant() hass.config.config_dir = runtime_config.config_dir @@ -188,7 +190,7 @@ def open_hass_ui(hass: core.HomeAssistant) -> None: async def async_from_config_dict( config: ConfigType, hass: core.HomeAssistant -) -> Optional[core.HomeAssistant]: +) -> core.HomeAssistant | None: """Try to configure Home Assistant from a configuration dictionary. Dynamically loads required components and its dependencies. @@ -255,8 +257,8 @@ async def async_from_config_dict( def async_enable_logging( hass: core.HomeAssistant, verbose: bool = False, - log_rotate_days: Optional[int] = None, - log_file: Optional[str] = None, + log_rotate_days: int | None = None, + log_file: str | None = None, log_no_color: bool = False, ) -> None: """Set up the logging. @@ -362,7 +364,7 @@ async def async_mount_local_lib_path(config_dir: str) -> str: @core.callback -def _get_domains(hass: core.HomeAssistant, config: Dict[str, Any]) -> Set[str]: +def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]: """Get domains of components to set up.""" # Filter out the repeating and common config section [homeassistant] domains = {key.split(" ")[0] for key in config if key != core.DOMAIN} @@ -379,7 +381,7 @@ def _get_domains(hass: core.HomeAssistant, config: Dict[str, Any]) -> Set[str]: async def _async_log_pending_setups( - hass: core.HomeAssistant, domains: Set[str], setup_started: Dict[str, datetime] + hass: core.HomeAssistant, domains: set[str], setup_started: dict[str, datetime] ) -> None: """Periodic log of setups that are pending for longer than LOG_SLOW_STARTUP_INTERVAL.""" while True: @@ -396,9 +398,9 @@ async def _async_log_pending_setups( async def async_setup_multi_components( hass: core.HomeAssistant, - domains: Set[str], - config: Dict[str, Any], - setup_started: Dict[str, datetime], + domains: set[str], + config: dict[str, Any], + setup_started: dict[str, datetime], ) -> None: """Set up multiple domains. Log on failure.""" futures = { @@ -422,7 +424,7 @@ async def async_setup_multi_components( async def _async_set_up_integrations( - hass: core.HomeAssistant, config: Dict[str, Any] + hass: core.HomeAssistant, config: dict[str, Any] ) -> None: """Set up all the integrations.""" setup_started = hass.data[DATA_SETUP_STARTED] = {} @@ -430,7 +432,7 @@ async def _async_set_up_integrations( # Resolve all dependencies so we know all integrations # that will have to be loaded and start rightaway - integration_cache: Dict[str, loader.Integration] = {} + integration_cache: dict[str, loader.Integration] = {} to_resolve = domains_to_setup while to_resolve: old_to_resolve = to_resolve diff --git a/homeassistant/config.py b/homeassistant/config.py index cfc1390a37b..362c93d04fa 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -1,4 +1,6 @@ """Module to help with parsing and generating configuration files.""" +from __future__ import annotations + from collections import OrderedDict import logging import os @@ -6,7 +8,7 @@ from pathlib import Path import re import shutil from types import ModuleType -from typing import Any, Callable, Dict, Optional, Sequence, Set, Tuple, Union +from typing import Any, Callable, Sequence from awesomeversion import AwesomeVersion import voluptuous as vol @@ -114,14 +116,14 @@ tts: def _no_duplicate_auth_provider( - configs: Sequence[Dict[str, Any]] -) -> Sequence[Dict[str, Any]]: + configs: Sequence[dict[str, Any]] +) -> Sequence[dict[str, Any]]: """No duplicate auth provider config allowed in a list. Each type of auth provider can only have one config without optional id. Unique id is required if same type of auth provider used multiple times. """ - config_keys: Set[Tuple[str, Optional[str]]] = set() + config_keys: set[tuple[str, str | None]] = set() for config in configs: key = (config[CONF_TYPE], config.get(CONF_ID)) if key in config_keys: @@ -135,8 +137,8 @@ def _no_duplicate_auth_provider( def _no_duplicate_auth_mfa_module( - configs: Sequence[Dict[str, Any]] -) -> Sequence[Dict[str, Any]]: + configs: Sequence[dict[str, Any]] +) -> Sequence[dict[str, Any]]: """No duplicate auth mfa module item allowed in a list. Each type of mfa module can only have one config without optional id. @@ -144,7 +146,7 @@ def _no_duplicate_auth_mfa_module( times. Note: this is different than auth provider """ - config_keys: Set[str] = set() + config_keys: set[str] = set() for config in configs: key = config.get(CONF_ID, config[CONF_TYPE]) if key in config_keys: @@ -313,7 +315,7 @@ def _write_default_config(config_dir: str) -> bool: return False -async def async_hass_config_yaml(hass: HomeAssistant) -> Dict: +async def async_hass_config_yaml(hass: HomeAssistant) -> dict: """Load YAML from a Home Assistant configuration file. This function allow a component inside the asyncio loop to reload its @@ -337,8 +339,8 @@ async def async_hass_config_yaml(hass: HomeAssistant) -> Dict: def load_yaml_config_file( - config_path: str, secrets: Optional[Secrets] = None -) -> Dict[Any, Any]: + config_path: str, secrets: Secrets | None = None +) -> dict[Any, Any]: """Parse a YAML configuration file. Raises FileNotFoundError or HomeAssistantError. @@ -421,9 +423,9 @@ def process_ha_config_upgrade(hass: HomeAssistant) -> None: def async_log_exception( ex: Exception, domain: str, - config: Dict, + config: dict, hass: HomeAssistant, - link: Optional[str] = None, + link: str | None = None, ) -> None: """Log an error for configuration validation. @@ -437,8 +439,8 @@ def async_log_exception( @callback def _format_config_error( - ex: Exception, domain: str, config: Dict, link: Optional[str] = None -) -> Tuple[str, bool]: + ex: Exception, domain: str, config: dict, link: str | None = None +) -> tuple[str, bool]: """Generate log exception for configuration validation. This method must be run in the event loop. @@ -474,7 +476,7 @@ def _format_config_error( return message, is_friendly -async def async_process_ha_core_config(hass: HomeAssistant, config: Dict) -> None: +async def async_process_ha_core_config(hass: HomeAssistant, config: dict) -> None: """Process the [homeassistant] section from the configuration. This method is a coroutine. @@ -603,7 +605,7 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: Dict) -> Non ) -def _log_pkg_error(package: str, component: str, config: Dict, message: str) -> None: +def _log_pkg_error(package: str, component: str, config: dict, message: str) -> None: """Log an error while merging packages.""" message = f"Package {package} setup failed. Integration {component} {message}" @@ -616,7 +618,7 @@ def _log_pkg_error(package: str, component: str, config: Dict, message: str) -> _LOGGER.error(message) -def _identify_config_schema(module: ModuleType) -> Optional[str]: +def _identify_config_schema(module: ModuleType) -> str | None: """Extract the schema and identify list or dict based.""" if not isinstance(module.CONFIG_SCHEMA, vol.Schema): # type: ignore return None @@ -664,9 +666,9 @@ def _identify_config_schema(module: ModuleType) -> Optional[str]: return None -def _recursive_merge(conf: Dict[str, Any], package: Dict[str, Any]) -> Union[bool, str]: +def _recursive_merge(conf: dict[str, Any], package: dict[str, Any]) -> bool | str: """Merge package into conf, recursively.""" - error: Union[bool, str] = False + error: bool | str = False for key, pack_conf in package.items(): if isinstance(pack_conf, dict): if not pack_conf: @@ -688,10 +690,10 @@ def _recursive_merge(conf: Dict[str, Any], package: Dict[str, Any]) -> Union[boo async def merge_packages_config( hass: HomeAssistant, - config: Dict, - packages: Dict[str, Any], + config: dict, + packages: dict[str, Any], _log_pkg_error: Callable = _log_pkg_error, -) -> Dict: +) -> dict: """Merge packages into the top-level configuration. Mutate config.""" PACKAGES_CONFIG_SCHEMA(packages) for pack_name, pack_conf in packages.items(): @@ -754,7 +756,7 @@ async def merge_packages_config( async def async_process_component_config( hass: HomeAssistant, config: ConfigType, integration: Integration -) -> Optional[ConfigType]: +) -> ConfigType | None: """Check component configuration and return processed configuration. Returns None on error. @@ -879,13 +881,13 @@ async def async_process_component_config( @callback -def config_without_domain(config: Dict, domain: str) -> Dict: +def config_without_domain(config: dict, domain: str) -> dict: """Return a config with all configuration for a domain removed.""" filter_keys = extract_domain_configs(config, domain) return {key: value for key, value in config.items() if key not in filter_keys} -async def async_check_ha_config_file(hass: HomeAssistant) -> Optional[str]: +async def async_check_ha_config_file(hass: HomeAssistant) -> str | None: """Check if Home Assistant configuration file is valid. This method is a coroutine. @@ -902,7 +904,7 @@ async def async_check_ha_config_file(hass: HomeAssistant) -> Optional[str]: @callback def async_notify_setup_error( - hass: HomeAssistant, component: str, display_link: Optional[str] = None + hass: HomeAssistant, component: str, display_link: str | None = None ) -> None: """Print a persistent notification. diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index c105be42ba3..a18e207a730 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -5,7 +5,7 @@ import asyncio import functools import logging from types import MappingProxyType, MethodType -from typing import Any, Callable, Dict, List, Optional, Set, Union, cast +from typing import Any, Callable, Optional, cast import weakref import attr @@ -143,11 +143,11 @@ class ConfigEntry: source: str, connection_class: str, system_options: dict, - options: Optional[dict] = None, - unique_id: Optional[str] = None, - entry_id: Optional[str] = None, + options: dict | None = None, + unique_id: str | None = None, + entry_id: str | None = None, state: str = ENTRY_STATE_NOT_LOADED, - disabled_by: Optional[str] = None, + disabled_by: str | None = None, ) -> None: """Initialize a config entry.""" # Unique id of the config entry @@ -190,18 +190,18 @@ class ConfigEntry: self.supports_unload = False # Listeners to call on update - self.update_listeners: List[ - Union[weakref.ReferenceType[UpdateListenerType], weakref.WeakMethod] + self.update_listeners: list[ + weakref.ReferenceType[UpdateListenerType] | weakref.WeakMethod ] = [] # Function to cancel a scheduled retry - self._async_cancel_retry_setup: Optional[Callable[[], Any]] = None + self._async_cancel_retry_setup: Callable[[], Any] | None = None async def async_setup( self, hass: HomeAssistant, *, - integration: Optional[loader.Integration] = None, + integration: loader.Integration | None = None, tries: int = 0, ) -> None: """Set up an entry.""" @@ -295,7 +295,7 @@ class ConfigEntry: self.state = ENTRY_STATE_SETUP_ERROR async def async_unload( - self, hass: HomeAssistant, *, integration: Optional[loader.Integration] = None + self, hass: HomeAssistant, *, integration: loader.Integration | None = None ) -> bool: """Unload an entry. @@ -442,7 +442,7 @@ class ConfigEntry: return lambda: self.update_listeners.remove(weak_listener) - def as_dict(self) -> Dict[str, Any]: + def as_dict(self) -> dict[str, Any]: """Return dictionary version of this entry.""" return { "entry_id": self.entry_id, @@ -471,8 +471,8 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager): self._hass_config = hass_config async def async_finish_flow( - self, flow: data_entry_flow.FlowHandler, result: Dict[str, Any] - ) -> Dict[str, Any]: + self, flow: data_entry_flow.FlowHandler, result: dict[str, Any] + ) -> dict[str, Any]: """Finish a config flow and add an entry.""" flow = cast(ConfigFlow, flow) @@ -542,7 +542,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager): return result async def async_create_flow( - self, handler_key: Any, *, context: Optional[Dict] = None, data: Any = None + self, handler_key: Any, *, context: dict | None = None, data: Any = None ) -> ConfigFlow: """Create a flow for specified handler. @@ -619,14 +619,14 @@ class ConfigEntries: self.flow = ConfigEntriesFlowManager(hass, self, hass_config) self.options = OptionsFlowManager(hass) self._hass_config = hass_config - self._entries: List[ConfigEntry] = [] + self._entries: list[ConfigEntry] = [] self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) EntityRegistryDisabledHandler(hass).async_setup() @callback - def async_domains(self) -> List[str]: + def async_domains(self) -> list[str]: """Return domains for which we have entries.""" - seen: Set[str] = set() + seen: set[str] = set() result = [] for entry in self._entries: @@ -637,7 +637,7 @@ class ConfigEntries: return result @callback - def async_get_entry(self, entry_id: str) -> Optional[ConfigEntry]: + def async_get_entry(self, entry_id: str) -> ConfigEntry | None: """Return entry with matching entry_id.""" for entry in self._entries: if entry_id == entry.entry_id: @@ -645,7 +645,7 @@ class ConfigEntries: return None @callback - def async_entries(self, domain: Optional[str] = None) -> List[ConfigEntry]: + def async_entries(self, domain: str | None = None) -> list[ConfigEntry]: """Return all entries or entries for a specific domain.""" if domain is None: return list(self._entries) @@ -657,7 +657,7 @@ class ConfigEntries: await self.async_setup(entry.entry_id) self._async_schedule_save() - async def async_remove(self, entry_id: str) -> Dict[str, Any]: + async def async_remove(self, entry_id: str) -> dict[str, Any]: """Remove an entry.""" entry = self.async_get_entry(entry_id) @@ -789,7 +789,7 @@ class ConfigEntries: return await self.async_setup(entry_id) async def async_set_disabled_by( - self, entry_id: str, disabled_by: Optional[str] + self, entry_id: str, disabled_by: str | None ) -> bool: """Disable an entry. @@ -829,11 +829,11 @@ class ConfigEntries: self, entry: ConfigEntry, *, - unique_id: Union[str, dict, None, UndefinedType] = UNDEFINED, - title: Union[str, dict, UndefinedType] = UNDEFINED, - data: Union[dict, UndefinedType] = UNDEFINED, - options: Union[dict, UndefinedType] = UNDEFINED, - system_options: Union[dict, UndefinedType] = UNDEFINED, + unique_id: str | dict | None | UndefinedType = UNDEFINED, + title: str | dict | UndefinedType = UNDEFINED, + data: dict | UndefinedType = UNDEFINED, + options: dict | UndefinedType = UNDEFINED, + system_options: dict | UndefinedType = UNDEFINED, ) -> bool: """Update a config entry. @@ -918,12 +918,12 @@ class ConfigEntries: self._store.async_delay_save(self._data_to_save, SAVE_DELAY) @callback - def _data_to_save(self) -> Dict[str, List[Dict[str, Any]]]: + def _data_to_save(self) -> dict[str, list[dict[str, Any]]]: """Return data to save.""" return {"entries": [entry.as_dict() for entry in self._entries]} -async def _old_conf_migrator(old_config: Dict[str, Any]) -> Dict[str, Any]: +async def _old_conf_migrator(old_config: dict[str, Any]) -> dict[str, Any]: """Migrate the pre-0.73 config format to the latest version.""" return {"entries": old_config} @@ -931,7 +931,7 @@ async def _old_conf_migrator(old_config: Dict[str, Any]) -> Dict[str, Any]: class ConfigFlow(data_entry_flow.FlowHandler): """Base class for config flows with some helpers.""" - def __init_subclass__(cls, domain: Optional[str] = None, **kwargs: Any) -> None: + def __init_subclass__(cls, domain: str | None = None, **kwargs: Any) -> None: """Initialize a subclass, register if possible.""" super().__init_subclass__(**kwargs) # type: ignore if domain is not None: @@ -940,7 +940,7 @@ class ConfigFlow(data_entry_flow.FlowHandler): CONNECTION_CLASS = CONN_CLASS_UNKNOWN @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return unique ID if available.""" if not self.context: return None @@ -956,7 +956,7 @@ class ConfigFlow(data_entry_flow.FlowHandler): @callback def _abort_if_unique_id_configured( self, - updates: Optional[Dict[Any, Any]] = None, + updates: dict[Any, Any] | None = None, reload_on_update: bool = True, ) -> None: """Abort if the unique ID is already configured.""" @@ -983,8 +983,8 @@ class ConfigFlow(data_entry_flow.FlowHandler): raise data_entry_flow.AbortFlow("already_configured") async def async_set_unique_id( - self, unique_id: Optional[str] = None, *, raise_on_progress: bool = True - ) -> Optional[ConfigEntry]: + self, unique_id: str | None = None, *, raise_on_progress: bool = True + ) -> ConfigEntry | None: """Set a unique ID for the config flow. Returns optionally existing config entry with same ID. @@ -1020,7 +1020,7 @@ class ConfigFlow(data_entry_flow.FlowHandler): self.context["confirm_only"] = True @callback - def _async_current_entries(self, include_ignore: bool = False) -> List[ConfigEntry]: + def _async_current_entries(self, include_ignore: bool = False) -> list[ConfigEntry]: """Return current entries. If the flow is user initiated, filter out ignored entries unless include_ignore is True. @@ -1033,7 +1033,7 @@ class ConfigFlow(data_entry_flow.FlowHandler): return [entry for entry in config_entries if entry.source != SOURCE_IGNORE] @callback - def _async_current_ids(self, include_ignore: bool = True) -> Set[Optional[str]]: + def _async_current_ids(self, include_ignore: bool = True) -> set[str | None]: """Return current unique IDs.""" return { entry.unique_id @@ -1042,7 +1042,7 @@ class ConfigFlow(data_entry_flow.FlowHandler): } @callback - def _async_in_progress(self) -> List[Dict]: + def _async_in_progress(self) -> list[dict]: """Return other in progress flows for current domain.""" return [ flw @@ -1050,18 +1050,18 @@ class ConfigFlow(data_entry_flow.FlowHandler): if flw["handler"] == self.handler and flw["flow_id"] != self.flow_id ] - async def async_step_ignore(self, user_input: Dict[str, Any]) -> Dict[str, Any]: + async def async_step_ignore(self, user_input: dict[str, Any]) -> dict[str, Any]: """Ignore this config flow.""" await self.async_set_unique_id(user_input["unique_id"], raise_on_progress=False) return self.async_create_entry(title=user_input["title"], data={}) - async def async_step_unignore(self, user_input: Dict[str, Any]) -> Dict[str, Any]: + async def async_step_unignore(self, user_input: dict[str, Any]) -> dict[str, Any]: """Rediscover a config entry by it's unique_id.""" return self.async_abort(reason="not_implemented") async def async_step_user( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" return self.async_abort(reason="not_implemented") @@ -1090,16 +1090,16 @@ class ConfigFlow(data_entry_flow.FlowHandler): raise data_entry_flow.AbortFlow("already_in_progress") async def async_step_discovery( - self, discovery_info: Dict[str, Any] - ) -> Dict[str, Any]: + self, discovery_info: dict[str, Any] + ) -> dict[str, Any]: """Handle a flow initialized by discovery.""" await self._async_handle_discovery_without_unique_id() return await self.async_step_user() @callback def async_abort( - self, *, reason: str, description_placeholders: Optional[Dict] = None - ) -> Dict[str, Any]: + self, *, reason: str, description_placeholders: dict | None = None + ) -> dict[str, Any]: """Abort the config flow.""" # Remove reauth notification if no reauth flows are in progress if self.source == SOURCE_REAUTH and not any( @@ -1130,8 +1130,8 @@ class OptionsFlowManager(data_entry_flow.FlowManager): self, handler_key: Any, *, - context: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, + context: dict[str, Any] | None = None, + data: dict[str, Any] | None = None, ) -> OptionsFlow: """Create an options flow for a config entry. @@ -1147,8 +1147,8 @@ class OptionsFlowManager(data_entry_flow.FlowManager): return cast(OptionsFlow, HANDLERS[entry.domain].async_get_options_flow(entry)) async def async_finish_flow( - self, flow: data_entry_flow.FlowHandler, result: Dict[str, Any] - ) -> Dict[str, Any]: + self, flow: data_entry_flow.FlowHandler, result: dict[str, Any] + ) -> dict[str, Any]: """Finish an options flow and update options for configuration entry. Flow.handler and entry_id is the same thing to map flow with entry. @@ -1184,7 +1184,7 @@ class SystemOptions: """Update properties.""" self.disable_new_entities = disable_new_entities - def as_dict(self) -> Dict[str, Any]: + def as_dict(self) -> dict[str, Any]: """Return dictionary version of this config entries system options.""" return {"disable_new_entities": self.disable_new_entities} @@ -1195,9 +1195,9 @@ class EntityRegistryDisabledHandler: def __init__(self, hass: HomeAssistant) -> None: """Initialize the handler.""" self.hass = hass - self.registry: Optional[entity_registry.EntityRegistry] = None - self.changed: Set[str] = set() - self._remove_call_later: Optional[Callable[[], None]] = None + self.registry: entity_registry.EntityRegistry | None = None + self.changed: set[str] = set() + self._remove_call_later: Callable[[], None] | None = None @callback def async_setup(self) -> None: diff --git a/homeassistant/core.py b/homeassistant/core.py index b7bb645be36..4e0c3c48283 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -4,6 +4,8 @@ Core components of Home Assistant. Home Assistant is a Home Automation framework for observing the state of entities and react to changes. """ +from __future__ import annotations + import asyncio import datetime import enum @@ -22,15 +24,10 @@ from typing import ( Callable, Collection, Coroutine, - Dict, Iterable, - List, Mapping, Optional, - Set, - Tuple, TypeVar, - Union, cast, ) @@ -119,7 +116,7 @@ TIMEOUT_EVENT_START = 15 _LOGGER = logging.getLogger(__name__) -def split_entity_id(entity_id: str) -> List[str]: +def split_entity_id(entity_id: str) -> list[str]: """Split a state entity ID into domain and object ID.""" return entity_id.split(".", 1) @@ -237,7 +234,7 @@ class HomeAssistant: self.state: CoreState = CoreState.not_running self.exit_code: int = 0 # If not None, use to signal end-of-loop - self._stopped: Optional[asyncio.Event] = None + self._stopped: asyncio.Event | None = None # Timeout handler for Core/Helper namespace self.timeout: TimeoutManager = TimeoutManager() @@ -342,7 +339,7 @@ class HomeAssistant: @callback def async_add_job( self, target: Callable[..., Any], *args: Any - ) -> Optional[asyncio.Future]: + ) -> asyncio.Future | None: """Add a job from within the event loop. This method must be run in the event loop. @@ -359,9 +356,7 @@ class HomeAssistant: return self.async_add_hass_job(HassJob(target), *args) @callback - def async_add_hass_job( - self, hassjob: HassJob, *args: Any - ) -> Optional[asyncio.Future]: + def async_add_hass_job(self, hassjob: HassJob, *args: Any) -> asyncio.Future | None: """Add a HassJob from within the event loop. This method must be run in the event loop. @@ -423,9 +418,7 @@ class HomeAssistant: self._track_task = False @callback - def async_run_hass_job( - self, hassjob: HassJob, *args: Any - ) -> Optional[asyncio.Future]: + def async_run_hass_job(self, hassjob: HassJob, *args: Any) -> asyncio.Future | None: """Run a HassJob from within the event loop. This method must be run in the event loop. @@ -441,8 +434,8 @@ class HomeAssistant: @callback def async_run_job( - self, target: Callable[..., Union[None, Awaitable]], *args: Any - ) -> Optional[asyncio.Future]: + self, target: Callable[..., None | Awaitable], *args: Any + ) -> asyncio.Future | None: """Run a job from within the event loop. This method must be run in the event loop. @@ -465,7 +458,7 @@ class HomeAssistant: """Block until all pending work is done.""" # To flush out any call_soon_threadsafe await asyncio.sleep(0) - start_time: Optional[float] = None + start_time: float | None = None while self._pending_tasks: pending = [task for task in self._pending_tasks if not task.done()] @@ -582,10 +575,10 @@ class Context: """The context that triggered something.""" user_id: str = attr.ib(default=None) - parent_id: Optional[str] = attr.ib(default=None) + parent_id: str | None = attr.ib(default=None) id: str = attr.ib(factory=uuid_util.random_uuid_hex) - def as_dict(self) -> Dict[str, Optional[str]]: + def as_dict(self) -> dict[str, str | None]: """Return a dictionary representation of the context.""" return {"id": self.id, "parent_id": self.parent_id, "user_id": self.user_id} @@ -610,10 +603,10 @@ class Event: def __init__( self, event_type: str, - data: Optional[Dict[str, Any]] = None, + data: dict[str, Any] | None = None, origin: EventOrigin = EventOrigin.local, - time_fired: Optional[datetime.datetime] = None, - context: Optional[Context] = None, + time_fired: datetime.datetime | None = None, + context: Context | None = None, ) -> None: """Initialize a new event.""" self.event_type = event_type @@ -627,7 +620,7 @@ class Event: # The only event type that shares context are the TIME_CHANGED return hash((self.event_type, self.context.id, self.time_fired)) - def as_dict(self) -> Dict[str, Any]: + def as_dict(self) -> dict[str, Any]: """Create a dict representation of this Event. Async friendly. @@ -664,11 +657,11 @@ class EventBus: def __init__(self, hass: HomeAssistant) -> None: """Initialize a new event bus.""" - self._listeners: Dict[str, List[Tuple[HassJob, Optional[Callable]]]] = {} + self._listeners: dict[str, list[tuple[HassJob, Callable | None]]] = {} self._hass = hass @callback - def async_listeners(self) -> Dict[str, int]: + def async_listeners(self) -> dict[str, int]: """Return dictionary with events and the number of listeners. This method must be run in the event loop. @@ -676,16 +669,16 @@ class EventBus: return {key: len(self._listeners[key]) for key in self._listeners} @property - def listeners(self) -> Dict[str, int]: + def listeners(self) -> dict[str, int]: """Return dictionary with events and the number of listeners.""" return run_callback_threadsafe(self._hass.loop, self.async_listeners).result() def fire( self, event_type: str, - event_data: Optional[Dict] = None, + event_data: dict | None = None, origin: EventOrigin = EventOrigin.local, - context: Optional[Context] = None, + context: Context | None = None, ) -> None: """Fire an event.""" self._hass.loop.call_soon_threadsafe( @@ -696,10 +689,10 @@ class EventBus: def async_fire( self, event_type: str, - event_data: Optional[Dict[str, Any]] = None, + event_data: dict[str, Any] | None = None, origin: EventOrigin = EventOrigin.local, - context: Optional[Context] = None, - time_fired: Optional[datetime.datetime] = None, + context: Context | None = None, + time_fired: datetime.datetime | None = None, ) -> None: """Fire an event. @@ -751,7 +744,7 @@ class EventBus: self, event_type: str, listener: Callable, - event_filter: Optional[Callable] = None, + event_filter: Callable | None = None, ) -> CALLBACK_TYPE: """Listen for all events or events of a specific type. @@ -772,7 +765,7 @@ class EventBus: @callback def _async_listen_filterable_job( - self, event_type: str, filterable_job: Tuple[HassJob, Optional[Callable]] + self, event_type: str, filterable_job: tuple[HassJob, Callable | None] ) -> CALLBACK_TYPE: self._listeners.setdefault(event_type, []).append(filterable_job) @@ -811,7 +804,7 @@ class EventBus: This method must be run in the event loop. """ - filterable_job: Optional[Tuple[HassJob, Optional[Callable]]] = None + filterable_job: tuple[HassJob, Callable | None] | None = None @callback def _onetime_listener(event: Event) -> None: @@ -835,7 +828,7 @@ class EventBus: @callback def _async_remove_listener( - self, event_type: str, filterable_job: Tuple[HassJob, Optional[Callable]] + self, event_type: str, filterable_job: tuple[HassJob, Callable | None] ) -> None: """Remove a listener of a specific event_type. @@ -884,11 +877,11 @@ class State: self, entity_id: str, state: str, - attributes: Optional[Mapping[str, Any]] = None, - last_changed: Optional[datetime.datetime] = None, - last_updated: Optional[datetime.datetime] = None, - context: Optional[Context] = None, - validate_entity_id: Optional[bool] = True, + attributes: Mapping[str, Any] | None = None, + last_changed: datetime.datetime | None = None, + last_updated: datetime.datetime | None = None, + context: Context | None = None, + validate_entity_id: bool | None = True, ) -> None: """Initialize a new state.""" state = str(state) @@ -912,7 +905,7 @@ class State: self.last_changed = last_changed or self.last_updated self.context = context or Context() self.domain, self.object_id = split_entity_id(self.entity_id) - self._as_dict: Optional[Dict[str, Collection[Any]]] = None + self._as_dict: dict[str, Collection[Any]] | None = None @property def name(self) -> str: @@ -921,7 +914,7 @@ class State: "_", " " ) - def as_dict(self) -> Dict: + def as_dict(self) -> dict: """Return a dict representation of the State. Async friendly. @@ -946,7 +939,7 @@ class State: return self._as_dict @classmethod - def from_dict(cls, json_dict: Dict) -> Any: + def from_dict(cls, json_dict: dict) -> Any: """Initialize a state from a dict. Async friendly. @@ -1004,12 +997,12 @@ class StateMachine: def __init__(self, bus: EventBus, loop: asyncio.events.AbstractEventLoop) -> None: """Initialize state machine.""" - self._states: Dict[str, State] = {} - self._reservations: Set[str] = set() + self._states: dict[str, State] = {} + self._reservations: set[str] = set() self._bus = bus self._loop = loop - def entity_ids(self, domain_filter: Optional[str] = None) -> List[str]: + def entity_ids(self, domain_filter: str | None = None) -> list[str]: """List of entity ids that are being tracked.""" future = run_callback_threadsafe( self._loop, self.async_entity_ids, domain_filter @@ -1018,8 +1011,8 @@ class StateMachine: @callback def async_entity_ids( - self, domain_filter: Optional[Union[str, Iterable]] = None - ) -> List[str]: + self, domain_filter: str | Iterable | None = None + ) -> list[str]: """List of entity ids that are being tracked. This method must be run in the event loop. @@ -1038,7 +1031,7 @@ class StateMachine: @callback def async_entity_ids_count( - self, domain_filter: Optional[Union[str, Iterable]] = None + self, domain_filter: str | Iterable | None = None ) -> int: """Count the entity ids that are being tracked. @@ -1054,16 +1047,14 @@ class StateMachine: [None for state in self._states.values() if state.domain in domain_filter] ) - def all(self, domain_filter: Optional[Union[str, Iterable]] = None) -> List[State]: + def all(self, domain_filter: str | Iterable | None = None) -> list[State]: """Create a list of all states.""" return run_callback_threadsafe( self._loop, self.async_all, domain_filter ).result() @callback - def async_all( - self, domain_filter: Optional[Union[str, Iterable]] = None - ) -> List[State]: + def async_all(self, domain_filter: str | Iterable | None = None) -> list[State]: """Create a list of all states matching the filter. This method must be run in the event loop. @@ -1078,7 +1069,7 @@ class StateMachine: state for state in self._states.values() if state.domain in domain_filter ] - def get(self, entity_id: str) -> Optional[State]: + def get(self, entity_id: str) -> State | None: """Retrieve state of entity_id or None if not found. Async friendly. @@ -1103,7 +1094,7 @@ class StateMachine: ).result() @callback - def async_remove(self, entity_id: str, context: Optional[Context] = None) -> bool: + def async_remove(self, entity_id: str, context: Context | None = None) -> bool: """Remove the state of an entity. Returns boolean to indicate if an entity was removed. @@ -1131,9 +1122,9 @@ class StateMachine: self, entity_id: str, new_state: str, - attributes: Optional[Mapping[str, Any]] = None, + attributes: Mapping[str, Any] | None = None, force_update: bool = False, - context: Optional[Context] = None, + context: Context | None = None, ) -> None: """Set the state of an entity, add entity if it does not exist. @@ -1180,9 +1171,9 @@ class StateMachine: self, entity_id: str, new_state: str, - attributes: Optional[Mapping[str, Any]] = None, + attributes: Mapping[str, Any] | None = None, force_update: bool = False, - context: Optional[Context] = None, + context: Context | None = None, ) -> None: """Set the state of an entity, add entity if it does not exist. @@ -1241,8 +1232,8 @@ class Service: def __init__( self, func: Callable, - schema: Optional[vol.Schema], - context: Optional[Context] = None, + schema: vol.Schema | None, + context: Context | None = None, ) -> None: """Initialize a service.""" self.job = HassJob(func) @@ -1258,8 +1249,8 @@ class ServiceCall: self, domain: str, service: str, - data: Optional[Dict] = None, - context: Optional[Context] = None, + data: dict | None = None, + context: Context | None = None, ) -> None: """Initialize a service call.""" self.domain = domain.lower() @@ -1283,16 +1274,16 @@ class ServiceRegistry: def __init__(self, hass: HomeAssistant) -> None: """Initialize a service registry.""" - self._services: Dict[str, Dict[str, Service]] = {} + self._services: dict[str, dict[str, Service]] = {} self._hass = hass @property - def services(self) -> Dict[str, Dict[str, Service]]: + def services(self) -> dict[str, dict[str, Service]]: """Return dictionary with per domain a list of available services.""" return run_callback_threadsafe(self._hass.loop, self.async_services).result() @callback - def async_services(self) -> Dict[str, Dict[str, Service]]: + def async_services(self) -> dict[str, dict[str, Service]]: """Return dictionary with per domain a list of available services. This method must be run in the event loop. @@ -1311,7 +1302,7 @@ class ServiceRegistry: domain: str, service: str, service_func: Callable, - schema: Optional[vol.Schema] = None, + schema: vol.Schema | None = None, ) -> None: """ Register a service. @@ -1328,7 +1319,7 @@ class ServiceRegistry: domain: str, service: str, service_func: Callable, - schema: Optional[vol.Schema] = None, + schema: vol.Schema | None = None, ) -> None: """ Register a service. @@ -1382,12 +1373,12 @@ class ServiceRegistry: self, domain: str, service: str, - service_data: Optional[Dict] = None, + service_data: dict | None = None, blocking: bool = False, - context: Optional[Context] = None, - limit: Optional[float] = SERVICE_CALL_LIMIT, - target: Optional[Dict] = None, - ) -> Optional[bool]: + context: Context | None = None, + limit: float | None = SERVICE_CALL_LIMIT, + target: dict | None = None, + ) -> bool | None: """ Call a service. @@ -1404,12 +1395,12 @@ class ServiceRegistry: self, domain: str, service: str, - service_data: Optional[Dict] = None, + service_data: dict | None = None, blocking: bool = False, - context: Optional[Context] = None, - limit: Optional[float] = SERVICE_CALL_LIMIT, - target: Optional[Dict] = None, - ) -> Optional[bool]: + context: Context | None = None, + limit: float | None = SERVICE_CALL_LIMIT, + target: dict | None = None, + ) -> bool | None: """ Call a service. @@ -1497,7 +1488,7 @@ class ServiceRegistry: return False def _run_service_in_background( - self, coro_or_task: Union[Coroutine, asyncio.Task], service_call: ServiceCall + self, coro_or_task: Coroutine | asyncio.Task, service_call: ServiceCall ) -> None: """Run service call in background, catching and logging any exceptions.""" @@ -1542,8 +1533,8 @@ class Config: self.location_name: str = "Home" self.time_zone: datetime.tzinfo = dt_util.UTC self.units: UnitSystem = METRIC_SYSTEM - self.internal_url: Optional[str] = None - self.external_url: Optional[str] = None + self.internal_url: str | None = None + self.external_url: str | None = None self.config_source: str = "default" @@ -1551,22 +1542,22 @@ class Config: self.skip_pip: bool = False # List of loaded components - self.components: Set[str] = set() + self.components: set[str] = set() # API (HTTP) server configuration, see components.http.ApiConfig - self.api: Optional[Any] = None + self.api: Any | None = None # Directory that holds the configuration - self.config_dir: Optional[str] = None + self.config_dir: str | None = None # List of allowed external dirs to access - self.allowlist_external_dirs: Set[str] = set() + self.allowlist_external_dirs: set[str] = set() # List of allowed external URLs that integrations may use - self.allowlist_external_urls: Set[str] = set() + self.allowlist_external_urls: set[str] = set() # Dictionary of Media folders that integrations may use - self.media_dirs: Dict[str, str] = {} + self.media_dirs: dict[str, str] = {} # If Home Assistant is running in safe mode self.safe_mode: bool = False @@ -1574,7 +1565,7 @@ class Config: # Use legacy template behavior self.legacy_templates: bool = False - def distance(self, lat: float, lon: float) -> Optional[float]: + def distance(self, lat: float, lon: float) -> float | None: """Calculate distance from Home Assistant. Async friendly. @@ -1625,7 +1616,7 @@ class Config: return False - def as_dict(self) -> Dict: + def as_dict(self) -> dict: """Create a dictionary representation of the configuration. Async friendly. @@ -1670,15 +1661,15 @@ class Config: self, *, source: str, - latitude: Optional[float] = None, - longitude: Optional[float] = None, - elevation: Optional[int] = None, - unit_system: Optional[str] = None, - location_name: Optional[str] = None, - time_zone: Optional[str] = None, + latitude: float | None = None, + longitude: float | None = None, + elevation: int | None = None, + unit_system: str | None = None, + location_name: str | None = None, + time_zone: str | None = None, # pylint: disable=dangerous-default-value # _UNDEFs not modified - external_url: Optional[Union[str, dict]] = _UNDEF, - internal_url: Optional[Union[str, dict]] = _UNDEF, + external_url: str | dict | None = _UNDEF, + internal_url: str | dict | None = _UNDEF, ) -> None: """Update the configuration from a dictionary.""" self.config_source = source diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index 15ad417c6ad..f16c1859fcd 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -4,7 +4,7 @@ from __future__ import annotations import abc import asyncio from types import MappingProxyType -from typing import Any, Dict, List, Optional +from typing import Any import uuid import voluptuous as vol @@ -43,7 +43,7 @@ class UnknownStep(FlowError): class AbortFlow(FlowError): """Exception to indicate a flow needs to be aborted.""" - def __init__(self, reason: str, description_placeholders: Optional[Dict] = None): + def __init__(self, reason: str, description_placeholders: dict | None = None): """Initialize an abort flow exception.""" super().__init__(f"Flow aborted: {reason}") self.reason = reason @@ -59,8 +59,8 @@ class FlowManager(abc.ABC): ) -> None: """Initialize the flow manager.""" self.hass = hass - self._initializing: Dict[str, List[asyncio.Future]] = {} - self._progress: Dict[str, Any] = {} + self._initializing: dict[str, list[asyncio.Future]] = {} + self._progress: dict[str, Any] = {} async def async_wait_init_flow_finish(self, handler: str) -> None: """Wait till all flows in progress are initialized.""" @@ -76,8 +76,8 @@ class FlowManager(abc.ABC): self, handler_key: Any, *, - context: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, + context: dict[str, Any] | None = None, + data: dict[str, Any] | None = None, ) -> FlowHandler: """Create a flow for specified handler. @@ -86,17 +86,17 @@ class FlowManager(abc.ABC): @abc.abstractmethod async def async_finish_flow( - self, flow: "FlowHandler", result: Dict[str, Any] - ) -> Dict[str, Any]: + self, flow: "FlowHandler", result: dict[str, Any] + ) -> dict[str, Any]: """Finish a config flow and add an entry.""" async def async_post_init( - self, flow: "FlowHandler", result: Dict[str, Any] + self, flow: "FlowHandler", result: dict[str, Any] ) -> None: """Entry has finished executing its first step asynchronously.""" @callback - def async_progress(self) -> List[Dict]: + def async_progress(self) -> list[dict]: """Return the flows in progress.""" return [ { @@ -110,7 +110,7 @@ class FlowManager(abc.ABC): ] async def async_init( - self, handler: str, *, context: Optional[Dict] = None, data: Any = None + self, handler: str, *, context: dict | None = None, data: Any = None ) -> Any: """Start a configuration flow.""" if context is None: @@ -142,7 +142,7 @@ class FlowManager(abc.ABC): return result async def async_configure( - self, flow_id: str, user_input: Optional[Dict] = None + self, flow_id: str, user_input: dict | None = None ) -> Any: """Continue a configuration flow.""" flow = self._progress.get(flow_id) @@ -198,9 +198,9 @@ class FlowManager(abc.ABC): self, flow: Any, step_id: str, - user_input: Optional[Dict], - step_done: Optional[asyncio.Future] = None, - ) -> Dict: + user_input: dict | None, + step_done: asyncio.Future | None = None, + ) -> dict: """Handle a step of a flow.""" method = f"async_step_{step_id}" @@ -213,7 +213,7 @@ class FlowManager(abc.ABC): ) try: - result: Dict = await getattr(flow, method)(user_input) + result: dict = await getattr(flow, method)(user_input) except AbortFlow as err: result = _create_abort_data( flow.flow_id, flow.handler, err.reason, err.description_placeholders @@ -265,13 +265,13 @@ class FlowHandler: """Handle the configuration flow of a component.""" # Set by flow manager - cur_step: Optional[Dict[str, str]] = None + cur_step: dict[str, str] | None = None # Ignore types: https://github.com/PyCQA/pylint/issues/3167 flow_id: str = None # type: ignore hass: HomeAssistant = None # type: ignore handler: str = None # type: ignore # Ensure the attribute has a subscriptable, but immutable, default value. - context: Dict = MappingProxyType({}) # type: ignore + context: dict = MappingProxyType({}) # type: ignore # Set by _async_create_flow callback init_step = "init" @@ -280,7 +280,7 @@ class FlowHandler: VERSION = 1 @property - def source(self) -> Optional[str]: + def source(self) -> str | None: """Source that initialized the flow.""" if not hasattr(self, "context"): return None @@ -301,9 +301,9 @@ class FlowHandler: *, step_id: str, data_schema: vol.Schema = None, - errors: Optional[Dict] = None, - description_placeholders: Optional[Dict] = None, - ) -> Dict[str, Any]: + errors: dict | None = None, + description_placeholders: dict | None = None, + ) -> dict[str, Any]: """Return the definition of a form to gather user input.""" return { "type": RESULT_TYPE_FORM, @@ -320,10 +320,10 @@ class FlowHandler: self, *, title: str, - data: Dict, - description: Optional[str] = None, - description_placeholders: Optional[Dict] = None, - ) -> Dict[str, Any]: + data: dict, + description: str | None = None, + description_placeholders: dict | None = None, + ) -> dict[str, Any]: """Finish config flow and create a config entry.""" return { "version": self.VERSION, @@ -338,8 +338,8 @@ class FlowHandler: @callback def async_abort( - self, *, reason: str, description_placeholders: Optional[Dict] = None - ) -> Dict[str, Any]: + self, *, reason: str, description_placeholders: dict | None = None + ) -> dict[str, Any]: """Abort the config flow.""" return _create_abort_data( self.flow_id, self.handler, reason, description_placeholders @@ -347,8 +347,8 @@ class FlowHandler: @callback def async_external_step( - self, *, step_id: str, url: str, description_placeholders: Optional[Dict] = None - ) -> Dict[str, Any]: + self, *, step_id: str, url: str, description_placeholders: dict | None = None + ) -> dict[str, Any]: """Return the definition of an external step for the user to take.""" return { "type": RESULT_TYPE_EXTERNAL_STEP, @@ -360,7 +360,7 @@ class FlowHandler: } @callback - def async_external_step_done(self, *, next_step_id: str) -> Dict[str, Any]: + def async_external_step_done(self, *, next_step_id: str) -> dict[str, Any]: """Return the definition of an external step for the user to take.""" return { "type": RESULT_TYPE_EXTERNAL_STEP_DONE, @@ -375,8 +375,8 @@ class FlowHandler: *, step_id: str, progress_action: str, - description_placeholders: Optional[Dict] = None, - ) -> Dict[str, Any]: + description_placeholders: dict | None = None, + ) -> dict[str, Any]: """Show a progress message to the user, without user input allowed.""" return { "type": RESULT_TYPE_SHOW_PROGRESS, @@ -388,7 +388,7 @@ class FlowHandler: } @callback - def async_show_progress_done(self, *, next_step_id: str) -> Dict[str, Any]: + def async_show_progress_done(self, *, next_step_id: str) -> dict[str, Any]: """Mark the progress done.""" return { "type": RESULT_TYPE_SHOW_PROGRESS_DONE, @@ -403,8 +403,8 @@ def _create_abort_data( flow_id: str, handler: str, reason: str, - description_placeholders: Optional[Dict] = None, -) -> Dict[str, Any]: + description_placeholders: dict | None = None, +) -> dict[str, Any]: """Return the definition of an external step for the user to take.""" return { "type": RESULT_TYPE_ABORT, diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index 9a7b3da0f1d..499d4052849 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -1,5 +1,7 @@ """The exceptions used by Home Assistant.""" -from typing import TYPE_CHECKING, Generator, Optional, Sequence +from __future__ import annotations + +from typing import TYPE_CHECKING, Generator, Sequence import attr @@ -113,12 +115,12 @@ class Unauthorized(HomeAssistantError): def __init__( self, - context: Optional["Context"] = None, - user_id: Optional[str] = None, - entity_id: Optional[str] = None, - config_entry_id: Optional[str] = None, - perm_category: Optional[str] = None, - permission: Optional[str] = None, + context: "Context" | None = None, + user_id: str | None = None, + entity_id: str | None = None, + config_entry_id: str | None = None, + perm_category: str | None = None, + permission: str | None = None, ) -> None: """Unauthorized error.""" super().__init__(self.__class__.__name__) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 2ae279da79e..f6cb24698ec 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -14,19 +14,7 @@ import logging import pathlib import sys from types import ModuleType -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Dict, - List, - Optional, - Set, - TypedDict, - TypeVar, - Union, - cast, -) +from typing import TYPE_CHECKING, Any, Callable, Dict, TypedDict, TypeVar, cast from awesomeversion import AwesomeVersion, AwesomeVersionStrategy @@ -86,21 +74,21 @@ class Manifest(TypedDict, total=False): name: str disabled: str domain: str - dependencies: List[str] - after_dependencies: List[str] - requirements: List[str] + dependencies: list[str] + after_dependencies: list[str] + requirements: list[str] config_flow: bool documentation: str issue_tracker: str quality_scale: str - mqtt: List[str] - ssdp: List[Dict[str, str]] - zeroconf: List[Union[str, Dict[str, str]]] - dhcp: List[Dict[str, str]] - homekit: Dict[str, List[str]] + mqtt: list[str] + ssdp: list[dict[str, str]] + zeroconf: list[str | dict[str, str]] + dhcp: list[dict[str, str]] + homekit: dict[str, list[str]] is_built_in: bool version: str - codeowners: List[str] + codeowners: list[str] def manifest_from_legacy_module(domain: str, module: ModuleType) -> Manifest: @@ -116,7 +104,7 @@ def manifest_from_legacy_module(domain: str, module: ModuleType) -> Manifest: async def _async_get_custom_components( hass: "HomeAssistant", -) -> Dict[str, Integration]: +) -> dict[str, Integration]: """Return list of custom integrations.""" if hass.config.safe_mode: return {} @@ -126,7 +114,7 @@ async def _async_get_custom_components( except ImportError: return {} - def get_sub_directories(paths: List[str]) -> List[pathlib.Path]: + def get_sub_directories(paths: list[str]) -> list[pathlib.Path]: """Return all sub directories in a set of paths.""" return [ entry @@ -157,7 +145,7 @@ async def _async_get_custom_components( async def async_get_custom_components( hass: "HomeAssistant", -) -> Dict[str, Integration]: +) -> dict[str, Integration]: """Return cached list of custom integrations.""" reg_or_evt = hass.data.get(DATA_CUSTOM_COMPONENTS) @@ -177,12 +165,12 @@ async def async_get_custom_components( return cast(Dict[str, "Integration"], reg_or_evt) -async def async_get_config_flows(hass: HomeAssistant) -> Set[str]: +async def async_get_config_flows(hass: HomeAssistant) -> set[str]: """Return cached list of config flows.""" # pylint: disable=import-outside-toplevel from homeassistant.generated.config_flows import FLOWS - flows: Set[str] = set() + flows: set[str] = set() flows.update(FLOWS) integrations = await async_get_custom_components(hass) @@ -197,9 +185,9 @@ async def async_get_config_flows(hass: HomeAssistant) -> Set[str]: return flows -async def async_get_zeroconf(hass: HomeAssistant) -> Dict[str, List[Dict[str, str]]]: +async def async_get_zeroconf(hass: HomeAssistant) -> dict[str, list[dict[str, str]]]: """Return cached list of zeroconf types.""" - zeroconf: Dict[str, List[Dict[str, str]]] = ZEROCONF.copy() + zeroconf: dict[str, list[dict[str, str]]] = ZEROCONF.copy() integrations = await async_get_custom_components(hass) for integration in integrations.values(): @@ -220,9 +208,9 @@ async def async_get_zeroconf(hass: HomeAssistant) -> Dict[str, List[Dict[str, st return zeroconf -async def async_get_dhcp(hass: HomeAssistant) -> List[Dict[str, str]]: +async def async_get_dhcp(hass: HomeAssistant) -> list[dict[str, str]]: """Return cached list of dhcp types.""" - dhcp: List[Dict[str, str]] = DHCP.copy() + dhcp: list[dict[str, str]] = DHCP.copy() integrations = await async_get_custom_components(hass) for integration in integrations.values(): @@ -234,10 +222,10 @@ async def async_get_dhcp(hass: HomeAssistant) -> List[Dict[str, str]]: return dhcp -async def async_get_homekit(hass: HomeAssistant) -> Dict[str, str]: +async def async_get_homekit(hass: HomeAssistant) -> dict[str, str]: """Return cached list of homekit models.""" - homekit: Dict[str, str] = HOMEKIT.copy() + homekit: dict[str, str] = HOMEKIT.copy() integrations = await async_get_custom_components(hass) for integration in integrations.values(): @@ -253,10 +241,10 @@ async def async_get_homekit(hass: HomeAssistant) -> Dict[str, str]: return homekit -async def async_get_ssdp(hass: HomeAssistant) -> Dict[str, List[Dict[str, str]]]: +async def async_get_ssdp(hass: HomeAssistant) -> dict[str, list[dict[str, str]]]: """Return cached list of ssdp mappings.""" - ssdp: Dict[str, List[Dict[str, str]]] = SSDP.copy() + ssdp: dict[str, list[dict[str, str]]] = SSDP.copy() integrations = await async_get_custom_components(hass) for integration in integrations.values(): @@ -268,10 +256,10 @@ async def async_get_ssdp(hass: HomeAssistant) -> Dict[str, List[Dict[str, str]]] return ssdp -async def async_get_mqtt(hass: HomeAssistant) -> Dict[str, List[str]]: +async def async_get_mqtt(hass: HomeAssistant) -> dict[str, list[str]]: """Return cached list of MQTT mappings.""" - mqtt: Dict[str, List[str]] = MQTT.copy() + mqtt: dict[str, list[str]] = MQTT.copy() integrations = await async_get_custom_components(hass) for integration in integrations.values(): @@ -289,7 +277,7 @@ class Integration: @classmethod def resolve_from_root( cls, hass: "HomeAssistant", root_module: ModuleType, domain: str - ) -> Optional[Integration]: + ) -> Integration | None: """Resolve an integration from a root module.""" for base in root_module.__path__: # type: ignore manifest_path = pathlib.Path(base) / domain / "manifest.json" @@ -312,9 +300,7 @@ class Integration: return None @classmethod - def resolve_legacy( - cls, hass: "HomeAssistant", domain: str - ) -> Optional[Integration]: + def resolve_legacy(cls, hass: "HomeAssistant", domain: str) -> Integration | None: """Resolve legacy component. Will create a stub manifest. @@ -346,8 +332,8 @@ class Integration: manifest["is_built_in"] = self.is_built_in if self.dependencies: - self._all_dependencies_resolved: Optional[bool] = None - self._all_dependencies: Optional[Set[str]] = None + self._all_dependencies_resolved: bool | None = None + self._all_dependencies: set[str] | None = None else: self._all_dependencies_resolved = True self._all_dependencies = set() @@ -360,7 +346,7 @@ class Integration: return self.manifest["name"] @property - def disabled(self) -> Optional[str]: + def disabled(self) -> str | None: """Return reason integration is disabled.""" return self.manifest.get("disabled") @@ -370,17 +356,17 @@ class Integration: return self.manifest["domain"] @property - def dependencies(self) -> List[str]: + def dependencies(self) -> list[str]: """Return dependencies.""" return self.manifest.get("dependencies", []) @property - def after_dependencies(self) -> List[str]: + def after_dependencies(self) -> list[str]: """Return after_dependencies.""" return self.manifest.get("after_dependencies", []) @property - def requirements(self) -> List[str]: + def requirements(self) -> list[str]: """Return requirements.""" return self.manifest.get("requirements", []) @@ -390,42 +376,42 @@ class Integration: return self.manifest.get("config_flow") or False @property - def documentation(self) -> Optional[str]: + def documentation(self) -> str | None: """Return documentation.""" return self.manifest.get("documentation") @property - def issue_tracker(self) -> Optional[str]: + def issue_tracker(self) -> str | None: """Return issue tracker link.""" return self.manifest.get("issue_tracker") @property - def quality_scale(self) -> Optional[str]: + def quality_scale(self) -> str | None: """Return Integration Quality Scale.""" return self.manifest.get("quality_scale") @property - def mqtt(self) -> Optional[List[str]]: + def mqtt(self) -> list[str] | None: """Return Integration MQTT entries.""" return self.manifest.get("mqtt") @property - def ssdp(self) -> Optional[List[Dict[str, str]]]: + def ssdp(self) -> list[dict[str, str]] | None: """Return Integration SSDP entries.""" return self.manifest.get("ssdp") @property - def zeroconf(self) -> Optional[List[Union[str, Dict[str, str]]]]: + def zeroconf(self) -> list[str | dict[str, str]] | None: """Return Integration zeroconf entries.""" return self.manifest.get("zeroconf") @property - def dhcp(self) -> Optional[List[Dict[str, str]]]: + def dhcp(self) -> list[dict[str, str]] | None: """Return Integration dhcp entries.""" return self.manifest.get("dhcp") @property - def homekit(self) -> Optional[Dict[str, List[str]]]: + def homekit(self) -> dict[str, list[str]] | None: """Return Integration homekit entries.""" return self.manifest.get("homekit") @@ -435,14 +421,14 @@ class Integration: return self.pkg_path.startswith(PACKAGE_BUILTIN) @property - def version(self) -> Optional[AwesomeVersion]: + def version(self) -> AwesomeVersion | None: """Return the version of the integration.""" if "version" not in self.manifest: return None return AwesomeVersion(self.manifest["version"]) @property - def all_dependencies(self) -> Set[str]: + def all_dependencies(self) -> set[str]: """Return all dependencies including sub-dependencies.""" if self._all_dependencies is None: raise RuntimeError("Dependencies not resolved!") @@ -516,7 +502,7 @@ async def async_get_integration(hass: "HomeAssistant", domain: str) -> Integrati raise IntegrationNotFound(domain) cache = hass.data[DATA_INTEGRATIONS] = {} - int_or_evt: Union[Integration, asyncio.Event, None] = cache.get(domain, _UNDEF) + int_or_evt: Integration | asyncio.Event | None = cache.get(domain, _UNDEF) if isinstance(int_or_evt, asyncio.Event): await int_or_evt.wait() @@ -593,8 +579,8 @@ class CircularDependency(LoaderError): def _load_file( - hass: "HomeAssistant", comp_or_platform: str, base_paths: List[str] -) -> Optional[ModuleType]: + hass: "HomeAssistant", comp_or_platform: str, base_paths: list[str] +) -> ModuleType | None: """Try to load specified file. Looks in config dir first, then built-in components. @@ -683,7 +669,7 @@ class Components: integration = self._hass.data.get(DATA_INTEGRATIONS, {}).get(comp_name) if isinstance(integration, Integration): - component: Optional[ModuleType] = integration.get_component() + component: ModuleType | None = integration.get_component() else: # Fallback to importing old-school component = _load_file(self._hass, comp_name, _lookup_path(self._hass)) @@ -721,9 +707,9 @@ async def _async_component_dependencies( hass: "HomeAssistant", start_domain: str, integration: Integration, - loaded: Set[str], - loading: Set[str], -) -> Set[str]: + loaded: set[str], + loading: set[str], +) -> set[str]: """Recursive function to get component dependencies. Async friendly. @@ -773,7 +759,7 @@ def _async_mount_config_dir(hass: HomeAssistant) -> bool: return True -def _lookup_path(hass: HomeAssistant) -> List[str]: +def _lookup_path(hass: HomeAssistant) -> list[str]: """Return the lookup paths for legacy lookups.""" if hass.config.safe_mode: return [PACKAGE_BUILTIN] diff --git a/homeassistant/requirements.py b/homeassistant/requirements.py index 6ea2074ddf4..f073fd13df8 100644 --- a/homeassistant/requirements.py +++ b/homeassistant/requirements.py @@ -1,7 +1,9 @@ """Module to handle installing requirements.""" +from __future__ import annotations + import asyncio import os -from typing import Any, Dict, Iterable, List, Optional, Set, Union, cast +from typing import Any, Iterable, cast from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError @@ -15,7 +17,7 @@ DATA_PIP_LOCK = "pip_lock" DATA_PKG_CACHE = "pkg_cache" DATA_INTEGRATIONS_WITH_REQS = "integrations_with_reqs" CONSTRAINT_FILE = "package_constraints.txt" -DISCOVERY_INTEGRATIONS: Dict[str, Iterable[str]] = { +DISCOVERY_INTEGRATIONS: dict[str, Iterable[str]] = { "dhcp": ("dhcp",), "mqtt": ("mqtt",), "ssdp": ("ssdp",), @@ -26,7 +28,7 @@ DISCOVERY_INTEGRATIONS: Dict[str, Iterable[str]] = { class RequirementsNotFound(HomeAssistantError): """Raised when a component is not found.""" - def __init__(self, domain: str, requirements: List[str]) -> None: + def __init__(self, domain: str, requirements: list[str]) -> None: """Initialize a component not found error.""" super().__init__(f"Requirements for {domain} not found: {requirements}.") self.domain = domain @@ -34,7 +36,7 @@ class RequirementsNotFound(HomeAssistantError): async def async_get_integration_with_requirements( - hass: HomeAssistant, domain: str, done: Optional[Set[str]] = None + hass: HomeAssistant, domain: str, done: set[str] | None = None ) -> Integration: """Get an integration with all requirements installed, including the dependencies. @@ -56,7 +58,7 @@ async def async_get_integration_with_requirements( if cache is None: cache = hass.data[DATA_INTEGRATIONS_WITH_REQS] = {} - int_or_evt: Union[Integration, asyncio.Event, None, UndefinedType] = cache.get( + int_or_evt: Integration | asyncio.Event | None | UndefinedType = cache.get( domain, UNDEFINED ) @@ -108,7 +110,7 @@ async def async_get_integration_with_requirements( async def async_process_requirements( - hass: HomeAssistant, name: str, requirements: List[str] + hass: HomeAssistant, name: str, requirements: list[str] ) -> None: """Install the requirements for a component or platform. @@ -126,7 +128,7 @@ async def async_process_requirements( if pkg_util.is_installed(req): continue - def _install(req: str, kwargs: Dict[str, Any]) -> bool: + def _install(req: str, kwargs: dict[str, Any]) -> bool: """Install requirement.""" return pkg_util.install_package(req, **kwargs) @@ -136,7 +138,7 @@ async def async_process_requirements( raise RequirementsNotFound(name, [req]) -def pip_kwargs(config_dir: Optional[str]) -> Dict[str, Any]: +def pip_kwargs(config_dir: str | None) -> dict[str, Any]: """Return keyword arguments for PIP install.""" is_docker = pkg_util.is_docker_env() kwargs = { diff --git a/homeassistant/runner.py b/homeassistant/runner.py index 6f13a47be81..5adddb5f6ef 100644 --- a/homeassistant/runner.py +++ b/homeassistant/runner.py @@ -1,9 +1,11 @@ """Run Home Assistant.""" +from __future__ import annotations + import asyncio from concurrent.futures import ThreadPoolExecutor import dataclasses import logging -from typing import Any, Dict, Optional +from typing import Any from homeassistant import bootstrap from homeassistant.core import callback @@ -34,8 +36,8 @@ class RuntimeConfig: verbose: bool = False - log_rotate_days: Optional[int] = None - log_file: Optional[str] = None + log_rotate_days: int | None = None + log_file: str | None = None log_no_color: bool = False debug: bool = False @@ -83,7 +85,7 @@ class HassEventLoopPolicy(asyncio.DefaultEventLoopPolicy): # type: ignore[valid @callback -def _async_loop_exception_handler(_: Any, context: Dict[str, Any]) -> None: +def _async_loop_exception_handler(_: Any, context: dict[str, Any]) -> None: """Handle all exception inside the core loop.""" kwargs = {} exception = context.get("exception") diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 4060b0410d6..4c5e10a254b 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -1,9 +1,11 @@ """All methods needed to bootstrap a Home Assistant instance.""" +from __future__ import annotations + import asyncio import logging.handlers from timeit import default_timer as timer from types import ModuleType -from typing import Awaitable, Callable, Optional, Set +from typing import Awaitable, Callable from homeassistant import config as conf_util, core, loader, requirements from homeassistant.config import async_notify_setup_error @@ -26,7 +28,7 @@ SLOW_SETUP_MAX_WAIT = 300 @core.callback -def async_set_domains_to_be_loaded(hass: core.HomeAssistant, domains: Set[str]) -> None: +def async_set_domains_to_be_loaded(hass: core.HomeAssistant, domains: set[str]) -> None: """Set domains that are going to be loaded from the config. This will allow us to properly handle after_dependencies. @@ -133,7 +135,7 @@ async def _async_setup_component( This method is a coroutine. """ - def log_error(msg: str, link: Optional[str] = None) -> None: + def log_error(msg: str, link: str | None = None) -> None: """Log helper.""" _LOGGER.error("Setup failed for %s: %s", domain, msg) async_notify_setup_error(hass, domain, link) @@ -268,7 +270,7 @@ async def _async_setup_component( async def async_prepare_setup_platform( hass: core.HomeAssistant, hass_config: ConfigType, domain: str, platform_name: str -) -> Optional[ModuleType]: +) -> ModuleType | None: """Load a platform and makes sure dependencies are setup. This method is a coroutine. From 86d3baa34e4c296f112b4bbc9540f4b0461379aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20Kr=C3=B6ner?= Date: Wed, 17 Mar 2021 17:39:47 +0100 Subject: [PATCH 428/831] Improve OWM Precipitation sensors (#47945) --- .../components/openweathermap/const.py | 8 +++- .../weather_update_coordinator.py | 37 +++++++++++++------ 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/openweathermap/const.py b/homeassistant/components/openweathermap/const.py index 4ab61b486f1..36d38ff4688 100644 --- a/homeassistant/components/openweathermap/const.py +++ b/homeassistant/components/openweathermap/const.py @@ -47,6 +47,7 @@ CONFIG_FLOW_VERSION = 2 ENTRY_NAME = "name" ENTRY_WEATHER_COORDINATOR = "weather_coordinator" ATTR_API_PRECIPITATION = "precipitation" +ATTR_API_PRECIPITATION_KIND = "precipitation_kind" ATTR_API_DATETIME = "datetime" ATTR_API_DEW_POINT = "dew_point" ATTR_API_WEATHER = "weather" @@ -94,6 +95,7 @@ MONITORED_CONDITIONS = [ ATTR_API_CLOUDS, ATTR_API_RAIN, ATTR_API_SNOW, + ATTR_API_PRECIPITATION_KIND, ATTR_API_UV_INDEX, ATTR_API_CONDITION, ATTR_API_WEATHER_CODE, @@ -225,6 +227,7 @@ WEATHER_SENSOR_TYPES = { ATTR_API_CLOUDS: {SENSOR_NAME: "Cloud coverage", SENSOR_UNIT: PERCENTAGE}, ATTR_API_RAIN: {SENSOR_NAME: "Rain", SENSOR_UNIT: LENGTH_MILLIMETERS}, ATTR_API_SNOW: {SENSOR_NAME: "Snow", SENSOR_UNIT: LENGTH_MILLIMETERS}, + ATTR_API_PRECIPITATION_KIND: {SENSOR_NAME: "Precipitation kind"}, ATTR_API_UV_INDEX: { SENSOR_NAME: "UV Index", SENSOR_UNIT: UV_INDEX, @@ -234,7 +237,10 @@ WEATHER_SENSOR_TYPES = { } FORECAST_SENSOR_TYPES = { ATTR_FORECAST_CONDITION: {SENSOR_NAME: "Condition"}, - ATTR_FORECAST_PRECIPITATION: {SENSOR_NAME: "Precipitation"}, + ATTR_FORECAST_PRECIPITATION: { + SENSOR_NAME: "Precipitation", + SENSOR_UNIT: LENGTH_MILLIMETERS, + }, ATTR_FORECAST_PRECIPITATION_PROBABILITY: { SENSOR_NAME: "Precipitation probability", SENSOR_UNIT: PERCENTAGE, diff --git a/homeassistant/components/openweathermap/weather_update_coordinator.py b/homeassistant/components/openweathermap/weather_update_coordinator.py index 26ada47bef6..51e475eb754 100644 --- a/homeassistant/components/openweathermap/weather_update_coordinator.py +++ b/homeassistant/components/openweathermap/weather_update_coordinator.py @@ -29,6 +29,7 @@ from .const import ( ATTR_API_FEELS_LIKE_TEMPERATURE, ATTR_API_FORECAST, ATTR_API_HUMIDITY, + ATTR_API_PRECIPITATION_KIND, ATTR_API_PRESSURE, ATTR_API_RAIN, ATTR_API_SNOW, @@ -129,6 +130,9 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): ATTR_API_CLOUDS: current_weather.clouds, ATTR_API_RAIN: self._get_rain(current_weather.rain), ATTR_API_SNOW: self._get_snow(current_weather.snow), + ATTR_API_PRECIPITATION_KIND: self._calc_precipitation_kind( + current_weather.rain, current_weather.snow + ), ATTR_API_WEATHER: current_weather.detailed_status, ATTR_API_CONDITION: self._get_condition(current_weather.weather_code), ATTR_API_UV_INDEX: current_weather.uvi, @@ -178,36 +182,45 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): def _get_rain(rain): """Get rain data from weather data.""" if "all" in rain: - return round(rain["all"], 0) + return round(rain["all"], 2) if "1h" in rain: - return round(rain["1h"], 0) - return "not raining" + return round(rain["1h"], 2) + return 0 @staticmethod def _get_snow(snow): """Get snow data from weather data.""" if snow: if "all" in snow: - return round(snow["all"], 0) + return round(snow["all"], 2) if "1h" in snow: - return round(snow["1h"], 0) - return "not snowing" - return "not snowing" + return round(snow["1h"], 2) + return 0 @staticmethod def _calc_precipitation(rain, snow): """Calculate the precipitation.""" rain_value = 0 - if WeatherUpdateCoordinator._get_rain(rain) != "not raining": + if WeatherUpdateCoordinator._get_rain(rain) != 0: rain_value = WeatherUpdateCoordinator._get_rain(rain) snow_value = 0 - if WeatherUpdateCoordinator._get_snow(snow) != "not snowing": + if WeatherUpdateCoordinator._get_snow(snow) != 0: snow_value = WeatherUpdateCoordinator._get_snow(snow) - if round(rain_value + snow_value, 1) == 0: - return None - return round(rain_value + snow_value, 1) + return round(rain_value + snow_value, 2) + + @staticmethod + def _calc_precipitation_kind(rain, snow): + """Determine the precipitation kind.""" + if WeatherUpdateCoordinator._get_rain(rain) != 0: + if WeatherUpdateCoordinator._get_snow(snow) != 0: + return "Snow and Rain" + return "Rain" + + if WeatherUpdateCoordinator._get_snow(snow) != 0: + return "Snow" + return "None" def _get_condition(self, weather_code, timestamp=None): """Get weather condition from weather data.""" From 6fb2e63e49f01d9eaf09c7b21122981e462e4e99 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 17 Mar 2021 18:34:19 +0100 Subject: [PATCH 429/831] Update typing 02 (#48014) --- homeassistant/helpers/__init__.py | 6 +- homeassistant/helpers/aiohttp_client.py | 10 +- homeassistant/helpers/area_registry.py | 14 +- homeassistant/helpers/check_config.py | 12 +- homeassistant/helpers/collection.py | 24 +-- homeassistant/helpers/condition.py | 60 +++--- homeassistant/helpers/config_entry_flow.py | 22 ++- .../helpers/config_entry_oauth2_flow.py | 24 +-- homeassistant/helpers/config_validation.py | 72 +++---- homeassistant/helpers/data_entry_flow.py | 9 +- homeassistant/helpers/debounce.py | 12 +- homeassistant/helpers/deprecation.py | 10 +- homeassistant/helpers/device_registry.py | 176 +++++++++--------- homeassistant/helpers/discovery.py | 14 +- homeassistant/helpers/entity.py | 56 +++--- homeassistant/helpers/entity_component.py | 32 ++-- homeassistant/helpers/entity_platform.py | 36 ++-- homeassistant/helpers/entity_registry.py | 129 ++++++------- homeassistant/helpers/entity_values.py | 16 +- homeassistant/helpers/entityfilter.py | 22 ++- homeassistant/helpers/event.py | 131 ++++++------- homeassistant/helpers/frame.py | 10 +- homeassistant/helpers/httpx_client.py | 6 +- homeassistant/helpers/icon.py | 6 +- homeassistant/helpers/instance_id.py | 5 +- homeassistant/helpers/intent.py | 32 ++-- homeassistant/helpers/location.py | 11 +- homeassistant/helpers/logging.py | 8 +- homeassistant/helpers/network.py | 6 +- homeassistant/helpers/ratelimit.py | 14 +- homeassistant/helpers/reload.py | 13 +- homeassistant/helpers/restore_state.py | 14 +- homeassistant/helpers/script.py | 69 +++---- homeassistant/helpers/script_variables.py | 12 +- homeassistant/helpers/service.py | 57 +++--- homeassistant/helpers/significant_change.py | 24 ++- homeassistant/helpers/singleton.py | 6 +- homeassistant/helpers/state.py | 28 +-- homeassistant/helpers/storage.py | 22 ++- homeassistant/helpers/sun.py | 16 +- homeassistant/helpers/system_info.py | 6 +- homeassistant/helpers/temperature.py | 7 +- homeassistant/helpers/template.py | 38 ++-- homeassistant/helpers/trace.py | 36 ++-- homeassistant/helpers/translation.py | 56 +++--- homeassistant/helpers/trigger.py | 14 +- homeassistant/helpers/update_coordinator.py | 20 +- 47 files changed, 717 insertions(+), 706 deletions(-) diff --git a/homeassistant/helpers/__init__.py b/homeassistant/helpers/__init__.py index 6f22be8d323..195c1838146 100644 --- a/homeassistant/helpers/__init__.py +++ b/homeassistant/helpers/__init__.py @@ -1,6 +1,8 @@ """Helper methods for components within Home Assistant.""" +from __future__ import annotations + import re -from typing import TYPE_CHECKING, Any, Iterable, Sequence, Tuple +from typing import TYPE_CHECKING, Any, Iterable, Sequence from homeassistant.const import CONF_PLATFORM @@ -8,7 +10,7 @@ if TYPE_CHECKING: from .typing import ConfigType -def config_per_platform(config: "ConfigType", domain: str) -> Iterable[Tuple[Any, Any]]: +def config_per_platform(config: "ConfigType", domain: str) -> Iterable[tuple[Any, Any]]: """Break a component config into different platforms. For example, will find 'switch', 'switch 2', 'switch 3', .. etc diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py index 3e1e45e5981..2a362028d51 100644 --- a/homeassistant/helpers/aiohttp_client.py +++ b/homeassistant/helpers/aiohttp_client.py @@ -1,8 +1,10 @@ """Helper for aiohttp webclient stuff.""" +from __future__ import annotations + import asyncio from ssl import SSLContext import sys -from typing import Any, Awaitable, Optional, Union, cast +from typing import Any, Awaitable, cast import aiohttp from aiohttp import web @@ -87,7 +89,7 @@ async def async_aiohttp_proxy_web( web_coro: Awaitable[aiohttp.ClientResponse], buffer_size: int = 102400, timeout: int = 10, -) -> Optional[web.StreamResponse]: +) -> web.StreamResponse | None: """Stream websession request to aiohttp web response.""" try: with async_timeout.timeout(timeout): @@ -118,7 +120,7 @@ async def async_aiohttp_proxy_stream( hass: HomeAssistantType, request: web.BaseRequest, stream: aiohttp.StreamReader, - content_type: Optional[str], + content_type: str | None, buffer_size: int = 102400, timeout: int = 10, ) -> web.StreamResponse: @@ -175,7 +177,7 @@ def _async_get_connector( return cast(aiohttp.BaseConnector, hass.data[key]) if verify_ssl: - ssl_context: Union[bool, SSLContext] = ssl_util.client_context() + ssl_context: bool | SSLContext = ssl_util.client_context() else: ssl_context = False diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index 164207a8b2a..c181fadcfd3 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -1,6 +1,8 @@ """Provide a way to connect devices to one physical location.""" +from __future__ import annotations + from collections import OrderedDict -from typing import Container, Dict, Iterable, List, MutableMapping, Optional, cast +from typing import Container, Iterable, MutableMapping, cast import attr @@ -26,7 +28,7 @@ class AreaEntry: name: str = attr.ib() normalized_name: str = attr.ib() - id: Optional[str] = attr.ib(default=None) + id: str | None = attr.ib(default=None) def generate_id(self, existing_ids: Container[str]) -> None: """Initialize ID.""" @@ -46,15 +48,15 @@ class AreaRegistry: self.hass = hass self.areas: MutableMapping[str, AreaEntry] = {} self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) - self._normalized_name_area_idx: Dict[str, str] = {} + self._normalized_name_area_idx: dict[str, str] = {} @callback - def async_get_area(self, area_id: str) -> Optional[AreaEntry]: + def async_get_area(self, area_id: str) -> AreaEntry | None: """Get area by id.""" return self.areas.get(area_id) @callback - def async_get_area_by_name(self, name: str) -> Optional[AreaEntry]: + def async_get_area_by_name(self, name: str) -> AreaEntry | None: """Get area by name.""" normalized_name = normalize_area_name(name) if normalized_name not in self._normalized_name_area_idx: @@ -171,7 +173,7 @@ class AreaRegistry: self._store.async_delay_save(self._data_to_save, SAVE_DELAY) @callback - def _data_to_save(self) -> Dict[str, List[Dict[str, Optional[str]]]]: + def _data_to_save(self) -> dict[str, list[dict[str, str | None]]]: """Return data of area registry to store in a file.""" data = {} diff --git a/homeassistant/helpers/check_config.py b/homeassistant/helpers/check_config.py index 5dd7623ecc8..a486c8bcc14 100644 --- a/homeassistant/helpers/check_config.py +++ b/homeassistant/helpers/check_config.py @@ -5,7 +5,7 @@ from collections import OrderedDict import logging import os from pathlib import Path -from typing import List, NamedTuple, Optional +from typing import NamedTuple import voluptuous as vol @@ -35,8 +35,8 @@ class CheckConfigError(NamedTuple): """Configuration check error.""" message: str - domain: Optional[str] - config: Optional[ConfigType] + domain: str | None + config: ConfigType | None class HomeAssistantConfig(OrderedDict): @@ -45,13 +45,13 @@ class HomeAssistantConfig(OrderedDict): def __init__(self) -> None: """Initialize HA config.""" super().__init__() - self.errors: List[CheckConfigError] = [] + self.errors: list[CheckConfigError] = [] def add_error( self, message: str, - domain: Optional[str] = None, - config: Optional[ConfigType] = None, + domain: str | None = None, + config: ConfigType | None = None, ) -> HomeAssistantConfig: """Add a single error.""" self.errors.append(CheckConfigError(str(message), domain, config)) diff --git a/homeassistant/helpers/collection.py b/homeassistant/helpers/collection.py index abeef0f0d68..0c74ac413e7 100644 --- a/homeassistant/helpers/collection.py +++ b/homeassistant/helpers/collection.py @@ -1,9 +1,11 @@ """Helper to deal with YAML + storage.""" +from __future__ import annotations + from abc import ABC, abstractmethod import asyncio from dataclasses import dataclass import logging -from typing import Any, Awaitable, Callable, Dict, Iterable, List, Optional, cast +from typing import Any, Awaitable, Callable, Iterable, Optional, cast import voluptuous as vol from voluptuous.humanize import humanize_error @@ -72,9 +74,9 @@ class IDManager: def __init__(self) -> None: """Initiate the ID manager.""" - self.collections: List[Dict[str, Any]] = [] + self.collections: list[dict[str, Any]] = [] - def add_collection(self, collection: Dict[str, Any]) -> None: + def add_collection(self, collection: dict[str, Any]) -> None: """Add a collection to check for ID usage.""" self.collections.append(collection) @@ -98,17 +100,17 @@ class IDManager: class ObservableCollection(ABC): """Base collection type that can be observed.""" - def __init__(self, logger: logging.Logger, id_manager: Optional[IDManager] = None): + def __init__(self, logger: logging.Logger, id_manager: IDManager | None = None): """Initialize the base collection.""" self.logger = logger self.id_manager = id_manager or IDManager() - self.data: Dict[str, dict] = {} - self.listeners: List[ChangeListener] = [] + self.data: dict[str, dict] = {} + self.listeners: list[ChangeListener] = [] self.id_manager.add_collection(self.data) @callback - def async_items(self) -> List[dict]: + def async_items(self) -> list[dict]: """Return list of items in collection.""" return list(self.data.values()) @@ -134,7 +136,7 @@ class ObservableCollection(ABC): class YamlCollection(ObservableCollection): """Offer a collection based on static data.""" - async def async_load(self, data: List[dict]) -> None: + async def async_load(self, data: list[dict]) -> None: """Load the YAML collection. Overrides existing data.""" old_ids = set(self.data) @@ -171,7 +173,7 @@ class StorageCollection(ObservableCollection): self, store: Store, logger: logging.Logger, - id_manager: Optional[IDManager] = None, + id_manager: IDManager | None = None, ): """Initialize the storage collection.""" super().__init__(logger, id_manager) @@ -182,7 +184,7 @@ class StorageCollection(ObservableCollection): """Home Assistant object.""" return self.store.hass - async def _async_load_data(self) -> Optional[dict]: + async def _async_load_data(self) -> dict | None: """Load the data.""" return cast(Optional[dict], await self.store.async_load()) @@ -274,7 +276,7 @@ class IDLessCollection(ObservableCollection): counter = 0 - async def async_load(self, data: List[dict]) -> None: + async def async_load(self, data: list[dict]) -> None: """Load the collection. Overrides existing data.""" await self.notify_changes( [ diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 10b59645ed0..ac2fa91afe6 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -1,4 +1,6 @@ """Offer reusable conditions.""" +from __future__ import annotations + import asyncio from collections import deque from contextlib import contextmanager @@ -7,7 +9,7 @@ import functools as ft import logging import re import sys -from typing import Any, Callable, Container, Generator, List, Optional, Set, Union, cast +from typing import Any, Callable, Container, Generator, cast from homeassistant.components import zone as zone_cmp from homeassistant.components.device_automation import ( @@ -124,7 +126,7 @@ def trace_condition_function(condition: ConditionCheckerType) -> ConditionChecke async def async_from_config( hass: HomeAssistant, - config: Union[ConfigType, Template], + config: ConfigType | Template, config_validation: bool = True, ) -> ConditionCheckerType: """Turn a condition configuration into a method. @@ -267,10 +269,10 @@ async def async_not_from_config( def numeric_state( hass: HomeAssistant, - entity: Union[None, str, State], - below: Optional[Union[float, str]] = None, - above: Optional[Union[float, str]] = None, - value_template: Optional[Template] = None, + entity: None | str | State, + below: float | str | None = None, + above: float | str | None = None, + value_template: Template | None = None, variables: TemplateVarsType = None, ) -> bool: """Test a numeric state condition.""" @@ -288,12 +290,12 @@ def numeric_state( def async_numeric_state( hass: HomeAssistant, - entity: Union[None, str, State], - below: Optional[Union[float, str]] = None, - above: Optional[Union[float, str]] = None, - value_template: Optional[Template] = None, + entity: None | str | State, + below: float | str | None = None, + above: float | str | None = None, + value_template: Template | None = None, variables: TemplateVarsType = None, - attribute: Optional[str] = None, + attribute: str | None = None, ) -> bool: """Test a numeric state condition.""" if entity is None: @@ -456,10 +458,10 @@ def async_numeric_state_from_config( def state( hass: HomeAssistant, - entity: Union[None, str, State], + entity: None | str | State, req_state: Any, - for_period: Optional[timedelta] = None, - attribute: Optional[str] = None, + for_period: timedelta | None = None, + attribute: str | None = None, ) -> bool: """Test if state matches requirements. @@ -526,7 +528,7 @@ def state_from_config( if config_validation: config = cv.STATE_CONDITION_SCHEMA(config) entity_ids = config.get(CONF_ENTITY_ID, []) - req_states: Union[str, List[str]] = config.get(CONF_STATE, []) + req_states: str | list[str] = config.get(CONF_STATE, []) for_period = config.get("for") attribute = config.get(CONF_ATTRIBUTE) @@ -560,10 +562,10 @@ def state_from_config( def sun( hass: HomeAssistant, - before: Optional[str] = None, - after: Optional[str] = None, - before_offset: Optional[timedelta] = None, - after_offset: Optional[timedelta] = None, + before: str | None = None, + after: str | None = None, + before_offset: timedelta | None = None, + after_offset: timedelta | None = None, ) -> bool: """Test if current time matches sun requirements.""" utcnow = dt_util.utcnow() @@ -673,9 +675,9 @@ def async_template_from_config( def time( hass: HomeAssistant, - before: Optional[Union[dt_util.dt.time, str]] = None, - after: Optional[Union[dt_util.dt.time, str]] = None, - weekday: Union[None, str, Container[str]] = None, + before: dt_util.dt.time | str | None = None, + after: dt_util.dt.time | str | None = None, + weekday: None | str | Container[str] = None, ) -> bool: """Test if local time condition matches. @@ -752,8 +754,8 @@ def time_from_config( def zone( hass: HomeAssistant, - zone_ent: Union[None, str, State], - entity: Union[None, str, State], + zone_ent: None | str | State, + entity: None | str | State, ) -> bool: """Test if zone-condition matches. @@ -858,8 +860,8 @@ async def async_device_from_config( async def async_validate_condition_config( - hass: HomeAssistant, config: Union[ConfigType, Template] -) -> Union[ConfigType, Template]: + hass: HomeAssistant, config: ConfigType | Template +) -> ConfigType | Template: """Validate config.""" if isinstance(config, Template): return config @@ -884,9 +886,9 @@ async def async_validate_condition_config( @callback -def async_extract_entities(config: Union[ConfigType, Template]) -> Set[str]: +def async_extract_entities(config: ConfigType | Template) -> set[str]: """Extract entities from a condition.""" - referenced: Set[str] = set() + referenced: set[str] = set() to_process = deque([config]) while to_process: @@ -912,7 +914,7 @@ def async_extract_entities(config: Union[ConfigType, Template]) -> Set[str]: @callback -def async_extract_devices(config: Union[ConfigType, Template]) -> Set[str]: +def async_extract_devices(config: ConfigType | Template) -> set[str]: """Extract devices from a condition.""" referenced = set() to_process = deque([config]) diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index fea188ca336..8d0178caa8e 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -1,5 +1,7 @@ """Helpers for data entry flows for config entries.""" -from typing import Any, Awaitable, Callable, Dict, Optional, Union +from __future__ import annotations + +from typing import Any, Awaitable, Callable, Union from homeassistant import config_entries @@ -27,8 +29,8 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow): self.CONNECTION_CLASS = connection_class # pylint: disable=invalid-name async def async_step_user( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a flow initialized by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -38,8 +40,8 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow): return await self.async_step_confirm() async def async_step_confirm( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Confirm setup.""" if user_input is None: self._set_confirm_only() @@ -68,8 +70,8 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow): return self.async_create_entry(title=self._title, data={}) async def async_step_discovery( - self, discovery_info: Dict[str, Any] - ) -> Dict[str, Any]: + self, discovery_info: dict[str, Any] + ) -> dict[str, Any]: """Handle a flow initialized by discovery.""" if self._async_in_progress() or self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -84,7 +86,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow): async_step_homekit = async_step_discovery async_step_dhcp = async_step_discovery - async def async_step_import(self, _: Optional[Dict[str, Any]]) -> Dict[str, Any]: + async def async_step_import(self, _: dict[str, Any] | None) -> dict[str, Any]: """Handle a flow initialized by import.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -133,8 +135,8 @@ class WebhookFlowHandler(config_entries.ConfigFlow): self._allow_multiple = allow_multiple async def async_step_user( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a user initiated set up flow to create a webhook.""" if not self._allow_multiple and self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/helpers/config_entry_oauth2_flow.py b/homeassistant/helpers/config_entry_oauth2_flow.py index 691715452ea..a949685c7b1 100644 --- a/homeassistant/helpers/config_entry_oauth2_flow.py +++ b/homeassistant/helpers/config_entry_oauth2_flow.py @@ -5,12 +5,14 @@ This module exists of the following parts: - OAuth2 implementation that works with local provided client ID/secret """ +from __future__ import annotations + from abc import ABC, ABCMeta, abstractmethod import asyncio import logging import secrets import time -from typing import Any, Awaitable, Callable, Dict, Optional, cast +from typing import Any, Awaitable, Callable, Dict, cast from aiohttp import client, web import async_timeout @@ -231,7 +233,7 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): return {} async def async_step_pick_implementation( - self, user_input: Optional[dict] = None + self, user_input: dict | None = None ) -> dict: """Handle a flow start.""" implementations = await async_get_implementations(self.hass, self.DOMAIN) @@ -260,8 +262,8 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): ) async def async_step_auth( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Create an entry for auth.""" # Flow has been triggered by external data if user_input: @@ -286,8 +288,8 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): return self.async_external_step(step_id="auth", url=url) async def async_step_creation( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Create config entry from external data.""" token = await self.flow_impl.async_resolve_external_data(self.external_data) # Force int for non-compliant oauth2 providers @@ -312,8 +314,8 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): return self.async_create_entry(title=self.flow_impl.name, data=data) async def async_step_discovery( - self, discovery_info: Dict[str, Any] - ) -> Dict[str, Any]: + self, discovery_info: dict[str, Any] + ) -> dict[str, Any]: """Handle a flow initialized by discovery.""" await self.async_set_unique_id(self.DOMAIN) @@ -354,7 +356,7 @@ def async_register_implementation( async def async_get_implementations( hass: HomeAssistant, domain: str -) -> Dict[str, AbstractOAuth2Implementation]: +) -> dict[str, AbstractOAuth2Implementation]: """Return OAuth2 implementations for specified domain.""" registered = cast( Dict[str, AbstractOAuth2Implementation], @@ -392,7 +394,7 @@ def async_add_implementation_provider( hass: HomeAssistant, provider_domain: str, async_provide_implementation: Callable[ - [HomeAssistant, str], Awaitable[Optional[AbstractOAuth2Implementation]] + [HomeAssistant, str], Awaitable[AbstractOAuth2Implementation | None] ], ) -> None: """Add an implementation provider. @@ -516,7 +518,7 @@ def _encode_jwt(hass: HomeAssistant, data: dict) -> str: @callback -def _decode_jwt(hass: HomeAssistant, encoded: str) -> Optional[dict]: +def _decode_jwt(hass: HomeAssistant, encoded: str) -> dict | None: """JWT encode data.""" secret = cast(str, hass.data.get(DATA_JWT_SECRET)) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index b06e9974125..7f2f1550cfe 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1,4 +1,6 @@ """Helpers for config validation using voluptuous.""" +from __future__ import annotations + from datetime import ( date as date_sys, datetime as datetime_sys, @@ -12,19 +14,7 @@ from numbers import Number import os import re from socket import _GLOBAL_DEFAULT_TIMEOUT # type: ignore # private, not in typeshed -from typing import ( - Any, - Callable, - Dict, - Hashable, - List, - Optional, - Pattern, - Type, - TypeVar, - Union, - cast, -) +from typing import Any, Callable, Dict, Hashable, Pattern, TypeVar, cast from urllib.parse import urlparse from uuid import UUID @@ -131,7 +121,7 @@ def path(value: Any) -> str: def has_at_least_one_key(*keys: str) -> Callable: """Validate that at least one key exists.""" - def validate(obj: Dict) -> Dict: + def validate(obj: dict) -> dict: """Test keys exist in dict.""" if not isinstance(obj, dict): raise vol.Invalid("expected dictionary") @@ -144,10 +134,10 @@ def has_at_least_one_key(*keys: str) -> Callable: return validate -def has_at_most_one_key(*keys: str) -> Callable[[Dict], Dict]: +def has_at_most_one_key(*keys: str) -> Callable[[dict], dict]: """Validate that zero keys exist or one key exists.""" - def validate(obj: Dict) -> Dict: + def validate(obj: dict) -> dict: """Test zero keys exist or one key exists in dict.""" if not isinstance(obj, dict): raise vol.Invalid("expected dictionary") @@ -253,7 +243,7 @@ def isdir(value: Any) -> str: return dir_in -def ensure_list(value: Union[T, List[T], None]) -> List[T]: +def ensure_list(value: T | list[T] | None) -> list[T]: """Wrap value in list if it is not one.""" if value is None: return [] @@ -269,7 +259,7 @@ def entity_id(value: Any) -> str: raise vol.Invalid(f"Entity ID {value} is an invalid entity ID") -def entity_ids(value: Union[str, List]) -> List[str]: +def entity_ids(value: str | list) -> list[str]: """Validate Entity IDs.""" if value is None: raise vol.Invalid("Entity IDs can not be None") @@ -284,7 +274,7 @@ comp_entity_ids = vol.Any( ) -def entity_domain(domain: Union[str, List[str]]) -> Callable[[Any], str]: +def entity_domain(domain: str | list[str]) -> Callable[[Any], str]: """Validate that entity belong to domain.""" ent_domain = entities_domain(domain) @@ -298,9 +288,7 @@ def entity_domain(domain: Union[str, List[str]]) -> Callable[[Any], str]: return validate -def entities_domain( - domain: Union[str, List[str]] -) -> Callable[[Union[str, List]], List[str]]: +def entities_domain(domain: str | list[str]) -> Callable[[str | list], list[str]]: """Validate that entities belong to domain.""" if isinstance(domain, str): @@ -312,7 +300,7 @@ def entities_domain( def check_invalid(val: str) -> bool: return val not in domain - def validate(values: Union[str, List]) -> List[str]: + def validate(values: str | list) -> list[str]: """Test if entity domain is domain.""" values = entity_ids(values) for ent_id in values: @@ -325,7 +313,7 @@ def entities_domain( return validate -def enum(enumClass: Type[Enum]) -> vol.All: +def enum(enumClass: type[Enum]) -> vol.All: """Create validator for specified enum.""" return vol.All(vol.In(enumClass.__members__), enumClass.__getitem__) @@ -423,7 +411,7 @@ def time_period_str(value: str) -> timedelta: return offset -def time_period_seconds(value: Union[float, str]) -> timedelta: +def time_period_seconds(value: float | str) -> timedelta: """Validate and transform seconds to a time offset.""" try: return timedelta(seconds=float(value)) @@ -450,7 +438,7 @@ positive_time_period_dict = vol.All(time_period_dict, positive_timedelta) positive_time_period = vol.All(time_period, positive_timedelta) -def remove_falsy(value: List[T]) -> List[T]: +def remove_falsy(value: list[T]) -> list[T]: """Remove falsy values from a list.""" return [v for v in value if v] @@ -477,7 +465,7 @@ def slug(value: Any) -> str: def schema_with_slug_keys( - value_schema: Union[T, Callable], *, slug_validator: Callable[[Any], str] = slug + value_schema: T | Callable, *, slug_validator: Callable[[Any], str] = slug ) -> Callable: """Ensure dicts have slugs as keys. @@ -486,7 +474,7 @@ def schema_with_slug_keys( """ schema = vol.Schema({str: value_schema}) - def verify(value: Dict) -> Dict: + def verify(value: dict) -> dict: """Validate all keys are slugs and then the value_schema.""" if not isinstance(value, dict): raise vol.Invalid("expected dictionary") @@ -547,7 +535,7 @@ unit_system = vol.All( ) -def template(value: Optional[Any]) -> template_helper.Template: +def template(value: Any | None) -> template_helper.Template: """Validate a jinja2 template.""" if value is None: raise vol.Invalid("template value is None") @@ -563,7 +551,7 @@ def template(value: Optional[Any]) -> template_helper.Template: raise vol.Invalid(f"invalid template ({ex})") from ex -def dynamic_template(value: Optional[Any]) -> template_helper.Template: +def dynamic_template(value: Any | None) -> template_helper.Template: """Validate a dynamic (non static) jinja2 template.""" if value is None: raise vol.Invalid("template value is None") @@ -632,7 +620,7 @@ def time_zone(value: str) -> str: weekdays = vol.All(ensure_list, [vol.In(WEEKDAYS)]) -def socket_timeout(value: Optional[Any]) -> object: +def socket_timeout(value: Any | None) -> object: """Validate timeout float > 0.0. None coerced to socket._GLOBAL_DEFAULT_TIMEOUT bare object. @@ -681,7 +669,7 @@ def uuid4_hex(value: Any) -> str: return result.hex -def ensure_list_csv(value: Any) -> List: +def ensure_list_csv(value: Any) -> list: """Ensure that input is a list or make one from comma-separated string.""" if isinstance(value, str): return [member.strip() for member in value.split(",")] @@ -709,9 +697,9 @@ class multi_select: def deprecated( key: str, - replacement_key: Optional[str] = None, - default: Optional[Any] = None, -) -> Callable[[Dict], Dict]: + replacement_key: str | None = None, + default: Any | None = None, +) -> Callable[[dict], dict]: """ Log key as deprecated and provide a replacement (if exists). @@ -743,7 +731,7 @@ def deprecated( " please remove it from your configuration" ) - def validator(config: Dict) -> Dict: + def validator(config: dict) -> dict: """Check if key is in config and log warning.""" if key in config: try: @@ -781,14 +769,14 @@ def deprecated( def key_value_schemas( - key: str, value_schemas: Dict[str, vol.Schema] -) -> Callable[[Any], Dict[str, Any]]: + key: str, value_schemas: dict[str, vol.Schema] +) -> Callable[[Any], dict[str, Any]]: """Create a validator that validates based on a value for specific key. This gives better error messages. """ - def key_value_validator(value: Any) -> Dict[str, Any]: + def key_value_validator(value: Any) -> dict[str, Any]: if not isinstance(value, dict): raise vol.Invalid("Expected a dictionary") @@ -809,10 +797,10 @@ def key_value_schemas( def key_dependency( key: Hashable, dependency: Hashable -) -> Callable[[Dict[Hashable, Any]], Dict[Hashable, Any]]: +) -> Callable[[dict[Hashable, Any]], dict[Hashable, Any]]: """Validate that all dependencies exist for key.""" - def validator(value: Dict[Hashable, Any]) -> Dict[Hashable, Any]: + def validator(value: dict[Hashable, Any]) -> dict[Hashable, Any]: """Test dependencies.""" if not isinstance(value, dict): raise vol.Invalid("key dependencies require a dict") @@ -1247,7 +1235,7 @@ def determine_script_action(action: dict) -> str: return SCRIPT_ACTION_CALL_SERVICE -ACTION_TYPE_SCHEMAS: Dict[str, Callable[[Any], dict]] = { +ACTION_TYPE_SCHEMAS: dict[str, Callable[[Any], dict]] = { SCRIPT_ACTION_CALL_SERVICE: SERVICE_SCHEMA, SCRIPT_ACTION_DELAY: _SCRIPT_DELAY_SCHEMA, SCRIPT_ACTION_WAIT_TEMPLATE: _SCRIPT_WAIT_TEMPLATE_SCHEMA, diff --git a/homeassistant/helpers/data_entry_flow.py b/homeassistant/helpers/data_entry_flow.py index e686dd2ae4b..00d12d3ab90 100644 --- a/homeassistant/helpers/data_entry_flow.py +++ b/homeassistant/helpers/data_entry_flow.py @@ -1,6 +1,7 @@ """Helpers for the data entry flow.""" +from __future__ import annotations -from typing import Any, Dict +from typing import Any from aiohttp import web import voluptuous as vol @@ -20,7 +21,7 @@ class _BaseFlowManagerView(HomeAssistantView): self._flow_mgr = flow_mgr # pylint: disable=no-self-use - def _prepare_result_json(self, result: Dict[str, Any]) -> Dict[str, Any]: + def _prepare_result_json(self, result: dict[str, Any]) -> dict[str, Any]: """Convert result to JSON.""" if result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: data = result.copy() @@ -58,7 +59,7 @@ class FlowManagerIndexView(_BaseFlowManagerView): extra=vol.ALLOW_EXTRA, ) ) - async def post(self, request: web.Request, data: Dict[str, Any]) -> web.Response: + async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response: """Handle a POST request.""" if isinstance(data["handler"], list): handler = tuple(data["handler"]) @@ -99,7 +100,7 @@ class FlowManagerResourceView(_BaseFlowManagerView): @RequestDataValidator(vol.Schema(dict), allow_empty=True) async def post( - self, request: web.Request, flow_id: str, data: Dict[str, Any] + self, request: web.Request, flow_id: str, data: dict[str, Any] ) -> web.Response: """Handle a POST request.""" try: diff --git a/homeassistant/helpers/debounce.py b/homeassistant/helpers/debounce.py index 23727c2a00f..705f48bbd70 100644 --- a/homeassistant/helpers/debounce.py +++ b/homeassistant/helpers/debounce.py @@ -1,7 +1,9 @@ """Debounce helper.""" +from __future__ import annotations + import asyncio from logging import Logger -from typing import Any, Awaitable, Callable, Optional +from typing import Any, Awaitable, Callable from homeassistant.core import HassJob, HomeAssistant, callback @@ -16,7 +18,7 @@ class Debouncer: *, cooldown: float, immediate: bool, - function: Optional[Callable[..., Awaitable[Any]]] = None, + function: Callable[..., Awaitable[Any]] | None = None, ): """Initialize debounce. @@ -29,13 +31,13 @@ class Debouncer: self._function = function self.cooldown = cooldown self.immediate = immediate - self._timer_task: Optional[asyncio.TimerHandle] = None + self._timer_task: asyncio.TimerHandle | None = None self._execute_at_end_of_timer: bool = False self._execute_lock = asyncio.Lock() - self._job: Optional[HassJob] = None if function is None else HassJob(function) + self._job: HassJob | None = None if function is None else HassJob(function) @property - def function(self) -> Optional[Callable[..., Awaitable[Any]]]: + def function(self) -> Callable[..., Awaitable[Any]] | None: """Return the function being wrapped by the Debouncer.""" return self._function diff --git a/homeassistant/helpers/deprecation.py b/homeassistant/helpers/deprecation.py index 7478a7fede9..38b1dfca437 100644 --- a/homeassistant/helpers/deprecation.py +++ b/homeassistant/helpers/deprecation.py @@ -1,8 +1,10 @@ """Deprecation helpers for Home Assistant.""" +from __future__ import annotations + import functools import inspect import logging -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable from ..helpers.frame import MissingIntegrationFrame, get_integration_frame @@ -49,8 +51,8 @@ def deprecated_substitute(substitute_name: str) -> Callable[..., Callable]: def get_deprecated( - config: Dict[str, Any], new_name: str, old_name: str, default: Optional[Any] = None -) -> Optional[Any]: + config: dict[str, Any], new_name: str, old_name: str, default: Any | None = None +) -> Any | None: """Allow an old config name to be deprecated with a replacement. If the new config isn't found, but the old one is, the old value is used @@ -85,7 +87,7 @@ def deprecated_function(replacement: str) -> Callable[..., Callable]: """Decorate function as deprecated.""" @functools.wraps(func) - def deprecated_func(*args: tuple, **kwargs: Dict[str, Any]) -> Any: + def deprecated_func(*args: tuple, **kwargs: dict[str, Any]) -> Any: """Wrap for the original function.""" logger = logging.getLogger(func.__module__) try: diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index d311538f27f..4018ba6204c 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -1,8 +1,10 @@ """Provide a way to connect entities belonging to one device.""" +from __future__ import annotations + from collections import OrderedDict import logging import time -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union, cast +from typing import TYPE_CHECKING, Any, cast import attr @@ -50,21 +52,21 @@ ORPHANED_DEVICE_KEEP_SECONDS = 86400 * 30 class DeviceEntry: """Device Registry Entry.""" - config_entries: Set[str] = attr.ib(converter=set, factory=set) - connections: Set[Tuple[str, str]] = attr.ib(converter=set, factory=set) - identifiers: Set[Tuple[str, str]] = attr.ib(converter=set, factory=set) - manufacturer: Optional[str] = attr.ib(default=None) - model: Optional[str] = attr.ib(default=None) - name: Optional[str] = attr.ib(default=None) - sw_version: Optional[str] = attr.ib(default=None) - via_device_id: Optional[str] = attr.ib(default=None) - area_id: Optional[str] = attr.ib(default=None) - name_by_user: Optional[str] = attr.ib(default=None) - entry_type: Optional[str] = attr.ib(default=None) + config_entries: set[str] = attr.ib(converter=set, factory=set) + connections: set[tuple[str, str]] = attr.ib(converter=set, factory=set) + identifiers: set[tuple[str, str]] = attr.ib(converter=set, factory=set) + manufacturer: str | None = attr.ib(default=None) + model: str | None = attr.ib(default=None) + name: str | None = attr.ib(default=None) + sw_version: str | None = attr.ib(default=None) + via_device_id: str | None = attr.ib(default=None) + area_id: str | None = attr.ib(default=None) + name_by_user: str | None = attr.ib(default=None) + entry_type: str | None = attr.ib(default=None) id: str = attr.ib(factory=uuid_util.random_uuid_hex) # This value is not stored, just used to keep track of events to fire. is_new: bool = attr.ib(default=False) - disabled_by: Optional[str] = attr.ib( + disabled_by: str | None = attr.ib( default=None, validator=attr.validators.in_( ( @@ -75,7 +77,7 @@ class DeviceEntry: ) ), ) - suggested_area: Optional[str] = attr.ib(default=None) + suggested_area: str | None = attr.ib(default=None) @property def disabled(self) -> bool: @@ -87,17 +89,17 @@ class DeviceEntry: class DeletedDeviceEntry: """Deleted Device Registry Entry.""" - config_entries: Set[str] = attr.ib() - connections: Set[Tuple[str, str]] = attr.ib() - identifiers: Set[Tuple[str, str]] = attr.ib() + config_entries: set[str] = attr.ib() + connections: set[tuple[str, str]] = attr.ib() + identifiers: set[tuple[str, str]] = attr.ib() id: str = attr.ib() - orphaned_timestamp: Optional[float] = attr.ib() + orphaned_timestamp: float | None = attr.ib() def to_device_entry( self, config_entry_id: str, - connections: Set[Tuple[str, str]], - identifiers: Set[Tuple[str, str]], + connections: set[tuple[str, str]], + identifiers: set[tuple[str, str]], ) -> DeviceEntry: """Create DeviceEntry from DeletedDeviceEntry.""" return DeviceEntry( @@ -133,9 +135,9 @@ def format_mac(mac: str) -> str: class DeviceRegistry: """Class to hold a registry of devices.""" - devices: Dict[str, DeviceEntry] - deleted_devices: Dict[str, DeletedDeviceEntry] - _devices_index: Dict[str, Dict[str, Dict[Tuple[str, str], str]]] + devices: dict[str, DeviceEntry] + deleted_devices: dict[str, DeletedDeviceEntry] + _devices_index: dict[str, dict[str, dict[tuple[str, str], str]]] def __init__(self, hass: HomeAssistantType) -> None: """Initialize the device registry.""" @@ -144,16 +146,16 @@ class DeviceRegistry: self._clear_index() @callback - def async_get(self, device_id: str) -> Optional[DeviceEntry]: + def async_get(self, device_id: str) -> DeviceEntry | None: """Get device.""" return self.devices.get(device_id) @callback def async_get_device( self, - identifiers: Set[Tuple[str, str]], - connections: Optional[Set[Tuple[str, str]]] = None, - ) -> Optional[DeviceEntry]: + identifiers: set[tuple[str, str]], + connections: set[tuple[str, str]] | None = None, + ) -> DeviceEntry | None: """Check if device is registered.""" device_id = self._async_get_device_id_from_index( REGISTERED_DEVICE, identifiers, connections @@ -164,9 +166,9 @@ class DeviceRegistry: def _async_get_deleted_device( self, - identifiers: Set[Tuple[str, str]], - connections: Optional[Set[Tuple[str, str]]], - ) -> Optional[DeletedDeviceEntry]: + identifiers: set[tuple[str, str]], + connections: set[tuple[str, str]] | None, + ) -> DeletedDeviceEntry | None: """Check if device is deleted.""" device_id = self._async_get_device_id_from_index( DELETED_DEVICE, identifiers, connections @@ -178,9 +180,9 @@ class DeviceRegistry: def _async_get_device_id_from_index( self, index: str, - identifiers: Set[Tuple[str, str]], - connections: Optional[Set[Tuple[str, str]]], - ) -> Optional[str]: + identifiers: set[tuple[str, str]], + connections: set[tuple[str, str]] | None, + ) -> str | None: """Check if device has previously been registered.""" devices_index = self._devices_index[index] for identifier in identifiers: @@ -193,7 +195,7 @@ class DeviceRegistry: return devices_index[IDX_CONNECTIONS][connection] return None - def _add_device(self, device: Union[DeviceEntry, DeletedDeviceEntry]) -> None: + def _add_device(self, device: DeviceEntry | DeletedDeviceEntry) -> None: """Add a device and index it.""" if isinstance(device, DeletedDeviceEntry): devices_index = self._devices_index[DELETED_DEVICE] @@ -204,7 +206,7 @@ class DeviceRegistry: _add_device_to_index(devices_index, device) - def _remove_device(self, device: Union[DeviceEntry, DeletedDeviceEntry]) -> None: + def _remove_device(self, device: DeviceEntry | DeletedDeviceEntry) -> None: """Remove a device and remove it from the index.""" if isinstance(device, DeletedDeviceEntry): devices_index = self._devices_index[DELETED_DEVICE] @@ -243,21 +245,21 @@ class DeviceRegistry: self, *, config_entry_id: str, - connections: Optional[Set[Tuple[str, str]]] = None, - identifiers: Optional[Set[Tuple[str, str]]] = None, - manufacturer: Union[str, None, UndefinedType] = UNDEFINED, - model: Union[str, None, UndefinedType] = UNDEFINED, - name: Union[str, None, UndefinedType] = UNDEFINED, - default_manufacturer: Union[str, None, UndefinedType] = UNDEFINED, - default_model: Union[str, None, UndefinedType] = UNDEFINED, - default_name: Union[str, None, UndefinedType] = UNDEFINED, - sw_version: Union[str, None, UndefinedType] = UNDEFINED, - entry_type: Union[str, None, UndefinedType] = UNDEFINED, - via_device: Optional[Tuple[str, str]] = None, + connections: set[tuple[str, str]] | None = None, + identifiers: set[tuple[str, str]] | None = None, + manufacturer: str | None | UndefinedType = UNDEFINED, + model: str | None | UndefinedType = UNDEFINED, + name: str | None | UndefinedType = UNDEFINED, + default_manufacturer: str | None | UndefinedType = UNDEFINED, + default_model: str | None | UndefinedType = UNDEFINED, + default_name: str | None | UndefinedType = UNDEFINED, + sw_version: str | None | UndefinedType = UNDEFINED, + entry_type: str | None | UndefinedType = UNDEFINED, + via_device: tuple[str, str] | None = None, # To disable a device if it gets created - disabled_by: Union[str, None, UndefinedType] = UNDEFINED, - suggested_area: Union[str, None, UndefinedType] = UNDEFINED, - ) -> Optional[DeviceEntry]: + disabled_by: str | None | UndefinedType = UNDEFINED, + suggested_area: str | None | UndefinedType = UNDEFINED, + ) -> DeviceEntry | None: """Get device. Create if it doesn't exist.""" if not identifiers and not connections: return None @@ -294,7 +296,7 @@ class DeviceRegistry: if via_device is not None: via = self.async_get_device({via_device}) - via_device_id: Union[str, UndefinedType] = via.id if via else UNDEFINED + via_device_id: str | UndefinedType = via.id if via else UNDEFINED else: via_device_id = UNDEFINED @@ -318,18 +320,18 @@ class DeviceRegistry: self, device_id: str, *, - area_id: Union[str, None, UndefinedType] = UNDEFINED, - manufacturer: Union[str, None, UndefinedType] = UNDEFINED, - model: Union[str, None, UndefinedType] = UNDEFINED, - name: Union[str, None, UndefinedType] = UNDEFINED, - name_by_user: Union[str, None, UndefinedType] = UNDEFINED, - new_identifiers: Union[Set[Tuple[str, str]], UndefinedType] = UNDEFINED, - sw_version: Union[str, None, UndefinedType] = UNDEFINED, - via_device_id: Union[str, None, UndefinedType] = UNDEFINED, - remove_config_entry_id: Union[str, UndefinedType] = UNDEFINED, - disabled_by: Union[str, None, UndefinedType] = UNDEFINED, - suggested_area: Union[str, None, UndefinedType] = UNDEFINED, - ) -> Optional[DeviceEntry]: + area_id: str | None | UndefinedType = UNDEFINED, + manufacturer: str | None | UndefinedType = UNDEFINED, + model: str | None | UndefinedType = UNDEFINED, + name: str | None | UndefinedType = UNDEFINED, + name_by_user: str | None | UndefinedType = UNDEFINED, + new_identifiers: set[tuple[str, str]] | UndefinedType = UNDEFINED, + sw_version: str | None | UndefinedType = UNDEFINED, + via_device_id: str | None | UndefinedType = UNDEFINED, + remove_config_entry_id: str | UndefinedType = UNDEFINED, + disabled_by: str | None | UndefinedType = UNDEFINED, + suggested_area: str | None | UndefinedType = UNDEFINED, + ) -> DeviceEntry | None: """Update properties of a device.""" return self._async_update_device( device_id, @@ -351,26 +353,26 @@ class DeviceRegistry: self, device_id: str, *, - add_config_entry_id: Union[str, UndefinedType] = UNDEFINED, - remove_config_entry_id: Union[str, UndefinedType] = UNDEFINED, - merge_connections: Union[Set[Tuple[str, str]], UndefinedType] = UNDEFINED, - merge_identifiers: Union[Set[Tuple[str, str]], UndefinedType] = UNDEFINED, - new_identifiers: Union[Set[Tuple[str, str]], UndefinedType] = UNDEFINED, - manufacturer: Union[str, None, UndefinedType] = UNDEFINED, - model: Union[str, None, UndefinedType] = UNDEFINED, - name: Union[str, None, UndefinedType] = UNDEFINED, - sw_version: Union[str, None, UndefinedType] = UNDEFINED, - entry_type: Union[str, None, UndefinedType] = UNDEFINED, - via_device_id: Union[str, None, UndefinedType] = UNDEFINED, - area_id: Union[str, None, UndefinedType] = UNDEFINED, - name_by_user: Union[str, None, UndefinedType] = UNDEFINED, - disabled_by: Union[str, None, UndefinedType] = UNDEFINED, - suggested_area: Union[str, None, UndefinedType] = UNDEFINED, - ) -> Optional[DeviceEntry]: + add_config_entry_id: str | UndefinedType = UNDEFINED, + remove_config_entry_id: str | UndefinedType = UNDEFINED, + merge_connections: set[tuple[str, str]] | UndefinedType = UNDEFINED, + merge_identifiers: set[tuple[str, str]] | UndefinedType = UNDEFINED, + new_identifiers: set[tuple[str, str]] | UndefinedType = UNDEFINED, + manufacturer: str | None | UndefinedType = UNDEFINED, + model: str | None | UndefinedType = UNDEFINED, + name: str | None | UndefinedType = UNDEFINED, + sw_version: str | None | UndefinedType = UNDEFINED, + entry_type: str | None | UndefinedType = UNDEFINED, + via_device_id: str | None | UndefinedType = UNDEFINED, + area_id: str | None | UndefinedType = UNDEFINED, + name_by_user: str | None | UndefinedType = UNDEFINED, + disabled_by: str | None | UndefinedType = UNDEFINED, + suggested_area: str | None | UndefinedType = UNDEFINED, + ) -> DeviceEntry | None: """Update device attributes.""" old = self.devices[device_id] - changes: Dict[str, Any] = {} + changes: dict[str, Any] = {} config_entries = old.config_entries @@ -529,7 +531,7 @@ class DeviceRegistry: self._store.async_delay_save(self._data_to_save, SAVE_DELAY) @callback - def _data_to_save(self) -> Dict[str, List[Dict[str, Any]]]: + def _data_to_save(self) -> dict[str, list[dict[str, Any]]]: """Return data of device registry to store in a file.""" data = {} @@ -637,7 +639,7 @@ async def async_get_registry(hass: HomeAssistantType) -> DeviceRegistry: @callback -def async_entries_for_area(registry: DeviceRegistry, area_id: str) -> List[DeviceEntry]: +def async_entries_for_area(registry: DeviceRegistry, area_id: str) -> list[DeviceEntry]: """Return entries that match an area.""" return [device for device in registry.devices.values() if device.area_id == area_id] @@ -645,7 +647,7 @@ def async_entries_for_area(registry: DeviceRegistry, area_id: str) -> List[Devic @callback def async_entries_for_config_entry( registry: DeviceRegistry, config_entry_id: str -) -> List[DeviceEntry]: +) -> list[DeviceEntry]: """Return entries that match a config entry.""" return [ device @@ -769,7 +771,7 @@ def async_setup_cleanup(hass: HomeAssistantType, dev_reg: DeviceRegistry) -> Non hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, startup_clean) -def _normalize_connections(connections: Set[Tuple[str, str]]) -> Set[Tuple[str, str]]: +def _normalize_connections(connections: set[tuple[str, str]]) -> set[tuple[str, str]]: """Normalize connections to ensure we can match mac addresses.""" return { (key, format_mac(value)) if key == CONNECTION_NETWORK_MAC else (key, value) @@ -778,8 +780,8 @@ def _normalize_connections(connections: Set[Tuple[str, str]]) -> Set[Tuple[str, def _add_device_to_index( - devices_index: Dict[str, Dict[Tuple[str, str], str]], - device: Union[DeviceEntry, DeletedDeviceEntry], + devices_index: dict[str, dict[tuple[str, str], str]], + device: DeviceEntry | DeletedDeviceEntry, ) -> None: """Add a device to the index.""" for identifier in device.identifiers: @@ -789,8 +791,8 @@ def _add_device_to_index( def _remove_device_from_index( - devices_index: Dict[str, Dict[Tuple[str, str], str]], - device: Union[DeviceEntry, DeletedDeviceEntry], + devices_index: dict[str, dict[tuple[str, str], str]], + device: DeviceEntry | DeletedDeviceEntry, ) -> None: """Remove a device from the index.""" for identifier in device.identifiers: diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index 7ee72759d65..53dbca867d7 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -5,7 +5,9 @@ There are two different types of discoveries that can be fired/listened for. - listen_platform/discover_platform is for platforms. These are used by components to allow discovery of their platforms. """ -from typing import Any, Callable, Dict, Optional, TypedDict +from __future__ import annotations + +from typing import Any, Callable, TypedDict from homeassistant import core, setup from homeassistant.core import CALLBACK_TYPE @@ -26,8 +28,8 @@ class DiscoveryDict(TypedDict): """Discovery data.""" service: str - platform: Optional[str] - discovered: Optional[DiscoveryInfoType] + platform: str | None + discovered: DiscoveryInfoType | None @core.callback @@ -76,8 +78,8 @@ def discover( async def async_discover( hass: core.HomeAssistant, service: str, - discovered: Optional[DiscoveryInfoType], - component: Optional[str], + discovered: DiscoveryInfoType | None, + component: str | None, hass_config: ConfigType, ) -> None: """Fire discovery event. Can ensure a component is loaded.""" @@ -97,7 +99,7 @@ async def async_discover( def async_listen_platform( hass: core.HomeAssistant, component: str, - callback: Callable[[str, Optional[Dict[str, Any]]], Any], + callback: Callable[[str, dict[str, Any] | None], Any], ) -> None: """Register a platform loader listener. diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 791b1e251cd..894ae6c8225 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -1,11 +1,13 @@ """An abstract class for entities.""" +from __future__ import annotations + from abc import ABC import asyncio from datetime import datetime, timedelta import functools as ft import logging from timeit import default_timer as timer -from typing import Any, Awaitable, Dict, Iterable, List, Optional +from typing import Any, Awaitable, Iterable from homeassistant.config import DATA_CUSTOMIZE from homeassistant.const import ( @@ -42,16 +44,16 @@ SOURCE_PLATFORM_CONFIG = "platform_config" @callback @bind_hass -def entity_sources(hass: HomeAssistant) -> Dict[str, Dict[str, str]]: +def entity_sources(hass: HomeAssistant) -> dict[str, dict[str, str]]: """Get the entity sources.""" return hass.data.get(DATA_ENTITY_SOURCE, {}) def generate_entity_id( entity_id_format: str, - name: Optional[str], - current_ids: Optional[List[str]] = None, - hass: Optional[HomeAssistant] = None, + name: str | None, + current_ids: list[str] | None = None, + hass: HomeAssistant | None = None, ) -> str: """Generate a unique entity ID based on given entity IDs or used IDs.""" return async_generate_entity_id(entity_id_format, name, current_ids, hass) @@ -60,9 +62,9 @@ def generate_entity_id( @callback def async_generate_entity_id( entity_id_format: str, - name: Optional[str], - current_ids: Optional[Iterable[str]] = None, - hass: Optional[HomeAssistant] = None, + name: str | None, + current_ids: Iterable[str] | None = None, + hass: HomeAssistant | None = None, ) -> str: """Generate a unique entity ID based on given entity IDs or used IDs.""" name = (name or DEVICE_DEFAULT_NAME).lower() @@ -98,7 +100,7 @@ class Entity(ABC): hass: HomeAssistant = None # type: ignore # Owning platform instance. Will be set by EntityPlatform - platform: Optional[EntityPlatform] = None + platform: EntityPlatform | None = None # If we reported if this entity was slow _slow_reported = False @@ -110,17 +112,17 @@ class Entity(ABC): _update_staged = False # Process updates in parallel - parallel_updates: Optional[asyncio.Semaphore] = None + parallel_updates: asyncio.Semaphore | None = None # Entry in the entity registry - registry_entry: Optional[RegistryEntry] = None + registry_entry: RegistryEntry | None = None # Hold list for functions to call on remove. - _on_remove: Optional[List[CALLBACK_TYPE]] = None + _on_remove: list[CALLBACK_TYPE] | None = None # Context - _context: Optional[Context] = None - _context_set: Optional[datetime] = None + _context: Context | None = None + _context_set: datetime | None = None # If entity is added to an entity platform _added = False @@ -134,12 +136,12 @@ class Entity(ABC): return True @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return None @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return None @@ -149,7 +151,7 @@ class Entity(ABC): return STATE_UNKNOWN @property - def capability_attributes(self) -> Optional[Dict[str, Any]]: + def capability_attributes(self) -> dict[str, Any] | None: """Return the capability attributes. Attributes that explain the capabilities of an entity. @@ -160,7 +162,7 @@ class Entity(ABC): return None @property - def state_attributes(self) -> Optional[Dict[str, Any]]: + def state_attributes(self) -> dict[str, Any] | None: """Return the state attributes. Implemented by component base class, should not be extended by integrations. @@ -169,7 +171,7 @@ class Entity(ABC): return None @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def device_state_attributes(self) -> dict[str, Any] | None: """Return entity specific state attributes. This method is deprecated, platform classes should implement @@ -178,7 +180,7 @@ class Entity(ABC): return None @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return entity specific state attributes. Implemented by platform classes. Convention for attribute names @@ -187,7 +189,7 @@ class Entity(ABC): return None @property - def device_info(self) -> Optional[Dict[str, Any]]: + def device_info(self) -> dict[str, Any] | None: """Return device specific attributes. Implemented by platform classes. @@ -195,22 +197,22 @@ class Entity(ABC): return None @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" return None @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit of measurement of this entity, if any.""" return None @property - def icon(self) -> Optional[str]: + def icon(self) -> str | None: """Return the icon to use in the frontend, if any.""" return None @property - def entity_picture(self) -> Optional[str]: + def entity_picture(self) -> str | None: """Return the entity picture to use in the frontend, if any.""" return None @@ -234,7 +236,7 @@ class Entity(ABC): return False @property - def supported_features(self) -> Optional[int]: + def supported_features(self) -> int | None: """Flag supported features.""" return None @@ -516,7 +518,7 @@ class Entity(ABC): self, hass: HomeAssistant, platform: EntityPlatform, - parallel_updates: Optional[asyncio.Semaphore], + parallel_updates: asyncio.Semaphore | None, ) -> None: """Start adding an entity to a platform.""" if self._added: diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 6fb8696d845..17131665240 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -1,10 +1,12 @@ """Helpers for components that manage entities.""" +from __future__ import annotations + import asyncio from datetime import timedelta from itertools import chain import logging from types import ModuleType -from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union +from typing import Any, Callable, Iterable import voluptuous as vol @@ -76,10 +78,10 @@ class EntityComponent: self.domain = domain self.scan_interval = scan_interval - self.config: Optional[ConfigType] = None + self.config: ConfigType | None = None - self._platforms: Dict[ - Union[str, Tuple[str, Optional[timedelta], Optional[str]]], EntityPlatform + self._platforms: dict[ + str | tuple[str, timedelta | None, str | None], EntityPlatform ] = {domain: self._async_init_entity_platform(domain, None)} self.async_add_entities = self._platforms[domain].async_add_entities self.add_entities = self._platforms[domain].add_entities @@ -93,7 +95,7 @@ class EntityComponent: platform.entities.values() for platform in self._platforms.values() ) - def get_entity(self, entity_id: str) -> Optional[entity.Entity]: + def get_entity(self, entity_id: str) -> entity.Entity | None: """Get an entity.""" for platform in self._platforms.values(): entity_obj = platform.entities.get(entity_id) @@ -125,7 +127,7 @@ class EntityComponent: # Generic discovery listener for loading platform dynamically # Refer to: homeassistant.helpers.discovery.async_load_platform() async def component_platform_discovered( - platform: str, info: Optional[Dict[str, Any]] + platform: str, info: dict[str, Any] | None ) -> None: """Handle the loading of a platform.""" await self.async_setup_platform(platform, {}, info) @@ -176,7 +178,7 @@ class EntityComponent: async def async_extract_from_service( self, service_call: ServiceCall, expand_group: bool = True - ) -> List[entity.Entity]: + ) -> list[entity.Entity]: """Extract all known and available entities from a service call. Will return an empty list if entities specified but unknown. @@ -191,9 +193,9 @@ class EntityComponent: def async_register_entity_service( self, name: str, - schema: Union[Dict[str, Any], vol.Schema], - func: Union[str, Callable[..., Any]], - required_features: Optional[List[int]] = None, + schema: dict[str, Any] | vol.Schema, + func: str | Callable[..., Any], + required_features: list[int] | None = None, ) -> None: """Register an entity service.""" if isinstance(schema, dict): @@ -211,7 +213,7 @@ class EntityComponent: self, platform_type: str, platform_config: ConfigType, - discovery_info: Optional[DiscoveryInfoType] = None, + discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up a platform for this component.""" if self.config is None: @@ -274,7 +276,7 @@ class EntityComponent: async def async_prepare_reload( self, *, skip_reset: bool = False - ) -> Optional[ConfigType]: + ) -> ConfigType | None: """Prepare reloading this entity component. This method must be run in the event loop. @@ -303,9 +305,9 @@ class EntityComponent: def _async_init_entity_platform( self, platform_type: str, - platform: Optional[ModuleType], - scan_interval: Optional[timedelta] = None, - entity_namespace: Optional[str] = None, + platform: ModuleType | None, + scan_interval: timedelta | None = None, + entity_namespace: str | None = None, ) -> EntityPlatform: """Initialize an entity platform.""" if scan_interval is None: diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index d860a8a3390..382ebf8055e 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -6,7 +6,7 @@ from contextvars import ContextVar from datetime import datetime, timedelta from logging import Logger from types import ModuleType -from typing import TYPE_CHECKING, Callable, Coroutine, Dict, Iterable, List, Optional +from typing import TYPE_CHECKING, Callable, Coroutine, Iterable from homeassistant import config_entries from homeassistant.const import ATTR_RESTORED, DEVICE_DEFAULT_NAME @@ -49,9 +49,9 @@ class EntityPlatform: logger: Logger, domain: str, platform_name: str, - platform: Optional[ModuleType], + platform: ModuleType | None, scan_interval: timedelta, - entity_namespace: Optional[str], + entity_namespace: str | None, ): """Initialize the entity platform.""" self.hass = hass @@ -61,18 +61,18 @@ class EntityPlatform: self.platform = platform self.scan_interval = scan_interval self.entity_namespace = entity_namespace - self.config_entry: Optional[config_entries.ConfigEntry] = None - self.entities: Dict[str, Entity] = {} - self._tasks: List[asyncio.Future] = [] + self.config_entry: config_entries.ConfigEntry | None = None + self.entities: dict[str, Entity] = {} + self._tasks: list[asyncio.Future] = [] # Stop tracking tasks after setup is completed self._setup_complete = False # Method to cancel the state change listener - self._async_unsub_polling: Optional[CALLBACK_TYPE] = None + self._async_unsub_polling: CALLBACK_TYPE | None = None # Method to cancel the retry of setup - self._async_cancel_retry_setup: Optional[CALLBACK_TYPE] = None - self._process_updates: Optional[asyncio.Lock] = None + self._async_cancel_retry_setup: CALLBACK_TYPE | None = None + self._process_updates: asyncio.Lock | None = None - self.parallel_updates: Optional[asyncio.Semaphore] = None + self.parallel_updates: asyncio.Semaphore | None = None # Platform is None for the EntityComponent "catch-all" EntityPlatform # which powers entity_component.add_entities @@ -89,7 +89,7 @@ class EntityPlatform: @callback def _get_parallel_updates_semaphore( self, entity_has_async_update: bool - ) -> Optional[asyncio.Semaphore]: + ) -> asyncio.Semaphore | None: """Get or create a semaphore for parallel updates. Semaphore will be created on demand because we base it off if update method is async or not. @@ -364,7 +364,7 @@ class EntityPlatform: return requested_entity_id = None - suggested_object_id: Optional[str] = None + suggested_object_id: str | None = None # Get entity_id from unique ID registration if entity.unique_id is not None: @@ -378,7 +378,7 @@ class EntityPlatform: suggested_object_id = f"{self.entity_namespace} {suggested_object_id}" if self.config_entry is not None: - config_entry_id: Optional[str] = self.config_entry.entry_id + config_entry_id: str | None = self.config_entry.entry_id else: config_entry_id = None @@ -408,7 +408,7 @@ class EntityPlatform: if device: device_id = device.id - disabled_by: Optional[str] = None + disabled_by: str | None = None if not entity.entity_registry_enabled_default: disabled_by = DISABLED_INTEGRATION @@ -550,7 +550,7 @@ class EntityPlatform: async def async_extract_from_service( self, service_call: ServiceCall, expand_group: bool = True - ) -> List[Entity]: + ) -> list[Entity]: """Extract all known and available entities from a service call. Will return an empty list if entities specified but unknown. @@ -621,7 +621,7 @@ class EntityPlatform: await asyncio.gather(*tasks) -current_platform: ContextVar[Optional[EntityPlatform]] = ContextVar( +current_platform: ContextVar[EntityPlatform | None] = ContextVar( "current_platform", default=None ) @@ -629,7 +629,7 @@ current_platform: ContextVar[Optional[EntityPlatform]] = ContextVar( @callback def async_get_platforms( hass: HomeAssistantType, integration_name: str -) -> List[EntityPlatform]: +) -> list[EntityPlatform]: """Find existing platforms.""" if ( DATA_ENTITY_PLATFORM not in hass.data @@ -637,6 +637,6 @@ def async_get_platforms( ): return [] - platforms: List[EntityPlatform] = hass.data[DATA_ENTITY_PLATFORM][integration_name] + platforms: list[EntityPlatform] = hass.data[DATA_ENTITY_PLATFORM][integration_name] return platforms diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 36b010c82a0..3d33b73271d 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -7,20 +7,11 @@ The Entity Registry will persist itself 10 seconds after a new entity is registered. Registering a new entity while a timer is in progress resets the timer. """ +from __future__ import annotations + from collections import OrderedDict import logging -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Dict, - Iterable, - List, - Optional, - Tuple, - Union, - cast, -) +from typing import TYPE_CHECKING, Any, Callable, Iterable, cast import attr @@ -80,12 +71,12 @@ class RegistryEntry: entity_id: str = attr.ib() unique_id: str = attr.ib() platform: str = attr.ib() - name: Optional[str] = attr.ib(default=None) - icon: Optional[str] = attr.ib(default=None) - device_id: Optional[str] = attr.ib(default=None) - area_id: Optional[str] = attr.ib(default=None) - config_entry_id: Optional[str] = attr.ib(default=None) - disabled_by: Optional[str] = attr.ib( + name: str | None = attr.ib(default=None) + icon: str | None = attr.ib(default=None) + device_id: str | None = attr.ib(default=None) + area_id: str | None = attr.ib(default=None) + config_entry_id: str | None = attr.ib(default=None) + disabled_by: str | None = attr.ib( default=None, validator=attr.validators.in_( ( @@ -98,13 +89,13 @@ class RegistryEntry: ) ), ) - capabilities: Optional[Dict[str, Any]] = attr.ib(default=None) + capabilities: dict[str, Any] | None = attr.ib(default=None) supported_features: int = attr.ib(default=0) - device_class: Optional[str] = attr.ib(default=None) - unit_of_measurement: Optional[str] = attr.ib(default=None) + device_class: str | None = attr.ib(default=None) + unit_of_measurement: str | None = attr.ib(default=None) # As set by integration - original_name: Optional[str] = attr.ib(default=None) - original_icon: Optional[str] = attr.ib(default=None) + original_name: str | None = attr.ib(default=None) + original_icon: str | None = attr.ib(default=None) domain: str = attr.ib(init=False, repr=False) @domain.default @@ -120,7 +111,7 @@ class RegistryEntry: @callback def write_unavailable_state(self, hass: HomeAssistantType) -> None: """Write the unavailable state to the state machine.""" - attrs: Dict[str, Any] = {ATTR_RESTORED: True} + attrs: dict[str, Any] = {ATTR_RESTORED: True} if self.capabilities is not None: attrs.update(self.capabilities) @@ -151,8 +142,8 @@ class EntityRegistry: def __init__(self, hass: HomeAssistantType): """Initialize the registry.""" self.hass = hass - self.entities: Dict[str, RegistryEntry] - self._index: Dict[Tuple[str, str, str], str] = {} + self.entities: dict[str, RegistryEntry] + self._index: dict[tuple[str, str, str], str] = {} self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) self.hass.bus.async_listen( EVENT_DEVICE_REGISTRY_UPDATED, self.async_device_modified @@ -161,7 +152,7 @@ class EntityRegistry: @callback def async_get_device_class_lookup(self, domain_device_classes: set) -> dict: """Return a lookup for the device class by domain.""" - lookup: Dict[str, Dict[Tuple[Any, Any], str]] = {} + lookup: dict[str, dict[tuple[Any, Any], str]] = {} for entity in self.entities.values(): if not entity.device_id: continue @@ -180,14 +171,14 @@ class EntityRegistry: return entity_id in self.entities @callback - def async_get(self, entity_id: str) -> Optional[RegistryEntry]: + def async_get(self, entity_id: str) -> RegistryEntry | None: """Get EntityEntry for an entity_id.""" return self.entities.get(entity_id) @callback def async_get_entity_id( self, domain: str, platform: str, unique_id: str - ) -> Optional[str]: + ) -> str | None: """Check if an entity_id is currently registered.""" return self._index.get((domain, platform, unique_id)) @@ -196,7 +187,7 @@ class EntityRegistry: self, domain: str, suggested_object_id: str, - known_object_ids: Optional[Iterable[str]] = None, + known_object_ids: Iterable[str] | None = None, ) -> str: """Generate an entity ID that does not conflict. @@ -226,20 +217,20 @@ class EntityRegistry: unique_id: str, *, # To influence entity ID generation - suggested_object_id: Optional[str] = None, - known_object_ids: Optional[Iterable[str]] = None, + suggested_object_id: str | None = None, + known_object_ids: Iterable[str] | None = None, # To disable an entity if it gets created - disabled_by: Optional[str] = None, + disabled_by: str | None = None, # Data that we want entry to have - config_entry: Optional["ConfigEntry"] = None, - device_id: Optional[str] = None, - area_id: Optional[str] = None, - capabilities: Optional[Dict[str, Any]] = None, - supported_features: Optional[int] = None, - device_class: Optional[str] = None, - unit_of_measurement: Optional[str] = None, - original_name: Optional[str] = None, - original_icon: Optional[str] = None, + config_entry: "ConfigEntry" | None = None, + device_id: str | None = None, + area_id: str | None = None, + capabilities: dict[str, Any] | None = None, + supported_features: int | None = None, + device_class: str | None = None, + unit_of_measurement: str | None = None, + original_name: str | None = None, + original_icon: str | None = None, ) -> RegistryEntry: """Get entity. Create if it doesn't exist.""" config_entry_id = None @@ -363,12 +354,12 @@ class EntityRegistry: self, entity_id: str, *, - name: Union[str, None, UndefinedType] = UNDEFINED, - icon: Union[str, None, UndefinedType] = UNDEFINED, - area_id: Union[str, None, UndefinedType] = UNDEFINED, - new_entity_id: Union[str, UndefinedType] = UNDEFINED, - new_unique_id: Union[str, UndefinedType] = UNDEFINED, - disabled_by: Union[str, None, UndefinedType] = UNDEFINED, + name: str | None | UndefinedType = UNDEFINED, + icon: str | None | UndefinedType = UNDEFINED, + area_id: str | None | UndefinedType = UNDEFINED, + new_entity_id: str | UndefinedType = UNDEFINED, + new_unique_id: str | UndefinedType = UNDEFINED, + disabled_by: str | None | UndefinedType = UNDEFINED, ) -> RegistryEntry: """Update properties of an entity.""" return self._async_update_entity( @@ -386,20 +377,20 @@ class EntityRegistry: self, entity_id: str, *, - name: Union[str, None, UndefinedType] = UNDEFINED, - icon: Union[str, None, UndefinedType] = UNDEFINED, - config_entry_id: Union[str, None, UndefinedType] = UNDEFINED, - new_entity_id: Union[str, UndefinedType] = UNDEFINED, - device_id: Union[str, None, UndefinedType] = UNDEFINED, - area_id: Union[str, None, UndefinedType] = UNDEFINED, - new_unique_id: Union[str, UndefinedType] = UNDEFINED, - disabled_by: Union[str, None, UndefinedType] = UNDEFINED, - capabilities: Union[Dict[str, Any], None, UndefinedType] = UNDEFINED, - supported_features: Union[int, UndefinedType] = UNDEFINED, - device_class: Union[str, None, UndefinedType] = UNDEFINED, - unit_of_measurement: Union[str, None, UndefinedType] = UNDEFINED, - original_name: Union[str, None, UndefinedType] = UNDEFINED, - original_icon: Union[str, None, UndefinedType] = UNDEFINED, + name: str | None | UndefinedType = UNDEFINED, + icon: str | None | UndefinedType = UNDEFINED, + config_entry_id: str | None | UndefinedType = UNDEFINED, + new_entity_id: str | UndefinedType = UNDEFINED, + device_id: str | None | UndefinedType = UNDEFINED, + area_id: str | None | UndefinedType = UNDEFINED, + new_unique_id: str | UndefinedType = UNDEFINED, + disabled_by: str | None | UndefinedType = UNDEFINED, + capabilities: dict[str, Any] | None | UndefinedType = UNDEFINED, + supported_features: int | UndefinedType = UNDEFINED, + device_class: str | None | UndefinedType = UNDEFINED, + unit_of_measurement: str | None | UndefinedType = UNDEFINED, + original_name: str | None | UndefinedType = UNDEFINED, + original_icon: str | None | UndefinedType = UNDEFINED, ) -> RegistryEntry: """Private facing update properties method.""" old = self.entities[entity_id] @@ -479,7 +470,7 @@ class EntityRegistry: old_conf_load_func=load_yaml, old_conf_migrate_func=_async_migrate, ) - entities: Dict[str, RegistryEntry] = OrderedDict() + entities: dict[str, RegistryEntry] = OrderedDict() if data is not None: for entity in data["entities"]: @@ -516,7 +507,7 @@ class EntityRegistry: self._store.async_delay_save(self._data_to_save, SAVE_DELAY) @callback - def _data_to_save(self) -> Dict[str, Any]: + def _data_to_save(self) -> dict[str, Any]: """Return data of entity registry to store in a file.""" data = {} @@ -605,7 +596,7 @@ async def async_get_registry(hass: HomeAssistantType) -> EntityRegistry: @callback def async_entries_for_device( registry: EntityRegistry, device_id: str, include_disabled_entities: bool = False -) -> List[RegistryEntry]: +) -> list[RegistryEntry]: """Return entries that match a device.""" return [ entry @@ -618,7 +609,7 @@ def async_entries_for_device( @callback def async_entries_for_area( registry: EntityRegistry, area_id: str -) -> List[RegistryEntry]: +) -> list[RegistryEntry]: """Return entries that match an area.""" return [entry for entry in registry.entities.values() if entry.area_id == area_id] @@ -626,7 +617,7 @@ def async_entries_for_area( @callback def async_entries_for_config_entry( registry: EntityRegistry, config_entry_id: str -) -> List[RegistryEntry]: +) -> list[RegistryEntry]: """Return entries that match a config entry.""" return [ entry @@ -665,7 +656,7 @@ def async_config_entry_disabled_by_changed( ) -async def _async_migrate(entities: Dict[str, Any]) -> Dict[str, List[Dict[str, Any]]]: +async def _async_migrate(entities: dict[str, Any]) -> dict[str, list[dict[str, Any]]]: """Migrate the YAML config file to storage helper format.""" return { "entities": [ @@ -721,7 +712,7 @@ def async_setup_entity_restore( async def async_migrate_entries( hass: HomeAssistantType, config_entry_id: str, - entry_callback: Callable[[RegistryEntry], Optional[dict]], + entry_callback: Callable[[RegistryEntry], dict | None], ) -> None: """Migrator of unique IDs.""" ent_reg = await async_get_registry(hass) diff --git a/homeassistant/helpers/entity_values.py b/homeassistant/helpers/entity_values.py index 7f44e8b2768..57dbb34c560 100644 --- a/homeassistant/helpers/entity_values.py +++ b/homeassistant/helpers/entity_values.py @@ -1,8 +1,10 @@ """A class to hold entity values.""" +from __future__ import annotations + from collections import OrderedDict import fnmatch import re -from typing import Any, Dict, Optional, Pattern +from typing import Any, Pattern from homeassistant.core import split_entity_id @@ -14,17 +16,17 @@ class EntityValues: def __init__( self, - exact: Optional[Dict[str, Dict[str, str]]] = None, - domain: Optional[Dict[str, Dict[str, str]]] = None, - glob: Optional[Dict[str, Dict[str, str]]] = None, + exact: dict[str, dict[str, str]] | None = None, + domain: dict[str, dict[str, str]] | None = None, + glob: dict[str, dict[str, str]] | None = None, ) -> None: """Initialize an EntityConfigDict.""" - self._cache: Dict[str, Dict[str, str]] = {} + self._cache: dict[str, dict[str, str]] = {} self._exact = exact self._domain = domain if glob is None: - compiled: Optional[Dict[Pattern[str], Any]] = None + compiled: dict[Pattern[str], Any] | None = None else: compiled = OrderedDict() for key, value in glob.items(): @@ -32,7 +34,7 @@ class EntityValues: self._glob = compiled - def get(self, entity_id: str) -> Dict[str, str]: + def get(self, entity_id: str) -> dict[str, str]: """Get config for an entity id.""" if entity_id in self._cache: return self._cache[entity_id] diff --git a/homeassistant/helpers/entityfilter.py b/homeassistant/helpers/entityfilter.py index 608fae0242e..ebde309de14 100644 --- a/homeassistant/helpers/entityfilter.py +++ b/homeassistant/helpers/entityfilter.py @@ -1,7 +1,9 @@ """Helper class to implement include/exclude of entities and domains.""" +from __future__ import annotations + import fnmatch import re -from typing import Callable, Dict, List, Pattern +from typing import Callable, Pattern import voluptuous as vol @@ -19,7 +21,7 @@ CONF_EXCLUDE_ENTITIES = "exclude_entities" CONF_ENTITY_GLOBS = "entity_globs" -def convert_filter(config: Dict[str, List[str]]) -> Callable[[str], bool]: +def convert_filter(config: dict[str, list[str]]) -> Callable[[str], bool]: """Convert the filter schema into a filter.""" filt = generate_filter( config[CONF_INCLUDE_DOMAINS], @@ -57,7 +59,7 @@ FILTER_SCHEMA = vol.All(BASE_FILTER_SCHEMA, convert_filter) def convert_include_exclude_filter( - config: Dict[str, Dict[str, List[str]]] + config: dict[str, dict[str, list[str]]] ) -> Callable[[str], bool]: """Convert the include exclude filter schema into a filter.""" include = config[CONF_INCLUDE] @@ -107,7 +109,7 @@ def _glob_to_re(glob: str) -> Pattern[str]: return re.compile(fnmatch.translate(glob)) -def _test_against_patterns(patterns: List[Pattern[str]], entity_id: str) -> bool: +def _test_against_patterns(patterns: list[Pattern[str]], entity_id: str) -> bool: """Test entity against list of patterns, true if any match.""" for pattern in patterns: if pattern.match(entity_id): @@ -119,12 +121,12 @@ def _test_against_patterns(patterns: List[Pattern[str]], entity_id: str) -> bool # It's safe since we don't modify it. And None causes typing warnings # pylint: disable=dangerous-default-value def generate_filter( - include_domains: List[str], - include_entities: List[str], - exclude_domains: List[str], - exclude_entities: List[str], - include_entity_globs: List[str] = [], - exclude_entity_globs: List[str] = [], + include_domains: list[str], + include_entities: list[str], + exclude_domains: list[str], + exclude_entities: list[str], + include_entity_globs: list[str] = [], + exclude_entity_globs: list[str] = [], ) -> Callable[[str], bool]: """Return a function that will filter entities based on the args.""" include_d = set(include_domains) diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index f496c7088a4..2a3ee75ce75 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -1,4 +1,6 @@ """Helpers for listening to events.""" +from __future__ import annotations + import asyncio import copy from dataclasses import dataclass @@ -6,18 +8,7 @@ from datetime import datetime, timedelta import functools as ft import logging import time -from typing import ( - Any, - Awaitable, - Callable, - Dict, - Iterable, - List, - Optional, - Set, - Tuple, - Union, -) +from typing import Any, Awaitable, Callable, Iterable, List import attr @@ -79,8 +70,8 @@ class TrackStates: """ all_states: bool - entities: Set - domains: Set + entities: set + domains: set @dataclass @@ -94,7 +85,7 @@ class TrackTemplate: template: Template variables: TemplateVarsType - rate_limit: Optional[timedelta] = None + rate_limit: timedelta | None = None @dataclass @@ -146,10 +137,10 @@ def threaded_listener_factory( @bind_hass def async_track_state_change( hass: HomeAssistant, - entity_ids: Union[str, Iterable[str]], + entity_ids: str | Iterable[str], action: Callable[[str, State, State], None], - from_state: Union[None, str, Iterable[str]] = None, - to_state: Union[None, str, Iterable[str]] = None, + from_state: None | str | Iterable[str] = None, + to_state: None | str | Iterable[str] = None, ) -> CALLBACK_TYPE: """Track specific state changes. @@ -240,7 +231,7 @@ track_state_change = threaded_listener_factory(async_track_state_change) @bind_hass def async_track_state_change_event( hass: HomeAssistant, - entity_ids: Union[str, Iterable[str]], + entity_ids: str | Iterable[str], action: Callable[[Event], Any], ) -> Callable[[], None]: """Track specific state change events indexed by entity_id. @@ -337,7 +328,7 @@ def _async_remove_indexed_listeners( @bind_hass def async_track_entity_registry_updated_event( hass: HomeAssistant, - entity_ids: Union[str, Iterable[str]], + entity_ids: str | Iterable[str], action: Callable[[Event], Any], ) -> Callable[[], None]: """Track specific entity registry updated events indexed by entity_id. @@ -402,7 +393,7 @@ def async_track_entity_registry_updated_event( @callback def _async_dispatch_domain_event( - hass: HomeAssistant, event: Event, callbacks: Dict[str, List] + hass: HomeAssistant, event: Event, callbacks: dict[str, list] ) -> None: domain = split_entity_id(event.data["entity_id"])[0] @@ -423,7 +414,7 @@ def _async_dispatch_domain_event( @bind_hass def async_track_state_added_domain( hass: HomeAssistant, - domains: Union[str, Iterable[str]], + domains: str | Iterable[str], action: Callable[[Event], Any], ) -> Callable[[], None]: """Track state change events when an entity is added to domains.""" @@ -476,7 +467,7 @@ def async_track_state_added_domain( @bind_hass def async_track_state_removed_domain( hass: HomeAssistant, - domains: Union[str, Iterable[str]], + domains: str | Iterable[str], action: Callable[[Event], Any], ) -> Callable[[], None]: """Track state change events when an entity is removed from domains.""" @@ -527,7 +518,7 @@ def async_track_state_removed_domain( @callback -def _async_string_to_lower_list(instr: Union[str, Iterable[str]]) -> List[str]: +def _async_string_to_lower_list(instr: str | Iterable[str]) -> list[str]: if isinstance(instr, str): return [instr.lower()] @@ -546,7 +537,7 @@ class _TrackStateChangeFiltered: """Handle removal / refresh of tracker init.""" self.hass = hass self._action = action - self._listeners: Dict[str, Callable] = {} + self._listeners: dict[str, Callable] = {} self._last_track_states: TrackStates = track_states @callback @@ -569,7 +560,7 @@ class _TrackStateChangeFiltered: self._setup_entities_listener(track_states.domains, track_states.entities) @property - def listeners(self) -> Dict: + def listeners(self) -> dict: """State changes that will cause a re-render.""" track_states = self._last_track_states return { @@ -628,7 +619,7 @@ class _TrackStateChangeFiltered: self._listeners.pop(listener_name)() @callback - def _setup_entities_listener(self, domains: Set, entities: Set) -> None: + def _setup_entities_listener(self, domains: set, entities: set) -> None: if domains: entities = entities.copy() entities.update(self.hass.states.async_entity_ids(domains)) @@ -642,7 +633,7 @@ class _TrackStateChangeFiltered: ) @callback - def _setup_domains_listener(self, domains: Set) -> None: + def _setup_domains_listener(self, domains: set) -> None: if not domains: return @@ -691,8 +682,8 @@ def async_track_state_change_filtered( def async_track_template( hass: HomeAssistant, template: Template, - action: Callable[[str, Optional[State], Optional[State]], None], - variables: Optional[TemplateVarsType] = None, + action: Callable[[str, State | None, State | None], None], + variables: TemplateVarsType | None = None, ) -> Callable[[], None]: """Add a listener that fires when a a template evaluates to 'true'. @@ -734,7 +725,7 @@ def async_track_template( @callback def _template_changed_listener( - event: Event, updates: List[TrackTemplateResult] + event: Event, updates: list[TrackTemplateResult] ) -> None: """Check if condition is correct and run action.""" track_result = updates.pop() @@ -792,12 +783,12 @@ class _TrackTemplateResultInfo: track_template_.template.hass = hass self._track_templates = track_templates - self._last_result: Dict[Template, Union[str, TemplateError]] = {} + self._last_result: dict[Template, str | TemplateError] = {} self._rate_limit = KeyedRateLimit(hass) - self._info: Dict[Template, RenderInfo] = {} - self._track_state_changes: Optional[_TrackStateChangeFiltered] = None - self._time_listeners: Dict[Template, Callable] = {} + self._info: dict[Template, RenderInfo] = {} + self._track_state_changes: _TrackStateChangeFiltered | None = None + self._time_listeners: dict[Template, Callable] = {} def async_setup(self, raise_on_template_error: bool) -> None: """Activation of template tracking.""" @@ -826,7 +817,7 @@ class _TrackTemplateResultInfo: ) @property - def listeners(self) -> Dict: + def listeners(self) -> dict: """State changes that will cause a re-render.""" assert self._track_state_changes return { @@ -882,8 +873,8 @@ class _TrackTemplateResultInfo: self, track_template_: TrackTemplate, now: datetime, - event: Optional[Event], - ) -> Union[bool, TrackTemplateResult]: + event: Event | None, + ) -> bool | TrackTemplateResult: """Re-render the template if conditions match. Returns False if the template was not be re-rendered @@ -927,7 +918,7 @@ class _TrackTemplateResultInfo: ) try: - result: Union[str, TemplateError] = info.result() + result: str | TemplateError = info.result() except TemplateError as ex: result = ex @@ -945,9 +936,9 @@ class _TrackTemplateResultInfo: @callback def _refresh( self, - event: Optional[Event], - track_templates: Optional[Iterable[TrackTemplate]] = None, - replayed: Optional[bool] = False, + event: Event | None, + track_templates: Iterable[TrackTemplate] | None = None, + replayed: bool | None = False, ) -> None: """Refresh the template. @@ -1076,16 +1067,16 @@ def async_track_same_state( hass: HomeAssistant, period: timedelta, action: Callable[..., None], - async_check_same_func: Callable[[str, Optional[State], Optional[State]], bool], - entity_ids: Union[str, Iterable[str]] = MATCH_ALL, + async_check_same_func: Callable[[str, State | None, State | None], bool], + entity_ids: str | Iterable[str] = MATCH_ALL, ) -> CALLBACK_TYPE: """Track the state of entities for a period and run an action. If async_check_func is None it use the state of orig_value. Without entity_ids we track all state changes. """ - async_remove_state_for_cancel: Optional[CALLBACK_TYPE] = None - async_remove_state_for_listener: Optional[CALLBACK_TYPE] = None + async_remove_state_for_cancel: CALLBACK_TYPE | None = None + async_remove_state_for_listener: CALLBACK_TYPE | None = None job = HassJob(action) @@ -1113,8 +1104,8 @@ def async_track_same_state( def state_for_cancel_listener(event: Event) -> None: """Fire on changes and cancel for listener if changed.""" entity: str = event.data["entity_id"] - from_state: Optional[State] = event.data.get("old_state") - to_state: Optional[State] = event.data.get("new_state") + from_state: State | None = event.data.get("old_state") + to_state: State | None = event.data.get("new_state") if not async_check_same_func(entity, from_state, to_state): clear_listener() @@ -1144,7 +1135,7 @@ track_same_state = threaded_listener_factory(async_track_same_state) @bind_hass def async_track_point_in_time( hass: HomeAssistant, - action: Union[HassJob, Callable[..., None]], + action: HassJob | Callable[..., None], point_in_time: datetime, ) -> CALLBACK_TYPE: """Add a listener that fires once after a specific point in time.""" @@ -1165,7 +1156,7 @@ track_point_in_time = threaded_listener_factory(async_track_point_in_time) @bind_hass def async_track_point_in_utc_time( hass: HomeAssistant, - action: Union[HassJob, Callable[..., None]], + action: HassJob | Callable[..., None], point_in_time: datetime, ) -> CALLBACK_TYPE: """Add a listener that fires once after a specific point in UTC time.""" @@ -1176,7 +1167,7 @@ def async_track_point_in_utc_time( # having to figure out how to call the action every time its called. job = action if isinstance(action, HassJob) else HassJob(action) - cancel_callback: Optional[asyncio.TimerHandle] = None + cancel_callback: asyncio.TimerHandle | None = None @callback def run_action() -> None: @@ -1217,7 +1208,7 @@ track_point_in_utc_time = threaded_listener_factory(async_track_point_in_utc_tim @callback @bind_hass def async_call_later( - hass: HomeAssistant, delay: float, action: Union[HassJob, Callable[..., None]] + hass: HomeAssistant, delay: float, action: HassJob | Callable[..., None] ) -> CALLBACK_TYPE: """Add a listener that is called in .""" return async_track_point_in_utc_time( @@ -1232,7 +1223,7 @@ call_later = threaded_listener_factory(async_call_later) @bind_hass def async_track_time_interval( hass: HomeAssistant, - action: Callable[..., Union[None, Awaitable]], + action: Callable[..., None | Awaitable], interval: timedelta, ) -> CALLBACK_TYPE: """Add a listener that fires repetitively at every timedelta interval.""" @@ -1276,9 +1267,9 @@ class SunListener: hass: HomeAssistant = attr.ib() job: HassJob = attr.ib() event: str = attr.ib() - offset: Optional[timedelta] = attr.ib() - _unsub_sun: Optional[CALLBACK_TYPE] = attr.ib(default=None) - _unsub_config: Optional[CALLBACK_TYPE] = attr.ib(default=None) + offset: timedelta | None = attr.ib() + _unsub_sun: CALLBACK_TYPE | None = attr.ib(default=None) + _unsub_config: CALLBACK_TYPE | None = attr.ib(default=None) @callback def async_attach(self) -> None: @@ -1332,7 +1323,7 @@ class SunListener: @callback @bind_hass def async_track_sunrise( - hass: HomeAssistant, action: Callable[..., None], offset: Optional[timedelta] = None + hass: HomeAssistant, action: Callable[..., None], offset: timedelta | None = None ) -> CALLBACK_TYPE: """Add a listener that will fire a specified offset from sunrise daily.""" listener = SunListener(hass, HassJob(action), SUN_EVENT_SUNRISE, offset) @@ -1346,7 +1337,7 @@ track_sunrise = threaded_listener_factory(async_track_sunrise) @callback @bind_hass def async_track_sunset( - hass: HomeAssistant, action: Callable[..., None], offset: Optional[timedelta] = None + hass: HomeAssistant, action: Callable[..., None], offset: timedelta | None = None ) -> CALLBACK_TYPE: """Add a listener that will fire a specified offset from sunset daily.""" listener = SunListener(hass, HassJob(action), SUN_EVENT_SUNSET, offset) @@ -1365,9 +1356,9 @@ time_tracker_utcnow = dt_util.utcnow def async_track_utc_time_change( hass: HomeAssistant, action: Callable[..., None], - hour: Optional[Any] = None, - minute: Optional[Any] = None, - second: Optional[Any] = None, + hour: Any | None = None, + minute: Any | None = None, + second: Any | None = None, local: bool = False, ) -> CALLBACK_TYPE: """Add a listener that will fire if time matches a pattern.""" @@ -1394,7 +1385,7 @@ def async_track_utc_time_change( localized_now, matching_seconds, matching_minutes, matching_hours ) - time_listener: Optional[CALLBACK_TYPE] = None + time_listener: CALLBACK_TYPE | None = None @callback def pattern_time_change_listener(_: datetime) -> None: @@ -1431,9 +1422,9 @@ track_utc_time_change = threaded_listener_factory(async_track_utc_time_change) def async_track_time_change( hass: HomeAssistant, action: Callable[..., None], - hour: Optional[Any] = None, - minute: Optional[Any] = None, - second: Optional[Any] = None, + hour: Any | None = None, + minute: Any | None = None, + second: Any | None = None, ) -> CALLBACK_TYPE: """Add a listener that will fire if UTC time matches a pattern.""" return async_track_utc_time_change(hass, action, hour, minute, second, local=True) @@ -1442,9 +1433,7 @@ def async_track_time_change( track_time_change = threaded_listener_factory(async_track_time_change) -def process_state_match( - parameter: Union[None, str, Iterable[str]] -) -> Callable[[str], bool]: +def process_state_match(parameter: None | str | Iterable[str]) -> Callable[[str], bool]: """Convert parameter to function that matches input against parameter.""" if parameter is None or parameter == MATCH_ALL: return lambda _: True @@ -1459,7 +1448,7 @@ def process_state_match( @callback def _entities_domains_from_render_infos( render_infos: Iterable[RenderInfo], -) -> Tuple[Set, Set]: +) -> tuple[set, set]: """Combine from multiple RenderInfo.""" entities = set() domains = set() @@ -1520,7 +1509,7 @@ def _event_triggers_rerender(event: Event, info: RenderInfo) -> bool: @callback def _rate_limit_for_event( event: Event, info: RenderInfo, track_template_: TrackTemplate -) -> Optional[timedelta]: +) -> timedelta | None: """Determine the rate limit for an event.""" entity_id = event.data.get(ATTR_ENTITY_ID) @@ -1532,7 +1521,7 @@ def _rate_limit_for_event( if track_template_.rate_limit is not None: return track_template_.rate_limit - rate_limit: Optional[timedelta] = info.rate_limit + rate_limit: timedelta | None = info.rate_limit return rate_limit diff --git a/homeassistant/helpers/frame.py b/homeassistant/helpers/frame.py index a0517338ec8..f10e8f4c25c 100644 --- a/homeassistant/helpers/frame.py +++ b/homeassistant/helpers/frame.py @@ -1,9 +1,11 @@ """Provide frame helper for finding the current frame context.""" +from __future__ import annotations + import asyncio import functools import logging from traceback import FrameSummary, extract_stack -from typing import Any, Callable, Optional, Tuple, TypeVar, cast +from typing import Any, Callable, TypeVar, cast from homeassistant.exceptions import HomeAssistantError @@ -13,8 +15,8 @@ CALLABLE_T = TypeVar("CALLABLE_T", bound=Callable) # pylint: disable=invalid-na def get_integration_frame( - exclude_integrations: Optional[set] = None, -) -> Tuple[FrameSummary, str, str]: + exclude_integrations: set | None = None, +) -> tuple[FrameSummary, str, str]: """Return the frame, integration and integration path of the current stack frame.""" found_frame = None if not exclude_integrations: @@ -64,7 +66,7 @@ def report(what: str) -> None: def report_integration( - what: str, integration_frame: Tuple[FrameSummary, str, str] + what: str, integration_frame: tuple[FrameSummary, str, str] ) -> None: """Report incorrect usage in an integration. diff --git a/homeassistant/helpers/httpx_client.py b/homeassistant/helpers/httpx_client.py index b86223964b3..dfac0694a07 100644 --- a/homeassistant/helpers/httpx_client.py +++ b/homeassistant/helpers/httpx_client.py @@ -1,6 +1,8 @@ """Helper for httpx.""" +from __future__ import annotations + import sys -from typing import Any, Callable, Optional +from typing import Any, Callable import httpx @@ -29,7 +31,7 @@ def get_async_client( """ key = DATA_ASYNC_CLIENT if verify_ssl else DATA_ASYNC_CLIENT_NOVERIFY - client: Optional[httpx.AsyncClient] = hass.data.get(key) + client: httpx.AsyncClient | None = hass.data.get(key) if client is None: client = hass.data[key] = create_async_httpx_client(hass, verify_ssl) diff --git a/homeassistant/helpers/icon.py b/homeassistant/helpers/icon.py index dd64e9c92f1..628dc9d341e 100644 --- a/homeassistant/helpers/icon.py +++ b/homeassistant/helpers/icon.py @@ -1,9 +1,9 @@ """Icon helper methods.""" -from typing import Optional +from __future__ import annotations def icon_for_battery_level( - battery_level: Optional[int] = None, charging: bool = False + battery_level: int | None = None, charging: bool = False ) -> str: """Return a battery icon valid identifier.""" icon = "mdi:battery" @@ -20,7 +20,7 @@ def icon_for_battery_level( return icon -def icon_for_signal_level(signal_level: Optional[int] = None) -> str: +def icon_for_signal_level(signal_level: int | None = None) -> str: """Return a signal icon valid identifier.""" if signal_level is None or signal_level == 0: return "mdi:signal-cellular-outline" diff --git a/homeassistant/helpers/instance_id.py b/homeassistant/helpers/instance_id.py index 5feca605099..5b6f645c55a 100644 --- a/homeassistant/helpers/instance_id.py +++ b/homeassistant/helpers/instance_id.py @@ -1,5 +1,6 @@ """Helper to create a unique instance ID.""" -from typing import Dict, Optional +from __future__ import annotations + import uuid from homeassistant.core import HomeAssistant @@ -17,7 +18,7 @@ async def async_get(hass: HomeAssistant) -> str: """Get unique ID for the hass instance.""" store = storage.Store(hass, DATA_VERSION, DATA_KEY, True) - data: Optional[Dict[str, str]] = await storage.async_migrator( # type: ignore + data: dict[str, str] | None = await storage.async_migrator( # type: ignore hass, hass.config.path(LEGACY_UUID_FILE), store, diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 1c5d56ccbd1..2bc2ff0f837 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -3,7 +3,7 @@ from __future__ import annotations import logging import re -from typing import Any, Callable, Dict, Iterable, Optional +from typing import Any, Callable, Dict, Iterable import voluptuous as vol @@ -52,9 +52,9 @@ async def async_handle( hass: HomeAssistantType, platform: str, intent_type: str, - slots: Optional[_SlotsType] = None, - text_input: Optional[str] = None, - context: Optional[Context] = None, + slots: _SlotsType | None = None, + text_input: str | None = None, + context: Context | None = None, ) -> IntentResponse: """Handle an intent.""" handler: IntentHandler = hass.data.get(DATA_KEY, {}).get(intent_type) @@ -103,7 +103,7 @@ class IntentUnexpectedError(IntentError): @callback @bind_hass def async_match_state( - hass: HomeAssistantType, name: str, states: Optional[Iterable[State]] = None + hass: HomeAssistantType, name: str, states: Iterable[State] | None = None ) -> State: """Find a state that matches the name.""" if states is None: @@ -127,10 +127,10 @@ def async_test_feature(state: State, feature: int, feature_name: str) -> None: class IntentHandler: """Intent handler registration.""" - intent_type: Optional[str] = None - slot_schema: Optional[vol.Schema] = None - _slot_schema: Optional[vol.Schema] = None - platforms: Optional[Iterable[str]] = [] + intent_type: str | None = None + slot_schema: vol.Schema | None = None + _slot_schema: vol.Schema | None = None + platforms: Iterable[str] | None = [] @callback def async_can_handle(self, intent_obj: Intent) -> bool: @@ -163,7 +163,7 @@ class IntentHandler: return f"<{self.__class__.__name__} - {self.intent_type}>" -def _fuzzymatch(name: str, items: Iterable[T], key: Callable[[T], str]) -> Optional[T]: +def _fuzzymatch(name: str, items: Iterable[T], key: Callable[[T], str]) -> T | None: """Fuzzy matching function.""" matches = [] pattern = ".*?".join(name) @@ -226,7 +226,7 @@ class Intent: platform: str, intent_type: str, slots: _SlotsType, - text_input: Optional[str], + text_input: str | None, context: Context, ) -> None: """Initialize an intent.""" @@ -246,15 +246,15 @@ class Intent: class IntentResponse: """Response to an intent.""" - def __init__(self, intent: Optional[Intent] = None) -> None: + def __init__(self, intent: Intent | None = None) -> None: """Initialize an IntentResponse.""" self.intent = intent - self.speech: Dict[str, Dict[str, Any]] = {} - self.card: Dict[str, Dict[str, str]] = {} + self.speech: dict[str, dict[str, Any]] = {} + self.card: dict[str, dict[str, str]] = {} @callback def async_set_speech( - self, speech: str, speech_type: str = "plain", extra_data: Optional[Any] = None + self, speech: str, speech_type: str = "plain", extra_data: Any | None = None ) -> None: """Set speech response.""" self.speech[speech_type] = {"speech": speech, "extra_data": extra_data} @@ -267,6 +267,6 @@ class IntentResponse: self.card[card_type] = {"title": title, "content": content} @callback - def as_dict(self) -> Dict[str, Dict[str, Dict[str, Any]]]: + def as_dict(self) -> dict[str, dict[str, dict[str, Any]]]: """Return a dictionary representation of an intent response.""" return {"speech": self.speech, "card": self.card} diff --git a/homeassistant/helpers/location.py b/homeassistant/helpers/location.py index 19058bc3e7f..4e02b40abbb 100644 --- a/homeassistant/helpers/location.py +++ b/homeassistant/helpers/location.py @@ -1,7 +1,8 @@ """Location helpers for Home Assistant.""" +from __future__ import annotations import logging -from typing import Optional, Sequence +from typing import Sequence import voluptuous as vol @@ -25,9 +26,7 @@ def has_location(state: State) -> bool: ) -def closest( - latitude: float, longitude: float, states: Sequence[State] -) -> Optional[State]: +def closest(latitude: float, longitude: float, states: Sequence[State]) -> State | None: """Return closest state to point. Async friendly. @@ -50,8 +49,8 @@ def closest( def find_coordinates( - hass: HomeAssistantType, entity_id: str, recursion_history: Optional[list] = None -) -> Optional[str]: + hass: HomeAssistantType, entity_id: str, recursion_history: list | None = None +) -> str | None: """Find the gps coordinates of the entity in the form of '90.000,180.000'.""" entity_state = hass.states.get(entity_id) diff --git a/homeassistant/helpers/logging.py b/homeassistant/helpers/logging.py index 49b9bfcffec..a32d13ce513 100644 --- a/homeassistant/helpers/logging.py +++ b/homeassistant/helpers/logging.py @@ -1,7 +1,9 @@ """Helpers for logging allowing more advanced logging styles to be used.""" +from __future__ import annotations + import inspect import logging -from typing import Any, Mapping, MutableMapping, Optional, Tuple +from typing import Any, Mapping, MutableMapping class KeywordMessage: @@ -26,7 +28,7 @@ class KeywordStyleAdapter(logging.LoggerAdapter): """Represents an adapter wrapping the logger allowing KeywordMessages.""" def __init__( - self, logger: logging.Logger, extra: Optional[Mapping[str, Any]] = None + self, logger: logging.Logger, extra: Mapping[str, Any] | None = None ) -> None: """Initialize a new StyleAdapter for the provided logger.""" super().__init__(logger, extra or {}) @@ -41,7 +43,7 @@ class KeywordStyleAdapter(logging.LoggerAdapter): def process( self, msg: Any, kwargs: MutableMapping[str, Any] - ) -> Tuple[Any, MutableMapping[str, Any]]: + ) -> tuple[Any, MutableMapping[str, Any]]: """Process the keyword args in preparation for logging.""" return ( msg, diff --git a/homeassistant/helpers/network.py b/homeassistant/helpers/network.py index 21f69dc539a..6278bf8f855 100644 --- a/homeassistant/helpers/network.py +++ b/homeassistant/helpers/network.py @@ -1,6 +1,8 @@ """Network helpers.""" +from __future__ import annotations + from ipaddress import ip_address -from typing import Optional, cast +from typing import cast import yarl @@ -117,7 +119,7 @@ def get_url( raise NoURLAvailableError -def _get_request_host() -> Optional[str]: +def _get_request_host() -> str | None: """Get the host address of the current request.""" request = http.current_request.get() if request is None: diff --git a/homeassistant/helpers/ratelimit.py b/homeassistant/helpers/ratelimit.py index 40f10e69d25..fa671c6627f 100644 --- a/homeassistant/helpers/ratelimit.py +++ b/homeassistant/helpers/ratelimit.py @@ -1,8 +1,10 @@ """Ratelimit helper.""" +from __future__ import annotations + import asyncio from datetime import datetime, timedelta import logging -from typing import Any, Callable, Dict, Hashable, Optional +from typing import Any, Callable, Hashable from homeassistant.core import HomeAssistant, callback import homeassistant.util.dt as dt_util @@ -19,8 +21,8 @@ class KeyedRateLimit: ): """Initialize ratelimit tracker.""" self.hass = hass - self._last_triggered: Dict[Hashable, datetime] = {} - self._rate_limit_timers: Dict[Hashable, asyncio.TimerHandle] = {} + self._last_triggered: dict[Hashable, datetime] = {} + self._rate_limit_timers: dict[Hashable, asyncio.TimerHandle] = {} @callback def async_has_timer(self, key: Hashable) -> bool: @@ -30,7 +32,7 @@ class KeyedRateLimit: return key in self._rate_limit_timers @callback - def async_triggered(self, key: Hashable, now: Optional[datetime] = None) -> None: + def async_triggered(self, key: Hashable, now: datetime | None = None) -> None: """Call when the action we are tracking was triggered.""" self.async_cancel_timer(key) self._last_triggered[key] = now or dt_util.utcnow() @@ -54,11 +56,11 @@ class KeyedRateLimit: def async_schedule_action( self, key: Hashable, - rate_limit: Optional[timedelta], + rate_limit: timedelta | None, now: datetime, action: Callable, *args: Any, - ) -> Optional[datetime]: + ) -> datetime | None: """Check rate limits and schedule an action if we hit the limit. If the rate limit is hit: diff --git a/homeassistant/helpers/reload.py b/homeassistant/helpers/reload.py index 4a768a79320..b53ed554e86 100644 --- a/homeassistant/helpers/reload.py +++ b/homeassistant/helpers/reload.py @@ -1,8 +1,9 @@ """Class to reload platforms.""" +from __future__ import annotations import asyncio import logging -from typing import Dict, Iterable, List, Optional +from typing import Iterable from homeassistant import config as conf_util from homeassistant.const import SERVICE_RELOAD @@ -61,7 +62,7 @@ async def _resetup_platform( if not conf: return - root_config: Dict = {integration_platform: []} + root_config: dict = {integration_platform: []} # Extract only the config for template, ignore the rest. for p_type, p_config in config_per_platform(conf, integration_platform): if p_type != integration_name: @@ -101,7 +102,7 @@ async def _async_setup_platform( hass: HomeAssistantType, integration_name: str, integration_platform: str, - platform_configs: List[Dict], + platform_configs: list[dict], ) -> None: """Platform for the first time when new configuration is added.""" if integration_platform not in hass.data: @@ -119,7 +120,7 @@ async def _async_setup_platform( async def _async_reconfig_platform( - platform: EntityPlatform, platform_configs: List[Dict] + platform: EntityPlatform, platform_configs: list[dict] ) -> None: """Reconfigure an already loaded platform.""" await platform.async_reset() @@ -129,7 +130,7 @@ async def _async_reconfig_platform( async def async_integration_yaml_config( hass: HomeAssistantType, integration_name: str -) -> Optional[ConfigType]: +) -> ConfigType | None: """Fetch the latest yaml configuration for an integration.""" integration = await async_get_integration(hass, integration_name) @@ -141,7 +142,7 @@ async def async_integration_yaml_config( @callback def async_get_platform_without_config_entry( hass: HomeAssistantType, integration_name: str, integration_platform_name: str -) -> Optional[EntityPlatform]: +) -> EntityPlatform | None: """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: diff --git a/homeassistant/helpers/restore_state.py b/homeassistant/helpers/restore_state.py index 51ebc6fa774..3350ed7a073 100644 --- a/homeassistant/helpers/restore_state.py +++ b/homeassistant/helpers/restore_state.py @@ -4,7 +4,7 @@ from __future__ import annotations import asyncio from datetime import datetime, timedelta import logging -from typing import Any, Dict, List, Optional, Set, cast +from typing import Any, cast from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP from homeassistant.core import ( @@ -45,12 +45,12 @@ class StoredState: self.state = state self.last_seen = last_seen - def as_dict(self) -> Dict[str, Any]: + def as_dict(self) -> dict[str, Any]: """Return a dict representation of the stored state.""" return {"state": self.state.as_dict(), "last_seen": self.last_seen} @classmethod - def from_dict(cls, json_dict: Dict) -> StoredState: + def from_dict(cls, json_dict: dict) -> StoredState: """Initialize a stored state from a dict.""" last_seen = json_dict["last_seen"] @@ -106,11 +106,11 @@ class RestoreStateData: self.store: Store = Store( hass, STORAGE_VERSION, STORAGE_KEY, encoder=JSONEncoder ) - self.last_states: Dict[str, StoredState] = {} - self.entity_ids: Set[str] = set() + self.last_states: dict[str, StoredState] = {} + self.entity_ids: set[str] = set() @callback - def async_get_stored_states(self) -> List[StoredState]: + def async_get_stored_states(self) -> list[StoredState]: """Get the set of states which should be stored. This includes the states of all registered entities, as well as the @@ -249,7 +249,7 @@ class RestoreEntity(Entity): ) data.async_restore_entity_removed(self.entity_id) - async def async_get_last_state(self) -> Optional[State]: + async def async_get_last_state(self) -> State | None: """Get the entity state from the previous run.""" if self.hass is None or self.entity_id is None: # Return None if this entity isn't added to hass yet diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 51bf7b14927..be6be32da1e 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -1,4 +1,6 @@ """Helpers to execute scripts.""" +from __future__ import annotations + import asyncio from contextlib import asynccontextmanager from datetime import datetime, timedelta @@ -6,18 +8,7 @@ from functools import partial import itertools import logging from types import MappingProxyType -from typing import ( - Any, - Callable, - Dict, - List, - Optional, - Sequence, - Set, - Tuple, - Union, - cast, -) +from typing import Any, Callable, Dict, Sequence, Union, cast import async_timeout import voluptuous as vol @@ -232,8 +223,8 @@ STATIC_VALIDATION_ACTION_TYPES = ( async def async_validate_actions_config( - hass: HomeAssistant, actions: List[ConfigType] -) -> List[ConfigType]: + hass: HomeAssistant, actions: list[ConfigType] +) -> list[ConfigType]: """Validate a list of actions.""" return await asyncio.gather( *[async_validate_action_config(hass, action) for action in actions] @@ -300,8 +291,8 @@ class _ScriptRun: self, hass: HomeAssistant, script: "Script", - variables: Dict[str, Any], - context: Optional[Context], + variables: dict[str, Any], + context: Context | None, log_exceptions: bool, ) -> None: self._hass = hass @@ -310,7 +301,7 @@ class _ScriptRun: self._context = context self._log_exceptions = log_exceptions self._step = -1 - self._action: Optional[Dict[str, Any]] = None + self._action: dict[str, Any] | None = None self._stop = asyncio.Event() self._stopped = asyncio.Event() @@ -890,7 +881,7 @@ async def _async_stop_scripts_at_shutdown(hass, event): _VarsType = Union[Dict[str, Any], MappingProxyType] -def _referenced_extract_ids(data: Dict[str, Any], key: str, found: Set[str]) -> None: +def _referenced_extract_ids(data: dict[str, Any], key: str, found: set[str]) -> None: """Extract referenced IDs.""" if not data: return @@ -913,20 +904,20 @@ class Script: def __init__( self, hass: HomeAssistant, - sequence: Sequence[Dict[str, Any]], + sequence: Sequence[dict[str, Any]], name: str, domain: str, *, # Used in "Running " log message - running_description: Optional[str] = None, - change_listener: Optional[Callable[..., Any]] = None, + running_description: str | None = None, + change_listener: Callable[..., Any] | None = None, script_mode: str = DEFAULT_SCRIPT_MODE, max_runs: int = DEFAULT_MAX, max_exceeded: str = DEFAULT_MAX_EXCEEDED, - logger: Optional[logging.Logger] = None, + logger: logging.Logger | None = None, log_exceptions: bool = True, top_level: bool = True, - variables: Optional[ScriptVariables] = None, + variables: ScriptVariables | None = None, ) -> None: """Initialize the script.""" all_scripts = hass.data.get(DATA_SCRIPTS) @@ -959,25 +950,25 @@ class Script: self._log_exceptions = log_exceptions self.last_action = None - self.last_triggered: Optional[datetime] = None + self.last_triggered: datetime | None = None - self._runs: List[_ScriptRun] = [] + self._runs: list[_ScriptRun] = [] self.max_runs = max_runs self._max_exceeded = max_exceeded if script_mode == SCRIPT_MODE_QUEUED: self._queue_lck = asyncio.Lock() - self._config_cache: Dict[Set[Tuple], Callable[..., bool]] = {} - self._repeat_script: Dict[int, Script] = {} - self._choose_data: Dict[int, Dict[str, Any]] = {} - self._referenced_entities: Optional[Set[str]] = None - self._referenced_devices: Optional[Set[str]] = None + self._config_cache: dict[set[tuple], Callable[..., bool]] = {} + self._repeat_script: dict[int, Script] = {} + self._choose_data: dict[int, dict[str, Any]] = {} + self._referenced_entities: set[str] | None = None + self._referenced_devices: set[str] | None = None self.variables = variables self._variables_dynamic = template.is_complex(variables) if self._variables_dynamic: template.attach(hass, variables) @property - def change_listener(self) -> Optional[Callable[..., Any]]: + def change_listener(self) -> Callable[..., Any] | None: """Return the change_listener.""" return self._change_listener @@ -991,13 +982,13 @@ class Script: ): self._change_listener_job = HassJob(change_listener) - def _set_logger(self, logger: Optional[logging.Logger] = None) -> None: + def _set_logger(self, logger: logging.Logger | None = None) -> None: if logger: self._logger = logger else: self._logger = logging.getLogger(f"{__name__}.{slugify(self.name)}") - def update_logger(self, logger: Optional[logging.Logger] = None) -> None: + def update_logger(self, logger: logging.Logger | None = None) -> None: """Update logger.""" self._set_logger(logger) for script in self._repeat_script.values(): @@ -1038,7 +1029,7 @@ class Script: if self._referenced_devices is not None: return self._referenced_devices - referenced: Set[str] = set() + referenced: set[str] = set() for step in self.sequence: action = cv.determine_script_action(step) @@ -1067,7 +1058,7 @@ class Script: if self._referenced_entities is not None: return self._referenced_entities - referenced: Set[str] = set() + referenced: set[str] = set() for step in self.sequence: action = cv.determine_script_action(step) @@ -1091,7 +1082,7 @@ class Script: return referenced def run( - self, variables: Optional[_VarsType] = None, context: Optional[Context] = None + self, variables: _VarsType | None = None, context: Context | None = None ) -> None: """Run script.""" asyncio.run_coroutine_threadsafe( @@ -1100,9 +1091,9 @@ class Script: async def async_run( self, - run_variables: Optional[_VarsType] = None, - context: Optional[Context] = None, - started_action: Optional[Callable[..., Any]] = None, + run_variables: _VarsType | None = None, + context: Context | None = None, + started_action: Callable[..., Any] | None = None, ) -> None: """Run script.""" if context is None: diff --git a/homeassistant/helpers/script_variables.py b/homeassistant/helpers/script_variables.py index 818263c9dd5..86a700bc62b 100644 --- a/homeassistant/helpers/script_variables.py +++ b/homeassistant/helpers/script_variables.py @@ -1,5 +1,7 @@ """Script variables.""" -from typing import Any, Dict, Mapping, Optional +from __future__ import annotations + +from typing import Any, Mapping from homeassistant.core import HomeAssistant, callback @@ -9,20 +11,20 @@ from . import template class ScriptVariables: """Class to hold and render script variables.""" - def __init__(self, variables: Dict[str, Any]): + def __init__(self, variables: dict[str, Any]): """Initialize script variables.""" self.variables = variables - self._has_template: Optional[bool] = None + self._has_template: bool | None = None @callback def async_render( self, hass: HomeAssistant, - run_variables: Optional[Mapping[str, Any]], + run_variables: Mapping[str, Any] | None, *, render_as_defaults: bool = True, limited: bool = False, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Render script variables. The run variables are used to compute the static variables. diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 85595998a54..f1ab245d38a 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -10,14 +10,9 @@ from typing import ( Any, Awaitable, Callable, - Dict, Iterable, - List, - Optional, - Set, Tuple, TypedDict, - Union, cast, ) @@ -79,8 +74,8 @@ class ServiceParams(TypedDict): domain: str service: str - service_data: Dict[str, Any] - target: Optional[Dict] + service_data: dict[str, Any] + target: dict | None @dataclasses.dataclass @@ -88,17 +83,17 @@ class SelectedEntities: """Class to hold the selected entities.""" # Entities that were explicitly mentioned. - referenced: Set[str] = dataclasses.field(default_factory=set) + referenced: set[str] = dataclasses.field(default_factory=set) # Entities that were referenced via device/area ID. # Should not trigger a warning when they don't exist. - indirectly_referenced: Set[str] = dataclasses.field(default_factory=set) + indirectly_referenced: set[str] = dataclasses.field(default_factory=set) # Referenced items that could not be found. - missing_devices: Set[str] = dataclasses.field(default_factory=set) - missing_areas: Set[str] = dataclasses.field(default_factory=set) + missing_devices: set[str] = dataclasses.field(default_factory=set) + missing_areas: set[str] = dataclasses.field(default_factory=set) - def log_missing(self, missing_entities: Set[str]) -> None: + def log_missing(self, missing_entities: set[str]) -> None: """Log about missing items.""" parts = [] for label, items in ( @@ -137,7 +132,7 @@ async def async_call_from_config( blocking: bool = False, variables: TemplateVarsType = None, validate_config: bool = True, - context: Optional[ha.Context] = None, + context: ha.Context | None = None, ) -> None: """Call a service based on a config hash.""" try: @@ -235,7 +230,7 @@ def async_prepare_call_from_config( @bind_hass def extract_entity_ids( hass: HomeAssistantType, service_call: ha.ServiceCall, expand_group: bool = True -) -> Set[str]: +) -> set[str]: """Extract a list of entity ids from a service call. Will convert group entity ids to the entity ids it represents. @@ -251,7 +246,7 @@ async def async_extract_entities( entities: Iterable[Entity], service_call: ha.ServiceCall, expand_group: bool = True, -) -> List[Entity]: +) -> list[Entity]: """Extract a list of entity objects from a service call. Will convert group entity ids to the entity ids it represents. @@ -287,7 +282,7 @@ async def async_extract_entities( @bind_hass async def async_extract_entity_ids( hass: HomeAssistantType, service_call: ha.ServiceCall, expand_group: bool = True -) -> Set[str]: +) -> set[str]: """Extract a set of entity ids from a service call. Will convert group entity ids to the entity ids it represents. @@ -408,7 +403,7 @@ def _load_services_file(hass: HomeAssistantType, integration: Integration) -> JS def _load_services_files( hass: HomeAssistantType, integrations: Iterable[Integration] -) -> List[JSON_TYPE]: +) -> list[JSON_TYPE]: """Load service files for multiple intergrations.""" return [_load_services_file(hass, integration) for integration in integrations] @@ -416,7 +411,7 @@ def _load_services_files( @bind_hass async def async_get_all_descriptions( hass: HomeAssistantType, -) -> Dict[str, Dict[str, Any]]: +) -> dict[str, dict[str, Any]]: """Return descriptions (i.e. user documentation) for all service calls.""" descriptions_cache = hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {}) format_cache_key = "{}.{}".format @@ -448,7 +443,7 @@ async def async_get_all_descriptions( loaded[domain] = content # Build response - descriptions: Dict[str, Dict[str, Any]] = {} + descriptions: dict[str, dict[str, Any]] = {} for domain in services: descriptions[domain] = {} @@ -483,7 +478,7 @@ async def async_get_all_descriptions( @ha.callback @bind_hass def async_set_service_schema( - hass: HomeAssistantType, domain: str, service: str, schema: Dict[str, Any] + hass: HomeAssistantType, domain: str, service: str, schema: dict[str, Any] ) -> None: """Register a description for a service.""" hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {}) @@ -504,9 +499,9 @@ def async_set_service_schema( async def entity_service_call( hass: HomeAssistantType, platforms: Iterable["EntityPlatform"], - func: Union[str, Callable[..., Any]], + func: str | Callable[..., Any], call: ha.ServiceCall, - required_features: Optional[Iterable[int]] = None, + required_features: Iterable[int] | None = None, ) -> None: """Handle an entity service call. @@ -516,17 +511,17 @@ async def entity_service_call( user = await hass.auth.async_get_user(call.context.user_id) if user is None: raise UnknownUser(context=call.context) - entity_perms: Optional[ + entity_perms: None | ( Callable[[str, str], bool] - ] = user.permissions.check_entity + ) = user.permissions.check_entity else: entity_perms = None target_all_entities = call.data.get(ATTR_ENTITY_ID) == ENTITY_MATCH_ALL if target_all_entities: - referenced: Optional[SelectedEntities] = None - all_referenced: Optional[Set[str]] = None + referenced: SelectedEntities | None = None + all_referenced: set[str] | None = None else: # A set of entities we're trying to target. referenced = await async_extract_referenced_entity_ids(hass, call, True) @@ -534,7 +529,7 @@ async def entity_service_call( # If the service function is a string, we'll pass it the service call data if isinstance(func, str): - data: Union[Dict, ha.ServiceCall] = { + data: dict | ha.ServiceCall = { key: val for key, val in call.data.items() if key not in cv.ENTITY_SERVICE_FIELDS @@ -546,7 +541,7 @@ async def entity_service_call( # Check the permissions # A list with entities to call the service on. - entity_candidates: List["Entity"] = [] + entity_candidates: list["Entity"] = [] if entity_perms is None: for platform in platforms: @@ -662,8 +657,8 @@ async def entity_service_call( async def _handle_entity_call( hass: HomeAssistantType, entity: Entity, - func: Union[str, Callable[..., Any]], - data: Union[Dict, ha.ServiceCall], + func: str | Callable[..., Any], + data: dict | ha.ServiceCall, context: ha.Context, ) -> None: """Handle calling service method.""" @@ -693,7 +688,7 @@ def async_register_admin_service( hass: HomeAssistantType, domain: str, service: str, - service_func: Callable[[ha.ServiceCall], Optional[Awaitable]], + service_func: Callable[[ha.ServiceCall], Awaitable | None], schema: vol.Schema = vol.Schema({}, extra=vol.PREVENT_EXTRA), ) -> None: """Register a service that requires admin access.""" diff --git a/homeassistant/helpers/significant_change.py b/homeassistant/helpers/significant_change.py index a7be57693ba..b34df0075a3 100644 --- a/homeassistant/helpers/significant_change.py +++ b/homeassistant/helpers/significant_change.py @@ -29,7 +29,7 @@ The following cases will never be passed to your function: from __future__ import annotations from types import MappingProxyType -from typing import Any, Callable, Dict, Optional, Tuple, Union +from typing import Any, Callable, Optional, Union from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.core import HomeAssistant, State, callback @@ -66,7 +66,7 @@ ExtraCheckTypeFunc = Callable[ async def create_checker( hass: HomeAssistant, _domain: str, - extra_significant_check: Optional[ExtraCheckTypeFunc] = None, + extra_significant_check: ExtraCheckTypeFunc | None = None, ) -> SignificantlyChangedChecker: """Create a significantly changed checker for a domain.""" await _initialize(hass) @@ -90,15 +90,15 @@ async def _initialize(hass: HomeAssistant) -> None: await async_process_integration_platforms(hass, PLATFORM, process_platform) -def either_one_none(val1: Optional[Any], val2: Optional[Any]) -> bool: +def either_one_none(val1: Any | None, val2: Any | None) -> bool: """Test if exactly one value is None.""" return (val1 is None and val2 is not None) or (val1 is not None and val2 is None) def check_numeric_changed( - val1: Optional[Union[int, float]], - val2: Optional[Union[int, float]], - change: Union[int, float], + val1: int | float | None, + val2: int | float | None, + change: int | float, ) -> bool: """Check if two numeric values have changed.""" if val1 is None and val2 is None: @@ -125,22 +125,22 @@ class SignificantlyChangedChecker: def __init__( self, hass: HomeAssistant, - extra_significant_check: Optional[ExtraCheckTypeFunc] = None, + extra_significant_check: ExtraCheckTypeFunc | None = None, ) -> None: """Test if an entity has significantly changed.""" self.hass = hass - self.last_approved_entities: Dict[str, Tuple[State, Any]] = {} + self.last_approved_entities: dict[str, tuple[State, Any]] = {} self.extra_significant_check = extra_significant_check @callback def async_is_significant_change( - self, new_state: State, *, extra_arg: Optional[Any] = None + self, new_state: State, *, extra_arg: Any | None = None ) -> bool: """Return if this was a significant change. Extra kwargs are passed to the extra significant checker. """ - old_data: Optional[Tuple[State, Any]] = self.last_approved_entities.get( + old_data: tuple[State, Any] | None = self.last_approved_entities.get( new_state.entity_id ) @@ -164,9 +164,7 @@ class SignificantlyChangedChecker: self.last_approved_entities[new_state.entity_id] = (new_state, extra_arg) return True - functions: Optional[Dict[str, CheckTypeFunc]] = self.hass.data.get( - DATA_FUNCTIONS - ) + functions: dict[str, CheckTypeFunc] | None = self.hass.data.get(DATA_FUNCTIONS) if functions is None: raise RuntimeError("Significant Change not initialized") diff --git a/homeassistant/helpers/singleton.py b/homeassistant/helpers/singleton.py index ab4d12dc1cc..a48ea5d64f0 100644 --- a/homeassistant/helpers/singleton.py +++ b/homeassistant/helpers/singleton.py @@ -1,7 +1,9 @@ """Helper to help coordinating calls.""" +from __future__ import annotations + import asyncio import functools -from typing import Callable, Optional, TypeVar, cast +from typing import Callable, TypeVar, cast from homeassistant.core import HomeAssistant from homeassistant.loader import bind_hass @@ -24,7 +26,7 @@ def singleton(data_key: str) -> Callable[[FUNC], FUNC]: @bind_hass @functools.wraps(func) def wrapped(hass: HomeAssistant) -> T: - obj: Optional[T] = hass.data.get(data_key) + obj: T | None = hass.data.get(data_key) if obj is None: obj = hass.data[data_key] = func(hass) return obj diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index eda026c8563..32026f3d2d6 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -1,10 +1,12 @@ """Helpers that help with state related things.""" +from __future__ import annotations + import asyncio from collections import defaultdict import datetime as dt import logging from types import ModuleType, TracebackType -from typing import Any, Dict, Iterable, List, Optional, Type, Union +from typing import Any, Iterable from homeassistant.components.sun import STATE_ABOVE_HORIZON, STATE_BELOW_HORIZON from homeassistant.const import ( @@ -44,19 +46,19 @@ class AsyncTrackStates: def __init__(self, hass: HomeAssistantType) -> None: """Initialize a TrackStates block.""" self.hass = hass - self.states: List[State] = [] + self.states: list[State] = [] # pylint: disable=attribute-defined-outside-init - def __enter__(self) -> List[State]: + def __enter__(self) -> list[State]: """Record time from which to track changes.""" self.now = dt_util.utcnow() return self.states def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_value: Optional[BaseException], - traceback: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, ) -> None: """Add changes states to changes list.""" self.states.extend(get_changed_since(self.hass.states.async_all(), self.now)) @@ -64,7 +66,7 @@ class AsyncTrackStates: def get_changed_since( states: Iterable[State], utc_point_in_time: dt.datetime -) -> List[State]: +) -> list[State]: """Return list of states that have been changed since utc_point_in_time. Deprecated. Remove after June 2021. @@ -76,21 +78,21 @@ def get_changed_since( @bind_hass async def async_reproduce_state( hass: HomeAssistantType, - states: Union[State, Iterable[State]], + states: State | Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a list of states on multiple domains.""" if isinstance(states, State): states = [states] - to_call: Dict[str, List[State]] = defaultdict(list) + to_call: dict[str, list[State]] = defaultdict(list) for state in states: to_call[state.domain].append(state) - async def worker(domain: str, states_by_domain: List[State]) -> None: + async def worker(domain: str, states_by_domain: list[State]) -> None: try: integration = await async_get_integration(hass, domain) except IntegrationNotFound: @@ -100,7 +102,7 @@ async def async_reproduce_state( return try: - platform: Optional[ModuleType] = integration.get_platform("reproduce_state") + platform: ModuleType | None = integration.get_platform("reproduce_state") except ImportError: _LOGGER.warning("Integration %s does not support reproduce state", domain) return diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index 2bc13fbdf44..c1138b54f79 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -1,9 +1,11 @@ """Helper to help store data.""" +from __future__ import annotations + import asyncio from json import JSONEncoder import logging import os -from typing import Any, Callable, Dict, List, Optional, Type, Union +from typing import Any, Callable from homeassistant.const import EVENT_HOMEASSISTANT_FINAL_WRITE from homeassistant.core import CALLBACK_TYPE, CoreState, HomeAssistant, callback @@ -71,18 +73,18 @@ class Store: key: str, private: bool = False, *, - encoder: Optional[Type[JSONEncoder]] = None, + encoder: type[JSONEncoder] | None = None, ): """Initialize storage class.""" self.version = version self.key = key self.hass = hass self._private = private - self._data: Optional[Dict[str, Any]] = None - self._unsub_delay_listener: Optional[CALLBACK_TYPE] = None - self._unsub_final_write_listener: Optional[CALLBACK_TYPE] = None + self._data: dict[str, Any] | None = None + self._unsub_delay_listener: CALLBACK_TYPE | None = None + self._unsub_final_write_listener: CALLBACK_TYPE | None = None self._write_lock = asyncio.Lock() - self._load_task: Optional[asyncio.Future] = None + self._load_task: asyncio.Future | None = None self._encoder = encoder @property @@ -90,7 +92,7 @@ class Store: """Return the config path.""" return self.hass.config.path(STORAGE_DIR, self.key) - async def async_load(self) -> Union[Dict, List, None]: + async def async_load(self) -> dict | list | None: """Load data. If the expected version does not match the given version, the migrate @@ -140,7 +142,7 @@ class Store: return stored - async def async_save(self, data: Union[Dict, List]) -> None: + async def async_save(self, data: dict | list) -> None: """Save data.""" self._data = {"version": self.version, "key": self.key, "data": data} @@ -151,7 +153,7 @@ class Store: await self._async_handle_write_data() @callback - def async_delay_save(self, data_func: Callable[[], Dict], delay: float = 0) -> None: + def async_delay_save(self, data_func: Callable[[], dict], delay: float = 0) -> None: """Save data with an optional delay.""" self._data = {"version": self.version, "key": self.key, "data_func": data_func} @@ -224,7 +226,7 @@ class Store: except (json_util.SerializationError, json_util.WriteError) as err: _LOGGER.error("Error writing config for %s: %s", self.key, err) - def _write_data(self, path: str, data: Dict) -> None: + def _write_data(self, path: str, data: dict) -> None: """Write the data.""" if not os.path.isdir(os.path.dirname(path)): os.makedirs(os.path.dirname(path)) diff --git a/homeassistant/helpers/sun.py b/homeassistant/helpers/sun.py index ce0f0598318..ded1b3a7123 100644 --- a/homeassistant/helpers/sun.py +++ b/homeassistant/helpers/sun.py @@ -2,7 +2,7 @@ from __future__ import annotations import datetime -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET from homeassistant.core import callback @@ -44,8 +44,8 @@ def get_astral_location(hass: HomeAssistantType) -> astral.Location: def get_astral_event_next( hass: HomeAssistantType, event: str, - utc_point_in_time: Optional[datetime.datetime] = None, - offset: Optional[datetime.timedelta] = None, + utc_point_in_time: datetime.datetime | None = None, + offset: datetime.timedelta | None = None, ) -> datetime.datetime: """Calculate the next specified solar event.""" location = get_astral_location(hass) @@ -56,8 +56,8 @@ def get_astral_event_next( def get_location_astral_event_next( location: "astral.Location", event: str, - utc_point_in_time: Optional[datetime.datetime] = None, - offset: Optional[datetime.timedelta] = None, + utc_point_in_time: datetime.datetime | None = None, + offset: datetime.timedelta | None = None, ) -> datetime.datetime: """Calculate the next specified solar event.""" from astral import AstralError # pylint: disable=import-outside-toplevel @@ -91,8 +91,8 @@ def get_location_astral_event_next( def get_astral_event_date( hass: HomeAssistantType, event: str, - date: Union[datetime.date, datetime.datetime, None] = None, -) -> Optional[datetime.datetime]: + date: datetime.date | datetime.datetime | None = None, +) -> datetime.datetime | None: """Calculate the astral event time for the specified date.""" from astral import AstralError # pylint: disable=import-outside-toplevel @@ -114,7 +114,7 @@ def get_astral_event_date( @callback @bind_hass def is_up( - hass: HomeAssistantType, utc_point_in_time: Optional[datetime.datetime] = None + hass: HomeAssistantType, utc_point_in_time: datetime.datetime | None = None ) -> bool: """Calculate if the sun is currently up.""" if utc_point_in_time is None: diff --git a/homeassistant/helpers/system_info.py b/homeassistant/helpers/system_info.py index 9c2c4b181ad..b9894ca18fa 100644 --- a/homeassistant/helpers/system_info.py +++ b/homeassistant/helpers/system_info.py @@ -1,7 +1,9 @@ """Helper to gather system info.""" +from __future__ import annotations + import os import platform -from typing import Any, Dict +from typing import Any from homeassistant.const import __version__ as current_version from homeassistant.loader import bind_hass @@ -11,7 +13,7 @@ from .typing import HomeAssistantType @bind_hass -async def async_get_system_info(hass: HomeAssistantType) -> Dict[str, Any]: +async def async_get_system_info(hass: HomeAssistantType) -> dict[str, Any]: """Return info about the system.""" info_object = { "installation_type": "Unknown", diff --git a/homeassistant/helpers/temperature.py b/homeassistant/helpers/temperature.py index 18ca8355159..e0f089e93b9 100644 --- a/homeassistant/helpers/temperature.py +++ b/homeassistant/helpers/temperature.py @@ -1,6 +1,7 @@ """Temperature helpers for Home Assistant.""" +from __future__ import annotations + from numbers import Number -from typing import Optional from homeassistant.const import PRECISION_HALVES, PRECISION_TENTHS from homeassistant.core import HomeAssistant @@ -8,8 +9,8 @@ from homeassistant.util.temperature import convert as convert_temperature def display_temp( - hass: HomeAssistant, temperature: Optional[float], unit: str, precision: float -) -> Optional[float]: + hass: HomeAssistant, temperature: float | None, unit: str, precision: float +) -> float | None: """Convert temperature into preferred units/precision for display.""" temperature_unit = unit ha_unit = hass.config.units.temperature_unit diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 7f768354035..e9752645804 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -13,7 +13,7 @@ import math from operator import attrgetter import random import re -from typing import Any, Dict, Generator, Iterable, Optional, Type, Union, cast +from typing import Any, Generator, Iterable, cast from urllib.parse import urlencode as urllib_urlencode import weakref @@ -125,7 +125,7 @@ def is_template_string(maybe_template: str) -> bool: class ResultWrapper: """Result wrapper class to store render result.""" - render_result: Optional[str] + render_result: str | None def gen_result_wrapper(kls): @@ -134,7 +134,7 @@ def gen_result_wrapper(kls): class Wrapper(kls, ResultWrapper): """Wrapper of a kls that can store render_result.""" - def __init__(self, *args: tuple, render_result: Optional[str] = None) -> None: + def __init__(self, *args: tuple, render_result: str | None = None) -> None: super().__init__(*args) self.render_result = render_result @@ -156,15 +156,13 @@ class TupleWrapper(tuple, ResultWrapper): # This is all magic to be allowed to subclass a tuple. - def __new__( - cls, value: tuple, *, render_result: Optional[str] = None - ) -> TupleWrapper: + def __new__(cls, value: tuple, *, render_result: str | None = 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: Optional[str] = None): + def __init__(self, value: tuple, *, render_result: str | None = None): """Initialize a new tuple class.""" self.render_result = render_result @@ -176,7 +174,7 @@ class TupleWrapper(tuple, ResultWrapper): return self.render_result -RESULT_WRAPPERS: Dict[Type, Type] = { +RESULT_WRAPPERS: dict[type, type] = { kls: gen_result_wrapper(kls) # type: ignore[no-untyped-call] for kls in (list, dict, set) } @@ -200,15 +198,15 @@ class RenderInfo: # Will be set sensibly once frozen. self.filter_lifecycle = _true self.filter = _true - self._result: Optional[str] = None + self._result: str | None = None self.is_static = False - self.exception: Optional[TemplateError] = None + self.exception: TemplateError | None = None self.all_states = False self.all_states_lifecycle = False self.domains = set() self.domains_lifecycle = set() self.entities = set() - self.rate_limit: Optional[timedelta] = None + self.rate_limit: timedelta | None = None self.has_time = False def __repr__(self) -> str: @@ -294,7 +292,7 @@ class Template: self.template: str = template.strip() self._compiled_code = None - self._compiled: Optional[Template] = None + self._compiled: Template | None = None self.hass = hass self.is_static = not is_template_string(template) self._limited = None @@ -304,7 +302,7 @@ class Template: if self.hass is None: return _NO_HASS_ENV wanted_env = _ENVIRONMENT_LIMITED if self._limited else _ENVIRONMENT - ret: Optional[TemplateEnvironment] = self.hass.data.get(wanted_env) + ret: TemplateEnvironment | None = self.hass.data.get(wanted_env) if ret is None: ret = self.hass.data[wanted_env] = TemplateEnvironment(self.hass, self._limited) # type: ignore[no-untyped-call] return ret @@ -776,7 +774,7 @@ def _collect_state(hass: HomeAssistantType, entity_id: str) -> None: entity_collect.entities.add(entity_id) -def _state_generator(hass: HomeAssistantType, domain: Optional[str]) -> Generator: +def _state_generator(hass: HomeAssistantType, domain: str | None) -> Generator: """State generator for a domain or all states.""" for state in sorted(hass.states.async_all(domain), key=attrgetter("entity_id")): yield TemplateState(hass, state, collect=False) @@ -784,20 +782,20 @@ def _state_generator(hass: HomeAssistantType, domain: Optional[str]) -> Generato def _get_state_if_valid( hass: HomeAssistantType, entity_id: str -) -> Optional[TemplateState]: +) -> TemplateState | None: state = hass.states.get(entity_id) if state is None and not valid_entity_id(entity_id): raise TemplateError(f"Invalid entity ID '{entity_id}'") # type: ignore return _get_template_state_from_state(hass, entity_id, state) -def _get_state(hass: HomeAssistantType, entity_id: str) -> Optional[TemplateState]: +def _get_state(hass: HomeAssistantType, entity_id: str) -> TemplateState | None: return _get_template_state_from_state(hass, entity_id, hass.states.get(entity_id)) def _get_template_state_from_state( - hass: HomeAssistantType, entity_id: str, state: Optional[State] -) -> Optional[TemplateState]: + hass: HomeAssistantType, entity_id: str, state: State | None +) -> TemplateState | None: if state is None: # Only need to collect if none, if not none collect first actual # access to the state properties in the state wrapper. @@ -808,7 +806,7 @@ def _get_template_state_from_state( def _resolve_state( hass: HomeAssistantType, entity_id_or_state: Any -) -> Union[State, TemplateState, None]: +) -> State | TemplateState | None: """Return state or entity_id if given.""" if isinstance(entity_id_or_state, State): return entity_id_or_state @@ -817,7 +815,7 @@ def _resolve_state( return None -def result_as_boolean(template_result: Optional[str]) -> bool: +def result_as_boolean(template_result: str | None) -> bool: """Convert the template result to a boolean. True/not 0/'1'/'true'/'yes'/'on'/'enable' are considered truthy diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py index a7cd63de479..2e5f1dd8c54 100644 --- a/homeassistant/helpers/trace.py +++ b/homeassistant/helpers/trace.py @@ -1,8 +1,10 @@ """Helpers for script and condition tracing.""" +from __future__ import annotations + from collections import deque from contextlib import contextmanager from contextvars import ContextVar -from typing import Any, Deque, Dict, Generator, List, Optional, Tuple, Union, cast +from typing import Any, Deque, Generator, cast from homeassistant.helpers.typing import TemplateVarsType import homeassistant.util.dt as dt_util @@ -13,9 +15,9 @@ class TraceElement: def __init__(self, variables: TemplateVarsType, path: str): """Container for trace data.""" - self._error: Optional[Exception] = None + self._error: Exception | None = None self.path: str = path - self._result: Optional[dict] = None + self._result: dict | None = None self._timestamp = dt_util.utcnow() if variables is None: @@ -41,9 +43,9 @@ class TraceElement: """Set result.""" self._result = {**kwargs} - def as_dict(self) -> Dict[str, Any]: + def as_dict(self) -> dict[str, Any]: """Return dictionary version of this TraceElement.""" - result: Dict[str, Any] = {"path": self.path, "timestamp": self._timestamp} + result: dict[str, Any] = {"path": self.path, "timestamp": self._timestamp} if self._variables: result["changed_variables"] = self._variables if self._error is not None: @@ -55,31 +57,31 @@ class TraceElement: # Context variables for tracing # Current trace -trace_cv: ContextVar[Optional[Dict[str, Deque[TraceElement]]]] = ContextVar( +trace_cv: ContextVar[dict[str, Deque[TraceElement]] | None] = ContextVar( "trace_cv", default=None ) # Stack of TraceElements -trace_stack_cv: ContextVar[Optional[List[TraceElement]]] = ContextVar( +trace_stack_cv: ContextVar[list[TraceElement] | None] = ContextVar( "trace_stack_cv", default=None ) # Current location in config tree -trace_path_stack_cv: ContextVar[Optional[List[str]]] = ContextVar( +trace_path_stack_cv: ContextVar[list[str] | None] = ContextVar( "trace_path_stack_cv", default=None ) # Copy of last variables -variables_cv: ContextVar[Optional[Any]] = ContextVar("variables_cv", default=None) +variables_cv: ContextVar[Any | None] = ContextVar("variables_cv", default=None) # Automation ID + Run ID -trace_id_cv: ContextVar[Optional[Tuple[str, str]]] = ContextVar( +trace_id_cv: ContextVar[tuple[str, str] | None] = ContextVar( "trace_id_cv", default=None ) -def trace_id_set(trace_id: Tuple[str, str]) -> None: +def trace_id_set(trace_id: tuple[str, str]) -> None: """Set id of the current trace.""" trace_id_cv.set(trace_id) -def trace_id_get() -> Optional[Tuple[str, str]]: +def trace_id_get() -> tuple[str, str] | None: """Get id if the current trace.""" return trace_id_cv.get() @@ -99,13 +101,13 @@ def trace_stack_pop(trace_stack_var: ContextVar) -> None: trace_stack.pop() -def trace_stack_top(trace_stack_var: ContextVar) -> Optional[Any]: +def trace_stack_top(trace_stack_var: ContextVar) -> Any | None: """Return the element at the top of a trace stack.""" trace_stack = trace_stack_var.get() return trace_stack[-1] if trace_stack else None -def trace_path_push(suffix: Union[str, List[str]]) -> int: +def trace_path_push(suffix: str | list[str]) -> int: """Go deeper in the config tree.""" if isinstance(suffix, str): suffix = [suffix] @@ -130,7 +132,7 @@ def trace_path_get() -> str: def trace_append_element( trace_element: TraceElement, - maxlen: Optional[int] = None, + maxlen: int | None = None, ) -> None: """Append a TraceElement to trace[path].""" path = trace_element.path @@ -143,7 +145,7 @@ def trace_append_element( trace[path].append(trace_element) -def trace_get(clear: bool = True) -> Optional[Dict[str, Deque[TraceElement]]]: +def trace_get(clear: bool = True) -> dict[str, Deque[TraceElement]] | None: """Return the current trace.""" if clear: trace_clear() @@ -165,7 +167,7 @@ def trace_set_result(**kwargs: Any) -> None: @contextmanager -def trace_path(suffix: Union[str, List[str]]) -> Generator: +def trace_path(suffix: str | list[str]) -> Generator: """Go deeper in the config tree.""" count = trace_path_push(suffix) try: diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index bd229d79111..da7613d86a8 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -1,8 +1,10 @@ """Translation string lookup helpers.""" +from __future__ import annotations + import asyncio from collections import ChainMap import logging -from typing import Any, Dict, List, Optional, Set +from typing import Any from homeassistant.core import callback from homeassistant.loader import ( @@ -24,7 +26,7 @@ TRANSLATION_FLATTEN_CACHE = "translation_flatten_cache" LOCALE_EN = "en" -def recursive_flatten(prefix: Any, data: Dict) -> Dict[str, Any]: +def recursive_flatten(prefix: Any, data: dict) -> dict[str, Any]: """Return a flattened representation of dict data.""" output = {} for key, value in data.items(): @@ -38,7 +40,7 @@ def recursive_flatten(prefix: Any, data: Dict) -> Dict[str, Any]: @callback def component_translation_path( component: str, language: str, integration: Integration -) -> Optional[str]: +) -> str | None: """Return the translation json file location for a component. For component: @@ -69,8 +71,8 @@ def component_translation_path( def load_translations_files( - translation_files: Dict[str, str] -) -> Dict[str, Dict[str, Any]]: + translation_files: dict[str, str] +) -> dict[str, dict[str, Any]]: """Load and parse translation.json files.""" loaded = {} for component, translation_file in translation_files.items(): @@ -90,13 +92,13 @@ def load_translations_files( def _merge_resources( - translation_strings: Dict[str, Dict[str, Any]], - components: Set[str], + translation_strings: dict[str, dict[str, Any]], + components: set[str], category: str, -) -> Dict[str, Dict[str, Any]]: +) -> dict[str, dict[str, Any]]: """Build and merge the resources response for the given components and platforms.""" # Build response - resources: Dict[str, Dict[str, Any]] = {} + resources: dict[str, dict[str, Any]] = {} for component in components: if "." not in component: domain = component @@ -131,10 +133,10 @@ def _merge_resources( def _build_resources( - translation_strings: Dict[str, Dict[str, Any]], - components: Set[str], + translation_strings: dict[str, dict[str, Any]], + components: set[str], category: str, -) -> Dict[str, Dict[str, Any]]: +) -> dict[str, dict[str, Any]]: """Build the resources response for the given components.""" # Build response return { @@ -146,8 +148,8 @@ def _build_resources( async def async_get_component_strings( - hass: HomeAssistantType, language: str, components: Set[str] -) -> Dict[str, Any]: + hass: HomeAssistantType, language: str, components: set[str] +) -> dict[str, Any]: """Load translations.""" domains = list({loaded.split(".")[-1] for loaded in components}) integrations = dict( @@ -160,7 +162,7 @@ async def async_get_component_strings( ) ) - translations: Dict[str, Any] = {} + translations: dict[str, Any] = {} # Determine paths of missing components/platforms files_to_load = {} @@ -205,15 +207,15 @@ class _TranslationCache: def __init__(self, hass: HomeAssistantType) -> None: """Initialize the cache.""" self.hass = hass - self.loaded: Dict[str, Set[str]] = {} - self.cache: Dict[str, Dict[str, Dict[str, Any]]] = {} + self.loaded: dict[str, set[str]] = {} + self.cache: dict[str, dict[str, dict[str, Any]]] = {} async def async_fetch( self, language: str, category: str, - components: Set, - ) -> List[Dict[str, Dict[str, Any]]]: + components: set, + ) -> list[dict[str, dict[str, Any]]]: """Load resources into the cache.""" components_to_load = components - self.loaded.setdefault(language, set()) @@ -224,7 +226,7 @@ class _TranslationCache: return [cached.get(component, {}).get(category, {}) for component in components] - async def _async_load(self, language: str, components: Set) -> None: + async def _async_load(self, language: str, components: set) -> None: """Populate the cache for a given set of components.""" _LOGGER.debug( "Cache miss for %s: %s", @@ -247,12 +249,12 @@ class _TranslationCache: def _build_category_cache( self, language: str, - components: Set, - translation_strings: Dict[str, Dict[str, Any]], + components: set, + translation_strings: dict[str, dict[str, Any]], ) -> None: """Extract resources into the cache.""" cached = self.cache.setdefault(language, {}) - categories: Set[str] = set() + categories: set[str] = set() for resource in translation_strings.values(): categories.update(resource) @@ -263,7 +265,7 @@ class _TranslationCache: new_resources = resource_func(translation_strings, components, category) for component, resource in new_resources.items(): - category_cache: Dict[str, Any] = cached.setdefault( + category_cache: dict[str, Any] = cached.setdefault( component, {} ).setdefault(category, {}) @@ -283,9 +285,9 @@ async def async_get_translations( hass: HomeAssistantType, language: str, category: str, - integration: Optional[str] = None, - config_flow: Optional[bool] = None, -) -> Dict[str, Any]: + integration: str | None = None, + config_flow: bool | None = None, +) -> dict[str, Any]: """Return all backend translations. If integration specified, load it for that one. diff --git a/homeassistant/helpers/trigger.py b/homeassistant/helpers/trigger.py index 58ac71a515e..ef6bf6ca5df 100644 --- a/homeassistant/helpers/trigger.py +++ b/homeassistant/helpers/trigger.py @@ -1,8 +1,10 @@ """Triggers.""" +from __future__ import annotations + import asyncio import logging from types import MappingProxyType -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Any, Callable import voluptuous as vol @@ -39,8 +41,8 @@ async def _async_get_trigger_platform( async def async_validate_trigger_config( - hass: HomeAssistantType, trigger_config: List[ConfigType] -) -> List[ConfigType]: + hass: HomeAssistantType, trigger_config: list[ConfigType] +) -> list[ConfigType]: """Validate triggers.""" config = [] for conf in trigger_config: @@ -55,14 +57,14 @@ async def async_validate_trigger_config( async def async_initialize_triggers( hass: HomeAssistantType, - trigger_config: List[ConfigType], + trigger_config: list[ConfigType], action: Callable, domain: str, name: str, log_cb: Callable, home_assistant_start: bool = False, - variables: Optional[Union[Dict[str, Any], MappingProxyType]] = None, -) -> Optional[CALLBACK_TYPE]: + variables: dict[str, Any] | MappingProxyType | None = None, +) -> CALLBACK_TYPE | None: """Initialize triggers.""" info = { "domain": domain, diff --git a/homeassistant/helpers/update_coordinator.py b/homeassistant/helpers/update_coordinator.py index 8ba355e6489..5b892ea79c4 100644 --- a/homeassistant/helpers/update_coordinator.py +++ b/homeassistant/helpers/update_coordinator.py @@ -1,9 +1,11 @@ """Helpers to help coordinate updates.""" +from __future__ import annotations + import asyncio from datetime import datetime, timedelta import logging from time import monotonic -from typing import Any, Awaitable, Callable, Generic, List, Optional, TypeVar +from typing import Any, Awaitable, Callable, Generic, TypeVar import urllib.error import aiohttp @@ -37,9 +39,9 @@ class DataUpdateCoordinator(Generic[T]): logger: logging.Logger, *, name: str, - update_interval: Optional[timedelta] = None, - update_method: Optional[Callable[[], Awaitable[T]]] = None, - request_refresh_debouncer: Optional[Debouncer] = None, + update_interval: timedelta | None = None, + update_method: Callable[[], Awaitable[T]] | None = None, + request_refresh_debouncer: Debouncer | None = None, ): """Initialize global data updater.""" self.hass = hass @@ -48,12 +50,12 @@ class DataUpdateCoordinator(Generic[T]): self.update_method = update_method self.update_interval = update_interval - self.data: Optional[T] = None + self.data: T | None = None - self._listeners: List[CALLBACK_TYPE] = [] + self._listeners: list[CALLBACK_TYPE] = [] self._job = HassJob(self._handle_refresh_interval) - self._unsub_refresh: Optional[CALLBACK_TYPE] = None - self._request_refresh_task: Optional[asyncio.TimerHandle] = None + self._unsub_refresh: CALLBACK_TYPE | None = None + self._request_refresh_task: asyncio.TimerHandle | None = None self.last_update_success = True if request_refresh_debouncer is None: @@ -132,7 +134,7 @@ class DataUpdateCoordinator(Generic[T]): """ await self._debounced_refresh.async_call() - async def _async_update_data(self) -> Optional[T]: + async def _async_update_data(self) -> T | None: """Fetch the latest data from the source.""" if self.update_method is None: raise NotImplementedError("Update method not implemented") From fabd73f08bdc6be81bdb30b39efa3ea7e91a4768 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 17 Mar 2021 21:46:07 +0100 Subject: [PATCH 430/831] Update typing 03 (#48015) --- homeassistant/auth/__init__.py | 78 +++++++++---------- homeassistant/auth/auth_store.py | 68 ++++++++-------- homeassistant/auth/mfa_modules/__init__.py | 14 ++-- .../auth/mfa_modules/insecure_example.py | 8 +- homeassistant/auth/mfa_modules/notify.py | 42 +++++----- homeassistant/auth/mfa_modules/totp.py | 22 +++--- homeassistant/auth/models.py | 34 ++++---- homeassistant/auth/permissions/__init__.py | 6 +- homeassistant/auth/permissions/entities.py | 12 +-- homeassistant/auth/permissions/merge.py | 14 ++-- homeassistant/auth/permissions/util.py | 10 ++- homeassistant/auth/providers/__init__.py | 42 +++++----- homeassistant/auth/providers/command_line.py | 17 ++-- homeassistant/auth/providers/homeassistant.py | 22 +++--- .../auth/providers/insecure_example.py | 14 ++-- .../auth/providers/legacy_api_password.py | 12 +-- .../auth/providers/trusted_networks.py | 20 ++--- homeassistant/scripts/__init__.py | 8 +- homeassistant/scripts/benchmark/__init__.py | 6 +- homeassistant/scripts/check_config.py | 16 ++-- homeassistant/util/__init__.py | 23 ++---- homeassistant/util/aiohttp.py | 10 ++- homeassistant/util/color.py | 57 +++++++------- homeassistant/util/distance.py | 8 +- homeassistant/util/dt.py | 34 ++++---- homeassistant/util/json.py | 16 ++-- homeassistant/util/location.py | 18 +++-- homeassistant/util/logging.py | 8 +- homeassistant/util/network.py | 11 +-- homeassistant/util/package.py | 11 +-- homeassistant/util/percentage.py | 15 ++-- homeassistant/util/pil.py | 6 +- homeassistant/util/ruamel_yaml.py | 8 +- homeassistant/util/timeout.py | 58 +++++++------- homeassistant/util/unit_system.py | 11 +-- homeassistant/util/yaml/input.py | 11 +-- homeassistant/util/yaml/loader.py | 26 +++---- 37 files changed, 417 insertions(+), 379 deletions(-) diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py index 7d6f94dda85..3830419c537 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -4,7 +4,7 @@ from __future__ import annotations import asyncio from collections import OrderedDict from datetime import timedelta -from typing import Any, Dict, List, Optional, Tuple, cast +from typing import Any, Dict, Optional, Tuple, cast import jwt @@ -36,8 +36,8 @@ class InvalidProvider(Exception): async def auth_manager_from_config( hass: HomeAssistant, - provider_configs: List[Dict[str, Any]], - module_configs: List[Dict[str, Any]], + provider_configs: list[dict[str, Any]], + module_configs: list[dict[str, Any]], ) -> AuthManager: """Initialize an auth manager from config. @@ -87,8 +87,8 @@ class AuthManagerFlowManager(data_entry_flow.FlowManager): self, handler_key: Any, *, - context: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, + context: dict[str, Any] | None = None, + data: dict[str, Any] | None = None, ) -> data_entry_flow.FlowHandler: """Create a login flow.""" auth_provider = self.auth_manager.get_auth_provider(*handler_key) @@ -97,8 +97,8 @@ class AuthManagerFlowManager(data_entry_flow.FlowManager): return await auth_provider.async_login_flow(context) async def async_finish_flow( - self, flow: data_entry_flow.FlowHandler, result: Dict[str, Any] - ) -> Dict[str, Any]: + self, flow: data_entry_flow.FlowHandler, result: dict[str, Any] + ) -> dict[str, Any]: """Return a user as result of login flow.""" flow = cast(LoginFlow, flow) @@ -157,22 +157,22 @@ class AuthManager: self.login_flow = AuthManagerFlowManager(hass, self) @property - def auth_providers(self) -> List[AuthProvider]: + def auth_providers(self) -> list[AuthProvider]: """Return a list of available auth providers.""" return list(self._providers.values()) @property - def auth_mfa_modules(self) -> List[MultiFactorAuthModule]: + def auth_mfa_modules(self) -> list[MultiFactorAuthModule]: """Return a list of available auth modules.""" return list(self._mfa_modules.values()) def get_auth_provider( - self, provider_type: str, provider_id: Optional[str] - ) -> Optional[AuthProvider]: + self, provider_type: str, provider_id: str | None + ) -> AuthProvider | None: """Return an auth provider, None if not found.""" return self._providers.get((provider_type, provider_id)) - def get_auth_providers(self, provider_type: str) -> List[AuthProvider]: + def get_auth_providers(self, provider_type: str) -> list[AuthProvider]: """Return a List of auth provider of one type, Empty if not found.""" return [ provider @@ -180,30 +180,30 @@ class AuthManager: if p_type == provider_type ] - def get_auth_mfa_module(self, module_id: str) -> Optional[MultiFactorAuthModule]: + def get_auth_mfa_module(self, module_id: str) -> MultiFactorAuthModule | None: """Return a multi-factor auth module, None if not found.""" return self._mfa_modules.get(module_id) - async def async_get_users(self) -> List[models.User]: + async def async_get_users(self) -> list[models.User]: """Retrieve all users.""" return await self._store.async_get_users() - async def async_get_user(self, user_id: str) -> Optional[models.User]: + async def async_get_user(self, user_id: str) -> models.User | None: """Retrieve a user.""" return await self._store.async_get_user(user_id) - async def async_get_owner(self) -> Optional[models.User]: + async def async_get_owner(self) -> models.User | None: """Retrieve the owner.""" users = await self.async_get_users() return next((user for user in users if user.is_owner), None) - async def async_get_group(self, group_id: str) -> Optional[models.Group]: + async def async_get_group(self, group_id: str) -> models.Group | None: """Retrieve all groups.""" return await self._store.async_get_group(group_id) async def async_get_user_by_credentials( self, credentials: models.Credentials - ) -> Optional[models.User]: + ) -> models.User | None: """Get a user by credential, return None if not found.""" for user in await self.async_get_users(): for creds in user.credentials: @@ -213,7 +213,7 @@ class AuthManager: return None async def async_create_system_user( - self, name: str, group_ids: Optional[List[str]] = None + self, name: str, group_ids: list[str] | None = None ) -> models.User: """Create a system user.""" user = await self._store.async_create_user( @@ -225,10 +225,10 @@ class AuthManager: return user async def async_create_user( - self, name: str, group_ids: Optional[List[str]] = None + self, name: str, group_ids: list[str] | None = None ) -> models.User: """Create a user.""" - kwargs: Dict[str, Any] = { + kwargs: dict[str, Any] = { "name": name, "is_active": True, "group_ids": group_ids or [], @@ -294,12 +294,12 @@ class AuthManager: async def async_update_user( self, user: models.User, - name: Optional[str] = None, - is_active: Optional[bool] = None, - group_ids: Optional[List[str]] = None, + name: str | None = None, + is_active: bool | None = None, + group_ids: list[str] | None = None, ) -> None: """Update a user.""" - kwargs: Dict[str, Any] = {} + kwargs: dict[str, Any] = {} if name is not None: kwargs["name"] = name if group_ids is not None: @@ -362,9 +362,9 @@ class AuthManager: await module.async_depose_user(user.id) - async def async_get_enabled_mfa(self, user: models.User) -> Dict[str, str]: + async def async_get_enabled_mfa(self, user: models.User) -> dict[str, str]: """List enabled mfa modules for user.""" - modules: Dict[str, str] = OrderedDict() + modules: dict[str, str] = OrderedDict() for module_id, module in self._mfa_modules.items(): if await module.async_is_user_setup(user.id): modules[module_id] = module.name @@ -373,12 +373,12 @@ class AuthManager: async def async_create_refresh_token( self, user: models.User, - client_id: Optional[str] = None, - client_name: Optional[str] = None, - client_icon: Optional[str] = None, - token_type: Optional[str] = None, + client_id: str | None = None, + client_name: str | None = None, + client_icon: str | None = None, + token_type: str | None = None, access_token_expiration: timedelta = ACCESS_TOKEN_EXPIRATION, - credential: Optional[models.Credentials] = None, + credential: models.Credentials | None = None, ) -> models.RefreshToken: """Create a new refresh token for a user.""" if not user.is_active: @@ -432,13 +432,13 @@ class AuthManager: async def async_get_refresh_token( self, token_id: str - ) -> Optional[models.RefreshToken]: + ) -> models.RefreshToken | None: """Get refresh token by id.""" return await self._store.async_get_refresh_token(token_id) async def async_get_refresh_token_by_token( self, token: str - ) -> Optional[models.RefreshToken]: + ) -> models.RefreshToken | None: """Get refresh token by token.""" return await self._store.async_get_refresh_token_by_token(token) @@ -450,7 +450,7 @@ class AuthManager: @callback def async_create_access_token( - self, refresh_token: models.RefreshToken, remote_ip: Optional[str] = None + self, refresh_token: models.RefreshToken, remote_ip: str | None = None ) -> str: """Create a new access token.""" self.async_validate_refresh_token(refresh_token, remote_ip) @@ -471,7 +471,7 @@ class AuthManager: @callback def _async_resolve_provider( self, refresh_token: models.RefreshToken - ) -> Optional[AuthProvider]: + ) -> AuthProvider | None: """Get the auth provider for the given refresh token. Raises an exception if the expected provider is no longer available or return @@ -492,7 +492,7 @@ class AuthManager: @callback def async_validate_refresh_token( - self, refresh_token: models.RefreshToken, remote_ip: Optional[str] = None + self, refresh_token: models.RefreshToken, remote_ip: str | None = None ) -> None: """Validate that a refresh token is usable. @@ -504,7 +504,7 @@ class AuthManager: async def async_validate_access_token( self, token: str - ) -> Optional[models.RefreshToken]: + ) -> models.RefreshToken | None: """Return refresh token if an access token is valid.""" try: unverif_claims = jwt.decode(token, verify=False) @@ -535,7 +535,7 @@ class AuthManager: @callback def _async_get_auth_provider( self, credentials: models.Credentials - ) -> Optional[AuthProvider]: + ) -> AuthProvider | None: """Get auth provider from a set of credentials.""" auth_provider_key = ( credentials.auth_provider_type, diff --git a/homeassistant/auth/auth_store.py b/homeassistant/auth/auth_store.py index 724f1c86722..0b360668ad4 100644 --- a/homeassistant/auth/auth_store.py +++ b/homeassistant/auth/auth_store.py @@ -1,10 +1,12 @@ """Storage for auth models.""" +from __future__ import annotations + import asyncio from collections import OrderedDict from datetime import timedelta import hmac from logging import getLogger -from typing import Any, Dict, List, Optional +from typing import Any from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION from homeassistant.core import HomeAssistant, callback @@ -34,15 +36,15 @@ class AuthStore: def __init__(self, hass: HomeAssistant) -> None: """Initialize the auth store.""" self.hass = hass - self._users: Optional[Dict[str, models.User]] = None - self._groups: Optional[Dict[str, models.Group]] = None - self._perm_lookup: Optional[PermissionLookup] = None + self._users: dict[str, models.User] | None = None + self._groups: dict[str, models.Group] | None = None + self._perm_lookup: PermissionLookup | None = None self._store = hass.helpers.storage.Store( STORAGE_VERSION, STORAGE_KEY, private=True ) self._lock = asyncio.Lock() - async def async_get_groups(self) -> List[models.Group]: + async def async_get_groups(self) -> list[models.Group]: """Retrieve all users.""" if self._groups is None: await self._async_load() @@ -50,7 +52,7 @@ class AuthStore: return list(self._groups.values()) - async def async_get_group(self, group_id: str) -> Optional[models.Group]: + async def async_get_group(self, group_id: str) -> models.Group | None: """Retrieve all users.""" if self._groups is None: await self._async_load() @@ -58,7 +60,7 @@ class AuthStore: return self._groups.get(group_id) - async def async_get_users(self) -> List[models.User]: + async def async_get_users(self) -> list[models.User]: """Retrieve all users.""" if self._users is None: await self._async_load() @@ -66,7 +68,7 @@ class AuthStore: return list(self._users.values()) - async def async_get_user(self, user_id: str) -> Optional[models.User]: + async def async_get_user(self, user_id: str) -> models.User | None: """Retrieve a user by id.""" if self._users is None: await self._async_load() @@ -76,12 +78,12 @@ class AuthStore: async def async_create_user( self, - name: Optional[str], - is_owner: Optional[bool] = None, - is_active: Optional[bool] = None, - system_generated: Optional[bool] = None, - credentials: Optional[models.Credentials] = None, - group_ids: Optional[List[str]] = None, + name: str | None, + is_owner: bool | None = None, + is_active: bool | None = None, + system_generated: bool | None = None, + credentials: models.Credentials | None = None, + group_ids: list[str] | None = None, ) -> models.User: """Create a new user.""" if self._users is None: @@ -97,7 +99,7 @@ class AuthStore: raise ValueError(f"Invalid group specified {group_id}") groups.append(group) - kwargs: Dict[str, Any] = { + kwargs: dict[str, Any] = { "name": name, # Until we get group management, we just put everyone in the # same group. @@ -146,9 +148,9 @@ class AuthStore: async def async_update_user( self, user: models.User, - name: Optional[str] = None, - is_active: Optional[bool] = None, - group_ids: Optional[List[str]] = None, + name: str | None = None, + is_active: bool | None = None, + group_ids: list[str] | None = None, ) -> None: """Update a user.""" assert self._groups is not None @@ -203,15 +205,15 @@ class AuthStore: async def async_create_refresh_token( self, user: models.User, - client_id: Optional[str] = None, - client_name: Optional[str] = None, - client_icon: Optional[str] = None, + client_id: str | None = None, + client_name: str | None = None, + client_icon: str | None = None, token_type: str = models.TOKEN_TYPE_NORMAL, access_token_expiration: timedelta = ACCESS_TOKEN_EXPIRATION, - credential: Optional[models.Credentials] = None, + credential: models.Credentials | None = None, ) -> models.RefreshToken: """Create a new token for a user.""" - kwargs: Dict[str, Any] = { + kwargs: dict[str, Any] = { "user": user, "client_id": client_id, "token_type": token_type, @@ -244,7 +246,7 @@ class AuthStore: async def async_get_refresh_token( self, token_id: str - ) -> Optional[models.RefreshToken]: + ) -> models.RefreshToken | None: """Get refresh token by id.""" if self._users is None: await self._async_load() @@ -259,7 +261,7 @@ class AuthStore: async def async_get_refresh_token_by_token( self, token: str - ) -> Optional[models.RefreshToken]: + ) -> models.RefreshToken | None: """Get refresh token by token.""" if self._users is None: await self._async_load() @@ -276,7 +278,7 @@ class AuthStore: @callback def async_log_refresh_token_usage( - self, refresh_token: models.RefreshToken, remote_ip: Optional[str] = None + self, refresh_token: models.RefreshToken, remote_ip: str | None = None ) -> None: """Update refresh token last used information.""" refresh_token.last_used_at = dt_util.utcnow() @@ -309,9 +311,9 @@ class AuthStore: self._set_defaults() return - users: Dict[str, models.User] = OrderedDict() - groups: Dict[str, models.Group] = OrderedDict() - credentials: Dict[str, models.Credentials] = OrderedDict() + users: dict[str, models.User] = OrderedDict() + groups: dict[str, models.Group] = OrderedDict() + credentials: dict[str, models.Credentials] = OrderedDict() # Soft-migrating data as we load. We are going to make sure we have a # read only group and an admin group. There are two states that we can @@ -328,7 +330,7 @@ class AuthStore: # was added. for group_dict in data.get("groups", []): - policy: Optional[PolicyType] = None + policy: PolicyType | None = None if group_dict["id"] == GROUP_ID_ADMIN: has_admin_group = True @@ -489,7 +491,7 @@ class AuthStore: self._store.async_delay_save(self._data_to_save, 1) @callback - def _data_to_save(self) -> Dict: + def _data_to_save(self) -> dict: """Return the data to store.""" assert self._users is not None assert self._groups is not None @@ -508,7 +510,7 @@ class AuthStore: groups = [] for group in self._groups.values(): - g_dict: Dict[str, Any] = { + g_dict: dict[str, Any] = { "id": group.id, # Name not read for sys groups. Kept here for backwards compat "name": group.name, @@ -567,7 +569,7 @@ class AuthStore: """Set default values for auth store.""" self._users = OrderedDict() - groups: Dict[str, models.Group] = OrderedDict() + groups: dict[str, models.Group] = OrderedDict() admin_group = _system_admin_group() groups[admin_group.id] = admin_group user_group = _system_user_group() diff --git a/homeassistant/auth/mfa_modules/__init__.py b/homeassistant/auth/mfa_modules/__init__.py index f29f5f8fcc2..d6989b6416f 100644 --- a/homeassistant/auth/mfa_modules/__init__.py +++ b/homeassistant/auth/mfa_modules/__init__.py @@ -4,7 +4,7 @@ from __future__ import annotations import importlib import logging import types -from typing import Any, Dict, Optional +from typing import Any import voluptuous as vol from voluptuous.humanize import humanize_error @@ -38,7 +38,7 @@ class MultiFactorAuthModule: DEFAULT_TITLE = "Unnamed auth module" MAX_RETRY_TIME = 3 - def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: + def __init__(self, hass: HomeAssistant, config: dict[str, Any]) -> None: """Initialize an auth module.""" self.hass = hass self.config = config @@ -87,7 +87,7 @@ class MultiFactorAuthModule: """Return whether user is setup.""" raise NotImplementedError - async def async_validate(self, user_id: str, user_input: Dict[str, Any]) -> bool: + async def async_validate(self, user_id: str, user_input: dict[str, Any]) -> bool: """Return True if validation passed.""" raise NotImplementedError @@ -104,14 +104,14 @@ class SetupFlow(data_entry_flow.FlowHandler): self._user_id = user_id async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the first step of setup flow. Return self.async_show_form(step_id='init') if user_input is None. Return self.async_create_entry(data={'result': result}) if finish. """ - errors: Dict[str, str] = {} + errors: dict[str, str] = {} if user_input: result = await self._auth_module.async_setup_user(self._user_id, user_input) @@ -125,7 +125,7 @@ class SetupFlow(data_entry_flow.FlowHandler): async def auth_mfa_module_from_config( - hass: HomeAssistant, config: Dict[str, Any] + hass: HomeAssistant, config: dict[str, Any] ) -> MultiFactorAuthModule: """Initialize an auth module from a config.""" module_name = config[CONF_TYPE] diff --git a/homeassistant/auth/mfa_modules/insecure_example.py b/homeassistant/auth/mfa_modules/insecure_example.py index ddceeaae826..c25f70ca31b 100644 --- a/homeassistant/auth/mfa_modules/insecure_example.py +++ b/homeassistant/auth/mfa_modules/insecure_example.py @@ -1,5 +1,7 @@ """Example auth module.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any import voluptuous as vol @@ -28,7 +30,7 @@ class InsecureExampleModule(MultiFactorAuthModule): DEFAULT_TITLE = "Insecure Personal Identify Number" - def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: + def __init__(self, hass: HomeAssistant, config: dict[str, Any]) -> None: """Initialize the user data store.""" super().__init__(hass, config) self._data = config["data"] @@ -80,7 +82,7 @@ class InsecureExampleModule(MultiFactorAuthModule): return True return False - async def async_validate(self, user_id: str, user_input: Dict[str, Any]) -> bool: + async def async_validate(self, user_id: str, user_input: dict[str, Any]) -> bool: """Return True if validation passed.""" for data in self._data: if data["user_id"] == user_id: diff --git a/homeassistant/auth/mfa_modules/notify.py b/homeassistant/auth/mfa_modules/notify.py index c4e5800821e..76a5676d562 100644 --- a/homeassistant/auth/mfa_modules/notify.py +++ b/homeassistant/auth/mfa_modules/notify.py @@ -2,10 +2,12 @@ Sending HOTP through notify service """ +from __future__ import annotations + import asyncio from collections import OrderedDict import logging -from typing import Any, Dict, List, Optional +from typing import Any, Dict import attr import voluptuous as vol @@ -79,8 +81,8 @@ class NotifySetting: secret: str = attr.ib(factory=_generate_secret) # not persistent counter: int = attr.ib(factory=_generate_random) # not persistent - notify_service: Optional[str] = attr.ib(default=None) - target: Optional[str] = attr.ib(default=None) + notify_service: str | None = attr.ib(default=None) + target: str | None = attr.ib(default=None) _UsersDict = Dict[str, NotifySetting] @@ -92,10 +94,10 @@ class NotifyAuthModule(MultiFactorAuthModule): DEFAULT_TITLE = "Notify One-Time Password" - def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: + def __init__(self, hass: HomeAssistant, config: dict[str, Any]) -> None: """Initialize the user data store.""" super().__init__(hass, config) - self._user_settings: Optional[_UsersDict] = None + self._user_settings: _UsersDict | None = None self._user_store = hass.helpers.storage.Store( STORAGE_VERSION, STORAGE_KEY, private=True ) @@ -146,7 +148,7 @@ class NotifyAuthModule(MultiFactorAuthModule): ) @callback - def aync_get_available_notify_services(self) -> List[str]: + def aync_get_available_notify_services(self) -> list[str]: """Return list of notify services.""" unordered_services = set() @@ -198,7 +200,7 @@ class NotifyAuthModule(MultiFactorAuthModule): return user_id in self._user_settings - async def async_validate(self, user_id: str, user_input: Dict[str, Any]) -> bool: + async def async_validate(self, user_id: str, user_input: dict[str, Any]) -> bool: """Return True if validation passed.""" if self._user_settings is None: await self._async_load() @@ -258,7 +260,7 @@ class NotifyAuthModule(MultiFactorAuthModule): ) async def async_notify( - self, code: str, notify_service: str, target: Optional[str] = None + self, code: str, notify_service: str, target: str | None = None ) -> None: """Send code by notify service.""" data = {"message": self._message_template.format(code)} @@ -276,23 +278,23 @@ class NotifySetupFlow(SetupFlow): auth_module: NotifyAuthModule, setup_schema: vol.Schema, user_id: str, - available_notify_services: List[str], + available_notify_services: list[str], ) -> None: """Initialize the setup flow.""" super().__init__(auth_module, setup_schema, user_id) # to fix typing complaint self._auth_module: NotifyAuthModule = auth_module self._available_notify_services = available_notify_services - self._secret: Optional[str] = None - self._count: Optional[int] = None - self._notify_service: Optional[str] = None - self._target: Optional[str] = None + self._secret: str | None = None + self._count: int | None = None + self._notify_service: str | None = None + self._target: str | None = None async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Let user select available notify services.""" - errors: Dict[str, str] = {} + errors: dict[str, str] = {} hass = self._auth_module.hass if user_input: @@ -306,7 +308,7 @@ class NotifySetupFlow(SetupFlow): if not self._available_notify_services: return self.async_abort(reason="no_available_service") - schema: Dict[str, Any] = OrderedDict() + schema: dict[str, Any] = OrderedDict() schema["notify_service"] = vol.In(self._available_notify_services) schema["target"] = vol.Optional(str) @@ -315,10 +317,10 @@ class NotifySetupFlow(SetupFlow): ) async def async_step_setup( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Verify user can receive one-time password.""" - errors: Dict[str, str] = {} + errors: dict[str, str] = {} hass = self._auth_module.hass if user_input: diff --git a/homeassistant/auth/mfa_modules/totp.py b/homeassistant/auth/mfa_modules/totp.py index 4a6faef96c0..d20c8465546 100644 --- a/homeassistant/auth/mfa_modules/totp.py +++ b/homeassistant/auth/mfa_modules/totp.py @@ -1,7 +1,9 @@ """Time-based One Time Password auth module.""" +from __future__ import annotations + import asyncio from io import BytesIO -from typing import Any, Dict, Optional, Tuple +from typing import Any import voluptuous as vol @@ -50,7 +52,7 @@ def _generate_qr_code(data: str) -> str: ) -def _generate_secret_and_qr_code(username: str) -> Tuple[str, str, str]: +def _generate_secret_and_qr_code(username: str) -> tuple[str, str, str]: """Generate a secret, url, and QR code.""" import pyotp # pylint: disable=import-outside-toplevel @@ -69,10 +71,10 @@ class TotpAuthModule(MultiFactorAuthModule): DEFAULT_TITLE = "Time-based One Time Password" MAX_RETRY_TIME = 5 - def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: + def __init__(self, hass: HomeAssistant, config: dict[str, Any]) -> None: """Initialize the user data store.""" super().__init__(hass, config) - self._users: Optional[Dict[str, str]] = None + self._users: dict[str, str] | None = None self._user_store = hass.helpers.storage.Store( STORAGE_VERSION, STORAGE_KEY, private=True ) @@ -100,7 +102,7 @@ class TotpAuthModule(MultiFactorAuthModule): """Save data.""" await self._user_store.async_save({STORAGE_USERS: self._users}) - def _add_ota_secret(self, user_id: str, secret: Optional[str] = None) -> str: + def _add_ota_secret(self, user_id: str, secret: str | None = None) -> str: """Create a ota_secret for user.""" import pyotp # pylint: disable=import-outside-toplevel @@ -145,7 +147,7 @@ class TotpAuthModule(MultiFactorAuthModule): return user_id in self._users # type: ignore - async def async_validate(self, user_id: str, user_input: Dict[str, Any]) -> bool: + async def async_validate(self, user_id: str, user_input: dict[str, Any]) -> bool: """Return True if validation passed.""" if self._users is None: await self._async_load() @@ -181,13 +183,13 @@ class TotpSetupFlow(SetupFlow): # to fix typing complaint self._auth_module: TotpAuthModule = auth_module self._user = user - self._ota_secret: Optional[str] = None + self._ota_secret: str | None = None self._url = None # type Optional[str] self._image = None # type Optional[str] async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the first step of setup flow. Return self.async_show_form(step_id='init') if user_input is None. @@ -195,7 +197,7 @@ class TotpSetupFlow(SetupFlow): """ import pyotp # pylint: disable=import-outside-toplevel - errors: Dict[str, str] = {} + errors: dict[str, str] = {} if user_input: verified = await self.hass.async_add_executor_job( diff --git a/homeassistant/auth/models.py b/homeassistant/auth/models.py index 4cc67b2ebd4..a8a70b8fc3b 100644 --- a/homeassistant/auth/models.py +++ b/homeassistant/auth/models.py @@ -1,7 +1,9 @@ """Auth models.""" +from __future__ import annotations + from datetime import datetime, timedelta import secrets -from typing import Dict, List, NamedTuple, Optional +from typing import NamedTuple import uuid import attr @@ -21,7 +23,7 @@ TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN = "long_lived_access_token" class Group: """A group.""" - name: Optional[str] = attr.ib() + name: str | None = attr.ib() policy: perm_mdl.PolicyType = attr.ib() id: str = attr.ib(factory=lambda: uuid.uuid4().hex) system_generated: bool = attr.ib(default=False) @@ -31,24 +33,24 @@ class Group: class User: """A user.""" - name: Optional[str] = attr.ib() + name: str | None = attr.ib() perm_lookup: perm_mdl.PermissionLookup = attr.ib(eq=False, order=False) id: str = attr.ib(factory=lambda: uuid.uuid4().hex) is_owner: bool = attr.ib(default=False) is_active: bool = attr.ib(default=False) system_generated: bool = attr.ib(default=False) - groups: List[Group] = attr.ib(factory=list, eq=False, order=False) + groups: list[Group] = attr.ib(factory=list, eq=False, order=False) # List of credentials of a user. - credentials: List["Credentials"] = attr.ib(factory=list, eq=False, order=False) + credentials: list["Credentials"] = attr.ib(factory=list, eq=False, order=False) # Tokens associated with a user. - refresh_tokens: Dict[str, "RefreshToken"] = attr.ib( + refresh_tokens: dict[str, "RefreshToken"] = attr.ib( factory=dict, eq=False, order=False ) - _permissions: Optional[perm_mdl.PolicyPermissions] = attr.ib( + _permissions: perm_mdl.PolicyPermissions | None = attr.ib( init=False, eq=False, order=False, @@ -89,10 +91,10 @@ class RefreshToken: """RefreshToken for a user to grant new access tokens.""" user: User = attr.ib() - client_id: Optional[str] = attr.ib() + client_id: str | None = attr.ib() access_token_expiration: timedelta = attr.ib() - client_name: Optional[str] = attr.ib(default=None) - client_icon: Optional[str] = attr.ib(default=None) + client_name: str | None = attr.ib(default=None) + client_icon: str | None = attr.ib(default=None) token_type: str = attr.ib( default=TOKEN_TYPE_NORMAL, validator=attr.validators.in_( @@ -104,12 +106,12 @@ class RefreshToken: token: str = attr.ib(factory=lambda: secrets.token_hex(64)) jwt_key: str = attr.ib(factory=lambda: secrets.token_hex(64)) - last_used_at: Optional[datetime] = attr.ib(default=None) - last_used_ip: Optional[str] = attr.ib(default=None) + last_used_at: datetime | None = attr.ib(default=None) + last_used_ip: str | None = attr.ib(default=None) - credential: Optional["Credentials"] = attr.ib(default=None) + credential: "Credentials" | None = attr.ib(default=None) - version: Optional[str] = attr.ib(default=__version__) + version: str | None = attr.ib(default=__version__) @attr.s(slots=True) @@ -117,7 +119,7 @@ class Credentials: """Credentials for a user on an auth provider.""" auth_provider_type: str = attr.ib() - auth_provider_id: Optional[str] = attr.ib() + auth_provider_id: str | None = attr.ib() # Allow the auth provider to store data to represent their auth. data: dict = attr.ib() @@ -129,5 +131,5 @@ class Credentials: class UserMeta(NamedTuple): """User metadata.""" - name: Optional[str] + name: str | None is_active: bool diff --git a/homeassistant/auth/permissions/__init__.py b/homeassistant/auth/permissions/__init__.py index 2f887d21b02..28ff3f638d4 100644 --- a/homeassistant/auth/permissions/__init__.py +++ b/homeassistant/auth/permissions/__init__.py @@ -1,6 +1,8 @@ """Permissions for Home Assistant.""" +from __future__ import annotations + import logging -from typing import Any, Callable, Optional +from typing import Any, Callable import voluptuous as vol @@ -19,7 +21,7 @@ _LOGGER = logging.getLogger(__name__) class AbstractPermissions: """Default permissions class.""" - _cached_entity_func: Optional[Callable[[str, str], bool]] = None + _cached_entity_func: Callable[[str, str], bool] | None = None def _entity_func(self) -> Callable[[str, str], bool]: """Return a function that can test entity access.""" diff --git a/homeassistant/auth/permissions/entities.py b/homeassistant/auth/permissions/entities.py index be30c7bf69a..f19590a6349 100644 --- a/homeassistant/auth/permissions/entities.py +++ b/homeassistant/auth/permissions/entities.py @@ -1,6 +1,8 @@ """Entity permissions.""" +from __future__ import annotations + from collections import OrderedDict -from typing import Callable, Optional +from typing import Callable import voluptuous as vol @@ -43,14 +45,14 @@ ENTITY_POLICY_SCHEMA = vol.Any( def _lookup_domain( perm_lookup: PermissionLookup, domains_dict: SubCategoryDict, entity_id: str -) -> Optional[ValueType]: +) -> ValueType | None: """Look up entity permissions by domain.""" return domains_dict.get(entity_id.split(".", 1)[0]) def _lookup_area( perm_lookup: PermissionLookup, area_dict: SubCategoryDict, entity_id: str -) -> Optional[ValueType]: +) -> ValueType | None: """Look up entity permissions by area.""" entity_entry = perm_lookup.entity_registry.async_get(entity_id) @@ -67,7 +69,7 @@ def _lookup_area( def _lookup_device( perm_lookup: PermissionLookup, devices_dict: SubCategoryDict, entity_id: str -) -> Optional[ValueType]: +) -> ValueType | None: """Look up entity permissions by device.""" entity_entry = perm_lookup.entity_registry.async_get(entity_id) @@ -79,7 +81,7 @@ def _lookup_device( def _lookup_entity_id( perm_lookup: PermissionLookup, entities_dict: SubCategoryDict, entity_id: str -) -> Optional[ValueType]: +) -> ValueType | None: """Look up entity permission by entity id.""" return entities_dict.get(entity_id) diff --git a/homeassistant/auth/permissions/merge.py b/homeassistant/auth/permissions/merge.py index fad98b3f22a..121d87f7848 100644 --- a/homeassistant/auth/permissions/merge.py +++ b/homeassistant/auth/permissions/merge.py @@ -1,13 +1,15 @@ """Merging of policies.""" -from typing import Dict, List, Set, cast +from __future__ import annotations + +from typing import cast from .types import CategoryType, PolicyType -def merge_policies(policies: List[PolicyType]) -> PolicyType: +def merge_policies(policies: list[PolicyType]) -> PolicyType: """Merge policies.""" - new_policy: Dict[str, CategoryType] = {} - seen: Set[str] = set() + new_policy: dict[str, CategoryType] = {} + seen: set[str] = set() for policy in policies: for category in policy: if category in seen: @@ -20,7 +22,7 @@ def merge_policies(policies: List[PolicyType]) -> PolicyType: return new_policy -def _merge_policies(sources: List[CategoryType]) -> CategoryType: +def _merge_policies(sources: list[CategoryType]) -> CategoryType: """Merge a policy.""" # When merging policies, the most permissive wins. # This means we order it like this: @@ -34,7 +36,7 @@ def _merge_policies(sources: List[CategoryType]) -> CategoryType: # merge each key in the source. policy: CategoryType = None - seen: Set[str] = set() + seen: set[str] = set() for source in sources: if source is None: continue diff --git a/homeassistant/auth/permissions/util.py b/homeassistant/auth/permissions/util.py index 11bbd878eb2..e95e0080b50 100644 --- a/homeassistant/auth/permissions/util.py +++ b/homeassistant/auth/permissions/util.py @@ -1,6 +1,8 @@ """Helpers to deal with permissions.""" +from __future__ import annotations + from functools import wraps -from typing import Callable, Dict, List, Optional, cast +from typing import Callable, Dict, Optional, cast from .const import SUBCAT_ALL from .models import PermissionLookup @@ -45,7 +47,7 @@ def compile_policy( assert isinstance(policy, dict) - funcs: List[Callable[[str, str], Optional[bool]]] = [] + funcs: list[Callable[[str, str], bool | None]] = [] for key, lookup_func in subcategories.items(): lookup_value = policy.get(key) @@ -80,10 +82,10 @@ def compile_policy( def _gen_dict_test_func( perm_lookup: PermissionLookup, lookup_func: LookupFunc, lookup_dict: SubCategoryDict -) -> Callable[[str, str], Optional[bool]]: +) -> Callable[[str, str], bool | None]: """Generate a lookup function.""" - def test_value(object_id: str, key: str) -> Optional[bool]: + def test_value(object_id: str, key: str) -> bool | None: """Test if permission is allowed based on the keys.""" schema: ValueType = lookup_func(perm_lookup, lookup_dict, object_id) diff --git a/homeassistant/auth/providers/__init__.py b/homeassistant/auth/providers/__init__.py index 2afe1333c6a..6e188be1ffc 100644 --- a/homeassistant/auth/providers/__init__.py +++ b/homeassistant/auth/providers/__init__.py @@ -4,7 +4,7 @@ from __future__ import annotations import importlib import logging import types -from typing import Any, Dict, List, Optional +from typing import Any import voluptuous as vol from voluptuous.humanize import humanize_error @@ -42,7 +42,7 @@ class AuthProvider: DEFAULT_TITLE = "Unnamed auth provider" def __init__( - self, hass: HomeAssistant, store: AuthStore, config: Dict[str, Any] + self, hass: HomeAssistant, store: AuthStore, config: dict[str, Any] ) -> None: """Initialize an auth provider.""" self.hass = hass @@ -50,7 +50,7 @@ class AuthProvider: self.config = config @property - def id(self) -> Optional[str]: + def id(self) -> str | None: """Return id of the auth provider. Optional, can be None. @@ -72,7 +72,7 @@ class AuthProvider: """Return whether multi-factor auth supported by the auth provider.""" return True - async def async_credentials(self) -> List[Credentials]: + async def async_credentials(self) -> list[Credentials]: """Return all credentials of this provider.""" users = await self.store.async_get_users() return [ @@ -86,7 +86,7 @@ class AuthProvider: ] @callback - def async_create_credentials(self, data: Dict[str, str]) -> Credentials: + def async_create_credentials(self, data: dict[str, str]) -> Credentials: """Create credentials.""" return Credentials( auth_provider_type=self.type, auth_provider_id=self.id, data=data @@ -94,7 +94,7 @@ class AuthProvider: # Implement by extending class - async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: + async def async_login_flow(self, context: dict | None) -> LoginFlow: """Return the data flow for logging in with auth provider. Auth provider should extend LoginFlow and return an instance. @@ -102,7 +102,7 @@ class AuthProvider: raise NotImplementedError async def async_get_or_create_credentials( - self, flow_result: Dict[str, str] + self, flow_result: dict[str, str] ) -> Credentials: """Get credentials based on the flow result.""" raise NotImplementedError @@ -121,7 +121,7 @@ class AuthProvider: @callback def async_validate_refresh_token( - self, refresh_token: RefreshToken, remote_ip: Optional[str] = None + self, refresh_token: RefreshToken, remote_ip: str | None = None ) -> None: """Verify a refresh token is still valid. @@ -131,7 +131,7 @@ class AuthProvider: async def auth_provider_from_config( - hass: HomeAssistant, store: AuthStore, config: Dict[str, Any] + hass: HomeAssistant, store: AuthStore, config: dict[str, Any] ) -> AuthProvider: """Initialize an auth provider from a config.""" provider_name = config[CONF_TYPE] @@ -188,17 +188,17 @@ class LoginFlow(data_entry_flow.FlowHandler): def __init__(self, auth_provider: AuthProvider) -> None: """Initialize the login flow.""" self._auth_provider = auth_provider - self._auth_module_id: Optional[str] = None + self._auth_module_id: str | None = None self._auth_manager = auth_provider.hass.auth - self.available_mfa_modules: Dict[str, str] = {} + self.available_mfa_modules: dict[str, str] = {} self.created_at = dt_util.utcnow() self.invalid_mfa_times = 0 - self.user: Optional[User] = None - self.credential: Optional[Credentials] = None + self.user: User | None = None + self.credential: Credentials | None = None async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the first step of login flow. Return self.async_show_form(step_id='init') if user_input is None. @@ -207,8 +207,8 @@ class LoginFlow(data_entry_flow.FlowHandler): raise NotImplementedError async def async_step_select_mfa_module( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the step of select mfa module.""" errors = {} @@ -232,8 +232,8 @@ class LoginFlow(data_entry_flow.FlowHandler): ) async def async_step_mfa( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the step of mfa validation.""" assert self.credential assert self.user @@ -273,7 +273,7 @@ class LoginFlow(data_entry_flow.FlowHandler): if not errors: return await self.async_finish(self.credential) - description_placeholders: Dict[str, Optional[str]] = { + description_placeholders: dict[str, str | None] = { "mfa_module_name": auth_module.name, "mfa_module_id": auth_module.id, } @@ -285,6 +285,6 @@ class LoginFlow(data_entry_flow.FlowHandler): errors=errors, ) - async def async_finish(self, flow_result: Any) -> Dict: + async def async_finish(self, flow_result: Any) -> dict: """Handle the pass of login flow.""" return self.async_create_entry(title=self._auth_provider.name, data=flow_result) diff --git a/homeassistant/auth/providers/command_line.py b/homeassistant/auth/providers/command_line.py index b2b19221979..47a56d87097 100644 --- a/homeassistant/auth/providers/command_line.py +++ b/homeassistant/auth/providers/command_line.py @@ -1,10 +1,11 @@ """Auth provider that validates credentials via an external command.""" +from __future__ import annotations import asyncio.subprocess import collections import logging import os -from typing import Any, Dict, Optional, cast +from typing import Any, cast import voluptuous as vol @@ -51,9 +52,9 @@ class CommandLineAuthProvider(AuthProvider): attributes provided by external programs. """ super().__init__(*args, **kwargs) - self._user_meta: Dict[str, Dict[str, Any]] = {} + self._user_meta: dict[str, dict[str, Any]] = {} - async def async_login_flow(self, context: Optional[dict]) -> LoginFlow: + async def async_login_flow(self, context: dict | None) -> LoginFlow: """Return a flow to login.""" return CommandLineLoginFlow(self) @@ -82,7 +83,7 @@ class CommandLineAuthProvider(AuthProvider): raise InvalidAuthError if self.config[CONF_META]: - meta: Dict[str, str] = {} + meta: dict[str, str] = {} for _line in stdout.splitlines(): try: line = _line.decode().lstrip() @@ -99,7 +100,7 @@ class CommandLineAuthProvider(AuthProvider): self._user_meta[username] = meta async def async_get_or_create_credentials( - self, flow_result: Dict[str, str] + self, flow_result: dict[str, str] ) -> Credentials: """Get credentials based on the flow result.""" username = flow_result["username"] @@ -125,8 +126,8 @@ class CommandLineLoginFlow(LoginFlow): """Handler for the login flow.""" async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the step of the form.""" errors = {} @@ -143,7 +144,7 @@ class CommandLineLoginFlow(LoginFlow): user_input.pop("password") return await self.async_finish(user_input) - schema: Dict[str, type] = collections.OrderedDict() + schema: dict[str, type] = collections.OrderedDict() schema["username"] = str schema["password"] = str diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py index c66ffa7332e..54d82013a75 100644 --- a/homeassistant/auth/providers/homeassistant.py +++ b/homeassistant/auth/providers/homeassistant.py @@ -5,7 +5,7 @@ import asyncio import base64 from collections import OrderedDict import logging -from typing import Any, Dict, List, Optional, Set, cast +from typing import Any, cast import bcrypt import voluptuous as vol @@ -21,7 +21,7 @@ STORAGE_VERSION = 1 STORAGE_KEY = "auth_provider.homeassistant" -def _disallow_id(conf: Dict[str, Any]) -> Dict[str, Any]: +def _disallow_id(conf: dict[str, Any]) -> dict[str, Any]: """Disallow ID in config.""" if CONF_ID in conf: raise vol.Invalid("ID is not allowed for the homeassistant auth provider.") @@ -62,7 +62,7 @@ class Data: self._store = hass.helpers.storage.Store( STORAGE_VERSION, STORAGE_KEY, private=True ) - self._data: Optional[Dict[str, Any]] = None + self._data: dict[str, Any] | None = None # Legacy mode will allow usernames to start/end with whitespace # and will compare usernames case-insensitive. # Remove in 2020 or when we launch 1.0. @@ -83,7 +83,7 @@ class Data: if data is None: data = {"users": []} - seen: Set[str] = set() + seen: set[str] = set() for user in data["users"]: username = user["username"] @@ -121,7 +121,7 @@ class Data: self._data = data @property - def users(self) -> List[Dict[str, str]]: + def users(self) -> list[dict[str, str]]: """Return users.""" return self._data["users"] # type: ignore @@ -220,7 +220,7 @@ class HassAuthProvider(AuthProvider): def __init__(self, *args: Any, **kwargs: Any) -> None: """Initialize an Home Assistant auth provider.""" super().__init__(*args, **kwargs) - self.data: Optional[Data] = None + self.data: Data | None = None self._init_lock = asyncio.Lock() async def async_initialize(self) -> None: @@ -233,7 +233,7 @@ class HassAuthProvider(AuthProvider): await data.async_load() self.data = data - async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: + async def async_login_flow(self, context: dict | None) -> LoginFlow: """Return a flow to login.""" return HassLoginFlow(self) @@ -277,7 +277,7 @@ class HassAuthProvider(AuthProvider): await self.data.async_save() async def async_get_or_create_credentials( - self, flow_result: Dict[str, str] + self, flow_result: dict[str, str] ) -> Credentials: """Get credentials based on the flow result.""" if self.data is None: @@ -318,8 +318,8 @@ class HassLoginFlow(LoginFlow): """Handler for the login flow.""" async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the step of the form.""" errors = {} @@ -335,7 +335,7 @@ class HassLoginFlow(LoginFlow): user_input.pop("password") return await self.async_finish(user_input) - schema: Dict[str, type] = OrderedDict() + schema: dict[str, type] = OrderedDict() schema["username"] = str schema["password"] = str diff --git a/homeassistant/auth/providers/insecure_example.py b/homeassistant/auth/providers/insecure_example.py index 70014a236cd..c938a6fac81 100644 --- a/homeassistant/auth/providers/insecure_example.py +++ b/homeassistant/auth/providers/insecure_example.py @@ -1,7 +1,9 @@ """Example auth provider.""" +from __future__ import annotations + from collections import OrderedDict import hmac -from typing import Any, Dict, Optional, cast +from typing import Any, cast import voluptuous as vol @@ -33,7 +35,7 @@ class InvalidAuthError(HomeAssistantError): class ExampleAuthProvider(AuthProvider): """Example auth provider based on hardcoded usernames and passwords.""" - async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: + async def async_login_flow(self, context: dict | None) -> LoginFlow: """Return a flow to login.""" return ExampleLoginFlow(self) @@ -60,7 +62,7 @@ class ExampleAuthProvider(AuthProvider): raise InvalidAuthError async def async_get_or_create_credentials( - self, flow_result: Dict[str, str] + self, flow_result: dict[str, str] ) -> Credentials: """Get credentials based on the flow result.""" username = flow_result["username"] @@ -94,8 +96,8 @@ class ExampleLoginFlow(LoginFlow): """Handler for the login flow.""" async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the step of the form.""" errors = {} @@ -111,7 +113,7 @@ class ExampleLoginFlow(LoginFlow): user_input.pop("password") return await self.async_finish(user_input) - schema: Dict[str, type] = OrderedDict() + schema: dict[str, type] = OrderedDict() schema["username"] = str schema["password"] = str diff --git a/homeassistant/auth/providers/legacy_api_password.py b/homeassistant/auth/providers/legacy_api_password.py index ba96fa285f1..522751c70d6 100644 --- a/homeassistant/auth/providers/legacy_api_password.py +++ b/homeassistant/auth/providers/legacy_api_password.py @@ -3,8 +3,10 @@ Support Legacy API password auth provider. It will be removed when auth system production ready """ +from __future__ import annotations + import hmac -from typing import Any, Dict, Optional, cast +from typing import Any, cast import voluptuous as vol @@ -40,7 +42,7 @@ class LegacyApiPasswordAuthProvider(AuthProvider): """Return api_password.""" return str(self.config[CONF_API_PASSWORD]) - async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: + async def async_login_flow(self, context: dict | None) -> LoginFlow: """Return a flow to login.""" return LegacyLoginFlow(self) @@ -55,7 +57,7 @@ class LegacyApiPasswordAuthProvider(AuthProvider): raise InvalidAuthError async def async_get_or_create_credentials( - self, flow_result: Dict[str, str] + self, flow_result: dict[str, str] ) -> Credentials: """Return credentials for this login.""" credentials = await self.async_credentials() @@ -79,8 +81,8 @@ class LegacyLoginFlow(LoginFlow): """Handler for the login flow.""" async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the step of the form.""" errors = {} diff --git a/homeassistant/auth/providers/trusted_networks.py b/homeassistant/auth/providers/trusted_networks.py index c45cca4bd1a..85b43d89f3f 100644 --- a/homeassistant/auth/providers/trusted_networks.py +++ b/homeassistant/auth/providers/trusted_networks.py @@ -3,6 +3,8 @@ It shows list of users if access from trusted network. Abort login flow if not access from trusted network. """ +from __future__ import annotations + from ipaddress import ( IPv4Address, IPv4Network, @@ -11,7 +13,7 @@ from ipaddress import ( ip_address, ip_network, ) -from typing import Any, Dict, List, Optional, Union, cast +from typing import Any, Dict, List, Union, cast import voluptuous as vol @@ -68,12 +70,12 @@ class TrustedNetworksAuthProvider(AuthProvider): DEFAULT_TITLE = "Trusted Networks" @property - def trusted_networks(self) -> List[IPNetwork]: + def trusted_networks(self) -> list[IPNetwork]: """Return trusted networks.""" return cast(List[IPNetwork], self.config[CONF_TRUSTED_NETWORKS]) @property - def trusted_users(self) -> Dict[IPNetwork, Any]: + def trusted_users(self) -> dict[IPNetwork, Any]: """Return trusted users per network.""" return cast(Dict[IPNetwork, Any], self.config[CONF_TRUSTED_USERS]) @@ -82,7 +84,7 @@ class TrustedNetworksAuthProvider(AuthProvider): """Trusted Networks auth provider does not support MFA.""" return False - async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: + async def async_login_flow(self, context: dict | None) -> LoginFlow: """Return a flow to login.""" assert context is not None ip_addr = cast(IPAddress, context.get("ip_address")) @@ -125,7 +127,7 @@ class TrustedNetworksAuthProvider(AuthProvider): ) async def async_get_or_create_credentials( - self, flow_result: Dict[str, str] + self, flow_result: dict[str, str] ) -> Credentials: """Get credentials based on the flow result.""" user_id = flow_result["user"] @@ -169,7 +171,7 @@ class TrustedNetworksAuthProvider(AuthProvider): @callback def async_validate_refresh_token( - self, refresh_token: RefreshToken, remote_ip: Optional[str] = None + self, refresh_token: RefreshToken, remote_ip: str | None = None ) -> None: """Verify a refresh token is still valid.""" if remote_ip is None: @@ -186,7 +188,7 @@ class TrustedNetworksLoginFlow(LoginFlow): self, auth_provider: TrustedNetworksAuthProvider, ip_addr: IPAddress, - available_users: Dict[str, Optional[str]], + available_users: dict[str, str | None], allow_bypass_login: bool, ) -> None: """Initialize the login flow.""" @@ -196,8 +198,8 @@ class TrustedNetworksLoginFlow(LoginFlow): self._allow_bypass_login = allow_bypass_login async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the step of the form.""" try: cast( diff --git a/homeassistant/scripts/__init__.py b/homeassistant/scripts/__init__.py index 8042e546884..9aa07b94dc8 100644 --- a/homeassistant/scripts/__init__.py +++ b/homeassistant/scripts/__init__.py @@ -1,11 +1,13 @@ """Home Assistant command line scripts.""" +from __future__ import annotations + import argparse import asyncio import importlib import logging import os import sys -from typing import List, Optional, Sequence, Text +from typing import Sequence from homeassistant import runner from homeassistant.bootstrap import async_mount_local_lib_path @@ -16,7 +18,7 @@ from homeassistant.util.package import install_package, is_installed, is_virtual # mypy: allow-untyped-defs, no-warn-return-any -def run(args: List) -> int: +def run(args: list) -> int: """Run a script.""" scripts = [] path = os.path.dirname(__file__) @@ -65,7 +67,7 @@ def run(args: List) -> int: return script.run(args[1:]) # type: ignore -def extract_config_dir(args: Optional[Sequence[Text]] = None) -> str: +def extract_config_dir(args: Sequence[str] | None = None) -> str: """Extract the config dir from the arguments or get the default.""" parser = argparse.ArgumentParser(add_help=False) parser.add_argument("-c", "--config", default=None) diff --git a/homeassistant/scripts/benchmark/__init__.py b/homeassistant/scripts/benchmark/__init__.py index 3f590362504..2acefbce128 100644 --- a/homeassistant/scripts/benchmark/__init__.py +++ b/homeassistant/scripts/benchmark/__init__.py @@ -1,4 +1,6 @@ """Script to run benchmarks.""" +from __future__ import annotations + import argparse import asyncio import collections @@ -7,7 +9,7 @@ from datetime import datetime import json import logging from timeit import default_timer as timer -from typing import Callable, Dict, TypeVar +from typing import Callable, TypeVar from homeassistant import core from homeassistant.components.websocket_api.const import JSON_DUMP @@ -21,7 +23,7 @@ from homeassistant.util import dt as dt_util CALLABLE_T = TypeVar("CALLABLE_T", bound=Callable) # pylint: disable=invalid-name -BENCHMARKS: Dict[str, Callable] = {} +BENCHMARKS: dict[str, Callable] = {} def run(args): diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index 4fc8383782c..40ce9a521df 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -1,4 +1,6 @@ """Script to check the configuration file.""" +from __future__ import annotations + import argparse import asyncio from collections import OrderedDict @@ -6,7 +8,7 @@ from collections.abc import Mapping, Sequence from glob import glob import logging import os -from typing import Any, Callable, Dict, List, Tuple +from typing import Any, Callable from unittest.mock import patch from homeassistant import core @@ -22,13 +24,13 @@ REQUIREMENTS = ("colorlog==4.7.2",) _LOGGER = logging.getLogger(__name__) # pylint: disable=protected-access -MOCKS: Dict[str, Tuple[str, Callable]] = { +MOCKS: dict[str, tuple[str, Callable]] = { "load": ("homeassistant.util.yaml.loader.load_yaml", yaml_loader.load_yaml), "load*": ("homeassistant.config.load_yaml", yaml_loader.load_yaml), "secrets": ("homeassistant.util.yaml.loader.secret_yaml", yaml_loader.secret_yaml), } -PATCHES: Dict[str, Any] = {} +PATCHES: dict[str, Any] = {} C_HEAD = "bold" ERROR_STR = "General Errors" @@ -48,7 +50,7 @@ def color(the_color, *args, reset=None): raise ValueError(f"Invalid color {k!s} in {the_color}") from k -def run(script_args: List) -> int: +def run(script_args: list) -> int: """Handle check config commandline script.""" parser = argparse.ArgumentParser(description="Check Home Assistant configuration.") parser.add_argument("--script", choices=["check_config"]) @@ -83,7 +85,7 @@ def run(script_args: List) -> int: res = check(config_dir, args.secrets) - domain_info: List[str] = [] + domain_info: list[str] = [] if args.info: domain_info = args.info.split(",") @@ -123,7 +125,7 @@ def run(script_args: List) -> int: dump_dict(res["components"].get(domain)) if args.secrets: - flatsecret: Dict[str, str] = {} + flatsecret: dict[str, str] = {} for sfn, sdict in res["secret_cache"].items(): sss = [] @@ -149,7 +151,7 @@ def run(script_args: List) -> int: def check(config_dir, secrets=False): """Perform a check by mocking hass load functions.""" logging.getLogger("homeassistant.loader").setLevel(logging.CRITICAL) - res: Dict[str, Any] = { + res: dict[str, Any] = { "yaml_files": OrderedDict(), # yaml_files loaded "secrets": OrderedDict(), # secret cache and secrets loaded "except": OrderedDict(), # exceptions raised (with config) diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index 281a7d5308c..79ceeada0c2 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -1,4 +1,6 @@ """Helper methods for various modules.""" +from __future__ import annotations + import asyncio from datetime import datetime, timedelta import enum @@ -9,16 +11,7 @@ import socket import string import threading from types import MappingProxyType -from typing import ( - Any, - Callable, - Coroutine, - Iterable, - KeysView, - Optional, - TypeVar, - Union, -) +from typing import Any, Callable, Coroutine, Iterable, KeysView, TypeVar import slugify as unicode_slug @@ -106,8 +99,8 @@ def repr_helper(inp: Any) -> str: def convert( - value: Optional[T], to_type: Callable[[T], U], default: Optional[U] = None -) -> Optional[U]: + value: T | None, to_type: Callable[[T], U], default: U | None = None +) -> U | None: """Convert value to to_type, returns default if fails.""" try: return default if value is None else to_type(value) @@ -117,7 +110,7 @@ def convert( def ensure_unique_string( - preferred_string: str, current_strings: Union[Iterable[str], KeysView[str]] + preferred_string: str, current_strings: Iterable[str] | KeysView[str] ) -> str: """Return a string that is not present in current_strings. @@ -213,7 +206,7 @@ class Throttle: """ def __init__( - self, min_time: timedelta, limit_no_throttle: Optional[timedelta] = None + self, min_time: timedelta, limit_no_throttle: timedelta | None = None ) -> None: """Initialize the throttle.""" self.min_time = min_time @@ -253,7 +246,7 @@ class Throttle: ) @wraps(method) - def wrapper(*args: Any, **kwargs: Any) -> Union[Callable, Coroutine]: + def wrapper(*args: Any, **kwargs: Any) -> Callable | Coroutine: """Wrap that allows wrapped to be called only once per min_time. If we cannot acquire the lock, it is running so return None. diff --git a/homeassistant/util/aiohttp.py b/homeassistant/util/aiohttp.py index f2c761282bc..7d14ec252d9 100644 --- a/homeassistant/util/aiohttp.py +++ b/homeassistant/util/aiohttp.py @@ -1,7 +1,9 @@ """Utilities to help with aiohttp.""" +from __future__ import annotations + import io import json -from typing import Any, Dict, Optional +from typing import Any from urllib.parse import parse_qsl from multidict import CIMultiDict, MultiDict @@ -26,7 +28,7 @@ class MockStreamReader: class MockRequest: """Mock an aiohttp request.""" - mock_source: Optional[str] = None + mock_source: str | None = None def __init__( self, @@ -34,8 +36,8 @@ class MockRequest: mock_source: str, method: str = "GET", status: int = HTTP_OK, - headers: Optional[Dict[str, str]] = None, - query_string: Optional[str] = None, + headers: dict[str, str] | None = None, + query_string: str | None = None, url: str = "", ) -> None: """Initialize a request.""" diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index 36bf47aaf96..f41461aada5 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -1,7 +1,8 @@ """Color util methods.""" +from __future__ import annotations + import colorsys import math -from typing import List, Optional, Tuple import attr @@ -183,7 +184,7 @@ class GamutType: blue: XYPoint = attr.ib() -def color_name_to_rgb(color_name: str) -> Tuple[int, int, int]: +def color_name_to_rgb(color_name: str) -> tuple[int, int, int]: """Convert color name to RGB hex value.""" # COLORS map has no spaces in it, so make the color_name have no # spaces in it as well for matching purposes @@ -198,8 +199,8 @@ def color_name_to_rgb(color_name: str) -> Tuple[int, int, int]: def color_RGB_to_xy( - iR: int, iG: int, iB: int, Gamut: Optional[GamutType] = None -) -> Tuple[float, float]: + iR: int, iG: int, iB: int, Gamut: GamutType | None = None +) -> tuple[float, float]: """Convert from RGB color to XY color.""" return color_RGB_to_xy_brightness(iR, iG, iB, Gamut)[:2] @@ -208,8 +209,8 @@ def color_RGB_to_xy( # http://www.developers.meethue.com/documentation/color-conversions-rgb-xy # License: Code is given as is. Use at your own risk and discretion. def color_RGB_to_xy_brightness( - iR: int, iG: int, iB: int, Gamut: Optional[GamutType] = None -) -> Tuple[float, float, int]: + iR: int, iG: int, iB: int, Gamut: GamutType | None = None +) -> tuple[float, float, int]: """Convert from RGB color to XY color.""" if iR + iG + iB == 0: return 0.0, 0.0, 0 @@ -248,8 +249,8 @@ def color_RGB_to_xy_brightness( def color_xy_to_RGB( - vX: float, vY: float, Gamut: Optional[GamutType] = None -) -> Tuple[int, int, int]: + vX: float, vY: float, Gamut: GamutType | None = None +) -> tuple[int, int, int]: """Convert from XY to a normalized RGB.""" return color_xy_brightness_to_RGB(vX, vY, 255, Gamut) @@ -257,8 +258,8 @@ def color_xy_to_RGB( # Converted to Python from Obj-C, original source from: # http://www.developers.meethue.com/documentation/color-conversions-rgb-xy def color_xy_brightness_to_RGB( - vX: float, vY: float, ibrightness: int, Gamut: Optional[GamutType] = None -) -> Tuple[int, int, int]: + vX: float, vY: float, ibrightness: int, Gamut: GamutType | None = None +) -> tuple[int, int, int]: """Convert from XYZ to RGB.""" if Gamut: if not check_point_in_lamps_reach((vX, vY), Gamut): @@ -304,7 +305,7 @@ def color_xy_brightness_to_RGB( return (ir, ig, ib) -def color_hsb_to_RGB(fH: float, fS: float, fB: float) -> Tuple[int, int, int]: +def color_hsb_to_RGB(fH: float, fS: float, fB: float) -> tuple[int, int, int]: """Convert a hsb into its rgb representation.""" if fS == 0.0: fV = int(fB * 255) @@ -345,7 +346,7 @@ def color_hsb_to_RGB(fH: float, fS: float, fB: float) -> Tuple[int, int, int]: return (r, g, b) -def color_RGB_to_hsv(iR: float, iG: float, iB: float) -> Tuple[float, float, float]: +def color_RGB_to_hsv(iR: float, iG: float, iB: float) -> tuple[float, float, float]: """Convert an rgb color to its hsv representation. Hue is scaled 0-360 @@ -356,12 +357,12 @@ def color_RGB_to_hsv(iR: float, iG: float, iB: float) -> Tuple[float, float, flo return round(fHSV[0] * 360, 3), round(fHSV[1] * 100, 3), round(fHSV[2] * 100, 3) -def color_RGB_to_hs(iR: float, iG: float, iB: float) -> Tuple[float, float]: +def color_RGB_to_hs(iR: float, iG: float, iB: float) -> tuple[float, float]: """Convert an rgb color to its hs representation.""" return color_RGB_to_hsv(iR, iG, iB)[:2] -def color_hsv_to_RGB(iH: float, iS: float, iV: float) -> Tuple[int, int, int]: +def color_hsv_to_RGB(iH: float, iS: float, iV: float) -> tuple[int, int, int]: """Convert an hsv color into its rgb representation. Hue is scaled 0-360 @@ -372,27 +373,27 @@ def color_hsv_to_RGB(iH: float, iS: float, iV: float) -> Tuple[int, int, int]: return (int(fRGB[0] * 255), int(fRGB[1] * 255), int(fRGB[2] * 255)) -def color_hs_to_RGB(iH: float, iS: float) -> Tuple[int, int, int]: +def color_hs_to_RGB(iH: float, iS: float) -> tuple[int, int, int]: """Convert an hsv color into its rgb representation.""" return color_hsv_to_RGB(iH, iS, 100) def color_xy_to_hs( - vX: float, vY: float, Gamut: Optional[GamutType] = None -) -> Tuple[float, float]: + vX: float, vY: float, Gamut: GamutType | None = None +) -> tuple[float, float]: """Convert an xy color to its hs representation.""" h, s, _ = color_RGB_to_hsv(*color_xy_to_RGB(vX, vY, Gamut)) return h, s def color_hs_to_xy( - iH: float, iS: float, Gamut: Optional[GamutType] = None -) -> Tuple[float, float]: + iH: float, iS: float, Gamut: GamutType | None = None +) -> tuple[float, float]: """Convert an hs color to its xy representation.""" return color_RGB_to_xy(*color_hs_to_RGB(iH, iS), Gamut) -def _match_max_scale(input_colors: Tuple, output_colors: Tuple) -> Tuple: +def _match_max_scale(input_colors: tuple, output_colors: tuple) -> tuple: """Match the maximum value of the output to the input.""" max_in = max(input_colors) max_out = max(output_colors) @@ -403,7 +404,7 @@ def _match_max_scale(input_colors: Tuple, output_colors: Tuple) -> Tuple: return tuple(int(round(i * factor)) for i in output_colors) -def color_rgb_to_rgbw(r: int, g: int, b: int) -> Tuple[int, int, int, int]: +def color_rgb_to_rgbw(r: int, g: int, b: int) -> tuple[int, int, int, int]: """Convert an rgb color to an rgbw representation.""" # Calculate the white channel as the minimum of input rgb channels. # Subtract the white portion from the remaining rgb channels. @@ -415,7 +416,7 @@ def color_rgb_to_rgbw(r: int, g: int, b: int) -> Tuple[int, int, int, int]: return _match_max_scale((r, g, b), rgbw) # type: ignore -def color_rgbw_to_rgb(r: int, g: int, b: int, w: int) -> Tuple[int, int, int]: +def color_rgbw_to_rgb(r: int, g: int, b: int, w: int) -> tuple[int, int, int]: """Convert an rgbw color to an rgb representation.""" # Add the white channel back into the rgb channels. rgb = (r + w, g + w, b + w) @@ -430,7 +431,7 @@ def color_rgb_to_hex(r: int, g: int, b: int) -> str: return "{:02x}{:02x}{:02x}".format(round(r), round(g), round(b)) -def rgb_hex_to_rgb_list(hex_string: str) -> List[int]: +def rgb_hex_to_rgb_list(hex_string: str) -> list[int]: """Return an RGB color value list from a hex color string.""" return [ int(hex_string[i : i + len(hex_string) // 3], 16) @@ -438,14 +439,14 @@ def rgb_hex_to_rgb_list(hex_string: str) -> List[int]: ] -def color_temperature_to_hs(color_temperature_kelvin: float) -> Tuple[float, float]: +def color_temperature_to_hs(color_temperature_kelvin: float) -> tuple[float, float]: """Return an hs color from a color temperature in Kelvin.""" return color_RGB_to_hs(*color_temperature_to_rgb(color_temperature_kelvin)) def color_temperature_to_rgb( color_temperature_kelvin: float, -) -> Tuple[float, float, float]: +) -> tuple[float, float, float]: """ Return an RGB color from a color temperature in Kelvin. @@ -555,8 +556,8 @@ def get_closest_point_to_line(A: XYPoint, B: XYPoint, P: XYPoint) -> XYPoint: def get_closest_point_to_point( - xy_tuple: Tuple[float, float], Gamut: GamutType -) -> Tuple[float, float]: + xy_tuple: tuple[float, float], Gamut: GamutType +) -> tuple[float, float]: """ Get the closest matching color within the gamut of the light. @@ -592,7 +593,7 @@ def get_closest_point_to_point( return (cx, cy) -def check_point_in_lamps_reach(p: Tuple[float, float], Gamut: GamutType) -> bool: +def check_point_in_lamps_reach(p: tuple[float, float], Gamut: GamutType) -> bool: """Check if the provided XYPoint can be recreated by a Hue lamp.""" v1 = XYPoint(Gamut.green.x - Gamut.red.x, Gamut.green.y - Gamut.red.y) v2 = XYPoint(Gamut.blue.x - Gamut.red.x, Gamut.blue.y - Gamut.red.y) diff --git a/homeassistant/util/distance.py b/homeassistant/util/distance.py index 0e0a060c49c..592c7c3145e 100644 --- a/homeassistant/util/distance.py +++ b/homeassistant/util/distance.py @@ -1,6 +1,8 @@ """Distance util functions.""" +from __future__ import annotations + from numbers import Number -from typing import Callable, Dict +from typing import Callable from homeassistant.const import ( LENGTH, @@ -26,7 +28,7 @@ VALID_UNITS = [ LENGTH_YARD, ] -TO_METERS: Dict[str, Callable[[float], float]] = { +TO_METERS: dict[str, Callable[[float], float]] = { LENGTH_METERS: lambda meters: meters, LENGTH_MILES: lambda miles: miles * 1609.344, LENGTH_YARD: lambda yards: yards * 0.9144, @@ -37,7 +39,7 @@ TO_METERS: Dict[str, Callable[[float], float]] = { LENGTH_MILLIMETERS: lambda millimeters: millimeters * 0.001, } -METERS_TO: Dict[str, Callable[[float], float]] = { +METERS_TO: dict[str, Callable[[float], float]] = { LENGTH_METERS: lambda meters: meters, LENGTH_MILES: lambda meters: meters * 0.000621371, LENGTH_YARD: lambda meters: meters * 1.09361, diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index a4d5fd81c4f..a659d0add38 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -1,7 +1,9 @@ """Helper methods to handle the time in Home Assistant.""" +from __future__ import annotations + import datetime as dt import re -from typing import Any, Dict, List, Optional, Union, cast +from typing import Any, cast import ciso8601 import pytz @@ -40,7 +42,7 @@ def set_default_time_zone(time_zone: dt.tzinfo) -> None: DEFAULT_TIME_ZONE = time_zone -def get_time_zone(time_zone_str: str) -> Optional[dt.tzinfo]: +def get_time_zone(time_zone_str: str) -> dt.tzinfo | None: """Get time zone from string. Return None if unable to determine. Async friendly. @@ -56,7 +58,7 @@ def utcnow() -> dt.datetime: return dt.datetime.now(NATIVE_UTC) -def now(time_zone: Optional[dt.tzinfo] = None) -> dt.datetime: +def now(time_zone: dt.tzinfo | None = None) -> dt.datetime: """Get now in specified time zone.""" return dt.datetime.now(time_zone or DEFAULT_TIME_ZONE) @@ -77,7 +79,7 @@ def as_utc(dattim: dt.datetime) -> dt.datetime: def as_timestamp(dt_value: dt.datetime) -> float: """Convert a date/time into a unix time (seconds since 1970).""" if hasattr(dt_value, "timestamp"): - parsed_dt: Optional[dt.datetime] = dt_value + parsed_dt: dt.datetime | None = dt_value else: parsed_dt = parse_datetime(str(dt_value)) if parsed_dt is None: @@ -100,9 +102,7 @@ def utc_from_timestamp(timestamp: float) -> dt.datetime: return UTC.localize(dt.datetime.utcfromtimestamp(timestamp)) -def start_of_local_day( - dt_or_d: Union[dt.date, dt.datetime, None] = None -) -> dt.datetime: +def start_of_local_day(dt_or_d: dt.date | dt.datetime | None = None) -> dt.datetime: """Return local datetime object of start of day from date or datetime.""" if dt_or_d is None: date: dt.date = now().date() @@ -119,7 +119,7 @@ def start_of_local_day( # Copyright (c) Django Software Foundation and individual contributors. # All rights reserved. # https://github.com/django/django/blob/master/LICENSE -def parse_datetime(dt_str: str) -> Optional[dt.datetime]: +def parse_datetime(dt_str: str) -> dt.datetime | None: """Parse a string and return a datetime.datetime. This function supports time zone offsets. When the input contains one, @@ -134,12 +134,12 @@ def parse_datetime(dt_str: str) -> Optional[dt.datetime]: match = DATETIME_RE.match(dt_str) if not match: return None - kws: Dict[str, Any] = match.groupdict() + kws: dict[str, Any] = match.groupdict() if kws["microsecond"]: kws["microsecond"] = kws["microsecond"].ljust(6, "0") tzinfo_str = kws.pop("tzinfo") - tzinfo: Optional[dt.tzinfo] = None + tzinfo: dt.tzinfo | None = None if tzinfo_str == "Z": tzinfo = UTC elif tzinfo_str is not None: @@ -154,7 +154,7 @@ def parse_datetime(dt_str: str) -> Optional[dt.datetime]: return dt.datetime(**kws) -def parse_date(dt_str: str) -> Optional[dt.date]: +def parse_date(dt_str: str) -> dt.date | None: """Convert a date string to a date object.""" try: return dt.datetime.strptime(dt_str, DATE_STR_FORMAT).date() @@ -162,7 +162,7 @@ def parse_date(dt_str: str) -> Optional[dt.date]: return None -def parse_time(time_str: str) -> Optional[dt.time]: +def parse_time(time_str: str) -> dt.time | None: """Parse a time string (00:20:00) into Time object. Return None if invalid. @@ -213,7 +213,7 @@ def get_age(date: dt.datetime) -> str: return formatn(rounded_delta, selected_unit) -def parse_time_expression(parameter: Any, min_value: int, max_value: int) -> List[int]: +def parse_time_expression(parameter: Any, min_value: int, max_value: int) -> list[int]: """Parse the time expression part and return a list of times to match.""" if parameter is None or parameter == MATCH_ALL: res = list(range(min_value, max_value + 1)) @@ -241,9 +241,9 @@ def parse_time_expression(parameter: Any, min_value: int, max_value: int) -> Lis def find_next_time_expression_time( now: dt.datetime, # pylint: disable=redefined-outer-name - seconds: List[int], - minutes: List[int], - hours: List[int], + seconds: list[int], + minutes: list[int], + hours: list[int], ) -> dt.datetime: """Find the next datetime from now for which the time expression matches. @@ -257,7 +257,7 @@ def find_next_time_expression_time( if not seconds or not minutes or not hours: raise ValueError("Cannot find a next time: Time expression never matches!") - def _lower_bound(arr: List[int], cmp: int) -> Optional[int]: + def _lower_bound(arr: list[int], cmp: int) -> int | None: """Return the first value in arr greater or equal to cmp. Return None if no such value exists. diff --git a/homeassistant/util/json.py b/homeassistant/util/json.py index 2ce98ffef77..fac008d9f0f 100644 --- a/homeassistant/util/json.py +++ b/homeassistant/util/json.py @@ -1,10 +1,12 @@ """JSON utility functions.""" +from __future__ import annotations + from collections import deque import json import logging import os import tempfile -from typing import Any, Callable, Dict, List, Optional, Type, Union +from typing import Any, Callable from homeassistant.core import Event, State from homeassistant.exceptions import HomeAssistantError @@ -20,9 +22,7 @@ class WriteError(HomeAssistantError): """Error writing the data.""" -def load_json( - filename: str, default: Union[List, Dict, None] = None -) -> Union[List, Dict]: +def load_json(filename: str, default: list | dict | None = None) -> list | dict: """Load JSON data from a file and return as dict or list. Defaults to returning empty dict if file is not found. @@ -44,10 +44,10 @@ def load_json( def save_json( filename: str, - data: Union[List, Dict], + data: list | dict, private: bool = False, *, - encoder: Optional[Type[json.JSONEncoder]] = None, + encoder: type[json.JSONEncoder] | None = None, ) -> None: """Save JSON data to a file. @@ -85,7 +85,7 @@ def save_json( _LOGGER.error("JSON replacement cleanup failed: %s", err) -def format_unserializable_data(data: Dict[str, Any]) -> str: +def format_unserializable_data(data: dict[str, Any]) -> str: """Format output of find_paths in a friendly way. Format is comma separated: =() @@ -95,7 +95,7 @@ def format_unserializable_data(data: Dict[str, Any]) -> str: def find_paths_unserializable_data( bad_data: Any, *, dump: Callable[[Any], str] = json.dumps -) -> Dict[str, Any]: +) -> dict[str, Any]: """Find the paths to unserializable data. This method is slow! Only use for error handling. diff --git a/homeassistant/util/location.py b/homeassistant/util/location.py index 85db07e2d42..c22f5213130 100644 --- a/homeassistant/util/location.py +++ b/homeassistant/util/location.py @@ -3,10 +3,12 @@ Module with location helpers. detect_location_info and elevation are mocked by default during tests. """ +from __future__ import annotations + import asyncio import collections import math -from typing import Any, Dict, Optional, Tuple +from typing import Any import aiohttp @@ -47,7 +49,7 @@ LocationInfo = collections.namedtuple( async def async_detect_location_info( session: aiohttp.ClientSession, -) -> Optional[LocationInfo]: +) -> LocationInfo | None: """Detect location information.""" data = await _get_ipapi(session) @@ -63,8 +65,8 @@ async def async_detect_location_info( def distance( - lat1: Optional[float], lon1: Optional[float], lat2: float, lon2: float -) -> Optional[float]: + lat1: float | None, lon1: float | None, lat2: float, lon2: float +) -> float | None: """Calculate the distance in meters between two points. Async friendly. @@ -81,8 +83,8 @@ def distance( # Source: https://github.com/maurycyp/vincenty # License: https://github.com/maurycyp/vincenty/blob/master/LICENSE def vincenty( - point1: Tuple[float, float], point2: Tuple[float, float], miles: bool = False -) -> Optional[float]: + point1: tuple[float, float], point2: tuple[float, float], miles: bool = False +) -> float | None: """ Vincenty formula (inverse method) to calculate the distance. @@ -162,7 +164,7 @@ def vincenty( return round(s, 6) -async def _get_ipapi(session: aiohttp.ClientSession) -> Optional[Dict[str, Any]]: +async def _get_ipapi(session: aiohttp.ClientSession) -> dict[str, Any] | None: """Query ipapi.co for location data.""" try: resp = await session.get(IPAPI, timeout=5) @@ -192,7 +194,7 @@ async def _get_ipapi(session: aiohttp.ClientSession) -> Optional[Dict[str, Any]] } -async def _get_ip_api(session: aiohttp.ClientSession) -> Optional[Dict[str, Any]]: +async def _get_ip_api(session: aiohttp.ClientSession) -> dict[str, Any] | None: """Query ip-api.com for location data.""" try: resp = await session.get(IP_API, timeout=5) diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index 423685fe9d4..5653523b677 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -1,4 +1,6 @@ """Logging utilities.""" +from __future__ import annotations + import asyncio from functools import partial, wraps import inspect @@ -6,7 +8,7 @@ import logging import logging.handlers import queue import traceback -from typing import Any, Awaitable, Callable, Coroutine, Union, cast, overload +from typing import Any, Awaitable, Callable, Coroutine, cast, overload from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE from homeassistant.core import HomeAssistant, callback @@ -115,7 +117,7 @@ def catch_log_exception( def catch_log_exception( func: Callable[..., Any], format_err: Callable[..., Any], *args: Any -) -> Union[Callable[..., None], Callable[..., Awaitable[None]]]: +) -> Callable[..., None] | Callable[..., Awaitable[None]]: """Decorate a callback to catch and log exceptions.""" # Check for partials to properly determine if coroutine function @@ -123,7 +125,7 @@ def catch_log_exception( while isinstance(check_func, partial): check_func = check_func.func - wrapper_func: Union[Callable[..., None], Callable[..., Awaitable[None]]] + wrapper_func: Callable[..., None] | Callable[..., Awaitable[None]] if asyncio.iscoroutinefunction(check_func): async_func = cast(Callable[..., Awaitable[None]], func) diff --git a/homeassistant/util/network.py b/homeassistant/util/network.py index 94b43ad7803..c36e7f3793a 100644 --- a/homeassistant/util/network.py +++ b/homeassistant/util/network.py @@ -1,6 +1,7 @@ """Network utilities.""" +from __future__ import annotations + from ipaddress import IPv4Address, IPv6Address, ip_address, ip_network -from typing import Union import yarl @@ -23,22 +24,22 @@ PRIVATE_NETWORKS = ( LINK_LOCAL_NETWORK = ip_network("169.254.0.0/16") -def is_loopback(address: Union[IPv4Address, IPv6Address]) -> bool: +def is_loopback(address: IPv4Address | IPv6Address) -> bool: """Check if an address is a loopback address.""" return any(address in network for network in LOOPBACK_NETWORKS) -def is_private(address: Union[IPv4Address, IPv6Address]) -> bool: +def is_private(address: IPv4Address | IPv6Address) -> bool: """Check if an address is a private address.""" return any(address in network for network in PRIVATE_NETWORKS) -def is_link_local(address: Union[IPv4Address, IPv6Address]) -> bool: +def is_link_local(address: IPv4Address | IPv6Address) -> bool: """Check if an address is link local.""" return address in LINK_LOCAL_NETWORK -def is_local(address: Union[IPv4Address, IPv6Address]) -> bool: +def is_local(address: IPv4Address | IPv6Address) -> bool: """Check if an address is loopback or private.""" return is_loopback(address) or is_private(address) diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py index 34628b4ca4d..99afcd0fcf8 100644 --- a/homeassistant/util/package.py +++ b/homeassistant/util/package.py @@ -1,4 +1,6 @@ """Helpers to install PyPi packages.""" +from __future__ import annotations + import asyncio from importlib.metadata import PackageNotFoundError, version import logging @@ -6,7 +8,6 @@ import os from pathlib import Path from subprocess import PIPE, Popen import sys -from typing import Optional from urllib.parse import urlparse import pkg_resources @@ -59,10 +60,10 @@ def is_installed(package: str) -> bool: def install_package( package: str, upgrade: bool = True, - target: Optional[str] = None, - constraints: Optional[str] = None, - find_links: Optional[str] = None, - no_cache_dir: Optional[bool] = False, + target: str | None = None, + constraints: str | None = None, + find_links: str | None = None, + no_cache_dir: bool | None = False, ) -> bool: """Install a package on PyPi. Accepts pip compatible package strings. diff --git a/homeassistant/util/percentage.py b/homeassistant/util/percentage.py index 949af7dbb32..c257ca2268c 100644 --- a/homeassistant/util/percentage.py +++ b/homeassistant/util/percentage.py @@ -1,9 +1,8 @@ """Percentage util functions.""" - -from typing import List, Tuple +from __future__ import annotations -def ordered_list_item_to_percentage(ordered_list: List[str], item: str) -> int: +def ordered_list_item_to_percentage(ordered_list: list[str], item: str) -> int: """Determine the percentage of an item in an ordered list. When using this utility for fan speeds, do not include "off" @@ -26,7 +25,7 @@ def ordered_list_item_to_percentage(ordered_list: List[str], item: str) -> int: return (list_position * 100) // list_len -def percentage_to_ordered_list_item(ordered_list: List[str], percentage: int) -> str: +def percentage_to_ordered_list_item(ordered_list: list[str], percentage: int) -> str: """Find the item that most closely matches the percentage in an ordered list. When using this utility for fan speeds, do not include "off" @@ -54,7 +53,7 @@ def percentage_to_ordered_list_item(ordered_list: List[str], percentage: int) -> def ranged_value_to_percentage( - low_high_range: Tuple[float, float], value: float + low_high_range: tuple[float, float], value: float ) -> int: """Given a range of low and high values convert a single value to a percentage. @@ -71,7 +70,7 @@ def ranged_value_to_percentage( def percentage_to_ranged_value( - low_high_range: Tuple[float, float], percentage: int + low_high_range: tuple[float, float], percentage: int ) -> float: """Given a range of low and high values convert a percentage to a single value. @@ -87,11 +86,11 @@ def percentage_to_ranged_value( return states_in_range(low_high_range) * percentage / 100 -def states_in_range(low_high_range: Tuple[float, float]) -> float: +def states_in_range(low_high_range: tuple[float, float]) -> float: """Given a range of low and high values return how many states exist.""" return low_high_range[1] - low_high_range[0] + 1 -def int_states_in_range(low_high_range: Tuple[float, float]) -> int: +def int_states_in_range(low_high_range: tuple[float, float]) -> int: """Given a range of low and high values return how many integer states exist.""" return int(states_in_range(low_high_range)) diff --git a/homeassistant/util/pil.py b/homeassistant/util/pil.py index 80c11c9c410..7caeac15458 100644 --- a/homeassistant/util/pil.py +++ b/homeassistant/util/pil.py @@ -2,18 +2,18 @@ Can only be used by integrations that have pillow in their requirements. """ -from typing import Tuple +from __future__ import annotations from PIL import ImageDraw def draw_box( draw: ImageDraw, - box: Tuple[float, float, float, float], + box: tuple[float, float, float, float], img_width: int, img_height: int, text: str = "", - color: Tuple[int, int, int] = (255, 255, 0), + color: tuple[int, int, int] = (255, 255, 0), ) -> None: """ Draw a bounding box on and image. diff --git a/homeassistant/util/ruamel_yaml.py b/homeassistant/util/ruamel_yaml.py index 496ca377936..91aad5f381c 100644 --- a/homeassistant/util/ruamel_yaml.py +++ b/homeassistant/util/ruamel_yaml.py @@ -1,9 +1,11 @@ """ruamel.yaml utility functions.""" +from __future__ import annotations + from collections import OrderedDict import logging import os from os import O_CREAT, O_TRUNC, O_WRONLY, stat_result -from typing import Dict, List, Optional, Union +from typing import Dict, List, Union import ruamel.yaml from ruamel.yaml import YAML # type: ignore @@ -22,7 +24,7 @@ JSON_TYPE = Union[List, Dict, str] # pylint: disable=invalid-name class ExtSafeConstructor(SafeConstructor): """Extended SafeConstructor.""" - name: Optional[str] = None + name: str | None = None class UnsupportedYamlError(HomeAssistantError): @@ -77,7 +79,7 @@ def yaml_to_object(data: str) -> JSON_TYPE: """Create object from yaml string.""" yaml = YAML(typ="rt") try: - result: Union[List, Dict, str] = yaml.load(data) + result: list | dict | str = yaml.load(data) return result except YAMLError as exc: _LOGGER.error("YAML error: %s", exc) diff --git a/homeassistant/util/timeout.py b/homeassistant/util/timeout.py index d8fc3e48fe6..64208d775ea 100644 --- a/homeassistant/util/timeout.py +++ b/homeassistant/util/timeout.py @@ -8,7 +8,7 @@ from __future__ import annotations import asyncio import enum from types import TracebackType -from typing import Any, Dict, List, Optional, Type, Union +from typing import Any from .async_ import run_callback_threadsafe @@ -38,10 +38,10 @@ class _GlobalFreezeContext: async def __aexit__( self, - exc_type: Type[BaseException], + exc_type: type[BaseException], exc_val: BaseException, exc_tb: TracebackType, - ) -> Optional[bool]: + ) -> bool | None: self._exit() return None @@ -51,10 +51,10 @@ class _GlobalFreezeContext: def __exit__( # pylint: disable=useless-return self, - exc_type: Type[BaseException], + exc_type: type[BaseException], exc_val: BaseException, exc_tb: TracebackType, - ) -> Optional[bool]: + ) -> bool | None: self._loop.call_soon_threadsafe(self._exit) return None @@ -106,10 +106,10 @@ class _ZoneFreezeContext: async def __aexit__( self, - exc_type: Type[BaseException], + exc_type: type[BaseException], exc_val: BaseException, exc_tb: TracebackType, - ) -> Optional[bool]: + ) -> bool | None: self._exit() return None @@ -119,10 +119,10 @@ class _ZoneFreezeContext: def __exit__( # pylint: disable=useless-return self, - exc_type: Type[BaseException], + exc_type: type[BaseException], exc_val: BaseException, exc_tb: TracebackType, - ) -> Optional[bool]: + ) -> bool | None: self._loop.call_soon_threadsafe(self._exit) return None @@ -155,8 +155,8 @@ class _GlobalTaskContext: self._manager: TimeoutManager = manager self._task: asyncio.Task[Any] = task self._time_left: float = timeout - self._expiration_time: Optional[float] = None - self._timeout_handler: Optional[asyncio.Handle] = None + self._expiration_time: float | None = None + self._timeout_handler: asyncio.Handle | None = None self._wait_zone: asyncio.Event = asyncio.Event() self._state: _State = _State.INIT self._cool_down: float = cool_down @@ -169,10 +169,10 @@ class _GlobalTaskContext: async def __aexit__( self, - exc_type: Type[BaseException], + exc_type: type[BaseException], exc_val: BaseException, exc_tb: TracebackType, - ) -> Optional[bool]: + ) -> bool | None: self._stop_timer() self._manager.global_tasks.remove(self) @@ -263,8 +263,8 @@ class _ZoneTaskContext: self._task: asyncio.Task[Any] = task self._state: _State = _State.INIT self._time_left: float = timeout - self._expiration_time: Optional[float] = None - self._timeout_handler: Optional[asyncio.Handle] = None + self._expiration_time: float | None = None + self._timeout_handler: asyncio.Handle | None = None @property def state(self) -> _State: @@ -283,10 +283,10 @@ class _ZoneTaskContext: async def __aexit__( self, - exc_type: Type[BaseException], + exc_type: type[BaseException], exc_val: BaseException, exc_tb: TracebackType, - ) -> Optional[bool]: + ) -> bool | None: self._zone.exit_task(self) self._stop_timer() @@ -344,8 +344,8 @@ class _ZoneTimeoutManager: """Initialize internal timeout context manager.""" self._manager: TimeoutManager = manager self._zone: str = zone - self._tasks: List[_ZoneTaskContext] = [] - self._freezes: List[_ZoneFreezeContext] = [] + self._tasks: list[_ZoneTaskContext] = [] + self._freezes: list[_ZoneFreezeContext] = [] def __repr__(self) -> str: """Representation of a zone.""" @@ -418,9 +418,9 @@ class TimeoutManager: def __init__(self) -> None: """Initialize TimeoutManager.""" self._loop: asyncio.AbstractEventLoop = asyncio.get_running_loop() - self._zones: Dict[str, _ZoneTimeoutManager] = {} - self._globals: List[_GlobalTaskContext] = [] - self._freezes: List[_GlobalFreezeContext] = [] + self._zones: dict[str, _ZoneTimeoutManager] = {} + self._globals: list[_GlobalTaskContext] = [] + self._freezes: list[_GlobalFreezeContext] = [] @property def zones_done(self) -> bool: @@ -433,17 +433,17 @@ class TimeoutManager: return not self._freezes @property - def zones(self) -> Dict[str, _ZoneTimeoutManager]: + def zones(self) -> dict[str, _ZoneTimeoutManager]: """Return all Zones.""" return self._zones @property - def global_tasks(self) -> List[_GlobalTaskContext]: + def global_tasks(self) -> list[_GlobalTaskContext]: """Return all global Tasks.""" return self._globals @property - def global_freezes(self) -> List[_GlobalFreezeContext]: + def global_freezes(self) -> list[_GlobalFreezeContext]: """Return all global Freezes.""" return self._freezes @@ -459,12 +459,12 @@ class TimeoutManager: def async_timeout( self, timeout: float, zone_name: str = ZONE_GLOBAL, cool_down: float = 0 - ) -> Union[_ZoneTaskContext, _GlobalTaskContext]: + ) -> _ZoneTaskContext | _GlobalTaskContext: """Timeout based on a zone. For using as Async Context Manager. """ - current_task: Optional[asyncio.Task[Any]] = asyncio.current_task() + current_task: asyncio.Task[Any] | None = asyncio.current_task() assert current_task # Global Zone @@ -483,7 +483,7 @@ class TimeoutManager: def async_freeze( self, zone_name: str = ZONE_GLOBAL - ) -> Union[_ZoneFreezeContext, _GlobalFreezeContext]: + ) -> _ZoneFreezeContext | _GlobalFreezeContext: """Freeze all timer until job is done. For using as Async Context Manager. @@ -502,7 +502,7 @@ class TimeoutManager: def freeze( self, zone_name: str = ZONE_GLOBAL - ) -> Union[_ZoneFreezeContext, _GlobalFreezeContext]: + ) -> _ZoneFreezeContext | _GlobalFreezeContext: """Freeze all timer until job is done. For using as Context Manager. diff --git a/homeassistant/util/unit_system.py b/homeassistant/util/unit_system.py index 5cba1bfeb19..b5c8c38425a 100644 --- a/homeassistant/util/unit_system.py +++ b/homeassistant/util/unit_system.py @@ -1,6 +1,7 @@ """Unit system helper class and methods.""" +from __future__ import annotations + from numbers import Number -from typing import Dict, Optional from homeassistant.const import ( CONF_UNIT_SYSTEM_IMPERIAL, @@ -109,7 +110,7 @@ class UnitSystem: return temperature_util.convert(temperature, from_unit, self.temperature_unit) - def length(self, length: Optional[float], from_unit: str) -> float: + def length(self, length: float | None, from_unit: str) -> float: """Convert the given length to this unit system.""" if not isinstance(length, Number): raise TypeError(f"{length!s} is not a numeric value.") @@ -119,7 +120,7 @@ class UnitSystem: length, from_unit, self.length_unit ) - def pressure(self, pressure: Optional[float], from_unit: str) -> float: + def pressure(self, pressure: float | None, from_unit: str) -> float: """Convert the given pressure to this unit system.""" if not isinstance(pressure, Number): raise TypeError(f"{pressure!s} is not a numeric value.") @@ -129,7 +130,7 @@ class UnitSystem: pressure, from_unit, self.pressure_unit ) - def volume(self, volume: Optional[float], from_unit: str) -> float: + def volume(self, volume: float | None, from_unit: str) -> float: """Convert the given volume to this unit system.""" if not isinstance(volume, Number): raise TypeError(f"{volume!s} is not a numeric value.") @@ -137,7 +138,7 @@ class UnitSystem: # type ignore: https://github.com/python/mypy/issues/7207 return volume_util.convert(volume, from_unit, self.volume_unit) # type: ignore - def as_dict(self) -> Dict[str, str]: + def as_dict(self) -> dict[str, str]: """Convert the unit system to a dictionary.""" return { LENGTH: self.length_unit, diff --git a/homeassistant/util/yaml/input.py b/homeassistant/util/yaml/input.py index 6282509fae2..ab5948db605 100644 --- a/homeassistant/util/yaml/input.py +++ b/homeassistant/util/yaml/input.py @@ -1,6 +1,7 @@ """Deal with YAML input.""" +from __future__ import annotations -from typing import Any, Dict, Set +from typing import Any from .objects import Input @@ -14,14 +15,14 @@ class UndefinedSubstitution(Exception): self.input = input -def extract_inputs(obj: Any) -> Set[str]: +def extract_inputs(obj: Any) -> set[str]: """Extract input from a structure.""" - found: Set[str] = set() + found: set[str] = set() _extract_inputs(obj, found) return found -def _extract_inputs(obj: Any, found: Set[str]) -> None: +def _extract_inputs(obj: Any, found: set[str]) -> None: """Extract input from a structure.""" if isinstance(obj, Input): found.add(obj.name) @@ -38,7 +39,7 @@ def _extract_inputs(obj: Any, found: Set[str]) -> None: return -def substitute(obj: Any, substitutions: Dict[str, Any]) -> Any: +def substitute(obj: Any, substitutions: dict[str, Any]) -> Any: """Substitute values.""" if isinstance(obj, Input): if obj.name not in substitutions: diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index b4699ed95d2..386f14ac157 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -1,10 +1,12 @@ """Custom loader.""" +from __future__ import annotations + from collections import OrderedDict import fnmatch import logging import os from pathlib import Path -from typing import Any, Dict, Iterator, List, Optional, TextIO, TypeVar, Union, overload +from typing import Any, Dict, Iterator, List, TextIO, TypeVar, Union, overload import yaml @@ -27,7 +29,7 @@ class Secrets: def __init__(self, config_dir: Path): """Initialize secrets.""" self.config_dir = config_dir - self._cache: Dict[Path, Dict[str, str]] = {} + self._cache: dict[Path, dict[str, str]] = {} def get(self, requester_path: str, secret: str) -> str: """Return the value of a secret.""" @@ -55,7 +57,7 @@ class Secrets: raise HomeAssistantError(f"Secret {secret} not defined") - def _load_secret_yaml(self, secret_dir: Path) -> Dict[str, str]: + def _load_secret_yaml(self, secret_dir: Path) -> dict[str, str]: """Load the secrets yaml from path.""" secret_path = secret_dir / SECRET_YAML @@ -90,7 +92,7 @@ class Secrets: class SafeLineLoader(yaml.SafeLoader): """Loader class that keeps track of line numbers.""" - def __init__(self, stream: Any, secrets: Optional[Secrets] = None) -> None: + def __init__(self, stream: Any, secrets: Secrets | None = None) -> None: """Initialize a safe line loader.""" super().__init__(stream) self.secrets = secrets @@ -103,7 +105,7 @@ class SafeLineLoader(yaml.SafeLoader): return node -def load_yaml(fname: str, secrets: Optional[Secrets] = None) -> JSON_TYPE: +def load_yaml(fname: str, secrets: Secrets | None = None) -> JSON_TYPE: """Load a YAML file.""" try: with open(fname, encoding="utf-8") as conf_file: @@ -113,9 +115,7 @@ def load_yaml(fname: str, secrets: Optional[Secrets] = None) -> JSON_TYPE: raise HomeAssistantError(exc) from exc -def parse_yaml( - content: Union[str, TextIO], secrets: Optional[Secrets] = None -) -> JSON_TYPE: +def parse_yaml(content: str | TextIO, secrets: Secrets | None = None) -> JSON_TYPE: """Load a YAML file.""" try: # If configuration file is empty YAML returns None @@ -131,14 +131,14 @@ def parse_yaml( @overload def _add_reference( - obj: Union[list, NodeListClass], loader: SafeLineLoader, node: yaml.nodes.Node + obj: list | NodeListClass, loader: SafeLineLoader, node: yaml.nodes.Node ) -> NodeListClass: ... @overload def _add_reference( - obj: Union[str, NodeStrClass], loader: SafeLineLoader, node: yaml.nodes.Node + obj: str | NodeStrClass, loader: SafeLineLoader, node: yaml.nodes.Node ) -> NodeStrClass: ... @@ -223,7 +223,7 @@ def _include_dir_merge_named_yaml( def _include_dir_list_yaml( loader: SafeLineLoader, node: yaml.nodes.Node -) -> List[JSON_TYPE]: +) -> list[JSON_TYPE]: """Load multiple files from directory as a list.""" loc = os.path.join(os.path.dirname(loader.name), node.value) return [ @@ -238,7 +238,7 @@ def _include_dir_merge_list_yaml( ) -> JSON_TYPE: """Load multiple files from directory as a merged list.""" loc: str = os.path.join(os.path.dirname(loader.name), node.value) - merged_list: List[JSON_TYPE] = [] + merged_list: list[JSON_TYPE] = [] for fname in _find_files(loc, "*.yaml"): if os.path.basename(fname) == SECRET_YAML: continue @@ -253,7 +253,7 @@ def _ordered_dict(loader: SafeLineLoader, node: yaml.nodes.MappingNode) -> Order loader.flatten_mapping(node) nodes = loader.construct_pairs(node) - seen: Dict = {} + seen: dict = {} for (key, _), (child_node, _) in zip(nodes, node.value): line = child_node.start_mark.line From dd56cc801060291d75184390bb0a522f3e546bf4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 17 Mar 2021 11:47:42 -1000 Subject: [PATCH 431/831] Fix rest sensor data misalignment with multiple sensors (#48043) If there were multiple rest data sources, the index needed to be incremented by type instead of by data source/type --- homeassistant/components/rest/__init__.py | 9 ++- tests/components/rest/test_init.py | 75 +++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/rest/__init__.py b/homeassistant/components/rest/__init__.py index 233a12f44c8..26e8fde57e0 100644 --- a/homeassistant/components/rest/__init__.py +++ b/homeassistant/components/rest/__init__.py @@ -77,6 +77,7 @@ async def _async_process_config(hass, config) -> bool: refresh_tasks = [] load_tasks = [] + platform_idxs = {} for rest_idx, conf in enumerate(config[DOMAIN]): scan_interval = conf.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) resource_template = conf.get(CONF_RESOURCE_TEMPLATE) @@ -91,7 +92,13 @@ async def _async_process_config(hass, config) -> bool: if platform_domain not in conf: continue - for platform_idx, platform_conf in enumerate(conf[platform_domain]): + for platform_conf in conf[platform_domain]: + if platform_domain not in platform_idxs: + platform_idxs[platform_domain] = 0 + else: + platform_idxs[platform_domain] += 1 + platform_idx = platform_idxs[platform_domain] + hass.data[DOMAIN][platform_domain][platform_idx] = platform_conf load = discovery.async_load_platform( diff --git a/tests/components/rest/test_init.py b/tests/components/rest/test_init.py index 19a5651e989..2902addca0c 100644 --- a/tests/components/rest/test_init.py +++ b/tests/components/rest/test_init.py @@ -338,3 +338,78 @@ async def test_reload_fails_to_read_configuration(hass): def _get_fixtures_base_path(): return path.dirname(path.dirname(path.dirname(__file__))) + + +@respx.mock +async def test_multiple_rest_endpoints(hass): + """Test multiple rest endpoints.""" + + respx.get("http://date.jsontest.com").respond( + status_code=200, + json={ + "date": "03-17-2021", + "milliseconds_since_epoch": 1616008268573, + "time": "07:11:08 PM", + }, + ) + + respx.get("http://time.jsontest.com").respond( + status_code=200, + json={ + "date": "03-17-2021", + "milliseconds_since_epoch": 1616008299665, + "time": "07:11:39 PM", + }, + ) + respx.get("http://localhost").respond( + status_code=200, + json={ + "value": "1", + }, + ) + assert await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: [ + { + "resource": "http://date.jsontest.com", + "sensor": [ + { + "name": "JSON Date", + "value_template": "{{ value_json.date }}", + }, + { + "name": "JSON Date Time", + "value_template": "{{ value_json.time }}", + }, + ], + }, + { + "resource": "http://time.jsontest.com", + "sensor": [ + { + "name": "JSON Time", + "value_template": "{{ value_json.time }}", + }, + ], + }, + { + "resource": "http://localhost", + "binary_sensor": [ + { + "name": "Binary Sensor", + "value_template": "{{ value_json.value }}", + }, + ], + }, + ] + }, + ) + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 4 + + assert hass.states.get("sensor.json_date").state == "03-17-2021" + assert hass.states.get("sensor.json_date_time").state == "07:11:08 PM" + assert hass.states.get("sensor.json_time").state == "07:11:39 PM" + assert hass.states.get("binary_sensor.binary_sensor").state == "on" From 02619ca2cd8bc4c7250eabbee28f83d123005698 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Wed, 17 Mar 2021 18:50:21 -0300 Subject: [PATCH 432/831] Add service schema for ESPHome api services (#47426) --- homeassistant/components/esphome/__init__.py | 73 +++++++++++++++++--- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index dee0813007c..650b630e34c 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -22,6 +22,7 @@ from homeassistant.components import zeroconf from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_HOST, + CONF_MODE, CONF_PASSWORD, CONF_PORT, EVENT_HOMEASSISTANT_STOP, @@ -35,6 +36,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.json import JSONEncoder +from homeassistant.helpers.service import async_set_service_schema from homeassistant.helpers.storage import Store from homeassistant.helpers.template import Template from homeassistant.helpers.typing import ConfigType, HomeAssistantType @@ -308,17 +310,63 @@ async def _register_service( ): service_name = f"{entry_data.device_info.name}_{service.name}" schema = {} + fields = {} + for arg in service.args: - schema[vol.Required(arg.name)] = { - UserServiceArgType.BOOL: cv.boolean, - UserServiceArgType.INT: vol.Coerce(int), - UserServiceArgType.FLOAT: vol.Coerce(float), - UserServiceArgType.STRING: cv.string, - UserServiceArgType.BOOL_ARRAY: [cv.boolean], - UserServiceArgType.INT_ARRAY: [vol.Coerce(int)], - UserServiceArgType.FLOAT_ARRAY: [vol.Coerce(float)], - UserServiceArgType.STRING_ARRAY: [cv.string], + metadata = { + UserServiceArgType.BOOL: { + "validator": cv.boolean, + "example": "False", + "selector": {"boolean": None}, + }, + UserServiceArgType.INT: { + "validator": vol.Coerce(int), + "example": "42", + "selector": {"number": {CONF_MODE: "box"}}, + }, + UserServiceArgType.FLOAT: { + "validator": vol.Coerce(float), + "example": "12.3", + "selector": {"number": {CONF_MODE: "box", "step": 1e-3}}, + }, + UserServiceArgType.STRING: { + "validator": cv.string, + "example": "Example text", + "selector": {"text": None}, + }, + UserServiceArgType.BOOL_ARRAY: { + "validator": [cv.boolean], + "description": "A list of boolean values.", + "example": "[True, False]", + "selector": {"object": {}}, + }, + UserServiceArgType.INT_ARRAY: { + "validator": [vol.Coerce(int)], + "description": "A list of integer values.", + "example": "[42, 34]", + "selector": {"object": {}}, + }, + UserServiceArgType.FLOAT_ARRAY: { + "validator": [vol.Coerce(float)], + "description": "A list of floating point numbers.", + "example": "[ 12.3, 34.5 ]", + "selector": {"object": {}}, + }, + UserServiceArgType.STRING_ARRAY: { + "validator": [cv.string], + "description": "A list of strings.", + "example": "['Example text', 'Another example']", + "selector": {"object": {}}, + }, }[arg.type_] + schema[vol.Required(arg.name)] = metadata["validator"] + fields[arg.name] = { + "name": arg.name, + "required": True, + "description": metadata.get("description"), + "example": metadata["example"], + "selector": metadata["selector"], + } async def execute_service(call): await entry_data.client.execute_service(service, call.data) @@ -327,6 +375,13 @@ async def _register_service( DOMAIN, service_name, execute_service, vol.Schema(schema) ) + service_desc = { + "description": f"Calls the service {service.name} of the node {entry_data.device_info.name}", + "fields": fields, + } + + async_set_service_schema(hass, DOMAIN, service_name, service_desc) + async def _setup_services( hass: HomeAssistantType, entry_data: RuntimeEntryData, services: List[UserService] From 76199c0eb23ab7612152cfb17b656c68a787f195 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 17 Mar 2021 23:34:25 +0100 Subject: [PATCH 433/831] Update typing 04 (#48037) --- .../components/acmeda/config_flow.py | 5 +-- homeassistant/components/acmeda/hub.py | 5 +-- .../alarm_control_panel/device_action.py | 6 ++-- .../alarm_control_panel/device_condition.py | 4 +-- .../alarm_control_panel/device_trigger.py | 4 +-- .../alarm_control_panel/reproduce_state.py | 12 ++++--- .../components/alert/reproduce_state.py | 12 ++++--- .../components/alexa/capabilities.py | 7 ++-- homeassistant/components/alexa/entities.py | 8 +++-- .../components/alexa/state_report.py | 7 ++-- homeassistant/components/almond/__init__.py | 5 +-- .../components/arcam_fmj/device_trigger.py | 4 +-- .../arris_tg2492lg/device_tracker.py | 4 +-- .../components/asuswrt/device_tracker.py | 6 ++-- homeassistant/components/asuswrt/router.py | 24 +++++++------ homeassistant/components/asuswrt/sensor.py | 9 ++--- homeassistant/components/atag/climate.py | 18 +++++----- homeassistant/components/auth/__init__.py | 5 +-- .../components/automation/__init__.py | 26 +++++++------- .../components/automation/reproduce_state.py | 12 ++++--- homeassistant/components/automation/trace.py | 32 +++++++++-------- homeassistant/components/awair/__init__.py | 5 +-- homeassistant/components/awair/config_flow.py | 7 ++-- homeassistant/components/awair/sensor.py | 11 +++--- .../components/azure_devops/__init__.py | 6 ++-- .../components/azure_devops/sensor.py | 5 +-- .../components/azure_event_hub/__init__.py | 6 ++-- .../components/bbox/device_tracker.py | 5 +-- .../binary_sensor/device_condition.py | 6 ++-- .../binary_sensor/significant_change.py | 6 ++-- .../components/blueprint/importer.py | 7 ++-- homeassistant/components/blueprint/models.py | 18 +++++----- .../components/blueprint/websocket_api.py | 8 ++--- .../bluetooth_tracker/device_tracker.py | 13 +++---- homeassistant/components/bond/config_flow.py | 18 +++++----- homeassistant/components/bond/cover.py | 14 ++++---- homeassistant/components/bond/entity.py | 14 ++++---- homeassistant/components/bond/fan.py | 24 +++++++------ homeassistant/components/bond/light.py | 36 ++++++++++--------- homeassistant/components/bond/switch.py | 10 +++--- homeassistant/components/bond/utils.py | 36 ++++++++++--------- homeassistant/components/bsblan/climate.py | 14 ++++---- .../components/bsblan/config_flow.py | 16 +++++---- homeassistant/components/buienradar/camera.py | 11 +++--- 44 files changed, 282 insertions(+), 229 deletions(-) diff --git a/homeassistant/components/acmeda/config_flow.py b/homeassistant/components/acmeda/config_flow.py index f421fa9ca25..b8913e31f2a 100644 --- a/homeassistant/components/acmeda/config_flow.py +++ b/homeassistant/components/acmeda/config_flow.py @@ -1,6 +1,7 @@ """Config flow for Rollease Acmeda Automate Pulse Hub.""" +from __future__ import annotations + import asyncio -from typing import Dict, Optional import aiopulse import async_timeout @@ -19,7 +20,7 @@ class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize the config flow.""" - self.discovered_hubs: Optional[Dict[str, aiopulse.Hub]] = None + self.discovered_hubs: dict[str, aiopulse.Hub] | None = None async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" diff --git a/homeassistant/components/acmeda/hub.py b/homeassistant/components/acmeda/hub.py index 0b74b874dcc..e156ee5cb78 100644 --- a/homeassistant/components/acmeda/hub.py +++ b/homeassistant/components/acmeda/hub.py @@ -1,6 +1,7 @@ """Code to handle a Pulse Hub.""" +from __future__ import annotations + import asyncio -from typing import Optional import aiopulse @@ -17,7 +18,7 @@ class PulseHub: """Initialize the system.""" self.config_entry = config_entry self.hass = hass - self.api: Optional[aiopulse.Hub] = None + self.api: aiopulse.Hub | None = None self.tasks = [] self.current_rollers = {} self.cleanup_callbacks = [] diff --git a/homeassistant/components/alarm_control_panel/device_action.py b/homeassistant/components/alarm_control_panel/device_action.py index 0dc16fdcf42..67637550db2 100644 --- a/homeassistant/components/alarm_control_panel/device_action.py +++ b/homeassistant/components/alarm_control_panel/device_action.py @@ -1,5 +1,5 @@ """Provides device automations for Alarm control panel.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -41,7 +41,7 @@ ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Alarm control panel devices.""" registry = await entity_registry.async_get_registry(hass) actions = [] @@ -109,7 +109,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/alarm_control_panel/device_condition.py b/homeassistant/components/alarm_control_panel/device_condition.py index e5b3ec6aeee..3817cf37b45 100644 --- a/homeassistant/components/alarm_control_panel/device_condition.py +++ b/homeassistant/components/alarm_control_panel/device_condition.py @@ -1,5 +1,5 @@ """Provide the device automations for Alarm control panel.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -58,7 +58,7 @@ CONDITION_SCHEMA = DEVICE_CONDITION_BASE_SCHEMA.extend( async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions for Alarm control panel devices.""" registry = await entity_registry.async_get_registry(hass) conditions = [] diff --git a/homeassistant/components/alarm_control_panel/device_trigger.py b/homeassistant/components/alarm_control_panel/device_trigger.py index 5669340c2ce..9ab28e3e863 100644 --- a/homeassistant/components/alarm_control_panel/device_trigger.py +++ b/homeassistant/components/alarm_control_panel/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Alarm control panel.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -48,7 +48,7 @@ TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Alarm control panel devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/alarm_control_panel/reproduce_state.py b/homeassistant/components/alarm_control_panel/reproduce_state.py index 9e7d8e6f1a7..3021d4421d9 100644 --- a/homeassistant/components/alarm_control_panel/reproduce_state.py +++ b/homeassistant/components/alarm_control_panel/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Alarm control panel state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -39,8 +41,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -83,8 +85,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Alarm control panel states.""" await asyncio.gather( diff --git a/homeassistant/components/alert/reproduce_state.py b/homeassistant/components/alert/reproduce_state.py index 7645b642d59..de40649854e 100644 --- a/homeassistant/components/alert/reproduce_state.py +++ b/homeassistant/components/alert/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Alert state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -24,8 +26,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -61,8 +63,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Alert states.""" # Reproduce states in parallel. diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index acfba91a933..69acf95e207 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -1,6 +1,7 @@ """Alexa capabilities.""" +from __future__ import annotations + import logging -from typing import List, Optional from homeassistant.components import ( cover, @@ -72,7 +73,7 @@ class AlexaCapability: supported_locales = {"en-US"} - def __init__(self, entity: State, instance: Optional[str] = None): + def __init__(self, entity: State, instance: str | None = None): """Initialize an Alexa capability.""" self.entity = entity self.instance = instance @@ -82,7 +83,7 @@ class AlexaCapability: raise NotImplementedError @staticmethod - def properties_supported() -> List[dict]: + def properties_supported() -> list[dict]: """Return what properties this entity supports.""" return [] diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index c05d9641b9a..71f4e41a810 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -1,6 +1,8 @@ """Alexa entity adapters.""" +from __future__ import annotations + import logging -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING from homeassistant.components import ( alarm_control_panel, @@ -300,7 +302,7 @@ class AlexaEntity: Raises _UnsupportedInterface. """ - def interfaces(self) -> List[AlexaCapability]: + def interfaces(self) -> list[AlexaCapability]: """Return a list of supported interfaces. Used for discovery. The list should contain AlexaInterface instances. @@ -353,7 +355,7 @@ class AlexaEntity: @callback -def async_get_entities(hass, config) -> List[AlexaEntity]: +def async_get_entities(hass, config) -> list[AlexaEntity]: """Return all entities that are supported by Alexa.""" entities = [] for state in hass.states.async_all(): diff --git a/homeassistant/components/alexa/state_report.py b/homeassistant/components/alexa/state_report.py index c34dc34f0dd..712a08ac6b9 100644 --- a/homeassistant/components/alexa/state_report.py +++ b/homeassistant/components/alexa/state_report.py @@ -1,8 +1,9 @@ """Alexa state report code.""" +from __future__ import annotations + import asyncio import json import logging -from typing import Optional import aiohttp import async_timeout @@ -45,8 +46,8 @@ async def async_enable_proactive_mode(hass, smart_home_config): async def async_entity_state_listener( changed_entity: str, - old_state: Optional[State], - new_state: Optional[State], + old_state: State | None, + new_state: State | None, ): if not hass.is_running: return diff --git a/homeassistant/components/almond/__init__.py b/homeassistant/components/almond/__init__.py index b9f75ff8c6b..554a4aa47bc 100644 --- a/homeassistant/components/almond/__init__.py +++ b/homeassistant/components/almond/__init__.py @@ -1,9 +1,10 @@ """Support for Almond.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging import time -from typing import Optional from aiohttp import ClientError, ClientSession import async_timeout @@ -281,7 +282,7 @@ class AlmondAgent(conversation.AbstractConversationAgent): return True async def async_process( - self, text: str, context: Context, conversation_id: Optional[str] = None + self, text: str, context: Context, conversation_id: str | None = None ) -> intent.IntentResponse: """Process a sentence.""" response = await self.api.async_converse_text(text, conversation_id) diff --git a/homeassistant/components/arcam_fmj/device_trigger.py b/homeassistant/components/arcam_fmj/device_trigger.py index c03a082c149..060c56e5953 100644 --- a/homeassistant/components/arcam_fmj/device_trigger.py +++ b/homeassistant/components/arcam_fmj/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Arcam FMJ Receiver control.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -28,7 +28,7 @@ TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Arcam FMJ Receiver control devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/arris_tg2492lg/device_tracker.py b/homeassistant/components/arris_tg2492lg/device_tracker.py index e63bef9c108..1011d76f8aa 100644 --- a/homeassistant/components/arris_tg2492lg/device_tracker.py +++ b/homeassistant/components/arris_tg2492lg/device_tracker.py @@ -1,5 +1,5 @@ """Support for Arris TG2492LG router.""" -from typing import List +from __future__ import annotations from arris_tg2492lg import ConnectBox, Device import voluptuous as vol @@ -36,7 +36,7 @@ class ArrisDeviceScanner(DeviceScanner): def __init__(self, connect_box: ConnectBox): """Initialize the scanner.""" self.connect_box = connect_box - self.last_results: List[Device] = [] + self.last_results: list[Device] = [] def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" diff --git a/homeassistant/components/asuswrt/device_tracker.py b/homeassistant/components/asuswrt/device_tracker.py index f5cb9b934e3..1ef68f9d65d 100644 --- a/homeassistant/components/asuswrt/device_tracker.py +++ b/homeassistant/components/asuswrt/device_tracker.py @@ -1,5 +1,5 @@ """Support for ASUSWRT routers.""" -from typing import Dict +from __future__ import annotations from homeassistant.components.device_tracker import SOURCE_TYPE_ROUTER from homeassistant.components.device_tracker.config_entry import ScannerEntity @@ -103,12 +103,12 @@ class AsusWrtDevice(ScannerEntity): return self._icon @property - def extra_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> dict[str, any]: """Return the attributes.""" return self._attrs @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "connections": {(CONNECTION_NETWORK_MAC, self._mac)}, diff --git a/homeassistant/components/asuswrt/router.py b/homeassistant/components/asuswrt/router.py index 4c92ee2ef67..550e4c8fc16 100644 --- a/homeassistant/components/asuswrt/router.py +++ b/homeassistant/components/asuswrt/router.py @@ -1,7 +1,9 @@ """Represent the AsusWrt router.""" +from __future__ import annotations + from datetime import datetime, timedelta import logging -from typing import Any, Dict, Optional +from typing import Any from aioasuswrt.asuswrt import AsusWrt @@ -74,7 +76,7 @@ class AsusWrtSensorDataHandler: async def _get_bytes(self): """Fetch byte information from the router.""" - ret_dict: Dict[str, Any] = {} + ret_dict: dict[str, Any] = {} try: datas = await self._api.async_get_bytes_total() except OSError as exc: @@ -87,7 +89,7 @@ class AsusWrtSensorDataHandler: async def _get_rates(self): """Fetch rates information from the router.""" - ret_dict: Dict[str, Any] = {} + ret_dict: dict[str, Any] = {} try: rates = await self._api.async_get_current_transfer_rates() except OSError as exc: @@ -194,12 +196,12 @@ class AsusWrtRouter: self._protocol = entry.data[CONF_PROTOCOL] self._host = entry.data[CONF_HOST] - self._devices: Dict[str, Any] = {} + self._devices: dict[str, Any] = {} self._connected_devices = 0 self._connect_error = False self._sensors_data_handler: AsusWrtSensorDataHandler = None - self._sensors_coordinator: Dict[str, Any] = {} + self._sensors_coordinator: dict[str, Any] = {} self._on_close = [] @@ -245,7 +247,7 @@ class AsusWrtRouter: async_track_time_interval(self.hass, self.update_all, SCAN_INTERVAL) ) - async def update_all(self, now: Optional[datetime] = None) -> None: + async def update_all(self, now: datetime | None = None) -> None: """Update all AsusWrt platforms.""" await self.update_devices() @@ -353,7 +355,7 @@ class AsusWrtRouter: """Add a function to call when router is closed.""" self._on_close.append(func) - def update_options(self, new_options: Dict) -> bool: + def update_options(self, new_options: dict) -> bool: """Update router options.""" req_reload = False for name, new_opt in new_options.items(): @@ -367,7 +369,7 @@ class AsusWrtRouter: return req_reload @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return the device information.""" return { "identifiers": {(DOMAIN, "AsusWRT")}, @@ -392,12 +394,12 @@ class AsusWrtRouter: return self._host @property - def devices(self) -> Dict[str, Any]: + def devices(self) -> dict[str, Any]: """Return devices.""" return self._devices @property - def sensors_coordinator(self) -> Dict[str, Any]: + def sensors_coordinator(self) -> dict[str, Any]: """Return sensors coordinators.""" return self._sensors_coordinator @@ -407,7 +409,7 @@ class AsusWrtRouter: return self._api -def get_api(conf: Dict, options: Optional[Dict] = None) -> AsusWrt: +def get_api(conf: dict, options: dict | None = None) -> AsusWrt: """Get the AsusWrt API.""" opt = options or {} diff --git a/homeassistant/components/asuswrt/sensor.py b/homeassistant/components/asuswrt/sensor.py index 0cd427d3b64..7dc8208ee67 100644 --- a/homeassistant/components/asuswrt/sensor.py +++ b/homeassistant/components/asuswrt/sensor.py @@ -1,7 +1,8 @@ """Asuswrt status sensors.""" +from __future__ import annotations + import logging from numbers import Number -from typing import Dict from homeassistant.config_entries import ConfigEntry from homeassistant.const import DATA_GIGABYTES, DATA_RATE_MEGABITS_PER_SECOND @@ -104,7 +105,7 @@ class AsusWrtSensor(CoordinatorEntity): coordinator: DataUpdateCoordinator, router: AsusWrtRouter, sensor_type: str, - sensor: Dict[str, any], + sensor: dict[str, any], ) -> None: """Initialize a AsusWrt sensor.""" super().__init__(coordinator) @@ -159,11 +160,11 @@ class AsusWrtSensor(CoordinatorEntity): return self._device_class @property - def extra_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> dict[str, any]: """Return the attributes.""" return {"hostname": self._router.host} @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return self._router.device_info diff --git a/homeassistant/components/atag/climate.py b/homeassistant/components/atag/climate.py index a2aa5cf16e4..da7e6a14a73 100644 --- a/homeassistant/components/atag/climate.py +++ b/homeassistant/components/atag/climate.py @@ -1,5 +1,5 @@ """Initialization of ATAG One climate platform.""" -from typing import List, Optional +from __future__ import annotations from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( @@ -43,46 +43,46 @@ class AtagThermostat(AtagEntity, ClimateEntity): return SUPPORT_FLAGS @property - def hvac_mode(self) -> Optional[str]: + def hvac_mode(self) -> str | None: """Return hvac operation ie. heat, cool mode.""" if self.coordinator.data.climate.hvac_mode in HVAC_MODES: return self.coordinator.data.climate.hvac_mode return None @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return HVAC_MODES @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation.""" is_active = self.coordinator.data.climate.status return CURRENT_HVAC_HEAT if is_active else CURRENT_HVAC_IDLE @property - def temperature_unit(self) -> Optional[str]: + def temperature_unit(self) -> str | None: """Return the unit of measurement.""" return self.coordinator.data.climate.temp_unit @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self.coordinator.data.climate.temperature @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return self.coordinator.data.climate.target_temperature @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., auto, manual, fireplace, extend, etc.""" preset = self.coordinator.data.climate.preset_mode return PRESET_INVERTED.get(preset) @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" return list(PRESET_MAP.keys()) diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index 4ddf82cc022..7381be5e9de 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -114,8 +114,9 @@ Result will be a long-lived access token: } """ +from __future__ import annotations + from datetime import timedelta -from typing import Union import uuid from aiohttp import web @@ -183,7 +184,7 @@ RESULT_TYPE_USER = "user" @bind_hass def create_auth_code( - hass, client_id: str, credential_or_user: Union[Credentials, User] + hass, client_id: str, credential_or_user: Credentials | User ) -> str: """Create an authorization code to fetch tokens.""" return hass.data[DOMAIN](client_id, credential_or_user) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index b769b0329cc..425ffe17979 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -1,6 +1,8 @@ """Allow to set up simple automation rules via the config file.""" +from __future__ import annotations + import logging -from typing import Any, Awaitable, Callable, Dict, List, Optional, Set, Union, cast +from typing import Any, Awaitable, Callable, Dict, cast import voluptuous as vol from voluptuous.humanize import humanize_error @@ -109,7 +111,7 @@ def is_on(hass, entity_id): @callback -def automations_with_entity(hass: HomeAssistant, entity_id: str) -> List[str]: +def automations_with_entity(hass: HomeAssistant, entity_id: str) -> list[str]: """Return all automations that reference the entity.""" if DOMAIN not in hass.data: return [] @@ -124,7 +126,7 @@ def automations_with_entity(hass: HomeAssistant, entity_id: str) -> List[str]: @callback -def entities_in_automation(hass: HomeAssistant, entity_id: str) -> List[str]: +def entities_in_automation(hass: HomeAssistant, entity_id: str) -> list[str]: """Return all entities in a scene.""" if DOMAIN not in hass.data: return [] @@ -140,7 +142,7 @@ def entities_in_automation(hass: HomeAssistant, entity_id: str) -> List[str]: @callback -def automations_with_device(hass: HomeAssistant, device_id: str) -> List[str]: +def automations_with_device(hass: HomeAssistant, device_id: str) -> list[str]: """Return all automations that reference the device.""" if DOMAIN not in hass.data: return [] @@ -155,7 +157,7 @@ def automations_with_device(hass: HomeAssistant, device_id: str) -> List[str]: @callback -def devices_in_automation(hass: HomeAssistant, entity_id: str) -> List[str]: +def devices_in_automation(hass: HomeAssistant, entity_id: str) -> list[str]: """Return all devices in a scene.""" if DOMAIN not in hass.data: return [] @@ -249,8 +251,8 @@ class AutomationEntity(ToggleEntity, RestoreEntity): self.action_script.change_listener = self.async_write_ha_state self._initial_state = initial_state self._is_enabled = False - self._referenced_entities: Optional[Set[str]] = None - self._referenced_devices: Optional[Set[str]] = None + self._referenced_entities: set[str] | None = None + self._referenced_devices: set[str] | None = None self._logger = LOGGER self._variables: ScriptVariables = variables self._trigger_variables: ScriptVariables = trigger_variables @@ -509,7 +511,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity): async def _async_attach_triggers( self, home_assistant_start: bool - ) -> Optional[Callable[[], None]]: + ) -> Callable[[], None] | None: """Set up the triggers.""" def log_cb(level, msg, **kwargs): @@ -539,7 +541,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity): async def _async_process_config( hass: HomeAssistant, - config: Dict[str, Any], + config: dict[str, Any], component: EntityComponent, ) -> bool: """Process config and add automations. @@ -550,7 +552,7 @@ async def _async_process_config( blueprints_used = False for config_key in extract_domain_configs(config, DOMAIN): - conf: List[Union[Dict[str, Any], blueprint.BlueprintInputs]] = config[ # type: ignore + conf: list[dict[str, Any] | blueprint.BlueprintInputs] = config[ # type: ignore config_key ] @@ -680,7 +682,7 @@ async def _async_process_if(hass, name, config, p_config): @callback -def _trigger_extract_device(trigger_conf: dict) -> Optional[str]: +def _trigger_extract_device(trigger_conf: dict) -> str | None: """Extract devices from a trigger config.""" if trigger_conf[CONF_PLATFORM] != "device": return None @@ -689,7 +691,7 @@ def _trigger_extract_device(trigger_conf: dict) -> Optional[str]: @callback -def _trigger_extract_entities(trigger_conf: dict) -> List[str]: +def _trigger_extract_entities(trigger_conf: dict) -> list[str]: """Extract entities from a trigger config.""" if trigger_conf[CONF_PLATFORM] in ("state", "numeric_state"): return trigger_conf[CONF_ENTITY_ID] diff --git a/homeassistant/components/automation/reproduce_state.py b/homeassistant/components/automation/reproduce_state.py index bcd0cc4e585..efe83960f41 100644 --- a/homeassistant/components/automation/reproduce_state.py +++ b/homeassistant/components/automation/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Automation state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -24,8 +26,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -60,8 +62,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Automation states.""" await asyncio.gather( diff --git a/homeassistant/components/automation/trace.py b/homeassistant/components/automation/trace.py index 68cca5a4a41..79fa4c844bc 100644 --- a/homeassistant/components/automation/trace.py +++ b/homeassistant/components/automation/trace.py @@ -1,11 +1,13 @@ """Trace support for automation.""" +from __future__ import annotations + from collections import OrderedDict from contextlib import contextmanager import datetime as dt from datetime import timedelta from itertools import count import logging -from typing import Any, Awaitable, Callable, Deque, Dict, Optional +from typing import Any, Awaitable, Callable, Deque from homeassistant.core import Context, HomeAssistant, callback from homeassistant.helpers.json import JSONEncoder as HAJSONEncoder @@ -30,28 +32,28 @@ class AutomationTrace: def __init__( self, - unique_id: Optional[str], - config: Dict[str, Any], + unique_id: str | None, + config: dict[str, Any], context: Context, ): """Container for automation trace.""" - self._action_trace: Optional[Dict[str, Deque[TraceElement]]] = None - self._condition_trace: Optional[Dict[str, Deque[TraceElement]]] = None - self._config: Dict[str, Any] = config + self._action_trace: dict[str, Deque[TraceElement]] | None = None + self._condition_trace: dict[str, Deque[TraceElement]] | None = None + self._config: dict[str, Any] = config self.context: Context = context - self._error: Optional[Exception] = None + self._error: Exception | None = None self._state: str = "running" self.run_id: str = str(next(self._run_ids)) - self._timestamp_finish: Optional[dt.datetime] = None + self._timestamp_finish: dt.datetime | None = None self._timestamp_start: dt.datetime = dt_util.utcnow() - self._unique_id: Optional[str] = unique_id - self._variables: Optional[Dict[str, Any]] = None + self._unique_id: str | None = unique_id + self._variables: dict[str, Any] | None = None - def set_action_trace(self, trace: Dict[str, Deque[TraceElement]]) -> None: + def set_action_trace(self, trace: dict[str, Deque[TraceElement]]) -> None: """Set action trace.""" self._action_trace = trace - def set_condition_trace(self, trace: Dict[str, Deque[TraceElement]]) -> None: + def set_condition_trace(self, trace: dict[str, Deque[TraceElement]]) -> None: """Set condition trace.""" self._condition_trace = trace @@ -59,7 +61,7 @@ class AutomationTrace: """Set error.""" self._error = ex - def set_variables(self, variables: Dict[str, Any]) -> None: + def set_variables(self, variables: dict[str, Any]) -> None: """Set variables.""" self._variables = variables @@ -68,7 +70,7 @@ class AutomationTrace: self._timestamp_finish = dt_util.utcnow() self._state = "stopped" - def as_dict(self) -> Dict[str, Any]: + def as_dict(self) -> dict[str, Any]: """Return dictionary version of this AutomationTrace.""" result = self.as_short_dict() @@ -96,7 +98,7 @@ class AutomationTrace: result["error"] = str(self._error) return result - def as_short_dict(self) -> Dict[str, Any]: + def as_short_dict(self) -> dict[str, Any]: """Return a brief dictionary version of this AutomationTrace.""" last_action = None diff --git a/homeassistant/components/awair/__init__.py b/homeassistant/components/awair/__init__.py index 56af5d2b662..eefc7445d36 100644 --- a/homeassistant/components/awair/__init__.py +++ b/homeassistant/components/awair/__init__.py @@ -1,7 +1,8 @@ """The awair component.""" +from __future__ import annotations from asyncio import gather -from typing import Any, Optional +from typing import Any from async_timeout import timeout from python_awair import Awair @@ -70,7 +71,7 @@ class AwairDataUpdateCoordinator(DataUpdateCoordinator): super().__init__(hass, LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL) - async def _async_update_data(self) -> Optional[Any]: + async def _async_update_data(self) -> Any | None: """Update data via Awair client library.""" with timeout(API_TIMEOUT): try: diff --git a/homeassistant/components/awair/config_flow.py b/homeassistant/components/awair/config_flow.py index c28ac55f216..76c7cbca3a9 100644 --- a/homeassistant/components/awair/config_flow.py +++ b/homeassistant/components/awair/config_flow.py @@ -1,6 +1,5 @@ """Config flow for Awair.""" - -from typing import Optional +from __future__ import annotations from python_awair import Awair from python_awair.exceptions import AuthError, AwairError @@ -36,7 +35,7 @@ class AwairFlowHandler(ConfigFlow, domain=DOMAIN): data={CONF_ACCESS_TOKEN: conf[CONF_ACCESS_TOKEN]}, ) - async def async_step_user(self, user_input: Optional[dict] = None): + async def async_step_user(self, user_input: dict | None = None): """Handle a flow initialized by the user.""" errors = {} @@ -61,7 +60,7 @@ class AwairFlowHandler(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, user_input: Optional[dict] = None): + async def async_step_reauth(self, user_input: dict | None = None): """Handle re-auth if token invalid.""" errors = {} diff --git a/homeassistant/components/awair/sensor.py b/homeassistant/components/awair/sensor.py index d685b3ec17b..81dc0562fc4 100644 --- a/homeassistant/components/awair/sensor.py +++ b/homeassistant/components/awair/sensor.py @@ -1,6 +1,7 @@ """Support for Awair sensors.""" +from __future__ import annotations -from typing import Callable, List, Optional +from typing import Callable from python_awair.devices import AwairDevice import voluptuous as vol @@ -55,13 +56,13 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigType, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ): """Set up Awair sensor entity based on a config entry.""" coordinator = hass.data[DOMAIN][config_entry.entry_id] sensors = [] - data: List[AwairResult] = coordinator.data.values() + data: list[AwairResult] = coordinator.data.values() for result in data: if result.air_data: sensors.append(AwairSensor(API_SCORE, result.device, coordinator)) @@ -228,9 +229,9 @@ class AwairSensor(CoordinatorEntity): return info @property - def _air_data(self) -> Optional[AwairResult]: + def _air_data(self) -> AwairResult | None: """Return the latest data for our device, or None.""" - result: Optional[AwairResult] = self.coordinator.data.get(self._device.uuid) + result: AwairResult | None = self.coordinator.data.get(self._device.uuid) if result: return result.air_data diff --git a/homeassistant/components/azure_devops/__init__.py b/homeassistant/components/azure_devops/__init__.py index f72a4c44918..b856dc5aa00 100644 --- a/homeassistant/components/azure_devops/__init__.py +++ b/homeassistant/components/azure_devops/__init__.py @@ -1,6 +1,8 @@ """Support for Azure DevOps.""" +from __future__ import annotations + import logging -from typing import Any, Dict +from typing import Any from aioazuredevops.client import DevOpsClient import aiohttp @@ -114,7 +116,7 @@ class AzureDevOpsDeviceEntity(AzureDevOpsEntity): """Defines a Azure DevOps device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this Azure DevOps instance.""" return { "identifiers": { diff --git a/homeassistant/components/azure_devops/sensor.py b/homeassistant/components/azure_devops/sensor.py index a26f0c65f9c..1d30bfcb9f9 100644 --- a/homeassistant/components/azure_devops/sensor.py +++ b/homeassistant/components/azure_devops/sensor.py @@ -1,7 +1,8 @@ """Support for Azure DevOps sensors.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import List from aioazuredevops.builds import DevOpsBuild from aioazuredevops.client import DevOpsClient @@ -39,7 +40,7 @@ async def async_setup_entry( sensors = [] try: - builds: List[DevOpsBuild] = await client.get_builds( + builds: list[DevOpsBuild] = await client.get_builds( organization, project, BUILDS_QUERY ) except aiohttp.ClientError as exception: diff --git a/homeassistant/components/azure_event_hub/__init__.py b/homeassistant/components/azure_event_hub/__init__.py index 3b44c6423be..0473c4ff5a7 100644 --- a/homeassistant/components/azure_event_hub/__init__.py +++ b/homeassistant/components/azure_event_hub/__init__.py @@ -1,9 +1,11 @@ """Support for Azure Event Hubs.""" +from __future__ import annotations + import asyncio import json import logging import time -from typing import Any, Dict +from typing import Any from azure.eventhub import EventData from azure.eventhub.aio import EventHubProducerClient, EventHubSharedKeyCredential @@ -95,7 +97,7 @@ class AzureEventHub: def __init__( self, hass: HomeAssistant, - client_args: Dict[str, Any], + client_args: dict[str, Any], conn_str_client: bool, entities_filter: vol.Schema, send_interval: int, diff --git a/homeassistant/components/bbox/device_tracker.py b/homeassistant/components/bbox/device_tracker.py index 8097c11eb89..130d315197b 100644 --- a/homeassistant/components/bbox/device_tracker.py +++ b/homeassistant/components/bbox/device_tracker.py @@ -1,8 +1,9 @@ """Support for French FAI Bouygues Bbox routers.""" +from __future__ import annotations + from collections import namedtuple from datetime import timedelta import logging -from typing import List import pybbox import voluptuous as vol @@ -47,7 +48,7 @@ class BboxDeviceScanner(DeviceScanner): self.host = config[CONF_HOST] """Initialize the scanner.""" - self.last_results: List[Device] = [] + self.last_results: list[Device] = [] self.success_init = self._update_info() _LOGGER.info("Scanner initialized") diff --git a/homeassistant/components/binary_sensor/device_condition.py b/homeassistant/components/binary_sensor/device_condition.py index 999a62b3a80..8c506634200 100644 --- a/homeassistant/components/binary_sensor/device_condition.py +++ b/homeassistant/components/binary_sensor/device_condition.py @@ -1,5 +1,5 @@ """Implement device conditions for binary sensor.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -205,9 +205,9 @@ CONDITION_SCHEMA = cv.DEVICE_CONDITION_BASE_SCHEMA.extend( async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions.""" - conditions: List[Dict[str, str]] = [] + conditions: list[dict[str, str]] = [] entity_registry = await async_get_registry(hass) entries = [ entry diff --git a/homeassistant/components/binary_sensor/significant_change.py b/homeassistant/components/binary_sensor/significant_change.py index bc2dba04f09..8421483ba0c 100644 --- a/homeassistant/components/binary_sensor/significant_change.py +++ b/homeassistant/components/binary_sensor/significant_change.py @@ -1,5 +1,7 @@ """Helper to test significant Binary Sensor state changes.""" -from typing import Any, Optional +from __future__ import annotations + +from typing import Any from homeassistant.core import HomeAssistant, callback @@ -12,7 +14,7 @@ def async_check_significant_change( new_state: str, new_attrs: dict, **kwargs: Any, -) -> Optional[bool]: +) -> bool | None: """Test if state significantly changed.""" if old_state != new_state: return True diff --git a/homeassistant/components/blueprint/importer.py b/homeassistant/components/blueprint/importer.py index 217851df980..99dffb114e1 100644 --- a/homeassistant/components/blueprint/importer.py +++ b/homeassistant/components/blueprint/importer.py @@ -1,8 +1,9 @@ """Import logic for blueprint.""" +from __future__ import annotations + from dataclasses import dataclass import html import re -from typing import Optional import voluptuous as vol import yarl @@ -93,7 +94,7 @@ def _get_community_post_import_url(url: str) -> str: def _extract_blueprint_from_community_topic( url: str, topic: dict, -) -> Optional[ImportedBlueprint]: +) -> ImportedBlueprint | None: """Extract a blueprint from a community post JSON. Async friendly. @@ -136,7 +137,7 @@ def _extract_blueprint_from_community_topic( async def fetch_blueprint_from_community_post( hass: HomeAssistant, url: str -) -> Optional[ImportedBlueprint]: +) -> ImportedBlueprint | None: """Get blueprints from a community post url. Method can raise aiohttp client exceptions, vol.Invalid. diff --git a/homeassistant/components/blueprint/models.py b/homeassistant/components/blueprint/models.py index 84931a04310..797f9bd1512 100644 --- a/homeassistant/components/blueprint/models.py +++ b/homeassistant/components/blueprint/models.py @@ -1,9 +1,11 @@ """Blueprint models.""" +from __future__ import annotations + import asyncio import logging import pathlib import shutil -from typing import Any, Dict, List, Optional, Union +from typing import Any from awesomeversion import AwesomeVersion import voluptuous as vol @@ -49,8 +51,8 @@ class Blueprint: self, data: dict, *, - path: Optional[str] = None, - expected_domain: Optional[str] = None, + path: str | None = None, + expected_domain: str | None = None, ) -> None: """Initialize a blueprint.""" try: @@ -95,7 +97,7 @@ class Blueprint: """Return blueprint metadata.""" return self.data[CONF_BLUEPRINT] - def update_metadata(self, *, source_url: Optional[str] = None) -> None: + def update_metadata(self, *, source_url: str | None = None) -> None: """Update metadata.""" if source_url is not None: self.data[CONF_BLUEPRINT][CONF_SOURCE_URL] = source_url @@ -105,7 +107,7 @@ class Blueprint: return yaml.dump(self.data) @callback - def validate(self) -> Optional[List[str]]: + def validate(self) -> list[str] | None: """Test if the Home Assistant installation supports this blueprint. Return list of errors if not valid. @@ -126,7 +128,7 @@ class BlueprintInputs: """Inputs for a blueprint.""" def __init__( - self, blueprint: Blueprint, config_with_inputs: Dict[str, Any] + self, blueprint: Blueprint, config_with_inputs: dict[str, Any] ) -> None: """Instantiate a blueprint inputs object.""" self.blueprint = blueprint @@ -218,7 +220,7 @@ class DomainBlueprints: blueprint_data, expected_domain=self.domain, path=blueprint_path ) - def _load_blueprints(self) -> Dict[str, Union[Blueprint, BlueprintException]]: + def _load_blueprints(self) -> dict[str, Blueprint | BlueprintException]: """Load all the blueprints.""" blueprint_folder = pathlib.Path( self.hass.config.path(BLUEPRINT_FOLDER, self.domain) @@ -243,7 +245,7 @@ class DomainBlueprints: async def async_get_blueprints( self, - ) -> Dict[str, Union[Blueprint, BlueprintException]]: + ) -> dict[str, Blueprint | BlueprintException]: """Get all the blueprints.""" async with self._load_lock: return await self.hass.async_add_executor_job(self._load_blueprints) diff --git a/homeassistant/components/blueprint/websocket_api.py b/homeassistant/components/blueprint/websocket_api.py index 05ae2816696..b8a4c214a2e 100644 --- a/homeassistant/components/blueprint/websocket_api.py +++ b/homeassistant/components/blueprint/websocket_api.py @@ -1,5 +1,5 @@ """Websocket API for blueprint.""" -from typing import Dict, Optional +from __future__ import annotations import async_timeout import voluptuous as vol @@ -33,7 +33,7 @@ def async_setup(hass: HomeAssistant): ) async def ws_list_blueprints(hass, connection, msg): """List available blueprints.""" - domain_blueprints: Optional[Dict[str, models.DomainBlueprints]] = hass.data.get( + domain_blueprints: dict[str, models.DomainBlueprints] | None = hass.data.get( DOMAIN, {} ) results = {} @@ -102,7 +102,7 @@ async def ws_save_blueprint(hass, connection, msg): path = msg["path"] domain = msg["domain"] - domain_blueprints: Optional[Dict[str, models.DomainBlueprints]] = hass.data.get( + domain_blueprints: dict[str, models.DomainBlueprints] | None = hass.data.get( DOMAIN, {} ) @@ -149,7 +149,7 @@ async def ws_delete_blueprint(hass, connection, msg): path = msg["path"] domain = msg["domain"] - domain_blueprints: Optional[Dict[str, models.DomainBlueprints]] = hass.data.get( + domain_blueprints: dict[str, models.DomainBlueprints] | None = hass.data.get( DOMAIN, {} ) diff --git a/homeassistant/components/bluetooth_tracker/device_tracker.py b/homeassistant/components/bluetooth_tracker/device_tracker.py index 9bc2e630ba0..f00bd672892 100644 --- a/homeassistant/components/bluetooth_tracker/device_tracker.py +++ b/homeassistant/components/bluetooth_tracker/device_tracker.py @@ -1,7 +1,8 @@ """Tracking for bluetooth devices.""" +from __future__ import annotations + import asyncio import logging -from typing import List, Optional, Set, Tuple import bluetooth # pylint: disable=import-error from bt_proximity import BluetoothRSSI @@ -50,7 +51,7 @@ def is_bluetooth_device(device) -> bool: return device.mac and device.mac[:3].upper() == BT_PREFIX -def discover_devices(device_id: int) -> List[Tuple[str, str]]: +def discover_devices(device_id: int) -> list[tuple[str, str]]: """Discover Bluetooth devices.""" result = bluetooth.discover_devices( duration=8, @@ -79,7 +80,7 @@ async def see_device( ) -async def get_tracking_devices(hass: HomeAssistantType) -> Tuple[Set[str], Set[str]]: +async def get_tracking_devices(hass: HomeAssistantType) -> tuple[set[str], set[str]]: """ Load all known devices. @@ -90,17 +91,17 @@ async def get_tracking_devices(hass: HomeAssistantType) -> Tuple[Set[str], Set[s devices = await async_load_config(yaml_path, hass, 0) bluetooth_devices = [device for device in devices if is_bluetooth_device(device)] - devices_to_track: Set[str] = { + devices_to_track: set[str] = { device.mac[3:] for device in bluetooth_devices if device.track } - devices_to_not_track: Set[str] = { + devices_to_not_track: set[str] = { device.mac[3:] for device in bluetooth_devices if not device.track } return devices_to_track, devices_to_not_track -def lookup_name(mac: str) -> Optional[str]: +def lookup_name(mac: str) -> str | None: """Lookup a Bluetooth device name.""" _LOGGER.debug("Scanning %s", mac) return bluetooth.lookup_name(mac, timeout=5) diff --git a/homeassistant/components/bond/config_flow.py b/homeassistant/components/bond/config_flow.py index af889b803b5..71ad54ee35c 100644 --- a/homeassistant/components/bond/config_flow.py +++ b/homeassistant/components/bond/config_flow.py @@ -1,6 +1,8 @@ """Config flow for Bond integration.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional, Tuple +from typing import Any from aiohttp import ClientConnectionError, ClientResponseError from bond_api import Bond @@ -28,7 +30,7 @@ DISCOVERY_SCHEMA = vol.Schema({vol.Required(CONF_ACCESS_TOKEN): str}) TOKEN_SCHEMA = vol.Schema({}) -async def _validate_input(data: Dict[str, Any]) -> Tuple[str, str]: +async def _validate_input(data: dict[str, Any]) -> tuple[str, str]: """Validate the user input allows us to connect.""" bond = Bond(data[CONF_HOST], data[CONF_ACCESS_TOKEN]) @@ -60,7 +62,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize config flow.""" - self._discovered: Dict[str, str] = {} + self._discovered: dict[str, str] = {} async def _async_try_automatic_configure(self) -> None: """Try to auto configure the device. @@ -83,7 +85,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): _, hub_name = await _validate_input(self._discovered) self._discovered[CONF_NAME] = hub_name - async def async_step_zeroconf(self, discovery_info: DiscoveryInfoType) -> Dict[str, Any]: # type: ignore + async def async_step_zeroconf(self, discovery_info: DiscoveryInfoType) -> dict[str, Any]: # type: ignore """Handle a flow initialized by zeroconf discovery.""" name: str = discovery_info[CONF_NAME] host: str = discovery_info[CONF_HOST] @@ -106,8 +108,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_confirm() async def async_step_confirm( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle confirmation flow for discovered bond hub.""" errors = {} if user_input is not None: @@ -147,8 +149,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) async def async_step_user( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a flow initialized by the user.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/bond/cover.py b/homeassistant/components/bond/cover.py index 0c73bdbc8f9..60dcc4ec1f0 100644 --- a/homeassistant/components/bond/cover.py +++ b/homeassistant/components/bond/cover.py @@ -1,5 +1,7 @@ """Support for Bond covers.""" -from typing import Any, Callable, List, Optional +from __future__ import annotations + +from typing import Any, Callable from bond_api import Action, BPUPSubscriptions, DeviceType @@ -16,14 +18,14 @@ from .utils import BondDevice, BondHub async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Bond cover devices.""" data = hass.data[DOMAIN][entry.entry_id] hub: BondHub = data[HUB] bpup_subs: BPUPSubscriptions = data[BPUP_SUBS] - covers: List[Entity] = [ + covers: list[Entity] = [ BondCover(hub, device, bpup_subs) for device in hub.devices if device.type == DeviceType.MOTORIZED_SHADES @@ -41,19 +43,19 @@ class BondCover(BondEntity, CoverEntity): """Create HA entity representing Bond cover.""" super().__init__(hub, device, bpup_subs) - self._closed: Optional[bool] = None + self._closed: bool | None = None def _apply_state(self, state: dict) -> None: cover_open = state.get("open") self._closed = True if cover_open == 0 else False if cover_open == 1 else None @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Get device class.""" return DEVICE_CLASS_SHADE @property - def is_closed(self) -> Optional[bool]: + def is_closed(self) -> bool | None: """Return if the cover is closed or not.""" return self._closed diff --git a/homeassistant/components/bond/entity.py b/homeassistant/components/bond/entity.py index b56c87f692f..a676d99e9ad 100644 --- a/homeassistant/components/bond/entity.py +++ b/homeassistant/components/bond/entity.py @@ -1,9 +1,11 @@ """An abstract class common to all Bond entities.""" +from __future__ import annotations + from abc import abstractmethod from asyncio import Lock, TimeoutError as AsyncIOTimeoutError from datetime import timedelta import logging -from typing import Any, Dict, Optional +from typing import Any from aiohttp import ClientError from bond_api import BPUPSubscriptions @@ -29,7 +31,7 @@ class BondEntity(Entity): hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscriptions, - sub_device: Optional[str] = None, + sub_device: str | None = None, ): """Initialize entity with API and device info.""" self._hub = hub @@ -38,11 +40,11 @@ class BondEntity(Entity): self._sub_device = sub_device self._available = True self._bpup_subs = bpup_subs - self._update_lock: Optional[Lock] = None + self._update_lock: Lock | None = None self._initialized = False @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Get unique ID for the entity.""" hub_id = self._hub.bond_id device_id = self._device_id @@ -50,7 +52,7 @@ class BondEntity(Entity): return f"{hub_id}_{device_id}{sub_device_id}" @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Get entity name.""" if self._sub_device: sub_device_name = self._sub_device.replace("_", " ").title() @@ -63,7 +65,7 @@ class BondEntity(Entity): return False @property - def device_info(self) -> Optional[Dict[str, Any]]: + def device_info(self) -> dict[str, Any] | None: """Get a an HA device representing this Bond controlled device.""" device_info = { ATTR_NAME: self.name, diff --git a/homeassistant/components/bond/fan.py b/homeassistant/components/bond/fan.py index 1c94a6f3e9a..817cf0f99a2 100644 --- a/homeassistant/components/bond/fan.py +++ b/homeassistant/components/bond/fan.py @@ -1,7 +1,9 @@ """Support for Bond fans.""" +from __future__ import annotations + import logging import math -from typing import Any, Callable, List, Optional, Tuple +from typing import Any, Callable from bond_api import Action, BPUPSubscriptions, DeviceType, Direction @@ -31,14 +33,14 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Bond fan devices.""" data = hass.data[DOMAIN][entry.entry_id] hub: BondHub = data[HUB] bpup_subs: BPUPSubscriptions = data[BPUP_SUBS] - fans: List[Entity] = [ + fans: list[Entity] = [ BondFan(hub, device, bpup_subs) for device in hub.devices if DeviceType.is_fan(device.type) @@ -54,9 +56,9 @@ class BondFan(BondEntity, FanEntity): """Create HA entity representing Bond fan.""" super().__init__(hub, device, bpup_subs) - self._power: Optional[bool] = None - self._speed: Optional[int] = None - self._direction: Optional[int] = None + self._power: bool | None = None + self._speed: int | None = None + self._direction: int | None = None def _apply_state(self, state: dict) -> None: self._power = state.get("power") @@ -75,7 +77,7 @@ class BondFan(BondEntity, FanEntity): return features @property - def _speed_range(self) -> Tuple[int, int]: + def _speed_range(self) -> tuple[int, int]: """Return the range of speeds.""" return (1, self._device.props.get("max_speed", 3)) @@ -92,7 +94,7 @@ class BondFan(BondEntity, FanEntity): return int_states_in_range(self._speed_range) @property - def current_direction(self) -> Optional[str]: + def current_direction(self) -> str | None: """Return fan rotation direction.""" direction = None if self._direction == Direction.FORWARD: @@ -125,9 +127,9 @@ class BondFan(BondEntity, FanEntity): async def async_turn_on( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs: Any, ) -> None: """Turn on the fan.""" diff --git a/homeassistant/components/bond/light.py b/homeassistant/components/bond/light.py index d2b06012ed3..8faab26f785 100644 --- a/homeassistant/components/bond/light.py +++ b/homeassistant/components/bond/light.py @@ -1,6 +1,8 @@ """Support for Bond lights.""" +from __future__ import annotations + import logging -from typing import Any, Callable, List, Optional +from typing import Any, Callable from bond_api import Action, BPUPSubscriptions, DeviceType @@ -24,14 +26,14 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Bond light devices.""" data = hass.data[DOMAIN][entry.entry_id] hub: BondHub = data[HUB] bpup_subs: BPUPSubscriptions = data[BPUP_SUBS] - fan_lights: List[Entity] = [ + fan_lights: list[Entity] = [ BondLight(hub, device, bpup_subs) for device in hub.devices if DeviceType.is_fan(device.type) @@ -39,31 +41,31 @@ async def async_setup_entry( and not (device.supports_up_light() and device.supports_down_light()) ] - fan_up_lights: List[Entity] = [ + fan_up_lights: list[Entity] = [ BondUpLight(hub, device, bpup_subs, "up_light") for device in hub.devices if DeviceType.is_fan(device.type) and device.supports_up_light() ] - fan_down_lights: List[Entity] = [ + fan_down_lights: list[Entity] = [ BondDownLight(hub, device, bpup_subs, "down_light") for device in hub.devices if DeviceType.is_fan(device.type) and device.supports_down_light() ] - fireplaces: List[Entity] = [ + fireplaces: list[Entity] = [ BondFireplace(hub, device, bpup_subs) for device in hub.devices if DeviceType.is_fireplace(device.type) ] - fp_lights: List[Entity] = [ + fp_lights: list[Entity] = [ BondLight(hub, device, bpup_subs, "light") for device in hub.devices if DeviceType.is_fireplace(device.type) and device.supports_light() ] - lights: List[Entity] = [ + lights: list[Entity] = [ BondLight(hub, device, bpup_subs) for device in hub.devices if DeviceType.is_light(device.type) @@ -83,11 +85,11 @@ class BondBaseLight(BondEntity, LightEntity): hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscriptions, - sub_device: Optional[str] = None, + sub_device: str | None = None, ): """Create HA entity representing Bond light.""" super().__init__(hub, device, bpup_subs, sub_device) - self._light: Optional[int] = None + self._light: int | None = None @property def is_on(self) -> bool: @@ -108,11 +110,11 @@ class BondLight(BondBaseLight, BondEntity, LightEntity): hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscriptions, - sub_device: Optional[str] = None, + sub_device: str | None = None, ): """Create HA entity representing Bond light.""" super().__init__(hub, device, bpup_subs, sub_device) - self._brightness: Optional[int] = None + self._brightness: int | None = None def _apply_state(self, state: dict) -> None: self._light = state.get("light") @@ -126,7 +128,7 @@ class BondLight(BondBaseLight, BondEntity, LightEntity): return 0 @property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the brightness of this light between 1..255.""" brightness_value = ( round(self._brightness * 255 / 100) if self._brightness else None @@ -194,9 +196,9 @@ class BondFireplace(BondEntity, LightEntity): """Create HA entity representing Bond fireplace.""" super().__init__(hub, device, bpup_subs) - self._power: Optional[bool] = None + self._power: bool | None = None # Bond flame level, 0-100 - self._flame: Optional[int] = None + self._flame: int | None = None def _apply_state(self, state: dict) -> None: self._power = state.get("power") @@ -230,11 +232,11 @@ class BondFireplace(BondEntity, LightEntity): await self._hub.bond.action(self._device.device_id, Action.turn_off()) @property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the flame of this fireplace converted to HA brightness between 0..255.""" return round(self._flame * 255 / 100) if self._flame else None @property - def icon(self) -> Optional[str]: + def icon(self) -> str | None: """Show fireplace icon for the entity.""" return "mdi:fireplace" if self._power == 1 else "mdi:fireplace-off" diff --git a/homeassistant/components/bond/switch.py b/homeassistant/components/bond/switch.py index abbc2e2b44c..23e99d6af30 100644 --- a/homeassistant/components/bond/switch.py +++ b/homeassistant/components/bond/switch.py @@ -1,5 +1,7 @@ """Support for Bond generic devices.""" -from typing import Any, Callable, List, Optional +from __future__ import annotations + +from typing import Any, Callable from bond_api import Action, BPUPSubscriptions, DeviceType @@ -16,14 +18,14 @@ from .utils import BondDevice, BondHub async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Bond generic devices.""" data = hass.data[DOMAIN][entry.entry_id] hub: BondHub = data[HUB] bpup_subs: BPUPSubscriptions = data[BPUP_SUBS] - switches: List[Entity] = [ + switches: list[Entity] = [ BondSwitch(hub, device, bpup_subs) for device in hub.devices if DeviceType.is_generic(device.type) @@ -39,7 +41,7 @@ class BondSwitch(BondEntity, SwitchEntity): """Create HA entity representing Bond generic device (switch).""" super().__init__(hub, device, bpup_subs) - self._power: Optional[bool] = None + self._power: bool | None = None def _apply_state(self, state: dict) -> None: self._power = state.get("power") diff --git a/homeassistant/components/bond/utils.py b/homeassistant/components/bond/utils.py index 28580ae415e..e3d951b8137 100644 --- a/homeassistant/components/bond/utils.py +++ b/homeassistant/components/bond/utils.py @@ -1,7 +1,9 @@ """Reusable utilities for the Bond component.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, List, Optional, Set, cast +from typing import Any, cast from aiohttp import ClientResponseError from bond_api import Action, Bond @@ -15,7 +17,7 @@ class BondDevice: """Helper device class to hold ID and attributes together.""" def __init__( - self, device_id: str, attrs: Dict[str, Any], props: Dict[str, Any] + self, device_id: str, attrs: dict[str, Any], props: dict[str, Any] ) -> None: """Create a helper device from ID and attributes returned by API.""" self.device_id = device_id @@ -41,17 +43,17 @@ class BondDevice: return cast(str, self._attrs["type"]) @property - def location(self) -> Optional[str]: + def location(self) -> str | None: """Get the location of this device.""" return self._attrs.get("location") @property - def template(self) -> Optional[str]: + def template(self) -> str | None: """Return this model template.""" return self._attrs.get("template") @property - def branding_profile(self) -> Optional[str]: + def branding_profile(self) -> str | None: """Return this branding profile.""" return self.props.get("branding_profile") @@ -60,9 +62,9 @@ class BondDevice: """Check if Trust State is turned on.""" return self.props.get("trust_state", False) - def _has_any_action(self, actions: Set[str]) -> bool: + def _has_any_action(self, actions: set[str]) -> bool: """Check to see if the device supports any of the actions.""" - supported_actions: List[str] = self._attrs["actions"] + supported_actions: list[str] = self._attrs["actions"] for action in supported_actions: if action in actions: return True @@ -101,11 +103,11 @@ class BondHub: def __init__(self, bond: Bond): """Initialize Bond Hub.""" self.bond: Bond = bond - self._bridge: Dict[str, Any] = {} - self._version: Dict[str, Any] = {} - self._devices: List[BondDevice] = [] + self._bridge: dict[str, Any] = {} + self._version: dict[str, Any] = {} + self._devices: list[BondDevice] = [] - async def setup(self, max_devices: Optional[int] = None) -> None: + async def setup(self, max_devices: int | None = None) -> None: """Read hub version information.""" self._version = await self.bond.version() _LOGGER.debug("Bond reported the following version info: %s", self._version) @@ -131,18 +133,18 @@ class BondHub: _LOGGER.debug("Bond reported the following bridge info: %s", self._bridge) @property - def bond_id(self) -> Optional[str]: + def bond_id(self) -> str | None: """Return unique Bond ID for this hub.""" # Old firmwares are missing the bondid return self._version.get("bondid") @property - def target(self) -> Optional[str]: + def target(self) -> str | None: """Return this hub target.""" return self._version.get("target") @property - def model(self) -> Optional[str]: + def model(self) -> str | None: """Return this hub model.""" return self._version.get("model") @@ -159,19 +161,19 @@ class BondHub: return cast(str, self._bridge["name"]) @property - def location(self) -> Optional[str]: + def location(self) -> str | None: """Get the location of this bridge.""" if not self.is_bridge and self._devices: return self._devices[0].location return self._bridge.get("location") @property - def fw_ver(self) -> Optional[str]: + def fw_ver(self) -> str | None: """Return this hub firmware version.""" return self._version.get("fw_ver") @property - def devices(self) -> List[BondDevice]: + def devices(self) -> list[BondDevice]: """Return a list of all devices controlled by this hub.""" return self._devices diff --git a/homeassistant/components/bsblan/climate.py b/homeassistant/components/bsblan/climate.py index a97c13c3424..4d83fb04dbe 100644 --- a/homeassistant/components/bsblan/climate.py +++ b/homeassistant/components/bsblan/climate.py @@ -1,7 +1,9 @@ """BSBLAN platform to control a compatible Climate Device.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable from bsblan import BSBLan, BSBLanError, Info, State @@ -74,7 +76,7 @@ BSBLAN_TO_HA_PRESET = { async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up BSBLan device based on a config entry.""" bsblan: BSBLan = hass.data[DOMAIN][entry.entry_id][DATA_BSBLAN_CLIENT] @@ -92,10 +94,10 @@ class BSBLanClimate(ClimateEntity): info: Info, ): """Initialize BSBLan climate device.""" - self._current_temperature: Optional[float] = None + self._current_temperature: float | None = None self._available = True - self._hvac_mode: Optional[str] = None - self._target_temperature: Optional[float] = None + self._hvac_mode: str | None = None + self._target_temperature: float | None = None self._temperature_unit = None self._preset_mode = None self._store_hvac_mode = None @@ -229,7 +231,7 @@ class BSBLanClimate(ClimateEntity): self._temperature_unit = state.current_temperature.unit @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this BSBLan device.""" return { ATTR_IDENTIFIERS: {(DOMAIN, self._info.device_identification)}, diff --git a/homeassistant/components/bsblan/config_flow.py b/homeassistant/components/bsblan/config_flow.py index dee04e6ef85..8ea597d2386 100644 --- a/homeassistant/components/bsblan/config_flow.py +++ b/homeassistant/components/bsblan/config_flow.py @@ -1,6 +1,8 @@ """Config flow for BSB-Lan integration.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from bsblan import BSBLan, BSBLanError, Info import voluptuous as vol @@ -26,8 +28,8 @@ class BSBLanFlowHandler(ConfigFlow, domain=DOMAIN): CONNECTION_CLASS = CONN_CLASS_LOCAL_POLL async def async_step_user( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" if user_input is None: return self._show_setup_form() @@ -59,7 +61,7 @@ class BSBLanFlowHandler(ConfigFlow, domain=DOMAIN): }, ) - def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: + def _show_setup_form(self, errors: dict | None = None) -> dict[str, Any]: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -78,9 +80,9 @@ class BSBLanFlowHandler(ConfigFlow, domain=DOMAIN): async def _get_bsblan_info( self, host: str, - username: Optional[str], - password: Optional[str], - passkey: Optional[str], + username: str | None, + password: str | None, + passkey: str | None, port: int, ) -> Info: """Get device information from an BSBLan device.""" diff --git a/homeassistant/components/buienradar/camera.py b/homeassistant/components/buienradar/camera.py index 44f86589b27..92f25b7ffc6 100644 --- a/homeassistant/components/buienradar/camera.py +++ b/homeassistant/components/buienradar/camera.py @@ -1,8 +1,9 @@ """Provide animated GIF loops of Buienradar imagery.""" +from __future__ import annotations + import asyncio from datetime import datetime, timedelta import logging -from typing import Optional import aiohttp import voluptuous as vol @@ -85,13 +86,13 @@ class BuienradarCam(Camera): # invariant: this condition is private to and owned by this instance. self._condition = asyncio.Condition() - self._last_image: Optional[bytes] = None + self._last_image: bytes | None = None # value of the last seen last modified header - self._last_modified: Optional[str] = None + self._last_modified: str | None = None # loading status self._loading = False # deadline for image refresh - self.delta after last successful load - self._deadline: Optional[datetime] = None + self._deadline: datetime | None = None self._unique_id = f"{self._dimension}_{self._country}" @@ -140,7 +141,7 @@ class BuienradarCam(Camera): _LOGGER.error("Failed to fetch image, %s", type(err)) return False - async def async_camera_image(self) -> Optional[bytes]: + async def async_camera_image(self) -> bytes | None: """ Return a still image response from the camera. From 7c0734bdd5a38fc192f3faf865ad731d7687ac0d Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 17 Mar 2021 23:43:55 +0100 Subject: [PATCH 434/831] Update typing 05 (#48038) --- homeassistant/components/calendar/__init__.py | 6 ++- .../components/canary/alarm_control_panel.py | 6 ++- homeassistant/components/canary/camera.py | 6 ++- .../components/canary/config_flow.py | 16 ++++---- homeassistant/components/canary/sensor.py | 6 ++- .../components/cast/home_assistant_cast.py | 8 ++-- homeassistant/components/cast/media_player.py | 17 ++++---- .../components/cert_expiry/__init__.py | 5 ++- .../components/climacell/__init__.py | 12 +++--- .../components/climacell/config_flow.py | 16 ++++---- homeassistant/components/climacell/weather.py | 24 +++++------ homeassistant/components/climate/__init__.py | 40 ++++++++++--------- .../components/climate/device_action.py | 6 +-- .../components/climate/device_condition.py | 4 +- .../components/climate/device_trigger.py | 4 +- .../components/climate/reproduce_state.py | 12 +++--- homeassistant/components/cloud/client.py | 18 +++++---- homeassistant/components/cloud/prefs.py | 9 +++-- homeassistant/components/cloud/stt.py | 14 +++---- homeassistant/components/cloud/utils.py | 6 ++- .../components/cloudflare/__init__.py | 5 ++- .../components/cloudflare/config_flow.py | 15 +++---- homeassistant/components/comfoconnect/fan.py | 5 ++- .../components/conversation/agent.py | 5 ++- .../components/conversation/default_agent.py | 5 ++- homeassistant/components/counter/__init__.py | 27 ++++++------- .../components/counter/reproduce_state.py | 12 +++--- .../components/cover/device_action.py | 6 +-- .../components/cover/device_condition.py | 8 ++-- .../components/cover/device_trigger.py | 4 +- .../components/cover/reproduce_state.py | 12 +++--- homeassistant/components/debugpy/__init__.py | 5 ++- homeassistant/components/deconz/climate.py | 4 +- homeassistant/components/deconz/logbook.py | 5 ++- homeassistant/components/demo/fan.py | 18 ++++----- homeassistant/components/demo/geo_location.py | 11 ++--- homeassistant/components/demo/stt.py | 14 +++---- .../components/device_automation/__init__.py | 6 ++- .../device_automation/toggle_entity.py | 16 ++++---- .../components/device_tracker/config_entry.py | 4 +- .../device_tracker/device_condition.py | 4 +- .../device_tracker/device_trigger.py | 4 +- .../components/device_tracker/legacy.py | 10 +++-- .../components/devolo_home_control/climate.py | 8 ++-- homeassistant/components/directv/__init__.py | 8 ++-- .../components/directv/config_flow.py | 16 ++++---- .../components/directv/media_player.py | 8 ++-- homeassistant/components/directv/remote.py | 6 ++- .../components/dlna_dmr/media_player.py | 5 ++- homeassistant/components/dsmr/config_flow.py | 6 ++- homeassistant/components/dsmr/sensor.py | 5 ++- homeassistant/components/dynalite/__init__.py | 9 +++-- homeassistant/components/dynalite/bridge.py | 13 +++--- .../components/dynalite/config_flow.py | 6 ++- .../components/dynalite/convert_config.py | 15 +++---- .../components/dynalite/dynalitebase.py | 6 ++- homeassistant/components/dyson/fan.py | 15 +++---- 57 files changed, 315 insertions(+), 251 deletions(-) diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index 478dafb3423..81ce49e9c94 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -1,8 +1,10 @@ """Support for Google Calendar event device sensors.""" +from __future__ import annotations + from datetime import timedelta import logging import re -from typing import Dict, List, cast +from typing import cast from aiohttp import web @@ -218,7 +220,7 @@ class CalendarListView(http.HomeAssistantView): async def get(self, request: web.Request) -> web.Response: """Retrieve calendar list.""" hass = request.app["hass"] - calendar_list: List[Dict[str, str]] = [] + calendar_list: list[dict[str, str]] = [] for entity in self.component.entities: state = hass.states.get(entity.entity_id) diff --git a/homeassistant/components/canary/alarm_control_panel.py b/homeassistant/components/canary/alarm_control_panel.py index d0beece8df2..933e6708e22 100644 --- a/homeassistant/components/canary/alarm_control_panel.py +++ b/homeassistant/components/canary/alarm_control_panel.py @@ -1,5 +1,7 @@ """Support for Canary alarm.""" -from typing import Callable, List +from __future__ import annotations + +from typing import Callable from canary.api import LOCATION_MODE_AWAY, LOCATION_MODE_HOME, LOCATION_MODE_NIGHT @@ -27,7 +29,7 @@ from .coordinator import CanaryDataUpdateCoordinator async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Canary alarm control panels based on a config entry.""" coordinator: CanaryDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ diff --git a/homeassistant/components/canary/camera.py b/homeassistant/components/canary/camera.py index 0493a964cc4..703ae2edc8a 100644 --- a/homeassistant/components/canary/camera.py +++ b/homeassistant/components/canary/camera.py @@ -1,7 +1,9 @@ """Support for Canary camera.""" +from __future__ import annotations + import asyncio from datetime import timedelta -from typing import Callable, List +from typing import Callable from haffmpeg.camera import CameraMjpeg from haffmpeg.tools import IMAGE_JPEG, ImageFrame @@ -44,7 +46,7 @@ PLATFORM_SCHEMA = vol.All( async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Canary sensors based on a config entry.""" coordinator: CanaryDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ diff --git a/homeassistant/components/canary/config_flow.py b/homeassistant/components/canary/config_flow.py index dc2822d836a..7b6b5b3f322 100644 --- a/homeassistant/components/canary/config_flow.py +++ b/homeassistant/components/canary/config_flow.py @@ -1,6 +1,8 @@ """Config flow for Canary.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from canary.api import Api from requests import ConnectTimeout, HTTPError @@ -17,7 +19,7 @@ from .const import DOMAIN # pylint: disable=unused-import _LOGGER = logging.getLogger(__name__) -def validate_input(hass: HomeAssistantType, data: dict) -> Dict[str, Any]: +def validate_input(hass: HomeAssistantType, data: dict) -> dict[str, Any]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -45,14 +47,14 @@ class CanaryConfigFlow(ConfigFlow, domain=DOMAIN): return CanaryOptionsFlowHandler(config_entry) async def async_step_import( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initiated by configuration file.""" return await self.async_step_user(user_input) async def async_step_user( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -100,7 +102,7 @@ class CanaryOptionsFlowHandler(OptionsFlow): """Initialize options flow.""" self.config_entry = config_entry - async def async_step_init(self, user_input: Optional[ConfigType] = None): + async def async_step_init(self, user_input: ConfigType | None = None): """Manage Canary options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/canary/sensor.py b/homeassistant/components/canary/sensor.py index 2d5a7885fbf..87e40d268bd 100644 --- a/homeassistant/components/canary/sensor.py +++ b/homeassistant/components/canary/sensor.py @@ -1,5 +1,7 @@ """Support for Canary sensors.""" -from typing import Callable, List +from __future__ import annotations + +from typing import Callable from canary.api import SensorType @@ -54,7 +56,7 @@ STATE_AIR_QUALITY_VERY_ABNORMAL = "very_abnormal" async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Canary sensors based on a config entry.""" coordinator: CanaryDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ diff --git a/homeassistant/components/cast/home_assistant_cast.py b/homeassistant/components/cast/home_assistant_cast.py index 3edc1ce2cde..bb0354bb68e 100644 --- a/homeassistant/components/cast/home_assistant_cast.py +++ b/homeassistant/components/cast/home_assistant_cast.py @@ -1,5 +1,5 @@ """Home Assistant Cast integration for Cast.""" -from typing import Optional +from __future__ import annotations from pychromecast.controllers.homeassistant import HomeAssistantController import voluptuous as vol @@ -20,8 +20,8 @@ async def async_setup_ha_cast( hass: core.HomeAssistant, entry: config_entries.ConfigEntry ): """Set up Home Assistant Cast.""" - user_id: Optional[str] = entry.data.get("user_id") - user: Optional[auth.models.User] = None + user_id: str | None = entry.data.get("user_id") + user: auth.models.User | None = None if user_id is not None: user = await hass.auth.async_get_user(user_id) @@ -78,7 +78,7 @@ async def async_remove_user( hass: core.HomeAssistant, entry: config_entries.ConfigEntry ): """Remove Home Assistant Cast user.""" - user_id: Optional[str] = entry.data.get("user_id") + user_id: str | None = entry.data.get("user_id") if user_id is not None: user = await hass.auth.async_get_user(user_id) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index fbb08437830..2a1aeaacaa6 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -1,10 +1,11 @@ """Provide functionality to interact with Cast devices on the network.""" +from __future__ import annotations + import asyncio from datetime import timedelta import functools as ft import json import logging -from typing import Optional import pychromecast from pychromecast.controllers.homeassistant import HomeAssistantController @@ -195,7 +196,7 @@ class CastDevice(MediaPlayerEntity): self._cast_info = cast_info self.services = cast_info.services - self._chromecast: Optional[pychromecast.Chromecast] = None + self._chromecast: pychromecast.Chromecast | None = None self.cast_status = None self.media_status = None self.media_status_received = None @@ -203,8 +204,8 @@ class CastDevice(MediaPlayerEntity): self.mz_media_status_received = {} self.mz_mgr = None self._available = False - self._status_listener: Optional[CastStatusListener] = None - self._hass_cast_controller: Optional[HomeAssistantController] = None + self._status_listener: CastStatusListener | None = None + self._hass_cast_controller: HomeAssistantController | None = None self._add_remove_handler = None self._cast_view_remove_handler = None @@ -783,7 +784,7 @@ class CastDevice(MediaPlayerEntity): return media_status_recevied @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return self._cast_info.uuid @@ -805,7 +806,7 @@ class CastDevice(MediaPlayerEntity): controller: HomeAssistantController, entity_id: str, view_path: str, - url_path: Optional[str], + url_path: str | None, ): """Handle a show view signal.""" if entity_id != self.entity_id: @@ -827,9 +828,9 @@ class DynamicCastGroup: self.hass = hass self._cast_info = cast_info self.services = cast_info.services - self._chromecast: Optional[pychromecast.Chromecast] = None + self._chromecast: pychromecast.Chromecast | None = None self.mz_mgr = None - self._status_listener: Optional[CastStatusListener] = None + self._status_listener: CastStatusListener | None = None self._add_remove_handler = None self._del_remove_handler = None diff --git a/homeassistant/components/cert_expiry/__init__.py b/homeassistant/components/cert_expiry/__init__.py index d01e38a2e2c..2d4be924a17 100644 --- a/homeassistant/components/cert_expiry/__init__.py +++ b/homeassistant/components/cert_expiry/__init__.py @@ -1,7 +1,8 @@ """The cert_expiry component.""" +from __future__ import annotations + from datetime import datetime, timedelta import logging -from typing import Optional from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PORT @@ -71,7 +72,7 @@ class CertExpiryDataUpdateCoordinator(DataUpdateCoordinator[datetime]): update_interval=SCAN_INTERVAL, ) - async def _async_update_data(self) -> Optional[datetime]: + async def _async_update_data(self) -> datetime | None: """Fetch certificate.""" try: timestamp = await get_cert_expiry_timestamp(self.hass, self.host, self.port) diff --git a/homeassistant/components/climacell/__init__.py b/homeassistant/components/climacell/__init__.py index c3c3f702780..96d7735a03c 100644 --- a/homeassistant/components/climacell/__init__.py +++ b/homeassistant/components/climacell/__init__.py @@ -1,9 +1,11 @@ """The ClimaCell integration.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging from math import ceil -from typing import Any, Dict, Optional, Union +from typing import Any from pyclimacell import ClimaCell from pyclimacell.const import ( @@ -169,7 +171,7 @@ class ClimaCellDataUpdateCoordinator(DataUpdateCoordinator): update_interval=update_interval, ) - async def _async_update_data(self) -> Dict[str, Any]: + async def _async_update_data(self) -> dict[str, Any]: """Update data via library.""" data = {FORECASTS: {}} try: @@ -217,8 +219,8 @@ class ClimaCellEntity(CoordinatorEntity): @staticmethod def _get_cc_value( - weather_dict: Dict[str, Any], key: str - ) -> Optional[Union[int, float, str]]: + weather_dict: dict[str, Any], key: str + ) -> int | float | str | None: """Return property from weather_dict.""" items = weather_dict.get(key, {}) # Handle cases where value returned is a list. @@ -252,7 +254,7 @@ class ClimaCellEntity(CoordinatorEntity): return ATTRIBUTION @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device registry information.""" return { "identifiers": {(DOMAIN, self._config_entry.data[CONF_API_KEY])}, diff --git a/homeassistant/components/climacell/config_flow.py b/homeassistant/components/climacell/config_flow.py index 7048e4e5c2a..cf3bccd3392 100644 --- a/homeassistant/components/climacell/config_flow.py +++ b/homeassistant/components/climacell/config_flow.py @@ -1,6 +1,8 @@ """Config flow for ClimaCell integration.""" +from __future__ import annotations + import logging -from typing import Any, Dict +from typing import Any from pyclimacell import ClimaCell from pyclimacell.const import REALTIME @@ -25,7 +27,7 @@ _LOGGER = logging.getLogger(__name__) def _get_config_schema( - hass: core.HomeAssistant, input_dict: Dict[str, Any] = None + hass: core.HomeAssistant, input_dict: dict[str, Any] = None ) -> vol.Schema: """ Return schema defaults for init step based on user input/config dict. @@ -57,7 +59,7 @@ def _get_config_schema( ) -def _get_unique_id(hass: HomeAssistantType, input_dict: Dict[str, Any]): +def _get_unique_id(hass: HomeAssistantType, input_dict: dict[str, Any]): """Return unique ID from config data.""" return ( f"{input_dict[CONF_API_KEY]}" @@ -74,8 +76,8 @@ class ClimaCellOptionsConfigFlow(config_entries.OptionsFlow): self._config_entry = config_entry async def async_step_init( - self, user_input: Dict[str, Any] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] = None + ) -> dict[str, Any]: """Manage the ClimaCell options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) @@ -107,8 +109,8 @@ class ClimaCellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return ClimaCellOptionsConfigFlow(config_entry) async def async_step_user( - self, user_input: Dict[str, Any] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] = None + ) -> dict[str, Any]: """Handle the initial step.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/climacell/weather.py b/homeassistant/components/climacell/weather.py index e5a24197d6b..e6485f90936 100644 --- a/homeassistant/components/climacell/weather.py +++ b/homeassistant/components/climacell/weather.py @@ -1,7 +1,9 @@ """Weather component that handles meteorological data for your location.""" +from __future__ import annotations + from datetime import datetime import logging -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable from homeassistant.components.weather import ( ATTR_FORECAST_CONDITION, @@ -64,9 +66,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -def _translate_condition( - condition: Optional[str], sun_is_up: bool = True -) -> Optional[str]: +def _translate_condition(condition: str | None, sun_is_up: bool = True) -> str | None: """Translate ClimaCell condition into an HA condition.""" if not condition: return None @@ -82,13 +82,13 @@ def _forecast_dict( forecast_dt: datetime, use_datetime: bool, condition: str, - precipitation: Optional[float], - precipitation_probability: Optional[float], - temp: Optional[float], - temp_low: Optional[float], - wind_direction: Optional[float], - wind_speed: Optional[float], -) -> Dict[str, Any]: + precipitation: float | None, + precipitation_probability: float | None, + temp: float | None, + temp_low: float | None, + wind_direction: float | None, + wind_speed: float | None, +) -> dict[str, Any]: """Return formatted Forecast dict from ClimaCell forecast data.""" if use_datetime: translated_condition = _translate_condition(condition, is_up(hass, forecast_dt)) @@ -120,7 +120,7 @@ def _forecast_dict( async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up a config entry.""" coordinator = hass.data[DOMAIN][config_entry.entry_id] diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 32dfaa0e8fb..93041a6bb33 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -1,9 +1,11 @@ """Provides functionality to interact with climate devices.""" +from __future__ import annotations + from abc import abstractmethod from datetime import timedelta import functools as ft import logging -from typing import Any, Dict, List, Optional +from typing import Any import voluptuous as vol @@ -180,7 +182,7 @@ class ClimateEntity(Entity): return PRECISION_WHOLE @property - def capability_attributes(self) -> Optional[Dict[str, Any]]: + def capability_attributes(self) -> dict[str, Any] | None: """Return the capability attributes.""" supported_features = self.supported_features data = { @@ -212,7 +214,7 @@ class ClimateEntity(Entity): return data @property - def state_attributes(self) -> Dict[str, Any]: + def state_attributes(self) -> dict[str, Any]: """Return the optional state attributes.""" supported_features = self.supported_features data = { @@ -275,12 +277,12 @@ class ClimateEntity(Entity): raise NotImplementedError() @property - def current_humidity(self) -> Optional[int]: + def current_humidity(self) -> int | None: """Return the current humidity.""" return None @property - def target_humidity(self) -> Optional[int]: + def target_humidity(self) -> int | None: """Return the humidity we try to reach.""" return None @@ -294,14 +296,14 @@ class ClimateEntity(Entity): @property @abstractmethod - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes. Need to be a subset of HVAC_MODES. """ @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation if supported. Need to be one of CURRENT_HVAC_*. @@ -309,22 +311,22 @@ class ClimateEntity(Entity): return None @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return None @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return None @property - def target_temperature_step(self) -> Optional[float]: + def target_temperature_step(self) -> float | None: """Return the supported step of target temperature.""" return None @property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach. Requires SUPPORT_TARGET_TEMPERATURE_RANGE. @@ -332,7 +334,7 @@ class ClimateEntity(Entity): raise NotImplementedError @property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach. Requires SUPPORT_TARGET_TEMPERATURE_RANGE. @@ -340,7 +342,7 @@ class ClimateEntity(Entity): raise NotImplementedError @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp. Requires SUPPORT_PRESET_MODE. @@ -348,7 +350,7 @@ class ClimateEntity(Entity): raise NotImplementedError @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes. Requires SUPPORT_PRESET_MODE. @@ -356,7 +358,7 @@ class ClimateEntity(Entity): raise NotImplementedError @property - def is_aux_heat(self) -> Optional[bool]: + def is_aux_heat(self) -> bool | None: """Return true if aux heater. Requires SUPPORT_AUX_HEAT. @@ -364,7 +366,7 @@ class ClimateEntity(Entity): raise NotImplementedError @property - def fan_mode(self) -> Optional[str]: + def fan_mode(self) -> str | None: """Return the fan setting. Requires SUPPORT_FAN_MODE. @@ -372,7 +374,7 @@ class ClimateEntity(Entity): raise NotImplementedError @property - def fan_modes(self) -> Optional[List[str]]: + def fan_modes(self) -> list[str] | None: """Return the list of available fan modes. Requires SUPPORT_FAN_MODE. @@ -380,7 +382,7 @@ class ClimateEntity(Entity): raise NotImplementedError @property - def swing_mode(self) -> Optional[str]: + def swing_mode(self) -> str | None: """Return the swing setting. Requires SUPPORT_SWING_MODE. @@ -388,7 +390,7 @@ class ClimateEntity(Entity): raise NotImplementedError @property - def swing_modes(self) -> Optional[List[str]]: + def swing_modes(self) -> list[str] | None: """Return the list of available swing modes. Requires SUPPORT_SWING_MODE. diff --git a/homeassistant/components/climate/device_action.py b/homeassistant/components/climate/device_action.py index 3f2b8dc23f2..18123ab11f7 100644 --- a/homeassistant/components/climate/device_action.py +++ b/homeassistant/components/climate/device_action.py @@ -1,5 +1,5 @@ """Provides device automations for Climate.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -38,7 +38,7 @@ SET_PRESET_MODE_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( ACTION_SCHEMA = vol.Any(SET_HVAC_MODE_SCHEMA, SET_PRESET_MODE_SCHEMA) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Climate devices.""" registry = await entity_registry.async_get_registry(hass) actions = [] @@ -76,7 +76,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/climate/device_condition.py b/homeassistant/components/climate/device_condition.py index 423efdf8196..10f3b5069b9 100644 --- a/homeassistant/components/climate/device_condition.py +++ b/homeassistant/components/climate/device_condition.py @@ -1,5 +1,5 @@ """Provide the device automations for Climate.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -42,7 +42,7 @@ CONDITION_SCHEMA = vol.Any(HVAC_MODE_CONDITION, PRESET_MODE_CONDITION) async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions for Climate devices.""" registry = await entity_registry.async_get_registry(hass) conditions = [] diff --git a/homeassistant/components/climate/device_trigger.py b/homeassistant/components/climate/device_trigger.py index 84a7c35162a..bee019fa6da 100644 --- a/homeassistant/components/climate/device_trigger.py +++ b/homeassistant/components/climate/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Climate.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -58,7 +58,7 @@ CURRENT_TRIGGER_SCHEMA = vol.All( TRIGGER_SCHEMA = vol.Any(HVAC_MODE_TRIGGER_SCHEMA, CURRENT_TRIGGER_SCHEMA) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Climate devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/climate/reproduce_state.py b/homeassistant/components/climate/reproduce_state.py index 1217d5fde4c..831a67c3882 100644 --- a/homeassistant/components/climate/reproduce_state.py +++ b/homeassistant/components/climate/reproduce_state.py @@ -1,6 +1,8 @@ """Module that groups code required to handle state restore for component.""" +from __future__ import annotations + import asyncio -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ATTR_TEMPERATURE from homeassistant.core import Context, State @@ -29,8 +31,8 @@ async def _async_reproduce_states( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce component states.""" @@ -76,8 +78,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce component states.""" await asyncio.gather( diff --git a/homeassistant/components/cloud/client.py b/homeassistant/components/cloud/client.py index 155a39e49b6..fda2014c9c3 100644 --- a/homeassistant/components/cloud/client.py +++ b/homeassistant/components/cloud/client.py @@ -1,8 +1,10 @@ """Interface implementation for cloud client.""" +from __future__ import annotations + import asyncio import logging from pathlib import Path -from typing import Any, Dict +from typing import Any import aiohttp from hass_nabucasa.client import CloudClient as Interface @@ -32,8 +34,8 @@ class CloudClient(Interface): hass: HomeAssistantType, prefs: CloudPreferences, websession: aiohttp.ClientSession, - alexa_user_config: Dict[str, Any], - google_user_config: Dict[str, Any], + alexa_user_config: dict[str, Any], + google_user_config: dict[str, Any], ): """Initialize client interface to Cloud.""" self._hass = hass @@ -70,7 +72,7 @@ class CloudClient(Interface): return self._hass.http.runner @property - def cloudhooks(self) -> Dict[str, Dict[str, str]]: + def cloudhooks(self) -> dict[str, dict[str, str]]: """Return list of cloudhooks.""" return self._prefs.cloudhooks @@ -164,7 +166,7 @@ class CloudClient(Interface): if identifier.startswith("remote_"): async_dispatcher_send(self._hass, DISPATCHER_REMOTE_UPDATE, data) - async def async_alexa_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]: + async def async_alexa_message(self, payload: dict[Any, Any]) -> dict[Any, Any]: """Process cloud alexa message to client.""" cloud_user = await self._prefs.get_cloud_user() aconfig = await self.get_alexa_config() @@ -176,7 +178,7 @@ class CloudClient(Interface): enabled=self._prefs.alexa_enabled, ) - async def async_google_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]: + async def async_google_message(self, payload: dict[Any, Any]) -> dict[Any, Any]: """Process cloud google message to client.""" if not self._prefs.google_enabled: return ga.turned_off_response(payload) @@ -187,7 +189,7 @@ class CloudClient(Interface): self._hass, gconf, gconf.cloud_user, payload, gc.SOURCE_CLOUD ) - async def async_webhook_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]: + async def async_webhook_message(self, payload: dict[Any, Any]) -> dict[Any, Any]: """Process cloud webhook message to client.""" cloudhook_id = payload["cloudhook_id"] @@ -221,6 +223,6 @@ class CloudClient(Interface): "headers": {"Content-Type": response.content_type}, } - async def async_cloudhooks_update(self, data: Dict[str, Dict[str, str]]) -> None: + async def async_cloudhooks_update(self, data: dict[str, dict[str, str]]) -> None: """Update local list of cloudhooks.""" await self._prefs.async_update(cloudhooks=data) diff --git a/homeassistant/components/cloud/prefs.py b/homeassistant/components/cloud/prefs.py index a15eafc4d08..2b15a620b83 100644 --- a/homeassistant/components/cloud/prefs.py +++ b/homeassistant/components/cloud/prefs.py @@ -1,6 +1,7 @@ """Preference management for cloud.""" +from __future__ import annotations + from ipaddress import ip_address -from typing import List, Optional from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.auth.models import User @@ -234,7 +235,7 @@ class CloudPreferences: return self._prefs.get(PREF_ALEXA_REPORT_STATE, DEFAULT_ALEXA_REPORT_STATE) @property - def alexa_default_expose(self) -> Optional[List[str]]: + def alexa_default_expose(self) -> list[str] | None: """Return array of entity domains that are exposed by default to Alexa. Can return None, in which case for backwards should be interpreted as allow all domains. @@ -272,7 +273,7 @@ class CloudPreferences: return self._prefs[PREF_GOOGLE_LOCAL_WEBHOOK_ID] @property - def google_default_expose(self) -> Optional[List[str]]: + def google_default_expose(self) -> list[str] | None: """Return array of entity domains that are exposed by default to Google. Can return None, in which case for backwards should be interpreted as allow all domains. @@ -302,7 +303,7 @@ class CloudPreferences: await self.async_update(cloud_user=user.id) return user.id - async def _load_cloud_user(self) -> Optional[User]: + async def _load_cloud_user(self) -> User | None: """Load cloud user if available.""" user_id = self._prefs.get(PREF_CLOUD_USER) diff --git a/homeassistant/components/cloud/stt.py b/homeassistant/components/cloud/stt.py index 6c069ce16d7..80578a8d721 100644 --- a/homeassistant/components/cloud/stt.py +++ b/homeassistant/components/cloud/stt.py @@ -1,5 +1,5 @@ """Support for the cloud for speech to text service.""" -from typing import List +from __future__ import annotations from aiohttp import StreamReader from hass_nabucasa import Cloud @@ -56,32 +56,32 @@ class CloudProvider(Provider): self.cloud = cloud @property - def supported_languages(self) -> List[str]: + def supported_languages(self) -> list[str]: """Return a list of supported languages.""" return SUPPORT_LANGUAGES @property - def supported_formats(self) -> List[AudioFormats]: + def supported_formats(self) -> list[AudioFormats]: """Return a list of supported formats.""" return [AudioFormats.WAV, AudioFormats.OGG] @property - def supported_codecs(self) -> List[AudioCodecs]: + def supported_codecs(self) -> list[AudioCodecs]: """Return a list of supported codecs.""" return [AudioCodecs.PCM, AudioCodecs.OPUS] @property - def supported_bit_rates(self) -> List[AudioBitRates]: + def supported_bit_rates(self) -> list[AudioBitRates]: """Return a list of supported bitrates.""" return [AudioBitRates.BITRATE_16] @property - def supported_sample_rates(self) -> List[AudioSampleRates]: + def supported_sample_rates(self) -> list[AudioSampleRates]: """Return a list of supported samplerates.""" return [AudioSampleRates.SAMPLERATE_16000] @property - def supported_channels(self) -> List[AudioChannels]: + def supported_channels(self) -> list[AudioChannels]: """Return a list of supported channels.""" return [AudioChannels.CHANNEL_MONO] diff --git a/homeassistant/components/cloud/utils.py b/homeassistant/components/cloud/utils.py index 36599b42ad3..57f84b057f7 100644 --- a/homeassistant/components/cloud/utils.py +++ b/homeassistant/components/cloud/utils.py @@ -1,10 +1,12 @@ """Helper functions for cloud components.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from aiohttp import payload, web -def aiohttp_serialize_response(response: web.Response) -> Dict[str, Any]: +def aiohttp_serialize_response(response: web.Response) -> dict[str, Any]: """Serialize an aiohttp response to a dictionary.""" body = response.body diff --git a/homeassistant/components/cloudflare/__init__.py b/homeassistant/components/cloudflare/__init__.py index 446890887c1..abef32a4c5c 100644 --- a/homeassistant/components/cloudflare/__init__.py +++ b/homeassistant/components/cloudflare/__init__.py @@ -1,7 +1,8 @@ """Update the IP addresses of your Cloudflare DNS records.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Dict from pycfdns import CloudflareUpdater from pycfdns.exceptions import ( @@ -51,7 +52,7 @@ CONFIG_SCHEMA = vol.Schema( ) -async def async_setup(hass: HomeAssistant, config: Dict) -> bool: +async def async_setup(hass: HomeAssistant, config: dict) -> bool: """Set up the component.""" hass.data.setdefault(DOMAIN, {}) diff --git a/homeassistant/components/cloudflare/config_flow.py b/homeassistant/components/cloudflare/config_flow.py index 066fff9f704..f530652fe90 100644 --- a/homeassistant/components/cloudflare/config_flow.py +++ b/homeassistant/components/cloudflare/config_flow.py @@ -1,6 +1,7 @@ """Config flow for Cloudflare integration.""" +from __future__ import annotations + import logging -from typing import Dict, List, Optional from pycfdns import CloudflareUpdater from pycfdns.exceptions import ( @@ -30,7 +31,7 @@ DATA_SCHEMA = vol.Schema( ) -def _zone_schema(zones: Optional[List] = None): +def _zone_schema(zones: list | None = None): """Zone selection schema.""" zones_list = [] @@ -40,7 +41,7 @@ def _zone_schema(zones: Optional[List] = None): return vol.Schema({vol.Required(CONF_ZONE): vol.In(zones_list)}) -def _records_schema(records: Optional[List] = None): +def _records_schema(records: list | None = None): """Zone records selection schema.""" records_dict = {} @@ -50,7 +51,7 @@ def _records_schema(records: Optional[List] = None): return vol.Schema({vol.Required(CONF_RECORDS): cv.multi_select(records_dict)}) -async def validate_input(hass: HomeAssistant, data: Dict): +async def validate_input(hass: HomeAssistant, data: dict): """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -92,7 +93,7 @@ class CloudflareConfigFlow(ConfigFlow, domain=DOMAIN): self.zones = None self.records = None - async def async_step_user(self, user_input: Optional[Dict] = None): + async def async_step_user(self, user_input: dict | None = None): """Handle a flow initiated by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -113,7 +114,7 @@ class CloudflareConfigFlow(ConfigFlow, domain=DOMAIN): step_id="user", data_schema=DATA_SCHEMA, errors=errors ) - async def async_step_zone(self, user_input: Optional[Dict] = None): + async def async_step_zone(self, user_input: dict | None = None): """Handle the picking the zone.""" errors = {} @@ -133,7 +134,7 @@ class CloudflareConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_records(self, user_input: Optional[Dict] = None): + async def async_step_records(self, user_input: dict | None = None): """Handle the picking the zone records.""" errors = {} diff --git a/homeassistant/components/comfoconnect/fan.py b/homeassistant/components/comfoconnect/fan.py index d248bf74ac4..53bc242ba2f 100644 --- a/homeassistant/components/comfoconnect/fan.py +++ b/homeassistant/components/comfoconnect/fan.py @@ -1,7 +1,8 @@ """Platform to control a Zehnder ComfoAir Q350/450/600 ventilation unit.""" +from __future__ import annotations + import logging import math -from typing import Optional from pycomfoconnect import ( CMD_FAN_MODE_AWAY, @@ -96,7 +97,7 @@ class ComfoConnectFan(FanEntity): return SUPPORT_SET_SPEED @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" speed = self._ccb.data.get(SENSOR_FAN_SPEED_MODE) if speed is None: diff --git a/homeassistant/components/conversation/agent.py b/homeassistant/components/conversation/agent.py index c9c2ab46cf9..56cf4aecdea 100644 --- a/homeassistant/components/conversation/agent.py +++ b/homeassistant/components/conversation/agent.py @@ -1,6 +1,7 @@ """Agent foundation for conversation integration.""" +from __future__ import annotations + from abc import ABC, abstractmethod -from typing import Optional from homeassistant.core import Context from homeassistant.helpers import intent @@ -24,6 +25,6 @@ class AbstractConversationAgent(ABC): @abstractmethod async def async_process( - self, text: str, context: Context, conversation_id: Optional[str] = None + self, text: str, context: Context, conversation_id: str | None = None ) -> intent.IntentResponse: """Process a sentence.""" diff --git a/homeassistant/components/conversation/default_agent.py b/homeassistant/components/conversation/default_agent.py index d2dcf13a62a..a98f685ea1d 100644 --- a/homeassistant/components/conversation/default_agent.py +++ b/homeassistant/components/conversation/default_agent.py @@ -1,6 +1,7 @@ """Standard conversastion implementation for Home Assistant.""" +from __future__ import annotations + import re -from typing import Optional from homeassistant import core, setup from homeassistant.components.cover.intent import INTENT_CLOSE_COVER, INTENT_OPEN_COVER @@ -112,7 +113,7 @@ class DefaultAgent(AbstractConversationAgent): async_register(self.hass, intent_type, sentences) async def async_process( - self, text: str, context: core.Context, conversation_id: Optional[str] = None + self, text: str, context: core.Context, conversation_id: str | None = None ) -> intent.IntentResponse: """Process a sentence.""" intents = self.hass.data[DOMAIN] diff --git a/homeassistant/components/counter/__init__.py b/homeassistant/components/counter/__init__.py index 868a74cc7b7..a4e04825ef6 100644 --- a/homeassistant/components/counter/__init__.py +++ b/homeassistant/components/counter/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations import logging -from typing import Dict, Optional import voluptuous as vol @@ -156,16 +155,16 @@ class CounterStorageCollection(collection.StorageCollection): CREATE_SCHEMA = vol.Schema(CREATE_FIELDS) UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS) - async def _process_create_data(self, data: Dict) -> Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" return self.CREATE_SCHEMA(data) @callback - def _get_suggested_id(self, info: Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return info[CONF_NAME] - async def _update_data(self, data: dict, update_data: Dict) -> Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" update_data = self.UPDATE_SCHEMA(update_data) return {**data, **update_data} @@ -174,14 +173,14 @@ class CounterStorageCollection(collection.StorageCollection): class Counter(RestoreEntity): """Representation of a counter.""" - def __init__(self, config: Dict): + def __init__(self, config: dict): """Initialize a counter.""" - self._config: Dict = config - self._state: Optional[int] = config[CONF_INITIAL] + self._config: dict = config + self._state: int | None = config[CONF_INITIAL] self.editable: bool = True @classmethod - def from_yaml(cls, config: Dict) -> Counter: + def from_yaml(cls, config: dict) -> Counter: """Create counter instance from yaml config.""" counter = cls(config) counter.editable = False @@ -194,22 +193,22 @@ class Counter(RestoreEntity): return False @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return name of the counter.""" return self._config.get(CONF_NAME) @property - def icon(self) -> Optional[str]: + def icon(self) -> str | None: """Return the icon to be used for this entity.""" return self._config.get(CONF_ICON) @property - def state(self) -> Optional[int]: + def state(self) -> int | None: """Return the current value of the counter.""" return self._state @property - def state_attributes(self) -> Dict: + def state_attributes(self) -> dict: """Return the state attributes.""" ret = { ATTR_EDITABLE: self.editable, @@ -223,7 +222,7 @@ class Counter(RestoreEntity): return ret @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return unique id of the entity.""" return self._config[CONF_ID] @@ -276,7 +275,7 @@ class Counter(RestoreEntity): self._state = self.compute_next_state(new_state) self.async_write_ha_state() - async def async_update_config(self, config: Dict) -> None: + async def async_update_config(self, config: dict) -> None: """Change the counter's settings WS CRUD.""" self._config = config self._state = self.compute_next_state(self._state) diff --git a/homeassistant/components/counter/reproduce_state.py b/homeassistant/components/counter/reproduce_state.py index b2dd63adedc..5d51a074e67 100644 --- a/homeassistant/components/counter/reproduce_state.py +++ b/homeassistant/components/counter/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Counter state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State @@ -24,8 +26,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -70,8 +72,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Counter states.""" await asyncio.gather( diff --git a/homeassistant/components/cover/device_action.py b/homeassistant/components/cover/device_action.py index 490ce162d9a..4f92e7f09bd 100644 --- a/homeassistant/components/cover/device_action.py +++ b/homeassistant/components/cover/device_action.py @@ -1,5 +1,5 @@ """Provides device automations for Cover.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -58,7 +58,7 @@ POSITION_ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( ACTION_SCHEMA = vol.Any(CMD_ACTION_SCHEMA, POSITION_ACTION_SCHEMA) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Cover devices.""" registry = await entity_registry.async_get_registry(hass) actions = [] @@ -162,7 +162,7 @@ async def async_get_action_capabilities(hass: HomeAssistant, config: dict) -> di async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/cover/device_condition.py b/homeassistant/components/cover/device_condition.py index 0bcec2a6e43..2943f589f7b 100644 --- a/homeassistant/components/cover/device_condition.py +++ b/homeassistant/components/cover/device_condition.py @@ -1,5 +1,7 @@ """Provides device automations for Cover.""" -from typing import Any, Dict, List +from __future__ import annotations + +from typing import Any import voluptuous as vol @@ -65,10 +67,10 @@ STATE_CONDITION_SCHEMA = DEVICE_CONDITION_BASE_SCHEMA.extend( CONDITION_SCHEMA = vol.Any(POSITION_CONDITION_SCHEMA, STATE_CONDITION_SCHEMA) -async def async_get_conditions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_conditions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device conditions for Cover devices.""" registry = await entity_registry.async_get_registry(hass) - conditions: List[Dict[str, Any]] = [] + conditions: list[dict[str, Any]] = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): diff --git a/homeassistant/components/cover/device_trigger.py b/homeassistant/components/cover/device_trigger.py index 764cc173e5f..119bfc835f5 100644 --- a/homeassistant/components/cover/device_trigger.py +++ b/homeassistant/components/cover/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Cover.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -65,7 +65,7 @@ STATE_TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( TRIGGER_SCHEMA = vol.Any(POSITION_TRIGGER_SCHEMA, STATE_TRIGGER_SCHEMA) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Cover devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/cover/reproduce_state.py b/homeassistant/components/cover/reproduce_state.py index 2a12172bdab..5a053947ec9 100644 --- a/homeassistant/components/cover/reproduce_state.py +++ b/homeassistant/components/cover/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Cover state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.components.cover import ( ATTR_CURRENT_POSITION, @@ -36,8 +38,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -117,8 +119,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Cover states.""" # Reproduce states in parallel. diff --git a/homeassistant/components/debugpy/__init__.py b/homeassistant/components/debugpy/__init__.py index caa691b2369..98f08827c23 100644 --- a/homeassistant/components/debugpy/__init__.py +++ b/homeassistant/components/debugpy/__init__.py @@ -1,8 +1,9 @@ """The Remote Python Debugger integration.""" +from __future__ import annotations + from asyncio import Event import logging from threading import Thread -from typing import Optional import debugpy import voluptuous as vol @@ -40,7 +41,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: conf = config[DOMAIN] async def debug_start( - call: Optional[ServiceCall] = None, *, wait: bool = True + call: ServiceCall | None = None, *, wait: bool = True ) -> None: """Start the debugger.""" debugpy.listen((conf[CONF_HOST], conf[CONF_PORT])) diff --git a/homeassistant/components/deconz/climate.py b/homeassistant/components/deconz/climate.py index dad3f3adf67..49f0cc4d149 100644 --- a/homeassistant/components/deconz/climate.py +++ b/homeassistant/components/deconz/climate.py @@ -1,5 +1,5 @@ """Support for deCONZ climate devices.""" -from typing import Optional +from __future__ import annotations from pydeconz.sensor import Thermostat @@ -195,7 +195,7 @@ class DeconzThermostat(DeconzDevice, ClimateEntity): # Preset control @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return preset mode.""" return DECONZ_TO_PRESET_MODE.get(self._device.preset) diff --git a/homeassistant/components/deconz/logbook.py b/homeassistant/components/deconz/logbook.py index 85982244364..e6f3e9362cd 100644 --- a/homeassistant/components/deconz/logbook.py +++ b/homeassistant/components/deconz/logbook.py @@ -1,6 +1,7 @@ """Describe deCONZ logbook events.""" +from __future__ import annotations -from typing import Callable, Optional +from typing import Callable from homeassistant.const import ATTR_DEVICE_ID, CONF_EVENT from homeassistant.core import HomeAssistant, callback @@ -125,7 +126,7 @@ def async_describe_events( @callback def async_describe_deconz_event(event: Event) -> dict: """Describe deCONZ logbook event.""" - deconz_event: Optional[DeconzEvent] = _get_deconz_event_from_device_id( + deconz_event: DeconzEvent | None = _get_deconz_event_from_device_id( hass, event.data[ATTR_DEVICE_ID] ) diff --git a/homeassistant/components/demo/fan.py b/homeassistant/components/demo/fan.py index 77d6f39a794..c406ffdb214 100644 --- a/homeassistant/components/demo/fan.py +++ b/homeassistant/components/demo/fan.py @@ -1,5 +1,5 @@ """Demo fan platform that has a fake fan.""" -from typing import List, Optional +from __future__ import annotations from homeassistant.components.fan import ( SPEED_HIGH, @@ -110,8 +110,8 @@ class BaseDemoFan(FanEntity): unique_id: str, name: str, supported_features: int, - preset_modes: Optional[List[str]], - speed_list: Optional[List[str]], + preset_modes: list[str] | None, + speed_list: list[str] | None, ) -> None: """Initialize the entity.""" self.hass = hass @@ -211,7 +211,7 @@ class DemoPercentageFan(BaseDemoFan, FanEntity): """A demonstration fan component that uses percentages.""" @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed.""" return self._percentage @@ -227,12 +227,12 @@ class DemoPercentageFan(BaseDemoFan, FanEntity): self.schedule_update_ha_state() @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., auto, smart, interval, favorite.""" return self._preset_mode @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" return self._preset_modes @@ -271,7 +271,7 @@ class AsyncDemoPercentageFan(BaseDemoFan, FanEntity): """An async demonstration fan component that uses percentages.""" @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed.""" return self._percentage @@ -287,12 +287,12 @@ class AsyncDemoPercentageFan(BaseDemoFan, FanEntity): self.async_write_ha_state() @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., auto, smart, interval, favorite.""" return self._preset_mode @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" return self._preset_modes diff --git a/homeassistant/components/demo/geo_location.py b/homeassistant/components/demo/geo_location.py index 76dea52e846..f288a2fd340 100644 --- a/homeassistant/components/demo/geo_location.py +++ b/homeassistant/components/demo/geo_location.py @@ -1,9 +1,10 @@ """Demo platform for the geolocation component.""" +from __future__ import annotations + from datetime import timedelta import logging from math import cos, pi, radians, sin import random -from typing import Optional from homeassistant.components.geo_location import GeolocationEvent from homeassistant.const import LENGTH_KILOMETERS @@ -117,7 +118,7 @@ class DemoGeolocationEvent(GeolocationEvent): return SOURCE @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the event.""" return self._name @@ -127,17 +128,17 @@ class DemoGeolocationEvent(GeolocationEvent): return False @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """Return distance value of this external event.""" return self._distance @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of this external event.""" return self._latitude @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of this external event.""" return self._longitude diff --git a/homeassistant/components/demo/stt.py b/homeassistant/components/demo/stt.py index e0367fad6a9..0497e2335d3 100644 --- a/homeassistant/components/demo/stt.py +++ b/homeassistant/components/demo/stt.py @@ -1,5 +1,5 @@ """Support for the demo for speech to text service.""" -from typing import List +from __future__ import annotations from aiohttp import StreamReader @@ -25,32 +25,32 @@ class DemoProvider(Provider): """Demo speech API provider.""" @property - def supported_languages(self) -> List[str]: + def supported_languages(self) -> list[str]: """Return a list of supported languages.""" return SUPPORT_LANGUAGES @property - def supported_formats(self) -> List[AudioFormats]: + def supported_formats(self) -> list[AudioFormats]: """Return a list of supported formats.""" return [AudioFormats.WAV] @property - def supported_codecs(self) -> List[AudioCodecs]: + def supported_codecs(self) -> list[AudioCodecs]: """Return a list of supported codecs.""" return [AudioCodecs.PCM] @property - def supported_bit_rates(self) -> List[AudioBitRates]: + def supported_bit_rates(self) -> list[AudioBitRates]: """Return a list of supported bit rates.""" return [AudioBitRates.BITRATE_16] @property - def supported_sample_rates(self) -> List[AudioSampleRates]: + def supported_sample_rates(self) -> list[AudioSampleRates]: """Return a list of supported sample rates.""" return [AudioSampleRates.SAMPLERATE_16000, AudioSampleRates.SAMPLERATE_44100] @property - def supported_channels(self) -> List[AudioChannels]: + def supported_channels(self) -> list[AudioChannels]: """Return a list of supported channels.""" return [AudioChannels.CHANNEL_STEREO] diff --git a/homeassistant/components/device_automation/__init__.py b/homeassistant/components/device_automation/__init__.py index bba02147c71..4741dbdb7f5 100644 --- a/homeassistant/components/device_automation/__init__.py +++ b/homeassistant/components/device_automation/__init__.py @@ -1,8 +1,10 @@ """Helpers for device automations.""" +from __future__ import annotations + import asyncio from functools import wraps from types import ModuleType -from typing import Any, List, MutableMapping +from typing import Any, MutableMapping import voluptuous as vol import voluptuous_serialize @@ -116,7 +118,7 @@ async def _async_get_device_automations(hass, automation_type, device_id): ) domains = set() - automations: List[MutableMapping[str, Any]] = [] + automations: list[MutableMapping[str, Any]] = [] device = device_registry.async_get(device_id) if device is None: diff --git a/homeassistant/components/device_automation/toggle_entity.py b/homeassistant/components/device_automation/toggle_entity.py index 61c50da6868..ec7b25b9c24 100644 --- a/homeassistant/components/device_automation/toggle_entity.py +++ b/homeassistant/components/device_automation/toggle_entity.py @@ -1,5 +1,7 @@ """Device automation helpers for toggle entity.""" -from typing import Any, Dict, List +from __future__ import annotations + +from typing import Any import voluptuous as vol @@ -170,10 +172,10 @@ async def async_attach_trigger( async def _async_get_automations( - hass: HomeAssistant, device_id: str, automation_templates: List[dict], domain: str -) -> List[dict]: + hass: HomeAssistant, device_id: str, automation_templates: list[dict], domain: str +) -> list[dict]: """List device automations.""" - automations: List[Dict[str, Any]] = [] + automations: list[dict[str, Any]] = [] entity_registry = await hass.helpers.entity_registry.async_get_registry() entries = [ @@ -198,21 +200,21 @@ async def _async_get_automations( async def async_get_actions( hass: HomeAssistant, device_id: str, domain: str -) -> List[dict]: +) -> list[dict]: """List device actions.""" return await _async_get_automations(hass, device_id, ENTITY_ACTIONS, domain) async def async_get_conditions( hass: HomeAssistant, device_id: str, domain: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions.""" return await _async_get_automations(hass, device_id, ENTITY_CONDITIONS, domain) async def async_get_triggers( hass: HomeAssistant, device_id: str, domain: str -) -> List[dict]: +) -> list[dict]: """List device triggers.""" return await _async_get_automations(hass, device_id, ENTITY_TRIGGERS, domain) diff --git a/homeassistant/components/device_tracker/config_entry.py b/homeassistant/components/device_tracker/config_entry.py index 16e7d022c92..0d8970e087b 100644 --- a/homeassistant/components/device_tracker/config_entry.py +++ b/homeassistant/components/device_tracker/config_entry.py @@ -1,5 +1,5 @@ """Code to set up a device tracker platform using a config entry.""" -from typing import Optional +from __future__ import annotations from homeassistant.components import zone from homeassistant.const import ( @@ -18,7 +18,7 @@ from .const import ATTR_HOST_NAME, ATTR_IP, ATTR_MAC, ATTR_SOURCE_TYPE, DOMAIN, async def async_setup_entry(hass, entry): """Set up an entry.""" - component: Optional[EntityComponent] = hass.data.get(DOMAIN) + component: EntityComponent | None = hass.data.get(DOMAIN) if component is None: component = hass.data[DOMAIN] = EntityComponent(LOGGER, DOMAIN, hass) diff --git a/homeassistant/components/device_tracker/device_condition.py b/homeassistant/components/device_tracker/device_condition.py index 9c102bfa745..0260a4bbd3a 100644 --- a/homeassistant/components/device_tracker/device_condition.py +++ b/homeassistant/components/device_tracker/device_condition.py @@ -1,5 +1,5 @@ """Provides device automations for Device tracker.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -32,7 +32,7 @@ CONDITION_SCHEMA = DEVICE_CONDITION_BASE_SCHEMA.extend( async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions for Device tracker devices.""" registry = await entity_registry.async_get_registry(hass) conditions = [] diff --git a/homeassistant/components/device_tracker/device_trigger.py b/homeassistant/components/device_tracker/device_trigger.py index 2c92304a246..49b77024a1e 100644 --- a/homeassistant/components/device_tracker/device_trigger.py +++ b/homeassistant/components/device_tracker/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Device Tracker.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -32,7 +32,7 @@ TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Device Tracker devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index e947ae7b828..1d3e428b46a 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -1,9 +1,11 @@ """Legacy device tracker classes.""" +from __future__ import annotations + import asyncio from datetime import timedelta import hashlib from types import ModuleType -from typing import Any, Callable, Dict, List, Optional, Sequence +from typing import Any, Callable, Sequence import attr import voluptuous as vol @@ -205,7 +207,7 @@ class DeviceTrackerPlatform: name: str = attr.ib() platform: ModuleType = attr.ib() - config: Dict = attr.ib() + config: dict = attr.ib() @property def type(self): @@ -285,7 +287,7 @@ async def async_extract_config(hass, config): async def async_create_platform_type( hass, config, p_type, p_config -) -> Optional[DeviceTrackerPlatform]: +) -> DeviceTrackerPlatform | None: """Determine type of platform.""" platform = await async_prepare_setup_platform(hass, config, DOMAIN, p_type) @@ -786,7 +788,7 @@ class DeviceScanner: hass: HomeAssistantType = None - def scan_devices(self) -> List[str]: + def scan_devices(self) -> list[str]: """Scan for devices.""" raise NotImplementedError() diff --git a/homeassistant/components/devolo_home_control/climate.py b/homeassistant/components/devolo_home_control/climate.py index d333a5c7609..7ad375bf44d 100644 --- a/homeassistant/components/devolo_home_control/climate.py +++ b/homeassistant/components/devolo_home_control/climate.py @@ -1,5 +1,5 @@ """Platform for climate integration.""" -from typing import List, Optional +from __future__ import annotations from homeassistant.components.climate import ( ATTR_TEMPERATURE, @@ -45,7 +45,7 @@ class DevoloClimateDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, ClimateEntit """Representation of a climate/thermostat device within devolo Home Control.""" @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" if hasattr(self._device_instance, "multi_level_sensor_property"): return next( @@ -60,7 +60,7 @@ class DevoloClimateDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, ClimateEntit return None @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the target temperature.""" return self._value @@ -75,7 +75,7 @@ class DevoloClimateDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, ClimateEntit return HVAC_MODE_HEAT @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return [HVAC_MODE_HEAT] diff --git a/homeassistant/components/directv/__init__.py b/homeassistant/components/directv/__init__.py index 34b8c5cd30c..7ede8fae946 100644 --- a/homeassistant/components/directv/__init__.py +++ b/homeassistant/components/directv/__init__.py @@ -1,7 +1,9 @@ """The DirecTV integration.""" +from __future__ import annotations + import asyncio from datetime import timedelta -from typing import Any, Dict +from typing import Any from directv import DIRECTV, DIRECTVError @@ -28,7 +30,7 @@ PLATFORMS = ["media_player", "remote"] SCAN_INTERVAL = timedelta(seconds=30) -async def async_setup(hass: HomeAssistant, config: Dict) -> bool: +async def async_setup(hass: HomeAssistant, config: dict) -> bool: """Set up the DirecTV component.""" hass.data.setdefault(DOMAIN, {}) return True @@ -87,7 +89,7 @@ class DIRECTVEntity(Entity): return self._name @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this DirecTV receiver.""" return { ATTR_IDENTIFIERS: {(DOMAIN, self._device_id)}, diff --git a/homeassistant/components/directv/config_flow.py b/homeassistant/components/directv/config_flow.py index fed13c63dc8..401ec3b39b6 100644 --- a/homeassistant/components/directv/config_flow.py +++ b/homeassistant/components/directv/config_flow.py @@ -1,6 +1,8 @@ """Config flow for DirecTV.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from urllib.parse import urlparse from directv import DIRECTV, DIRECTVError @@ -25,7 +27,7 @@ ERROR_CANNOT_CONNECT = "cannot_connect" ERROR_UNKNOWN = "unknown" -async def validate_input(hass: HomeAssistantType, data: dict) -> Dict[str, Any]: +async def validate_input(hass: HomeAssistantType, data: dict) -> dict[str, Any]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -48,8 +50,8 @@ class DirecTVConfigFlow(ConfigFlow, domain=DOMAIN): self.discovery_info = {} async def async_step_user( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" if user_input is None: return self._show_setup_form() @@ -71,7 +73,7 @@ class DirecTVConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_ssdp( self, discovery_info: DiscoveryInfoType - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Handle SSDP discovery.""" host = urlparse(discovery_info[ATTR_SSDP_LOCATION]).hostname receiver_id = None @@ -104,7 +106,7 @@ class DirecTVConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_ssdp_confirm( self, user_input: ConfigType = None - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Handle a confirmation flow initiated by SSDP.""" if user_input is None: return self.async_show_form( @@ -118,7 +120,7 @@ class DirecTVConfigFlow(ConfigFlow, domain=DOMAIN): data=self.discovery_info, ) - def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: + def _show_setup_form(self, errors: dict | None = None) -> dict[str, Any]: """Show the setup form to the user.""" return self.async_show_form( step_id="user", diff --git a/homeassistant/components/directv/media_player.py b/homeassistant/components/directv/media_player.py index ee290ba5f2c..4004592e5dc 100644 --- a/homeassistant/components/directv/media_player.py +++ b/homeassistant/components/directv/media_player.py @@ -1,6 +1,8 @@ """Support for the DirecTV receivers.""" +from __future__ import annotations + import logging -from typing import Callable, List, Optional +from typing import Callable from directv import DIRECTV @@ -64,7 +66,7 @@ SUPPORT_DTV_CLIENT = ( async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List, bool], None], + async_add_entities: Callable[[list, bool], None], ) -> bool: """Set up the DirecTV config entry.""" dtv = hass.data[DOMAIN][entry.entry_id] @@ -141,7 +143,7 @@ class DIRECTVMediaPlayer(DIRECTVEntity, MediaPlayerEntity): return self._name @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device.""" return DEVICE_CLASS_RECEIVER diff --git a/homeassistant/components/directv/remote.py b/homeassistant/components/directv/remote.py index 64695ae3813..b35580928ac 100644 --- a/homeassistant/components/directv/remote.py +++ b/homeassistant/components/directv/remote.py @@ -1,7 +1,9 @@ """Support for the DIRECTV remote.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Any, Callable, Iterable, List +from typing import Any, Callable, Iterable from directv import DIRECTV, DIRECTVError @@ -20,7 +22,7 @@ SCAN_INTERVAL = timedelta(minutes=2) async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List, bool], None], + async_add_entities: Callable[[list, bool], None], ) -> bool: """Load DirecTV remote based on a config entry.""" dtv = hass.data[DOMAIN][entry.entry_id] diff --git a/homeassistant/components/dlna_dmr/media_player.py b/homeassistant/components/dlna_dmr/media_player.py index f8af118caed..db0f60d14bc 100644 --- a/homeassistant/components/dlna_dmr/media_player.py +++ b/homeassistant/components/dlna_dmr/media_player.py @@ -1,9 +1,10 @@ """Support for DLNA DMR (Device Media Renderer).""" +from __future__ import annotations + import asyncio from datetime import timedelta import functools import logging -from typing import Optional import aiohttp from async_upnp_client import UpnpFactory @@ -116,7 +117,7 @@ async def async_start_event_handler( server_host: str, server_port: int, requester, - callback_url_override: Optional[str] = None, + callback_url_override: str | None = None, ): """Register notify view.""" hass_data = hass.data[DLNA_DMR_DATA] diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index f0899598351..95edb87e947 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -1,8 +1,10 @@ """Config flow for DSMR integration.""" +from __future__ import annotations + import asyncio from functools import partial import logging -from typing import Any, Dict, Optional +from typing import Any from async_timeout import timeout from dsmr_parser import obis_references as obis_ref @@ -133,7 +135,7 @@ class DSMRFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self, port: str, host: str = None, - updates: Optional[Dict[Any, Any]] = None, + updates: dict[Any, Any] | None = None, reload_on_update: bool = True, ): """Test if host and port are already configured.""" diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index aea12a863f0..0d2c55051e8 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -1,10 +1,11 @@ """Support for Dutch Smart Meter (also known as Smartmeter or P1 port).""" +from __future__ import annotations + import asyncio from asyncio import CancelledError from datetime import timedelta from functools import partial import logging -from typing import Dict from dsmr_parser import obis_references as obis_ref from dsmr_parser.clients.protocol import create_dsmr_reader, create_tcp_dsmr_reader @@ -363,7 +364,7 @@ class DSMREntity(Entity): return self._unique_id @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "identifiers": {(DOMAIN, self._device_serial)}, diff --git a/homeassistant/components/dynalite/__init__.py b/homeassistant/components/dynalite/__init__.py index e19ab319896..92392e4b51a 100644 --- a/homeassistant/components/dynalite/__init__.py +++ b/homeassistant/components/dynalite/__init__.py @@ -1,7 +1,8 @@ """Support for the Dynalite networks.""" +from __future__ import annotations import asyncio -from typing import Any, Dict, Union +from typing import Any import voluptuous as vol @@ -54,7 +55,7 @@ from .const import ( ) -def num_string(value: Union[int, str]) -> str: +def num_string(value: int | str) -> str: """Test if value is a string of digits, aka an integer.""" new_value = str(value) if new_value.isdigit(): @@ -105,7 +106,7 @@ TEMPLATE_DATA_SCHEMA = vol.Any(TEMPLATE_ROOM_SCHEMA, TEMPLATE_TIMECOVER_SCHEMA) TEMPLATE_SCHEMA = vol.Schema({str: TEMPLATE_DATA_SCHEMA}) -def validate_area(config: Dict[str, Any]) -> Dict[str, Any]: +def validate_area(config: dict[str, Any]) -> dict[str, Any]: """Validate that template parameters are only used if area is using the relevant template.""" conf_set = set() for template in DEFAULT_TEMPLATES: @@ -178,7 +179,7 @@ CONFIG_SCHEMA = vol.Schema( ) -async def async_setup(hass: HomeAssistant, config: Dict[str, Any]) -> bool: +async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool: """Set up the Dynalite platform.""" conf = config.get(DOMAIN) LOGGER.debug("Setting up dynalite component config = %s", conf) diff --git a/homeassistant/components/dynalite/bridge.py b/homeassistant/components/dynalite/bridge.py index e024ba11802..71cecee8d43 100644 --- a/homeassistant/components/dynalite/bridge.py +++ b/homeassistant/components/dynalite/bridge.py @@ -1,6 +1,7 @@ """Code to handle a Dynalite bridge.""" +from __future__ import annotations -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable from dynalite_devices_lib.dynalite_devices import ( CONF_AREA as dyn_CONF_AREA, @@ -23,7 +24,7 @@ from .convert_config import convert_config class DynaliteBridge: """Manages a single Dynalite bridge.""" - def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: + def __init__(self, hass: HomeAssistant, config: dict[str, Any]) -> None: """Initialize the system based on host parameter.""" self.hass = hass self.area = {} @@ -44,12 +45,12 @@ class DynaliteBridge: LOGGER.debug("Setting up bridge - host %s", self.host) return await self.dynalite_devices.async_setup() - def reload_config(self, config: Dict[str, Any]) -> None: + def reload_config(self, config: dict[str, Any]) -> None: """Reconfigure a bridge when config changes.""" LOGGER.debug("Reloading bridge - host %s, config %s", self.host, config) self.dynalite_devices.configure(convert_config(config)) - def update_signal(self, device: Optional[DynaliteBaseDevice] = None) -> str: + def update_signal(self, device: DynaliteBaseDevice | None = None) -> str: """Create signal to use to trigger entity update.""" if device: signal = f"dynalite-update-{self.host}-{device.unique_id}" @@ -58,7 +59,7 @@ class DynaliteBridge: return signal @callback - def update_device(self, device: Optional[DynaliteBaseDevice] = None) -> None: + def update_device(self, device: DynaliteBaseDevice | None = None) -> None: """Call when a device or all devices should be updated.""" if not device: # This is used to signal connection or disconnection, so all devices may become available or not. @@ -98,7 +99,7 @@ class DynaliteBridge: if platform in self.waiting_devices: self.async_add_devices[platform](self.waiting_devices[platform]) - def add_devices_when_registered(self, devices: List[DynaliteBaseDevice]) -> None: + def add_devices_when_registered(self, devices: list[DynaliteBaseDevice]) -> None: """Add the devices to HA if the add devices callback was registered, otherwise queue until it is.""" for platform in PLATFORMS: platform_devices = [ diff --git a/homeassistant/components/dynalite/config_flow.py b/homeassistant/components/dynalite/config_flow.py index 4c5b2ceb7d8..2bd2b142252 100644 --- a/homeassistant/components/dynalite/config_flow.py +++ b/homeassistant/components/dynalite/config_flow.py @@ -1,5 +1,7 @@ """Config flow to configure Dynalite hub.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from homeassistant import config_entries from homeassistant.const import CONF_HOST @@ -18,7 +20,7 @@ class DynaliteFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Initialize the Dynalite flow.""" self.host = None - async def async_step_import(self, import_info: Dict[str, Any]) -> Any: + async def async_step_import(self, import_info: dict[str, Any]) -> Any: """Import a new bridge as a config entry.""" LOGGER.debug("Starting async_step_import - %s", import_info) host = import_info[CONF_HOST] diff --git a/homeassistant/components/dynalite/convert_config.py b/homeassistant/components/dynalite/convert_config.py index 6a85147a2e0..89a7f32b47a 100644 --- a/homeassistant/components/dynalite/convert_config.py +++ b/homeassistant/components/dynalite/convert_config.py @@ -1,6 +1,7 @@ """Convert the HA config to the dynalite config.""" +from __future__ import annotations -from typing import Any, Dict +from typing import Any from dynalite_devices_lib import const as dyn_const @@ -62,7 +63,7 @@ def convert_with_map(config, conf_map): return result -def convert_channel(config: Dict[str, Any]) -> Dict[str, Any]: +def convert_channel(config: dict[str, Any]) -> dict[str, Any]: """Convert the config for a channel.""" my_map = { CONF_NAME: dyn_const.CONF_NAME, @@ -72,7 +73,7 @@ def convert_channel(config: Dict[str, Any]) -> Dict[str, Any]: return convert_with_map(config, my_map) -def convert_preset(config: Dict[str, Any]) -> Dict[str, Any]: +def convert_preset(config: dict[str, Any]) -> dict[str, Any]: """Convert the config for a preset.""" my_map = { CONF_NAME: dyn_const.CONF_NAME, @@ -82,7 +83,7 @@ def convert_preset(config: Dict[str, Any]) -> Dict[str, Any]: return convert_with_map(config, my_map) -def convert_area(config: Dict[str, Any]) -> Dict[str, Any]: +def convert_area(config: dict[str, Any]) -> dict[str, Any]: """Convert the config for an area.""" my_map = { CONF_NAME: dyn_const.CONF_NAME, @@ -114,12 +115,12 @@ def convert_area(config: Dict[str, Any]) -> Dict[str, Any]: return result -def convert_default(config: Dict[str, Any]) -> Dict[str, Any]: +def convert_default(config: dict[str, Any]) -> dict[str, Any]: """Convert the config for the platform defaults.""" return convert_with_map(config, {CONF_FADE: dyn_const.CONF_FADE}) -def convert_template(config: Dict[str, Any]) -> Dict[str, Any]: +def convert_template(config: dict[str, Any]) -> dict[str, Any]: """Convert the config for a template.""" my_map = { CONF_ROOM_ON: dyn_const.CONF_ROOM_ON, @@ -135,7 +136,7 @@ def convert_template(config: Dict[str, Any]) -> Dict[str, Any]: return convert_with_map(config, my_map) -def convert_config(config: Dict[str, Any]) -> Dict[str, Any]: +def convert_config(config: dict[str, Any]) -> dict[str, Any]: """Convert a config dict by replacing component consts with library consts.""" my_map = { CONF_NAME: dyn_const.CONF_NAME, diff --git a/homeassistant/components/dynalite/dynalitebase.py b/homeassistant/components/dynalite/dynalitebase.py index 31879c5c118..2cc28002a2c 100644 --- a/homeassistant/components/dynalite/dynalitebase.py +++ b/homeassistant/components/dynalite/dynalitebase.py @@ -1,5 +1,7 @@ """Support for the Dynalite devices as entities.""" -from typing import Any, Callable, Dict +from __future__ import annotations + +from typing import Any, Callable from homeassistant.components.dynalite.bridge import DynaliteBridge from homeassistant.config_entries import ConfigEntry @@ -58,7 +60,7 @@ class DynaliteBase(Entity): return self._device.available @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Device info for this entity.""" return { "identifiers": {(DOMAIN, self._device.unique_id)}, diff --git a/homeassistant/components/dyson/fan.py b/homeassistant/components/dyson/fan.py index 5b69092ac5d..e646babb944 100644 --- a/homeassistant/components/dyson/fan.py +++ b/homeassistant/components/dyson/fan.py @@ -1,7 +1,8 @@ """Support for Dyson Pure Cool link fan.""" +from __future__ import annotations + import logging import math -from typing import Optional from libpurecool.const import FanMode, FanSpeed, NightMode, Oscillation from libpurecool.dyson_pure_cool import DysonPureCool @@ -243,9 +244,9 @@ class DysonFanEntity(DysonEntity, FanEntity): def turn_on( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs, ) -> None: """Turn on the fan.""" @@ -325,9 +326,9 @@ class DysonPureCoolEntity(DysonFanEntity): def turn_on( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs, ) -> None: """Turn on the fan.""" From 91df3fa9049955517319e9e2fe175faab57433e6 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 17 Mar 2021 23:49:01 +0100 Subject: [PATCH 435/831] Update typing 06 (#48039) --- .../components/ecoal_boiler/switch.py | 4 +- homeassistant/components/ecobee/climate.py | 7 ++-- homeassistant/components/esphome/__init__.py | 20 +++++---- .../components/esphome/binary_sensor.py | 6 +-- homeassistant/components/esphome/camera.py | 9 ++-- homeassistant/components/esphome/climate.py | 18 ++++---- .../components/esphome/config_flow.py | 11 ++--- homeassistant/components/esphome/cover.py | 10 ++--- .../components/esphome/entry_data.py | 28 +++++++------ homeassistant/components/esphome/fan.py | 15 +++---- homeassistant/components/esphome/light.py | 18 ++++---- homeassistant/components/esphome/sensor.py | 11 ++--- homeassistant/components/esphome/switch.py | 6 +-- homeassistant/components/essent/sensor.py | 5 ++- homeassistant/components/everlights/light.py | 5 ++- homeassistant/components/evohome/__init__.py | 20 +++++---- homeassistant/components/evohome/climate.py | 19 +++++---- .../components/evohome/water_heater.py | 5 ++- homeassistant/components/fan/__init__.py | 41 ++++++++++--------- homeassistant/components/fan/device_action.py | 6 +-- .../components/fan/device_condition.py | 4 +- .../components/fan/device_trigger.py | 4 +- .../components/fan/reproduce_state.py | 12 +++--- homeassistant/components/ffmpeg/__init__.py | 5 ++- homeassistant/components/fibaro/__init__.py | 5 ++- homeassistant/components/filter/sensor.py | 13 +++--- homeassistant/components/firmata/entity.py | 4 +- homeassistant/components/firmata/light.py | 4 +- homeassistant/components/flexit/climate.py | 5 ++- homeassistant/components/flo/binary_sensor.py | 5 +-- homeassistant/components/flo/device.py | 8 ++-- homeassistant/components/flo/entity.py | 5 ++- homeassistant/components/flo/sensor.py | 27 ++++++------ homeassistant/components/flo/switch.py | 5 +-- .../components/freebox/device_tracker.py | 9 ++-- homeassistant/components/freebox/router.py | 28 +++++++------ homeassistant/components/freebox/sensor.py | 19 +++++---- homeassistant/components/freebox/switch.py | 5 ++- homeassistant/components/fronius/sensor.py | 5 ++- homeassistant/components/frontend/__init__.py | 16 ++++---- 40 files changed, 241 insertions(+), 211 deletions(-) diff --git a/homeassistant/components/ecoal_boiler/switch.py b/homeassistant/components/ecoal_boiler/switch.py index 57a8d420c43..995a49554e6 100644 --- a/homeassistant/components/ecoal_boiler/switch.py +++ b/homeassistant/components/ecoal_boiler/switch.py @@ -1,5 +1,5 @@ """Allows to configuration ecoal (esterownik.pl) pumps as switches.""" -from typing import Optional +from __future__ import annotations from homeassistant.components.switch import SwitchEntity @@ -40,7 +40,7 @@ class EcoalSwitch(SwitchEntity): self._state = None @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the switch.""" return self._name diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index c3224410993..47c2ff969ec 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -1,6 +1,7 @@ """Support for Ecobee Thermostats.""" +from __future__ import annotations + import collections -from typing import Optional import voluptuous as vol @@ -406,7 +407,7 @@ class Thermostat(ClimateEntity): ) @property - def target_humidity(self) -> Optional[int]: + def target_humidity(self) -> int | None: """Return the desired humidity set point.""" if self.has_humidifier_control: return self.thermostat["runtime"]["desiredHumidity"] @@ -484,7 +485,7 @@ class Thermostat(ClimateEntity): return self._operation_list @property - def current_humidity(self) -> Optional[int]: + def current_humidity(self) -> int | None: """Return the current humidity.""" return self.thermostat["runtime"]["actualHumidity"] diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index 650b630e34c..d895fc9216d 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -1,9 +1,11 @@ """Support for esphome devices.""" +from __future__ import annotations + import asyncio import functools import logging import math -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable from aioesphomeapi import ( APIClient, @@ -155,7 +157,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool await cli.send_home_assistant_state(entity_id, new_state.state) async def _send_home_assistant_state( - entity_id: str, new_state: Optional[State] + entity_id: str, new_state: State | None ) -> None: """Forward Home Assistant states to ESPHome.""" await cli.send_home_assistant_state(entity_id, new_state.state) @@ -384,7 +386,7 @@ async def _register_service( async def _setup_services( - hass: HomeAssistantType, entry_data: RuntimeEntryData, services: List[UserService] + hass: HomeAssistantType, entry_data: RuntimeEntryData, services: list[UserService] ): old_services = entry_data.services.copy() to_unregister = [] @@ -461,7 +463,7 @@ async def platform_async_setup_entry( entry_data.state[component_key] = {} @callback - def async_list_entities(infos: List[EntityInfo]): + def async_list_entities(infos: list[EntityInfo]): """Update entities of this platform when entities are listed.""" old_infos = entry_data.info[component_key] new_infos = {} @@ -535,7 +537,7 @@ def esphome_state_property(func): class EsphomeEnumMapper: """Helper class to convert between hass and esphome enum values.""" - def __init__(self, func: Callable[[], Dict[int, str]]): + def __init__(self, func: Callable[[], dict[int, str]]): """Construct a EsphomeEnumMapper.""" self._func = func @@ -549,7 +551,7 @@ class EsphomeEnumMapper: return inverse[value] -def esphome_map_enum(func: Callable[[], Dict[int, str]]): +def esphome_map_enum(func: Callable[[], dict[int, str]]): """Map esphome int enum values to hass string constants. This class has to be used as a decorator. This ensures the aioesphomeapi @@ -621,7 +623,7 @@ class EsphomeBaseEntity(Entity): return self._entry_data.client @property - def _state(self) -> Optional[EntityState]: + def _state(self) -> EntityState | None: try: return self._entry_data.state[self._component_key][self._key] except KeyError: @@ -640,14 +642,14 @@ class EsphomeBaseEntity(Entity): return self._entry_data.available @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique id identifying the entity.""" if not self._static_info.unique_id: return None return self._static_info.unique_id @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device registry information for this entity.""" return { "connections": {(dr.CONNECTION_NETWORK_MAC, self._device_info.mac_address)} diff --git a/homeassistant/components/esphome/binary_sensor.py b/homeassistant/components/esphome/binary_sensor.py index d605a48410b..28cc47691f5 100644 --- a/homeassistant/components/esphome/binary_sensor.py +++ b/homeassistant/components/esphome/binary_sensor.py @@ -1,5 +1,5 @@ """Support for ESPHome binary sensors.""" -from typing import Optional +from __future__ import annotations from aioesphomeapi import BinarySensorInfo, BinarySensorState @@ -29,11 +29,11 @@ class EsphomeBinarySensor(EsphomeEntity, BinarySensorEntity): return super()._static_info @property - def _state(self) -> Optional[BinarySensorState]: + def _state(self) -> BinarySensorState | None: return super()._state @property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return true if the binary sensor is on.""" if self._static_info.is_status_binary_sensor: # Status binary sensors indicated connected state. diff --git a/homeassistant/components/esphome/camera.py b/homeassistant/components/esphome/camera.py index 5b8f4f0d7e6..c868d7b320a 100644 --- a/homeassistant/components/esphome/camera.py +++ b/homeassistant/components/esphome/camera.py @@ -1,6 +1,7 @@ """Support for ESPHome cameras.""" +from __future__ import annotations + import asyncio -from typing import Optional from aioesphomeapi import CameraInfo, CameraState @@ -42,7 +43,7 @@ class EsphomeCamera(Camera, EsphomeBaseEntity): return super()._static_info @property - def _state(self) -> Optional[CameraState]: + def _state(self) -> CameraState | None: return super()._state async def async_added_to_hass(self) -> None: @@ -67,7 +68,7 @@ class EsphomeCamera(Camera, EsphomeBaseEntity): async with self._image_cond: self._image_cond.notify_all() - async def async_camera_image(self) -> Optional[bytes]: + async def async_camera_image(self) -> bytes | None: """Return single camera image bytes.""" if not self.available: return None @@ -78,7 +79,7 @@ class EsphomeCamera(Camera, EsphomeBaseEntity): return None return self._state.image[:] - async def _async_camera_stream_image(self) -> Optional[bytes]: + async def _async_camera_stream_image(self) -> bytes | None: """Return a single camera image in a stream.""" if not self.available: return None diff --git a/homeassistant/components/esphome/climate.py b/homeassistant/components/esphome/climate.py index fe171d24fb9..5d21d495ec2 100644 --- a/homeassistant/components/esphome/climate.py +++ b/homeassistant/components/esphome/climate.py @@ -1,5 +1,5 @@ """Support for ESPHome climate devices.""" -from typing import List, Optional +from __future__ import annotations from aioesphomeapi import ( ClimateAction, @@ -134,7 +134,7 @@ class EsphomeClimateEntity(EsphomeEntity, ClimateEntity): return super()._static_info @property - def _state(self) -> Optional[ClimateState]: + def _state(self) -> ClimateState | None: return super()._state @property @@ -153,7 +153,7 @@ class EsphomeClimateEntity(EsphomeEntity, ClimateEntity): return TEMP_CELSIUS @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available operation modes.""" return [ _climate_modes.from_esphome(mode) @@ -217,12 +217,12 @@ class EsphomeClimateEntity(EsphomeEntity, ClimateEntity): # pylint: disable=invalid-overridden-method @esphome_state_property - def hvac_mode(self) -> Optional[str]: + def hvac_mode(self) -> str | None: """Return current operation ie. heat, cool, idle.""" return _climate_modes.from_esphome(self._state.mode) @esphome_state_property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return current action.""" # HA has no support feature field for hvac_action if not self._static_info.supports_action: @@ -245,22 +245,22 @@ class EsphomeClimateEntity(EsphomeEntity, ClimateEntity): return _swing_modes.from_esphome(self._state.swing_mode) @esphome_state_property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._state.current_temperature @esphome_state_property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return self._state.target_temperature @esphome_state_property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" return self._state.target_temperature_low @esphome_state_property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" return self._state.target_temperature_high diff --git a/homeassistant/components/esphome/config_flow.py b/homeassistant/components/esphome/config_flow.py index a84aa2959ea..45a33a0dc24 100644 --- a/homeassistant/components/esphome/config_flow.py +++ b/homeassistant/components/esphome/config_flow.py @@ -1,6 +1,7 @@ """Config flow to configure esphome component.""" +from __future__ import annotations + from collections import OrderedDict -from typing import Optional from aioesphomeapi import APIClient, APIConnectionError import voluptuous as vol @@ -23,12 +24,12 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize flow.""" - self._host: Optional[str] = None - self._port: Optional[int] = None - self._password: Optional[str] = None + self._host: str | None = None + self._port: int | None = None + self._password: str | None = None async def async_step_user( - self, user_input: Optional[ConfigType] = None, error: Optional[str] = None + self, user_input: ConfigType | None = None, error: str | None = None ): # pylint: disable=arguments-differ """Handle a flow initialized by the user.""" if user_input is not None: diff --git a/homeassistant/components/esphome/cover.py b/homeassistant/components/esphome/cover.py index 4c42fe0d522..294689d075a 100644 --- a/homeassistant/components/esphome/cover.py +++ b/homeassistant/components/esphome/cover.py @@ -1,5 +1,5 @@ """Support for ESPHome covers.""" -from typing import Optional +from __future__ import annotations from aioesphomeapi import CoverInfo, CoverOperation, CoverState @@ -64,14 +64,14 @@ class EsphomeCover(EsphomeEntity, CoverEntity): return self._static_info.assumed_state @property - def _state(self) -> Optional[CoverState]: + def _state(self) -> CoverState | None: return super()._state # https://github.com/PyCQA/pylint/issues/3150 for all @esphome_state_property # pylint: disable=invalid-overridden-method @esphome_state_property - def is_closed(self) -> Optional[bool]: + def is_closed(self) -> bool | None: """Return if the cover is closed or not.""" # Check closed state with api version due to a protocol change return self._state.is_closed(self._client.api_version) @@ -87,14 +87,14 @@ class EsphomeCover(EsphomeEntity, CoverEntity): return self._state.current_operation == CoverOperation.IS_CLOSING @esphome_state_property - def current_cover_position(self) -> Optional[int]: + def current_cover_position(self) -> int | None: """Return current position of cover. 0 is closed, 100 is open.""" if not self._static_info.supports_position: return None return round(self._state.position * 100.0) @esphome_state_property - def current_cover_tilt_position(self) -> Optional[float]: + def current_cover_tilt_position(self) -> float | None: """Return current position of cover tilt. 0 is closed, 100 is open.""" if not self._static_info.supports_tilt: return None diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index 58b20d18e12..f308239fa23 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -1,6 +1,8 @@ """Runtime entry data for ESPHome stored in hass.data.""" +from __future__ import annotations + import asyncio -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set, Tuple +from typing import TYPE_CHECKING, Any, Callable from aioesphomeapi import ( COMPONENT_TYPE_TO_INFO, @@ -52,22 +54,22 @@ class RuntimeEntryData: entry_id: str = attr.ib() client: "APIClient" = attr.ib() store: Store = attr.ib() - reconnect_task: Optional[asyncio.Task] = attr.ib(default=None) - state: Dict[str, Dict[str, Any]] = attr.ib(factory=dict) - info: Dict[str, Dict[str, Any]] = attr.ib(factory=dict) + reconnect_task: asyncio.Task | None = attr.ib(default=None) + state: dict[str, dict[str, Any]] = attr.ib(factory=dict) + info: dict[str, dict[str, Any]] = attr.ib(factory=dict) # A second list of EntityInfo objects # This is necessary for when an entity is being removed. HA requires # some static info to be accessible during removal (unique_id, maybe others) # If an entity can't find anything in the info array, it will look for info here. - old_info: Dict[str, Dict[str, Any]] = attr.ib(factory=dict) + old_info: dict[str, dict[str, Any]] = attr.ib(factory=dict) - services: Dict[int, "UserService"] = attr.ib(factory=dict) + services: dict[int, "UserService"] = attr.ib(factory=dict) available: bool = attr.ib(default=False) - device_info: Optional[DeviceInfo] = attr.ib(default=None) - cleanup_callbacks: List[Callable[[], None]] = attr.ib(factory=list) - disconnect_callbacks: List[Callable[[], None]] = attr.ib(factory=list) - loaded_platforms: Set[str] = attr.ib(factory=set) + device_info: DeviceInfo | None = attr.ib(default=None) + cleanup_callbacks: list[Callable[[], None]] = attr.ib(factory=list) + disconnect_callbacks: list[Callable[[], None]] = attr.ib(factory=list) + loaded_platforms: set[str] = attr.ib(factory=set) platform_load_lock: asyncio.Lock = attr.ib(factory=asyncio.Lock) @callback @@ -87,7 +89,7 @@ class RuntimeEntryData: async_dispatcher_send(hass, signal) async def _ensure_platforms_loaded( - self, hass: HomeAssistantType, entry: ConfigEntry, platforms: Set[str] + self, hass: HomeAssistantType, entry: ConfigEntry, platforms: set[str] ): async with self.platform_load_lock: needed = platforms - self.loaded_platforms @@ -101,7 +103,7 @@ class RuntimeEntryData: self.loaded_platforms |= needed async def async_update_static_infos( - self, hass: HomeAssistantType, entry: ConfigEntry, infos: List[EntityInfo] + self, hass: HomeAssistantType, entry: ConfigEntry, infos: list[EntityInfo] ) -> None: """Distribute an update of static infos to all platforms.""" # First, load all platforms @@ -129,7 +131,7 @@ class RuntimeEntryData: signal = f"esphome_{self.entry_id}_on_device_update" async_dispatcher_send(hass, signal) - async def async_load_from_store(self) -> Tuple[List[EntityInfo], List[UserService]]: + async def async_load_from_store(self) -> tuple[list[EntityInfo], list[UserService]]: """Load the retained data from store and return de-serialized data.""" restored = await self.store.async_load() if restored is None: diff --git a/homeassistant/components/esphome/fan.py b/homeassistant/components/esphome/fan.py index bac35fdb93f..5d7cf24f2c5 100644 --- a/homeassistant/components/esphome/fan.py +++ b/homeassistant/components/esphome/fan.py @@ -1,6 +1,7 @@ """Support for ESPHome fans.""" +from __future__ import annotations + import math -from typing import Optional from aioesphomeapi import FanDirection, FanInfo, FanSpeed, FanState @@ -62,7 +63,7 @@ class EsphomeFan(EsphomeEntity, FanEntity): return super()._static_info @property - def _state(self) -> Optional[FanState]: + def _state(self) -> FanState | None: return super()._state @property @@ -93,9 +94,9 @@ class EsphomeFan(EsphomeEntity, FanEntity): async def async_turn_on( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs, ) -> None: """Turn on the fan.""" @@ -121,12 +122,12 @@ class EsphomeFan(EsphomeEntity, FanEntity): # pylint: disable=invalid-overridden-method @esphome_state_property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return true if the entity is on.""" return self._state.state @esphome_state_property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" if not self._static_info.supports_speed: return None diff --git a/homeassistant/components/esphome/light.py b/homeassistant/components/esphome/light.py index adbb36188fd..29fd969d479 100644 --- a/homeassistant/components/esphome/light.py +++ b/homeassistant/components/esphome/light.py @@ -1,5 +1,5 @@ """Support for ESPHome lights.""" -from typing import List, Optional, Tuple +from __future__ import annotations from aioesphomeapi import LightInfo, LightState @@ -54,14 +54,14 @@ class EsphomeLight(EsphomeEntity, LightEntity): return super()._static_info @property - def _state(self) -> Optional[LightState]: + def _state(self) -> LightState | None: return super()._state # https://github.com/PyCQA/pylint/issues/3150 for all @esphome_state_property # pylint: disable=invalid-overridden-method @esphome_state_property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return true if the switch is on.""" return self._state.state @@ -96,29 +96,29 @@ class EsphomeLight(EsphomeEntity, LightEntity): await self._client.light_command(**data) @esphome_state_property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the brightness of this light between 0..255.""" return round(self._state.brightness * 255) @esphome_state_property - def hs_color(self) -> Optional[Tuple[float, float]]: + def hs_color(self) -> tuple[float, float] | None: """Return the hue and saturation color value [float, float].""" return color_util.color_RGB_to_hs( self._state.red * 255, self._state.green * 255, self._state.blue * 255 ) @esphome_state_property - def color_temp(self) -> Optional[float]: + def color_temp(self) -> float | None: """Return the CT color value in mireds.""" return self._state.color_temperature @esphome_state_property - def white_value(self) -> Optional[int]: + def white_value(self) -> int | None: """Return the white value of this light between 0..255.""" return round(self._state.white * 255) @esphome_state_property - def effect(self) -> Optional[str]: + def effect(self) -> str | None: """Return the current effect.""" return self._state.effect @@ -140,7 +140,7 @@ class EsphomeLight(EsphomeEntity, LightEntity): return flags @property - def effect_list(self) -> List[str]: + def effect_list(self) -> list[str]: """Return the list of supported effects.""" return self._static_info.effects diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index fe9922cf0ed..17799e4484f 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -1,6 +1,7 @@ """Support for esphome sensors.""" +from __future__ import annotations + import math -from typing import Optional from aioesphomeapi import SensorInfo, SensorState, TextSensorInfo, TextSensorState import voluptuous as vol @@ -49,7 +50,7 @@ class EsphomeSensor(EsphomeEntity): return super()._static_info @property - def _state(self) -> Optional[SensorState]: + def _state(self) -> SensorState | None: return super()._state @property @@ -65,7 +66,7 @@ class EsphomeSensor(EsphomeEntity): return self._static_info.force_update @esphome_state_property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the entity.""" if math.isnan(self._state.state): return None @@ -96,7 +97,7 @@ class EsphomeTextSensor(EsphomeEntity): return super()._static_info @property - def _state(self) -> Optional[TextSensorState]: + def _state(self) -> TextSensorState | None: return super()._state @property @@ -105,7 +106,7 @@ class EsphomeTextSensor(EsphomeEntity): return self._static_info.icon @esphome_state_property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the entity.""" if self._state.missing_state: return None diff --git a/homeassistant/components/esphome/switch.py b/homeassistant/components/esphome/switch.py index 3f8ca90d6c5..992f014e829 100644 --- a/homeassistant/components/esphome/switch.py +++ b/homeassistant/components/esphome/switch.py @@ -1,5 +1,5 @@ """Support for ESPHome switches.""" -from typing import Optional +from __future__ import annotations from aioesphomeapi import SwitchInfo, SwitchState @@ -33,7 +33,7 @@ class EsphomeSwitch(EsphomeEntity, SwitchEntity): return super()._static_info @property - def _state(self) -> Optional[SwitchState]: + def _state(self) -> SwitchState | None: return super()._state @property @@ -49,7 +49,7 @@ class EsphomeSwitch(EsphomeEntity, SwitchEntity): # https://github.com/PyCQA/pylint/issues/3150 for @esphome_state_property # pylint: disable=invalid-overridden-method @esphome_state_property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return true if the switch is on.""" return self._state.state diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index 3ac7af315b7..26f7c930241 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -1,6 +1,7 @@ """Support for Essent API.""" +from __future__ import annotations + from datetime import timedelta -from typing import Optional from pyessent import PyEssent import voluptuous as vol @@ -94,7 +95,7 @@ class EssentMeter(Entity): self._unit = unit @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return f"{self._meter}-{self._type}-{self._tariff}" diff --git a/homeassistant/components/everlights/light.py b/homeassistant/components/everlights/light.py index 243bd2913b1..e451b83c1fa 100644 --- a/homeassistant/components/everlights/light.py +++ b/homeassistant/components/everlights/light.py @@ -1,7 +1,8 @@ """Support for EverLights lights.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Tuple import pyeverlights import voluptuous as vol @@ -38,7 +39,7 @@ def color_rgb_to_int(red: int, green: int, blue: int) -> int: return red * 256 * 256 + green * 256 + blue -def color_int_to_rgb(value: int) -> Tuple[int, int, int]: +def color_int_to_rgb(value: int) -> tuple[int, int, int]: """Return an RGB tuple from an integer.""" return (value >> 16, (value >> 8) & 0xFF, value & 0xFF) diff --git a/homeassistant/components/evohome/__init__.py b/homeassistant/components/evohome/__init__.py index 006fbb1610d..8c83308a8b7 100644 --- a/homeassistant/components/evohome/__init__.py +++ b/homeassistant/components/evohome/__init__.py @@ -2,10 +2,12 @@ Such systems include evohome, Round Thermostat, and others. """ +from __future__ import annotations + from datetime import datetime as dt, timedelta import logging import re -from typing import Any, Dict, Optional, Tuple +from typing import Any import aiohttp.client_exceptions import evohomeasync @@ -114,7 +116,7 @@ def convert_until(status_dict: dict, until_key: str) -> None: status_dict[until_key] = dt_util.as_local(dt_utc_naive).isoformat() -def convert_dict(dictionary: Dict[str, Any]) -> Dict[str, Any]: +def convert_dict(dictionary: dict[str, Any]) -> dict[str, Any]: """Recursively convert a dict's keys to snake_case.""" def convert_key(key: str) -> str: @@ -176,7 +178,7 @@ def _handle_exception(err) -> bool: async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: """Create a (EMEA/EU-based) Honeywell TCC system.""" - async def load_auth_tokens(store) -> Tuple[Dict, Optional[Dict]]: + async def load_auth_tokens(store) -> tuple[dict, dict | None]: app_storage = await store.async_load() tokens = dict(app_storage if app_storage else {}) @@ -435,7 +437,7 @@ class EvoBroker: async def _update_v1_api_temps(self, *args, **kwargs) -> None: """Get the latest high-precision temperatures of the default Location.""" - def get_session_id(client_v1) -> Optional[str]: + def get_session_id(client_v1) -> str | None: user_data = client_v1.user_data if client_v1 else None return user_data.get("sessionId") if user_data else None @@ -520,7 +522,7 @@ class EvoDevice(Entity): self._supported_features = None self._device_state_attrs = {} - async def async_refresh(self, payload: Optional[dict] = None) -> None: + async def async_refresh(self, payload: dict | None = None) -> None: """Process any signals.""" if payload is None: self.async_schedule_update_ha_state(force_refresh=True) @@ -546,7 +548,7 @@ class EvoDevice(Entity): return False @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return self._unique_id @@ -556,7 +558,7 @@ class EvoDevice(Entity): return self._name @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the evohome-specific state attributes.""" status = self._device_state_attrs if "systemModeStatus" in status: @@ -606,7 +608,7 @@ class EvoChild(EvoDevice): self._setpoints = {} @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature of a Zone.""" if ( self._evo_broker.temps @@ -618,7 +620,7 @@ class EvoChild(EvoDevice): return self._evo_device.temperatureStatus["temperature"] @property - def setpoints(self) -> Dict[str, Any]: + def setpoints(self) -> dict[str, Any]: """Return the current/next setpoints from the schedule. Only Zones & DHW controllers (but not the TCS) can have schedules. diff --git a/homeassistant/components/evohome/climate.py b/homeassistant/components/evohome/climate.py index e99cae5e22e..f291fcd9cb3 100644 --- a/homeassistant/components/evohome/climate.py +++ b/homeassistant/components/evohome/climate.py @@ -1,7 +1,8 @@ """Support for Climate devices of (EMEA/EU-based) Honeywell TCC systems.""" +from __future__ import annotations + from datetime import datetime as dt import logging -from typing import List, Optional from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( @@ -129,12 +130,12 @@ class EvoClimateEntity(EvoDevice, ClimateEntity): self._preset_modes = None @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return a list of available hvac operation modes.""" return list(HA_HVAC_TO_TCS) @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" return self._preset_modes @@ -203,7 +204,7 @@ class EvoZone(EvoChild, EvoClimateEntity): return self._evo_device.setpointStatus["targetHeatTemperature"] @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" if self._evo_tcs.systemModeStatus["mode"] in [EVO_AWAY, EVO_HEATOFF]: return TCS_PRESET_TO_HA.get(self._evo_tcs.systemModeStatus["mode"]) @@ -268,7 +269,7 @@ class EvoZone(EvoChild, EvoClimateEntity): self._evo_device.cancel_temp_override() ) - async def async_set_preset_mode(self, preset_mode: Optional[str]) -> None: + async def async_set_preset_mode(self, preset_mode: str | None) -> None: """Set the preset mode; if None, then revert to following the schedule.""" evo_preset_mode = HA_PRESET_TO_EVO.get(preset_mode, EVO_FOLLOW) @@ -347,7 +348,7 @@ class EvoController(EvoClimateEntity): await self._set_tcs_mode(mode, until=until) - async def _set_tcs_mode(self, mode: str, until: Optional[dt] = None) -> None: + async def _set_tcs_mode(self, mode: str, until: dt | None = None) -> None: """Set a Controller to any of its native EVO_* operating modes.""" until = dt_util.as_utc(until) if until else None await self._evo_broker.call_client_api( @@ -361,7 +362,7 @@ class EvoController(EvoClimateEntity): return HVAC_MODE_OFF if tcs_mode == EVO_HEATOFF else HVAC_MODE_HEAT @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the average current temperature of the heating Zones. Controllers do not have a current temp, but one is expected by HA. @@ -374,7 +375,7 @@ class EvoController(EvoClimateEntity): return round(sum(temps) / len(temps), 1) if temps else None @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" return TCS_PRESET_TO_HA.get(self._evo_tcs.systemModeStatus["mode"]) @@ -396,7 +397,7 @@ class EvoController(EvoClimateEntity): """Set an operating mode for a Controller.""" await self._set_tcs_mode(HA_HVAC_TO_TCS.get(hvac_mode)) - async def async_set_preset_mode(self, preset_mode: Optional[str]) -> None: + async def async_set_preset_mode(self, preset_mode: str | None) -> None: """Set the preset mode; if None, then revert to 'Auto' mode.""" await self._set_tcs_mode(HA_PRESET_TO_TCS.get(preset_mode, EVO_AUTO)) diff --git a/homeassistant/components/evohome/water_heater.py b/homeassistant/components/evohome/water_heater.py index 846c8c09155..4e05c553461 100644 --- a/homeassistant/components/evohome/water_heater.py +++ b/homeassistant/components/evohome/water_heater.py @@ -1,6 +1,7 @@ """Support for WaterHeater devices of (EMEA/EU) Honeywell TCC systems.""" +from __future__ import annotations + import logging -from typing import List from homeassistant.components.water_heater import ( SUPPORT_AWAY_MODE, @@ -70,7 +71,7 @@ class EvoDHW(EvoChild, WaterHeaterEntity): return EVO_STATE_TO_HA[self._evo_device.stateStatus["state"]] @property - def operation_list(self) -> List[str]: + def operation_list(self) -> list[str]: """Return the list of available operations.""" return list(HA_STATE_TO_EVO) diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 7a50997d76d..e707de5f543 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -1,9 +1,10 @@ """Provides functionality to interact with fans.""" +from __future__ import annotations + from datetime import timedelta import functools as ft import logging import math -from typing import List, Optional import voluptuous as vol @@ -274,16 +275,16 @@ class FanEntity(ToggleEntity): else: await self.async_set_speed(self.percentage_to_speed(percentage)) - async def async_increase_speed(self, percentage_step: Optional[int] = None) -> None: + async def async_increase_speed(self, percentage_step: int | None = None) -> None: """Increase the speed of the fan.""" await self._async_adjust_speed(1, percentage_step) - async def async_decrease_speed(self, percentage_step: Optional[int] = None) -> None: + async def async_decrease_speed(self, percentage_step: int | None = None) -> None: """Decrease the speed of the fan.""" await self._async_adjust_speed(-1, percentage_step) async def _async_adjust_speed( - self, modifier: int, percentage_step: Optional[int] + self, modifier: int, percentage_step: int | None ) -> None: """Increase or decrease the speed of the fan.""" current_percentage = self.percentage or 0 @@ -338,9 +339,9 @@ class FanEntity(ToggleEntity): # pylint: disable=arguments-differ def turn_on( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs, ) -> None: """Turn on the fan.""" @@ -348,9 +349,9 @@ class FanEntity(ToggleEntity): async def async_turn_on_compat( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs, ) -> None: """Turn on the fan. @@ -387,9 +388,9 @@ class FanEntity(ToggleEntity): # pylint: disable=arguments-differ async def async_turn_on( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs, ) -> None: """Turn on the fan.""" @@ -441,7 +442,7 @@ class FanEntity(ToggleEntity): ) @property - def speed(self) -> Optional[str]: + def speed(self) -> str | None: """Return the current speed.""" if self._implemented_preset_mode: preset_mode = self.preset_mode @@ -455,7 +456,7 @@ class FanEntity(ToggleEntity): return None @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed as a percentage.""" if not self._implemented_preset_mode: if self.speed in self.preset_modes: @@ -488,7 +489,7 @@ class FanEntity(ToggleEntity): return speeds @property - def current_direction(self) -> Optional[str]: + def current_direction(self) -> str | None: """Return the current direction of the fan.""" return None @@ -616,7 +617,7 @@ class FanEntity(ToggleEntity): return 0 @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., auto, smart, interval, favorite. Requires SUPPORT_SET_SPEED. @@ -627,7 +628,7 @@ class FanEntity(ToggleEntity): return None @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes. Requires SUPPORT_SET_SPEED. @@ -635,7 +636,7 @@ class FanEntity(ToggleEntity): return preset_modes_from_speed_list(self.speed_list) -def speed_list_without_preset_modes(speed_list: List): +def speed_list_without_preset_modes(speed_list: list): """Filter out non-speeds from the speed list. The goal is to get the speeds in a list from lowest to @@ -659,7 +660,7 @@ def speed_list_without_preset_modes(speed_list: List): return [speed for speed in speed_list if speed.lower() not in _NOT_SPEEDS_FILTER] -def preset_modes_from_speed_list(speed_list: List): +def preset_modes_from_speed_list(speed_list: list): """Filter out non-preset modes from the speed list. The goal is to return only preset modes. diff --git a/homeassistant/components/fan/device_action.py b/homeassistant/components/fan/device_action.py index a5d35d741b6..b42c8145470 100644 --- a/homeassistant/components/fan/device_action.py +++ b/homeassistant/components/fan/device_action.py @@ -1,5 +1,5 @@ """Provides device automations for Fan.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -28,7 +28,7 @@ ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Fan devices.""" registry = await entity_registry.async_get_registry(hass) actions = [] @@ -59,7 +59,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/fan/device_condition.py b/homeassistant/components/fan/device_condition.py index d3a8aa5c395..9aa9620ef72 100644 --- a/homeassistant/components/fan/device_condition.py +++ b/homeassistant/components/fan/device_condition.py @@ -1,5 +1,5 @@ """Provide the device automations for Fan.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -32,7 +32,7 @@ CONDITION_SCHEMA = DEVICE_CONDITION_BASE_SCHEMA.extend( async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions for Fan devices.""" registry = await entity_registry.async_get_registry(hass) conditions = [] diff --git a/homeassistant/components/fan/device_trigger.py b/homeassistant/components/fan/device_trigger.py index 95f4b429a24..f109cfef117 100644 --- a/homeassistant/components/fan/device_trigger.py +++ b/homeassistant/components/fan/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Fan.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -31,7 +31,7 @@ TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Fan devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/fan/reproduce_state.py b/homeassistant/components/fan/reproduce_state.py index b5f0fca47b7..fc4d942231b 100644 --- a/homeassistant/components/fan/reproduce_state.py +++ b/homeassistant/components/fan/reproduce_state.py @@ -1,8 +1,10 @@ """Reproduce an Fan state.""" +from __future__ import annotations + import asyncio import logging from types import MappingProxyType -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -44,8 +46,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -98,8 +100,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Fan states.""" await asyncio.gather( diff --git a/homeassistant/components/ffmpeg/__init__.py b/homeassistant/components/ffmpeg/__init__.py index 4b67f06ba23..4bf8de91edc 100644 --- a/homeassistant/components/ffmpeg/__init__.py +++ b/homeassistant/components/ffmpeg/__init__.py @@ -1,7 +1,8 @@ """Support for FFmpeg.""" +from __future__ import annotations + import asyncio import re -from typing import Optional from haffmpeg.tools import IMAGE_JPEG, FFVersion, ImageFrame import voluptuous as vol @@ -93,7 +94,7 @@ async def async_get_image( hass: HomeAssistantType, input_source: str, output_format: str = IMAGE_JPEG, - extra_cmd: Optional[str] = None, + extra_cmd: str | None = None, ): """Get an image from a frame of an RTSP stream.""" manager = hass.data[DATA_FFMPEG] diff --git a/homeassistant/components/fibaro/__init__.py b/homeassistant/components/fibaro/__init__.py index 9ce284d51ab..964c1112840 100644 --- a/homeassistant/components/fibaro/__init__.py +++ b/homeassistant/components/fibaro/__init__.py @@ -1,7 +1,8 @@ """Support for the Fibaro devices.""" +from __future__ import annotations + from collections import defaultdict import logging -from typing import Optional from fiblary3.client.v4.client import Client as FibaroClient, StateHandler import voluptuous as vol @@ -496,7 +497,7 @@ class FibaroDevice(Entity): return self.fibaro_device.unique_id_str @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the device.""" return self._name diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py index f4be797d12b..d74491dd9c6 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -1,4 +1,6 @@ """Allows the creation of a sensor that filters state property.""" +from __future__ import annotations + from collections import Counter, deque from copy import copy from datetime import timedelta @@ -6,7 +8,6 @@ from functools import partial import logging from numbers import Number import statistics -from typing import Optional import voluptuous as vol @@ -394,8 +395,8 @@ class Filter: self, name, window_size: int = 1, - precision: Optional[int] = None, - entity: Optional[str] = None, + precision: int | None = None, + entity: str | None = None, ): """Initialize common attributes. @@ -463,9 +464,9 @@ class RangeFilter(Filter): def __init__( self, entity, - precision: Optional[int] = DEFAULT_PRECISION, - lower_bound: Optional[float] = None, - upper_bound: Optional[float] = None, + precision: int | None = DEFAULT_PRECISION, + lower_bound: float | None = None, + upper_bound: float | None = None, ): """Initialize Filter. diff --git a/homeassistant/components/firmata/entity.py b/homeassistant/components/firmata/entity.py index 50ab58b9046..8f843d29272 100644 --- a/homeassistant/components/firmata/entity.py +++ b/homeassistant/components/firmata/entity.py @@ -1,5 +1,5 @@ """Entity for Firmata devices.""" -from typing import Type +from __future__ import annotations from homeassistant.config_entries import ConfigEntry @@ -32,7 +32,7 @@ class FirmataPinEntity(FirmataEntity): def __init__( self, - api: Type[FirmataBoardPin], + api: type[FirmataBoardPin], config_entry: ConfigEntry, name: str, pin: FirmataPinType, diff --git a/homeassistant/components/firmata/light.py b/homeassistant/components/firmata/light.py index e95b5101413..3c50a559e51 100644 --- a/homeassistant/components/firmata/light.py +++ b/homeassistant/components/firmata/light.py @@ -1,7 +1,7 @@ """Support for Firmata light output.""" +from __future__ import annotations import logging -from typing import Type from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -55,7 +55,7 @@ class FirmataLight(FirmataPinEntity, LightEntity): def __init__( self, - api: Type[FirmataBoardPin], + api: type[FirmataBoardPin], config_entry: ConfigEntry, name: str, pin: FirmataPinType, diff --git a/homeassistant/components/flexit/climate.py b/homeassistant/components/flexit/climate.py index ef399796898..cf4662b9866 100644 --- a/homeassistant/components/flexit/climate.py +++ b/homeassistant/components/flexit/climate.py @@ -1,6 +1,7 @@ """Platform for Flexit AC units with CI66 Modbus adapter.""" +from __future__ import annotations + import logging -from typing import List from pyflexit.pyflexit import pyflexit import voluptuous as vol @@ -135,7 +136,7 @@ class Flexit(ClimateEntity): return self._current_operation @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes. Need to be a subset of HVAC_MODES. diff --git a/homeassistant/components/flo/binary_sensor.py b/homeassistant/components/flo/binary_sensor.py index 96909b3bd9c..bd623aa38bb 100644 --- a/homeassistant/components/flo/binary_sensor.py +++ b/homeassistant/components/flo/binary_sensor.py @@ -1,6 +1,5 @@ """Support for Flo Water Monitor binary sensors.""" - -from typing import List +from __future__ import annotations from homeassistant.components.binary_sensor import ( DEVICE_CLASS_PROBLEM, @@ -14,7 +13,7 @@ from .entity import FloEntity async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Flo sensors from config entry.""" - devices: List[FloDeviceDataUpdateCoordinator] = hass.data[FLO_DOMAIN][ + devices: list[FloDeviceDataUpdateCoordinator] = hass.data[FLO_DOMAIN][ config_entry.entry_id ]["devices"] entities = [] diff --git a/homeassistant/components/flo/device.py b/homeassistant/components/flo/device.py index 49e0e991376..50e0ccda87f 100644 --- a/homeassistant/components/flo/device.py +++ b/homeassistant/components/flo/device.py @@ -1,7 +1,9 @@ """Flo device object.""" +from __future__ import annotations + import asyncio from datetime import datetime, timedelta -from typing import Any, Dict, Optional +from typing import Any from aioflo.api import API from aioflo.errors import RequestError @@ -26,8 +28,8 @@ class FloDeviceDataUpdateCoordinator(DataUpdateCoordinator): self._flo_location_id: str = location_id self._flo_device_id: str = device_id self._manufacturer: str = "Flo by Moen" - self._device_information: Optional[Dict[str, Any]] = None - self._water_usage: Optional[Dict[str, Any]] = None + self._device_information: dict[str, Any] | None = None + self._water_usage: dict[str, Any] | None = None super().__init__( hass, LOGGER, diff --git a/homeassistant/components/flo/entity.py b/homeassistant/components/flo/entity.py index 35c6e022dcf..878c4188815 100644 --- a/homeassistant/components/flo/entity.py +++ b/homeassistant/components/flo/entity.py @@ -1,6 +1,7 @@ """Base entity class for Flo entities.""" +from __future__ import annotations -from typing import Any, Dict +from typing import Any from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.entity import Entity @@ -36,7 +37,7 @@ class FloEntity(Entity): return self._unique_id @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return a device description for device registry.""" return { "identifiers": {(FLO_DOMAIN, self._device.id)}, diff --git a/homeassistant/components/flo/sensor.py b/homeassistant/components/flo/sensor.py index d88f2b950fc..18ee067af5a 100644 --- a/homeassistant/components/flo/sensor.py +++ b/homeassistant/components/flo/sensor.py @@ -1,6 +1,5 @@ """Support for Flo Water Monitor sensors.""" - -from typing import List, Optional +from __future__ import annotations from homeassistant.const import ( DEVICE_CLASS_BATTERY, @@ -31,7 +30,7 @@ NAME_BATTERY = "Battery" async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Flo sensors from config entry.""" - devices: List[FloDeviceDataUpdateCoordinator] = hass.data[FLO_DOMAIN][ + devices: list[FloDeviceDataUpdateCoordinator] = hass.data[FLO_DOMAIN][ config_entry.entry_id ]["devices"] entities = [] @@ -71,7 +70,7 @@ class FloDailyUsageSensor(FloEntity): return WATER_ICON @property - def state(self) -> Optional[float]: + def state(self) -> float | None: """Return the current daily usage.""" if self._device.consumption_today is None: return None @@ -92,7 +91,7 @@ class FloSystemModeSensor(FloEntity): self._state: str = None @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the current system mode.""" if not self._device.current_system_mode: return None @@ -113,7 +112,7 @@ class FloCurrentFlowRateSensor(FloEntity): return GAUGE_ICON @property - def state(self) -> Optional[float]: + def state(self) -> float | None: """Return the current flow rate.""" if self._device.current_flow_rate is None: return None @@ -134,7 +133,7 @@ class FloTemperatureSensor(FloEntity): self._state: float = None @property - def state(self) -> Optional[float]: + def state(self) -> float | None: """Return the current temperature.""" if self._device.temperature is None: return None @@ -146,7 +145,7 @@ class FloTemperatureSensor(FloEntity): return TEMP_FAHRENHEIT @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class for this sensor.""" return DEVICE_CLASS_TEMPERATURE @@ -160,7 +159,7 @@ class FloHumiditySensor(FloEntity): self._state: float = None @property - def state(self) -> Optional[float]: + def state(self) -> float | None: """Return the current humidity.""" if self._device.humidity is None: return None @@ -172,7 +171,7 @@ class FloHumiditySensor(FloEntity): return PERCENTAGE @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class for this sensor.""" return DEVICE_CLASS_HUMIDITY @@ -186,7 +185,7 @@ class FloPressureSensor(FloEntity): self._state: float = None @property - def state(self) -> Optional[float]: + def state(self) -> float | None: """Return the current water pressure.""" if self._device.current_psi is None: return None @@ -198,7 +197,7 @@ class FloPressureSensor(FloEntity): return PRESSURE_PSI @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class for this sensor.""" return DEVICE_CLASS_PRESSURE @@ -212,7 +211,7 @@ class FloBatterySensor(FloEntity): self._state: float = None @property - def state(self) -> Optional[float]: + def state(self) -> float | None: """Return the current battery level.""" return self._device.battery_level @@ -222,6 +221,6 @@ class FloBatterySensor(FloEntity): return PERCENTAGE @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class for this sensor.""" return DEVICE_CLASS_BATTERY diff --git a/homeassistant/components/flo/switch.py b/homeassistant/components/flo/switch.py index 895212c56a0..e5f00a6125f 100644 --- a/homeassistant/components/flo/switch.py +++ b/homeassistant/components/flo/switch.py @@ -1,6 +1,5 @@ """Switch representing the shutoff valve for the Flo by Moen integration.""" - -from typing import List +from __future__ import annotations from aioflo.location import SLEEP_MINUTE_OPTIONS, SYSTEM_MODE_HOME, SYSTEM_REVERT_MODES import voluptuous as vol @@ -23,7 +22,7 @@ SERVICE_RUN_HEALTH_TEST = "run_health_test" async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Flo switches from config entry.""" - devices: List[FloDeviceDataUpdateCoordinator] = hass.data[FLO_DOMAIN][ + devices: list[FloDeviceDataUpdateCoordinator] = hass.data[FLO_DOMAIN][ config_entry.entry_id ]["devices"] entities = [] diff --git a/homeassistant/components/freebox/device_tracker.py b/homeassistant/components/freebox/device_tracker.py index 8b1b214c33a..6510a29bbfc 100644 --- a/homeassistant/components/freebox/device_tracker.py +++ b/homeassistant/components/freebox/device_tracker.py @@ -1,6 +1,7 @@ """Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" +from __future__ import annotations + from datetime import datetime -from typing import Dict from homeassistant.components.device_tracker import SOURCE_TYPE_ROUTER from homeassistant.components.device_tracker.config_entry import ScannerEntity @@ -52,7 +53,7 @@ def add_entities(router, async_add_entities, tracked): class FreeboxDevice(ScannerEntity): """Representation of a Freebox device.""" - def __init__(self, router: FreeboxRouter, device: Dict[str, any]) -> None: + def __init__(self, router: FreeboxRouter, device: dict[str, any]) -> None: """Initialize a Freebox device.""" self._router = router self._name = device["primary_name"].strip() or DEFAULT_DEVICE_NAME @@ -105,12 +106,12 @@ class FreeboxDevice(ScannerEntity): return self._icon @property - def extra_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> dict[str, any]: """Return the attributes.""" return self._attrs @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "connections": {(CONNECTION_NETWORK_MAC, self._mac)}, diff --git a/homeassistant/components/freebox/router.py b/homeassistant/components/freebox/router.py index a38992320eb..fbeca869d1d 100644 --- a/homeassistant/components/freebox/router.py +++ b/homeassistant/components/freebox/router.py @@ -1,9 +1,11 @@ """Represent the Freebox router and its devices and sensors.""" +from __future__ import annotations + from datetime import datetime, timedelta import logging import os from pathlib import Path -from typing import Any, Dict, List, Optional +from typing import Any from freebox_api import Freepybox from freebox_api.api.wifi import Wifi @@ -60,11 +62,11 @@ class FreeboxRouter: self._sw_v = None self._attrs = {} - self.devices: Dict[str, Dict[str, Any]] = {} - self.disks: Dict[int, Dict[str, Any]] = {} - self.sensors_temperature: Dict[str, int] = {} - self.sensors_connection: Dict[str, float] = {} - self.call_list: List[Dict[str, Any]] = [] + self.devices: dict[str, dict[str, Any]] = {} + self.disks: dict[int, dict[str, Any]] = {} + self.sensors_temperature: dict[str, int] = {} + self.sensors_connection: dict[str, float] = {} + self.call_list: list[dict[str, Any]] = [] self._unsub_dispatcher = None self.listeners = [] @@ -91,7 +93,7 @@ class FreeboxRouter: self.hass, self.update_all, SCAN_INTERVAL ) - async def update_all(self, now: Optional[datetime] = None) -> None: + async def update_all(self, now: datetime | None = None) -> None: """Update all Freebox platforms.""" await self.update_device_trackers() await self.update_sensors() @@ -99,7 +101,7 @@ class FreeboxRouter: async def update_device_trackers(self) -> None: """Update Freebox devices.""" new_device = False - fbx_devices: [Dict[str, Any]] = await self._api.lan.get_hosts_list() + fbx_devices: [dict[str, Any]] = await self._api.lan.get_hosts_list() # Adds the Freebox itself fbx_devices.append( @@ -129,7 +131,7 @@ class FreeboxRouter: async def update_sensors(self) -> None: """Update Freebox sensors.""" # System sensors - syst_datas: Dict[str, Any] = await self._api.system.get_config() + syst_datas: dict[str, Any] = await self._api.system.get_config() # According to the doc `syst_datas["sensors"]` is temperature sensors in celsius degree. # Name and id of sensors may vary under Freebox devices. @@ -137,7 +139,7 @@ class FreeboxRouter: self.sensors_temperature[sensor["name"]] = sensor["value"] # Connection sensors - connection_datas: Dict[str, Any] = await self._api.connection.get_status() + connection_datas: dict[str, Any] = await self._api.connection.get_status() for sensor_key in CONNECTION_SENSORS: self.sensors_connection[sensor_key] = connection_datas[sensor_key] @@ -161,7 +163,7 @@ class FreeboxRouter: async def _update_disks_sensors(self) -> None: """Update Freebox disks.""" # None at first request - fbx_disks: [Dict[str, Any]] = await self._api.storage.get_disks() or [] + fbx_disks: [dict[str, Any]] = await self._api.storage.get_disks() or [] for fbx_disk in fbx_disks: self.disks[fbx_disk["id"]] = fbx_disk @@ -178,7 +180,7 @@ class FreeboxRouter: self._api = None @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return the device information.""" return { "connections": {(CONNECTION_NETWORK_MAC, self.mac)}, @@ -204,7 +206,7 @@ class FreeboxRouter: return f"{DOMAIN}-{self._host}-sensor-update" @property - def sensors(self) -> Dict[str, Any]: + def sensors(self) -> dict[str, Any]: """Return sensors.""" return {**self.sensors_temperature, **self.sensors_connection} diff --git a/homeassistant/components/freebox/sensor.py b/homeassistant/components/freebox/sensor.py index d014d85f6dc..9f125ff69bc 100644 --- a/homeassistant/components/freebox/sensor.py +++ b/homeassistant/components/freebox/sensor.py @@ -1,6 +1,7 @@ """Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" +from __future__ import annotations + import logging -from typing import Dict from homeassistant.config_entries import ConfigEntry from homeassistant.const import DATA_RATE_KILOBYTES_PER_SECOND @@ -77,7 +78,7 @@ class FreeboxSensor(Entity): """Representation of a Freebox sensor.""" def __init__( - self, router: FreeboxRouter, sensor_type: str, sensor: Dict[str, any] + self, router: FreeboxRouter, sensor_type: str, sensor: dict[str, any] ) -> None: """Initialize a Freebox sensor.""" self._state = None @@ -129,7 +130,7 @@ class FreeboxSensor(Entity): return self._device_class @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return self._router.device_info @@ -160,7 +161,7 @@ class FreeboxCallSensor(FreeboxSensor): """Representation of a Freebox call sensor.""" def __init__( - self, router: FreeboxRouter, sensor_type: str, sensor: Dict[str, any] + self, router: FreeboxRouter, sensor_type: str, sensor: dict[str, any] ) -> None: """Initialize a Freebox call sensor.""" super().__init__(router, sensor_type, sensor) @@ -180,7 +181,7 @@ class FreeboxCallSensor(FreeboxSensor): self._state = len(self._call_list_for_type) @property - def extra_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> dict[str, any]: """Return device specific state attributes.""" return { dt_util.utc_from_timestamp(call["datetime"]).isoformat(): call["name"] @@ -194,10 +195,10 @@ class FreeboxDiskSensor(FreeboxSensor): def __init__( self, router: FreeboxRouter, - disk: Dict[str, any], - partition: Dict[str, any], + disk: dict[str, any], + partition: dict[str, any], sensor_type: str, - sensor: Dict[str, any], + sensor: dict[str, any], ) -> None: """Initialize a Freebox disk sensor.""" super().__init__(router, sensor_type, sensor) @@ -207,7 +208,7 @@ class FreeboxDiskSensor(FreeboxSensor): self._unique_id = f"{self._router.mac} {sensor_type} {self._disk['id']} {self._partition['id']}" @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "identifiers": {(DOMAIN, self._disk["id"])}, diff --git a/homeassistant/components/freebox/switch.py b/homeassistant/components/freebox/switch.py index b1cfc93eb53..a15a86f46d8 100644 --- a/homeassistant/components/freebox/switch.py +++ b/homeassistant/components/freebox/switch.py @@ -1,6 +1,7 @@ """Support for Freebox Delta, Revolution and Mini 4K.""" +from __future__ import annotations + import logging -from typing import Dict from freebox_api.exceptions import InsufficientPermissionsError @@ -48,7 +49,7 @@ class FreeboxWifiSwitch(SwitchEntity): return self._state @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return self._router.device_info diff --git a/homeassistant/components/fronius/sensor.py b/homeassistant/components/fronius/sensor.py index 02ff760e574..7e578701e2c 100644 --- a/homeassistant/components/fronius/sensor.py +++ b/homeassistant/components/fronius/sensor.py @@ -1,8 +1,9 @@ """Support for Fronius devices.""" +from __future__ import annotations + import copy from datetime import timedelta import logging -from typing import Dict from pyfronius import Fronius import voluptuous as vol @@ -195,7 +196,7 @@ class FroniusAdapter: for sensor in self._registered_sensors: sensor.async_schedule_update_ha_state(True) - async def _update(self) -> Dict: + async def _update(self) -> dict: """Return values of interest.""" async def register(self, sensor): diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index bb503ee8673..0176987274e 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -1,10 +1,12 @@ """Handle the frontend for Home Assistant.""" +from __future__ import annotations + import json import logging import mimetypes import os import pathlib -from typing import Any, Dict, Optional, Set, Tuple +from typing import Any from aiohttp import hdrs, web, web_urldispatcher import jinja2 @@ -119,19 +121,19 @@ class Panel: """Abstract class for panels.""" # Name of the webcomponent - component_name: Optional[str] = None + component_name: str | None = None # Icon to show in the sidebar - sidebar_icon: Optional[str] = None + sidebar_icon: str | None = None # Title to show in the sidebar - sidebar_title: Optional[str] = None + sidebar_title: str | None = None # Url to show the panel in the frontend - frontend_url_path: Optional[str] = None + frontend_url_path: str | None = None # Config to pass to the webcomponent - config: Optional[Dict[str, Any]] = None + config: dict[str, Any] | None = None # If the panel should only be visible to admins require_admin = False @@ -443,7 +445,7 @@ class IndexView(web_urldispatcher.AbstractResource): async def resolve( self, request: web.Request - ) -> Tuple[Optional[web_urldispatcher.UrlMappingMatchInfo], Set[str]]: + ) -> tuple[web_urldispatcher.UrlMappingMatchInfo | None, set[str]]: """Resolve resource. Return (UrlMappingMatchInfo, allowed_methods) pair. From f625e324dd52f51d7e049920d88c2086909010dc Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 18 Mar 2021 00:07:07 +0000 Subject: [PATCH 436/831] [ci skip] Translation update --- .../components/adguard/translations/ko.json | 4 +- .../components/almond/translations/ko.json | 4 +- .../components/august/translations/ca.json | 16 +++++++ .../components/august/translations/de.json | 16 +++++++ .../components/august/translations/et.json | 16 +++++++ .../components/august/translations/hu.json | 15 ++++++ .../components/august/translations/ko.json | 16 +++++++ .../components/august/translations/no.json | 16 +++++++ .../components/august/translations/ru.json | 16 +++++++ .../august/translations/zh-Hant.json | 16 +++++++ .../azure_devops/translations/ko.json | 2 +- .../bmw_connected_drive/translations/de.json | 1 + .../components/braviatv/translations/ko.json | 4 +- .../components/deconz/translations/ko.json | 4 +- .../components/dexcom/translations/nl.json | 4 +- .../dialogflow/translations/ko.json | 2 +- .../faa_delays/translations/hu.json | 1 + .../components/flume/translations/ko.json | 2 +- .../fritzbox_callmonitor/translations/ko.json | 2 +- .../components/geofency/translations/ko.json | 2 +- .../components/goalzero/translations/de.json | 3 +- .../components/gpslogger/translations/ko.json | 2 +- .../components/hassio/translations/ko.json | 2 +- .../components/hive/translations/hu.json | 48 +++++++++++++++++++ .../huawei_lte/translations/ko.json | 2 +- .../components/hue/translations/nl.json | 12 ++++- .../components/ifttt/translations/ko.json | 2 +- .../input_boolean/translations/ko.json | 2 +- .../input_datetime/translations/ko.json | 2 +- .../input_number/translations/ko.json | 2 +- .../input_select/translations/ko.json | 2 +- .../input_text/translations/ko.json | 2 +- .../components/kodi/translations/de.json | 1 + .../components/locative/translations/ko.json | 2 +- .../components/lock/translations/ko.json | 2 +- .../lutron_caseta/translations/de.json | 21 +++++++- .../lutron_caseta/translations/hu.json | 3 +- .../components/mailgun/translations/ko.json | 2 +- .../motion_blinds/translations/de.json | 8 +++- .../components/mqtt/translations/ko.json | 4 +- .../components/mysensors/translations/hu.json | 8 ++++ .../components/netatmo/translations/hu.json | 13 ++++- .../ovo_energy/translations/ko.json | 2 +- .../components/ozw/translations/de.json | 13 ++++- .../components/ozw/translations/ko.json | 18 +++---- .../panasonic_viera/translations/ko.json | 4 +- .../philips_js/translations/ca.json | 4 +- .../philips_js/translations/de.json | 3 +- .../philips_js/translations/et.json | 7 +++ .../philips_js/translations/hu.json | 7 +++ .../philips_js/translations/ko.json | 4 +- .../philips_js/translations/no.json | 7 +++ .../philips_js/translations/ru.json | 7 +++ .../philips_js/translations/zh-Hant.json | 7 +++ .../components/pi_hole/translations/hu.json | 1 + .../components/plaato/translations/de.json | 5 ++ .../components/plaato/translations/hu.json | 4 ++ .../components/plaato/translations/ko.json | 2 +- .../components/plex/translations/ko.json | 2 +- .../components/plugwise/translations/nl.json | 1 + .../components/rfxtrx/translations/de.json | 17 ++++++- .../components/samsungtv/translations/ko.json | 2 +- .../screenlogic/translations/ca.json | 39 +++++++++++++++ .../screenlogic/translations/de.json | 39 +++++++++++++++ .../screenlogic/translations/et.json | 39 +++++++++++++++ .../screenlogic/translations/hu.json | 38 +++++++++++++++ .../screenlogic/translations/ko.json | 39 +++++++++++++++ .../screenlogic/translations/no.json | 39 +++++++++++++++ .../screenlogic/translations/ru.json | 39 +++++++++++++++ .../screenlogic/translations/zh-Hant.json | 39 +++++++++++++++ .../components/sensor/translations/hu.json | 4 ++ .../components/smarthab/translations/nl.json | 4 +- .../somfy_mylink/translations/de.json | 7 ++- .../somfy_mylink/translations/hu.json | 10 ++++ .../components/sonarr/translations/de.json | 1 + .../components/traccar/translations/ko.json | 2 +- .../components/tuya/translations/de.json | 14 ++++++ .../components/tuya/translations/ko.json | 4 +- .../components/twilio/translations/ko.json | 2 +- .../components/unifi/translations/de.json | 1 + .../components/unifi/translations/ko.json | 6 +-- .../components/upcloud/translations/ko.json | 2 +- .../components/upnp/translations/ko.json | 2 +- .../components/verisure/translations/hu.json | 46 ++++++++++++++++++ .../components/vizio/translations/ko.json | 4 +- .../water_heater/translations/hu.json | 11 +++++ .../xiaomi_aqara/translations/ko.json | 2 +- .../components/zwave_js/translations/de.json | 7 ++- .../components/zwave_js/translations/ko.json | 28 +++++------ 89 files changed, 802 insertions(+), 87 deletions(-) create mode 100644 homeassistant/components/hive/translations/hu.json create mode 100644 homeassistant/components/screenlogic/translations/ca.json create mode 100644 homeassistant/components/screenlogic/translations/de.json create mode 100644 homeassistant/components/screenlogic/translations/et.json create mode 100644 homeassistant/components/screenlogic/translations/hu.json create mode 100644 homeassistant/components/screenlogic/translations/ko.json create mode 100644 homeassistant/components/screenlogic/translations/no.json create mode 100644 homeassistant/components/screenlogic/translations/ru.json create mode 100644 homeassistant/components/screenlogic/translations/zh-Hant.json create mode 100644 homeassistant/components/verisure/translations/hu.json diff --git a/homeassistant/components/adguard/translations/ko.json b/homeassistant/components/adguard/translations/ko.json index c60d16e53b1..8564ba19f3c 100644 --- a/homeassistant/components/adguard/translations/ko.json +++ b/homeassistant/components/adguard/translations/ko.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "Hass.io {addon} \ucd94\uac00 \uae30\ub2a5\uc73c\ub85c AdGuard Home\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Hass.io \ucd94\uac00 \uae30\ub2a5\uc758 AdGuard Home" + "description": "Hass.io {addon} \uc560\ub4dc\uc628\uc73c\ub85c AdGuard Home\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Hass.io \uc560\ub4dc\uc628\uc758 AdGuard Home" }, "user": { "data": { diff --git a/homeassistant/components/almond/translations/ko.json b/homeassistant/components/almond/translations/ko.json index 9fe759a6ba4..cd9d4d67874 100644 --- a/homeassistant/components/almond/translations/ko.json +++ b/homeassistant/components/almond/translations/ko.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "Hass.io {addon} \ucd94\uac00 \uae30\ub2a5\uc73c\ub85c Almond\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Hass.io \ucd94\uac00 \uae30\ub2a5\uc758 Almond" + "description": "Hass.io {addon} \uc560\ub4dc\uc628\uc73c\ub85c Almond\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Hass.io \uc560\ub4dc\uc628\uc758 Almond" }, "pick_implementation": { "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" diff --git a/homeassistant/components/august/translations/ca.json b/homeassistant/components/august/translations/ca.json index f530b2cdc59..8faa12e2757 100644 --- a/homeassistant/components/august/translations/ca.json +++ b/homeassistant/components/august/translations/ca.json @@ -10,6 +10,13 @@ "unknown": "Error inesperat" }, "step": { + "reauth_validate": { + "data": { + "password": "Contrasenya" + }, + "description": "Introdueix la contrasenya per a {username}.", + "title": "Torna a autenticar compte d'August" + }, "user": { "data": { "login_method": "M\u00e8tode d'inici de sessi\u00f3", @@ -20,6 +27,15 @@ "description": "Si el m\u00e8tode d'inici de sessi\u00f3 \u00e9s 'email', el nom d'usuari \u00e9s l'adre\u00e7a de correu electr\u00f2nic. Si el m\u00e8tode d'inici de sessi\u00f3 \u00e9s 'phone', el nom d'usuari \u00e9s el n\u00famero de tel\u00e8fon en el format \"+NNNNNNNNN\".", "title": "Configuraci\u00f3 de compte August" }, + "user_validate": { + "data": { + "login_method": "M\u00e8tode d'inici de sessi\u00f3", + "password": "Contrasenya", + "username": "Nom d'usuari" + }, + "description": "Si el m\u00e8tode d'inici de sessi\u00f3 \u00e9s 'email', el nom d'usuari \u00e9s l'adre\u00e7a de correu electr\u00f2nic. Si el m\u00e8tode d'inici de sessi\u00f3 \u00e9s 'phone', el nom d'usuari \u00e9s el n\u00famero de tel\u00e8fon en format \"+NNNNNNNNN\".", + "title": "Configuraci\u00f3 de compte August" + }, "validation": { "data": { "code": "Codi de verificaci\u00f3" diff --git a/homeassistant/components/august/translations/de.json b/homeassistant/components/august/translations/de.json index 3a5bd70f1af..ef525fb665d 100644 --- a/homeassistant/components/august/translations/de.json +++ b/homeassistant/components/august/translations/de.json @@ -10,6 +10,13 @@ "unknown": "Unerwarteter Fehler" }, "step": { + "reauth_validate": { + "data": { + "password": "Passwort" + }, + "description": "Gib das Passwort f\u00fcr {username} ein.", + "title": "August-Konto erneut authentifizieren" + }, "user": { "data": { "login_method": "Anmeldemethode", @@ -20,6 +27,15 @@ "description": "Wenn die Anmeldemethode \"E-Mail\" lautet, ist Benutzername die E-Mail-Adresse. Wenn die Anmeldemethode \"Telefon\" ist, ist Benutzername die Telefonnummer im Format \"+ NNNNNNNNN\".", "title": "Richten Sie ein August-Konto ein" }, + "user_validate": { + "data": { + "login_method": "Anmeldemethode", + "password": "Passwort", + "username": "Benutzername" + }, + "description": "Wenn die Anmeldemethode \"E-Mail\" lautet, ist Benutzername die E-Mail-Adresse. Wenn die Anmeldemethode \"Telefon\" ist, ist Benutzername die Telefonnummer im Format \"+ NNNNNNNNN\".", + "title": "Richte ein August-Konto ein" + }, "validation": { "data": { "code": "Verifizierungs-Code" diff --git a/homeassistant/components/august/translations/et.json b/homeassistant/components/august/translations/et.json index 0b455b06d00..69cd9e66ce3 100644 --- a/homeassistant/components/august/translations/et.json +++ b/homeassistant/components/august/translations/et.json @@ -10,6 +10,13 @@ "unknown": "Tundmatu viga" }, "step": { + "reauth_validate": { + "data": { + "password": "Salas\u00f5na" + }, + "description": "Sisesta kasutaja {username} salas\u00f5na.", + "title": "Autendi Augusti konto uuesti" + }, "user": { "data": { "login_method": "Sisselogimismeetod", @@ -20,6 +27,15 @@ "description": "Kui sisselogimismeetod on \"e-post\" on kasutajanimi e-posti aadress. Kui sisselogimismeetod on \"telefon\" on kasutajanimi telefoninumber vormingus \"+NNNNNNNNN\".", "title": "Seadista Augusti sidumise konto" }, + "user_validate": { + "data": { + "login_method": "Sisselogimismeetod", + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + }, + "description": "Kui sisselogimismeetod on \"e-post\" on kasutajanimi e-posti aadress. Kui sisselogimismeetod on \"telefon\" on kasutajanimi telefoninumber vormingus \"+NNNNNNNNN\".", + "title": "Seadista Augusti sidumise konto" + }, "validation": { "data": { "code": "Kinnituskood" diff --git a/homeassistant/components/august/translations/hu.json b/homeassistant/components/august/translations/hu.json index dd2f6004354..1bced4e1036 100644 --- a/homeassistant/components/august/translations/hu.json +++ b/homeassistant/components/august/translations/hu.json @@ -10,6 +10,13 @@ "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { + "reauth_validate": { + "data": { + "password": "Jelsz\u00f3" + }, + "description": "Add meg a(z) {username} jelszav\u00e1t.", + "title": "August fi\u00f3k \u00fajrahiteles\u00edt\u00e9se" + }, "user": { "data": { "password": "Jelsz\u00f3", @@ -17,6 +24,14 @@ "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } }, + "user_validate": { + "data": { + "login_method": "Bejelentkez\u00e9si m\u00f3d", + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "title": "August fi\u00f3k be\u00e1ll\u00edt\u00e1sa" + }, "validation": { "data": { "code": "Ellen\u0151rz\u0151 k\u00f3d" diff --git a/homeassistant/components/august/translations/ko.json b/homeassistant/components/august/translations/ko.json index e7aed3d4c2c..f3bc64a706c 100644 --- a/homeassistant/components/august/translations/ko.json +++ b/homeassistant/components/august/translations/ko.json @@ -10,6 +10,13 @@ "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { + "reauth_validate": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638" + }, + "description": "{username}\uc758 \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "August \uacc4\uc815 \uc7ac\uc778\uc99d\ud558\uae30" + }, "user": { "data": { "login_method": "\ub85c\uadf8\uc778 \ubc29\ubc95", @@ -20,6 +27,15 @@ "description": "\ub85c\uadf8\uc778 \ubc29\ubc95\uc774 '\uc774\uba54\uc77c'\uc778 \uacbd\uc6b0, \uc0ac\uc6a9\uc790 \uc774\ub984\uc740 \uc774\uba54\uc77c \uc8fc\uc18c\uc785\ub2c8\ub2e4. \ub85c\uadf8\uc778 \ubc29\ubc95\uc774 '\uc804\ud654\ubc88\ud638'\uc778 \uacbd\uc6b0, \uc0ac\uc6a9\uc790 \uc774\ub984\uc740 '+NNNNNNNNN' \ud615\uc2dd\uc758 \uc804\ud654\ubc88\ud638\uc785\ub2c8\ub2e4.", "title": "August \uacc4\uc815 \uc124\uc815\ud558\uae30" }, + "user_validate": { + "data": { + "login_method": "\ub85c\uadf8\uc778 \ubc29\ubc95", + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "\ub85c\uadf8\uc778 \ubc29\ubc95\uc774 '\uc774\uba54\uc77c'\uc778 \uacbd\uc6b0, \uc0ac\uc6a9\uc790 \uc774\ub984\uc740 \uc774\uba54\uc77c \uc8fc\uc18c\uc785\ub2c8\ub2e4. \ub85c\uadf8\uc778 \ubc29\ubc95\uc774 '\uc804\ud654\ubc88\ud638'\uc778 \uacbd\uc6b0, \uc0ac\uc6a9\uc790 \uc774\ub984\uc740 '+NNNNNNNNN' \ud615\uc2dd\uc758 \uc804\ud654\ubc88\ud638\uc785\ub2c8\ub2e4.", + "title": "August \uacc4\uc815 \uc124\uc815\ud558\uae30" + }, "validation": { "data": { "code": "\uc778\uc99d \ucf54\ub4dc" diff --git a/homeassistant/components/august/translations/no.json b/homeassistant/components/august/translations/no.json index ae314897e74..d90e7f8080a 100644 --- a/homeassistant/components/august/translations/no.json +++ b/homeassistant/components/august/translations/no.json @@ -10,6 +10,13 @@ "unknown": "Uventet feil" }, "step": { + "reauth_validate": { + "data": { + "password": "Passord" + }, + "description": "Skriv inn passordet for {username} .", + "title": "Godkjenn en August-konto p\u00e5 nytt" + }, "user": { "data": { "login_method": "P\u00e5loggingsmetode", @@ -20,6 +27,15 @@ "description": "Hvis p\u00e5loggingsmetoden er 'e-post', er brukernavnet e-postadressen. Hvis p\u00e5loggingsmetoden er 'telefon', er brukernavn telefonnummeret i formatet '+ NNNNNNNNN'.", "title": "Sett opp en August konto" }, + "user_validate": { + "data": { + "login_method": "P\u00e5loggingsmetode", + "password": "Passord", + "username": "Brukernavn" + }, + "description": "Hvis p\u00e5loggingsmetoden er 'e-post', er brukernavnet e-postadressen. Hvis p\u00e5loggingsmetoden er 'telefon', er brukernavn telefonnummeret i formatet '+ NNNNNNNNN'.", + "title": "Sett opp en August konto" + }, "validation": { "data": { "code": "Bekreftelseskode" diff --git a/homeassistant/components/august/translations/ru.json b/homeassistant/components/august/translations/ru.json index 277fd6abec2..0263ef6ee18 100644 --- a/homeassistant/components/august/translations/ru.json +++ b/homeassistant/components/august/translations/ru.json @@ -10,6 +10,13 @@ "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "step": { + "reauth_validate": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f {username}.", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0444\u0438\u043b\u044f" + }, "user": { "data": { "login_method": "\u0421\u043f\u043e\u0441\u043e\u0431 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438", @@ -20,6 +27,15 @@ "description": "\u0415\u0441\u043b\u0438 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043e 'email', \u0442\u043e \u043b\u043e\u0433\u0438\u043d\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0430\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b. \u0415\u0441\u043b\u0438 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043e 'phone', \u0442\u043e \u043b\u043e\u0433\u0438\u043d\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 '+NNNNNNNNN'.", "title": "August" }, + "user_validate": { + "data": { + "login_method": "\u0421\u043f\u043e\u0441\u043e\u0431 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + }, + "description": "\u0415\u0441\u043b\u0438 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043e 'email', \u0442\u043e \u043b\u043e\u0433\u0438\u043d\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0430\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b. \u0415\u0441\u043b\u0438 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043e 'phone', \u0442\u043e \u043b\u043e\u0433\u0438\u043d\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 '+NNNNNNNNN'.", + "title": "August" + }, "validation": { "data": { "code": "\u041a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f" diff --git a/homeassistant/components/august/translations/zh-Hant.json b/homeassistant/components/august/translations/zh-Hant.json index d2721cd33cc..ab157e3da3c 100644 --- a/homeassistant/components/august/translations/zh-Hant.json +++ b/homeassistant/components/august/translations/zh-Hant.json @@ -10,6 +10,13 @@ "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { + "reauth_validate": { + "data": { + "password": "\u5bc6\u78bc" + }, + "description": "\u8f38\u5165{username} \u5bc6\u78bc", + "title": "\u91cd\u65b0\u8a8d\u8b49 August \u5e33\u865f" + }, "user": { "data": { "login_method": "\u767b\u5165\u65b9\u5f0f", @@ -20,6 +27,15 @@ "description": "\u5047\u5982\u767b\u5165\u65b9\u5f0f\u70ba\u90f5\u4ef6\u300cemail\u300d\u3001\u4f7f\u7528\u8005\u540d\u7a31\u70ba\u96fb\u5b50\u90f5\u4ef6\u4f4d\u5740\u3002\u5047\u5982\u767b\u5165\u65b9\u5f0f\u70ba\u96fb\u8a71\u300cphone\u300d\u3001\u5247\u4f7f\u7528\u8005\u540d\u7a31\u70ba\u5305\u542b\u570b\u78bc\u4e4b\u96fb\u8a71\u865f\u78bc\uff0c\u5982\u300c+NNNNNNNNN\u300d\u3002", "title": "\u8a2d\u5b9a August \u5e33\u865f" }, + "user_validate": { + "data": { + "login_method": "\u767b\u5165\u65b9\u5f0f", + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "description": "\u5047\u5982\u767b\u5165\u65b9\u5f0f\u70ba\u90f5\u4ef6\u300cemail\u300d\u3001\u4f7f\u7528\u8005\u540d\u7a31\u70ba\u96fb\u5b50\u90f5\u4ef6\u4f4d\u5740\u3002\u5047\u5982\u767b\u5165\u65b9\u5f0f\u70ba\u96fb\u8a71\u300cphone\u300d\u3001\u5247\u4f7f\u7528\u8005\u540d\u7a31\u70ba\u5305\u542b\u570b\u78bc\u4e4b\u96fb\u8a71\u865f\u78bc\uff0c\u5982\u300c+NNNNNNNNN\u300d\u3002", + "title": "\u8a2d\u5b9a August \u5e33\u865f" + }, "validation": { "data": { "code": "\u9a57\u8b49\u78bc" diff --git a/homeassistant/components/azure_devops/translations/ko.json b/homeassistant/components/azure_devops/translations/ko.json index 4a8807e5f67..cdb67cf77df 100644 --- a/homeassistant/components/azure_devops/translations/ko.json +++ b/homeassistant/components/azure_devops/translations/ko.json @@ -24,7 +24,7 @@ "personal_access_token": "\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070 (PAT)", "project": "\ud504\ub85c\uc81d\ud2b8" }, - "description": "\ud504\ub85c\uc81d\ud2b8\uc5d0 \uc561\uc138\uc2a4\ud560 Azure DevOps \uc778\uc2a4\ud134\uc2a4\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694. \uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070\uc740 \uac1c\uc778 \ud504\ub85c\uc81d\ud2b8\uc5d0\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4.", + "description": "\ud504\ub85c\uc81d\ud2b8\uc5d0 \uc811\uadfc\ud560 Azure DevOps \uc778\uc2a4\ud134\uc2a4\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694. \uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070\uc740 \uac1c\uc778 \ud504\ub85c\uc81d\ud2b8\uc5d0\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4.", "title": "Azure DevOps \ud504\ub85c\uc81d\ud2b8 \ucd94\uac00\ud558\uae30" } } diff --git a/homeassistant/components/bmw_connected_drive/translations/de.json b/homeassistant/components/bmw_connected_drive/translations/de.json index 12a870b4cc9..d274719d7d0 100644 --- a/homeassistant/components/bmw_connected_drive/translations/de.json +++ b/homeassistant/components/bmw_connected_drive/translations/de.json @@ -11,6 +11,7 @@ "user": { "data": { "password": "Passwort", + "region": "ConnectedDrive Region", "username": "Benutzername" } } diff --git a/homeassistant/components/braviatv/translations/ko.json b/homeassistant/components/braviatv/translations/ko.json index c20c6b2eb7b..7bad0f0047c 100644 --- a/homeassistant/components/braviatv/translations/ko.json +++ b/homeassistant/components/braviatv/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "no_ip_control": "TV \uc5d0\uc11c IP \uc81c\uc5b4\uac00 \ube44\ud65c\uc131\ud654\ub418\uc5c8\uac70\ub098 TV \uac00 \uc9c0\uc6d0\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." + "no_ip_control": "TV\uc5d0\uc11c IP \uc81c\uc5b4\uac00 \ube44\ud65c\uc131\ud654\ub418\uc5c8\uac70\ub098 TV\uac00 \uc9c0\uc6d0\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", @@ -21,7 +21,7 @@ "data": { "host": "\ud638\uc2a4\ud2b8" }, - "description": "Sony Bravia TV \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694. \uad6c\uc131\uc5d0 \ubb38\uc81c\uac00 \uc788\ub294 \uacbd\uc6b0 https://www.home-assistant.io/integrations/braviatv \ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.\n\nTV \uac00 \ucf1c\uc838 \uc788\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "description": "Sony Bravia TV \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694. \uad6c\uc131\uc5d0 \ubb38\uc81c\uac00 \uc788\ub294 \uacbd\uc6b0 https://www.home-assistant.io/integrations/braviatv \ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.\n\nTV\uac00 \ucf1c\uc838 \uc788\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", "title": "Sony Bravia TV" } } diff --git a/homeassistant/components/deconz/translations/ko.json b/homeassistant/components/deconz/translations/ko.json index ade0f11b759..811b2400ddd 100644 --- a/homeassistant/components/deconz/translations/ko.json +++ b/homeassistant/components/deconz/translations/ko.json @@ -14,8 +14,8 @@ "flow_title": "deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774 ({host})", "step": { "hassio_confirm": { - "description": "Hass.io {addon} \ucd94\uac00 \uae30\ub2a5\uc5d0\uc11c \uc81c\uacf5\ub41c deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Hass.io \ucd94\uac00 \uae30\ub2a5\uc758 deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774" + "description": "Hass.io {addon} \uc560\ub4dc\uc628\uc5d0\uc11c \uc81c\uacf5\ub41c deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Hass.io \uc560\ub4dc\uc628\uc758 deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774" }, "link": { "description": "Home Assistant\uc5d0 \ub4f1\ub85d\ud558\ub824\uba74 deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \uc7a0\uae08 \ud574\uc81c\ud574\uc8fc\uc138\uc694.\n\n 1. deCONZ \uc124\uc815 -> \uac8c\uc774\ud2b8\uc6e8\uc774 -> \uace0\uae09\uc73c\ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694\n 2. \"\uc571 \uc778\uc99d\ud558\uae30\" \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694", diff --git a/homeassistant/components/dexcom/translations/nl.json b/homeassistant/components/dexcom/translations/nl.json index 6c5027efaae..9be09aff0c8 100644 --- a/homeassistant/components/dexcom/translations/nl.json +++ b/homeassistant/components/dexcom/translations/nl.json @@ -14,7 +14,9 @@ "password": "Wachtwoord", "server": "Server", "username": "Gebruikersnaam" - } + }, + "description": "Voer Dexcom Share-gegevens in", + "title": "Dexcom integratie instellen" } } }, diff --git a/homeassistant/components/dialogflow/translations/ko.json b/homeassistant/components/dialogflow/translations/ko.json index 2cd6d208f00..e0414018787 100644 --- a/homeassistant/components/dialogflow/translations/ko.json +++ b/homeassistant/components/dialogflow/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Dialogflow \uc6f9 \ud6c5]({dialogflow_url})\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/faa_delays/translations/hu.json b/homeassistant/components/faa_delays/translations/hu.json index 95dfabc213a..c511f42a726 100644 --- a/homeassistant/components/faa_delays/translations/hu.json +++ b/homeassistant/components/faa_delays/translations/hu.json @@ -13,6 +13,7 @@ "data": { "id": "Rep\u00fcl\u0151t\u00e9r" }, + "description": "Amerikai rep\u00fcl\u0151t\u00e9ri k\u00f3d be\u00edr\u00e1sa IATA form\u00e1tumban", "title": "FAA Delays" } } diff --git a/homeassistant/components/flume/translations/ko.json b/homeassistant/components/flume/translations/ko.json index b700854ab57..c82f0a990d8 100644 --- a/homeassistant/components/flume/translations/ko.json +++ b/homeassistant/components/flume/translations/ko.json @@ -16,7 +16,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "Flume Personal API \uc5d0 \uc561\uc138\uc2a4 \ud558\ub824\uba74 https://portal.flumetech.com/settings#token \uc5d0\uc11c '\ud074\ub77c\uc774\uc5b8\ud2b8 ID'\ubc0f '\ud074\ub77c\uc774\uc5b8\ud2b8 \uc2dc\ud06c\ub9bf'\uc744 \uc694\uccad\ud574\uc57c \ud569\ub2c8\ub2e4.", + "description": "Flume Personal API \uc5d0 \uc811\uadfc\ud558\ub824\uba74 https://portal.flumetech.com/settings#token \uc5d0\uc11c '\ud074\ub77c\uc774\uc5b8\ud2b8 ID'\ubc0f '\ud074\ub77c\uc774\uc5b8\ud2b8 \uc2dc\ud06c\ub9bf'\uc744 \uc694\uccad\ud574\uc57c \ud569\ub2c8\ub2e4.", "title": "Flume \uacc4\uc815\uc5d0 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/fritzbox_callmonitor/translations/ko.json b/homeassistant/components/fritzbox_callmonitor/translations/ko.json index a9bfff9a5e5..51f4ccec35a 100644 --- a/homeassistant/components/fritzbox_callmonitor/translations/ko.json +++ b/homeassistant/components/fritzbox_callmonitor/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "insufficient_permissions": "AVM FRIZ!Box \uc124\uc815 \ubc0f \uc804\ud654\ubc88\ud638\ubd80\uc5d0 \uc561\uc138\uc2a4\uc5d0 \ud544\uc694\ud55c \uc0ac\uc6a9\uc790\uc758 \uad8c\ud55c\uc774 \ubd80\uc871\ud569\ub2c8\ub2e4.", + "insufficient_permissions": "AVM FRIZ!Box \uc124\uc815 \ubc0f \uc804\ud654\ubc88\ud638\ubd80\uc5d0 \ud544\uc694\ud55c \uc0ac\uc6a9\uc790 \uc811\uadfc \uad8c\ud55c\uc774 \ubd80\uc871\ud569\ub2c8\ub2e4.", "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" }, "error": { diff --git a/homeassistant/components/geofency/translations/ko.json b/homeassistant/components/geofency/translations/ko.json index 2482ccca454..b9303110e35 100644 --- a/homeassistant/components/geofency/translations/ko.json +++ b/homeassistant/components/geofency/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Geofency\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/goalzero/translations/de.json b/homeassistant/components/goalzero/translations/de.json index 7d8962cdb11..3916b987981 100644 --- a/homeassistant/components/goalzero/translations/de.json +++ b/homeassistant/components/goalzero/translations/de.json @@ -13,7 +13,8 @@ "data": { "host": "Host", "name": "Name" - } + }, + "description": "Zun\u00e4chst musst du die Goal Zero App herunterladen: https://www.goalzero.com/product-features/yeti-app/\n\nFolge den Anweisungen, um deinen Yeti mit deinem Wifi-Netzwerk zu verbinden. Bekomme dann die Host-IP von deinem Router. DHCP muss in den Router-Einstellungen f\u00fcr das Ger\u00e4t richtig eingerichtet werden, um sicherzustellen, dass sich die Host-IP nicht \u00e4ndert. Schaue hierzu im Benutzerhandbuch deines Routers nach." } } } diff --git a/homeassistant/components/gpslogger/translations/ko.json b/homeassistant/components/gpslogger/translations/ko.json index 7a4e8b37c2e..3d32cb44736 100644 --- a/homeassistant/components/gpslogger/translations/ko.json +++ b/homeassistant/components/gpslogger/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 GPSLogger\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/hassio/translations/ko.json b/homeassistant/components/hassio/translations/ko.json index 0acdd0c77a1..aba9a665f70 100644 --- a/homeassistant/components/hassio/translations/ko.json +++ b/homeassistant/components/hassio/translations/ko.json @@ -7,7 +7,7 @@ "docker_version": "Docker \ubc84\uc804", "healthy": "\uc2dc\uc2a4\ud15c \uc0c1\ud0dc", "host_os": "\ud638\uc2a4\ud2b8 \uc6b4\uc601 \uccb4\uc81c", - "installed_addons": "\uc124\uce58\ub41c \ucd94\uac00 \uae30\ub2a5", + "installed_addons": "\uc124\uce58\ub41c \uc560\ub4dc\uc628", "supervisor_api": "Supervisor API", "supervisor_version": "Supervisor \ubc84\uc804", "supported": "\uc9c0\uc6d0 \uc5ec\ubd80", diff --git a/homeassistant/components/hive/translations/hu.json b/homeassistant/components/hive/translations/hu.json new file mode 100644 index 00000000000..80c6a7e40f1 --- /dev/null +++ b/homeassistant/components/hive/translations/hu.json @@ -0,0 +1,48 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt", + "unknown_entry": "Nem tal\u00e1lhat\u00f3 megl\u00e9v\u0151 bejegyz\u00e9s." + }, + "error": { + "invalid_code": "Nem siker\u00fclt bejelentkezni a Hive-ba. A k\u00e9tfaktoros hiteles\u00edt\u00e9si k\u00f3d helytelen volt.", + "invalid_password": "Nem siker\u00fclt bejelentkezni a Hive-ba. Helytelen jelsz\u00f3, pr\u00f3b\u00e1lkozz \u00fajra.", + "invalid_username": "Nem siker\u00fclt bejelentkezni a Hive-ba. Az email c\u00edmedet nem siker\u00fclt felismerni.", + "no_internet_available": "A Hive-hoz val\u00f3 csatlakoz\u00e1shoz internetkapcsolat sz\u00fcks\u00e9ges.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "2fa": { + "data": { + "2fa": "K\u00e9tfaktoros k\u00f3d" + }, + "description": "Add meg a Hive hiteles\u00edt\u00e9si k\u00f3dj\u00e1t. \n \n\u00cdrd be a 0000 k\u00f3dot m\u00e1sik k\u00f3d k\u00e9r\u00e9s\u00e9hez.", + "title": "Hive k\u00e9tfaktoros hiteles\u00edt\u00e9s." + }, + "reauth": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "description": "Add meg \u00fajra a Hive bejelentkez\u00e9si adatait.", + "title": "Hive Bejelentkez\u00e9s" + }, + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "description": "Add meg a Hive bejelentkez\u00e9si adatait \u00e9s konfigur\u00e1ci\u00f3j\u00e1t.", + "title": "Hive Bejelentkez\u00e9s" + } + } + }, + "options": { + "step": { + "user": { + "title": "Hive be\u00e1ll\u00edt\u00e1sok" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/translations/ko.json b/homeassistant/components/huawei_lte/translations/ko.json index 1713b81cf7e..ab108964ed8 100644 --- a/homeassistant/components/huawei_lte/translations/ko.json +++ b/homeassistant/components/huawei_lte/translations/ko.json @@ -23,7 +23,7 @@ "url": "URL \uc8fc\uc18c", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "\uae30\uae30 \uc561\uc138\uc2a4 \uc138\ubd80 \uc0ac\ud56d\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc124\uc815\ud558\ub294 \uac83\uc740 \uc120\ud0dd \uc0ac\ud56d\uc774\uc9c0\ub9cc \ub354 \ub9ce\uc740 \uae30\ub2a5\uc744 \uc9c0\uc6d0\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ubc18\uba74, \uc778\uc99d\ub41c \uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uba74, \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uac00 \ud65c\uc131\ud654\ub41c \uc0c1\ud0dc\uc5d0\uc11c \ub2e4\ub978 \ubc29\ubc95\uc73c\ub85c Home Assistant\uc758 \uc678\ubd80\uc5d0\uc11c \uae30\uae30\uc758 \uc6f9 \uc778\ud130\ud398\uc774\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud558\ub294 \ub370 \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "\uae30\uae30 \uc811\uadfc\uc5d0 \ub300\ud55c \uc138\ubd80 \uc0ac\ud56d\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc124\uc815\ud558\ub294 \uac83\uc740 \uc120\ud0dd \uc0ac\ud56d\uc774\uc9c0\ub9cc \ub354 \ub9ce\uc740 \uae30\ub2a5\uc744 \uc9c0\uc6d0\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ubc18\uba74, \uc778\uc99d\ub41c \uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uba74, \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uac00 \ud65c\uc131\ud654\ub41c \uc0c1\ud0dc\uc5d0\uc11c \ub2e4\ub978 \ubc29\ubc95\uc73c\ub85c Home Assistant\uc758 \uc678\ubd80\uc5d0\uc11c \uae30\uae30\uc758 \uc6f9 \uc778\ud130\ud398\uc774\uc2a4\uc5d0 \uc811\uadfc\ud558\ub294 \ub370 \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "Huawei LTE \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/hue/translations/nl.json b/homeassistant/components/hue/translations/nl.json index c2ed895dc8c..d4047ec35ec 100644 --- a/homeassistant/components/hue/translations/nl.json +++ b/homeassistant/components/hue/translations/nl.json @@ -28,7 +28,8 @@ "manual": { "data": { "host": "Host" - } + }, + "title": "Handmatig een Hue bridge configureren" } } }, @@ -52,5 +53,14 @@ "remote_double_button_long_press": "Beide \"{subtype}\" losgelaten na lang indrukken", "remote_double_button_short_press": "Beide \"{subtype}\" losgelaten" } + }, + "options": { + "step": { + "init": { + "data": { + "allow_unreachable": "Onbereikbare lampen toestaan hun status correct te melden" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/ifttt/translations/ko.json b/homeassistant/components/ifttt/translations/ko.json index 826a03c7339..8f01109da76 100644 --- a/homeassistant/components/ifttt/translations/ko.json +++ b/homeassistant/components/ifttt/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\uae30 \uc704\ud574\uc11c\ub294 [IFTTT \uc6f9 \ud6c5 \uc560\ud50c\ub9bf]({applet_url})\uc5d0\uc11c \"Make a web request\"\ub97c \uc0ac\uc6a9\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c\uc758 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\nHome Assistant\ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/input_boolean/translations/ko.json b/homeassistant/components/input_boolean/translations/ko.json index 712051d04a4..5d7d597f790 100644 --- a/homeassistant/components/input_boolean/translations/ko.json +++ b/homeassistant/components/input_boolean/translations/ko.json @@ -5,5 +5,5 @@ "on": "\ucf1c\uc9d0" } }, - "title": "\ub17c\ub9ac\uc785\ub825" + "title": "\ub17c\ub9ac \uc785\ub825" } \ No newline at end of file diff --git a/homeassistant/components/input_datetime/translations/ko.json b/homeassistant/components/input_datetime/translations/ko.json index a5984527e15..3f6a71e23d7 100644 --- a/homeassistant/components/input_datetime/translations/ko.json +++ b/homeassistant/components/input_datetime/translations/ko.json @@ -1,3 +1,3 @@ { - "title": "\ub0a0\uc9dc / \uc2dc\uac04\uc785\ub825" + "title": "\ub0a0\uc9dc/\uc2dc\uac04 \uc785\ub825" } \ No newline at end of file diff --git a/homeassistant/components/input_number/translations/ko.json b/homeassistant/components/input_number/translations/ko.json index 68960733dd8..635e1e26b5e 100644 --- a/homeassistant/components/input_number/translations/ko.json +++ b/homeassistant/components/input_number/translations/ko.json @@ -1,3 +1,3 @@ { - "title": "\uc22b\uc790\uc785\ub825" + "title": "\uc22b\uc790 \uc785\ub825" } \ No newline at end of file diff --git a/homeassistant/components/input_select/translations/ko.json b/homeassistant/components/input_select/translations/ko.json index 5620635d903..59b3f59f143 100644 --- a/homeassistant/components/input_select/translations/ko.json +++ b/homeassistant/components/input_select/translations/ko.json @@ -1,3 +1,3 @@ { - "title": "\uc120\ud0dd\uc785\ub825" + "title": "\uc120\ud0dd \uc785\ub825" } \ No newline at end of file diff --git a/homeassistant/components/input_text/translations/ko.json b/homeassistant/components/input_text/translations/ko.json index 6f8e3a04f2a..43e792dd2ad 100644 --- a/homeassistant/components/input_text/translations/ko.json +++ b/homeassistant/components/input_text/translations/ko.json @@ -1,3 +1,3 @@ { - "title": "\ubb38\uc790\uc785\ub825" + "title": "\ubb38\uc790 \uc785\ub825" } \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/de.json b/homeassistant/components/kodi/translations/de.json index 1d229e5a428..15fd212fdbd 100644 --- a/homeassistant/components/kodi/translations/de.json +++ b/homeassistant/components/kodi/translations/de.json @@ -4,6 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert", "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", + "no_uuid": "Die Kodi-Instanz hat keine eindeutige ID. Dies ist h\u00f6chstwahrscheinlich auf eine alte Kodi-Version (17.x oder niedriger) zur\u00fcckzuf\u00fchren. Du kannst die Integration manuell konfigurieren oder auf eine neuere Kodi-Version aktualisieren.", "unknown": "Unerwarteter Fehler" }, "error": { diff --git a/homeassistant/components/locative/translations/ko.json b/homeassistant/components/locative/translations/ko.json index da063335ff8..50652f76fc5 100644 --- a/homeassistant/components/locative/translations/ko.json +++ b/homeassistant/components/locative/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Locative\uc571\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/lock/translations/ko.json b/homeassistant/components/lock/translations/ko.json index f89a1d1b54f..b8db32ad513 100644 --- a/homeassistant/components/lock/translations/ko.json +++ b/homeassistant/components/lock/translations/ko.json @@ -20,5 +20,5 @@ "unlocked": "\ud574\uc81c" } }, - "title": "\uc7a0\uae40" + "title": "\uc7a0\uae08\uc7a5\uce58" } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/de.json b/homeassistant/components/lutron_caseta/translations/de.json index b6aacf2d0ef..ce771f52acd 100644 --- a/homeassistant/components/lutron_caseta/translations/de.json +++ b/homeassistant/components/lutron_caseta/translations/de.json @@ -2,17 +2,34 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "cannot_connect": "Verbindung fehlgeschlagen" + "cannot_connect": "Verbindung fehlgeschlagen", + "not_lutron_device": "Erkanntes Ger\u00e4t ist kein Lutron-Ger\u00e4t" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen" }, + "flow_title": "Lutron Cas\u00e9ta {name} ({host})", "step": { + "link": { + "title": "Mit der Bridge verbinden" + }, "user": { "data": { "host": "Host" - } + }, + "description": "Gib die IP-Adresse des Ger\u00e4ts ein.", + "title": "Automatisch mit der Bridge verbinden" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Erste Taste", + "button_2": "Zweite Taste", + "button_3": "Dritte Taste", + "button_4": "Vierte Taste", + "off": "Aus", + "on": "An" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/hu.json b/homeassistant/components/lutron_caseta/translations/hu.json index bf949ddf21c..921f5e83409 100644 --- a/homeassistant/components/lutron_caseta/translations/hu.json +++ b/homeassistant/components/lutron_caseta/translations/hu.json @@ -23,7 +23,8 @@ "button_3": "Harmadik gomb", "button_4": "Negyedik gomb", "off": "Ki", - "on": "Be" + "on": "Be", + "stop_all": "Az \u00f6sszes le\u00e1ll\u00edt\u00e1sa" } } } \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/ko.json b/homeassistant/components/mailgun/translations/ko.json index 1be1721d78f..2a296303d58 100644 --- a/homeassistant/components/mailgun/translations/ko.json +++ b/homeassistant/components/mailgun/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Mailgun \uc6f9 \ud6c5]({mailgun_url})\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \nHome Assistant\ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/motion_blinds/translations/de.json b/homeassistant/components/motion_blinds/translations/de.json index c1a7ac0bc8d..01eba9c7ecd 100644 --- a/homeassistant/components/motion_blinds/translations/de.json +++ b/homeassistant/components/motion_blinds/translations/de.json @@ -10,12 +10,16 @@ "connect": { "data": { "api_key": "API-Schl\u00fcssel" - } + }, + "description": "Ein 16-Zeichen-API-Schl\u00fcssel wird ben\u00f6tigt, siehe https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", + "title": "Motion Jalousien" }, "select": { "data": { "select_ip": "IP-Adresse" - } + }, + "description": "F\u00fchre das Setup erneut aus, wenn du weitere Motion Gateways verbinden m\u00f6chtest", + "title": "W\u00e4hle das Motion Gateway aus, zu dem du eine Verbindung herstellen m\u00f6chten" }, "user": { "data": { diff --git a/homeassistant/components/mqtt/translations/ko.json b/homeassistant/components/mqtt/translations/ko.json index f10c9bbf7e9..e7631c5805d 100644 --- a/homeassistant/components/mqtt/translations/ko.json +++ b/homeassistant/components/mqtt/translations/ko.json @@ -21,8 +21,8 @@ "data": { "discovery": "\uae30\uae30 \uac80\uc0c9 \ud65c\uc131\ud654" }, - "description": "Hass.io {addon} \ucd94\uac00 \uae30\ub2a5\uc5d0\uc11c \uc81c\uacf5\ub41c MQTT \ube0c\ub85c\ucee4\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Hass.io \ucd94\uac00 \uae30\ub2a5\uc758 MQTT \ube0c\ub85c\ucee4" + "description": "Hass.io {addon} \uc560\ub4dc\uc628\uc5d0\uc11c \uc81c\uacf5\ub41c MQTT \ube0c\ub85c\ucee4\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Hass.io \uc560\ub4dc\uc628\uc758 MQTT \ube0c\ub85c\ucee4" } } }, diff --git a/homeassistant/components/mysensors/translations/hu.json b/homeassistant/components/mysensors/translations/hu.json index 5faaf7aea56..7d4df1f12da 100644 --- a/homeassistant/components/mysensors/translations/hu.json +++ b/homeassistant/components/mysensors/translations/hu.json @@ -11,9 +11,17 @@ "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "invalid_serial": "\u00c9rv\u00e9nytelen soros port", + "invalid_version": "\u00c9rv\u00e9nytelen MySensors verzi\u00f3", + "port_out_of_range": "A portsz\u00e1mnak legal\u00e1bb 1-nek \u00e9s legfeljebb 65535-nek kell lennie", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { + "gw_mqtt": { + "data": { + "version": "MySensors verzi\u00f3" + } + }, "gw_serial": { "data": { "version": "MySensors verzi\u00f3" diff --git a/homeassistant/components/netatmo/translations/hu.json b/homeassistant/components/netatmo/translations/hu.json index b3140a6f115..b4979396eeb 100644 --- a/homeassistant/components/netatmo/translations/hu.json +++ b/homeassistant/components/netatmo/translations/hu.json @@ -20,8 +20,19 @@ "away": "t\u00e1vol" }, "trigger_type": { + "alarm_started": "{entity_name} riaszt\u00e1st \u00e9szlelt", + "animal": "{entity_name} \u00e9szlelt egy \u00e1llatot", + "cancel_set_point": "{entity_name} folytatta \u00fctemez\u00e9s\u00e9t", + "human": "{entity_name} embert \u00e9szlelt", + "movement": "{entity_name} mozg\u00e1st \u00e9szlelt", + "outdoor": "{entity_name} k\u00fclt\u00e9ri esem\u00e9nyt \u00e9szlelt", + "person": "{entity_name} szem\u00e9lyt \u00e9szlelt", + "person_away": "{entity_name} \u00e9szlelte, hogy egy szem\u00e9ly t\u00e1vozott", + "set_point": "A(z) {entity_name} c\u00e9lh\u0151m\u00e9rs\u00e9klet manu\u00e1lisan lett be\u00e1ll\u00edtva", + "therm_mode": "{entity_name} \u00e1tv\u00e1ltott erre: \"{subtype}\"", "turned_off": "{entity_name} ki lett kapcsolva", - "turned_on": "{entity_name} be lett kapcsolva" + "turned_on": "{entity_name} be lett kapcsolva", + "vehicle": "{entity_name} j\u00e1rm\u0171vet \u00e9szlelt" } }, "options": { diff --git a/homeassistant/components/ovo_energy/translations/ko.json b/homeassistant/components/ovo_energy/translations/ko.json index 273c146e805..4ca904725b4 100644 --- a/homeassistant/components/ovo_energy/translations/ko.json +++ b/homeassistant/components/ovo_energy/translations/ko.json @@ -19,7 +19,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "\uc5d0\ub108\uc9c0 \uc0ac\uc6a9\ub7c9\uc5d0 \uc561\uc138\uc2a4 \ud558\ub824\uba74 OVO Energy \uc778\uc2a4\ud134\uc2a4\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694.", + "description": "\uc5d0\ub108\uc9c0 \uc0ac\uc6a9\ub7c9\uc5d0 \uc811\uadfc\ud558\ub824\uba74 OVO Energy \uc778\uc2a4\ud134\uc2a4\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694.", "title": "OVO Energy \uacc4\uc815 \ucd94\uac00\ud558\uae30" } } diff --git a/homeassistant/components/ozw/translations/de.json b/homeassistant/components/ozw/translations/de.json index afa26fb7e03..c58c55c49ad 100644 --- a/homeassistant/components/ozw/translations/de.json +++ b/homeassistant/components/ozw/translations/de.json @@ -1,11 +1,17 @@ { "config": { "abort": { + "addon_info_failed": "Fehler beim Abrufen von OpenZWave Add-on Informationen.", + "addon_install_failed": "Installation des OpenZWave Add-ons fehlgeschlagen.", + "addon_set_config_failed": "Setzen der OpenZWave Konfiguration fehlgeschlagen.", "already_configured": "Ger\u00e4t ist bereits konfiguriert", "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "mqtt_required": "Die MQTT-Integration ist nicht eingerichtet", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, + "error": { + "addon_start_failed": "Fehler beim Starten des OpenZWave Add-ons. \u00dcberpr\u00fcfe die Konfiguration." + }, "progress": { "install_addon": "Bitte warten, bis die Installation des OpenZWave-Add-Ons abgeschlossen ist. Dies kann einige Minuten dauern." }, @@ -17,13 +23,18 @@ "title": "Die Installation des OpenZWave-Add-On wurde gestartet" }, "on_supervisor": { + "data": { + "use_addon": "Verwende das OpenZWave Supervisor Add-on" + }, + "description": "M\u00f6chtest du das OpenZWave Supervisor Add-on verwenden?", "title": "Verbindungstyp ausw\u00e4hlen" }, "start_addon": { "data": { "network_key": "Netzwerk-Schl\u00fcssel", "usb_path": "USB-Ger\u00e4te-Pfad" - } + }, + "title": "Gib die Konfiguration des OpenZWave Add-ons ein" } } } diff --git a/homeassistant/components/ozw/translations/ko.json b/homeassistant/components/ozw/translations/ko.json index f118db7f72c..f6dddf5c96a 100644 --- a/homeassistant/components/ozw/translations/ko.json +++ b/homeassistant/components/ozw/translations/ko.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "addon_info_failed": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc758 \uc815\ubcf4\ub97c \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", - "addon_install_failed": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc744 \uc124\uce58\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_info_failed": "OpenZWave \uc560\ub4dc\uc628\uc758 \uc815\ubcf4\ub97c \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_install_failed": "OpenZWave \uc560\ub4dc\uc628\uc744 \uc124\uce58\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", "addon_set_config_failed": "OpenZWave \uad6c\uc131\uc744 \uc124\uc815\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", @@ -10,23 +10,23 @@ "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { - "addon_start_failed": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uad6c\uc131 \ub0b4\uc6a9\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694." + "addon_start_failed": "OpenZWave \uc560\ub4dc\uc628\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uad6c\uc131 \ub0b4\uc6a9\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694." }, "progress": { - "install_addon": "Openzwave \ucd94\uac00 \uae30\ub2a5\uc758 \uc124\uce58\uac00 \uc644\ub8cc\ub418\ub294 \ub3d9\uc548 \uc7a0\uc2dc \uae30\ub2e4\ub824\uc8fc\uc138\uc694. \uba87 \ubd84 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "install_addon": "Openzwave \uc560\ub4dc\uc628\uc758 \uc124\uce58\uac00 \uc644\ub8cc\ub418\ub294 \ub3d9\uc548 \uc7a0\uc2dc \uae30\ub2e4\ub824\uc8fc\uc138\uc694. \uba87 \ubd84 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "hassio_confirm": { - "title": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc73c\ub85c OpenZWave \ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc124\uc815\ud558\uae30" + "title": "OpenZWave \uc560\ub4dc\uc628\uc73c\ub85c OpenZWave \ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc124\uc815\ud558\uae30" }, "install_addon": { - "title": "Openzwave \ucd94\uac00 \uae30\ub2a5 \uc124\uce58\uac00 \uc2dc\uc791\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "title": "Openzwave \uc560\ub4dc\uc628 \uc124\uce58\uac00 \uc2dc\uc791\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "on_supervisor": { "data": { - "use_addon": "OpenZWave Supervisor \ucd94\uac00 \uae30\ub2a5\uc744 \uc0ac\uc6a9\ud558\uae30" + "use_addon": "OpenZWave Supervisor \uc560\ub4dc\uc628\uc744 \uc0ac\uc6a9\ud558\uae30" }, - "description": "OpenZWave Supervisor \ucd94\uac00 \uae30\ub2a5\uc744 \uc0ac\uc6a9\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "OpenZWave Supervisor \uc560\ub4dc\uc628\uc744 \uc0ac\uc6a9\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "\uc5f0\uacb0 \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" }, "start_addon": { @@ -34,7 +34,7 @@ "network_key": "\ub124\ud2b8\uc6cc\ud06c \ud0a4", "usb_path": "USB \uc7a5\uce58 \uacbd\ub85c" }, - "title": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc758 \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" + "title": "OpenZWave \uc560\ub4dc\uc628\uc758 \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" } } } diff --git a/homeassistant/components/panasonic_viera/translations/ko.json b/homeassistant/components/panasonic_viera/translations/ko.json index 0f3252a4ab1..4eb136c1149 100644 --- a/homeassistant/components/panasonic_viera/translations/ko.json +++ b/homeassistant/components/panasonic_viera/translations/ko.json @@ -14,7 +14,7 @@ "data": { "pin": "PIN \ucf54\ub4dc" }, - "description": "TV \uc5d0 \ud45c\uc2dc\ub41c PIN \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", + "description": "TV\uc5d0 \ud45c\uc2dc\ub41c PIN \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", "title": "\ud398\uc5b4\ub9c1\ud558\uae30" }, "user": { @@ -22,7 +22,7 @@ "host": "IP \uc8fc\uc18c", "name": "\uc774\ub984" }, - "description": "Panasonic Viera TV \uc758 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", + "description": "Panasonic Viera TV\uc758 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", "title": "TV \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/philips_js/translations/ca.json b/homeassistant/components/philips_js/translations/ca.json index b12d8e7a848..b94faccd615 100644 --- a/homeassistant/components/philips_js/translations/ca.json +++ b/homeassistant/components/philips_js/translations/ca.json @@ -14,8 +14,8 @@ "data": { "pin": "Codi PIN" }, - "description": "Introdueix el PIN que apareix a la pantalla del televisor", - "title": "Emparellar" + "description": "Introdueix el PIN que es mostra al televisor", + "title": "Vinculaci\u00f3" }, "user": { "data": { diff --git a/homeassistant/components/philips_js/translations/de.json b/homeassistant/components/philips_js/translations/de.json index d0c9932b3cd..6288e9fb5c4 100644 --- a/homeassistant/components/philips_js/translations/de.json +++ b/homeassistant/components/philips_js/translations/de.json @@ -13,7 +13,8 @@ "pair": { "data": { "pin": "PIN-Code" - } + }, + "description": "Gib die auf deinem Fernseher angezeigten PIN ein" }, "user": { "data": { diff --git a/homeassistant/components/philips_js/translations/et.json b/homeassistant/components/philips_js/translations/et.json index 9953df9c272..4a5bf9fe6e9 100644 --- a/homeassistant/components/philips_js/translations/et.json +++ b/homeassistant/components/philips_js/translations/et.json @@ -10,6 +10,13 @@ "unknown": "Ootamatu t\u00f5rge" }, "step": { + "pair": { + "data": { + "pin": "PIN kood" + }, + "description": "Sisesta teleris kuvatav PIN-kood", + "title": "Paarita" + }, "user": { "data": { "api_version": "API versioon", diff --git a/homeassistant/components/philips_js/translations/hu.json b/homeassistant/components/philips_js/translations/hu.json index 0065a78ae0c..f7ce3f708b0 100644 --- a/homeassistant/components/philips_js/translations/hu.json +++ b/homeassistant/components/philips_js/translations/hu.json @@ -10,6 +10,13 @@ "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { + "pair": { + "data": { + "pin": "PIN-k\u00f3d" + }, + "description": "\u00cdrd be a t\u00e9v\u00e9n megjelen\u0151 PIN-k\u00f3dot", + "title": "P\u00e1ros\u00edt\u00e1s" + }, "user": { "data": { "api_version": "API Verzi\u00f3", diff --git a/homeassistant/components/philips_js/translations/ko.json b/homeassistant/components/philips_js/translations/ko.json index 9a7c530ce52..a76fd70418c 100644 --- a/homeassistant/components/philips_js/translations/ko.json +++ b/homeassistant/components/philips_js/translations/ko.json @@ -13,7 +13,9 @@ "pair": { "data": { "pin": "PIN \ucf54\ub4dc" - } + }, + "description": "TV\uc5d0 \ud45c\uc2dc\ub41c PIN \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694", + "title": "\ud398\uc5b4\ub9c1" }, "user": { "data": { diff --git a/homeassistant/components/philips_js/translations/no.json b/homeassistant/components/philips_js/translations/no.json index a9c647a644b..5b1df7c8e8b 100644 --- a/homeassistant/components/philips_js/translations/no.json +++ b/homeassistant/components/philips_js/translations/no.json @@ -10,6 +10,13 @@ "unknown": "Uventet feil" }, "step": { + "pair": { + "data": { + "pin": "PIN kode" + }, + "description": "Angi PIN-koden som vises p\u00e5 TV-en", + "title": "Par" + }, "user": { "data": { "api_version": "API-versjon", diff --git a/homeassistant/components/philips_js/translations/ru.json b/homeassistant/components/philips_js/translations/ru.json index 83511ff246a..df3dfd4b6f6 100644 --- a/homeassistant/components/philips_js/translations/ru.json +++ b/homeassistant/components/philips_js/translations/ru.json @@ -10,6 +10,13 @@ "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "step": { + "pair": { + "data": { + "pin": "PIN-\u043a\u043e\u0434" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 PIN-\u043a\u043e\u0434, \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u044b\u0439 \u043d\u0430 \u044d\u043a\u0440\u0430\u043d\u0435 \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440\u0430", + "title": "\u0421\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435" + }, "user": { "data": { "api_version": "\u0412\u0435\u0440\u0441\u0438\u044f API", diff --git a/homeassistant/components/philips_js/translations/zh-Hant.json b/homeassistant/components/philips_js/translations/zh-Hant.json index 13bfd52e980..7ae9c8893d5 100644 --- a/homeassistant/components/philips_js/translations/zh-Hant.json +++ b/homeassistant/components/philips_js/translations/zh-Hant.json @@ -10,6 +10,13 @@ "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { + "pair": { + "data": { + "pin": "PIN \u78bc" + }, + "description": "\u8f38\u5165\u96fb\u8996\u986f\u793a\u4e4b PIN \u78bc", + "title": "\u914d\u5c0d" + }, "user": { "data": { "api_version": "API \u7248\u672c", diff --git a/homeassistant/components/pi_hole/translations/hu.json b/homeassistant/components/pi_hole/translations/hu.json index 104f711d8d5..a8f8563da41 100644 --- a/homeassistant/components/pi_hole/translations/hu.json +++ b/homeassistant/components/pi_hole/translations/hu.json @@ -20,6 +20,7 @@ "name": "N\u00e9v", "port": "Port", "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "statistics_only": "Csak statisztik\u00e1k", "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" } } diff --git a/homeassistant/components/plaato/translations/de.json b/homeassistant/components/plaato/translations/de.json index e3e94ddf848..9a092ef4fa6 100644 --- a/homeassistant/components/plaato/translations/de.json +++ b/homeassistant/components/plaato/translations/de.json @@ -8,6 +8,11 @@ "create_entry": { "default": "Um Ereignisse an Home Assistant zu senden, muss das Webhook Feature in Plaato Airlock konfiguriert werden.\n\n F\u00fcge die folgenden Informationen ein: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n \n Weitere Informationen finden sich in der [Dokumentation]({docs_url})." }, + "error": { + "invalid_webhook_device": "Du hast ein Ger\u00e4t gew\u00e4hlt, das das Senden von Daten an einen Webhook nicht unterst\u00fctzt. Es ist nur f\u00fcr die Airlock verf\u00fcgbar", + "no_api_method": "Du musst ein Authentifizierungstoken hinzuf\u00fcgen oder ein Webhook ausw\u00e4hlen", + "no_auth_token": "Du musst ein Authentifizierungstoken hinzuf\u00fcgen" + }, "step": { "api_method": { "data": { diff --git a/homeassistant/components/plaato/translations/hu.json b/homeassistant/components/plaato/translations/hu.json index aee8ceb07cd..8347b5d2f98 100644 --- a/homeassistant/components/plaato/translations/hu.json +++ b/homeassistant/components/plaato/translations/hu.json @@ -10,6 +10,10 @@ }, "step": { "user": { + "data": { + "device_name": "Eszk\u00f6z neve", + "device_type": "A Plaato eszk\u00f6z t\u00edpusa" + }, "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?", "title": "A Plaato eszk\u00f6z\u00f6k be\u00e1ll\u00edt\u00e1sa" } diff --git a/homeassistant/components/plaato/translations/ko.json b/homeassistant/components/plaato/translations/ko.json index 1cb999c4729..fb75bbb7d7c 100644 --- a/homeassistant/components/plaato/translations/ko.json +++ b/homeassistant/components/plaato/translations/ko.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { "default": "**{device_name}**\uc758 Plaato {device_type}\uc774(\uac00) \uc131\uacf5\uc801\uc73c\ub85c \uc124\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4!" diff --git a/homeassistant/components/plex/translations/ko.json b/homeassistant/components/plex/translations/ko.json index df728533467..d6b0c6a2341 100644 --- a/homeassistant/components/plex/translations/ko.json +++ b/homeassistant/components/plex/translations/ko.json @@ -35,7 +35,7 @@ "title": "Plex \uc11c\ubc84 \uc120\ud0dd\ud558\uae30" }, "user": { - "description": "Plex \uc11c\ubc84\ub97c \uc5f0\uacb0\ud558\ub824\uba74 [plex.tv](https://plex.tv) \ub85c \uacc4\uc18d \uc9c4\ud589\ud574\uc8fc\uc138\uc694.", + "description": "Plex \uc11c\ubc84\ub97c \uc5f0\uacb0\ud558\ub824\uba74 [plex.tv](https://plex.tv)\ub85c \uacc4\uc18d \uc9c4\ud589\ud574\uc8fc\uc138\uc694.", "title": "Plex \ubbf8\ub514\uc5b4 \uc11c\ubc84" }, "user_advanced": { diff --git a/homeassistant/components/plugwise/translations/nl.json b/homeassistant/components/plugwise/translations/nl.json index a432b8bbfd8..efd679a1ce3 100644 --- a/homeassistant/components/plugwise/translations/nl.json +++ b/homeassistant/components/plugwise/translations/nl.json @@ -8,6 +8,7 @@ "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, + "flow_title": "Glimlach: {name}", "step": { "user": { "data": { diff --git a/homeassistant/components/rfxtrx/translations/de.json b/homeassistant/components/rfxtrx/translations/de.json index 2315e739909..0f3837f3a59 100644 --- a/homeassistant/components/rfxtrx/translations/de.json +++ b/homeassistant/components/rfxtrx/translations/de.json @@ -38,19 +38,32 @@ "options": { "error": { "already_configured_device": "Ger\u00e4t ist bereits konfiguriert", + "invalid_event_code": "Ung\u00fcltiger Ereigniscode", + "invalid_input_2262_off": "Ung\u00fcltige Eingabe f\u00fcr Befehl \"aus\"", + "invalid_input_2262_on": "Ung\u00fcltige Eingabe f\u00fcr Befehl \"an\"", + "invalid_input_off_delay": "Ung\u00fcltige Eingabe f\u00fcr Ausschaltverz\u00f6gerung", "unknown": "Unerwarteter Fehler" }, "step": { "prompt_options": { "data": { - "debug": "Debugging aktivieren" - } + "automatic_add": "Automatisches Hinzuf\u00fcgen aktivieren", + "debug": "Debugging aktivieren", + "device": "Zu konfigurierendes Ger\u00e4t ausw\u00e4hlen", + "remove_device": "Zu l\u00f6schendes Ger\u00e4t ausw\u00e4hlen" + }, + "title": "Rfxtrx Optionen" }, "set_device_options": { "data": { + "command_off": "Datenbitwert f\u00fcr den Befehl \"aus\"", + "command_on": "Datenbitwert f\u00fcr den Befehl \"ein\"", + "data_bit": "Anzahl der Datenbits", + "fire_event": "Ger\u00e4teereignis aktivieren", "off_delay": "Ausschaltverz\u00f6gerung", "off_delay_enabled": "Ausschaltverz\u00f6gerung aktivieren", "replace_device": "W\u00e4hle ein Ger\u00e4t aus, das ersetzt werden soll", + "signal_repetitions": "Anzahl der Signalwiederholungen", "venetian_blind_mode": "Jalousie-Modus" } } diff --git a/homeassistant/components/samsungtv/translations/ko.json b/homeassistant/components/samsungtv/translations/ko.json index 5686c684e1f..7efb88bf7eb 100644 --- a/homeassistant/components/samsungtv/translations/ko.json +++ b/homeassistant/components/samsungtv/translations/ko.json @@ -18,7 +18,7 @@ "host": "\ud638\uc2a4\ud2b8", "name": "\uc774\ub984" }, - "description": "\uc0bc\uc131 TV \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. Home Assistant \ub97c \uc5f0\uacb0 \ud55c \uc801\uc774 \uc5c6\ub2e4\uba74 TV \uc5d0\uc11c \uc778\uc99d\uc744 \uc694\uccad\ud558\ub294 \ud31d\uc5c5\uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4." + "description": "\uc0bc\uc131 TV \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. Home Assistant\ub97c \uc5f0\uacb0 \ud55c \uc801\uc774 \uc5c6\ub2e4\uba74 TV\uc5d0\uc11c \uc778\uc99d\uc744 \uc694\uccad\ud558\ub294 \ud31d\uc5c5\uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4." } } } diff --git a/homeassistant/components/screenlogic/translations/ca.json b/homeassistant/components/screenlogic/translations/ca.json new file mode 100644 index 00000000000..68bfad1ff94 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/ca.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "Adre\u00e7a IP", + "port": "Port" + }, + "description": "Introdueix la informaci\u00f3 de la passarel\u00b7la ScreenLogic.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "Passarel\u00b7la" + }, + "description": "S'han descobert les seg\u00fcents passarel\u00b7les ScreenLogic. Tria'n una per configurar-la o escull configurar manualment una passarel\u00b7la ScreenLogic.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Segons entre escanejos" + }, + "description": "Especifica la configuraci\u00f3 de {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/de.json b/homeassistant/components/screenlogic/translations/de.json new file mode 100644 index 00000000000..6afe42e37ee --- /dev/null +++ b/homeassistant/components/screenlogic/translations/de.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "IP-Adresse", + "port": "Port" + }, + "description": "Gib deine ScreenLogic Gateway-Informationen ein.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "" + }, + "description": "Die folgenden ScreenLogic-Gateways wurden erkannt. Bitte w\u00e4hle eines aus, um es zu konfigurieren oder w\u00e4hle ein ScreenLogic-Gateway zum manuellen Konfigurieren.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Sekunden zwischen den Scans" + }, + "description": "Einstellungen f\u00fcr {gateway_name} angeben", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/et.json b/homeassistant/components/screenlogic/translations/et.json new file mode 100644 index 00000000000..cf2cf19418f --- /dev/null +++ b/homeassistant/components/screenlogic/translations/et.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "IP aadress", + "port": "Port" + }, + "description": "Sisesta oma ScreenLogic Gateway teave.", + "title": "" + }, + "gateway_select": { + "data": { + "selected_gateway": "L\u00fc\u00fcs" + }, + "description": "Avastati j\u00e4rgmised ScreenLogicu l\u00fc\u00fcsid. Vali seadistatav l\u00fc\u00fcs v\u00f5i seadista ScreenLogicu l\u00fc\u00fcs k\u00e4sitsi.", + "title": "" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "P\u00e4ringute vahe sekundites" + }, + "description": "M\u00e4\u00e4ra {gateway_name} s\u00e4tted", + "title": "" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/hu.json b/homeassistant/components/screenlogic/translations/hu.json new file mode 100644 index 00000000000..59e48fda273 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/hu.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "IP c\u00edm", + "port": "Port" + }, + "description": "Add meg a ScreenLogic Gateway adatait.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "Gateway" + }, + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Szkennel\u00e9sek k\u00f6z\u00f6tti m\u00e1sodpercek" + }, + "description": "{gateway_name} be\u00e1ll\u00edt\u00e1sainak megad\u00e1sa", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/ko.json b/homeassistant/components/screenlogic/translations/ko.json new file mode 100644 index 00000000000..3a8d457c603 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/ko.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "IP \uc8fc\uc18c", + "port": "\ud3ec\ud2b8" + }, + "description": "ScreenLogic \uac8c\uc774\ud2b8\uc6e8\uc774 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "\uac8c\uc774\ud2b8\uc6e8\uc774" + }, + "description": "\ub2e4\uc74c ScreenLogic \uac8c\uc774\ud2b8\uc6e8\uc774\uac00 \uac80\uc0c9\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \uad6c\uc131\ud558\uac70\ub098 \uc218\ub3d9\uc73c\ub85c ScreenLogic \uac8c\uc774\ud2b8\uc6e8\uc774\ub85c \uad6c\uc131\ud560 \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\uc2a4\uce94 \uac04\uaca9(\ucd08)" + }, + "description": "{gateway_name}\uc5d0 \ub300\ud55c \uc124\uc815 \uc9c0\uc815\ud558\uae30", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/no.json b/homeassistant/components/screenlogic/translations/no.json new file mode 100644 index 00000000000..0ca4827514a --- /dev/null +++ b/homeassistant/components/screenlogic/translations/no.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "IP adresse", + "port": "Port" + }, + "description": "Skriv inn din ScreenLogic Gateway-informasjon.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "Gateway" + }, + "description": "F\u00f8lgende ScreenLogic-gateways ble oppdaget. Velg en \u00e5 konfigurere, eller velg \u00e5 konfigurere en ScreenLogic gateway manuelt.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Sekunder mellom skanninger" + }, + "description": "Angi innstillinger for {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/ru.json b/homeassistant/components/screenlogic/translations/ru.json new file mode 100644 index 00000000000..a657b7360c7 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/ru.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0448\u043b\u044e\u0437\u0435 ScreenLogic.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "\u0428\u043b\u044e\u0437" + }, + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043e\u0434\u0438\u043d \u0438\u0437 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0445 \u0448\u043b\u044e\u0437\u043e\u0432 ScreenLogic \u0438\u043b\u0438 \u0443\u043a\u0430\u0436\u0438\u0442\u0435 \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043c\u0435\u0436\u0434\u0443 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\u043c\u0438 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + }, + "description": "\u0423\u043a\u0430\u0436\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0434\u043b\u044f {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/zh-Hant.json b/homeassistant/components/screenlogic/translations/zh-Hant.json new file mode 100644 index 00000000000..40ca94fd779 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/zh-Hant.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "IP \u4f4d\u5740", + "port": "\u901a\u8a0a\u57e0" + }, + "description": "\u8f38\u5165 ScreenLogic \u9598\u9053\u5668\u8cc7\u8a0a\u3002", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "\u9598\u9053\u5668" + }, + "description": "\u641c\u5c0b\u5230\u4ee5\u4e0b ScreenLogic \u9598\u9053\u5668\uff0c\u8acb\u9078\u64c7\u6240\u8981\u8a2d\u5b9a\u7684\u9598\u9053\u5668\u3001\u6216\u9032\u884c\u624b\u52d5\u8a2d\u5b9a\u3002", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u6383\u63cf\u9593\u9694\u79d2\u6578" + }, + "description": "{gateway_name} \u7279\u5b9a\u8a2d\u5b9a", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sensor/translations/hu.json b/homeassistant/components/sensor/translations/hu.json index 9c49de27a7b..98e817fc164 100644 --- a/homeassistant/components/sensor/translations/hu.json +++ b/homeassistant/components/sensor/translations/hu.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "{entity_name} aktu\u00e1lis akku szintje", + "is_carbon_dioxide": "Jelenlegi {entity_name} sz\u00e9n-dioxid koncentr\u00e1ci\u00f3 szint", + "is_carbon_monoxide": "Jelenlegi {entity_name} sz\u00e9n-monoxid koncentr\u00e1ci\u00f3 szint", "is_humidity": "{entity_name} aktu\u00e1lis p\u00e1ratartalma", "is_illuminance": "{entity_name} aktu\u00e1lis megvil\u00e1g\u00edt\u00e1sa", "is_power": "{entity_name} aktu\u00e1lis teljes\u00edtm\u00e9nye", @@ -13,6 +15,8 @@ }, "trigger_type": { "battery_level": "{entity_name} akku szintje v\u00e1ltozik", + "carbon_dioxide": "{entity_name} sz\u00e9n-dioxid koncentr\u00e1ci\u00f3ja megv\u00e1ltozik", + "carbon_monoxide": "{entity_name} sz\u00e9n-monoxid koncentr\u00e1ci\u00f3ja megv\u00e1ltozik", "humidity": "{entity_name} p\u00e1ratartalma v\u00e1ltozik", "illuminance": "{entity_name} megvil\u00e1g\u00edt\u00e1sa v\u00e1ltozik", "power": "{entity_name} teljes\u00edtm\u00e9nye v\u00e1ltozik", diff --git a/homeassistant/components/smarthab/translations/nl.json b/homeassistant/components/smarthab/translations/nl.json index 7f5fc7fe27c..31a02ae2b97 100644 --- a/homeassistant/components/smarthab/translations/nl.json +++ b/homeassistant/components/smarthab/translations/nl.json @@ -10,7 +10,9 @@ "data": { "email": "E-mail", "password": "Wachtwoord" - } + }, + "description": "Om technische redenen moet u een tweede account gebruiken dat specifiek is voor uw Home Assistant-installatie. U kunt er een aanmaken vanuit de SmartHab-toepassing.", + "title": "Stel SmartHab in" } } } diff --git a/homeassistant/components/somfy_mylink/translations/de.json b/homeassistant/components/somfy_mylink/translations/de.json index 522e185af5d..4382ccd4a0c 100644 --- a/homeassistant/components/somfy_mylink/translations/de.json +++ b/homeassistant/components/somfy_mylink/translations/de.json @@ -25,9 +25,13 @@ }, "step": { "entity_config": { + "description": "Optionen f\u00fcr `{entity_id}` konfigurieren", "title": "Entit\u00e4t konfigurieren" }, "init": { + "data": { + "entity_id": "Konfiguriere eine bestimmte Entit\u00e4t." + }, "title": "MyLink-Optionen konfigurieren" }, "target_config": { @@ -35,5 +39,6 @@ "title": "MyLink-Cover konfigurieren" } } - } + }, + "title": "" } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/hu.json b/homeassistant/components/somfy_mylink/translations/hu.json index 1ef6d25d34c..08d0db14866 100644 --- a/homeassistant/components/somfy_mylink/translations/hu.json +++ b/homeassistant/components/somfy_mylink/translations/hu.json @@ -23,8 +23,18 @@ "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { + "entity_config": { + "title": "Entit\u00e1s konfigur\u00e1l\u00e1sa" + }, "init": { + "data": { + "target_id": "Az \u00e1rny\u00e9kol\u00f3 be\u00e1ll\u00edt\u00e1sainak konfigur\u00e1l\u00e1sa." + }, "title": "Mylink be\u00e1ll\u00edt\u00e1sok konfigur\u00e1l\u00e1sa" + }, + "target_config": { + "description": "A(z) `{target_name}` be\u00e1ll\u00edt\u00e1sainak konfigur\u00e1l\u00e1sa", + "title": "MyLink \u00e1rny\u00e9kol\u00f3 konfigur\u00e1l\u00e1sa" } } }, diff --git a/homeassistant/components/sonarr/translations/de.json b/homeassistant/components/sonarr/translations/de.json index 19a37dbcc4f..939c5eed1c8 100644 --- a/homeassistant/components/sonarr/translations/de.json +++ b/homeassistant/components/sonarr/translations/de.json @@ -12,6 +12,7 @@ "flow_title": "Sonarr: {name}", "step": { "reauth_confirm": { + "description": "Die Sonarr-Integration muss manuell mit der Sonarr-API, die unter {host} gehostet wird, neu authentifiziert werden", "title": "Integration erneut authentifizieren" }, "user": { diff --git a/homeassistant/components/traccar/translations/ko.json b/homeassistant/components/traccar/translations/ko.json index 2af5079ae2d..aa5e2a65736 100644 --- a/homeassistant/components/traccar/translations/ko.json +++ b/homeassistant/components/traccar/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Traccar\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c\uc758 URL \uc8fc\uc18c\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4: `{webhook_url}`\n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/tuya/translations/de.json b/homeassistant/components/tuya/translations/de.json index cf5d3dfffe8..6650dc754b3 100644 --- a/homeassistant/components/tuya/translations/de.json +++ b/homeassistant/components/tuya/translations/de.json @@ -28,6 +28,20 @@ "error": { "dev_not_config": "Ger\u00e4tetyp nicht konfigurierbar", "dev_not_found": "Ger\u00e4t nicht gefunden" + }, + "step": { + "device": { + "data": { + "brightness_range_mode": "Vom Ger\u00e4t genutzter Helligkeitsbereich", + "max_kelvin": "Maximal unterst\u00fctzte Farbtemperatur in Kelvin", + "max_temp": "Maximale Solltemperatur (f\u00fcr Voreinstellung min und max = 0 verwenden)", + "min_kelvin": "Minimale unterst\u00fctzte Farbtemperatur in Kelvin", + "min_temp": "Minimal Solltemperatur (f\u00fcr Voreinstellung min und max = 0 verwenden)", + "support_color": "Farbunterst\u00fctzung erzwingen", + "tuya_max_coltemp": "Vom Ger\u00e4t gemeldete maximale Farbtemperatur", + "unit_of_measurement": "Vom Ger\u00e4t verwendete Temperatureinheit" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/ko.json b/homeassistant/components/tuya/translations/ko.json index 3e3fbc68bee..afa2541e7b9 100644 --- a/homeassistant/components/tuya/translations/ko.json +++ b/homeassistant/components/tuya/translations/ko.json @@ -37,9 +37,9 @@ "brightness_range_mode": "\uae30\uae30\uc5d0\uc11c \uc0ac\uc6a9\ud558\ub294 \ubc1d\uae30 \ubc94\uc704", "curr_temp_divider": "\ud604\uc7ac \uc628\ub3c4 \uac12 \ubd84\ud560 (0 = \uae30\ubcf8\uac12 \uc0ac\uc6a9)", "max_kelvin": "\uce98\ube48 \ub2e8\uc704\uc758 \ucd5c\ub300 \uc0c9\uc628\ub3c4", - "max_temp": "\ucd5c\ub300 \ubaa9\ud45c \uc628\ub3c4 (\uae30\ubcf8\uac12\uc758 \uacbd\uc6b0 \ucd5c\uc18c\uac12 \ubc0f \ucd5c\ub300\uac12 = 0)", + "max_temp": "\ucd5c\ub300 \ubaa9\ud45c \uc628\ub3c4 (\uae30\ubcf8\uac12\uc758 \uacbd\uc6b0 \ucd5c\uc19f\uac12 \ubc0f \ucd5c\ub313\uac12 = 0)", "min_kelvin": "\uce98\ube48 \ub2e8\uc704\uc758 \ucd5c\uc18c \uc0c9\uc628\ub3c4", - "min_temp": "\ucd5c\uc18c \ubaa9\ud45c \uc628\ub3c4 (\uae30\ubcf8\uac12\uc758 \uacbd\uc6b0 \ucd5c\uc18c\uac12 \ubc0f \ucd5c\ub300\uac12 = 0)", + "min_temp": "\ucd5c\uc18c \ubaa9\ud45c \uc628\ub3c4 (\uae30\ubcf8\uac12\uc758 \uacbd\uc6b0 \ucd5c\uc19f\uac12 \ubc0f \ucd5c\ub313\uac12 = 0)", "set_temp_divided": "\uc124\uc815 \uc628\ub3c4 \uba85\ub839\uc5d0 \ubd84\ud560\ub41c \uc628\ub3c4 \uac12 \uc0ac\uc6a9\ud558\uae30", "support_color": "\uc0c9\uc0c1 \uc9c0\uc6d0 \uac15\uc81c \uc801\uc6a9\ud558\uae30", "temp_divider": "\uc628\ub3c4 \uac12 \ubd84\ud560 (0 = \uae30\ubcf8\uac12 \uc0ac\uc6a9)", diff --git a/homeassistant/components/twilio/translations/ko.json b/homeassistant/components/twilio/translations/ko.json index 4feeacf1a20..aeb7c09474d 100644 --- a/homeassistant/components/twilio/translations/ko.json +++ b/homeassistant/components/twilio/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Twilio \uc6f9 \ud6c5]({twilio_url})\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/x-www-form-urlencoded\n \nHome Assistant\ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/unifi/translations/de.json b/homeassistant/components/unifi/translations/de.json index 05dd66fe56c..b1d3e495f94 100644 --- a/homeassistant/components/unifi/translations/de.json +++ b/homeassistant/components/unifi/translations/de.json @@ -10,6 +10,7 @@ "service_unavailable": "Verbindung fehlgeschlagen", "unknown_client_mac": "Unter dieser MAC-Adresse ist kein Client verf\u00fcgbar." }, + "flow_title": "UniFi Netzwerk {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/ko.json b/homeassistant/components/unifi/translations/ko.json index f5ada3df99a..3a13b420097 100644 --- a/homeassistant/components/unifi/translations/ko.json +++ b/homeassistant/components/unifi/translations/ko.json @@ -29,11 +29,11 @@ "step": { "client_control": { "data": { - "block_client": "\ub124\ud2b8\uc6cc\ud06c \uc561\uc138\uc2a4 \uc81c\uc5b4 \ud074\ub77c\uc774\uc5b8\ud2b8", + "block_client": "\ub124\ud2b8\uc6cc\ud06c \uc811\uadfc \uc81c\uc5b4 \ud074\ub77c\uc774\uc5b8\ud2b8", "dpi_restrictions": "DPI \uc81c\ud55c \uadf8\ub8f9\uc758 \uc81c\uc5b4 \ud5c8\uc6a9\ud558\uae30", "poe_clients": "\ud074\ub77c\uc774\uc5b8\ud2b8\uc758 POE \uc81c\uc5b4 \ud5c8\uc6a9\ud558\uae30" }, - "description": "\ud074\ub77c\uc774\uc5b8\ud2b8 \ucee8\ud2b8\ub864 \uad6c\uc131 \n\n\ub124\ud2b8\uc6cc\ud06c \uc561\uc138\uc2a4\ub97c \uc81c\uc5b4\ud558\ub824\ub294 \uc2dc\ub9ac\uc5bc \ubc88\ud638\uc5d0 \ub300\ud55c \uc2a4\uc704\uce58\ub97c \ub9cc\ub4ed\ub2c8\ub2e4.", + "description": "\ud074\ub77c\uc774\uc5b8\ud2b8 \ucee8\ud2b8\ub864 \uad6c\uc131 \n\n\ub124\ud2b8\uc6cc\ud06c \uc811\uadfc\uc744 \uc81c\uc5b4\ud558\ub824\ub294 \uc2dc\ub9ac\uc5bc \ubc88\ud638\uc5d0 \ub300\ud55c \uc2a4\uc704\uce58\ub97c \ub9cc\ub4ed\ub2c8\ub2e4.", "title": "UniFi \uc635\uc158 2/3" }, "device_tracker": { @@ -50,7 +50,7 @@ }, "simple_options": { "data": { - "block_client": "\ub124\ud2b8\uc6cc\ud06c \uc561\uc138\uc2a4 \uc81c\uc5b4 \ud074\ub77c\uc774\uc5b8\ud2b8", + "block_client": "\ub124\ud2b8\uc6cc\ud06c \uc811\uadfc \uc81c\uc5b4 \ud074\ub77c\uc774\uc5b8\ud2b8", "track_clients": "\ub124\ud2b8\uc6cc\ud06c \ud074\ub77c\uc774\uc5b8\ud2b8 \ucd94\uc801 \ub300\uc0c1", "track_devices": "\ub124\ud2b8\uc6cc\ud06c \uae30\uae30 \ucd94\uc801 (Ubiquiti \uae30\uae30)" }, diff --git a/homeassistant/components/upcloud/translations/ko.json b/homeassistant/components/upcloud/translations/ko.json index 073236bd80b..24b4ab0a446 100644 --- a/homeassistant/components/upcloud/translations/ko.json +++ b/homeassistant/components/upcloud/translations/ko.json @@ -17,7 +17,7 @@ "step": { "init": { "data": { - "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ucd08, \ucd5c\uc18c\uac12 30)" + "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ucd08, \ucd5c\uc19f\uac12 30)" } } } diff --git a/homeassistant/components/upnp/translations/ko.json b/homeassistant/components/upnp/translations/ko.json index 7dd2e5c685c..ab80ceb9caa 100644 --- a/homeassistant/components/upnp/translations/ko.json +++ b/homeassistant/components/upnp/translations/ko.json @@ -12,7 +12,7 @@ }, "user": { "data": { - "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ucd08, \ucd5c\uc18c\uac12 30)", + "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ucd08, \ucd5c\uc19f\uac12 30)", "usn": "\uae30\uae30" } } diff --git a/homeassistant/components/verisure/translations/hu.json b/homeassistant/components/verisure/translations/hu.json new file mode 100644 index 00000000000..85e53003566 --- /dev/null +++ b/homeassistant/components/verisure/translations/hu.json @@ -0,0 +1,46 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "installation": { + "data": { + "giid": "Telep\u00edt\u00e9s" + } + }, + "reauth_confirm": { + "data": { + "description": "Hiteles\u00edts \u00fajra a Verisure My Pages fi\u00f3koddal.", + "email": "E-mail", + "password": "Jelsz\u00f3" + } + }, + "user": { + "data": { + "description": "Jelentkezz be a Verisure My Pages fi\u00f3koddal.", + "email": "E-mail", + "password": "Jelsz\u00f3" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "Az alap\u00e9rtelmezett PIN-k\u00f3d nem egyezik meg a sz\u00fcks\u00e9ges sz\u00e1mjegyek sz\u00e1m\u00e1val" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Sz\u00e1mjegyek sz\u00e1ma a z\u00e1rak PIN-k\u00f3dj\u00e1ban", + "lock_default_code": "Alap\u00e9rtelmezett PIN-k\u00f3d z\u00e1rakhoz, akkor haszn\u00e1latos, ha nincs m\u00e1s megadva" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vizio/translations/ko.json b/homeassistant/components/vizio/translations/ko.json index 9310adfa4b9..cc86e020a1c 100644 --- a/homeassistant/components/vizio/translations/ko.json +++ b/homeassistant/components/vizio/translations/ko.json @@ -15,7 +15,7 @@ "data": { "pin": "PIN \ucf54\ub4dc" }, - "description": "TV \uc5d0 \ucf54\ub4dc\uac00 \ud45c\uc2dc\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud574\ub2f9 \ucf54\ub4dc\ub97c \uc785\ub825\ub780\uc5d0 \uc785\ub825\ud55c \ud6c4 \ub2e4\uc74c \ub2e8\uacc4\ub97c \uacc4\uc18d\ud558\uc5ec \ud398\uc5b4\ub9c1\uc744 \uc644\ub8cc\ud574\uc8fc\uc138\uc694.", + "description": "TV\uc5d0 \ucf54\ub4dc\uac00 \ud45c\uc2dc\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud574\ub2f9 \ucf54\ub4dc\ub97c \uc785\ub825\ub780\uc5d0 \uc785\ub825\ud55c \ud6c4 \ub2e4\uc74c \ub2e8\uacc4\ub97c \uacc4\uc18d\ud558\uc5ec \ud398\uc5b4\ub9c1\uc744 \uc644\ub8cc\ud574\uc8fc\uc138\uc694.", "title": "\ud398\uc5b4\ub9c1 \uacfc\uc815 \ub05d\ub0b4\uae30" }, "pairing_complete": { @@ -33,7 +33,7 @@ "host": "\ud638\uc2a4\ud2b8", "name": "\uc774\ub984" }, - "description": "\uc561\uc138\uc2a4 \ud1a0\ud070\uc740 TV \uc5d0\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4. TV \ub97c \uad6c\uc131\ud558\uace0 \uc788\uace0 \uc544\uc9c1 \uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \uc5c6\ub294 \uacbd\uc6b0 \ud398\uc5b4\ub9c1 \uacfc\uc815\uc744 \uc9c4\ud589\ud558\ub824\uba74 \ube44\uc6cc\ub450\uc138\uc694.", + "description": "\uc561\uc138\uc2a4 \ud1a0\ud070\uc740 TV\uc5d0\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4. TV\ub97c \uad6c\uc131\ud558\uace0 \uc788\uace0 \uc544\uc9c1 \uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \uc5c6\ub294 \uacbd\uc6b0 \ud398\uc5b4\ub9c1 \uacfc\uc815\uc744 \uc9c4\ud589\ud558\ub824\uba74 \ube44\uc6cc\ub450\uc138\uc694.", "title": "VIZIO SmartCast \uae30\uae30" } } diff --git a/homeassistant/components/water_heater/translations/hu.json b/homeassistant/components/water_heater/translations/hu.json index c3c47030acb..82f88d1f0de 100644 --- a/homeassistant/components/water_heater/translations/hu.json +++ b/homeassistant/components/water_heater/translations/hu.json @@ -4,5 +4,16 @@ "turn_off": "{entity_name} kikapcsol\u00e1sa", "turn_on": "{entity_name} bekapcsol\u00e1sa" } + }, + "state": { + "_": { + "eco": "Takar\u00e9kos", + "electric": "Elektromos", + "gas": "G\u00e1z", + "heat_pump": "H\u0151szivatty\u00fa", + "high_demand": "Magas ig\u00e9nybev\u00e9tel", + "off": "Ki", + "performance": "Teljes\u00edtm\u00e9ny" + } } } \ No newline at end of file diff --git a/homeassistant/components/xiaomi_aqara/translations/ko.json b/homeassistant/components/xiaomi_aqara/translations/ko.json index 131975c1d8c..dd8a9ae5ede 100644 --- a/homeassistant/components/xiaomi_aqara/translations/ko.json +++ b/homeassistant/components/xiaomi_aqara/translations/ko.json @@ -26,7 +26,7 @@ "key": "\uac8c\uc774\ud2b8\uc6e8\uc774 \ud0a4", "name": "\uac8c\uc774\ud2b8\uc6e8\uc774 \uc774\ub984" }, - "description": "\ud0a4\uac00 \uc81c\uacf5\ub418\uc9c0 \uc54a\uc73c\uba74 \uc13c\uc11c\uc5d0\ub9cc \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ud0a4(\ube44\ubc00\ubc88\ud638)\ub97c \uc5bb\ub294 \ubc29\ubc95\uc740 \ub2e4\uc74c\uc758 \uc548\ub0b4\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz", + "description": "\ud0a4\uac00 \uc81c\uacf5\ub418\uc9c0 \uc54a\uc73c\uba74 \uc13c\uc11c\uc5d0\ub9cc \uc811\uadfc\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ud0a4(\ube44\ubc00\ubc88\ud638)\ub97c \uc5bb\ub294 \ubc29\ubc95\uc740 \ub2e4\uc74c\uc758 \uc548\ub0b4\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz", "title": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774 \ucd94\uac00 \uc124\uc815\ud558\uae30" }, "user": { diff --git a/homeassistant/components/zwave_js/translations/de.json b/homeassistant/components/zwave_js/translations/de.json index 30254e2a523..ae9293cf926 100644 --- a/homeassistant/components/zwave_js/translations/de.json +++ b/homeassistant/components/zwave_js/translations/de.json @@ -1,7 +1,9 @@ { "config": { "abort": { + "addon_info_failed": "Fehler beim Abrufen von Z-Wave JS Add-on Informationen.", "addon_install_failed": "Installation des Z-Wave JS Add-Ons fehlgeschlagen.", + "addon_set_config_failed": "Setzen der Z-Wave JS Konfiguration fehlgeschlagen", "addon_start_failed": "Starten des Z-Wave JS Add-ons fehlgeschlagen.", "already_configured": "Ger\u00e4t ist bereits konfiguriert", "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", @@ -10,6 +12,7 @@ "error": { "addon_start_failed": "Fehler beim Starten des Z-Wave JS Add-Ons. \u00dcberpr\u00fcfe die Konfiguration.", "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_ws_url": "Ung\u00fcltige Websocket-URL", "unknown": "Unerwarteter Fehler" }, "progress": { @@ -19,6 +22,7 @@ "step": { "configure_addon": { "data": { + "network_key": "Netzwerk-Schl\u00fcssel", "usb_path": "USB-Ger\u00e4te-Pfad" }, "title": "Gib die Konfiguration des Z-Wave JS Add-ons ein" @@ -50,5 +54,6 @@ } } } - } + }, + "title": "" } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/ko.json b/homeassistant/components/zwave_js/translations/ko.json index 72d81531551..22149af7496 100644 --- a/homeassistant/components/zwave_js/translations/ko.json +++ b/homeassistant/components/zwave_js/translations/ko.json @@ -1,25 +1,25 @@ { "config": { "abort": { - "addon_get_discovery_info_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uac80\uc0c9 \uc815\ubcf4\ub97c \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", - "addon_info_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uc815\ubcf4\ub97c \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", - "addon_install_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc744 \uc124\uce58\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", - "addon_missing_discovery_info": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uac80\uc0c9 \uc815\ubcf4\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.", + "addon_get_discovery_info_failed": "Z-Wave JS \uc560\ub4dc\uc628\uc758 \uac80\uc0c9 \uc815\ubcf4\ub97c \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_info_failed": "Z-Wave JS \uc560\ub4dc\uc628\uc758 \uc815\ubcf4\ub97c \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_install_failed": "Z-Wave JS \uc560\ub4dc\uc628\uc744 \uc124\uce58\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_missing_discovery_info": "Z-Wave JS \uc560\ub4dc\uc628\uc758 \uac80\uc0c9 \uc815\ubcf4\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.", "addon_set_config_failed": "Z-Wave JS \uad6c\uc131\uc744 \uc124\uc815\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", - "addon_start_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_start_failed": "Z-Wave JS \uc560\ub4dc\uc628\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "addon_start_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uad6c\uc131 \ub0b4\uc6a9\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "addon_start_failed": "Z-Wave JS \uc560\ub4dc\uc628\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uad6c\uc131 \ub0b4\uc6a9\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694.", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_ws_url": "\uc6f9 \uc18c\ucf13 URL \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "progress": { - "install_addon": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uc124\uce58\uac00 \uc644\ub8cc\ub418\ub294 \ub3d9\uc548 \uc7a0\uc2dc \uae30\ub2e4\ub824\uc8fc\uc138\uc694. \uba87 \ubd84 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "start_addon": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5 \uc2dc\uc791\uc774 \uc644\ub8cc\ub418\ub294 \ub3d9\uc548 \uc7a0\uc2dc \uae30\ub2e4\ub824\uc8fc\uc138\uc694. \uba87 \ucd08 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "install_addon": "Z-Wave JS \uc560\ub4dc\uc628\uc758 \uc124\uce58\uac00 \uc644\ub8cc\ub418\ub294 \ub3d9\uc548 \uc7a0\uc2dc \uae30\ub2e4\ub824\uc8fc\uc138\uc694. \uba87 \ubd84 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "start_addon": "Z-Wave JS \uc560\ub4dc\uc628 \uc2dc\uc791\uc774 \uc644\ub8cc\ub418\ub294 \ub3d9\uc548 \uc7a0\uc2dc \uae30\ub2e4\ub824\uc8fc\uc138\uc694. \uba87 \ucd08 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "configure_addon": { @@ -27,13 +27,13 @@ "network_key": "\ub124\ud2b8\uc6cc\ud06c \ud0a4", "usb_path": "USB \uc7a5\uce58 \uacbd\ub85c" }, - "title": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" + "title": "Z-Wave JS \uc560\ub4dc\uc628\uc758 \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" }, "hassio_confirm": { - "title": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc73c\ub85c Z-Wave JS \ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc124\uc815\ud558\uae30" + "title": "Z-Wave JS \uc560\ub4dc\uc628\uc73c\ub85c Z-Wave JS \ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc124\uc815\ud558\uae30" }, "install_addon": { - "title": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5 \uc124\uce58\uac00 \uc2dc\uc791\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "title": "Z-Wave JS \uc560\ub4dc\uc628 \uc124\uce58\uac00 \uc2dc\uc791\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "manual": { "data": { @@ -42,13 +42,13 @@ }, "on_supervisor": { "data": { - "use_addon": "Z-Wave JS Supervisor \ucd94\uac00 \uae30\ub2a5\uc744 \uc0ac\uc6a9\ud558\uae30" + "use_addon": "Z-Wave JS Supervisor \uc560\ub4dc\uc628\uc744 \uc0ac\uc6a9\ud558\uae30" }, - "description": "Z-Wave JS Supervisor \ucd94\uac00 \uae30\ub2a5\uc744 \uc0ac\uc6a9\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Z-Wave JS Supervisor \uc560\ub4dc\uc628\uc744 \uc0ac\uc6a9\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "\uc5f0\uacb0 \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" }, "start_addon": { - "title": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc774 \uc2dc\uc791\ud558\ub294 \uc911\uc785\ub2c8\ub2e4." + "title": "Z-Wave JS \uc560\ub4dc\uc628\uc774 \uc2dc\uc791\ud558\ub294 \uc911\uc785\ub2c8\ub2e4." }, "user": { "data": { From f785cc7d9a4c5cf885b6c5cda0141d3b3c1f4cd8 Mon Sep 17 00:00:00 2001 From: Tobias Haber Date: Thu, 18 Mar 2021 04:59:06 +0100 Subject: [PATCH 437/831] Google has deprecated a comma separated list for modes changed it to array (#48029) --- homeassistant/components/google_assistant/trait.py | 2 +- tests/components/google_assistant/__init__.py | 10 +++++++++- tests/components/google_assistant/test_trait.py | 10 +++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index be7ceb98ad3..7babc5e4836 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -763,7 +763,7 @@ class TemperatureSettingTrait(_Trait): mode in modes for mode in ("heatcool", "heat", "cool") ): modes.append("on") - response["availableThermostatModes"] = ",".join(modes) + response["availableThermostatModes"] = modes return response diff --git a/tests/components/google_assistant/__init__.py b/tests/components/google_assistant/__init__.py index 4bef45cf0ee..9047e175ae6 100644 --- a/tests/components/google_assistant/__init__.py +++ b/tests/components/google_assistant/__init__.py @@ -276,7 +276,15 @@ DEMO_DEVICES = [ "type": "action.devices.types.THERMOSTAT", "willReportState": False, "attributes": { - "availableThermostatModes": "off,heat,cool,heatcool,auto,dry,fan-only", + "availableThermostatModes": [ + "off", + "heat", + "cool", + "heatcool", + "auto", + "dry", + "fan-only", + ], "thermostatTemperatureUnit": "C", }, }, diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index 43e9c30f91a..fd62d225aac 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -713,7 +713,7 @@ async def test_temperature_setting_climate_onoff(hass): BASIC_CONFIG, ) assert trt.sync_attributes() == { - "availableThermostatModes": "off,cool,heat,heatcool,on", + "availableThermostatModes": ["off", "cool", "heat", "heatcool", "on"], "thermostatTemperatureUnit": "F", } assert trt.can_execute(trait.COMMAND_THERMOSTAT_SET_MODE, {}) @@ -752,7 +752,7 @@ async def test_temperature_setting_climate_no_modes(hass): BASIC_CONFIG, ) assert trt.sync_attributes() == { - "availableThermostatModes": "heat", + "availableThermostatModes": ["heat"], "thermostatTemperatureUnit": "C", } @@ -788,7 +788,7 @@ async def test_temperature_setting_climate_range(hass): BASIC_CONFIG, ) assert trt.sync_attributes() == { - "availableThermostatModes": "off,cool,heat,auto,on", + "availableThermostatModes": ["off", "cool", "heat", "auto", "on"], "thermostatTemperatureUnit": "F", } assert trt.query_attributes() == { @@ -862,7 +862,7 @@ async def test_temperature_setting_climate_setpoint(hass): BASIC_CONFIG, ) assert trt.sync_attributes() == { - "availableThermostatModes": "off,cool,on", + "availableThermostatModes": ["off", "cool", "on"], "thermostatTemperatureUnit": "C", } assert trt.query_attributes() == { @@ -920,7 +920,7 @@ async def test_temperature_setting_climate_setpoint_auto(hass): BASIC_CONFIG, ) assert trt.sync_attributes() == { - "availableThermostatModes": "off,heatcool,on", + "availableThermostatModes": ["off", "heatcool", "on"], "thermostatTemperatureUnit": "C", } assert trt.query_attributes() == { From 6fb0e493357a683ee4f77c3c636fa61767355f8f Mon Sep 17 00:00:00 2001 From: corneyl Date: Thu, 18 Mar 2021 04:59:48 +0100 Subject: [PATCH 438/831] Upgraded aiopylgtv to v0.4.0 (#47014) Co-authored-by: Paulus Schoutsen --- homeassistant/components/webostv/__init__.py | 33 ++++++++++++- .../components/webostv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/webostv/test_media_player.py | 47 ++++++++++++++++++- 5 files changed, 81 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/webostv/__init__.py b/homeassistant/components/webostv/__init__.py index 3a117955bee..34e32dce163 100644 --- a/homeassistant/components/webostv/__init__.py +++ b/homeassistant/components/webostv/__init__.py @@ -1,8 +1,11 @@ """Support for LG webOS Smart TV.""" import asyncio +import json import logging +import os from aiopylgtv import PyLGTVCmdException, PyLGTVPairException, WebOsClient +from sqlitedict import SqliteDict import voluptuous as vol from websockets.exceptions import ConnectionClosed @@ -101,13 +104,41 @@ async def async_setup(hass, config): return True +def convert_client_keys(config_file): + """In case the config file contains JSON, convert it to a Sqlite config file.""" + # Return early if config file is non-existing + if not os.path.isfile(config_file): + return + + # Try to parse the file as being JSON + with open(config_file) as json_file: + try: + json_conf = json.load(json_file) + except (json.JSONDecodeError, UnicodeDecodeError): + json_conf = None + + # If the file contains JSON, convert it to an Sqlite DB + if json_conf: + _LOGGER.warning("LG webOS TV client-key file is being migrated to Sqlite!") + + # Clean the JSON file + os.remove(config_file) + + # Write the data to the Sqlite DB + with SqliteDict(config_file) as conf: + for host, key in json_conf.items(): + conf[host] = key + conf.commit() + + async def async_setup_tv(hass, config, conf): """Set up a LG WebOS TV based on host parameter.""" host = conf[CONF_HOST] config_file = hass.config.path(WEBOSTV_CONFIG_FILE) + await hass.async_add_executor_job(convert_client_keys, config_file) - client = WebOsClient(host, config_file) + client = await WebOsClient.create(host, config_file) hass.data[DOMAIN][host] = {"client": client} if client.is_registered(): diff --git a/homeassistant/components/webostv/manifest.json b/homeassistant/components/webostv/manifest.json index acdee1d9ca9..7773e9c4963 100644 --- a/homeassistant/components/webostv/manifest.json +++ b/homeassistant/components/webostv/manifest.json @@ -2,7 +2,7 @@ "domain": "webostv", "name": "LG webOS Smart TV", "documentation": "https://www.home-assistant.io/integrations/webostv", - "requirements": ["aiopylgtv==0.3.3"], + "requirements": ["aiopylgtv==0.4.0"], "dependencies": ["configurator"], "codeowners": ["@bendavid"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0398f053ec3..40a3e8545be 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -215,7 +215,7 @@ aiopvapi==1.6.14 aiopvpc==2.0.2 # homeassistant.components.webostv -aiopylgtv==0.3.3 +aiopylgtv==0.4.0 # homeassistant.components.recollect_waste aiorecollect==1.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 47c10b5700c..5a17b05864d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -134,7 +134,7 @@ aiopvapi==1.6.14 aiopvpc==2.0.2 # homeassistant.components.webostv -aiopylgtv==0.3.3 +aiopylgtv==0.4.0 # homeassistant.components.recollect_waste aiorecollect==1.0.1 diff --git a/tests/components/webostv/test_media_player.py b/tests/components/webostv/test_media_player.py index 3015fcccf9f..716c496d88a 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -1,8 +1,10 @@ """The tests for the LG webOS media player platform.""" - +import json +import os from unittest.mock import patch import pytest +from sqlitedict import SqliteDict from homeassistant.components import media_player from homeassistant.components.media_player.const import ( @@ -16,6 +18,7 @@ from homeassistant.components.webostv.const import ( DOMAIN, SERVICE_BUTTON, SERVICE_COMMAND, + WEBOSTV_CONFIG_FILE, ) from homeassistant.const import ( ATTR_COMMAND, @@ -36,6 +39,7 @@ def client_fixture(): with patch( "homeassistant.components.webostv.WebOsClient", autospec=True ) as mock_client_class: + mock_client_class.create.return_value = mock_client_class.return_value client = mock_client_class.return_value client.software_info = {"device_id": "a1:b1:c1:d1:e1:f1"} client.client_key = "0123456789" @@ -52,6 +56,13 @@ async def setup_webostv(hass): await hass.async_block_till_done() +@pytest.fixture +def cleanup_config(hass): + """Test cleanup, remove the config file.""" + yield + os.remove(hass.config.path(WEBOSTV_CONFIG_FILE)) + + async def test_mute(hass, client): """Test simple service call.""" @@ -128,3 +139,37 @@ async def test_command_with_optional_arg(hass, client): client.request.assert_called_with( "test", payload={"target": "https://www.google.com"} ) + + +async def test_migrate_keyfile_to_sqlite(hass, client, cleanup_config): + """Test migration from JSON key-file to Sqlite based one.""" + key = "3d5b1aeeb98e" + # Create config file with JSON content + config_file = hass.config.path(WEBOSTV_CONFIG_FILE) + with open(config_file, "w+") as file: + json.dump({"host": key}, file) + + # Run the component setup + await setup_webostv(hass) + + # Assert that the config file is a Sqlite database which contains the key + with SqliteDict(config_file) as conf: + assert conf.get("host") == key + + +async def test_dont_migrate_sqlite_keyfile(hass, client, cleanup_config): + """Test that migration is not performed and setup still succeeds when config file is already an Sqlite DB.""" + key = "3d5b1aeeb98e" + + # Create config file with Sqlite DB + config_file = hass.config.path(WEBOSTV_CONFIG_FILE) + with SqliteDict(config_file) as conf: + conf["host"] = key + conf.commit() + + # Run the component setup + await setup_webostv(hass) + + # Assert that the config file is still an Sqlite database and setup didn't fail + with SqliteDict(config_file) as conf: + assert conf.get("host") == key From 08db262972c33c7a466b93b9dc90f460a255fb33 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 17 Mar 2021 18:27:21 -1000 Subject: [PATCH 439/831] Add a service to reload config entries that can easily be called though automations (#46762) --- .../components/homeassistant/__init__.py | 39 ++++- .../components/homeassistant/services.yaml | 16 ++ homeassistant/helpers/service.py | 158 ++++++++++-------- tests/components/homeassistant/test_init.py | 62 +++++++ tests/helpers/test_service.py | 25 +++ 5 files changed, 228 insertions(+), 72 deletions(-) diff --git a/homeassistant/components/homeassistant/__init__.py b/homeassistant/components/homeassistant/__init__.py index c2ee40b7d43..309f98e6095 100644 --- a/homeassistant/components/homeassistant/__init__.py +++ b/homeassistant/components/homeassistant/__init__.py @@ -21,15 +21,30 @@ from homeassistant.const import ( import homeassistant.core as ha from homeassistant.exceptions import HomeAssistantError, Unauthorized, UnknownUser from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.service import async_extract_referenced_entity_ids +from homeassistant.helpers.service import ( + async_extract_config_entry_ids, + async_extract_referenced_entity_ids, +) + +ATTR_ENTRY_ID = "entry_id" _LOGGER = logging.getLogger(__name__) DOMAIN = ha.DOMAIN SERVICE_RELOAD_CORE_CONFIG = "reload_core_config" +SERVICE_RELOAD_CONFIG_ENTRY = "reload_config_entry" SERVICE_CHECK_CONFIG = "check_config" SERVICE_UPDATE_ENTITY = "update_entity" SERVICE_SET_LOCATION = "set_location" SCHEMA_UPDATE_ENTITY = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids}) +SCHEMA_RELOAD_CONFIG_ENTRY = vol.All( + vol.Schema( + { + vol.Optional(ATTR_ENTRY_ID): str, + **cv.ENTITY_SERVICE_FIELDS, + }, + ), + cv.has_at_least_one_key(ATTR_ENTRY_ID, *cv.ENTITY_SERVICE_FIELDS), +) async def async_setup(hass: ha.HomeAssistant, config: dict) -> bool: @@ -203,4 +218,26 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> bool: vol.Schema({ATTR_LATITUDE: cv.latitude, ATTR_LONGITUDE: cv.longitude}), ) + async def async_handle_reload_config_entry(call): + """Service handler for reloading a config entry.""" + reload_entries = set() + if ATTR_ENTRY_ID in call.data: + reload_entries.add(call.data[ATTR_ENTRY_ID]) + reload_entries.update(await async_extract_config_entry_ids(hass, call)) + if not reload_entries: + raise ValueError("There were no matching config entries to reload") + await asyncio.gather( + *[ + hass.config_entries.async_reload(config_entry_id) + for config_entry_id in reload_entries + ] + ) + + hass.helpers.service.async_register_admin_service( + ha.DOMAIN, + SERVICE_RELOAD_CONFIG_ENTRY, + async_handle_reload_config_entry, + schema=SCHEMA_RELOAD_CONFIG_ENTRY, + ) + return True diff --git a/homeassistant/components/homeassistant/services.yaml b/homeassistant/components/homeassistant/services.yaml index 38814d9f902..251ee171b6a 100644 --- a/homeassistant/components/homeassistant/services.yaml +++ b/homeassistant/components/homeassistant/services.yaml @@ -58,3 +58,19 @@ update_entity: description: Force one or more entities to update its data target: entity: {} + +reload_config_entry: + name: Reload config entry + description: Reload a config entry that matches a target. + target: + entity: {} + device: {} + fields: + entry_id: + advanced: true + name: Config entry id + description: A configuration entry id + required: false + example: 8955375327824e14ba89e4b29cc3ec9a + selector: + text: diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index f1ab245d38a..f43b85575b8 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -11,9 +11,9 @@ from typing import ( Awaitable, Callable, Iterable, - Tuple, + Optional, TypedDict, - cast, + Union, ) import voluptuous as vol @@ -78,6 +78,29 @@ class ServiceParams(TypedDict): target: dict | None +class ServiceTargetSelector: + """Class to hold a target selector for a service.""" + + def __init__(self, service_call: ha.ServiceCall): + """Extract ids from service call data.""" + entity_ids: Optional[Union[str, list]] = service_call.data.get(ATTR_ENTITY_ID) + device_ids: Optional[Union[str, list]] = service_call.data.get(ATTR_DEVICE_ID) + area_ids: Optional[Union[str, list]] = service_call.data.get(ATTR_AREA_ID) + + self.entity_ids = ( + set(cv.ensure_list(entity_ids)) if _has_match(entity_ids) else set() + ) + self.device_ids = ( + set(cv.ensure_list(device_ids)) if _has_match(device_ids) else set() + ) + self.area_ids = set(cv.ensure_list(area_ids)) if _has_match(area_ids) else set() + + @property + def has_any_selector(self) -> bool: + """Determine if any selectors are present.""" + return bool(self.entity_ids or self.device_ids or self.area_ids) + + @dataclasses.dataclass class SelectedEntities: """Class to hold the selected entities.""" @@ -93,6 +116,9 @@ class SelectedEntities: missing_devices: set[str] = dataclasses.field(default_factory=set) missing_areas: set[str] = dataclasses.field(default_factory=set) + # Referenced devices + referenced_devices: set[str] = dataclasses.field(default_factory=set) + def log_missing(self, missing_entities: set[str]) -> None: """Log about missing items.""" parts = [] @@ -293,98 +319,88 @@ async def async_extract_entity_ids( return referenced.referenced | referenced.indirectly_referenced +def _has_match(ids: Optional[Union[str, list]]) -> bool: + """Check if ids can match anything.""" + return ids not in (None, ENTITY_MATCH_NONE) + + @bind_hass async def async_extract_referenced_entity_ids( hass: HomeAssistantType, service_call: ha.ServiceCall, expand_group: bool = True ) -> SelectedEntities: """Extract referenced entity IDs from a service call.""" - entity_ids = service_call.data.get(ATTR_ENTITY_ID) - device_ids = service_call.data.get(ATTR_DEVICE_ID) - area_ids = service_call.data.get(ATTR_AREA_ID) - - selects_entity_ids = entity_ids not in (None, ENTITY_MATCH_NONE) - selects_device_ids = device_ids not in (None, ENTITY_MATCH_NONE) - selects_area_ids = area_ids not in (None, ENTITY_MATCH_NONE) - + selector = ServiceTargetSelector(service_call) selected = SelectedEntities() - if not selects_entity_ids and not selects_device_ids and not selects_area_ids: + if not selector.has_any_selector: return selected - if selects_entity_ids: - assert entity_ids is not None + entity_ids = selector.entity_ids + if expand_group: + entity_ids = hass.components.group.expand_entity_ids(entity_ids) - # Entity ID attr can be a list or a string - if isinstance(entity_ids, str): - entity_ids = [entity_ids] + selected.referenced.update(entity_ids) - if expand_group: - entity_ids = hass.components.group.expand_entity_ids(entity_ids) - - selected.referenced.update(entity_ids) - - if not selects_device_ids and not selects_area_ids: + if not selector.device_ids and not selector.area_ids: return selected - area_reg, dev_reg, ent_reg = cast( - Tuple[ - area_registry.AreaRegistry, - device_registry.DeviceRegistry, - entity_registry.EntityRegistry, - ], - await asyncio.gather( - area_registry.async_get_registry(hass), - device_registry.async_get_registry(hass), - entity_registry.async_get_registry(hass), - ), - ) + ent_reg = entity_registry.async_get(hass) + dev_reg = device_registry.async_get(hass) + area_reg = area_registry.async_get(hass) - picked_devices = set() + for device_id in selector.device_ids: + if device_id not in dev_reg.devices: + selected.missing_devices.add(device_id) - if selects_device_ids: - if isinstance(device_ids, str): - picked_devices = {device_ids} - else: - assert isinstance(device_ids, list) - picked_devices = set(device_ids) + for area_id in selector.area_ids: + if area_id not in area_reg.areas: + selected.missing_areas.add(area_id) - for device_id in picked_devices: - if device_id not in dev_reg.devices: - selected.missing_devices.add(device_id) + # Find devices for this area + selected.referenced_devices.update(selector.device_ids) + for device_entry in dev_reg.devices.values(): + if device_entry.area_id in selector.area_ids: + selected.referenced_devices.add(device_entry.id) - if selects_area_ids: - assert area_ids is not None - - if isinstance(area_ids, str): - area_lookup = {area_ids} - else: - area_lookup = set(area_ids) - - for area_id in area_lookup: - if area_id not in area_reg.areas: - selected.missing_areas.add(area_id) - continue - - # Find entities tied to an area - for entity_entry in ent_reg.entities.values(): - if entity_entry.area_id in area_lookup: - selected.indirectly_referenced.add(entity_entry.entity_id) - - # Find devices for this area - for device_entry in dev_reg.devices.values(): - if device_entry.area_id in area_lookup: - picked_devices.add(device_entry.id) - - if not picked_devices: + if not selector.area_ids and not selected.referenced_devices: return selected - for entity_entry in ent_reg.entities.values(): - if not entity_entry.area_id and entity_entry.device_id in picked_devices: - selected.indirectly_referenced.add(entity_entry.entity_id) + for ent_entry in ent_reg.entities.values(): + if ent_entry.area_id in selector.area_ids or ( + not ent_entry.area_id and ent_entry.device_id in selected.referenced_devices + ): + selected.indirectly_referenced.add(ent_entry.entity_id) return selected +@bind_hass +async def async_extract_config_entry_ids( + hass: HomeAssistantType, service_call: ha.ServiceCall, expand_group: bool = True +) -> set: + """Extract referenced config entry ids from a service call.""" + referenced = await async_extract_referenced_entity_ids( + hass, service_call, expand_group + ) + ent_reg = entity_registry.async_get(hass) + dev_reg = device_registry.async_get(hass) + config_entry_ids: set[str] = set() + + # Some devices may have no entities + for device_id in referenced.referenced_devices: + if device_id in dev_reg.devices: + device = dev_reg.async_get(device_id) + if device is not None: + config_entry_ids.update(device.config_entries) + + for entity_id in referenced.referenced | referenced.indirectly_referenced: + entry = ent_reg.async_get(entity_id) + if entry is not None and entry.config_entry_id is not None: + config_entry_ids.add(entry.config_entry_id) + + return config_entry_ids + + def _load_services_file(hass: HomeAssistantType, integration: Integration) -> JSON_TYPE: """Load services file for an integration.""" try: diff --git a/tests/components/homeassistant/test_init.py b/tests/components/homeassistant/test_init.py index ef830c7ee77..51646ae7139 100644 --- a/tests/components/homeassistant/test_init.py +++ b/tests/components/homeassistant/test_init.py @@ -11,6 +11,7 @@ import yaml from homeassistant import config import homeassistant.components as comps from homeassistant.components.homeassistant import ( + ATTR_ENTRY_ID, SERVICE_CHECK_CONFIG, SERVICE_RELOAD_CORE_CONFIG, SERVICE_SET_LOCATION, @@ -34,9 +35,11 @@ from homeassistant.helpers import entity from homeassistant.setup import async_setup_component from tests.common import ( + MockConfigEntry, async_capture_events, async_mock_service, get_test_home_assistant, + mock_registry, mock_service, patch_yaml_files, ) @@ -385,3 +388,62 @@ async def test_not_allowing_recursion(hass, caplog): f"Called service homeassistant.{service} with invalid entities homeassistant.light" in caplog.text ), service + + +async def test_reload_config_entry_by_entity_id(hass): + """Test being able to reload a config entry by entity_id.""" + await async_setup_component(hass, "homeassistant", {}) + entity_reg = mock_registry(hass) + entry1 = MockConfigEntry(domain="mockdomain") + entry1.add_to_hass(hass) + entry2 = MockConfigEntry(domain="mockdomain") + entry2.add_to_hass(hass) + reg_entity1 = entity_reg.async_get_or_create( + "binary_sensor", "powerwall", "battery_charging", config_entry=entry1 + ) + reg_entity2 = entity_reg.async_get_or_create( + "binary_sensor", "powerwall", "battery_status", config_entry=entry2 + ) + with patch( + "homeassistant.config_entries.ConfigEntries.async_reload", + return_value=None, + ) as mock_reload: + await hass.services.async_call( + "homeassistant", + "reload_config_entry", + {"entity_id": f"{reg_entity1.entity_id},{reg_entity2.entity_id}"}, + blocking=True, + ) + + assert len(mock_reload.mock_calls) == 2 + assert {mock_reload.mock_calls[0][1][0], mock_reload.mock_calls[1][1][0]} == { + entry1.entry_id, + entry2.entry_id, + } + + with pytest.raises(ValueError): + await hass.services.async_call( + "homeassistant", + "reload_config_entry", + {"entity_id": "unknown.entity_id"}, + blocking=True, + ) + + +async def test_reload_config_entry_by_entry_id(hass): + """Test being able to reload a config entry by config entry id.""" + await async_setup_component(hass, "homeassistant", {}) + + with patch( + "homeassistant.config_entries.ConfigEntries.async_reload", + return_value=None, + ) as mock_reload: + await hass.services.async_call( + "homeassistant", + "reload_config_entry", + {ATTR_ENTRY_ID: "8955375327824e14ba89e4b29cc3ec9a"}, + blocking=True, + ) + + assert len(mock_reload.mock_calls) == 1 + assert mock_reload.mock_calls[0][1][0] == "8955375327824e14ba89e4b29cc3ec9a" diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 92cbd5514e6..7a084fed9dd 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -1015,3 +1015,28 @@ async def test_async_extract_entities_warn_referenced(hass, caplog): "Unable to find referenced areas non-existent-area, devices non-existent-device, entities non.existent" in caplog.text ) + + +async def test_async_extract_config_entry_ids(hass): + """Test we can find devices that have no entities.""" + + device_no_entities = dev_reg.DeviceEntry( + id="device-no-entities", config_entries={"abc"} + ) + + call = ha.ServiceCall( + "homeassistant", + "reload_config_entry", + { + "device_id": "device-no-entities", + }, + ) + + mock_device_registry( + hass, + { + device_no_entities.id: device_no_entities, + }, + ) + + assert await service.async_extract_config_entry_ids(hass, call) == {"abc"} From 9e1a6610dc4f46922b2f2571c6f547b182de52e5 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 08:02:55 +0100 Subject: [PATCH 440/831] Update typing 07 (#48057) --- .../components/garmin_connect/sensor.py | 6 ++- .../components/gdacs/geo_location.py | 13 ++++--- homeassistant/components/gdacs/sensor.py | 7 ++-- .../components/geniushub/__init__.py | 16 ++++---- homeassistant/components/geniushub/climate.py | 10 ++--- homeassistant/components/geniushub/sensor.py | 6 ++- .../components/geniushub/water_heater.py | 4 +- .../geo_json_events/geo_location.py | 11 +++--- .../components/geo_location/__init__.py | 9 +++-- .../geonetnz_quakes/geo_location.py | 13 ++++--- .../components/geonetnz_quakes/sensor.py | 5 ++- .../components/geonetnz_volcano/__init__.py | 7 ++-- .../components/geonetnz_volcano/sensor.py | 5 ++- homeassistant/components/gogogate2/common.py | 14 ++++--- homeassistant/components/gogogate2/cover.py | 6 ++- homeassistant/components/gogogate2/sensor.py | 6 ++- .../components/google_assistant/__init__.py | 6 ++- .../components/google_assistant/helpers.py | 13 ++++--- .../components/google_assistant/trait.py | 5 ++- .../components/google_pubsub/__init__.py | 6 ++- homeassistant/components/gree/bridge.py | 5 ++- homeassistant/components/gree/climate.py | 11 +++--- homeassistant/components/gree/switch.py | 4 +- homeassistant/components/group/__init__.py | 24 ++++++------ homeassistant/components/group/cover.py | 12 +++--- homeassistant/components/group/light.py | 38 ++++++++++--------- .../components/group/reproduce_state.py | 8 ++-- homeassistant/components/gtfs/sensor.py | 14 ++++--- homeassistant/components/guardian/__init__.py | 5 ++- .../components/guardian/binary_sensor.py | 14 ++++--- homeassistant/components/guardian/sensor.py | 18 +++++---- homeassistant/components/guardian/switch.py | 6 ++- 32 files changed, 185 insertions(+), 142 deletions(-) diff --git a/homeassistant/components/garmin_connect/sensor.py b/homeassistant/components/garmin_connect/sensor.py index d2a6419cc75..ba43a894cfe 100644 --- a/homeassistant/components/garmin_connect/sensor.py +++ b/homeassistant/components/garmin_connect/sensor.py @@ -1,6 +1,8 @@ """Platform for Garmin Connect integration.""" +from __future__ import annotations + import logging -from typing import Any, Dict +from typing import Any from garminconnect import ( GarminConnectAuthenticationError, @@ -136,7 +138,7 @@ class GarminConnectSensor(Entity): return attributes @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information.""" return { "identifiers": {(DOMAIN, self._unique_id)}, diff --git a/homeassistant/components/gdacs/geo_location.py b/homeassistant/components/gdacs/geo_location.py index a2178bb78e0..7e3dc7484bb 100644 --- a/homeassistant/components/gdacs/geo_location.py +++ b/homeassistant/components/gdacs/geo_location.py @@ -1,6 +1,7 @@ """Geolocation support for GDACS Feed.""" +from __future__ import annotations + import logging -from typing import Optional from homeassistant.components.geo_location import GeolocationEvent from homeassistant.const import ( @@ -169,7 +170,7 @@ class GdacsEvent(GeolocationEvent): self._version = feed_entry.version @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID containing latitude/longitude and external id.""" return f"{self._integration_id}_{self._external_id}" @@ -186,22 +187,22 @@ class GdacsEvent(GeolocationEvent): return SOURCE @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return self._title @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """Return distance value of this external event.""" return self._distance @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of this external event.""" return self._latitude @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of this external event.""" return self._longitude diff --git a/homeassistant/components/gdacs/sensor.py b/homeassistant/components/gdacs/sensor.py index c3642181427..7ab63fd6c6a 100644 --- a/homeassistant/components/gdacs/sensor.py +++ b/homeassistant/components/gdacs/sensor.py @@ -1,6 +1,7 @@ """Feed Entity Manager Sensor support for GDACS Feed.""" +from __future__ import annotations + import logging -from typing import Optional from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -109,12 +110,12 @@ class GdacsSensor(Entity): return self._total @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID containing latitude/longitude.""" return self._config_unique_id @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return f"GDACS ({self._config_title})" diff --git a/homeassistant/components/geniushub/__init__.py b/homeassistant/components/geniushub/__init__.py index 15a2c8e7686..f1d2a1d47c1 100644 --- a/homeassistant/components/geniushub/__init__.py +++ b/homeassistant/components/geniushub/__init__.py @@ -1,7 +1,9 @@ """Support for a Genius Hub system.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Any, Dict, Optional +from typing import Any import aiohttp from geniushubclient import GeniusHub @@ -218,12 +220,12 @@ class GeniusEntity(Entity): """Set up a listener when this entity is added to HA.""" self.async_on_remove(async_dispatcher_connect(self.hass, DOMAIN, self._refresh)) - async def _refresh(self, payload: Optional[dict] = None) -> None: + async def _refresh(self, payload: dict | None = None) -> None: """Process any signals.""" self.async_schedule_update_ha_state(force_refresh=True) @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return self._unique_id @@ -250,7 +252,7 @@ class GeniusDevice(GeniusEntity): self._last_comms = self._state_attr = None @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the device state attributes.""" attrs = {} attrs["assigned_zone"] = self._device.data["assignedZones"][0]["name"] @@ -285,7 +287,7 @@ class GeniusZone(GeniusEntity): self._zone = zone self._unique_id = f"{broker.hub_uid}_zone_{zone.id}" - async def _refresh(self, payload: Optional[dict] = None) -> None: + async def _refresh(self, payload: dict | None = None) -> None: """Process any signals.""" if payload is None: self.async_schedule_update_ha_state(force_refresh=True) @@ -317,7 +319,7 @@ class GeniusZone(GeniusEntity): return self._zone.name @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the device state attributes.""" status = {k: v for k, v in self._zone.data.items() if k in GH_ZONE_ATTRS} return {"status": status} @@ -333,7 +335,7 @@ class GeniusHeatingZone(GeniusZone): self._max_temp = self._min_temp = self._supported_features = None @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._zone.data.get("temperature") diff --git a/homeassistant/components/geniushub/climate.py b/homeassistant/components/geniushub/climate.py index 70d08dc2d1f..089fd964835 100644 --- a/homeassistant/components/geniushub/climate.py +++ b/homeassistant/components/geniushub/climate.py @@ -1,5 +1,5 @@ """Support for Genius Hub climate devices.""" -from typing import List, Optional +from __future__ import annotations from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( @@ -67,12 +67,12 @@ class GeniusClimateZone(GeniusHeatingZone, ClimateEntity): return GH_HVAC_TO_HA.get(self._zone.data["mode"], HVAC_MODE_HEAT) @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return list(HA_HVAC_TO_GH) @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation if supported.""" if "_state" in self._zone.data: # only for v3 API if not self._zone.data["_state"].get("bIsActive"): @@ -83,12 +83,12 @@ class GeniusClimateZone(GeniusHeatingZone, ClimateEntity): return None @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" return GH_PRESET_TO_HA.get(self._zone.data["mode"]) @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" if "occupied" in self._zone.data: # if has a movement sensor return [PRESET_ACTIVITY, PRESET_BOOST] diff --git a/homeassistant/components/geniushub/sensor.py b/homeassistant/components/geniushub/sensor.py index 9d87fb46cb0..10c53994529 100644 --- a/homeassistant/components/geniushub/sensor.py +++ b/homeassistant/components/geniushub/sensor.py @@ -1,6 +1,8 @@ """Support for Genius Hub sensor devices.""" +from __future__ import annotations + from datetime import timedelta -from typing import Any, Dict +from typing import Any from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE from homeassistant.helpers.typing import ConfigType, HomeAssistantType @@ -106,7 +108,7 @@ class GeniusIssue(GeniusEntity): return len(self._issues) @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the device state attributes.""" return {f"{self._level}_list": self._issues} diff --git a/homeassistant/components/geniushub/water_heater.py b/homeassistant/components/geniushub/water_heater.py index 51fdce4a6d7..bb775432d8e 100644 --- a/homeassistant/components/geniushub/water_heater.py +++ b/homeassistant/components/geniushub/water_heater.py @@ -1,5 +1,5 @@ """Support for Genius Hub water_heater devices.""" -from typing import List +from __future__ import annotations from homeassistant.components.water_heater import ( SUPPORT_OPERATION_MODE, @@ -61,7 +61,7 @@ class GeniusWaterHeater(GeniusHeatingZone, WaterHeaterEntity): self._supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE @property - def operation_list(self) -> List[str]: + def operation_list(self) -> list[str]: """Return the list of available operation modes.""" return list(HA_OPMODE_TO_GH) diff --git a/homeassistant/components/geo_json_events/geo_location.py b/homeassistant/components/geo_json_events/geo_location.py index feb0b98b3fa..cfd58124c16 100644 --- a/homeassistant/components/geo_json_events/geo_location.py +++ b/homeassistant/components/geo_json_events/geo_location.py @@ -1,7 +1,8 @@ """Support for generic GeoJSON events.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Optional from geojson_client.generic_feed import GenericFeedManager import voluptuous as vol @@ -176,22 +177,22 @@ class GeoJsonLocationEvent(GeolocationEvent): return SOURCE @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return self._name @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """Return distance value of this external event.""" return self._distance @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of this external event.""" return self._latitude @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of this external event.""" return self._longitude diff --git a/homeassistant/components/geo_location/__init__.py b/homeassistant/components/geo_location/__init__.py index 6142fa22209..2041c147b2b 100644 --- a/homeassistant/components/geo_location/__init__.py +++ b/homeassistant/components/geo_location/__init__.py @@ -1,7 +1,8 @@ """Support for Geolocation.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Optional from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE from homeassistant.helpers.config_validation import ( # noqa: F401 @@ -60,17 +61,17 @@ class GeolocationEvent(Entity): raise NotImplementedError @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """Return distance value of this external event.""" return None @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of this external event.""" return None @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of this external event.""" return None diff --git a/homeassistant/components/geonetnz_quakes/geo_location.py b/homeassistant/components/geonetnz_quakes/geo_location.py index 62086f059f4..1643264fd75 100644 --- a/homeassistant/components/geonetnz_quakes/geo_location.py +++ b/homeassistant/components/geonetnz_quakes/geo_location.py @@ -1,6 +1,7 @@ """Geolocation support for GeoNet NZ Quakes Feeds.""" +from __future__ import annotations + import logging -from typing import Optional from homeassistant.components.geo_location import GeolocationEvent from homeassistant.const import ( @@ -142,7 +143,7 @@ class GeonetnzQuakesEvent(GeolocationEvent): self._time = feed_entry.time @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID containing latitude/longitude and external id.""" return f"{self._integration_id}_{self._external_id}" @@ -157,22 +158,22 @@ class GeonetnzQuakesEvent(GeolocationEvent): return SOURCE @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return self._title @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """Return distance value of this external event.""" return self._distance @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of this external event.""" return self._latitude @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of this external event.""" return self._longitude diff --git a/homeassistant/components/geonetnz_quakes/sensor.py b/homeassistant/components/geonetnz_quakes/sensor.py index e5476757024..565e022f036 100644 --- a/homeassistant/components/geonetnz_quakes/sensor.py +++ b/homeassistant/components/geonetnz_quakes/sensor.py @@ -1,6 +1,7 @@ """Feed Entity Manager Sensor support for GeoNet NZ Quakes Feeds.""" +from __future__ import annotations + import logging -from typing import Optional from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -115,7 +116,7 @@ class GeonetnzQuakesSensor(Entity): return self._config_unique_id @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return f"GeoNet NZ Quakes ({self._config_title})" diff --git a/homeassistant/components/geonetnz_volcano/__init__.py b/homeassistant/components/geonetnz_volcano/__init__.py index e2c6cb77083..c3db7770499 100644 --- a/homeassistant/components/geonetnz_volcano/__init__.py +++ b/homeassistant/components/geonetnz_volcano/__init__.py @@ -1,8 +1,9 @@ """The GeoNet NZ Volcano integration.""" +from __future__ import annotations + import asyncio from datetime import datetime, timedelta import logging -from typing import Optional from aio_geojson_geonetnz_volcano import GeonetnzVolcanoFeedManager import voluptuous as vol @@ -172,11 +173,11 @@ class GeonetnzVolcanoFeedEntityManager: """Get feed entry by external id.""" return self._feed_manager.feed_entries.get(external_id) - def last_update(self) -> Optional[datetime]: + def last_update(self) -> datetime | None: """Return the last update of this feed.""" return self._feed_manager.last_update - def last_update_successful(self) -> Optional[datetime]: + def last_update_successful(self) -> datetime | None: """Return the last successful update of this feed.""" return self._feed_manager.last_update_successful diff --git a/homeassistant/components/geonetnz_volcano/sensor.py b/homeassistant/components/geonetnz_volcano/sensor.py index 7044b3c5609..2ec4940f88d 100644 --- a/homeassistant/components/geonetnz_volcano/sensor.py +++ b/homeassistant/components/geonetnz_volcano/sensor.py @@ -1,6 +1,7 @@ """Feed Entity Manager Sensor support for GeoNet NZ Volcano Feeds.""" +from __future__ import annotations + import logging -from typing import Optional from homeassistant.const import ( ATTR_ATTRIBUTION, @@ -139,7 +140,7 @@ class GeonetnzVolcanoSensor(Entity): return DEFAULT_ICON @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return f"Volcano {self._title}" diff --git a/homeassistant/components/gogogate2/common.py b/homeassistant/components/gogogate2/common.py index 404a22e3312..e8b17184bbe 100644 --- a/homeassistant/components/gogogate2/common.py +++ b/homeassistant/components/gogogate2/common.py @@ -1,7 +1,9 @@ """Common code for GogoGate2 component.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Awaitable, Callable, NamedTuple, Optional +from typing import Awaitable, Callable, NamedTuple from gogogate2_api import AbstractGateApi, GogoGate2Api, ISmartGateApi from gogogate2_api.common import AbstractDoor, get_door_by_id @@ -30,8 +32,8 @@ class StateData(NamedTuple): """State data for a cover entity.""" config_unique_id: str - unique_id: Optional[str] - door: Optional[AbstractDoor] + unique_id: str | None + door: AbstractDoor | None class DeviceDataUpdateCoordinator(DataUpdateCoordinator): @@ -45,8 +47,8 @@ class DeviceDataUpdateCoordinator(DataUpdateCoordinator): *, name: str, update_interval: timedelta, - update_method: Optional[Callable[[], Awaitable]] = None, - request_refresh_debouncer: Optional[Debouncer] = None, + update_method: Callable[[], Awaitable] | None = None, + request_refresh_debouncer: Debouncer | None = None, ): """Initialize the data update coordinator.""" DataUpdateCoordinator.__init__( @@ -78,7 +80,7 @@ class GoGoGate2Entity(CoordinatorEntity): self._unique_id = unique_id @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return self._unique_id diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index 9a1b53f7bee..62302e8f669 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -1,6 +1,8 @@ """Support for Gogogate2 garage Doors.""" +from __future__ import annotations + import logging -from typing import Callable, List, Optional +from typing import Callable from gogogate2_api.common import AbstractDoor, DoorStatus, get_configured_doors @@ -44,7 +46,7 @@ async def async_setup_platform( async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], Optional[bool]], None], + async_add_entities: Callable[[list[Entity], bool | None], None], ) -> None: """Set up the config entry.""" data_update_coordinator = get_data_update_coordinator(hass, config_entry) diff --git a/homeassistant/components/gogogate2/sensor.py b/homeassistant/components/gogogate2/sensor.py index eea557639ad..2bdb1b3170c 100644 --- a/homeassistant/components/gogogate2/sensor.py +++ b/homeassistant/components/gogogate2/sensor.py @@ -1,6 +1,8 @@ """Support for Gogogate2 garage Doors.""" +from __future__ import annotations + from itertools import chain -from typing import Callable, List, Optional +from typing import Callable from gogogate2_api.common import AbstractDoor, get_configured_doors @@ -26,7 +28,7 @@ SENSOR_ID_WIRED = "WIRE" async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], Optional[bool]], None], + async_add_entities: Callable[[list[Entity], bool | None], None], ) -> None: """Set up the config entry.""" data_update_coordinator = get_data_update_coordinator(hass, config_entry) diff --git a/homeassistant/components/google_assistant/__init__.py b/homeassistant/components/google_assistant/__init__.py index 00c09242517..7793ed4d659 100644 --- a/homeassistant/components/google_assistant/__init__.py +++ b/homeassistant/components/google_assistant/__init__.py @@ -1,6 +1,8 @@ """Support for Actions on Google Assistant Smart Home Control.""" +from __future__ import annotations + import logging -from typing import Any, Dict +from typing import Any import voluptuous as vol @@ -87,7 +89,7 @@ GOOGLE_ASSISTANT_SCHEMA = vol.All( CONFIG_SCHEMA = vol.Schema({DOMAIN: GOOGLE_ASSISTANT_SCHEMA}, extra=vol.ALLOW_EXTRA) -async def async_setup(hass: HomeAssistant, yaml_config: Dict[str, Any]): +async def async_setup(hass: HomeAssistant, yaml_config: dict[str, Any]): """Activate Google Actions component.""" config = yaml_config.get(DOMAIN, {}) diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py index b4900d83b64..9b133ad6a30 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -1,10 +1,11 @@ """Helper classes for Google Assistant integration.""" +from __future__ import annotations + from abc import ABC, abstractmethod from asyncio import gather from collections.abc import Mapping import logging import pprint -from typing import Dict, List, Optional, Tuple from aiohttp.web import json_response @@ -44,7 +45,7 @@ _LOGGER = logging.getLogger(__name__) async def _get_entity_and_device( hass, entity_id -) -> Optional[Tuple[RegistryEntry, DeviceEntry]]: +) -> tuple[RegistryEntry, DeviceEntry] | None: """Fetch the entity and device entries for a entity_id.""" dev_reg, ent_reg = await gather( hass.helpers.device_registry.async_get_registry(), @@ -58,7 +59,7 @@ async def _get_entity_and_device( return entity_entry, device_entry -async def _get_area(hass, entity_entry, device_entry) -> Optional[AreaEntry]: +async def _get_area(hass, entity_entry, device_entry) -> AreaEntry | None: """Calculate the area for an entity.""" if entity_entry and entity_entry.area_id: area_id = entity_entry.area_id @@ -71,7 +72,7 @@ async def _get_area(hass, entity_entry, device_entry) -> Optional[AreaEntry]: return area_reg.areas.get(area_id) -async def _get_device_info(device_entry) -> Optional[Dict[str, str]]: +async def _get_device_info(device_entry) -> dict[str, str] | None: """Retrieve the device info for a device.""" if not device_entry: return None @@ -344,7 +345,7 @@ class RequestData: user_id: str, source: str, request_id: str, - devices: Optional[List[dict]], + devices: list[dict] | None, ): """Initialize the request data.""" self.config = config @@ -578,7 +579,7 @@ def deep_update(target, source): @callback -def async_get_entities(hass, config) -> List[GoogleEntity]: +def async_get_entities(hass, config) -> list[GoogleEntity]: """Return all entities that are supported by Google.""" entities = [] for state in hass.states.async_all(): diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 7babc5e4836..8f01482aa45 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -1,6 +1,7 @@ """Implement the Google Smart Home traits.""" +from __future__ import annotations + import logging -from typing import List, Optional from homeassistant.components import ( alarm_control_panel, @@ -153,7 +154,7 @@ def _google_temp_unit(units): return "C" -def _next_selected(items: List[str], selected: Optional[str]) -> Optional[str]: +def _next_selected(items: list[str], selected: str | None) -> str | None: """Return the next item in a item list starting at given value. If selected is missing in items, None is returned diff --git a/homeassistant/components/google_pubsub/__init__.py b/homeassistant/components/google_pubsub/__init__.py index c832c87318c..365c118e99e 100644 --- a/homeassistant/components/google_pubsub/__init__.py +++ b/homeassistant/components/google_pubsub/__init__.py @@ -1,9 +1,11 @@ """Support for Google Cloud Pub/Sub.""" +from __future__ import annotations + import datetime import json import logging import os -from typing import Any, Dict +from typing import Any from google.cloud import pubsub_v1 import voluptuous as vol @@ -37,7 +39,7 @@ CONFIG_SCHEMA = vol.Schema( ) -def setup(hass: HomeAssistant, yaml_config: Dict[str, Any]): +def setup(hass: HomeAssistant, yaml_config: dict[str, Any]): """Activate Google Pub/Sub component.""" config = yaml_config[DOMAIN] diff --git a/homeassistant/components/gree/bridge.py b/homeassistant/components/gree/bridge.py index 3fbf4a21fb3..af523f385aa 100644 --- a/homeassistant/components/gree/bridge.py +++ b/homeassistant/components/gree/bridge.py @@ -1,7 +1,8 @@ """Helper and wrapper classes for Gree module.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import List from greeclimate.device import Device, DeviceInfo from greeclimate.discovery import Discovery @@ -86,7 +87,7 @@ class DeviceHelper: return device @staticmethod - async def find_devices() -> List[DeviceInfo]: + async def find_devices() -> list[DeviceInfo]: """Gather a list of device infos from the local network.""" return await Discovery.search_devices() diff --git a/homeassistant/components/gree/climate.py b/homeassistant/components/gree/climate.py index 8d0170fbe50..a5ef39be071 100644 --- a/homeassistant/components/gree/climate.py +++ b/homeassistant/components/gree/climate.py @@ -1,6 +1,7 @@ """Support for interface with a Gree climate systems.""" +from __future__ import annotations + import logging -from typing import List from greeclimate.device import ( FanSpeed, @@ -234,7 +235,7 @@ class GreeClimateEntity(CoordinatorEntity, ClimateEntity): self.async_write_ha_state() @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the HVAC modes support by the device.""" modes = [*HVAC_MODES_REVERSE] modes.append(HVAC_MODE_OFF) @@ -282,7 +283,7 @@ class GreeClimateEntity(CoordinatorEntity, ClimateEntity): self.async_write_ha_state() @property - def preset_modes(self) -> List[str]: + def preset_modes(self) -> list[str]: """Return the preset modes support by the device.""" return PRESET_MODES @@ -302,7 +303,7 @@ class GreeClimateEntity(CoordinatorEntity, ClimateEntity): self.async_write_ha_state() @property - def fan_modes(self) -> List[str]: + def fan_modes(self) -> list[str]: """Return the fan modes support by the device.""" return [*FAN_MODES_REVERSE] @@ -342,7 +343,7 @@ class GreeClimateEntity(CoordinatorEntity, ClimateEntity): self.async_write_ha_state() @property - def swing_modes(self) -> List[str]: + def swing_modes(self) -> list[str]: """Return the swing modes currently supported for this device.""" return SWING_MODES diff --git a/homeassistant/components/gree/switch.py b/homeassistant/components/gree/switch.py index fa1f1550e83..12c94ddec61 100644 --- a/homeassistant/components/gree/switch.py +++ b/homeassistant/components/gree/switch.py @@ -1,5 +1,5 @@ """Support for interface with a Gree climate systems.""" -from typing import Optional +from __future__ import annotations from homeassistant.components.switch import DEVICE_CLASS_SWITCH, SwitchEntity from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC @@ -38,7 +38,7 @@ class GreeSwitchEntity(CoordinatorEntity, SwitchEntity): return f"{self._mac}-panel-light" @property - def icon(self) -> Optional[str]: + def icon(self) -> str | None: """Return the icon for the device.""" return "mdi:lightbulb" diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index 1ce0b3e71d7..96822b9f993 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -1,9 +1,11 @@ """Provide the functionality to group entities.""" +from __future__ import annotations + from abc import abstractmethod import asyncio from contextvars import ContextVar import logging -from typing import Any, Dict, Iterable, List, Optional, Set, cast +from typing import Any, Iterable, List, cast import voluptuous as vol @@ -91,16 +93,16 @@ CONFIG_SCHEMA = vol.Schema( class GroupIntegrationRegistry: """Class to hold a registry of integrations.""" - on_off_mapping: Dict[str, str] = {STATE_ON: STATE_OFF} - off_on_mapping: Dict[str, str] = {STATE_OFF: STATE_ON} - on_states_by_domain: Dict[str, Set] = {} - exclude_domains: Set = set() + on_off_mapping: dict[str, str] = {STATE_ON: STATE_OFF} + off_on_mapping: dict[str, str] = {STATE_OFF: STATE_ON} + on_states_by_domain: dict[str, set] = {} + exclude_domains: set = set() def exclude_domain(self) -> None: """Exclude the current domain.""" self.exclude_domains.add(current_domain.get()) - def on_off_states(self, on_states: Set, off_state: str) -> None: + def on_off_states(self, on_states: set, off_state: str) -> None: """Register on and off states for the current domain.""" for on_state in on_states: if on_state not in self.on_off_mapping: @@ -128,12 +130,12 @@ def is_on(hass, entity_id): @bind_hass -def expand_entity_ids(hass: HomeAssistantType, entity_ids: Iterable[Any]) -> List[str]: +def expand_entity_ids(hass: HomeAssistantType, entity_ids: Iterable[Any]) -> list[str]: """Return entity_ids with group entity ids replaced by their members. Async friendly. """ - found_ids: List[str] = [] + found_ids: list[str] = [] for entity_id in entity_ids: if not isinstance(entity_id, str) or entity_id in ( ENTITY_MATCH_NONE, @@ -171,8 +173,8 @@ def expand_entity_ids(hass: HomeAssistantType, entity_ids: Iterable[Any]) -> Lis @bind_hass def get_entity_ids( - hass: HomeAssistantType, entity_id: str, domain_filter: Optional[str] = None -) -> List[str]: + hass: HomeAssistantType, entity_id: str, domain_filter: str | None = None +) -> list[str]: """Get members of this group. Async friendly. @@ -192,7 +194,7 @@ def get_entity_ids( @bind_hass -def groups_with_entity(hass: HomeAssistantType, entity_id: str) -> List[str]: +def groups_with_entity(hass: HomeAssistantType, entity_id: str) -> list[str]: """Get all groups that contain this entity. Async friendly. diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index c19e81778a0..5e8d18b28e2 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -1,5 +1,5 @@ """This platform allows several cover to be grouped into one cover.""" -from typing import Dict, Optional, Set +from __future__ import annotations import voluptuous as vol @@ -76,18 +76,18 @@ class CoverGroup(GroupEntity, CoverEntity): self._is_closed = False self._is_closing = False self._is_opening = False - self._cover_position: Optional[int] = 100 + self._cover_position: int | None = 100 self._tilt_position = None self._supported_features = 0 self._assumed_state = True self._entities = entities - self._covers: Dict[str, Set[str]] = { + self._covers: dict[str, set[str]] = { KEY_OPEN_CLOSE: set(), KEY_STOP: set(), KEY_POSITION: set(), } - self._tilts: Dict[str, Set[str]] = { + self._tilts: dict[str, set[str]] = { KEY_OPEN_CLOSE: set(), KEY_STOP: set(), KEY_POSITION: set(), @@ -102,7 +102,7 @@ class CoverGroup(GroupEntity, CoverEntity): async def async_update_supported_features( self, entity_id: str, - new_state: Optional[State], + new_state: State | None, update_state: bool = True, ) -> None: """Update dictionaries with supported features.""" @@ -197,7 +197,7 @@ class CoverGroup(GroupEntity, CoverEntity): return self._is_closing @property - def current_cover_position(self) -> Optional[int]: + def current_cover_position(self) -> int | None: """Return current position for all covers.""" return self._cover_position diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index cebc697efae..a1cf47411fc 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -1,8 +1,10 @@ """This platform allows several lights to be grouped into one light.""" +from __future__ import annotations + import asyncio from collections import Counter import itertools -from typing import Any, Callable, Iterator, List, Optional, Tuple, cast +from typing import Any, Callable, Iterator, cast import voluptuous as vol @@ -78,21 +80,21 @@ async def async_setup_platform( class LightGroup(GroupEntity, light.LightEntity): """Representation of a light group.""" - def __init__(self, name: str, entity_ids: List[str]) -> None: + def __init__(self, name: str, entity_ids: list[str]) -> None: """Initialize a light group.""" self._name = name self._entity_ids = entity_ids self._is_on = False self._available = False self._icon = "mdi:lightbulb-group" - self._brightness: Optional[int] = None - self._hs_color: Optional[Tuple[float, float]] = None - self._color_temp: Optional[int] = None + self._brightness: int | None = None + self._hs_color: tuple[float, float] | None = None + self._color_temp: int | None = None self._min_mireds: int = 154 self._max_mireds: int = 500 - self._white_value: Optional[int] = None - self._effect_list: Optional[List[str]] = None - self._effect: Optional[str] = None + self._white_value: int | None = None + self._effect_list: list[str] | None = None + self._effect: str | None = None self._supported_features: int = 0 async def async_added_to_hass(self) -> None: @@ -136,17 +138,17 @@ class LightGroup(GroupEntity, light.LightEntity): return self._icon @property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the brightness of this light group between 0..255.""" return self._brightness @property - def hs_color(self) -> Optional[Tuple[float, float]]: + def hs_color(self) -> tuple[float, float] | None: """Return the HS color value [float, float].""" return self._hs_color @property - def color_temp(self) -> Optional[int]: + def color_temp(self) -> int | None: """Return the CT color value in mireds.""" return self._color_temp @@ -161,17 +163,17 @@ class LightGroup(GroupEntity, light.LightEntity): return self._max_mireds @property - def white_value(self) -> Optional[int]: + def white_value(self) -> int | None: """Return the white value of this light group between 0..255.""" return self._white_value @property - def effect_list(self) -> Optional[List[str]]: + def effect_list(self) -> list[str] | None: """Return the list of supported effects.""" return self._effect_list @property - def effect(self) -> Optional[str]: + def effect(self) -> str | None: """Return the current effect.""" return self._effect @@ -288,7 +290,7 @@ class LightGroup(GroupEntity, light.LightEntity): async def async_update(self): """Query all members and determine the light group state.""" all_states = [self.hass.states.get(x) for x in self._entity_ids] - states: List[State] = list(filter(None, all_states)) + states: list[State] = list(filter(None, all_states)) on_states = [state for state in states if state.state == STATE_ON] self._is_on = len(on_states) > 0 @@ -331,7 +333,7 @@ class LightGroup(GroupEntity, light.LightEntity): self._supported_features &= SUPPORT_GROUP_LIGHT -def _find_state_attributes(states: List[State], key: str) -> Iterator[Any]: +def _find_state_attributes(states: list[State], key: str) -> Iterator[Any]: """Find attributes with matching key from states.""" for state in states: value = state.attributes.get(key) @@ -350,9 +352,9 @@ def _mean_tuple(*args): def _reduce_attribute( - states: List[State], + states: list[State], key: str, - default: Optional[Any] = None, + default: Any | None = None, reduce: Callable[..., Any] = _mean_int, ) -> Any: """Find the first attribute matching key from states. diff --git a/homeassistant/components/group/reproduce_state.py b/homeassistant/components/group/reproduce_state.py index adeb0cfee0a..f91b1e119af 100644 --- a/homeassistant/components/group/reproduce_state.py +++ b/homeassistant/components/group/reproduce_state.py @@ -1,5 +1,7 @@ """Module that groups code required to handle state restore for component.""" -from typing import Any, Dict, Iterable, Optional +from __future__ import annotations + +from typing import Any, Iterable from homeassistant.core import Context, State from homeassistant.helpers.state import async_reproduce_state @@ -12,8 +14,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce component states.""" states_copy = [] diff --git a/homeassistant/components/gtfs/sensor.py b/homeassistant/components/gtfs/sensor.py index 23f9e2a8021..737348548a8 100644 --- a/homeassistant/components/gtfs/sensor.py +++ b/homeassistant/components/gtfs/sensor.py @@ -1,9 +1,11 @@ """Support for GTFS (Google/General Transport Format Schema).""" +from __future__ import annotations + import datetime import logging import os import threading -from typing import Any, Callable, Optional +from typing import Any, Callable import pygtfs from sqlalchemy.sql import text @@ -484,7 +486,7 @@ def setup_platform( hass: HomeAssistantType, config: ConfigType, add_entities: Callable[[list], None], - discovery_info: Optional[DiscoveryInfoType] = None, + discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the GTFS sensor.""" gtfs_dir = hass.config.path(DEFAULT_PATH) @@ -523,7 +525,7 @@ class GTFSDepartureSensor(Entity): def __init__( self, gtfs: Any, - name: Optional[Any], + name: Any | None, origin: Any, destination: Any, offset: cv.time_period, @@ -540,7 +542,7 @@ class GTFSDepartureSensor(Entity): self._available = False self._icon = ICON self._name = "" - self._state: Optional[str] = None + self._state: str | None = None self._attributes = {} self._agency = None @@ -559,7 +561,7 @@ class GTFSDepartureSensor(Entity): return self._name @property - def state(self) -> Optional[str]: # type: ignore + def state(self) -> str | None: # type: ignore """Return the state of the sensor.""" return self._state @@ -811,7 +813,7 @@ class GTFSDepartureSensor(Entity): col: getattr(resource, col) for col in resource.__table__.columns.keys() } - def append_keys(self, resource: dict, prefix: Optional[str] = None) -> None: + def append_keys(self, resource: dict, prefix: str | None = None) -> None: """Properly format key val pairs to append to attributes.""" for attr, val in resource.items(): if val == "" or val is None or attr == "feed_id": diff --git a/homeassistant/components/guardian/__init__.py b/homeassistant/components/guardian/__init__.py index 98f35d34e14..ebb5e71e1cb 100644 --- a/homeassistant/components/guardian/__init__.py +++ b/homeassistant/components/guardian/__init__.py @@ -1,6 +1,7 @@ """The Elexa Guardian integration.""" +from __future__ import annotations + import asyncio -from typing import Dict from aioguardian import Client @@ -314,7 +315,7 @@ class ValveControllerEntity(GuardianEntity): def __init__( self, entry: ConfigEntry, - coordinators: Dict[str, DataUpdateCoordinator], + coordinators: dict[str, DataUpdateCoordinator], kind: str, name: str, device_class: str, diff --git a/homeassistant/components/guardian/binary_sensor.py b/homeassistant/components/guardian/binary_sensor.py index d8d0498304d..e8c736eabe5 100644 --- a/homeassistant/components/guardian/binary_sensor.py +++ b/homeassistant/components/guardian/binary_sensor.py @@ -1,5 +1,7 @@ """Binary sensors for the Elexa Guardian integration.""" -from typing import Callable, Dict, Optional +from __future__ import annotations + +from typing import Callable from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, @@ -122,8 +124,8 @@ class PairedSensorBinarySensor(PairedSensorEntity, BinarySensorEntity): coordinator: DataUpdateCoordinator, kind: str, name: str, - device_class: Optional[str], - icon: Optional[str], + device_class: str | None, + icon: str | None, ) -> None: """Initialize.""" super().__init__(entry, coordinator, kind, name, device_class, icon) @@ -155,11 +157,11 @@ class ValveControllerBinarySensor(ValveControllerEntity, BinarySensorEntity): def __init__( self, entry: ConfigEntry, - coordinators: Dict[str, DataUpdateCoordinator], + coordinators: dict[str, DataUpdateCoordinator], kind: str, name: str, - device_class: Optional[str], - icon: Optional[str], + device_class: str | None, + icon: str | None, ) -> None: """Initialize.""" super().__init__(entry, coordinators, kind, name, device_class, icon) diff --git a/homeassistant/components/guardian/sensor.py b/homeassistant/components/guardian/sensor.py index 160246b2014..9b778128d19 100644 --- a/homeassistant/components/guardian/sensor.py +++ b/homeassistant/components/guardian/sensor.py @@ -1,5 +1,7 @@ """Sensors for the Elexa Guardian integration.""" -from typing import Callable, Dict, Optional +from __future__ import annotations + +from typing import Callable from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -117,9 +119,9 @@ class PairedSensorSensor(PairedSensorEntity): coordinator: DataUpdateCoordinator, kind: str, name: str, - device_class: Optional[str], - icon: Optional[str], - unit: Optional[str], + device_class: str | None, + icon: str | None, + unit: str | None, ) -> None: """Initialize.""" super().__init__(entry, coordinator, kind, name, device_class, icon) @@ -157,12 +159,12 @@ class ValveControllerSensor(ValveControllerEntity): def __init__( self, entry: ConfigEntry, - coordinators: Dict[str, DataUpdateCoordinator], + coordinators: dict[str, DataUpdateCoordinator], kind: str, name: str, - device_class: Optional[str], - icon: Optional[str], - unit: Optional[str], + device_class: str | None, + icon: str | None, + unit: str | None, ) -> None: """Initialize.""" super().__init__(entry, coordinators, kind, name, device_class, icon) diff --git a/homeassistant/components/guardian/switch.py b/homeassistant/components/guardian/switch.py index 20a38ea5ce7..c574f283bdd 100644 --- a/homeassistant/components/guardian/switch.py +++ b/homeassistant/components/guardian/switch.py @@ -1,5 +1,7 @@ """Switches for the Elexa Guardian integration.""" -from typing import Callable, Dict +from __future__ import annotations + +from typing import Callable from aioguardian import Client from aioguardian.errors import GuardianError @@ -84,7 +86,7 @@ class ValveControllerSwitch(ValveControllerEntity, SwitchEntity): self, entry: ConfigEntry, client: Client, - coordinators: Dict[str, DataUpdateCoordinator], + coordinators: dict[str, DataUpdateCoordinator], ): """Initialize.""" super().__init__( From 5cdd945f440c39d98979f156b88d9ecf29c13011 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 09:25:40 +0100 Subject: [PATCH 441/831] Update typing 08 (#48058) --- .../components/habitica/config_flow.py | 7 +-- homeassistant/components/hassio/__init__.py | 14 ++--- .../components/hassio/binary_sensor.py | 6 ++- homeassistant/components/hassio/entity.py | 14 ++--- homeassistant/components/hassio/http.py | 9 ++-- homeassistant/components/hassio/ingress.py | 13 +++-- homeassistant/components/hassio/sensor.py | 6 ++- homeassistant/components/heatmiser/climate.py | 5 +- homeassistant/components/heos/__init__.py | 5 +- .../components/here_travel_time/sensor.py | 16 +++--- homeassistant/components/history/__init__.py | 6 ++- .../components/homeassistant/scene.py | 8 +-- .../homeassistant/triggers/state.py | 10 ++-- .../components/homekit_controller/__init__.py | 6 ++- .../homekit_controller/device_trigger.py | 4 +- .../homekit_controller/humidifier.py | 14 ++--- .../homematicip_cloud/alarm_control_panel.py | 6 ++- .../homematicip_cloud/binary_sensor.py | 16 +++--- .../components/homematicip_cloud/climate.py | 24 +++++---- .../homematicip_cloud/config_flow.py | 12 +++-- .../components/homematicip_cloud/cover.py | 10 ++-- .../homematicip_cloud/generic_entity.py | 16 +++--- .../components/homematicip_cloud/light.py | 8 +-- .../components/homematicip_cloud/sensor.py | 12 +++-- .../components/homematicip_cloud/services.py | 5 +- .../components/homematicip_cloud/switch.py | 6 ++- homeassistant/components/honeywell/climate.py | 32 ++++++------ homeassistant/components/http/__init__.py | 12 +++-- homeassistant/components/http/ban.py | 9 ++-- homeassistant/components/http/view.py | 14 ++--- homeassistant/components/http/web_runner.py | 11 ++-- .../components/huawei_lte/__init__.py | 25 ++++----- .../components/huawei_lte/binary_sensor.py | 11 ++-- .../components/huawei_lte/config_flow.py | 30 +++++------ .../components/huawei_lte/device_tracker.py | 21 ++++---- homeassistant/components/huawei_lte/notify.py | 10 ++-- homeassistant/components/huawei_lte/sensor.py | 33 ++++++------ homeassistant/components/huawei_lte/switch.py | 9 ++-- homeassistant/components/hue/config_flow.py | 18 ++++--- .../components/humidifier/__init__.py | 14 ++--- .../components/humidifier/device_action.py | 6 +-- .../components/humidifier/device_condition.py | 4 +- .../components/humidifier/device_trigger.py | 4 +- .../components/humidifier/reproduce_state.py | 12 +++-- homeassistant/components/hyperion/__init__.py | 15 +++--- .../components/hyperion/config_flow.py | 52 +++++++++---------- homeassistant/components/hyperion/light.py | 46 ++++++++-------- homeassistant/components/hyperion/switch.py | 5 +- 48 files changed, 355 insertions(+), 296 deletions(-) diff --git a/homeassistant/components/habitica/config_flow.py b/homeassistant/components/habitica/config_flow.py index 6e3311ea9b5..3a77aa3a5b6 100644 --- a/homeassistant/components/habitica/config_flow.py +++ b/homeassistant/components/habitica/config_flow.py @@ -1,6 +1,7 @@ """Config flow for habitica integration.""" +from __future__ import annotations + import logging -from typing import Dict from aiohttp import ClientResponseError from habitipy.aio import HabitipyAsync @@ -25,8 +26,8 @@ _LOGGER = logging.getLogger(__name__) async def validate_input( - hass: core.HomeAssistant, data: Dict[str, str] -) -> Dict[str, str]: + hass: core.HomeAssistant, data: dict[str, str] +) -> dict[str, str]: """Validate the user input allows us to connect.""" websession = async_get_clientsession(hass) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index c09927fa7d2..9fcb73884b3 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -1,9 +1,11 @@ """Support for Hass.io.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging import os -from typing import Any, Dict, List, Optional +from typing import Any import voluptuous as vol @@ -236,7 +238,7 @@ async def async_set_addon_options( @bind_hass async def async_get_addon_discovery_info( hass: HomeAssistantType, slug: str -) -> Optional[dict]: +) -> dict | None: """Return discovery data for an add-on.""" hassio = hass.data[DOMAIN] data = await hassio.retrieve_discovery_messages() @@ -545,7 +547,7 @@ async def async_unload_entry( @callback def async_register_addons_in_dev_reg( - entry_id: str, dev_reg: DeviceRegistry, addons: List[Dict[str, Any]] + entry_id: str, dev_reg: DeviceRegistry, addons: list[dict[str, Any]] ) -> None: """Register addons in the device registry.""" for addon in addons: @@ -564,7 +566,7 @@ def async_register_addons_in_dev_reg( @callback def async_register_os_in_dev_reg( - entry_id: str, dev_reg: DeviceRegistry, os_dict: Dict[str, Any] + entry_id: str, dev_reg: DeviceRegistry, os_dict: dict[str, Any] ) -> None: """Register OS in the device registry.""" params = { @@ -581,7 +583,7 @@ def async_register_os_in_dev_reg( @callback def async_remove_addons_from_dev_reg( - dev_reg: DeviceRegistry, addons: List[Dict[str, Any]] + dev_reg: DeviceRegistry, addons: list[dict[str, Any]] ) -> None: """Remove addons from the device registry.""" for addon_slug in addons: @@ -607,7 +609,7 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator): self.dev_reg = dev_reg self.is_hass_os = "hassos" in get_info(self.hass) - async def _async_update_data(self) -> Dict[str, Any]: + async def _async_update_data(self) -> dict[str, Any]: """Update data via library.""" new_data = {} addon_data = get_supervisor_info(self.hass) diff --git a/homeassistant/components/hassio/binary_sensor.py b/homeassistant/components/hassio/binary_sensor.py index 2208f3fb580..b6faf566807 100644 --- a/homeassistant/components/hassio/binary_sensor.py +++ b/homeassistant/components/hassio/binary_sensor.py @@ -1,5 +1,7 @@ """Binary sensor platform for Hass.io addons.""" -from typing import Callable, List +from __future__ import annotations + +from typing import Callable from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry @@ -14,7 +16,7 @@ from .entity import HassioAddonEntity, HassioOSEntity async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Binary sensor set up for Hass.io config entry.""" coordinator = hass.data[ADDONS_COORDINATOR] diff --git a/homeassistant/components/hassio/entity.py b/homeassistant/components/hassio/entity.py index daadeb514a2..5f35235bb5d 100644 --- a/homeassistant/components/hassio/entity.py +++ b/homeassistant/components/hassio/entity.py @@ -1,5 +1,7 @@ """Base for Hass.io entities.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from homeassistant.const import ATTR_NAME from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -14,7 +16,7 @@ class HassioAddonEntity(CoordinatorEntity): def __init__( self, coordinator: HassioDataUpdateCoordinator, - addon: Dict[str, Any], + addon: dict[str, Any], attribute_name: str, sensor_name: str, ) -> None: @@ -27,7 +29,7 @@ class HassioAddonEntity(CoordinatorEntity): super().__init__(coordinator) @property - def addon_info(self) -> Dict[str, Any]: + def addon_info(self) -> dict[str, Any]: """Return add-on info.""" return self.coordinator.data[self._data_key][self.addon_slug] @@ -47,7 +49,7 @@ class HassioAddonEntity(CoordinatorEntity): return f"{self.addon_slug}_{self.attribute_name}" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device specific attributes.""" return {"identifiers": {(DOMAIN, self.addon_slug)}} @@ -68,7 +70,7 @@ class HassioOSEntity(CoordinatorEntity): super().__init__(coordinator) @property - def os_info(self) -> Dict[str, Any]: + def os_info(self) -> dict[str, Any]: """Return OS info.""" return self.coordinator.data[self._data_key] @@ -88,6 +90,6 @@ class HassioOSEntity(CoordinatorEntity): return f"home_assistant_os_{self.attribute_name}" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device specific attributes.""" return {"identifiers": {(DOMAIN, "OS")}} diff --git a/homeassistant/components/hassio/http.py b/homeassistant/components/hassio/http.py index 2aa05ae6ab4..e1bd1cb095c 100644 --- a/homeassistant/components/hassio/http.py +++ b/homeassistant/components/hassio/http.py @@ -1,9 +1,10 @@ """HTTP Support for Hass.io.""" +from __future__ import annotations + import asyncio import logging import os import re -from typing import Dict, Union import aiohttp from aiohttp import web @@ -57,7 +58,7 @@ class HassIOView(HomeAssistantView): async def _handle( self, request: web.Request, path: str - ) -> Union[web.Response, web.StreamResponse]: + ) -> web.Response | web.StreamResponse: """Route data to Hass.io.""" hass = request.app["hass"] if _need_auth(hass, path) and not request[KEY_AUTHENTICATED]: @@ -71,7 +72,7 @@ class HassIOView(HomeAssistantView): async def _command_proxy( self, path: str, request: web.Request - ) -> Union[web.Response, web.StreamResponse]: + ) -> web.Response | web.StreamResponse: """Return a client request with proxy origin for Hass.io supervisor. This method is a coroutine. @@ -131,7 +132,7 @@ class HassIOView(HomeAssistantView): raise HTTPBadGateway() -def _init_header(request: web.Request) -> Dict[str, str]: +def _init_header(request: web.Request) -> dict[str, str]: """Create initial header.""" headers = { X_HASSIO: os.environ.get("HASSIO_TOKEN", ""), diff --git a/homeassistant/components/hassio/ingress.py b/homeassistant/components/hassio/ingress.py index c69d2078468..1f0a49ae497 100644 --- a/homeassistant/components/hassio/ingress.py +++ b/homeassistant/components/hassio/ingress.py @@ -1,9 +1,10 @@ """Hass.io Add-on ingress service.""" +from __future__ import annotations + import asyncio from ipaddress import ip_address import logging import os -from typing import Dict, Union import aiohttp from aiohttp import hdrs, web @@ -46,7 +47,7 @@ class HassIOIngress(HomeAssistantView): async def _handle( self, request: web.Request, token: str, path: str - ) -> Union[web.Response, web.StreamResponse, web.WebSocketResponse]: + ) -> web.Response | web.StreamResponse | web.WebSocketResponse: """Route data to Hass.io ingress service.""" try: # Websocket @@ -114,7 +115,7 @@ class HassIOIngress(HomeAssistantView): async def _handle_request( self, request: web.Request, token: str, path: str - ) -> Union[web.Response, web.StreamResponse]: + ) -> web.Response | web.StreamResponse: """Ingress route for request.""" url = self._create_url(token, path) data = await request.read() @@ -159,9 +160,7 @@ class HassIOIngress(HomeAssistantView): return response -def _init_header( - request: web.Request, token: str -) -> Union[CIMultiDict, Dict[str, str]]: +def _init_header(request: web.Request, token: str) -> CIMultiDict | dict[str, str]: """Create initial header.""" headers = {} @@ -208,7 +207,7 @@ def _init_header( return headers -def _response_header(response: aiohttp.ClientResponse) -> Dict[str, str]: +def _response_header(response: aiohttp.ClientResponse) -> dict[str, str]: """Create response header.""" headers = {} diff --git a/homeassistant/components/hassio/sensor.py b/homeassistant/components/hassio/sensor.py index 711a2a46300..ae2bd20d6ed 100644 --- a/homeassistant/components/hassio/sensor.py +++ b/homeassistant/components/hassio/sensor.py @@ -1,5 +1,7 @@ """Sensor platform for Hass.io addons.""" -from typing import Callable, List +from __future__ import annotations + +from typing import Callable from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -13,7 +15,7 @@ from .entity import HassioAddonEntity, HassioOSEntity async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Sensor set up for Hass.io config entry.""" coordinator = hass.data[ADDONS_COORDINATOR] diff --git a/homeassistant/components/heatmiser/climate.py b/homeassistant/components/heatmiser/climate.py index b3f3363818c..c8e1db0a10f 100644 --- a/homeassistant/components/heatmiser/climate.py +++ b/homeassistant/components/heatmiser/climate.py @@ -1,6 +1,7 @@ """Support for the PRT Heatmiser themostats using the V3 protocol.""" +from __future__ import annotations + import logging -from typing import List from heatmiserV3 import connection, heatmiser import voluptuous as vol @@ -103,7 +104,7 @@ class HeatmiserV3Thermostat(ClimateEntity): return self._hvac_mode @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes. Need to be a subset of HVAC_MODES. diff --git a/homeassistant/components/heos/__init__.py b/homeassistant/components/heos/__init__.py index 11020d1166e..a71d0d2de50 100644 --- a/homeassistant/components/heos/__init__.py +++ b/homeassistant/components/heos/__init__.py @@ -1,8 +1,9 @@ """Denon HEOS Media Player.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Dict from pyheos import Heos, HeosError, const as heos_const import voluptuous as vol @@ -191,7 +192,7 @@ class ControllerManager: # Update players self._hass.helpers.dispatcher.async_dispatcher_send(SIGNAL_HEOS_UPDATED) - def update_ids(self, mapped_ids: Dict[int, int]): + def update_ids(self, mapped_ids: dict[int, int]): """Update the IDs in the device and entity registry.""" # mapped_ids contains the mapped IDs (new:old) for new_id, old_id in mapped_ids.items(): diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index e9b3f4ff9a4..9a6b0724caa 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -1,7 +1,9 @@ """Support for HERE travel time sensors.""" +from __future__ import annotations + from datetime import datetime, timedelta import logging -from typing import Callable, Dict, Optional, Union +from typing import Callable import herepy import voluptuous as vol @@ -143,9 +145,9 @@ PLATFORM_SCHEMA = vol.All( async def async_setup_platform( hass: HomeAssistant, - config: Dict[str, Union[str, bool]], + config: dict[str, str | bool], async_add_entities: Callable, - discovery_info: Optional[DiscoveryInfoType] = None, + discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the HERE travel time platform.""" api_key = config[CONF_API_KEY] @@ -255,7 +257,7 @@ class HERETravelTimeSensor(Entity): ) @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the sensor.""" if self._here_data.traffic_mode: if self._here_data.traffic_time is not None: @@ -273,7 +275,7 @@ class HERETravelTimeSensor(Entity): @property def extra_state_attributes( self, - ) -> Optional[Dict[str, Union[None, float, str, bool]]]: + ) -> dict[str, None | float | str | bool] | None: """Return the state attributes.""" if self._here_data.base_time is None: return None @@ -324,7 +326,7 @@ class HERETravelTimeSensor(Entity): await self.hass.async_add_executor_job(self._here_data.update) - async def _get_location_from_entity(self, entity_id: str) -> Optional[str]: + async def _get_location_from_entity(self, entity_id: str) -> str | None: """Get the location from the entity state or attributes.""" entity = self.hass.states.get(entity_id) @@ -480,7 +482,7 @@ class HERETravelTimeData: self.destination_name = waypoint[1]["mappedRoadName"] @staticmethod - def _build_hass_attribution(source_attribution: Dict) -> Optional[str]: + def _build_hass_attribution(source_attribution: dict) -> str | None: """Build a hass frontend ready string out of the sourceAttribution.""" suppliers = source_attribution.get("supplier") if suppliers is not None: diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index dd98ba4ef25..fbce4909e13 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -1,11 +1,13 @@ """Provide pre-made queries on top of the recorder component.""" +from __future__ import annotations + from collections import defaultdict from datetime import datetime as dt, timedelta from itertools import groupby import json import logging import time -from typing import Iterable, Optional, cast +from typing import Iterable, cast from aiohttp import web from sqlalchemy import and_, bindparam, func, not_, or_ @@ -462,7 +464,7 @@ class HistoryPeriodView(HomeAssistantView): self.use_include_order = use_include_order async def get( - self, request: web.Request, datetime: Optional[str] = None + self, request: web.Request, datetime: str | None = None ) -> web.Response: """Return history over a period of time.""" datetime_ = None diff --git a/homeassistant/components/homeassistant/scene.py b/homeassistant/components/homeassistant/scene.py index cca2c601493..3173d2d8c32 100644 --- a/homeassistant/components/homeassistant/scene.py +++ b/homeassistant/components/homeassistant/scene.py @@ -1,7 +1,9 @@ """Allow users to set and activate scenes.""" +from __future__ import annotations + from collections import namedtuple import logging -from typing import Any, List +from typing import Any import voluptuous as vol @@ -118,7 +120,7 @@ _LOGGER = logging.getLogger(__name__) @callback -def scenes_with_entity(hass: HomeAssistant, entity_id: str) -> List[str]: +def scenes_with_entity(hass: HomeAssistant, entity_id: str) -> list[str]: """Return all scenes that reference the entity.""" if DATA_PLATFORM not in hass.data: return [] @@ -133,7 +135,7 @@ def scenes_with_entity(hass: HomeAssistant, entity_id: str) -> List[str]: @callback -def entities_in_scene(hass: HomeAssistant, entity_id: str) -> List[str]: +def entities_in_scene(hass: HomeAssistant, entity_id: str) -> list[str]: """Return all entities in a scene.""" if DATA_PLATFORM not in hass.data: return [] diff --git a/homeassistant/components/homeassistant/triggers/state.py b/homeassistant/components/homeassistant/triggers/state.py index 8a03905d98d..df8e3c419e1 100644 --- a/homeassistant/components/homeassistant/triggers/state.py +++ b/homeassistant/components/homeassistant/triggers/state.py @@ -1,7 +1,9 @@ """Offer state listening automation rules.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Any, Dict, Optional +from typing import Any import voluptuous as vol @@ -79,7 +81,7 @@ async def async_attach_trigger( template.attach(hass, time_delta) match_all = from_state == MATCH_ALL and to_state == MATCH_ALL unsub_track_same = {} - period: Dict[str, timedelta] = {} + period: dict[str, timedelta] = {} match_from_state = process_state_match(from_state) match_to_state = process_state_match(to_state) attribute = config.get(CONF_ATTRIBUTE) @@ -93,8 +95,8 @@ async def async_attach_trigger( def state_automation_listener(event: Event): """Listen for state changes and calls action.""" entity: str = event.data["entity_id"] - from_s: Optional[State] = event.data.get("old_state") - to_s: Optional[State] = event.data.get("new_state") + from_s: State | None = event.data.get("old_state") + to_s: State | None = event.data.get("new_state") if from_s is None: old_value = None diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index 0a8f376fb33..d7b28036426 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -1,5 +1,7 @@ """Support for Homekit device discovery.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any import aiohomekit from aiohomekit.model import Accessory @@ -77,7 +79,7 @@ class HomeKitEntity(Entity): signal_remove() self._signals.clear() - async def async_put_characteristics(self, characteristics: Dict[str, Any]): + async def async_put_characteristics(self, characteristics: dict[str, Any]): """ Write characteristics to the device. diff --git a/homeassistant/components/homekit_controller/device_trigger.py b/homeassistant/components/homekit_controller/device_trigger.py index b2e668915d7..4de5ce66a09 100644 --- a/homeassistant/components/homekit_controller/device_trigger.py +++ b/homeassistant/components/homekit_controller/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for homekit devices.""" -from typing import List +from __future__ import annotations from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics.const import InputEventValues @@ -226,7 +226,7 @@ def async_fire_triggers(conn, events): source.fire(iid, ev) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for homekit devices.""" if device_id not in hass.data.get(TRIGGERS, {}): diff --git a/homeassistant/components/homekit_controller/humidifier.py b/homeassistant/components/homekit_controller/humidifier.py index e4bed25d618..227174d00e9 100644 --- a/homeassistant/components/homekit_controller/humidifier.py +++ b/homeassistant/components/homekit_controller/humidifier.py @@ -1,5 +1,5 @@ """Support for HomeKit Controller humidifier.""" -from typing import List, Optional +from __future__ import annotations from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes @@ -69,14 +69,14 @@ class HomeKitHumidifier(HomeKitEntity, HumidifierEntity): await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: False}) @property - def target_humidity(self) -> Optional[int]: + def target_humidity(self) -> int | None: """Return the humidity we try to reach.""" return self.service.value( CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD ) @property - def mode(self) -> Optional[str]: + def mode(self) -> str | None: """Return the current mode, e.g., home, auto, baby. Requires SUPPORT_MODES. @@ -87,7 +87,7 @@ class HomeKitHumidifier(HomeKitEntity, HumidifierEntity): return MODE_AUTO if mode == 1 else MODE_NORMAL @property - def available_modes(self) -> Optional[List[str]]: + def available_modes(self) -> list[str] | None: """Return a list of available modes. Requires SUPPORT_MODES. @@ -175,14 +175,14 @@ class HomeKitDehumidifier(HomeKitEntity, HumidifierEntity): await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: False}) @property - def target_humidity(self) -> Optional[int]: + def target_humidity(self) -> int | None: """Return the humidity we try to reach.""" return self.service.value( CharacteristicsTypes.RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD ) @property - def mode(self) -> Optional[str]: + def mode(self) -> str | None: """Return the current mode, e.g., home, auto, baby. Requires SUPPORT_MODES. @@ -193,7 +193,7 @@ class HomeKitDehumidifier(HomeKitEntity, HumidifierEntity): return MODE_AUTO if mode == 1 else MODE_NORMAL @property - def available_modes(self) -> Optional[List[str]]: + def available_modes(self) -> list[str] | None: """Return a list of available modes. Requires SUPPORT_MODES. diff --git a/homeassistant/components/homematicip_cloud/alarm_control_panel.py b/homeassistant/components/homematicip_cloud/alarm_control_panel.py index 51cec6ac0cd..7fa5e197aa8 100644 --- a/homeassistant/components/homematicip_cloud/alarm_control_panel.py +++ b/homeassistant/components/homematicip_cloud/alarm_control_panel.py @@ -1,6 +1,8 @@ """Support for HomematicIP Cloud alarm control panel.""" +from __future__ import annotations + import logging -from typing import Any, Dict +from typing import Any from homematicip.functionalHomes import SecurityAndAlarmHome @@ -44,7 +46,7 @@ class HomematicipAlarmControlPanelEntity(AlarmControlPanelEntity): _LOGGER.info("Setting up %s", self.name) @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device specific attributes.""" return { "identifiers": {(HMIPC_DOMAIN, f"ACP {self._home.id}")}, diff --git a/homeassistant/components/homematicip_cloud/binary_sensor.py b/homeassistant/components/homematicip_cloud/binary_sensor.py index 0d21967b242..4fcf1f67dd4 100644 --- a/homeassistant/components/homematicip_cloud/binary_sensor.py +++ b/homeassistant/components/homematicip_cloud/binary_sensor.py @@ -1,5 +1,7 @@ """Support for HomematicIP Cloud binary sensor.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from homematicip.aio.device import ( AsyncAccelerationSensor, @@ -166,7 +168,7 @@ class HomematicipCloudConnectionSensor(HomematicipGenericEntity, BinarySensorEnt return name if not self._home.name else f"{self._home.name} {name}" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device specific attributes.""" # Adds a sensor to the existing HAP device return { @@ -210,7 +212,7 @@ class HomematicipBaseActionSensor(HomematicipGenericEntity, BinarySensorEntity): return self._device.accelerationSensorTriggered @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the acceleration sensor.""" state_attr = super().extra_state_attributes @@ -285,7 +287,7 @@ class HomematicipShutterContact(HomematicipMultiContactInterface, BinarySensorEn return DEVICE_CLASS_DOOR @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the Shutter Contact.""" state_attr = super().extra_state_attributes @@ -412,7 +414,7 @@ class HomematicipSunshineSensor(HomematicipGenericEntity, BinarySensorEntity): return self._device.sunshine @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the illuminance sensor.""" state_attr = super().extra_state_attributes @@ -482,7 +484,7 @@ class HomematicipSecurityZoneSensorGroup(HomematicipGenericEntity, BinarySensorE return True @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the security zone group.""" state_attr = super().extra_state_attributes @@ -526,7 +528,7 @@ class HomematicipSecuritySensorGroup( super().__init__(hap, device, post="Sensors") @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the security group.""" state_attr = super().extra_state_attributes diff --git a/homeassistant/components/homematicip_cloud/climate.py b/homeassistant/components/homematicip_cloud/climate.py index c09bd2cc53e..5cdadf4d5f1 100644 --- a/homeassistant/components/homematicip_cloud/climate.py +++ b/homeassistant/components/homematicip_cloud/climate.py @@ -1,5 +1,7 @@ """Support for HomematicIP Cloud climate devices.""" -from typing import Any, Dict, List, Optional, Union +from __future__ import annotations + +from typing import Any from homematicip.aio.device import AsyncHeatingThermostat, AsyncHeatingThermostatCompact from homematicip.aio.group import AsyncHeatingGroup @@ -71,7 +73,7 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): self._simple_heating = self._first_radiator_thermostat @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device specific attributes.""" return { "identifiers": {(HMIPC_DOMAIN, self._device.id)}, @@ -121,7 +123,7 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): return HVAC_MODE_AUTO @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" if self._disabled_by_cooling_mode and not self._has_switch: return [HVAC_MODE_OFF] @@ -133,7 +135,7 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): ) @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """ Return the current hvac_action. @@ -151,7 +153,7 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): return None @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode.""" if self._device.boostMode: return PRESET_BOOST @@ -174,7 +176,7 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): ) @property - def preset_modes(self) -> List[str]: + def preset_modes(self) -> list[str]: """Return a list of available preset modes incl. hmip profiles.""" # Boost is only available if a radiator thermostat is in the room, # and heat mode is enabled. @@ -237,7 +239,7 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): await self._device.set_active_profile(profile_idx) @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the access point.""" state_attr = super().extra_state_attributes @@ -259,7 +261,7 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): return self._home.get_functionalHome(IndoorClimateHome) @property - def _device_profiles(self) -> List[str]: + def _device_profiles(self) -> list[str]: """Return the relevant profiles.""" return [ profile @@ -270,7 +272,7 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): ] @property - def _device_profile_names(self) -> List[str]: + def _device_profile_names(self) -> list[str]: """Return a collection of profile names.""" return [profile.name for profile in self._device_profiles] @@ -298,7 +300,7 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): ) @property - def _relevant_profile_group(self) -> List[str]: + def _relevant_profile_group(self) -> list[str]: """Return the relevant profile groups.""" if self._disabled_by_cooling_mode: return [] @@ -322,7 +324,7 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): @property def _first_radiator_thermostat( self, - ) -> Optional[Union[AsyncHeatingThermostat, AsyncHeatingThermostatCompact]]: + ) -> AsyncHeatingThermostat | AsyncHeatingThermostatCompact | None: """Return the first radiator thermostat from the hmip heating group.""" for device in self._device.devices: if isinstance( diff --git a/homeassistant/components/homematicip_cloud/config_flow.py b/homeassistant/components/homematicip_cloud/config_flow.py index b6b78948894..d90d8d7023b 100644 --- a/homeassistant/components/homematicip_cloud/config_flow.py +++ b/homeassistant/components/homematicip_cloud/config_flow.py @@ -1,5 +1,7 @@ """Config flow to configure the HomematicIP Cloud component.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any import voluptuous as vol @@ -27,11 +29,11 @@ class HomematicipCloudFlowHandler(config_entries.ConfigFlow): """Initialize HomematicIP Cloud config flow.""" self.auth = None - async def async_step_user(self, user_input=None) -> Dict[str, Any]: + async def async_step_user(self, user_input=None) -> dict[str, Any]: """Handle a flow initialized by the user.""" return await self.async_step_init(user_input) - async def async_step_init(self, user_input=None) -> Dict[str, Any]: + async def async_step_init(self, user_input=None) -> dict[str, Any]: """Handle a flow start.""" errors = {} @@ -62,7 +64,7 @@ class HomematicipCloudFlowHandler(config_entries.ConfigFlow): errors=errors, ) - async def async_step_link(self, user_input=None) -> Dict[str, Any]: + async def async_step_link(self, user_input=None) -> dict[str, Any]: """Attempt to link with the HomematicIP Cloud access point.""" errors = {} @@ -84,7 +86,7 @@ class HomematicipCloudFlowHandler(config_entries.ConfigFlow): return self.async_show_form(step_id="link", errors=errors) - async def async_step_import(self, import_info) -> Dict[str, Any]: + async def async_step_import(self, import_info) -> dict[str, Any]: """Import a new access point as a config entry.""" hapid = import_info[HMIPC_HAPID].replace("-", "").upper() authtoken = import_info[HMIPC_AUTHTOKEN] diff --git a/homeassistant/components/homematicip_cloud/cover.py b/homeassistant/components/homematicip_cloud/cover.py index 29a06c558fe..aa1be11758e 100644 --- a/homeassistant/components/homematicip_cloud/cover.py +++ b/homeassistant/components/homematicip_cloud/cover.py @@ -1,5 +1,5 @@ """Support for HomematicIP Cloud cover devices.""" -from typing import Optional +from __future__ import annotations from homematicip.aio.device import ( AsyncBlindModule, @@ -95,7 +95,7 @@ class HomematicipBlindModule(HomematicipGenericEntity, CoverEntity): ) @property - def is_closed(self) -> Optional[bool]: + def is_closed(self) -> bool | None: """Return if the cover is closed.""" if self._device.primaryShadingLevel is not None: return self._device.primaryShadingLevel == HMIP_COVER_CLOSED @@ -168,7 +168,7 @@ class HomematicipMultiCoverShutter(HomematicipGenericEntity, CoverEntity): await self._device.set_shutter_level(level, self._channel) @property - def is_closed(self) -> Optional[bool]: + def is_closed(self) -> bool | None: """Return if the cover is closed.""" if self._device.functionalChannels[self._channel].shutterLevel is not None: return ( @@ -265,7 +265,7 @@ class HomematicipGarageDoorModule(HomematicipGenericEntity, CoverEntity): return door_state_to_position.get(self._device.doorState) @property - def is_closed(self) -> Optional[bool]: + def is_closed(self) -> bool | None: """Return if the cover is closed.""" return self._device.doorState == DoorState.CLOSED @@ -305,7 +305,7 @@ class HomematicipCoverShutterGroup(HomematicipGenericEntity, CoverEntity): return None @property - def is_closed(self) -> Optional[bool]: + def is_closed(self) -> bool | None: """Return if the cover is closed.""" if self._device.shutterLevel is not None: return self._device.shutterLevel == HMIP_COVER_CLOSED diff --git a/homeassistant/components/homematicip_cloud/generic_entity.py b/homeassistant/components/homematicip_cloud/generic_entity.py index 345446e78b2..856e47a1dee 100644 --- a/homeassistant/components/homematicip_cloud/generic_entity.py +++ b/homeassistant/components/homematicip_cloud/generic_entity.py @@ -1,6 +1,8 @@ """Generic entity for the HomematicIP Cloud component.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from homematicip.aio.device import AsyncDevice from homematicip.aio.group import AsyncGroup @@ -74,9 +76,9 @@ class HomematicipGenericEntity(Entity): self, hap: HomematicipHAP, device, - post: Optional[str] = None, - channel: Optional[int] = None, - is_multi_channel: Optional[bool] = False, + post: str | None = None, + channel: int | None = None, + is_multi_channel: bool | None = False, ) -> None: """Initialize the generic entity.""" self._hap = hap @@ -90,7 +92,7 @@ class HomematicipGenericEntity(Entity): _LOGGER.info("Setting up %s (%s)", self.name, self._device.modelType) @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device specific attributes.""" # Only physical devices should be HA devices. if isinstance(self._device, AsyncDevice): @@ -223,7 +225,7 @@ class HomematicipGenericEntity(Entity): return unique_id @property - def icon(self) -> Optional[str]: + def icon(self) -> str | None: """Return the icon.""" for attr, icon in DEVICE_ATTRIBUTE_ICONS.items(): if getattr(self._device, attr, None): @@ -232,7 +234,7 @@ class HomematicipGenericEntity(Entity): return None @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the generic entity.""" state_attr = {} diff --git a/homeassistant/components/homematicip_cloud/light.py b/homeassistant/components/homematicip_cloud/light.py index c453cf516dc..5732ea1bf96 100644 --- a/homeassistant/components/homematicip_cloud/light.py +++ b/homeassistant/components/homematicip_cloud/light.py @@ -1,5 +1,7 @@ """Support for HomematicIP Cloud lights.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from homematicip.aio.device import ( AsyncBrandDimmer, @@ -90,7 +92,7 @@ class HomematicipLightMeasuring(HomematicipLight): """Representation of the HomematicIP measuring light.""" @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the light.""" state_attr = super().extra_state_attributes @@ -206,7 +208,7 @@ class HomematicipNotificationLight(HomematicipGenericEntity, LightEntity): return self._color_switcher.get(simple_rgb_color, [0.0, 0.0]) @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the notification light sensor.""" state_attr = super().extra_state_attributes diff --git a/homeassistant/components/homematicip_cloud/sensor.py b/homeassistant/components/homematicip_cloud/sensor.py index 9f7c7517c3a..6f4acc6137a 100644 --- a/homeassistant/components/homematicip_cloud/sensor.py +++ b/homeassistant/components/homematicip_cloud/sensor.py @@ -1,5 +1,7 @@ """Support for HomematicIP Cloud sensors.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from homematicip.aio.device import ( AsyncBrandSwitchMeasuring, @@ -222,7 +224,7 @@ class HomematicipTemperatureSensor(HomematicipGenericEntity): return TEMP_CELSIUS @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the windspeed sensor.""" state_attr = super().extra_state_attributes @@ -259,7 +261,7 @@ class HomematicipIlluminanceSensor(HomematicipGenericEntity): return LIGHT_LUX @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the wind speed sensor.""" state_attr = super().extra_state_attributes @@ -312,7 +314,7 @@ class HomematicipWindspeedSensor(HomematicipGenericEntity): return SPEED_KILOMETERS_PER_HOUR @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the wind speed sensor.""" state_attr = super().extra_state_attributes @@ -354,7 +356,7 @@ class HomematicipPassageDetectorDeltaCounter(HomematicipGenericEntity): return self._device.leftRightCounterDelta @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the delta counter.""" state_attr = super().extra_state_attributes diff --git a/homeassistant/components/homematicip_cloud/services.py b/homeassistant/components/homematicip_cloud/services.py index 7c92ac5e721..aa82e72e284 100644 --- a/homeassistant/components/homematicip_cloud/services.py +++ b/homeassistant/components/homematicip_cloud/services.py @@ -1,7 +1,8 @@ """Support for HomematicIP Cloud devices.""" +from __future__ import annotations + import logging from pathlib import Path -from typing import Optional from homematicip.aio.device import AsyncSwitchMeasuring from homematicip.aio.group import AsyncHeatingGroup @@ -342,7 +343,7 @@ async def _async_reset_energy_counter( await device.reset_energy_counter() -def _get_home(hass: HomeAssistantType, hapid: str) -> Optional[AsyncHome]: +def _get_home(hass: HomeAssistantType, hapid: str) -> AsyncHome | None: """Return a HmIP home.""" hap = hass.data[HMIPC_DOMAIN].get(hapid) if hap: diff --git a/homeassistant/components/homematicip_cloud/switch.py b/homeassistant/components/homematicip_cloud/switch.py index e75615bf336..8172d64d357 100644 --- a/homeassistant/components/homematicip_cloud/switch.py +++ b/homeassistant/components/homematicip_cloud/switch.py @@ -1,5 +1,7 @@ """Support for HomematicIP Cloud switches.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from homematicip.aio.device import ( AsyncBrandSwitchMeasuring, @@ -141,7 +143,7 @@ class HomematicipGroupSwitch(HomematicipGenericEntity, SwitchEntity): return True @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the switch-group.""" state_attr = super().extra_state_attributes diff --git a/homeassistant/components/honeywell/climate.py b/homeassistant/components/honeywell/climate.py index a825916628d..8053ad85502 100644 --- a/homeassistant/components/honeywell/climate.py +++ b/homeassistant/components/honeywell/climate.py @@ -1,7 +1,9 @@ """Support for Honeywell (US) Total Connect Comfort climate systems.""" +from __future__ import annotations + import datetime import logging -from typing import Any, Dict, List, Optional +from typing import Any import requests import somecomfort @@ -192,12 +194,12 @@ class HoneywellUSThermostat(ClimateEntity): self._supported_features |= SUPPORT_FAN_MODE @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the honeywell, if any.""" return self._device.name @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the device specific state attributes.""" data = {} data[ATTR_FAN_ACTION] = "running" if self._device.fan_running else "idle" @@ -235,7 +237,7 @@ class HoneywellUSThermostat(ClimateEntity): return TEMP_CELSIUS if self._device.temperature_unit == "C" else TEMP_FAHRENHEIT @property - def current_humidity(self) -> Optional[int]: + def current_humidity(self) -> int | None: """Return the current humidity.""" return self._device.current_humidity @@ -245,24 +247,24 @@ class HoneywellUSThermostat(ClimateEntity): return HW_MODE_TO_HVAC_MODE[self._device.system_mode] @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return list(self._hvac_mode_map) @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation if supported.""" if self.hvac_mode == HVAC_MODE_OFF: return None return HW_MODE_TO_HA_HVAC_ACTION[self._device.equipment_output_status] @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._device.current_temperature @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" if self.hvac_mode == HVAC_MODE_COOL: return self._device.setpoint_cool @@ -271,41 +273,41 @@ class HoneywellUSThermostat(ClimateEntity): return None @property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" if self.hvac_mode == HVAC_MODE_HEAT_COOL: return self._device.setpoint_cool return None @property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" if self.hvac_mode == HVAC_MODE_HEAT_COOL: return self._device.setpoint_heat return None @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" return PRESET_AWAY if self._away else None @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" return [PRESET_NONE, PRESET_AWAY] @property - def is_aux_heat(self) -> Optional[str]: + def is_aux_heat(self) -> str | None: """Return true if aux heater.""" return self._device.system_mode == "emheat" @property - def fan_mode(self) -> Optional[str]: + def fan_mode(self) -> str | None: """Return the fan setting.""" return HW_FAN_MODE_TO_HA[self._device.fan_mode] @property - def fan_modes(self) -> Optional[List[str]]: + def fan_modes(self) -> list[str] | None: """Return the list of available fan modes.""" return list(self._fan_mode_map) diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 993d466ae18..5f57b4b77b8 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -1,10 +1,12 @@ """Support to serve the Home Assistant API as WSGI application.""" +from __future__ import annotations + from contextvars import ContextVar from ipaddress import ip_network import logging import os import ssl -from typing import Dict, Optional, cast +from typing import Optional, cast from aiohttp import web from aiohttp.web_exceptions import HTTPMovedPermanently @@ -102,7 +104,7 @@ CONFIG_SCHEMA = vol.Schema({DOMAIN: HTTP_SCHEMA}, extra=vol.ALLOW_EXTRA) @bind_hass -async def async_get_last_config(hass: HomeAssistant) -> Optional[dict]: +async def async_get_last_config(hass: HomeAssistant) -> dict | None: """Return the last known working config.""" store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) return cast(Optional[dict], await store.async_load()) @@ -115,7 +117,7 @@ class ApiConfig: self, local_ip: str, host: str, - port: Optional[int] = SERVER_PORT, + port: int | None = SERVER_PORT, use_ssl: bool = False, ) -> None: """Initialize a new API config object.""" @@ -379,7 +381,7 @@ class HomeAssistantHTTP: async def start_http_server_and_save_config( - hass: HomeAssistant, conf: Dict, server: HomeAssistantHTTP + hass: HomeAssistant, conf: dict, server: HomeAssistantHTTP ) -> None: """Startup the http server and save the config.""" await server.start() # type: ignore @@ -395,6 +397,6 @@ async def start_http_server_and_save_config( await store.async_save(conf) -current_request: ContextVar[Optional[web.Request]] = ContextVar( +current_request: ContextVar[web.Request | None] = ContextVar( "current_request", default=None ) diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index 2e51dc35d88..009a4c7afb5 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -1,10 +1,11 @@ """Ban logic for HTTP component.""" +from __future__ import annotations + from collections import defaultdict from datetime import datetime from ipaddress import ip_address import logging from socket import gethostbyaddr, herror -from typing import List, Optional from aiohttp.web import middleware from aiohttp.web_exceptions import HTTPForbidden, HTTPUnauthorized @@ -178,15 +179,15 @@ async def process_success_login(request): class IpBan: """Represents banned IP address.""" - def __init__(self, ip_ban: str, banned_at: Optional[datetime] = None) -> None: + def __init__(self, ip_ban: str, banned_at: datetime | None = None) -> None: """Initialize IP Ban object.""" self.ip_address = ip_address(ip_ban) self.banned_at = banned_at or dt_util.utcnow() -async def async_load_ip_bans_config(hass: HomeAssistant, path: str) -> List[IpBan]: +async def async_load_ip_bans_config(hass: HomeAssistant, path: str) -> list[IpBan]: """Load list of banned IPs from config file.""" - ip_list: List[IpBan] = [] + ip_list: list[IpBan] = [] try: list_ = await hass.async_add_executor_job(load_yaml_config_file, path) diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py index 354159f13be..b4dbb845638 100644 --- a/homeassistant/components/http/view.py +++ b/homeassistant/components/http/view.py @@ -1,8 +1,10 @@ """Support for views.""" +from __future__ import annotations + import asyncio import json import logging -from typing import Any, Callable, List, Optional +from typing import Any, Callable from aiohttp import web from aiohttp.typedefs import LooseHeaders @@ -26,8 +28,8 @@ _LOGGER = logging.getLogger(__name__) class HomeAssistantView: """Base view for all views.""" - url: Optional[str] = None - extra_urls: List[str] = [] + url: str | None = None + extra_urls: list[str] = [] # Views inheriting from this class can override this requires_auth = True cors_allowed = False @@ -45,7 +47,7 @@ class HomeAssistantView: def json( result: Any, status_code: int = HTTP_OK, - headers: Optional[LooseHeaders] = None, + headers: LooseHeaders | None = None, ) -> web.Response: """Return a JSON response.""" try: @@ -66,8 +68,8 @@ class HomeAssistantView: self, message: str, status_code: int = HTTP_OK, - message_code: Optional[str] = None, - headers: Optional[LooseHeaders] = None, + message_code: str | None = None, + headers: LooseHeaders | None = None, ) -> web.Response: """Return a JSON message response.""" data = {"message": message} diff --git a/homeassistant/components/http/web_runner.py b/homeassistant/components/http/web_runner.py index c30ba32b780..74410026f94 100644 --- a/homeassistant/components/http/web_runner.py +++ b/homeassistant/components/http/web_runner.py @@ -1,7 +1,8 @@ """HomeAssistant specific aiohttp Site.""" +from __future__ import annotations + import asyncio from ssl import SSLContext -from typing import List, Optional, Union from aiohttp import web from yarl import URL @@ -25,14 +26,14 @@ class HomeAssistantTCPSite(web.BaseSite): def __init__( self, runner: "web.BaseRunner", - host: Union[None, str, List[str]], + host: None | str | list[str], port: int, *, shutdown_timeout: float = 10.0, - ssl_context: Optional[SSLContext] = None, + ssl_context: SSLContext | None = None, backlog: int = 128, - reuse_address: Optional[bool] = None, - reuse_port: Optional[bool] = None, + reuse_address: bool | None = None, + reuse_port: bool | None = None, ) -> None: # noqa: D107 super().__init__( runner, diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index 2d33d1a1822..d82e7e03e40 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -1,4 +1,5 @@ """Support for Huawei LTE routers.""" +from __future__ import annotations from collections import defaultdict from datetime import timedelta @@ -6,7 +7,7 @@ from functools import partial import ipaddress import logging import time -from typing import Any, Callable, Dict, List, Set, Tuple, cast +from typing import Any, Callable, cast from urllib.parse import urlparse import attr @@ -138,13 +139,13 @@ class Router: mac: str = attr.ib() signal_update: CALLBACK_TYPE = attr.ib() - data: Dict[str, Any] = attr.ib(init=False, factory=dict) - subscriptions: Dict[str, Set[str]] = attr.ib( + data: dict[str, Any] = attr.ib(init=False, factory=dict) + subscriptions: dict[str, set[str]] = attr.ib( init=False, factory=lambda: defaultdict(set, ((x, {"initial_scan"}) for x in ALL_KEYS)), ) - inflight_gets: Set[str] = attr.ib(init=False, factory=set) - unload_handlers: List[CALLBACK_TYPE] = attr.ib(init=False, factory=list) + inflight_gets: set[str] = attr.ib(init=False, factory=set) + unload_handlers: list[CALLBACK_TYPE] = attr.ib(init=False, factory=list) client: Client suspended = attr.ib(init=False, default=False) notify_last_attempt: float = attr.ib(init=False, default=-1) @@ -167,7 +168,7 @@ class Router: return DEFAULT_DEVICE_NAME @property - def device_identifiers(self) -> Set[Tuple[str, str]]: + def device_identifiers(self) -> set[tuple[str, str]]: """Get router identifiers for device registry.""" try: return {(DOMAIN, self.data[KEY_DEVICE_INFORMATION]["SerialNumber"])} @@ -175,7 +176,7 @@ class Router: return set() @property - def device_connections(self) -> Set[Tuple[str, str]]: + def device_connections(self) -> set[tuple[str, str]]: """Get router connections for device registry.""" return {(dr.CONNECTION_NETWORK_MAC, self.mac)} if self.mac else set() @@ -304,8 +305,8 @@ class HuaweiLteData: hass_config: dict = attr.ib() # Our YAML config, keyed by router URL - config: Dict[str, Dict[str, Any]] = attr.ib() - routers: Dict[str, Router] = attr.ib(init=False, factory=dict) + config: dict[str, dict[str, Any]] = attr.ib() + routers: dict[str, Router] = attr.ib(init=False, factory=dict) async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) -> bool: @@ -484,7 +485,7 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: logging.getLogger("dicttoxml").setLevel(logging.WARNING) # Arrange our YAML config to dict with normalized URLs as keys - domain_config: Dict[str, Dict[str, Any]] = {} + domain_config: dict[str, dict[str, Any]] = {} if DOMAIN not in hass.data: hass.data[DOMAIN] = HuaweiLteData(hass_config=config, config=domain_config) for router_config in config.get(DOMAIN, []): @@ -588,7 +589,7 @@ class HuaweiLteBaseEntity(Entity): router: Router = attr.ib() _available: bool = attr.ib(init=False, default=True) - _unsub_handlers: List[Callable] = attr.ib(init=False, factory=list) + _unsub_handlers: list[Callable] = attr.ib(init=False, factory=list) @property def _entity_name(self) -> str: @@ -620,7 +621,7 @@ class HuaweiLteBaseEntity(Entity): return False @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Get info for matching with parent router.""" return { "identifiers": self.router.device_identifiers, diff --git a/homeassistant/components/huawei_lte/binary_sensor.py b/homeassistant/components/huawei_lte/binary_sensor.py index 525dd3352e7..833a632b0db 100644 --- a/homeassistant/components/huawei_lte/binary_sensor.py +++ b/homeassistant/components/huawei_lte/binary_sensor.py @@ -1,7 +1,8 @@ """Support for Huawei LTE binary sensors.""" +from __future__ import annotations import logging -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable import attr from huawei_lte_api.enums.cradle import ConnectionStatusEnum @@ -29,11 +30,11 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up from config entry.""" router = hass.data[DOMAIN].routers[config_entry.data[CONF_URL]] - entities: List[Entity] = [] + entities: list[Entity] = [] if router.data.get(KEY_MONITORING_STATUS): entities.append(HuaweiLteMobileConnectionBinarySensor(router)) @@ -53,7 +54,7 @@ class HuaweiLteBaseBinarySensor(HuaweiLteBaseEntity, BinarySensorEntity): key: str item: str - _raw_state: Optional[str] = attr.ib(init=False, default=None) + _raw_state: str | None = attr.ib(init=False, default=None) @property def entity_registry_enabled_default(self) -> bool: @@ -142,7 +143,7 @@ class HuaweiLteMobileConnectionBinarySensor(HuaweiLteBaseBinarySensor): return True @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Get additional attributes related to connection status.""" attributes = {} if self._raw_state in CONNECTION_STATE_ATTRIBUTES: diff --git a/homeassistant/components/huawei_lte/config_flow.py b/homeassistant/components/huawei_lte/config_flow.py index e38b873a5bb..80f44e87319 100644 --- a/homeassistant/components/huawei_lte/config_flow.py +++ b/homeassistant/components/huawei_lte/config_flow.py @@ -3,7 +3,7 @@ from __future__ import annotations from collections import OrderedDict import logging -from typing import Any, Dict, Optional +from typing import Any from urllib.parse import urlparse from huawei_lte_api.AuthorizedConnection import AuthorizedConnection @@ -55,9 +55,9 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def _async_show_user_form( self, - user_input: Optional[Dict[str, Any]] = None, - errors: Optional[Dict[str, str]] = None, - ) -> Dict[str, Any]: + user_input: dict[str, Any] | None = None, + errors: dict[str, str] | None = None, + ) -> dict[str, Any]: if user_input is None: user_input = {} return self.async_show_form( @@ -94,12 +94,12 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) async def async_step_import( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle import initiated config flow.""" return await self.async_step_user(user_input) - def _already_configured(self, user_input: Dict[str, Any]) -> bool: + def _already_configured(self, user_input: dict[str, Any]) -> bool: """See if we already have a router matching user input configured.""" existing_urls = { url_normalize(entry.data[CONF_URL], default_scheme="http") @@ -108,8 +108,8 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return user_input[CONF_URL] in existing_urls async def async_step_user( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle user initiated config flow.""" if user_input is None: return await self._async_show_user_form() @@ -129,7 +129,7 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if self._already_configured(user_input): return self.async_abort(reason="already_configured") - conn: Optional[Connection] = None + conn: Connection | None = None def logout() -> None: if isinstance(conn, AuthorizedConnection): @@ -138,7 +138,7 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): except Exception: # pylint: disable=broad-except _LOGGER.debug("Could not logout", exc_info=True) - def try_connect(user_input: Dict[str, Any]) -> Connection: + def try_connect(user_input: dict[str, Any]) -> Connection: """Try connecting with given credentials.""" username = user_input.get(CONF_USERNAME) password = user_input.get(CONF_PASSWORD) @@ -222,8 +222,8 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=title, data=user_input) async def async_step_ssdp( # type: ignore # mypy says signature incompatible with supertype, but it's the same? - self, discovery_info: Dict[str, Any] - ) -> Dict[str, Any]: + self, discovery_info: dict[str, Any] + ) -> dict[str, Any]: """Handle SSDP initiated config flow.""" await self.async_set_unique_id(discovery_info[ssdp.ATTR_UPNP_UDN]) self._abort_if_unique_id_configured() @@ -263,8 +263,8 @@ class OptionsFlowHandler(config_entries.OptionsFlow): self.config_entry = config_entry async def async_step_init( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle options flow.""" # Recipients are persisted as a list, but handled as comma separated string in UI diff --git a/homeassistant/components/huawei_lte/device_tracker.py b/homeassistant/components/huawei_lte/device_tracker.py index 3e889263b76..b042c0c2912 100644 --- a/homeassistant/components/huawei_lte/device_tracker.py +++ b/homeassistant/components/huawei_lte/device_tracker.py @@ -1,8 +1,9 @@ """Support for device tracking of Huawei LTE routers.""" +from __future__ import annotations import logging import re -from typing import Any, Callable, Dict, List, Optional, Set, cast +from typing import Any, Callable, cast import attr from stringcase import snakecase @@ -31,7 +32,7 @@ _DEVICE_SCAN = f"{DEVICE_TRACKER_DOMAIN}/device_scan" async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up from config entry.""" @@ -46,9 +47,9 @@ async def async_setup_entry( return # Initialize already tracked entities - tracked: Set[str] = set() + tracked: set[str] = set() registry = await entity_registry.async_get_registry(hass) - known_entities: List[Entity] = [] + known_entities: list[Entity] = [] for entity in registry.entities.values(): if ( entity.domain == DEVICE_TRACKER_DOMAIN @@ -82,8 +83,8 @@ async def async_setup_entry( def async_add_new_entities( hass: HomeAssistantType, router_url: str, - async_add_entities: Callable[[List[Entity], bool], None], - tracked: Set[str], + async_add_entities: Callable[[list[Entity], bool], None], + tracked: set[str], ) -> None: """Add new entities that are not already being tracked.""" router = hass.data[DOMAIN].routers[router_url] @@ -93,7 +94,7 @@ def async_add_new_entities( _LOGGER.debug("%s[%s][%s] not in data", KEY_WLAN_HOST_LIST, "Hosts", "Host") return - new_entities: List[Entity] = [] + new_entities: list[Entity] = [] for host in (x for x in hosts if x.get("MacAddress")): entity = HuaweiLteScannerEntity(router, host["MacAddress"]) if entity.unique_id in tracked: @@ -125,8 +126,8 @@ class HuaweiLteScannerEntity(HuaweiLteBaseEntity, ScannerEntity): mac: str = attr.ib() _is_connected: bool = attr.ib(init=False, default=False) - _hostname: Optional[str] = attr.ib(init=False, default=None) - _extra_state_attributes: Dict[str, Any] = attr.ib(init=False, factory=dict) + _hostname: str | None = attr.ib(init=False, default=None) + _extra_state_attributes: dict[str, Any] = attr.ib(init=False, factory=dict) def __attrs_post_init__(self) -> None: """Initialize internal state.""" @@ -151,7 +152,7 @@ class HuaweiLteScannerEntity(HuaweiLteBaseEntity, ScannerEntity): return self._is_connected @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Get additional attributes related to entity state.""" return self._extra_state_attributes diff --git a/homeassistant/components/huawei_lte/notify.py b/homeassistant/components/huawei_lte/notify.py index ef354fefaf3..ea7b5d9f6ab 100644 --- a/homeassistant/components/huawei_lte/notify.py +++ b/homeassistant/components/huawei_lte/notify.py @@ -3,7 +3,7 @@ from __future__ import annotations import logging import time -from typing import Any, Dict, List, Optional +from typing import Any import attr from huawei_lte_api.exceptions import ResponseErrorException @@ -20,9 +20,9 @@ _LOGGER = logging.getLogger(__name__) async def async_get_service( hass: HomeAssistantType, - config: Dict[str, Any], - discovery_info: Optional[Dict[str, Any]] = None, -) -> Optional[HuaweiLteSmsNotificationService]: + config: dict[str, Any], + discovery_info: dict[str, Any] | None = None, +) -> HuaweiLteSmsNotificationService | None: """Get the notification service.""" if discovery_info is None: return None @@ -38,7 +38,7 @@ class HuaweiLteSmsNotificationService(BaseNotificationService): """Huawei LTE router SMS notification service.""" router: Router = attr.ib() - default_targets: List[str] = attr.ib() + default_targets: list[str] = attr.ib() def send_message(self, message: str = "", **kwargs: Any) -> None: """Send message to target numbers.""" diff --git a/homeassistant/components/huawei_lte/sensor.py b/homeassistant/components/huawei_lte/sensor.py index 3815ac831b5..c0773fdf808 100644 --- a/homeassistant/components/huawei_lte/sensor.py +++ b/homeassistant/components/huawei_lte/sensor.py @@ -1,9 +1,10 @@ """Support for Huawei LTE sensors.""" +from __future__ import annotations from bisect import bisect import logging import re -from typing import Callable, Dict, List, NamedTuple, Optional, Pattern, Tuple, Union +from typing import Callable, NamedTuple, Pattern import attr @@ -45,17 +46,17 @@ _LOGGER = logging.getLogger(__name__) class SensorMeta(NamedTuple): """Metadata for defining sensors.""" - name: Optional[str] = None - device_class: Optional[str] = None - icon: Union[str, Callable[[StateType], str], None] = None - unit: Optional[str] = None + name: str | None = None + device_class: str | None = None + icon: str | Callable[[StateType], str] | None = None + unit: str | None = None enabled_default: bool = False - include: Optional[Pattern[str]] = None - exclude: Optional[Pattern[str]] = None - formatter: Optional[Callable[[str], Tuple[StateType, Optional[str]]]] = None + include: Pattern[str] | None = None + exclude: Pattern[str] | None = None + formatter: Callable[[str], tuple[StateType, str | None]] | None = None -SENSOR_META: Dict[Union[str, Tuple[str, str]], SensorMeta] = { +SENSOR_META: dict[str | tuple[str, str], SensorMeta] = { KEY_DEVICE_INFORMATION: SensorMeta( include=re.compile(r"^WanIP.*Address$", re.IGNORECASE) ), @@ -329,11 +330,11 @@ SENSOR_META: Dict[Union[str, Tuple[str, str]], SensorMeta] = { async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up from config entry.""" router = hass.data[DOMAIN].routers[config_entry.data[CONF_URL]] - sensors: List[Entity] = [] + sensors: list[Entity] = [] for key in SENSOR_KEYS: items = router.data.get(key) if not items: @@ -354,7 +355,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -def format_default(value: StateType) -> Tuple[StateType, Optional[str]]: +def format_default(value: StateType) -> tuple[StateType, str | None]: """Format value.""" unit = None if value is not None: @@ -380,7 +381,7 @@ class HuaweiLteSensor(HuaweiLteBaseEntity): meta: SensorMeta = attr.ib() _state: StateType = attr.ib(init=False, default=STATE_UNKNOWN) - _unit: Optional[str] = attr.ib(init=False) + _unit: str | None = attr.ib(init=False) async def async_added_to_hass(self) -> None: """Subscribe to needed data on add.""" @@ -406,17 +407,17 @@ class HuaweiLteSensor(HuaweiLteBaseEntity): return self._state @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return sensor device class.""" return self.meta.device_class @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return sensor's unit of measurement.""" return self.meta.unit or self._unit @property - def icon(self) -> Optional[str]: + def icon(self) -> str | None: """Return icon for sensor.""" icon = self.meta.icon if callable(icon): diff --git a/homeassistant/components/huawei_lte/switch.py b/homeassistant/components/huawei_lte/switch.py index 4dfa1e32df2..9279226e8ec 100644 --- a/homeassistant/components/huawei_lte/switch.py +++ b/homeassistant/components/huawei_lte/switch.py @@ -1,7 +1,8 @@ """Support for Huawei LTE switches.""" +from __future__ import annotations import logging -from typing import Any, Callable, List, Optional +from typing import Any, Callable import attr @@ -24,11 +25,11 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up from config entry.""" router = hass.data[DOMAIN].routers[config_entry.data[CONF_URL]] - switches: List[Entity] = [] + switches: list[Entity] = [] if router.data.get(KEY_DIALUP_MOBILE_DATASWITCH): switches.append(HuaweiLteMobileDataSwitch(router)) @@ -42,7 +43,7 @@ class HuaweiLteBaseSwitch(HuaweiLteBaseEntity, SwitchEntity): key: str item: str - _raw_state: Optional[str] = attr.ib(init=False, default=None) + _raw_state: str | None = attr.ib(init=False, default=None) def _turn(self, state: bool) -> None: raise NotImplementedError diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index 95e4a1ad7f2..3c9fabf3c9e 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -1,6 +1,8 @@ """Config flow to configure Philips Hue.""" +from __future__ import annotations + import asyncio -from typing import Any, Dict, Optional +from typing import Any from urllib.parse import urlparse import aiohue @@ -44,8 +46,8 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize the Hue flow.""" - self.bridge: Optional[aiohue.Bridge] = None - self.discovered_bridges: Optional[Dict[str, aiohue.Bridge]] = None + self.bridge: aiohue.Bridge | None = None + self.discovered_bridges: dict[str, aiohue.Bridge] | None = None async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" @@ -53,7 +55,7 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_init(user_input) @core.callback - def _async_get_bridge(self, host: str, bridge_id: Optional[str] = None): + def _async_get_bridge(self, host: str, bridge_id: str | None = None): """Return a bridge object.""" if bridge_id is not None: bridge_id = normalize_bridge_id(bridge_id) @@ -114,8 +116,8 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) async def async_step_manual( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle manual bridge setup.""" if user_input is None: return self.async_show_form( @@ -249,8 +251,8 @@ class HueOptionsFlowHandler(config_entries.OptionsFlow): self.config_entry = config_entry async def async_step_init( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Manage Hue options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/humidifier/__init__.py b/homeassistant/components/humidifier/__init__.py index 1763e169d50..01a9fc2ada0 100644 --- a/homeassistant/components/humidifier/__init__.py +++ b/homeassistant/components/humidifier/__init__.py @@ -1,7 +1,9 @@ """Provides functionality to interact with humidifier devices.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Any, Dict, List, Optional +from typing import Any import voluptuous as vol @@ -100,7 +102,7 @@ class HumidifierEntity(ToggleEntity): """Representation of a humidifier device.""" @property - def capability_attributes(self) -> Dict[str, Any]: + def capability_attributes(self) -> dict[str, Any]: """Return capability attributes.""" supported_features = self.supported_features or 0 data = { @@ -114,7 +116,7 @@ class HumidifierEntity(ToggleEntity): return data @property - def state_attributes(self) -> Dict[str, Any]: + def state_attributes(self) -> dict[str, Any]: """Return the optional state attributes.""" supported_features = self.supported_features or 0 data = {} @@ -128,12 +130,12 @@ class HumidifierEntity(ToggleEntity): return data @property - def target_humidity(self) -> Optional[int]: + def target_humidity(self) -> int | None: """Return the humidity we try to reach.""" return None @property - def mode(self) -> Optional[str]: + def mode(self) -> str | None: """Return the current mode, e.g., home, auto, baby. Requires SUPPORT_MODES. @@ -141,7 +143,7 @@ class HumidifierEntity(ToggleEntity): raise NotImplementedError @property - def available_modes(self) -> Optional[List[str]]: + def available_modes(self) -> list[str] | None: """Return a list of available modes. Requires SUPPORT_MODES. diff --git a/homeassistant/components/humidifier/device_action.py b/homeassistant/components/humidifier/device_action.py index c702a7c2a2d..a68b4d771ef 100644 --- a/homeassistant/components/humidifier/device_action.py +++ b/homeassistant/components/humidifier/device_action.py @@ -1,5 +1,5 @@ """Provides device actions for Humidifier.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -40,7 +40,7 @@ ONOFF_SCHEMA = toggle_entity.ACTION_SCHEMA.extend({vol.Required(CONF_DOMAIN): DO ACTION_SCHEMA = vol.Any(SET_HUMIDITY_SCHEMA, SET_MODE_SCHEMA, ONOFF_SCHEMA) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Humidifier devices.""" registry = await entity_registry.async_get_registry(hass) actions = await toggle_entity.async_get_actions(hass, device_id, DOMAIN) @@ -79,7 +79,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/humidifier/device_condition.py b/homeassistant/components/humidifier/device_condition.py index 86a049d838b..137fd6af73d 100644 --- a/homeassistant/components/humidifier/device_condition.py +++ b/homeassistant/components/humidifier/device_condition.py @@ -1,5 +1,5 @@ """Provide the device automations for Humidifier.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -38,7 +38,7 @@ CONDITION_SCHEMA = vol.Any(TOGGLE_CONDITION, MODE_CONDITION) async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions for Humidifier devices.""" registry = await entity_registry.async_get_registry(hass) conditions = await toggle_entity.async_get_conditions(hass, device_id, DOMAIN) diff --git a/homeassistant/components/humidifier/device_trigger.py b/homeassistant/components/humidifier/device_trigger.py index 6bc9682f79a..d0f462f6b0f 100644 --- a/homeassistant/components/humidifier/device_trigger.py +++ b/homeassistant/components/humidifier/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Climate.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -48,7 +48,7 @@ TOGGLE_TRIGGER_SCHEMA = toggle_entity.TRIGGER_SCHEMA.extend( TRIGGER_SCHEMA = vol.Any(TARGET_TRIGGER_SCHEMA, TOGGLE_TRIGGER_SCHEMA) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Humidifier devices.""" registry = await entity_registry.async_get_registry(hass) triggers = await toggle_entity.async_get_triggers(hass, device_id, DOMAIN) diff --git a/homeassistant/components/humidifier/reproduce_state.py b/homeassistant/components/humidifier/reproduce_state.py index f6fff75203e..b20edacec46 100644 --- a/homeassistant/components/humidifier/reproduce_state.py +++ b/homeassistant/components/humidifier/reproduce_state.py @@ -1,7 +1,9 @@ """Module that groups code required to handle state restore for component.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_MODE, @@ -22,8 +24,8 @@ async def _async_reproduce_states( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce component states.""" cur_state = hass.states.get(state.entity_id) @@ -82,8 +84,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce component states.""" await asyncio.gather( diff --git a/homeassistant/components/hyperion/__init__.py b/homeassistant/components/hyperion/__init__.py index e5e6cb4494a..ad6990c6ab4 100644 --- a/homeassistant/components/hyperion/__init__.py +++ b/homeassistant/components/hyperion/__init__.py @@ -1,8 +1,9 @@ """The Hyperion component.""" +from __future__ import annotations import asyncio import logging -from typing import Any, Callable, Dict, List, Optional, Set, Tuple, cast +from typing import Any, Callable, cast from awesomeversion import AwesomeVersion from hyperion import client, const as hyperion_const @@ -70,7 +71,7 @@ def get_hyperion_unique_id(server_id: str, instance: int, name: str) -> str: return f"{server_id}_{instance}_{name}" -def split_hyperion_unique_id(unique_id: str) -> Optional[Tuple[str, int, str]]: +def split_hyperion_unique_id(unique_id: str) -> tuple[str, int, str] | None: """Split a unique_id into a (server_id, instance, type) tuple.""" data = tuple(unique_id.split("_", 2)) if len(data) != 3: @@ -92,7 +93,7 @@ def create_hyperion_client( async def async_create_connect_hyperion_client( *args: Any, **kwargs: Any, -) -> Optional[client.HyperionClient]: +) -> client.HyperionClient | None: """Create and connect a Hyperion Client.""" hyperion_client = create_hyperion_client(*args, **kwargs) @@ -207,17 +208,17 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b CONF_ON_UNLOAD: [], } - async def async_instances_to_clients(response: Dict[str, Any]) -> None: + async def async_instances_to_clients(response: dict[str, Any]) -> None: """Convert instances to Hyperion clients.""" if not response or hyperion_const.KEY_DATA not in response: return await async_instances_to_clients_raw(response[hyperion_const.KEY_DATA]) - async def async_instances_to_clients_raw(instances: List[Dict[str, Any]]) -> None: + async def async_instances_to_clients_raw(instances: list[dict[str, Any]]) -> None: """Convert instances to Hyperion clients.""" registry = await async_get_registry(hass) - running_instances: Set[int] = set() - stopped_instances: Set[int] = set() + running_instances: set[int] = set() + stopped_instances: set[int] = set() existing_instances = hass.data[DOMAIN][config_entry.entry_id][ CONF_INSTANCE_CLIENTS ] diff --git a/homeassistant/components/hyperion/config_flow.py b/homeassistant/components/hyperion/config_flow.py index 8d02028dc38..bea8971cfa6 100644 --- a/homeassistant/components/hyperion/config_flow.py +++ b/homeassistant/components/hyperion/config_flow.py @@ -3,7 +3,7 @@ from __future__ import annotations import asyncio import logging -from typing import Any, Dict, Optional +from typing import Any from urllib.parse import urlparse from hyperion import client, const @@ -111,9 +111,9 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Instantiate config flow.""" - self._data: Dict[str, Any] = {} - self._request_token_task: Optional[asyncio.Task] = None - self._auth_id: Optional[str] = None + self._data: dict[str, Any] = {} + self._request_token_task: asyncio.Task | None = None + self._auth_id: str | None = None self._require_confirm: bool = False self._port_ui: int = const.DEFAULT_PORT_UI @@ -128,7 +128,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): async def _advance_to_auth_step_if_necessary( self, hyperion_client: client.HyperionClient - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Determine if auth is required.""" auth_resp = await hyperion_client.async_is_auth_required() @@ -143,7 +143,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_reauth( self, config_data: ConfigType, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Handle a reauthentication flow.""" self._data = dict(config_data) async with self._create_client(raw_connection=True) as hyperion_client: @@ -152,8 +152,8 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): return await self._advance_to_auth_step_if_necessary(hyperion_client) async def async_step_ssdp( # type: ignore[override] - self, discovery_info: Dict[str, Any] - ) -> Dict[str, Any]: + self, discovery_info: dict[str, Any] + ) -> dict[str, Any]: """Handle a flow initiated by SSDP.""" # Sample data provided by SSDP: { # 'ssdp_location': 'http://192.168.0.1:8090/description.xml', @@ -223,8 +223,8 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, - user_input: Optional[ConfigType] = None, - ) -> Dict[str, Any]: + user_input: ConfigType | None = None, + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" errors = {} if user_input: @@ -262,7 +262,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): async def _request_token_task_func(self, auth_id: str) -> None: """Send an async_request_token request.""" - auth_resp: Optional[Dict[str, Any]] = None + auth_resp: dict[str, Any] | None = None async with self._create_client(raw_connection=True) as hyperion_client: if hyperion_client: # The Hyperion-py client has a default timeout of 3 minutes on this request. @@ -283,7 +283,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): # used to open a URL, that the user already knows the address of). return f"http://{self._data[CONF_HOST]}:{self._port_ui}" - async def _can_login(self) -> Optional[bool]: + async def _can_login(self) -> bool | None: """Verify login details.""" async with self._create_client(raw_connection=True) as hyperion_client: if not hyperion_client: @@ -296,8 +296,8 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_auth( self, - user_input: Optional[ConfigType] = None, - ) -> Dict[str, Any]: + user_input: ConfigType | None = None, + ) -> dict[str, Any]: """Handle the auth step of a flow.""" errors = {} if user_input: @@ -325,8 +325,8 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): ) async def async_step_create_token( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Send a request for a new token.""" if user_input is None: self._auth_id = client.generate_random_auth_id() @@ -351,8 +351,8 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): ) async def async_step_create_token_external( - self, auth_resp: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, auth_resp: ConfigType | None = None + ) -> dict[str, Any]: """Handle completion of the request for a new token.""" if auth_resp is not None and client.ResponseOK(auth_resp): token = auth_resp.get(const.KEY_INFO, {}).get(const.KEY_TOKEN) @@ -364,8 +364,8 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): return self.async_external_step_done(next_step_id="create_token_fail") async def async_step_create_token_success( - self, _: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, _: ConfigType | None = None + ) -> dict[str, Any]: """Create an entry after successful token creation.""" # Clean-up the request task. await self._cancel_request_token_task() @@ -380,16 +380,16 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): return await self.async_step_confirm() async def async_step_create_token_fail( - self, _: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, _: ConfigType | None = None + ) -> dict[str, Any]: """Show an error on the auth form.""" # Clean-up the request task. await self._cancel_request_token_task() return self.async_abort(reason="auth_new_token_not_granted_error") async def async_step_confirm( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Get final confirmation before entry creation.""" if user_input is None and self._require_confirm: return self.async_show_form( @@ -440,8 +440,8 @@ class HyperionOptionsFlow(OptionsFlow): self._config_entry = config_entry async def async_step_init( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index 89f70c8f24d..d322362e959 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -4,7 +4,7 @@ from __future__ import annotations import functools import logging from types import MappingProxyType -from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple +from typing import Any, Callable, Mapping, Sequence from hyperion import client, const @@ -63,7 +63,7 @@ DEFAULT_EFFECT = KEY_EFFECT_SOLID DEFAULT_NAME = "Hyperion" DEFAULT_PORT = const.DEFAULT_PORT_JSON DEFAULT_HDMI_PRIORITY = 880 -DEFAULT_EFFECT_LIST: List[str] = [] +DEFAULT_EFFECT_LIST: list[str] = [] SUPPORT_HYPERION = SUPPORT_COLOR | SUPPORT_BRIGHTNESS | SUPPORT_EFFECT @@ -142,12 +142,12 @@ class HyperionBaseLight(LightEntity): self._rgb_color: Sequence[int] = DEFAULT_COLOR self._effect: str = KEY_EFFECT_SOLID - self._static_effect_list: List[str] = [KEY_EFFECT_SOLID] + self._static_effect_list: list[str] = [KEY_EFFECT_SOLID] if self._support_external_effects: self._static_effect_list += list(const.KEY_COMPONENTID_EXTERNAL_SOURCES) - self._effect_list: List[str] = self._static_effect_list[:] + self._effect_list: list[str] = self._static_effect_list[:] - self._client_callbacks: Mapping[str, Callable[[Dict[str, Any]], None]] = { + self._client_callbacks: Mapping[str, Callable[[dict[str, Any]], None]] = { f"{const.KEY_ADJUSTMENT}-{const.KEY_UPDATE}": self._update_adjustment, f"{const.KEY_COMPONENTS}-{const.KEY_UPDATE}": self._update_components, f"{const.KEY_EFFECTS}-{const.KEY_UPDATE}": self._update_effect_list, @@ -176,7 +176,7 @@ class HyperionBaseLight(LightEntity): return self._brightness @property - def hs_color(self) -> Tuple[float, float]: + def hs_color(self) -> tuple[float, float]: """Return last color value set.""" return color_util.color_RGB_to_hs(*self._rgb_color) @@ -196,7 +196,7 @@ class HyperionBaseLight(LightEntity): return self._effect @property - def effect_list(self) -> List[str]: + def effect_list(self) -> list[str]: """Return the list of supported effects.""" return self._effect_list @@ -305,9 +305,9 @@ class HyperionBaseLight(LightEntity): def _set_internal_state( self, - brightness: Optional[int] = None, - rgb_color: Optional[Sequence[int]] = None, - effect: Optional[str] = None, + brightness: int | None = None, + rgb_color: Sequence[int] | None = None, + effect: str | None = None, ) -> None: """Set the internal state.""" if brightness is not None: @@ -318,12 +318,12 @@ class HyperionBaseLight(LightEntity): self._effect = effect @callback - def _update_components(self, _: Optional[Dict[str, Any]] = None) -> None: + def _update_components(self, _: dict[str, Any] | None = None) -> None: """Update Hyperion components.""" self.async_write_ha_state() @callback - def _update_adjustment(self, _: Optional[Dict[str, Any]] = None) -> None: + def _update_adjustment(self, _: dict[str, Any] | None = None) -> None: """Update Hyperion adjustments.""" if self._client.adjustment: brightness_pct = self._client.adjustment[0].get( @@ -337,7 +337,7 @@ class HyperionBaseLight(LightEntity): self.async_write_ha_state() @callback - def _update_priorities(self, _: Optional[Dict[str, Any]] = None) -> None: + def _update_priorities(self, _: dict[str, Any] | None = None) -> None: """Update Hyperion priorities.""" priority = self._get_priority_entry_that_dictates_state() if priority and self._allow_priority_update(priority): @@ -361,11 +361,11 @@ class HyperionBaseLight(LightEntity): self.async_write_ha_state() @callback - def _update_effect_list(self, _: Optional[Dict[str, Any]] = None) -> None: + def _update_effect_list(self, _: dict[str, Any] | None = None) -> None: """Update Hyperion effects.""" if not self._client.effects: return - effect_list: List[str] = [] + effect_list: list[str] = [] for effect in self._client.effects or []: if const.KEY_NAME in effect: effect_list.append(effect[const.KEY_NAME]) @@ -391,7 +391,7 @@ class HyperionBaseLight(LightEntity): ) @callback - def _update_client(self, _: Optional[Dict[str, Any]] = None) -> None: + def _update_client(self, _: dict[str, Any] | None = None) -> None: """Update client connection state.""" self.async_write_ha_state() @@ -419,18 +419,18 @@ class HyperionBaseLight(LightEntity): """Whether or not to support setting external effects from the light entity.""" return True - def _get_priority_entry_that_dictates_state(self) -> Optional[Dict[str, Any]]: + def _get_priority_entry_that_dictates_state(self) -> dict[str, Any] | None: """Get the relevant Hyperion priority entry to consider.""" # Return the visible priority (whether or not it is the HA priority). # Explicit type specifier to ensure this works when the underlying (typed) # library is installed along with the tests. Casts would trigger a # redundant-cast warning in this case. - priority: Optional[Dict[str, Any]] = self._client.visible_priority + priority: dict[str, Any] | None = self._client.visible_priority return priority # pylint: disable=no-self-use - def _allow_priority_update(self, priority: Optional[Dict[str, Any]] = None) -> bool: + def _allow_priority_update(self, priority: dict[str, Any] | None = None) -> bool: """Determine whether to allow a priority to update internal state.""" return True @@ -525,7 +525,7 @@ class HyperionPriorityLight(HyperionBaseLight): """Whether or not to support setting external effects from the light entity.""" return False - def _get_priority_entry_that_dictates_state(self) -> Optional[Dict[str, Any]]: + def _get_priority_entry_that_dictates_state(self) -> dict[str, Any] | None: """Get the relevant Hyperion priority entry to consider.""" # Return the active priority (if any) at the configured HA priority. for candidate in self._client.priorities or []: @@ -537,12 +537,12 @@ class HyperionPriorityLight(HyperionBaseLight): # Explicit type specifier to ensure this works when the underlying # (typed) library is installed along with the tests. Casts would trigger # a redundant-cast warning in this case. - output: Dict[str, Any] = candidate + output: dict[str, Any] = candidate return output return None @classmethod - def _is_priority_entry_black(cls, priority: Optional[Dict[str, Any]]) -> bool: + def _is_priority_entry_black(cls, priority: dict[str, Any] | None) -> bool: """Determine if a given priority entry is the color black.""" if not priority: return False @@ -552,7 +552,7 @@ class HyperionPriorityLight(HyperionBaseLight): return True return False - def _allow_priority_update(self, priority: Optional[Dict[str, Any]] = None) -> bool: + def _allow_priority_update(self, priority: dict[str, Any] | None = None) -> bool: """Determine whether to allow a Hyperion priority to update entity attributes.""" # Black is treated as 'off' (and Home Assistant does not support selecting black # from the color selector). Do not set our internal attributes if the priority is diff --git a/homeassistant/components/hyperion/switch.py b/homeassistant/components/hyperion/switch.py index f6317a8f396..4a4f8d4da13 100644 --- a/homeassistant/components/hyperion/switch.py +++ b/homeassistant/components/hyperion/switch.py @@ -1,7 +1,8 @@ """Switch platform for Hyperion.""" +from __future__ import annotations import functools -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable from hyperion import client from hyperion.const import ( @@ -187,7 +188,7 @@ class HyperionComponentSwitch(SwitchEntity): await self._async_send_set_component(False) @callback - def _update_components(self, _: Optional[Dict[str, Any]] = None) -> None: + def _update_components(self, _: dict[str, Any] | None = None) -> None: """Update Hyperion components.""" self.async_write_ha_state() From 333f5da03674f679f7da548da1cc412ee399be77 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 09:51:42 +0100 Subject: [PATCH 442/831] Use websocket fixture in deCONZ binary sensor tests (#47820) Localize test data Improve asserts --- tests/components/deconz/test_binary_sensor.py | 196 ++++++++++-------- 1 file changed, 108 insertions(+), 88 deletions(-) diff --git a/tests/components/deconz/test_binary_sensor.py b/tests/components/deconz/test_binary_sensor.py index 39809ecc231..9d4c86ead6c 100644 --- a/tests/components/deconz/test_binary_sensor.py +++ b/tests/components/deconz/test_binary_sensor.py @@ -1,5 +1,6 @@ """deCONZ binary sensor platform tests.""" -from copy import deepcopy + +from unittest.mock import patch from homeassistant.components.binary_sensor import ( DEVICE_CLASS_MOTION, @@ -11,7 +12,6 @@ from homeassistant.components.deconz.const import ( CONF_MASTER_GATEWAY, DOMAIN as DECONZ_DOMAIN, ) -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.deconz.services import SERVICE_DEVICE_REFRESH from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE from homeassistant.helpers import entity_registry as er @@ -23,46 +23,6 @@ from .test_gateway import ( setup_deconz_integration, ) -SENSORS = { - "1": { - "id": "Presence sensor id", - "name": "Presence sensor", - "type": "ZHAPresence", - "state": {"dark": False, "presence": False}, - "config": {"on": True, "reachable": True, "temperature": 10}, - "uniqueid": "00:00:00:00:00:00:00:00-00", - }, - "2": { - "id": "Temperature sensor id", - "name": "Temperature sensor", - "type": "ZHATemperature", - "state": {"temperature": False}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, - "3": { - "id": "CLIP presence sensor id", - "name": "CLIP presence sensor", - "type": "CLIPPresence", - "state": {"presence": False}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:02-00", - }, - "4": { - "id": "Vibration sensor id", - "name": "Vibration sensor", - "type": "ZHAVibration", - "state": { - "orientation": [1, 2, 3], - "tiltangle": 36, - "vibration": True, - "vibrationstrength": 10, - }, - "config": {"on": True, "reachable": True, "temperature": 10}, - "uniqueid": "00:00:00:00:00:00:00:03-00", - }, -} - async def test_no_binary_sensors(hass, aioclient_mock): """Test that no sensors in deconz results in no sensor entities.""" @@ -70,14 +30,47 @@ async def test_no_binary_sensors(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_binary_sensors(hass, aioclient_mock): +async def test_binary_sensors(hass, aioclient_mock, mock_deconz_websocket): """Test successful creation of binary sensor entities.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "sensors": { + "1": { + "name": "Presence sensor", + "type": "ZHAPresence", + "state": {"dark": False, "presence": False}, + "config": {"on": True, "reachable": True, "temperature": 10}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + "2": { + "name": "Temperature sensor", + "type": "ZHATemperature", + "state": {"temperature": False}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + "3": { + "name": "CLIP presence sensor", + "type": "CLIPPresence", + "state": {"presence": False}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:02-00", + }, + "4": { + "name": "Vibration sensor", + "type": "ZHAVibration", + "state": { + "orientation": [1, 2, 3], + "tiltangle": 36, + "vibration": True, + "vibrationstrength": 10, + }, + "config": {"on": True, "reachable": True, "temperature": 10}, + "uniqueid": "00:00:00:00:00:00:00:03-00", + }, + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 3 presence_sensor = hass.states.get("binary_sensor.presence_sensor") @@ -89,14 +82,14 @@ async def test_binary_sensors(hass, aioclient_mock): assert vibration_sensor.state == STATE_ON assert vibration_sensor.attributes["device_class"] == DEVICE_CLASS_VIBRATION - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "1", "state": {"presence": True}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("binary_sensor.presence_sensor").state == STATE_ON @@ -107,25 +100,39 @@ async def test_binary_sensors(hass, aioclient_mock): await hass.config_entries.async_remove(config_entry.entry_id) await hass.async_block_till_done() + assert len(hass.states.async_all()) == 0 async def test_allow_clip_sensor(hass, aioclient_mock): """Test that CLIP sensors can be allowed.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, - aioclient_mock, - options={CONF_ALLOW_CLIP_SENSOR: True}, - get_state_response=data, - ) + data = { + "sensors": { + "1": { + "name": "Presence sensor", + "type": "ZHAPresence", + "state": {"presence": False}, + "config": {"on": True, "reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + "2": { + "name": "CLIP presence sensor", + "type": "CLIPPresence", + "state": {"presence": False}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:02-00", + }, + } + } - assert len(hass.states.async_all()) == 4 + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration( + hass, aioclient_mock, options={CONF_ALLOW_CLIP_SENSOR: True} + ) + + assert len(hass.states.async_all()) == 2 assert hass.states.get("binary_sensor.presence_sensor").state == STATE_OFF - assert hass.states.get("binary_sensor.temperature_sensor") is None assert hass.states.get("binary_sensor.clip_presence_sensor").state == STATE_OFF - assert hass.states.get("binary_sensor.vibration_sensor").state == STATE_ON # Disallow clip sensors @@ -134,8 +141,8 @@ async def test_allow_clip_sensor(hass, aioclient_mock): ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 3 - assert hass.states.get("binary_sensor.clip_presence_sensor") is None + assert len(hass.states.async_all()) == 1 + assert not hass.states.get("binary_sensor.clip_presence_sensor") # Allow clip sensors @@ -144,48 +151,65 @@ async def test_allow_clip_sensor(hass, aioclient_mock): ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 4 + assert len(hass.states.async_all()) == 2 assert hass.states.get("binary_sensor.clip_presence_sensor").state == STATE_OFF -async def test_add_new_binary_sensor(hass, aioclient_mock): +async def test_add_new_binary_sensor(hass, aioclient_mock, mock_deconz_websocket): """Test that adding a new binary sensor works.""" - config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway = get_gateway_from_config_entry(hass, config_entry) - assert len(hass.states.async_all()) == 0 - - state_added_event = { + event_added_sensor = { "t": "event", "e": "added", "r": "sensors", "id": "1", - "sensor": deepcopy(SENSORS["1"]), + "sensor": { + "id": "Presence sensor id", + "name": "Presence sensor", + "type": "ZHAPresence", + "state": {"presence": False}, + "config": {"on": True, "reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, } - gateway.api.event_handler(state_added_event) + + await setup_deconz_integration(hass, aioclient_mock) + assert len(hass.states.async_all()) == 0 + + await mock_deconz_websocket(data=event_added_sensor) await hass.async_block_till_done() assert len(hass.states.async_all()) == 1 assert hass.states.get("binary_sensor.presence_sensor").state == STATE_OFF -async def test_add_new_binary_sensor_ignored(hass, aioclient_mock): +async def test_add_new_binary_sensor_ignored( + hass, aioclient_mock, mock_deconz_websocket +): """Test that adding a new binary sensor is not allowed.""" + sensor = { + "name": "Presence sensor", + "type": "ZHAPresence", + "state": {"presence": False}, + "config": {"on": True, "reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + } + event_added_sensor = { + "t": "event", + "e": "added", + "r": "sensors", + "id": "1", + "sensor": sensor, + } + config_entry = await setup_deconz_integration( hass, aioclient_mock, options={CONF_MASTER_GATEWAY: True, CONF_ALLOW_NEW_DEVICES: False}, ) - gateway = get_gateway_from_config_entry(hass, config_entry) + assert len(hass.states.async_all()) == 0 - state_added_event = { - "t": "event", - "e": "added", - "r": "sensors", - "id": "1", - "sensor": deepcopy(SENSORS["1"]), - } - gateway.api.event_handler(state_added_event) + await mock_deconz_websocket(data=event_added_sensor) await hass.async_block_till_done() assert len(hass.states.async_all()) == 0 @@ -197,11 +221,7 @@ async def test_add_new_binary_sensor_ignored(hass, aioclient_mock): ) aioclient_mock.clear_requests() - data = { - "groups": {}, - "lights": {}, - "sensors": {"1": deepcopy(SENSORS["1"])}, - } + data = {"groups": {}, "lights": {}, "sensors": {"1": sensor}} mock_deconz_request(aioclient_mock, config_entry.data, data) await hass.services.async_call(DECONZ_DOMAIN, SERVICE_DEVICE_REFRESH) From fea0e39fa0d8c5650eadb2de765e47e163650838 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 17 Mar 2021 22:55:38 -1000 Subject: [PATCH 443/831] Reduce rest setup code (#48062) - Switch to storing each platform config/rest data in a list --- homeassistant/components/rest/__init__.py | 24 ++++++++--------------- homeassistant/components/rest/const.py | 2 ++ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/rest/__init__.py b/homeassistant/components/rest/__init__.py index 26e8fde57e0..8b9390bb1c9 100644 --- a/homeassistant/components/rest/__init__.py +++ b/homeassistant/components/rest/__init__.py @@ -33,7 +33,7 @@ from homeassistant.helpers.entity_component import ( from homeassistant.helpers.reload import async_reload_integration_platforms from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import COORDINATOR, DOMAIN, PLATFORM_IDX, REST, REST_IDX +from .const import COORDINATOR, DOMAIN, PLATFORM_IDX, REST, REST_DATA, REST_IDX from .data import RestData from .schema import CONFIG_SCHEMA # noqa: F401 @@ -67,7 +67,7 @@ async def async_setup(hass: HomeAssistant, config: dict): @callback def _async_setup_shared_data(hass: HomeAssistant): """Create shared data for platform config and rest coordinators.""" - hass.data[DOMAIN] = {platform: {} for platform in COORDINATOR_AWARE_PLATFORMS} + hass.data[DOMAIN] = {key: [] for key in [REST_DATA, *COORDINATOR_AWARE_PLATFORMS]} async def _async_process_config(hass, config) -> bool: @@ -77,29 +77,21 @@ async def _async_process_config(hass, config) -> bool: refresh_tasks = [] load_tasks = [] - platform_idxs = {} for rest_idx, conf in enumerate(config[DOMAIN]): scan_interval = conf.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) resource_template = conf.get(CONF_RESOURCE_TEMPLATE) rest = create_rest_data_from_config(hass, conf) - coordinator = _wrap_rest_in_coordinator( - hass, rest, resource_template, scan_interval - ) + coordinator = _rest_coordinator(hass, rest, resource_template, scan_interval) refresh_tasks.append(coordinator.async_refresh()) - hass.data[DOMAIN][rest_idx] = {REST: rest, COORDINATOR: coordinator} + hass.data[DOMAIN][REST_DATA].append({REST: rest, COORDINATOR: coordinator}) for platform_domain in COORDINATOR_AWARE_PLATFORMS: if platform_domain not in conf: continue for platform_conf in conf[platform_domain]: - if platform_domain not in platform_idxs: - platform_idxs[platform_domain] = 0 - else: - platform_idxs[platform_domain] += 1 - platform_idx = platform_idxs[platform_domain] - - hass.data[DOMAIN][platform_domain][platform_idx] = platform_conf + hass.data[DOMAIN][platform_domain].append(platform_conf) + platform_idx = len(hass.data[DOMAIN][platform_domain]) - 1 load = discovery.async_load_platform( hass, @@ -121,7 +113,7 @@ async def _async_process_config(hass, config) -> bool: async def async_get_config_and_coordinator(hass, platform_domain, discovery_info): """Get the config and coordinator for the platform from discovery.""" - shared_data = hass.data[DOMAIN][discovery_info[REST_IDX]] + shared_data = hass.data[DOMAIN][REST_DATA][discovery_info[REST_IDX]] conf = hass.data[DOMAIN][platform_domain][discovery_info[PLATFORM_IDX]] coordinator = shared_data[COORDINATOR] rest = shared_data[REST] @@ -130,7 +122,7 @@ async def async_get_config_and_coordinator(hass, platform_domain, discovery_info return conf, coordinator, rest -def _wrap_rest_in_coordinator(hass, rest, resource_template, update_interval): +def _rest_coordinator(hass, rest, resource_template, update_interval): """Wrap a DataUpdateCoordinator around the rest object.""" if resource_template: diff --git a/homeassistant/components/rest/const.py b/homeassistant/components/rest/const.py index 31216b65968..5fd32d8fba7 100644 --- a/homeassistant/components/rest/const.py +++ b/homeassistant/components/rest/const.py @@ -17,4 +17,6 @@ PLATFORM_IDX = "platform_idx" COORDINATOR = "coordinator" REST = "rest" +REST_DATA = "rest_data" + METHODS = ["POST", "GET"] From 2ab640aaefdfccc1ecafcc28f18aa71236bc93c8 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 09:57:27 +0100 Subject: [PATCH 444/831] Use websocket fixture in deCONZ climate tests (#47821) Localize test data Improve asserts --- tests/components/deconz/test_climate.py | 486 +++++++++++++----------- 1 file changed, 261 insertions(+), 225 deletions(-) diff --git a/tests/components/deconz/test_climate.py b/tests/components/deconz/test_climate.py index 5577a2d0414..92ca38fdf4d 100644 --- a/tests/components/deconz/test_climate.py +++ b/tests/components/deconz/test_climate.py @@ -1,6 +1,6 @@ """deCONZ climate platform tests.""" -from copy import deepcopy +from unittest.mock import patch import pytest @@ -27,14 +27,18 @@ from homeassistant.components.climate.const import ( HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF, + PRESET_BOOST, PRESET_COMFORT, + PRESET_ECO, ) from homeassistant.components.deconz.climate import ( DECONZ_FAN_SMART, + DECONZ_PRESET_AUTO, + DECONZ_PRESET_COMPLEX, + DECONZ_PRESET_HOLIDAY, DECONZ_PRESET_MANUAL, ) from homeassistant.components.deconz.const import CONF_ALLOW_CLIP_SENSOR -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_TEMPERATURE, @@ -48,31 +52,6 @@ from .test_gateway import ( setup_deconz_integration, ) -SENSORS = { - "1": { - "id": "Thermostat id", - "name": "Thermostat", - "type": "ZHAThermostat", - "state": {"on": True, "temperature": 2260, "valve": 30}, - "config": { - "battery": 100, - "heatsetpoint": 2200, - "mode": "auto", - "offset": 10, - "reachable": True, - }, - "uniqueid": "00:00:00:00:00:00:00:00-00", - }, - "2": { - "id": "CLIP thermostat id", - "name": "CLIP thermostat", - "type": "CLIPThermostat", - "state": {"on": True, "temperature": 2260, "valve": 30}, - "config": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:02-00", - }, -} - async def test_no_sensors(hass, aioclient_mock): """Test that no sensors in deconz results in no climate entities.""" @@ -80,48 +59,47 @@ async def test_no_sensors(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_simple_climate_device(hass, aioclient_mock): +async def test_simple_climate_device(hass, aioclient_mock, mock_deconz_websocket): """Test successful creation of climate entities. This is a simple water heater that only supports setting temperature and on and off. """ - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = { - "0": { - "config": { - "battery": 59, - "displayflipped": None, - "heatsetpoint": 2100, - "locked": True, - "mountingmode": None, - "offset": 0, - "on": True, - "reachable": True, - }, - "ep": 1, - "etag": "6130553ac247174809bae47144ee23f8", - "lastseen": "2020-11-29T19:31Z", - "manufacturername": "Danfoss", - "modelid": "eTRV0100", - "name": "thermostat", - "state": { - "errorcode": None, - "lastupdated": "2020-11-29T19:28:40.665", - "mountingmodeactive": False, - "on": True, - "temperature": 2102, - "valve": 24, - "windowopen": "Closed", - }, - "swversion": "01.02.0008 01.02", - "type": "ZHAThermostat", - "uniqueid": "14:b4:57:ff:fe:d5:4e:77-01-0201", + data = { + "sensors": { + "0": { + "config": { + "battery": 59, + "displayflipped": None, + "heatsetpoint": 2100, + "locked": True, + "mountingmode": None, + "offset": 0, + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "6130553ac247174809bae47144ee23f8", + "lastseen": "2020-11-29T19:31Z", + "manufacturername": "Danfoss", + "modelid": "eTRV0100", + "name": "thermostat", + "state": { + "errorcode": None, + "lastupdated": "2020-11-29T19:28:40.665", + "mountingmodeactive": False, + "on": True, + "temperature": 2102, + "valve": 24, + "windowopen": "Closed", + }, + "swversion": "01.02.0008 01.02", + "type": "ZHAThermostat", + "uniqueid": "14:b4:57:ff:fe:d5:4e:77-01-0201", + } } } - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 2 climate_thermostat = hass.states.get("climate.thermostat") @@ -137,28 +115,28 @@ async def test_simple_climate_device(hass, aioclient_mock): # Event signals thermostat configured off - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "0", "state": {"on": False}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.thermostat").state == STATE_OFF # Event signals thermostat state on - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "0", "state": {"on": True}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.thermostat").state == HVAC_MODE_HEAT @@ -198,14 +176,29 @@ async def test_simple_climate_device(hass, aioclient_mock): ) -async def test_climate_device_without_cooling_support(hass, aioclient_mock): +async def test_climate_device_without_cooling_support( + hass, aioclient_mock, mock_deconz_websocket +): """Test successful creation of sensor entities.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "sensors": { + "1": { + "name": "Thermostat", + "type": "ZHAThermostat", + "state": {"on": True, "temperature": 2260, "valve": 30}, + "config": { + "battery": 100, + "heatsetpoint": 2200, + "mode": "auto", + "offset": 10, + "reachable": True, + }, + "uniqueid": "00:00:00:00:00:00:00:00-00", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 2 climate_thermostat = hass.states.get("climate.thermostat") @@ -224,21 +217,21 @@ async def test_climate_device_without_cooling_support(hass, aioclient_mock): # Event signals thermostat configured off - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "1", "config": {"mode": "off"}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.thermostat").state == STATE_OFF # Event signals thermostat state on - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", @@ -246,21 +239,21 @@ async def test_climate_device_without_cooling_support(hass, aioclient_mock): "config": {"mode": "other"}, "state": {"on": True}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.thermostat").state == HVAC_MODE_HEAT # Event signals thermostat state off - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "1", "state": {"on": False}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.thermostat").state == STATE_OFF @@ -336,7 +329,7 @@ async def test_climate_device_without_cooling_support(hass, aioclient_mock): await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(hass.states.async_all()) == 2 + assert len(states) == 2 for state in states: assert state.state == STATE_UNAVAILABLE @@ -345,40 +338,41 @@ async def test_climate_device_without_cooling_support(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_climate_device_with_cooling_support(hass, aioclient_mock): +async def test_climate_device_with_cooling_support( + hass, aioclient_mock, mock_deconz_websocket +): """Test successful creation of sensor entities.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = { - "0": { - "config": { - "battery": 25, - "coolsetpoint": None, - "fanmode": None, - "heatsetpoint": 2222, - "mode": "heat", - "offset": 0, - "on": True, - "reachable": True, - }, - "ep": 1, - "etag": "074549903686a77a12ef0f06c499b1ef", - "lastseen": "2020-11-27T13:45Z", - "manufacturername": "Zen Within", - "modelid": "Zen-01", - "name": "Zen-01", - "state": { - "lastupdated": "2020-11-27T13:42:40.863", - "on": False, - "temperature": 2320, - }, - "type": "ZHAThermostat", - "uniqueid": "00:24:46:00:00:11:6f:56-01-0201", + data = { + "sensors": { + "0": { + "config": { + "battery": 25, + "coolsetpoint": None, + "fanmode": None, + "heatsetpoint": 2222, + "mode": "heat", + "offset": 0, + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "074549903686a77a12ef0f06c499b1ef", + "lastseen": "2020-11-27T13:45Z", + "manufacturername": "Zen Within", + "modelid": "Zen-01", + "name": "Zen-01", + "state": { + "lastupdated": "2020-11-27T13:42:40.863", + "on": False, + "temperature": 2320, + }, + "type": "ZHAThermostat", + "uniqueid": "00:24:46:00:00:11:6f:56-01-0201", + } } } - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 2 climate_thermostat = hass.states.get("climate.zen_01") @@ -395,14 +389,14 @@ async def test_climate_device_with_cooling_support(hass, aioclient_mock): # Event signals thermostat state cool - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "0", "config": {"mode": "cool"}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.zen_01").state == HVAC_MODE_COOL @@ -422,40 +416,41 @@ async def test_climate_device_with_cooling_support(hass, aioclient_mock): assert aioclient_mock.mock_calls[1][2] == {"coolsetpoint": 2000.0} -async def test_climate_device_with_fan_support(hass, aioclient_mock): +async def test_climate_device_with_fan_support( + hass, aioclient_mock, mock_deconz_websocket +): """Test successful creation of sensor entities.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = { - "0": { - "config": { - "battery": 25, - "coolsetpoint": None, - "fanmode": "auto", - "heatsetpoint": 2222, - "mode": "heat", - "offset": 0, - "on": True, - "reachable": True, - }, - "ep": 1, - "etag": "074549903686a77a12ef0f06c499b1ef", - "lastseen": "2020-11-27T13:45Z", - "manufacturername": "Zen Within", - "modelid": "Zen-01", - "name": "Zen-01", - "state": { - "lastupdated": "2020-11-27T13:42:40.863", - "on": False, - "temperature": 2320, - }, - "type": "ZHAThermostat", - "uniqueid": "00:24:46:00:00:11:6f:56-01-0201", + data = { + "sensors": { + "0": { + "config": { + "battery": 25, + "coolsetpoint": None, + "fanmode": "auto", + "heatsetpoint": 2222, + "mode": "heat", + "offset": 0, + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "074549903686a77a12ef0f06c499b1ef", + "lastseen": "2020-11-27T13:45Z", + "manufacturername": "Zen Within", + "modelid": "Zen-01", + "name": "Zen-01", + "state": { + "lastupdated": "2020-11-27T13:42:40.863", + "on": False, + "temperature": 2320, + }, + "type": "ZHAThermostat", + "uniqueid": "00:24:46:00:00:11:6f:56-01-0201", + } } } - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 2 climate_thermostat = hass.states.get("climate.zen_01") @@ -473,21 +468,21 @@ async def test_climate_device_with_fan_support(hass, aioclient_mock): # Event signals fan mode defaults to off - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "0", "config": {"fanmode": "unsupported"}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.zen_01").attributes["fan_mode"] == FAN_OFF # Event signals unsupported fan mode - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", @@ -495,21 +490,21 @@ async def test_climate_device_with_fan_support(hass, aioclient_mock): "config": {"fanmode": "unsupported"}, "state": {"on": True}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.zen_01").attributes["fan_mode"] == FAN_ON # Event signals unsupported fan mode - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "0", "config": {"fanmode": "unsupported"}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.zen_01").attributes["fan_mode"] == FAN_ON @@ -549,41 +544,40 @@ async def test_climate_device_with_fan_support(hass, aioclient_mock): ) -async def test_climate_device_with_preset(hass, aioclient_mock): +async def test_climate_device_with_preset(hass, aioclient_mock, mock_deconz_websocket): """Test successful creation of sensor entities.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = { - "0": { - "config": { - "battery": 25, - "coolsetpoint": None, - "fanmode": None, - "heatsetpoint": 2222, - "mode": "heat", - "preset": "auto", - "offset": 0, - "on": True, - "reachable": True, - }, - "ep": 1, - "etag": "074549903686a77a12ef0f06c499b1ef", - "lastseen": "2020-11-27T13:45Z", - "manufacturername": "Zen Within", - "modelid": "Zen-01", - "name": "Zen-01", - "state": { - "lastupdated": "2020-11-27T13:42:40.863", - "on": False, - "temperature": 2320, - }, - "type": "ZHAThermostat", - "uniqueid": "00:24:46:00:00:11:6f:56-01-0201", + data = { + "sensors": { + "0": { + "config": { + "battery": 25, + "coolsetpoint": None, + "fanmode": None, + "heatsetpoint": 2222, + "mode": "heat", + "preset": "auto", + "offset": 0, + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "074549903686a77a12ef0f06c499b1ef", + "lastseen": "2020-11-27T13:45Z", + "manufacturername": "Zen Within", + "modelid": "Zen-01", + "name": "Zen-01", + "state": { + "lastupdated": "2020-11-27T13:42:40.863", + "on": False, + "temperature": 2320, + }, + "type": "ZHAThermostat", + "uniqueid": "00:24:46:00:00:11:6f:56-01-0201", + } } } - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 2 @@ -591,27 +585,27 @@ async def test_climate_device_with_preset(hass, aioclient_mock): assert climate_zen_01.state == HVAC_MODE_HEAT assert climate_zen_01.attributes["current_temperature"] == 23.2 assert climate_zen_01.attributes["temperature"] == 22.2 - assert climate_zen_01.attributes["preset_mode"] == "auto" + assert climate_zen_01.attributes["preset_mode"] == DECONZ_PRESET_AUTO assert climate_zen_01.attributes["preset_modes"] == [ - "auto", - "boost", - "comfort", - "complex", - "eco", - "holiday", - "manual", + DECONZ_PRESET_AUTO, + PRESET_BOOST, + PRESET_COMFORT, + DECONZ_PRESET_COMPLEX, + PRESET_ECO, + DECONZ_PRESET_HOLIDAY, + DECONZ_PRESET_MANUAL, ] # Event signals deCONZ preset - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "0", "config": {"preset": "manual"}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert ( @@ -621,14 +615,14 @@ async def test_climate_device_with_preset(hass, aioclient_mock): # Event signals unknown preset - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "0", "config": {"preset": "unsupported"}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.zen_01").attributes["preset_mode"] is None @@ -670,19 +664,36 @@ async def test_climate_device_with_preset(hass, aioclient_mock): async def test_clip_climate_device(hass, aioclient_mock): """Test successful creation of sensor entities.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, - aioclient_mock, - options={CONF_ALLOW_CLIP_SENSOR: True}, - get_state_response=data, - ) + data = { + "sensors": { + "1": { + "name": "Thermostat", + "type": "ZHAThermostat", + "state": {"on": True, "temperature": 2260, "valve": 30}, + "config": { + "battery": 100, + "heatsetpoint": 2200, + "mode": "auto", + "offset": 10, + "reachable": True, + }, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + "2": { + "name": "CLIP thermostat", + "type": "CLIPThermostat", + "state": {"on": True, "temperature": 2260, "valve": 30}, + "config": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:02-00", + }, + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration( + hass, aioclient_mock, options={CONF_ALLOW_CLIP_SENSOR: True} + ) assert len(hass.states.async_all()) == 3 - assert hass.states.get("climate.thermostat").state == HVAC_MODE_AUTO - assert hass.states.get("sensor.thermostat") is None - assert hass.states.get("sensor.thermostat_battery_level").state == "100" assert hass.states.get("climate.clip_thermostat").state == HVAC_MODE_HEAT # Disallow clip sensors @@ -693,7 +704,7 @@ async def test_clip_climate_device(hass, aioclient_mock): await hass.async_block_till_done() assert len(hass.states.async_all()) == 2 - assert hass.states.get("climate.clip_thermostat") is None + assert not hass.states.get("climate.clip_thermostat") # Allow clip sensors @@ -706,45 +717,70 @@ async def test_clip_climate_device(hass, aioclient_mock): assert hass.states.get("climate.clip_thermostat").state == HVAC_MODE_HEAT -async def test_verify_state_update(hass, aioclient_mock): +async def test_verify_state_update(hass, aioclient_mock, mock_deconz_websocket): """Test that state update properly.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "sensors": { + "1": { + "name": "Thermostat", + "type": "ZHAThermostat", + "state": {"on": True, "temperature": 2260, "valve": 30}, + "config": { + "battery": 100, + "heatsetpoint": 2200, + "mode": "auto", + "offset": 10, + "reachable": True, + }, + "uniqueid": "00:00:00:00:00:00:00:00-00", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert hass.states.get("climate.thermostat").state == HVAC_MODE_AUTO - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "1", "state": {"on": False}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.thermostat").state == HVAC_MODE_AUTO - assert gateway.api.sensors["1"].changed_keys == {"state", "r", "t", "on", "e", "id"} -async def test_add_new_climate_device(hass, aioclient_mock): +async def test_add_new_climate_device(hass, aioclient_mock, mock_deconz_websocket): """Test that adding a new climate device works.""" - config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway = get_gateway_from_config_entry(hass, config_entry) - assert len(hass.states.async_all()) == 0 - - state_added_event = { + event_added_sensor = { "t": "event", "e": "added", "r": "sensors", "id": "1", - "sensor": deepcopy(SENSORS["1"]), + "sensor": { + "id": "Thermostat id", + "name": "Thermostat", + "type": "ZHAThermostat", + "state": {"on": True, "temperature": 2260, "valve": 30}, + "config": { + "battery": 100, + "heatsetpoint": 2200, + "mode": "auto", + "offset": 10, + "reachable": True, + }, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, } - gateway.api.event_handler(state_added_event) + + await setup_deconz_integration(hass, aioclient_mock) + assert len(hass.states.async_all()) == 0 + + await mock_deconz_websocket(data=event_added_sensor) await hass.async_block_till_done() assert len(hass.states.async_all()) == 2 From 283b4abe6719da72d99c72b45700a2b22c065ab1 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 10:02:00 +0100 Subject: [PATCH 445/831] Update typing 09 (#48059) --- .../components/iaqualink/__init__.py | 6 +++-- homeassistant/components/iaqualink/climate.py | 7 ++--- .../components/iaqualink/config_flow.py | 6 ++--- homeassistant/components/iaqualink/sensor.py | 8 +++--- homeassistant/components/icloud/account.py | 13 +++++----- .../components/icloud/device_tracker.py | 6 ++--- homeassistant/components/icloud/sensor.py | 6 ++--- .../components/ign_sismologia/geo_location.py | 11 ++++---- homeassistant/components/image/__init__.py | 9 ++++--- .../components/incomfort/__init__.py | 7 ++--- .../components/incomfort/binary_sensor.py | 6 +++-- homeassistant/components/incomfort/climate.py | 12 +++++---- homeassistant/components/incomfort/sensor.py | 12 +++++---- .../components/incomfort/water_heater.py | 6 +++-- homeassistant/components/influxdb/__init__.py | 16 +++++++----- homeassistant/components/influxdb/sensor.py | 5 ++-- .../components/input_boolean/__init__.py | 13 +++++----- .../input_boolean/reproduce_state.py | 12 +++++---- .../components/input_datetime/__init__.py | 15 +++++------ .../input_datetime/reproduce_state.py | 12 +++++---- .../components/input_number/__init__.py | 15 +++++------ .../input_number/reproduce_state.py | 12 +++++---- .../components/input_select/__init__.py | 17 ++++++------ .../input_select/reproduce_state.py | 12 +++++---- .../components/input_text/__init__.py | 15 +++++------ .../components/input_text/reproduce_state.py | 12 +++++---- homeassistant/components/insteon/climate.py | 22 ++++++++-------- homeassistant/components/insteon/schemas.py | 5 ++-- homeassistant/components/ipp/__init__.py | 8 +++--- homeassistant/components/ipp/config_flow.py | 16 +++++++----- homeassistant/components/ipp/sensor.py | 16 +++++++----- homeassistant/components/isy994/__init__.py | 5 ++-- .../components/isy994/binary_sensor.py | 6 +++-- homeassistant/components/isy994/climate.py | 22 +++++++++------- homeassistant/components/isy994/fan.py | 8 +++--- homeassistant/components/isy994/helpers.py | 26 ++++++++++--------- homeassistant/components/isy994/light.py | 6 +++-- homeassistant/components/isy994/sensor.py | 8 +++--- homeassistant/components/izone/climate.py | 16 ++++++------ 39 files changed, 239 insertions(+), 196 deletions(-) diff --git a/homeassistant/components/iaqualink/__init__.py b/homeassistant/components/iaqualink/__init__.py index d0667aab72a..0435645d87c 100644 --- a/homeassistant/components/iaqualink/__init__.py +++ b/homeassistant/components/iaqualink/__init__.py @@ -1,8 +1,10 @@ """Component to embed Aqualink devices.""" +from __future__ import annotations + import asyncio from functools import wraps import logging -from typing import Any, Dict +from typing import Any import aiohttp.client_exceptions from iaqualink import ( @@ -234,7 +236,7 @@ class AqualinkEntity(Entity): return self.dev.system.online @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return the device info.""" return { "identifiers": {(DOMAIN, self.unique_id)}, diff --git a/homeassistant/components/iaqualink/climate.py b/homeassistant/components/iaqualink/climate.py index 2c26b2bc363..73988c4e523 100644 --- a/homeassistant/components/iaqualink/climate.py +++ b/homeassistant/components/iaqualink/climate.py @@ -1,6 +1,7 @@ """Support for Aqualink Thermostats.""" +from __future__ import annotations + import logging -from typing import List, Optional from iaqualink import AqualinkHeater, AqualinkPump, AqualinkSensor, AqualinkState from iaqualink.const import ( @@ -53,7 +54,7 @@ class HassAqualinkThermostat(AqualinkEntity, ClimateEntity): return SUPPORT_TARGET_TEMPERATURE @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of supported HVAC modes.""" return CLIMATE_SUPPORTED_MODES @@ -119,7 +120,7 @@ class HassAqualinkThermostat(AqualinkEntity, ClimateEntity): return self.dev.system.devices[sensor] @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" if self.sensor.state != "": return float(self.sensor.state) diff --git a/homeassistant/components/iaqualink/config_flow.py b/homeassistant/components/iaqualink/config_flow.py index c083aee7c1c..df8880a0e8f 100644 --- a/homeassistant/components/iaqualink/config_flow.py +++ b/homeassistant/components/iaqualink/config_flow.py @@ -1,5 +1,5 @@ """Config flow to configure zone component.""" -from typing import Optional +from __future__ import annotations from iaqualink import AqualinkClient, AqualinkLoginException import voluptuous as vol @@ -18,7 +18,7 @@ class AqualinkFlowHandler(config_entries.ConfigFlow): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL - async def async_step_user(self, user_input: Optional[ConfigType] = None): + async def async_step_user(self, user_input: ConfigType | None = None): """Handle a flow start.""" # Supporting a single account. entries = self.hass.config_entries.async_entries(DOMAIN) @@ -46,6 +46,6 @@ class AqualinkFlowHandler(config_entries.ConfigFlow): errors=errors, ) - async def async_step_import(self, user_input: Optional[ConfigType] = None): + async def async_step_import(self, user_input: ConfigType | None = None): """Occurs when an entry is setup through config.""" return await self.async_step_user(user_input) diff --git a/homeassistant/components/iaqualink/sensor.py b/homeassistant/components/iaqualink/sensor.py index 50f2a832211..565cfcca667 100644 --- a/homeassistant/components/iaqualink/sensor.py +++ b/homeassistant/components/iaqualink/sensor.py @@ -1,5 +1,5 @@ """Support for Aqualink temperature sensors.""" -from typing import Optional +from __future__ import annotations from homeassistant.components.sensor import DOMAIN from homeassistant.config_entries import ConfigEntry @@ -31,7 +31,7 @@ class HassAqualinkSensor(AqualinkEntity): return self.dev.label @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the measurement unit for the sensor.""" if self.dev.name.endswith("_temp"): if self.dev.system.temp_unit == "F": @@ -40,7 +40,7 @@ class HassAqualinkSensor(AqualinkEntity): return None @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the sensor.""" if self.dev.state == "": return None @@ -52,7 +52,7 @@ class HassAqualinkSensor(AqualinkEntity): return state @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of the sensor.""" if self.dev.name.endswith("_temp"): return DEVICE_CLASS_TEMPERATURE diff --git a/homeassistant/components/icloud/account.py b/homeassistant/components/icloud/account.py index 4221cf635ba..5c7f448668d 100644 --- a/homeassistant/components/icloud/account.py +++ b/homeassistant/components/icloud/account.py @@ -1,8 +1,9 @@ """iCloud account.""" +from __future__ import annotations + from datetime import timedelta import logging import operator -from typing import Dict, Optional from pyicloud import PyiCloudService from pyicloud.exceptions import ( @@ -95,7 +96,7 @@ class IcloudAccount: self._icloud_dir = icloud_dir - self.api: Optional[PyiCloudService] = None + self.api: PyiCloudService | None = None self._owner_fullname = None self._family_members_fullname = {} self._devices = {} @@ -345,7 +346,7 @@ class IcloudAccount: return self._owner_fullname @property - def family_members_fullname(self) -> Dict[str, str]: + def family_members_fullname(self) -> dict[str, str]: """Return the account family members fullname.""" return self._family_members_fullname @@ -355,7 +356,7 @@ class IcloudAccount: return self._fetch_interval @property - def devices(self) -> Dict[str, any]: + def devices(self) -> dict[str, any]: """Return the account devices.""" return self._devices @@ -496,11 +497,11 @@ class IcloudDevice: return self._battery_status @property - def location(self) -> Dict[str, any]: + def location(self) -> dict[str, any]: """Return the Apple device location.""" return self._location @property - def state_attributes(self) -> Dict[str, any]: + def state_attributes(self) -> dict[str, any]: """Return the attributes.""" return self._attrs diff --git a/homeassistant/components/icloud/device_tracker.py b/homeassistant/components/icloud/device_tracker.py index 77fcf0a3039..3dbc10bcf1b 100644 --- a/homeassistant/components/icloud/device_tracker.py +++ b/homeassistant/components/icloud/device_tracker.py @@ -1,5 +1,5 @@ """Support for tracking for iCloud devices.""" -from typing import Dict +from __future__ import annotations from homeassistant.components.device_tracker import SOURCE_TYPE_GPS from homeassistant.components.device_tracker.config_entry import TrackerEntity @@ -108,12 +108,12 @@ class IcloudTrackerEntity(TrackerEntity): return icon_for_icloud_device(self._device) @property - def extra_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> dict[str, any]: """Return the device state attributes.""" return self._device.state_attributes @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "identifiers": {(DOMAIN, self._device.unique_id)}, diff --git a/homeassistant/components/icloud/sensor.py b/homeassistant/components/icloud/sensor.py index 9a7c568112d..e75c9aa3854 100644 --- a/homeassistant/components/icloud/sensor.py +++ b/homeassistant/components/icloud/sensor.py @@ -1,5 +1,5 @@ """Support for iCloud sensors.""" -from typing import Dict +from __future__ import annotations from homeassistant.config_entries import ConfigEntry from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE @@ -91,12 +91,12 @@ class IcloudDeviceBatterySensor(Entity): ) @property - def extra_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> dict[str, any]: """Return default attributes for the iCloud device entity.""" return self._device.state_attributes @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "identifiers": {(DOMAIN, self._device.unique_id)}, diff --git a/homeassistant/components/ign_sismologia/geo_location.py b/homeassistant/components/ign_sismologia/geo_location.py index fc9bdcbe87e..314a7bdea31 100644 --- a/homeassistant/components/ign_sismologia/geo_location.py +++ b/homeassistant/components/ign_sismologia/geo_location.py @@ -1,7 +1,8 @@ """Support for IGN Sismologia (Earthquakes) Feeds.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Optional from georss_ign_sismologia_client import IgnSismologiaFeedManager import voluptuous as vol @@ -207,7 +208,7 @@ class IgnSismologiaLocationEvent(GeolocationEvent): return SOURCE @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" if self._magnitude and self._region: return f"M {self._magnitude:.1f} - {self._region}" @@ -218,17 +219,17 @@ class IgnSismologiaLocationEvent(GeolocationEvent): return self._title @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """Return distance value of this external event.""" return self._distance @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of this external event.""" return self._latitude @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of this external event.""" return self._longitude diff --git a/homeassistant/components/image/__init__.py b/homeassistant/components/image/__init__.py index c68df580643..37b3bd7ff6a 100644 --- a/homeassistant/components/image/__init__.py +++ b/homeassistant/components/image/__init__.py @@ -1,10 +1,11 @@ """The Picture integration.""" +from __future__ import annotations + import asyncio import logging import pathlib import secrets import shutil -import typing from PIL import Image, ImageOps, UnidentifiedImageError from aiohttp import hdrs, web @@ -69,7 +70,7 @@ class ImageStorageCollection(collection.StorageCollection): self.async_add_listener(self._change_listener) self.image_dir = image_dir - async def _process_create_data(self, data: typing.Dict) -> typing.Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" data = self.CREATE_SCHEMA(dict(data)) uploaded_file: FileField = data["file"] @@ -117,11 +118,11 @@ class ImageStorageCollection(collection.StorageCollection): return media_file.stat().st_size @callback - def _get_suggested_id(self, info: typing.Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return info[CONF_ID] - async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" return {**data, **self.UPDATE_SCHEMA(update_data)} diff --git a/homeassistant/components/incomfort/__init__.py b/homeassistant/components/incomfort/__init__.py index cec550d24d9..cea7244919b 100644 --- a/homeassistant/components/incomfort/__init__.py +++ b/homeassistant/components/incomfort/__init__.py @@ -1,6 +1,7 @@ """Support for an Intergas boiler via an InComfort/Intouch Lan2RF gateway.""" +from __future__ import annotations + import logging -from typing import Optional from aiohttp import ClientResponseError from incomfortclient import Gateway as InComfortGateway @@ -68,12 +69,12 @@ class IncomfortEntity(Entity): self._unique_id = self._name = None @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return self._unique_id @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the sensor.""" return self._name diff --git a/homeassistant/components/incomfort/binary_sensor.py b/homeassistant/components/incomfort/binary_sensor.py index 86cdf2d8687..cc8d2e24a0a 100644 --- a/homeassistant/components/incomfort/binary_sensor.py +++ b/homeassistant/components/incomfort/binary_sensor.py @@ -1,5 +1,7 @@ """Support for an Intergas heater via an InComfort/InTouch Lan2RF gateway.""" -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import Any from homeassistant.components.binary_sensor import ( DOMAIN as BINARY_SENSOR_DOMAIN, @@ -40,6 +42,6 @@ class IncomfortFailed(IncomfortChild, BinarySensorEntity): return self._heater.status["is_failed"] @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the device state attributes.""" return {"fault_code": self._heater.status["fault_code"]} diff --git a/homeassistant/components/incomfort/climate.py b/homeassistant/components/incomfort/climate.py index c3db86a79ac..e44090a0b48 100644 --- a/homeassistant/components/incomfort/climate.py +++ b/homeassistant/components/incomfort/climate.py @@ -1,5 +1,7 @@ """Support for an Intergas boiler via an InComfort/InTouch Lan2RF gateway.""" -from typing import Any, Dict, List, Optional +from __future__ import annotations + +from typing import Any from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, ClimateEntity from homeassistant.components.climate.const import ( @@ -39,7 +41,7 @@ class InComfortClimate(IncomfortChild, ClimateEntity): self._room = room @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the device state attributes.""" return {"status": self._room.status} @@ -54,17 +56,17 @@ class InComfortClimate(IncomfortChild, ClimateEntity): return HVAC_MODE_HEAT @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return [HVAC_MODE_HEAT] @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._room.room_temp @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return self._room.setpoint diff --git a/homeassistant/components/incomfort/sensor.py b/homeassistant/components/incomfort/sensor.py index 0672d19b2a9..8cb07fce14b 100644 --- a/homeassistant/components/incomfort/sensor.py +++ b/homeassistant/components/incomfort/sensor.py @@ -1,5 +1,7 @@ """Support for an Intergas heater via an InComfort/InTouch Lan2RF gateway.""" -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import Any from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ( @@ -57,17 +59,17 @@ class IncomfortSensor(IncomfortChild): self._unit_of_measurement = None @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the sensor.""" return self._heater.status[self._state_attr] @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class of the sensor.""" return self._device_class @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit of measurement of the sensor.""" return self._unit_of_measurement @@ -95,6 +97,6 @@ class IncomfortTemperature(IncomfortSensor): self._unit_of_measurement = TEMP_CELSIUS @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the device state attributes.""" return {self._attr: self._heater.status[self._attr]} diff --git a/homeassistant/components/incomfort/water_heater.py b/homeassistant/components/incomfort/water_heater.py index b3db66bd93d..84ed0212d3b 100644 --- a/homeassistant/components/incomfort/water_heater.py +++ b/homeassistant/components/incomfort/water_heater.py @@ -1,7 +1,9 @@ """Support for an Intergas boiler via an InComfort/Intouch Lan2RF gateway.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict +from typing import Any from aiohttp import ClientResponseError @@ -50,7 +52,7 @@ class IncomfortWaterHeater(IncomfortEntity, WaterHeaterEntity): return "mdi:thermometer-lines" @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the device state attributes.""" return {k: v for k, v in self._heater.status.items() if k in HEATER_ATTRS} diff --git a/homeassistant/components/influxdb/__init__.py b/homeassistant/components/influxdb/__init__.py index e327f34d128..df7a8fda786 100644 --- a/homeassistant/components/influxdb/__init__.py +++ b/homeassistant/components/influxdb/__init__.py @@ -1,11 +1,13 @@ """Support for sending data to an Influx database.""" +from __future__ import annotations + from dataclasses import dataclass import logging import math import queue import threading import time -from typing import Any, Callable, Dict, List +from typing import Any, Callable from influxdb import InfluxDBClient, exceptions from influxdb_client import InfluxDBClient as InfluxDBClientV2 @@ -100,7 +102,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -def create_influx_url(conf: Dict) -> Dict: +def create_influx_url(conf: dict) -> dict: """Build URL used from config inputs and default when necessary.""" if conf[CONF_API_VERSION] == API_VERSION_2: if CONF_SSL not in conf: @@ -125,7 +127,7 @@ def create_influx_url(conf: Dict) -> Dict: return conf -def validate_version_specific_config(conf: Dict) -> Dict: +def validate_version_specific_config(conf: dict) -> dict: """Ensure correct config fields are provided based on API version used.""" if conf[CONF_API_VERSION] == API_VERSION_2: if CONF_TOKEN not in conf: @@ -193,7 +195,7 @@ CONFIG_SCHEMA = vol.Schema( ) -def _generate_event_to_json(conf: Dict) -> Callable[[Dict], str]: +def _generate_event_to_json(conf: dict) -> Callable[[dict], str]: """Build event to json converter and add to config.""" entity_filter = convert_include_exclude_filter(conf) tags = conf.get(CONF_TAGS) @@ -208,7 +210,7 @@ def _generate_event_to_json(conf: Dict) -> Callable[[Dict], str]: conf[CONF_COMPONENT_CONFIG_GLOB], ) - def event_to_json(event: Dict) -> str: + def event_to_json(event: dict) -> str: """Convert event into json in format Influx expects.""" state = event.data.get(EVENT_NEW_STATE) if ( @@ -319,9 +321,9 @@ def _generate_event_to_json(conf: Dict) -> Callable[[Dict], str]: class InfluxClient: """An InfluxDB client wrapper for V1 or V2.""" - data_repositories: List[str] + data_repositories: list[str] write: Callable[[str], None] - query: Callable[[str, str], List[Any]] + query: Callable[[str, str], list[Any]] close: Callable[[], None] diff --git a/homeassistant/components/influxdb/sensor.py b/homeassistant/components/influxdb/sensor.py index ff9f6f93153..cfabd3ee099 100644 --- a/homeassistant/components/influxdb/sensor.py +++ b/homeassistant/components/influxdb/sensor.py @@ -1,6 +1,7 @@ """InfluxDB component which allows you to get data from an Influx database.""" +from __future__ import annotations + import logging -from typing import Dict import voluptuous as vol @@ -67,7 +68,7 @@ def _merge_connection_config_into_query(conf, query): query[key] = conf[key] -def validate_query_format_for_version(conf: Dict) -> Dict: +def validate_query_format_for_version(conf: dict) -> dict: """Ensure queries are provided in correct format based on API version.""" if conf[CONF_API_VERSION] == API_VERSION_2: if CONF_QUERIES_FLUX not in conf: diff --git a/homeassistant/components/input_boolean/__init__.py b/homeassistant/components/input_boolean/__init__.py index fbfe4cd0454..f1e4ebd57dc 100644 --- a/homeassistant/components/input_boolean/__init__.py +++ b/homeassistant/components/input_boolean/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations import logging -import typing import voluptuous as vol @@ -62,16 +61,16 @@ class InputBooleanStorageCollection(collection.StorageCollection): CREATE_SCHEMA = vol.Schema(CREATE_FIELDS) UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS) - async def _process_create_data(self, data: typing.Dict) -> typing.Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" return self.CREATE_SCHEMA(data) @callback - def _get_suggested_id(self, info: typing.Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return info[CONF_NAME] - async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" update_data = self.UPDATE_SCHEMA(update_data) return {**data, **update_data} @@ -145,14 +144,14 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: class InputBoolean(ToggleEntity, RestoreEntity): """Representation of a boolean input.""" - def __init__(self, config: typing.Optional[dict]): + def __init__(self, config: dict | None): """Initialize a boolean input.""" self._config = config self.editable = True self._state = config.get(CONF_INITIAL) @classmethod - def from_yaml(cls, config: typing.Dict) -> InputBoolean: + def from_yaml(cls, config: dict) -> InputBoolean: """Return entity instance initialized from yaml storage.""" input_bool = cls(config) input_bool.entity_id = f"{DOMAIN}.{config[CONF_ID]}" @@ -209,7 +208,7 @@ class InputBoolean(ToggleEntity, RestoreEntity): self._state = False self.async_write_ha_state() - async def async_update_config(self, config: typing.Dict) -> None: + async def async_update_config(self, config: dict) -> None: """Handle when the config is updated.""" self._config = config self.async_write_ha_state() diff --git a/homeassistant/components/input_boolean/reproduce_state.py b/homeassistant/components/input_boolean/reproduce_state.py index d01e931c5cc..8e8edcfd11b 100644 --- a/homeassistant/components/input_boolean/reproduce_state.py +++ b/homeassistant/components/input_boolean/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an input boolean state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -22,8 +24,8 @@ async def _async_reproduce_states( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce input boolean states.""" cur_state = hass.states.get(state.entity_id) @@ -56,8 +58,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce component states.""" await asyncio.gather( diff --git a/homeassistant/components/input_datetime/__init__.py b/homeassistant/components/input_datetime/__init__.py index adefa36639a..8273652be43 100644 --- a/homeassistant/components/input_datetime/__init__.py +++ b/homeassistant/components/input_datetime/__init__.py @@ -3,7 +3,6 @@ from __future__ import annotations import datetime as py_datetime import logging -import typing import voluptuous as vol @@ -178,16 +177,16 @@ class DateTimeStorageCollection(collection.StorageCollection): CREATE_SCHEMA = vol.Schema(vol.All(CREATE_FIELDS, has_date_or_time)) UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS) - async def _process_create_data(self, data: typing.Dict) -> typing.Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" return self.CREATE_SCHEMA(data) @callback - def _get_suggested_id(self, info: typing.Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return info[CONF_NAME] - async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" update_data = self.UPDATE_SCHEMA(update_data) return has_date_or_time({**data, **update_data}) @@ -196,7 +195,7 @@ class DateTimeStorageCollection(collection.StorageCollection): class InputDatetime(RestoreEntity): """Representation of a datetime input.""" - def __init__(self, config: typing.Dict) -> None: + def __init__(self, config: dict) -> None: """Initialize a select input.""" self._config = config self.editable = True @@ -230,7 +229,7 @@ class InputDatetime(RestoreEntity): ) @classmethod - def from_yaml(cls, config: typing.Dict) -> InputDatetime: + def from_yaml(cls, config: dict) -> InputDatetime: """Return entity instance initialized from yaml storage.""" input_dt = cls(config) input_dt.entity_id = f"{DOMAIN}.{config[CONF_ID]}" @@ -360,7 +359,7 @@ class InputDatetime(RestoreEntity): return attrs @property - def unique_id(self) -> typing.Optional[str]: + def unique_id(self) -> str | None: """Return unique id of the entity.""" return self._config[CONF_ID] @@ -394,7 +393,7 @@ class InputDatetime(RestoreEntity): ) self.async_write_ha_state() - async def async_update_config(self, config: typing.Dict) -> None: + async def async_update_config(self, config: dict) -> None: """Handle when the config is updated.""" self._config = config self.async_write_ha_state() diff --git a/homeassistant/components/input_datetime/reproduce_state.py b/homeassistant/components/input_datetime/reproduce_state.py index cc906ac50b3..a537a921f39 100644 --- a/homeassistant/components/input_datetime/reproduce_state.py +++ b/homeassistant/components/input_datetime/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Input datetime state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State @@ -35,8 +37,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -80,8 +82,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Input datetime states.""" await asyncio.gather( diff --git a/homeassistant/components/input_number/__init__.py b/homeassistant/components/input_number/__init__.py index b68e6fff45d..5f73dec0192 100644 --- a/homeassistant/components/input_number/__init__.py +++ b/homeassistant/components/input_number/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations import logging -import typing import voluptuous as vol @@ -179,16 +178,16 @@ class NumberStorageCollection(collection.StorageCollection): CREATE_SCHEMA = vol.Schema(vol.All(CREATE_FIELDS, _cv_input_number)) UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS) - async def _process_create_data(self, data: typing.Dict) -> typing.Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" return self.CREATE_SCHEMA(data) @callback - def _get_suggested_id(self, info: typing.Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return info[CONF_NAME] - async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" update_data = self.UPDATE_SCHEMA(update_data) return _cv_input_number({**data, **update_data}) @@ -197,14 +196,14 @@ class NumberStorageCollection(collection.StorageCollection): class InputNumber(RestoreEntity): """Representation of a slider.""" - def __init__(self, config: typing.Dict): + def __init__(self, config: dict): """Initialize an input number.""" self._config = config self.editable = True self._current_value = config.get(CONF_INITIAL) @classmethod - def from_yaml(cls, config: typing.Dict) -> InputNumber: + def from_yaml(cls, config: dict) -> InputNumber: """Return entity instance initialized from yaml storage.""" input_num = cls(config) input_num.entity_id = f"{DOMAIN}.{config[CONF_ID]}" @@ -252,7 +251,7 @@ class InputNumber(RestoreEntity): return self._config.get(CONF_UNIT_OF_MEASUREMENT) @property - def unique_id(self) -> typing.Optional[str]: + def unique_id(self) -> str | None: """Return unique id of the entity.""" return self._config[CONF_ID] @@ -303,7 +302,7 @@ class InputNumber(RestoreEntity): """Decrement value.""" await self.async_set_value(max(self._current_value - self._step, self._minimum)) - async def async_update_config(self, config: typing.Dict) -> None: + async def async_update_config(self, config: dict) -> None: """Handle when the config is updated.""" self._config = config # just in case min/max values changed diff --git a/homeassistant/components/input_number/reproduce_state.py b/homeassistant/components/input_number/reproduce_state.py index 5a6324c4333..1f9af63e01b 100644 --- a/homeassistant/components/input_number/reproduce_state.py +++ b/homeassistant/components/input_number/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Input number state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable import voluptuous as vol @@ -18,8 +20,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -56,8 +58,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Input number states.""" # Reproduce states in parallel. diff --git a/homeassistant/components/input_select/__init__.py b/homeassistant/components/input_select/__init__.py index 5c10c33421a..f14d23124e7 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations import logging -import typing import voluptuous as vol @@ -184,16 +183,16 @@ class InputSelectStorageCollection(collection.StorageCollection): CREATE_SCHEMA = vol.Schema(vol.All(CREATE_FIELDS, _cv_input_select)) UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS) - async def _process_create_data(self, data: typing.Dict) -> typing.Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" return self.CREATE_SCHEMA(data) @callback - def _get_suggested_id(self, info: typing.Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return info[CONF_NAME] - async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" update_data = self.UPDATE_SCHEMA(update_data) return _cv_input_select({**data, **update_data}) @@ -202,14 +201,14 @@ class InputSelectStorageCollection(collection.StorageCollection): class InputSelect(RestoreEntity): """Representation of a select input.""" - def __init__(self, config: typing.Dict): + def __init__(self, config: dict): """Initialize a select input.""" self._config = config self.editable = True self._current_option = config.get(CONF_INITIAL) @classmethod - def from_yaml(cls, config: typing.Dict) -> InputSelect: + def from_yaml(cls, config: dict) -> InputSelect: """Return entity instance initialized from yaml storage.""" input_select = cls(config) input_select.entity_id = f"{DOMAIN}.{config[CONF_ID]}" @@ -244,7 +243,7 @@ class InputSelect(RestoreEntity): return self._config.get(CONF_ICON) @property - def _options(self) -> typing.List[str]: + def _options(self) -> list[str]: """Return a list of selection options.""" return self._config[CONF_OPTIONS] @@ -259,7 +258,7 @@ class InputSelect(RestoreEntity): return {ATTR_OPTIONS: self._config[ATTR_OPTIONS], ATTR_EDITABLE: self.editable} @property - def unique_id(self) -> typing.Optional[str]: + def unique_id(self) -> str | None: """Return unique id for the entity.""" return self._config[CONF_ID] @@ -315,7 +314,7 @@ class InputSelect(RestoreEntity): self._config[CONF_OPTIONS] = options self.async_write_ha_state() - async def async_update_config(self, config: typing.Dict) -> None: + async def async_update_config(self, config: dict) -> None: """Handle when the config is updated.""" self._config = config self.async_write_ha_state() diff --git a/homeassistant/components/input_select/reproduce_state.py b/homeassistant/components/input_select/reproduce_state.py index bf687738740..beaee109750 100644 --- a/homeassistant/components/input_select/reproduce_state.py +++ b/homeassistant/components/input_select/reproduce_state.py @@ -1,8 +1,10 @@ """Reproduce an Input select state.""" +from __future__ import annotations + import asyncio import logging from types import MappingProxyType -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State @@ -25,8 +27,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -71,8 +73,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Input select states.""" # Reproduce states in parallel. diff --git a/homeassistant/components/input_text/__init__.py b/homeassistant/components/input_text/__init__.py index 3f8c1d6a13e..d4e0fc705f2 100644 --- a/homeassistant/components/input_text/__init__.py +++ b/homeassistant/components/input_text/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations import logging -import typing import voluptuous as vol @@ -173,16 +172,16 @@ class InputTextStorageCollection(collection.StorageCollection): CREATE_SCHEMA = vol.Schema(vol.All(CREATE_FIELDS, _cv_input_text)) UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS) - async def _process_create_data(self, data: typing.Dict) -> typing.Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" return self.CREATE_SCHEMA(data) @callback - def _get_suggested_id(self, info: typing.Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return info[CONF_NAME] - async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" update_data = self.UPDATE_SCHEMA(update_data) return _cv_input_text({**data, **update_data}) @@ -191,14 +190,14 @@ class InputTextStorageCollection(collection.StorageCollection): class InputText(RestoreEntity): """Represent a text box.""" - def __init__(self, config: typing.Dict): + def __init__(self, config: dict): """Initialize a text input.""" self._config = config self.editable = True self._current_value = config.get(CONF_INITIAL) @classmethod - def from_yaml(cls, config: typing.Dict) -> InputText: + def from_yaml(cls, config: dict) -> InputText: """Return entity instance initialized from yaml storage.""" input_text = cls(config) input_text.entity_id = f"{DOMAIN}.{config[CONF_ID]}" @@ -241,7 +240,7 @@ class InputText(RestoreEntity): return self._config.get(CONF_UNIT_OF_MEASUREMENT) @property - def unique_id(self) -> typing.Optional[str]: + def unique_id(self) -> str | None: """Return unique id for the entity.""" return self._config[CONF_ID] @@ -282,7 +281,7 @@ class InputText(RestoreEntity): self._current_value = value self.async_write_ha_state() - async def async_update_config(self, config: typing.Dict) -> None: + async def async_update_config(self, config: dict) -> None: """Handle when the config is updated.""" self._config = config self.async_write_ha_state() diff --git a/homeassistant/components/input_text/reproduce_state.py b/homeassistant/components/input_text/reproduce_state.py index abd28195d8d..62e1a1fbd68 100644 --- a/homeassistant/components/input_text/reproduce_state.py +++ b/homeassistant/components/input_text/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Input text state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State @@ -16,8 +18,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -44,8 +46,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Input text states.""" # Reproduce states in parallel. diff --git a/homeassistant/components/insteon/climate.py b/homeassistant/components/insteon/climate.py index 536c30bc6b9..7e034311a82 100644 --- a/homeassistant/components/insteon/climate.py +++ b/homeassistant/components/insteon/climate.py @@ -1,5 +1,5 @@ """Support for Insteon thermostat.""" -from typing import List, Optional +from __future__ import annotations from pyinsteon.constants import ThermostatMode from pyinsteon.operating_flag import CELSIUS @@ -97,7 +97,7 @@ class InsteonClimateEntity(InsteonEntity, ClimateEntity): return TEMP_FAHRENHEIT @property - def current_humidity(self) -> Optional[int]: + def current_humidity(self) -> int | None: """Return the current humidity.""" return self._insteon_device.groups[HUMIDITY].value @@ -107,17 +107,17 @@ class InsteonClimateEntity(InsteonEntity, ClimateEntity): return HVAC_MODES[self._insteon_device.groups[SYSTEM_MODE].value] @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return list(HVAC_MODES.values()) @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._insteon_device.groups[TEMPERATURE].value @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" if self._insteon_device.groups[SYSTEM_MODE].value == ThermostatMode.HEAT: return self._insteon_device.groups[HEAT_SET_POINT].value @@ -126,31 +126,31 @@ class InsteonClimateEntity(InsteonEntity, ClimateEntity): return None @property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" if self._insteon_device.groups[SYSTEM_MODE].value == ThermostatMode.AUTO: return self._insteon_device.groups[COOL_SET_POINT].value return None @property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" if self._insteon_device.groups[SYSTEM_MODE].value == ThermostatMode.AUTO: return self._insteon_device.groups[HEAT_SET_POINT].value return None @property - def fan_mode(self) -> Optional[str]: + def fan_mode(self) -> str | None: """Return the fan setting.""" return FAN_MODES[self._insteon_device.groups[FAN_MODE].value] @property - def fan_modes(self) -> Optional[List[str]]: + def fan_modes(self) -> list[str] | None: """Return the list of available fan modes.""" return list(FAN_MODES.values()) @property - def target_humidity(self) -> Optional[int]: + def target_humidity(self) -> int | None: """Return the humidity we try to reach.""" high = self._insteon_device.groups[HUMIDITY_HIGH].value low = self._insteon_device.groups[HUMIDITY_LOW].value @@ -163,7 +163,7 @@ class InsteonClimateEntity(InsteonEntity, ClimateEntity): return 1 @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation if supported. Need to be one of CURRENT_HVAC_*. diff --git a/homeassistant/components/insteon/schemas.py b/homeassistant/components/insteon/schemas.py index 8698a358b21..c43df24b4cb 100644 --- a/homeassistant/components/insteon/schemas.py +++ b/homeassistant/components/insteon/schemas.py @@ -1,6 +1,7 @@ """Schemas used by insteon component.""" +from __future__ import annotations + from binascii import Error as HexError, unhexlify -from typing import Dict from pyinsteon.address import Address from pyinsteon.constants import HC_LOOKUP @@ -51,7 +52,7 @@ from .const import ( ) -def set_default_port(schema: Dict) -> Dict: +def set_default_port(schema: dict) -> dict: """Set the default port based on the Hub version.""" # If the ip_port is found do nothing # If it is not found the set the default diff --git a/homeassistant/components/ipp/__init__.py b/homeassistant/components/ipp/__init__.py index c36b0cb7959..6e522a999c0 100644 --- a/homeassistant/components/ipp/__init__.py +++ b/homeassistant/components/ipp/__init__.py @@ -1,8 +1,10 @@ """The Internet Printing Protocol (IPP) integration.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Any, Dict +from typing import Any from pyipp import IPP, IPPError, Printer as IPPPrinter @@ -39,7 +41,7 @@ SCAN_INTERVAL = timedelta(seconds=60) _LOGGER = logging.getLogger(__name__) -async def async_setup(hass: HomeAssistant, config: Dict) -> bool: +async def async_setup(hass: HomeAssistant, config: dict) -> bool: """Set up the IPP component.""" hass.data.setdefault(DOMAIN, {}) return True @@ -166,7 +168,7 @@ class IPPEntity(CoordinatorEntity): return self._enabled_default @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this IPP device.""" if self._device_id is None: return None diff --git a/homeassistant/components/ipp/config_flow.py b/homeassistant/components/ipp/config_flow.py index 3815dcf8f69..d167e79cba5 100644 --- a/homeassistant/components/ipp/config_flow.py +++ b/homeassistant/components/ipp/config_flow.py @@ -1,6 +1,8 @@ """Config flow to configure the IPP integration.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from pyipp import ( IPP, @@ -30,7 +32,7 @@ from .const import DOMAIN # pylint: disable=unused-import _LOGGER = logging.getLogger(__name__) -async def validate_input(hass: HomeAssistantType, data: dict) -> Dict[str, Any]: +async def validate_input(hass: HomeAssistantType, data: dict) -> dict[str, Any]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -61,8 +63,8 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN): self.discovery_info = {} async def async_step_user( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" if user_input is None: return self._show_setup_form() @@ -98,7 +100,7 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=user_input[CONF_HOST], data=user_input) - async def async_step_zeroconf(self, discovery_info: ConfigType) -> Dict[str, Any]: + async def async_step_zeroconf(self, discovery_info: ConfigType) -> dict[str, Any]: """Handle zeroconf discovery.""" port = discovery_info[CONF_PORT] zctype = discovery_info["type"] @@ -166,7 +168,7 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf_confirm( self, user_input: ConfigType = None - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Handle a confirmation flow initiated by zeroconf.""" if user_input is None: return self.async_show_form( @@ -180,7 +182,7 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN): data=self.discovery_info, ) - def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: + def _show_setup_form(self, errors: dict | None = None) -> dict[str, Any]: """Show the setup form to the user.""" return self.async_show_form( step_id="user", diff --git a/homeassistant/components/ipp/sensor.py b/homeassistant/components/ipp/sensor.py index b24e05f2720..1ff98df7a5f 100644 --- a/homeassistant/components/ipp/sensor.py +++ b/homeassistant/components/ipp/sensor.py @@ -1,6 +1,8 @@ """Support for IPP sensors.""" +from __future__ import annotations + from datetime import timedelta -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_LOCATION, DEVICE_CLASS_TIMESTAMP, PERCENTAGE @@ -26,7 +28,7 @@ from .const import ( async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up IPP sensor based on a config entry.""" coordinator: IPPDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] @@ -63,7 +65,7 @@ class IPPSensor(IPPEntity): icon: str, key: str, name: str, - unit_of_measurement: Optional[str] = None, + unit_of_measurement: str | None = None, ) -> None: """Initialize IPP sensor.""" self._unit_of_measurement = unit_of_measurement @@ -117,7 +119,7 @@ class IPPMarkerSensor(IPPSensor): ) @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" return { ATTR_MARKER_HIGH_LEVEL: self.coordinator.data.markers[ @@ -132,7 +134,7 @@ class IPPMarkerSensor(IPPSensor): } @property - def state(self) -> Optional[int]: + def state(self) -> int | None: """Return the state of the sensor.""" level = self.coordinator.data.markers[self.marker_index].level @@ -160,7 +162,7 @@ class IPPPrinterSensor(IPPSensor): ) @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" return { ATTR_INFO: self.coordinator.data.info.printer_info, @@ -202,6 +204,6 @@ class IPPUptimeSensor(IPPSensor): return uptime.replace(microsecond=0).isoformat() @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this sensor.""" return DEVICE_CLASS_TIMESTAMP diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py index d07fef97edd..a2648d0dbc4 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -1,7 +1,8 @@ """Support the ISY-994 controllers.""" +from __future__ import annotations + import asyncio from functools import partial -from typing import Optional from urllib.parse import urlparse from pyisy import ISY @@ -67,7 +68,7 @@ CONFIG_SCHEMA = vol.Schema( async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the isy994 integration from YAML.""" - isy_config: Optional[ConfigType] = config.get(DOMAIN) + isy_config: ConfigType | None = config.get(DOMAIN) hass.data.setdefault(DOMAIN, {}) if not isy_config: diff --git a/homeassistant/components/isy994/binary_sensor.py b/homeassistant/components/isy994/binary_sensor.py index 807f99734b7..3b0bb9fd144 100644 --- a/homeassistant/components/isy994/binary_sensor.py +++ b/homeassistant/components/isy994/binary_sensor.py @@ -1,6 +1,8 @@ """Support for ISY994 binary sensors.""" +from __future__ import annotations + from datetime import timedelta -from typing import Callable, Union +from typing import Callable from pyisy.constants import ( CMD_OFF, @@ -173,7 +175,7 @@ async def async_setup_entry( async_add_entities(devices) -def _detect_device_type_and_class(node: Union[Group, Node]) -> (str, str): +def _detect_device_type_and_class(node: Group | Node) -> (str, str): try: device_type = node.type except AttributeError: diff --git a/homeassistant/components/isy994/climate.py b/homeassistant/components/isy994/climate.py index bb98c3d31bf..2c9aa52b3a7 100644 --- a/homeassistant/components/isy994/climate.py +++ b/homeassistant/components/isy994/climate.py @@ -1,5 +1,7 @@ """Support for Insteon Thermostats via ISY994 Platform.""" -from typing import Callable, List, Optional +from __future__ import annotations + +from typing import Callable from pyisy.constants import ( CMD_CLIMATE_FAN_SETTING, @@ -114,7 +116,7 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): return TEMP_FAHRENHEIT @property - def current_humidity(self) -> Optional[int]: + def current_humidity(self) -> int | None: """Return the current humidity.""" humidity = self._node.aux_properties.get(PROP_HUMIDITY) if not humidity: @@ -122,7 +124,7 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): return int(humidity.value) @property - def hvac_mode(self) -> Optional[str]: + def hvac_mode(self) -> str | None: """Return hvac operation ie. heat, cool mode.""" hvac_mode = self._node.aux_properties.get(CMD_CLIMATE_MODE) if not hvac_mode: @@ -140,12 +142,12 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): return UOM_TO_STATES[uom].get(hvac_mode.value) @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return ISY_HVAC_MODES @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation if supported.""" hvac_action = self._node.aux_properties.get(PROP_HEAT_COOL_STATE) if not hvac_action: @@ -153,19 +155,19 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): return UOM_TO_STATES[UOM_HVAC_ACTIONS].get(hvac_action.value) @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return convert_isy_value_to_hass( self._node.status, self._uom, self._node.prec, 1 ) @property - def target_temperature_step(self) -> Optional[float]: + def target_temperature_step(self) -> float | None: """Return the supported step of target temperature.""" return 1.0 @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" if self.hvac_mode == HVAC_MODE_COOL: return self.target_temperature_high @@ -174,7 +176,7 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): return None @property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" target = self._node.aux_properties.get(PROP_SETPOINT_COOL) if not target: @@ -182,7 +184,7 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): return convert_isy_value_to_hass(target.value, target.uom, target.prec, 1) @property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" target = self._node.aux_properties.get(PROP_SETPOINT_HEAT) if not target: diff --git a/homeassistant/components/isy994/fan.py b/homeassistant/components/isy994/fan.py index 43323cc5546..183d4b31d3b 100644 --- a/homeassistant/components/isy994/fan.py +++ b/homeassistant/components/isy994/fan.py @@ -1,6 +1,8 @@ """Support for ISY994 fans.""" +from __future__ import annotations + import math -from typing import Callable, Optional +from typing import Callable from pyisy.constants import ISY_VALUE_UNKNOWN, PROTO_INSTEON @@ -43,7 +45,7 @@ class ISYFanEntity(ISYNodeEntity, FanEntity): """Representation of an ISY994 fan device.""" @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" if self._node.status == ISY_VALUE_UNKNOWN: return None @@ -97,7 +99,7 @@ class ISYFanProgramEntity(ISYProgramEntity, FanEntity): """Representation of an ISY994 fan program.""" @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" if self._node.status == ISY_VALUE_UNKNOWN: return None diff --git a/homeassistant/components/isy994/helpers.py b/homeassistant/components/isy994/helpers.py index fecf18f789b..780e24843bd 100644 --- a/homeassistant/components/isy994/helpers.py +++ b/homeassistant/components/isy994/helpers.py @@ -1,5 +1,7 @@ """Sorting helpers for ISY994 device classifications.""" -from typing import Any, List, Optional, Union +from __future__ import annotations + +from typing import Any from pyisy.constants import ( ISY_VALUE_UNKNOWN, @@ -56,7 +58,7 @@ BINARY_SENSOR_ISY_STATES = ["on", "off"] def _check_for_node_def( - hass_isy_data: dict, node: Union[Group, Node], single_platform: str = None + hass_isy_data: dict, node: Group | Node, single_platform: str = None ) -> bool: """Check if the node matches the node_def_id for any platforms. @@ -79,7 +81,7 @@ def _check_for_node_def( def _check_for_insteon_type( - hass_isy_data: dict, node: Union[Group, Node], single_platform: str = None + hass_isy_data: dict, node: Group | Node, single_platform: str = None ) -> bool: """Check if the node matches the Insteon type for any platforms. @@ -144,7 +146,7 @@ def _check_for_insteon_type( def _check_for_zwave_cat( - hass_isy_data: dict, node: Union[Group, Node], single_platform: str = None + hass_isy_data: dict, node: Group | Node, single_platform: str = None ) -> bool: """Check if the node matches the ISY Z-Wave Category for any platforms. @@ -174,7 +176,7 @@ def _check_for_zwave_cat( def _check_for_uom_id( hass_isy_data: dict, - node: Union[Group, Node], + node: Group | Node, single_platform: str = None, uom_list: list = None, ) -> bool: @@ -209,7 +211,7 @@ def _check_for_uom_id( def _check_for_states_in_uom( hass_isy_data: dict, - node: Union[Group, Node], + node: Group | Node, single_platform: str = None, states_list: list = None, ) -> bool: @@ -244,7 +246,7 @@ def _check_for_states_in_uom( return False -def _is_sensor_a_binary_sensor(hass_isy_data: dict, node: Union[Group, Node]) -> bool: +def _is_sensor_a_binary_sensor(hass_isy_data: dict, node: Group | Node) -> bool: """Determine if the given sensor node should be a binary_sensor.""" if _check_for_node_def(hass_isy_data, node, single_platform=BINARY_SENSOR): return True @@ -364,7 +366,7 @@ def _categorize_variables( async def migrate_old_unique_ids( - hass: HomeAssistantType, platform: str, devices: Optional[List[Any]] + hass: HomeAssistantType, platform: str, devices: list[Any] | None ) -> None: """Migrate to new controller-specific unique ids.""" registry = await async_get_registry(hass) @@ -396,11 +398,11 @@ async def migrate_old_unique_ids( def convert_isy_value_to_hass( - value: Union[int, float, None], + value: int | float | None, uom: str, - precision: Union[int, str], - fallback_precision: Optional[int] = None, -) -> Union[float, int]: + precision: int | str, + fallback_precision: int | None = None, +) -> float | int: """Fix ISY Reported Values. ISY provides float values as an integer and precision component. diff --git a/homeassistant/components/isy994/light.py b/homeassistant/components/isy994/light.py index 04800a3f211..7f35e96acaf 100644 --- a/homeassistant/components/isy994/light.py +++ b/homeassistant/components/isy994/light.py @@ -1,5 +1,7 @@ """Support for ISY994 lights.""" -from typing import Callable, Dict +from __future__ import annotations + +from typing import Callable from pyisy.constants import ISY_VALUE_UNKNOWN @@ -98,7 +100,7 @@ class ISYLightEntity(ISYNodeEntity, LightEntity, RestoreEntity): _LOGGER.debug("Unable to turn on light") @property - def extra_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> dict: """Return the light attributes.""" attribs = super().extra_state_attributes attribs[ATTR_LAST_BRIGHTNESS] = self._last_brightness diff --git a/homeassistant/components/isy994/sensor.py b/homeassistant/components/isy994/sensor.py index 2f393227df5..4d3c4c72763 100644 --- a/homeassistant/components/isy994/sensor.py +++ b/homeassistant/components/isy994/sensor.py @@ -1,5 +1,7 @@ """Support for ISY994 sensors.""" -from typing import Callable, Dict, Union +from __future__ import annotations + +from typing import Callable from pyisy.constants import ISY_VALUE_UNKNOWN @@ -47,7 +49,7 @@ class ISYSensorEntity(ISYNodeEntity): """Representation of an ISY994 sensor device.""" @property - def raw_unit_of_measurement(self) -> Union[dict, str]: + def raw_unit_of_measurement(self) -> dict | str: """Get the raw unit of measurement for the ISY994 sensor device.""" uom = self._node.uom @@ -117,7 +119,7 @@ class ISYSensorVariableEntity(ISYEntity): return convert_isy_value_to_hass(self._node.status, "", self._node.prec) @property - def extra_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> dict: """Get the state attributes for the device.""" return { "init_value": convert_isy_value_to_hass( diff --git a/homeassistant/components/izone/climate.py b/homeassistant/components/izone/climate.py index 53651683725..18c8b5e9a2c 100644 --- a/homeassistant/components/izone/climate.py +++ b/homeassistant/components/izone/climate.py @@ -1,7 +1,7 @@ """Support for the iZone HVAC.""" +from __future__ import annotations import logging -from typing import List, Optional from pizone import Controller, Zone import voluptuous as vol @@ -324,7 +324,7 @@ class ControllerDevice(ClimateEntity): @property @_return_on_connection_error([]) - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available operation modes.""" if self._controller.free_air: return [HVAC_MODE_OFF, HVAC_MODE_FAN_ONLY] @@ -346,7 +346,7 @@ class ControllerDevice(ClimateEntity): @property @_return_on_connection_error() - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" if self._controller.mode == Controller.Mode.FREE_AIR: return self._controller.temp_supply @@ -364,7 +364,7 @@ class ControllerDevice(ClimateEntity): return zone.name @property - def control_zone_setpoint(self) -> Optional[float]: + def control_zone_setpoint(self) -> float | None: """Return the temperature setpoint of the zone that currently controls the AC unit (if target temp not set by controller).""" if self._supported_features & SUPPORT_TARGET_TEMPERATURE: return None @@ -376,7 +376,7 @@ class ControllerDevice(ClimateEntity): @property @_return_on_connection_error() - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach (either from control zone or master unit).""" if self._supported_features & SUPPORT_TARGET_TEMPERATURE: return self._controller.temp_setpoint @@ -388,17 +388,17 @@ class ControllerDevice(ClimateEntity): return self._controller.temp_supply @property - def target_temperature_step(self) -> Optional[float]: + def target_temperature_step(self) -> float | None: """Return the supported step of target temperature.""" return 0.5 @property - def fan_mode(self) -> Optional[str]: + def fan_mode(self) -> str | None: """Return the fan setting.""" return _IZONE_FAN_TO_HA[self._controller.fan] @property - def fan_modes(self) -> Optional[List[str]]: + def fan_modes(self) -> list[str] | None: """Return the list of available fan modes.""" return list(self._fan_to_pizone) From a57d340037a227b04d3c7884611355470a6f93a7 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 10:07:11 +0100 Subject: [PATCH 446/831] Use websocket fixture in deCONZ cover tests (#47822) Localize test data Improve asserts --- tests/components/deconz/test_cover.py | 160 ++++++++++++-------------- 1 file changed, 76 insertions(+), 84 deletions(-) diff --git a/tests/components/deconz/test_cover.py b/tests/components/deconz/test_cover.py index e48d44fb61e..c252b00a228 100644 --- a/tests/components/deconz/test_cover.py +++ b/tests/components/deconz/test_cover.py @@ -1,8 +1,9 @@ """deCONZ cover platform tests.""" -from copy import deepcopy +from unittest.mock import patch from homeassistant.components.cover import ( + ATTR_CURRENT_POSITION, ATTR_CURRENT_TILT_POSITION, ATTR_POSITION, ATTR_TILT_POSITION, @@ -16,7 +17,6 @@ from homeassistant.components.cover import ( SERVICE_STOP_COVER, SERVICE_STOP_COVER_TILT, ) -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.const import ( ATTR_ENTITY_ID, STATE_CLOSED, @@ -30,48 +30,6 @@ from .test_gateway import ( setup_deconz_integration, ) -COVERS = { - "1": { - "id": "Level controllable cover id", - "name": "Level controllable cover", - "type": "Level controllable output", - "state": {"bri": 254, "on": False, "reachable": True}, - "modelid": "Not zigbee spec", - "uniqueid": "00:00:00:00:00:00:00:00-00", - }, - "2": { - "id": "Window covering device id", - "name": "Window covering device", - "type": "Window covering device", - "state": {"lift": 100, "open": False, "reachable": True}, - "modelid": "lumi.curtain", - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, - "3": { - "id": "Unsupported cover id", - "name": "Unsupported cover", - "type": "Not a cover", - "state": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:02-00", - }, - "4": { - "id": "deconz old brightness cover id", - "name": "deconz old brightness cover", - "type": "Level controllable output", - "state": {"bri": 255, "on": False, "reachable": True}, - "modelid": "Not zigbee spec", - "uniqueid": "00:00:00:00:00:00:00:03-00", - }, - "5": { - "id": "Window covering controller id", - "name": "Window covering controller", - "type": "Window covering controller", - "state": {"bri": 253, "on": True, "reachable": True}, - "modelid": "Motor controller", - "uniqueid": "00:00:00:00:00:00:00:04-00", - }, -} - async def test_no_covers(hass, aioclient_mock): """Test that no cover entities are created.""" @@ -79,32 +37,66 @@ async def test_no_covers(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_cover(hass, aioclient_mock): +async def test_cover(hass, aioclient_mock, mock_deconz_websocket): """Test that all supported cover entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = deepcopy(COVERS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "lights": { + "1": { + "name": "Level controllable cover", + "type": "Level controllable output", + "state": {"bri": 254, "on": False, "reachable": True}, + "modelid": "Not zigbee spec", + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + "2": { + "name": "Window covering device", + "type": "Window covering device", + "state": {"lift": 100, "open": False, "reachable": True}, + "modelid": "lumi.curtain", + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + "3": { + "name": "Unsupported cover", + "type": "Not a cover", + "state": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:02-00", + }, + "4": { + "name": "deconz old brightness cover", + "type": "Level controllable output", + "state": {"bri": 255, "on": False, "reachable": True}, + "modelid": "Not zigbee spec", + "uniqueid": "00:00:00:00:00:00:00:03-00", + }, + "5": { + "name": "Window covering controller", + "type": "Window covering controller", + "state": {"bri": 253, "on": True, "reachable": True}, + "modelid": "Motor controller", + "uniqueid": "00:00:00:00:00:00:00:04-00", + }, + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 5 assert hass.states.get("cover.level_controllable_cover").state == STATE_OPEN assert hass.states.get("cover.window_covering_device").state == STATE_CLOSED - assert hass.states.get("cover.unsupported_cover") is None + assert not hass.states.get("cover.unsupported_cover") assert hass.states.get("cover.deconz_old_brightness_cover").state == STATE_OPEN assert hass.states.get("cover.window_covering_controller").state == STATE_CLOSED # Event signals cover is closed - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", "state": {"on": True}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() assert hass.states.get("cover.level_controllable_cover").state == STATE_CLOSED @@ -200,24 +192,24 @@ async def test_cover(hass, aioclient_mock): # Test that a reported cover position of 255 (deconz-rest-api < 2.05.73) is interpreted correctly. assert hass.states.get("cover.deconz_old_brightness_cover").state == STATE_OPEN - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "4", "state": {"on": True}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() deconz_old_brightness_cover = hass.states.get("cover.deconz_old_brightness_cover") assert deconz_old_brightness_cover.state == STATE_CLOSED - assert deconz_old_brightness_cover.attributes["current_position"] == 0 + assert deconz_old_brightness_cover.attributes[ATTR_CURRENT_POSITION] == 0 await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(hass.states.async_all()) == 5 + assert len(states) == 5 for state in states: assert state.state == STATE_UNAVAILABLE @@ -228,36 +220,36 @@ async def test_cover(hass, aioclient_mock): async def test_tilt_cover(hass, aioclient_mock): """Test that tilting a cover works.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = { - "0": { - "etag": "87269755b9b3a046485fdae8d96b252c", - "lastannounced": None, - "lastseen": "2020-08-01T16:22:05Z", - "manufacturername": "AXIS", - "modelid": "Gear", - "name": "Covering device", - "state": { - "bri": 0, - "lift": 0, - "on": False, - "open": True, - "reachable": True, - "tilt": 0, - }, - "swversion": "100-5.3.5.1122", - "type": "Window covering device", - "uniqueid": "00:24:46:00:00:12:34:56-01", + data = { + "lights": { + "0": { + "etag": "87269755b9b3a046485fdae8d96b252c", + "lastannounced": None, + "lastseen": "2020-08-01T16:22:05Z", + "manufacturername": "AXIS", + "modelid": "Gear", + "name": "Covering device", + "state": { + "bri": 0, + "lift": 0, + "on": False, + "open": True, + "reachable": True, + "tilt": 0, + }, + "swversion": "100-5.3.5.1122", + "type": "Window covering device", + "uniqueid": "00:24:46:00:00:12:34:56-01", + } } } - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 1 - entity = hass.states.get("cover.covering_device") - assert entity.state == STATE_OPEN - assert entity.attributes[ATTR_CURRENT_TILT_POSITION] == 100 + covering_device = hass.states.get("cover.covering_device") + assert covering_device.state == STATE_OPEN + assert covering_device.attributes[ATTR_CURRENT_TILT_POSITION] == 100 # Verify service calls for tilting cover From 7ff9610e6761128036b2b8b3811a09964b124759 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 10:44:31 +0100 Subject: [PATCH 447/831] Use websocket fixture in deCONZ event related tests (#47823) Localize test data Improve asserts --- tests/components/deconz/test_deconz_event.py | 191 +++++++++++------- .../components/deconz/test_device_trigger.py | 76 ++++--- tests/components/deconz/test_logbook.py | 135 +++++++------ 3 files changed, 236 insertions(+), 166 deletions(-) diff --git a/tests/components/deconz/test_deconz_event.py b/tests/components/deconz/test_deconz_event.py index 1212d72a6ee..8a2e6a1d465 100644 --- a/tests/components/deconz/test_deconz_event.py +++ b/tests/components/deconz/test_deconz_event.py @@ -1,125 +1,164 @@ """Test deCONZ remote events.""" -from copy import deepcopy +from unittest.mock import patch +from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.deconz_event import CONF_DECONZ_EVENT -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.helpers.device_registry import async_entries_for_config_entry from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration from tests.common import async_capture_events -SENSORS = { - "1": { - "id": "Switch 1 id", - "name": "Switch 1", - "type": "ZHASwitch", - "state": {"buttonevent": 1000}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, - "2": { - "id": "Switch 2 id", - "name": "Switch 2", - "type": "ZHASwitch", - "state": {"buttonevent": 1000}, - "config": {"battery": 100}, - "uniqueid": "00:00:00:00:00:00:00:02-00", - }, - "3": { - "id": "Switch 3 id", - "name": "Switch 3", - "type": "ZHASwitch", - "state": {"buttonevent": 1000, "gesture": 1}, - "config": {"battery": 100}, - "uniqueid": "00:00:00:00:00:00:00:03-00", - }, - "4": { - "id": "Switch 4 id", - "name": "Switch 4", - "type": "ZHASwitch", - "state": {"buttonevent": 1000, "gesture": 1}, - "config": {"battery": 100}, - "uniqueid": "00:00:00:00:00:00:00:04-00", - }, - "5": { - "id": "ZHA remote 1 id", - "name": "ZHA remote 1", - "type": "ZHASwitch", - "state": {"angle": 0, "buttonevent": 1000, "xy": [0.0, 0.0]}, - "config": {"group": "4,5,6", "reachable": True, "on": True}, - "uniqueid": "00:00:00:00:00:00:00:05-00", - }, -} - -async def test_deconz_events(hass, aioclient_mock): +async def test_deconz_events(hass, aioclient_mock, mock_deconz_websocket): """Test successful creation of deconz events.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "sensors": { + "1": { + "name": "Switch 1", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + "2": { + "name": "Switch 2", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {"battery": 100}, + "uniqueid": "00:00:00:00:00:00:00:02-00", + }, + "3": { + "name": "Switch 3", + "type": "ZHASwitch", + "state": {"buttonevent": 1000, "gesture": 1}, + "config": {"battery": 100}, + "uniqueid": "00:00:00:00:00:00:00:03-00", + }, + "4": { + "name": "Switch 4", + "type": "ZHASwitch", + "state": {"buttonevent": 1000, "gesture": 1}, + "config": {"battery": 100}, + "uniqueid": "00:00:00:00:00:00:00:04-00", + }, + "5": { + "name": "ZHA remote 1", + "type": "ZHASwitch", + "state": {"angle": 0, "buttonevent": 1000, "xy": [0.0, 0.0]}, + "config": {"group": "4,5,6", "reachable": True, "on": True}, + "uniqueid": "00:00:00:00:00:00:00:05-00", + }, + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + device_registry = await hass.helpers.device_registry.async_get_registry() assert len(hass.states.async_all()) == 3 - assert len(gateway.events) == 5 - assert hass.states.get("sensor.switch_1") is None - assert hass.states.get("sensor.switch_1_battery_level") is None - assert hass.states.get("sensor.switch_2") is None + # 5 switches + 2 additional devices for deconz service and host + assert ( + len(async_entries_for_config_entry(device_registry, config_entry.entry_id)) == 7 + ) assert hass.states.get("sensor.switch_2_battery_level").state == "100" + assert hass.states.get("sensor.switch_3_battery_level").state == "100" + assert hass.states.get("sensor.switch_4_battery_level").state == "100" - events = async_capture_events(hass, CONF_DECONZ_EVENT) + captured_events = async_capture_events(hass, CONF_DECONZ_EVENT) - gateway.api.sensors["1"].update({"state": {"buttonevent": 2000}}) + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "1", + "state": {"buttonevent": 2000}, + } + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() - assert len(events) == 1 - assert events[0].data == { + device = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, "00:00:00:00:00:00:00:01")} + ) + + assert len(captured_events) == 1 + assert captured_events[0].data == { "id": "switch_1", "unique_id": "00:00:00:00:00:00:00:01", "event": 2000, - "device_id": gateway.events[0].device_id, + "device_id": device.id, } - gateway.api.sensors["3"].update({"state": {"buttonevent": 2000}}) + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "3", + "state": {"buttonevent": 2000}, + } + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() - assert len(events) == 2 - assert events[1].data == { + device = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, "00:00:00:00:00:00:00:03")} + ) + + assert len(captured_events) == 2 + assert captured_events[1].data == { "id": "switch_3", "unique_id": "00:00:00:00:00:00:00:03", "event": 2000, "gesture": 1, - "device_id": gateway.events[2].device_id, + "device_id": device.id, } - gateway.api.sensors["4"].update({"state": {"gesture": 0}}) + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "4", + "state": {"gesture": 0}, + } + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() - assert len(events) == 3 - assert events[2].data == { + device = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, "00:00:00:00:00:00:00:04")} + ) + + assert len(captured_events) == 3 + assert captured_events[2].data == { "id": "switch_4", "unique_id": "00:00:00:00:00:00:00:04", "event": 1000, "gesture": 0, - "device_id": gateway.events[3].device_id, + "device_id": device.id, } - gateway.api.sensors["5"].update( - {"state": {"buttonevent": 6002, "angle": 110, "xy": [0.5982, 0.3897]}} - ) + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "5", + "state": {"buttonevent": 6002, "angle": 110, "xy": [0.5982, 0.3897]}, + } + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() - assert len(events) == 4 - assert events[3].data == { + device = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, "00:00:00:00:00:00:00:05")} + ) + + assert len(captured_events) == 4 + assert captured_events[3].data == { "id": "zha_remote_1", "unique_id": "00:00:00:00:00:00:00:05", "event": 6002, "angle": 110, "xy": [0.5982, 0.3897], - "device_id": gateway.events[4].device_id, + "device_id": device.id, } await hass.config_entries.async_unload(config_entry.entry_id) @@ -128,9 +167,7 @@ async def test_deconz_events(hass, aioclient_mock): assert len(hass.states.async_all()) == 3 for state in states: assert state.state == STATE_UNAVAILABLE - assert len(gateway.events) == 0 await hass.config_entries.async_remove(config_entry.entry_id) await hass.async_block_till_done() assert len(hass.states.async_all()) == 0 - assert len(gateway.events) == 0 diff --git a/tests/components/deconz/test_device_trigger.py b/tests/components/deconz/test_device_trigger.py index 20fd79e9e8c..7a39b6fe48f 100644 --- a/tests/components/deconz/test_device_trigger.py +++ b/tests/components/deconz/test_device_trigger.py @@ -1,11 +1,10 @@ """deCONZ device automation tests.""" -from copy import deepcopy +from unittest.mock import patch from homeassistant.components.deconz import device_trigger from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.device_trigger import CONF_SUBTYPE -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ( ATTR_BATTERY_LEVEL, @@ -38,7 +37,7 @@ SENSORS = { "name": "TRÅDFRI on/off switch ", "state": {"buttonevent": 2002, "lastupdated": "2019-09-07T07:39:39"}, "swversion": "1.4.018", - CONF_TYPE: "ZHASwitch", + "type": "ZHASwitch", "uniqueid": "d0:cf:5e:ff:fe:71:a4:3a-01-1000", } } @@ -46,60 +45,86 @@ SENSORS = { async def test_get_triggers(hass, aioclient_mock): """Test triggers work.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data + data = { + "sensors": { + "1": { + "config": { + "alert": "none", + "battery": 60, + "group": "10", + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "1b355c0b6d2af28febd7ca9165881952", + "manufacturername": "IKEA of Sweden", + "mode": 1, + "modelid": "TRADFRI on/off switch", + "name": "TRÅDFRI on/off switch ", + "state": {"buttonevent": 2002, "lastupdated": "2019-09-07T07:39:39"}, + "swversion": "1.4.018", + "type": "ZHASwitch", + "uniqueid": "d0:cf:5e:ff:fe:71:a4:3a-01-1000", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, "d0:cf:5e:ff:fe:71:a4:3a")} ) - gateway = get_gateway_from_config_entry(hass, config_entry) - device_id = gateway.events[0].device_id - triggers = await async_get_device_automations(hass, "trigger", device_id) + + assert device_trigger._get_deconz_event_from_device_id(hass, device.id) + + triggers = await async_get_device_automations(hass, "trigger", device.id) expected_triggers = [ { - CONF_DEVICE_ID: device_id, + CONF_DEVICE_ID: device.id, CONF_DOMAIN: DECONZ_DOMAIN, CONF_PLATFORM: "device", CONF_TYPE: device_trigger.CONF_SHORT_PRESS, CONF_SUBTYPE: device_trigger.CONF_TURN_ON, }, { - CONF_DEVICE_ID: device_id, + CONF_DEVICE_ID: device.id, CONF_DOMAIN: DECONZ_DOMAIN, CONF_PLATFORM: "device", CONF_TYPE: device_trigger.CONF_LONG_PRESS, CONF_SUBTYPE: device_trigger.CONF_TURN_ON, }, { - CONF_DEVICE_ID: device_id, + CONF_DEVICE_ID: device.id, CONF_DOMAIN: DECONZ_DOMAIN, CONF_PLATFORM: "device", CONF_TYPE: device_trigger.CONF_LONG_RELEASE, CONF_SUBTYPE: device_trigger.CONF_TURN_ON, }, { - CONF_DEVICE_ID: device_id, + CONF_DEVICE_ID: device.id, CONF_DOMAIN: DECONZ_DOMAIN, CONF_PLATFORM: "device", CONF_TYPE: device_trigger.CONF_SHORT_PRESS, CONF_SUBTYPE: device_trigger.CONF_TURN_OFF, }, { - CONF_DEVICE_ID: device_id, + CONF_DEVICE_ID: device.id, CONF_DOMAIN: DECONZ_DOMAIN, CONF_PLATFORM: "device", CONF_TYPE: device_trigger.CONF_LONG_PRESS, CONF_SUBTYPE: device_trigger.CONF_TURN_OFF, }, { - CONF_DEVICE_ID: device_id, + CONF_DEVICE_ID: device.id, CONF_DOMAIN: DECONZ_DOMAIN, CONF_PLATFORM: "device", CONF_TYPE: device_trigger.CONF_LONG_RELEASE, CONF_SUBTYPE: device_trigger.CONF_TURN_OFF, }, { - CONF_DEVICE_ID: device_id, + CONF_DEVICE_ID: device.id, CONF_DOMAIN: SENSOR_DOMAIN, ATTR_ENTITY_ID: "sensor.tradfri_on_off_switch_battery_level", CONF_PLATFORM: "device", @@ -110,27 +135,14 @@ async def test_get_triggers(hass, aioclient_mock): assert_lists_same(triggers, expected_triggers) -async def test_helper_successful(hass, aioclient_mock): - """Verify trigger helper.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) - device_id = gateway.events[0].device_id - deconz_event = device_trigger._get_deconz_event_from_device_id(hass, device_id) - assert deconz_event == gateway.events[0] - - async def test_helper_no_match(hass, aioclient_mock): """Verify trigger helper returns None when no event could be matched.""" await setup_deconz_integration(hass, aioclient_mock) deconz_event = device_trigger._get_deconz_event_from_device_id(hass, "mock-id") - assert deconz_event is None + assert not deconz_event async def test_helper_no_gateway_exist(hass): """Verify trigger helper returns None when no gateway exist.""" deconz_event = device_trigger._get_deconz_event_from_device_id(hass, "mock-id") - assert deconz_event is None + assert not deconz_event diff --git a/tests/components/deconz/test_logbook.py b/tests/components/deconz/test_logbook.py index 5886a29a8bf..e8e5244a222 100644 --- a/tests/components/deconz/test_logbook.py +++ b/tests/components/deconz/test_logbook.py @@ -1,13 +1,13 @@ """The tests for deCONZ logbook.""" -from copy import deepcopy +from unittest.mock import patch from homeassistant.components import logbook -from homeassistant.components.deconz.const import CONF_GESTURE +from homeassistant.components.deconz.const import CONF_GESTURE, DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.deconz_event import CONF_DECONZ_EVENT -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.const import CONF_DEVICE_ID, CONF_EVENT, CONF_ID, CONF_UNIQUE_ID from homeassistant.setup import async_setup_component +from homeassistant.util import slugify from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration @@ -16,47 +16,68 @@ from tests.components.logbook.test_init import MockLazyEventPartialState async def test_humanifying_deconz_event(hass, aioclient_mock): """Test humanifying deCONZ event.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = { - "0": { - "id": "Switch 1 id", - "name": "Switch 1", - "type": "ZHASwitch", - "state": {"buttonevent": 1000}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, - "1": { - "id": "Hue remote id", - "name": "Hue remote", - "type": "ZHASwitch", - "modelid": "RWL021", - "state": {"buttonevent": 1000}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:02-00", - }, - "2": { - "id": "Xiaomi cube id", - "name": "Xiaomi cube", - "type": "ZHASwitch", - "modelid": "lumi.sensor_cube", - "state": {"buttonevent": 1000, "gesture": 1}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:03-00", - }, - "3": { - "id": "faulty", - "name": "Faulty event", - "type": "ZHASwitch", - "state": {}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:04-00", - }, + data = { + "sensors": { + "1": { + "name": "Switch 1", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + "2": { + "name": "Hue remote", + "type": "ZHASwitch", + "modelid": "RWL021", + "state": {"buttonevent": 1000}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:02-00", + }, + "3": { + "name": "Xiaomi cube", + "type": "ZHASwitch", + "modelid": "lumi.sensor_cube", + "state": {"buttonevent": 1000, "gesture": 1}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:03-00", + }, + "4": { + "name": "Faulty event", + "type": "ZHASwitch", + "state": {}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:04-00", + }, + } } - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) + + device_registry = await hass.helpers.device_registry.async_get_registry() + + switch_event_id = slugify(data["sensors"]["1"]["name"]) + switch_serial = data["sensors"]["1"]["uniqueid"].split("-", 1)[0] + switch_entry = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, switch_serial)} + ) + + hue_remote_event_id = slugify(data["sensors"]["2"]["name"]) + hue_remote_serial = data["sensors"]["2"]["uniqueid"].split("-", 1)[0] + hue_remote_entry = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, hue_remote_serial)} + ) + + xiaomi_cube_event_id = slugify(data["sensors"]["3"]["name"]) + xiaomi_cube_serial = data["sensors"]["3"]["uniqueid"].split("-", 1)[0] + xiaomi_cube_entry = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, xiaomi_cube_serial)} + ) + + faulty_event_id = slugify(data["sensors"]["4"]["name"]) + faulty_serial = data["sensors"]["4"]["uniqueid"].split("-", 1)[0] + faulty_entry = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, faulty_serial)} ) - gateway = get_gateway_from_config_entry(hass, config_entry) hass.config.components.add("recorder") assert await async_setup_component(hass, "logbook", {}) @@ -70,50 +91,50 @@ async def test_humanifying_deconz_event(hass, aioclient_mock): MockLazyEventPartialState( CONF_DECONZ_EVENT, { - CONF_DEVICE_ID: gateway.events[0].device_id, + CONF_DEVICE_ID: switch_entry.id, CONF_EVENT: 2000, - CONF_ID: gateway.events[0].event_id, - CONF_UNIQUE_ID: gateway.events[0].serial, + CONF_ID: switch_event_id, + CONF_UNIQUE_ID: switch_serial, }, ), # Event with matching device trigger MockLazyEventPartialState( CONF_DECONZ_EVENT, { - CONF_DEVICE_ID: gateway.events[1].device_id, + CONF_DEVICE_ID: hue_remote_entry.id, CONF_EVENT: 2001, - CONF_ID: gateway.events[1].event_id, - CONF_UNIQUE_ID: gateway.events[1].serial, + CONF_ID: hue_remote_event_id, + CONF_UNIQUE_ID: hue_remote_serial, }, ), # Gesture with matching device trigger MockLazyEventPartialState( CONF_DECONZ_EVENT, { - CONF_DEVICE_ID: gateway.events[2].device_id, + CONF_DEVICE_ID: xiaomi_cube_entry.id, CONF_GESTURE: 1, - CONF_ID: gateway.events[2].event_id, - CONF_UNIQUE_ID: gateway.events[2].serial, + CONF_ID: xiaomi_cube_event_id, + CONF_UNIQUE_ID: xiaomi_cube_serial, }, ), # Unsupported device trigger MockLazyEventPartialState( CONF_DECONZ_EVENT, { - CONF_DEVICE_ID: gateway.events[2].device_id, + CONF_DEVICE_ID: xiaomi_cube_entry.id, CONF_GESTURE: "unsupported_gesture", - CONF_ID: gateway.events[2].event_id, - CONF_UNIQUE_ID: gateway.events[2].serial, + CONF_ID: xiaomi_cube_event_id, + CONF_UNIQUE_ID: xiaomi_cube_serial, }, ), # Unknown event MockLazyEventPartialState( CONF_DECONZ_EVENT, { - CONF_DEVICE_ID: gateway.events[3].device_id, + CONF_DEVICE_ID: faulty_entry.id, "unknown_event": None, - CONF_ID: gateway.events[3].event_id, - CONF_UNIQUE_ID: gateway.events[3].serial, + CONF_ID: faulty_event_id, + CONF_UNIQUE_ID: faulty_serial, }, ), ], From 7350215b4e9f95cb697d67eaa095a92679225900 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 10:49:48 +0100 Subject: [PATCH 448/831] Use websocket fixture in deCONZ fan tests (#47824) Localize test data Improve asserts --- tests/components/deconz/test_fan.py | 75 ++++++++++++++--------------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/tests/components/deconz/test_fan.py b/tests/components/deconz/test_fan.py index c6acbb7f6aa..930645689f8 100644 --- a/tests/components/deconz/test_fan.py +++ b/tests/components/deconz/test_fan.py @@ -1,10 +1,9 @@ """deCONZ fan platform tests.""" -from copy import deepcopy +from unittest.mock import patch import pytest -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.fan import ( ATTR_SPEED, DOMAIN as FAN_DOMAIN, @@ -24,26 +23,6 @@ from .test_gateway import ( setup_deconz_integration, ) -FANS = { - "1": { - "etag": "432f3de28965052961a99e3c5494daf4", - "hascolor": False, - "manufacturername": "King Of Fans, Inc.", - "modelid": "HDC52EastwindFan", - "name": "Ceiling fan", - "state": { - "alert": "none", - "bri": 254, - "on": False, - "reachable": True, - "speed": 4, - }, - "swversion": "0000000F", - "type": "Fan", - "uniqueid": "00:22:a3:00:00:27:8b:81-01", - } -} - async def test_no_fans(hass, aioclient_mock): """Test that no fan entities are created.""" @@ -51,35 +30,51 @@ async def test_no_fans(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_fans(hass, aioclient_mock): +async def test_fans(hass, aioclient_mock, mock_deconz_websocket): """Test that all supported fan entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = deepcopy(FANS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "lights": { + "1": { + "etag": "432f3de28965052961a99e3c5494daf4", + "hascolor": False, + "manufacturername": "King Of Fans, Inc.", + "modelid": "HDC52EastwindFan", + "name": "Ceiling fan", + "state": { + "alert": "none", + "bri": 254, + "on": False, + "reachable": True, + "speed": 4, + }, + "swversion": "0000000F", + "type": "Fan", + "uniqueid": "00:22:a3:00:00:27:8b:81-01", + } + } + } + + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 2 # Light and fan - assert hass.states.get("fan.ceiling_fan") + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_HIGH # Test states - assert hass.states.get("fan.ceiling_fan").state == STATE_ON - assert hass.states.get("fan.ceiling_fan").attributes["speed"] == SPEED_HIGH - - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", "state": {"speed": 0}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() assert hass.states.get("fan.ceiling_fan").state == STATE_OFF - assert hass.states.get("fan.ceiling_fan").attributes["speed"] == SPEED_OFF + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_OFF # Test service calls @@ -157,23 +152,23 @@ async def test_fans(hass, aioclient_mock): # Events with an unsupported speed gets converted to default speed "medium" - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", "state": {"speed": 3}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() assert hass.states.get("fan.ceiling_fan").state == STATE_ON - assert hass.states.get("fan.ceiling_fan").attributes["speed"] == SPEED_MEDIUM + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_MEDIUM await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(hass.states.async_all()) == 2 + assert len(states) == 2 for state in states: assert state.state == STATE_UNAVAILABLE From 3f2d3bd1b2710e14ca5bb58f4386a23a0ae933d2 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 11:01:32 +0100 Subject: [PATCH 449/831] Use websocket fixture in deCONZ light tests (#47826) Localize test data Improve asserts# --- tests/components/deconz/test_light.py | 526 ++++++++++++++------------ 1 file changed, 284 insertions(+), 242 deletions(-) diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py index c7f7fab1868..84c7a9b1078 100644 --- a/tests/components/deconz/test_light.py +++ b/tests/components/deconz/test_light.py @@ -1,11 +1,10 @@ """deCONZ light platform tests.""" -from copy import deepcopy +from unittest.mock import patch import pytest from homeassistant.components.deconz.const import CONF_ALLOW_DECONZ_GROUPS -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, @@ -36,75 +35,6 @@ from .test_gateway import ( setup_deconz_integration, ) -GROUPS = { - "1": { - "id": "Light group id", - "name": "Light group", - "type": "LightGroup", - "state": {"all_on": False, "any_on": True}, - "action": {}, - "scenes": [], - "lights": ["1", "2"], - }, - "2": { - "id": "Empty group id", - "name": "Empty group", - "type": "LightGroup", - "state": {}, - "action": {}, - "scenes": [], - "lights": [], - }, -} - -LIGHTS = { - "1": { - "id": "RGB light id", - "name": "RGB light", - "state": { - "on": True, - "bri": 255, - "colormode": "xy", - "effect": "colorloop", - "xy": (500, 500), - "reachable": True, - }, - "type": "Extended color light", - "uniqueid": "00:00:00:00:00:00:00:00-00", - }, - "2": { - "ctmax": 454, - "ctmin": 155, - "id": "Tunable white light id", - "name": "Tunable white light", - "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, - "type": "Tunable white light", - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, - "3": { - "id": "On off switch id", - "name": "On off switch", - "type": "On/Off plug-in unit", - "state": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:02-00", - }, - "4": { - "name": "On off light", - "state": {"on": True, "reachable": True}, - "type": "On and Off light", - "uniqueid": "00:00:00:00:00:00:00:03-00", - }, - "5": { - "ctmax": 1000, - "ctmin": 0, - "id": "Tunable white light with bad maxmin values id", - "name": "Tunable white light with bad maxmin values", - "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, - "type": "Tunable white light", - "uniqueid": "00:00:00:00:00:00:00:04-00", - }, -} - async def test_no_lights_or_groups(hass, aioclient_mock): """Test that no lights or groups entities are created.""" @@ -112,15 +42,75 @@ async def test_no_lights_or_groups(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_lights_and_groups(hass, aioclient_mock): +async def test_lights_and_groups(hass, aioclient_mock, mock_deconz_websocket): """Test that lights or groups entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["groups"] = deepcopy(GROUPS) - data["lights"] = deepcopy(LIGHTS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "groups": { + "1": { + "id": "Light group id", + "name": "Light group", + "type": "LightGroup", + "state": {"all_on": False, "any_on": True}, + "action": {}, + "scenes": [], + "lights": ["1", "2"], + }, + "2": { + "id": "Empty group id", + "name": "Empty group", + "type": "LightGroup", + "state": {}, + "action": {}, + "scenes": [], + "lights": [], + }, + }, + "lights": { + "1": { + "name": "RGB light", + "state": { + "on": True, + "bri": 255, + "colormode": "xy", + "effect": "colorloop", + "xy": (500, 500), + "reachable": True, + }, + "type": "Extended color light", + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + "2": { + "ctmax": 454, + "ctmin": 155, + "name": "Tunable white light", + "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, + "type": "Tunable white light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + "3": { + "name": "On off switch", + "type": "On/Off plug-in unit", + "state": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:02-00", + }, + "4": { + "name": "On off light", + "state": {"on": True, "reachable": True}, + "type": "On and Off light", + "uniqueid": "00:00:00:00:00:00:00:03-00", + }, + "5": { + "ctmax": 1000, + "ctmin": 0, + "name": "Tunable white light with bad maxmin values", + "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, + "type": "Tunable white light", + "uniqueid": "00:00:00:00:00:00:00:04-00", + }, + }, + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 6 @@ -151,25 +141,23 @@ async def test_lights_and_groups(hass, aioclient_mock): assert on_off_light.state == STATE_ON assert on_off_light.attributes[ATTR_SUPPORTED_FEATURES] == 0 - light_group = hass.states.get("light.light_group") - assert light_group.state == STATE_ON - assert light_group.attributes["all_on"] is False + assert hass.states.get("light.light_group").state == STATE_ON + assert hass.states.get("light.light_group").attributes["all_on"] is False empty_group = hass.states.get("light.empty_group") assert empty_group is None - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", "state": {"on": False}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() - rgb_light = hass.states.get("light.rgb_light") - assert rgb_light.state == STATE_OFF + assert hass.states.get("light.rgb_light").state == STATE_OFF # Verify service calls @@ -231,14 +219,14 @@ async def test_lights_and_groups(hass, aioclient_mock): ) assert len(aioclient_mock.mock_calls) == 3 # Not called - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", "state": {"on": True}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() # Service turn off light with short flashing @@ -272,7 +260,7 @@ async def test_lights_and_groups(hass, aioclient_mock): await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(hass.states.async_all()) == 6 + assert len(states) == 6 for state in states: assert state.state == STATE_UNAVAILABLE @@ -283,28 +271,56 @@ async def test_lights_and_groups(hass, aioclient_mock): async def test_disable_light_groups(hass, aioclient_mock): """Test disallowing light groups work.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["groups"] = deepcopy(GROUPS) - data["lights"] = deepcopy(LIGHTS) - config_entry = await setup_deconz_integration( - hass, - aioclient_mock, - options={CONF_ALLOW_DECONZ_GROUPS: False}, - get_state_response=data, - ) + data = { + "groups": { + "1": { + "id": "Light group id", + "name": "Light group", + "type": "LightGroup", + "state": {"all_on": False, "any_on": True}, + "action": {}, + "scenes": [], + "lights": ["1"], + }, + "2": { + "id": "Empty group id", + "name": "Empty group", + "type": "LightGroup", + "state": {}, + "action": {}, + "scenes": [], + "lights": [], + }, + }, + "lights": { + "1": { + "ctmax": 454, + "ctmin": 155, + "name": "Tunable white light", + "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, + "type": "Tunable white light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + }, + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration( + hass, + aioclient_mock, + options={CONF_ALLOW_DECONZ_GROUPS: False}, + ) - assert len(hass.states.async_all()) == 5 - assert hass.states.get("light.rgb_light") + assert len(hass.states.async_all()) == 1 assert hass.states.get("light.tunable_white_light") - assert hass.states.get("light.light_group") is None - assert hass.states.get("light.empty_group") is None + assert not hass.states.get("light.light_group") + assert not hass.states.get("light.empty_group") hass.config_entries.async_update_entry( config_entry, options={CONF_ALLOW_DECONZ_GROUPS: True} ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 6 + assert len(hass.states.async_all()) == 2 assert hass.states.get("light.light_group") hass.config_entries.async_update_entry( @@ -312,62 +328,96 @@ async def test_disable_light_groups(hass, aioclient_mock): ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 5 - assert hass.states.get("light.light_group") is None + assert len(hass.states.async_all()) == 1 + assert not hass.states.get("light.light_group") async def test_configuration_tool(hass, aioclient_mock): - """Test that lights or groups entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = { - "0": { - "etag": "26839cb118f5bf7ba1f2108256644010", - "hascolor": False, - "lastannounced": None, - "lastseen": "2020-11-22T11:27Z", - "manufacturername": "dresden elektronik", - "modelid": "ConBee II", - "name": "Configuration tool 1", - "state": {"reachable": True}, - "swversion": "0x264a0700", - "type": "Configuration tool", - "uniqueid": "00:21:2e:ff:ff:05:a7:a3-01", + """Test that configuration tool is not created.""" + data = { + "lights": { + "0": { + "etag": "26839cb118f5bf7ba1f2108256644010", + "hascolor": False, + "lastannounced": None, + "lastseen": "2020-11-22T11:27Z", + "manufacturername": "dresden elektronik", + "modelid": "ConBee II", + "name": "Configuration tool 1", + "state": {"reachable": True}, + "swversion": "0x264a0700", + "type": "Configuration tool", + "uniqueid": "00:21:2e:ff:ff:05:a7:a3-01", + } } } - await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 0 +async def test_ikea_default_transition_time(hass, aioclient_mock): + """Verify that service calls to IKEA lights always extend with transition tinme 0 if absent.""" + data = { + "lights": { + "1": { + "manufacturername": "IKEA", + "name": "Dimmable light", + "state": {"on": True, "bri": 255, "reachable": True}, + "type": "Dimmable light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + }, + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1/state") + + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.dimmable_light", ATTR_BRIGHTNESS: 100}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == { + "bri": 100, + "on": True, + "transitiontime": 0, + } + + async def test_lidl_christmas_light(hass, aioclient_mock): """Test that lights or groups entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = { - "0": { - "etag": "87a89542bf9b9d0aa8134919056844f8", - "hascolor": True, - "lastannounced": None, - "lastseen": "2020-12-05T22:57Z", - "manufacturername": "_TZE200_s8gkrkxk", - "modelid": "TS0601", - "name": "xmas light", - "state": { - "bri": 25, - "colormode": "hs", - "effect": "none", - "hue": 53691, - "on": True, - "reachable": True, - "sat": 141, - }, - "swversion": None, - "type": "Color dimmable light", - "uniqueid": "58:8e:81:ff:fe:db:7b:be-01", + data = { + "lights": { + "0": { + "etag": "87a89542bf9b9d0aa8134919056844f8", + "hascolor": True, + "lastannounced": None, + "lastseen": "2020-12-05T22:57Z", + "manufacturername": "_TZE200_s8gkrkxk", + "modelid": "TS0601", + "name": "xmas light", + "state": { + "bri": 25, + "colormode": "hs", + "effect": "none", + "hue": 53691, + "on": True, + "reachable": True, + "sat": 141, + }, + "swversion": None, + "type": "Color dimmable light", + "uniqueid": "58:8e:81:ff:fe:db:7b:be-01", + } } } - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) + + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/0/state") @@ -385,100 +435,98 @@ async def test_lidl_christmas_light(hass, aioclient_mock): assert hass.states.get("light.xmas_light") -async def test_non_color_light_reports_color(hass, aioclient_mock): +async def test_non_color_light_reports_color( + hass, aioclient_mock, mock_deconz_websocket +): """Verify hs_color does not crash when a group gets updated with a bad color value. After calling a scene color temp light of certain manufacturers report color temp in color space. """ - data = deepcopy(DECONZ_WEB_REQUEST) - - data["groups"] = { - "0": { - "action": { - "alert": "none", - "bri": 127, - "colormode": "hs", - "ct": 0, - "effect": "none", - "hue": 0, - "on": True, - "sat": 127, - "scene": None, - "xy": [0, 0], - }, - "devicemembership": [], - "etag": "81e42cf1b47affb72fa72bc2e25ba8bf", - "id": "0", - "lights": ["0", "1"], - "name": "All", - "scenes": [], - "state": {"all_on": False, "any_on": True}, - "type": "LightGroup", - } - } - - data["lights"] = { - "0": { - "ctmax": 500, - "ctmin": 153, - "etag": "026bcfe544ad76c7534e5ca8ed39047c", - "hascolor": True, - "manufacturername": "dresden elektronik", - "modelid": "FLS-PP3", - "name": "Light 1", - "pointsymbol": {}, - "state": { - "alert": None, - "bri": 111, - "colormode": "ct", - "ct": 307, - "effect": None, + data = { + "groups": { + "0": { + "action": { + "alert": "none", + "bri": 127, + "colormode": "hs", + "ct": 0, + "effect": "none", + "hue": 0, + "on": True, + "sat": 127, + "scene": None, + "xy": [0, 0], + }, + "devicemembership": [], + "etag": "81e42cf1b47affb72fa72bc2e25ba8bf", + "lights": ["0", "1"], + "name": "All", + "scenes": [], + "state": {"all_on": False, "any_on": True}, + "type": "LightGroup", + } + }, + "lights": { + "0": { + "ctmax": 500, + "ctmin": 153, + "etag": "026bcfe544ad76c7534e5ca8ed39047c", "hascolor": True, - "hue": 7998, - "on": False, - "reachable": True, - "sat": 172, - "xy": [0.421253, 0.39921], + "manufacturername": "dresden elektronik", + "modelid": "FLS-PP3", + "name": "Light 1", + "pointsymbol": {}, + "state": { + "alert": None, + "bri": 111, + "colormode": "ct", + "ct": 307, + "effect": None, + "hascolor": True, + "hue": 7998, + "on": False, + "reachable": True, + "sat": 172, + "xy": [0.421253, 0.39921], + }, + "swversion": "020C.201000A0", + "type": "Extended color light", + "uniqueid": "00:21:2E:FF:FF:EE:DD:CC-0A", }, - "swversion": "020C.201000A0", - "type": "Extended color light", - "uniqueid": "00:21:2E:FF:FF:EE:DD:CC-0A", - }, - "1": { - "colorcapabilities": 0, - "ctmax": 65535, - "ctmin": 0, - "etag": "9dd510cd474791481f189d2a68a3c7f1", - "hascolor": True, - "lastannounced": "2020-12-17T17:44:38Z", - "lastseen": "2021-01-11T18:36Z", - "manufacturername": "IKEA of Sweden", - "modelid": "TRADFRI bulb E27 WS opal 1000lm", - "name": "Küchenlicht", - "state": { - "alert": "none", - "bri": 156, - "colormode": "ct", - "ct": 250, - "on": True, - "reachable": True, + "1": { + "colorcapabilities": 0, + "ctmax": 65535, + "ctmin": 0, + "etag": "9dd510cd474791481f189d2a68a3c7f1", + "hascolor": True, + "lastannounced": "2020-12-17T17:44:38Z", + "lastseen": "2021-01-11T18:36Z", + "manufacturername": "IKEA of Sweden", + "modelid": "TRADFRI bulb E27 WS opal 1000lm", + "name": "Küchenlicht", + "state": { + "alert": "none", + "bri": 156, + "colormode": "ct", + "ct": 250, + "on": True, + "reachable": True, + }, + "swversion": "2.0.022", + "type": "Color temperature light", + "uniqueid": "ec:1b:bd:ff:fe:ee:ed:dd-01", }, - "swversion": "2.0.022", - "type": "Color temperature light", - "uniqueid": "ec:1b:bd:ff:fe:ee:ed:dd-01", }, } - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 3 assert hass.states.get("light.all").attributes[ATTR_COLOR_TEMP] == 307 # Updating a scene will return a faulty color value for a non-color light causing an exception in hs_color - state_changed_event = { + event_changed_light = { "e": "changed", "id": "1", "r": "lights", @@ -493,7 +541,7 @@ async def test_non_color_light_reports_color(hass, aioclient_mock): "t": "event", "uniqueid": "ec:1b:bd:ff:fe:ee:ed:dd-01", } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() # Bug is fixed if we reach this point, but device won't have neither color temp nor color @@ -504,9 +552,8 @@ async def test_non_color_light_reports_color(hass, aioclient_mock): async def test_verify_group_supported_features(hass, aioclient_mock): """Test that group supported features reflect what included lights support.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["groups"] = deepcopy( - { + data = { + "groups": { "1": { "id": "Group1", "name": "group", @@ -516,19 +563,15 @@ async def test_verify_group_supported_features(hass, aioclient_mock): "scenes": [], "lights": ["1", "2", "3"], }, - } - ) - data["lights"] = deepcopy( - { + }, + "lights": { "1": { - "id": "light1", "name": "Dimmable light", "state": {"on": True, "bri": 255, "reachable": True}, "type": "Light", "uniqueid": "00:00:00:00:00:00:00:01-00", }, "2": { - "id": "light2", "name": "Color light", "state": { "on": True, @@ -544,18 +587,17 @@ async def test_verify_group_supported_features(hass, aioclient_mock): "3": { "ctmax": 454, "ctmin": 155, - "id": "light3", "name": "Tunable light", "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, "type": "Tunable white light", "uniqueid": "00:00:00:00:00:00:00:03-00", }, - } - ) - await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) + }, + } + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 4 - group = hass.states.get("light.group") - assert group.state == STATE_ON - assert group.attributes[ATTR_SUPPORTED_FEATURES] == 63 + assert hass.states.get("light.group").state == STATE_ON + assert hass.states.get("light.group").attributes[ATTR_SUPPORTED_FEATURES] == 63 From a21d0cadf8795a44649dcade50744250296d3246 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 11:06:44 +0100 Subject: [PATCH 450/831] Use websocket fixture in deCONZ lock tests (#47827) Localize test data Improve asserts --- tests/components/deconz/test_lock.py | 55 ++++++++++++---------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/tests/components/deconz/test_lock.py b/tests/components/deconz/test_lock.py index a6b4caaec19..1b4bf3c160c 100644 --- a/tests/components/deconz/test_lock.py +++ b/tests/components/deconz/test_lock.py @@ -1,8 +1,7 @@ """deCONZ lock platform tests.""" -from copy import deepcopy +from unittest.mock import patch -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.lock import ( DOMAIN as LOCK_DOMAIN, SERVICE_LOCK, @@ -21,22 +20,6 @@ from .test_gateway import ( setup_deconz_integration, ) -LOCKS = { - "1": { - "etag": "5c2ec06cde4bd654aef3a555fcd8ad12", - "hascolor": False, - "lastannounced": None, - "lastseen": "2020-08-22T15:29:03Z", - "manufacturername": "Danalock", - "modelid": "V3-BTZB", - "name": "Door lock", - "state": {"alert": "none", "on": False, "reachable": True}, - "swversion": "19042019", - "type": "Door Lock", - "uniqueid": "00:00:00:00:00:00:00:00-00", - } -} - async def test_no_locks(hass, aioclient_mock): """Test that no lock entities are created.""" @@ -44,29 +27,39 @@ async def test_no_locks(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_locks(hass, aioclient_mock): +async def test_locks(hass, aioclient_mock, mock_deconz_websocket): """Test that all supported lock entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = deepcopy(LOCKS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "lights": { + "1": { + "etag": "5c2ec06cde4bd654aef3a555fcd8ad12", + "hascolor": False, + "lastannounced": None, + "lastseen": "2020-08-22T15:29:03Z", + "manufacturername": "Danalock", + "modelid": "V3-BTZB", + "name": "Door lock", + "state": {"alert": "none", "on": False, "reachable": True}, + "swversion": "19042019", + "type": "Door Lock", + "uniqueid": "00:00:00:00:00:00:00:00-00", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 1 assert hass.states.get("lock.door_lock").state == STATE_UNLOCKED - door_lock = hass.states.get("lock.door_lock") - assert door_lock.state == STATE_UNLOCKED - - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", "state": {"on": True}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() assert hass.states.get("lock.door_lock").state == STATE_LOCKED @@ -98,7 +91,7 @@ async def test_locks(hass, aioclient_mock): await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(hass.states.async_all()) == 1 + assert len(states) == 1 for state in states: assert state.state == STATE_UNAVAILABLE From a4b2dff58d9c59c101e3cf0b3812b1d1e8197b50 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 11:41:27 +0100 Subject: [PATCH 451/831] Use websocket fixture in deCONZ sensor tests (#47830) Localize test data Improve asserts --- tests/components/deconz/test_sensor.py | 634 +++++++++++++------------ 1 file changed, 333 insertions(+), 301 deletions(-) diff --git a/tests/components/deconz/test_sensor.py b/tests/components/deconz/test_sensor.py index 1a521946335..e9a35337aef 100644 --- a/tests/components/deconz/test_sensor.py +++ b/tests/components/deconz/test_sensor.py @@ -1,86 +1,21 @@ """deCONZ sensor platform tests.""" -from copy import deepcopy +from unittest.mock import patch from homeassistant.components.deconz.const import CONF_ALLOW_CLIP_SENSOR -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry +from homeassistant.components.deconz.sensor import ATTR_DAYLIGHT from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ( + ATTR_DEVICE_CLASS, DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, STATE_UNAVAILABLE, + STATE_UNKNOWN, ) from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration -SENSORS = { - "1": { - "id": "Light sensor id", - "name": "Light level sensor", - "type": "ZHALightLevel", - "state": {"lightlevel": 30000, "dark": False}, - "config": {"on": True, "reachable": True, "temperature": 10}, - "uniqueid": "00:00:00:00:00:00:00:00-00", - }, - "2": { - "id": "Presence sensor id", - "name": "Presence sensor", - "type": "ZHAPresence", - "state": {"presence": False}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, - "3": { - "id": "Switch 1 id", - "name": "Switch 1", - "type": "ZHASwitch", - "state": {"buttonevent": 1000}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:02-00", - }, - "4": { - "id": "Switch 2 id", - "name": "Switch 2", - "type": "ZHASwitch", - "state": {"buttonevent": 1000}, - "config": {"battery": 100}, - "uniqueid": "00:00:00:00:00:00:00:03-00", - }, - "5": { - "id": "Daylight sensor id", - "name": "Daylight sensor", - "type": "Daylight", - "state": {"daylight": True, "status": 130}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:04-00", - }, - "6": { - "id": "Power sensor id", - "name": "Power sensor", - "type": "ZHAPower", - "state": {"current": 2, "power": 6, "voltage": 3}, - "config": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:05-00", - }, - "7": { - "id": "Consumption id", - "name": "Consumption sensor", - "type": "ZHAConsumption", - "state": {"consumption": 2, "power": 6}, - "config": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:06-00", - }, - "8": { - "id": "CLIP light sensor id", - "name": "CLIP light level sensor", - "type": "CLIPLightLevel", - "state": {"lightlevel": 30000}, - "config": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:07-00", - }, -} - async def test_no_sensors(hass, aioclient_mock): """Test that no sensors in deconz results in no sensor entities.""" @@ -88,76 +23,138 @@ async def test_no_sensors(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_sensors(hass, aioclient_mock): +async def test_sensors(hass, aioclient_mock, mock_deconz_websocket): """Test successful creation of sensor entities.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "sensors": { + "1": { + "name": "Light level sensor", + "type": "ZHALightLevel", + "state": {"daylight": 6955, "lightlevel": 30000, "dark": False}, + "config": {"on": True, "reachable": True, "temperature": 10}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + "2": { + "name": "Presence sensor", + "type": "ZHAPresence", + "state": {"presence": False}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + "3": { + "name": "Switch 1", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:02-00", + }, + "4": { + "name": "Switch 2", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {"battery": 100}, + "uniqueid": "00:00:00:00:00:00:00:03-00", + }, + "5": { + "name": "Daylight sensor", + "type": "Daylight", + "state": {"daylight": True, "status": 130}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:04-00", + }, + "6": { + "name": "Power sensor", + "type": "ZHAPower", + "state": {"current": 2, "power": 6, "voltage": 3}, + "config": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:05-00", + }, + "7": { + "name": "Consumption sensor", + "type": "ZHAConsumption", + "state": {"consumption": 2, "power": 6}, + "config": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:06-00", + }, + "8": { + "id": "CLIP light sensor id", + "name": "CLIP light level sensor", + "type": "CLIPLightLevel", + "state": {"lightlevel": 30000}, + "config": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:07-00", + }, + } + } + + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 5 light_level_sensor = hass.states.get("sensor.light_level_sensor") assert light_level_sensor.state == "999.8" - assert light_level_sensor.attributes["device_class"] == DEVICE_CLASS_ILLUMINANCE + assert light_level_sensor.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_ILLUMINANCE + assert light_level_sensor.attributes[ATTR_DAYLIGHT] == 6955 - assert hass.states.get("sensor.presence_sensor") is None - assert hass.states.get("sensor.switch_1") is None - assert hass.states.get("sensor.switch_1_battery_level") is None - assert hass.states.get("sensor.switch_2") is None + assert not hass.states.get("sensor.presence_sensor") + assert not hass.states.get("sensor.switch_1") + assert not hass.states.get("sensor.switch_1_battery_level") + assert not hass.states.get("sensor.switch_2") switch_2_battery_level = hass.states.get("sensor.switch_2_battery_level") assert switch_2_battery_level.state == "100" - assert switch_2_battery_level.attributes["device_class"] == DEVICE_CLASS_BATTERY + assert switch_2_battery_level.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_BATTERY - assert hass.states.get("sensor.daylight_sensor") is None + assert not hass.states.get("sensor.daylight_sensor") power_sensor = hass.states.get("sensor.power_sensor") assert power_sensor.state == "6" - assert power_sensor.attributes["device_class"] == DEVICE_CLASS_POWER + assert power_sensor.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_POWER consumption_sensor = hass.states.get("sensor.consumption_sensor") assert consumption_sensor.state == "0.002" - assert "device_class" not in consumption_sensor.attributes + assert ATTR_DEVICE_CLASS not in consumption_sensor.attributes - assert hass.states.get("sensor.clip_light_level_sensor") is None + assert not hass.states.get("sensor.clip_light_level_sensor") # Event signals new light level - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "1", "state": {"lightlevel": 2000}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) assert hass.states.get("sensor.light_level_sensor").state == "1.6" # Event signals new battery level - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "4", "config": {"battery": 75}, } - gateway.api.event_handler(state_changed_event) - await hass.async_block_till_done() + await mock_deconz_websocket(data=event_changed_sensor) assert hass.states.get("sensor.switch_2_battery_level").state == "75" + # Unload entry + await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(hass.states.async_all()) == 5 + assert len(states) == 5 for state in states: assert state.state == STATE_UNAVAILABLE + # Remove entry + await hass.config_entries.async_remove(config_entry.entry_id) await hass.async_block_till_done() assert len(hass.states.async_all()) == 0 @@ -165,16 +162,33 @@ async def test_sensors(hass, aioclient_mock): async def test_allow_clip_sensors(hass, aioclient_mock): """Test that CLIP sensors can be allowed.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, - aioclient_mock, - options={CONF_ALLOW_CLIP_SENSOR: True}, - get_state_response=data, - ) + data = { + "sensors": { + "1": { + "name": "Light level sensor", + "type": "ZHALightLevel", + "state": {"lightlevel": 30000, "dark": False}, + "config": {"on": True, "reachable": True, "temperature": 10}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + "2": { + "id": "CLIP light sensor id", + "name": "CLIP light level sensor", + "type": "CLIPLightLevel", + "state": {"lightlevel": 30000}, + "config": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration( + hass, + aioclient_mock, + options={CONF_ALLOW_CLIP_SENSOR: True}, + ) - assert len(hass.states.async_all()) == 6 + assert len(hass.states.async_all()) == 2 assert hass.states.get("sensor.clip_light_level_sensor").state == "999.8" # Disallow clip sensors @@ -184,8 +198,8 @@ async def test_allow_clip_sensors(hass, aioclient_mock): ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 5 - assert hass.states.get("sensor.clip_light_level_sensor") is None + assert len(hass.states.async_all()) == 1 + assert not hass.states.get("sensor.clip_light_level_sensor") # Allow clip sensors @@ -194,52 +208,70 @@ async def test_allow_clip_sensors(hass, aioclient_mock): ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 6 - assert hass.states.get("sensor.clip_light_level_sensor") + assert len(hass.states.async_all()) == 2 + assert hass.states.get("sensor.clip_light_level_sensor").state == "999.8" -async def test_add_new_sensor(hass, aioclient_mock): +async def test_add_new_sensor(hass, aioclient_mock, mock_deconz_websocket): """Test that adding a new sensor works.""" - config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway = get_gateway_from_config_entry(hass, config_entry) - assert len(hass.states.async_all()) == 0 - - state_added_event = { + event_added_sensor = { "t": "event", "e": "added", "r": "sensors", "id": "1", - "sensor": deepcopy(SENSORS["1"]), + "sensor": { + "id": "Light sensor id", + "name": "Light level sensor", + "type": "ZHALightLevel", + "state": {"lightlevel": 30000, "dark": False}, + "config": {"on": True, "reachable": True, "temperature": 10}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, } - gateway.api.event_handler(state_added_event) + + await setup_deconz_integration(hass, aioclient_mock) + + assert len(hass.states.async_all()) == 0 + + await mock_deconz_websocket(data=event_added_sensor) await hass.async_block_till_done() assert len(hass.states.async_all()) == 1 assert hass.states.get("sensor.light_level_sensor").state == "999.8" -async def test_add_battery_later(hass, aioclient_mock): +async def test_add_battery_later(hass, aioclient_mock, mock_deconz_websocket): """Test that a sensor without an initial battery state creates a battery sensor once state exist.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = {"1": deepcopy(SENSORS["3"])} - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) - remote = gateway.api.sensors["1"] + data = { + "sensors": { + "1": { + "name": "Switch 1", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 0 - assert len(gateway.events) == 1 - assert len(remote._callbacks) == 2 # Event and battery tracker + assert not hass.states.get("sensor.switch_1_battery_level") - remote.update({"config": {"battery": 50}}) + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "1", + "config": {"battery": 50}, + } + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert len(hass.states.async_all()) == 1 - assert len(gateway.events) == 1 - assert len(remote._callbacks) == 2 # Event and battery entity - assert hass.states.get("sensor.switch_1_battery_level") + assert hass.states.get("sensor.switch_1_battery_level").state == "50" async def test_special_danfoss_battery_creation(hass, aioclient_mock): @@ -248,131 +280,133 @@ async def test_special_danfoss_battery_creation(hass, aioclient_mock): Normally there should only be one battery sensor per device from deCONZ. With specific Danfoss devices each endpoint can report its own battery state. """ - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = { - "1": { - "config": { - "battery": 70, - "heatsetpoint": 2300, - "offset": 0, - "on": True, - "reachable": True, - "schedule": {}, - "schedule_on": False, + data = { + "sensors": { + "1": { + "config": { + "battery": 70, + "heatsetpoint": 2300, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 1, + "etag": "982d9acc38bee5b251e24a9be26558e4", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": { + "lastupdated": "2021-02-15T12:23:07.994", + "on": False, + "temperature": 2307, + }, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-01-0201", }, - "ep": 1, - "etag": "982d9acc38bee5b251e24a9be26558e4", - "lastseen": "2021-02-15T12:23Z", - "manufacturername": "Danfoss", - "modelid": "0x8030", - "name": "0x8030", - "state": { - "lastupdated": "2021-02-15T12:23:07.994", - "on": False, - "temperature": 2307, + "2": { + "config": { + "battery": 86, + "heatsetpoint": 2300, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 2, + "etag": "62f12749f9f51c950086aff37dd02b61", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": { + "lastupdated": "2021-02-15T12:23:22.399", + "on": False, + "temperature": 2316, + }, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-02-0201", }, - "swversion": "YYYYMMDD", - "type": "ZHAThermostat", - "uniqueid": "58:8e:81:ff:fe:00:11:22-01-0201", - }, - "2": { - "config": { - "battery": 86, - "heatsetpoint": 2300, - "offset": 0, - "on": True, - "reachable": True, - "schedule": {}, - "schedule_on": False, + "3": { + "config": { + "battery": 86, + "heatsetpoint": 2350, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 3, + "etag": "f50061174bb7f18a3d95789bab8b646d", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": { + "lastupdated": "2021-02-15T12:23:25.466", + "on": False, + "temperature": 2337, + }, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-03-0201", }, - "ep": 2, - "etag": "62f12749f9f51c950086aff37dd02b61", - "lastseen": "2021-02-15T12:23Z", - "manufacturername": "Danfoss", - "modelid": "0x8030", - "name": "0x8030", - "state": { - "lastupdated": "2021-02-15T12:23:22.399", - "on": False, - "temperature": 2316, + "4": { + "config": { + "battery": 85, + "heatsetpoint": 2300, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 4, + "etag": "eea97adf8ce1b971b8b6a3a31793f96b", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": { + "lastupdated": "2021-02-15T12:23:41.939", + "on": False, + "temperature": 2333, + }, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-04-0201", }, - "swversion": "YYYYMMDD", - "type": "ZHAThermostat", - "uniqueid": "58:8e:81:ff:fe:00:11:22-02-0201", - }, - "3": { - "config": { - "battery": 86, - "heatsetpoint": 2350, - "offset": 0, - "on": True, - "reachable": True, - "schedule": {}, - "schedule_on": False, + "5": { + "config": { + "battery": 83, + "heatsetpoint": 2300, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 5, + "etag": "1f7cd1a5d66dc27ac5eb44b8c47362fb", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": {"lastupdated": "none", "on": False, "temperature": 2325}, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-05-0201", }, - "ep": 3, - "etag": "f50061174bb7f18a3d95789bab8b646d", - "lastseen": "2021-02-15T12:23Z", - "manufacturername": "Danfoss", - "modelid": "0x8030", - "name": "0x8030", - "state": { - "lastupdated": "2021-02-15T12:23:25.466", - "on": False, - "temperature": 2337, - }, - "swversion": "YYYYMMDD", - "type": "ZHAThermostat", - "uniqueid": "58:8e:81:ff:fe:00:11:22-03-0201", - }, - "4": { - "config": { - "battery": 85, - "heatsetpoint": 2300, - "offset": 0, - "on": True, - "reachable": True, - "schedule": {}, - "schedule_on": False, - }, - "ep": 4, - "etag": "eea97adf8ce1b971b8b6a3a31793f96b", - "lastseen": "2021-02-15T12:23Z", - "manufacturername": "Danfoss", - "modelid": "0x8030", - "name": "0x8030", - "state": { - "lastupdated": "2021-02-15T12:23:41.939", - "on": False, - "temperature": 2333, - }, - "swversion": "YYYYMMDD", - "type": "ZHAThermostat", - "uniqueid": "58:8e:81:ff:fe:00:11:22-04-0201", - }, - "5": { - "config": { - "battery": 83, - "heatsetpoint": 2300, - "offset": 0, - "on": True, - "reachable": True, - "schedule": {}, - "schedule_on": False, - }, - "ep": 5, - "etag": "1f7cd1a5d66dc27ac5eb44b8c47362fb", - "lastseen": "2021-02-15T12:23Z", - "manufacturername": "Danfoss", - "modelid": "0x8030", - "name": "0x8030", - "state": {"lastupdated": "none", "on": False, "temperature": 2325}, - "swversion": "YYYYMMDD", - "type": "ZHAThermostat", - "uniqueid": "58:8e:81:ff:fe:00:11:22-05-0201", - }, + } } - await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 10 assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 5 @@ -380,77 +414,75 @@ async def test_special_danfoss_battery_creation(hass, aioclient_mock): async def test_air_quality_sensor(hass, aioclient_mock): """Test successful creation of air quality sensor entities.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = { - "0": { - "config": {"on": True, "reachable": True}, - "ep": 2, - "etag": "c2d2e42396f7c78e11e46c66e2ec0200", - "lastseen": "2020-11-20T22:48Z", - "manufacturername": "BOSCH", - "modelid": "AIR", - "name": "Air quality", - "state": { - "airquality": "poor", - "airqualityppb": 809, - "lastupdated": "2020-11-20T22:48:00.209", - }, - "swversion": "20200402", - "type": "ZHAAirQuality", - "uniqueid": "00:12:4b:00:14:4d:00:07-02-fdef", + data = { + "sensors": { + "0": { + "config": {"on": True, "reachable": True}, + "ep": 2, + "etag": "c2d2e42396f7c78e11e46c66e2ec0200", + "lastseen": "2020-11-20T22:48Z", + "manufacturername": "BOSCH", + "modelid": "AIR", + "name": "Air quality", + "state": { + "airquality": "poor", + "airqualityppb": 809, + "lastupdated": "2020-11-20T22:48:00.209", + }, + "swversion": "20200402", + "type": "ZHAAirQuality", + "uniqueid": "00:12:4b:00:14:4d:00:07-02-fdef", + } } } - await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 1 - - air_quality = hass.states.get("sensor.air_quality") - assert air_quality.state == "poor" + assert hass.states.get("sensor.air_quality").state == "poor" async def test_time_sensor(hass, aioclient_mock): """Test successful creation of time sensor entities.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = { - "0": { - "config": {"battery": 40, "on": True, "reachable": True}, - "ep": 1, - "etag": "28e796678d9a24712feef59294343bb6", - "lastseen": "2020-11-22T11:26Z", - "manufacturername": "Danfoss", - "modelid": "eTRV0100", - "name": "Time", - "state": { - "lastset": "2020-11-19T08:07:08Z", - "lastupdated": "2020-11-22T10:51:03.444", - "localtime": "2020-11-22T10:51:01", - "utc": "2020-11-22T10:51:01Z", - }, - "swversion": "20200429", - "type": "ZHATime", - "uniqueid": "cc:cc:cc:ff:fe:38:4d:b3-01-000a", + data = { + "sensors": { + "0": { + "config": {"battery": 40, "on": True, "reachable": True}, + "ep": 1, + "etag": "28e796678d9a24712feef59294343bb6", + "lastseen": "2020-11-22T11:26Z", + "manufacturername": "Danfoss", + "modelid": "eTRV0100", + "name": "Time", + "state": { + "lastset": "2020-11-19T08:07:08Z", + "lastupdated": "2020-11-22T10:51:03.444", + "localtime": "2020-11-22T10:51:01", + "utc": "2020-11-22T10:51:01Z", + }, + "swversion": "20200429", + "type": "ZHATime", + "uniqueid": "cc:cc:cc:ff:fe:38:4d:b3-01-000a", + } } } - await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 2 - - time = hass.states.get("sensor.time") - assert time.state == "2020-11-19T08:07:08Z" - - time_battery = hass.states.get("sensor.time_battery_level") - assert time_battery.state == "40" + assert hass.states.get("sensor.time").state == "2020-11-19T08:07:08Z" + assert hass.states.get("sensor.time_battery_level").state == "40" async def test_unsupported_sensor(hass, aioclient_mock): """Test that unsupported sensors doesn't break anything.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = { - "0": {"type": "not supported", "name": "name", "state": {}, "config": {}} + data = { + "sensors": { + "0": {"type": "not supported", "name": "name", "state": {}, "config": {}} + } } - await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 1 - - unsupported_sensor = hass.states.get("sensor.name") - assert unsupported_sensor.state == "unknown" + assert hass.states.get("sensor.name").state == STATE_UNKNOWN From ad5dbebc03e6047f8afb3fa9802eab325545346f Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 11:44:52 +0100 Subject: [PATCH 452/831] Use websocket fixture in deCONZ switch tests (#47831) Localize test data Improve asserts --- tests/components/deconz/test_switch.py | 131 +++++++++++-------------- 1 file changed, 60 insertions(+), 71 deletions(-) diff --git a/tests/components/deconz/test_switch.py b/tests/components/deconz/test_switch.py index 6aafac1bd42..cffdf07ae2b 100644 --- a/tests/components/deconz/test_switch.py +++ b/tests/components/deconz/test_switch.py @@ -1,8 +1,7 @@ """deCONZ switch platform tests.""" -from copy import deepcopy +from unittest.mock import patch -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.switch import ( DOMAIN as SWITCH_DOMAIN, SERVICE_TURN_OFF, @@ -16,54 +15,6 @@ from .test_gateway import ( setup_deconz_integration, ) -POWER_PLUGS = { - "1": { - "id": "On off switch id", - "name": "On off switch", - "type": "On/Off plug-in unit", - "state": {"on": True, "reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:00-00", - }, - "2": { - "id": "Smart plug id", - "name": "Smart plug", - "type": "Smart plug", - "state": {"on": False, "reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, - "3": { - "id": "Unsupported switch id", - "name": "Unsupported switch", - "type": "Not a switch", - "state": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:03-00", - }, - "4": { - "id": "On off relay id", - "name": "On off relay", - "state": {"on": True, "reachable": True}, - "type": "On/Off light", - "uniqueid": "00:00:00:00:00:00:00:04-00", - }, -} - -SIRENS = { - "1": { - "id": "Warning device id", - "name": "Warning device", - "type": "Warning device", - "state": {"alert": "lselect", "reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:00-00", - }, - "2": { - "id": "Unsupported switch id", - "name": "Unsupported switch", - "type": "Not a switch", - "state": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, -} - async def test_no_switches(hass, aioclient_mock): """Test that no switch entities are created.""" @@ -71,14 +22,38 @@ async def test_no_switches(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_power_plugs(hass, aioclient_mock): +async def test_power_plugs(hass, aioclient_mock, mock_deconz_websocket): """Test that all supported switch entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = deepcopy(POWER_PLUGS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "lights": { + "1": { + "name": "On off switch", + "type": "On/Off plug-in unit", + "state": {"on": True, "reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + "2": { + "name": "Smart plug", + "type": "Smart plug", + "state": {"on": False, "reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + "3": { + "name": "Unsupported switch", + "type": "Not a switch", + "state": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:03-00", + }, + "4": { + "name": "On off relay", + "state": {"on": True, "reachable": True}, + "type": "On/Off light", + "uniqueid": "00:00:00:00:00:00:00:04-00", + }, + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 4 assert hass.states.get("switch.on_off_switch").state == STATE_ON @@ -86,14 +61,15 @@ async def test_power_plugs(hass, aioclient_mock): assert hass.states.get("switch.on_off_relay").state == STATE_ON assert hass.states.get("switch.unsupported_switch") is None - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", "state": {"on": False}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() assert hass.states.get("switch.on_off_switch").state == STATE_OFF @@ -124,7 +100,7 @@ async def test_power_plugs(hass, aioclient_mock): await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(hass.states.async_all()) == 4 + assert len(states) == 4 for state in states: assert state.state == STATE_UNAVAILABLE @@ -133,27 +109,40 @@ async def test_power_plugs(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_sirens(hass, aioclient_mock): +async def test_sirens(hass, aioclient_mock, mock_deconz_websocket): """Test that siren entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = deepcopy(SIRENS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "lights": { + "1": { + "name": "Warning device", + "type": "Warning device", + "state": {"alert": "lselect", "reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + "2": { + "name": "Unsupported switch", + "type": "Not a switch", + "state": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 2 assert hass.states.get("switch.warning_device").state == STATE_ON - assert hass.states.get("switch.unsupported_switch") is None + assert not hass.states.get("switch.unsupported_switch") - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", "state": {"alert": None}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() assert hass.states.get("switch.warning_device").state == STATE_OFF @@ -184,7 +173,7 @@ async def test_sirens(hass, aioclient_mock): await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(hass.states.async_all()) == 2 + assert len(states) == 2 for state in states: assert state.state == STATE_UNAVAILABLE From 8b8a54b36773831cf3fc56fed98863fe9d995a71 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 11:57:56 +0100 Subject: [PATCH 453/831] Improve deCONZ services and scenes tests (#47829) Use patch.dict rather than deep copy to change DECONZ_WEB_REQUEST --- tests/components/deconz/test_scene.py | 34 +++-- tests/components/deconz/test_services.py | 170 ++++++++++++----------- 2 files changed, 105 insertions(+), 99 deletions(-) diff --git a/tests/components/deconz/test_scene.py b/tests/components/deconz/test_scene.py index 229111bf9ae..189eb1e6eb7 100644 --- a/tests/components/deconz/test_scene.py +++ b/tests/components/deconz/test_scene.py @@ -1,6 +1,6 @@ """deCONZ scene platform tests.""" -from copy import deepcopy +from unittest.mock import patch from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN, SERVICE_TURN_ON from homeassistant.const import ATTR_ENTITY_ID @@ -11,18 +11,6 @@ from .test_gateway import ( setup_deconz_integration, ) -GROUPS = { - "1": { - "id": "Light group id", - "name": "Light group", - "type": "LightGroup", - "state": {"all_on": False, "any_on": True}, - "action": {}, - "scenes": [{"id": "1", "name": "Scene"}], - "lights": [], - } -} - async def test_no_scenes(hass, aioclient_mock): """Test that scenes can be loaded without scenes being available.""" @@ -32,11 +20,21 @@ async def test_no_scenes(hass, aioclient_mock): async def test_scenes(hass, aioclient_mock): """Test that scenes works.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["groups"] = deepcopy(GROUPS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) + data = { + "groups": { + "1": { + "id": "Light group id", + "name": "Light group", + "type": "LightGroup", + "state": {"all_on": False, "any_on": True}, + "action": {}, + "scenes": [{"id": "1", "name": "Scene"}], + "lights": [], + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 1 assert hass.states.get("scene.light_group_scene") diff --git a/tests/components/deconz/test_services.py b/tests/components/deconz/test_services.py index 35279572113..a631327351f 100644 --- a/tests/components/deconz/test_services.py +++ b/tests/components/deconz/test_services.py @@ -1,5 +1,4 @@ """deCONZ service tests.""" -from copy import deepcopy from unittest.mock import Mock, patch import pytest @@ -9,7 +8,6 @@ from homeassistant.components.deconz.const import ( CONF_BRIDGE_ID, DOMAIN as DECONZ_DOMAIN, ) -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.deconz.services import ( DECONZ_SERVICES, SERVICE_CONFIGURE_DEVICE, @@ -33,50 +31,6 @@ from .test_gateway import ( setup_deconz_integration, ) -GROUP = { - "1": { - "id": "Group 1 id", - "name": "Group 1 name", - "type": "LightGroup", - "state": {}, - "action": {}, - "scenes": [{"id": "1", "name": "Scene 1"}], - "lights": ["1"], - } -} - -LIGHT = { - "1": { - "id": "Light 1 id", - "name": "Light 1 name", - "state": {"reachable": True}, - "type": "Light", - "uniqueid": "00:00:00:00:00:00:00:01-00", - } -} - -SENSOR = { - "1": { - "id": "Sensor 1 id", - "name": "Sensor 1 name", - "type": "ZHALightLevel", - "state": {"lightlevel": 30000, "dark": False}, - "config": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:02-00", - } -} - -SWITCH = { - "1": { - "id": "Switch 1 id", - "name": "Switch 1", - "type": "ZHASwitch", - "state": {"buttonevent": 1000, "gesture": 1}, - "config": {"battery": 100}, - "uniqueid": "00:00:00:00:00:00:00:03-00", - }, -} - async def test_service_setup(hass): """Verify service setup works.""" @@ -140,10 +94,19 @@ async def test_configure_service_with_field(hass, aioclient_mock): async def test_configure_service_with_entity(hass, aioclient_mock): """Test that service invokes pydeconz with the correct path and data.""" - config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "lights": { + "1": { + "name": "Test", + "state": {"reachable": True}, + "type": "Light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway.deconz_ids["light.test"] = "/lights/1" data = { SERVICE_ENTITY: "light.test", SERVICE_DATA: {"on": True, "attr1": 10, "attr2": 20}, @@ -159,10 +122,19 @@ async def test_configure_service_with_entity(hass, aioclient_mock): async def test_configure_service_with_entity_and_field(hass, aioclient_mock): """Test that service invokes pydeconz with the correct path and data.""" - config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "lights": { + "1": { + "name": "Test", + "state": {"reachable": True}, + "type": "Light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway.deconz_ids["light.test"] = "/lights/1" data = { SERVICE_ENTITY: "light.test", SERVICE_FIELD: "/state", @@ -193,57 +165,93 @@ async def test_configure_service_with_faulty_field(hass, aioclient_mock): async def test_configure_service_with_faulty_entity(hass, aioclient_mock): """Test that service invokes pydeconz with the correct path and data.""" await setup_deconz_integration(hass, aioclient_mock) + aioclient_mock.clear_requests() data = { SERVICE_ENTITY: "light.nonexisting", SERVICE_DATA: {}, } - with patch("pydeconz.DeconzSession.request", return_value=Mock(True)) as put_state: - await hass.services.async_call( - DECONZ_DOMAIN, SERVICE_CONFIGURE_DEVICE, service_data=data - ) - await hass.async_block_till_done() - put_state.assert_not_called() + await hass.services.async_call( + DECONZ_DOMAIN, SERVICE_CONFIGURE_DEVICE, service_data=data + ) + await hass.async_block_till_done() + + assert len(aioclient_mock.mock_calls) == 0 async def test_service_refresh_devices(hass, aioclient_mock): """Test that service can refresh devices.""" config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway = get_gateway_from_config_entry(hass, config_entry) + + assert len(hass.states.async_all()) == 0 + aioclient_mock.clear_requests() - data = {CONF_BRIDGE_ID: BRIDGEID} + data = { + "groups": { + "1": { + "id": "Group 1 id", + "name": "Group 1 name", + "type": "LightGroup", + "state": {}, + "action": {}, + "scenes": [{"id": "1", "name": "Scene 1"}], + "lights": ["1"], + } + }, + "lights": { + "1": { + "name": "Light 1 name", + "state": {"reachable": True}, + "type": "Light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + } + }, + "sensors": { + "1": { + "name": "Sensor 1 name", + "type": "ZHALightLevel", + "state": {"lightlevel": 30000, "dark": False}, + "config": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:02-00", + } + }, + } - mock_deconz_request( - aioclient_mock, - config_entry.data, - {"groups": GROUP, "lights": LIGHT, "sensors": SENSOR}, - ) + mock_deconz_request(aioclient_mock, config_entry.data, data) await hass.services.async_call( - DECONZ_DOMAIN, SERVICE_DEVICE_REFRESH, service_data=data + DECONZ_DOMAIN, SERVICE_DEVICE_REFRESH, service_data={CONF_BRIDGE_ID: BRIDGEID} ) await hass.async_block_till_done() - assert gateway.deconz_ids == { - "light.group_1_name": "/groups/1", - "light.light_1_name": "/lights/1", - "scene.group_1_name_scene_1": "/groups/1/scenes/1", - "sensor.sensor_1_name": "/sensors/1", - } + assert len(hass.states.async_all()) == 4 async def test_remove_orphaned_entries_service(hass, aioclient_mock): """Test service works and also don't remove more than expected.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = deepcopy(LIGHT) - data["sensors"] = deepcopy(SWITCH) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - - data = {CONF_BRIDGE_ID: BRIDGEID} + data = { + "lights": { + "1": { + "name": "Light 1 name", + "state": {"reachable": True}, + "type": "Light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + } + }, + "sensors": { + "1": { + "name": "Switch 1", + "type": "ZHASwitch", + "state": {"buttonevent": 1000, "gesture": 1}, + "config": {"battery": 100}, + "uniqueid": "00:00:00:00:00:00:00:03-00", + }, + }, + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) device_registry = dr.async_get(hass) device = device_registry.async_get_or_create( @@ -279,7 +287,7 @@ async def test_remove_orphaned_entries_service(hass, aioclient_mock): await hass.services.async_call( DECONZ_DOMAIN, SERVICE_REMOVE_ORPHANED_ENTRIES, - service_data=data, + service_data={CONF_BRIDGE_ID: BRIDGEID}, ) await hass.async_block_till_done() From c8950870a2413c21457fe86ce62cb537095f01ed Mon Sep 17 00:00:00 2001 From: javicalle <31999997+javicalle@users.noreply.github.com> Date: Thu, 18 Mar 2021 12:25:00 +0100 Subject: [PATCH 454/831] Propagate RFLink 'send_command' event (#43588) * propagate send_command event * propagate send_command event --- homeassistant/components/rflink/__init__.py | 11 ++++++ tests/components/rflink/test_init.py | 44 +++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/homeassistant/components/rflink/__init__.py b/homeassistant/components/rflink/__init__.py index 68783c3426a..18f02d66a31 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -67,6 +67,7 @@ SERVICE_SEND_COMMAND = "send_command" SIGNAL_AVAILABILITY = "rflink_device_available" SIGNAL_HANDLE_EVENT = "rflink_handle_event_{}" +SIGNAL_EVENT = "rflink_event" TMP_ENTITY = "tmp.{}" @@ -140,6 +141,15 @@ async def async_setup(hass, config): ) ): _LOGGER.error("Failed Rflink command for %s", str(call.data)) + else: + async_dispatcher_send( + hass, + SIGNAL_EVENT, + { + EVENT_KEY_ID: call.data.get(CONF_DEVICE_ID), + EVENT_KEY_COMMAND: call.data.get(CONF_COMMAND), + }, + ) hass.services.async_register( DOMAIN, SERVICE_SEND_COMMAND, async_send_command, schema=SEND_COMMAND_SCHEMA @@ -293,6 +303,7 @@ async def async_setup(hass, config): _LOGGER.info("Connected to Rflink") hass.async_create_task(connect()) + async_dispatcher_connect(hass, SIGNAL_EVENT, event_callback) return True diff --git a/tests/components/rflink/test_init.py b/tests/components/rflink/test_init.py index 7ba90286e62..233170d8cd2 100644 --- a/tests/components/rflink/test_init.py +++ b/tests/components/rflink/test_init.py @@ -196,6 +196,50 @@ async def test_send_command_invalid_arguments(hass, monkeypatch): assert not success, "send command should not succeed for unknown command" +async def test_send_command_event_propagation(hass, monkeypatch): + """Test event propagation for send_command service.""" + domain = "light" + config = { + "rflink": {"port": "/dev/ttyABC0"}, + domain: { + "platform": "rflink", + "devices": { + "protocol_0_1": {"name": "test1"}, + }, + }, + } + + # setup mocking rflink module + _, _, protocol, _ = await mock_rflink(hass, config, domain, monkeypatch) + + # default value = 'off' + assert hass.states.get(f"{domain}.test1").state == "off" + + hass.async_create_task( + hass.services.async_call( + "rflink", + SERVICE_SEND_COMMAND, + {"device_id": "protocol_0_1", "command": "on"}, + ) + ) + await hass.async_block_till_done() + assert protocol.send_command_ack.call_args_list[0][0][0] == "protocol_0_1" + assert protocol.send_command_ack.call_args_list[0][0][1] == "on" + assert hass.states.get(f"{domain}.test1").state == "on" + + hass.async_create_task( + hass.services.async_call( + "rflink", + SERVICE_SEND_COMMAND, + {"device_id": "protocol_0_1", "command": "alloff"}, + ) + ) + await hass.async_block_till_done() + assert protocol.send_command_ack.call_args_list[1][0][0] == "protocol_0_1" + assert protocol.send_command_ack.call_args_list[1][0][1] == "alloff" + assert hass.states.get(f"{domain}.test1").state == "off" + + async def test_reconnecting_after_disconnect(hass, monkeypatch): """An unexpected disconnect should cause a reconnect.""" domain = "sensor" From 00dca88024fa5cf6b25fa9220c17d4e16e5dd25a Mon Sep 17 00:00:00 2001 From: Andreas <28764847+andreas-amlabs@users.noreply.github.com> Date: Thu, 18 Mar 2021 12:32:08 +0100 Subject: [PATCH 455/831] Amcrest add support for CrossLineDetection (#44582) Co-authored-by: andreas-amlabs --- homeassistant/components/amcrest/binary_sensor.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/homeassistant/components/amcrest/binary_sensor.py b/homeassistant/components/amcrest/binary_sensor.py index 649258c42c7..0824021f31e 100644 --- a/homeassistant/components/amcrest/binary_sensor.py +++ b/homeassistant/components/amcrest/binary_sensor.py @@ -38,6 +38,8 @@ BINARY_SENSOR_AUDIO_DETECTED_POLLED = "audio_detected_polled" BINARY_SENSOR_MOTION_DETECTED = "motion_detected" BINARY_SENSOR_MOTION_DETECTED_POLLED = "motion_detected_polled" BINARY_SENSOR_ONLINE = "online" +BINARY_SENSOR_CROSSLINE_DETECTED = "crossline_detected" +BINARY_SENSOR_CROSSLINE_DETECTED_POLLED = "crossline_detected_polled" BINARY_POLLED_SENSORS = [ BINARY_SENSOR_AUDIO_DETECTED_POLLED, BINARY_SENSOR_MOTION_DETECTED_POLLED, @@ -45,11 +47,18 @@ BINARY_POLLED_SENSORS = [ ] _AUDIO_DETECTED_PARAMS = ("Audio Detected", DEVICE_CLASS_SOUND, "AudioMutation") _MOTION_DETECTED_PARAMS = ("Motion Detected", DEVICE_CLASS_MOTION, "VideoMotion") +_CROSSLINE_DETECTED_PARAMS = ( + "CrossLine Detected", + DEVICE_CLASS_MOTION, + "CrossLineDetection", +) BINARY_SENSORS = { BINARY_SENSOR_AUDIO_DETECTED: _AUDIO_DETECTED_PARAMS, BINARY_SENSOR_AUDIO_DETECTED_POLLED: _AUDIO_DETECTED_PARAMS, BINARY_SENSOR_MOTION_DETECTED: _MOTION_DETECTED_PARAMS, BINARY_SENSOR_MOTION_DETECTED_POLLED: _MOTION_DETECTED_PARAMS, + BINARY_SENSOR_CROSSLINE_DETECTED: _CROSSLINE_DETECTED_PARAMS, + BINARY_SENSOR_CROSSLINE_DETECTED_POLLED: _CROSSLINE_DETECTED_PARAMS, BINARY_SENSOR_ONLINE: ("Online", DEVICE_CLASS_CONNECTIVITY, None), } BINARY_SENSORS = { @@ -58,6 +67,7 @@ BINARY_SENSORS = { } _EXCLUSIVE_OPTIONS = [ {BINARY_SENSOR_MOTION_DETECTED, BINARY_SENSOR_MOTION_DETECTED_POLLED}, + {BINARY_SENSOR_CROSSLINE_DETECTED, BINARY_SENSOR_CROSSLINE_DETECTED_POLLED}, ] _UPDATE_MSG = "Updating %s binary sensor" From 25a13d1554a318f53c0fef9b680fe12aacd6bfcd Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 13:07:04 +0100 Subject: [PATCH 456/831] Update typing 10 (#48071) --- .../components/jewish_calendar/__init__.py | 6 +-- .../components/keenetic_ndms2/config_flow.py | 4 +- .../keenetic_ndms2/device_tracker.py | 11 ++-- .../components/keenetic_ndms2/router.py | 14 ++--- homeassistant/components/knx/__init__.py | 5 +- homeassistant/components/knx/binary_sensor.py | 6 ++- homeassistant/components/knx/climate.py | 10 ++-- homeassistant/components/knx/expose.py | 6 +-- homeassistant/components/knx/factory.py | 4 +- homeassistant/components/knx/fan.py | 12 +++-- homeassistant/components/knx/notify.py | 4 +- .../components/kodi/device_trigger.py | 4 +- homeassistant/components/kulersky/light.py | 6 ++- .../components/launch_library/sensor.py | 7 +-- homeassistant/components/light/__init__.py | 52 +++++++++---------- .../components/light/device_action.py | 4 +- .../components/light/device_condition.py | 4 +- .../components/light/device_trigger.py | 4 +- .../components/light/reproduce_state.py | 14 ++--- .../components/light/significant_change.py | 6 ++- .../components/litejet/config_flow.py | 8 +-- homeassistant/components/litterrobot/hub.py | 6 ++- .../components/litterrobot/sensor.py | 4 +- homeassistant/components/locative/__init__.py | 5 +- .../components/lock/device_action.py | 6 +-- .../components/lock/device_condition.py | 4 +- .../components/lock/device_trigger.py | 4 +- .../components/lock/reproduce_state.py | 12 +++-- .../components/lock/significant_change.py | 6 ++- .../components/lovelace/resources.py | 8 +-- .../lutron_caseta/device_trigger.py | 4 +- homeassistant/components/lutron_caseta/fan.py | 5 +- homeassistant/components/lyric/__init__.py | 8 +-- homeassistant/components/lyric/climate.py | 17 +++--- .../components/media_player/__init__.py | 25 +++++---- .../media_player/device_condition.py | 4 +- .../components/media_player/device_trigger.py | 4 +- .../media_player/reproduce_state.py | 12 +++-- .../components/media_source/__init__.py | 5 +- .../components/media_source/local_source.py | 7 +-- .../components/media_source/models.py | 9 ++-- homeassistant/components/melcloud/__init__.py | 8 +-- homeassistant/components/melcloud/climate.py | 34 ++++++------ .../components/melcloud/config_flow.py | 7 +-- .../components/melcloud/water_heater.py | 14 ++--- homeassistant/components/met/config_flow.py | 8 +-- .../components/minecraft_server/__init__.py | 5 +- .../components/minecraft_server/helpers.py | 5 +- .../components/minecraft_server/sensor.py | 6 ++- homeassistant/components/minio/__init__.py | 5 +- .../components/minio/minio_helper.py | 8 +-- .../components/mobile_app/device_action.py | 6 +-- .../components/mobile_app/helpers.py | 22 ++++---- .../components/mobile_app/http_api.py | 5 +- homeassistant/components/mobile_app/util.py | 8 +-- .../components/modbus/binary_sensor.py | 4 +- homeassistant/components/modbus/climate.py | 10 ++-- homeassistant/components/modbus/cover.py | 14 ++--- homeassistant/components/modbus/sensor.py | 8 +-- homeassistant/components/modbus/switch.py | 12 +++-- homeassistant/components/mqtt/__init__.py | 12 +++-- .../components/mqtt/device_trigger.py | 10 ++-- homeassistant/components/mqtt/mixins.py | 5 +- homeassistant/components/mqtt/models.py | 8 +-- homeassistant/components/mqtt/sensor.py | 5 +- homeassistant/components/mqtt/subscription.py | 10 ++-- .../components/mysensors/__init__.py | 26 +++++----- .../components/mysensors/config_flow.py | 34 ++++++------ homeassistant/components/mysensors/const.py | 28 +++++----- homeassistant/components/mysensors/device.py | 8 +-- homeassistant/components/mysensors/gateway.py | 24 +++++---- homeassistant/components/mysensors/handler.py | 8 +-- homeassistant/components/mysensors/helpers.py | 32 ++++++------ 73 files changed, 412 insertions(+), 333 deletions(-) diff --git a/homeassistant/components/jewish_calendar/__init__.py b/homeassistant/components/jewish_calendar/__init__.py index d1474c3cf5f..35c1505561d 100644 --- a/homeassistant/components/jewish_calendar/__init__.py +++ b/homeassistant/components/jewish_calendar/__init__.py @@ -1,5 +1,5 @@ """The jewish_calendar component.""" -from typing import Optional +from __future__ import annotations import hdate import voluptuous as vol @@ -78,8 +78,8 @@ CONFIG_SCHEMA = vol.Schema( def get_unique_prefix( location: hdate.Location, language: str, - candle_lighting_offset: Optional[int], - havdalah_offset: Optional[int], + candle_lighting_offset: int | None, + havdalah_offset: int | None, ) -> str: """Create a prefix for unique ids.""" config_properties = [ diff --git a/homeassistant/components/keenetic_ndms2/config_flow.py b/homeassistant/components/keenetic_ndms2/config_flow.py index 9338cb05935..a832f68e017 100644 --- a/homeassistant/components/keenetic_ndms2/config_flow.py +++ b/homeassistant/components/keenetic_ndms2/config_flow.py @@ -1,5 +1,5 @@ """Config flow for Keenetic NDMS2.""" -from typing import List +from __future__ import annotations from ndms2_client import Client, ConnectionException, InterfaceInfo, TelnetConnection import voluptuous as vol @@ -103,7 +103,7 @@ class KeeneticOptionsFlowHandler(config_entries.OptionsFlow): ROUTER ] - interfaces: List[InterfaceInfo] = await self.hass.async_add_executor_job( + interfaces: list[InterfaceInfo] = await self.hass.async_add_executor_job( router.client.get_interfaces ) diff --git a/homeassistant/components/keenetic_ndms2/device_tracker.py b/homeassistant/components/keenetic_ndms2/device_tracker.py index a8b0f943dd6..461814b1917 100644 --- a/homeassistant/components/keenetic_ndms2/device_tracker.py +++ b/homeassistant/components/keenetic_ndms2/device_tracker.py @@ -1,7 +1,8 @@ """Support for Keenetic routers as device tracker.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import List, Optional, Set from ndms2_client import Device import voluptuous as vol @@ -57,8 +58,8 @@ async def async_get_scanner(hass: HomeAssistant, config): """Import legacy configuration from YAML.""" scanner_config = config[DEVICE_TRACKER_DOMAIN] - scan_interval: Optional[timedelta] = scanner_config.get(CONF_SCAN_INTERVAL) - consider_home: Optional[timedelta] = scanner_config.get(CONF_CONSIDER_HOME) + scan_interval: timedelta | None = scanner_config.get(CONF_SCAN_INTERVAL) + consider_home: timedelta | None = scanner_config.get(CONF_CONSIDER_HOME) host: str = scanner_config[CONF_HOST] hass.data[DOMAIN][f"imported_options_{host}"] = { @@ -139,9 +140,9 @@ async def async_setup_entry( @callback -def update_items(router: KeeneticRouter, async_add_entities, tracked: Set[str]): +def update_items(router: KeeneticRouter, async_add_entities, tracked: set[str]): """Update tracked device state from the hub.""" - new_tracked: List[KeeneticTracker] = [] + new_tracked: list[KeeneticTracker] = [] for mac, device in router.last_devices.items(): if mac not in tracked: tracked.add(mac) diff --git a/homeassistant/components/keenetic_ndms2/router.py b/homeassistant/components/keenetic_ndms2/router.py index 340b25ff725..0066b49223b 100644 --- a/homeassistant/components/keenetic_ndms2/router.py +++ b/homeassistant/components/keenetic_ndms2/router.py @@ -1,7 +1,9 @@ """The Keenetic Client class.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Callable, Dict, Optional +from typing import Callable from ndms2_client import Client, ConnectionException, Device, TelnetConnection from ndms2_client.client import RouterInfo @@ -39,11 +41,11 @@ class KeeneticRouter: """Initialize the Client.""" self.hass = hass self.config_entry = config_entry - self._last_devices: Dict[str, Device] = {} - self._router_info: Optional[RouterInfo] = None - self._connection: Optional[TelnetConnection] = None - self._client: Optional[Client] = None - self._cancel_periodic_update: Optional[Callable] = None + self._last_devices: dict[str, Device] = {} + self._router_info: RouterInfo | None = None + self._connection: TelnetConnection | None = None + self._client: Client | None = None + self._cancel_periodic_update: Callable | None = None self._available = False self._progress = None diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index e05c18e5d5c..348eac8f40e 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -1,7 +1,8 @@ """Support KNX devices.""" +from __future__ import annotations + import asyncio import logging -from typing import Union import voluptuous as vol from xknx import XKNX @@ -466,7 +467,7 @@ class KNXModule: attr_payload = call.data.get(SERVICE_KNX_ATTR_PAYLOAD) attr_type = call.data.get(SERVICE_KNX_ATTR_TYPE) - payload: Union[DPTBinary, DPTArray] + payload: DPTBinary | DPTArray if attr_type is not None: transcoder = DPTBase.parse_transcoder(attr_type) if transcoder is None: diff --git a/homeassistant/components/knx/binary_sensor.py b/homeassistant/components/knx/binary_sensor.py index ecb79664afd..6ee37abee18 100644 --- a/homeassistant/components/knx/binary_sensor.py +++ b/homeassistant/components/knx/binary_sensor.py @@ -1,5 +1,7 @@ """Support for KNX/IP binary sensors.""" -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import Any from xknx.devices import BinarySensor as XknxBinarySensor @@ -38,7 +40,7 @@ class KNXBinarySensor(KnxEntity, BinarySensorEntity): return self._device.is_on() @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return device specific state attributes.""" if self._device.counter is not None: return {ATTR_COUNTER: self._device.counter} diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py index 565c41298a3..e90371e3282 100644 --- a/homeassistant/components/knx/climate.py +++ b/homeassistant/components/knx/climate.py @@ -1,5 +1,5 @@ """Support for KNX/IP climate devices.""" -from typing import List, Optional +from __future__ import annotations from xknx.devices import Climate as XknxClimate from xknx.dpt.dpt_hvac_mode import HVACControllerMode, HVACOperationMode @@ -88,7 +88,7 @@ class KNXClimate(KnxEntity, ClimateEntity): self.async_write_ha_state() @property - def hvac_mode(self) -> Optional[str]: + def hvac_mode(self) -> str | None: """Return current operation ie. heat, cool, idle.""" if self._device.supports_on_off and not self._device.is_on: return HVAC_MODE_OFF @@ -100,7 +100,7 @@ class KNXClimate(KnxEntity, ClimateEntity): return HVAC_MODE_HEAT @property - def hvac_modes(self) -> Optional[List[str]]: + def hvac_modes(self) -> list[str] | None: """Return the list of available operation/controller modes.""" _controller_modes = [ CONTROLLER_MODES.get(controller_mode.value) @@ -131,7 +131,7 @@ class KNXClimate(KnxEntity, ClimateEntity): self.async_write_ha_state() @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp. Requires SUPPORT_PRESET_MODE. @@ -141,7 +141,7 @@ class KNXClimate(KnxEntity, ClimateEntity): return None @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes. Requires SUPPORT_PRESET_MODE. diff --git a/homeassistant/components/knx/expose.py b/homeassistant/components/knx/expose.py index 3a010810862..8bdd3d1d1d1 100644 --- a/homeassistant/components/knx/expose.py +++ b/homeassistant/components/knx/expose.py @@ -1,5 +1,5 @@ """Exposures to KNX bus.""" -from typing import Union +from __future__ import annotations from xknx import XKNX from xknx.devices import DateTime, ExposeSensor @@ -22,7 +22,7 @@ from .schema import ExposeSchema @callback def create_knx_exposure( hass: HomeAssistant, xknx: XKNX, config: ConfigType -) -> Union["KNXExposeSensor", "KNXExposeTime"]: +) -> KNXExposeSensor | KNXExposeTime: """Create exposures from config.""" address = config[KNX_ADDRESS] attribute = config.get(ExposeSchema.CONF_KNX_EXPOSE_ATTRIBUTE) @@ -30,7 +30,7 @@ def create_knx_exposure( expose_type = config.get(ExposeSchema.CONF_KNX_EXPOSE_TYPE) default = config.get(ExposeSchema.CONF_KNX_EXPOSE_DEFAULT) - exposure: Union["KNXExposeSensor", "KNXExposeTime"] + exposure: KNXExposeSensor | KNXExposeTime if expose_type.lower() in ["time", "date", "datetime"]: exposure = KNXExposeTime(xknx, expose_type, address) else: diff --git a/homeassistant/components/knx/factory.py b/homeassistant/components/knx/factory.py index 893ac1e55e3..0543119cadf 100644 --- a/homeassistant/components/knx/factory.py +++ b/homeassistant/components/knx/factory.py @@ -1,5 +1,5 @@ """Factory function to initialize KNX devices from config.""" -from typing import Optional, Tuple +from __future__ import annotations from xknx import XKNX from xknx.devices import ( @@ -95,7 +95,7 @@ def _create_cover(knx_module: XKNX, config: ConfigType) -> XknxCover: def _create_light_color( color: str, config: ConfigType -) -> Tuple[Optional[str], Optional[str], Optional[str], Optional[str]]: +) -> tuple[str | None, str | None, str | None, str | None]: """Load color configuration from configuration structure.""" if "individual_colors" in config and color in config["individual_colors"]: sub_config = config["individual_colors"][color] diff --git a/homeassistant/components/knx/fan.py b/homeassistant/components/knx/fan.py index 43d1cd7d6f2..2d9f48fe804 100644 --- a/homeassistant/components/knx/fan.py +++ b/homeassistant/components/knx/fan.py @@ -1,6 +1,8 @@ """Support for KNX/IP fans.""" +from __future__ import annotations + import math -from typing import Any, Optional +from typing import Any from xknx.devices import Fan as XknxFan from xknx.devices.fan import FanSpeedMode @@ -58,7 +60,7 @@ class KNXFan(KnxEntity, FanEntity): return flags @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed as a percentage.""" if self._device.current_speed is None: return None @@ -78,9 +80,9 @@ class KNXFan(KnxEntity, FanEntity): async def async_turn_on( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs, ) -> None: """Turn on the fan.""" diff --git a/homeassistant/components/knx/notify.py b/homeassistant/components/knx/notify.py index 7210795bd71..9d6ed35e36b 100644 --- a/homeassistant/components/knx/notify.py +++ b/homeassistant/components/knx/notify.py @@ -1,5 +1,5 @@ """Support for KNX/IP notification services.""" -from typing import List +from __future__ import annotations from xknx.devices import Notification as XknxNotification @@ -22,7 +22,7 @@ async def async_get_service(hass, config, discovery_info=None): class KNXNotificationService(BaseNotificationService): """Implement demo notification service.""" - def __init__(self, devices: List[XknxNotification]): + def __init__(self, devices: list[XknxNotification]): """Initialize the service.""" self.devices = devices diff --git a/homeassistant/components/kodi/device_trigger.py b/homeassistant/components/kodi/device_trigger.py index 314f73a927e..3454fc122ed 100644 --- a/homeassistant/components/kodi/device_trigger.py +++ b/homeassistant/components/kodi/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Kodi.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -29,7 +29,7 @@ TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Kodi devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/kulersky/light.py b/homeassistant/components/kulersky/light.py index 9098975d500..599f99a83e8 100644 --- a/homeassistant/components/kulersky/light.py +++ b/homeassistant/components/kulersky/light.py @@ -1,7 +1,9 @@ """Kuler Sky light platform.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Callable, List +from typing import Callable import pykulersky @@ -34,7 +36,7 @@ DISCOVERY_INTERVAL = timedelta(seconds=60) async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Kuler sky light devices.""" if DOMAIN not in hass.data: diff --git a/homeassistant/components/launch_library/sensor.py b/homeassistant/components/launch_library/sensor.py index 366cd4e7d44..7663bf86fc5 100644 --- a/homeassistant/components/launch_library/sensor.py +++ b/homeassistant/components/launch_library/sensor.py @@ -1,7 +1,8 @@ """A sensor platform that give you information about the next space launch.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Optional from pylaunches import PyLaunches, PyLaunchesException import voluptuous as vol @@ -64,7 +65,7 @@ class LaunchLibrarySensor(Entity): return self._name @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the sensor.""" if self.next_launch: return self.next_launch.name @@ -76,7 +77,7 @@ class LaunchLibrarySensor(Entity): return "mdi:rocket" @property - def extra_state_attributes(self) -> Optional[dict]: + def extra_state_attributes(self) -> dict | None: """Return attributes for the sensor.""" if self.next_launch: return { diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 56fd5841388..c2e2fdbeaa9 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -6,7 +6,7 @@ import dataclasses from datetime import timedelta import logging import os -from typing import Dict, List, Optional, Set, Tuple, cast +from typing import cast import voluptuous as vol @@ -364,11 +364,11 @@ class Profile: """Representation of a profile.""" name: str - color_x: Optional[float] = dataclasses.field(repr=False) - color_y: Optional[float] = dataclasses.field(repr=False) - brightness: Optional[int] - transition: Optional[int] = None - hs_color: Optional[Tuple[float, float]] = dataclasses.field(init=False) + color_x: float | None = dataclasses.field(repr=False) + color_y: float | None = dataclasses.field(repr=False) + brightness: int | None + transition: int | None = None + hs_color: tuple[float, float] | None = dataclasses.field(init=False) SCHEMA = vol.Schema( # pylint: disable=invalid-name vol.Any( @@ -403,7 +403,7 @@ class Profile: ) @classmethod - def from_csv_row(cls, csv_row: List[str]) -> Profile: + def from_csv_row(cls, csv_row: list[str]) -> Profile: """Create profile from a CSV row tuple.""" return cls(*cls.SCHEMA(csv_row)) @@ -414,9 +414,9 @@ class Profiles: def __init__(self, hass: HomeAssistantType): """Initialize profiles.""" self.hass = hass - self.data: Dict[str, Profile] = {} + self.data: dict[str, Profile] = {} - def _load_profile_data(self) -> Dict[str, Profile]: + def _load_profile_data(self) -> dict[str, Profile]: """Load built-in profiles and custom profiles.""" profile_paths = [ os.path.join(os.path.dirname(__file__), LIGHT_PROFILES_FILE), @@ -453,7 +453,7 @@ class Profiles: self.data = await self.hass.async_add_executor_job(self._load_profile_data) @callback - def apply_default(self, entity_id: str, params: Dict) -> None: + def apply_default(self, entity_id: str, params: dict) -> None: """Return the default turn-on profile for the given light.""" for _entity_id in (entity_id, "group.all_lights"): name = f"{_entity_id}.default" @@ -462,7 +462,7 @@ class Profiles: return @callback - def apply_profile(self, name: str, params: Dict) -> None: + def apply_profile(self, name: str, params: dict) -> None: """Apply a profile.""" profile = self.data.get(name) @@ -481,12 +481,12 @@ class LightEntity(ToggleEntity): """Representation of a light.""" @property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the brightness of this light between 0..255.""" return None @property - def color_mode(self) -> Optional[str]: + def color_mode(self) -> str | None: """Return the color mode of the light.""" return None @@ -519,27 +519,27 @@ class LightEntity(ToggleEntity): return color_mode @property - def hs_color(self) -> Optional[Tuple[float, float]]: + def hs_color(self) -> tuple[float, float] | None: """Return the hue and saturation color value [float, float].""" return None @property - def xy_color(self) -> Optional[Tuple[float, float]]: + def xy_color(self) -> tuple[float, float] | None: """Return the xy color value [float, float].""" return None @property - def rgb_color(self) -> Optional[Tuple[int, int, int]]: + def rgb_color(self) -> tuple[int, int, int] | None: """Return the rgb color value [int, int, int].""" return None @property - def rgbw_color(self) -> Optional[Tuple[int, int, int, int]]: + def rgbw_color(self) -> tuple[int, int, int, int] | None: """Return the rgbw color value [int, int, int, int].""" return None @property - def _light_internal_rgbw_color(self) -> Optional[Tuple[int, int, int, int]]: + def _light_internal_rgbw_color(self) -> tuple[int, int, int, int] | None: """Return the rgbw color value [int, int, int, int].""" rgbw_color = self.rgbw_color if ( @@ -558,12 +558,12 @@ class LightEntity(ToggleEntity): return rgbw_color @property - def rgbww_color(self) -> Optional[Tuple[int, int, int, int, int]]: + def rgbww_color(self) -> tuple[int, int, int, int, int] | None: """Return the rgbww color value [int, int, int, int, int].""" return None @property - def color_temp(self) -> Optional[int]: + def color_temp(self) -> int | None: """Return the CT color value in mireds.""" return None @@ -582,17 +582,17 @@ class LightEntity(ToggleEntity): return 500 @property - def white_value(self) -> Optional[int]: + def white_value(self) -> int | None: """Return the white value of this light between 0..255.""" return None @property - def effect_list(self) -> Optional[List[str]]: + def effect_list(self) -> list[str] | None: """Return the list of supported effects.""" return None @property - def effect(self) -> Optional[str]: + def effect(self) -> str | None: """Return the current effect.""" return None @@ -616,7 +616,7 @@ class LightEntity(ToggleEntity): return data def _light_internal_convert_color(self, color_mode: str) -> dict: - data: Dict[str, Tuple] = {} + data: dict[str, tuple] = {} if color_mode == COLOR_MODE_HS and self.hs_color: hs_color = self.hs_color data[ATTR_HS_COLOR] = (round(hs_color[0], 3), round(hs_color[1], 3)) @@ -692,7 +692,7 @@ class LightEntity(ToggleEntity): return {key: val for key, val in data.items() if val is not None} @property - def _light_internal_supported_color_modes(self) -> Set: + def _light_internal_supported_color_modes(self) -> set: """Calculate supported color modes with backwards compatibility.""" supported_color_modes = self.supported_color_modes @@ -717,7 +717,7 @@ class LightEntity(ToggleEntity): return supported_color_modes @property - def supported_color_modes(self) -> Optional[Set]: + def supported_color_modes(self) -> set | None: """Flag supported color modes.""" return None diff --git a/homeassistant/components/light/device_action.py b/homeassistant/components/light/device_action.py index d499bc0c2a2..4c37647f168 100644 --- a/homeassistant/components/light/device_action.py +++ b/homeassistant/components/light/device_action.py @@ -1,5 +1,5 @@ """Provides device actions for lights.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -78,7 +78,7 @@ async def async_call_action_from_config( ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions.""" actions = await toggle_entity.async_get_actions(hass, device_id, DOMAIN) diff --git a/homeassistant/components/light/device_condition.py b/homeassistant/components/light/device_condition.py index 1d9323907f2..7396ddeea31 100644 --- a/homeassistant/components/light/device_condition.py +++ b/homeassistant/components/light/device_condition.py @@ -1,5 +1,5 @@ """Provides device conditions for lights.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -28,7 +28,7 @@ def async_condition_from_config( async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions.""" return await toggle_entity.async_get_conditions(hass, device_id, DOMAIN) diff --git a/homeassistant/components/light/device_trigger.py b/homeassistant/components/light/device_trigger.py index 066d1f4c020..e1b14124831 100644 --- a/homeassistant/components/light/device_trigger.py +++ b/homeassistant/components/light/device_trigger.py @@ -1,5 +1,5 @@ """Provides device trigger for lights.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -28,7 +28,7 @@ async def async_attach_trigger( ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers.""" return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN) diff --git a/homeassistant/components/light/reproduce_state.py b/homeassistant/components/light/reproduce_state.py index 863790cff71..13a014426a0 100644 --- a/homeassistant/components/light/reproduce_state.py +++ b/homeassistant/components/light/reproduce_state.py @@ -1,8 +1,10 @@ """Reproduce an Light state.""" +from __future__ import annotations + import asyncio import logging from types import MappingProxyType -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -95,8 +97,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -123,7 +125,7 @@ async def _async_reproduce_state( ): return - service_data: Dict[str, Any] = {ATTR_ENTITY_ID: state.entity_id} + service_data: dict[str, Any] = {ATTR_ENTITY_ID: state.entity_id} if reproduce_options is not None and ATTR_TRANSITION in reproduce_options: service_data[ATTR_TRANSITION] = reproduce_options[ATTR_TRANSITION] @@ -171,8 +173,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Light states.""" await asyncio.gather( diff --git a/homeassistant/components/light/significant_change.py b/homeassistant/components/light/significant_change.py index a0bd5203101..9e0f10fae47 100644 --- a/homeassistant/components/light/significant_change.py +++ b/homeassistant/components/light/significant_change.py @@ -1,5 +1,7 @@ """Helper to test significant Light state changes.""" -from typing import Any, Optional +from __future__ import annotations + +from typing import Any from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.significant_change import ( @@ -24,7 +26,7 @@ def async_check_significant_change( new_state: str, new_attrs: dict, **kwargs: Any, -) -> Optional[bool]: +) -> bool | None: """Test if state significantly changed.""" if old_state != new_state: return True diff --git a/homeassistant/components/litejet/config_flow.py b/homeassistant/components/litejet/config_flow.py index e1c7d8ab7b9..124b229c786 100644 --- a/homeassistant/components/litejet/config_flow.py +++ b/homeassistant/components/litejet/config_flow.py @@ -1,6 +1,8 @@ """Config flow for the LiteJet lighting system.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any import pylitejet from serial import SerialException @@ -18,8 +20,8 @@ class LiteJetConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """LiteJet config flow.""" async def async_step_user( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Create a LiteJet config entry based upon user input.""" if self.hass.config_entries.async_entries(DOMAIN): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/litterrobot/hub.py b/homeassistant/components/litterrobot/hub.py index 1847f37ed64..86c3aff5462 100644 --- a/homeassistant/components/litterrobot/hub.py +++ b/homeassistant/components/litterrobot/hub.py @@ -1,8 +1,10 @@ """A wrapper 'hub' for the Litter-Robot API and base entity for common attributes.""" +from __future__ import annotations + from datetime import time, timedelta import logging from types import MethodType -from typing import Any, Optional +from typing import Any import pylitterbot from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException @@ -106,7 +108,7 @@ class LitterRobotEntity(CoordinatorEntity): async_call_later(self.hass, REFRESH_WAIT_TIME, async_call_later_callback) @staticmethod - def parse_time_at_default_timezone(time_str: str) -> Optional[time]: + def parse_time_at_default_timezone(time_str: str) -> time | None: """Parse a time string and add default timezone.""" parsed_time = dt_util.parse_time(time_str) diff --git a/homeassistant/components/litterrobot/sensor.py b/homeassistant/components/litterrobot/sensor.py index 8900c6c54ca..8ae512fa801 100644 --- a/homeassistant/components/litterrobot/sensor.py +++ b/homeassistant/components/litterrobot/sensor.py @@ -1,5 +1,5 @@ """Support for Litter-Robot sensors.""" -from typing import Optional +from __future__ import annotations from pylitterbot.robot import Robot @@ -10,7 +10,7 @@ from .const import DOMAIN from .hub import LitterRobotEntity, LitterRobotHub -def icon_for_gauge_level(gauge_level: Optional[int] = None, offset: int = 0) -> str: +def icon_for_gauge_level(gauge_level: int | None = None, offset: int = 0) -> str: """Return a gauge icon valid identifier.""" if gauge_level is None or gauge_level <= 0 + offset: return "mdi:gauge-empty" diff --git a/homeassistant/components/locative/__init__.py b/homeassistant/components/locative/__init__.py index 28af822ae63..bb2a19c6380 100644 --- a/homeassistant/components/locative/__init__.py +++ b/homeassistant/components/locative/__init__.py @@ -1,6 +1,7 @@ """Support for Locative.""" +from __future__ import annotations + import logging -from typing import Dict from aiohttp import web import voluptuous as vol @@ -34,7 +35,7 @@ def _id(value: str) -> str: return value.replace("-", "") -def _validate_test_mode(obj: Dict) -> Dict: +def _validate_test_mode(obj: dict) -> dict: """Validate that id is provided outside of test mode.""" if ATTR_ID not in obj and obj[ATTR_TRIGGER] != "test": raise vol.Invalid("Location id not specified") diff --git a/homeassistant/components/lock/device_action.py b/homeassistant/components/lock/device_action.py index efdb5e352cf..639947f3b88 100644 --- a/homeassistant/components/lock/device_action.py +++ b/homeassistant/components/lock/device_action.py @@ -1,5 +1,5 @@ """Provides device automations for Lock.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -30,7 +30,7 @@ ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Lock devices.""" registry = await entity_registry.async_get_registry(hass) actions = [] @@ -75,7 +75,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/lock/device_condition.py b/homeassistant/components/lock/device_condition.py index a25018dc709..0fae680f829 100644 --- a/homeassistant/components/lock/device_condition.py +++ b/homeassistant/components/lock/device_condition.py @@ -1,5 +1,5 @@ """Provides device automations for Lock.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -30,7 +30,7 @@ CONDITION_SCHEMA = DEVICE_CONDITION_BASE_SCHEMA.extend( ) -async def async_get_conditions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_conditions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device conditions for Lock devices.""" registry = await entity_registry.async_get_registry(hass) conditions = [] diff --git a/homeassistant/components/lock/device_trigger.py b/homeassistant/components/lock/device_trigger.py index 05d5041ca65..3e5bee49a22 100644 --- a/homeassistant/components/lock/device_trigger.py +++ b/homeassistant/components/lock/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Lock.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -31,7 +31,7 @@ TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Lock devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/lock/reproduce_state.py b/homeassistant/components/lock/reproduce_state.py index 812b9bf04df..0d575964b2b 100644 --- a/homeassistant/components/lock/reproduce_state.py +++ b/homeassistant/components/lock/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Lock state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -24,8 +26,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -60,8 +62,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Lock states.""" await asyncio.gather( diff --git a/homeassistant/components/lock/significant_change.py b/homeassistant/components/lock/significant_change.py index 59a3b1a95c5..172bf2559c5 100644 --- a/homeassistant/components/lock/significant_change.py +++ b/homeassistant/components/lock/significant_change.py @@ -1,5 +1,7 @@ """Helper to test significant Lock state changes.""" -from typing import Any, Optional +from __future__ import annotations + +from typing import Any from homeassistant.core import HomeAssistant, callback @@ -12,7 +14,7 @@ def async_check_significant_change( new_state: str, new_attrs: dict, **kwargs: Any, -) -> Optional[bool]: +) -> bool | None: """Test if state significantly changed.""" if old_state != new_state: return True diff --git a/homeassistant/components/lovelace/resources.py b/homeassistant/components/lovelace/resources.py index 0a3e36892d5..6a97d5c4192 100644 --- a/homeassistant/components/lovelace/resources.py +++ b/homeassistant/components/lovelace/resources.py @@ -1,6 +1,8 @@ """Lovelace resources support.""" +from __future__ import annotations + import logging -from typing import List, Optional, cast +from typing import Optional, cast import uuid import voluptuous as vol @@ -38,7 +40,7 @@ class ResourceYAMLCollection: return {"resources": len(self.async_items() or [])} @callback - def async_items(self) -> List[dict]: + def async_items(self) -> list[dict]: """Return list of items in collection.""" return self.data @@ -66,7 +68,7 @@ class ResourceStorageCollection(collection.StorageCollection): return {"resources": len(self.async_items() or [])} - async def _async_load_data(self) -> Optional[dict]: + async def _async_load_data(self) -> dict | None: """Load the data.""" data = await self.store.async_load() diff --git a/homeassistant/components/lutron_caseta/device_trigger.py b/homeassistant/components/lutron_caseta/device_trigger.py index 86ee5e46b51..230301c12f2 100644 --- a/homeassistant/components/lutron_caseta/device_trigger.py +++ b/homeassistant/components/lutron_caseta/device_trigger.py @@ -1,5 +1,5 @@ """Provides device triggers for lutron caseta.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -225,7 +225,7 @@ async def async_validate_trigger_config(hass: HomeAssistant, config: ConfigType) return schema(config) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for lutron caseta devices.""" triggers = [] diff --git a/homeassistant/components/lutron_caseta/fan.py b/homeassistant/components/lutron_caseta/fan.py index de2b2a2ae8c..edca88c10fc 100644 --- a/homeassistant/components/lutron_caseta/fan.py +++ b/homeassistant/components/lutron_caseta/fan.py @@ -1,6 +1,7 @@ """Support for Lutron Caseta fans.""" +from __future__ import annotations + import logging -from typing import Optional from pylutron_caseta import FAN_HIGH, FAN_LOW, FAN_MEDIUM, FAN_MEDIUM_HIGH, FAN_OFF @@ -42,7 +43,7 @@ class LutronCasetaFan(LutronCasetaDevice, FanEntity): """Representation of a Lutron Caseta fan. Including Fan Speed.""" @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" if self._device["fan_speed"] is None: return None diff --git a/homeassistant/components/lyric/__init__.py b/homeassistant/components/lyric/__init__.py index 8b23d6f85cf..8536fa03e8a 100644 --- a/homeassistant/components/lyric/__init__.py +++ b/homeassistant/components/lyric/__init__.py @@ -1,8 +1,10 @@ """The Honeywell Lyric integration.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Any, Dict, Optional +from typing import Any from aiolyric import Lyric from aiolyric.objects.device import LyricDevice @@ -147,7 +149,7 @@ class LyricEntity(CoordinatorEntity): device: LyricDevice, key: str, name: str, - icon: Optional[str], + icon: str | None, ) -> None: """Initialize the Honeywell Lyric entity.""" super().__init__(coordinator) @@ -190,7 +192,7 @@ class LyricDeviceEntity(LyricEntity): """Defines a Honeywell Lyric device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this Honeywell Lyric instance.""" return { "connections": {(dr.CONNECTION_NETWORK_MAC, self._mac_id)}, diff --git a/homeassistant/components/lyric/climate.py b/homeassistant/components/lyric/climate.py index 4338a6e92cc..0e3672f952e 100644 --- a/homeassistant/components/lyric/climate.py +++ b/homeassistant/components/lyric/climate.py @@ -1,7 +1,8 @@ """Support for Honeywell Lyric climate platform.""" +from __future__ import annotations + import logging from time import gmtime, strftime, time -from typing import List, Optional from aiolyric.objects.device import LyricDevice from aiolyric.objects.location import LyricLocation @@ -162,7 +163,7 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity): return self._temperature_unit @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self.device.indoorTemperature @@ -180,12 +181,12 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity): return HVAC_MODES[self.device.changeableValues.mode] @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """List of available hvac modes.""" return self._hvac_modes @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" device = self.device if not device.hasDualSetpointStatus: @@ -193,7 +194,7 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity): return None @property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the upper bound temperature we try to reach.""" device = self.device if device.hasDualSetpointStatus: @@ -201,7 +202,7 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity): return None @property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the upper bound temperature we try to reach.""" device = self.device if device.hasDualSetpointStatus: @@ -209,12 +210,12 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity): return None @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return current preset mode.""" return self.device.changeableValues.thermostatSetpointStatus @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return preset modes.""" return [ PRESET_NO_HOLD, diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 87ecff7a54c..1e0a9b9f6bb 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -9,7 +9,6 @@ import functools as ft import hashlib import logging import secrets -from typing import List, Optional, Tuple from urllib.parse import urlparse from aiohttp import web @@ -355,7 +354,7 @@ async def async_unload_entry(hass, entry): class MediaPlayerEntity(Entity): """ABC for media player entities.""" - _access_token: Optional[str] = None + _access_token: str | None = None # Implement these for your media player @property @@ -439,8 +438,8 @@ class MediaPlayerEntity(Entity): self, media_content_type: str, media_content_id: str, - media_image_id: Optional[str] = None, - ) -> Tuple[Optional[str], Optional[str]]: + media_image_id: str | None = None, + ) -> tuple[str | None, str | None]: """ Optionally fetch internally accessible image for media browser. @@ -851,8 +850,8 @@ class MediaPlayerEntity(Entity): async def async_browse_media( self, - media_content_type: Optional[str] = None, - media_content_id: Optional[str] = None, + media_content_type: str | None = None, + media_content_id: str | None = None, ) -> BrowseMedia: """Return a BrowseMedia instance. @@ -914,7 +913,7 @@ class MediaPlayerEntity(Entity): self, media_content_type: str, media_content_id: str, - media_image_id: Optional[str] = None, + media_image_id: str | None = None, ) -> str: """Generate an url for a media browser image.""" url_path = ( @@ -947,8 +946,8 @@ class MediaPlayerImageView(HomeAssistantView): self, request: web.Request, entity_id: str, - media_content_type: Optional[str] = None, - media_content_id: Optional[str] = None, + media_content_type: str | None = None, + media_content_id: str | None = None, ) -> web.Response: """Start a get request.""" player = self.component.get_entity(entity_id) @@ -1047,7 +1046,7 @@ async def websocket_browse_media(hass, connection, msg): To use, media_player integrations can implement MediaPlayerEntity.async_browse_media() """ component = hass.data[DOMAIN] - player: Optional[MediaPlayerDevice] = component.get_entity(msg["entity_id"]) + player: MediaPlayerDevice | None = component.get_entity(msg["entity_id"]) if player is None: connection.send_error(msg["id"], "entity_not_found", "Entity not found") @@ -1119,9 +1118,9 @@ class BrowseMedia: title: str, can_play: bool, can_expand: bool, - children: Optional[List["BrowseMedia"]] = None, - children_media_class: Optional[str] = None, - thumbnail: Optional[str] = None, + children: list[BrowseMedia] | None = None, + children_media_class: str | None = None, + thumbnail: str | None = None, ): """Initialize browse media item.""" self.media_class = media_class diff --git a/homeassistant/components/media_player/device_condition.py b/homeassistant/components/media_player/device_condition.py index 6faa6521b70..0e6e0f96c40 100644 --- a/homeassistant/components/media_player/device_condition.py +++ b/homeassistant/components/media_player/device_condition.py @@ -1,5 +1,5 @@ """Provides device automations for Media player.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -35,7 +35,7 @@ CONDITION_SCHEMA = DEVICE_CONDITION_BASE_SCHEMA.extend( async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions for Media player devices.""" registry = await entity_registry.async_get_registry(hass) conditions = [] diff --git a/homeassistant/components/media_player/device_trigger.py b/homeassistant/components/media_player/device_trigger.py index 6db5f16cf01..03c165412e9 100644 --- a/homeassistant/components/media_player/device_trigger.py +++ b/homeassistant/components/media_player/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Media player.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -34,7 +34,7 @@ TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Media player entities.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/media_player/reproduce_state.py b/homeassistant/components/media_player/reproduce_state.py index 64955d1913b..1707109197f 100644 --- a/homeassistant/components/media_player/reproduce_state.py +++ b/homeassistant/components/media_player/reproduce_state.py @@ -1,6 +1,8 @@ """Module that groups code required to handle state restore for component.""" +from __future__ import annotations + import asyncio -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( SERVICE_MEDIA_PAUSE, @@ -40,8 +42,8 @@ async def _async_reproduce_states( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce component states.""" @@ -104,8 +106,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce component states.""" await asyncio.gather( diff --git a/homeassistant/components/media_source/__init__.py b/homeassistant/components/media_source/__init__.py index 3dff949d5dd..6aa01403a5f 100644 --- a/homeassistant/components/media_source/__init__.py +++ b/homeassistant/components/media_source/__init__.py @@ -1,6 +1,7 @@ """The media_source integration.""" +from __future__ import annotations + from datetime import timedelta -from typing import Optional import voluptuous as vol @@ -54,7 +55,7 @@ async def _process_media_source_platform(hass, domain, platform): @callback def _get_media_item( - hass: HomeAssistant, media_content_id: Optional[str] + hass: HomeAssistant, media_content_id: str | None ) -> models.MediaSourceItem: """Return media item.""" if media_content_id: diff --git a/homeassistant/components/media_source/local_source.py b/homeassistant/components/media_source/local_source.py index fa62ba48c5f..fb5e9094dfb 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -1,7 +1,8 @@ """Local Media Source Implementation.""" +from __future__ import annotations + import mimetypes from pathlib import Path -from typing import Tuple from aiohttp import web @@ -40,7 +41,7 @@ class LocalSource(MediaSource): return Path(self.hass.config.media_dirs[source_dir_id], location) @callback - def async_parse_identifier(self, item: MediaSourceItem) -> Tuple[str, str]: + def async_parse_identifier(self, item: MediaSourceItem) -> tuple[str, str]: """Parse identifier.""" if not item.identifier: # Empty source_dir_id and location @@ -69,7 +70,7 @@ class LocalSource(MediaSource): return PlayMedia(f"/media/{item.identifier}", mime_type) async def async_browse_media( - self, item: MediaSourceItem, media_types: Tuple[str] = MEDIA_MIME_TYPES + self, item: MediaSourceItem, media_types: tuple[str] = MEDIA_MIME_TYPES ) -> BrowseMediaSource: """Return media.""" try: diff --git a/homeassistant/components/media_source/models.py b/homeassistant/components/media_source/models.py index 98b817344d9..aa17fff320e 100644 --- a/homeassistant/components/media_source/models.py +++ b/homeassistant/components/media_source/models.py @@ -3,7 +3,6 @@ from __future__ import annotations from abc import ABC from dataclasses import dataclass -from typing import List, Optional, Tuple from homeassistant.components.media_player import BrowseMedia from homeassistant.components.media_player.const import ( @@ -28,9 +27,9 @@ class PlayMedia: class BrowseMediaSource(BrowseMedia): """Represent a browsable media file.""" - children: Optional[List["BrowseMediaSource"]] + children: list[BrowseMediaSource] | None - def __init__(self, *, domain: Optional[str], identifier: Optional[str], **kwargs): + def __init__(self, *, domain: str | None, identifier: str | None, **kwargs): """Initialize media source browse media.""" media_content_id = f"{URI_SCHEME}{domain or ''}" if identifier: @@ -47,7 +46,7 @@ class MediaSourceItem: """A parsed media item.""" hass: HomeAssistant - domain: Optional[str] + domain: str | None identifier: str async def async_browse(self) -> BrowseMediaSource: @@ -118,7 +117,7 @@ class MediaSource(ABC): raise NotImplementedError async def async_browse_media( - self, item: MediaSourceItem, media_types: Tuple[str] + self, item: MediaSourceItem, media_types: tuple[str] ) -> BrowseMediaSource: """Browse media.""" raise NotImplementedError diff --git a/homeassistant/components/melcloud/__init__.py b/homeassistant/components/melcloud/__init__.py index 0e81d6101b3..0f48db96bf8 100644 --- a/homeassistant/components/melcloud/__init__.py +++ b/homeassistant/components/melcloud/__init__.py @@ -1,8 +1,10 @@ """The MELCloud Climate integration.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Any, Dict, List +from typing import Any from aiohttp import ClientConnectionError from async_timeout import timeout @@ -101,7 +103,7 @@ class MelCloudDevice: _LOGGER.warning("Connection failed for %s", self.name) self._available = False - async def async_set(self, properties: Dict[str, Any]): + async def async_set(self, properties: dict[str, Any]): """Write state changes to the MELCloud API.""" try: await self.device.set(properties) @@ -142,7 +144,7 @@ class MelCloudDevice: return _device_info -async def mel_devices_setup(hass, token) -> List[MelCloudDevice]: +async def mel_devices_setup(hass, token) -> list[MelCloudDevice]: """Query connected devices from MELCloud.""" session = hass.helpers.aiohttp_client.async_get_clientsession() try: diff --git a/homeassistant/components/melcloud/climate.py b/homeassistant/components/melcloud/climate.py index 1abb86cf5e5..8e45cc3d9a4 100644 --- a/homeassistant/components/melcloud/climate.py +++ b/homeassistant/components/melcloud/climate.py @@ -1,6 +1,8 @@ """Platform for climate integration.""" +from __future__ import annotations + from datetime import timedelta -from typing import Any, Dict, List, Optional +from typing import Any from pymelcloud import DEVICE_TYPE_ATA, DEVICE_TYPE_ATW, AtaDevice, AtwDevice import pymelcloud.ata_device as ata @@ -114,7 +116,7 @@ class MelCloudClimate(ClimateEntity): return self.api.device_info @property - def target_temperature_step(self) -> Optional[float]: + def target_temperature_step(self) -> float | None: """Return the supported step of target temperature.""" return self._base_device.temperature_increment @@ -128,7 +130,7 @@ class AtaDeviceClimate(MelCloudClimate): self._device = ata_device @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return f"{self.api.device.serial}-{self.api.device.mac}" @@ -138,7 +140,7 @@ class AtaDeviceClimate(MelCloudClimate): return self._name @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the optional state attributes with device specific additions.""" attr = {} @@ -190,19 +192,19 @@ class AtaDeviceClimate(MelCloudClimate): await self._device.set(props) @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return [HVAC_MODE_OFF] + [ ATA_HVAC_MODE_LOOKUP.get(mode) for mode in self._device.operation_modes ] @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._device.room_temperature @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return self._device.target_temperature @@ -213,7 +215,7 @@ class AtaDeviceClimate(MelCloudClimate): ) @property - def fan_mode(self) -> Optional[str]: + def fan_mode(self) -> str | None: """Return the fan setting.""" return self._device.fan_speed @@ -222,7 +224,7 @@ class AtaDeviceClimate(MelCloudClimate): await self._device.set({"fan_speed": fan_mode}) @property - def fan_modes(self) -> Optional[List[str]]: + def fan_modes(self) -> list[str] | None: """Return the list of available fan modes.""" return self._device.fan_speeds @@ -243,7 +245,7 @@ class AtaDeviceClimate(MelCloudClimate): await self._device.set({ata.PROPERTY_VANE_VERTICAL: position}) @property - def swing_mode(self) -> Optional[str]: + def swing_mode(self) -> str | None: """Return vertical vane position or mode.""" return self._device.vane_vertical @@ -252,7 +254,7 @@ class AtaDeviceClimate(MelCloudClimate): await self.async_set_vane_vertical(swing_mode) @property - def swing_modes(self) -> Optional[str]: + def swing_modes(self) -> str | None: """Return a list of available vertical vane positions and modes.""" return self._device.vane_vertical_positions @@ -300,7 +302,7 @@ class AtwDeviceZoneClimate(MelCloudClimate): self._zone = atw_zone @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return f"{self.api.device.serial}-{self._zone.zone_index}" @@ -310,7 +312,7 @@ class AtwDeviceZoneClimate(MelCloudClimate): return f"{self._name} {self._zone.name}" @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the optional state attributes with device specific additions.""" data = { ATTR_STATUS: ATW_ZONE_HVAC_MODE_LOOKUP.get( @@ -351,17 +353,17 @@ class AtwDeviceZoneClimate(MelCloudClimate): await self._device.set(props) @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return [self.hvac_mode] @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._zone.room_temperature @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return self._zone.target_temperature diff --git a/homeassistant/components/melcloud/config_flow.py b/homeassistant/components/melcloud/config_flow.py index 41ce24989a5..a487a446d9b 100644 --- a/homeassistant/components/melcloud/config_flow.py +++ b/homeassistant/components/melcloud/config_flow.py @@ -1,6 +1,7 @@ """Config flow for the MELCloud platform.""" +from __future__ import annotations + import asyncio -from typing import Optional from aiohttp import ClientError, ClientResponseError from async_timeout import timeout @@ -37,8 +38,8 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self, username: str, *, - password: Optional[str] = None, - token: Optional[str] = None, + password: str | None = None, + token: str | None = None, ): """Create client.""" if password is None and token is None: diff --git a/homeassistant/components/melcloud/water_heater.py b/homeassistant/components/melcloud/water_heater.py index 3474fc07540..e01d78a5270 100644 --- a/homeassistant/components/melcloud/water_heater.py +++ b/homeassistant/components/melcloud/water_heater.py @@ -1,5 +1,5 @@ """Platform for water_heater integration.""" -from typing import List, Optional +from __future__ import annotations from pymelcloud import DEVICE_TYPE_ATW, AtwDevice from pymelcloud.atw_device import ( @@ -49,7 +49,7 @@ class AtwWaterHeater(WaterHeaterEntity): await self._api.async_update() @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return f"{self._api.device.serial}" @@ -83,17 +83,17 @@ class AtwWaterHeater(WaterHeaterEntity): return TEMP_CELSIUS @property - def current_operation(self) -> Optional[str]: + def current_operation(self) -> str | None: """Return current operation as reported by pymelcloud.""" return self._device.operation_mode @property - def operation_list(self) -> List[str]: + def operation_list(self) -> list[str]: """Return the list of available operation modes as reported by pymelcloud.""" return self._device.operation_modes @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._device.tank_temperature @@ -122,11 +122,11 @@ class AtwWaterHeater(WaterHeaterEntity): return SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE @property - def min_temp(self) -> Optional[float]: + def min_temp(self) -> float | None: """Return the minimum temperature.""" return self._device.target_tank_temperature_min @property - def max_temp(self) -> Optional[float]: + def max_temp(self) -> float | None: """Return the maximum temperature.""" return self._device.target_tank_temperature_max diff --git a/homeassistant/components/met/config_flow.py b/homeassistant/components/met/config_flow.py index 6b3d6735f46..b9d50ba59a5 100644 --- a/homeassistant/components/met/config_flow.py +++ b/homeassistant/components/met/config_flow.py @@ -1,5 +1,7 @@ """Config flow to configure Met component.""" -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import Any import voluptuous as vol @@ -73,9 +75,7 @@ class MetFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors=self._errors, ) - async def async_step_import( - self, user_input: Optional[Dict] = None - ) -> Dict[str, Any]: + async def async_step_import(self, user_input: dict | None = None) -> dict[str, Any]: """Handle configuration by yaml file.""" return await self.async_step_user(user_input) diff --git a/homeassistant/components/minecraft_server/__init__.py b/homeassistant/components/minecraft_server/__init__.py index 0e7096881f5..f76e8e8467e 100644 --- a/homeassistant/components/minecraft_server/__init__.py +++ b/homeassistant/components/minecraft_server/__init__.py @@ -1,9 +1,10 @@ """The Minecraft Server integration.""" +from __future__ import annotations import asyncio from datetime import datetime, timedelta import logging -from typing import Any, Dict +from typing import Any from mcstatus.server import MinecraftServer as MCStatus @@ -260,7 +261,7 @@ class MinecraftServerEntity(Entity): return self._unique_id @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information.""" return self._device_info diff --git a/homeassistant/components/minecraft_server/helpers.py b/homeassistant/components/minecraft_server/helpers.py index 7f9380cdec2..f6409ce525d 100644 --- a/homeassistant/components/minecraft_server/helpers.py +++ b/homeassistant/components/minecraft_server/helpers.py @@ -1,6 +1,7 @@ """Helper functions for the Minecraft Server integration.""" +from __future__ import annotations -from typing import Any, Dict +from typing import Any import aiodns @@ -10,7 +11,7 @@ from homeassistant.helpers.typing import HomeAssistantType from .const import SRV_RECORD_PREFIX -async def async_check_srv_record(hass: HomeAssistantType, host: str) -> Dict[str, Any]: +async def async_check_srv_record(hass: HomeAssistantType, host: str) -> dict[str, Any]: """Check if the given host is a valid Minecraft SRV record.""" # Check if 'host' is a valid SRV record. return_value = None diff --git a/homeassistant/components/minecraft_server/sensor.py b/homeassistant/components/minecraft_server/sensor.py index 8cc626390dd..4cc9e9746bb 100644 --- a/homeassistant/components/minecraft_server/sensor.py +++ b/homeassistant/components/minecraft_server/sensor.py @@ -1,5 +1,7 @@ """The Minecraft Server sensor platform.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from homeassistant.config_entries import ConfigEntry from homeassistant.const import TIME_MILLISECONDS @@ -151,7 +153,7 @@ class MinecraftServerPlayersOnlineSensor(MinecraftServerSensorEntity): self._extra_state_attributes = extra_state_attributes @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return players list in device state attributes.""" return self._extra_state_attributes diff --git a/homeassistant/components/minio/__init__.py b/homeassistant/components/minio/__init__.py index 178058986cc..6e7174b60ee 100644 --- a/homeassistant/components/minio/__init__.py +++ b/homeassistant/components/minio/__init__.py @@ -1,9 +1,10 @@ """Minio component.""" +from __future__ import annotations + import logging import os from queue import Queue import threading -from typing import List import voluptuous as vol @@ -230,7 +231,7 @@ class MinioListener: bucket_name: str, prefix: str, suffix: str, - events: List[str], + events: list[str], ): """Create Listener.""" self._queue = queue diff --git a/homeassistant/components/minio/minio_helper.py b/homeassistant/components/minio/minio_helper.py index 2aaba9d4085..f2d86067552 100644 --- a/homeassistant/components/minio/minio_helper.py +++ b/homeassistant/components/minio/minio_helper.py @@ -1,4 +1,6 @@ """Minio helper methods.""" +from __future__ import annotations + from collections.abc import Iterable import json import logging @@ -6,7 +8,7 @@ from queue import Queue import re import threading import time -from typing import Iterator, List +from typing import Iterator from urllib.parse import unquote from minio import Minio @@ -38,7 +40,7 @@ def create_minio_client( def get_minio_notification_response( - minio_client, bucket_name: str, prefix: str, suffix: str, events: List[str] + minio_client, bucket_name: str, prefix: str, suffix: str, events: list[str] ): """Start listening to minio events. Copied from minio-py.""" query = {"prefix": prefix, "suffix": suffix, "events": events} @@ -87,7 +89,7 @@ class MinioEventThread(threading.Thread): bucket_name: str, prefix: str, suffix: str, - events: List[str], + events: list[str], ): """Copy over all Minio client options.""" super().__init__() diff --git a/homeassistant/components/mobile_app/device_action.py b/homeassistant/components/mobile_app/device_action.py index 2592d4b486b..33a7510da21 100644 --- a/homeassistant/components/mobile_app/device_action.py +++ b/homeassistant/components/mobile_app/device_action.py @@ -1,5 +1,5 @@ """Provides device actions for Mobile App.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -22,7 +22,7 @@ ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Mobile App devices.""" webhook_id = webhook_id_from_device_id(hass, device_id) @@ -33,7 +33,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" webhook_id = webhook_id_from_device_id(hass, config[CONF_DEVICE_ID]) diff --git a/homeassistant/components/mobile_app/helpers.py b/homeassistant/components/mobile_app/helpers.py index fed322df464..63d638cd9e5 100644 --- a/homeassistant/components/mobile_app/helpers.py +++ b/homeassistant/components/mobile_app/helpers.py @@ -1,7 +1,9 @@ """Helpers for mobile_app.""" +from __future__ import annotations + import json import logging -from typing import Callable, Dict, Tuple +from typing import Callable from aiohttp.web import Response, json_response from nacl.encoding import Base64Encoder @@ -36,7 +38,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -def setup_decrypt() -> Tuple[int, Callable]: +def setup_decrypt() -> tuple[int, Callable]: """Return decryption function and length of key. Async friendly. @@ -49,7 +51,7 @@ def setup_decrypt() -> Tuple[int, Callable]: return (SecretBox.KEY_SIZE, decrypt) -def setup_encrypt() -> Tuple[int, Callable]: +def setup_encrypt() -> tuple[int, Callable]: """Return encryption function and length of key. Async friendly. @@ -62,7 +64,7 @@ def setup_encrypt() -> Tuple[int, Callable]: return (SecretBox.KEY_SIZE, encrypt) -def _decrypt_payload(key: str, ciphertext: str) -> Dict[str, str]: +def _decrypt_payload(key: str, ciphertext: str) -> dict[str, str]: """Decrypt encrypted payload.""" try: keylen, decrypt = setup_decrypt() @@ -88,12 +90,12 @@ def _decrypt_payload(key: str, ciphertext: str) -> Dict[str, str]: return None -def registration_context(registration: Dict) -> Context: +def registration_context(registration: dict) -> Context: """Generate a context from a request.""" return Context(user_id=registration[CONF_USER_ID]) -def empty_okay_response(headers: Dict = None, status: int = HTTP_OK) -> Response: +def empty_okay_response(headers: dict = None, status: int = HTTP_OK) -> Response: """Return a Response with empty JSON object and a 200.""" return Response( text="{}", status=status, content_type=CONTENT_TYPE_JSON, headers=headers @@ -121,7 +123,7 @@ def supports_encryption() -> bool: return False -def safe_registration(registration: Dict) -> Dict: +def safe_registration(registration: dict) -> dict: """Return a registration without sensitive values.""" # Sensitive values: webhook_id, secret, cloudhook_url return { @@ -137,7 +139,7 @@ def safe_registration(registration: Dict) -> Dict: } -def savable_state(hass: HomeAssistantType) -> Dict: +def savable_state(hass: HomeAssistantType) -> dict: """Return a clean object containing things that should be saved.""" return { DATA_DELETED_IDS: hass.data[DOMAIN][DATA_DELETED_IDS], @@ -145,7 +147,7 @@ def savable_state(hass: HomeAssistantType) -> Dict: def webhook_response( - data, *, registration: Dict, status: int = HTTP_OK, headers: Dict = None + data, *, registration: dict, status: int = HTTP_OK, headers: dict = None ) -> Response: """Return a encrypted response if registration supports it.""" data = json.dumps(data, cls=JSONEncoder) @@ -165,7 +167,7 @@ def webhook_response( ) -def device_info(registration: Dict) -> Dict: +def device_info(registration: dict) -> dict: """Return the device info for this registration.""" return { "identifiers": {(DOMAIN, registration[ATTR_DEVICE_ID])}, diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py index 4bd8d0cd76b..5583b7c58d1 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -1,6 +1,7 @@ """Provides an HTTP API for mobile_app.""" +from __future__ import annotations + import secrets -from typing import Dict from aiohttp.web import Request, Response import emoji @@ -58,7 +59,7 @@ class RegistrationsView(HomeAssistantView): extra=vol.REMOVE_EXTRA, ) ) - async def post(self, request: Request, data: Dict) -> Response: + async def post(self, request: Request, data: dict) -> Response: """Handle the POST request for registration.""" hass = request.app["hass"] diff --git a/homeassistant/components/mobile_app/util.py b/homeassistant/components/mobile_app/util.py index 60dfe242e04..b0a3f52e394 100644 --- a/homeassistant/components/mobile_app/util.py +++ b/homeassistant/components/mobile_app/util.py @@ -1,5 +1,7 @@ """Mobile app utility functions.""" -from typing import TYPE_CHECKING, Optional +from __future__ import annotations + +from typing import TYPE_CHECKING from homeassistant.core import callback @@ -18,7 +20,7 @@ if TYPE_CHECKING: @callback -def webhook_id_from_device_id(hass, device_id: str) -> Optional[str]: +def webhook_id_from_device_id(hass, device_id: str) -> str | None: """Get webhook ID from device ID.""" if DOMAIN not in hass.data: return None @@ -39,7 +41,7 @@ def supports_push(hass, webhook_id: str) -> bool: @callback -def get_notify_service(hass, webhook_id: str) -> Optional[str]: +def get_notify_service(hass, webhook_id: str) -> str | None: """Return the notify service for this webhook ID.""" notify_service: "MobileAppNotificationService" = hass.data[DOMAIN][DATA_NOTIFY] diff --git a/homeassistant/components/modbus/binary_sensor.py b/homeassistant/components/modbus/binary_sensor.py index 8e91945d073..16cd191bba7 100644 --- a/homeassistant/components/modbus/binary_sensor.py +++ b/homeassistant/components/modbus/binary_sensor.py @@ -1,5 +1,5 @@ """Support for Modbus Coil and Discrete Input sensors.""" -from typing import Optional +from __future__ import annotations from pymodbus.exceptions import ConnectionException, ModbusException from pymodbus.pdu import ExceptionResponse @@ -94,7 +94,7 @@ class ModbusBinarySensor(BinarySensorEntity): return self._value @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class of the sensor.""" return self._device_class diff --git a/homeassistant/components/modbus/climate.py b/homeassistant/components/modbus/climate.py index 45cfbf5eb57..d32f2ae3cf5 100644 --- a/homeassistant/components/modbus/climate.py +++ b/homeassistant/components/modbus/climate.py @@ -1,8 +1,10 @@ """Support for Generic Modbus Thermostats.""" +from __future__ import annotations + from datetime import timedelta import logging import struct -from typing import Any, Dict, Optional +from typing import Any from pymodbus.exceptions import ConnectionException, ModbusException from pymodbus.pdu import ExceptionResponse @@ -57,7 +59,7 @@ async def async_setup_platform( hass: HomeAssistantType, config: ConfigType, async_add_entities, - discovery_info: Optional[DiscoveryInfoType] = None, + discovery_info: DiscoveryInfoType | None = None, ): """Read configuration and create Modbus climate.""" if discovery_info is None: @@ -108,7 +110,7 @@ class ModbusThermostat(ClimateEntity): def __init__( self, hub: ModbusHub, - config: Dict[str, Any], + config: dict[str, Any], ): """Initialize the modbus thermostat.""" self._hub: ModbusHub = hub @@ -232,7 +234,7 @@ class ModbusThermostat(ClimateEntity): self.schedule_update_ha_state() - def _read_register(self, register_type, register) -> Optional[float]: + def _read_register(self, register_type, register) -> float | None: """Read register using the Modbus hub slave.""" try: if register_type == CALL_TYPE_REGISTER_INPUT: diff --git a/homeassistant/components/modbus/cover.py b/homeassistant/components/modbus/cover.py index 09a465a2cdd..a8003676640 100644 --- a/homeassistant/components/modbus/cover.py +++ b/homeassistant/components/modbus/cover.py @@ -1,6 +1,8 @@ """Support for Modbus covers.""" +from __future__ import annotations + from datetime import timedelta -from typing import Any, Dict, Optional +from typing import Any from pymodbus.exceptions import ConnectionException, ModbusException from pymodbus.pdu import ExceptionResponse @@ -41,7 +43,7 @@ async def async_setup_platform( hass: HomeAssistantType, config: ConfigType, async_add_entities, - discovery_info: Optional[DiscoveryInfoType] = None, + discovery_info: DiscoveryInfoType | None = None, ): """Read configuration and create Modbus cover.""" if discovery_info is None: @@ -61,7 +63,7 @@ class ModbusCover(CoverEntity, RestoreEntity): def __init__( self, hub: ModbusHub, - config: Dict[str, Any], + config: dict[str, Any], ): """Initialize the modbus cover.""" self._hub: ModbusHub = hub @@ -108,7 +110,7 @@ class ModbusCover(CoverEntity, RestoreEntity): ) @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class of the sensor.""" return self._device_class @@ -178,7 +180,7 @@ class ModbusCover(CoverEntity, RestoreEntity): self.schedule_update_ha_state() - def _read_status_register(self) -> Optional[int]: + def _read_status_register(self) -> int | None: """Read status register using the Modbus hub slave.""" try: if self._status_register_type == CALL_TYPE_REGISTER_INPUT: @@ -212,7 +214,7 @@ class ModbusCover(CoverEntity, RestoreEntity): self._available = True - def _read_coil(self) -> Optional[bool]: + def _read_coil(self) -> bool | None: """Read coil using the Modbus hub slave.""" try: result = self._hub.read_coils(self._slave, self._coil, 1) diff --git a/homeassistant/components/modbus/sensor.py b/homeassistant/components/modbus/sensor.py index 656e5e2986d..d0fad973802 100644 --- a/homeassistant/components/modbus/sensor.py +++ b/homeassistant/components/modbus/sensor.py @@ -1,7 +1,9 @@ """Support for Modbus Register sensors.""" +from __future__ import annotations + import logging import struct -from typing import Any, Optional, Union +from typing import Any from pymodbus.exceptions import ConnectionException, ModbusException from pymodbus.pdu import ExceptionResponse @@ -44,7 +46,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -def number(value: Any) -> Union[int, float]: +def number(value: Any) -> int | float: """Coerce a value to number without losing precision.""" if isinstance(value, int): return value @@ -217,7 +219,7 @@ class ModbusRegisterSensor(RestoreEntity): return self._unit_of_measurement @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class of the sensor.""" return self._device_class diff --git a/homeassistant/components/modbus/switch.py b/homeassistant/components/modbus/switch.py index 36fbef08428..bf2233e2407 100644 --- a/homeassistant/components/modbus/switch.py +++ b/homeassistant/components/modbus/switch.py @@ -1,7 +1,9 @@ """Support for Modbus switches.""" +from __future__ import annotations + from abc import ABC import logging -from typing import Any, Dict, Optional +from typing import Any from pymodbus.exceptions import ConnectionException, ModbusException from pymodbus.pdu import ExceptionResponse @@ -99,7 +101,7 @@ async def async_setup_platform( class ModbusBaseSwitch(ToggleEntity, RestoreEntity, ABC): """Base class representing a Modbus switch.""" - def __init__(self, hub: ModbusHub, config: Dict[str, Any]): + def __init__(self, hub: ModbusHub, config: dict[str, Any]): """Initialize the switch.""" self._hub: ModbusHub = hub self._name = config[CONF_NAME] @@ -133,7 +135,7 @@ class ModbusBaseSwitch(ToggleEntity, RestoreEntity, ABC): class ModbusCoilSwitch(ModbusBaseSwitch, SwitchEntity): """Representation of a Modbus coil switch.""" - def __init__(self, hub: ModbusHub, config: Dict[str, Any]): + def __init__(self, hub: ModbusHub, config: dict[str, Any]): """Initialize the coil switch.""" super().__init__(hub, config) self._coil = config[CALL_TYPE_COIL] @@ -184,7 +186,7 @@ class ModbusCoilSwitch(ModbusBaseSwitch, SwitchEntity): class ModbusRegisterSwitch(ModbusBaseSwitch, SwitchEntity): """Representation of a Modbus register switch.""" - def __init__(self, hub: ModbusHub, config: Dict[str, Any]): + def __init__(self, hub: ModbusHub, config: dict[str, Any]): """Initialize the register switch.""" super().__init__(hub, config) self._register = config[CONF_REGISTER] @@ -238,7 +240,7 @@ class ModbusRegisterSwitch(ModbusBaseSwitch, SwitchEntity): value, ) - def _read_register(self) -> Optional[int]: + def _read_register(self) -> int | None: try: if self._register_type == CALL_TYPE_REGISTER_INPUT: result = self._hub.read_input_registers( diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 503083b0067..ce2d413e1b6 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -1,4 +1,6 @@ """Support for MQTT message handling.""" +from __future__ import annotations + import asyncio from functools import lru_cache, partial, wraps import inspect @@ -8,7 +10,7 @@ from operator import attrgetter import os import ssl import time -from typing import Any, Callable, List, Optional, Union +from typing import Any, Callable, Union import uuid import attr @@ -310,7 +312,7 @@ async def async_subscribe( topic: str, msg_callback: MessageCallbackType, qos: int = DEFAULT_QOS, - encoding: Optional[str] = "utf-8", + encoding: str | None = "utf-8", ): """Subscribe to an MQTT topic. @@ -385,7 +387,7 @@ async def _async_setup_discovery( async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: """Start the MQTT protocol service.""" - conf: Optional[ConfigType] = config.get(DOMAIN) + conf: ConfigType | None = config.get(DOMAIN) websocket_api.async_register_command(hass, websocket_subscribe) websocket_api.async_register_command(hass, websocket_remove_device) @@ -552,7 +554,7 @@ class MQTT: self.hass = hass self.config_entry = config_entry self.conf = conf - self.subscriptions: List[Subscription] = [] + self.subscriptions: list[Subscription] = [] self.connected = False self._ha_started = asyncio.Event() self._last_subscribe = time.time() @@ -730,7 +732,7 @@ class MQTT: topic: str, msg_callback: MessageCallbackType, qos: int, - encoding: Optional[str] = None, + encoding: str | None = None, ) -> Callable[[], None]: """Set up a subscription to a topic with the provided qos. diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index d6e2ee0fc65..1e4981d7d6f 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -1,6 +1,8 @@ """Provides device automations for MQTT.""" +from __future__ import annotations + import logging -from typing import Callable, List, Optional +from typing import Callable import attr import voluptuous as vol @@ -86,7 +88,7 @@ class TriggerInstance: action: AutomationActionType = attr.ib() automation_info: dict = attr.ib() trigger: "Trigger" = attr.ib() - remove: Optional[CALLBACK_TYPE] = attr.ib(default=None) + remove: CALLBACK_TYPE | None = attr.ib(default=None) async def async_attach_trigger(self): """Attach MQTT trigger.""" @@ -126,7 +128,7 @@ class Trigger: topic: str = attr.ib() type: str = attr.ib() value_template: str = attr.ib() - trigger_instances: List[TriggerInstance] = attr.ib(factory=list) + trigger_instances: list[TriggerInstance] = attr.ib(factory=list) async def add_trigger(self, action, automation_info): """Add MQTT trigger.""" @@ -285,7 +287,7 @@ async def async_device_removed(hass: HomeAssistant, device_id: str): ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for MQTT devices.""" triggers = [] diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 5737dad255c..332632f4e0f 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -1,8 +1,9 @@ """MQTT component mixins and helpers.""" +from __future__ import annotations + from abc import abstractmethod import json import logging -from typing import Optional import voluptuous as vol @@ -502,7 +503,7 @@ def device_info_from_config(config): class MqttEntityDeviceInfo(Entity): """Mixin used for mqtt platforms that support the device registry.""" - def __init__(self, device_config: Optional[ConfigType], config_entry=None) -> None: + def __init__(self, device_config: ConfigType | None, config_entry=None) -> None: """Initialize the device mixin.""" self._device_config = device_config self._config_entry = config_entry diff --git a/homeassistant/components/mqtt/models.py b/homeassistant/components/mqtt/models.py index 202f457372c..7cdafeef98d 100644 --- a/homeassistant/components/mqtt/models.py +++ b/homeassistant/components/mqtt/models.py @@ -1,6 +1,8 @@ """Modesl used by multiple MQTT modules.""" +from __future__ import annotations + import datetime as dt -from typing import Callable, Optional, Union +from typing import Callable, Union import attr @@ -15,8 +17,8 @@ class Message: payload: PublishPayloadType = attr.ib() qos: int = attr.ib() retain: bool = attr.ib() - subscribed_topic: Optional[str] = attr.ib(default=None) - timestamp: Optional[dt.datetime] = attr.ib(default=None) + subscribed_topic: str | None = attr.ib(default=None) + timestamp: dt.datetime | None = attr.ib(default=None) MessageCallbackType = Callable[[Message], None] diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index d32f4c42383..b20595922cd 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -1,7 +1,8 @@ """Support for MQTT sensors.""" +from __future__ import annotations + from datetime import timedelta import functools -from typing import Optional import voluptuous as vol @@ -166,7 +167,7 @@ class MqttSensor(MqttEntity, Entity): return self._state @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class of the sensor.""" return self._config.get(CONF_DEVICE_CLASS) diff --git a/homeassistant/components/mqtt/subscription.py b/homeassistant/components/mqtt/subscription.py index 5c2efabc266..e6c99c09fd5 100644 --- a/homeassistant/components/mqtt/subscription.py +++ b/homeassistant/components/mqtt/subscription.py @@ -1,5 +1,7 @@ """Helper to handle a set of topics to subscribe to.""" -from typing import Any, Callable, Dict, Optional +from __future__ import annotations + +from typing import Any, Callable import attr @@ -19,7 +21,7 @@ class EntitySubscription: hass: HomeAssistantType = attr.ib() topic: str = attr.ib() message_callback: MessageCallbackType = attr.ib() - unsubscribe_callback: Optional[Callable[[], None]] = attr.ib() + unsubscribe_callback: Callable[[], None] | None = attr.ib() qos: int = attr.ib(default=0) encoding: str = attr.ib(default="utf-8") @@ -62,8 +64,8 @@ class EntitySubscription: @bind_hass async def async_subscribe_topics( hass: HomeAssistantType, - new_state: Optional[Dict[str, EntitySubscription]], - topics: Dict[str, Any], + new_state: dict[str, EntitySubscription] | None, + topics: dict[str, Any], ): """(Re)Subscribe to a set of MQTT topics. diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index 7dd118099f7..c9ad496762d 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -1,8 +1,10 @@ """Connect to a MySensors gateway via pymysensors API.""" +from __future__ import annotations + import asyncio from functools import partial import logging -from typing import Callable, Dict, List, Optional, Tuple, Type, Union +from typing import Callable from mysensors import BaseAsyncGateway import voluptuous as vol @@ -265,13 +267,13 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo def setup_mysensors_platform( hass: HomeAssistant, domain: str, # hass platform name - discovery_info: Dict[str, List[DevId]], - device_class: Union[Type[MySensorsDevice], Dict[SensorType, Type[MySensorsEntity]]], - device_args: Optional[ - Tuple - ] = None, # extra arguments that will be given to the entity constructor - async_add_entities: Optional[Callable] = None, -) -> Optional[List[MySensorsDevice]]: + discovery_info: dict[str, list[DevId]], + device_class: type[MySensorsDevice] | dict[SensorType, type[MySensorsEntity]], + device_args: ( + None | tuple + ) = None, # extra arguments that will be given to the entity constructor + async_add_entities: Callable | None = None, +) -> list[MySensorsDevice] | None: """Set up a MySensors platform. Sets up a bunch of instances of a single platform that is supported by this integration. @@ -281,10 +283,10 @@ def setup_mysensors_platform( """ if device_args is None: device_args = () - new_devices: List[MySensorsDevice] = [] - new_dev_ids: List[DevId] = discovery_info[ATTR_DEVICES] + new_devices: list[MySensorsDevice] = [] + new_dev_ids: list[DevId] = discovery_info[ATTR_DEVICES] for dev_id in new_dev_ids: - devices: Dict[DevId, MySensorsDevice] = get_mysensors_devices(hass, domain) + devices: dict[DevId, MySensorsDevice] = get_mysensors_devices(hass, domain) if dev_id in devices: _LOGGER.debug( "Skipping setup of %s for platform %s as it already exists", @@ -293,7 +295,7 @@ def setup_mysensors_platform( ) continue gateway_id, node_id, child_id, value_type = dev_id - gateway: Optional[BaseAsyncGateway] = get_mysensors_gateway(hass, gateway_id) + gateway: BaseAsyncGateway | None = get_mysensors_gateway(hass, gateway_id) if not gateway: _LOGGER.warning("Skipping setup of %s, no gateway found", dev_id) continue diff --git a/homeassistant/components/mysensors/config_flow.py b/homeassistant/components/mysensors/config_flow.py index 7bf30e4cab5..3a37799106a 100644 --- a/homeassistant/components/mysensors/config_flow.py +++ b/homeassistant/components/mysensors/config_flow.py @@ -1,7 +1,9 @@ """Config flow for MySensors.""" +from __future__ import annotations + import logging import os -from typing import Any, Dict, Optional +from typing import Any from awesomeversion import ( AwesomeVersion, @@ -42,7 +44,7 @@ from .gateway import MQTT_COMPONENT, is_serial_port, is_socket_address, try_conn _LOGGER = logging.getLogger(__name__) -def _get_schema_common(user_input: Dict[str, str]) -> dict: +def _get_schema_common(user_input: dict[str, str]) -> dict: """Create a schema with options common to all gateway types.""" schema = { vol.Required( @@ -57,7 +59,7 @@ def _get_schema_common(user_input: Dict[str, str]) -> dict: return schema -def _validate_version(version: str) -> Dict[str, str]: +def _validate_version(version: str) -> dict[str, str]: """Validate a version string from the user.""" version_okay = False try: @@ -75,7 +77,7 @@ def _validate_version(version: str) -> Dict[str, str]: def _is_same_device( - gw_type: ConfGatewayType, user_input: Dict[str, str], entry: ConfigEntry + gw_type: ConfGatewayType, user_input: dict[str, str], entry: ConfigEntry ): """Check if another ConfigDevice is actually the same as user_input. @@ -102,9 +104,9 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Set up config flow.""" - self._gw_type: Optional[str] = None + self._gw_type: str | None = None - async def async_step_import(self, user_input: Optional[Dict[str, str]] = None): + async def async_step_import(self, user_input: dict[str, str] | None = None): """Import a config entry. This method is called by async_setup and it has already @@ -124,12 +126,12 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): else: user_input[CONF_GATEWAY_TYPE] = CONF_GATEWAY_TYPE_SERIAL - result: Dict[str, Any] = await self.async_step_user(user_input=user_input) + result: dict[str, Any] = await self.async_step_user(user_input=user_input) if result["type"] == "form": return self.async_abort(reason=next(iter(result["errors"].values()))) return result - async def async_step_user(self, user_input: Optional[Dict[str, str]] = None): + async def async_step_user(self, user_input: dict[str, str] | None = None): """Create a config entry from frontend user input.""" schema = {vol.Required(CONF_GATEWAY_TYPE): vol.In(CONF_GATEWAY_TYPE_ALL)} schema = vol.Schema(schema) @@ -146,7 +148,7 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="user", data_schema=schema) - async def async_step_gw_serial(self, user_input: Optional[Dict[str, str]] = None): + async def async_step_gw_serial(self, user_input: dict[str, str] | None = None): """Create config entry for a serial gateway.""" errors = {} if user_input is not None: @@ -175,7 +177,7 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): step_id="gw_serial", data_schema=schema, errors=errors ) - async def async_step_gw_tcp(self, user_input: Optional[Dict[str, str]] = None): + async def async_step_gw_tcp(self, user_input: dict[str, str] | None = None): """Create a config entry for a tcp gateway.""" errors = {} if user_input is not None: @@ -213,7 +215,7 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return True return False - async def async_step_gw_mqtt(self, user_input: Optional[Dict[str, str]] = None): + async def async_step_gw_mqtt(self, user_input: dict[str, str] | None = None): """Create a config entry for a mqtt gateway.""" errors = {} if user_input is not None: @@ -269,8 +271,8 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @callback def _async_create_entry( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Create the config entry.""" return self.async_create_entry( title=f"{user_input[CONF_DEVICE]}", @@ -283,9 +285,9 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def validate_common( self, gw_type: ConfGatewayType, - errors: Dict[str, str], - user_input: Optional[Dict[str, str]] = None, - ) -> Dict[str, str]: + errors: dict[str, str], + user_input: dict[str, str] | None = None, + ) -> dict[str, str]: """Validate parameters common to all gateway types.""" if user_input is not None: errors.update(_validate_version(user_input.get(CONF_VERSION))) diff --git a/homeassistant/components/mysensors/const.py b/homeassistant/components/mysensors/const.py index 1b8fa5e24e8..7a9027d9b72 100644 --- a/homeassistant/components/mysensors/const.py +++ b/homeassistant/components/mysensors/const.py @@ -1,6 +1,8 @@ """MySensors constants.""" +from __future__ import annotations + from collections import defaultdict -from typing import Dict, List, Literal, Set, Tuple +from typing import Literal, Tuple ATTR_DEVICES: str = "devices" ATTR_GATEWAY_ID: str = "gateway_id" @@ -21,7 +23,7 @@ ConfGatewayType = Literal["Serial", "TCP", "MQTT"] CONF_GATEWAY_TYPE_SERIAL: ConfGatewayType = "Serial" CONF_GATEWAY_TYPE_TCP: ConfGatewayType = "TCP" CONF_GATEWAY_TYPE_MQTT: ConfGatewayType = "MQTT" -CONF_GATEWAY_TYPE_ALL: List[str] = [ +CONF_GATEWAY_TYPE_ALL: list[str] = [ CONF_GATEWAY_TYPE_MQTT, CONF_GATEWAY_TYPE_SERIAL, CONF_GATEWAY_TYPE_TCP, @@ -62,7 +64,7 @@ DevId = Tuple[GatewayId, int, int, int] # The MySensors integration brings these together by creating an entity for every v_type of every child_id of every node. # The DevId tuple perfectly captures this. -BINARY_SENSOR_TYPES: Dict[SensorType, Set[ValueType]] = { +BINARY_SENSOR_TYPES: dict[SensorType, set[ValueType]] = { "S_DOOR": {"V_TRIPPED"}, "S_MOTION": {"V_TRIPPED"}, "S_SMOKE": {"V_TRIPPED"}, @@ -73,23 +75,23 @@ BINARY_SENSOR_TYPES: Dict[SensorType, Set[ValueType]] = { "S_MOISTURE": {"V_TRIPPED"}, } -CLIMATE_TYPES: Dict[SensorType, Set[ValueType]] = {"S_HVAC": {"V_HVAC_FLOW_STATE"}} +CLIMATE_TYPES: dict[SensorType, set[ValueType]] = {"S_HVAC": {"V_HVAC_FLOW_STATE"}} -COVER_TYPES: Dict[SensorType, Set[ValueType]] = { +COVER_TYPES: dict[SensorType, set[ValueType]] = { "S_COVER": {"V_DIMMER", "V_PERCENTAGE", "V_LIGHT", "V_STATUS"} } -DEVICE_TRACKER_TYPES: Dict[SensorType, Set[ValueType]] = {"S_GPS": {"V_POSITION"}} +DEVICE_TRACKER_TYPES: dict[SensorType, set[ValueType]] = {"S_GPS": {"V_POSITION"}} -LIGHT_TYPES: Dict[SensorType, Set[ValueType]] = { +LIGHT_TYPES: dict[SensorType, set[ValueType]] = { "S_DIMMER": {"V_DIMMER", "V_PERCENTAGE"}, "S_RGB_LIGHT": {"V_RGB"}, "S_RGBW_LIGHT": {"V_RGBW"}, } -NOTIFY_TYPES: Dict[SensorType, Set[ValueType]] = {"S_INFO": {"V_TEXT"}} +NOTIFY_TYPES: dict[SensorType, set[ValueType]] = {"S_INFO": {"V_TEXT"}} -SENSOR_TYPES: Dict[SensorType, Set[ValueType]] = { +SENSOR_TYPES: dict[SensorType, set[ValueType]] = { "S_SOUND": {"V_LEVEL"}, "S_VIBRATION": {"V_LEVEL"}, "S_MOISTURE": {"V_LEVEL"}, @@ -117,7 +119,7 @@ SENSOR_TYPES: Dict[SensorType, Set[ValueType]] = { "S_DUST": {"V_DUST_LEVEL", "V_LEVEL"}, } -SWITCH_TYPES: Dict[SensorType, Set[ValueType]] = { +SWITCH_TYPES: dict[SensorType, set[ValueType]] = { "S_LIGHT": {"V_LIGHT"}, "S_BINARY": {"V_STATUS"}, "S_DOOR": {"V_ARMED"}, @@ -134,7 +136,7 @@ SWITCH_TYPES: Dict[SensorType, Set[ValueType]] = { } -PLATFORM_TYPES: Dict[str, Dict[SensorType, Set[ValueType]]] = { +PLATFORM_TYPES: dict[str, dict[SensorType, set[ValueType]]] = { "binary_sensor": BINARY_SENSOR_TYPES, "climate": CLIMATE_TYPES, "cover": COVER_TYPES, @@ -145,13 +147,13 @@ PLATFORM_TYPES: Dict[str, Dict[SensorType, Set[ValueType]]] = { "switch": SWITCH_TYPES, } -FLAT_PLATFORM_TYPES: Dict[Tuple[str, SensorType], Set[ValueType]] = { +FLAT_PLATFORM_TYPES: dict[tuple[str, SensorType], set[ValueType]] = { (platform, s_type_name): v_type_name for platform, platform_types in PLATFORM_TYPES.items() for s_type_name, v_type_name in platform_types.items() } -TYPE_TO_PLATFORMS: Dict[SensorType, List[str]] = defaultdict(list) +TYPE_TO_PLATFORMS: dict[SensorType, list[str]] = defaultdict(list) for platform, platform_types in PLATFORM_TYPES.items(): for s_type_name in platform_types: diff --git a/homeassistant/components/mysensors/device.py b/homeassistant/components/mysensors/device.py index d5ae6c0c156..4e770f70bf0 100644 --- a/homeassistant/components/mysensors/device.py +++ b/homeassistant/components/mysensors/device.py @@ -1,7 +1,9 @@ """Handle MySensors devices.""" +from __future__ import annotations + from functools import partial import logging -from typing import Any, Dict, Optional +from typing import Any from mysensors import BaseAsyncGateway, Sensor from mysensors.sensor import ChildSensor @@ -107,7 +109,7 @@ class MySensorsDevice: return f"{self.gateway_id}-{self.node_id}-{self.child_id}-{self.value_type}" @property - def device_info(self) -> Optional[Dict[str, Any]]: + def device_info(self) -> dict[str, Any] | None: """Return a dict that allows home assistant to puzzle all entities belonging to a node together.""" return { "identifiers": {(DOMAIN, f"{self.gateway_id}-{self.node_id}")}, @@ -196,7 +198,7 @@ class MySensorsDevice: self.hass.loop.call_later(UPDATE_DELAY, delayed_update) -def get_mysensors_devices(hass, domain: str) -> Dict[DevId, MySensorsDevice]: +def get_mysensors_devices(hass, domain: str) -> dict[DevId, MySensorsDevice]: """Return MySensors devices for a hass platform name.""" if MYSENSORS_PLATFORM_DEVICES.format(domain) not in hass.data[DOMAIN]: hass.data[DOMAIN][MYSENSORS_PLATFORM_DEVICES.format(domain)] = {} diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index a7f3a053d3f..6cf8e7d7383 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -1,10 +1,12 @@ """Handle MySensors gateways.""" +from __future__ import annotations + import asyncio from collections import defaultdict import logging import socket import sys -from typing import Any, Callable, Coroutine, Dict, Optional +from typing import Any, Callable, Coroutine import async_timeout from mysensors import BaseAsyncGateway, Message, Sensor, mysensors @@ -63,7 +65,7 @@ def is_socket_address(value): raise vol.Invalid("Device is not a valid domain name or ip address") from err -async def try_connect(hass: HomeAssistantType, user_input: Dict[str, str]) -> bool: +async def try_connect(hass: HomeAssistantType, user_input: dict[str, str]) -> bool: """Try to connect to a gateway and report if it worked.""" if user_input[CONF_DEVICE] == MQTT_COMPONENT: return True # dont validate mqtt. mqtt gateways dont send ready messages :( @@ -73,7 +75,7 @@ async def try_connect(hass: HomeAssistantType, user_input: Dict[str, str]) -> bo def on_conn_made(_: BaseAsyncGateway) -> None: gateway_ready.set() - gateway: Optional[BaseAsyncGateway] = await _get_gateway( + gateway: BaseAsyncGateway | None = await _get_gateway( hass, device=user_input[CONF_DEVICE], version=user_input[CONF_VERSION], @@ -110,7 +112,7 @@ async def try_connect(hass: HomeAssistantType, user_input: Dict[str, str]) -> bo def get_mysensors_gateway( hass: HomeAssistantType, gateway_id: GatewayId -) -> Optional[BaseAsyncGateway]: +) -> BaseAsyncGateway | None: """Return the Gateway for a given GatewayId.""" if MYSENSORS_GATEWAYS not in hass.data[DOMAIN]: hass.data[DOMAIN][MYSENSORS_GATEWAYS] = {} @@ -120,7 +122,7 @@ def get_mysensors_gateway( async def setup_gateway( hass: HomeAssistantType, entry: ConfigEntry -) -> Optional[BaseAsyncGateway]: +) -> BaseAsyncGateway | None: """Set up the Gateway for the given ConfigEntry.""" ready_gateway = await _get_gateway( @@ -145,14 +147,14 @@ async def _get_gateway( device: str, version: str, event_callback: Callable[[Message], None], - persistence_file: Optional[str] = None, - baud_rate: Optional[int] = None, - tcp_port: Optional[int] = None, - topic_in_prefix: Optional[str] = None, - topic_out_prefix: Optional[str] = None, + persistence_file: str | None = None, + baud_rate: int | None = None, + tcp_port: int | None = None, + topic_in_prefix: str | None = None, + topic_out_prefix: str | None = None, retain: bool = False, persistence: bool = True, # old persistence option has been deprecated. kwarg is here so we can run try_connect() without persistence -) -> Optional[BaseAsyncGateway]: +) -> BaseAsyncGateway | None: """Return gateway after setup of the gateway.""" if persistence_file is not None: diff --git a/homeassistant/components/mysensors/handler.py b/homeassistant/components/mysensors/handler.py index a47c9174b23..d21140701f9 100644 --- a/homeassistant/components/mysensors/handler.py +++ b/homeassistant/components/mysensors/handler.py @@ -1,5 +1,5 @@ """Handle MySensors messages.""" -from typing import Dict, List +from __future__ import annotations from mysensors import Message @@ -70,16 +70,16 @@ async def handle_sketch_version( @callback def _handle_child_update( - hass: HomeAssistantType, gateway_id: GatewayId, validated: Dict[str, List[DevId]] + hass: HomeAssistantType, gateway_id: GatewayId, validated: dict[str, list[DevId]] ): """Handle a child update.""" - signals: List[str] = [] + signals: list[str] = [] # Update all platforms for the device via dispatcher. # Add/update entity for validated children. for platform, dev_ids in validated.items(): devices = get_mysensors_devices(hass, platform) - new_dev_ids: List[DevId] = [] + new_dev_ids: list[DevId] = [] for dev_id in dev_ids: if dev_id in devices: signals.append(CHILD_CALLBACK.format(*dev_id)) diff --git a/homeassistant/components/mysensors/helpers.py b/homeassistant/components/mysensors/helpers.py index 0b8dc361158..0d18b243520 100644 --- a/homeassistant/components/mysensors/helpers.py +++ b/homeassistant/components/mysensors/helpers.py @@ -1,8 +1,10 @@ """Helper functions for mysensors package.""" +from __future__ import annotations + from collections import defaultdict from enum import IntEnum import logging -from typing import Callable, DefaultDict, Dict, List, Optional, Set, Union +from typing import Callable, DefaultDict from mysensors import BaseAsyncGateway, Message from mysensors.sensor import ChildSensor @@ -35,7 +37,7 @@ SCHEMAS = Registry() async def on_unload( - hass: HomeAssistantType, entry: Union[ConfigEntry, GatewayId], fnct: Callable + hass: HomeAssistantType, entry: ConfigEntry | GatewayId, fnct: Callable ) -> None: """Register a callback to be called when entry is unloaded. @@ -53,7 +55,7 @@ async def on_unload( @callback def discover_mysensors_platform( - hass: HomeAssistant, gateway_id: GatewayId, platform: str, new_devices: List[DevId] + hass: HomeAssistant, gateway_id: GatewayId, platform: str, new_devices: list[DevId] ) -> None: """Discover a MySensors platform.""" _LOGGER.debug("Discovering platform %s with devIds: %s", platform, new_devices) @@ -150,7 +152,7 @@ def invalid_msg( ) -def validate_set_msg(gateway_id: GatewayId, msg: Message) -> Dict[str, List[DevId]]: +def validate_set_msg(gateway_id: GatewayId, msg: Message) -> dict[str, list[DevId]]: """Validate a set message.""" if not validate_node(msg.gateway, msg.node_id): return {} @@ -171,34 +173,34 @@ def validate_child( gateway: BaseAsyncGateway, node_id: int, child: ChildSensor, - value_type: Optional[int] = None, -) -> DefaultDict[str, List[DevId]]: + value_type: int | None = None, +) -> DefaultDict[str, list[DevId]]: """Validate a child. Returns a dict mapping hass platform names to list of DevId.""" - validated: DefaultDict[str, List[DevId]] = defaultdict(list) + validated: DefaultDict[str, list[DevId]] = defaultdict(list) pres: IntEnum = gateway.const.Presentation set_req: IntEnum = gateway.const.SetReq - child_type_name: Optional[SensorType] = next( + child_type_name: SensorType | None = next( (member.name for member in pres if member.value == child.type), None ) - value_types: Set[int] = {value_type} if value_type else {*child.values} - value_type_names: Set[ValueType] = { + value_types: set[int] = {value_type} if value_type else {*child.values} + value_type_names: set[ValueType] = { member.name for member in set_req if member.value in value_types } - platforms: List[str] = TYPE_TO_PLATFORMS.get(child_type_name, []) + platforms: list[str] = TYPE_TO_PLATFORMS.get(child_type_name, []) if not platforms: _LOGGER.warning("Child type %s is not supported", child.type) return validated for platform in platforms: - platform_v_names: Set[ValueType] = FLAT_PLATFORM_TYPES[ + platform_v_names: set[ValueType] = FLAT_PLATFORM_TYPES[ platform, child_type_name ] - v_names: Set[ValueType] = platform_v_names & value_type_names + v_names: set[ValueType] = platform_v_names & value_type_names if not v_names: - child_value_names: Set[ValueType] = { + child_value_names: set[ValueType] = { member.name for member in set_req if member.value in child.values } - v_names: Set[ValueType] = platform_v_names & child_value_names + v_names: set[ValueType] = platform_v_names & child_value_names for v_name in v_names: child_schema_gen = SCHEMAS.get((platform, v_name), default_schema) From 99f9f8dec031d6e8d5f3f5443950d7980fceb739 Mon Sep 17 00:00:00 2001 From: Raj Laud <50647620+rajlaud@users.noreply.github.com> Date: Thu, 18 Mar 2021 07:07:35 -0500 Subject: [PATCH 457/831] Allow hdmi_cec to recover from lost connection to adapter without restart (#40714) * Only update CecDevice state when there is new data * Replace CecDevice with CecEntity * Support for losing and reconnecting to pycec TcpAdapter * Register listener in async_added_to_hass * Rename hdmi_cec watchdog * Only update CecDevice state when there is new data * Fix flake8 docstring error * Fix linter error * Bump pycec version to 0.5.0 * Bump pycec version to 0.5.1 * Fixe merge mistake Co-authored-by: Erik Montnemery --- homeassistant/components/hdmi_cec/__init__.py | 31 +++++++++++++++++-- .../components/hdmi_cec/manifest.json | 2 +- requirements_all.txt | 2 +- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/hdmi_cec/__init__.py b/homeassistant/components/hdmi_cec/__init__.py index 2f821c1d3a7..d92342c1fb0 100644 --- a/homeassistant/components/hdmi_cec/__init__.py +++ b/homeassistant/components/hdmi_cec/__init__.py @@ -1,6 +1,6 @@ """Support for HDMI CEC.""" from collections import defaultdict -from functools import reduce +from functools import partial, reduce import logging import multiprocessing @@ -38,9 +38,10 @@ from homeassistant.const import ( STATE_ON, STATE_PAUSED, STATE_PLAYING, + STATE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers import discovery +from homeassistant.helpers import discovery, event import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -162,6 +163,9 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) +WATCHDOG_INTERVAL = 120 +EVENT_HDMI_CEC_UNAVAILABLE = "hdmi_cec_unavailable" + def pad_physical_address(addr): """Right-pad a physical address.""" @@ -210,6 +214,18 @@ def setup(hass: HomeAssistant, base_config): adapter = CecAdapter(name=display_name[:12], activate_source=False) hdmi_network = HDMINetwork(adapter, loop=loop) + def _adapter_watchdog(now=None): + _LOGGER.debug("Reached _adapter_watchdog") + event.async_call_later(hass, WATCHDOG_INTERVAL, _adapter_watchdog) + if not adapter.initialized: + _LOGGER.info("Adapter not initialized. Trying to restart.") + hass.bus.fire(EVENT_HDMI_CEC_UNAVAILABLE) + adapter.init() + + hdmi_network.set_initialized_callback( + partial(event.async_call_later, hass, WATCHDOG_INTERVAL, _adapter_watchdog) + ) + def _volume(call): """Increase/decrease volume and mute/unmute system.""" mute_key_mapping = { @@ -327,7 +343,7 @@ def setup(hass: HomeAssistant, base_config): def _shutdown(call): hdmi_network.stop() - def _start_cec(event): + def _start_cec(callback_event): """Register services and start HDMI network to watch for devices.""" hass.services.register( DOMAIN, SERVICE_SEND_COMMAND, _tx, SERVICE_SEND_COMMAND_SCHEMA @@ -364,6 +380,12 @@ class CecEntity(Entity): self._logical_address = logical self.entity_id = "%s.%d" % (DOMAIN, self._logical_address) + def _hdmi_cec_unavailable(self, callback_event): + # Change state to unavailable. Without this, entity would remain in + # its last state, since the state changes are pushed. + self._state = STATE_UNAVAILABLE + self.schedule_update_ha_state(False) + def update(self): """Update device status.""" device = self._device @@ -383,6 +405,9 @@ class CecEntity(Entity): async def async_added_to_hass(self): """Register HDMI callbacks after initialization.""" self._device.set_update_callback(self._update) + self.hass.bus.async_listen( + EVENT_HDMI_CEC_UNAVAILABLE, self._hdmi_cec_unavailable + ) def _update(self, device=None): """Device status changed, schedule an update.""" diff --git a/homeassistant/components/hdmi_cec/manifest.json b/homeassistant/components/hdmi_cec/manifest.json index 90ed7cc3359..4f6975f52df 100644 --- a/homeassistant/components/hdmi_cec/manifest.json +++ b/homeassistant/components/hdmi_cec/manifest.json @@ -2,6 +2,6 @@ "domain": "hdmi_cec", "name": "HDMI-CEC", "documentation": "https://www.home-assistant.io/integrations/hdmi_cec", - "requirements": ["pyCEC==0.4.14"], + "requirements": ["pyCEC==0.5.1"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 40a3e8545be..963b262ec60 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1216,7 +1216,7 @@ py-zabbix==1.1.7 py17track==2.2.2 # homeassistant.components.hdmi_cec -pyCEC==0.4.14 +pyCEC==0.5.1 # homeassistant.components.control4 pyControl4==0.0.6 From 3d2b81a401ba2aaf160517fdbccead44b3bd394c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 13:21:46 +0100 Subject: [PATCH 458/831] Update typing 11 (#48072) --- homeassistant/components/neato/config_flow.py | 9 ++++----- homeassistant/components/nest/camera_sdm.py | 4 ++-- homeassistant/components/nest/climate_sdm.py | 5 ++--- homeassistant/components/nest/config_flow.py | 4 ++-- .../components/nest/device_trigger.py | 6 +++--- homeassistant/components/nest/sensor_sdm.py | 6 +++--- homeassistant/components/netatmo/climate.py | 13 ++++++------ .../components/netatmo/data_handler.py | 8 +++++--- .../components/netatmo/device_trigger.py | 4 ++-- .../components/netatmo/media_source.py | 7 ++++--- .../components/netatmo/netatmo_entity_base.py | 7 ++++--- homeassistant/components/nightscout/sensor.py | 6 ++++-- homeassistant/components/notify/__init__.py | 6 ++++-- .../components/nsw_fuel_station/sensor.py | 5 +++-- .../geo_location.py | 11 +++++----- homeassistant/components/number/__init__.py | 6 ++++-- .../components/number/device_action.py | 10 ++++++---- .../components/number/reproduce_state.py | 12 ++++++----- homeassistant/components/nws/__init__.py | 8 +++++--- .../components/nzbget/config_flow.py | 16 ++++++++------- homeassistant/components/nzbget/sensor.py | 8 +++++--- homeassistant/components/nzbget/switch.py | 6 ++++-- .../components/onewire/onewire_entities.py | 20 ++++++++++--------- .../components/onkyo/media_player.py | 5 +++-- .../components/onvif/binary_sensor.py | 4 ++-- homeassistant/components/onvif/config_flow.py | 5 +++-- homeassistant/components/onvif/device.py | 7 ++++--- homeassistant/components/onvif/event.py | 14 +++++++------ homeassistant/components/onvif/models.py | 6 ++++-- homeassistant/components/onvif/sensor.py | 8 ++++---- .../components/ovo_energy/__init__.py | 6 ++++-- homeassistant/components/ozw/climate.py | 9 +++++---- .../persistent_notification/__init__.py | 8 +++++--- homeassistant/components/person/__init__.py | 12 ++++++----- .../components/person/significant_change.py | 6 ++++-- .../components/philips_js/__init__.py | 14 ++++++------- .../components/philips_js/config_flow.py | 12 ++++++----- .../components/philips_js/device_trigger.py | 6 +++--- .../components/philips_js/media_player.py | 14 +++++++------ .../components/ping/binary_sensor.py | 6 ++++-- homeassistant/components/plaato/sensor.py | 4 ++-- homeassistant/components/plugwise/gateway.py | 4 ++-- .../components/plum_lightpad/config_flow.py | 12 ++++++----- .../components/plum_lightpad/light.py | 6 ++++-- .../components/pvpc_hourly_pricing/sensor.py | 5 +++-- 45 files changed, 206 insertions(+), 154 deletions(-) diff --git a/homeassistant/components/neato/config_flow.py b/homeassistant/components/neato/config_flow.py index 5f7487fe5cc..3f7f7831f54 100644 --- a/homeassistant/components/neato/config_flow.py +++ b/homeassistant/components/neato/config_flow.py @@ -1,6 +1,7 @@ """Config flow for Neato Botvac.""" +from __future__ import annotations + import logging -from typing import Optional import voluptuous as vol @@ -24,7 +25,7 @@ class OAuth2FlowHandler( """Return logger.""" return logging.getLogger(__name__) - async def async_step_user(self, user_input: Optional[dict] = None) -> dict: + async def async_step_user(self, user_input: dict | None = None) -> dict: """Create an entry for the flow.""" current_entries = self._async_current_entries() if current_entries and CONF_TOKEN in current_entries[0].data: @@ -37,9 +38,7 @@ class OAuth2FlowHandler( """Perform reauth upon migration of old entries.""" return await self.async_step_reauth_confirm() - async def async_step_reauth_confirm( - self, user_input: Optional[dict] = None - ) -> dict: + async def async_step_reauth_confirm(self, user_input: dict | None = None) -> dict: """Confirm reauth upon migration of old entries.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/nest/camera_sdm.py b/homeassistant/components/nest/camera_sdm.py index dd84b53d719..ce6ff897a2f 100644 --- a/homeassistant/components/nest/camera_sdm.py +++ b/homeassistant/components/nest/camera_sdm.py @@ -1,8 +1,8 @@ """Support for Google Nest SDM Cameras.""" +from __future__ import annotations import datetime import logging -from typing import Optional from google_nest_sdm.camera_traits import ( CameraEventImageTrait, @@ -74,7 +74,7 @@ class NestCamera(Camera): return False @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" # The API "name" field is a unique device identifier. return f"{self._device.name}-camera" diff --git a/homeassistant/components/nest/climate_sdm.py b/homeassistant/components/nest/climate_sdm.py index b0c64329ffd..169f9d23957 100644 --- a/homeassistant/components/nest/climate_sdm.py +++ b/homeassistant/components/nest/climate_sdm.py @@ -1,6 +1,5 @@ """Support for Google Nest SDM climate devices.""" - -from typing import Optional +from __future__ import annotations from google_nest_sdm.device import Device from google_nest_sdm.device_traits import FanTrait, TemperatureTrait @@ -111,7 +110,7 @@ class ThermostatEntity(ClimateEntity): return False @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" # The API "name" field is a unique device identifier. return self._device.name diff --git a/homeassistant/components/nest/config_flow.py b/homeassistant/components/nest/config_flow.py index 36b0da239a9..fd5eef34c7d 100644 --- a/homeassistant/components/nest/config_flow.py +++ b/homeassistant/components/nest/config_flow.py @@ -11,12 +11,12 @@ and everything else custom is for the old api. When configured with the new api via NestFlowHandler.register_sdm_api, the custom methods just invoke the AbstractOAuth2FlowHandler methods. """ +from __future__ import annotations import asyncio from collections import OrderedDict import logging import os -from typing import Dict import async_timeout import voluptuous as vol @@ -98,7 +98,7 @@ class NestFlowHandler( return logging.getLogger(__name__) @property - def extra_authorize_data(self) -> Dict[str, str]: + def extra_authorize_data(self) -> dict[str, str]: """Extra data that needs to be appended to the authorize url.""" return { "scope": " ".join(SDM_SCOPES), diff --git a/homeassistant/components/nest/device_trigger.py b/homeassistant/components/nest/device_trigger.py index ee16ca1166f..bd2b59c6cfe 100644 --- a/homeassistant/components/nest/device_trigger.py +++ b/homeassistant/components/nest/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Nest.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -39,7 +39,7 @@ async def async_get_nest_device_id(hass: HomeAssistant, device_id: str) -> str: async def async_get_device_trigger_types( hass: HomeAssistant, nest_device_id: str -) -> List[str]: +) -> list[str]: """List event triggers supported for a Nest device.""" # All devices should have already been loaded so any failures here are # "shouldn't happen" cases @@ -58,7 +58,7 @@ async def async_get_device_trigger_types( return trigger_types -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for a Nest device.""" nest_device_id = await async_get_nest_device_id(hass, device_id) if not nest_device_id: diff --git a/homeassistant/components/nest/sensor_sdm.py b/homeassistant/components/nest/sensor_sdm.py index 52490f41f86..946be2e95b0 100644 --- a/homeassistant/components/nest/sensor_sdm.py +++ b/homeassistant/components/nest/sensor_sdm.py @@ -1,7 +1,7 @@ """Support for Google Nest SDM sensors.""" +from __future__ import annotations import logging -from typing import Optional from google_nest_sdm.device import Device from google_nest_sdm.device_traits import HumidityTrait, TemperatureTrait @@ -67,7 +67,7 @@ class SensorBase(Entity): return False @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" # The API "name" field is a unique device identifier. return f"{self._device.name}-{self.device_class}" @@ -113,7 +113,7 @@ class HumiditySensor(SensorBase): """Representation of a Humidity Sensor.""" @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" # The API returns the identifier under the name field. return f"{self._device.name}-humidity" diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index 3b05e263f02..6558717b847 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -1,6 +1,7 @@ """Support for Netatmo Smart thermostats.""" +from __future__ import annotations + import logging -from typing import List, Optional import pyatmo import voluptuous as vol @@ -320,7 +321,7 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): return self._target_temperature @property - def target_temperature_step(self) -> Optional[float]: + def target_temperature_step(self) -> float | None: """Return the supported step of target temperature.""" return PRECISION_HALVES @@ -335,7 +336,7 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): return self._operation_list @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation if supported.""" if self._model == NA_THERM and self._boilerstatus is not None: return CURRENT_HVAC_MAP_NETATMO[self._boilerstatus] @@ -400,12 +401,12 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): self.async_write_ha_state() @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" return self._preset @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" return SUPPORT_PRESET @@ -631,7 +632,7 @@ def interpolate(batterylevel: int, module_type: str) -> int: return int(pct) -def get_all_home_ids(home_data: pyatmo.HomeData) -> List[str]: +def get_all_home_ids(home_data: pyatmo.HomeData) -> list[str]: """Get all the home ids returned by NetAtmo API.""" if home_data is None: return [] diff --git a/homeassistant/components/netatmo/data_handler.py b/homeassistant/components/netatmo/data_handler.py index be0120bd1a0..358ae79edd2 100644 --- a/homeassistant/components/netatmo/data_handler.py +++ b/homeassistant/components/netatmo/data_handler.py @@ -1,11 +1,13 @@ """The Netatmo data handler.""" +from __future__ import annotations + from collections import deque from datetime import timedelta from functools import partial from itertools import islice import logging from time import time -from typing import Deque, Dict, List +from typing import Deque import pyatmo @@ -55,8 +57,8 @@ class NetatmoDataHandler: """Initialize self.""" self.hass = hass self._auth = hass.data[DOMAIN][entry.entry_id][AUTH] - self.listeners: List[CALLBACK_TYPE] = [] - self._data_classes: Dict = {} + self.listeners: list[CALLBACK_TYPE] = [] + self._data_classes: dict = {} self.data = {} self._queue: Deque = deque() self._webhook: bool = False diff --git a/homeassistant/components/netatmo/device_trigger.py b/homeassistant/components/netatmo/device_trigger.py index 38601e981db..3893fbed4c7 100644 --- a/homeassistant/components/netatmo/device_trigger.py +++ b/homeassistant/components/netatmo/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Netatmo.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -82,7 +82,7 @@ async def async_validate_trigger_config(hass, config): return config -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Netatmo devices.""" registry = await entity_registry.async_get_registry(hass) device_registry = await hass.helpers.device_registry.async_get_registry() diff --git a/homeassistant/components/netatmo/media_source.py b/homeassistant/components/netatmo/media_source.py index 6375c46d394..db00df5129f 100644 --- a/homeassistant/components/netatmo/media_source.py +++ b/homeassistant/components/netatmo/media_source.py @@ -1,8 +1,9 @@ """Netatmo Media Source Implementation.""" +from __future__ import annotations + import datetime as dt import logging import re -from typing import Optional, Tuple from homeassistant.components.media_player.const import ( MEDIA_CLASS_DIRECTORY, @@ -53,7 +54,7 @@ class NetatmoSource(MediaSource): return PlayMedia(url, MIME_TYPE) async def async_browse_media( - self, item: MediaSourceItem, media_types: Tuple[str] = MEDIA_MIME_TYPES + self, item: MediaSourceItem, media_types: tuple[str] = MEDIA_MIME_TYPES ) -> BrowseMediaSource: """Return media.""" try: @@ -156,7 +157,7 @@ def remove_html_tags(text): @callback def async_parse_identifier( item: MediaSourceItem, -) -> Tuple[str, str, Optional[int]]: +) -> tuple[str, str, int | None]: """Parse identifier.""" if not item.identifier: return "events", "", None diff --git a/homeassistant/components/netatmo/netatmo_entity_base.py b/homeassistant/components/netatmo/netatmo_entity_base.py index 1845cbe76e9..e41b873bdc4 100644 --- a/homeassistant/components/netatmo/netatmo_entity_base.py +++ b/homeassistant/components/netatmo/netatmo_entity_base.py @@ -1,6 +1,7 @@ """Base class for Netatmo entities.""" +from __future__ import annotations + import logging -from typing import Dict, List from homeassistant.core import CALLBACK_TYPE, callback from homeassistant.helpers.entity import Entity @@ -17,8 +18,8 @@ class NetatmoBase(Entity): def __init__(self, data_handler: NetatmoDataHandler) -> None: """Set up Netatmo entity base.""" self.data_handler = data_handler - self._data_classes: List[Dict] = [] - self._listeners: List[CALLBACK_TYPE] = [] + self._data_classes: list[dict] = [] + self._listeners: list[CALLBACK_TYPE] = [] self._device_name = None self._id = None diff --git a/homeassistant/components/nightscout/sensor.py b/homeassistant/components/nightscout/sensor.py index 53f13f3b69b..d190da48a16 100644 --- a/homeassistant/components/nightscout/sensor.py +++ b/homeassistant/components/nightscout/sensor.py @@ -1,8 +1,10 @@ """Support for Nightscout sensors.""" +from __future__ import annotations + from asyncio import TimeoutError as AsyncIOTimeoutError from datetime import timedelta import logging -from typing import Callable, List +from typing import Callable from aiohttp import ClientError from py_nightscout import Api as NightscoutAPI @@ -24,7 +26,7 @@ DEFAULT_NAME = "Blood Glucose" async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the Glucose Sensor.""" api = hass.data[DOMAIN][entry.entry_id] diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index 5e1493a68eb..f85f07a0bd5 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -1,8 +1,10 @@ """Provides functionality to notify people.""" +from __future__ import annotations + import asyncio from functools import partial import logging -from typing import Any, Dict, cast +from typing import Any, cast import voluptuous as vol @@ -120,7 +122,7 @@ class BaseNotificationService: hass: HomeAssistantType = None # type: ignore # Name => target - registered_targets: Dict[str, str] + registered_targets: dict[str, str] def send_message(self, message, **kwargs): """Send a message. diff --git a/homeassistant/components/nsw_fuel_station/sensor.py b/homeassistant/components/nsw_fuel_station/sensor.py index 0bf82c1d162..92a071ec439 100644 --- a/homeassistant/components/nsw_fuel_station/sensor.py +++ b/homeassistant/components/nsw_fuel_station/sensor.py @@ -1,7 +1,8 @@ """Sensor platform to display the current fuel prices at a NSW fuel station.""" +from __future__ import annotations + import datetime import logging -from typing import Optional from nsw_fuel import FuelCheckClient, FuelCheckError import voluptuous as vol @@ -159,7 +160,7 @@ class StationPriceSensor(Entity): return f"{self._station_data.get_station_name()} {self._fuel_type}" @property - def state(self) -> Optional[float]: + def state(self) -> float | None: """Return the state of the sensor.""" price_info = self._station_data.for_fuel_type(self._fuel_type) if price_info: diff --git a/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py b/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py index 0467b9cc353..08e62e6c6a3 100644 --- a/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py +++ b/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py @@ -1,7 +1,8 @@ """Support for NSW Rural Fire Service Feeds.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Optional from aio_geojson_nsw_rfs_incidents import NswRuralFireServiceIncidentsFeedManager import voluptuous as vol @@ -259,22 +260,22 @@ class NswRuralFireServiceLocationEvent(GeolocationEvent): return SOURCE @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return self._name @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """Return distance value of this external event.""" return self._distance @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of this external event.""" return self._latitude @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of this external event.""" return self._longitude diff --git a/homeassistant/components/number/__init__.py b/homeassistant/components/number/__init__.py index 933b07e4f58..e61398f6582 100644 --- a/homeassistant/components/number/__init__.py +++ b/homeassistant/components/number/__init__.py @@ -1,8 +1,10 @@ """Component to allow numeric input for platforms.""" +from __future__ import annotations + from abc import abstractmethod from datetime import timedelta import logging -from typing import Any, Dict +from typing import Any import voluptuous as vol @@ -66,7 +68,7 @@ class NumberEntity(Entity): """Representation of a Number entity.""" @property - def capability_attributes(self) -> Dict[str, Any]: + def capability_attributes(self) -> dict[str, Any]: """Return capability attributes.""" return { ATTR_MIN: self.min_value, diff --git a/homeassistant/components/number/device_action.py b/homeassistant/components/number/device_action.py index c22ba720e37..1a26226962c 100644 --- a/homeassistant/components/number/device_action.py +++ b/homeassistant/components/number/device_action.py @@ -1,5 +1,7 @@ """Provides device actions for Number.""" -from typing import Any, Dict, List, Optional +from __future__ import annotations + +from typing import Any import voluptuous as vol @@ -27,10 +29,10 @@ ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Number.""" registry = await entity_registry.async_get_registry(hass) - actions: List[Dict[str, Any]] = [] + actions: list[dict[str, Any]] = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): @@ -50,7 +52,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/number/reproduce_state.py b/homeassistant/components/number/reproduce_state.py index 611744e3191..4364dffe1e8 100644 --- a/homeassistant/components/number/reproduce_state.py +++ b/homeassistant/components/number/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce a Number entity state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State @@ -16,8 +18,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -50,8 +52,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce multiple Number states.""" # Reproduce states in parallel. diff --git a/homeassistant/components/nws/__init__.py b/homeassistant/components/nws/__init__.py index 83fca527d43..569a8adf83b 100644 --- a/homeassistant/components/nws/__init__.py +++ b/homeassistant/components/nws/__init__.py @@ -1,8 +1,10 @@ """The National Weather Service integration.""" +from __future__ import annotations + import asyncio import datetime import logging -from typing import Awaitable, Callable, Optional +from typing import Awaitable, Callable from pynws import SimpleNWS @@ -58,8 +60,8 @@ class NwsDataUpdateCoordinator(DataUpdateCoordinator): name: str, update_interval: datetime.timedelta, failed_update_interval: datetime.timedelta, - update_method: Optional[Callable[[], Awaitable]] = None, - request_refresh_debouncer: Optional[debounce.Debouncer] = None, + update_method: Callable[[], Awaitable] | None = None, + request_refresh_debouncer: debounce.Debouncer | None = None, ): """Initialize NWS coordinator.""" super().__init__( diff --git a/homeassistant/components/nzbget/config_flow.py b/homeassistant/components/nzbget/config_flow.py index f593eeb0729..eb81540e39e 100644 --- a/homeassistant/components/nzbget/config_flow.py +++ b/homeassistant/components/nzbget/config_flow.py @@ -1,6 +1,8 @@ """Config flow for NZBGet.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any import voluptuous as vol @@ -31,7 +33,7 @@ from .coordinator import NZBGetAPI, NZBGetAPIException _LOGGER = logging.getLogger(__name__) -def validate_input(hass: HomeAssistantType, data: dict) -> Dict[str, Any]: +def validate_input(hass: HomeAssistantType, data: dict) -> dict[str, Any]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -63,8 +65,8 @@ class NZBGetConfigFlow(ConfigFlow, domain=DOMAIN): return NZBGetOptionsFlowHandler(config_entry) async def async_step_import( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initiated by configuration file.""" if CONF_SCAN_INTERVAL in user_input: user_input[CONF_SCAN_INTERVAL] = user_input[CONF_SCAN_INTERVAL].seconds @@ -72,8 +74,8 @@ class NZBGetConfigFlow(ConfigFlow, domain=DOMAIN): return await self.async_step_user(user_input) async def async_step_user( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -127,7 +129,7 @@ class NZBGetOptionsFlowHandler(OptionsFlow): """Initialize options flow.""" self.config_entry = config_entry - async def async_step_init(self, user_input: Optional[ConfigType] = None): + async def async_step_init(self, user_input: ConfigType | None = None): """Manage NZBGet options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/nzbget/sensor.py b/homeassistant/components/nzbget/sensor.py index b4133e7550d..a8870db52a3 100644 --- a/homeassistant/components/nzbget/sensor.py +++ b/homeassistant/components/nzbget/sensor.py @@ -1,7 +1,9 @@ """Monitor the NZBGet API.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Callable, List, Optional +from typing import Callable from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -41,7 +43,7 @@ SENSOR_TYPES = { async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up NZBGet sensor based on a config entry.""" coordinator: NZBGetDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ @@ -74,7 +76,7 @@ class NZBGetSensor(NZBGetEntity): entry_name: str, sensor_type: str, sensor_name: str, - unit_of_measurement: Optional[str] = None, + unit_of_measurement: str | None = None, ): """Initialize a new NZBGet sensor.""" self._sensor_type = sensor_type diff --git a/homeassistant/components/nzbget/switch.py b/homeassistant/components/nzbget/switch.py index c4ceaab5ded..4f0eae17c23 100644 --- a/homeassistant/components/nzbget/switch.py +++ b/homeassistant/components/nzbget/switch.py @@ -1,5 +1,7 @@ """Support for NZBGet switches.""" -from typing import Callable, List +from __future__ import annotations + +from typing import Callable from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry @@ -15,7 +17,7 @@ from .coordinator import NZBGetDataUpdateCoordinator async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up NZBGet sensor based on a config entry.""" coordinator: NZBGetDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ diff --git a/homeassistant/components/onewire/onewire_entities.py b/homeassistant/components/onewire/onewire_entities.py index 724783b5686..e8c013094f7 100644 --- a/homeassistant/components/onewire/onewire_entities.py +++ b/homeassistant/components/onewire/onewire_entities.py @@ -1,6 +1,8 @@ """Support for 1-Wire entities.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from pyownet import protocol @@ -43,32 +45,32 @@ class OneWireBaseEntity(Entity): self._unique_id = unique_id or device_file @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return self._name @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device.""" return self._device_class @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit the value is expressed in.""" return self._unit_of_measurement @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" return {"device_file": self._device_file, "raw_value": self._value_raw} @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return self._unique_id @property - def device_info(self) -> Optional[Dict[str, Any]]: + def device_info(self) -> dict[str, Any] | None: """Return device specific attributes.""" return self._device_info @@ -85,9 +87,9 @@ class OneWireProxyEntity(OneWireBaseEntity): self, device_id: str, device_name: str, - device_info: Dict[str, Any], + device_info: dict[str, Any], entity_path: str, - entity_specs: Dict[str, Any], + entity_specs: dict[str, Any], owproxy: protocol._Proxy, ): """Initialize the sensor.""" diff --git a/homeassistant/components/onkyo/media_player.py b/homeassistant/components/onkyo/media_player.py index 3d882884aa2..2e4b6eff6da 100644 --- a/homeassistant/components/onkyo/media_player.py +++ b/homeassistant/components/onkyo/media_player.py @@ -1,6 +1,7 @@ """Support for Onkyo Receivers.""" +from __future__ import annotations + import logging -from typing import List import eiscp from eiscp import eISCP @@ -56,7 +57,7 @@ SUPPORT_ONKYO_WO_VOLUME = ( | SUPPORT_PLAY_MEDIA ) -KNOWN_HOSTS: List[str] = [] +KNOWN_HOSTS: list[str] = [] DEFAULT_SOURCES = { "tv": "TV", "bd": "Bluray", diff --git a/homeassistant/components/onvif/binary_sensor.py b/homeassistant/components/onvif/binary_sensor.py index 9b5469ee0d0..680f92efd2b 100644 --- a/homeassistant/components/onvif/binary_sensor.py +++ b/homeassistant/components/onvif/binary_sensor.py @@ -1,5 +1,5 @@ """Support for ONVIF binary sensors.""" -from typing import Optional +from __future__ import annotations from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.core import callback @@ -55,7 +55,7 @@ class ONVIFBinarySensor(ONVIFBaseEntity, BinarySensorEntity): return self.device.events.get_uid(self.uid).name @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" return self.device.events.get_uid(self.uid).device_class diff --git a/homeassistant/components/onvif/config_flow.py b/homeassistant/components/onvif/config_flow.py index 273640ab6b5..6c6e155a046 100644 --- a/homeassistant/components/onvif/config_flow.py +++ b/homeassistant/components/onvif/config_flow.py @@ -1,6 +1,7 @@ """Config flow for ONVIF.""" +from __future__ import annotations + from pprint import pformat -from typing import List from urllib.parse import urlparse from onvif.exceptions import ONVIFError @@ -36,7 +37,7 @@ from .device import get_device CONF_MANUAL_INPUT = "Manually configure ONVIF device" -def wsdiscovery() -> List[Service]: +def wsdiscovery() -> list[Service]: """Get ONVIF Profile S devices from network.""" discovery = WSDiscovery(ttl=4) discovery.start() diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index c0851cbe32f..07be1fbfd03 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -1,8 +1,9 @@ """ONVIF device abstraction.""" +from __future__ import annotations + import asyncio import datetime as dt import os -from typing import List from httpx import RequestError import onvif @@ -50,7 +51,7 @@ class ONVIFDevice: self.info: DeviceInfo = DeviceInfo() self.capabilities: Capabilities = Capabilities() - self.profiles: List[Profile] = [] + self.profiles: list[Profile] = [] self.max_resolution: int = 0 self._dt_diff_seconds: int = 0 @@ -262,7 +263,7 @@ class ONVIFDevice: return Capabilities(snapshot, pullpoint, ptz) - async def async_get_profiles(self) -> List[Profile]: + async def async_get_profiles(self) -> list[Profile]: """Obtain media profiles for this device.""" media_service = self.device.create_media_service() result = await media_service.GetProfiles() diff --git a/homeassistant/components/onvif/event.py b/homeassistant/components/onvif/event.py index eaf23236042..064f0dcfa0f 100644 --- a/homeassistant/components/onvif/event.py +++ b/homeassistant/components/onvif/event.py @@ -1,7 +1,9 @@ """ONVIF event abstraction.""" +from __future__ import annotations + import asyncio import datetime as dt -from typing import Callable, Dict, List, Optional, Set +from typing import Callable from httpx import RemoteProtocolError, TransportError from onvif import ONVIFCamera, ONVIFService @@ -34,14 +36,14 @@ class EventManager: self.started: bool = False self._subscription: ONVIFService = None - self._events: Dict[str, Event] = {} - self._listeners: List[CALLBACK_TYPE] = [] - self._unsub_refresh: Optional[CALLBACK_TYPE] = None + self._events: dict[str, Event] = {} + self._listeners: list[CALLBACK_TYPE] = [] + self._unsub_refresh: CALLBACK_TYPE | None = None super().__init__() @property - def platforms(self) -> Set[str]: + def platforms(self) -> set[str]: """Return platforms to setup.""" return {event.platform for event in self._events.values()} @@ -229,6 +231,6 @@ class EventManager: """Retrieve event for given id.""" return self._events[uid] - def get_platform(self, platform) -> List[Event]: + def get_platform(self, platform) -> list[Event]: """Retrieve events for given platform.""" return [event for event in self._events.values() if event.platform == platform] diff --git a/homeassistant/components/onvif/models.py b/homeassistant/components/onvif/models.py index 2a129d3bc44..feda891f772 100644 --- a/homeassistant/components/onvif/models.py +++ b/homeassistant/components/onvif/models.py @@ -1,6 +1,8 @@ """ONVIF models.""" +from __future__ import annotations + from dataclasses import dataclass -from typing import Any, List +from typing import Any @dataclass @@ -37,7 +39,7 @@ class PTZ: continuous: bool relative: bool absolute: bool - presets: List[str] = None + presets: list[str] = None @dataclass diff --git a/homeassistant/components/onvif/sensor.py b/homeassistant/components/onvif/sensor.py index b1d7ff7986c..3895e8a4abb 100644 --- a/homeassistant/components/onvif/sensor.py +++ b/homeassistant/components/onvif/sensor.py @@ -1,5 +1,5 @@ """Support for ONVIF binary sensors.""" -from typing import Optional, Union +from __future__ import annotations from homeassistant.core import callback @@ -43,7 +43,7 @@ class ONVIFSensor(ONVIFBaseEntity): super().__init__(device) @property - def state(self) -> Union[None, str, int, float]: + def state(self) -> None | str | int | float: """Return the state of the entity.""" return self.device.events.get_uid(self.uid).value @@ -53,12 +53,12 @@ class ONVIFSensor(ONVIFBaseEntity): return self.device.events.get_uid(self.uid).name @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" return self.device.events.get_uid(self.uid).device_class @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit of measurement of this entity, if any.""" return self.device.events.get_uid(self.uid).unit_of_measurement diff --git a/homeassistant/components/ovo_energy/__init__.py b/homeassistant/components/ovo_energy/__init__.py index 0130ba30c30..7bf79f47991 100644 --- a/homeassistant/components/ovo_energy/__init__.py +++ b/homeassistant/components/ovo_energy/__init__.py @@ -1,7 +1,9 @@ """Support for OVO Energy.""" +from __future__ import annotations + from datetime import datetime, timedelta import logging -from typing import Any, Dict +from typing import Any import aiohttp import async_timeout @@ -148,7 +150,7 @@ class OVOEnergyDeviceEntity(OVOEnergyEntity): """Defines a OVO Energy device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this OVO Energy instance.""" return { "identifiers": {(DOMAIN, self._client.account_id)}, diff --git a/homeassistant/components/ozw/climate.py b/homeassistant/components/ozw/climate.py index a6532af4d26..e403c4f5517 100644 --- a/homeassistant/components/ozw/climate.py +++ b/homeassistant/components/ozw/climate.py @@ -1,7 +1,8 @@ """Support for Z-Wave climate devices.""" +from __future__ import annotations + from enum import IntEnum import logging -from typing import Optional, Tuple from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, ClimateEntity from homeassistant.components.climate.const import ( @@ -239,12 +240,12 @@ class ZWaveClimateEntity(ZWaveDeviceEntity, ClimateEntity): return self._current_mode_setpoint_values[0].value @property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" return self._current_mode_setpoint_values[0].value @property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" return self._current_mode_setpoint_values[1].value @@ -333,7 +334,7 @@ class ZWaveClimateEntity(ZWaveDeviceEntity, ClimateEntity): support |= SUPPORT_PRESET_MODE return support - def _get_current_mode_setpoint_values(self) -> Tuple: + def _get_current_mode_setpoint_values(self) -> tuple: """Return a tuple of current setpoint Z-Wave value(s).""" if not self.values.mode: setpoint_names = ("setpoint_heating",) diff --git a/homeassistant/components/persistent_notification/__init__.py b/homeassistant/components/persistent_notification/__init__.py index 589cc97baea..05d52cf7830 100644 --- a/homeassistant/components/persistent_notification/__init__.py +++ b/homeassistant/components/persistent_notification/__init__.py @@ -1,7 +1,9 @@ """Support for displaying persistent notifications.""" +from __future__ import annotations + from collections import OrderedDict import logging -from typing import Any, Mapping, MutableMapping, Optional +from typing import Any, Mapping, MutableMapping import voluptuous as vol @@ -71,8 +73,8 @@ def dismiss(hass, notification_id): def async_create( hass: HomeAssistant, message: str, - title: Optional[str] = None, - notification_id: Optional[str] = None, + title: str | None = None, + notification_id: str | None = None, ) -> None: """Generate a notification.""" data = { diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index d3e17d904ea..cf6403b7334 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -1,6 +1,8 @@ """Support for tracking people.""" +from __future__ import annotations + import logging -from typing import List, Optional, cast +from typing import cast import voluptuous as vol @@ -171,7 +173,7 @@ class PersonStorageCollection(collection.StorageCollection): super().__init__(store, logger, id_manager) self.yaml_collection = yaml_collection - async def _async_load_data(self) -> Optional[dict]: + async def _async_load_data(self) -> dict | None: """Load the data. A past bug caused onboarding to create invalid person objects. @@ -257,7 +259,7 @@ class PersonStorageCollection(collection.StorageCollection): raise ValueError("User already taken") -async def filter_yaml_data(hass: HomeAssistantType, persons: List[dict]) -> List[dict]: +async def filter_yaml_data(hass: HomeAssistantType, persons: list[dict]) -> list[dict]: """Validate YAML data that we can't validate via schema.""" filtered = [] person_invalid_user = [] @@ -380,7 +382,7 @@ class Person(RestoreEntity): return self._config[CONF_NAME] @property - def entity_picture(self) -> Optional[str]: + def entity_picture(self) -> str | None: """Return entity picture.""" return self._config.get(CONF_PICTURE) @@ -522,7 +524,7 @@ def ws_list_person( ) -def _get_latest(prev: Optional[State], curr: State): +def _get_latest(prev: State | None, curr: State): """Get latest state.""" if prev is None or curr.last_updated > prev.last_updated: return curr diff --git a/homeassistant/components/person/significant_change.py b/homeassistant/components/person/significant_change.py index d9c1ec6cc23..680b9194144 100644 --- a/homeassistant/components/person/significant_change.py +++ b/homeassistant/components/person/significant_change.py @@ -1,5 +1,7 @@ """Helper to test significant Person state changes.""" -from typing import Any, Optional +from __future__ import annotations + +from typing import Any from homeassistant.core import HomeAssistant, callback @@ -12,7 +14,7 @@ def async_check_significant_change( new_state: str, new_attrs: dict, **kwargs: Any, -) -> Optional[bool]: +) -> bool | None: """Test if state significantly changed.""" if new_state != old_state: diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index 088e29e4e26..7be5efeaf2f 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -1,8 +1,10 @@ """The Philips TV integration.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable from haphilipsjs import ConnectionFailure, PhilipsTV @@ -77,14 +79,14 @@ class PluggableAction: def __init__(self, update: Callable[[], None]): """Initialize.""" self._update = update - self._actions: Dict[Any, AutomationActionType] = {} + self._actions: dict[Any, AutomationActionType] = {} def __bool__(self): """Return if we have something attached.""" return bool(self._actions) @callback - def async_attach(self, action: AutomationActionType, variables: Dict[str, Any]): + def async_attach(self, action: AutomationActionType, variables: dict[str, Any]): """Attach a device trigger for turn on.""" @callback @@ -99,9 +101,7 @@ class PluggableAction: return _remove - async def async_run( - self, hass: HomeAssistantType, context: Optional[Context] = None - ): + async def async_run(self, hass: HomeAssistantType, context: Context | None = None): """Run all turn on triggers.""" for job, variables in self._actions.values(): hass.async_run_hass_job(job, variables, context) @@ -113,7 +113,7 @@ class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]): def __init__(self, hass, api: PhilipsTV) -> None: """Set up the coordinator.""" self.api = api - self._notify_future: Optional[asyncio.Task] = None + self._notify_future: asyncio.Task | None = None @callback def _update_listeners(): diff --git a/homeassistant/components/philips_js/config_flow.py b/homeassistant/components/philips_js/config_flow.py index 39150be9ce8..7b06558f3fe 100644 --- a/homeassistant/components/philips_js/config_flow.py +++ b/homeassistant/components/philips_js/config_flow.py @@ -1,6 +1,8 @@ """Config flow for Philips TV integration.""" +from __future__ import annotations + import platform -from typing import Any, Dict, Optional, Tuple +from typing import Any from haphilipsjs import ConnectionFailure, PairingFailure, PhilipsTV import voluptuous as vol @@ -25,7 +27,7 @@ from .const import ( # pylint:disable=unused-import async def validate_input( hass: core.HomeAssistant, host: str, api_version: int -) -> Tuple[Dict, PhilipsTV]: +) -> tuple[dict, PhilipsTV]: """Validate the user input allows us to connect.""" hub = PhilipsTV(host, api_version) @@ -48,7 +50,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Initialize flow.""" super().__init__() self._current = {} - self._hub: Optional[PhilipsTV] = None + self._hub: PhilipsTV | None = None self._pair_state: Any = None async def async_step_import(self, conf: dict) -> dict: @@ -72,7 +74,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): data=self._current, ) - async def async_step_pair(self, user_input: Optional[dict] = None) -> dict: + async def async_step_pair(self, user_input: dict | None = None) -> dict: """Attempt to pair with device.""" assert self._hub @@ -123,7 +125,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._current[CONF_PASSWORD] = password return await self._async_create_current() - async def async_step_user(self, user_input: Optional[dict] = None) -> dict: + async def async_step_user(self, user_input: dict | None = None) -> dict: """Handle the initial step.""" errors = {} if user_input: diff --git a/homeassistant/components/philips_js/device_trigger.py b/homeassistant/components/philips_js/device_trigger.py index 2a60a1664bc..615d758735e 100644 --- a/homeassistant/components/philips_js/device_trigger.py +++ b/homeassistant/components/philips_js/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for control of device.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -23,7 +23,7 @@ TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for device.""" triggers = [] triggers.append( @@ -43,7 +43,7 @@ async def async_attach_trigger( config: ConfigType, action: AutomationActionType, automation_info: dict, -) -> Optional[CALLBACK_TYPE]: +) -> CALLBACK_TYPE | None: """Attach a trigger.""" registry: DeviceRegistry = await async_get_registry(hass) if config[CONF_TYPE] == TRIGGER_TYPE_TURN_ON: diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index f3b2fe651e2..83adf61ed1e 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -1,5 +1,7 @@ """Media Player component to integrate TVs exposing the Joint Space API.""" -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import Any from haphilipsjs import ConnectionFailure import voluptuous as vol @@ -125,7 +127,7 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): def __init__( self, coordinator: PhilipsTVDataUpdateCoordinator, - system: Dict[str, Any], + system: dict[str, Any], unique_id: str, ): """Initialize the Philips TV.""" @@ -137,10 +139,10 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): self._system = system self._unique_id = unique_id self._state = STATE_OFF - self._media_content_type: Optional[str] = None - self._media_content_id: Optional[str] = None - self._media_title: Optional[str] = None - self._media_channel: Optional[str] = None + self._media_content_type: str | None = None + self._media_content_id: str | None = None + self._media_title: str | None = None + self._media_channel: str | None = None super().__init__(coordinator) self._update_from_coordinator() diff --git a/homeassistant/components/ping/binary_sensor.py b/homeassistant/components/ping/binary_sensor.py index a91f7235254..e660b5af38d 100644 --- a/homeassistant/components/ping/binary_sensor.py +++ b/homeassistant/components/ping/binary_sensor.py @@ -1,11 +1,13 @@ """Tracks the latency of a host by sending ICMP echo requests (ping).""" +from __future__ import annotations + import asyncio from datetime import timedelta from functools import partial import logging import re import sys -from typing import Any, Dict +from typing import Any from icmplib import SocketPermissionError, ping as icmp_ping import voluptuous as vol @@ -105,7 +107,7 @@ class PingBinarySensor(BinarySensorEntity): return self._ping.available @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the ICMP checo request.""" if self._ping.data is not False: return { diff --git a/homeassistant/components/plaato/sensor.py b/homeassistant/components/plaato/sensor.py index ae767add18b..c2c59d347f3 100644 --- a/homeassistant/components/plaato/sensor.py +++ b/homeassistant/components/plaato/sensor.py @@ -1,5 +1,5 @@ """Support for Plaato Airlock sensors.""" -from typing import Optional +from __future__ import annotations from pyplaato.models.device import PlaatoDevice from pyplaato.plaato import PlaatoKeg @@ -63,7 +63,7 @@ class PlaatoSensor(PlaatoEntity): """Representation of a Plaato Sensor.""" @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" if self._coordinator is not None: if self._sensor_type == PlaatoKeg.Pins.TEMPERATURE: diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index 280de59898b..4e0e31810cb 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -1,9 +1,9 @@ """Plugwise platform for Home Assistant Core.""" +from __future__ import annotations import asyncio from datetime import timedelta import logging -from typing import Dict import async_timeout from plugwise.exceptions import ( @@ -201,7 +201,7 @@ class SmileGateway(CoordinatorEntity): return self._name @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" device_information = { "identifiers": {(DOMAIN, self._dev_id)}, diff --git a/homeassistant/components/plum_lightpad/config_flow.py b/homeassistant/components/plum_lightpad/config_flow.py index c6261307d13..561f071bc7b 100644 --- a/homeassistant/components/plum_lightpad/config_flow.py +++ b/homeassistant/components/plum_lightpad/config_flow.py @@ -1,6 +1,8 @@ """Config flow for Plum Lightpad.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from aiohttp import ContentTypeError from requests.exceptions import ConnectTimeout, HTTPError @@ -34,8 +36,8 @@ class PlumLightpadConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) async def async_step_user( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initialized by the user or redirected to by import.""" if not user_input: return self._show_form() @@ -58,7 +60,7 @@ class PlumLightpadConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) async def async_step_import( - self, import_config: Optional[ConfigType] - ) -> Dict[str, Any]: + self, import_config: ConfigType | None + ) -> dict[str, Any]: """Import a config entry from configuration.yaml.""" return await self.async_step_user(import_config) diff --git a/homeassistant/components/plum_lightpad/light.py b/homeassistant/components/plum_lightpad/light.py index feacec4492b..90558eb2523 100644 --- a/homeassistant/components/plum_lightpad/light.py +++ b/homeassistant/components/plum_lightpad/light.py @@ -1,6 +1,8 @@ """Support for Plum Lightpad lights.""" +from __future__ import annotations + import asyncio -from typing import Callable, List +from typing import Callable from plumlightpad import Plum @@ -23,7 +25,7 @@ from .const import DOMAIN async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity]], None], + async_add_entities: Callable[[list[Entity]], None], ) -> None: """Set up Plum Lightpad dimmer lights and glow rings.""" diff --git a/homeassistant/components/pvpc_hourly_pricing/sensor.py b/homeassistant/components/pvpc_hourly_pricing/sensor.py index b3486f9d534..3aca4db67e7 100644 --- a/homeassistant/components/pvpc_hourly_pricing/sensor.py +++ b/homeassistant/components/pvpc_hourly_pricing/sensor.py @@ -1,7 +1,8 @@ """Sensor to collect the reference daily prices of electricity ('PVPC') in Spain.""" +from __future__ import annotations + import logging from random import randint -from typing import Optional from aiopvpc import PVPCData @@ -92,7 +93,7 @@ class ElecPriceSensor(RestoreEntity): self.update_current_price(dt_util.utcnow()) @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return self._unique_id From 6cd6ad69040311df103da7f23ac2d35389677880 Mon Sep 17 00:00:00 2001 From: Berni Moses Date: Thu, 18 Mar 2021 14:06:17 +0100 Subject: [PATCH 459/831] Ignore not implemented lg_soundbar source/equaliser. (#45868) --- homeassistant/components/lg_soundbar/media_player.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lg_soundbar/media_player.py b/homeassistant/components/lg_soundbar/media_player.py index c2d196196f9..78bf7e050ef 100644 --- a/homeassistant/components/lg_soundbar/media_player.py +++ b/homeassistant/components/lg_soundbar/media_player.py @@ -159,7 +159,8 @@ class LGDevice(MediaPlayerEntity): """Return the available sound modes.""" modes = [] for equaliser in self._equalisers: - modes.append(temescal.equalisers[equaliser]) + if equaliser < len(temescal.equalisers): + modes.append(temescal.equalisers[equaliser]) return sorted(modes) @property @@ -174,7 +175,8 @@ class LGDevice(MediaPlayerEntity): """List of available input sources.""" sources = [] for function in self._functions: - sources.append(temescal.functions[function]) + if function < len(temescal.functions): + sources.append(temescal.functions[function]) return sorted(sources) @property From a3cd1854f6fe0cdf83aab56de0bc5932aed8ed74 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 14:31:38 +0100 Subject: [PATCH 460/831] Update typing 12 (#48073) --- .../components/qld_bushfire/geo_location.py | 11 ++--- homeassistant/components/rachio/device.py | 5 ++- .../components/recollect_waste/__init__.py | 5 ++- .../components/recollect_waste/config_flow.py | 4 +- .../components/recollect_waste/sensor.py | 8 ++-- homeassistant/components/recorder/__init__.py | 12 ++--- homeassistant/components/remote/__init__.py | 10 +++-- .../components/remote/device_action.py | 4 +- .../components/remote/device_condition.py | 4 +- .../components/remote/device_trigger.py | 4 +- .../components/remote/reproduce_state.py | 12 ++--- homeassistant/components/ring/__init__.py | 5 ++- homeassistant/components/roku/__init__.py | 8 ++-- homeassistant/components/roku/config_flow.py | 20 ++++----- homeassistant/components/roku/media_player.py | 7 +-- homeassistant/components/roku/remote.py | 8 ++-- homeassistant/components/route53/__init__.py | 5 ++- .../components/rpi_power/config_flow.py | 8 ++-- .../ruckus_unleashed/device_tracker.py | 4 +- homeassistant/components/scene/__init__.py | 6 ++- homeassistant/components/script/__init__.py | 11 ++--- .../components/sensor/device_condition.py | 6 +-- .../components/sensor/significant_change.py | 8 ++-- homeassistant/components/sentry/__init__.py | 7 +-- .../components/sentry/config_flow.py | 10 ++--- .../components/sharkiq/config_flow.py | 6 +-- .../components/sharkiq/update_coordinator.py | 8 ++-- homeassistant/components/sharkiq/vacuum.py | 22 +++++----- .../components/shelly/device_trigger.py | 4 +- homeassistant/components/shelly/entity.py | 26 ++++++----- homeassistant/components/shelly/light.py | 8 ++-- homeassistant/components/shelly/utils.py | 6 +-- .../components/smartthings/binary_sensor.py | 6 ++- .../components/smartthings/climate.py | 8 ++-- homeassistant/components/smartthings/cover.py | 6 ++- homeassistant/components/smartthings/fan.py | 6 ++- homeassistant/components/smartthings/light.py | 6 ++- homeassistant/components/smartthings/lock.py | 6 ++- .../components/smartthings/sensor.py | 6 ++- .../components/smartthings/switch.py | 6 ++- homeassistant/components/smhi/weather.py | 7 +-- homeassistant/components/somfy/api.py | 5 ++- homeassistant/components/somfy/climate.py | 9 ++-- homeassistant/components/sonarr/__init__.py | 8 ++-- .../components/sonarr/config_flow.py | 24 +++++----- homeassistant/components/sonarr/sensor.py | 24 +++++----- .../components/songpal/config_flow.py | 5 ++- .../components/spotify/config_flow.py | 16 ++++--- .../components/spotify/media_player.py | 44 ++++++++++--------- homeassistant/components/starline/account.py | 20 +++++---- .../components/starline/config_flow.py | 12 ++--- homeassistant/components/starline/entity.py | 6 ++- homeassistant/components/stream/core.py | 6 ++- homeassistant/components/stream/recorder.py | 6 ++- homeassistant/components/stt/__init__.py | 25 ++++++----- homeassistant/components/supla/__init__.py | 5 ++- .../components/surepetcare/__init__.py | 8 ++-- .../components/surepetcare/binary_sensor.py | 16 ++++--- .../components/surepetcare/sensor.py | 18 ++++---- .../components/switch/device_action.py | 4 +- .../components/switch/device_condition.py | 4 +- .../components/switch/device_trigger.py | 4 +- homeassistant/components/switch/light.py | 8 ++-- .../components/switch/reproduce_state.py | 12 ++--- .../components/switch/significant_change.py | 6 ++- homeassistant/components/switchbot/switch.py | 6 ++- .../components/switcher_kis/__init__.py | 7 +-- .../components/switcher_kis/switch.py | 14 +++--- homeassistant/components/syncthru/__init__.py | 6 +-- .../components/synology_dsm/__init__.py | 13 +++--- .../components/synology_dsm/binary_sensor.py | 4 +- .../components/synology_dsm/camera.py | 5 ++- .../components/synology_dsm/sensor.py | 5 ++- .../components/synology_dsm/switch.py | 7 +-- .../components/system_health/__init__.py | 20 +++++---- 75 files changed, 399 insertions(+), 312 deletions(-) diff --git a/homeassistant/components/qld_bushfire/geo_location.py b/homeassistant/components/qld_bushfire/geo_location.py index 0887e6b7cdd..669e9d1e884 100644 --- a/homeassistant/components/qld_bushfire/geo_location.py +++ b/homeassistant/components/qld_bushfire/geo_location.py @@ -1,7 +1,8 @@ """Support for Queensland Bushfire Alert Feeds.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Optional from georss_qld_bushfire_alert_client import QldBushfireAlertFeedManager import voluptuous as vol @@ -209,22 +210,22 @@ class QldBushfireLocationEvent(GeolocationEvent): return SOURCE @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return self._name @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """Return distance value of this external event.""" return self._distance @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of this external event.""" return self._latitude @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of this external event.""" return self._longitude diff --git a/homeassistant/components/rachio/device.py b/homeassistant/components/rachio/device.py index c9de7eea7d4..a6ed596db04 100644 --- a/homeassistant/components/rachio/device.py +++ b/homeassistant/components/rachio/device.py @@ -1,6 +1,7 @@ """Adapter to wrap the rachiopy api for home assistant.""" +from __future__ import annotations + import logging -from typing import Optional import voluptuous as vol @@ -239,7 +240,7 @@ class RachioIro: # Only enabled zones return [z for z in self._zones if z[KEY_ENABLED]] - def get_zone(self, zone_id) -> Optional[dict]: + def get_zone(self, zone_id) -> dict | None: """Return the zone with the given ID.""" for zone in self.list_zones(include_disabled=True): if zone[KEY_ID] == zone_id: diff --git a/homeassistant/components/recollect_waste/__init__.py b/homeassistant/components/recollect_waste/__init__.py index 0d6961831d8..f37cabdd698 100644 --- a/homeassistant/components/recollect_waste/__init__.py +++ b/homeassistant/components/recollect_waste/__init__.py @@ -1,7 +1,8 @@ """The ReCollect Waste integration.""" +from __future__ import annotations + import asyncio from datetime import date, timedelta -from typing import List from aiorecollect.client import Client, PickupEvent from aiorecollect.errors import RecollectError @@ -35,7 +36,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry.data[CONF_PLACE_ID], entry.data[CONF_SERVICE_ID], session=session ) - async def async_get_pickup_events() -> List[PickupEvent]: + async def async_get_pickup_events() -> list[PickupEvent]: """Get the next pickup.""" try: return await client.async_get_pickup_events( diff --git a/homeassistant/components/recollect_waste/config_flow.py b/homeassistant/components/recollect_waste/config_flow.py index 8e208f57cc6..37dca6a064e 100644 --- a/homeassistant/components/recollect_waste/config_flow.py +++ b/homeassistant/components/recollect_waste/config_flow.py @@ -1,5 +1,5 @@ """Config flow for ReCollect Waste integration.""" -from typing import Optional +from __future__ import annotations from aiorecollect.client import Client from aiorecollect.errors import RecollectError @@ -83,7 +83,7 @@ class RecollectWasteOptionsFlowHandler(config_entries.OptionsFlow): """Initialize.""" self._entry = entry - async def async_step_init(self, user_input: Optional[dict] = None): + async def async_step_init(self, user_input: dict | None = None): """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/recollect_waste/sensor.py b/homeassistant/components/recollect_waste/sensor.py index 0822cdb1f3a..b6a99f4ebea 100644 --- a/homeassistant/components/recollect_waste/sensor.py +++ b/homeassistant/components/recollect_waste/sensor.py @@ -1,5 +1,7 @@ """Support for ReCollect Waste sensors.""" -from typing import Callable, List +from __future__ import annotations + +from typing import Callable from aiorecollect.client import PickupType import voluptuous as vol @@ -36,8 +38,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @callback def async_get_pickup_type_names( - entry: ConfigEntry, pickup_types: List[PickupType] -) -> List[str]: + entry: ConfigEntry, pickup_types: list[PickupType] +) -> list[str]: """Return proper pickup type names from their associated objects.""" return [ t.friendly_name diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 12fb1b38c25..cff8119356f 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -1,4 +1,6 @@ """Support for recording details.""" +from __future__ import annotations + import asyncio import concurrent.futures from datetime import datetime @@ -7,7 +9,7 @@ import queue import sqlite3 import threading import time -from typing import Any, Callable, List, NamedTuple, Optional +from typing import Any, Callable, NamedTuple from sqlalchemy import create_engine, event as sqlalchemy_event, exc, select from sqlalchemy.orm import scoped_session, sessionmaker @@ -125,7 +127,7 @@ CONFIG_SCHEMA = vol.Schema( ) -def run_information(hass, point_in_time: Optional[datetime] = None): +def run_information(hass, point_in_time: datetime | None = None): """Return information about current run. There is also the run that covers point_in_time. @@ -138,7 +140,7 @@ def run_information(hass, point_in_time: Optional[datetime] = None): return run_information_with_session(session, point_in_time) -def run_information_from_instance(hass, point_in_time: Optional[datetime] = None): +def run_information_from_instance(hass, point_in_time: datetime | None = None): """Return information about current run from the existing instance. Does not query the database for older runs. @@ -149,7 +151,7 @@ def run_information_from_instance(hass, point_in_time: Optional[datetime] = None return ins.run_info -def run_information_with_session(session, point_in_time: Optional[datetime] = None): +def run_information_with_session(session, point_in_time: datetime | None = None): """Return information about current run from the database.""" recorder_runs = RecorderRuns @@ -249,7 +251,7 @@ class Recorder(threading.Thread): db_max_retries: int, db_retry_wait: int, entity_filter: Callable[[str], bool], - exclude_t: List[str], + exclude_t: list[str], db_integrity_check: bool, ) -> None: """Initialize the recorder.""" diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index 12b81402d61..fe220dd46a8 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -1,8 +1,10 @@ """Support to interface with universal remote control devices.""" +from __future__ import annotations + from datetime import timedelta import functools as ft import logging -from typing import Any, Dict, Iterable, List, Optional, cast +from typing import Any, Iterable, cast import voluptuous as vol @@ -147,17 +149,17 @@ class RemoteEntity(ToggleEntity): return 0 @property - def current_activity(self) -> Optional[str]: + def current_activity(self) -> str | None: """Active activity.""" return None @property - def activity_list(self) -> Optional[List[str]]: + def activity_list(self) -> list[str] | None: """List of available activities.""" return None @property - def state_attributes(self) -> Optional[Dict[str, Any]]: + def state_attributes(self) -> dict[str, Any] | None: """Return optional state attributes.""" if not self.supported_features & SUPPORT_ACTIVITY: return None diff --git a/homeassistant/components/remote/device_action.py b/homeassistant/components/remote/device_action.py index aa819f3eb46..aa34eb33224 100644 --- a/homeassistant/components/remote/device_action.py +++ b/homeassistant/components/remote/device_action.py @@ -1,5 +1,5 @@ """Provides device actions for remotes.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -25,6 +25,6 @@ async def async_call_action_from_config( ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions.""" return await toggle_entity.async_get_actions(hass, device_id, DOMAIN) diff --git a/homeassistant/components/remote/device_condition.py b/homeassistant/components/remote/device_condition.py index 06c7bec89d4..ed200fd5579 100644 --- a/homeassistant/components/remote/device_condition.py +++ b/homeassistant/components/remote/device_condition.py @@ -1,5 +1,5 @@ """Provides device conditions for remotes.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -28,7 +28,7 @@ def async_condition_from_config( async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions.""" return await toggle_entity.async_get_conditions(hass, device_id, DOMAIN) diff --git a/homeassistant/components/remote/device_trigger.py b/homeassistant/components/remote/device_trigger.py index 5919e8c61ba..d8437604f6d 100644 --- a/homeassistant/components/remote/device_trigger.py +++ b/homeassistant/components/remote/device_trigger.py @@ -1,5 +1,5 @@ """Provides device triggers for remotes.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -28,7 +28,7 @@ async def async_attach_trigger( ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers.""" return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN) diff --git a/homeassistant/components/remote/reproduce_state.py b/homeassistant/components/remote/reproduce_state.py index 4e1f426c57b..b42a0bdc611 100644 --- a/homeassistant/components/remote/reproduce_state.py +++ b/homeassistant/components/remote/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Remote state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -24,8 +26,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -60,8 +62,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Remote states.""" await asyncio.gather( diff --git a/homeassistant/components/ring/__init__.py b/homeassistant/components/ring/__init__.py index a4d005d3c44..f5211ac54c0 100644 --- a/homeassistant/components/ring/__init__.py +++ b/homeassistant/components/ring/__init__.py @@ -1,10 +1,11 @@ """Support for Ring Doorbell/Chimes.""" +from __future__ import annotations + import asyncio from datetime import timedelta from functools import partial import logging from pathlib import Path -from typing import Optional from oauthlib.oauth2 import AccessDeniedError import requests @@ -187,7 +188,7 @@ class GlobalDataUpdater: self._unsub_interval() self._unsub_interval = None - async def async_refresh_all(self, _now: Optional[int] = None) -> None: + async def async_refresh_all(self, _now: int | None = None) -> None: """Time to update.""" if not self.listeners: return diff --git a/homeassistant/components/roku/__init__.py b/homeassistant/components/roku/__init__.py index 06e8d0ae848..174bd71b77a 100644 --- a/homeassistant/components/roku/__init__.py +++ b/homeassistant/components/roku/__init__.py @@ -1,8 +1,10 @@ """Support for Roku.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Any, Dict +from typing import Any from rokuecp import Roku, RokuConnectionError, RokuError from rokuecp.models import Device @@ -38,7 +40,7 @@ SCAN_INTERVAL = timedelta(seconds=15) _LOGGER = logging.getLogger(__name__) -async def async_setup(hass: HomeAssistantType, config: Dict) -> bool: +async def async_setup(hass: HomeAssistantType, config: dict) -> bool: """Set up the Roku integration.""" hass.data.setdefault(DOMAIN, {}) return True @@ -151,7 +153,7 @@ class RokuEntity(CoordinatorEntity): return self._name @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this Roku device.""" if self._device_id is None: return None diff --git a/homeassistant/components/roku/config_flow.py b/homeassistant/components/roku/config_flow.py index b086d7a9311..87ccd20cbda 100644 --- a/homeassistant/components/roku/config_flow.py +++ b/homeassistant/components/roku/config_flow.py @@ -1,6 +1,8 @@ """Config flow for Roku.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from urllib.parse import urlparse from rokuecp import Roku, RokuError @@ -27,7 +29,7 @@ ERROR_UNKNOWN = "unknown" _LOGGER = logging.getLogger(__name__) -async def validate_input(hass: HomeAssistantType, data: Dict) -> Dict: +async def validate_input(hass: HomeAssistantType, data: dict) -> dict: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -53,7 +55,7 @@ class RokuConfigFlow(ConfigFlow, domain=DOMAIN): self.discovery_info = {} @callback - def _show_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: + def _show_form(self, errors: dict | None = None) -> dict[str, Any]: """Show the form to the user.""" return self.async_show_form( step_id="user", @@ -61,9 +63,7 @@ class RokuConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors or {}, ) - async def async_step_user( - self, user_input: Optional[Dict] = None - ) -> Dict[str, Any]: + async def async_step_user(self, user_input: dict | None = None) -> dict[str, Any]: """Handle a flow initialized by the user.""" if not user_input: return self._show_form() @@ -115,8 +115,8 @@ class RokuConfigFlow(ConfigFlow, domain=DOMAIN): return await self.async_step_discovery_confirm() async def async_step_ssdp( - self, discovery_info: Optional[Dict] = None - ) -> Dict[str, Any]: + self, discovery_info: dict | None = None + ) -> dict[str, Any]: """Handle a flow initialized by discovery.""" host = urlparse(discovery_info[ATTR_SSDP_LOCATION]).hostname name = discovery_info[ATTR_UPNP_FRIENDLY_NAME] @@ -141,8 +141,8 @@ class RokuConfigFlow(ConfigFlow, domain=DOMAIN): return await self.async_step_discovery_confirm() async def async_step_discovery_confirm( - self, user_input: Optional[Dict] = None - ) -> Dict[str, Any]: + self, user_input: dict | None = None + ) -> dict[str, Any]: """Handle user-confirmation of discovered device.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index e50c28d0a43..6fee53595ac 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -1,6 +1,7 @@ """Support for the Roku media player.""" +from __future__ import annotations + import logging -from typing import List, Optional import voluptuous as vol @@ -100,7 +101,7 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): return self._unique_id @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device.""" if self.coordinator.data.info.device_type == "tv": return DEVICE_CLASS_TV @@ -230,7 +231,7 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): return None @property - def source_list(self) -> List: + def source_list(self) -> list: """List of available input sources.""" return ["Home"] + sorted(app.name for app in self.coordinator.data.apps) diff --git a/homeassistant/components/roku/remote.py b/homeassistant/components/roku/remote.py index 3fcd2ee1a34..da578667578 100644 --- a/homeassistant/components/roku/remote.py +++ b/homeassistant/components/roku/remote.py @@ -1,5 +1,7 @@ """Support for the Roku remote.""" -from typing import Callable, List +from __future__ import annotations + +from typing import Callable from homeassistant.components.remote import ATTR_NUM_REPEATS, RemoteEntity from homeassistant.config_entries import ConfigEntry @@ -12,7 +14,7 @@ from .const import DOMAIN async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List, bool], None], + async_add_entities: Callable[[list, bool], None], ) -> bool: """Load Roku remote based on a config entry.""" coordinator = hass.data[DOMAIN][entry.entry_id] @@ -56,7 +58,7 @@ class RokuRemote(RokuEntity, RemoteEntity): await self.coordinator.async_request_refresh() @roku_exception_handler - async def async_send_command(self, command: List, **kwargs) -> None: + async def async_send_command(self, command: list, **kwargs) -> None: """Send a command to one device.""" num_repeats = kwargs[ATTR_NUM_REPEATS] diff --git a/homeassistant/components/route53/__init__.py b/homeassistant/components/route53/__init__.py index 1061b7979ba..a2b6a854c62 100644 --- a/homeassistant/components/route53/__init__.py +++ b/homeassistant/components/route53/__init__.py @@ -1,7 +1,8 @@ """Update the IP addresses of your Route53 DNS records.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import List import boto3 import requests @@ -77,7 +78,7 @@ def _update_route53( aws_secret_access_key: str, zone: str, domain: str, - records: List[str], + records: list[str], ttl: int, ): _LOGGER.debug("Starting update for zone %s", zone) diff --git a/homeassistant/components/rpi_power/config_flow.py b/homeassistant/components/rpi_power/config_flow.py index 9924ebf0440..b635972f43f 100644 --- a/homeassistant/components/rpi_power/config_flow.py +++ b/homeassistant/components/rpi_power/config_flow.py @@ -1,5 +1,7 @@ """Config flow for Raspberry Pi Power Supply Checker.""" -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import Any from rpi_bad_power import new_under_voltage @@ -31,8 +33,8 @@ class RPiPowerFlow(DiscoveryFlowHandler, domain=DOMAIN): ) async def async_step_onboarding( - self, data: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, data: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a flow initialized by onboarding.""" has_devices = await self._discovery_function(self.hass) diff --git a/homeassistant/components/ruckus_unleashed/device_tracker.py b/homeassistant/components/ruckus_unleashed/device_tracker.py index 955e0581393..140aa3a8692 100644 --- a/homeassistant/components/ruckus_unleashed/device_tracker.py +++ b/homeassistant/components/ruckus_unleashed/device_tracker.py @@ -1,5 +1,5 @@ """Support for Ruckus Unleashed devices.""" -from typing import Optional +from __future__ import annotations from homeassistant.components.device_tracker import SOURCE_TYPE_ROUTER from homeassistant.components.device_tracker.config_entry import ScannerEntity @@ -115,7 +115,7 @@ class RuckusUnleashedDevice(CoordinatorEntity, ScannerEntity): return SOURCE_TYPE_ROUTER @property - def device_info(self) -> Optional[dict]: + def device_info(self) -> dict | None: """Return the device information.""" if self.is_connected: return { diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index 4bc63d585be..e11934c61c3 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -1,8 +1,10 @@ """Allow users to set and activate scenes.""" +from __future__ import annotations + import functools as ft import importlib import logging -from typing import Any, Optional +from typing import Any import voluptuous as vol @@ -94,7 +96,7 @@ class Scene(Entity): return False @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the scene.""" return STATE diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index e408be47f65..ec0bee73528 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -1,7 +1,8 @@ """Support for scripts.""" +from __future__ import annotations + import asyncio import logging -from typing import List import voluptuous as vol @@ -89,7 +90,7 @@ def is_on(hass, entity_id): @callback -def scripts_with_entity(hass: HomeAssistant, entity_id: str) -> List[str]: +def scripts_with_entity(hass: HomeAssistant, entity_id: str) -> list[str]: """Return all scripts that reference the entity.""" if DOMAIN not in hass.data: return [] @@ -104,7 +105,7 @@ def scripts_with_entity(hass: HomeAssistant, entity_id: str) -> List[str]: @callback -def entities_in_script(hass: HomeAssistant, entity_id: str) -> List[str]: +def entities_in_script(hass: HomeAssistant, entity_id: str) -> list[str]: """Return all entities in script.""" if DOMAIN not in hass.data: return [] @@ -120,7 +121,7 @@ def entities_in_script(hass: HomeAssistant, entity_id: str) -> List[str]: @callback -def scripts_with_device(hass: HomeAssistant, device_id: str) -> List[str]: +def scripts_with_device(hass: HomeAssistant, device_id: str) -> list[str]: """Return all scripts that reference the device.""" if DOMAIN not in hass.data: return [] @@ -135,7 +136,7 @@ def scripts_with_device(hass: HomeAssistant, device_id: str) -> List[str]: @callback -def devices_in_script(hass: HomeAssistant, entity_id: str) -> List[str]: +def devices_in_script(hass: HomeAssistant, entity_id: str) -> list[str]: """Return all devices in script.""" if DOMAIN not in hass.data: return [] diff --git a/homeassistant/components/sensor/device_condition.py b/homeassistant/components/sensor/device_condition.py index e2efac7b141..fea79530485 100644 --- a/homeassistant/components/sensor/device_condition.py +++ b/homeassistant/components/sensor/device_condition.py @@ -1,5 +1,5 @@ """Provides device conditions for sensors.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -109,9 +109,9 @@ CONDITION_SCHEMA = vol.All( async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions.""" - conditions: List[Dict[str, str]] = [] + conditions: list[dict[str, str]] = [] entity_registry = await async_get_registry(hass) entries = [ entry diff --git a/homeassistant/components/sensor/significant_change.py b/homeassistant/components/sensor/significant_change.py index 2c281c0a046..cda80991242 100644 --- a/homeassistant/components/sensor/significant_change.py +++ b/homeassistant/components/sensor/significant_change.py @@ -1,5 +1,7 @@ """Helper to test significant sensor state changes.""" -from typing import Any, Optional, Union +from __future__ import annotations + +from typing import Any from homeassistant.const import ( ATTR_DEVICE_CLASS, @@ -19,7 +21,7 @@ def async_check_significant_change( new_state: str, new_attrs: dict, **kwargs: Any, -) -> Optional[bool]: +) -> bool | None: """Test if state significantly changed.""" device_class = new_attrs.get(ATTR_DEVICE_CLASS) @@ -28,7 +30,7 @@ def async_check_significant_change( if device_class == DEVICE_CLASS_TEMPERATURE: if new_attrs.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_FAHRENHEIT: - change: Union[float, int] = 1 + change: float | int = 1 else: change = 0.5 diff --git a/homeassistant/components/sentry/__init__.py b/homeassistant/components/sentry/__init__.py index 6be02b9ba5e..c58d7bcd1a8 100644 --- a/homeassistant/components/sentry/__init__.py +++ b/homeassistant/components/sentry/__init__.py @@ -1,6 +1,7 @@ """The sentry integration.""" +from __future__ import annotations + import re -from typing import Dict, Union import sentry_sdk from sentry_sdk.integrations.aiohttp import AioHttpIntegration @@ -126,8 +127,8 @@ def process_before_send( options, channel: str, huuid: str, - system_info: Dict[str, Union[bool, str]], - custom_components: Dict[str, Integration], + system_info: dict[str, bool | str], + custom_components: dict[str, Integration], event, hint, ): diff --git a/homeassistant/components/sentry/config_flow.py b/homeassistant/components/sentry/config_flow.py index a308423f40b..66707c82ec9 100644 --- a/homeassistant/components/sentry/config_flow.py +++ b/homeassistant/components/sentry/config_flow.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging -from typing import Any, Dict, Optional +from typing import Any from sentry_sdk.utils import BadDsn, Dsn import voluptuous as vol @@ -47,8 +47,8 @@ class SentryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return SentryOptionsFlow(config_entry) async def async_step_user( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a user config flow.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -78,8 +78,8 @@ class SentryOptionsFlow(config_entries.OptionsFlow): self.config_entry = config_entry async def async_step_init( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Manage Sentry options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/sharkiq/config_flow.py b/homeassistant/components/sharkiq/config_flow.py index 9d2e80b8ec6..06a25a8de56 100644 --- a/homeassistant/components/sharkiq/config_flow.py +++ b/homeassistant/components/sharkiq/config_flow.py @@ -1,7 +1,7 @@ """Config flow for Shark IQ integration.""" +from __future__ import annotations import asyncio -from typing import Dict, Optional import aiohttp import async_timeout @@ -62,7 +62,7 @@ class SharkIqConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors["base"] = "unknown" return info, errors - async def async_step_user(self, user_input: Optional[Dict] = None): + async def async_step_user(self, user_input: dict | None = None): """Handle the initial step.""" errors = {} if user_input is not None: @@ -76,7 +76,7 @@ class SharkIqConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=SHARKIQ_SCHEMA, errors=errors ) - async def async_step_reauth(self, user_input: Optional[dict] = None): + async def async_step_reauth(self, user_input: dict | None = None): """Handle re-auth if login is invalid.""" errors = {} diff --git a/homeassistant/components/sharkiq/update_coordinator.py b/homeassistant/components/sharkiq/update_coordinator.py index 8675058de86..5374e873668 100644 --- a/homeassistant/components/sharkiq/update_coordinator.py +++ b/homeassistant/components/sharkiq/update_coordinator.py @@ -1,7 +1,7 @@ """Data update coordinator for shark iq vacuums.""" +from __future__ import annotations import asyncio -from typing import Dict, List, Set from async_timeout import timeout from sharkiqpy import ( @@ -27,11 +27,11 @@ class SharkIqUpdateCoordinator(DataUpdateCoordinator): hass: HomeAssistant, config_entry: ConfigEntry, ayla_api: AylaApi, - shark_vacs: List[SharkIqVacuum], + shark_vacs: list[SharkIqVacuum], ) -> None: """Set up the SharkIqUpdateCoordinator class.""" self.ayla_api = ayla_api - self.shark_vacs: Dict[str, SharkIqVacuum] = { + self.shark_vacs: dict[str, SharkIqVacuum] = { sharkiq.serial_number: sharkiq for sharkiq in shark_vacs } self._config_entry = config_entry @@ -40,7 +40,7 @@ class SharkIqUpdateCoordinator(DataUpdateCoordinator): super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL) @property - def online_dsns(self) -> Set[str]: + def online_dsns(self) -> set[str]: """Get the set of all online DSNs.""" return self._online_dsns diff --git a/homeassistant/components/sharkiq/vacuum.py b/homeassistant/components/sharkiq/vacuum.py index 5b4254eebb7..7e33d355729 100644 --- a/homeassistant/components/sharkiq/vacuum.py +++ b/homeassistant/components/sharkiq/vacuum.py @@ -1,8 +1,8 @@ """Shark IQ Wrapper.""" - +from __future__ import annotations import logging -from typing import Dict, Iterable, Optional +from typing import Iterable from sharkiqpy import OperatingModes, PowerModes, Properties, SharkIqVacuum @@ -118,7 +118,7 @@ class SharkVacuumEntity(CoordinatorEntity, StateVacuumEntity): return self.sharkiq.oem_model_number @property - def device_info(self) -> Dict: + def device_info(self) -> dict: """Device info dictionary.""" return { "identifiers": {(DOMAIN, self.serial_number)}, @@ -136,30 +136,30 @@ class SharkVacuumEntity(CoordinatorEntity, StateVacuumEntity): return SUPPORT_SHARKIQ @property - def is_docked(self) -> Optional[bool]: + def is_docked(self) -> bool | None: """Is vacuum docked.""" return self.sharkiq.get_property_value(Properties.DOCKED_STATUS) @property - def error_code(self) -> Optional[int]: + def error_code(self) -> int | None: """Return the last observed error code (or None).""" return self.sharkiq.error_code @property - def error_message(self) -> Optional[str]: + def error_message(self) -> str | None: """Return the last observed error message (or None).""" if not self.error_code: return None return self.sharkiq.error_text @property - def operating_mode(self) -> Optional[str]: + def operating_mode(self) -> str | None: """Operating mode..""" op_mode = self.sharkiq.get_property_value(Properties.OPERATING_MODE) return OPERATING_STATE_MAP.get(op_mode) @property - def recharging_to_resume(self) -> Optional[int]: + def recharging_to_resume(self) -> int | None: """Return True if vacuum set to recharge and resume cleaning.""" return self.sharkiq.get_property_value(Properties.RECHARGING_TO_RESUME) @@ -240,12 +240,12 @@ class SharkVacuumEntity(CoordinatorEntity, StateVacuumEntity): # Various attributes we want to expose @property - def recharge_resume(self) -> Optional[bool]: + def recharge_resume(self) -> bool | None: """Recharge and resume mode active.""" return self.sharkiq.get_property_value(Properties.RECHARGE_RESUME) @property - def rssi(self) -> Optional[int]: + def rssi(self) -> int | None: """Get the WiFi RSSI.""" return self.sharkiq.get_property_value(Properties.RSSI) @@ -255,7 +255,7 @@ class SharkVacuumEntity(CoordinatorEntity, StateVacuumEntity): return self.sharkiq.get_property_value(Properties.LOW_LIGHT_MISSION) @property - def extra_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> dict: """Return a dictionary of device state attributes specific to sharkiq.""" data = { ATTR_ERROR_CODE: self.error_code, diff --git a/homeassistant/components/shelly/device_trigger.py b/homeassistant/components/shelly/device_trigger.py index 9d4851c92a4..deec98a4915 100644 --- a/homeassistant/components/shelly/device_trigger.py +++ b/homeassistant/components/shelly/device_trigger.py @@ -1,5 +1,5 @@ """Provides device triggers for Shelly.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -60,7 +60,7 @@ async def async_validate_trigger_config(hass, config): ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Shelly devices.""" triggers = [] diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index 9457cbaf370..292a6050f9a 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -1,7 +1,9 @@ """Shelly entity helper.""" +from __future__ import annotations + from dataclasses import dataclass import logging -from typing import Any, Callable, Optional, Union +from typing import Any, Callable import aioshelly @@ -142,15 +144,15 @@ class BlockAttributeDescription: name: str # Callable = lambda attr_info: unit - icon: Optional[str] = None - unit: Union[None, str, Callable[[dict], str]] = None + icon: str | None = None + unit: None | str | Callable[[dict], str] = None value: Callable[[Any], Any] = lambda val: val - device_class: Optional[str] = None + device_class: str | None = None default_enabled: bool = True - available: Optional[Callable[[aioshelly.Block], bool]] = None + available: Callable[[aioshelly.Block], bool] | None = None # Callable (settings, block), return true if entity should be removed - removal_condition: Optional[Callable[[dict, aioshelly.Block], bool]] = None - extra_state_attributes: Optional[Callable[[aioshelly.Block], Optional[dict]]] = None + removal_condition: Callable[[dict, aioshelly.Block], bool] | None = None + extra_state_attributes: Callable[[aioshelly.Block], dict | None] | None = None @dataclass @@ -158,12 +160,12 @@ class RestAttributeDescription: """Class to describe a REST sensor.""" name: str - icon: Optional[str] = None - unit: Optional[str] = None + icon: str | None = None + unit: str | None = None value: Callable[[dict, Any], Any] = None - device_class: Optional[str] = None + device_class: str | None = None default_enabled: bool = True - extra_state_attributes: Optional[Callable[[dict], Optional[dict]]] = None + extra_state_attributes: Callable[[dict], dict | None] | None = None class ShellyBlockEntity(entity.Entity): @@ -385,7 +387,7 @@ class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEnti block: aioshelly.Block, attribute: str, description: BlockAttributeDescription, - entry: Optional[ConfigEntry] = None, + entry: ConfigEntry | None = None, ) -> None: """Initialize the sleeping sensor.""" self.last_state = None diff --git a/homeassistant/components/shelly/light.py b/homeassistant/components/shelly/light.py index 0379bfec1cf..a9e13796875 100644 --- a/homeassistant/components/shelly/light.py +++ b/homeassistant/components/shelly/light.py @@ -1,5 +1,5 @@ """Light for Shelly.""" -from typing import Optional, Tuple +from __future__ import annotations from aioshelly import Block @@ -96,7 +96,7 @@ class ShellyLight(ShellyBlockEntity, LightEntity): return self.block.output @property - def mode(self) -> Optional[str]: + def mode(self) -> str | None: """Return the color mode of the light.""" if self.mode_result: return self.mode_result["mode"] @@ -138,7 +138,7 @@ class ShellyLight(ShellyBlockEntity, LightEntity): return int(white) @property - def hs_color(self) -> Tuple[float, float]: + def hs_color(self) -> tuple[float, float]: """Return the hue and saturation color value of light.""" if self.mode == "white": return color_RGB_to_hs(255, 255, 255) @@ -154,7 +154,7 @@ class ShellyLight(ShellyBlockEntity, LightEntity): return color_RGB_to_hs(red, green, blue) @property - def color_temp(self) -> Optional[int]: + def color_temp(self) -> int | None: """Return the CT color value in mireds.""" if self.mode == "color": return None diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index 27152997ef7..126491f65c1 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -1,8 +1,8 @@ """Shelly helpers functions.""" +from __future__ import annotations from datetime import timedelta import logging -from typing import List, Optional, Tuple import aioshelly @@ -67,7 +67,7 @@ def get_number_of_channels(device: aioshelly.Device, block: aioshelly.Block) -> def get_entity_name( device: aioshelly.Device, block: aioshelly.Block, - description: Optional[str] = None, + description: str | None = None, ) -> str: """Naming for switch and sensors.""" channel_name = get_device_channel_name(device, block) @@ -143,7 +143,7 @@ def get_device_uptime(status: dict, last_uptime: str) -> str: def get_input_triggers( device: aioshelly.Device, block: aioshelly.Block -) -> List[Tuple[str, str]]: +) -> list[tuple[str, str]]: """Return list of input triggers for block.""" if "inputEvent" not in block.sensor_ids or "inputEventCnt" not in block.sensor_ids: return [] diff --git a/homeassistant/components/smartthings/binary_sensor.py b/homeassistant/components/smartthings/binary_sensor.py index 41e915d5c95..dd4c1e2928c 100644 --- a/homeassistant/components/smartthings/binary_sensor.py +++ b/homeassistant/components/smartthings/binary_sensor.py @@ -1,5 +1,7 @@ """Support for binary sensors through the SmartThings cloud API.""" -from typing import Optional, Sequence +from __future__ import annotations + +from typing import Sequence from pysmartthings import Attribute, Capability @@ -52,7 +54,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: +def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: """Return all capabilities supported if minimum required are present.""" return [ capability for capability in CAPABILITY_TO_ATTRIB if capability in capabilities diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py index d99cc1d60cf..76c168fbc38 100644 --- a/homeassistant/components/smartthings/climate.py +++ b/homeassistant/components/smartthings/climate.py @@ -1,8 +1,10 @@ """Support for climate devices through the SmartThings cloud API.""" +from __future__ import annotations + import asyncio from collections.abc import Iterable import logging -from typing import Optional, Sequence +from typing import Sequence from pysmartthings import Attribute, Capability @@ -103,7 +105,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: +def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: """Return all capabilities supported if minimum required are present.""" supported = [ Capability.air_conditioner_mode, @@ -274,7 +276,7 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateEntity): return self._device.status.supported_thermostat_fan_modes @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation if supported.""" return OPERATING_STATE_TO_ACTION.get( self._device.status.thermostat_operating_state diff --git a/homeassistant/components/smartthings/cover.py b/homeassistant/components/smartthings/cover.py index 7b837faca1c..8fff4ebbdfa 100644 --- a/homeassistant/components/smartthings/cover.py +++ b/homeassistant/components/smartthings/cover.py @@ -1,5 +1,7 @@ """Support for covers through the SmartThings cloud API.""" -from typing import Optional, Sequence +from __future__ import annotations + +from typing import Sequence from pysmartthings import Attribute, Capability @@ -46,7 +48,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: +def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: """Return all capabilities supported if minimum required are present.""" min_required = [ Capability.door_control, diff --git a/homeassistant/components/smartthings/fan.py b/homeassistant/components/smartthings/fan.py index ec133a1f6aa..167f3a38edf 100644 --- a/homeassistant/components/smartthings/fan.py +++ b/homeassistant/components/smartthings/fan.py @@ -1,6 +1,8 @@ """Support for fans through the SmartThings cloud API.""" +from __future__ import annotations + import math -from typing import Optional, Sequence +from typing import Sequence from pysmartthings import Capability @@ -29,7 +31,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: +def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: """Return all capabilities supported if minimum required are present.""" supported = [Capability.switch, Capability.fan_speed] # Must have switch and fan_speed diff --git a/homeassistant/components/smartthings/light.py b/homeassistant/components/smartthings/light.py index 1e4161abd0f..de678f255fa 100644 --- a/homeassistant/components/smartthings/light.py +++ b/homeassistant/components/smartthings/light.py @@ -1,6 +1,8 @@ """Support for lights through the SmartThings cloud API.""" +from __future__ import annotations + import asyncio -from typing import Optional, Sequence +from typing import Sequence from pysmartthings import Capability @@ -34,7 +36,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: +def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: """Return all capabilities supported if minimum required are present.""" supported = [ Capability.switch, diff --git a/homeassistant/components/smartthings/lock.py b/homeassistant/components/smartthings/lock.py index 55370e99993..2cd0b283cca 100644 --- a/homeassistant/components/smartthings/lock.py +++ b/homeassistant/components/smartthings/lock.py @@ -1,5 +1,7 @@ """Support for locks through the SmartThings cloud API.""" -from typing import Optional, Sequence +from __future__ import annotations + +from typing import Sequence from pysmartthings import Attribute, Capability @@ -31,7 +33,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: +def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: """Return all capabilities supported if minimum required are present.""" if Capability.lock in capabilities: return [Capability.lock] diff --git a/homeassistant/components/smartthings/sensor.py b/homeassistant/components/smartthings/sensor.py index 835c4168f07..4f924786e49 100644 --- a/homeassistant/components/smartthings/sensor.py +++ b/homeassistant/components/smartthings/sensor.py @@ -1,6 +1,8 @@ """Support for sensors through the SmartThings cloud API.""" +from __future__ import annotations + from collections import namedtuple -from typing import Optional, Sequence +from typing import Sequence from pysmartthings import Attribute, Capability @@ -297,7 +299,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: +def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: """Return all capabilities supported if minimum required are present.""" return [ capability for capability in CAPABILITY_TO_SENSORS if capability in capabilities diff --git a/homeassistant/components/smartthings/switch.py b/homeassistant/components/smartthings/switch.py index ff70648ddcf..d8bcd455415 100644 --- a/homeassistant/components/smartthings/switch.py +++ b/homeassistant/components/smartthings/switch.py @@ -1,5 +1,7 @@ """Support for switches through the SmartThings cloud API.""" -from typing import Optional, Sequence +from __future__ import annotations + +from typing import Sequence from pysmartthings import Attribute, Capability @@ -21,7 +23,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: +def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: """Return all capabilities supported if minimum required are present.""" # Must be able to be turned on/off. if Capability.switch in capabilities: diff --git a/homeassistant/components/smhi/weather.py b/homeassistant/components/smhi/weather.py index ca5e7f7ac23..86cdf72e65c 100644 --- a/homeassistant/components/smhi/weather.py +++ b/homeassistant/components/smhi/weather.py @@ -1,8 +1,9 @@ """Support for the Swedish weather institute weather service.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Dict, List import aiohttp import async_timeout @@ -210,7 +211,7 @@ class SmhiWeather(WeatherEntity): return "Swedish weather institute (SMHI)" @property - def forecast(self) -> List: + def forecast(self) -> list: """Return the forecast.""" if self._forecasts is None or len(self._forecasts) < 2: return None @@ -235,7 +236,7 @@ class SmhiWeather(WeatherEntity): return data @property - def extra_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> dict: """Return SMHI specific attributes.""" if self.cloudiness: return {ATTR_SMHI_CLOUDINESS: self.cloudiness} diff --git a/homeassistant/components/somfy/api.py b/homeassistant/components/somfy/api.py index a679af06d73..43db2c29060 100644 --- a/homeassistant/components/somfy/api.py +++ b/homeassistant/components/somfy/api.py @@ -1,6 +1,7 @@ """API for Somfy bound to Home Assistant OAuth.""" +from __future__ import annotations + from asyncio import run_coroutine_threadsafe -from typing import Dict, Union from pymfy.api import somfy_api @@ -27,7 +28,7 @@ class ConfigEntrySomfyApi(somfy_api.SomfyApi): def refresh_tokens( self, - ) -> Dict[str, Union[str, int]]: + ) -> dict[str, str | int]: """Refresh and return new Somfy tokens using Home Assistant OAuth2 session.""" run_coroutine_threadsafe( self.session.async_ensure_token_valid(), self.hass.loop diff --git a/homeassistant/components/somfy/climate.py b/homeassistant/components/somfy/climate.py index 99d6dca06ee..66602aea3e6 100644 --- a/homeassistant/components/somfy/climate.py +++ b/homeassistant/components/somfy/climate.py @@ -1,6 +1,5 @@ """Support for Somfy Thermostat.""" - -from typing import List, Optional +from __future__ import annotations from pymfy.api.devices.category import Category from pymfy.api.devices.thermostat import ( @@ -125,7 +124,7 @@ class SomfyClimate(SomfyEntity, ClimateEntity): return HVAC_MODES_MAPPING.get(self._climate.get_hvac_state()) @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes. HEAT and COOL mode are exclusive. End user has to enable a mode manually within the Somfy application. @@ -144,13 +143,13 @@ class SomfyClimate(SomfyEntity, ClimateEntity): ) @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode.""" mode = self._climate.get_target_mode() return PRESETS_MAPPING.get(mode) @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" return list(PRESETS_MAPPING.values()) diff --git a/homeassistant/components/sonarr/__init__.py b/homeassistant/components/sonarr/__init__.py index 636653dad00..946d9b1e047 100644 --- a/homeassistant/components/sonarr/__init__.py +++ b/homeassistant/components/sonarr/__init__.py @@ -1,8 +1,10 @@ """The Sonarr component.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Any, Dict +from typing import Any from sonarr import Sonarr, SonarrAccessRestricted, SonarrError @@ -40,7 +42,7 @@ SCAN_INTERVAL = timedelta(seconds=30) _LOGGER = logging.getLogger(__name__) -async def async_setup(hass: HomeAssistantType, config: Dict) -> bool: +async def async_setup(hass: HomeAssistantType, config: dict) -> bool: """Set up the Sonarr component.""" hass.data.setdefault(DOMAIN, {}) return True @@ -164,7 +166,7 @@ class SonarrEntity(Entity): return self._enabled_default @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about the application.""" if self._device_id is None: return None diff --git a/homeassistant/components/sonarr/config_flow.py b/homeassistant/components/sonarr/config_flow.py index fc11790356a..5329371bde8 100644 --- a/homeassistant/components/sonarr/config_flow.py +++ b/homeassistant/components/sonarr/config_flow.py @@ -1,6 +1,8 @@ """Config flow for Sonarr.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from sonarr import Sonarr, SonarrAccessRestricted, SonarrError import voluptuous as vol @@ -33,7 +35,7 @@ from .const import DOMAIN # pylint: disable=unused-import _LOGGER = logging.getLogger(__name__) -async def validate_input(hass: HomeAssistantType, data: dict) -> Dict[str, Any]: +async def validate_input(hass: HomeAssistantType, data: dict) -> dict[str, Any]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -73,9 +75,7 @@ class SonarrConfigFlow(ConfigFlow, domain=DOMAIN): """Get the options flow for this handler.""" return SonarrOptionsFlowHandler(config_entry) - async def async_step_reauth( - self, data: Optional[ConfigType] = None - ) -> Dict[str, Any]: + async def async_step_reauth(self, data: ConfigType | None = None) -> dict[str, Any]: """Handle configuration by re-auth.""" self._reauth = True self._entry_data = dict(data) @@ -84,8 +84,8 @@ class SonarrConfigFlow(ConfigFlow, domain=DOMAIN): return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Confirm reauth dialog.""" if user_input is None: return self.async_show_form( @@ -98,8 +98,8 @@ class SonarrConfigFlow(ConfigFlow, domain=DOMAIN): return await self.async_step_user() async def async_step_user( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" errors = {} @@ -138,7 +138,7 @@ class SonarrConfigFlow(ConfigFlow, domain=DOMAIN): async def _async_reauth_update_entry( self, entry_id: str, data: dict - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Update existing config entry.""" entry = self.hass.config_entries.async_get_entry(entry_id) self.hass.config_entries.async_update_entry(entry, data=data) @@ -146,7 +146,7 @@ class SonarrConfigFlow(ConfigFlow, domain=DOMAIN): return self.async_abort(reason="reauth_successful") - def _get_user_data_schema(self) -> Dict[str, Any]: + def _get_user_data_schema(self) -> dict[str, Any]: """Get the data schema to display user form.""" if self._reauth: return {vol.Required(CONF_API_KEY): str} @@ -174,7 +174,7 @@ class SonarrOptionsFlowHandler(OptionsFlow): """Initialize options flow.""" self.config_entry = config_entry - async def async_step_init(self, user_input: Optional[ConfigType] = None): + async def async_step_init(self, user_input: ConfigType | None = None): """Manage Sonarr options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/sonarr/sensor.py b/homeassistant/components/sonarr/sensor.py index ca489d95cfd..017d9b0f0d0 100644 --- a/homeassistant/components/sonarr/sensor.py +++ b/homeassistant/components/sonarr/sensor.py @@ -1,7 +1,9 @@ """Support for Sonarr sensors.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable from sonarr import Sonarr, SonarrConnectionError, SonarrError @@ -20,7 +22,7 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Sonarr sensors based on a config entry.""" options = entry.options @@ -75,7 +77,7 @@ class SonarrSensor(SonarrEntity): icon: str, key: str, name: str, - unit_of_measurement: Optional[str] = None, + unit_of_measurement: str | None = None, ) -> None: """Initialize Sonarr sensor.""" self._unit_of_measurement = unit_of_measurement @@ -131,7 +133,7 @@ class SonarrCommandsSensor(SonarrSensor): self._commands = await self.sonarr.commands() @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" attrs = {} @@ -172,7 +174,7 @@ class SonarrDiskspaceSensor(SonarrSensor): self._total_free = sum([disk.free for disk in self._disks]) @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" attrs = {} @@ -217,7 +219,7 @@ class SonarrQueueSensor(SonarrSensor): self._queue = await self.sonarr.queue() @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" attrs = {} @@ -258,7 +260,7 @@ class SonarrSeriesSensor(SonarrSensor): self._items = await self.sonarr.series() @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" attrs = {} @@ -301,7 +303,7 @@ class SonarrUpcomingSensor(SonarrSensor): ) @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" attrs = {} @@ -323,7 +325,7 @@ class SonarrWantedSensor(SonarrSensor): """Initialize Sonarr Wanted sensor.""" self._max_items = max_items self._results = None - self._total: Optional[int] = None + self._total: int | None = None super().__init__( sonarr=sonarr, @@ -342,7 +344,7 @@ class SonarrWantedSensor(SonarrSensor): self._total = self._results.total @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" attrs = {} @@ -354,6 +356,6 @@ class SonarrWantedSensor(SonarrSensor): return attrs @property - def state(self) -> Optional[int]: + def state(self) -> int | None: """Return the state of the sensor.""" return self._total diff --git a/homeassistant/components/songpal/config_flow.py b/homeassistant/components/songpal/config_flow.py index aaa9302cac2..b93a2b10bc0 100644 --- a/homeassistant/components/songpal/config_flow.py +++ b/homeassistant/components/songpal/config_flow.py @@ -1,6 +1,7 @@ """Config flow to configure songpal component.""" +from __future__ import annotations + import logging -from typing import Optional from urllib.parse import urlparse from songpal import Device, SongpalException @@ -34,7 +35,7 @@ class SongpalConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize the flow.""" - self.conf: Optional[SongpalConfig] = None + self.conf: SongpalConfig | None = None async def async_step_user(self, user_input=None): """Handle a flow initiated by the user.""" diff --git a/homeassistant/components/spotify/config_flow.py b/homeassistant/components/spotify/config_flow.py index afad75f0f39..d0fb73e18bd 100644 --- a/homeassistant/components/spotify/config_flow.py +++ b/homeassistant/components/spotify/config_flow.py @@ -1,6 +1,8 @@ """Config flow for Spotify.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from spotipy import Spotify import voluptuous as vol @@ -24,7 +26,7 @@ class SpotifyFlowHandler( def __init__(self) -> None: """Instantiate config flow.""" super().__init__() - self.entry: Optional[Dict[str, Any]] = None + self.entry: dict[str, Any] | None = None @property def logger(self) -> logging.Logger: @@ -32,11 +34,11 @@ class SpotifyFlowHandler( return logging.getLogger(__name__) @property - def extra_authorize_data(self) -> Dict[str, Any]: + def extra_authorize_data(self) -> dict[str, Any]: """Extra data that needs to be appended to the authorize url.""" return {"scope": ",".join(SPOTIFY_SCOPES)} - async def async_oauth_create_entry(self, data: Dict[str, Any]) -> Dict[str, Any]: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> dict[str, Any]: """Create an entry for Spotify.""" spotify = Spotify(auth=data["token"]["access_token"]) @@ -58,7 +60,7 @@ class SpotifyFlowHandler( return self.async_create_entry(title=name, data=data) - async def async_step_reauth(self, entry: Dict[str, Any]) -> Dict[str, Any]: + async def async_step_reauth(self, entry: dict[str, Any]) -> dict[str, Any]: """Perform reauth upon migration of old entries.""" if entry: self.entry = entry @@ -73,8 +75,8 @@ class SpotifyFlowHandler( return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Confirm reauth dialog.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index e4450e7a306..84c7d2b41ed 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -1,9 +1,11 @@ """Support for interacting with Spotify Connect.""" +from __future__ import annotations + from asyncio import run_coroutine_threadsafe import datetime as dt from datetime import timedelta import logging -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable import requests from spotipy import Spotify, SpotifyException @@ -185,7 +187,7 @@ class UnknownMediaType(BrowseError): async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Spotify based on a config entry.""" spotify = SpotifyMediaPlayer( @@ -237,9 +239,9 @@ class SpotifyMediaPlayer(MediaPlayerEntity): SPOTIFY_SCOPES ) - self._currently_playing: Optional[dict] = {} - self._devices: Optional[List[dict]] = [] - self._playlist: Optional[dict] = None + self._currently_playing: dict | None = {} + self._devices: list[dict] | None = [] + self._playlist: dict | None = None self._spotify: Spotify = None self.player_available = False @@ -265,7 +267,7 @@ class SpotifyMediaPlayer(MediaPlayerEntity): return self._id @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this entity.""" if self._me is not None: model = self._me["product"] @@ -278,7 +280,7 @@ class SpotifyMediaPlayer(MediaPlayerEntity): } @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the playback state.""" if not self._currently_playing: return STATE_IDLE @@ -287,44 +289,44 @@ class SpotifyMediaPlayer(MediaPlayerEntity): return STATE_PAUSED @property - def volume_level(self) -> Optional[float]: + def volume_level(self) -> float | None: """Return the device volume.""" return self._currently_playing.get("device", {}).get("volume_percent", 0) / 100 @property - def media_content_id(self) -> Optional[str]: + def media_content_id(self) -> str | None: """Return the media URL.""" item = self._currently_playing.get("item") or {} return item.get("uri") @property - def media_content_type(self) -> Optional[str]: + def media_content_type(self) -> str | None: """Return the media type.""" return MEDIA_TYPE_MUSIC @property - def media_duration(self) -> Optional[int]: + def media_duration(self) -> int | None: """Duration of current playing media in seconds.""" if self._currently_playing.get("item") is None: return None return self._currently_playing["item"]["duration_ms"] / 1000 @property - def media_position(self) -> Optional[str]: + def media_position(self) -> str | None: """Position of current playing media in seconds.""" if not self._currently_playing: return None return self._currently_playing["progress_ms"] / 1000 @property - def media_position_updated_at(self) -> Optional[dt.datetime]: + def media_position_updated_at(self) -> dt.datetime | None: """When was the position of the current playing media valid.""" if not self._currently_playing: return None return utc_from_timestamp(self._currently_playing["timestamp"] / 1000) @property - def media_image_url(self) -> Optional[str]: + def media_image_url(self) -> str | None: """Return the media image URL.""" if ( self._currently_playing.get("item") is None @@ -339,13 +341,13 @@ class SpotifyMediaPlayer(MediaPlayerEntity): return False @property - def media_title(self) -> Optional[str]: + def media_title(self) -> str | None: """Return the media title.""" item = self._currently_playing.get("item") or {} return item.get("name") @property - def media_artist(self) -> Optional[str]: + def media_artist(self) -> str | None: """Return the media artist.""" if self._currently_playing.get("item") is None: return None @@ -354,14 +356,14 @@ class SpotifyMediaPlayer(MediaPlayerEntity): ) @property - def media_album_name(self) -> Optional[str]: + def media_album_name(self) -> str | None: """Return the media album.""" if self._currently_playing.get("item") is None: return None return self._currently_playing["item"]["album"]["name"] @property - def media_track(self) -> Optional[int]: + def media_track(self) -> int | None: """Track number of current playing media, music track only.""" item = self._currently_playing.get("item") or {} return item.get("track_number") @@ -374,12 +376,12 @@ class SpotifyMediaPlayer(MediaPlayerEntity): return self._playlist["name"] @property - def source(self) -> Optional[str]: + def source(self) -> str | None: """Return the current playback device.""" return self._currently_playing.get("device", {}).get("name") @property - def source_list(self) -> Optional[List[str]]: + def source_list(self) -> list[str] | None: """Return a list of source devices.""" if not self._devices: return None @@ -391,7 +393,7 @@ class SpotifyMediaPlayer(MediaPlayerEntity): return bool(self._currently_playing.get("shuffle_state")) @property - def repeat(self) -> Optional[str]: + def repeat(self) -> str | None: """Return current repeat mode.""" repeat_state = self._currently_playing.get("repeat_state") return REPEAT_MODE_MAPPING_TO_HA.get(repeat_state) diff --git a/homeassistant/components/starline/account.py b/homeassistant/components/starline/account.py index 7452253019b..8d967dc2ea7 100644 --- a/homeassistant/components/starline/account.py +++ b/homeassistant/components/starline/account.py @@ -1,6 +1,8 @@ """StarLine Account.""" +from __future__ import annotations + from datetime import datetime, timedelta -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable from starline import StarlineApi, StarlineDevice @@ -29,8 +31,8 @@ class StarlineAccount: self._config_entry: ConfigEntry = config_entry self._update_interval: int = DEFAULT_SCAN_INTERVAL self._update_obd_interval: int = DEFAULT_SCAN_OBD_INTERVAL - self._unsubscribe_auto_updater: Optional[Callable] = None - self._unsubscribe_auto_obd_updater: Optional[Callable] = None + self._unsubscribe_auto_updater: Callable | None = None + self._unsubscribe_auto_obd_updater: Callable | None = None self._api: StarlineApi = StarlineApi( config_entry.data[DATA_USER_ID], config_entry.data[DATA_SLNET_TOKEN] ) @@ -123,7 +125,7 @@ class StarlineAccount: self._unsubscribe_auto_obd_updater = None @staticmethod - def device_info(device: StarlineDevice) -> Dict[str, Any]: + def device_info(device: StarlineDevice) -> dict[str, Any]: """Device information for entities.""" return { "identifiers": {(DOMAIN, device.device_id)}, @@ -134,7 +136,7 @@ class StarlineAccount: } @staticmethod - def gps_attrs(device: StarlineDevice) -> Dict[str, Any]: + def gps_attrs(device: StarlineDevice) -> dict[str, Any]: """Attributes for device tracker.""" return { "updated": datetime.utcfromtimestamp(device.position["ts"]).isoformat(), @@ -142,7 +144,7 @@ class StarlineAccount: } @staticmethod - def balance_attrs(device: StarlineDevice) -> Dict[str, Any]: + def balance_attrs(device: StarlineDevice) -> dict[str, Any]: """Attributes for balance sensor.""" return { "operator": device.balance.get("operator"), @@ -151,7 +153,7 @@ class StarlineAccount: } @staticmethod - def gsm_attrs(device: StarlineDevice) -> Dict[str, Any]: + def gsm_attrs(device: StarlineDevice) -> dict[str, Any]: """Attributes for GSM sensor.""" return { "raw": device.gsm_level, @@ -161,7 +163,7 @@ class StarlineAccount: } @staticmethod - def engine_attrs(device: StarlineDevice) -> Dict[str, Any]: + def engine_attrs(device: StarlineDevice) -> dict[str, Any]: """Attributes for engine switch.""" return { "autostart": device.car_state.get("r_start"), @@ -169,6 +171,6 @@ class StarlineAccount: } @staticmethod - def errors_attrs(device: StarlineDevice) -> Dict[str, Any]: + def errors_attrs(device: StarlineDevice) -> dict[str, Any]: """Attributes for errors sensor.""" return {"errors": device.errors.get("errors")} diff --git a/homeassistant/components/starline/config_flow.py b/homeassistant/components/starline/config_flow.py index d6e8d6f98ea..5e62127cc7a 100644 --- a/homeassistant/components/starline/config_flow.py +++ b/homeassistant/components/starline/config_flow.py @@ -1,5 +1,5 @@ """Config flow to configure StarLine component.""" -from typing import Optional +from __future__ import annotations from starline import StarlineAuth import voluptuous as vol @@ -32,11 +32,11 @@ class StarlineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize flow.""" - self._app_id: Optional[str] = None - self._app_secret: Optional[str] = None - self._username: Optional[str] = None - self._password: Optional[str] = None - self._mfa_code: Optional[str] = None + self._app_id: str | None = None + self._app_secret: str | None = None + self._username: str | None = None + self._password: str | None = None + self._mfa_code: str | None = None self._app_code = None self._app_token = None diff --git a/homeassistant/components/starline/entity.py b/homeassistant/components/starline/entity.py index 5db4d369f5e..9b81481b9d1 100644 --- a/homeassistant/components/starline/entity.py +++ b/homeassistant/components/starline/entity.py @@ -1,5 +1,7 @@ """StarLine base entity.""" -from typing import Callable, Optional +from __future__ import annotations + +from typing import Callable from homeassistant.helpers.entity import Entity @@ -17,7 +19,7 @@ class StarlineEntity(Entity): self._device = device self._key = key self._name = name - self._unsubscribe_api: Optional[Callable] = None + self._unsubscribe_api: Callable | None = None @property def should_poll(self): diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 17d4516344a..076eb3596d7 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -1,8 +1,10 @@ """Provides core stream functionality.""" +from __future__ import annotations + import asyncio from collections import deque import io -from typing import Any, Callable, List +from typing import Any, Callable from aiohttp import web import attr @@ -104,7 +106,7 @@ class StreamOutput: return self._idle_timer.idle @property - def segments(self) -> List[int]: + def segments(self) -> list[int]: """Return current sequence from segments.""" return [s.sequence for s in self._segments] diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py index f61211340ef..01a8ca9ea6b 100644 --- a/homeassistant/components/stream/recorder.py +++ b/homeassistant/components/stream/recorder.py @@ -1,8 +1,10 @@ """Provide functionality to record stream.""" +from __future__ import annotations + import logging import os import threading -from typing import Deque, List +from typing import Deque import av @@ -115,7 +117,7 @@ class RecorderOutput(StreamOutput): """Return provider name.""" return "recorder" - def prepend(self, segments: List[Segment]) -> None: + def prepend(self, segments: list[Segment]) -> None: """Prepend segments to existing list.""" self._segments.extendleft(reversed(segments)) diff --git a/homeassistant/components/stt/__init__.py b/homeassistant/components/stt/__init__.py index 0ad621f0707..5c45e5e3d44 100644 --- a/homeassistant/components/stt/__init__.py +++ b/homeassistant/components/stt/__init__.py @@ -1,8 +1,9 @@ """Provide functionality to STT.""" +from __future__ import annotations + from abc import ABC, abstractmethod import asyncio import logging -from typing import Dict, List, Optional from aiohttp import StreamReader, web from aiohttp.hdrs import istr @@ -96,44 +97,44 @@ class SpeechMetadata: class SpeechResult: """Result of audio Speech.""" - text: Optional[str] = attr.ib() + text: str | None = attr.ib() result: SpeechResultState = attr.ib() class Provider(ABC): """Represent a single STT provider.""" - hass: Optional[HomeAssistantType] = None - name: Optional[str] = None + hass: HomeAssistantType | None = None + name: str | None = None @property @abstractmethod - def supported_languages(self) -> List[str]: + def supported_languages(self) -> list[str]: """Return a list of supported languages.""" @property @abstractmethod - def supported_formats(self) -> List[AudioFormats]: + def supported_formats(self) -> list[AudioFormats]: """Return a list of supported formats.""" @property @abstractmethod - def supported_codecs(self) -> List[AudioCodecs]: + def supported_codecs(self) -> list[AudioCodecs]: """Return a list of supported codecs.""" @property @abstractmethod - def supported_bit_rates(self) -> List[AudioBitRates]: + def supported_bit_rates(self) -> list[AudioBitRates]: """Return a list of supported bit rates.""" @property @abstractmethod - def supported_sample_rates(self) -> List[AudioSampleRates]: + def supported_sample_rates(self) -> list[AudioSampleRates]: """Return a list of supported sample rates.""" @property @abstractmethod - def supported_channels(self) -> List[AudioChannels]: + def supported_channels(self) -> list[AudioChannels]: """Return a list of supported channels.""" @abstractmethod @@ -167,12 +168,12 @@ class SpeechToTextView(HomeAssistantView): url = "/api/stt/{provider}" name = "api:stt:provider" - def __init__(self, providers: Dict[str, Provider]) -> None: + def __init__(self, providers: dict[str, Provider]) -> None: """Initialize a tts view.""" self.providers = providers @staticmethod - def _metadata_from_header(request: web.Request) -> Optional[SpeechMetadata]: + def _metadata_from_header(request: web.Request) -> SpeechMetadata | None: """Extract metadata from header. X-Speech-Content: format=wav; codec=pcm; sample_rate=16000; bit_rate=16; channel=1; language=de_de diff --git a/homeassistant/components/supla/__init__.py b/homeassistant/components/supla/__init__.py index 084811c8fa0..5ebd6d6ca48 100644 --- a/homeassistant/components/supla/__init__.py +++ b/homeassistant/components/supla/__init__.py @@ -1,7 +1,8 @@ """Support for Supla devices.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Optional import async_timeout from asyncpysupla import SuplaAPI @@ -180,7 +181,7 @@ class SuplaChannel(CoordinatorEntity): ) @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the device.""" return self.channel_data["caption"] diff --git a/homeassistant/components/surepetcare/__init__.py b/homeassistant/components/surepetcare/__init__.py index 8ba6809ee05..4a65931d3f0 100644 --- a/homeassistant/components/surepetcare/__init__.py +++ b/homeassistant/components/surepetcare/__init__.py @@ -1,6 +1,8 @@ """Support for Sure Petcare cat/pet flaps.""" +from __future__ import annotations + import logging -from typing import Any, Dict, List +from typing import Any from surepy import ( MESTART_RESOURCE, @@ -185,12 +187,12 @@ async def async_setup(hass, config) -> bool: class SurePetcareAPI: """Define a generic Sure Petcare object.""" - def __init__(self, hass, surepy: SurePetcare, ids: List[Dict[str, Any]]) -> None: + def __init__(self, hass, surepy: SurePetcare, ids: list[dict[str, Any]]) -> None: """Initialize the Sure Petcare object.""" self.hass = hass self.surepy = surepy self.ids = ids - self.states: Dict[str, Any] = {} + self.states: dict[str, Any] = {} async def async_update(self, arg: Any = None) -> None: """Refresh Sure Petcare data.""" diff --git a/homeassistant/components/surepetcare/binary_sensor.py b/homeassistant/components/surepetcare/binary_sensor.py index 64e27669786..e96a5eaf35e 100644 --- a/homeassistant/components/surepetcare/binary_sensor.py +++ b/homeassistant/components/surepetcare/binary_sensor.py @@ -1,7 +1,9 @@ """Support for Sure PetCare Flaps/Pets binary sensors.""" +from __future__ import annotations + from datetime import datetime import logging -from typing import Any, Dict, Optional +from typing import Any from surepy import SureLocationID, SurepyProduct @@ -71,8 +73,8 @@ class SurePetcareBinarySensor(BinarySensorEntity): self._device_class = device_class self._spc: SurePetcareAPI = spc - self._spc_data: Dict[str, Any] = self._spc.states[self._sure_type].get(self._id) - self._state: Dict[str, Any] = {} + self._spc_data: dict[str, Any] = self._spc.states[self._sure_type].get(self._id) + self._state: dict[str, Any] = {} # cover special case where a device has no name set if "name" in self._spc_data: @@ -85,7 +87,7 @@ class SurePetcareBinarySensor(BinarySensorEntity): self._async_unsub_dispatcher_connect = None @property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return true if entity is on/unlocked.""" return bool(self._state) @@ -151,7 +153,7 @@ class Hub(SurePetcareBinarySensor): return self.available @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the device.""" attributes = None if self._state: @@ -179,7 +181,7 @@ class Pet(SurePetcareBinarySensor): return False @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the device.""" attributes = None if self._state: @@ -232,7 +234,7 @@ class DeviceConnectivity(SurePetcareBinarySensor): return self.available @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the device.""" attributes = None if self._state: diff --git a/homeassistant/components/surepetcare/sensor.py b/homeassistant/components/surepetcare/sensor.py index 54e7f4d5773..92f90faff2c 100644 --- a/homeassistant/components/surepetcare/sensor.py +++ b/homeassistant/components/surepetcare/sensor.py @@ -1,6 +1,8 @@ """Support for Sure PetCare Flaps/Pets sensors.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from surepy import SureLockStateID, SurepyProduct @@ -62,8 +64,8 @@ class SurePetcareSensor(Entity): self._sure_type = sure_type self._spc = spc - self._spc_data: Dict[str, Any] = self._spc.states[self._sure_type].get(self._id) - self._state: Dict[str, Any] = {} + self._spc_data: dict[str, Any] = self._spc.states[self._sure_type].get(self._id) + self._state: dict[str, Any] = {} self._name = ( f"{self._sure_type.name.capitalize()} " @@ -120,12 +122,12 @@ class Flap(SurePetcareSensor): """Sure Petcare Flap.""" @property - def state(self) -> Optional[int]: + def state(self) -> int | None: """Return battery level in percent.""" return SureLockStateID(self._state["locking"]["mode"]).name.capitalize() @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the device.""" attributes = None if self._state: @@ -143,9 +145,9 @@ class SureBattery(SurePetcareSensor): return f"{self._name} Battery Level" @property - def state(self) -> Optional[int]: + def state(self) -> int | None: """Return battery level in percent.""" - battery_percent: Optional[int] + battery_percent: int | None try: per_battery_voltage = self._state["battery"] / 4 voltage_diff = per_battery_voltage - SURE_BATT_VOLTAGE_LOW @@ -166,7 +168,7 @@ class SureBattery(SurePetcareSensor): return DEVICE_CLASS_BATTERY @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return state attributes.""" attributes = None if self._state: diff --git a/homeassistant/components/switch/device_action.py b/homeassistant/components/switch/device_action.py index a50131f094c..0f3890d329f 100644 --- a/homeassistant/components/switch/device_action.py +++ b/homeassistant/components/switch/device_action.py @@ -1,5 +1,5 @@ """Provides device actions for switches.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -25,6 +25,6 @@ async def async_call_action_from_config( ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions.""" return await toggle_entity.async_get_actions(hass, device_id, DOMAIN) diff --git a/homeassistant/components/switch/device_condition.py b/homeassistant/components/switch/device_condition.py index c928deef01a..15c2e54d193 100644 --- a/homeassistant/components/switch/device_condition.py +++ b/homeassistant/components/switch/device_condition.py @@ -1,5 +1,5 @@ """Provides device conditions for switches.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -28,7 +28,7 @@ def async_condition_from_config( async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions.""" return await toggle_entity.async_get_conditions(hass, device_id, DOMAIN) diff --git a/homeassistant/components/switch/device_trigger.py b/homeassistant/components/switch/device_trigger.py index cb5d5f7aa0e..15b700d9eb5 100644 --- a/homeassistant/components/switch/device_trigger.py +++ b/homeassistant/components/switch/device_trigger.py @@ -1,5 +1,5 @@ """Provides device triggers for switches.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -28,7 +28,7 @@ async def async_attach_trigger( ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers.""" return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN) diff --git a/homeassistant/components/switch/light.py b/homeassistant/components/switch/light.py index fc8638162e7..4ab030bc8e4 100644 --- a/homeassistant/components/switch/light.py +++ b/homeassistant/components/switch/light.py @@ -1,5 +1,7 @@ """Light support for switch entities.""" -from typing import Any, Callable, Optional, Sequence, cast +from __future__ import annotations + +from typing import Any, Callable, Sequence, cast import voluptuous as vol @@ -38,7 +40,7 @@ async def async_setup_platform( hass: HomeAssistantType, config: ConfigType, async_add_entities: Callable[[Sequence[Entity]], None], - discovery_info: Optional[DiscoveryInfoType] = None, + discovery_info: DiscoveryInfoType | None = None, ) -> None: """Initialize Light Switch platform.""" @@ -65,7 +67,7 @@ class LightSwitch(LightEntity): self._name = name self._switch_entity_id = switch_entity_id self._unique_id = unique_id - self._switch_state: Optional[State] = None + self._switch_state: State | None = None @property def name(self) -> str: diff --git a/homeassistant/components/switch/reproduce_state.py b/homeassistant/components/switch/reproduce_state.py index 0527f558f35..5a90af4181c 100644 --- a/homeassistant/components/switch/reproduce_state.py +++ b/homeassistant/components/switch/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Switch state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -24,8 +26,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -60,8 +62,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Switch states.""" await asyncio.gather( diff --git a/homeassistant/components/switch/significant_change.py b/homeassistant/components/switch/significant_change.py index f4dcddc3f34..231085a3eef 100644 --- a/homeassistant/components/switch/significant_change.py +++ b/homeassistant/components/switch/significant_change.py @@ -1,5 +1,7 @@ """Helper to test significant Switch state changes.""" -from typing import Any, Optional +from __future__ import annotations + +from typing import Any from homeassistant.core import HomeAssistant, callback @@ -12,6 +14,6 @@ def async_check_significant_change( new_state: str, new_attrs: dict, **kwargs: Any, -) -> Optional[bool]: +) -> bool | None: """Test if state significantly changed.""" return old_state != new_state diff --git a/homeassistant/components/switchbot/switch.py b/homeassistant/components/switchbot/switch.py index faf230507a2..cff1a0d0edc 100644 --- a/homeassistant/components/switchbot/switch.py +++ b/homeassistant/components/switchbot/switch.py @@ -1,5 +1,7 @@ """Support for Switchbot.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any # pylint: disable=import-error import switchbot @@ -86,6 +88,6 @@ class SwitchBot(SwitchEntity, RestoreEntity): return self._name @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes.""" return {"last_run_success": self._last_run_success} diff --git a/homeassistant/components/switcher_kis/__init__.py b/homeassistant/components/switcher_kis/__init__.py index d081b3331c7..8d39182dcc3 100644 --- a/homeassistant/components/switcher_kis/__init__.py +++ b/homeassistant/components/switcher_kis/__init__.py @@ -1,8 +1,9 @@ """Home Assistant Switcher Component.""" +from __future__ import annotations + from asyncio import QueueEmpty, TimeoutError as Asyncio_TimeoutError, wait_for from datetime import datetime, timedelta import logging -from typing import Dict, Optional from aioswitcher.bridge import SwitcherV2Bridge import voluptuous as vol @@ -45,7 +46,7 @@ CONFIG_SCHEMA = vol.Schema( ) -async def async_setup(hass: HomeAssistantType, config: Dict) -> bool: +async def async_setup(hass: HomeAssistantType, config: dict) -> bool: """Set up the switcher component.""" phone_id = config[DOMAIN][CONF_PHONE_ID] device_id = config[DOMAIN][CONF_DEVICE_ID] @@ -72,7 +73,7 @@ async def async_setup(hass: HomeAssistantType, config: Dict) -> bool: hass.async_create_task(async_load_platform(hass, SWITCH_DOMAIN, DOMAIN, {}, config)) @callback - def device_updates(timestamp: Optional[datetime]) -> None: + def device_updates(timestamp: datetime | None) -> None: """Use for updating the device data from the queue.""" if v2bridge.running: try: diff --git a/homeassistant/components/switcher_kis/switch.py b/homeassistant/components/switcher_kis/switch.py index 99d50c0c559..61297142716 100644 --- a/homeassistant/components/switcher_kis/switch.py +++ b/homeassistant/components/switcher_kis/switch.py @@ -1,5 +1,7 @@ """Home Assistant Switcher Component Switch platform.""" -from typing import Callable, Dict +from __future__ import annotations + +from typing import Callable from aioswitcher.api import SwitcherV2Api from aioswitcher.api.messages import SwitcherV2ControlResponseMSG @@ -52,9 +54,9 @@ SERVICE_TURN_ON_WITH_TIMER_SCHEMA = { async def async_setup_platform( hass: HomeAssistantType, - config: Dict, + config: dict, async_add_entities: Callable, - discovery_info: Dict, + discovery_info: dict, ) -> None: """Set up the switcher platform for the switch component.""" if discovery_info is None: @@ -139,7 +141,7 @@ class SwitcherControl(SwitchEntity): return self._device_data.power_consumption @property - def extra_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> dict: """Return the optional state attributes.""" attribs = {} @@ -173,11 +175,11 @@ class SwitcherControl(SwitchEntity): self._state = self._device_data.state self.async_write_ha_state() - async def async_turn_on(self, **kwargs: Dict) -> None: + async def async_turn_on(self, **kwargs: dict) -> None: """Turn the entity on.""" await self._control_device(True) - async def async_turn_off(self, **kwargs: Dict) -> None: + async def async_turn_off(self, **kwargs: dict) -> None: """Turn the entity off.""" await self._control_device(False) diff --git a/homeassistant/components/syncthru/__init__.py b/homeassistant/components/syncthru/__init__.py index 83d32eb9b47..293680151ff 100644 --- a/homeassistant/components/syncthru/__init__.py +++ b/homeassistant/components/syncthru/__init__.py @@ -1,7 +1,7 @@ """The syncthru component.""" +from __future__ import annotations import logging -from typing import Set, Tuple from pysyncthru import SyncThru @@ -65,12 +65,12 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo return True -def device_identifiers(printer: SyncThru) -> Set[Tuple[str, str]]: +def device_identifiers(printer: SyncThru) -> set[tuple[str, str]]: """Get device identifiers for device registry.""" return {(DOMAIN, printer.serial_number())} -def device_connections(printer: SyncThru) -> Set[Tuple[str, str]]: +def device_connections(printer: SyncThru) -> set[tuple[str, str]]: """Get device connections for device registry.""" connections = set() try: diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 002538061c8..673cb6717fc 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -1,8 +1,9 @@ """The Synology DSM component.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Dict import async_timeout from synology_dsm import SynologyDSM @@ -599,7 +600,7 @@ class SynologyDSMBaseEntity(CoordinatorEntity): self, api: SynoApi, entity_type: str, - entity_info: Dict[str, str], + entity_info: dict[str, str], coordinator: DataUpdateCoordinator, ): """Initialize the Synology DSM entity.""" @@ -643,12 +644,12 @@ class SynologyDSMBaseEntity(CoordinatorEntity): return self._class @property - def extra_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> dict[str, any]: """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "identifiers": {(DOMAIN, self._api.information.serial)}, @@ -676,7 +677,7 @@ class SynologyDSMDeviceEntity(SynologyDSMBaseEntity): self, api: SynoApi, entity_type: str, - entity_info: Dict[str, str], + entity_info: dict[str, str], coordinator: DataUpdateCoordinator, device_id: str = None, ): @@ -718,7 +719,7 @@ class SynologyDSMDeviceEntity(SynologyDSMBaseEntity): return bool(self._api.storage) @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "identifiers": {(DOMAIN, self._api.information.serial, self._device_id)}, diff --git a/homeassistant/components/synology_dsm/binary_sensor.py b/homeassistant/components/synology_dsm/binary_sensor.py index 042e46c636e..fb8ed5a23cd 100644 --- a/homeassistant/components/synology_dsm/binary_sensor.py +++ b/homeassistant/components/synology_dsm/binary_sensor.py @@ -1,5 +1,5 @@ """Support for Synology DSM binary sensors.""" -from typing import Dict +from __future__ import annotations from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry @@ -71,7 +71,7 @@ class SynoDSMSecurityBinarySensor(SynologyDSMBaseEntity, BinarySensorEntity): return bool(self._api.security) @property - def extra_state_attributes(self) -> Dict[str, str]: + def extra_state_attributes(self) -> dict[str, str]: """Return security checks details.""" return self._api.security.status_by_check diff --git a/homeassistant/components/synology_dsm/camera.py b/homeassistant/components/synology_dsm/camera.py index c0e0ded72ed..67052543569 100644 --- a/homeassistant/components/synology_dsm/camera.py +++ b/homeassistant/components/synology_dsm/camera.py @@ -1,6 +1,7 @@ """Support for Synology DSM cameras.""" +from __future__ import annotations + import logging -from typing import Dict from synology_dsm.api.surveillance_station import SynoSurveillanceStation from synology_dsm.exceptions import ( @@ -79,7 +80,7 @@ class SynoDSMCamera(SynologyDSMBaseEntity, Camera): return self.coordinator.data["cameras"][self._camera_id] @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "identifiers": { diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index 79350ce89d3..8cfab92975c 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -1,6 +1,7 @@ """Support for Synology DSM sensors.""" +from __future__ import annotations + from datetime import timedelta -from typing import Dict from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -145,7 +146,7 @@ class SynoDSMInfoSensor(SynologyDSMBaseEntity): self, api: SynoApi, entity_type: str, - entity_info: Dict[str, str], + entity_info: dict[str, str], coordinator: DataUpdateCoordinator, ): """Initialize the Synology SynoDSMInfoSensor entity.""" diff --git a/homeassistant/components/synology_dsm/switch.py b/homeassistant/components/synology_dsm/switch.py index 998f74adf2a..f9883b0c916 100644 --- a/homeassistant/components/synology_dsm/switch.py +++ b/homeassistant/components/synology_dsm/switch.py @@ -1,6 +1,7 @@ """Support for Synology DSM switch.""" +from __future__ import annotations + import logging -from typing import Dict from synology_dsm.api.surveillance_station import SynoSurveillanceStation @@ -49,7 +50,7 @@ class SynoDSMSurveillanceHomeModeToggle(SynologyDSMBaseEntity, ToggleEntity): self, api: SynoApi, entity_type: str, - entity_info: Dict[str, str], + entity_info: dict[str, str], version: str, coordinator: DataUpdateCoordinator, ): @@ -95,7 +96,7 @@ class SynoDSMSurveillanceHomeModeToggle(SynologyDSMBaseEntity, ToggleEntity): return bool(self._api.surveillance_station) @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "identifiers": { diff --git a/homeassistant/components/system_health/__init__.py b/homeassistant/components/system_health/__init__.py index c53cd9da1a5..ea87a2af832 100644 --- a/homeassistant/components/system_health/__init__.py +++ b/homeassistant/components/system_health/__init__.py @@ -1,9 +1,11 @@ """Support for System health .""" +from __future__ import annotations + import asyncio import dataclasses from datetime import datetime import logging -from typing import Awaitable, Callable, Dict, Optional +from typing import Awaitable, Callable import aiohttp import async_timeout @@ -27,7 +29,7 @@ INFO_CALLBACK_TIMEOUT = 5 def async_register_info( hass: HomeAssistant, domain: str, - info_callback: Callable[[HomeAssistant], Dict], + info_callback: Callable[[HomeAssistant], dict], ): """Register an info callback. @@ -89,10 +91,10 @@ def _format_value(val): @websocket_api.async_response @websocket_api.websocket_command({vol.Required("type"): "system_health/info"}) async def handle_info( - hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: Dict + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict ): """Handle an info request via a subscription.""" - registrations: Dict[str, SystemHealthRegistration] = hass.data[DOMAIN] + registrations: dict[str, SystemHealthRegistration] = hass.data[DOMAIN] data = {} pending_info = {} @@ -187,14 +189,14 @@ class SystemHealthRegistration: hass: HomeAssistant domain: str - info_callback: Optional[Callable[[HomeAssistant], Awaitable[Dict]]] = None - manage_url: Optional[str] = None + info_callback: Callable[[HomeAssistant], Awaitable[dict]] | None = None + manage_url: str | None = None @callback def async_register_info( self, - info_callback: Callable[[HomeAssistant], Awaitable[Dict]], - manage_url: Optional[str] = None, + info_callback: Callable[[HomeAssistant], Awaitable[dict]], + manage_url: str | None = None, ): """Register an info callback.""" self.info_callback = info_callback @@ -203,7 +205,7 @@ class SystemHealthRegistration: async def async_check_can_reach_url( - hass: HomeAssistant, url: str, more_info: Optional[str] = None + hass: HomeAssistant, url: str, more_info: str | None = None ) -> str: """Test if the url can be reached.""" session = aiohttp_client.async_get_clientsession(hass) From b67b9b94f9261deaa423a16acba344e59d66fe0a Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 14:43:52 +0100 Subject: [PATCH 461/831] Update typing 13 (#48077) --- homeassistant/components/tag/__init__.py | 9 +++-- .../components/tasmota/device_trigger.py | 10 +++-- homeassistant/components/tasmota/sensor.py | 4 +- homeassistant/components/template/sensor.py | 4 +- .../components/template/template_entity.py | 19 +++++----- homeassistant/components/tesla/climate.py | 7 ++-- .../components/tesla/device_tracker.py | 6 +-- homeassistant/components/tesla/sensor.py | 8 ++-- homeassistant/components/timer/__init__.py | 19 +++++----- .../components/timer/reproduce_state.py | 12 +++--- .../components/toon/binary_sensor.py | 4 +- homeassistant/components/toon/climate.py | 18 +++++---- homeassistant/components/toon/config_flow.py | 20 +++++----- homeassistant/components/toon/coordinator.py | 7 ++-- homeassistant/components/toon/models.py | 20 +++++----- homeassistant/components/toon/oauth2.py | 6 ++- homeassistant/components/toon/sensor.py | 8 ++-- homeassistant/components/tplink/common.py | 5 ++- homeassistant/components/tplink/light.py | 8 ++-- .../components/transmission/sensor.py | 4 +- homeassistant/components/tts/__init__.py | 10 +++-- homeassistant/components/tuya/fan.py | 5 ++- homeassistant/components/twinkly/light.py | 9 +++-- homeassistant/components/unifi/controller.py | 5 ++- .../components/universal/media_player.py | 5 ++- .../components/upc_connect/device_tracker.py | 7 ++-- homeassistant/components/upcloud/__init__.py | 11 +++--- homeassistant/components/upnp/config_flow.py | 12 +++--- homeassistant/components/upnp/device.py | 4 +- homeassistant/components/upnp/sensor.py | 8 ++-- .../usgs_earthquakes_feed/geo_location.py | 11 +++--- homeassistant/components/uvc/camera.py | 5 ++- .../components/vacuum/device_action.py | 6 +-- .../components/vacuum/device_condition.py | 4 +- .../components/vacuum/device_trigger.py | 4 +- .../components/vacuum/reproduce_state.py | 12 +++--- homeassistant/components/vera/__init__.py | 10 +++-- .../components/vera/binary_sensor.py | 8 ++-- homeassistant/components/vera/climate.py | 20 +++++----- homeassistant/components/vera/common.py | 10 +++-- homeassistant/components/vera/config_flow.py | 12 +++--- homeassistant/components/vera/cover.py | 6 ++- homeassistant/components/vera/light.py | 10 +++-- homeassistant/components/vera/lock.py | 12 +++--- homeassistant/components/vera/scene.py | 8 ++-- homeassistant/components/vera/sensor.py | 8 ++-- homeassistant/components/vera/switch.py | 8 ++-- homeassistant/components/vizio/__init__.py | 6 ++- homeassistant/components/vizio/config_flow.py | 38 ++++++++++--------- .../components/vizio/media_player.py | 30 ++++++++------- .../components/volumio/config_flow.py | 11 +++--- 51 files changed, 287 insertions(+), 226 deletions(-) diff --git a/homeassistant/components/tag/__init__.py b/homeassistant/components/tag/__init__.py index 6c385181aaa..6dcf2ec9a4d 100644 --- a/homeassistant/components/tag/__init__.py +++ b/homeassistant/components/tag/__init__.py @@ -1,6 +1,7 @@ """The Tag integration.""" +from __future__ import annotations + import logging -import typing import uuid import voluptuous as vol @@ -63,7 +64,7 @@ class TagStorageCollection(collection.StorageCollection): CREATE_SCHEMA = vol.Schema(CREATE_FIELDS) UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS) - async def _process_create_data(self, data: typing.Dict) -> typing.Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" data = self.CREATE_SCHEMA(data) if not data[TAG_ID]: @@ -74,11 +75,11 @@ class TagStorageCollection(collection.StorageCollection): return data @callback - def _get_suggested_id(self, info: typing.Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return info[TAG_ID] - async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" data = {**data, **self.UPDATE_SCHEMA(update_data)} # make last_scanned JSON serializeable diff --git a/homeassistant/components/tasmota/device_trigger.py b/homeassistant/components/tasmota/device_trigger.py index 463b1c65a98..0cbb6e10f1f 100644 --- a/homeassistant/components/tasmota/device_trigger.py +++ b/homeassistant/components/tasmota/device_trigger.py @@ -1,6 +1,8 @@ """Provides device automations for Tasmota.""" +from __future__ import annotations + import logging -from typing import Callable, List, Optional +from typing import Callable import attr from hatasmota.trigger import TasmotaTrigger @@ -47,7 +49,7 @@ class TriggerInstance: action: AutomationActionType = attr.ib() automation_info: dict = attr.ib() trigger: "Trigger" = attr.ib() - remove: Optional[CALLBACK_TYPE] = attr.ib(default=None) + remove: CALLBACK_TYPE | None = attr.ib(default=None) async def async_attach_trigger(self): """Attach event trigger.""" @@ -85,7 +87,7 @@ class Trigger: subtype: str = attr.ib() tasmota_trigger: TasmotaTrigger = attr.ib() type: str = attr.ib() - trigger_instances: List[TriggerInstance] = attr.ib(factory=list) + trigger_instances: list[TriggerInstance] = attr.ib(factory=list) async def add_trigger(self, action, automation_info): """Add Tasmota trigger.""" @@ -238,7 +240,7 @@ async def async_remove_triggers(hass: HomeAssistant, device_id: str): device_trigger.remove_update_signal() -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for a Tasmota device.""" triggers = [] diff --git a/homeassistant/components/tasmota/sensor.py b/homeassistant/components/tasmota/sensor.py index 0387e835522..9d7195a98e5 100644 --- a/homeassistant/components/tasmota/sensor.py +++ b/homeassistant/components/tasmota/sensor.py @@ -1,5 +1,5 @@ """Support for Tasmota sensors.""" -from typing import Optional +from __future__ import annotations from hatasmota import const as hc, status_sensor @@ -163,7 +163,7 @@ class TasmotaSensor(TasmotaAvailability, TasmotaDiscoveryUpdate, Entity): self.async_write_ha_state() @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class of the sensor.""" class_or_icon = SENSOR_DEVICE_CLASS_ICON_MAP.get( self._tasmota_entity.quantity, {} diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index c67e3a275a3..073924c51b6 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -1,5 +1,5 @@ """Allows the creation of a sensor that breaks out state_attributes.""" -from typing import Optional +from __future__ import annotations import voluptuous as vol @@ -165,7 +165,7 @@ class SensorTemplate(TemplateEntity, Entity): return self._state @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class of the sensor.""" return self._device_class diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index 0ae540edf97..f8909206dec 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -1,7 +1,8 @@ """TemplateEntity utility class.""" +from __future__ import annotations import logging -from typing import Any, Callable, List, Optional, Union +from typing import Any, Callable import voluptuous as vol @@ -30,8 +31,8 @@ class _TemplateAttribute: attribute: str, template: Template, validator: Callable[[Any], Any] = None, - on_update: Optional[Callable[[Any], None]] = None, - none_on_template_error: Optional[bool] = False, + on_update: Callable[[Any], None] | None = None, + none_on_template_error: bool | None = False, ): """Template attribute.""" self._entity = entity @@ -61,10 +62,10 @@ class _TemplateAttribute: @callback def handle_result( self, - event: Optional[Event], + event: Event | None, template: Template, - last_result: Union[str, None, TemplateError], - result: Union[str, TemplateError], + last_result: str | None | TemplateError, + result: str | TemplateError, ) -> None: """Handle a template result event callback.""" if isinstance(result, TemplateError): @@ -189,7 +190,7 @@ class TemplateEntity(Entity): attribute: str, template: Template, validator: Callable[[Any], Any] = None, - on_update: Optional[Callable[[Any], None]] = None, + on_update: Callable[[Any], None] | None = None, none_on_template_error: bool = False, ) -> None: """ @@ -219,8 +220,8 @@ class TemplateEntity(Entity): @callback def _handle_results( self, - event: Optional[Event], - updates: List[TrackTemplateResult], + event: Event | None, + updates: list[TrackTemplateResult], ) -> None: """Call back the results to the attributes.""" if event: diff --git a/homeassistant/components/tesla/climate.py b/homeassistant/components/tesla/climate.py index 4c7ed850749..81639bc3fe4 100644 --- a/homeassistant/components/tesla/climate.py +++ b/homeassistant/components/tesla/climate.py @@ -1,6 +1,7 @@ """Support for Tesla HVAC system.""" +from __future__ import annotations + import logging -from typing import List, Optional from teslajsonpy.exceptions import UnknownPresetMode @@ -103,7 +104,7 @@ class TeslaThermostat(TeslaDevice, ClimateEntity): _LOGGER.error("%s", ex.message) @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp. Requires SUPPORT_PRESET_MODE. @@ -111,7 +112,7 @@ class TeslaThermostat(TeslaDevice, ClimateEntity): return self.tesla_device.preset_mode @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes. Requires SUPPORT_PRESET_MODE. diff --git a/homeassistant/components/tesla/device_tracker.py b/homeassistant/components/tesla/device_tracker.py index 16e8ce6dbe2..6813b3769e7 100644 --- a/homeassistant/components/tesla/device_tracker.py +++ b/homeassistant/components/tesla/device_tracker.py @@ -1,5 +1,5 @@ """Support for tracking Tesla cars.""" -from typing import Optional +from __future__ import annotations from homeassistant.components.device_tracker import SOURCE_TYPE_GPS from homeassistant.components.device_tracker.config_entry import TrackerEntity @@ -25,13 +25,13 @@ class TeslaDeviceEntity(TeslaDevice, TrackerEntity): """A class representing a Tesla device.""" @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of the device.""" location = self.tesla_device.get_location() return self.tesla_device.get_location().get("latitude") if location else None @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of the device.""" location = self.tesla_device.get_location() return self.tesla_device.get_location().get("longitude") if location else None diff --git a/homeassistant/components/tesla/sensor.py b/homeassistant/components/tesla/sensor.py index 9094a7d6b01..40bf68bfa15 100644 --- a/homeassistant/components/tesla/sensor.py +++ b/homeassistant/components/tesla/sensor.py @@ -1,5 +1,5 @@ """Support for the Tesla sensors.""" -from typing import Optional +from __future__ import annotations from homeassistant.components.sensor import DEVICE_CLASSES from homeassistant.const import ( @@ -39,7 +39,7 @@ class TeslaSensor(TeslaDevice, Entity): self._unique_id = f"{super().unique_id}_{self.type}" @property - def state(self) -> Optional[float]: + def state(self) -> float | None: """Return the state of the sensor.""" if self.tesla_device.type == "temperature sensor": if self.type == "outside": @@ -58,7 +58,7 @@ class TeslaSensor(TeslaDevice, Entity): return self.tesla_device.get_value() @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit_of_measurement of the device.""" units = self.tesla_device.measurement if units == "F": @@ -72,7 +72,7 @@ class TeslaSensor(TeslaDevice, Entity): return units @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device_class of the device.""" return ( self.tesla_device.device_class diff --git a/homeassistant/components/timer/__init__.py b/homeassistant/components/timer/__init__.py index b0ff60bbcae..05955b46b5c 100644 --- a/homeassistant/components/timer/__init__.py +++ b/homeassistant/components/timer/__init__.py @@ -3,7 +3,6 @@ from __future__ import annotations from datetime import datetime, timedelta import logging -from typing import Dict, Optional import voluptuous as vol @@ -165,7 +164,7 @@ class TimerStorageCollection(collection.StorageCollection): CREATE_SCHEMA = vol.Schema(CREATE_FIELDS) UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS) - async def _process_create_data(self, data: Dict) -> Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" data = self.CREATE_SCHEMA(data) # make duration JSON serializeable @@ -173,11 +172,11 @@ class TimerStorageCollection(collection.StorageCollection): return data @callback - def _get_suggested_id(self, info: Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return info[CONF_NAME] - async def _update_data(self, data: dict, update_data: Dict) -> Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" data = {**data, **self.UPDATE_SCHEMA(update_data)} # make duration JSON serializeable @@ -189,18 +188,18 @@ class TimerStorageCollection(collection.StorageCollection): class Timer(RestoreEntity): """Representation of a timer.""" - def __init__(self, config: Dict): + def __init__(self, config: dict): """Initialize a timer.""" self._config: dict = config self.editable: bool = True self._state: str = STATUS_IDLE self._duration = cv.time_period_str(config[CONF_DURATION]) - self._remaining: Optional[timedelta] = None - self._end: Optional[datetime] = None + self._remaining: timedelta | None = None + self._end: datetime | None = None self._listener = None @classmethod - def from_yaml(cls, config: Dict) -> Timer: + def from_yaml(cls, config: dict) -> Timer: """Return entity instance initialized from yaml storage.""" timer = cls(config) timer.entity_id = ENTITY_ID_FORMAT.format(config[CONF_ID]) @@ -247,7 +246,7 @@ class Timer(RestoreEntity): return attrs @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return unique id for the entity.""" return self._config[CONF_ID] @@ -348,7 +347,7 @@ class Timer(RestoreEntity): self.hass.bus.async_fire(EVENT_TIMER_FINISHED, {"entity_id": self.entity_id}) self.async_write_ha_state() - async def async_update_config(self, config: Dict) -> None: + async def async_update_config(self, config: dict) -> None: """Handle when the config is updated.""" self._config = config self._duration = cv.time_period_str(config[CONF_DURATION]) diff --git a/homeassistant/components/timer/reproduce_state.py b/homeassistant/components/timer/reproduce_state.py index 71abb0bfd71..377f8a1dda2 100644 --- a/homeassistant/components/timer/reproduce_state.py +++ b/homeassistant/components/timer/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Timer state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State @@ -27,8 +29,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -69,8 +71,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Timer states.""" await asyncio.gather( diff --git a/homeassistant/components/toon/binary_sensor.py b/homeassistant/components/toon/binary_sensor.py index fe14435f2ab..6651806a21c 100644 --- a/homeassistant/components/toon/binary_sensor.py +++ b/homeassistant/components/toon/binary_sensor.py @@ -1,5 +1,5 @@ """Support for Toon binary sensors.""" -from typing import Optional +from __future__ import annotations from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry @@ -84,7 +84,7 @@ class ToonBinarySensor(ToonEntity, BinarySensorEntity): return BINARY_SENSOR_ENTITIES[self.key][ATTR_DEVICE_CLASS] @property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return the status of the binary sensor.""" section = getattr( self.coordinator.data, BINARY_SENSOR_ENTITIES[self.key][ATTR_SECTION] diff --git a/homeassistant/components/toon/climate.py b/homeassistant/components/toon/climate.py index 99b76600dce..db2bed47f51 100644 --- a/homeassistant/components/toon/climate.py +++ b/homeassistant/components/toon/climate.py @@ -1,5 +1,7 @@ """Support for Toon thermostat.""" -from typing import Any, Dict, List, Optional +from __future__ import annotations + +from typing import Any from toonapi import ( ACTIVE_STATE_AWAY, @@ -61,12 +63,12 @@ class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateEntity): return HVAC_MODE_HEAT @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return [HVAC_MODE_HEAT] @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation.""" if self.coordinator.data.thermostat.heating: return CURRENT_HVAC_HEAT @@ -78,7 +80,7 @@ class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateEntity): return TEMP_CELSIUS @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" mapping = { ACTIVE_STATE_AWAY: PRESET_AWAY, @@ -89,17 +91,17 @@ class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateEntity): return mapping.get(self.coordinator.data.thermostat.active_state) @property - def preset_modes(self) -> List[str]: + def preset_modes(self) -> list[str]: """Return a list of available preset modes.""" return [PRESET_AWAY, PRESET_COMFORT, PRESET_HOME, PRESET_SLEEP] @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self.coordinator.data.thermostat.current_display_temperature @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return self.coordinator.data.thermostat.current_setpoint @@ -114,7 +116,7 @@ class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateEntity): return DEFAULT_MAX_TEMP @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the current state of the burner.""" return {"heating_type": self.coordinator.data.agreement.heating_type} diff --git a/homeassistant/components/toon/config_flow.py b/homeassistant/components/toon/config_flow.py index 1e1739e85df..bc673d2d181 100644 --- a/homeassistant/components/toon/config_flow.py +++ b/homeassistant/components/toon/config_flow.py @@ -1,6 +1,8 @@ """Config flow to configure the Toon component.""" +from __future__ import annotations + import logging -from typing import Any, Dict, List, Optional +from typing import Any from toonapi import Agreement, Toon, ToonError import voluptuous as vol @@ -19,15 +21,15 @@ class ToonFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): DOMAIN = DOMAIN VERSION = 2 - agreements: Optional[List[Agreement]] = None - data: Optional[Dict[str, Any]] = None + agreements: list[Agreement] | None = None + data: dict[str, Any] | None = None @property def logger(self) -> logging.Logger: """Return logger.""" return logging.getLogger(__name__) - async def async_oauth_create_entry(self, data: Dict[str, Any]) -> Dict[str, Any]: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> dict[str, Any]: """Test connection and load up agreements.""" self.data = data @@ -46,8 +48,8 @@ class ToonFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): return await self.async_step_agreement() async def async_step_import( - self, config: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, config: dict[str, Any] | None = None + ) -> dict[str, Any]: """Start a configuration flow based on imported data. This step is merely here to trigger "discovery" when the `toon` @@ -63,8 +65,8 @@ class ToonFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): return await self.async_step_user() async def async_step_agreement( - self, user_input: Dict[str, Any] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] = None + ) -> dict[str, Any]: """Select Toon agreement to add.""" if len(self.agreements) == 1: return await self._create_entry(self.agreements[0]) @@ -85,7 +87,7 @@ class ToonFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): agreement_index = agreements_list.index(user_input[CONF_AGREEMENT]) return await self._create_entry(self.agreements[agreement_index]) - async def _create_entry(self, agreement: Agreement) -> Dict[str, Any]: + async def _create_entry(self, agreement: Agreement) -> dict[str, Any]: if CONF_MIGRATE in self.context: await self.hass.config_entries.async_remove(self.context[CONF_MIGRATE]) diff --git a/homeassistant/components/toon/coordinator.py b/homeassistant/components/toon/coordinator.py index 359cb5b0ffb..069bd58d922 100644 --- a/homeassistant/components/toon/coordinator.py +++ b/homeassistant/components/toon/coordinator.py @@ -1,7 +1,8 @@ """Provides the Toon DataUpdateCoordinator.""" +from __future__ import annotations + import logging import secrets -from typing import Optional from toonapi import Status, Toon, ToonError @@ -50,7 +51,7 @@ class ToonDataUpdateCoordinator(DataUpdateCoordinator[Status]): for update_callback in self._listeners: update_callback() - async def register_webhook(self, event: Optional[Event] = None) -> None: + async def register_webhook(self, event: Event | None = None) -> None: """Register a webhook with Toon to get live updates.""" if CONF_WEBHOOK_ID not in self.entry.data: data = {**self.entry.data, CONF_WEBHOOK_ID: secrets.token_hex()} @@ -124,7 +125,7 @@ class ToonDataUpdateCoordinator(DataUpdateCoordinator[Status]): except ToonError as err: _LOGGER.error("Could not process data received from Toon webhook - %s", err) - async def unregister_webhook(self, event: Optional[Event] = None) -> None: + async def unregister_webhook(self, event: Event | None = None) -> None: """Remove / Unregister webhook for toon.""" _LOGGER.debug( "Unregistering Toon webhook (%s)", self.entry.data[CONF_WEBHOOK_ID] diff --git a/homeassistant/components/toon/models.py b/homeassistant/components/toon/models.py index edcbed369a3..8aee2fe27e1 100644 --- a/homeassistant/components/toon/models.py +++ b/homeassistant/components/toon/models.py @@ -1,5 +1,7 @@ """DataUpdate Coordinator, and base Entity and Device models for Toon.""" -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import Any from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -31,7 +33,7 @@ class ToonEntity(CoordinatorEntity): return self._name @property - def icon(self) -> Optional[str]: + def icon(self) -> str | None: """Return the mdi icon of the entity.""" return self._icon @@ -45,7 +47,7 @@ class ToonDisplayDeviceEntity(ToonEntity): """Defines a Toon display device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this thermostat.""" agreement = self.coordinator.data.agreement model = agreement.display_hardware_version.rpartition("/")[0] @@ -63,7 +65,7 @@ class ToonElectricityMeterDeviceEntity(ToonEntity): """Defines a Electricity Meter device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this entity.""" agreement_id = self.coordinator.data.agreement.agreement_id return { @@ -77,7 +79,7 @@ class ToonGasMeterDeviceEntity(ToonEntity): """Defines a Gas Meter device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this entity.""" agreement_id = self.coordinator.data.agreement.agreement_id return { @@ -91,7 +93,7 @@ class ToonWaterMeterDeviceEntity(ToonEntity): """Defines a Water Meter device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this entity.""" agreement_id = self.coordinator.data.agreement.agreement_id return { @@ -105,7 +107,7 @@ class ToonSolarDeviceEntity(ToonEntity): """Defines a Solar Device device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this entity.""" agreement_id = self.coordinator.data.agreement.agreement_id return { @@ -119,7 +121,7 @@ class ToonBoilerModuleDeviceEntity(ToonEntity): """Defines a Boiler Module device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this entity.""" agreement_id = self.coordinator.data.agreement.agreement_id return { @@ -134,7 +136,7 @@ class ToonBoilerDeviceEntity(ToonEntity): """Defines a Boiler device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this entity.""" agreement_id = self.coordinator.data.agreement.agreement_id return { diff --git a/homeassistant/components/toon/oauth2.py b/homeassistant/components/toon/oauth2.py index e3a83583ac6..7539224ebba 100644 --- a/homeassistant/components/toon/oauth2.py +++ b/homeassistant/components/toon/oauth2.py @@ -1,5 +1,7 @@ """OAuth2 implementations for Toon.""" -from typing import Any, Optional, cast +from __future__ import annotations + +from typing import Any, cast from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_oauth2_flow @@ -55,7 +57,7 @@ class ToonLocalOAuth2Implementation(config_entry_oauth2_flow.LocalOAuth2Implemen client_secret: str, name: str, tenant_id: str, - issuer: Optional[str] = None, + issuer: str | None = None, ): """Local Toon Oauth Implementation.""" self._name = name diff --git a/homeassistant/components/toon/sensor.py b/homeassistant/components/toon/sensor.py index 52d3a68f2c1..583683e53ae 100644 --- a/homeassistant/components/toon/sensor.py +++ b/homeassistant/components/toon/sensor.py @@ -1,5 +1,5 @@ """Support for Toon sensors.""" -from typing import Optional +from __future__ import annotations from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -132,7 +132,7 @@ class ToonSensor(ToonEntity): return f"{DOMAIN}_{agreement_id}_sensor_{self.key}" @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the sensor.""" section = getattr( self.coordinator.data, SENSOR_ENTITIES[self.key][ATTR_SECTION] @@ -140,12 +140,12 @@ class ToonSensor(ToonEntity): return getattr(section, SENSOR_ENTITIES[self.key][ATTR_MEASUREMENT]) @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit this state is expressed in.""" return SENSOR_ENTITIES[self.key][ATTR_UNIT_OF_MEASUREMENT] @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class.""" return SENSOR_ENTITIES[self.key][ATTR_DEVICE_CLASS] diff --git a/homeassistant/components/tplink/common.py b/homeassistant/components/tplink/common.py index 1aca4bf7edc..b9318cf3fdd 100644 --- a/homeassistant/components/tplink/common.py +++ b/homeassistant/components/tplink/common.py @@ -1,6 +1,7 @@ """Common code for tplink.""" +from __future__ import annotations + import logging -from typing import List from pyHS100 import ( Discover, @@ -30,7 +31,7 @@ class SmartDevices: """Hold different kinds of devices.""" def __init__( - self, lights: List[SmartDevice] = None, switches: List[SmartDevice] = None + self, lights: list[SmartDevice] = None, switches: list[SmartDevice] = None ): """Initialize device holder.""" self._lights = lights or [] diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index 31b2319ead0..3cb7e663058 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -1,9 +1,11 @@ """Support for TPLink lights.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging import time -from typing import Any, Dict, NamedTuple, Tuple, cast +from typing import Any, NamedTuple, cast from pyHS100 import SmartBulb, SmartDeviceException @@ -88,7 +90,7 @@ class LightState(NamedTuple): state: bool brightness: int color_temp: float - hs: Tuple[int, int] + hs: tuple[int, int] def to_param(self): """Return a version that we can send to the bulb.""" @@ -109,7 +111,7 @@ class LightState(NamedTuple): class LightFeatures(NamedTuple): """Light features.""" - sysinfo: Dict[str, Any] + sysinfo: dict[str, Any] mac: str alias: str model: str diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index d2b52771124..6a59b3a3d61 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -1,5 +1,5 @@ """Support for monitoring the Transmission BitTorrent client API.""" -from typing import List +from __future__ import annotations from transmissionrpc.torrent import Torrent @@ -168,7 +168,7 @@ class TransmissionTorrentsSensor(TransmissionSensor): self._state = len(torrents) -def _filter_torrents(torrents: List[Torrent], statuses=None) -> List[Torrent]: +def _filter_torrents(torrents: list[Torrent], statuses=None) -> list[Torrent]: return [ torrent for torrent in torrents diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index f9b07a98595..3ec9c0645aa 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -1,4 +1,6 @@ """Provide functionality for TTS.""" +from __future__ import annotations + import asyncio import functools as ft import hashlib @@ -7,7 +9,7 @@ import logging import mimetypes import os import re -from typing import Dict, Optional, cast +from typing import cast from aiohttp import web import mutagen @@ -243,7 +245,7 @@ async def async_setup(hass, config): return True -def _hash_options(options: Dict) -> str: +def _hash_options(options: dict) -> str: """Hashes an options dictionary.""" opts_hash = hashlib.blake2s(digest_size=5) for key, value in sorted(options.items()): @@ -512,8 +514,8 @@ class SpeechManager: class Provider: """Represent a single TTS provider.""" - hass: Optional[HomeAssistantType] = None - name: Optional[str] = None + hass: HomeAssistantType | None = None + name: str | None = None @property def default_language(self): diff --git a/homeassistant/components/tuya/fan.py b/homeassistant/components/tuya/fan.py index cb6f96358c9..ab361c6ac31 100644 --- a/homeassistant/components/tuya/fan.py +++ b/homeassistant/components/tuya/fan.py @@ -1,6 +1,7 @@ """Support for Tuya fans.""" +from __future__ import annotations + from datetime import timedelta -from typing import Optional from homeassistant.components.fan import ( DOMAIN as SENSOR_DOMAIN, @@ -124,7 +125,7 @@ class TuyaFanDevice(TuyaDevice, FanEntity): return self._tuya.state() @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed.""" if not self.is_on: return 0 diff --git a/homeassistant/components/twinkly/light.py b/homeassistant/components/twinkly/light.py index 8de51d19d51..ece3e0b048c 100644 --- a/homeassistant/components/twinkly/light.py +++ b/homeassistant/components/twinkly/light.py @@ -1,8 +1,9 @@ """The Twinkly light component.""" +from __future__ import annotations import asyncio import logging -from typing import Any, Dict, Optional +from typing import Any from aiohttp import ClientError @@ -84,7 +85,7 @@ class TwinklyLight(LightEntity): return self._is_available @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Id of the device.""" return self._id @@ -104,7 +105,7 @@ class TwinklyLight(LightEntity): return "mdi:string-lights" @property - def device_info(self) -> Optional[Dict[str, Any]]: + def device_info(self) -> dict[str, Any] | None: """Get device specific attributes.""" return ( { @@ -123,7 +124,7 @@ class TwinklyLight(LightEntity): return self._is_on @property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the brightness of the light.""" return self._brightness diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 9096620f0ed..dc56cd9d9e3 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -1,8 +1,9 @@ """UniFi Controller abstraction.""" +from __future__ import annotations + import asyncio from datetime import datetime, timedelta import ssl -from typing import Optional from aiohttp import CookieJar import aiounifi @@ -385,7 +386,7 @@ class UniFiController: @callback def async_heartbeat( - self, unique_id: str, heartbeat_expire_time: Optional[datetime] = None + self, unique_id: str, heartbeat_expire_time: datetime | None = None ) -> None: """Signal when a device has fresh home state.""" if heartbeat_expire_time is not None: diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index c814abf5a82..f6e770c126f 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -1,6 +1,7 @@ """Combination of multiple media players for a universal controller.""" +from __future__ import annotations + from copy import copy -from typing import Optional import voluptuous as vol @@ -270,7 +271,7 @@ class UniversalMediaPlayer(MediaPlayerEntity): return False @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device.""" return self._device_class diff --git a/homeassistant/components/upc_connect/device_tracker.py b/homeassistant/components/upc_connect/device_tracker.py index d68311a8793..2a2a1b3798b 100644 --- a/homeassistant/components/upc_connect/device_tracker.py +++ b/homeassistant/components/upc_connect/device_tracker.py @@ -1,6 +1,7 @@ """Support for UPC ConnectBox router.""" +from __future__ import annotations + import logging -from typing import List, Optional from connect_box import ConnectBox from connect_box.exceptions import ConnectBoxError, ConnectBoxLoginError @@ -57,7 +58,7 @@ class UPCDeviceScanner(DeviceScanner): """Initialize the scanner.""" self.connect_box: ConnectBox = connect_box - async def async_scan_devices(self) -> List[str]: + async def async_scan_devices(self) -> list[str]: """Scan for new devices and return a list with found device IDs.""" try: await self.connect_box.async_get_devices() @@ -66,7 +67,7 @@ class UPCDeviceScanner(DeviceScanner): return [device.mac for device in self.connect_box.devices] - async def async_get_device_name(self, device: str) -> Optional[str]: + async def async_get_device_name(self, device: str) -> str | None: """Get the device name (the name of the wireless device not used).""" for connected_device in self.connect_box.devices: if ( diff --git a/homeassistant/components/upcloud/__init__.py b/homeassistant/components/upcloud/__init__.py index eba61058ca0..0f463aec666 100644 --- a/homeassistant/components/upcloud/__init__.py +++ b/homeassistant/components/upcloud/__init__.py @@ -1,9 +1,10 @@ """Support for UpCloud.""" +from __future__ import annotations import dataclasses from datetime import timedelta import logging -from typing import Dict, List +from typing import Dict import requests.exceptions import upcloud_api @@ -91,7 +92,7 @@ class UpCloudDataUpdateCoordinator( hass, _LOGGER, name=f"{username}@UpCloud", update_interval=update_interval ) self.cloud_manager = cloud_manager - self.unsub_handlers: List[CALLBACK_TYPE] = [] + self.unsub_handlers: list[CALLBACK_TYPE] = [] async def async_update_config(self, config_entry: ConfigEntry) -> None: """Handle config update.""" @@ -99,7 +100,7 @@ class UpCloudDataUpdateCoordinator( seconds=config_entry.options[CONF_SCAN_INTERVAL] ) - async def _async_update_data(self) -> Dict[str, upcloud_api.Server]: + async def _async_update_data(self) -> dict[str, upcloud_api.Server]: return { x.uuid: x for x in await self.hass.async_add_executor_job( @@ -112,10 +113,10 @@ class UpCloudDataUpdateCoordinator( class UpCloudHassData: """Home Assistant UpCloud runtime data.""" - coordinators: Dict[str, UpCloudDataUpdateCoordinator] = dataclasses.field( + coordinators: dict[str, UpCloudDataUpdateCoordinator] = dataclasses.field( default_factory=dict ) - scan_interval_migrations: Dict[str, int] = dataclasses.field(default_factory=dict) + scan_interval_migrations: dict[str, int] = dataclasses.field(default_factory=dict) async def async_setup(hass: HomeAssistantType, config) -> bool: diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index e1101c3713c..1cbaf931857 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -1,6 +1,8 @@ """Config flow for UPNP.""" +from __future__ import annotations + from datetime import timedelta -from typing import Any, Mapping, Optional +from typing import Any, Mapping import voluptuous as vol @@ -55,7 +57,7 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self._discoveries: Mapping = None async def async_step_user( - self, user_input: Optional[Mapping] = None + self, user_input: Mapping | None = None ) -> Mapping[str, Any]: """Handle a flow start.""" _LOGGER.debug("async_step_user: user_input: %s", user_input) @@ -111,9 +113,7 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): data_schema=data_schema, ) - async def async_step_import( - self, import_info: Optional[Mapping] - ) -> Mapping[str, Any]: + async def async_step_import(self, import_info: Mapping | None) -> Mapping[str, Any]: """Import a new UPnP/IGD device as a config entry. This flow is triggered by `async_setup`. If no device has been @@ -204,7 +204,7 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_ssdp_confirm() async def async_step_ssdp_confirm( - self, user_input: Optional[Mapping] = None + self, user_input: Mapping | None = None ) -> Mapping[str, Any]: """Confirm integration via SSDP.""" _LOGGER.debug("async_step_ssdp_confirm: user_input: %s", user_input) diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py index a06ca254c87..034496ec028 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -3,7 +3,7 @@ from __future__ import annotations import asyncio from ipaddress import IPv4Address -from typing import List, Mapping +from typing import Mapping from urllib.parse import urlparse from async_upnp_client import UpnpFactory @@ -42,7 +42,7 @@ class Device: self._igd_device: IgdDevice = igd_device @classmethod - async def async_discover(cls, hass: HomeAssistantType) -> List[Mapping]: + async def async_discover(cls, hass: HomeAssistantType) -> list[Mapping]: """Discover UPnP/IGD devices.""" _LOGGER.debug("Discovering UPnP/IGD devices") local_ip = None diff --git a/homeassistant/components/upnp/sensor.py b/homeassistant/components/upnp/sensor.py index 97d3c1a702c..76bb1d023b7 100644 --- a/homeassistant/components/upnp/sensor.py +++ b/homeassistant/components/upnp/sensor.py @@ -1,6 +1,8 @@ """Support for UPnP/IGD Sensors.""" +from __future__ import annotations + from datetime import timedelta -from typing import Any, Mapping, Optional +from typing import Any, Mapping from homeassistant.config_entries import ConfigEntry from homeassistant.const import DATA_BYTES, DATA_RATE_KIBIBYTES_PER_SECOND @@ -176,7 +178,7 @@ class RawUpnpSensor(UpnpSensor): """Representation of a UPnP/IGD sensor.""" @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the device.""" device_value_key = self._sensor_type["device_value_key"] value = self.coordinator.data[device_value_key] @@ -214,7 +216,7 @@ class DerivedUpnpSensor(UpnpSensor): return current_value < self._last_value @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the device.""" # Can't calculate any derivative if we have only one value. device_value_key = self._sensor_type["device_value_key"] diff --git a/homeassistant/components/usgs_earthquakes_feed/geo_location.py b/homeassistant/components/usgs_earthquakes_feed/geo_location.py index 364e0599b75..eefa2ed1d0d 100644 --- a/homeassistant/components/usgs_earthquakes_feed/geo_location.py +++ b/homeassistant/components/usgs_earthquakes_feed/geo_location.py @@ -1,7 +1,8 @@ """Support for U.S. Geological Survey Earthquake Hazards Program Feeds.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Optional from geojson_client.usgs_earthquake_hazards_program_feed import ( UsgsEarthquakeHazardsProgramFeedManager, @@ -255,22 +256,22 @@ class UsgsEarthquakesEvent(GeolocationEvent): return SOURCE @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return self._name @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """Return distance value of this external event.""" return self._distance @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of this external event.""" return self._latitude @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of this external event.""" return self._longitude diff --git a/homeassistant/components/uvc/camera.py b/homeassistant/components/uvc/camera.py index 82f59a40131..94181de37c4 100644 --- a/homeassistant/components/uvc/camera.py +++ b/homeassistant/components/uvc/camera.py @@ -1,8 +1,9 @@ """Support for Ubiquiti's UVC cameras.""" +from __future__ import annotations + from datetime import datetime import logging import re -from typing import Optional import requests from uvcclient import camera as uvc_camera, nvr @@ -255,7 +256,7 @@ class UnifiVideoCamera(Camera): self._caminfo = self._nvr.get_camera(self._uuid) -def timestamp_ms_to_date(epoch_ms: int) -> Optional[datetime]: +def timestamp_ms_to_date(epoch_ms: int) -> datetime | None: """Convert millisecond timestamp to datetime.""" if epoch_ms: return datetime.fromtimestamp(epoch_ms / 1000) diff --git a/homeassistant/components/vacuum/device_action.py b/homeassistant/components/vacuum/device_action.py index ed25289da10..2308882469e 100644 --- a/homeassistant/components/vacuum/device_action.py +++ b/homeassistant/components/vacuum/device_action.py @@ -1,5 +1,5 @@ """Provides device automations for Vacuum.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -26,7 +26,7 @@ ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Vacuum devices.""" registry = await entity_registry.async_get_registry(hass) actions = [] @@ -57,7 +57,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/vacuum/device_condition.py b/homeassistant/components/vacuum/device_condition.py index cb17505f6e1..4803ebdb988 100644 --- a/homeassistant/components/vacuum/device_condition.py +++ b/homeassistant/components/vacuum/device_condition.py @@ -1,5 +1,5 @@ """Provide the device automations for Vacuum.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -30,7 +30,7 @@ CONDITION_SCHEMA = DEVICE_CONDITION_BASE_SCHEMA.extend( async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions for Vacuum devices.""" registry = await entity_registry.async_get_registry(hass) conditions = [] diff --git a/homeassistant/components/vacuum/device_trigger.py b/homeassistant/components/vacuum/device_trigger.py index 21a2ae5e8c2..8023a3865a7 100644 --- a/homeassistant/components/vacuum/device_trigger.py +++ b/homeassistant/components/vacuum/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Vacuum.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -29,7 +29,7 @@ TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Vacuum devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/vacuum/reproduce_state.py b/homeassistant/components/vacuum/reproduce_state.py index 48aa9615f1e..38958bd4790 100644 --- a/homeassistant/components/vacuum/reproduce_state.py +++ b/homeassistant/components/vacuum/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Vacuum state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -44,8 +46,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -99,8 +101,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Vacuum states.""" # Reproduce states in parallel. diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index ff8dc8b96d1..929e4424d80 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -1,8 +1,10 @@ """Support for Vera devices.""" +from __future__ import annotations + import asyncio from collections import defaultdict import logging -from typing import Any, Dict, Generic, List, Optional, Type, TypeVar +from typing import Any, Generic, TypeVar import pyvera as veraApi from requests.exceptions import RequestException @@ -172,7 +174,7 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> return True -def map_vera_device(vera_device: veraApi.VeraDevice, remap: List[int]) -> str: +def map_vera_device(vera_device: veraApi.VeraDevice, remap: list[int]) -> str: """Map vera classes to Home Assistant types.""" type_map = { @@ -187,7 +189,7 @@ def map_vera_device(vera_device: veraApi.VeraDevice, remap: List[int]) -> str: veraApi.VeraSwitch: "switch", } - def map_special_case(instance_class: Type, entity_type: str) -> str: + def map_special_case(instance_class: type, entity_type: str) -> str: if instance_class is veraApi.VeraSwitch and vera_device.device_id in remap: return "light" return entity_type @@ -248,7 +250,7 @@ class VeraDevice(Generic[DeviceType], Entity): return self.vera_device.should_poll @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the device.""" attr = {} diff --git a/homeassistant/components/vera/binary_sensor.py b/homeassistant/components/vera/binary_sensor.py index 00d4fb3a758..816234bb602 100644 --- a/homeassistant/components/vera/binary_sensor.py +++ b/homeassistant/components/vera/binary_sensor.py @@ -1,5 +1,7 @@ """Support for Vera binary sensors.""" -from typing import Callable, List, Optional +from __future__ import annotations + +from typing import Callable import pyvera as veraApi @@ -19,7 +21,7 @@ from .common import ControllerData, get_controller_data async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" controller_data = get_controller_data(hass, entry) @@ -44,7 +46,7 @@ class VeraBinarySensor(VeraDevice[veraApi.VeraBinarySensor], BinarySensorEntity) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) @property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return true if sensor is on.""" return self._state diff --git a/homeassistant/components/vera/climate.py b/homeassistant/components/vera/climate.py index 9abfc485268..5027becb71f 100644 --- a/homeassistant/components/vera/climate.py +++ b/homeassistant/components/vera/climate.py @@ -1,5 +1,7 @@ """Support for Vera thermostats.""" -from typing import Any, Callable, List, Optional +from __future__ import annotations + +from typing import Any, Callable import pyvera as veraApi @@ -36,7 +38,7 @@ SUPPORT_HVAC = [HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_O async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" controller_data = get_controller_data(hass, entry) @@ -60,7 +62,7 @@ class VeraThermostat(VeraDevice[veraApi.VeraThermostat], ClimateEntity): self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) @property - def supported_features(self) -> Optional[int]: + def supported_features(self) -> int | None: """Return the list of supported features.""" return SUPPORT_FLAGS @@ -80,7 +82,7 @@ class VeraThermostat(VeraDevice[veraApi.VeraThermostat], ClimateEntity): return HVAC_MODE_OFF @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes. Need to be a subset of HVAC_MODES. @@ -88,7 +90,7 @@ class VeraThermostat(VeraDevice[veraApi.VeraThermostat], ClimateEntity): return SUPPORT_HVAC @property - def fan_mode(self) -> Optional[str]: + def fan_mode(self) -> str | None: """Return the fan setting.""" mode = self.vera_device.get_fan_mode() if mode == "ContinuousOn": @@ -96,7 +98,7 @@ class VeraThermostat(VeraDevice[veraApi.VeraThermostat], ClimateEntity): return FAN_AUTO @property - def fan_modes(self) -> Optional[List[str]]: + def fan_modes(self) -> list[str] | None: """Return a list of available fan modes.""" return FAN_OPERATION_LIST @@ -110,7 +112,7 @@ class VeraThermostat(VeraDevice[veraApi.VeraThermostat], ClimateEntity): self.schedule_update_ha_state() @property - def current_power_w(self) -> Optional[float]: + def current_power_w(self) -> float | None: """Return the current power usage in W.""" power = self.vera_device.power if power: @@ -127,7 +129,7 @@ class VeraThermostat(VeraDevice[veraApi.VeraThermostat], ClimateEntity): return TEMP_CELSIUS @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self.vera_device.get_current_temperature() @@ -137,7 +139,7 @@ class VeraThermostat(VeraDevice[veraApi.VeraThermostat], ClimateEntity): return self.vera_device.get_hvac_mode() @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return self.vera_device.get_current_goal_temperature() diff --git a/homeassistant/components/vera/common.py b/homeassistant/components/vera/common.py index fce6475f930..fcc501c2094 100644 --- a/homeassistant/components/vera/common.py +++ b/homeassistant/components/vera/common.py @@ -1,5 +1,7 @@ """Common vera code.""" -from typing import DefaultDict, List, NamedTuple, Set +from __future__ import annotations + +from typing import DefaultDict, NamedTuple import pyvera as pv @@ -15,12 +17,12 @@ class ControllerData(NamedTuple): """Controller data.""" controller: pv.VeraController - devices: DefaultDict[str, List[pv.VeraDevice]] - scenes: List[pv.VeraScene] + devices: DefaultDict[str, list[pv.VeraDevice]] + scenes: list[pv.VeraScene] config_entry: ConfigEntry -def get_configured_platforms(controller_data: ControllerData) -> Set[str]: +def get_configured_platforms(controller_data: ControllerData) -> set[str]: """Get configured platforms for a controller.""" platforms = [] for platform in controller_data.devices: diff --git a/homeassistant/components/vera/config_flow.py b/homeassistant/components/vera/config_flow.py index e62b21c82ea..a5450cd4a65 100644 --- a/homeassistant/components/vera/config_flow.py +++ b/homeassistant/components/vera/config_flow.py @@ -1,7 +1,9 @@ """Config flow for Vera.""" +from __future__ import annotations + import logging import re -from typing import Any, List +from typing import Any import pyvera as pv from requests.exceptions import RequestException @@ -19,22 +21,22 @@ LIST_REGEX = re.compile("[^0-9]+") _LOGGER = logging.getLogger(__name__) -def fix_device_id_list(data: List[Any]) -> List[int]: +def fix_device_id_list(data: list[Any]) -> list[int]: """Fix the id list by converting it to a supported int list.""" return str_to_int_list(list_to_str(data)) -def str_to_int_list(data: str) -> List[int]: +def str_to_int_list(data: str) -> list[int]: """Convert a string to an int list.""" return [int(s) for s in LIST_REGEX.split(data) if len(s) > 0] -def list_to_str(data: List[Any]) -> str: +def list_to_str(data: list[Any]) -> str: """Convert an int list to a string.""" return " ".join([str(i) for i in data]) -def new_options(lights: List[int], exclude: List[int]) -> dict: +def new_options(lights: list[int], exclude: list[int]) -> dict: """Create a standard options object.""" return {CONF_LIGHTS: lights, CONF_EXCLUDE: exclude} diff --git a/homeassistant/components/vera/cover.py b/homeassistant/components/vera/cover.py index 43f68fba786..cf3dd4a3d13 100644 --- a/homeassistant/components/vera/cover.py +++ b/homeassistant/components/vera/cover.py @@ -1,5 +1,7 @@ """Support for Vera cover - curtains, rollershutters etc.""" -from typing import Any, Callable, List +from __future__ import annotations + +from typing import Any, Callable import pyvera as veraApi @@ -20,7 +22,7 @@ from .common import ControllerData, get_controller_data async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" controller_data = get_controller_data(hass, entry) diff --git a/homeassistant/components/vera/light.py b/homeassistant/components/vera/light.py index 30c4e93a2ba..7fcb726efcc 100644 --- a/homeassistant/components/vera/light.py +++ b/homeassistant/components/vera/light.py @@ -1,5 +1,7 @@ """Support for Vera lights.""" -from typing import Any, Callable, List, Optional, Tuple +from __future__ import annotations + +from typing import Any, Callable import pyvera as veraApi @@ -24,7 +26,7 @@ from .common import ControllerData, get_controller_data async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" controller_data = get_controller_data(hass, entry) @@ -51,12 +53,12 @@ class VeraLight(VeraDevice[veraApi.VeraDimmer], LightEntity): self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) @property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the brightness of the light.""" return self._brightness @property - def hs_color(self) -> Optional[Tuple[float, float]]: + def hs_color(self) -> tuple[float, float] | None: """Return the color of the light.""" return self._color diff --git a/homeassistant/components/vera/lock.py b/homeassistant/components/vera/lock.py index f98319b6ccf..eada4b20550 100644 --- a/homeassistant/components/vera/lock.py +++ b/homeassistant/components/vera/lock.py @@ -1,5 +1,7 @@ """Support for Vera locks.""" -from typing import Any, Callable, Dict, List, Optional +from __future__ import annotations + +from typing import Any, Callable import pyvera as veraApi @@ -23,7 +25,7 @@ ATTR_LOW_BATTERY = "low_battery" async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" controller_data = get_controller_data(hass, entry) @@ -56,12 +58,12 @@ class VeraLock(VeraDevice[veraApi.VeraLock], LockEntity): self._state = STATE_UNLOCKED @property - def is_locked(self) -> Optional[bool]: + def is_locked(self) -> bool | None: """Return true if device is on.""" return self._state == STATE_LOCKED @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Who unlocked the lock and did a low battery alert fire. Reports on the previous poll cycle. @@ -78,7 +80,7 @@ class VeraLock(VeraDevice[veraApi.VeraLock], LockEntity): return data @property - def changed_by(self) -> Optional[str]: + def changed_by(self) -> str | None: """Who unlocked the lock. Reports on the previous poll cycle. diff --git a/homeassistant/components/vera/scene.py b/homeassistant/components/vera/scene.py index 4ecbe8c724e..c6eb983a8f7 100644 --- a/homeassistant/components/vera/scene.py +++ b/homeassistant/components/vera/scene.py @@ -1,5 +1,7 @@ """Support for Vera scenes.""" -from typing import Any, Callable, Dict, List, Optional +from __future__ import annotations + +from typing import Any, Callable import pyvera as veraApi @@ -16,7 +18,7 @@ from .const import VERA_ID_FORMAT async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" controller_data = get_controller_data(hass, entry) @@ -53,6 +55,6 @@ class VeraScene(Scene): return self._name @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the scene.""" return {"vera_scene_id": self.vera_scene.vera_scene_id} diff --git a/homeassistant/components/vera/sensor.py b/homeassistant/components/vera/sensor.py index 007290807e6..d52d49c2546 100644 --- a/homeassistant/components/vera/sensor.py +++ b/homeassistant/components/vera/sensor.py @@ -1,6 +1,8 @@ """Support for Vera sensors.""" +from __future__ import annotations + from datetime import timedelta -from typing import Callable, List, Optional, cast +from typing import Callable, cast import pyvera as veraApi @@ -20,7 +22,7 @@ SCAN_INTERVAL = timedelta(seconds=5) async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" controller_data = get_controller_data(hass, entry) @@ -52,7 +54,7 @@ class VeraSensor(VeraDevice[veraApi.VeraSensor], Entity): return self.current_value @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit of measurement of this entity, if any.""" if self.vera_device.category == veraApi.CATEGORY_TEMPERATURE_SENSOR: diff --git a/homeassistant/components/vera/switch.py b/homeassistant/components/vera/switch.py index f567893e5b0..c779a3c8cfc 100644 --- a/homeassistant/components/vera/switch.py +++ b/homeassistant/components/vera/switch.py @@ -1,5 +1,7 @@ """Support for Vera switches.""" -from typing import Any, Callable, List, Optional +from __future__ import annotations + +from typing import Any, Callable import pyvera as veraApi @@ -20,7 +22,7 @@ from .common import ControllerData, get_controller_data async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" controller_data = get_controller_data(hass, entry) @@ -57,7 +59,7 @@ class VeraSwitch(VeraDevice[veraApi.VeraSwitch], SwitchEntity): self.schedule_update_ha_state() @property - def current_power_w(self) -> Optional[float]: + def current_power_w(self) -> float | None: """Return the current power usage in W.""" power = self.vera_device.power if power: diff --git a/homeassistant/components/vizio/__init__.py b/homeassistant/components/vizio/__init__.py index a7a9c404f74..3719ada27ae 100644 --- a/homeassistant/components/vizio/__init__.py +++ b/homeassistant/components/vizio/__init__.py @@ -1,8 +1,10 @@ """The vizio component.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Any, Dict, List +from typing import Any from pyvizio.const import APPS from pyvizio.util import gen_apps_list_from_url @@ -116,7 +118,7 @@ class VizioAppsDataUpdateCoordinator(DataUpdateCoordinator): ) self.data = APPS - async def _async_update_data(self) -> List[Dict[str, Any]]: + async def _async_update_data(self) -> list[dict[str, Any]]: """Update data via library.""" data = await gen_apps_list_from_url(session=async_get_clientsession(self.hass)) if not data: diff --git a/homeassistant/components/vizio/config_flow.py b/homeassistant/components/vizio/config_flow.py index 3f57cdb81fa..c6632868ae3 100644 --- a/homeassistant/components/vizio/config_flow.py +++ b/homeassistant/components/vizio/config_flow.py @@ -1,8 +1,10 @@ """Config flow for Vizio.""" +from __future__ import annotations + import copy import logging import socket -from typing import Any, Dict, Optional +from typing import Any from pyvizio import VizioAsync, async_guess_device_type from pyvizio.const import APP_HOME @@ -48,7 +50,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -def _get_config_schema(input_dict: Dict[str, Any] = None) -> vol.Schema: +def _get_config_schema(input_dict: dict[str, Any] = None) -> vol.Schema: """ Return schema defaults for init step based on user input/config dict. @@ -76,7 +78,7 @@ def _get_config_schema(input_dict: Dict[str, Any] = None) -> vol.Schema: ) -def _get_pairing_schema(input_dict: Dict[str, Any] = None) -> vol.Schema: +def _get_pairing_schema(input_dict: dict[str, Any] = None) -> vol.Schema: """ Return schema defaults for pairing data based on user input. @@ -108,8 +110,8 @@ class VizioOptionsConfigFlow(config_entries.OptionsFlow): self.config_entry = config_entry async def async_step_init( - self, user_input: Dict[str, Any] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] = None + ) -> dict[str, Any]: """Manage the vizio options.""" if user_input is not None: if user_input.get(CONF_APPS_TO_INCLUDE_OR_EXCLUDE): @@ -191,7 +193,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._data = None self._apps = {} - async def _create_entry(self, input_dict: Dict[str, Any]) -> Dict[str, Any]: + async def _create_entry(self, input_dict: dict[str, Any]) -> dict[str, Any]: """Create vizio config entry.""" # Remove extra keys that will not be used by entry setup input_dict.pop(CONF_APPS_TO_INCLUDE_OR_EXCLUDE, None) @@ -203,8 +205,8 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=input_dict[CONF_NAME], data=input_dict) async def async_step_user( - self, user_input: Dict[str, Any] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] = None + ) -> dict[str, Any]: """Handle a flow initialized by the user.""" errors = {} @@ -276,7 +278,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="user", data_schema=schema, errors=errors) - async def async_step_import(self, import_config: Dict[str, Any]) -> Dict[str, Any]: + async def async_step_import(self, import_config: dict[str, Any]) -> dict[str, Any]: """Import a config entry from configuration.yaml.""" # Check if new config entry matches any existing config entries for entry in self.hass.config_entries.async_entries(DOMAIN): @@ -339,8 +341,8 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_user(user_input=import_config) async def async_step_zeroconf( - self, discovery_info: Optional[DiscoveryInfoType] = None - ) -> Dict[str, Any]: + self, discovery_info: DiscoveryInfoType | None = None + ) -> dict[str, Any]: """Handle zeroconf discovery.""" # If host already has port, no need to add it again if ":" not in discovery_info[CONF_HOST]: @@ -376,8 +378,8 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_user(user_input=discovery_info) async def async_step_pair_tv( - self, user_input: Dict[str, Any] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] = None + ) -> dict[str, Any]: """ Start pairing process for TV. @@ -442,7 +444,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def _pairing_complete(self, step_id: str) -> Dict[str, Any]: + async def _pairing_complete(self, step_id: str) -> dict[str, Any]: """Handle config flow completion.""" if not self._must_show_form: return await self._create_entry(self._data) @@ -455,8 +457,8 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) async def async_step_pairing_complete( - self, user_input: Dict[str, Any] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] = None + ) -> dict[str, Any]: """ Complete non-import sourced config flow. @@ -465,8 +467,8 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self._pairing_complete("pairing_complete") async def async_step_pairing_complete_import( - self, user_input: Dict[str, Any] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] = None + ) -> dict[str, Any]: """ Complete import sourced config flow. diff --git a/homeassistant/components/vizio/media_player.py b/homeassistant/components/vizio/media_player.py index 53c8a2bba88..fc955d48158 100644 --- a/homeassistant/components/vizio/media_player.py +++ b/homeassistant/components/vizio/media_player.py @@ -1,7 +1,9 @@ """Vizio SmartCast Device support.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Any, Callable from pyvizio import VizioAsync from pyvizio.api.apps import find_app_name @@ -64,7 +66,7 @@ PARALLEL_UPDATES = 0 async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up a Vizio media player entry.""" host = config_entry.data[CONF_HOST] @@ -166,7 +168,7 @@ class VizioDevice(MediaPlayerEntity): self._model = None self._sw_version = None - def _apps_list(self, apps: List[str]) -> List[str]: + def _apps_list(self, apps: list[str]) -> list[str]: """Return process apps list based on configured filters.""" if self._conf_apps.get(CONF_INCLUDE): return [app for app in apps if app in self._conf_apps[CONF_INCLUDE]] @@ -274,7 +276,7 @@ class VizioDevice(MediaPlayerEntity): if self._current_app == NO_APP_RUNNING: self._current_app = None - def _get_additional_app_names(self) -> List[Dict[str, Any]]: + def _get_additional_app_names(self) -> list[dict[str, Any]]: """Return list of additional apps that were included in configuration.yaml.""" return [ additional_app["name"] for additional_app in self._additional_app_configs @@ -296,7 +298,7 @@ class VizioDevice(MediaPlayerEntity): self._conf_apps.update(config_entry.options.get(CONF_APPS, {})) async def async_update_setting( - self, setting_type: str, setting_name: str, new_value: Union[int, str] + self, setting_type: str, setting_name: str, new_value: int | str ) -> None: """Update a setting when update_setting service is called.""" await self._device.set_setting( @@ -340,7 +342,7 @@ class VizioDevice(MediaPlayerEntity): return self._available @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the device.""" return self._state @@ -355,7 +357,7 @@ class VizioDevice(MediaPlayerEntity): return self._icon @property - def volume_level(self) -> Optional[float]: + def volume_level(self) -> float | None: """Return the volume level of the device.""" return self._volume_level @@ -365,7 +367,7 @@ class VizioDevice(MediaPlayerEntity): return self._is_volume_muted @property - def source(self) -> Optional[str]: + def source(self) -> str | None: """Return current input of the device.""" if self._current_app is not None and self._current_input in INPUT_APPS: return self._current_app @@ -373,7 +375,7 @@ class VizioDevice(MediaPlayerEntity): return self._current_input @property - def source_list(self) -> List[str]: + def source_list(self) -> list[str]: """Return list of available inputs of the device.""" # If Smartcast app is in input list, and the app list has been retrieved, # show the combination with , otherwise just return inputs @@ -395,7 +397,7 @@ class VizioDevice(MediaPlayerEntity): return self._available_inputs @property - def app_id(self) -> Optional[str]: + def app_id(self) -> str | None: """Return the ID of the current app if it is unknown by pyvizio.""" if self._current_app_config and self.app_name == UNKNOWN_APP: return { @@ -407,7 +409,7 @@ class VizioDevice(MediaPlayerEntity): return None @property - def app_name(self) -> Optional[str]: + def app_name(self) -> str | None: """Return the friendly name of the current app.""" return self._current_app @@ -422,7 +424,7 @@ class VizioDevice(MediaPlayerEntity): return self._config_entry.unique_id @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device registry information.""" return { "identifiers": {(DOMAIN, self._config_entry.unique_id)}, @@ -438,12 +440,12 @@ class VizioDevice(MediaPlayerEntity): return self._device_class @property - def sound_mode(self) -> Optional[str]: + def sound_mode(self) -> str | None: """Name of the current sound mode.""" return self._current_sound_mode @property - def sound_mode_list(self) -> Optional[List[str]]: + def sound_mode_list(self) -> list[str] | None: """List of available sound modes.""" return self._available_sound_modes diff --git a/homeassistant/components/volumio/config_flow.py b/homeassistant/components/volumio/config_flow.py index 950a161a5c3..a0ed860f061 100644 --- a/homeassistant/components/volumio/config_flow.py +++ b/homeassistant/components/volumio/config_flow.py @@ -1,6 +1,7 @@ """Config flow for Volumio integration.""" +from __future__ import annotations + import logging -from typing import Optional from pyvolumio import CannotConnectError, Volumio import voluptuous as vol @@ -39,10 +40,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize flow.""" - self._host: Optional[str] = None - self._port: Optional[int] = None - self._name: Optional[str] = None - self._uuid: Optional[str] = None + self._host: str | None = None + self._port: int | None = None + self._name: str | None = None + self._uuid: str | None = None @callback def _async_get_entry(self): From 7d196abc4a638c52f2f165018952788c8a79efd4 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Thu, 18 Mar 2021 14:55:39 +0100 Subject: [PATCH 462/831] Add tests for Netatmo oauth2 api (#46375) * Add Netatmo tests for api * Update tests/components/netatmo/test_api.py * Update .coveragerc Co-authored-by: Erik Montnemery --- .coveragerc | 1 - tests/components/netatmo/test_api.py | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 tests/components/netatmo/test_api.py diff --git a/.coveragerc b/.coveragerc index 8f609ec5b22..92d55e83527 100644 --- a/.coveragerc +++ b/.coveragerc @@ -644,7 +644,6 @@ omit = homeassistant/components/nello/lock.py homeassistant/components/nest/legacy/* homeassistant/components/netatmo/__init__.py - homeassistant/components/netatmo/api.py homeassistant/components/netatmo/camera.py homeassistant/components/netatmo/data_handler.py homeassistant/components/netatmo/helper.py diff --git a/tests/components/netatmo/test_api.py b/tests/components/netatmo/test_api.py new file mode 100644 index 00000000000..76d16d10515 --- /dev/null +++ b/tests/components/netatmo/test_api.py @@ -0,0 +1,14 @@ +"""The tests for the Netatmo oauth2 api.""" +from unittest.mock import patch + +from homeassistant.components.netatmo import api + + +async def test_api(hass, config_entry): + """Test auth instantiation.""" + with patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ) as fake_implementation: + auth = api.ConfigEntryNetatmoAuth(hass, config_entry, fake_implementation) + + assert isinstance(auth, api.ConfigEntryNetatmoAuth) From dcca29ef68e74cfcbca1c7bf7309e1041a8b7f90 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 15:08:35 +0100 Subject: [PATCH 463/831] Update typing 14 (#48078) --- .../components/water_heater/device_action.py | 6 +- .../water_heater/reproduce_state.py | 12 ++-- .../components/websocket_api/__init__.py | 10 +-- .../components/websocket_api/connection.py | 8 ++- .../components/websocket_api/http.py | 5 +- .../components/websocket_api/messages.py | 9 +-- homeassistant/components/wemo/entity.py | 12 ++-- homeassistant/components/wilight/fan.py | 5 +- homeassistant/components/withings/__init__.py | 5 +- .../components/withings/binary_sensor.py | 6 +- homeassistant/components/withings/common.py | 52 ++++++++-------- .../components/withings/config_flow.py | 5 +- homeassistant/components/withings/sensor.py | 8 ++- homeassistant/components/wled/__init__.py | 6 +- homeassistant/components/wled/config_flow.py | 22 ++++--- homeassistant/components/wled/light.py | 34 +++++----- homeassistant/components/wled/sensor.py | 16 ++--- homeassistant/components/wled/switch.py | 12 ++-- .../components/wunderground/sensor.py | 26 ++++---- homeassistant/components/xbox/__init__.py | 19 +++--- homeassistant/components/xbox/base_sensor.py | 4 +- .../components/xbox/binary_sensor.py | 7 ++- homeassistant/components/xbox/browse_media.py | 8 +-- homeassistant/components/xbox/media_player.py | 6 +- homeassistant/components/xbox/media_source.py | 11 ++-- homeassistant/components/xbox/sensor.py | 7 ++- homeassistant/components/yeelight/__init__.py | 7 ++- homeassistant/components/zerproc/light.py | 10 +-- homeassistant/components/zha/climate.py | 23 +++---- homeassistant/components/zha/config_flow.py | 6 +- .../components/zha/core/channels/__init__.py | 32 +++++----- .../components/zha/core/channels/base.py | 5 +- .../components/zha/core/channels/general.py | 28 +++++---- .../zha/core/channels/homeautomation.py | 8 ++- .../components/zha/core/channels/hvac.py | 28 +++++---- .../components/zha/core/channels/lighting.py | 12 ++-- .../zha/core/channels/smartenergy.py | 6 +- homeassistant/components/zha/core/const.py | 5 +- .../components/zha/core/decorators.py | 8 ++- homeassistant/components/zha/core/device.py | 6 +- .../components/zha/core/discovery.py | 13 ++-- homeassistant/components/zha/core/gateway.py | 8 +-- homeassistant/components/zha/core/group.py | 28 +++++---- homeassistant/components/zha/core/helpers.py | 13 ++-- .../components/zha/core/registries.py | 46 +++++++------- homeassistant/components/zha/core/store.py | 10 +-- homeassistant/components/zha/cover.py | 11 ++-- homeassistant/components/zha/device_action.py | 4 +- homeassistant/components/zha/entity.py | 21 ++++--- homeassistant/components/zha/fan.py | 23 +++---- homeassistant/components/zha/light.py | 32 +++++----- homeassistant/components/zha/sensor.py | 22 ++++--- homeassistant/components/zha/switch.py | 8 ++- homeassistant/components/zone/__init__.py | 28 ++++----- homeassistant/components/zwave/climate.py | 13 ++-- homeassistant/components/zwave_js/__init__.py | 6 +- homeassistant/components/zwave_js/addon.py | 8 +-- homeassistant/components/zwave_js/api.py | 5 +- .../components/zwave_js/binary_sensor.py | 25 ++++---- homeassistant/components/zwave_js/climate.py | 56 +++++++++-------- .../components/zwave_js/config_flow.py | 62 ++++++++++--------- homeassistant/components/zwave_js/cover.py | 20 +++--- .../components/zwave_js/discovery.py | 35 ++++++----- homeassistant/components/zwave_js/entity.py | 16 ++--- homeassistant/components/zwave_js/fan.py | 18 +++--- homeassistant/components/zwave_js/helpers.py | 8 ++- homeassistant/components/zwave_js/light.py | 24 +++---- homeassistant/components/zwave_js/lock.py | 16 ++--- homeassistant/components/zwave_js/migrate.py | 5 +- homeassistant/components/zwave_js/number.py | 10 +-- homeassistant/components/zwave_js/sensor.py | 17 ++--- homeassistant/components/zwave_js/services.py | 8 +-- homeassistant/components/zwave_js/switch.py | 11 ++-- 73 files changed, 614 insertions(+), 521 deletions(-) diff --git a/homeassistant/components/water_heater/device_action.py b/homeassistant/components/water_heater/device_action.py index 991929580d1..f138c777d44 100644 --- a/homeassistant/components/water_heater/device_action.py +++ b/homeassistant/components/water_heater/device_action.py @@ -1,5 +1,5 @@ """Provides device automations for Water Heater.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -28,7 +28,7 @@ ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Water Heater devices.""" registry = await entity_registry.async_get_registry(hass) actions = [] @@ -58,7 +58,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/water_heater/reproduce_state.py b/homeassistant/components/water_heater/reproduce_state.py index 77cdba93f96..4675cdb8621 100644 --- a/homeassistant/components/water_heater/reproduce_state.py +++ b/homeassistant/components/water_heater/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Water heater state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -47,8 +49,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -124,8 +126,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Water heater states.""" await asyncio.gather( diff --git a/homeassistant/components/websocket_api/__init__.py b/homeassistant/components/websocket_api/__init__.py index 43f5c807770..e7b10e18889 100644 --- a/homeassistant/components/websocket_api/__init__.py +++ b/homeassistant/components/websocket_api/__init__.py @@ -1,5 +1,7 @@ """WebSocket based API for Home Assistant.""" -from typing import Optional, Union, cast +from __future__ import annotations + +from typing import cast import voluptuous as vol @@ -43,9 +45,9 @@ DEPENDENCIES = ("http",) @callback def async_register_command( hass: HomeAssistant, - command_or_handler: Union[str, const.WebSocketCommandHandler], - handler: Optional[const.WebSocketCommandHandler] = None, - schema: Optional[vol.Schema] = None, + command_or_handler: str | const.WebSocketCommandHandler, + handler: const.WebSocketCommandHandler | None = None, + schema: vol.Schema | None = None, ) -> None: """Register a websocket command.""" # pylint: disable=protected-access diff --git a/homeassistant/components/websocket_api/connection.py b/homeassistant/components/websocket_api/connection.py index 108d4de5ada..dd1bb333693 100644 --- a/homeassistant/components/websocket_api/connection.py +++ b/homeassistant/components/websocket_api/connection.py @@ -1,6 +1,8 @@ """Connection session.""" +from __future__ import annotations + import asyncio -from typing import Any, Callable, Dict, Hashable, Optional +from typing import Any, Callable, Hashable import voluptuous as vol @@ -26,7 +28,7 @@ class ActiveConnection: else: self.refresh_token_id = None - self.subscriptions: Dict[Hashable, Callable[[], Any]] = {} + self.subscriptions: dict[Hashable, Callable[[], Any]] = {} self.last_id = 0 def context(self, msg): @@ -37,7 +39,7 @@ class ActiveConnection: return Context(user_id=user.id) @callback - def send_result(self, msg_id: int, result: Optional[Any] = None) -> None: + def send_result(self, msg_id: int, result: Any | None = None) -> None: """Send a result message.""" self.send_message(messages.result_message(msg_id, result)) diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py index daa8529e8bd..982bead296a 100644 --- a/homeassistant/components/websocket_api/http.py +++ b/homeassistant/components/websocket_api/http.py @@ -1,8 +1,9 @@ """View to accept incoming websocket connection.""" +from __future__ import annotations + import asyncio from contextlib import suppress import logging -from typing import Optional from aiohttp import WSMsgType, web import async_timeout @@ -57,7 +58,7 @@ class WebSocketHandler: """Initialize an active connection.""" self.hass = hass self.request = request - self.wsock: Optional[web.WebSocketResponse] = None + self.wsock: web.WebSocketResponse | None = None self._to_write: asyncio.Queue = asyncio.Queue(maxsize=MAX_PENDING_MSG) self._handle_task = None self._writer_task = None diff --git a/homeassistant/components/websocket_api/messages.py b/homeassistant/components/websocket_api/messages.py index f68beff5924..736a7ad59f0 100644 --- a/homeassistant/components/websocket_api/messages.py +++ b/homeassistant/components/websocket_api/messages.py @@ -1,8 +1,9 @@ """Message templates for websocket commands.""" +from __future__ import annotations from functools import lru_cache import logging -from typing import Any, Dict +from typing import Any import voluptuous as vol @@ -32,12 +33,12 @@ IDEN_TEMPLATE = "__IDEN__" IDEN_JSON_TEMPLATE = '"__IDEN__"' -def result_message(iden: int, result: Any = None) -> Dict: +def result_message(iden: int, result: Any = None) -> dict: """Return a success result message.""" return {"id": iden, "type": const.TYPE_RESULT, "success": True, "result": result} -def error_message(iden: int, code: str, message: str) -> Dict: +def error_message(iden: int, code: str, message: str) -> dict: """Return an error result message.""" return { "id": iden, @@ -47,7 +48,7 @@ def error_message(iden: int, code: str, message: str) -> Dict: } -def event_message(iden: JSON_TYPE, event: Any) -> Dict: +def event_message(iden: JSON_TYPE, event: Any) -> dict: """Return an event message.""" return {"id": iden, "type": "event", "event": event} diff --git a/homeassistant/components/wemo/entity.py b/homeassistant/components/wemo/entity.py index d9b4b0548f8..d10707f4590 100644 --- a/homeassistant/components/wemo/entity.py +++ b/homeassistant/components/wemo/entity.py @@ -1,8 +1,10 @@ """Classes shared among Wemo entities.""" +from __future__ import annotations + import asyncio import contextlib import logging -from typing import Any, Dict, Generator, Optional +from typing import Any, Generator import async_timeout from pywemo import WeMoDevice @@ -19,7 +21,7 @@ class ExceptionHandlerStatus: """Exit status from the _wemo_exception_handler context manager.""" # An exception if one was raised in the _wemo_exception_handler. - exception: Optional[Exception] = None + exception: Exception | None = None @property def success(self) -> bool: @@ -68,7 +70,7 @@ class WemoEntity(Entity): _LOGGER.info("Reconnected to %s", self.name) self._available = True - def _update(self, force_update: Optional[bool] = True): + def _update(self, force_update: bool | None = True): """Update the device state.""" raise NotImplementedError() @@ -99,7 +101,7 @@ class WemoEntity(Entity): self._available = False async def _async_locked_update( - self, force_update: bool, timeout: Optional[async_timeout.timeout] = None + self, force_update: bool, timeout: async_timeout.timeout | None = None ) -> None: """Try updating within an async lock.""" async with self._update_lock: @@ -124,7 +126,7 @@ class WemoSubscriptionEntity(WemoEntity): return self.wemo.serialnumber @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return the device info.""" return { "name": self.name, diff --git a/homeassistant/components/wilight/fan.py b/homeassistant/components/wilight/fan.py index 35727b19927..49402ecb911 100644 --- a/homeassistant/components/wilight/fan.py +++ b/homeassistant/components/wilight/fan.py @@ -1,6 +1,5 @@ """Support for WiLight Fan.""" - -from typing import Optional +from __future__ import annotations from pywilight.const import ( FAN_V1, @@ -79,7 +78,7 @@ class WiLightFan(WiLightDevice, FanEntity): return self._status.get("direction", WL_DIRECTION_OFF) != WL_DIRECTION_OFF @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" if "direction" in self._status: if self._status["direction"] == WL_DIRECTION_OFF: diff --git a/homeassistant/components/withings/__init__.py b/homeassistant/components/withings/__init__.py index 5efa22e6a86..7ccff020c0d 100644 --- a/homeassistant/components/withings/__init__.py +++ b/homeassistant/components/withings/__init__.py @@ -3,8 +3,9 @@ Support for the Withings API. For more details about this platform, please refer to the documentation at """ +from __future__ import annotations + import asyncio -from typing import Optional from aiohttp.web import Request, Response import voluptuous as vol @@ -175,7 +176,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_webhook_handler( hass: HomeAssistant, webhook_id: str, request: Request -) -> Optional[Response]: +) -> Response | None: """Handle webhooks calls.""" # Handle http head calls to the path. # When creating a notify subscription, Withings will check that the endpoint is running by sending a HEAD request. diff --git a/homeassistant/components/withings/binary_sensor.py b/homeassistant/components/withings/binary_sensor.py index 136a12a0e1d..a7d0a80e8e3 100644 --- a/homeassistant/components/withings/binary_sensor.py +++ b/homeassistant/components/withings/binary_sensor.py @@ -1,5 +1,7 @@ """Sensors flow for Withings.""" -from typing import Callable, List +from __future__ import annotations + +from typing import Callable from homeassistant.components.binary_sensor import ( DEVICE_CLASS_OCCUPANCY, @@ -16,7 +18,7 @@ from .common import BaseWithingsSensor, async_create_entities async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" entities = await async_create_entities( diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index 54a0a3c94ee..96be4fd3063 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -1,4 +1,6 @@ """Common code for Withings.""" +from __future__ import annotations + import asyncio from dataclasses import dataclass import datetime @@ -6,7 +8,7 @@ from datetime import timedelta from enum import Enum, IntEnum import logging import re -from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from typing import Any, Callable, Dict from aiohttp.web import Response import requests @@ -86,7 +88,7 @@ class WithingsAttribute: measute_type: Enum friendly_name: str unit_of_measurement: str - icon: Optional[str] + icon: str | None platform: str enabled_by_default: bool update_type: UpdateType @@ -461,12 +463,12 @@ WITHINGS_ATTRIBUTES = [ ), ] -WITHINGS_MEASUREMENTS_MAP: Dict[Measurement, WithingsAttribute] = { +WITHINGS_MEASUREMENTS_MAP: dict[Measurement, WithingsAttribute] = { attr.measurement: attr for attr in WITHINGS_ATTRIBUTES } -WITHINGS_MEASURE_TYPE_MAP: Dict[ - Union[NotifyAppli, GetSleepSummaryField, MeasureType], WithingsAttribute +WITHINGS_MEASURE_TYPE_MAP: dict[ + NotifyAppli | GetSleepSummaryField | MeasureType, WithingsAttribute ] = {attr.measute_type: attr for attr in WITHINGS_ATTRIBUTES} @@ -486,8 +488,8 @@ class ConfigEntryWithingsApi(AbstractWithingsApi): self.session = OAuth2Session(hass, config_entry, implementation) def _request( - self, path: str, params: Dict[str, Any], method: str = "GET" - ) -> Dict[str, Any]: + self, path: str, params: dict[str, Any], method: str = "GET" + ) -> dict[str, Any]: """Perform an async request.""" asyncio.run_coroutine_threadsafe( self.session.async_ensure_token_valid(), self._hass.loop @@ -524,7 +526,7 @@ class WebhookUpdateCoordinator: """Initialize the object.""" self._hass = hass self._user_id = user_id - self._listeners: List[CALLBACK_TYPE] = [] + self._listeners: list[CALLBACK_TYPE] = [] self.data: MeasurementData = {} def async_add_listener(self, listener: CALLBACK_TYPE) -> Callable[[], None]: @@ -573,10 +575,8 @@ class DataManager: self._notify_unsubscribe_delay = datetime.timedelta(seconds=1) self._is_available = True - self._cancel_interval_update_interval: Optional[CALLBACK_TYPE] = None - self._cancel_configure_webhook_subscribe_interval: Optional[ - CALLBACK_TYPE - ] = None + self._cancel_interval_update_interval: CALLBACK_TYPE | None = None + self._cancel_configure_webhook_subscribe_interval: CALLBACK_TYPE | None = None self._api_notification_id = f"withings_{self._user_id}" self.subscription_update_coordinator = DataUpdateCoordinator( @@ -600,7 +600,7 @@ class DataManager: self.webhook_update_coordinator = WebhookUpdateCoordinator( self._hass, self._user_id ) - self._cancel_subscription_update: Optional[Callable[[], None]] = None + self._cancel_subscription_update: Callable[[], None] | None = None self._subscribe_webhook_run_count = 0 @property @@ -728,7 +728,7 @@ class DataManager: self._api.notify_revoke, profile.callbackurl, profile.appli ) - async def async_get_all_data(self) -> Optional[Dict[MeasureType, Any]]: + async def async_get_all_data(self) -> dict[MeasureType, Any] | None: """Update all withings data.""" try: return await self._do_retry(self._async_get_all_data) @@ -764,14 +764,14 @@ class DataManager: raise exception - async def _async_get_all_data(self) -> Optional[Dict[MeasureType, Any]]: + async def _async_get_all_data(self) -> dict[MeasureType, Any] | None: _LOGGER.info("Updating all withings data") return { **await self.async_get_measures(), **await self.async_get_sleep_summary(), } - async def async_get_measures(self) -> Dict[MeasureType, Any]: + async def async_get_measures(self) -> dict[MeasureType, Any]: """Get the measures data.""" _LOGGER.debug("Updating withings measures") @@ -794,7 +794,7 @@ class DataManager: for measure in group.measures } - async def async_get_sleep_summary(self) -> Dict[MeasureType, Any]: + async def async_get_sleep_summary(self) -> dict[MeasureType, Any]: """Get the sleep summary data.""" _LOGGER.debug("Updating withing sleep summary") now = dt.utcnow() @@ -837,7 +837,7 @@ class DataManager: response = await self._hass.async_add_executor_job(get_sleep_summary) # Set the default to empty lists. - raw_values: Dict[GetSleepSummaryField, List[int]] = { + raw_values: dict[GetSleepSummaryField, list[int]] = { field: [] for field in GetSleepSummaryField } @@ -848,9 +848,9 @@ class DataManager: for field in GetSleepSummaryField: raw_values[field].append(dict(data)[field.value]) - values: Dict[GetSleepSummaryField, float] = {} + values: dict[GetSleepSummaryField, float] = {} - def average(data: List[int]) -> float: + def average(data: list[int]) -> float: return sum(data) / len(data) def set_value(field: GetSleepSummaryField, func: Callable) -> None: @@ -907,7 +907,7 @@ def get_attribute_unique_id(attribute: WithingsAttribute, user_id: int) -> str: async def async_get_entity_id( hass: HomeAssistant, attribute: WithingsAttribute, user_id: int -) -> Optional[str]: +) -> str | None: """Get an entity id for a user's attribute.""" entity_registry: EntityRegistry = ( await hass.helpers.entity_registry.async_get_registry() @@ -936,7 +936,7 @@ class BaseWithingsSensor(Entity): self._user_id = self._data_manager.user_id self._name = f"Withings {self._attribute.measurement.value} {self._profile}" self._unique_id = get_attribute_unique_id(self._attribute, self._user_id) - self._state_data: Optional[Any] = None + self._state_data: Any | None = None @property def should_poll(self) -> bool: @@ -1053,7 +1053,7 @@ async def async_get_data_manager( def get_data_manager_by_webhook_id( hass: HomeAssistant, webhook_id: str -) -> Optional[DataManager]: +) -> DataManager | None: """Get a data manager by it's webhook id.""" return next( iter( @@ -1067,7 +1067,7 @@ def get_data_manager_by_webhook_id( ) -def get_all_data_managers(hass: HomeAssistant) -> Tuple[DataManager, ...]: +def get_all_data_managers(hass: HomeAssistant) -> tuple[DataManager, ...]: """Get all configured data managers.""" return tuple( config_entry_data[const.DATA_MANAGER] @@ -1086,7 +1086,7 @@ async def async_create_entities( entry: ConfigEntry, create_func: Callable[[DataManager, WithingsAttribute], Entity], platform: str, -) -> List[Entity]: +) -> list[Entity]: """Create withings entities from config entry.""" data_manager = await async_get_data_manager(hass, entry) @@ -1096,7 +1096,7 @@ async def async_create_entities( ] -def get_platform_attributes(platform: str) -> Tuple[WithingsAttribute, ...]: +def get_platform_attributes(platform: str) -> tuple[WithingsAttribute, ...]: """Get withings attributes used for a specific platform.""" return tuple( attribute for attribute in WITHINGS_ATTRIBUTES if attribute.platform == platform diff --git a/homeassistant/components/withings/config_flow.py b/homeassistant/components/withings/config_flow.py index ddf51741c62..0cdc523266c 100644 --- a/homeassistant/components/withings/config_flow.py +++ b/homeassistant/components/withings/config_flow.py @@ -1,6 +1,7 @@ """Config flow for Withings.""" +from __future__ import annotations + import logging -from typing import Dict, Union import voluptuous as vol from withings_api.common import AuthScope @@ -19,7 +20,7 @@ class WithingsFlowHandler( DOMAIN = const.DOMAIN CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL # Temporarily holds authorization data during the profile step. - _current_data: Dict[str, Union[None, str, int]] = {} + _current_data: dict[str, None | str | int] = {} @property def logger(self) -> logging.Logger: diff --git a/homeassistant/components/withings/sensor.py b/homeassistant/components/withings/sensor.py index a55d83d717b..a7a7947dec5 100644 --- a/homeassistant/components/withings/sensor.py +++ b/homeassistant/components/withings/sensor.py @@ -1,5 +1,7 @@ """Sensors flow for Withings.""" -from typing import Callable, List, Union +from __future__ import annotations + +from typing import Callable from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.config_entries import ConfigEntry @@ -12,7 +14,7 @@ from .common import BaseWithingsSensor, async_create_entities async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" @@ -30,6 +32,6 @@ class WithingsHealthSensor(BaseWithingsSensor): """Implementation of a Withings sensor.""" @property - def state(self) -> Union[None, str, int, float]: + def state(self) -> None | str | int | float: """Return the state of the entity.""" return self._state_data diff --git a/homeassistant/components/wled/__init__.py b/homeassistant/components/wled/__init__.py index 72fee5f078c..2ec02931415 100644 --- a/homeassistant/components/wled/__init__.py +++ b/homeassistant/components/wled/__init__.py @@ -1,8 +1,10 @@ """Support for WLED.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Any, Dict +from typing import Any from wled import WLED, Device as WLEDDevice, WLEDConnectionError, WLEDError @@ -185,7 +187,7 @@ class WLEDDeviceEntity(WLEDEntity): """Defines a WLED device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this WLED device.""" return { ATTR_IDENTIFIERS: {(DOMAIN, self.coordinator.data.info.mac_address)}, diff --git a/homeassistant/components/wled/config_flow.py b/homeassistant/components/wled/config_flow.py index 5915447f2f5..75e59c88086 100644 --- a/homeassistant/components/wled/config_flow.py +++ b/homeassistant/components/wled/config_flow.py @@ -1,5 +1,7 @@ """Config flow to configure the WLED integration.""" -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import Any import voluptuous as vol from wled import WLED, WLEDConnectionError @@ -23,14 +25,14 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN): CONNECTION_CLASS = CONN_CLASS_LOCAL_POLL async def async_step_user( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" return await self._handle_config_flow(user_input) async def async_step_zeroconf( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle zeroconf discovery.""" if user_input is None: return self.async_abort(reason="cannot_connect") @@ -53,13 +55,13 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf_confirm( self, user_input: ConfigType = None - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Handle a flow initiated by zeroconf.""" return await self._handle_config_flow(user_input) async def _handle_config_flow( - self, user_input: Optional[ConfigType] = None, prepare: bool = False - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None, prepare: bool = False + ) -> dict[str, Any]: """Config flow handler for WLED.""" source = self.context.get("source") @@ -100,7 +102,7 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN): data={CONF_HOST: user_input[CONF_HOST], CONF_MAC: user_input[CONF_MAC]}, ) - def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: + def _show_setup_form(self, errors: dict | None = None) -> dict[str, Any]: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -108,7 +110,7 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN): errors=errors or {}, ) - def _show_confirm_dialog(self, errors: Optional[Dict] = None) -> Dict[str, Any]: + def _show_confirm_dialog(self, errors: dict | None = None) -> dict[str, Any]: """Show the confirm dialog to the user.""" name = self.context.get(CONF_NAME) return self.async_show_form( diff --git a/homeassistant/components/wled/light.py b/homeassistant/components/wled/light.py index d25c0fc9064..df412ba4c36 100644 --- a/homeassistant/components/wled/light.py +++ b/homeassistant/components/wled/light.py @@ -1,6 +1,8 @@ """Support for LED lights.""" +from __future__ import annotations + from functools import partial -from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from typing import Any, Callable import voluptuous as vol @@ -51,7 +53,7 @@ PARALLEL_UPDATES = 1 async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up WLED light based on a config entry.""" coordinator: WLEDDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] @@ -115,7 +117,7 @@ class WLEDMasterLight(LightEntity, WLEDDeviceEntity): return SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION @property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the brightness of this light between 1..255.""" return self.coordinator.data.state.brightness @@ -188,7 +190,7 @@ class WLEDSegmentLight(LightEntity, WLEDDeviceEntity): return super().available @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" playlist = self.coordinator.data.state.playlist if playlist == -1: @@ -209,18 +211,18 @@ class WLEDSegmentLight(LightEntity, WLEDDeviceEntity): } @property - def hs_color(self) -> Optional[Tuple[float, float]]: + def hs_color(self) -> tuple[float, float] | None: """Return the hue and saturation color value [float, float].""" color = self.coordinator.data.state.segments[self._segment].color_primary return color_util.color_RGB_to_hs(*color[:3]) @property - def effect(self) -> Optional[str]: + def effect(self) -> str | None: """Return the current effect of the light.""" return self.coordinator.data.state.segments[self._segment].effect.name @property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the brightness of this light between 1..255.""" state = self.coordinator.data.state @@ -234,7 +236,7 @@ class WLEDSegmentLight(LightEntity, WLEDDeviceEntity): return state.segments[self._segment].brightness @property - def white_value(self) -> Optional[int]: + def white_value(self) -> int | None: """Return the white value of this light between 0..255.""" color = self.coordinator.data.state.segments[self._segment].color_primary return color[-1] if self._rgbw else None @@ -256,7 +258,7 @@ class WLEDSegmentLight(LightEntity, WLEDDeviceEntity): return flags @property - def effect_list(self) -> List[str]: + def effect_list(self) -> list[str]: """Return the list of supported effects.""" return [effect.name for effect in self.coordinator.data.effects] @@ -357,11 +359,11 @@ class WLEDSegmentLight(LightEntity, WLEDDeviceEntity): @wled_exception_handler async def async_effect( self, - effect: Optional[Union[int, str]] = None, - intensity: Optional[int] = None, - palette: Optional[Union[int, str]] = None, - reverse: Optional[bool] = None, - speed: Optional[int] = None, + effect: int | str | None = None, + intensity: int | None = None, + palette: int | str | None = None, + reverse: bool | None = None, + speed: int | None = None, ) -> None: """Set the effect of a WLED light.""" data = {ATTR_SEGMENT_ID: self._segment} @@ -398,7 +400,7 @@ class WLEDSegmentLight(LightEntity, WLEDDeviceEntity): def async_update_segments( entry: ConfigEntry, coordinator: WLEDDataUpdateCoordinator, - current: Dict[int, WLEDSegmentLight], + current: dict[int, WLEDSegmentLight], async_add_entities, ) -> None: """Update segments.""" @@ -438,7 +440,7 @@ def async_update_segments( async def async_remove_entity( index: int, coordinator: WLEDDataUpdateCoordinator, - current: Dict[int, WLEDSegmentLight], + current: dict[int, WLEDSegmentLight], ) -> None: """Remove WLED segment light from Home Assistant.""" entity = current[index] diff --git a/homeassistant/components/wled/sensor.py b/homeassistant/components/wled/sensor.py index da002e1e8f0..4bfca885613 100644 --- a/homeassistant/components/wled/sensor.py +++ b/homeassistant/components/wled/sensor.py @@ -1,6 +1,8 @@ """Support for WLED sensors.""" +from __future__ import annotations + from datetime import timedelta -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable from homeassistant.components.sensor import DEVICE_CLASS_CURRENT from homeassistant.config_entries import ConfigEntry @@ -22,7 +24,7 @@ from .const import ATTR_LED_COUNT, ATTR_MAX_POWER, CURRENT_MA, DOMAIN async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up WLED sensor based on a config entry.""" coordinator: WLEDDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] @@ -52,7 +54,7 @@ class WLEDSensor(WLEDDeviceEntity): icon: str, key: str, name: str, - unit_of_measurement: Optional[str] = None, + unit_of_measurement: str | None = None, ) -> None: """Initialize WLED sensor.""" self._unit_of_measurement = unit_of_measurement @@ -92,7 +94,7 @@ class WLEDEstimatedCurrentSensor(WLEDSensor): ) @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" return { ATTR_LED_COUNT: self.coordinator.data.info.leds.count, @@ -105,7 +107,7 @@ class WLEDEstimatedCurrentSensor(WLEDSensor): return self.coordinator.data.info.leds.power @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this sensor.""" return DEVICE_CLASS_CURRENT @@ -131,7 +133,7 @@ class WLEDUptimeSensor(WLEDSensor): return uptime.replace(microsecond=0).isoformat() @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this sensor.""" return DEVICE_CLASS_TIMESTAMP @@ -199,7 +201,7 @@ class WLEDWifiRSSISensor(WLEDSensor): return self.coordinator.data.info.wifi.rssi @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this sensor.""" return DEVICE_CLASS_SIGNAL_STRENGTH diff --git a/homeassistant/components/wled/switch.py b/homeassistant/components/wled/switch.py index e58b32425cb..5902cd246a0 100644 --- a/homeassistant/components/wled/switch.py +++ b/homeassistant/components/wled/switch.py @@ -1,5 +1,7 @@ """Support for WLED switches.""" -from typing import Any, Callable, Dict, List, Optional +from __future__ import annotations + +from typing import Any, Callable from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry @@ -21,7 +23,7 @@ PARALLEL_UPDATES = 1 async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up WLED switch based on a config entry.""" coordinator: WLEDDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] @@ -72,7 +74,7 @@ class WLEDNightlightSwitch(WLEDSwitch): ) @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" return { ATTR_DURATION: self.coordinator.data.state.nightlight.duration, @@ -110,7 +112,7 @@ class WLEDSyncSendSwitch(WLEDSwitch): ) @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" return {ATTR_UDP_PORT: self.coordinator.data.info.udp_port} @@ -144,7 +146,7 @@ class WLEDSyncReceiveSwitch(WLEDSwitch): ) @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" return {ATTR_UDP_PORT: self.coordinator.data.info.udp_port} diff --git a/homeassistant/components/wunderground/sensor.py b/homeassistant/components/wunderground/sensor.py index 1f4332c35e7..9e4b764f34c 100644 --- a/homeassistant/components/wunderground/sensor.py +++ b/homeassistant/components/wunderground/sensor.py @@ -1,9 +1,11 @@ """Support for WUnderground weather service.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging import re -from typing import Any, Callable, Optional, Union +from typing import Any, Callable import aiohttp import async_timeout @@ -64,10 +66,10 @@ class WUSensorConfig: def __init__( self, - friendly_name: Union[str, Callable], + friendly_name: str | Callable, feature: str, value: Callable[["WUndergroundData"], Any], - unit_of_measurement: Optional[str] = None, + unit_of_measurement: str | None = None, entity_picture=None, icon: str = "mdi:gauge", extra_state_attributes=None, @@ -99,10 +101,10 @@ class WUCurrentConditionsSensorConfig(WUSensorConfig): def __init__( self, - friendly_name: Union[str, Callable], + friendly_name: str | Callable, field: str, - icon: Optional[str] = "mdi:gauge", - unit_of_measurement: Optional[str] = None, + icon: str | None = "mdi:gauge", + unit_of_measurement: str | None = None, device_class=None, ): """Initialize current conditions sensor configuration. @@ -131,9 +133,7 @@ class WUCurrentConditionsSensorConfig(WUSensorConfig): class WUDailyTextForecastSensorConfig(WUSensorConfig): """Helper for defining sensor configurations for daily text forecasts.""" - def __init__( - self, period: int, field: str, unit_of_measurement: Optional[str] = None - ): + def __init__(self, period: int, field: str, unit_of_measurement: str | None = None): """Initialize daily text forecast sensor configuration. :param period: forecast period number @@ -166,8 +166,8 @@ class WUDailySimpleForecastSensorConfig(WUSensorConfig): friendly_name: str, period: int, field: str, - wu_unit: Optional[str] = None, - ha_unit: Optional[str] = None, + wu_unit: str | None = None, + ha_unit: str | None = None, icon=None, device_class=None, ): @@ -273,7 +273,7 @@ class WUAlmanacSensorConfig(WUSensorConfig): def __init__( self, - friendly_name: Union[str, Callable], + friendly_name: str | Callable, field: str, value_type: str, wu_unit: str, @@ -303,7 +303,7 @@ class WUAlmanacSensorConfig(WUSensorConfig): class WUAlertsSensorConfig(WUSensorConfig): """Helper for defining field configuration for alerts.""" - def __init__(self, friendly_name: Union[str, Callable]): + def __init__(self, friendly_name: str | Callable): """Initialiize alerts sensor configuration. :param friendly_name: Friendly name diff --git a/homeassistant/components/xbox/__init__.py b/homeassistant/components/xbox/__init__.py index 1d921f5fd18..dd358286802 100644 --- a/homeassistant/components/xbox/__init__.py +++ b/homeassistant/components/xbox/__init__.py @@ -1,9 +1,10 @@ """The xbox integration.""" +from __future__ import annotations + import asyncio from dataclasses import dataclass from datetime import timedelta import logging -from typing import Dict, Optional import voluptuous as vol from xbox.webapi.api.client import XboxLiveClient @@ -133,7 +134,7 @@ class ConsoleData: """Xbox console status data.""" status: SmartglassConsoleStatus - app_details: Optional[Product] + app_details: Product | None @dataclass @@ -149,7 +150,7 @@ class PresenceData: in_game: bool in_multiplayer: bool gamer_score: str - gold_tenure: Optional[str] + gold_tenure: str | None account_tier: str @@ -157,8 +158,8 @@ class PresenceData: class XboxData: """Xbox dataclass for update coordinator.""" - consoles: Dict[str, ConsoleData] - presence: Dict[str, PresenceData] + consoles: dict[str, ConsoleData] + presence: dict[str, PresenceData] class XboxUpdateCoordinator(DataUpdateCoordinator): @@ -184,9 +185,9 @@ class XboxUpdateCoordinator(DataUpdateCoordinator): async def _async_update_data(self) -> XboxData: """Fetch the latest console status.""" # Update Console Status - new_console_data: Dict[str, ConsoleData] = {} + new_console_data: dict[str, ConsoleData] = {} for console in self.consoles.result: - current_state: Optional[ConsoleData] = self.data.consoles.get(console.id) + current_state: ConsoleData | None = self.data.consoles.get(console.id) status: SmartglassConsoleStatus = ( await self.client.smartglass.get_console_status(console.id) ) @@ -198,7 +199,7 @@ class XboxUpdateCoordinator(DataUpdateCoordinator): ) # Setup focus app - app_details: Optional[Product] = None + app_details: Product | None = None if current_state is not None: app_details = current_state.app_details @@ -246,7 +247,7 @@ class XboxUpdateCoordinator(DataUpdateCoordinator): def _build_presence_data(person: Person) -> PresenceData: """Build presence data from a person.""" - active_app: Optional[PresenceDetail] = None + active_app: PresenceDetail | None = None try: active_app = next( presence for presence in person.presence_details if presence.is_primary diff --git a/homeassistant/components/xbox/base_sensor.py b/homeassistant/components/xbox/base_sensor.py index d19fbfb918d..c149ce74c32 100644 --- a/homeassistant/components/xbox/base_sensor.py +++ b/homeassistant/components/xbox/base_sensor.py @@ -1,5 +1,5 @@ """Base Sensor for the Xbox Integration.""" -from typing import Optional +from __future__ import annotations from yarl import URL @@ -24,7 +24,7 @@ class XboxBaseSensorEntity(CoordinatorEntity): return f"{self.xuid}_{self.attribute}" @property - def data(self) -> Optional[PresenceData]: + def data(self) -> PresenceData | None: """Return coordinator data for this console.""" return self.coordinator.data.presence.get(self.xuid) diff --git a/homeassistant/components/xbox/binary_sensor.py b/homeassistant/components/xbox/binary_sensor.py index 109b2839b68..98e06257146 100644 --- a/homeassistant/components/xbox/binary_sensor.py +++ b/homeassistant/components/xbox/binary_sensor.py @@ -1,6 +1,7 @@ """Xbox friends binary sensors.""" +from __future__ import annotations + from functools import partial -from typing import Dict, List from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.core import callback @@ -44,7 +45,7 @@ class XboxBinarySensorEntity(XboxBaseSensorEntity, BinarySensorEntity): @callback def async_update_friends( coordinator: XboxUpdateCoordinator, - current: Dict[str, List[XboxBinarySensorEntity]], + current: dict[str, list[XboxBinarySensorEntity]], async_add_entities, ) -> None: """Update friends.""" @@ -73,7 +74,7 @@ def async_update_friends( async def async_remove_entities( xuid: str, coordinator: XboxUpdateCoordinator, - current: Dict[str, XboxBinarySensorEntity], + current: dict[str, XboxBinarySensorEntity], ) -> None: """Remove friend sensors from Home Assistant.""" registry = await async_get_entity_registry(coordinator.hass) diff --git a/homeassistant/components/xbox/browse_media.py b/homeassistant/components/xbox/browse_media.py index 2aebb07df1b..0c3eec95c6f 100644 --- a/homeassistant/components/xbox/browse_media.py +++ b/homeassistant/components/xbox/browse_media.py @@ -1,5 +1,5 @@ """Support for media browsing.""" -from typing import Dict, List, Optional +from __future__ import annotations from xbox.webapi.api.client import XboxLiveClient from xbox.webapi.api.provider.catalog.const import HOME_APP_IDS, SYSTEM_PFN_ID_MAP @@ -41,7 +41,7 @@ async def build_item_response( tv_configured: bool, media_content_type: str, media_content_id: str, -) -> Optional[BrowseMedia]: +) -> BrowseMedia | None: """Create response payload for the provided media query.""" apps: InstalledPackagesList = await client.smartglass.get_installed_apps(device_id) @@ -149,7 +149,7 @@ async def build_item_response( ) -def item_payload(item: InstalledPackage, images: Dict[str, List[Image]]): +def item_payload(item: InstalledPackage, images: dict[str, list[Image]]): """Create response payload for a single media item.""" thumbnail = None image = _find_media_image(images.get(item.one_store_product_id, [])) @@ -169,7 +169,7 @@ def item_payload(item: InstalledPackage, images: Dict[str, List[Image]]): ) -def _find_media_image(images: List[Image]) -> Optional[Image]: +def _find_media_image(images: list[Image]) -> Image | None: purpose_order = ["Poster", "Tile", "Logo", "BoxArt"] for purpose in purpose_order: for image in images: diff --git a/homeassistant/components/xbox/media_player.py b/homeassistant/components/xbox/media_player.py index 19e8a90d48e..e57f3971042 100644 --- a/homeassistant/components/xbox/media_player.py +++ b/homeassistant/components/xbox/media_player.py @@ -1,6 +1,8 @@ """Xbox Media Player Support.""" +from __future__ import annotations + import re -from typing import List, Optional +from typing import List from xbox.webapi.api.client import XboxLiveClient from xbox.webapi.api.provider.catalog.models import Image @@ -231,7 +233,7 @@ class XboxMediaPlayer(CoordinatorEntity, MediaPlayerEntity): } -def _find_media_image(images=List[Image]) -> Optional[Image]: +def _find_media_image(images=List[Image]) -> Image | None: purpose_order = ["FeaturePromotionalSquareArt", "Tile", "Logo", "BoxArt"] for purpose in purpose_order: for image in images: diff --git a/homeassistant/components/xbox/media_source.py b/homeassistant/components/xbox/media_source.py index 53346f3750d..1a459580d66 100644 --- a/homeassistant/components/xbox/media_source.py +++ b/homeassistant/components/xbox/media_source.py @@ -1,6 +1,7 @@ """Xbox Media Source Implementation.""" +from __future__ import annotations + from dataclasses import dataclass -from typing import List, Tuple from pydantic.error_wrappers import ValidationError # pylint: disable=no-name-in-module from xbox.webapi.api.client import XboxLiveClient @@ -50,7 +51,7 @@ async def async_get_media_source(hass: HomeAssistantType): @callback def async_parse_identifier( item: MediaSourceItem, -) -> Tuple[str, str, str]: +) -> tuple[str, str, str]: """Parse identifier.""" identifier = item.identifier or "" start = ["", "", ""] @@ -87,7 +88,7 @@ class XboxSource(MediaSource): return PlayMedia(url, MIME_TYPE_MAP[kind]) async def async_browse_media( - self, item: MediaSourceItem, media_types: Tuple[str] = MEDIA_MIME_TYPES + self, item: MediaSourceItem, media_types: tuple[str] = MEDIA_MIME_TYPES ) -> BrowseMediaSource: """Return media.""" title, category, _ = async_parse_identifier(item) @@ -136,7 +137,7 @@ class XboxSource(MediaSource): title_id, _, thumbnail = title.split("#", 2) owner, kind = category.split("#", 1) - items: List[XboxMediaItem] = [] + items: list[XboxMediaItem] = [] try: if kind == "gameclips": if owner == "my": @@ -206,7 +207,7 @@ class XboxSource(MediaSource): ) -def _build_game_item(item: InstalledPackage, images: List[Image]): +def _build_game_item(item: InstalledPackage, images: list[Image]): """Build individual game.""" thumbnail = "" image = _find_media_image(images.get(item.one_store_product_id, [])) diff --git a/homeassistant/components/xbox/sensor.py b/homeassistant/components/xbox/sensor.py index e0ab661b2d9..88bf112b728 100644 --- a/homeassistant/components/xbox/sensor.py +++ b/homeassistant/components/xbox/sensor.py @@ -1,6 +1,7 @@ """Xbox friends binary sensors.""" +from __future__ import annotations + from functools import partial -from typing import Dict, List from homeassistant.core import callback from homeassistant.helpers.entity_registry import ( @@ -43,7 +44,7 @@ class XboxSensorEntity(XboxBaseSensorEntity): @callback def async_update_friends( coordinator: XboxUpdateCoordinator, - current: Dict[str, List[XboxSensorEntity]], + current: dict[str, list[XboxSensorEntity]], async_add_entities, ) -> None: """Update friends.""" @@ -72,7 +73,7 @@ def async_update_friends( async def async_remove_entities( xuid: str, coordinator: XboxUpdateCoordinator, - current: Dict[str, XboxSensorEntity], + current: dict[str, XboxSensorEntity], ) -> None: """Remove friend sensors from Home Assistant.""" registry = await async_get_entity_registry(coordinator.hass) diff --git a/homeassistant/components/yeelight/__init__.py b/homeassistant/components/yeelight/__init__.py index a86af10fe1c..c1e0c555e02 100644 --- a/homeassistant/components/yeelight/__init__.py +++ b/homeassistant/components/yeelight/__init__.py @@ -1,8 +1,9 @@ """Support for Xiaomi Yeelight WiFi color bulb.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Optional import voluptuous as vol from yeelight import Bulb, BulbException, discover_bulbs @@ -181,7 +182,7 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Yeelight from a config entry.""" - async def _initialize(host: str, capabilities: Optional[dict] = None) -> None: + async def _initialize(host: str, capabilities: dict | None = None) -> None: remove_dispatcher = async_dispatcher_connect( hass, DEVICE_INITIALIZED.format(host), @@ -593,7 +594,7 @@ async def _async_get_device( hass: HomeAssistant, host: str, entry: ConfigEntry, - capabilities: Optional[dict], + capabilities: dict | None, ) -> YeelightDevice: # Get model from config and capabilities model = entry.options.get(CONF_MODEL) diff --git a/homeassistant/components/zerproc/light.py b/homeassistant/components/zerproc/light.py index bf9f917f230..16fe5607925 100644 --- a/homeassistant/components/zerproc/light.py +++ b/homeassistant/components/zerproc/light.py @@ -1,7 +1,9 @@ """Zerproc light platform.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Callable, List, Optional +from typing import Callable import pyzerproc @@ -29,7 +31,7 @@ SUPPORT_ZERPROC = SUPPORT_BRIGHTNESS | SUPPORT_COLOR DISCOVERY_INTERVAL = timedelta(seconds=60) -async def discover_entities(hass: HomeAssistant) -> List[Entity]: +async def discover_entities(hass: HomeAssistant) -> list[Entity]: """Attempt to discover new lights.""" lights = await pyzerproc.discover() @@ -49,7 +51,7 @@ async def discover_entities(hass: HomeAssistant) -> List[Entity]: async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Zerproc light devices.""" warned = False @@ -122,7 +124,7 @@ class ZerprocLight(LightEntity): } @property - def icon(self) -> Optional[str]: + def icon(self) -> str | None: """Return the icon to use in the frontend.""" return "mdi:string-lights" diff --git a/homeassistant/components/zha/climate.py b/homeassistant/components/zha/climate.py index f947012d3af..e2c4faa8539 100644 --- a/homeassistant/components/zha/climate.py +++ b/homeassistant/components/zha/climate.py @@ -4,11 +4,12 @@ Climate on Zigbee Home Automation networks. For more details on this platform, please refer to the documentation at https://home-assistant.io/components/zha.climate/ """ +from __future__ import annotations + from datetime import datetime, timedelta import enum import functools from random import randint -from typing import List, Optional, Tuple from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( @@ -212,7 +213,7 @@ class Thermostat(ZhaEntity, ClimateEntity): return data @property - def fan_mode(self) -> Optional[str]: + def fan_mode(self) -> str | None: """Return current FAN mode.""" if self._thrm.running_state is None: return FAN_AUTO @@ -224,14 +225,14 @@ class Thermostat(ZhaEntity, ClimateEntity): return FAN_AUTO @property - def fan_modes(self) -> Optional[List[str]]: + def fan_modes(self) -> list[str] | None: """Return supported FAN modes.""" if not self._fan: return None return [FAN_AUTO, FAN_ON] @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current HVAC action.""" if ( self._thrm.pi_heating_demand is None @@ -241,7 +242,7 @@ class Thermostat(ZhaEntity, ClimateEntity): return self._pi_demand_action @property - def _rm_rs_action(self) -> Optional[str]: + def _rm_rs_action(self) -> str | None: """Return the current HVAC action based on running mode and running state.""" running_mode = self._thrm.running_mode @@ -260,7 +261,7 @@ class Thermostat(ZhaEntity, ClimateEntity): return CURRENT_HVAC_OFF @property - def _pi_demand_action(self) -> Optional[str]: + def _pi_demand_action(self) -> str | None: """Return the current HVAC action based on pi_demands.""" heating_demand = self._thrm.pi_heating_demand @@ -275,12 +276,12 @@ class Thermostat(ZhaEntity, ClimateEntity): return CURRENT_HVAC_OFF @property - def hvac_mode(self) -> Optional[str]: + def hvac_mode(self) -> str | None: """Return HVAC operation mode.""" return SYSTEM_MODE_2_HVAC.get(self._thrm.system_mode) @property - def hvac_modes(self) -> Tuple[str, ...]: + def hvac_modes(self) -> tuple[str, ...]: """Return the list of available HVAC operation modes.""" return SEQ_OF_OPERATION.get(self._thrm.ctrl_seqe_of_oper, (HVAC_MODE_OFF,)) @@ -290,12 +291,12 @@ class Thermostat(ZhaEntity, ClimateEntity): return PRECISION_TENTHS @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return current preset mode.""" return self._preset @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return supported preset modes.""" return self._presets @@ -566,7 +567,7 @@ class ZenWithinThermostat(Thermostat): """Zen Within Thermostat implementation.""" @property - def _rm_rs_action(self) -> Optional[str]: + def _rm_rs_action(self) -> str | None: """Return the current HVAC action based on running mode and running state.""" running_state = self._thrm.running_state diff --git a/homeassistant/components/zha/config_flow.py b/homeassistant/components/zha/config_flow.py index f59f53c7995..9835a9a3937 100644 --- a/homeassistant/components/zha/config_flow.py +++ b/homeassistant/components/zha/config_flow.py @@ -1,6 +1,8 @@ """Config flow for ZHA.""" +from __future__ import annotations + import os -from typing import Any, Dict, Optional +from typing import Any import serial.tools.list_ports import voluptuous as vol @@ -127,7 +129,7 @@ class ZhaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) -async def detect_radios(dev_path: str) -> Optional[Dict[str, Any]]: +async def detect_radios(dev_path: str) -> dict[str, Any] | None: """Probe all radio types on the device port.""" for radio in RadioType: dev_config = radio.controller.SCHEMA_DEVICE({CONF_DEVICE_PATH: dev_path}) diff --git a/homeassistant/components/zha/core/channels/__init__.py b/homeassistant/components/zha/core/channels/__init__.py index d3de5f7004d..289f1c36d4d 100644 --- a/homeassistant/components/zha/core/channels/__init__.py +++ b/homeassistant/components/zha/core/channels/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Dict import zigpy.zcl.clusters.closures @@ -40,7 +40,7 @@ class Channels: def __init__(self, zha_device: zha_typing.ZhaDeviceType) -> None: """Initialize instance.""" - self._pools: List[zha_typing.ChannelPoolType] = [] + self._pools: list[zha_typing.ChannelPoolType] = [] self._power_config = None self._identify = None self._semaphore = asyncio.Semaphore(3) @@ -49,7 +49,7 @@ class Channels: self._zha_device = zha_device @property - def pools(self) -> List[ChannelPool]: + def pools(self) -> list[ChannelPool]: """Return channel pools list.""" return self._pools @@ -96,7 +96,7 @@ class Channels: return self._unique_id @property - def zigbee_signature(self) -> Dict[int, Dict[str, Any]]: + def zigbee_signature(self) -> dict[int, dict[str, Any]]: """Get the zigbee signatures for the pools in channels.""" return { signature[0]: signature[1] @@ -137,7 +137,7 @@ class Channels: component: str, entity_class: zha_typing.CALLABLE_T, unique_id: str, - channels: List[zha_typing.ChannelType], + channels: list[zha_typing.ChannelType], ): """Signal new entity addition.""" if self.zha_device.status == zha_core_device.DeviceStatus.INITIALIZED: @@ -153,7 +153,7 @@ class Channels: async_dispatcher_send(self.zha_device.hass, signal, *args) @callback - def zha_send_event(self, event_data: Dict[str, Union[str, int]]) -> None: + def zha_send_event(self, event_data: dict[str, str | int]) -> None: """Relay events to hass.""" self.zha_device.hass.bus.async_fire( "zha_event", @@ -175,7 +175,7 @@ class ChannelPool: self._channels: Channels = channels self._claimed_channels: ChannelsDict = {} self._id: int = ep_id - self._client_channels: Dict[str, zha_typing.ClientChannelType] = {} + self._client_channels: dict[str, zha_typing.ClientChannelType] = {} self._unique_id: str = f"{channels.unique_id}-{ep_id}" @property @@ -189,7 +189,7 @@ class ChannelPool: return self._claimed_channels @property - def client_channels(self) -> Dict[str, zha_typing.ClientChannelType]: + def client_channels(self) -> dict[str, zha_typing.ClientChannelType]: """Return a dict of client channels.""" return self._client_channels @@ -214,12 +214,12 @@ class ChannelPool: return self._channels.zha_device.is_mains_powered @property - def manufacturer(self) -> Optional[str]: + def manufacturer(self) -> str | None: """Return device manufacturer.""" return self._channels.zha_device.manufacturer @property - def manufacturer_code(self) -> Optional[int]: + def manufacturer_code(self) -> int | None: """Return device manufacturer.""" return self._channels.zha_device.manufacturer_code @@ -229,7 +229,7 @@ class ChannelPool: return self._channels.zha_device.hass @property - def model(self) -> Optional[str]: + def model(self) -> str | None: """Return device model.""" return self._channels.zha_device.model @@ -244,7 +244,7 @@ class ChannelPool: return self._unique_id @property - def zigbee_signature(self) -> Tuple[int, Dict[str, Any]]: + def zigbee_signature(self) -> tuple[int, dict[str, Any]]: """Get the zigbee signature for the endpoint this pool represents.""" return ( self.endpoint.endpoint_id, @@ -342,7 +342,7 @@ class ChannelPool: component: str, entity_class: zha_typing.CALLABLE_T, unique_id: str, - channels: List[zha_typing.ChannelType], + channels: list[zha_typing.ChannelType], ): """Signal new entity addition.""" self._channels.async_new_entity(component, entity_class, unique_id, channels) @@ -353,19 +353,19 @@ class ChannelPool: self._channels.async_send_signal(signal, *args) @callback - def claim_channels(self, channels: List[zha_typing.ChannelType]) -> None: + def claim_channels(self, channels: list[zha_typing.ChannelType]) -> None: """Claim a channel.""" self.claimed_channels.update({ch.id: ch for ch in channels}) @callback - def unclaimed_channels(self) -> List[zha_typing.ChannelType]: + def unclaimed_channels(self) -> list[zha_typing.ChannelType]: """Return a list of available (unclaimed) channels.""" claimed = set(self.claimed_channels) available = set(self.all_channels) return [self.all_channels[chan_id] for chan_id in (available - claimed)] @callback - def zha_send_event(self, event_data: Dict[str, Union[str, int]]) -> None: + def zha_send_event(self, event_data: dict[str, str | int]) -> None: """Relay events to hass.""" self._channels.zha_send_event( { diff --git a/homeassistant/components/zha/core/channels/base.py b/homeassistant/components/zha/core/channels/base.py index 14774c09550..bc93459dbad 100644 --- a/homeassistant/components/zha/core/channels/base.py +++ b/homeassistant/components/zha/core/channels/base.py @@ -1,10 +1,11 @@ """Base classes for channels.""" +from __future__ import annotations import asyncio from enum import Enum from functools import wraps import logging -from typing import Any, Union +from typing import Any import zigpy.exceptions @@ -238,7 +239,7 @@ class ZigbeeChannel(LogMixin): """Handle ZDO commands on this cluster.""" @callback - def zha_send_event(self, command: str, args: Union[int, dict]) -> None: + def zha_send_event(self, command: str, args: int | dict) -> None: """Relay events to hass.""" self._ch_pool.zha_send_event( { diff --git a/homeassistant/components/zha/core/channels/general.py b/homeassistant/components/zha/core/channels/general.py index 1c1ab28ba5d..626596e1a3e 100644 --- a/homeassistant/components/zha/core/channels/general.py +++ b/homeassistant/components/zha/core/channels/general.py @@ -1,6 +1,8 @@ """General channels module for Zigbee Home Automation.""" +from __future__ import annotations + import asyncio -from typing import Any, Coroutine, List, Optional +from typing import Any, Coroutine import zigpy.exceptions import zigpy.zcl.clusters.general as general @@ -44,42 +46,42 @@ class AnalogOutput(ZigbeeChannel): REPORT_CONFIG = [{"attr": "present_value", "config": REPORT_CONFIG_DEFAULT}] @property - def present_value(self) -> Optional[float]: + def present_value(self) -> float | None: """Return cached value of present_value.""" return self.cluster.get("present_value") @property - def min_present_value(self) -> Optional[float]: + def min_present_value(self) -> float | None: """Return cached value of min_present_value.""" return self.cluster.get("min_present_value") @property - def max_present_value(self) -> Optional[float]: + def max_present_value(self) -> float | None: """Return cached value of max_present_value.""" return self.cluster.get("max_present_value") @property - def resolution(self) -> Optional[float]: + def resolution(self) -> float | None: """Return cached value of resolution.""" return self.cluster.get("resolution") @property - def relinquish_default(self) -> Optional[float]: + def relinquish_default(self) -> float | None: """Return cached value of relinquish_default.""" return self.cluster.get("relinquish_default") @property - def description(self) -> Optional[str]: + def description(self) -> str | None: """Return cached value of description.""" return self.cluster.get("description") @property - def engineering_units(self) -> Optional[int]: + def engineering_units(self) -> int | None: """Return cached value of engineering_units.""" return self.cluster.get("engineering_units") @property - def application_type(self) -> Optional[int]: + def application_type(self) -> int | None: """Return cached value of application_type.""" return self.cluster.get("application_type") @@ -215,7 +217,7 @@ class LevelControlChannel(ZigbeeChannel): REPORT_CONFIG = ({"attr": "current_level", "config": REPORT_CONFIG_ASAP},) @property - def current_level(self) -> Optional[int]: + def current_level(self) -> int | None: """Return cached value of the current_level attribute.""" return self.cluster.get("current_level") @@ -293,7 +295,7 @@ class OnOffChannel(ZigbeeChannel): self._off_listener = None @property - def on_off(self) -> Optional[bool]: + def on_off(self) -> bool | None: """Return cached value of on/off attribute.""" return self.cluster.get("on_off") @@ -367,7 +369,7 @@ class Ota(ZigbeeChannel): @callback def cluster_command( - self, tsn: int, command_id: int, args: Optional[List[Any]] + self, tsn: int, command_id: int, args: list[Any] | None ) -> None: """Handle OTA commands.""" cmd_name = self.cluster.server_commands.get(command_id, [command_id])[0] @@ -402,7 +404,7 @@ class PollControl(ZigbeeChannel): @callback def cluster_command( - self, tsn: int, command_id: int, args: Optional[List[Any]] + self, tsn: int, command_id: int, args: list[Any] | None ) -> None: """Handle commands received to this cluster.""" cmd_name = self.cluster.client_commands.get(command_id, [command_id])[0] diff --git a/homeassistant/components/zha/core/channels/homeautomation.py b/homeassistant/components/zha/core/channels/homeautomation.py index 5b3a4778fcd..989cc17f97d 100644 --- a/homeassistant/components/zha/core/channels/homeautomation.py +++ b/homeassistant/components/zha/core/channels/homeautomation.py @@ -1,5 +1,7 @@ """Home automation channels module for Zigbee Home Automation.""" -from typing import Coroutine, Optional +from __future__ import annotations + +from typing import Coroutine import zigpy.zcl.clusters.homeautomation as homeautomation @@ -76,14 +78,14 @@ class ElectricalMeasurementChannel(ZigbeeChannel): ) @property - def divisor(self) -> Optional[int]: + def divisor(self) -> int | None: """Return active power divisor.""" return self.cluster.get( "ac_power_divisor", self.cluster.get("power_divisor", 1) ) @property - def multiplier(self) -> Optional[int]: + def multiplier(self) -> int | None: """Return active power divisor.""" return self.cluster.get( "ac_power_multiplier", self.cluster.get("power_multiplier", 1) diff --git a/homeassistant/components/zha/core/channels/hvac.py b/homeassistant/components/zha/core/channels/hvac.py index e02f6c01836..76f9c0b4e80 100644 --- a/homeassistant/components/zha/core/channels/hvac.py +++ b/homeassistant/components/zha/core/channels/hvac.py @@ -4,9 +4,11 @@ HVAC channels module for Zigbee Home Automation. For more details about this component, please refer to the documentation at https://home-assistant.io/integrations/zha/ """ +from __future__ import annotations + import asyncio from collections import namedtuple -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any from zigpy.exceptions import ZigbeeException import zigpy.zcl.clusters.hvac as hvac @@ -44,7 +46,7 @@ class FanChannel(ZigbeeChannel): REPORT_CONFIG = ({"attr": "fan_mode", "config": REPORT_CONFIG_OP},) @property - def fan_mode(self) -> Optional[int]: + def fan_mode(self) -> int | None: """Return current fan mode.""" return self.cluster.get("fan_mode") @@ -198,22 +200,22 @@ class ThermostatChannel(ZigbeeChannel): return self._min_heat_setpoint_limit @property - def local_temp(self) -> Optional[int]: + def local_temp(self) -> int | None: """Thermostat temperature.""" return self._local_temp @property - def occupancy(self) -> Optional[int]: + def occupancy(self) -> int | None: """Is occupancy detected.""" return self._occupancy @property - def occupied_cooling_setpoint(self) -> Optional[int]: + def occupied_cooling_setpoint(self) -> int | None: """Temperature when room is occupied.""" return self._occupied_cooling_setpoint @property - def occupied_heating_setpoint(self) -> Optional[int]: + def occupied_heating_setpoint(self) -> int | None: """Temperature when room is occupied.""" return self._occupied_heating_setpoint @@ -228,27 +230,27 @@ class ThermostatChannel(ZigbeeChannel): return self._pi_heating_demand @property - def running_mode(self) -> Optional[int]: + def running_mode(self) -> int | None: """Thermostat running mode.""" return self._running_mode @property - def running_state(self) -> Optional[int]: + def running_state(self) -> int | None: """Thermostat running state, state of heat, cool, fan relays.""" return self._running_state @property - def system_mode(self) -> Optional[int]: + def system_mode(self) -> int | None: """System mode.""" return self._system_mode @property - def unoccupied_cooling_setpoint(self) -> Optional[int]: + def unoccupied_cooling_setpoint(self) -> int | None: """Temperature when room is not occupied.""" return self._unoccupied_cooling_setpoint @property - def unoccupied_heating_setpoint(self) -> Optional[int]: + def unoccupied_heating_setpoint(self) -> int | None: """Temperature when room is not occupied.""" return self._unoccupied_heating_setpoint @@ -309,7 +311,7 @@ class ThermostatChannel(ZigbeeChannel): chunk, rest = rest[:4], rest[4:] def _configure_reporting_status( - self, attrs: Dict[Union[int, str], Tuple], res: Union[List, Tuple] + self, attrs: dict[int | str, tuple], res: list | tuple ) -> None: """Parse configure reporting result.""" if not isinstance(res, list): @@ -405,7 +407,7 @@ class ThermostatChannel(ZigbeeChannel): self.debug("set cooling setpoint to %s", temperature) return True - async def get_occupancy(self) -> Optional[bool]: + async def get_occupancy(self) -> bool | None: """Get unreportable occupancy attribute.""" try: res, fail = await self.cluster.read_attributes(["occupancy"]) diff --git a/homeassistant/components/zha/core/channels/lighting.py b/homeassistant/components/zha/core/channels/lighting.py index c8827e20e01..000549b0593 100644 --- a/homeassistant/components/zha/core/channels/lighting.py +++ b/homeassistant/components/zha/core/channels/lighting.py @@ -1,5 +1,7 @@ """Lighting channels module for Zigbee Home Automation.""" -from typing import Coroutine, Optional +from __future__ import annotations + +from typing import Coroutine import zigpy.zcl.clusters.lighting as lighting @@ -46,22 +48,22 @@ class ColorChannel(ZigbeeChannel): return self.CAPABILITIES_COLOR_XY @property - def color_loop_active(self) -> Optional[int]: + def color_loop_active(self) -> int | None: """Return cached value of the color_loop_active attribute.""" return self.cluster.get("color_loop_active") @property - def color_temperature(self) -> Optional[int]: + def color_temperature(self) -> int | None: """Return cached value of color temperature.""" return self.cluster.get("color_temperature") @property - def current_x(self) -> Optional[int]: + def current_x(self) -> int | None: """Return cached value of the current_x attribute.""" return self.cluster.get("current_x") @property - def current_y(self) -> Optional[int]: + def current_y(self) -> int | None: """Return cached value of the current_y attribute.""" return self.cluster.get("current_y") diff --git a/homeassistant/components/zha/core/channels/smartenergy.py b/homeassistant/components/zha/core/channels/smartenergy.py index 32e4902799e..a815c75c8b3 100644 --- a/homeassistant/components/zha/core/channels/smartenergy.py +++ b/homeassistant/components/zha/core/channels/smartenergy.py @@ -1,5 +1,7 @@ """Smart energy channels module for Zigbee Home Automation.""" -from typing import Coroutine, Union +from __future__ import annotations + +from typing import Coroutine import zigpy.zcl.clusters.smartenergy as smartenergy @@ -139,7 +141,7 @@ class Metering(ZigbeeChannel): else: self._format_spec = "{:0" + str(width) + "." + str(r_digits) + "f}" - def formatter_function(self, value: int) -> Union[int, float]: + def formatter_function(self, value: int) -> int | float: """Return formatted value for display.""" value = value * self.multiplier / self.divisor if self.unit_of_measurement == POWER_WATT: diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 672fafdd98f..45453ba7545 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -1,7 +1,8 @@ """All constants related to the ZHA component.""" +from __future__ import annotations + import enum import logging -from typing import List import bellows.zigbee.application from zigpy.config import CONF_DEVICE_PATH # noqa: F401 # pylint: disable=unused-import @@ -203,7 +204,7 @@ class RadioType(enum.Enum): ) @classmethod - def list(cls) -> List[str]: + def list(cls) -> list[str]: """Return a list of descriptions.""" return [e.description for e in RadioType] diff --git a/homeassistant/components/zha/core/decorators.py b/homeassistant/components/zha/core/decorators.py index c416548dbe9..c3eec07e980 100644 --- a/homeassistant/components/zha/core/decorators.py +++ b/homeassistant/components/zha/core/decorators.py @@ -1,5 +1,7 @@ """Decorators for ZHA core registries.""" -from typing import Callable, TypeVar, Union +from __future__ import annotations + +from typing import Callable, TypeVar CALLABLE_T = TypeVar("CALLABLE_T", bound=Callable) # pylint: disable=invalid-name @@ -8,7 +10,7 @@ class DictRegistry(dict): """Dict Registry of items.""" def register( - self, name: Union[int, str], item: Union[str, CALLABLE_T] = None + self, name: int | str, item: str | CALLABLE_T = None ) -> Callable[[CALLABLE_T], CALLABLE_T]: """Return decorator to register item with a specific name.""" @@ -26,7 +28,7 @@ class DictRegistry(dict): class SetRegistry(set): """Set Registry of items.""" - def register(self, name: Union[int, str]) -> Callable[[CALLABLE_T], CALLABLE_T]: + def register(self, name: int | str) -> Callable[[CALLABLE_T], CALLABLE_T]: """Return decorator to register item with a specific name.""" def decorator(channel: CALLABLE_T) -> CALLABLE_T: diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 4f93a7a95d6..65605b2f7a3 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -1,11 +1,13 @@ """Device for Zigbee Home Automation.""" +from __future__ import annotations + import asyncio from datetime import timedelta from enum import Enum import logging import random import time -from typing import Any, Dict +from typing import Any from zigpy import types import zigpy.exceptions @@ -275,7 +277,7 @@ class ZHADevice(LogMixin): self._available = new_availability @property - def zigbee_signature(self) -> Dict[str, Any]: + def zigbee_signature(self) -> dict[str, Any]: """Get zigbee signature for this device.""" return { ATTR_NODE_DESCRIPTOR: str(self._zigpy_device.node_desc), diff --git a/homeassistant/components/zha/core/discovery.py b/homeassistant/components/zha/core/discovery.py index ba970570ecb..338796acffe 100644 --- a/homeassistant/components/zha/core/discovery.py +++ b/homeassistant/components/zha/core/discovery.py @@ -1,8 +1,9 @@ """Device discovery functions for Zigbee Home Automation.""" +from __future__ import annotations from collections import Counter import logging -from typing import Callable, List, Tuple +from typing import Callable from homeassistant import const as ha_const from homeassistant.core import callback @@ -34,10 +35,10 @@ _LOGGER = logging.getLogger(__name__) @callback async def async_add_entities( _async_add_entities: Callable, - entities: List[ - Tuple[ + entities: list[ + tuple[ zha_typing.ZhaEntityType, - Tuple[str, zha_typing.ZhaDeviceType, List[zha_typing.ChannelType]], + tuple[str, zha_typing.ZhaDeviceType, list[zha_typing.ChannelType]], ] ], update_before_add: bool = True, @@ -235,9 +236,9 @@ class GroupProbe: @staticmethod def determine_entity_domains( hass: HomeAssistantType, group: zha_typing.ZhaGroupType - ) -> List[str]: + ) -> list[str]: """Determine the entity domains for this group.""" - entity_domains: List[str] = [] + entity_domains: list[str] = [] zha_gateway = hass.data[zha_const.DATA_ZHA][zha_const.DATA_ZHA_GATEWAY] all_domain_occurrences = [] for member in group.members: diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 3a65787cd48..d8baf89efcf 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -1,4 +1,5 @@ """Virtual gateway for Zigbee Home Automation.""" +from __future__ import annotations import asyncio import collections @@ -9,7 +10,6 @@ import logging import os import time import traceback -from typing import List, Optional from serial import SerialException from zigpy.config import CONF_DEVICE @@ -378,12 +378,12 @@ class ZHAGateway: """Return ZHADevice for given ieee.""" return self._devices.get(ieee) - def get_group(self, group_id: str) -> Optional[ZhaGroupType]: + def get_group(self, group_id: str) -> ZhaGroupType | None: """Return Group for given group id.""" return self.groups.get(group_id) @callback - def async_get_group_by_name(self, group_name: str) -> Optional[ZhaGroupType]: + def async_get_group_by_name(self, group_name: str) -> ZhaGroupType | None: """Get ZHA group by name.""" for group in self.groups.values(): if group.name == group_name: @@ -620,7 +620,7 @@ class ZHAGateway: zha_device.update_available(True) async def async_create_zigpy_group( - self, name: str, members: List[GroupMember] + self, name: str, members: list[GroupMember] ) -> ZhaGroupType: """Create a new Zigpy Zigbee group.""" # we start with two to fill any gaps from a user removing existing groups diff --git a/homeassistant/components/zha/core/group.py b/homeassistant/components/zha/core/group.py index 59277a394b3..beaebbe8767 100644 --- a/homeassistant/components/zha/core/group.py +++ b/homeassistant/components/zha/core/group.py @@ -1,8 +1,10 @@ """Group for Zigbee Home Automation.""" +from __future__ import annotations + import asyncio import collections import logging -from typing import Any, Dict, List +from typing import Any import zigpy.exceptions @@ -58,16 +60,16 @@ class ZHAGroupMember(LogMixin): return self._zha_device @property - def member_info(self) -> Dict[str, Any]: + def member_info(self) -> dict[str, Any]: """Get ZHA group info.""" - member_info: Dict[str, Any] = {} + member_info: dict[str, Any] = {} member_info["endpoint_id"] = self.endpoint_id member_info["device"] = self.device.zha_device_info member_info["entities"] = self.associated_entities return member_info @property - def associated_entities(self) -> List[GroupEntityReference]: + def associated_entities(self) -> list[GroupEntityReference]: """Return the list of entities that were derived from this endpoint.""" ha_entity_registry = self.device.gateway.ha_entity_registry zha_device_registry = self.device.gateway.device_registry @@ -136,7 +138,7 @@ class ZHAGroup(LogMixin): return self._zigpy_group.endpoint @property - def members(self) -> List[ZHAGroupMember]: + def members(self) -> list[ZHAGroupMember]: """Return the ZHA devices that are members of this group.""" return [ ZHAGroupMember( @@ -146,7 +148,7 @@ class ZHAGroup(LogMixin): if member_ieee in self._zha_gateway.devices ] - async def async_add_members(self, members: List[GroupMember]) -> None: + async def async_add_members(self, members: list[GroupMember]) -> None: """Add members to this group.""" if len(members) > 1: tasks = [] @@ -162,7 +164,7 @@ class ZHAGroup(LogMixin): members[0].ieee ].async_add_endpoint_to_group(members[0].endpoint_id, self.group_id) - async def async_remove_members(self, members: List[GroupMember]) -> None: + async def async_remove_members(self, members: list[GroupMember]) -> None: """Remove members from this group.""" if len(members) > 1: tasks = [] @@ -181,18 +183,18 @@ class ZHAGroup(LogMixin): ].async_remove_endpoint_from_group(members[0].endpoint_id, self.group_id) @property - def member_entity_ids(self) -> List[str]: + def member_entity_ids(self) -> list[str]: """Return the ZHA entity ids for all entities for the members of this group.""" - all_entity_ids: List[str] = [] + all_entity_ids: list[str] = [] for member in self.members: entity_references = member.associated_entities for entity_reference in entity_references: all_entity_ids.append(entity_reference["entity_id"]) return all_entity_ids - def get_domain_entity_ids(self, domain) -> List[str]: + def get_domain_entity_ids(self, domain) -> list[str]: """Return entity ids from the entity domain for this group.""" - domain_entity_ids: List[str] = [] + domain_entity_ids: list[str] = [] for member in self.members: if member.device.is_coordinator: continue @@ -207,9 +209,9 @@ class ZHAGroup(LogMixin): return domain_entity_ids @property - def group_info(self) -> Dict[str, Any]: + def group_info(self) -> dict[str, Any]: """Get ZHA group info.""" - group_info: Dict[str, Any] = {} + group_info: dict[str, Any] = {} group_info["group_id"] = self.group_id group_info["name"] = self.name group_info["members"] = [member.member_info for member in self.members] diff --git a/homeassistant/components/zha/core/helpers.py b/homeassistant/components/zha/core/helpers.py index 47911fc1078..cf3d040f020 100644 --- a/homeassistant/components/zha/core/helpers.py +++ b/homeassistant/components/zha/core/helpers.py @@ -4,6 +4,7 @@ Helpers for Zigbee Home Automation. For more details about this component, please refer to the documentation at https://home-assistant.io/integrations/zha/ """ +from __future__ import annotations import asyncio import binascii @@ -13,7 +14,7 @@ import itertools import logging from random import uniform import re -from typing import Any, Callable, Iterator, List, Optional, Tuple +from typing import Any, Callable, Iterator import voluptuous as vol import zigpy.exceptions @@ -67,7 +68,7 @@ async def safe_read( async def get_matched_clusters( source_zha_device: ZhaDeviceType, target_zha_device: ZhaDeviceType -) -> List[BindingPair]: +) -> list[BindingPair]: """Get matched input/output cluster pairs for 2 devices.""" source_clusters = source_zha_device.async_get_std_clusters() target_clusters = target_zha_device.async_get_std_clusters() @@ -131,7 +132,7 @@ async def async_get_zha_device(hass, device_id): return zha_gateway.devices[ieee] -def find_state_attributes(states: List[State], key: str) -> Iterator[Any]: +def find_state_attributes(states: list[State], key: str) -> Iterator[Any]: """Find attributes with matching key from states.""" for state in states: value = state.attributes.get(key) @@ -150,9 +151,9 @@ def mean_tuple(*args): def reduce_attribute( - states: List[State], + states: list[State], key: str, - default: Optional[Any] = None, + default: Any | None = None, reduce: Callable[..., Any] = mean_int, ) -> Any: """Find the first attribute matching key from states. @@ -280,7 +281,7 @@ QR_CODES = ( ) -def qr_to_install_code(qr_code: str) -> Tuple[zigpy.types.EUI64, bytes]: +def qr_to_install_code(qr_code: str) -> tuple[zigpy.types.EUI64, bytes]: """Try to parse the QR code. if successful, return a tuple of a EUI64 address and install code. diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py index 66a9a70e752..2f9ed57745a 100644 --- a/homeassistant/components/zha/core/registries.py +++ b/homeassistant/components/zha/core/registries.py @@ -1,6 +1,8 @@ """Mapping registries for Zigbee Home Automation.""" +from __future__ import annotations + import collections -from typing import Callable, Dict, List, Set, Tuple, Union +from typing import Callable, Dict import attr import zigpy.profiles.zha @@ -134,19 +136,19 @@ def set_or_callable(value): class MatchRule: """Match a ZHA Entity to a channel name or generic id.""" - channel_names: Union[Callable, Set[str], str] = attr.ib( + channel_names: Callable | set[str] | str = attr.ib( factory=frozenset, converter=set_or_callable ) - generic_ids: Union[Callable, Set[str], str] = attr.ib( + generic_ids: Callable | set[str] | str = attr.ib( factory=frozenset, converter=set_or_callable ) - manufacturers: Union[Callable, Set[str], str] = attr.ib( + manufacturers: Callable | set[str] | str = attr.ib( factory=frozenset, converter=set_or_callable ) - models: Union[Callable, Set[str], str] = attr.ib( + models: Callable | set[str] | str = attr.ib( factory=frozenset, converter=set_or_callable ) - aux_channels: Union[Callable, Set[str], str] = attr.ib( + aux_channels: Callable | set[str] | str = attr.ib( factory=frozenset, converter=set_or_callable ) @@ -176,7 +178,7 @@ class MatchRule: weight += 1 * len(self.aux_channels) return weight - def claim_channels(self, channel_pool: List[ChannelType]) -> List[ChannelType]: + def claim_channels(self, channel_pool: list[ChannelType]) -> list[ChannelType]: """Return a list of channels this rule matches + aux channels.""" claimed = [] if isinstance(self.channel_names, frozenset): @@ -189,15 +191,15 @@ class MatchRule: claimed.extend([ch for ch in channel_pool if ch.name in self.aux_channels]) return claimed - def strict_matched(self, manufacturer: str, model: str, channels: List) -> bool: + def strict_matched(self, manufacturer: str, model: str, channels: list) -> bool: """Return True if this device matches the criteria.""" return all(self._matched(manufacturer, model, channels)) - def loose_matched(self, manufacturer: str, model: str, channels: List) -> bool: + def loose_matched(self, manufacturer: str, model: str, channels: list) -> bool: """Return True if this device matches the criteria.""" return any(self._matched(manufacturer, model, channels)) - def _matched(self, manufacturer: str, model: str, channels: List) -> list: + def _matched(self, manufacturer: str, model: str, channels: list) -> list: """Return a list of field matches.""" if not any(attr.asdict(self).values()): return [False] @@ -245,9 +247,9 @@ class ZHAEntityRegistry: component: str, manufacturer: str, model: str, - channels: List[ChannelType], + channels: list[ChannelType], default: CALLABLE_T = None, - ) -> Tuple[CALLABLE_T, List[ChannelType]]: + ) -> tuple[CALLABLE_T, list[ChannelType]]: """Match a ZHA Channels to a ZHA Entity class.""" matches = self._strict_registry[component] for match in sorted(matches, key=lambda x: x.weight, reverse=True): @@ -264,11 +266,11 @@ class ZHAEntityRegistry: def strict_match( self, component: str, - channel_names: Union[Callable, Set[str], str] = None, - generic_ids: Union[Callable, Set[str], str] = None, - manufacturers: Union[Callable, Set[str], str] = None, - models: Union[Callable, Set[str], str] = None, - aux_channels: Union[Callable, Set[str], str] = None, + channel_names: Callable | set[str] | str = None, + generic_ids: Callable | set[str] | str = None, + manufacturers: Callable | set[str] | str = None, + models: Callable | set[str] | str = None, + aux_channels: Callable | set[str] | str = None, ) -> Callable[[CALLABLE_T], CALLABLE_T]: """Decorate a strict match rule.""" @@ -289,11 +291,11 @@ class ZHAEntityRegistry: def loose_match( self, component: str, - channel_names: Union[Callable, Set[str], str] = None, - generic_ids: Union[Callable, Set[str], str] = None, - manufacturers: Union[Callable, Set[str], str] = None, - models: Union[Callable, Set[str], str] = None, - aux_channels: Union[Callable, Set[str], str] = None, + channel_names: Callable | set[str] | str = None, + generic_ids: Callable | set[str] | str = None, + manufacturers: Callable | set[str] | str = None, + models: Callable | set[str] | str = None, + aux_channels: Callable | set[str] | str = None, ) -> Callable[[CALLABLE_T], CALLABLE_T]: """Decorate a loose match rule.""" diff --git a/homeassistant/components/zha/core/store.py b/homeassistant/components/zha/core/store.py index 2db803258bc..43b41153657 100644 --- a/homeassistant/components/zha/core/store.py +++ b/homeassistant/components/zha/core/store.py @@ -1,8 +1,10 @@ """Data storage helper for ZHA.""" +from __future__ import annotations + from collections import OrderedDict import datetime import time -from typing import MutableMapping, Optional, cast +from typing import MutableMapping, cast import attr @@ -24,9 +26,9 @@ TOMBSTONE_LIFETIME = datetime.timedelta(days=60).total_seconds() class ZhaDeviceEntry: """Zha Device storage Entry.""" - name: Optional[str] = attr.ib(default=None) - ieee: Optional[str] = attr.ib(default=None) - last_seen: Optional[float] = attr.ib(default=None) + name: str | None = attr.ib(default=None) + ieee: str | None = attr.ib(default=None) + last_seen: float | None = attr.ib(default=None) class ZhaStorage: diff --git a/homeassistant/components/zha/cover.py b/homeassistant/components/zha/cover.py index e202def46c5..5530cd3e3f5 100644 --- a/homeassistant/components/zha/cover.py +++ b/homeassistant/components/zha/cover.py @@ -1,8 +1,9 @@ """Support for ZHA covers.""" +from __future__ import annotations + import asyncio import functools import logging -from typing import List, Optional from zigpy.zcl.foundation import Status @@ -180,7 +181,7 @@ class Shade(ZhaEntity, CoverEntity): self, unique_id: str, zha_device: ZhaDeviceType, - channels: List[ChannelType], + channels: list[ChannelType], **kwargs, ): """Initialize the ZHA light.""" @@ -199,12 +200,12 @@ class Shade(ZhaEntity, CoverEntity): return self._position @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" return DEVICE_CLASS_SHADE @property - def is_closed(self) -> Optional[bool]: + def is_closed(self) -> bool | None: """Return True if shade is closed.""" if self._is_open is None: return None @@ -289,7 +290,7 @@ class KeenVent(Shade): """Keen vent cover.""" @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" return DEVICE_CLASS_DAMPER diff --git a/homeassistant/components/zha/device_action.py b/homeassistant/components/zha/device_action.py index 46363939190..9d419b16435 100644 --- a/homeassistant/components/zha/device_action.py +++ b/homeassistant/components/zha/device_action.py @@ -1,5 +1,5 @@ """Provides device actions for ZHA devices.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -54,7 +54,7 @@ async def async_call_action_from_config( ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions.""" try: zha_device = await async_get_zha_device(hass, device_id) diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index 9a573e92aa4..445151899ee 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -1,9 +1,10 @@ """Entity for Zigbee Home Automation.""" +from __future__ import annotations import asyncio import functools import logging -from typing import Any, Awaitable, Dict, List, Optional +from typing import Any, Awaitable from homeassistant.const import ATTR_NAME from homeassistant.core import CALLBACK_TYPE, Event, callback @@ -45,9 +46,9 @@ class BaseZhaEntity(LogMixin, entity.Entity): self._should_poll: bool = False self._unique_id: str = unique_id self._state: Any = None - self._extra_state_attributes: Dict[str, Any] = {} + self._extra_state_attributes: dict[str, Any] = {} self._zha_device: ZhaDeviceType = zha_device - self._unsubs: List[CALLABLE_T] = [] + self._unsubs: list[CALLABLE_T] = [] self.remove_future: Awaitable[None] = None @property @@ -66,7 +67,7 @@ class BaseZhaEntity(LogMixin, entity.Entity): return self._zha_device @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return device specific state attributes.""" return self._extra_state_attributes @@ -81,7 +82,7 @@ class BaseZhaEntity(LogMixin, entity.Entity): return self._should_poll @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return a device description for device registry.""" zha_device_info = self._zha_device.device_info ieee = zha_device_info["ieee"] @@ -143,7 +144,7 @@ class ZhaEntity(BaseZhaEntity, RestoreEntity): self, unique_id: str, zha_device: ZhaDeviceType, - channels: List[ChannelType], + channels: list[ChannelType], **kwargs, ): """Init ZHA entity.""" @@ -152,7 +153,7 @@ class ZhaEntity(BaseZhaEntity, RestoreEntity): ch_names = [ch.cluster.ep_attribute for ch in channels] ch_names = ", ".join(sorted(ch_names)) self._name: str = f"{zha_device.name} {ieeetail} {ch_names}" - self.cluster_channels: Dict[str, ChannelType] = {} + self.cluster_channels: dict[str, ChannelType] = {} for channel in channels: self.cluster_channels[channel.name] = channel @@ -217,7 +218,7 @@ class ZhaGroupEntity(BaseZhaEntity): """A base class for ZHA group entities.""" def __init__( - self, entity_ids: List[str], unique_id: str, group_id: int, zha_device, **kwargs + self, entity_ids: list[str], unique_id: str, group_id: int, zha_device, **kwargs ) -> None: """Initialize a light group.""" super().__init__(unique_id, zha_device, **kwargs) @@ -225,8 +226,8 @@ class ZhaGroupEntity(BaseZhaEntity): self._group = zha_device.gateway.groups.get(group_id) self._name = f"{self._group.name}_zha_group_0x{group_id:04x}" self._group_id: int = group_id - self._entity_ids: List[str] = entity_ids - self._async_unsub_state_changed: Optional[CALLBACK_TYPE] = None + self._entity_ids: list[str] = entity_ids + self._async_unsub_state_changed: CALLBACK_TYPE | None = None self._handled_group_membership = False @property diff --git a/homeassistant/components/zha/fan.py b/homeassistant/components/zha/fan.py index ed041f8c6c0..3c50261b565 100644 --- a/homeassistant/components/zha/fan.py +++ b/homeassistant/components/zha/fan.py @@ -1,8 +1,9 @@ """Fans on Zigbee Home Automation networks.""" +from __future__ import annotations + from abc import abstractmethod import functools import math -from typing import List, Optional from zigpy.exceptions import ZigbeeException import zigpy.zcl.clusters.hvac as hvac @@ -77,7 +78,7 @@ class BaseFan(FanEntity): """Base representation of a ZHA fan.""" @property - def preset_modes(self) -> List[str]: + def preset_modes(self) -> list[str]: """Return the available preset modes.""" return PRESET_MODES @@ -103,7 +104,7 @@ class BaseFan(FanEntity): """Turn the entity off.""" await self.async_set_percentage(0) - async def async_set_percentage(self, percentage: Optional[int]) -> None: + async def async_set_percentage(self, percentage: int | None) -> None: """Set the speed percenage of the fan.""" fan_mode = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) await self._async_set_fan_mode(fan_mode) @@ -142,7 +143,7 @@ class ZhaFan(BaseFan, ZhaEntity): ) @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" if ( self._fan_channel.fan_mode is None @@ -154,7 +155,7 @@ class ZhaFan(BaseFan, ZhaEntity): return ranged_value_to_percentage(SPEED_RANGE, self._fan_channel.fan_mode) @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode.""" return PRESET_MODES_TO_NAME.get(self._fan_channel.fan_mode) @@ -174,7 +175,7 @@ class FanGroup(BaseFan, ZhaGroupEntity): """Representation of a fan group.""" def __init__( - self, entity_ids: List[str], unique_id: str, group_id: int, zha_device, **kwargs + self, entity_ids: list[str], unique_id: str, group_id: int, zha_device, **kwargs ) -> None: """Initialize a fan group.""" super().__init__(entity_ids, unique_id, group_id, zha_device, **kwargs) @@ -185,12 +186,12 @@ class FanGroup(BaseFan, ZhaGroupEntity): self._preset_mode = None @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" return self._percentage @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode.""" return self._preset_mode @@ -205,11 +206,11 @@ class FanGroup(BaseFan, ZhaGroupEntity): async def async_update(self): """Attempt to retrieve on off state from the fan.""" all_states = [self.hass.states.get(x) for x in self._entity_ids] - states: List[State] = list(filter(None, all_states)) - percentage_states: List[State] = [ + states: list[State] = list(filter(None, all_states)) + percentage_states: list[State] = [ state for state in states if state.attributes.get(ATTR_PERCENTAGE) ] - preset_mode_states: List[State] = [ + preset_mode_states: list[State] = [ state for state in states if state.attributes.get(ATTR_PRESET_MODE) ] self._available = any(state.state != STATE_UNAVAILABLE for state in states) diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index f81b931a49d..72807458d26 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -1,4 +1,6 @@ """Lights on Zigbee Home Automation networks.""" +from __future__ import annotations + from collections import Counter from datetime import timedelta import enum @@ -6,7 +8,7 @@ import functools import itertools import logging import random -from typing import Any, Dict, List, Optional, Tuple +from typing import Any from zigpy.zcl.clusters.general import Identify, LevelControl, OnOff from zigpy.zcl.clusters.lighting import Color @@ -122,15 +124,15 @@ class BaseLight(LogMixin, light.LightEntity): """Initialize the light.""" super().__init__(*args, **kwargs) self._available: bool = False - self._brightness: Optional[int] = None - self._off_brightness: Optional[int] = None - self._hs_color: Optional[Tuple[float, float]] = None - self._color_temp: Optional[int] = None - self._min_mireds: Optional[int] = 153 - self._max_mireds: Optional[int] = 500 - self._white_value: Optional[int] = None - self._effect_list: Optional[List[str]] = None - self._effect: Optional[str] = None + self._brightness: int | None = None + self._off_brightness: int | None = None + self._hs_color: tuple[float, float] | None = None + self._color_temp: int | None = None + self._min_mireds: int | None = 153 + self._max_mireds: int | None = 500 + self._white_value: int | None = None + self._effect_list: list[str] | None = None + self._effect: str | None = None self._supported_features: int = 0 self._state: bool = False self._on_off_channel = None @@ -139,7 +141,7 @@ class BaseLight(LogMixin, light.LightEntity): self._identify_channel = None @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return state attributes.""" attributes = {"off_brightness": self._off_brightness} return attributes @@ -348,8 +350,8 @@ class Light(BaseLight, ZhaEntity): self._color_channel = self.cluster_channels.get(CHANNEL_COLOR) self._identify_channel = self.zha_device.channels.identify_ch if self._color_channel: - self._min_mireds: Optional[int] = self._color_channel.min_mireds - self._max_mireds: Optional[int] = self._color_channel.max_mireds + self._min_mireds: int | None = self._color_channel.min_mireds + self._max_mireds: int | None = self._color_channel.max_mireds self._cancel_refresh_handle = None effect_list = [] @@ -532,7 +534,7 @@ class LightGroup(BaseLight, ZhaGroupEntity): """Representation of a light group.""" def __init__( - self, entity_ids: List[str], unique_id: str, group_id: int, zha_device, **kwargs + self, entity_ids: list[str], unique_id: str, group_id: int, zha_device, **kwargs ) -> None: """Initialize a light group.""" super().__init__(entity_ids, unique_id, group_id, zha_device, **kwargs) @@ -569,7 +571,7 @@ class LightGroup(BaseLight, ZhaGroupEntity): async def async_update(self) -> None: """Query all members and determine the light group state.""" all_states = [self.hass.states.get(x) for x in self._entity_ids] - states: List[State] = list(filter(None, all_states)) + states: list[State] = list(filter(None, all_states)) on_states = [state for state in states if state.state == STATE_ON] self._state = len(on_states) > 0 diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 926fd4555e1..425a41a1340 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -1,7 +1,9 @@ """Sensors on Zigbee Home Automation networks.""" +from __future__ import annotations + import functools import numbers -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Any, Callable from homeassistant.components.sensor import ( DEVICE_CLASS_BATTERY, @@ -90,18 +92,18 @@ async def async_setup_entry( class Sensor(ZhaEntity): """Base ZHA sensor.""" - SENSOR_ATTR: Optional[Union[int, str]] = None + SENSOR_ATTR: int | str | None = None _decimals: int = 1 - _device_class: Optional[str] = None + _device_class: str | None = None _divisor: int = 1 _multiplier: int = 1 - _unit: Optional[str] = None + _unit: str | None = None def __init__( self, unique_id: str, zha_device: ZhaDeviceType, - channels: List[ChannelType], + channels: list[ChannelType], **kwargs, ): """Init this sensor.""" @@ -121,7 +123,7 @@ class Sensor(ZhaEntity): return self._device_class @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit of measurement of this entity.""" return self._unit @@ -139,7 +141,7 @@ class Sensor(ZhaEntity): """Handle state update from channel.""" self.async_write_ha_state() - def formatter(self, value: int) -> Union[int, float]: + def formatter(self, value: int) -> int | float: """Numeric pass-through formatter.""" if self._decimals > 0: return round( @@ -178,7 +180,7 @@ class Battery(Sensor): return value @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return device state attrs for battery sensors.""" state_attrs = {} battery_size = self._channel.cluster.get("battery_size") @@ -208,7 +210,7 @@ class ElectricalMeasurement(Sensor): """Return True if HA needs to poll for state changes.""" return True - def formatter(self, value: int) -> Union[int, float]: + def formatter(self, value: int) -> int | float: """Return 'normalized' value.""" value = value * self._channel.multiplier / self._channel.divisor if value < 100 and self._channel.divisor > 1: @@ -254,7 +256,7 @@ class SmartEnergyMetering(Sensor): SENSOR_ATTR = "instantaneous_demand" _device_class = DEVICE_CLASS_POWER - def formatter(self, value: int) -> Union[int, float]: + def formatter(self, value: int) -> int | float: """Pass through channel formatter.""" return self._channel.formatter_function(value) diff --git a/homeassistant/components/zha/switch.py b/homeassistant/components/zha/switch.py index 60db9ec0a08..75254f631b9 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -1,6 +1,8 @@ """Switches on Zigbee Home Automation networks.""" +from __future__ import annotations + import functools -from typing import Any, List +from typing import Any from zigpy.zcl.clusters.general import OnOff from zigpy.zcl.foundation import Status @@ -113,7 +115,7 @@ class SwitchGroup(BaseSwitch, ZhaGroupEntity): """Representation of a switch group.""" def __init__( - self, entity_ids: List[str], unique_id: str, group_id: int, zha_device, **kwargs + self, entity_ids: list[str], unique_id: str, group_id: int, zha_device, **kwargs ) -> None: """Initialize a switch group.""" super().__init__(entity_ids, unique_id, group_id, zha_device, **kwargs) @@ -124,7 +126,7 @@ class SwitchGroup(BaseSwitch, ZhaGroupEntity): async def async_update(self) -> None: """Query all members and determine the light group state.""" all_states = [self.hass.states.get(x) for x in self._entity_ids] - states: List[State] = list(filter(None, all_states)) + states: list[State] = list(filter(None, all_states)) on_states = [state for state in states if state.state == STATE_ON] self._state = len(on_states) > 0 diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index e1d48cbe1ff..7e329127d03 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging -from typing import Any, Dict, Optional, cast +from typing import Any, Dict, cast import voluptuous as vol @@ -92,7 +92,7 @@ STORAGE_VERSION = 1 @bind_hass def async_active_zone( hass: HomeAssistant, latitude: float, longitude: float, radius: int = 0 -) -> Optional[State]: +) -> State | None: """Find the active zone for given latitude, longitude. This method must be run in the event loop. @@ -161,22 +161,22 @@ class ZoneStorageCollection(collection.StorageCollection): CREATE_SCHEMA = vol.Schema(CREATE_FIELDS) UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS) - async def _process_create_data(self, data: Dict) -> Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" return cast(Dict, self.CREATE_SCHEMA(data)) @callback - def _get_suggested_id(self, info: Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return cast(str, info[CONF_NAME]) - async def _update_data(self, data: dict, update_data: Dict) -> Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" update_data = self.UPDATE_SCHEMA(update_data) return {**data, **update_data} -async def async_setup(hass: HomeAssistant, config: Dict) -> bool: +async def async_setup(hass: HomeAssistant, config: dict) -> bool: """Set up configured zones as well as Home Assistant zone if necessary.""" component = entity_component.EntityComponent(_LOGGER, DOMAIN, hass) id_manager = collection.IDManager() @@ -240,7 +240,7 @@ async def async_setup(hass: HomeAssistant, config: Dict) -> bool: @callback -def _home_conf(hass: HomeAssistant) -> Dict: +def _home_conf(hass: HomeAssistant) -> dict: """Return the home zone config.""" return { CONF_NAME: hass.config.location_name, @@ -279,15 +279,15 @@ async def async_unload_entry( class Zone(entity.Entity): """Representation of a Zone.""" - def __init__(self, config: Dict): + def __init__(self, config: dict): """Initialize the zone.""" self._config = config self.editable = True - self._attrs: Optional[Dict] = None + self._attrs: dict | None = None self._generate_attrs() @classmethod - def from_yaml(cls, config: Dict) -> Zone: + def from_yaml(cls, config: dict) -> Zone: """Return entity instance initialized from yaml storage.""" zone = cls(config) zone.editable = False @@ -305,17 +305,17 @@ class Zone(entity.Entity): return cast(str, self._config[CONF_NAME]) @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return unique ID.""" return self._config.get(CONF_ID) @property - def icon(self) -> Optional[str]: + def icon(self) -> str | None: """Return the icon if any.""" return self._config.get(CONF_ICON) @property - def state_attributes(self) -> Optional[Dict]: + def state_attributes(self) -> dict | None: """Return the state attributes of the zone.""" return self._attrs @@ -324,7 +324,7 @@ class Zone(entity.Entity): """Zone does not poll.""" return False - async def async_update_config(self, config: Dict) -> None: + async def async_update_config(self, config: dict) -> None: """Handle when the config is updated.""" if self._config == config: return diff --git a/homeassistant/components/zwave/climate.py b/homeassistant/components/zwave/climate.py index 20eb52c2c19..ac8c47af9a3 100644 --- a/homeassistant/components/zwave/climate.py +++ b/homeassistant/components/zwave/climate.py @@ -1,7 +1,8 @@ """Support for Z-Wave climate devices.""" # Because we do not compile openzwave on CI +from __future__ import annotations + import logging -from typing import Optional, Tuple from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( @@ -191,7 +192,7 @@ class ZWaveClimateBase(ZWaveDeviceEntity, ClimateEntity): """Return thermostat mode Z-Wave value.""" raise NotImplementedError() - def _current_mode_setpoints(self) -> Tuple: + def _current_mode_setpoints(self) -> tuple: """Return a tuple of current setpoint Z-Wave value(s).""" raise NotImplementedError() @@ -483,12 +484,12 @@ class ZWaveClimateBase(ZWaveDeviceEntity, ClimateEntity): return self._target_temperature @property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" return self._target_temperature_range[0] @property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" return self._target_temperature_range[1] @@ -590,7 +591,7 @@ class ZWaveClimateSingleSetpoint(ZWaveClimateBase): """Return thermostat mode Z-Wave value.""" return self.values.mode - def _current_mode_setpoints(self) -> Tuple: + def _current_mode_setpoints(self) -> tuple: """Return a tuple of current setpoint Z-Wave value(s).""" return (self.values.primary,) @@ -606,7 +607,7 @@ class ZWaveClimateMultipleSetpoint(ZWaveClimateBase): """Return thermostat mode Z-Wave value.""" return self.values.primary - def _current_mode_setpoints(self) -> Tuple: + def _current_mode_setpoints(self) -> tuple: """Return a tuple of current setpoint Z-Wave value(s).""" current_mode = str(self.values.primary.data).lower() setpoints_names = MODE_SETPOINT_MAPPINGS.get(current_mode, ()) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 2d4db960886..64dcf9614b2 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -1,6 +1,8 @@ """The Z-Wave JS integration.""" +from __future__ import annotations + import asyncio -from typing import Callable, List +from typing import Callable from async_timeout import timeout from zwave_js_server.client import Client as ZwaveClient @@ -226,7 +228,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry_hass_data[DATA_CONNECT_FAILED_LOGGED] = False entry_hass_data[DATA_INVALID_SERVER_VERSION_LOGGED] = False - unsubscribe_callbacks: List[Callable] = [] + unsubscribe_callbacks: list[Callable] = [] entry_hass_data[DATA_CLIENT] = client entry_hass_data[DATA_UNSUBSCRIBE] = unsubscribe_callbacks diff --git a/homeassistant/components/zwave_js/addon.py b/homeassistant/components/zwave_js/addon.py index 818e46a34aa..0c2fdb17944 100644 --- a/homeassistant/components/zwave_js/addon.py +++ b/homeassistant/components/zwave_js/addon.py @@ -3,7 +3,7 @@ from __future__ import annotations import asyncio from functools import partial -from typing import Any, Callable, Optional, TypeVar, cast +from typing import Any, Callable, TypeVar, cast from homeassistant.components.hassio import ( async_create_snapshot, @@ -66,9 +66,9 @@ class AddonManager: def __init__(self, hass: HomeAssistant) -> None: """Set up the add-on manager.""" self._hass = hass - self._install_task: Optional[asyncio.Task] = None - self._start_task: Optional[asyncio.Task] = None - self._update_task: Optional[asyncio.Task] = None + self._install_task: asyncio.Task | None = None + self._start_task: asyncio.Task | None = None + self._update_task: asyncio.Task | None = None def task_in_progress(self) -> bool: """Return True if any of the add-on tasks are in progress.""" diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 055115db7b9..087b94d0060 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -1,7 +1,8 @@ """Websocket API for Z-Wave JS.""" +from __future__ import annotations + import dataclasses import json -from typing import Dict from aiohttp import hdrs, web, web_exceptions import voluptuous as vol @@ -399,7 +400,7 @@ def convert_log_level_to_enum(value: str) -> LogLevel: return LogLevel[value.upper()] -def filename_is_present_if_logging_to_file(obj: Dict) -> Dict: +def filename_is_present_if_logging_to_file(obj: dict) -> dict: """Validate that filename is provided if log_to_file is True.""" if obj.get(LOG_TO_FILE, False) and FILENAME not in obj: raise vol.Invalid("`filename` must be provided if logging to file") diff --git a/homeassistant/components/zwave_js/binary_sensor.py b/homeassistant/components/zwave_js/binary_sensor.py index 8d266c83f22..47c374405b1 100644 --- a/homeassistant/components/zwave_js/binary_sensor.py +++ b/homeassistant/components/zwave_js/binary_sensor.py @@ -1,7 +1,8 @@ """Representation of Z-Wave binary sensors.""" +from __future__ import annotations import logging -from typing import Callable, List, Optional, TypedDict +from typing import Callable, TypedDict from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import CommandClass @@ -56,14 +57,14 @@ class NotificationSensorMapping(TypedDict, total=False): """Represent a notification sensor mapping dict type.""" type: int # required - states: List[str] + states: list[str] device_class: str enabled: bool # Mappings for Notification sensors # https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/notifications.json -NOTIFICATION_SENSOR_MAPPINGS: List[NotificationSensorMapping] = [ +NOTIFICATION_SENSOR_MAPPINGS: list[NotificationSensorMapping] = [ { # NotificationType 1: Smoke Alarm - State Id's 1 and 2 - Smoke detected "type": NOTIFICATION_SMOKE_ALARM, @@ -201,13 +202,13 @@ class PropertySensorMapping(TypedDict, total=False): """Represent a property sensor mapping dict type.""" property_name: str # required - on_states: List[str] # required + on_states: list[str] # required device_class: str enabled: bool # Mappings for property sensors -PROPERTY_SENSOR_MAPPINGS: List[PropertySensorMapping] = [ +PROPERTY_SENSOR_MAPPINGS: list[PropertySensorMapping] = [ { "property_name": PROPERTY_DOOR_STATUS, "on_states": ["open"], @@ -226,7 +227,7 @@ async def async_setup_entry( @callback def async_add_binary_sensor(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Binary Sensor.""" - entities: List[BinarySensorEntity] = [] + entities: list[BinarySensorEntity] = [] if info.platform_hint == "notification": # Get all sensors from Notification CC states @@ -268,14 +269,14 @@ class ZWaveBooleanBinarySensor(ZWaveBaseEntity, BinarySensorEntity): self._name = self.generate_name(include_value_name=True) @property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return if the sensor is on or off.""" if self.info.primary_value.value is None: return None return bool(self.info.primary_value.value) @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return device class.""" if self.info.primary_value.command_class == CommandClass.BATTERY: return DEVICE_CLASS_BATTERY @@ -314,14 +315,14 @@ class ZWaveNotificationBinarySensor(ZWaveBaseEntity, BinarySensorEntity): self._mapping_info = self._get_sensor_mapping() @property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return if the sensor is on or off.""" if self.info.primary_value.value is None: return None return int(self.info.primary_value.value) == int(self.state_key) @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return device class.""" return self._mapping_info.get("device_class") @@ -365,14 +366,14 @@ class ZWavePropertyBinarySensor(ZWaveBaseEntity, BinarySensorEntity): self._name = self.generate_name(include_value_name=True) @property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return if the sensor is on or off.""" if self.info.primary_value.value is None: return None return self.info.primary_value.value in self._mapping_info["on_states"] @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return device class.""" return self._mapping_info.get("device_class") diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 3f6b26f536f..78d76423378 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -1,5 +1,7 @@ """Representation of Z-Wave thermostats.""" -from typing import Any, Callable, Dict, List, Optional, cast +from __future__ import annotations + +from typing import Any, Callable, cast from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import ( @@ -50,7 +52,7 @@ from .entity import ZWaveBaseEntity # Map Z-Wave HVAC Mode to Home Assistant value # Note: We treat "auto" as "heat_cool" as most Z-Wave devices # report auto_changeover as auto without schedule support. -ZW_HVAC_MODE_MAP: Dict[int, str] = { +ZW_HVAC_MODE_MAP: dict[int, str] = { ThermostatMode.OFF: HVAC_MODE_OFF, ThermostatMode.HEAT: HVAC_MODE_HEAT, ThermostatMode.COOL: HVAC_MODE_COOL, @@ -67,7 +69,7 @@ ZW_HVAC_MODE_MAP: Dict[int, str] = { ThermostatMode.FULL_POWER: HVAC_MODE_HEAT, } -HVAC_CURRENT_MAP: Dict[int, str] = { +HVAC_CURRENT_MAP: dict[int, str] = { ThermostatOperatingState.IDLE: CURRENT_HVAC_IDLE, ThermostatOperatingState.PENDING_HEAT: CURRENT_HVAC_IDLE, ThermostatOperatingState.HEATING: CURRENT_HVAC_HEAT, @@ -94,7 +96,7 @@ async def async_setup_entry( @callback def async_add_climate(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Climate.""" - entities: List[ZWaveBaseEntity] = [] + entities: list[ZWaveBaseEntity] = [] entities.append(ZWaveClimate(config_entry, client, info)) async_add_entities(entities) @@ -116,14 +118,14 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): ) -> None: """Initialize lock.""" super().__init__(config_entry, client, info) - self._hvac_modes: Dict[str, Optional[int]] = {} - self._hvac_presets: Dict[str, Optional[int]] = {} - self._unit_value: Optional[ZwaveValue] = None + self._hvac_modes: dict[str, int | None] = {} + self._hvac_presets: dict[str, int | None] = {} + self._unit_value: ZwaveValue | None = None self._current_mode = self.get_zwave_value( THERMOSTAT_MODE_PROPERTY, command_class=CommandClass.THERMOSTAT_MODE ) - self._setpoint_values: Dict[ThermostatSetpointType, ZwaveValue] = {} + self._setpoint_values: dict[ThermostatSetpointType, ZwaveValue] = {} for enum in ThermostatSetpointType: self._setpoint_values[enum] = self.get_zwave_value( THERMOSTAT_SETPOINT_PROPERTY, @@ -184,8 +186,8 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): def _set_modes_and_presets(self) -> None: """Convert Z-Wave Thermostat modes into Home Assistant modes and presets.""" - all_modes: Dict[str, Optional[int]] = {} - all_presets: Dict[str, Optional[int]] = {PRESET_NONE: None} + all_modes: dict[str, int | None] = {} + all_presets: dict[str, int | None] = {PRESET_NONE: None} # Z-Wave uses one list for both modes and presets. # Iterate over all Z-Wave ThermostatModes and extract the hvac modes and presets. @@ -208,7 +210,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): self._hvac_presets = all_presets @property - def _current_mode_setpoint_enums(self) -> List[Optional[ThermostatSetpointType]]: + def _current_mode_setpoint_enums(self) -> list[ThermostatSetpointType | None]: """Return the list of enums that are relevant to the current thermostat mode.""" if self._current_mode is None: # Thermostat(valve) with no support for setting a mode is considered heating-only @@ -238,12 +240,12 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): return ZW_HVAC_MODE_MAP.get(int(self._current_mode.value), HVAC_MODE_HEAT_COOL) @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return list(self._hvac_modes) @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation if supported.""" if not self._operating_state: return None @@ -253,17 +255,17 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): return HVAC_CURRENT_MAP.get(int(self._operating_state.value)) @property - def current_humidity(self) -> Optional[int]: + def current_humidity(self) -> int | None: """Return the current humidity level.""" return self._current_humidity.value if self._current_humidity else None @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._current_temp.value if self._current_temp else None @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" if self._current_mode and self._current_mode.value is None: # guard missing value @@ -275,7 +277,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): return temp.value if temp else None @property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" if self._current_mode and self._current_mode.value is None: # guard missing value @@ -287,7 +289,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): return temp.value if temp else None @property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" if self._current_mode and self._current_mode.value is None: # guard missing value @@ -297,7 +299,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): return None @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" if self._current_mode and self._current_mode.value is None: # guard missing value @@ -310,12 +312,12 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): return PRESET_NONE @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" return list(self._hvac_presets) @property - def fan_mode(self) -> Optional[str]: + def fan_mode(self) -> str | None: """Return the fan setting.""" if ( self._fan_mode @@ -326,14 +328,14 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): return None @property - def fan_modes(self) -> Optional[List[str]]: + def fan_modes(self) -> list[str] | None: """Return the list of available fan modes.""" if self._fan_mode and self._fan_mode.metadata.states: return list(self._fan_mode.metadata.states.values()) return None @property - def extra_state_attributes(self) -> Optional[Dict[str, str]]: + def extra_state_attributes(self) -> dict[str, str] | None: """Return the optional state attributes.""" if ( self._fan_state @@ -373,7 +375,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" - hvac_mode: Optional[str] = kwargs.get(ATTR_HVAC_MODE) + hvac_mode: str | None = kwargs.get(ATTR_HVAC_MODE) if hvac_mode is not None: await self.async_set_hvac_mode(hvac_mode) @@ -381,7 +383,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): setpoint: ZwaveValue = self._setpoint_value( self._current_mode_setpoint_enums[0] ) - target_temp: Optional[float] = kwargs.get(ATTR_TEMPERATURE) + target_temp: float | None = kwargs.get(ATTR_TEMPERATURE) if target_temp is not None: await self.info.node.async_set_value(setpoint, target_temp) elif len(self._current_mode_setpoint_enums) == 2: @@ -391,8 +393,8 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): setpoint_high: ZwaveValue = self._setpoint_value( self._current_mode_setpoint_enums[1] ) - target_temp_low: Optional[float] = kwargs.get(ATTR_TARGET_TEMP_LOW) - target_temp_high: Optional[float] = kwargs.get(ATTR_TARGET_TEMP_HIGH) + target_temp_low: float | None = kwargs.get(ATTR_TARGET_TEMP_LOW) + target_temp_high: float | None = kwargs.get(ATTR_TARGET_TEMP_HIGH) if target_temp_low is not None: await self.info.node.async_set_value(setpoint_low, target_temp_low) if target_temp_high is not None: diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index 8ba2909cf20..75e948004cb 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -1,7 +1,9 @@ """Config flow for Z-Wave JS integration.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Optional, cast +from typing import Any, cast import aiohttp from async_timeout import timeout @@ -76,18 +78,18 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Set up flow instance.""" - self.network_key: Optional[str] = None - self.usb_path: Optional[str] = None + self.network_key: str | None = None + self.usb_path: str | None = None self.use_addon = False - self.ws_address: Optional[str] = None + self.ws_address: str | None = None # If we install the add-on we should uninstall it on entry remove. self.integration_created_addon = False - self.install_task: Optional[asyncio.Task] = None - self.start_task: Optional[asyncio.Task] = None + self.install_task: asyncio.Task | None = None + self.start_task: asyncio.Task | None = None async def async_step_user( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle the initial step.""" if is_hassio(self.hass): # type: ignore # no-untyped-call return await self.async_step_on_supervisor() @@ -95,8 +97,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_manual() async def async_step_manual( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a manual configuration.""" if user_input is None: return self.async_show_form( @@ -133,8 +135,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) async def async_step_hassio( # type: ignore # override - self, discovery_info: Dict[str, Any] - ) -> Dict[str, Any]: + self, discovery_info: dict[str, Any] + ) -> dict[str, Any]: """Receive configuration from add-on discovery info. This flow is triggered by the Z-Wave JS add-on. @@ -151,8 +153,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_hassio_confirm() async def async_step_hassio_confirm( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Confirm the add-on discovery.""" if user_input is not None: return await self.async_step_on_supervisor( @@ -162,7 +164,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="hassio_confirm") @callback - def _async_create_entry_from_vars(self) -> Dict[str, Any]: + def _async_create_entry_from_vars(self) -> dict[str, Any]: """Return a config entry for the flow.""" return self.async_create_entry( title=TITLE, @@ -176,8 +178,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) async def async_step_on_supervisor( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle logic when on Supervisor host.""" if user_input is None: return self.async_show_form( @@ -200,8 +202,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_install_addon() async def async_step_install_addon( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Install Z-Wave JS add-on.""" if not self.install_task: self.install_task = self.hass.async_create_task(self._async_install_addon()) @@ -220,18 +222,18 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_progress_done(next_step_id="configure_addon") async def async_step_install_failed( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Add-on installation failed.""" return self.async_abort(reason="addon_install_failed") async def async_step_configure_addon( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Ask for config for Z-Wave JS add-on.""" addon_config = await self._async_get_addon_config() - errors: Dict[str, str] = {} + errors: dict[str, str] = {} if user_input is not None: self.network_key = user_input[CONF_NETWORK_KEY] @@ -262,8 +264,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) async def async_step_start_addon( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Start Z-Wave JS add-on.""" if not self.start_task: self.start_task = self.hass.async_create_task(self._async_start_addon()) @@ -280,8 +282,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_progress_done(next_step_id="finish_addon_setup") async def async_step_start_failed( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Add-on start failed.""" return self.async_abort(reason="addon_start_failed") @@ -317,8 +319,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) async def async_step_finish_addon_setup( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Prepare info needed to complete the config entry. Get add-on discovery info and server version info. diff --git a/homeassistant/components/zwave_js/cover.py b/homeassistant/components/zwave_js/cover.py index ff77bdb408d..25c69335ed1 100644 --- a/homeassistant/components/zwave_js/cover.py +++ b/homeassistant/components/zwave_js/cover.py @@ -1,6 +1,8 @@ """Support for Z-Wave cover devices.""" +from __future__ import annotations + import logging -from typing import Any, Callable, List, Optional +from typing import Any, Callable from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.value import Value as ZwaveValue @@ -42,7 +44,7 @@ async def async_setup_entry( @callback def async_add_cover(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave cover.""" - entities: List[ZWaveBaseEntity] = [] + entities: list[ZWaveBaseEntity] = [] if info.platform_hint == "motorized_barrier": entities.append(ZwaveMotorizedBarrier(config_entry, client, info)) else: @@ -72,7 +74,7 @@ class ZWaveCover(ZWaveBaseEntity, CoverEntity): """Representation of a Z-Wave Cover device.""" @property - def is_closed(self) -> Optional[bool]: + def is_closed(self) -> bool | None: """Return true if cover is closed.""" if self.info.primary_value.value is None: # guard missing value @@ -80,7 +82,7 @@ class ZWaveCover(ZWaveBaseEntity, CoverEntity): return bool(self.info.primary_value.value == 0) @property - def current_cover_position(self) -> Optional[int]: + def current_cover_position(self) -> int | None: """Return the current position of cover where 0 means closed and 100 is fully open.""" if self.info.primary_value.value is None: # guard missing value @@ -130,31 +132,31 @@ class ZwaveMotorizedBarrier(ZWaveBaseEntity, CoverEntity): ) @property - def supported_features(self) -> Optional[int]: + def supported_features(self) -> int | None: """Flag supported features.""" return SUPPORT_OPEN | SUPPORT_CLOSE @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" return DEVICE_CLASS_GARAGE @property - def is_opening(self) -> Optional[bool]: + def is_opening(self) -> bool | None: """Return if the cover is opening or not.""" if self.info.primary_value.value is None: return None return bool(self.info.primary_value.value == BARRIER_STATE_OPENING) @property - def is_closing(self) -> Optional[bool]: + def is_closing(self) -> bool | None: """Return if the cover is closing or not.""" if self.info.primary_value.value is None: return None return bool(self.info.primary_value.value == BARRIER_STATE_CLOSING) @property - def is_closed(self) -> Optional[bool]: + def is_closed(self) -> bool | None: """Return if the cover is closed or not.""" if self.info.primary_value.value is None: return None diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index f27325b87d2..5f1f04274ba 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -1,7 +1,8 @@ """Map Z-Wave nodes and values to Home Assistant entities.""" +from __future__ import annotations from dataclasses import dataclass -from typing import Generator, List, Optional, Set, Union +from typing import Generator from zwave_js_server.const import CommandClass from zwave_js_server.model.device_class import DeviceClassItem @@ -22,7 +23,7 @@ class ZwaveDiscoveryInfo: # the home assistant platform for which an entity should be created platform: str # hint for the platform about this discovered entity - platform_hint: Optional[str] = "" + platform_hint: str | None = "" @dataclass @@ -35,13 +36,13 @@ class ZWaveValueDiscoverySchema: """ # [optional] the value's command class must match ANY of these values - command_class: Optional[Set[int]] = None + command_class: set[int] | None = None # [optional] the value's endpoint must match ANY of these values - endpoint: Optional[Set[int]] = None + endpoint: set[int] | None = None # [optional] the value's property must match ANY of these values - property: Optional[Set[Union[str, int]]] = None + property: set[str | int] | None = None # [optional] the value's metadata_type must match ANY of these values - type: Optional[Set[str]] = None + type: set[str] | None = None @dataclass @@ -58,25 +59,25 @@ class ZWaveDiscoverySchema: # primary value belonging to this discovery scheme primary_value: ZWaveValueDiscoverySchema # [optional] hint for platform - hint: Optional[str] = None + hint: str | None = None # [optional] the node's manufacturer_id must match ANY of these values - manufacturer_id: Optional[Set[int]] = None + manufacturer_id: set[int] | None = None # [optional] the node's product_id must match ANY of these values - product_id: Optional[Set[int]] = None + product_id: set[int] | None = None # [optional] the node's product_type must match ANY of these values - product_type: Optional[Set[int]] = None + product_type: set[int] | None = None # [optional] the node's firmware_version must match ANY of these values - firmware_version: Optional[Set[str]] = None + firmware_version: set[str] | None = None # [optional] the node's basic device class must match ANY of these values - device_class_basic: Optional[Set[Union[str, int]]] = None + device_class_basic: set[str | int] | None = None # [optional] the node's generic device class must match ANY of these values - device_class_generic: Optional[Set[Union[str, int]]] = None + device_class_generic: set[str | int] | None = None # [optional] the node's specific device class must match ANY of these values - device_class_specific: Optional[Set[Union[str, int]]] = None + device_class_specific: set[str | int] | None = None # [optional] additional values that ALL need to be present on the node for this scheme to pass - required_values: Optional[List[ZWaveValueDiscoverySchema]] = None + required_values: list[ZWaveValueDiscoverySchema] | None = None # [optional] additional values that MAY NOT be present on the node for this scheme to pass - absent_values: Optional[List[ZWaveValueDiscoverySchema]] = None + absent_values: list[ZWaveValueDiscoverySchema] | None = None # [optional] bool to specify if this primary value may be discovered by multiple platforms allow_multi: bool = False @@ -487,7 +488,7 @@ def check_value(value: ZwaveValue, schema: ZWaveValueDiscoverySchema) -> bool: @callback def check_device_class( - device_class: DeviceClassItem, required_value: Optional[Set[Union[str, int]]] + device_class: DeviceClassItem, required_value: set[str | int] | None ) -> bool: """Check if device class id or label matches.""" if required_value is None: diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 34b5940bfea..854fbe45039 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -1,7 +1,7 @@ """Generic Z-Wave Entity Class.""" +from __future__ import annotations import logging -from typing import List, Optional, Union from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.value import Value as ZwaveValue, get_value_id @@ -97,8 +97,8 @@ class ZWaveBaseEntity(Entity): def generate_name( self, include_value_name: bool = False, - alternate_value_name: Optional[str] = None, - additional_info: Optional[List[str]] = None, + alternate_value_name: str | None = None, + additional_info: list[str] | None = None, ) -> str: """Generate entity name.""" if additional_info is None: @@ -167,13 +167,13 @@ class ZWaveBaseEntity(Entity): @callback def get_zwave_value( self, - value_property: Union[str, int], - command_class: Optional[int] = None, - endpoint: Optional[int] = None, - value_property_key: Optional[int] = None, + value_property: str | int, + command_class: int | None = None, + endpoint: int | None = None, + value_property_key: int | None = None, add_to_watched_value_ids: bool = True, check_all_endpoints: bool = False, - ) -> Optional[ZwaveValue]: + ) -> ZwaveValue | None: """Return specific ZwaveValue on this ZwaveNode.""" # use commandclass and endpoint from primary value if omitted return_value = None diff --git a/homeassistant/components/zwave_js/fan.py b/homeassistant/components/zwave_js/fan.py index ea17fbe4cff..100e400f9f7 100644 --- a/homeassistant/components/zwave_js/fan.py +++ b/homeassistant/components/zwave_js/fan.py @@ -1,6 +1,8 @@ """Support for Z-Wave fans.""" +from __future__ import annotations + import math -from typing import Any, Callable, List, Optional +from typing import Any, Callable from zwave_js_server.client import Client as ZwaveClient @@ -36,7 +38,7 @@ async def async_setup_entry( @callback def async_add_fan(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave fan.""" - entities: List[ZWaveBaseEntity] = [] + entities: list[ZWaveBaseEntity] = [] entities.append(ZwaveFan(config_entry, client, info)) async_add_entities(entities) @@ -52,7 +54,7 @@ async def async_setup_entry( class ZwaveFan(ZWaveBaseEntity, FanEntity): """Representation of a Z-Wave fan.""" - async def async_set_percentage(self, percentage: Optional[int]) -> None: + async def async_set_percentage(self, percentage: int | None) -> None: """Set the speed percentage of the fan.""" target_value = self.get_zwave_value("targetValue") @@ -68,9 +70,9 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity): async def async_turn_on( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs: Any, ) -> None: """Turn the device on.""" @@ -82,7 +84,7 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity): await self.info.node.async_set_value(target_value, 0) @property - def is_on(self) -> Optional[bool]: # type: ignore + def is_on(self) -> bool | None: # type: ignore """Return true if device is on (speed above 0).""" if self.info.primary_value.value is None: # guard missing value @@ -90,7 +92,7 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity): return bool(self.info.primary_value.value > 0) @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" if self.info.primary_value.value is None: # guard missing value diff --git a/homeassistant/components/zwave_js/helpers.py b/homeassistant/components/zwave_js/helpers.py index 16baeb816c2..d535a22394c 100644 --- a/homeassistant/components/zwave_js/helpers.py +++ b/homeassistant/components/zwave_js/helpers.py @@ -1,5 +1,7 @@ """Helper functions for Z-Wave JS integration.""" -from typing import List, Tuple, cast +from __future__ import annotations + +from typing import cast from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.node import Node as ZwaveNode @@ -19,13 +21,13 @@ def get_unique_id(home_id: str, value_id: str) -> str: @callback -def get_device_id(client: ZwaveClient, node: ZwaveNode) -> Tuple[str, str]: +def get_device_id(client: ZwaveClient, node: ZwaveNode) -> tuple[str, str]: """Get device registry identifier for Z-Wave node.""" return (DOMAIN, f"{client.driver.controller.home_id}-{node.node_id}") @callback -def get_home_and_node_id_from_device_id(device_id: Tuple[str, str]) -> List[str]: +def get_home_and_node_id_from_device_id(device_id: tuple[str, str]) -> list[str]: """ Get home ID and node ID for Z-Wave device registry entry. diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index d66821c9ba3..6b42c0ef08e 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -1,6 +1,8 @@ """Support for Z-Wave lights.""" +from __future__ import annotations + import logging -from typing import Any, Callable, Dict, Optional, Tuple +from typing import Any, Callable from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import ColorComponent, CommandClass @@ -85,9 +87,9 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): self._supports_color = False self._supports_white_value = False self._supports_color_temp = False - self._hs_color: Optional[Tuple[float, float]] = None - self._white_value: Optional[int] = None - self._color_temp: Optional[int] = None + self._hs_color: tuple[float, float] | None = None + self._white_value: int | None = None + self._color_temp: int | None = None self._min_mireds = 153 # 6500K as a safe default self._max_mireds = 370 # 2700K as a safe default self._supported_features = SUPPORT_BRIGHTNESS @@ -126,17 +128,17 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): return self.brightness > 0 @property - def hs_color(self) -> Optional[Tuple[float, float]]: + def hs_color(self) -> tuple[float, float] | None: """Return the hs color.""" return self._hs_color @property - def white_value(self) -> Optional[int]: + def white_value(self) -> int | None: """Return the white value of this light between 0..255.""" return self._white_value @property - def color_temp(self) -> Optional[int]: + def color_temp(self) -> int | None: """Return the color temperature.""" return self._color_temp @@ -220,7 +222,7 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): """Turn the light off.""" await self._async_set_brightness(0, kwargs.get(ATTR_TRANSITION)) - async def _async_set_colors(self, colors: Dict[ColorComponent, int]) -> None: + async def _async_set_colors(self, colors: dict[ColorComponent, int]) -> None: """Set (multiple) defined colors to given value(s).""" # prefer the (new) combined color property # https://github.com/zwave-js/node-zwave-js/pull/1782 @@ -258,7 +260,7 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): await self.info.node.async_set_value(target_zwave_value, new_value) async def _async_set_brightness( - self, brightness: Optional[int], transition: Optional[int] = None + self, brightness: int | None, transition: int | None = None ) -> None: """Set new brightness to light.""" if brightness is None: @@ -273,9 +275,7 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): # setting a value requires setting targetValue await self.info.node.async_set_value(self._target_value, zwave_brightness) - async def _async_set_transition_duration( - self, duration: Optional[int] = None - ) -> None: + async def _async_set_transition_duration(self, duration: int | None = None) -> None: """Set the transition time for the brightness value.""" if self._dimming_duration is None: return diff --git a/homeassistant/components/zwave_js/lock.py b/homeassistant/components/zwave_js/lock.py index 6f2a1a72c7d..0647885345b 100644 --- a/homeassistant/components/zwave_js/lock.py +++ b/homeassistant/components/zwave_js/lock.py @@ -1,6 +1,8 @@ """Representation of Z-Wave locks.""" +from __future__ import annotations + import logging -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Any, Callable import voluptuous as vol from zwave_js_server.client import Client as ZwaveClient @@ -28,7 +30,7 @@ from .entity import ZWaveBaseEntity LOGGER = logging.getLogger(__name__) -STATE_TO_ZWAVE_MAP: Dict[int, Dict[str, Union[int, bool]]] = { +STATE_TO_ZWAVE_MAP: dict[int, dict[str, int | bool]] = { CommandClass.DOOR_LOCK: { STATE_UNLOCKED: DoorLockMode.UNSECURED, STATE_LOCKED: DoorLockMode.SECURED, @@ -52,7 +54,7 @@ async def async_setup_entry( @callback def async_add_lock(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Lock.""" - entities: List[ZWaveBaseEntity] = [] + entities: list[ZWaveBaseEntity] = [] entities.append(ZWaveLock(config_entry, client, info)) async_add_entities(entities) @@ -88,7 +90,7 @@ class ZWaveLock(ZWaveBaseEntity, LockEntity): """Representation of a Z-Wave lock.""" @property - def is_locked(self) -> Optional[bool]: + def is_locked(self) -> bool | None: """Return true if the lock is locked.""" if self.info.primary_value.value is None: # guard missing value @@ -100,7 +102,7 @@ class ZWaveLock(ZWaveBaseEntity, LockEntity): ) == int(self.info.primary_value.value) async def _set_lock_state( - self, target_state: str, **kwargs: Dict[str, Any] + self, target_state: str, **kwargs: dict[str, Any] ) -> None: """Set the lock state.""" target_value: ZwaveValue = self.get_zwave_value( @@ -112,11 +114,11 @@ class ZWaveLock(ZWaveBaseEntity, LockEntity): STATE_TO_ZWAVE_MAP[self.info.primary_value.command_class][target_state], ) - async def async_lock(self, **kwargs: Dict[str, Any]) -> None: + async def async_lock(self, **kwargs: dict[str, Any]) -> None: """Lock the lock.""" await self._set_lock_state(STATE_LOCKED) - async def async_unlock(self, **kwargs: Dict[str, Any]) -> None: + async def async_unlock(self, **kwargs: dict[str, Any]) -> None: """Unlock the lock.""" await self._set_lock_state(STATE_UNLOCKED) diff --git a/homeassistant/components/zwave_js/migrate.py b/homeassistant/components/zwave_js/migrate.py index 49c18073de5..016aa3066d7 100644 --- a/homeassistant/components/zwave_js/migrate.py +++ b/homeassistant/components/zwave_js/migrate.py @@ -1,6 +1,7 @@ """Functions used to migrate unique IDs for Z-Wave JS entities.""" +from __future__ import annotations + import logging -from typing import List from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.value import Value as ZwaveValue @@ -85,7 +86,7 @@ def async_migrate_discovered_value( @callback -def get_old_value_ids(value: ZwaveValue) -> List[str]: +def get_old_value_ids(value: ZwaveValue) -> list[str]: """Get old value IDs so we can migrate entity unique ID.""" value_ids = [] diff --git a/homeassistant/components/zwave_js/number.py b/homeassistant/components/zwave_js/number.py index 8f8e894cda2..f418ee3d35b 100644 --- a/homeassistant/components/zwave_js/number.py +++ b/homeassistant/components/zwave_js/number.py @@ -1,5 +1,7 @@ """Support for Z-Wave controls using the number platform.""" -from typing import Callable, List, Optional +from __future__ import annotations + +from typing import Callable from zwave_js_server.client import Client as ZwaveClient @@ -22,7 +24,7 @@ async def async_setup_entry( @callback def async_add_number(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave number entity.""" - entities: List[ZWaveBaseEntity] = [] + entities: list[ZWaveBaseEntity] = [] entities.append(ZwaveNumberEntity(config_entry, client, info)) async_add_entities(entities) @@ -66,14 +68,14 @@ class ZwaveNumberEntity(ZWaveBaseEntity, NumberEntity): return float(self.info.primary_value.metadata.max) @property - def value(self) -> Optional[float]: # type: ignore + def value(self) -> float | None: # type: ignore """Return the entity value.""" if self.info.primary_value.value is None: return None return float(self.info.primary_value.value) @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit of measurement of this entity, if any.""" if self.info.primary_value.metadata.unit is None: return None diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index d6cc9aabd49..ada6f6b5d06 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -1,7 +1,8 @@ """Representation of Z-Wave sensors.""" +from __future__ import annotations import logging -from typing import Callable, Dict, List, Optional +from typing import Callable from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import CommandClass @@ -39,7 +40,7 @@ async def async_setup_entry( @callback def async_add_sensor(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Sensor.""" - entities: List[ZWaveBaseEntity] = [] + entities: list[ZWaveBaseEntity] = [] if info.platform_hint == "string_sensor": entities.append(ZWaveStringSensor(config_entry, client, info)) @@ -80,7 +81,7 @@ class ZwaveSensorBase(ZWaveBaseEntity): self._name = self.generate_name(include_value_name=True) @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class of the sensor.""" if self.info.primary_value.command_class == CommandClass.BATTERY: return DEVICE_CLASS_BATTERY @@ -122,14 +123,14 @@ class ZWaveStringSensor(ZwaveSensorBase): """Representation of a Z-Wave String sensor.""" @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return state of the sensor.""" if self.info.primary_value.value is None: return None return str(self.info.primary_value.value) @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return unit of measurement the value is expressed in.""" if self.info.primary_value.metadata.unit is None: return None @@ -161,7 +162,7 @@ class ZWaveNumericSensor(ZwaveSensorBase): return round(float(self.info.primary_value.value), 2) @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return unit of measurement the value is expressed in.""" if self.info.primary_value.metadata.unit is None: return None @@ -191,7 +192,7 @@ class ZWaveListSensor(ZwaveSensorBase): ) @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return state of the sensor.""" if self.info.primary_value.value is None: return None @@ -205,7 +206,7 @@ class ZWaveListSensor(ZwaveSensorBase): ) @property - def extra_state_attributes(self) -> Optional[Dict[str, str]]: + def extra_state_attributes(self) -> dict[str, str] | None: """Return the device specific state attributes.""" # add the value's int value as property for multi-value (list) items return {"value": self.info.primary_value.value} diff --git a/homeassistant/components/zwave_js/services.py b/homeassistant/components/zwave_js/services.py index c971891b35b..1944e4b3dd0 100644 --- a/homeassistant/components/zwave_js/services.py +++ b/homeassistant/components/zwave_js/services.py @@ -1,7 +1,7 @@ """Methods and classes related to executing Z-Wave commands and publishing these to hass.""" +from __future__ import annotations import logging -from typing import Dict, Set, Union import voluptuous as vol from zwave_js_server.model.node import Node as ZwaveNode @@ -20,8 +20,8 @@ _LOGGER = logging.getLogger(__name__) def parameter_name_does_not_need_bitmask( - val: Dict[str, Union[int, str]] -) -> Dict[str, Union[int, str]]: + val: dict[str, int | str] +) -> dict[str, int | str]: """Validate that if a parameter name is provided, bitmask is not as well.""" if isinstance(val[const.ATTR_CONFIG_PARAMETER], str) and ( val.get(const.ATTR_CONFIG_PARAMETER_BITMASK) @@ -88,7 +88,7 @@ class ZWaveServices: async def async_set_config_parameter(self, service: ServiceCall) -> None: """Set a config value on a node.""" - nodes: Set[ZwaveNode] = set() + nodes: set[ZwaveNode] = set() if ATTR_ENTITY_ID in service.data: nodes |= { async_get_node_from_entity_id(self._hass, entity_id) diff --git a/homeassistant/components/zwave_js/switch.py b/homeassistant/components/zwave_js/switch.py index a325e9821f7..e64ea57703d 100644 --- a/homeassistant/components/zwave_js/switch.py +++ b/homeassistant/components/zwave_js/switch.py @@ -1,7 +1,8 @@ """Representation of Z-Wave switches.""" +from __future__ import annotations import logging -from typing import Any, Callable, List, Optional +from typing import Any, Callable from zwave_js_server.client import Client as ZwaveClient @@ -30,7 +31,7 @@ async def async_setup_entry( @callback def async_add_switch(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Switch.""" - entities: List[ZWaveBaseEntity] = [] + entities: list[ZWaveBaseEntity] = [] if info.platform_hint == "barrier_event_signaling_state": entities.append( ZWaveBarrierEventSignalingSwitch(config_entry, client, info) @@ -53,7 +54,7 @@ class ZWaveSwitch(ZWaveBaseEntity, SwitchEntity): """Representation of a Z-Wave switch.""" @property - def is_on(self) -> Optional[bool]: # type: ignore + def is_on(self) -> bool | None: # type: ignore """Return a boolean for the state of the switch.""" if self.info.primary_value.value is None: # guard missing value @@ -85,7 +86,7 @@ class ZWaveBarrierEventSignalingSwitch(ZWaveBaseEntity, SwitchEntity): """Initialize a ZWaveBarrierEventSignalingSwitch entity.""" super().__init__(config_entry, client, info) self._name = self.generate_name(include_value_name=True) - self._state: Optional[bool] = None + self._state: bool | None = None self._update_state() @@ -100,7 +101,7 @@ class ZWaveBarrierEventSignalingSwitch(ZWaveBaseEntity, SwitchEntity): return self._name @property - def is_on(self) -> Optional[bool]: # type: ignore + def is_on(self) -> bool | None: # type: ignore """Return a boolean for the state of the switch.""" return self._state From 54d1e9985feb4c7b9418414cd0e4c4fa9e9fe18b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 15:13:22 +0100 Subject: [PATCH 464/831] Update typing 15 (#48079) --- tests/common.py | 6 ++- tests/components/apache_kafka/test_init.py | 6 ++- tests/components/bond/common.py | 24 ++++++------ tests/components/bond/test_config_flow.py | 8 ++-- tests/components/bond/test_fan.py | 7 ++-- tests/components/cast/test_media_player.py | 9 +++-- tests/components/climate/test_init.py | 5 ++- tests/components/cloudflare/__init__.py | 5 ++- tests/components/deconz/conftest.py | 4 +- tests/components/directv/test_media_player.py | 25 ++++++------ tests/components/dyson/common.py | 6 +-- tests/components/dyson/test_climate.py | 5 +-- tests/components/dyson/test_fan.py | 4 +- tests/components/dyson/test_sensor.py | 7 ++-- tests/components/heos/conftest.py | 12 +++--- tests/components/homekit/test_homekit.py | 5 ++- tests/components/hyperion/__init__.py | 26 ++++++------- tests/components/hyperion/test_config_flow.py | 12 +++--- tests/components/hyperion/test_light.py | 5 ++- tests/components/influxdb/test_sensor.py | 7 ++-- tests/components/litterrobot/conftest.py | 7 ++-- .../components/mysensors/test_config_flow.py | 13 ++++--- tests/components/mysensors/test_init.py | 5 ++- .../components/seventeentrack/test_sensor.py | 5 ++- tests/components/sharkiq/test_vacuum.py | 12 +++--- tests/components/shell_command/test_init.py | 4 +- tests/components/slack/test_notify.py | 5 ++- tests/components/switcher_kis/conftest.py | 5 ++- tests/components/tplink/test_init.py | 11 ++---- tests/components/twinkly/test_twinkly.py | 5 ++- tests/components/unifi/conftest.py | 5 ++- tests/components/vera/common.py | 20 +++++----- tests/components/vera/test_sensor.py | 6 ++- tests/components/vizio/test_media_player.py | 26 +++++++------ tests/components/withings/common.py | 39 ++++++++----------- 35 files changed, 190 insertions(+), 166 deletions(-) diff --git a/tests/common.py b/tests/common.py index df8f8fd2fea..451eee5f997 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,4 +1,6 @@ """Test the helper method for writing tests.""" +from __future__ import annotations + import asyncio import collections from collections import OrderedDict @@ -14,7 +16,7 @@ import threading import time from time import monotonic import types -from typing import Any, Awaitable, Collection, Optional +from typing import Any, Awaitable, Collection from unittest.mock import AsyncMock, Mock, patch import uuid @@ -197,7 +199,7 @@ async def async_test_home_assistant(loop, load_registries=True): """ # To flush out any call_soon_threadsafe await asyncio.sleep(0) - start_time: Optional[float] = None + start_time: float | None = None while len(self._pending_tasks) > max_remaining_tasks: pending = [ diff --git a/tests/components/apache_kafka/test_init.py b/tests/components/apache_kafka/test_init.py index 7e793bce96a..c4285a0cc65 100644 --- a/tests/components/apache_kafka/test_init.py +++ b/tests/components/apache_kafka/test_init.py @@ -1,7 +1,9 @@ """The tests for the Apache Kafka component.""" +from __future__ import annotations + from asyncio import AbstractEventLoop from dataclasses import dataclass -from typing import Callable, Type +from typing import Callable from unittest.mock import patch import pytest @@ -31,7 +33,7 @@ class FilterTest: class MockKafkaClient: """Mock of the Apache Kafka client for testing.""" - init: Callable[[Type[AbstractEventLoop], str, str], None] + init: Callable[[type[AbstractEventLoop], str, str], None] start: Callable[[], None] send_and_wait: Callable[[str, str], None] diff --git a/tests/components/bond/common.py b/tests/components/bond/common.py index 061dc23797e..d8adf134293 100644 --- a/tests/components/bond/common.py +++ b/tests/components/bond/common.py @@ -1,8 +1,10 @@ """Common methods used across tests for Bond.""" +from __future__ import annotations + from asyncio import TimeoutError as AsyncIOTimeoutError from contextlib import nullcontext from datetime import timedelta -from typing import Any, Dict, Optional +from typing import Any from unittest.mock import MagicMock, patch from homeassistant import core @@ -54,14 +56,14 @@ async def setup_bond_entity( async def setup_platform( hass: core.HomeAssistant, platform: str, - discovered_device: Dict[str, Any], + discovered_device: dict[str, Any], *, bond_device_id: str = "bond-device-id", - bond_version: Dict[str, Any] = None, - props: Dict[str, Any] = None, - state: Dict[str, Any] = None, - bridge: Dict[str, Any] = None, - token: Dict[str, Any] = None, + bond_version: dict[str, Any] = None, + props: dict[str, Any] = None, + state: dict[str, Any] = None, + bridge: dict[str, Any] = None, + token: dict[str, Any] = None, ): """Set up the specified Bond platform.""" mock_entry = MockConfigEntry( @@ -89,7 +91,7 @@ async def setup_platform( def patch_bond_version( - enabled: bool = True, return_value: Optional[dict] = None, side_effect=None + enabled: bool = True, return_value: dict | None = None, side_effect=None ): """Patch Bond API version endpoint.""" if not enabled: @@ -106,7 +108,7 @@ def patch_bond_version( def patch_bond_bridge( - enabled: bool = True, return_value: Optional[dict] = None, side_effect=None + enabled: bool = True, return_value: dict | None = None, side_effect=None ): """Patch Bond API bridge endpoint.""" if not enabled: @@ -127,7 +129,7 @@ def patch_bond_bridge( def patch_bond_token( - enabled: bool = True, return_value: Optional[dict] = None, side_effect=None + enabled: bool = True, return_value: dict | None = None, side_effect=None ): """Patch Bond API token endpoint.""" if not enabled: @@ -203,7 +205,7 @@ def patch_bond_device_state(return_value=None, side_effect=None): async def help_test_entity_available( - hass: core.HomeAssistant, domain: str, device: Dict[str, Any], entity_id: str + hass: core.HomeAssistant, domain: str, device: dict[str, Any], entity_id: str ): """Run common test to verify available property.""" await setup_platform(hass, domain, device) diff --git a/tests/components/bond/test_config_flow.py b/tests/components/bond/test_config_flow.py index 39fd1a2db5d..b77c316f152 100644 --- a/tests/components/bond/test_config_flow.py +++ b/tests/components/bond/test_config_flow.py @@ -1,5 +1,7 @@ """Test the Bond config flow.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from unittest.mock import Mock, patch from aiohttp import ClientConnectionError, ClientResponseError @@ -334,8 +336,8 @@ async def _help_test_form_unexpected_error( hass: core.HomeAssistant, *, source: str, - initial_input: Dict[str, Any] = None, - user_input: Dict[str, Any], + initial_input: dict[str, Any] = None, + user_input: dict[str, Any], error: Exception, ): """Test we handle unexpected error gracefully.""" diff --git a/tests/components/bond/test_fan.py b/tests/components/bond/test_fan.py index ca3bc9ac7e7..bd5994f5182 100644 --- a/tests/components/bond/test_fan.py +++ b/tests/components/bond/test_fan.py @@ -1,6 +1,7 @@ """Tests for the Bond fan device.""" +from __future__ import annotations + from datetime import timedelta -from typing import Optional from bond_api import Action, DeviceType, Direction @@ -44,8 +45,8 @@ def ceiling_fan(name: str): async def turn_fan_on( hass: core.HomeAssistant, fan_id: str, - speed: Optional[str] = None, - percentage: Optional[int] = None, + speed: str | None = None, + percentage: int | None = None, ) -> None: """Turn the fan on at the specified speed.""" service_data = {ATTR_ENTITY_ID: fan_id} diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index a3d5676a878..8a6f84580d2 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -1,7 +1,8 @@ """The tests for the Cast Media player platform.""" # pylint: disable=protected-access +from __future__ import annotations + import json -from typing import Optional from unittest.mock import ANY, MagicMock, Mock, patch from uuid import UUID @@ -50,14 +51,14 @@ def get_fake_chromecast(info: ChromecastInfo): def get_fake_chromecast_info( - host="192.168.178.42", port=8009, uuid: Optional[UUID] = FakeUUID + host="192.168.178.42", port=8009, uuid: UUID | None = FakeUUID ): """Generate a Fake ChromecastInfo with the specified arguments.""" @attr.s(slots=True, frozen=True, eq=False) class ExtendedChromecastInfo(ChromecastInfo): - host: Optional[str] = attr.ib(default=None) - port: Optional[int] = attr.ib(default=0) + host: str | None = attr.ib(default=None) + port: int | None = attr.ib(default=0) def __eq__(self, other): if isinstance(other, ChromecastInfo): diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index 8113c1e343a..9473e61a165 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -1,5 +1,6 @@ """The tests for the climate component.""" -from typing import List +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -58,7 +59,7 @@ class MockClimateEntity(ClimateEntity): return HVAC_MODE_HEAT @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes. Need to be a subset of HVAC_MODES. diff --git a/tests/components/cloudflare/__init__.py b/tests/components/cloudflare/__init__.py index c72a9cd84b0..60ce0f055d5 100644 --- a/tests/components/cloudflare/__init__.py +++ b/tests/components/cloudflare/__init__.py @@ -1,5 +1,6 @@ """Tests for the Cloudflare integration.""" -from typing import List +from __future__ import annotations + from unittest.mock import AsyncMock, patch from pycfdns import CFRecord @@ -71,7 +72,7 @@ async def init_integration( def _get_mock_cfupdate( zone: str = MOCK_ZONE, zone_id: str = MOCK_ZONE_ID, - records: List = MOCK_ZONE_RECORDS, + records: list = MOCK_ZONE_RECORDS, ): client = AsyncMock() diff --git a/tests/components/deconz/conftest.py b/tests/components/deconz/conftest.py index a7825b80ea2..7b2c691bcae 100644 --- a/tests/components/deconz/conftest.py +++ b/tests/components/deconz/conftest.py @@ -1,6 +1,6 @@ """deconz conftest.""" +from __future__ import annotations -from typing import Optional from unittest.mock import patch import pytest @@ -13,7 +13,7 @@ def mock_deconz_websocket(): """No real websocket allowed.""" with patch("pydeconz.gateway.WSClient") as mock: - async def make_websocket_call(data: Optional[dict] = None, state: str = ""): + async def make_websocket_call(data: dict | None = None, state: str = ""): """Generate a websocket call.""" pydeconz_gateway_session_handler = mock.call_args[0][3] diff --git a/tests/components/directv/test_media_player.py b/tests/components/directv/test_media_player.py index aeaa245161d..8e7fad62c89 100644 --- a/tests/components/directv/test_media_player.py +++ b/tests/components/directv/test_media_player.py @@ -1,6 +1,7 @@ """The tests for the DirecTV Media player platform.""" +from __future__ import annotations + from datetime import datetime, timedelta -from typing import Optional from unittest.mock import patch from pytest import fixture @@ -77,24 +78,20 @@ def mock_now() -> datetime: return dt_util.utcnow() -async def async_turn_on( - hass: HomeAssistantType, entity_id: Optional[str] = None -) -> None: +async def async_turn_on(hass: HomeAssistantType, entity_id: str | None = None) -> None: """Turn on specified media player or all.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} await hass.services.async_call(MP_DOMAIN, SERVICE_TURN_ON, data) -async def async_turn_off( - hass: HomeAssistantType, entity_id: Optional[str] = None -) -> None: +async def async_turn_off(hass: HomeAssistantType, entity_id: str | None = None) -> None: """Turn off specified media player or all.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} await hass.services.async_call(MP_DOMAIN, SERVICE_TURN_OFF, data) async def async_media_pause( - hass: HomeAssistantType, entity_id: Optional[str] = None + hass: HomeAssistantType, entity_id: str | None = None ) -> None: """Send the media player the command for pause.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} @@ -102,7 +99,7 @@ async def async_media_pause( async def async_media_play( - hass: HomeAssistantType, entity_id: Optional[str] = None + hass: HomeAssistantType, entity_id: str | None = None ) -> None: """Send the media player the command for play/pause.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} @@ -110,7 +107,7 @@ async def async_media_play( async def async_media_stop( - hass: HomeAssistantType, entity_id: Optional[str] = None + hass: HomeAssistantType, entity_id: str | None = None ) -> None: """Send the media player the command for stop.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} @@ -118,7 +115,7 @@ async def async_media_stop( async def async_media_next_track( - hass: HomeAssistantType, entity_id: Optional[str] = None + hass: HomeAssistantType, entity_id: str | None = None ) -> None: """Send the media player the command for next track.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} @@ -126,7 +123,7 @@ async def async_media_next_track( async def async_media_previous_track( - hass: HomeAssistantType, entity_id: Optional[str] = None + hass: HomeAssistantType, entity_id: str | None = None ) -> None: """Send the media player the command for prev track.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} @@ -137,8 +134,8 @@ async def async_play_media( hass: HomeAssistantType, media_type: str, media_id: str, - entity_id: Optional[str] = None, - enqueue: Optional[str] = None, + entity_id: str | None = None, + enqueue: str | None = None, ) -> None: """Send the media player the command for playing media.""" data = {ATTR_MEDIA_CONTENT_TYPE: media_type, ATTR_MEDIA_CONTENT_ID: media_id} diff --git a/tests/components/dyson/common.py b/tests/components/dyson/common.py index b26c48d55f8..4fde47183d2 100644 --- a/tests/components/dyson/common.py +++ b/tests/components/dyson/common.py @@ -1,6 +1,6 @@ """Common utils for Dyson tests.""" +from __future__ import annotations -from typing import Optional, Type from unittest import mock from unittest.mock import MagicMock @@ -37,7 +37,7 @@ CONFIG = { @callback -def async_get_basic_device(spec: Type[DysonDevice]) -> DysonDevice: +def async_get_basic_device(spec: type[DysonDevice]) -> DysonDevice: """Return a basic device with common fields filled out.""" device = MagicMock(spec=spec) device.serial = SERIAL @@ -88,7 +88,7 @@ def async_get_purecool_device() -> DysonPureCool: async def async_update_device( - hass: HomeAssistant, device: DysonDevice, state_type: Optional[Type] = None + hass: HomeAssistant, device: DysonDevice, state_type: type | None = None ) -> None: """Update the device using callback function.""" callbacks = [args[0][0] for args in device.add_message_listener.call_args_list] diff --git a/tests/components/dyson/test_climate.py b/tests/components/dyson/test_climate.py index 90d91092dca..2591b90f596 100644 --- a/tests/components/dyson/test_climate.py +++ b/tests/components/dyson/test_climate.py @@ -1,6 +1,5 @@ """Test the Dyson fan component.""" - -from typing import Type +from __future__ import annotations from libpurecool.const import ( AutoMode, @@ -74,7 +73,7 @@ ENTITY_ID = f"{PLATFORM_DOMAIN}.{ENTITY_NAME}" @callback -def async_get_device(spec: Type[DysonDevice]) -> DysonDevice: +def async_get_device(spec: type[DysonDevice]) -> DysonDevice: """Return a Dyson climate device.""" device = async_get_basic_device(spec) device.state.heat_target = 2900 diff --git a/tests/components/dyson/test_fan.py b/tests/components/dyson/test_fan.py index eb83f319c1f..67149ff7f2e 100644 --- a/tests/components/dyson/test_fan.py +++ b/tests/components/dyson/test_fan.py @@ -1,5 +1,5 @@ """Test the Dyson fan component.""" -from typing import Type +from __future__ import annotations from libpurecool.const import FanMode, FanSpeed, NightMode, Oscillation from libpurecool.dyson_pure_cool import DysonPureCool, DysonPureCoolLink @@ -67,7 +67,7 @@ ENTITY_ID = f"{PLATFORM_DOMAIN}.{ENTITY_NAME}" @callback -def async_get_device(spec: Type[DysonPureCoolLink]) -> DysonPureCoolLink: +def async_get_device(spec: type[DysonPureCoolLink]) -> DysonPureCoolLink: """Return a Dyson fan device.""" if spec == DysonPureCoolLink: return async_get_purecoollink_device() diff --git a/tests/components/dyson/test_sensor.py b/tests/components/dyson/test_sensor.py index a27aeeb99e1..5bd6fd85c3a 100644 --- a/tests/components/dyson/test_sensor.py +++ b/tests/components/dyson/test_sensor.py @@ -1,5 +1,6 @@ """Test the Dyson sensor(s) component.""" -from typing import List, Type +from __future__ import annotations + from unittest.mock import patch from libpurecool.dyson_pure_cool import DysonPureCool @@ -79,7 +80,7 @@ def _async_assign_values( @callback -def async_get_device(spec: Type[DysonPureCoolLink], combi=False) -> DysonPureCoolLink: +def async_get_device(spec: type[DysonPureCoolLink], combi=False) -> DysonPureCoolLink: """Return a device of the given type.""" device = async_get_basic_device(spec) _async_assign_values(device, combi=combi) @@ -113,7 +114,7 @@ def _async_get_entity_id(sensor_type: str) -> str: indirect=["device"], ) async def test_sensors( - hass: HomeAssistant, device: DysonPureCoolLink, sensors: List[str] + hass: HomeAssistant, device: DysonPureCoolLink, sensors: list[str] ) -> None: """Test the sensors.""" # Temperature is given by the device in kelvin diff --git a/tests/components/heos/conftest.py b/tests/components/heos/conftest.py index fa7615e2de8..2c48b7fe8e1 100644 --- a/tests/components/heos/conftest.py +++ b/tests/components/heos/conftest.py @@ -1,5 +1,7 @@ """Configuration for HEOS tests.""" -from typing import Dict, Sequence +from __future__ import annotations + +from typing import Sequence from unittest.mock import Mock, patch as patch from pyheos import Dispatcher, Heos, HeosPlayer, HeosSource, InputSource, const @@ -86,7 +88,7 @@ def player_fixture(quick_selects): @pytest.fixture(name="favorites") -def favorites_fixture() -> Dict[int, HeosSource]: +def favorites_fixture() -> dict[int, HeosSource]: """Create favorites fixture.""" station = Mock(HeosSource) station.type = const.TYPE_STATION @@ -131,7 +133,7 @@ def discovery_data_fixture() -> dict: @pytest.fixture(name="quick_selects") -def quick_selects_fixture() -> Dict[int, str]: +def quick_selects_fixture() -> dict[int, str]: """Create a dict of quick selects for testing.""" return { 1: "Quick Select 1", @@ -153,12 +155,12 @@ def playlists_fixture() -> Sequence[HeosSource]: @pytest.fixture(name="change_data") -def change_data_fixture() -> Dict: +def change_data_fixture() -> dict: """Create player change data for testing.""" return {const.DATA_MAPPED_IDS: {}, const.DATA_NEW: []} @pytest.fixture(name="change_data_mapped_ids") -def change_data_mapped_ids_fixture() -> Dict: +def change_data_mapped_ids_fixture() -> dict: """Create player change data for testing.""" return {const.DATA_MAPPED_IDS: {101: 1}, const.DATA_NEW: []} diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 5895aae351e..fd7d74aeaba 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -1,7 +1,8 @@ """Tests for the HomeKit component.""" +from __future__ import annotations + import asyncio import os -from typing import Dict from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch from pyhap.accessory import Accessory @@ -863,7 +864,7 @@ async def test_homekit_uses_system_zeroconf(hass, hk_driver, mock_zeroconf): await hass.async_block_till_done() -def _write_data(path: str, data: Dict) -> None: +def _write_data(path: str, data: dict) -> None: """Write the data.""" if not os.path.isdir(os.path.dirname(path)): os.makedirs(os.path.dirname(path)) diff --git a/tests/components/hyperion/__init__.py b/tests/components/hyperion/__init__.py index 954d9abc129..e811a4dde32 100644 --- a/tests/components/hyperion/__init__.py +++ b/tests/components/hyperion/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations from types import TracebackType -from typing import Any, Dict, Optional, Type +from typing import Any from unittest.mock import AsyncMock, Mock, patch from hyperion import const @@ -30,25 +30,25 @@ TEST_TITLE = f"{TEST_HOST}:{TEST_PORT}" TEST_TOKEN = "sekr1t" TEST_CONFIG_ENTRY_ID = "74565ad414754616000674c87bdc876c" -TEST_CONFIG_ENTRY_OPTIONS: Dict[str, Any] = {CONF_PRIORITY: TEST_PRIORITY} +TEST_CONFIG_ENTRY_OPTIONS: dict[str, Any] = {CONF_PRIORITY: TEST_PRIORITY} -TEST_INSTANCE_1: Dict[str, Any] = { +TEST_INSTANCE_1: dict[str, Any] = { "friendly_name": "Test instance 1", "instance": 1, "running": True, } -TEST_INSTANCE_2: Dict[str, Any] = { +TEST_INSTANCE_2: dict[str, Any] = { "friendly_name": "Test instance 2", "instance": 2, "running": True, } -TEST_INSTANCE_3: Dict[str, Any] = { +TEST_INSTANCE_3: dict[str, Any] = { "friendly_name": "Test instance 3", "instance": 3, "running": True, } -TEST_AUTH_REQUIRED_RESP: Dict[str, Any] = { +TEST_AUTH_REQUIRED_RESP: dict[str, Any] = { "command": "authorize-tokenRequired", "info": { "required": True, @@ -66,16 +66,16 @@ TEST_AUTH_NOT_REQUIRED_RESP = { class AsyncContextManagerMock(Mock): """An async context manager mock for Hyperion.""" - async def __aenter__(self) -> Optional[AsyncContextManagerMock]: + async def __aenter__(self) -> AsyncContextManagerMock | None: """Enter context manager and connect the client.""" result = await self.async_client_connect() return self if result else None async def __aexit__( self, - exc_type: Optional[Type[BaseException]], - exc: Optional[BaseException], - traceback: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc: BaseException | None, + traceback: TracebackType | None, ) -> None: """Leave context manager and disconnect the client.""" await self.async_client_disconnect() @@ -118,7 +118,7 @@ def create_mock_client() -> Mock: def add_test_config_entry( - hass: HomeAssistantType, data: Optional[Dict[str, Any]] = None + hass: HomeAssistantType, data: dict[str, Any] | None = None ) -> ConfigEntry: """Add a test config entry.""" config_entry: MockConfigEntry = MockConfigEntry( # type: ignore[no-untyped-call] @@ -139,8 +139,8 @@ def add_test_config_entry( async def setup_test_config_entry( hass: HomeAssistantType, - config_entry: Optional[ConfigEntry] = None, - hyperion_client: Optional[Mock] = None, + config_entry: ConfigEntry | None = None, + hyperion_client: Mock | None = None, ) -> ConfigEntry: """Add a test Hyperion entity to hass.""" config_entry = config_entry or add_test_config_entry(hass) diff --git a/tests/components/hyperion/test_config_flow.py b/tests/components/hyperion/test_config_flow.py index beb642792c9..15bca12b03f 100644 --- a/tests/components/hyperion/test_config_flow.py +++ b/tests/components/hyperion/test_config_flow.py @@ -1,5 +1,7 @@ """Tests for the Hyperion config flow.""" -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import Any from unittest.mock import AsyncMock, patch from hyperion import const @@ -40,7 +42,7 @@ from . import ( from tests.common import MockConfigEntry TEST_IP_ADDRESS = "192.168.0.1" -TEST_HOST_PORT: Dict[str, Any] = { +TEST_HOST_PORT: dict[str, Any] = { CONF_HOST: TEST_HOST, CONF_PORT: TEST_PORT, } @@ -122,7 +124,7 @@ async def _create_mock_entry(hass: HomeAssistantType) -> MockConfigEntry: async def _init_flow( hass: HomeAssistantType, source: str = SOURCE_USER, - data: Optional[Dict[str, Any]] = None, + data: dict[str, Any] | None = None, ) -> Any: """Initialize a flow.""" data = data or {} @@ -133,7 +135,7 @@ async def _init_flow( async def _configure_flow( - hass: HomeAssistantType, result: Dict, user_input: Optional[Dict[str, Any]] = None + hass: HomeAssistantType, result: dict, user_input: dict[str, Any] | None = None ) -> Any: """Provide input to a flow.""" user_input = user_input or {} @@ -528,7 +530,7 @@ async def test_ssdp_failure_bad_port_json(hass: HomeAssistantType) -> None: """Check an SSDP flow with bad json port.""" client = create_mock_client() - bad_data: Dict[str, Any] = {**TEST_SSDP_SERVICE_INFO} + bad_data: dict[str, Any] = {**TEST_SSDP_SERVICE_INFO} bad_data["ports"]["jsonServer"] = "not_a_port" with patch( diff --git a/tests/components/hyperion/test_light.py b/tests/components/hyperion/test_light.py index 94f41aa4fd0..e83f5059939 100644 --- a/tests/components/hyperion/test_light.py +++ b/tests/components/hyperion/test_light.py @@ -1,5 +1,6 @@ """Tests for the Hyperion integration.""" -from typing import Optional +from __future__ import annotations + from unittest.mock import AsyncMock, Mock, call, patch from hyperion import const @@ -56,7 +57,7 @@ COLOR_BLACK = color_util.COLORS["black"] def _get_config_entry_from_unique_id( hass: HomeAssistantType, unique_id: str -) -> Optional[ConfigEntry]: +) -> ConfigEntry | None: for entry in hass.config_entries.async_entries(domain=DOMAIN): if TEST_SYSINFO_ID == entry.unique_id: return entry diff --git a/tests/components/influxdb/test_sensor.py b/tests/components/influxdb/test_sensor.py index 57983d14aba..55172011f4f 100644 --- a/tests/components/influxdb/test_sensor.py +++ b/tests/components/influxdb/test_sensor.py @@ -1,7 +1,8 @@ """The tests for the InfluxDB sensor.""" +from __future__ import annotations + from dataclasses import dataclass from datetime import timedelta -from typing import Dict, List, Type from unittest.mock import MagicMock, patch from influxdb.exceptions import InfluxDBClientError, InfluxDBServerError @@ -55,14 +56,14 @@ BASE_V2_QUERY = {"queries_flux": [{"name": "test", "query": "query"}]} class Record: """Record in a Table.""" - values: Dict + values: dict @dataclass class Table: """Table in an Influx 2 resultset.""" - records: List[Type[Record]] + records: list[type[Record]] @pytest.fixture(name="mock_client") diff --git a/tests/components/litterrobot/conftest.py b/tests/components/litterrobot/conftest.py index aadf7d810aa..11ed66fcb52 100644 --- a/tests/components/litterrobot/conftest.py +++ b/tests/components/litterrobot/conftest.py @@ -1,5 +1,6 @@ """Configure pytest for Litter-Robot tests.""" -from typing import Optional +from __future__ import annotations + from unittest.mock import AsyncMock, MagicMock, patch import pylitterbot @@ -13,7 +14,7 @@ from .common import CONFIG, ROBOT_DATA from tests.common import MockConfigEntry -def create_mock_robot(unit_status_code: Optional[str] = None): +def create_mock_robot(unit_status_code: str | None = None): """Create a mock Litter-Robot device.""" if not ( unit_status_code @@ -32,7 +33,7 @@ def create_mock_robot(unit_status_code: Optional[str] = None): return robot -def create_mock_account(unit_status_code: Optional[str] = None): +def create_mock_account(unit_status_code: str | None = None): """Create a mock Litter-Robot account.""" account = MagicMock(spec=pylitterbot.Account) account.connect = AsyncMock() diff --git a/tests/components/mysensors/test_config_flow.py b/tests/components/mysensors/test_config_flow.py index 5fd9e3e7ea1..e4c4016d11a 100644 --- a/tests/components/mysensors/test_config_flow.py +++ b/tests/components/mysensors/test_config_flow.py @@ -1,5 +1,6 @@ """Test the MySensors config flow.""" -from typing import Dict, Optional, Tuple +from __future__ import annotations + from unittest.mock import patch import pytest @@ -349,7 +350,7 @@ async def test_config_invalid( hass: HomeAssistantType, gateway_type: ConfGatewayType, expected_step_id: str, - user_input: Dict[str, any], + user_input: dict[str, any], err_field, err_string, ): @@ -420,7 +421,7 @@ async def test_config_invalid( }, ], ) -async def test_import(hass: HomeAssistantType, user_input: Dict): +async def test_import(hass: HomeAssistantType, user_input: dict): """Test importing a gateway.""" await setup.async_setup_component(hass, "persistent_notification", {}) @@ -712,9 +713,9 @@ async def test_import(hass: HomeAssistantType, user_input: Dict): ) async def test_duplicate( hass: HomeAssistantType, - first_input: Dict, - second_input: Dict, - expected_result: Optional[Tuple[str, str]], + first_input: dict, + second_input: dict, + expected_result: tuple[str, str] | None, ): """Test duplicate detection.""" await setup.async_setup_component(hass, "persistent_notification", {}) diff --git a/tests/components/mysensors/test_init.py b/tests/components/mysensors/test_init.py index 2775b73efd6..c85c627df9f 100644 --- a/tests/components/mysensors/test_init.py +++ b/tests/components/mysensors/test_init.py @@ -1,5 +1,6 @@ """Test function in __init__.py.""" -from typing import Dict +from __future__ import annotations + from unittest.mock import patch import pytest @@ -229,7 +230,7 @@ async def test_import( config: ConfigType, expected_calls: int, expected_to_succeed: bool, - expected_config_flow_user_input: Dict[str, any], + expected_config_flow_user_input: dict[str, any], ): """Test importing a gateway.""" with patch("sys.platform", "win32"), patch( diff --git a/tests/components/seventeentrack/test_sensor.py b/tests/components/seventeentrack/test_sensor.py index 6519b435c0a..65e40d02aff 100644 --- a/tests/components/seventeentrack/test_sensor.py +++ b/tests/components/seventeentrack/test_sensor.py @@ -1,6 +1,7 @@ """Tests for the seventeentrack sensor.""" +from __future__ import annotations + import datetime -from typing import Union from unittest.mock import MagicMock, patch from py17track.package import Package @@ -100,7 +101,7 @@ class ProfileMock: return self.__class__.login_result async def packages( - self, package_state: Union[int, str] = "", show_archived: bool = False + self, package_state: int | str = "", show_archived: bool = False ) -> list: """Packages mock.""" return self.__class__.package_list[:] diff --git a/tests/components/sharkiq/test_vacuum.py b/tests/components/sharkiq/test_vacuum.py index fb35d9d4cd2..b36359ed31a 100644 --- a/tests/components/sharkiq/test_vacuum.py +++ b/tests/components/sharkiq/test_vacuum.py @@ -1,7 +1,9 @@ """Test the Shark IQ vacuum entity.""" +from __future__ import annotations + from copy import deepcopy import enum -from typing import Any, Iterable, List, Optional +from typing import Any, Iterable from unittest.mock import patch import pytest @@ -80,11 +82,11 @@ class MockAyla(AylaApi): async def async_sign_in(self): """Instead of signing in, just return.""" - async def async_list_devices(self) -> List[dict]: + async def async_list_devices(self) -> list[dict]: """Return the device list.""" return [SHARK_DEVICE_DICT] - async def async_get_devices(self, update: bool = True) -> List[SharkIqVacuum]: + async def async_get_devices(self, update: bool = True) -> list[SharkIqVacuum]: """Get the list of devices.""" shark = MockShark(self, SHARK_DEVICE_DICT) shark.properties_full = deepcopy(SHARK_PROPERTIES_DICT) @@ -98,7 +100,7 @@ class MockAyla(AylaApi): class MockShark(SharkIqVacuum): """Mocked SharkIqVacuum that won't hit the API.""" - async def async_update(self, property_list: Optional[Iterable[str]] = None): + async def async_update(self, property_list: Iterable[str] | None = None): """Don't do anything.""" def set_property_value(self, property_name, value): @@ -224,7 +226,7 @@ async def test_locate(hass): ) @patch("sharkiqpy.ayla_api.AylaApi", MockAyla) async def test_coordinator_updates( - hass: HomeAssistant, side_effect: Optional[Exception], success: bool + hass: HomeAssistant, side_effect: Exception | None, success: bool ) -> None: """Test the update coordinator update functions.""" coordinator = hass.data[DOMAIN][ENTRY_ID] diff --git a/tests/components/shell_command/test_init.py b/tests/components/shell_command/test_init.py index 928c186bc11..d4581ae1fc7 100644 --- a/tests/components/shell_command/test_init.py +++ b/tests/components/shell_command/test_init.py @@ -1,8 +1,8 @@ """The tests for the Shell command component.""" +from __future__ import annotations import os import tempfile -from typing import Tuple from unittest.mock import MagicMock, patch from homeassistant.components import shell_command @@ -12,7 +12,7 @@ from homeassistant.setup import async_setup_component def mock_process_creator(error: bool = False): """Mock a coroutine that creates a process when yielded.""" - async def communicate() -> Tuple[bytes, bytes]: + async def communicate() -> tuple[bytes, bytes]: """Mock a coroutine that runs a process when yielded. Returns a tuple of (stdout, stderr). diff --git a/tests/components/slack/test_notify.py b/tests/components/slack/test_notify.py index 9fc6784a09e..6c353cf8fc6 100644 --- a/tests/components/slack/test_notify.py +++ b/tests/components/slack/test_notify.py @@ -1,7 +1,8 @@ """Test slack notifications.""" +from __future__ import annotations + import copy import logging -from typing import List from unittest.mock import AsyncMock, Mock, patch from _pytest.logging import LogCaptureFixture @@ -39,7 +40,7 @@ DEFAULT_CONFIG = { } -def filter_log_records(caplog: LogCaptureFixture) -> List[logging.LogRecord]: +def filter_log_records(caplog: LogCaptureFixture) -> list[logging.LogRecord]: """Filter all unrelated log records.""" return [ rec for rec in caplog.records if rec.name.endswith(f"{DOMAIN}.{notify.DOMAIN}") diff --git a/tests/components/switcher_kis/conftest.py b/tests/components/switcher_kis/conftest.py index e7303a20ea5..fda5f39922d 100644 --- a/tests/components/switcher_kis/conftest.py +++ b/tests/components/switcher_kis/conftest.py @@ -1,8 +1,9 @@ """Common fixtures and objects for the Switcher integration tests.""" +from __future__ import annotations from asyncio import Queue from datetime import datetime -from typing import Any, Generator, Optional +from typing import Any, Generator from unittest.mock import AsyncMock, patch from pytest import fixture @@ -56,7 +57,7 @@ class MockSwitcherV2Device: return DUMMY_DEVICE_STATE @property - def remaining_time(self) -> Optional[str]: + def remaining_time(self) -> str | None: """Return the time left to auto-off.""" return DUMMY_REMAINING_TIME diff --git a/tests/components/tplink/test_init.py b/tests/components/tplink/test_init.py index 8dbe7481339..49309a6ecef 100644 --- a/tests/components/tplink/test_init.py +++ b/tests/components/tplink/test_init.py @@ -1,5 +1,7 @@ """Tests for the TP-Link component.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from unittest.mock import MagicMock, patch from pyHS100 import SmartBulb, SmartDevice, SmartDeviceException, SmartPlug @@ -88,25 +90,20 @@ class UnknownSmartDevice(SmartDevice): @property def has_emeter(self) -> bool: """Do nothing.""" - pass def turn_off(self) -> None: """Do nothing.""" - pass def turn_on(self) -> None: """Do nothing.""" - pass @property def is_on(self) -> bool: """Do nothing.""" - pass @property - def state_information(self) -> Dict[str, Any]: + def state_information(self) -> dict[str, Any]: """Do nothing.""" - pass async def test_configuring_devices_from_multiple_sources(hass): diff --git a/tests/components/twinkly/test_twinkly.py b/tests/components/twinkly/test_twinkly.py index d4afe02c11b..fcbbdb035c7 100644 --- a/tests/components/twinkly/test_twinkly.py +++ b/tests/components/twinkly/test_twinkly.py @@ -1,5 +1,6 @@ """Tests for the integration of a twinly device.""" -from typing import Tuple +from __future__ import annotations + from unittest.mock import patch from homeassistant.components.twinkly.const import ( @@ -190,7 +191,7 @@ async def test_unload(hass: HomeAssistant): async def _create_entries( hass: HomeAssistant, client=None -) -> Tuple[RegistryEntry, DeviceEntry, ClientMock]: +) -> tuple[RegistryEntry, DeviceEntry, ClientMock]: client = ClientMock() if client is None else client def get_client_mock(client, _): diff --git a/tests/components/unifi/conftest.py b/tests/components/unifi/conftest.py index 83dc99fdaf8..81af3f7243f 100644 --- a/tests/components/unifi/conftest.py +++ b/tests/components/unifi/conftest.py @@ -1,5 +1,6 @@ """Fixtures for UniFi methods.""" -from typing import Optional +from __future__ import annotations + from unittest.mock import patch from aiounifi.websocket import SIGNAL_CONNECTION_STATE, SIGNAL_DATA @@ -11,7 +12,7 @@ def mock_unifi_websocket(): """No real websocket allowed.""" with patch("aiounifi.controller.WSClient") as mock: - def make_websocket_call(data: Optional[dict] = None, state: str = ""): + def make_websocket_call(data: dict | None = None, state: str = ""): """Generate a websocket call.""" if data: mock.return_value.data = data diff --git a/tests/components/vera/common.py b/tests/components/vera/common.py index 43ef154b588..e666d513897 100644 --- a/tests/components/vera/common.py +++ b/tests/components/vera/common.py @@ -1,6 +1,8 @@ """Common code for tests.""" +from __future__ import annotations + from enum import Enum -from typing import Callable, Dict, NamedTuple, Tuple +from typing import Callable, NamedTuple from unittest.mock import MagicMock import pyvera as pv @@ -29,7 +31,7 @@ class ControllerData(NamedTuple): class ComponentData(NamedTuple): """Test data about the vera component.""" - controller_data: Tuple[ControllerData] + controller_data: tuple[ControllerData] class ConfigSource(Enum): @@ -43,12 +45,12 @@ class ConfigSource(Enum): class ControllerConfig(NamedTuple): """Test config for mocking a vera controller.""" - config: Dict - options: Dict + config: dict + options: dict config_source: ConfigSource serial_number: str - devices: Tuple[pv.VeraDevice, ...] - scenes: Tuple[pv.VeraScene, ...] + devices: tuple[pv.VeraDevice, ...] + scenes: tuple[pv.VeraScene, ...] setup_callback: SetupCallback legacy_entity_unique_id: bool @@ -58,8 +60,8 @@ def new_simple_controller_config( options: dict = None, config_source=ConfigSource.CONFIG_FLOW, serial_number="1111", - devices: Tuple[pv.VeraDevice, ...] = (), - scenes: Tuple[pv.VeraScene, ...] = (), + devices: tuple[pv.VeraDevice, ...] = (), + scenes: tuple[pv.VeraScene, ...] = (), setup_callback: SetupCallback = None, legacy_entity_unique_id=False, ) -> ControllerConfig: @@ -87,7 +89,7 @@ class ComponentFactory: self, hass: HomeAssistant, controller_config: ControllerConfig = None, - controller_configs: Tuple[ControllerConfig] = (), + controller_configs: tuple[ControllerConfig] = (), ) -> ComponentData: """Configure the component with multiple specific mock data.""" configs = list(controller_configs) diff --git a/tests/components/vera/test_sensor.py b/tests/components/vera/test_sensor.py index 62639df3a35..566a3db482e 100644 --- a/tests/components/vera/test_sensor.py +++ b/tests/components/vera/test_sensor.py @@ -1,5 +1,7 @@ """Vera tests.""" -from typing import Any, Callable, Tuple +from __future__ import annotations + +from typing import Any, Callable from unittest.mock import MagicMock import pyvera as pv @@ -15,7 +17,7 @@ async def run_sensor_test( vera_component_factory: ComponentFactory, category: int, class_property: str, - assert_states: Tuple[Tuple[Any, Any]], + assert_states: tuple[tuple[Any, Any]], assert_unit_of_measurement: str = None, setup_callback: Callable[[pv.VeraController], None] = None, ) -> None: diff --git a/tests/components/vizio/test_media_player.py b/tests/components/vizio/test_media_player.py index 6d0ba2781e6..48a1b5b464f 100644 --- a/tests/components/vizio/test_media_player.py +++ b/tests/components/vizio/test_media_player.py @@ -1,7 +1,9 @@ """Tests for Vizio config flow.""" +from __future__ import annotations + from contextlib import asynccontextmanager from datetime import timedelta -from typing import Any, Dict, List, Optional +from typing import Any from unittest.mock import call, patch import pytest @@ -87,7 +89,7 @@ async def _add_config_entry_to_hass( await hass.async_block_till_done() -def _get_ha_power_state(vizio_power_state: Optional[bool]) -> str: +def _get_ha_power_state(vizio_power_state: bool | None) -> str: """Return HA power state given Vizio power state.""" if vizio_power_state: return STATE_ON @@ -98,7 +100,7 @@ def _get_ha_power_state(vizio_power_state: Optional[bool]) -> str: return STATE_UNAVAILABLE -def _assert_sources_and_volume(attr: Dict[str, Any], vizio_device_class: str) -> None: +def _assert_sources_and_volume(attr: dict[str, Any], vizio_device_class: str) -> None: """Assert source list, source, and volume level based on attr dict and device class.""" assert attr["source_list"] == INPUT_LIST assert attr["source"] == CURRENT_INPUT @@ -111,7 +113,7 @@ def _assert_sources_and_volume(attr: Dict[str, Any], vizio_device_class: str) -> def _get_attr_and_assert_base_attr( hass: HomeAssistantType, device_class: str, power_state: str -) -> Dict[str, Any]: +) -> dict[str, Any]: """Return entity attributes after asserting name, device class, and power state.""" attr = hass.states.get(ENTITY_ID).attributes assert attr["friendly_name"] == NAME @@ -123,7 +125,7 @@ def _get_attr_and_assert_base_attr( @asynccontextmanager async def _cm_for_test_setup_without_apps( - all_settings: Dict[str, Any], vizio_power_state: Optional[bool] + all_settings: dict[str, Any], vizio_power_state: bool | None ) -> None: """Context manager to setup test for Vizio devices without including app specific patches.""" with patch( @@ -140,7 +142,7 @@ async def _cm_for_test_setup_without_apps( async def _test_setup_tv( - hass: HomeAssistantType, vizio_power_state: Optional[bool] + hass: HomeAssistantType, vizio_power_state: bool | None ) -> None: """Test Vizio TV entity setup.""" ha_power_state = _get_ha_power_state(vizio_power_state) @@ -164,7 +166,7 @@ async def _test_setup_tv( async def _test_setup_speaker( - hass: HomeAssistantType, vizio_power_state: Optional[bool] + hass: HomeAssistantType, vizio_power_state: bool | None ) -> None: """Test Vizio Speaker entity setup.""" ha_power_state = _get_ha_power_state(vizio_power_state) @@ -201,7 +203,7 @@ async def _test_setup_speaker( @asynccontextmanager async def _cm_for_test_setup_tv_with_apps( - hass: HomeAssistantType, device_config: Dict[str, Any], app_config: Dict[str, Any] + hass: HomeAssistantType, device_config: dict[str, Any], app_config: dict[str, Any] ) -> None: """Context manager to setup test for Vizio TV with support for apps.""" config_entry = MockConfigEntry( @@ -229,7 +231,7 @@ async def _cm_for_test_setup_tv_with_apps( def _assert_source_list_with_apps( - list_to_test: List[str], attr: Dict[str, Any] + list_to_test: list[str], attr: dict[str, Any] ) -> None: """Assert source list matches list_to_test after removing INPUT_APPS from list.""" for app_to_remove in INPUT_APPS: @@ -244,7 +246,7 @@ async def _test_service( domain: str, vizio_func_name: str, ha_service_name: str, - additional_service_data: Optional[Dict[str, Any]], + additional_service_data: dict[str, Any] | None, *args, **kwargs, ) -> None: @@ -460,8 +462,8 @@ async def test_options_update( async def _test_update_availability_switch( hass: HomeAssistantType, - initial_power_state: Optional[bool], - final_power_state: Optional[bool], + initial_power_state: bool | None, + final_power_state: bool | None, caplog: pytest.fixture, ) -> None: now = dt_util.utcnow() diff --git a/tests/components/withings/common.py b/tests/components/withings/common.py index 80e6d07654c..4d3552bf662 100644 --- a/tests/components/withings/common.py +++ b/tests/components/withings/common.py @@ -1,6 +1,7 @@ """Common data for for the withings component tests.""" +from __future__ import annotations + from dataclasses import dataclass -from typing import List, Optional, Tuple, Union from unittest.mock import MagicMock from urllib.parse import urlparse @@ -49,27 +50,21 @@ class ProfileConfig: profile: str user_id: int - api_response_user_get_device: Union[UserGetDeviceResponse, Exception] - api_response_measure_get_meas: Union[MeasureGetMeasResponse, Exception] - api_response_sleep_get_summary: Union[SleepGetSummaryResponse, Exception] - api_response_notify_list: Union[NotifyListResponse, Exception] - api_response_notify_revoke: Optional[Exception] + api_response_user_get_device: UserGetDeviceResponse | Exception + api_response_measure_get_meas: MeasureGetMeasResponse | Exception + api_response_sleep_get_summary: SleepGetSummaryResponse | Exception + api_response_notify_list: NotifyListResponse | Exception + api_response_notify_revoke: Exception | None def new_profile_config( profile: str, user_id: int, - api_response_user_get_device: Optional[ - Union[UserGetDeviceResponse, Exception] - ] = None, - api_response_measure_get_meas: Optional[ - Union[MeasureGetMeasResponse, Exception] - ] = None, - api_response_sleep_get_summary: Optional[ - Union[SleepGetSummaryResponse, Exception] - ] = None, - api_response_notify_list: Optional[Union[NotifyListResponse, Exception]] = None, - api_response_notify_revoke: Optional[Exception] = None, + api_response_user_get_device: UserGetDeviceResponse | Exception | None = None, + api_response_measure_get_meas: MeasureGetMeasResponse | Exception | None = None, + api_response_sleep_get_summary: SleepGetSummaryResponse | Exception | None = None, + api_response_notify_list: NotifyListResponse | Exception | None = None, + api_response_notify_revoke: Exception | None = None, ) -> ProfileConfig: """Create a new profile config immutable object.""" return ProfileConfig( @@ -118,13 +113,13 @@ class ComponentFactory: self._aioclient_mock = aioclient_mock self._client_id = None self._client_secret = None - self._profile_configs: Tuple[ProfileConfig, ...] = () + self._profile_configs: tuple[ProfileConfig, ...] = () async def configure_component( self, client_id: str = "my_client_id", client_secret: str = "my_client_secret", - profile_configs: Tuple[ProfileConfig, ...] = (), + profile_configs: tuple[ProfileConfig, ...] = (), ) -> None: """Configure the wihings component.""" self._client_id = client_id @@ -294,7 +289,7 @@ class ComponentFactory: def get_config_entries_for_user_id( hass: HomeAssistant, user_id: int -) -> Tuple[ConfigEntry]: +) -> tuple[ConfigEntry]: """Get a list of config entries that apply to a specific withings user.""" return tuple( [ @@ -305,7 +300,7 @@ def get_config_entries_for_user_id( ) -def async_get_flow_for_user_id(hass: HomeAssistant, user_id: int) -> List[dict]: +def async_get_flow_for_user_id(hass: HomeAssistant, user_id: int) -> list[dict]: """Get a flow for a user id.""" return [ flow @@ -316,7 +311,7 @@ def async_get_flow_for_user_id(hass: HomeAssistant, user_id: int) -> List[dict]: def get_data_manager_by_user_id( hass: HomeAssistant, user_id: int -) -> Optional[DataManager]: +) -> DataManager | None: """Get a data manager by the user id.""" return next( iter( From 53687c766da66c6b9e86c3bc9fa33b7739269e8e Mon Sep 17 00:00:00 2001 From: elyobelyob Date: Thu, 18 Mar 2021 16:02:38 +0000 Subject: [PATCH 465/831] Add URL input for Prowl (#46427) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Joakim Sørensen --- homeassistant/components/prowl/notify.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/prowl/notify.py b/homeassistant/components/prowl/notify.py index 5d2efe76607..725c3b9de30 100644 --- a/homeassistant/components/prowl/notify.py +++ b/homeassistant/components/prowl/notify.py @@ -48,6 +48,8 @@ class ProwlNotificationService(BaseNotificationService): "description": message, "priority": data["priority"] if data and "priority" in data else 0, } + if data.get("url"): + payload["url"] = data["url"] _LOGGER.debug("Attempting call Prowl service at %s", url) session = async_get_clientsession(self._hass) From 9fca001eed7e22456a334f9a16fea7a14b02b652 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 18 Mar 2021 13:12:33 -0400 Subject: [PATCH 466/831] Bump zwave-js-server-python to 0.22.0 (#48085) --- .../components/zwave_js/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zwave_js/test_api.py | 16 ++--- tests/components/zwave_js/test_climate.py | 50 +++++++-------- tests/components/zwave_js/test_cover.py | 52 ++++++++-------- tests/components/zwave_js/test_fan.py | 22 +++---- tests/components/zwave_js/test_light.py | 62 +++++++++---------- tests/components/zwave_js/test_lock.py | 24 +++---- tests/components/zwave_js/test_number.py | 6 +- tests/components/zwave_js/test_services.py | 34 +++++----- tests/components/zwave_js/test_switch.py | 14 ++--- 12 files changed, 140 insertions(+), 146 deletions(-) diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index de0f2cc0a6f..7c97836c3b8 100644 --- a/homeassistant/components/zwave_js/manifest.json +++ b/homeassistant/components/zwave_js/manifest.json @@ -3,7 +3,7 @@ "name": "Z-Wave JS", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zwave_js", - "requirements": ["zwave-js-server-python==0.21.1"], + "requirements": ["zwave-js-server-python==0.22.0"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index 963b262ec60..b1581b797bd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2396,4 +2396,4 @@ zigpy==0.33.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.21.1 +zwave-js-server-python==0.22.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5a17b05864d..9c0222a12d6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1239,4 +1239,4 @@ zigpy-znp==0.4.0 zigpy==0.33.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.21.1 +zwave-js-server-python==0.22.0 diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index dd8679ddf73..75bde222111 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -229,7 +229,7 @@ async def test_set_config_parameter( entry = integration ws_client = await hass_ws_client(hass) - client.async_send_command.return_value = {"success": True} + client.async_send_command_no_wait.return_value = None await ws_client.send_json( { @@ -244,10 +244,10 @@ async def test_set_config_parameter( ) msg = await ws_client.receive_json() - assert msg["result"] + assert msg["success"] - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 52 assert args["valueId"] == { @@ -275,7 +275,7 @@ async def test_set_config_parameter( } assert args["value"] == 1 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() with patch( "homeassistant.components.zwave_js.api.async_set_config_parameter", @@ -295,7 +295,7 @@ async def test_set_config_parameter( msg = await ws_client.receive_json() - assert len(client.async_send_command.call_args_list) == 0 + assert len(client.async_send_command_no_wait.call_args_list) == 0 assert not msg["success"] assert msg["error"]["code"] == "not_supported" assert msg["error"]["message"] == "test" @@ -315,7 +315,7 @@ async def test_set_config_parameter( msg = await ws_client.receive_json() - assert len(client.async_send_command.call_args_list) == 0 + assert len(client.async_send_command_no_wait.call_args_list) == 0 assert not msg["success"] assert msg["error"]["code"] == "not_found" assert msg["error"]["message"] == "test" @@ -335,7 +335,7 @@ async def test_set_config_parameter( msg = await ws_client.receive_json() - assert len(client.async_send_command.call_args_list) == 0 + assert len(client.async_send_command_no_wait.call_args_list) == 0 assert not msg["success"] assert msg["error"]["code"] == "unknown_error" assert msg["error"]["message"] == "test" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index c69804b0158..636b6b3f9f1 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -72,7 +72,7 @@ async def test_thermostat_v2( | SUPPORT_FAN_MODE ) - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test setting hvac mode await hass.services.async_call( @@ -85,8 +85,8 @@ async def test_thermostat_v2( blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -108,7 +108,7 @@ async def test_thermostat_v2( } assert args["value"] == 2 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test setting temperature await hass.services.async_call( @@ -122,8 +122,8 @@ async def test_thermostat_v2( blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 2 - args = client.async_send_command_no_wait.call_args_list[0][0][0] + assert len(client.async_send_command.call_args_list) == 2 + args = client.async_send_command.call_args_list[0][0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -144,7 +144,7 @@ async def test_thermostat_v2( "value": 1, } assert args["value"] == 2 - args = client.async_send_command_no_wait.call_args_list[1][0][0] + args = client.async_send_command.call_args_list[1][0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -166,7 +166,7 @@ async def test_thermostat_v2( } assert args["value"] == 77 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test cool mode update from value updated event event = Event( @@ -217,7 +217,7 @@ async def test_thermostat_v2( assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 22.8 assert state.attributes[ATTR_TARGET_TEMP_LOW] == 22.2 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test setting temperature with heat_cool await hass.services.async_call( @@ -231,8 +231,8 @@ async def test_thermostat_v2( blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 2 - args = client.async_send_command_no_wait.call_args_list[0][0][0] + assert len(client.async_send_command.call_args_list) == 2 + args = client.async_send_command.call_args_list[0][0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -254,7 +254,7 @@ async def test_thermostat_v2( } assert args["value"] == 77 - args = client.async_send_command_no_wait.call_args_list[1][0][0] + args = client.async_send_command.call_args_list[1][0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -276,7 +276,7 @@ async def test_thermostat_v2( } assert args["value"] == 86 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test setting invalid hvac mode with pytest.raises(ValueError): @@ -290,7 +290,7 @@ async def test_thermostat_v2( blocking=True, ) - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test setting fan mode await hass.services.async_call( @@ -303,8 +303,8 @@ async def test_thermostat_v2( blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -327,7 +327,7 @@ async def test_thermostat_v2( } assert args["value"] == 1 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test setting invalid fan mode with pytest.raises(ValueError): @@ -485,8 +485,8 @@ async def test_preset_and_no_setpoint( blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 8 assert args["valueId"] == { @@ -514,7 +514,7 @@ async def test_preset_and_no_setpoint( } assert args["value"] == 15 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test Full power preset update from value updated event event = Event( @@ -553,9 +553,9 @@ async def test_preset_and_no_setpoint( blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 0 + assert len(client.async_send_command.call_args_list) == 0 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Restore hvac mode by setting preset None await hass.services.async_call( @@ -568,8 +568,8 @@ async def test_preset_and_no_setpoint( blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 8 assert args["valueId"]["commandClass"] == 64 @@ -577,4 +577,4 @@ async def test_preset_and_no_setpoint( assert args["valueId"]["property"] == "mode" assert args["value"] == 1 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() diff --git a/tests/components/zwave_js/test_cover.py b/tests/components/zwave_js/test_cover.py index e6118f9b37d..2378453e31a 100644 --- a/tests/components/zwave_js/test_cover.py +++ b/tests/components/zwave_js/test_cover.py @@ -38,8 +38,8 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 6 assert args["valueId"] == { @@ -60,7 +60,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert args["value"] == 50 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test setting position await hass.services.async_call( @@ -70,8 +70,8 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 6 assert args["valueId"] == { @@ -92,7 +92,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert args["value"] == 0 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test opening await hass.services.async_call( @@ -102,8 +102,8 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 6 assert args["valueId"] == { @@ -124,7 +124,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert args["value"] - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test stop after opening await hass.services.async_call( "cover", @@ -133,8 +133,8 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 2 - open_args = client.async_send_command_no_wait.call_args_list[0][0][0] + assert len(client.async_send_command.call_args_list) == 2 + open_args = client.async_send_command.call_args_list[0][0][0] assert open_args["command"] == "node.set_value" assert open_args["nodeId"] == 6 assert open_args["valueId"] == { @@ -153,7 +153,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert not open_args["value"] - close_args = client.async_send_command_no_wait.call_args_list[1][0][0] + close_args = client.async_send_command.call_args_list[1][0][0] assert close_args["command"] == "node.set_value" assert close_args["nodeId"] == 6 assert close_args["valueId"] == { @@ -191,7 +191,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): }, ) node.receive_event(event) - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() state = hass.states.get(WINDOW_COVER_ENTITY) assert state.state == "open" @@ -203,8 +203,8 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): {"entity_id": WINDOW_COVER_ENTITY}, blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 6 assert args["valueId"] == { @@ -225,7 +225,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert args["value"] == 0 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test stop after closing await hass.services.async_call( @@ -235,8 +235,8 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 2 - open_args = client.async_send_command_no_wait.call_args_list[0][0][0] + assert len(client.async_send_command.call_args_list) == 2 + open_args = client.async_send_command.call_args_list[0][0][0] assert open_args["command"] == "node.set_value" assert open_args["nodeId"] == 6 assert open_args["valueId"] == { @@ -255,7 +255,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert not open_args["value"] - close_args = client.async_send_command_no_wait.call_args_list[1][0][0] + close_args = client.async_send_command.call_args_list[1][0][0] assert close_args["command"] == "node.set_value" assert close_args["nodeId"] == 6 assert close_args["valueId"] == { @@ -274,7 +274,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert not close_args["value"] - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() event = Event( type="value updated", @@ -314,8 +314,8 @@ async def test_motor_barrier_cover(hass, client, gdc_zw062, integration): DOMAIN, SERVICE_OPEN_COVER, {"entity_id": GDC_COVER_ENTITY}, blocking=True ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 12 assert args["value"] == 255 @@ -341,15 +341,15 @@ async def test_motor_barrier_cover(hass, client, gdc_zw062, integration): state = hass.states.get(GDC_COVER_ENTITY) assert state.state == STATE_CLOSED - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test close await hass.services.async_call( DOMAIN, SERVICE_CLOSE_COVER, {"entity_id": GDC_COVER_ENTITY}, blocking=True ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 12 assert args["value"] == 0 @@ -375,7 +375,7 @@ async def test_motor_barrier_cover(hass, client, gdc_zw062, integration): state = hass.states.get(GDC_COVER_ENTITY) assert state.state == STATE_CLOSED - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Barrier sends an opening state event = Event( diff --git a/tests/components/zwave_js/test_fan.py b/tests/components/zwave_js/test_fan.py index 0ee007aab35..5bd856c664a 100644 --- a/tests/components/zwave_js/test_fan.py +++ b/tests/components/zwave_js/test_fan.py @@ -23,8 +23,8 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 17 assert args["valueId"] == { @@ -45,7 +45,7 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): } assert args["value"] == 66 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test setting unknown speed with pytest.raises(ValueError): @@ -56,7 +56,7 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): blocking=True, ) - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test turn on no speed await hass.services.async_call( @@ -66,8 +66,8 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 17 assert args["valueId"] == { @@ -88,7 +88,7 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): } assert args["value"] == 255 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test turning off await hass.services.async_call( @@ -98,8 +98,8 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 17 assert args["valueId"] == { @@ -120,7 +120,7 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): } assert args["value"] == 0 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test speed update from value updated event event = Event( @@ -146,7 +146,7 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): assert state.state == "on" assert state.attributes[ATTR_SPEED] == "high" - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() event = Event( type="value updated", diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index c16e2474980..9440f5967ed 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -38,8 +38,8 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 39 assert args["valueId"] == { @@ -60,7 +60,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): } assert args["value"] == 255 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test brightness update from value updated event event = Event( @@ -95,9 +95,9 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 + assert len(client.async_send_command.call_args_list) == 1 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test turning on with brightness await hass.services.async_call( @@ -107,8 +107,8 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 39 assert args["valueId"] == { @@ -129,7 +129,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): } assert args["value"] == 50 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test turning on with rgb color await hass.services.async_call( @@ -139,8 +139,8 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 6 - warm_args = client.async_send_command_no_wait.call_args_list[0][0][0] # red 255 + assert len(client.async_send_command.call_args_list) == 6 + warm_args = client.async_send_command.call_args_list[0][0][0] # red 255 assert warm_args["command"] == "node.set_value" assert warm_args["nodeId"] == 39 assert warm_args["valueId"]["commandClassName"] == "Color Switch" @@ -151,7 +151,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert warm_args["valueId"]["propertyName"] == "targetColor" assert warm_args["value"] == 255 - cold_args = client.async_send_command_no_wait.call_args_list[1][0][0] # green 76 + cold_args = client.async_send_command.call_args_list[1][0][0] # green 76 assert cold_args["command"] == "node.set_value" assert cold_args["nodeId"] == 39 assert cold_args["valueId"]["commandClassName"] == "Color Switch" @@ -161,7 +161,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert cold_args["valueId"]["property"] == "targetColor" assert cold_args["valueId"]["propertyName"] == "targetColor" assert cold_args["value"] == 76 - red_args = client.async_send_command_no_wait.call_args_list[2][0][0] # blue 255 + red_args = client.async_send_command.call_args_list[2][0][0] # blue 255 assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 assert red_args["valueId"]["commandClassName"] == "Color Switch" @@ -171,9 +171,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert red_args["valueId"]["property"] == "targetColor" assert red_args["valueId"]["propertyName"] == "targetColor" assert red_args["value"] == 255 - green_args = client.async_send_command_no_wait.call_args_list[3][0][ - 0 - ] # warm white 0 + green_args = client.async_send_command.call_args_list[3][0][0] # warm white 0 assert green_args["command"] == "node.set_value" assert green_args["nodeId"] == 39 assert green_args["valueId"]["commandClassName"] == "Color Switch" @@ -183,9 +181,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert green_args["valueId"]["property"] == "targetColor" assert green_args["valueId"]["propertyName"] == "targetColor" assert green_args["value"] == 0 - blue_args = client.async_send_command_no_wait.call_args_list[4][0][ - 0 - ] # cold white 0 + blue_args = client.async_send_command.call_args_list[4][0][0] # cold white 0 assert blue_args["command"] == "node.set_value" assert blue_args["nodeId"] == 39 assert blue_args["valueId"]["commandClassName"] == "Color Switch" @@ -236,7 +232,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert state.attributes[ATTR_BRIGHTNESS] == 255 assert state.attributes[ATTR_RGB_COLOR] == (255, 76, 255) - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test turning on with same rgb color await hass.services.async_call( @@ -246,9 +242,9 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 6 + assert len(client.async_send_command.call_args_list) == 6 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test turning on with color temp await hass.services.async_call( @@ -258,8 +254,8 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 6 - red_args = client.async_send_command_no_wait.call_args_list[0][0][0] # red 0 + assert len(client.async_send_command.call_args_list) == 6 + red_args = client.async_send_command.call_args_list[0][0][0] # red 0 assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 assert red_args["valueId"]["commandClassName"] == "Color Switch" @@ -269,7 +265,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert red_args["valueId"]["property"] == "targetColor" assert red_args["valueId"]["propertyName"] == "targetColor" assert red_args["value"] == 0 - red_args = client.async_send_command_no_wait.call_args_list[1][0][0] # green 0 + red_args = client.async_send_command.call_args_list[1][0][0] # green 0 assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 assert red_args["valueId"]["commandClassName"] == "Color Switch" @@ -279,7 +275,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert red_args["valueId"]["property"] == "targetColor" assert red_args["valueId"]["propertyName"] == "targetColor" assert red_args["value"] == 0 - red_args = client.async_send_command_no_wait.call_args_list[2][0][0] # blue 0 + red_args = client.async_send_command.call_args_list[2][0][0] # blue 0 assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 assert red_args["valueId"]["commandClassName"] == "Color Switch" @@ -289,9 +285,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert red_args["valueId"]["property"] == "targetColor" assert red_args["valueId"]["propertyName"] == "targetColor" assert red_args["value"] == 0 - warm_args = client.async_send_command_no_wait.call_args_list[3][0][ - 0 - ] # warm white 0 + warm_args = client.async_send_command.call_args_list[3][0][0] # warm white 0 assert warm_args["command"] == "node.set_value" assert warm_args["nodeId"] == 39 assert warm_args["valueId"]["commandClassName"] == "Color Switch" @@ -301,7 +295,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert warm_args["valueId"]["property"] == "targetColor" assert warm_args["valueId"]["propertyName"] == "targetColor" assert warm_args["value"] == 20 - red_args = client.async_send_command_no_wait.call_args_list[4][0][0] # cold white + red_args = client.async_send_command.call_args_list[4][0][0] # cold white assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 assert red_args["valueId"]["commandClassName"] == "Color Switch" @@ -312,7 +306,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert red_args["valueId"]["propertyName"] == "targetColor" assert red_args["value"] == 235 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test color temp update from value updated event red_event = Event( @@ -368,9 +362,9 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 6 + assert len(client.async_send_command.call_args_list) == 6 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test turning off await hass.services.async_call( @@ -380,8 +374,8 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 39 assert args["valueId"] == { diff --git a/tests/components/zwave_js/test_lock.py b/tests/components/zwave_js/test_lock.py index e4032cf42ed..9ddc7abdd88 100644 --- a/tests/components/zwave_js/test_lock.py +++ b/tests/components/zwave_js/test_lock.py @@ -33,8 +33,8 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 20 assert args["valueId"] == { @@ -64,7 +64,7 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): } assert args["value"] == 255 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test locked update from value updated event event = Event( @@ -88,7 +88,7 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): assert hass.states.get(SCHLAGE_BE469_LOCK_ENTITY).state == STATE_LOCKED - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test unlocking await hass.services.async_call( @@ -98,8 +98,8 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 20 assert args["valueId"] == { @@ -129,7 +129,7 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): } assert args["value"] == 0 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test set usercode service await hass.services.async_call( @@ -143,8 +143,8 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 20 assert args["valueId"] == { @@ -167,7 +167,7 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): } assert args["value"] == "1234" - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test clear usercode await hass.services.async_call( @@ -177,8 +177,8 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 20 assert args["valueId"] == { diff --git a/tests/components/zwave_js/test_number.py b/tests/components/zwave_js/test_number.py index 136e62c5405..b7d83068bea 100644 --- a/tests/components/zwave_js/test_number.py +++ b/tests/components/zwave_js/test_number.py @@ -20,8 +20,8 @@ async def test_number(hass, client, aeotec_radiator_thermostat, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 4 assert args["valueId"] == { @@ -43,7 +43,7 @@ async def test_number(hass, client, aeotec_radiator_thermostat, integration): } assert args["value"] == 30.0 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test value update from value updated event event = Event( diff --git a/tests/components/zwave_js/test_services.py b/tests/components/zwave_js/test_services.py index d0bf08c1b7a..585e03402eb 100644 --- a/tests/components/zwave_js/test_services.py +++ b/tests/components/zwave_js/test_services.py @@ -39,8 +39,8 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 52 assert args["valueId"] == { @@ -68,7 +68,7 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration): } assert args["value"] == 1 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test setting parameter by property name await hass.services.async_call( @@ -82,8 +82,8 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 52 assert args["valueId"] == { @@ -111,7 +111,7 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration): } assert args["value"] == 1 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test setting parameter by property name and state label await hass.services.async_call( @@ -125,8 +125,8 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 52 assert args["valueId"] == { @@ -154,7 +154,7 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration): } assert args["value"] == 2 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test setting parameter by property and bitmask await hass.services.async_call( @@ -169,8 +169,8 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 52 assert args["valueId"] == { @@ -302,15 +302,15 @@ async def test_poll_value( ): """Test the poll_value service.""" # Test polling the primary value - client.async_send_command_no_wait.return_value = {"result": 2} + client.async_send_command.return_value = {"result": 2} await hass.services.async_call( DOMAIN, SERVICE_REFRESH_VALUE, {ATTR_ENTITY_ID: CLIMATE_RADIO_THERMOSTAT_ENTITY}, blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.poll_value" assert args["nodeId"] == 26 assert args["valueId"] == { @@ -339,10 +339,10 @@ async def test_poll_value( "ccVersion": 2, } - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test polling all watched values - client.async_send_command_no_wait.return_value = {"result": 2} + client.async_send_command.return_value = {"result": 2} await hass.services.async_call( DOMAIN, SERVICE_REFRESH_VALUE, @@ -352,7 +352,7 @@ async def test_poll_value( }, blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 8 + assert len(client.async_send_command.call_args_list) == 8 # Test polling against an invalid entity raises ValueError with pytest.raises(ValueError): diff --git a/tests/components/zwave_js/test_switch.py b/tests/components/zwave_js/test_switch.py index dceaa17a816..ea6e27d9b72 100644 --- a/tests/components/zwave_js/test_switch.py +++ b/tests/components/zwave_js/test_switch.py @@ -21,7 +21,7 @@ async def test_switch(hass, hank_binary_switch, integration, client): "switch", "turn_on", {"entity_id": SWITCH_ENTITY}, blocking=True ) - args = client.async_send_command_no_wait.call_args[0][0] + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 32 assert args["valueId"] == { @@ -68,7 +68,7 @@ async def test_switch(hass, hank_binary_switch, integration, client): "switch", "turn_off", {"entity_id": SWITCH_ENTITY}, blocking=True ) - args = client.async_send_command_no_wait.call_args[0][0] + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 32 assert args["valueId"] == { @@ -102,8 +102,8 @@ async def test_barrier_signaling_switch(hass, gdc_zw062, integration, client): DOMAIN, SERVICE_TURN_OFF, {"entity_id": entity}, blocking=True ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 12 assert args["value"] == 0 @@ -134,7 +134,7 @@ async def test_barrier_signaling_switch(hass, gdc_zw062, integration, client): state = hass.states.get(entity) assert state.state == STATE_OFF - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test turning on await hass.services.async_call( @@ -143,8 +143,8 @@ async def test_barrier_signaling_switch(hass, gdc_zw062, integration, client): # Note: the valueId's value is still 255 because we never # received an updated value - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 12 assert args["value"] == 255 From 5174f63fd85fb752ce378a5c0615b2f035695918 Mon Sep 17 00:00:00 2001 From: Dan Klaffenbach Date: Thu, 18 Mar 2021 18:19:28 +0100 Subject: [PATCH 467/831] Add definitions for grouping media players (#41193) * Add definitions for grouping media players See https://github.com/home-assistant/architecture/issues/364 * Fix Google Assistant tests * Define sync versions of async_join_players/async_unjoin * Don't use async API in synchronous test methods * Fix tests and make pylint happy The method name `unjoin` is used by another component, so let's use `unjoin_player` instead. * Fix emulated_hue tests The new media player entity in the `demo` component requires a tiny adjustment in the emulated_hue tests. * Use "target:" in service description * Also use "name:" in service descriptions Co-authored-by: Franck Nijhof --- homeassistant/components/demo/media_player.py | 27 ++++++++++-- .../components/media_player/__init__.py | 42 +++++++++++++++++++ .../components/media_player/const.py | 4 ++ .../components/media_player/services.yaml | 25 ++++++++++- tests/components/demo/test_media_player.py | 36 ++++++++++++++++ tests/components/emulated_hue/test_hue_api.py | 1 + tests/components/google_assistant/__init__.py | 13 ++++++ 7 files changed, 143 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/demo/media_player.py b/homeassistant/components/demo/media_player.py index 2ea360ebb81..ea315707dea 100644 --- a/homeassistant/components/demo/media_player.py +++ b/homeassistant/components/demo/media_player.py @@ -6,6 +6,7 @@ from homeassistant.components.media_player.const import ( MEDIA_TYPE_TVSHOW, REPEAT_MODE_OFF, SUPPORT_CLEAR_PLAYLIST, + SUPPORT_GROUPING, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, @@ -40,6 +41,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= "Bedroom", "kxopViU98Xo", "Epic sax guy 10 hours", 360000 ), DemoMusicPlayer(), + DemoMusicPlayer("Kitchen"), DemoTVShowPlayer(), ] ) @@ -73,6 +75,7 @@ MUSIC_PLAYER_SUPPORT = ( | SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_CLEAR_PLAYLIST + | SUPPORT_GROUPING | SUPPORT_PLAY | SUPPORT_SHUFFLE_SET | SUPPORT_REPEAT_SET @@ -291,7 +294,7 @@ class DemoYoutubePlayer(AbstractDemoPlayer): class DemoMusicPlayer(AbstractDemoPlayer): - """A Demo media player that only supports YouTube.""" + """A Demo media player.""" # We only implement the methods that we support @@ -318,12 +321,18 @@ class DemoMusicPlayer(AbstractDemoPlayer): ), ] - def __init__(self): + def __init__(self, name="Walkman"): """Initialize the demo device.""" - super().__init__("Walkman") + super().__init__(name) self._cur_track = 0 + self._group_members = [] self._repeat = REPEAT_MODE_OFF + @property + def group_members(self): + """List of players which are currently grouped together.""" + return self._group_members + @property def media_content_id(self): """Return the content ID of current playing media.""" @@ -398,6 +407,18 @@ class DemoMusicPlayer(AbstractDemoPlayer): self._repeat = repeat self.schedule_update_ha_state() + def join_players(self, group_members): + """Join `group_members` as a player group with the current player.""" + self._group_members = [ + self.entity_id, + ] + group_members + self.schedule_update_ha_state() + + def unjoin_player(self): + """Remove this player from any group.""" + self._group_members = [] + self.schedule_update_ha_state() + class DemoTVShowPlayer(AbstractDemoPlayer): """A Demo media player that only supports YouTube.""" diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 1e0a9b9f6bb..8e3ffe5dd0d 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -64,6 +64,7 @@ from homeassistant.loader import bind_hass from .const import ( ATTR_APP_ID, ATTR_APP_NAME, + ATTR_GROUP_MEMBERS, ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE_LIST, ATTR_MEDIA_ALBUM_ARTIST, @@ -94,11 +95,14 @@ from .const import ( MEDIA_CLASS_DIRECTORY, REPEAT_MODES, SERVICE_CLEAR_PLAYLIST, + SERVICE_JOIN, SERVICE_PLAY_MEDIA, SERVICE_SELECT_SOUND_MODE, SERVICE_SELECT_SOURCE, + SERVICE_UNJOIN, SUPPORT_BROWSE_MEDIA, SUPPORT_CLEAR_PLAYLIST, + SUPPORT_GROUPING, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, @@ -299,6 +303,12 @@ async def async_setup(hass, config): "async_media_seek", [SUPPORT_SEEK], ) + component.async_register_entity_service( + SERVICE_JOIN, + {vol.Required(ATTR_GROUP_MEMBERS): list}, + "async_join_players", + [SUPPORT_GROUPING], + ) component.async_register_entity_service( SERVICE_SELECT_SOURCE, {vol.Required(ATTR_INPUT_SOURCE): cv.string}, @@ -330,6 +340,9 @@ async def async_setup(hass, config): "async_set_shuffle", [SUPPORT_SHUFFLE_SET], ) + component.async_register_entity_service( + SERVICE_UNJOIN, {}, "async_unjoin_player", [SUPPORT_GROUPING] + ) component.async_register_entity_service( SERVICE_REPEAT_SET, @@ -537,6 +550,11 @@ class MediaPlayerEntity(Entity): """Return current repeat mode.""" return None + @property + def group_members(self): + """List of members which are currently grouped together.""" + return None + @property def supported_features(self): """Flag media player features that are supported.""" @@ -738,6 +756,11 @@ class MediaPlayerEntity(Entity): """Boolean if shuffle is supported.""" return bool(self.supported_features & SUPPORT_SHUFFLE_SET) + @property + def support_grouping(self): + """Boolean if player grouping is supported.""" + return bool(self.supported_features & SUPPORT_GROUPING) + async def async_toggle(self): """Toggle the power on the media player.""" if hasattr(self, "toggle"): @@ -846,6 +869,9 @@ class MediaPlayerEntity(Entity): if self.media_image_remotely_accessible: state_attr["entity_picture_local"] = self.media_image_local + if self.support_grouping: + state_attr[ATTR_GROUP_MEMBERS] = self.group_members + return state_attr async def async_browse_media( @@ -860,6 +886,22 @@ class MediaPlayerEntity(Entity): """ raise NotImplementedError() + def join_players(self, group_members): + """Join `group_members` as a player group with the current player.""" + raise NotImplementedError() + + async def async_join_players(self, group_members): + """Join `group_members` as a player group with the current player.""" + await self.hass.async_add_executor_job(self.join_players, group_members) + + def unjoin_player(self): + """Remove this player from any group.""" + raise NotImplementedError() + + async def async_unjoin_player(self): + """Remove this player from any group.""" + await self.hass.async_add_executor_job(self.unjoin_player) + async def _async_fetch_image_from_cache(self, url): """Fetch image. diff --git a/homeassistant/components/media_player/const.py b/homeassistant/components/media_player/const.py index 87ccca75d36..67f4331aa60 100644 --- a/homeassistant/components/media_player/const.py +++ b/homeassistant/components/media_player/const.py @@ -2,6 +2,7 @@ ATTR_APP_ID = "app_id" ATTR_APP_NAME = "app_name" +ATTR_GROUP_MEMBERS = "group_members" ATTR_INPUT_SOURCE = "source" ATTR_INPUT_SOURCE_LIST = "source_list" ATTR_MEDIA_ALBUM_ARTIST = "media_album_artist" @@ -75,9 +76,11 @@ MEDIA_TYPE_URL = "url" MEDIA_TYPE_VIDEO = "video" SERVICE_CLEAR_PLAYLIST = "clear_playlist" +SERVICE_JOIN = "join" SERVICE_PLAY_MEDIA = "play_media" SERVICE_SELECT_SOUND_MODE = "select_sound_mode" SERVICE_SELECT_SOURCE = "select_source" +SERVICE_UNJOIN = "unjoin" REPEAT_MODE_ALL = "all" REPEAT_MODE_OFF = "off" @@ -103,3 +106,4 @@ SUPPORT_SHUFFLE_SET = 32768 SUPPORT_SELECT_SOUND_MODE = 65536 SUPPORT_BROWSE_MEDIA = 131072 SUPPORT_REPEAT_SET = 262144 +SUPPORT_GROUPING = 524288 diff --git a/homeassistant/components/media_player/services.yaml b/homeassistant/components/media_player/services.yaml index eaca8483be1..e2a260dc80f 100644 --- a/homeassistant/components/media_player/services.yaml +++ b/homeassistant/components/media_player/services.yaml @@ -118,8 +118,8 @@ play_media: media_content_type: name: Content type description: - The type of the content to play. Like image, music, tvshow, - video, episode, channel or playlist. + The type of the content to play. Like image, music, tvshow, video, + episode, channel or playlist. required: true example: "music" selector: @@ -184,3 +184,24 @@ repeat_set: - "off" - "all" - "one" + +join: + description: + Group players together. Only works on platforms with support for player + groups. + name: Join + target: + fields: + group_members: + description: + The players which will be synced with the target player. + example: + - "media_player.multiroom_player2" + - "media_player.multiroom_player3" + +unjoin: + description: + Unjoin the player from a group. Only works on platforms with support for + player groups. + name: Unjoin + target: diff --git a/tests/components/demo/test_media_player.py b/tests/components/demo/test_media_player.py index a32a99bbc63..4ed41ce9c83 100644 --- a/tests/components/demo/test_media_player.py +++ b/tests/components/demo/test_media_player.py @@ -442,3 +442,39 @@ async def test_media_image_proxy(hass, hass_client): req = await client.get(state.attributes.get(ATTR_ENTITY_PICTURE)) assert req.status == 200 assert await req.text() == fake_picture_data + + +async def test_grouping(hass): + """Test the join/unjoin services.""" + walkman = "media_player.walkman" + kitchen = "media_player.kitchen" + + assert await async_setup_component( + hass, mp.DOMAIN, {"media_player": {"platform": "demo"}} + ) + await hass.async_block_till_done() + state = hass.states.get(walkman) + assert state.attributes.get(mp.ATTR_GROUP_MEMBERS) == [] + + await hass.services.async_call( + mp.DOMAIN, + mp.SERVICE_JOIN, + { + ATTR_ENTITY_ID: walkman, + mp.ATTR_GROUP_MEMBERS: [ + kitchen, + ], + }, + blocking=True, + ) + state = hass.states.get(walkman) + assert state.attributes.get(mp.ATTR_GROUP_MEMBERS) == [walkman, kitchen] + + await hass.services.async_call( + mp.DOMAIN, + mp.SERVICE_UNJOIN, + {ATTR_ENTITY_ID: walkman}, + blocking=True, + ) + state = hass.states.get(walkman) + assert state.attributes.get(mp.ATTR_GROUP_MEMBERS) == [] diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index e3f965616f9..3c322c0b613 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -90,6 +90,7 @@ ENTITY_IDS_BY_NUMBER = { "21": "humidifier.hygrostat", "22": "scene.light_on", "23": "scene.light_off", + "24": "media_player.kitchen", } ENTITY_NUMBERS_BY_ID = {v: k for k, v in ENTITY_IDS_BY_NUMBER.items()} diff --git a/tests/components/google_assistant/__init__.py b/tests/components/google_assistant/__init__.py index 9047e175ae6..0fe89d0fa7b 100644 --- a/tests/components/google_assistant/__init__.py +++ b/tests/components/google_assistant/__init__.py @@ -192,6 +192,19 @@ DEMO_DEVICES = [ "type": "action.devices.types.SETTOP", "willReportState": False, }, + { + "id": "media_player.kitchen", + "name": {"name": "Kitchen"}, + "traits": [ + "action.devices.traits.OnOff", + "action.devices.traits.Volume", + "action.devices.traits.Modes", + "action.devices.traits.TransportControl", + "action.devices.traits.MediaState", + ], + "type": "action.devices.types.SETTOP", + "willReportState": False, + }, { "id": "media_player.living_room", "name": {"name": "Living Room"}, From 98d7e6b898fdafc1cb535f6dad14de617bea4679 Mon Sep 17 00:00:00 2001 From: bestlibre Date: Thu, 18 Mar 2021 19:30:38 +0100 Subject: [PATCH 468/831] Add images support to matrix notify (#37625) Co-authored-by: Paulus Schoutsen --- homeassistant/components/matrix/__init__.py | 47 +++++++++++++++++-- homeassistant/components/matrix/notify.py | 6 ++- homeassistant/components/matrix/services.yaml | 3 ++ 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/matrix/__init__.py b/homeassistant/components/matrix/__init__.py index c89de5552d5..62af53079e8 100644 --- a/homeassistant/components/matrix/__init__.py +++ b/homeassistant/components/matrix/__init__.py @@ -1,12 +1,13 @@ """The Matrix bot component.""" from functools import partial import logging +import mimetypes import os from matrix_client.client import MatrixClient, MatrixRequestError import voluptuous as vol -from homeassistant.components.notify import ATTR_MESSAGE, ATTR_TARGET +from homeassistant.components.notify import ATTR_DATA, ATTR_MESSAGE, ATTR_TARGET from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, @@ -31,8 +32,12 @@ CONF_COMMANDS = "commands" CONF_WORD = "word" CONF_EXPRESSION = "expression" +DEFAULT_CONTENT_TYPE = "application/octet-stream" + EVENT_MATRIX_COMMAND = "matrix_command" +ATTR_IMAGES = "images" # optional images + COMMAND_SCHEMA = vol.All( vol.Schema( { @@ -67,6 +72,9 @@ CONFIG_SCHEMA = vol.Schema( SERVICE_SCHEMA_SEND_MESSAGE = vol.Schema( { vol.Required(ATTR_MESSAGE): cv.string, + vol.Optional(ATTR_DATA): { + vol.Optional(ATTR_IMAGES): vol.All(cv.ensure_list, [cv.string]), + }, vol.Required(ATTR_TARGET): vol.All(cv.ensure_list, [cv.string]), } ) @@ -336,13 +344,20 @@ class MatrixBot: return _client - def _send_message(self, message, target_rooms): - """Send the message to the Matrix server.""" + def _send_image(self, img, target_rooms): + _LOGGER.debug("Uploading file from path, %s", img) + if not self.hass.config.is_allowed_path(img): + _LOGGER.error("Path not allowed: %s", img) + return + with open(img, "rb") as upfile: + imgfile = upfile.read() + content_type = mimetypes.guess_type(img)[0] + mxc = self._client.upload(imgfile, content_type) for target_room in target_rooms: try: room = self._join_or_get_room(target_room) - _LOGGER.debug(room.send_text(message)) + room.send_image(mxc, img) except MatrixRequestError as ex: _LOGGER.error( "Unable to deliver message to room '%s': %d, %s", @@ -351,6 +366,28 @@ class MatrixBot: ex.content, ) + def _send_message(self, message, data, target_rooms): + """Send the message to the Matrix server.""" + for target_room in target_rooms: + try: + room = self._join_or_get_room(target_room) + if message is not None: + _LOGGER.debug(room.send_text(message)) + except MatrixRequestError as ex: + _LOGGER.error( + "Unable to deliver message to room '%s': %d, %s", + target_room, + ex.code, + ex.content, + ) + if data is not None: + for img in data.get(ATTR_IMAGES, []): + self._send_image(img, target_rooms) + def handle_send_message(self, service): """Handle the send_message service.""" - self._send_message(service.data[ATTR_MESSAGE], service.data[ATTR_TARGET]) + self._send_message( + service.data.get(ATTR_MESSAGE), + service.data.get(ATTR_DATA), + service.data[ATTR_TARGET], + ) diff --git a/homeassistant/components/matrix/notify.py b/homeassistant/components/matrix/notify.py index 0965783bf4d..8643d7511bc 100644 --- a/homeassistant/components/matrix/notify.py +++ b/homeassistant/components/matrix/notify.py @@ -2,6 +2,7 @@ import voluptuous as vol from homeassistant.components.notify import ( + ATTR_DATA, ATTR_MESSAGE, ATTR_TARGET, PLATFORM_SCHEMA, @@ -31,9 +32,10 @@ class MatrixNotificationService(BaseNotificationService): def send_message(self, message="", **kwargs): """Send the message to the Matrix server.""" target_rooms = kwargs.get(ATTR_TARGET) or [self._default_room] - service_data = {ATTR_TARGET: target_rooms, ATTR_MESSAGE: message} - + data = kwargs.get(ATTR_DATA) + if data is not None: + service_data[ATTR_DATA] = data return self.hass.services.call( DOMAIN, SERVICE_SEND_MESSAGE, service_data=service_data ) diff --git a/homeassistant/components/matrix/services.yaml b/homeassistant/components/matrix/services.yaml index f8b0c53bda6..fe99bf6365a 100644 --- a/homeassistant/components/matrix/services.yaml +++ b/homeassistant/components/matrix/services.yaml @@ -7,3 +7,6 @@ send_message: target: description: A list of room(s) to send the message to. example: "#hasstest:matrix.org" + data: + description: Extended information of notification. Supports list of images. Optional. + example: "{'images': ['/tmp/test.jpg']}" From 7b717bc43724ff34c0c2b5252a93e711081917d8 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 18 Mar 2021 21:14:06 +0100 Subject: [PATCH 469/831] Correct trace for repeat script actions (#48031) --- homeassistant/helpers/script.py | 12 ++++++++---- homeassistant/helpers/trace.py | 28 ++++++++++++++++++++++++++-- tests/helpers/test_script.py | 2 +- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index be6be32da1e..ac56ce2beb4 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -72,6 +72,7 @@ from homeassistant.util.dt import utcnow from .trace import ( TraceElement, + async_trace_path, trace_append_element, trace_id_get, trace_path, @@ -613,11 +614,14 @@ class _ScriptRun: if not check: raise _StopScript - def _test_conditions(self, conditions, name): + def _test_conditions(self, conditions, name, condition_path=None): + if condition_path is None: + condition_path = name + @trace_condition_function def traced_test_conditions(hass, variables): try: - with trace_path("conditions"): + with trace_path(condition_path): for idx, cond in enumerate(conditions): with trace_path(str(idx)): if not cond(hass, variables): @@ -631,7 +635,7 @@ class _ScriptRun: result = traced_test_conditions(self._hass, self._variables) return result - @trace_path("repeat") + @async_trace_path("repeat") async def _async_repeat_step(self): """Repeat a sequence.""" description = self._action.get(CONF_ALIAS, "sequence") @@ -720,7 +724,7 @@ class _ScriptRun: for idx, (conditions, script) in enumerate(choose_data["choices"]): with trace_path(str(idx)): try: - if self._test_conditions(conditions, "choose"): + if self._test_conditions(conditions, "choose", "conditions"): trace_set_result(choice=idx) with trace_path("sequence"): await self._async_run_script(script) diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py index 2e5f1dd8c54..d6de845248f 100644 --- a/homeassistant/helpers/trace.py +++ b/homeassistant/helpers/trace.py @@ -4,7 +4,8 @@ from __future__ import annotations from collections import deque from contextlib import contextmanager from contextvars import ContextVar -from typing import Any, Deque, Generator, cast +from functools import wraps +from typing import Any, Callable, Deque, Generator, cast from homeassistant.helpers.typing import TemplateVarsType import homeassistant.util.dt as dt_util @@ -168,9 +169,32 @@ def trace_set_result(**kwargs: Any) -> None: @contextmanager def trace_path(suffix: str | list[str]) -> Generator: - """Go deeper in the config tree.""" + """Go deeper in the config tree. + + Can not be used as a decorator on couroutine functions. + """ count = trace_path_push(suffix) try: yield finally: trace_path_pop(count) + + +def async_trace_path(suffix: str | list[str]) -> Callable: + """Go deeper in the config tree. + + To be used as a decorator on coroutine functions. + """ + + def _trace_path_decorator(func: Callable) -> Callable: + """Decorate a coroutine function.""" + + @wraps(func) + async def async_wrapper(*args: Any) -> None: + """Catch and log exception.""" + with trace_path(suffix): + await func(*args) + + return async_wrapper + + return _trace_path_decorator diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index f6246299074..1f47aa9dbba 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -1236,7 +1236,7 @@ async def test_repeat_count(hass, caplog, count): assert_action_trace( { "0": [{}], - "0/sequence/0": [{}] * min(count, script.ACTION_TRACE_NODE_MAX_LEN), + "0/repeat/sequence/0": [{}] * min(count, script.ACTION_TRACE_NODE_MAX_LEN), } ) From 0f5efca76b6cbec9a0237b2229d5e01894e7431b Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Thu, 18 Mar 2021 21:26:20 +0100 Subject: [PATCH 470/831] Fix Shelly sleeping device initialization after reconfiguration (#48076) --- homeassistant/components/shelly/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index 52dae7f164d..93b75b139a9 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -75,6 +75,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): dev_reg = await device_registry.async_get_registry(hass) identifier = (DOMAIN, entry.unique_id) device_entry = dev_reg.async_get_device(identifiers={identifier}, connections=set()) + if entry.entry_id not in device_entry.config_entries: + device_entry = None sleep_period = entry.data.get("sleep_period") From 4cb77181920b25f668c75a19e0dd91eb703efb1b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 22:58:19 +0100 Subject: [PATCH 471/831] Update typing 16 (#48087) --- homeassistant/auth/models.py | 2 +- homeassistant/bootstrap.py | 2 +- homeassistant/components/alexa/entities.py | 2 +- homeassistant/components/cast/helpers.py | 6 ++--- .../components/esphome/entry_data.py | 2 +- .../components/here_travel_time/sensor.py | 2 +- homeassistant/components/izone/climate.py | 2 +- .../components/lovelace/dashboard.py | 4 +++- homeassistant/components/mobile_app/util.py | 2 +- .../components/mqtt/device_trigger.py | 2 +- homeassistant/components/sharkiq/vacuum.py | 2 +- .../components/system_health/__init__.py | 2 +- .../components/tasmota/device_trigger.py | 2 +- .../components/transmission/__init__.py | 23 +++++++++---------- .../components/transmission/sensor.py | 2 +- homeassistant/components/wiffi/__init__.py | 2 +- homeassistant/components/zeroconf/__init__.py | 4 +++- homeassistant/config_entries.py | 2 +- homeassistant/core.py | 6 ++--- homeassistant/data_entry_flow.py | 6 ++--- homeassistant/helpers/__init__.py | 4 ++-- homeassistant/helpers/device_registry.py | 2 +- homeassistant/helpers/entity_registry.py | 2 +- homeassistant/helpers/script.py | 2 +- homeassistant/helpers/selector.py | 4 +++- homeassistant/helpers/service.py | 23 ++++++------------- homeassistant/loader.py | 18 +++++++-------- script/hassfest/codeowners.py | 8 +++---- script/hassfest/config_flow.py | 9 ++++---- script/hassfest/coverage.py | 5 ++-- script/hassfest/dependencies.py | 15 ++++++------ script/hassfest/dhcp.py | 9 ++++---- script/hassfest/json.py | 5 ++-- script/hassfest/manifest.py | 5 ++-- script/hassfest/model.py | 22 ++++++++++-------- script/hassfest/mqtt.py | 9 ++++---- script/hassfest/requirements.py | 9 ++++---- script/hassfest/services.py | 5 ++-- script/hassfest/ssdp.py | 9 ++++---- script/hassfest/translations.py | 5 ++-- script/hassfest/zeroconf.py | 9 ++++---- script/scaffold/model.py | 9 ++++---- .../integration/device_action.py | 6 ++--- .../integration/device_condition.py | 4 ++-- .../integration/device_trigger.py | 4 ++-- .../integration/reproduce_state.py | 12 ++++++---- .../integration/significant_change.py | 6 +++-- script/translations/download.py | 5 ++-- tests/common.py | 4 ++-- tests/components/vera/common.py | 2 +- tests/components/vera/test_binary_sensor.py | 2 +- tests/components/vera/test_climate.py | 4 ++-- tests/components/vera/test_cover.py | 2 +- tests/components/vera/test_init.py | 18 +++++++-------- tests/components/vera/test_light.py | 2 +- tests/components/vera/test_lock.py | 2 +- tests/components/vera/test_scene.py | 2 +- tests/components/vera/test_sensor.py | 4 ++-- tests/components/vera/test_switch.py | 2 +- 59 files changed, 180 insertions(+), 166 deletions(-) diff --git a/homeassistant/auth/models.py b/homeassistant/auth/models.py index a8a70b8fc3b..aaef7e02174 100644 --- a/homeassistant/auth/models.py +++ b/homeassistant/auth/models.py @@ -43,7 +43,7 @@ class User: groups: list[Group] = attr.ib(factory=list, eq=False, order=False) # List of credentials of a user. - credentials: list["Credentials"] = attr.ib(factory=list, eq=False, order=False) + credentials: list[Credentials] = attr.ib(factory=list, eq=False, order=False) # Tokens associated with a user. refresh_tokens: dict[str, "RefreshToken"] = attr.ib( diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 8b191ae3a47..d19ddaf4f5d 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -76,7 +76,7 @@ STAGE_1_INTEGRATIONS = { async def async_setup_hass( - runtime_config: "RuntimeConfig", + runtime_config: RuntimeConfig, ) -> core.HomeAssistant | None: """Set up Home Assistant.""" hass = core.HomeAssistant() diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index 71f4e41a810..95c40cd7cef 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -253,7 +253,7 @@ class AlexaEntity: The API handlers should manipulate entities only through this interface. """ - def __init__(self, hass: HomeAssistant, config: "AbstractConfig", entity: State): + def __init__(self, hass: HomeAssistant, config: AbstractConfig, entity: State): """Initialize Alexa Entity.""" self.hass = hass self.config = config diff --git a/homeassistant/components/cast/helpers.py b/homeassistant/components/cast/helpers.py index 91382c69591..71caa6490d8 100644 --- a/homeassistant/components/cast/helpers.py +++ b/homeassistant/components/cast/helpers.py @@ -15,13 +15,13 @@ class ChromecastInfo: This also has the same attributes as the mDNS fields by zeroconf. """ - services: Optional[set] = attr.ib() - uuid: Optional[str] = attr.ib( + services: set | None = attr.ib() + uuid: str | None = attr.ib( converter=attr.converters.optional(str), default=None ) # always convert UUID to string if not None _manufacturer = attr.ib(type=Optional[str], default=None) model_name: str = attr.ib(default="") - friendly_name: Optional[str] = attr.ib(default=None) + friendly_name: str | None = attr.ib(default=None) is_audio_group = attr.ib(type=Optional[bool], default=False) is_dynamic_group = attr.ib(type=Optional[bool], default=None) diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index f308239fa23..0923c84acd7 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -52,7 +52,7 @@ class RuntimeEntryData: """Store runtime data for esphome config entries.""" entry_id: str = attr.ib() - client: "APIClient" = attr.ib() + client: APIClient = attr.ib() store: Store = attr.ib() reconnect_task: asyncio.Task | None = attr.ib(default=None) state: dict[str, dict[str, Any]] = attr.ib(factory=dict) diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index 9a6b0724caa..976c591f810 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -225,7 +225,7 @@ class HERETravelTimeSensor(Entity): destination: str, origin_entity_id: str, destination_entity_id: str, - here_data: "HERETravelTimeData", + here_data: HERETravelTimeData, ) -> None: """Initialize the sensor.""" self._name = name diff --git a/homeassistant/components/izone/climate.py b/homeassistant/components/izone/climate.py index 18c8b5e9a2c..d509896e841 100644 --- a/homeassistant/components/izone/climate.py +++ b/homeassistant/components/izone/climate.py @@ -78,7 +78,7 @@ async def async_setup_entry( @callback def init_controller(ctrl: Controller): """Register the controller device and the containing zones.""" - conf = hass.data.get(DATA_CONFIG) # type: ConfigType + conf: ConfigType = hass.data.get(DATA_CONFIG) # Filter out any entities excluded in the config file if conf and ctrl.device_uid in conf[CONF_EXCLUDE]: diff --git a/homeassistant/components/lovelace/dashboard.py b/homeassistant/components/lovelace/dashboard.py index c6f4726724b..93b127259d2 100644 --- a/homeassistant/components/lovelace/dashboard.py +++ b/homeassistant/components/lovelace/dashboard.py @@ -1,4 +1,6 @@ """Lovelace dashboard support.""" +from __future__ import annotations + from abc import ABC, abstractmethod import logging import os @@ -231,7 +233,7 @@ class DashboardsCollection(collection.StorageCollection): _LOGGER, ) - async def _async_load_data(self) -> Optional[dict]: + async def _async_load_data(self) -> dict | None: """Load the data.""" data = await self.store.async_load() diff --git a/homeassistant/components/mobile_app/util.py b/homeassistant/components/mobile_app/util.py index b0a3f52e394..cd4b7c22939 100644 --- a/homeassistant/components/mobile_app/util.py +++ b/homeassistant/components/mobile_app/util.py @@ -43,7 +43,7 @@ def supports_push(hass, webhook_id: str) -> bool: @callback def get_notify_service(hass, webhook_id: str) -> str | None: """Return the notify service for this webhook ID.""" - notify_service: "MobileAppNotificationService" = hass.data[DOMAIN][DATA_NOTIFY] + notify_service: MobileAppNotificationService = hass.data[DOMAIN][DATA_NOTIFY] for target_service, target_webhook_id in notify_service.registered_targets.items(): if target_webhook_id == webhook_id: diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index 1e4981d7d6f..12a98905ba5 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -87,7 +87,7 @@ class TriggerInstance: action: AutomationActionType = attr.ib() automation_info: dict = attr.ib() - trigger: "Trigger" = attr.ib() + trigger: Trigger = attr.ib() remove: CALLBACK_TYPE | None = attr.ib(default=None) async def async_attach_trigger(self): diff --git a/homeassistant/components/sharkiq/vacuum.py b/homeassistant/components/sharkiq/vacuum.py index 7e33d355729..eed41fb1438 100644 --- a/homeassistant/components/sharkiq/vacuum.py +++ b/homeassistant/components/sharkiq/vacuum.py @@ -69,7 +69,7 @@ ATTR_RSSI = "rssi" async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Shark IQ vacuum cleaner.""" coordinator: SharkIqUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] - devices: Iterable["SharkIqVacuum"] = coordinator.shark_vacs.values() + devices: Iterable[SharkIqVacuum] = coordinator.shark_vacs.values() device_names = [d.name for d in devices] _LOGGER.debug( "Found %d Shark IQ device(s): %s", diff --git a/homeassistant/components/system_health/__init__.py b/homeassistant/components/system_health/__init__.py index ea87a2af832..a5756ce9ddc 100644 --- a/homeassistant/components/system_health/__init__.py +++ b/homeassistant/components/system_health/__init__.py @@ -60,7 +60,7 @@ async def _register_system_health_platform(hass, integration_domain, platform): async def get_integration_info( - hass: HomeAssistant, registration: "SystemHealthRegistration" + hass: HomeAssistant, registration: SystemHealthRegistration ): """Get integration system health.""" try: diff --git a/homeassistant/components/tasmota/device_trigger.py b/homeassistant/components/tasmota/device_trigger.py index 0cbb6e10f1f..139e9be816a 100644 --- a/homeassistant/components/tasmota/device_trigger.py +++ b/homeassistant/components/tasmota/device_trigger.py @@ -48,7 +48,7 @@ class TriggerInstance: action: AutomationActionType = attr.ib() automation_info: dict = attr.ib() - trigger: "Trigger" = attr.ib() + trigger: Trigger = attr.ib() remove: CALLBACK_TYPE | None = attr.ib(default=None) async def async_attach_trigger(self): diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index 5a37cc4d771..cb4bcceeeea 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -3,7 +3,6 @@ from __future__ import annotations from datetime import timedelta import logging -from typing import List import transmissionrpc from transmissionrpc.error import TransmissionError @@ -173,8 +172,8 @@ class TransmissionClient: """Initialize the Transmission RPC API.""" self.hass = hass self.config_entry = config_entry - self.tm_api = None # type: transmissionrpc.Client - self._tm_data = None # type: TransmissionData + self.tm_api: transmissionrpc.Client = None + self._tm_data: TransmissionData = None self.unsub_timer = None @property @@ -345,14 +344,14 @@ class TransmissionData: """Initialize the Transmission RPC API.""" self.hass = hass self.config = config - self.data = None # type: transmissionrpc.Session - self.available = True # type: bool - self._all_torrents = [] # type: List[transmissionrpc.Torrent] - self._api = api # type: transmissionrpc.Client - self._completed_torrents = [] # type: List[transmissionrpc.Torrent] - self._session = None # type: transmissionrpc.Session - self._started_torrents = [] # type: List[transmissionrpc.Torrent] - self._torrents = [] # type: List[transmissionrpc.Torrent] + self.data: transmissionrpc.Session = None + self.available: bool = True + self._all_torrents: list[transmissionrpc.Torrent] = [] + self._api: transmissionrpc.Client = api + self._completed_torrents: list[transmissionrpc.Torrent] = [] + self._session: transmissionrpc.Session = None + self._started_torrents: list[transmissionrpc.Torrent] = [] + self._torrents: list[transmissionrpc.Torrent] = [] @property def host(self): @@ -365,7 +364,7 @@ class TransmissionData: return f"{DATA_UPDATED}-{self.host}" @property - def torrents(self) -> List[transmissionrpc.Torrent]: + def torrents(self) -> list[transmissionrpc.Torrent]: """Get the list of torrents.""" return self._torrents diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index 6a59b3a3d61..cd9e57c50d7 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -43,7 +43,7 @@ class TransmissionSensor(Entity): def __init__(self, tm_client, client_name, sensor_name, sub_type=None): """Initialize the sensor.""" - self._tm_client = tm_client # type: TransmissionClient + self._tm_client: TransmissionClient = tm_client self._client_name = client_name self._name = sensor_name self._sub_type = sub_type diff --git a/homeassistant/components/wiffi/__init__.py b/homeassistant/components/wiffi/__init__.py index d93bd54437b..3b85a7ff0f8 100644 --- a/homeassistant/components/wiffi/__init__.py +++ b/homeassistant/components/wiffi/__init__.py @@ -74,7 +74,7 @@ async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry): """Unload a config entry.""" - api: "WiffiIntegrationApi" = hass.data[DOMAIN][config_entry.entry_id] + api: WiffiIntegrationApi = hass.data[DOMAIN][config_entry.entry_id] await api.server.close_server() unload_ok = all( diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index fb2d097d063..d9c8dc52860 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -1,4 +1,6 @@ """Support for exposing Home Assistant via Zeroconf.""" +from __future__ import annotations + import fnmatch from functools import partial import ipaddress @@ -107,7 +109,7 @@ async def _async_get_instance(hass, **zcargs): class HaServiceBrowser(ServiceBrowser): """ServiceBrowser that only consumes DNSPointer records.""" - def update_record(self, zc: "Zeroconf", now: float, record: DNSRecord) -> None: + def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None: """Pre-Filter update_record to DNSPointers for the configured type.""" # diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index a18e207a730..157acb545c1 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -463,7 +463,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager): """Manage all the config entry flows that are in progress.""" def __init__( - self, hass: HomeAssistant, config_entries: "ConfigEntries", hass_config: dict + self, hass: HomeAssistant, config_entries: ConfigEntries, hass_config: dict ): """Initialize the config entry flow manager.""" super().__init__(hass) diff --git a/homeassistant/core.py b/homeassistant/core.py index 4e0c3c48283..64edf5742ce 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -214,9 +214,9 @@ class CoreState(enum.Enum): class HomeAssistant: """Root object of the Home Assistant home automation.""" - auth: "AuthManager" - http: "HomeAssistantHTTP" = None # type: ignore - config_entries: "ConfigEntries" = None # type: ignore + auth: AuthManager + http: HomeAssistantHTTP = None # type: ignore + config_entries: ConfigEntries = None # type: ignore def __init__(self) -> None: """Initialize new Home Assistant object.""" diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index f16c1859fcd..b149493373f 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -86,13 +86,11 @@ class FlowManager(abc.ABC): @abc.abstractmethod async def async_finish_flow( - self, flow: "FlowHandler", result: dict[str, Any] + self, flow: FlowHandler, result: dict[str, Any] ) -> dict[str, Any]: """Finish a config flow and add an entry.""" - async def async_post_init( - self, flow: "FlowHandler", result: dict[str, Any] - ) -> None: + async def async_post_init(self, flow: FlowHandler, result: dict[str, Any]) -> None: """Entry has finished executing its first step asynchronously.""" @callback diff --git a/homeassistant/helpers/__init__.py b/homeassistant/helpers/__init__.py index 195c1838146..a1964c432fc 100644 --- a/homeassistant/helpers/__init__.py +++ b/homeassistant/helpers/__init__.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from .typing import ConfigType -def config_per_platform(config: "ConfigType", domain: str) -> Iterable[tuple[Any, Any]]: +def config_per_platform(config: ConfigType, domain: str) -> Iterable[tuple[Any, Any]]: """Break a component config into different platforms. For example, will find 'switch', 'switch 2', 'switch 3', .. etc @@ -34,7 +34,7 @@ def config_per_platform(config: "ConfigType", domain: str) -> Iterable[tuple[Any yield platform, item -def extract_domain_configs(config: "ConfigType", domain: str) -> Sequence[str]: +def extract_domain_configs(config: ConfigType, domain: str) -> Sequence[str]: """Extract keys from config for given domain name. Async friendly. diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 4018ba6204c..fac937c4afb 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -658,7 +658,7 @@ def async_entries_for_config_entry( @callback def async_config_entry_disabled_by_changed( - registry: DeviceRegistry, config_entry: "ConfigEntry" + registry: DeviceRegistry, config_entry: ConfigEntry ) -> None: """Handle a config entry being disabled or enabled. diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 3d33b73271d..265dcd9d878 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -628,7 +628,7 @@ def async_entries_for_config_entry( @callback def async_config_entry_disabled_by_changed( - registry: EntityRegistry, config_entry: "ConfigEntry" + registry: EntityRegistry, config_entry: ConfigEntry ) -> None: """Handle a config entry being disabled or enabled. diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index ac56ce2beb4..1824bb9e2d6 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -291,7 +291,7 @@ class _ScriptRun: def __init__( self, hass: HomeAssistant, - script: "Script", + script: Script, variables: dict[str, Any], context: Context | None, log_exceptions: bool, diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index 34befe9c37b..6de8c58d279 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -1,4 +1,6 @@ """Selectors for Home Assistant.""" +from __future__ import annotations + from typing import Any, Callable, Dict, cast import voluptuous as vol @@ -9,7 +11,7 @@ from homeassistant.util import decorator SELECTORS = decorator.Registry() -def validate_selector(config: Any) -> Dict: +def validate_selector(config: Any) -> dict: """Validate a selector.""" if not isinstance(config, dict): raise vol.Invalid("Expected a dictionary") diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index f43b85575b8..d227422f6d5 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -5,16 +5,7 @@ import asyncio import dataclasses from functools import partial, wraps import logging -from typing import ( - TYPE_CHECKING, - Any, - Awaitable, - Callable, - Iterable, - Optional, - TypedDict, - Union, -) +from typing import TYPE_CHECKING, Any, Awaitable, Callable, Iterable, TypedDict import voluptuous as vol @@ -83,9 +74,9 @@ class ServiceTargetSelector: def __init__(self, service_call: ha.ServiceCall): """Extract ids from service call data.""" - entity_ids: Optional[Union[str, list]] = service_call.data.get(ATTR_ENTITY_ID) - device_ids: Optional[Union[str, list]] = service_call.data.get(ATTR_DEVICE_ID) - area_ids: Optional[Union[str, list]] = service_call.data.get(ATTR_AREA_ID) + entity_ids: str | list | None = service_call.data.get(ATTR_ENTITY_ID) + device_ids: str | list | None = service_call.data.get(ATTR_DEVICE_ID) + area_ids: str | list | None = service_call.data.get(ATTR_AREA_ID) self.entity_ids = ( set(cv.ensure_list(entity_ids)) if _has_match(entity_ids) else set() @@ -319,7 +310,7 @@ async def async_extract_entity_ids( return referenced.referenced | referenced.indirectly_referenced -def _has_match(ids: Optional[Union[str, list]]) -> bool: +def _has_match(ids: str | list | None) -> bool: """Check if ids can match anything.""" return ids not in (None, ENTITY_MATCH_NONE) @@ -514,7 +505,7 @@ def async_set_service_schema( @bind_hass async def entity_service_call( hass: HomeAssistantType, - platforms: Iterable["EntityPlatform"], + platforms: Iterable[EntityPlatform], func: str | Callable[..., Any], call: ha.ServiceCall, required_features: Iterable[int] | None = None, @@ -557,7 +548,7 @@ async def entity_service_call( # Check the permissions # A list with entities to call the service on. - entity_candidates: list["Entity"] = [] + entity_candidates: list[Entity] = [] if entity_perms is None: for platform in platforms: diff --git a/homeassistant/loader.py b/homeassistant/loader.py index f6cb24698ec..b3ef4fff271 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -103,7 +103,7 @@ def manifest_from_legacy_module(domain: str, module: ModuleType) -> Manifest: async def _async_get_custom_components( - hass: "HomeAssistant", + hass: HomeAssistant, ) -> dict[str, Integration]: """Return list of custom integrations.""" if hass.config.safe_mode: @@ -144,7 +144,7 @@ async def _async_get_custom_components( async def async_get_custom_components( - hass: "HomeAssistant", + hass: HomeAssistant, ) -> dict[str, Integration]: """Return cached list of custom integrations.""" reg_or_evt = hass.data.get(DATA_CUSTOM_COMPONENTS) @@ -276,7 +276,7 @@ class Integration: @classmethod def resolve_from_root( - cls, hass: "HomeAssistant", root_module: ModuleType, domain: str + cls, hass: HomeAssistant, root_module: ModuleType, domain: str ) -> Integration | None: """Resolve an integration from a root module.""" for base in root_module.__path__: # type: ignore @@ -300,7 +300,7 @@ class Integration: return None @classmethod - def resolve_legacy(cls, hass: "HomeAssistant", domain: str) -> Integration | None: + def resolve_legacy(cls, hass: HomeAssistant, domain: str) -> Integration | None: """Resolve legacy component. Will create a stub manifest. @@ -319,7 +319,7 @@ class Integration: def __init__( self, - hass: "HomeAssistant", + hass: HomeAssistant, pkg_path: str, file_path: pathlib.Path, manifest: Manifest, @@ -494,7 +494,7 @@ class Integration: return f"" -async def async_get_integration(hass: "HomeAssistant", domain: str) -> Integration: +async def async_get_integration(hass: HomeAssistant, domain: str) -> Integration: """Get an integration.""" cache = hass.data.get(DATA_INTEGRATIONS) if cache is None: @@ -579,7 +579,7 @@ class CircularDependency(LoaderError): def _load_file( - hass: "HomeAssistant", comp_or_platform: str, base_paths: list[str] + hass: HomeAssistant, comp_or_platform: str, base_paths: list[str] ) -> ModuleType | None: """Try to load specified file. @@ -640,7 +640,7 @@ def _load_file( class ModuleWrapper: """Class to wrap a Python module and auto fill in hass argument.""" - def __init__(self, hass: "HomeAssistant", module: ModuleType) -> None: + def __init__(self, hass: HomeAssistant, module: ModuleType) -> None: """Initialize the module wrapper.""" self._hass = hass self._module = module @@ -704,7 +704,7 @@ def bind_hass(func: CALLABLE_T) -> CALLABLE_T: async def _async_component_dependencies( - hass: "HomeAssistant", + hass: HomeAssistant, start_domain: str, integration: Integration, loaded: set[str], diff --git a/script/hassfest/codeowners.py b/script/hassfest/codeowners.py index eba657f64cf..81c3c883965 100644 --- a/script/hassfest/codeowners.py +++ b/script/hassfest/codeowners.py @@ -1,5 +1,5 @@ """Generate CODEOWNERS.""" -from typing import Dict +from __future__ import annotations from .model import Config, Integration @@ -33,7 +33,7 @@ homeassistant/components/demo/weather @fabaff """ -def generate_and_validate(integrations: Dict[str, Integration]): +def generate_and_validate(integrations: dict[str, Integration]): """Generate CODEOWNERS.""" parts = [BASE] @@ -61,7 +61,7 @@ def generate_and_validate(integrations: Dict[str, Integration]): return "\n".join(parts) -def validate(integrations: Dict[str, Integration], config: Config): +def validate(integrations: dict[str, Integration], config: Config): """Validate CODEOWNERS.""" codeowners_path = config.root / "CODEOWNERS" config.cache["codeowners"] = content = generate_and_validate(integrations) @@ -79,7 +79,7 @@ def validate(integrations: Dict[str, Integration], config: Config): return -def generate(integrations: Dict[str, Integration], config: Config): +def generate(integrations: dict[str, Integration], config: Config): """Generate CODEOWNERS.""" codeowners_path = config.root / "CODEOWNERS" with open(str(codeowners_path), "w") as fp: diff --git a/script/hassfest/config_flow.py b/script/hassfest/config_flow.py index 9ae0daee1b0..e4d1be7bc46 100644 --- a/script/hassfest/config_flow.py +++ b/script/hassfest/config_flow.py @@ -1,6 +1,7 @@ """Generate config flow file.""" +from __future__ import annotations + import json -from typing import Dict from .model import Config, Integration @@ -90,7 +91,7 @@ def validate_integration(config: Config, integration: Integration): ) -def generate_and_validate(integrations: Dict[str, Integration], config: Config): +def generate_and_validate(integrations: dict[str, Integration], config: Config): """Validate and generate config flow data.""" domains = [] @@ -117,7 +118,7 @@ def generate_and_validate(integrations: Dict[str, Integration], config: Config): return BASE.format(json.dumps(domains, indent=4)) -def validate(integrations: Dict[str, Integration], config: Config): +def validate(integrations: dict[str, Integration], config: Config): """Validate config flow file.""" config_flow_path = config.root / "homeassistant/generated/config_flows.py" config.cache["config_flow"] = content = generate_and_validate(integrations, config) @@ -136,7 +137,7 @@ def validate(integrations: Dict[str, Integration], config: Config): return -def generate(integrations: Dict[str, Integration], config: Config): +def generate(integrations: dict[str, Integration], config: Config): """Generate config flow file.""" config_flow_path = config.root / "homeassistant/generated/config_flows.py" with open(str(config_flow_path), "w") as fp: diff --git a/script/hassfest/coverage.py b/script/hassfest/coverage.py index c561c5d9f16..1a0c684cfab 100644 --- a/script/hassfest/coverage.py +++ b/script/hassfest/coverage.py @@ -1,6 +1,7 @@ """Validate coverage files.""" +from __future__ import annotations + from pathlib import Path -from typing import Dict from .model import Config, Integration @@ -69,7 +70,7 @@ ALLOWED_IGNORE_VIOLATIONS = { } -def validate(integrations: Dict[str, Integration], config: Config): +def validate(integrations: dict[str, Integration], config: Config): """Validate coverage.""" coverage_path = config.root / ".coveragerc" diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py index b13d7929042..ed780ed067b 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -1,7 +1,8 @@ """Validate dependencies.""" +from __future__ import annotations + import ast from pathlib import Path -from typing import Dict, Set from homeassistant.requirements import DISCOVERY_INTEGRATIONS @@ -14,7 +15,7 @@ class ImportCollector(ast.NodeVisitor): def __init__(self, integration: Integration): """Initialize the import collector.""" self.integration = integration - self.referenced: Dict[Path, Set[str]] = {} + self.referenced: dict[Path, set[str]] = {} # Current file or dir we're inspecting self._cur_fil_dir = None @@ -156,7 +157,7 @@ IGNORE_VIOLATIONS = { } -def calc_allowed_references(integration: Integration) -> Set[str]: +def calc_allowed_references(integration: Integration) -> set[str]: """Return a set of allowed references.""" allowed_references = ( ALLOWED_USED_COMPONENTS @@ -173,9 +174,9 @@ def calc_allowed_references(integration: Integration) -> Set[str]: def find_non_referenced_integrations( - integrations: Dict[str, Integration], + integrations: dict[str, Integration], integration: Integration, - references: Dict[Path, Set[str]], + references: dict[Path, set[str]], ): """Find intergrations that are not allowed to be referenced.""" allowed_references = calc_allowed_references(integration) @@ -221,7 +222,7 @@ def find_non_referenced_integrations( def validate_dependencies( - integrations: Dict[str, Integration], integration: Integration + integrations: dict[str, Integration], integration: Integration ): """Validate all dependencies.""" # Some integrations are allowed to have violations. @@ -244,7 +245,7 @@ def validate_dependencies( ) -def validate(integrations: Dict[str, Integration], config): +def validate(integrations: dict[str, Integration], config): """Handle dependencies for integrations.""" # check for non-existing dependencies for integration in integrations.values(): diff --git a/script/hassfest/dhcp.py b/script/hassfest/dhcp.py index fbf695a9f73..a3abe80063e 100644 --- a/script/hassfest/dhcp.py +++ b/script/hassfest/dhcp.py @@ -1,6 +1,7 @@ """Generate dhcp file.""" +from __future__ import annotations + import json -from typing import Dict, List from .model import Config, Integration @@ -16,7 +17,7 @@ DHCP = {} """.strip() -def generate_and_validate(integrations: List[Dict[str, str]]): +def generate_and_validate(integrations: list[dict[str, str]]): """Validate and generate dhcp data.""" match_list = [] @@ -37,7 +38,7 @@ def generate_and_validate(integrations: List[Dict[str, str]]): return BASE.format(json.dumps(match_list, indent=4)) -def validate(integrations: Dict[str, Integration], config: Config): +def validate(integrations: dict[str, Integration], config: Config): """Validate dhcp file.""" dhcp_path = config.root / "homeassistant/generated/dhcp.py" config.cache["dhcp"] = content = generate_and_validate(integrations) @@ -56,7 +57,7 @@ def validate(integrations: Dict[str, Integration], config: Config): return -def generate(integrations: Dict[str, Integration], config: Config): +def generate(integrations: dict[str, Integration], config: Config): """Generate dhcp file.""" dhcp_path = config.root / "homeassistant/generated/dhcp.py" with open(str(dhcp_path), "w") as fp: diff --git a/script/hassfest/json.py b/script/hassfest/json.py index 39c1f6f13a9..49ebb05bbea 100644 --- a/script/hassfest/json.py +++ b/script/hassfest/json.py @@ -1,6 +1,7 @@ """Validate integration JSON files.""" +from __future__ import annotations + import json -from typing import Dict from .model import Integration @@ -20,7 +21,7 @@ def validate_json_files(integration: Integration): return -def validate(integrations: Dict[str, Integration], config): +def validate(integrations: dict[str, Integration], config): """Handle JSON files inside integrations.""" if not config.specific_integrations: return diff --git a/script/hassfest/manifest.py b/script/hassfest/manifest.py index c6f8f71f2d9..55bfa717a3f 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -1,5 +1,6 @@ """Manifest validation.""" -from typing import Dict +from __future__ import annotations + from urllib.parse import urlparse import voluptuous as vol @@ -145,7 +146,7 @@ def validate_manifest(integration: Integration): validate_version(integration) -def validate(integrations: Dict[str, Integration], config): +def validate(integrations: dict[str, Integration], config): """Handle all integrations manifests.""" for integration in integrations.values(): if integration.manifest: diff --git a/script/hassfest/model.py b/script/hassfest/model.py index 0750ef10b6c..c5b8dbff618 100644 --- a/script/hassfest/model.py +++ b/script/hassfest/model.py @@ -1,8 +1,10 @@ """Models for manifest validator.""" +from __future__ import annotations + import importlib import json import pathlib -from typing import Any, Dict, List, Optional +from typing import Any import attr @@ -24,12 +26,12 @@ class Error: class Config: """Config for the run.""" - specific_integrations: Optional[pathlib.Path] = attr.ib() + specific_integrations: pathlib.Path | None = attr.ib() root: pathlib.Path = attr.ib() action: str = attr.ib() requirements: bool = attr.ib() - errors: List[Error] = attr.ib(factory=list) - cache: Dict[str, Any] = attr.ib(factory=dict) + errors: list[Error] = attr.ib(factory=list) + cache: dict[str, Any] = attr.ib(factory=dict) def add_error(self, *args, **kwargs): """Add an error.""" @@ -65,9 +67,9 @@ class Integration: return integrations path: pathlib.Path = attr.ib() - manifest: Optional[dict] = attr.ib(default=None) - errors: List[Error] = attr.ib(factory=list) - warnings: List[Error] = attr.ib(factory=list) + manifest: dict | None = attr.ib(default=None) + errors: list[Error] = attr.ib(factory=list) + warnings: list[Error] = attr.ib(factory=list) @property def domain(self) -> str: @@ -80,17 +82,17 @@ class Integration: return self.path.as_posix().startswith("homeassistant/components") @property - def disabled(self) -> Optional[str]: + def disabled(self) -> str | None: """List of disabled.""" return self.manifest.get("disabled") @property - def requirements(self) -> List[str]: + def requirements(self) -> list[str]: """List of requirements.""" return self.manifest.get("requirements", []) @property - def dependencies(self) -> List[str]: + def dependencies(self) -> list[str]: """List of dependencies.""" return self.manifest.get("dependencies", []) diff --git a/script/hassfest/mqtt.py b/script/hassfest/mqtt.py index fdc16895d8c..718df4ac827 100644 --- a/script/hassfest/mqtt.py +++ b/script/hassfest/mqtt.py @@ -1,7 +1,8 @@ """Generate MQTT file.""" +from __future__ import annotations + from collections import defaultdict import json -from typing import Dict from .model import Config, Integration @@ -17,7 +18,7 @@ MQTT = {} """.strip() -def generate_and_validate(integrations: Dict[str, Integration]): +def generate_and_validate(integrations: dict[str, Integration]): """Validate and generate MQTT data.""" data = defaultdict(list) @@ -39,7 +40,7 @@ def generate_and_validate(integrations: Dict[str, Integration]): return BASE.format(json.dumps(data, indent=4)) -def validate(integrations: Dict[str, Integration], config: Config): +def validate(integrations: dict[str, Integration], config: Config): """Validate MQTT file.""" mqtt_path = config.root / "homeassistant/generated/mqtt.py" config.cache["mqtt"] = content = generate_and_validate(integrations) @@ -57,7 +58,7 @@ def validate(integrations: Dict[str, Integration], config: Config): return -def generate(integrations: Dict[str, Integration], config: Config): +def generate(integrations: dict[str, Integration], config: Config): """Generate MQTT file.""" mqtt_path = config.root / "homeassistant/generated/mqtt.py" with open(str(mqtt_path), "w") as fp: diff --git a/script/hassfest/requirements.py b/script/hassfest/requirements.py index b51cbff7185..fa5a36c6559 100644 --- a/script/hassfest/requirements.py +++ b/script/hassfest/requirements.py @@ -1,4 +1,6 @@ """Validate requirements.""" +from __future__ import annotations + from collections import deque import json import operator @@ -6,7 +8,6 @@ import os import re import subprocess import sys -from typing import Dict, Set from stdlib_list import stdlib_list from tqdm import tqdm @@ -58,7 +59,7 @@ def normalize_package_name(requirement: str) -> str: return package -def validate(integrations: Dict[str, Integration], config: Config): +def validate(integrations: dict[str, Integration], config: Config): """Handle requirements for integrations.""" ensure_cache() @@ -153,7 +154,7 @@ def ensure_cache(): PIPDEPTREE_CACHE = cache -def get_requirements(integration: Integration, packages: Set[str]) -> Set[str]: +def get_requirements(integration: Integration, packages: set[str]) -> set[str]: """Return all (recursively) requirements for an integration.""" ensure_cache() @@ -184,7 +185,7 @@ def get_requirements(integration: Integration, packages: Set[str]) -> Set[str]: return all_requirements -def install_requirements(integration: Integration, requirements: Set[str]) -> bool: +def install_requirements(integration: Integration, requirements: set[str]) -> bool: """Install integration requirements. Return True if successful. diff --git a/script/hassfest/services.py b/script/hassfest/services.py index 62e9b2f88a1..9577d134ccc 100644 --- a/script/hassfest/services.py +++ b/script/hassfest/services.py @@ -1,7 +1,8 @@ """Validate dependencies.""" +from __future__ import annotations + import pathlib import re -from typing import Dict import voluptuous as vol from voluptuous.humanize import humanize_error @@ -93,7 +94,7 @@ def validate_services(integration: Integration): ) -def validate(integrations: Dict[str, Integration], config): +def validate(integrations: dict[str, Integration], config): """Handle dependencies for integrations.""" # check services.yaml is cool for integration in integrations.values(): diff --git a/script/hassfest/ssdp.py b/script/hassfest/ssdp.py index c9b3b893118..c71d5432adf 100644 --- a/script/hassfest/ssdp.py +++ b/script/hassfest/ssdp.py @@ -1,7 +1,8 @@ """Generate ssdp file.""" +from __future__ import annotations + from collections import OrderedDict, defaultdict import json -from typing import Dict from .model import Config, Integration @@ -22,7 +23,7 @@ def sort_dict(value): return OrderedDict((key, value[key]) for key in sorted(value)) -def generate_and_validate(integrations: Dict[str, Integration]): +def generate_and_validate(integrations: dict[str, Integration]): """Validate and generate ssdp data.""" data = defaultdict(list) @@ -44,7 +45,7 @@ def generate_and_validate(integrations: Dict[str, Integration]): return BASE.format(json.dumps(data, indent=4)) -def validate(integrations: Dict[str, Integration], config: Config): +def validate(integrations: dict[str, Integration], config: Config): """Validate ssdp file.""" ssdp_path = config.root / "homeassistant/generated/ssdp.py" config.cache["ssdp"] = content = generate_and_validate(integrations) @@ -62,7 +63,7 @@ def validate(integrations: Dict[str, Integration], config: Config): return -def generate(integrations: Dict[str, Integration], config: Config): +def generate(integrations: dict[str, Integration], config: Config): """Generate ssdp file.""" ssdp_path = config.root / "homeassistant/generated/ssdp.py" with open(str(ssdp_path), "w") as fp: diff --git a/script/hassfest/translations.py b/script/hassfest/translations.py index 75886eedc6f..b7a6341d25c 100644 --- a/script/hassfest/translations.py +++ b/script/hassfest/translations.py @@ -1,9 +1,10 @@ """Validate integration translation files.""" +from __future__ import annotations + from functools import partial from itertools import chain import json import re -from typing import Dict import voluptuous as vol from voluptuous.humanize import humanize_error @@ -295,7 +296,7 @@ def validate_translation_file(config: Config, integration: Integration, all_stri ) -def validate(integrations: Dict[str, Integration], config: Config): +def validate(integrations: dict[str, Integration], config: Config): """Handle JSON files inside integrations.""" if config.specific_integrations: all_strings = None diff --git a/script/hassfest/zeroconf.py b/script/hassfest/zeroconf.py index 61162b02761..907c6aaceff 100644 --- a/script/hassfest/zeroconf.py +++ b/script/hassfest/zeroconf.py @@ -1,7 +1,8 @@ """Generate zeroconf file.""" +from __future__ import annotations + from collections import OrderedDict, defaultdict import json -from typing import Dict from .model import Config, Integration @@ -19,7 +20,7 @@ HOMEKIT = {} """.strip() -def generate_and_validate(integrations: Dict[str, Integration]): +def generate_and_validate(integrations: dict[str, Integration]): """Validate and generate zeroconf data.""" service_type_dict = defaultdict(list) homekit_dict = {} @@ -89,7 +90,7 @@ def generate_and_validate(integrations: Dict[str, Integration]): return BASE.format(json.dumps(zeroconf, indent=4), json.dumps(homekit, indent=4)) -def validate(integrations: Dict[str, Integration], config: Config): +def validate(integrations: dict[str, Integration], config: Config): """Validate zeroconf file.""" zeroconf_path = config.root / "homeassistant/generated/zeroconf.py" config.cache["zeroconf"] = content = generate_and_validate(integrations) @@ -108,7 +109,7 @@ def validate(integrations: Dict[str, Integration], config: Config): return -def generate(integrations: Dict[str, Integration], config: Config): +def generate(integrations: dict[str, Integration], config: Config): """Generate zeroconf file.""" zeroconf_path = config.root / "homeassistant/generated/zeroconf.py" with open(str(zeroconf_path), "w") as fp: diff --git a/script/scaffold/model.py b/script/scaffold/model.py index bfbcfa52544..f9c71072a1b 100644 --- a/script/scaffold/model.py +++ b/script/scaffold/model.py @@ -1,7 +1,8 @@ """Models for scaffolding.""" +from __future__ import annotations + import json from pathlib import Path -from typing import Set import attr @@ -21,9 +22,9 @@ class Info: discoverable: str = attr.ib(default=None) oauth2: str = attr.ib(default=None) - files_added: Set[Path] = attr.ib(factory=set) - tests_added: Set[Path] = attr.ib(factory=set) - examples_added: Set[Path] = attr.ib(factory=set) + files_added: set[Path] = attr.ib(factory=set) + tests_added: set[Path] = attr.ib(factory=set) + examples_added: set[Path] = attr.ib(factory=set) @property def integration_dir(self) -> Path: diff --git a/script/scaffold/templates/device_action/integration/device_action.py b/script/scaffold/templates/device_action/integration/device_action.py index 27a27cb95ee..3bd1c0b91b3 100644 --- a/script/scaffold/templates/device_action/integration/device_action.py +++ b/script/scaffold/templates/device_action/integration/device_action.py @@ -1,5 +1,5 @@ """Provides device actions for NEW_NAME.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -29,7 +29,7 @@ ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for NEW_NAME devices.""" registry = await entity_registry.async_get_registry(hass) actions = [] @@ -69,7 +69,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} diff --git a/script/scaffold/templates/device_condition/integration/device_condition.py b/script/scaffold/templates/device_condition/integration/device_condition.py index 6ad89332f8e..413812828e5 100644 --- a/script/scaffold/templates/device_condition/integration/device_condition.py +++ b/script/scaffold/templates/device_condition/integration/device_condition.py @@ -1,5 +1,5 @@ """Provide the device conditions for NEW_NAME.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -33,7 +33,7 @@ CONDITION_SCHEMA = DEVICE_CONDITION_BASE_SCHEMA.extend( async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions for NEW_NAME devices.""" registry = await entity_registry.async_get_registry(hass) conditions = [] diff --git a/script/scaffold/templates/device_trigger/integration/device_trigger.py b/script/scaffold/templates/device_trigger/integration/device_trigger.py index 2fa59d4eac8..ca430368544 100644 --- a/script/scaffold/templates/device_trigger/integration/device_trigger.py +++ b/script/scaffold/templates/device_trigger/integration/device_trigger.py @@ -1,5 +1,5 @@ """Provides device triggers for NEW_NAME.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -32,7 +32,7 @@ TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for NEW_NAME devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/script/scaffold/templates/reproduce_state/integration/reproduce_state.py b/script/scaffold/templates/reproduce_state/integration/reproduce_state.py index bb2ee7aee96..2031109a6bd 100644 --- a/script/scaffold/templates/reproduce_state/integration/reproduce_state.py +++ b/script/scaffold/templates/reproduce_state/integration/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an NEW_NAME state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -25,8 +27,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -70,8 +72,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce NEW_NAME states.""" # TODO pick one and remove other one diff --git a/script/scaffold/templates/significant_change/integration/significant_change.py b/script/scaffold/templates/significant_change/integration/significant_change.py index 23a00c603ac..8e6022f171b 100644 --- a/script/scaffold/templates/significant_change/integration/significant_change.py +++ b/script/scaffold/templates/significant_change/integration/significant_change.py @@ -1,5 +1,7 @@ """Helper to test significant NEW_NAME state changes.""" -from typing import Any, Optional +from __future__ import annotations + +from typing import Any from homeassistant.const import ATTR_DEVICE_CLASS from homeassistant.core import HomeAssistant, callback @@ -13,7 +15,7 @@ def async_check_significant_change( new_state: str, new_attrs: dict, **kwargs: Any, -) -> Optional[bool]: +) -> bool | None: """Test if state significantly changed.""" device_class = new_attrs.get(ATTR_DEVICE_CLASS) diff --git a/script/translations/download.py b/script/translations/download.py index 7fc4c3365cc..eab9c40370e 100755 --- a/script/translations/download.py +++ b/script/translations/download.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 """Merge all translation sources into a single JSON file.""" +from __future__ import annotations + import json import os import pathlib import re import subprocess -from typing import Dict, List, Union from .const import CLI_2_DOCKER_IMAGE, CORE_PROJECT_ID, INTEGRATIONS_DIR from .error import ExitApp @@ -51,7 +52,7 @@ def run_download_docker(): raise ExitApp("Failed to download translations") -def save_json(filename: str, data: Union[List, Dict]): +def save_json(filename: str, data: list | dict): """Save JSON data to a file. Returns True on success. diff --git a/tests/common.py b/tests/common.py index 451eee5f997..f1449721c29 100644 --- a/tests/common.py +++ b/tests/common.py @@ -202,9 +202,9 @@ async def async_test_home_assistant(loop, load_registries=True): start_time: float | None = None while len(self._pending_tasks) > max_remaining_tasks: - pending = [ + pending: Collection[Awaitable[Any]] = [ task for task in self._pending_tasks if not task.done() - ] # type: Collection[Awaitable[Any]] + ] self._pending_tasks.clear() if len(pending) > max_remaining_tasks: remaining_pending = await self._await_count_and_log_pending( diff --git a/tests/components/vera/common.py b/tests/components/vera/common.py index e666d513897..ae3c0a1a1de 100644 --- a/tests/components/vera/common.py +++ b/tests/components/vera/common.py @@ -118,7 +118,7 @@ class ComponentFactory: if controller_config.legacy_entity_unique_id: component_config[CONF_LEGACY_UNIQUE_ID] = True - controller = MagicMock(spec=pv.VeraController) # type: pv.VeraController + controller: pv.VeraController = MagicMock(spec=pv.VeraController) controller.base_url = component_config.get(CONF_CONTROLLER) controller.register = MagicMock() controller.start = MagicMock() diff --git a/tests/components/vera/test_binary_sensor.py b/tests/components/vera/test_binary_sensor.py index b3b8d2d6ae1..58b8ed7f461 100644 --- a/tests/components/vera/test_binary_sensor.py +++ b/tests/components/vera/test_binary_sensor.py @@ -12,7 +12,7 @@ async def test_binary_sensor( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor + vera_device: pv.VeraBinarySensor = MagicMock(spec=pv.VeraBinarySensor) vera_device.device_id = 1 vera_device.comm_failure = False vera_device.vera_device_id = vera_device.device_id diff --git a/tests/components/vera/test_climate.py b/tests/components/vera/test_climate.py index 5ec39f07953..4b301a1c96d 100644 --- a/tests/components/vera/test_climate.py +++ b/tests/components/vera/test_climate.py @@ -20,7 +20,7 @@ async def test_climate( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device = MagicMock(spec=pv.VeraThermostat) # type: pv.VeraThermostat + vera_device: pv.VeraThermostat = MagicMock(spec=pv.VeraThermostat) vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id vera_device.comm_failure = False @@ -131,7 +131,7 @@ async def test_climate_f( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device = MagicMock(spec=pv.VeraThermostat) # type: pv.VeraThermostat + vera_device: pv.VeraThermostat = MagicMock(spec=pv.VeraThermostat) vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id vera_device.comm_failure = False diff --git a/tests/components/vera/test_cover.py b/tests/components/vera/test_cover.py index cfc33fb2dcf..15b95b55e39 100644 --- a/tests/components/vera/test_cover.py +++ b/tests/components/vera/test_cover.py @@ -12,7 +12,7 @@ async def test_cover( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device = MagicMock(spec=pv.VeraCurtain) # type: pv.VeraCurtain + vera_device: pv.VeraCurtain = MagicMock(spec=pv.VeraCurtain) vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id vera_device.comm_failure = False diff --git a/tests/components/vera/test_init.py b/tests/components/vera/test_init.py index 85735d0320e..c828ef55fcd 100644 --- a/tests/components/vera/test_init.py +++ b/tests/components/vera/test_init.py @@ -24,7 +24,7 @@ async def test_init( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor + vera_device1: pv.VeraBinarySensor = MagicMock(spec=pv.VeraBinarySensor) vera_device1.device_id = 1 vera_device1.vera_device_id = vera_device1.device_id vera_device1.name = "first_dev" @@ -51,7 +51,7 @@ async def test_init_from_file( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor + vera_device1: pv.VeraBinarySensor = MagicMock(spec=pv.VeraBinarySensor) vera_device1.device_id = 1 vera_device1.vera_device_id = vera_device1.device_id vera_device1.name = "first_dev" @@ -78,14 +78,14 @@ async def test_multiple_controllers_with_legacy_one( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test multiple controllers with one legacy controller.""" - vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor + vera_device1: pv.VeraBinarySensor = MagicMock(spec=pv.VeraBinarySensor) vera_device1.device_id = 1 vera_device1.vera_device_id = vera_device1.device_id vera_device1.name = "first_dev" vera_device1.is_tripped = False entity1_id = "binary_sensor.first_dev_1" - vera_device2 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor + vera_device2: pv.VeraBinarySensor = MagicMock(spec=pv.VeraBinarySensor) vera_device2.device_id = 2 vera_device2.vera_device_id = vera_device2.device_id vera_device2.name = "second_dev" @@ -133,7 +133,7 @@ async def test_unload( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor + vera_device1: pv.VeraBinarySensor = MagicMock(spec=pv.VeraBinarySensor) vera_device1.device_id = 1 vera_device1.vera_device_id = vera_device1.device_id vera_device1.name = "first_dev" @@ -187,21 +187,21 @@ async def test_exclude_and_light_ids( hass: HomeAssistant, vera_component_factory: ComponentFactory, options ) -> None: """Test device exclusion, marking switches as lights and fixing the data type.""" - vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor + vera_device1: pv.VeraBinarySensor = MagicMock(spec=pv.VeraBinarySensor) vera_device1.device_id = 1 vera_device1.vera_device_id = 1 vera_device1.name = "dev1" vera_device1.is_tripped = False entity_id1 = "binary_sensor.dev1_1" - vera_device2 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor + vera_device2: pv.VeraBinarySensor = MagicMock(spec=pv.VeraBinarySensor) vera_device2.device_id = 2 vera_device2.vera_device_id = 2 vera_device2.name = "dev2" vera_device2.is_tripped = False entity_id2 = "binary_sensor.dev2_2" - vera_device3 = MagicMock(spec=pv.VeraSwitch) # type: pv.VeraSwitch + vera_device3: pv.VeraSwitch = MagicMock(spec=pv.VeraSwitch) vera_device3.device_id = 3 vera_device3.vera_device_id = 3 vera_device3.name = "dev3" @@ -210,7 +210,7 @@ async def test_exclude_and_light_ids( entity_id3 = "switch.dev3_3" - vera_device4 = MagicMock(spec=pv.VeraSwitch) # type: pv.VeraSwitch + vera_device4: pv.VeraSwitch = MagicMock(spec=pv.VeraSwitch) vera_device4.device_id = 4 vera_device4.vera_device_id = 4 vera_device4.name = "dev4" diff --git a/tests/components/vera/test_light.py b/tests/components/vera/test_light.py index ad5ad7e0259..c96199e5989 100644 --- a/tests/components/vera/test_light.py +++ b/tests/components/vera/test_light.py @@ -13,7 +13,7 @@ async def test_light( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device = MagicMock(spec=pv.VeraDimmer) # type: pv.VeraDimmer + vera_device: pv.VeraDimmer = MagicMock(spec=pv.VeraDimmer) vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id vera_device.comm_failure = False diff --git a/tests/components/vera/test_lock.py b/tests/components/vera/test_lock.py index 171f799f87b..644d58b9fc5 100644 --- a/tests/components/vera/test_lock.py +++ b/tests/components/vera/test_lock.py @@ -13,7 +13,7 @@ async def test_lock( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device = MagicMock(spec=pv.VeraLock) # type: pv.VeraLock + vera_device: pv.VeraLock = MagicMock(spec=pv.VeraLock) vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id vera_device.comm_failure = False diff --git a/tests/components/vera/test_scene.py b/tests/components/vera/test_scene.py index 2d4b7375498..b23d220e74e 100644 --- a/tests/components/vera/test_scene.py +++ b/tests/components/vera/test_scene.py @@ -12,7 +12,7 @@ async def test_scene( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_scene = MagicMock(spec=pv.VeraScene) # type: pv.VeraScene + vera_scene: pv.VeraScene = MagicMock(spec=pv.VeraScene) vera_scene.scene_id = 1 vera_scene.vera_scene_id = vera_scene.scene_id vera_scene.name = "dev1" diff --git a/tests/components/vera/test_sensor.py b/tests/components/vera/test_sensor.py index 566a3db482e..a2086f9f5e0 100644 --- a/tests/components/vera/test_sensor.py +++ b/tests/components/vera/test_sensor.py @@ -22,7 +22,7 @@ async def run_sensor_test( setup_callback: Callable[[pv.VeraController], None] = None, ) -> None: """Test generic sensor.""" - vera_device = MagicMock(spec=pv.VeraSensor) # type: pv.VeraSensor + vera_device: pv.VeraSensor = MagicMock(spec=pv.VeraSensor) vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id vera_device.comm_failure = False @@ -178,7 +178,7 @@ async def test_scene_controller_sensor( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device = MagicMock(spec=pv.VeraSensor) # type: pv.VeraSensor + vera_device: pv.VeraSensor = MagicMock(spec=pv.VeraSensor) vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id vera_device.comm_failure = False diff --git a/tests/components/vera/test_switch.py b/tests/components/vera/test_switch.py index ac90edc9ded..2ffb472aeeb 100644 --- a/tests/components/vera/test_switch.py +++ b/tests/components/vera/test_switch.py @@ -12,7 +12,7 @@ async def test_switch( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device = MagicMock(spec=pv.VeraSwitch) # type: pv.VeraSwitch + vera_device: pv.VeraSwitch = MagicMock(spec=pv.VeraSwitch) vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id vera_device.comm_failure = False From 314a5518f1b0043c74581970dceac20547b8615b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 23:20:25 +0100 Subject: [PATCH 472/831] Add python-typing-update to pre-commit-config (#48088) --- .pre-commit-config.yaml | 13 +++++++++++++ requirements_test_pre_commit.txt | 1 + 2 files changed, 14 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4ab91245b9e..ee4b5246100 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -67,6 +67,19 @@ repos: hooks: - id: prettier stages: [manual] + - repo: https://github.com/cdce8p/python-typing-update + rev: v0.3.0 + hooks: + # Run `python-typing-update` hook manually from time to time + # to update python typing syntax. + # Will require manual work, before submitting changes! + - id: python-typing-update + stages: [manual] + args: + - --py38-plus + - --force + - --keep-updates + files: ^(homeassistant|tests|script)/.+\.py$ - repo: local hooks: # Run mypy through our wrapper script in order to get the possible diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 07f1efbc694..24c8bf3a7ff 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -7,5 +7,6 @@ flake8-docstrings==1.5.0 flake8==3.8.4 isort==5.7.0 pydocstyle==5.1.1 +python-typing-update==0.3.0 pyupgrade==2.7.2 yamllint==1.24.2 From 7def36746718fed0f6906fc4c58a80af8ef9e22c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 19 Mar 2021 00:03:27 +0100 Subject: [PATCH 473/831] Update pyupgrade to v2.10.1 (#48089) --- .pre-commit-config.yaml | 2 +- requirements_test_pre_commit.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ee4b5246100..6629065005a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.7.2 + rev: v2.10.1 hooks: - id: pyupgrade args: [--py38-plus] diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 24c8bf3a7ff..fd5329a8ca1 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -8,5 +8,5 @@ flake8==3.8.4 isort==5.7.0 pydocstyle==5.1.1 python-typing-update==0.3.0 -pyupgrade==2.7.2 +pyupgrade==2.10.1 yamllint==1.24.2 From 8a37b616bf16f77eefe9392edb1356263f89c9ec Mon Sep 17 00:00:00 2001 From: Martidjen Date: Fri, 19 Mar 2021 00:47:59 +0100 Subject: [PATCH 474/831] Add Opentherm Gateway current and setpoint precision (#47484) Co-authored-by: Martin Hjelmare --- .../components/opentherm_gw/__init__.py | 13 +++ .../components/opentherm_gw/climate.py | 24 +++-- .../components/opentherm_gw/config_flow.py | 15 ++- .../components/opentherm_gw/const.py | 2 + .../components/opentherm_gw/strings.json | 3 +- .../opentherm_gw/test_config_flow.py | 92 ++++++++++++++++++- 6 files changed, 134 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/opentherm_gw/__init__.py b/homeassistant/components/opentherm_gw/__init__.py index cc08ec3da69..8686997e748 100644 --- a/homeassistant/components/opentherm_gw/__init__.py +++ b/homeassistant/components/opentherm_gw/__init__.py @@ -39,6 +39,8 @@ from .const import ( CONF_CLIMATE, CONF_FLOOR_TEMP, CONF_PRECISION, + CONF_READ_PRECISION, + CONF_SET_PRECISION, DATA_GATEWAYS, DATA_OPENTHERM_GW, DOMAIN, @@ -94,6 +96,17 @@ async def async_setup_entry(hass, config_entry): gateway = OpenThermGatewayDevice(hass, config_entry) hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][config_entry.data[CONF_ID]] = gateway + if config_entry.options.get(CONF_PRECISION): + migrate_options = dict(config_entry.options) + migrate_options.update( + { + CONF_READ_PRECISION: config_entry.options[CONF_PRECISION], + CONF_SET_PRECISION: config_entry.options[CONF_PRECISION], + } + ) + del migrate_options[CONF_PRECISION] + hass.config_entries.async_update_entry(config_entry, options=migrate_options) + config_entry.add_update_listener(options_updated) # Schedule directly on the loop to avoid blocking HA startup. diff --git a/homeassistant/components/opentherm_gw/climate.py b/homeassistant/components/opentherm_gw/climate.py index 8ec536e7331..1253fe4c6d2 100644 --- a/homeassistant/components/opentherm_gw/climate.py +++ b/homeassistant/components/opentherm_gw/climate.py @@ -28,7 +28,13 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import async_generate_entity_id from . import DOMAIN -from .const import CONF_FLOOR_TEMP, CONF_PRECISION, DATA_GATEWAYS, DATA_OPENTHERM_GW +from .const import ( + CONF_FLOOR_TEMP, + CONF_READ_PRECISION, + CONF_SET_PRECISION, + DATA_GATEWAYS, + DATA_OPENTHERM_GW, +) _LOGGER = logging.getLogger(__name__) @@ -61,7 +67,8 @@ class OpenThermClimate(ClimateEntity): ) self.friendly_name = gw_dev.name self.floor_temp = options.get(CONF_FLOOR_TEMP, DEFAULT_FLOOR_TEMP) - self.temp_precision = options.get(CONF_PRECISION) + self.temp_read_precision = options.get(CONF_READ_PRECISION) + self.temp_set_precision = options.get(CONF_SET_PRECISION) self._available = False self._current_operation = None self._current_temperature = None @@ -79,7 +86,8 @@ class OpenThermClimate(ClimateEntity): def update_options(self, entry): """Update climate entity options.""" self.floor_temp = entry.options[CONF_FLOOR_TEMP] - self.temp_precision = entry.options[CONF_PRECISION] + self.temp_read_precision = entry.options[CONF_READ_PRECISION] + self.temp_set_precision = entry.options[CONF_SET_PRECISION] self.async_write_ha_state() async def async_added_to_hass(self): @@ -178,8 +186,8 @@ class OpenThermClimate(ClimateEntity): @property def precision(self): """Return the precision of the system.""" - if self.temp_precision is not None and self.temp_precision != 0: - return self.temp_precision + if self.temp_read_precision: + return self.temp_read_precision if self.hass.config.units.temperature_unit == TEMP_CELSIUS: return PRECISION_HALVES return PRECISION_WHOLE @@ -234,7 +242,11 @@ class OpenThermClimate(ClimateEntity): @property def target_temperature_step(self): """Return the supported step of target temperature.""" - return self.precision + if self.temp_set_precision: + return self.temp_set_precision + if self.hass.config.units.temperature_unit == TEMP_CELSIUS: + return PRECISION_HALVES + return PRECISION_WHOLE @property def preset_mode(self): diff --git a/homeassistant/components/opentherm_gw/config_flow.py b/homeassistant/components/opentherm_gw/config_flow.py index 8da530bebda..8081d3bf96c 100644 --- a/homeassistant/components/opentherm_gw/config_flow.py +++ b/homeassistant/components/opentherm_gw/config_flow.py @@ -19,7 +19,7 @@ from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from . import DOMAIN -from .const import CONF_FLOOR_TEMP, CONF_PRECISION +from .const import CONF_FLOOR_TEMP, CONF_READ_PRECISION, CONF_SET_PRECISION class OpenThermGwConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -121,8 +121,17 @@ class OpenThermGwOptionsFlow(config_entries.OptionsFlow): data_schema=vol.Schema( { vol.Optional( - CONF_PRECISION, - default=self.config_entry.options.get(CONF_PRECISION, 0), + CONF_READ_PRECISION, + default=self.config_entry.options.get(CONF_READ_PRECISION, 0), + ): vol.All( + vol.Coerce(float), + vol.In( + [0, PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE] + ), + ), + vol.Optional( + CONF_SET_PRECISION, + default=self.config_entry.options.get(CONF_SET_PRECISION, 0), ): vol.All( vol.Coerce(float), vol.In( diff --git a/homeassistant/components/opentherm_gw/const.py b/homeassistant/components/opentherm_gw/const.py index 2c3e2f7071d..1a129d1dfbd 100644 --- a/homeassistant/components/opentherm_gw/const.py +++ b/homeassistant/components/opentherm_gw/const.py @@ -19,6 +19,8 @@ ATTR_CH_OVRD = "ch_override" CONF_CLIMATE = "climate" CONF_FLOOR_TEMP = "floor_temperature" CONF_PRECISION = "precision" +CONF_READ_PRECISION = "read_precision" +CONF_SET_PRECISION = "set_precision" DATA_GATEWAYS = "gateways" DATA_OPENTHERM_GW = "opentherm_gw" diff --git a/homeassistant/components/opentherm_gw/strings.json b/homeassistant/components/opentherm_gw/strings.json index 306529e7be1..937917608fa 100644 --- a/homeassistant/components/opentherm_gw/strings.json +++ b/homeassistant/components/opentherm_gw/strings.json @@ -22,7 +22,8 @@ "description": "Options for the OpenTherm Gateway", "data": { "floor_temperature": "Floor Temperature", - "precision": "Precision" + "read_precision": "Read Precision", + "set_precision": "Set Precision" } } } diff --git a/tests/components/opentherm_gw/test_config_flow.py b/tests/components/opentherm_gw/test_config_flow.py index 4d811b9f985..43da10b19cf 100644 --- a/tests/components/opentherm_gw/test_config_flow.py +++ b/tests/components/opentherm_gw/test_config_flow.py @@ -9,9 +9,17 @@ from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.opentherm_gw.const import ( CONF_FLOOR_TEMP, CONF_PRECISION, + CONF_READ_PRECISION, + CONF_SET_PRECISION, DOMAIN, ) -from homeassistant.const import CONF_DEVICE, CONF_ID, CONF_NAME, PRECISION_HALVES +from homeassistant.const import ( + CONF_DEVICE, + CONF_ID, + CONF_NAME, + PRECISION_HALVES, + PRECISION_TENTHS, +) from tests.common import MockConfigEntry @@ -167,6 +175,48 @@ async def test_form_connection_error(hass): assert len(mock_connect.mock_calls) == 1 +async def test_options_migration(hass): + """Test migration of precision option after update.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="Mock Gateway", + data={ + CONF_NAME: "Test Entry 1", + CONF_DEVICE: "/dev/ttyUSB0", + CONF_ID: "test_entry_1", + }, + options={ + CONF_FLOOR_TEMP: True, + CONF_PRECISION: PRECISION_TENTHS, + }, + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.opentherm_gw.OpenThermGatewayDevice.connect_and_subscribe", + return_value=True, + ), patch("homeassistant.components.opentherm_gw.async_setup", return_value=True): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init( + entry.entry_id, context={"source": config_entries.SOURCE_USER}, data=None + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"][CONF_READ_PRECISION] == PRECISION_TENTHS + assert result["data"][CONF_SET_PRECISION] == PRECISION_TENTHS + assert result["data"][CONF_FLOOR_TEMP] is True + + async def test_options_form(hass): """Test the options form.""" entry = MockConfigEntry( @@ -181,6 +231,14 @@ async def test_options_form(hass): ) entry.add_to_hass(hass) + with patch( + "homeassistant.components.opentherm_gw.async_setup", return_value=True + ), patch( + "homeassistant.components.opentherm_gw.async_setup_entry", return_value=True + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + result = await hass.config_entries.options.async_init( entry.entry_id, context={"source": "test"}, data=None ) @@ -189,11 +247,16 @@ async def test_options_form(hass): result = await hass.config_entries.options.async_configure( result["flow_id"], - user_input={CONF_FLOOR_TEMP: True, CONF_PRECISION: PRECISION_HALVES}, + user_input={ + CONF_FLOOR_TEMP: True, + CONF_READ_PRECISION: PRECISION_HALVES, + CONF_SET_PRECISION: PRECISION_HALVES, + }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["data"][CONF_PRECISION] == PRECISION_HALVES + assert result["data"][CONF_READ_PRECISION] == PRECISION_HALVES + assert result["data"][CONF_SET_PRECISION] == PRECISION_HALVES assert result["data"][CONF_FLOOR_TEMP] is True result = await hass.config_entries.options.async_init( @@ -201,9 +264,28 @@ async def test_options_form(hass): ) result = await hass.config_entries.options.async_configure( - result["flow_id"], user_input={CONF_PRECISION: 0} + result["flow_id"], user_input={CONF_READ_PRECISION: 0} ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["data"][CONF_PRECISION] == 0.0 + assert result["data"][CONF_READ_PRECISION] == 0.0 + assert result["data"][CONF_SET_PRECISION] == PRECISION_HALVES assert result["data"][CONF_FLOOR_TEMP] is True + + result = await hass.config_entries.options.async_init( + entry.entry_id, context={"source": "test"}, data=None + ) + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_FLOOR_TEMP: False, + CONF_READ_PRECISION: PRECISION_TENTHS, + CONF_SET_PRECISION: PRECISION_HALVES, + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"][CONF_READ_PRECISION] == PRECISION_TENTHS + assert result["data"][CONF_SET_PRECISION] == PRECISION_HALVES + assert result["data"][CONF_FLOOR_TEMP] is False From d77a28b8a1a9a9beacc306f8f1eed8797819ee66 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 19 Mar 2021 00:03:03 +0000 Subject: [PATCH 475/831] [ci skip] Translation update --- .../components/azure_devops/translations/nl.json | 6 +++++- .../components/control4/translations/nl.json | 3 ++- homeassistant/components/demo/translations/nl.json | 1 + .../components/enocean/translations/nl.json | 3 ++- homeassistant/components/hue/translations/nl.json | 1 + .../components/meteo_france/translations/nl.json | 4 ++++ .../components/netatmo/translations/nl.json | 4 ++++ .../components/opentherm_gw/translations/en.json | 4 +++- .../components/poolsense/translations/nl.json | 3 ++- .../components/simplisafe/translations/nl.json | 5 +++++ .../components/wolflink/translations/nl.json | 6 ++++-- .../wolflink/translations/sensor.nl.json | 14 ++++++++++++++ 12 files changed, 47 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/azure_devops/translations/nl.json b/homeassistant/components/azure_devops/translations/nl.json index adf2a84e4e9..971af5b8d58 100644 --- a/homeassistant/components/azure_devops/translations/nl.json +++ b/homeassistant/components/azure_devops/translations/nl.json @@ -9,11 +9,14 @@ "invalid_auth": "Ongeldige authenticatie", "project_error": "Kon geen projectinformatie ophalen." }, + "flow_title": "Azure DevOps: {project_url}", "step": { "reauth": { "data": { "personal_access_token": "Persoonlijk toegangstoken (PAT)" - } + }, + "description": "Authenticatie mislukt voor {project_url}. Voer uw huidige inloggegevens in.", + "title": "Herauthenticatie" }, "user": { "data": { @@ -21,6 +24,7 @@ "personal_access_token": "Persoonlijk toegangstoken (PAT)", "project": "Project" }, + "description": "Stel een Azure DevOps instantie in om toegang te krijgen tot uw project. Een persoonlijke toegangstoken is alleen nodig voor een priv\u00e9project.", "title": "Azure DevOps-project toevoegen" } } diff --git a/homeassistant/components/control4/translations/nl.json b/homeassistant/components/control4/translations/nl.json index d591b4631a4..f13dd5e7f64 100644 --- a/homeassistant/components/control4/translations/nl.json +++ b/homeassistant/components/control4/translations/nl.json @@ -14,7 +14,8 @@ "host": "IP-adres", "password": "Wachtwoord", "username": "Gebruikersnaam" - } + }, + "description": "Voer uw Control4-accountgegevens en het IP-adres van uw lokale controller in." } } }, diff --git a/homeassistant/components/demo/translations/nl.json b/homeassistant/components/demo/translations/nl.json index ac10172933f..8e7c97f7c3f 100644 --- a/homeassistant/components/demo/translations/nl.json +++ b/homeassistant/components/demo/translations/nl.json @@ -10,6 +10,7 @@ "options_1": { "data": { "bool": "Optioneel Boolean", + "constant": "Constant", "int": "Numerieke invoer" } }, diff --git a/homeassistant/components/enocean/translations/nl.json b/homeassistant/components/enocean/translations/nl.json index 27e4091552a..c7dd4985133 100644 --- a/homeassistant/components/enocean/translations/nl.json +++ b/homeassistant/components/enocean/translations/nl.json @@ -17,7 +17,8 @@ "manual": { "data": { "path": "USB-dongle-pad" - } + }, + "title": "Voer het pad naar uw ENOcean dongle in" } } } diff --git a/homeassistant/components/hue/translations/nl.json b/homeassistant/components/hue/translations/nl.json index d4047ec35ec..0938c18e1ea 100644 --- a/homeassistant/components/hue/translations/nl.json +++ b/homeassistant/components/hue/translations/nl.json @@ -58,6 +58,7 @@ "step": { "init": { "data": { + "allow_hue_groups": "Sta Hue-groepen toe", "allow_unreachable": "Onbereikbare lampen toestaan hun status correct te melden" } } diff --git a/homeassistant/components/meteo_france/translations/nl.json b/homeassistant/components/meteo_france/translations/nl.json index e828fa6e09f..c8f7258e100 100644 --- a/homeassistant/components/meteo_france/translations/nl.json +++ b/homeassistant/components/meteo_france/translations/nl.json @@ -4,11 +4,15 @@ "already_configured": "Stad al geconfigureerd", "unknown": "Onbekende fout: probeer het later nog eens" }, + "error": { + "empty": "Geen resultaat bij het zoeken naar een stad: controleer de invoer: stad" + }, "step": { "cities": { "data": { "city": "Stad" }, + "description": "Kies uw stad uit de lijst", "title": "M\u00e9t\u00e9o-France" }, "user": { diff --git a/homeassistant/components/netatmo/translations/nl.json b/homeassistant/components/netatmo/translations/nl.json index 53ff8bb5cb5..a0f9682fd74 100644 --- a/homeassistant/components/netatmo/translations/nl.json +++ b/homeassistant/components/netatmo/translations/nl.json @@ -53,6 +53,10 @@ "title": "Netatmo openbare weersensor" }, "public_weather_areas": { + "data": { + "new_area": "Naam van het gebied", + "weather_areas": "Weersgebieden" + }, "description": "Configureer openbare weersensoren.", "title": "Netatmo openbare weersensor" } diff --git a/homeassistant/components/opentherm_gw/translations/en.json b/homeassistant/components/opentherm_gw/translations/en.json index 9d74a168bae..0743e53e015 100644 --- a/homeassistant/components/opentherm_gw/translations/en.json +++ b/homeassistant/components/opentherm_gw/translations/en.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "Floor Temperature", - "precision": "Precision" + "precision": "Precision", + "read_precision": "Read Precision", + "set_precision": "Set Precision" }, "description": "Options for the OpenTherm Gateway" } diff --git a/homeassistant/components/poolsense/translations/nl.json b/homeassistant/components/poolsense/translations/nl.json index 38ef34d5afc..f88d14e297a 100644 --- a/homeassistant/components/poolsense/translations/nl.json +++ b/homeassistant/components/poolsense/translations/nl.json @@ -12,7 +12,8 @@ "email": "E-mail", "password": "Wachtwoord" }, - "description": "Wil je beginnen met instellen?" + "description": "Wil je beginnen met instellen?", + "title": "PoolSense" } } } diff --git a/homeassistant/components/simplisafe/translations/nl.json b/homeassistant/components/simplisafe/translations/nl.json index 29b9d9ab70b..7a6d9c5c4e3 100644 --- a/homeassistant/components/simplisafe/translations/nl.json +++ b/homeassistant/components/simplisafe/translations/nl.json @@ -7,9 +7,14 @@ "error": { "identifier_exists": "Account bestaat al", "invalid_auth": "Ongeldige authenticatie", + "still_awaiting_mfa": "Wacht nog steeds op MFA-e-mailklik", "unknown": "Onverwachte fout" }, "step": { + "mfa": { + "description": "Controleer uw e-mail voor een link van SimpliSafe. Nadat u de link hebt geverifieerd, gaat u hier terug om de installatie van de integratie te voltooien.", + "title": "SimpliSafe Multi-Factor Authenticatie" + }, "reauth_confirm": { "data": { "password": "Wachtwoord" diff --git a/homeassistant/components/wolflink/translations/nl.json b/homeassistant/components/wolflink/translations/nl.json index d3cde252467..069ed928962 100644 --- a/homeassistant/components/wolflink/translations/nl.json +++ b/homeassistant/components/wolflink/translations/nl.json @@ -12,13 +12,15 @@ "device": { "data": { "device_name": "Apparaat" - } + }, + "title": "Selecteer WOLF-apparaat" }, "user": { "data": { "password": "Wachtwoord", "username": "Gebruikersnaam" - } + }, + "title": "WOLF SmartSet-verbinding" } } } diff --git a/homeassistant/components/wolflink/translations/sensor.nl.json b/homeassistant/components/wolflink/translations/sensor.nl.json index 02e8e3f8c2e..4056e4762ec 100644 --- a/homeassistant/components/wolflink/translations/sensor.nl.json +++ b/homeassistant/components/wolflink/translations/sensor.nl.json @@ -2,14 +2,27 @@ "state": { "wolflink__state": { "1_x_warmwasser": "1 x DHW", + "abgasklappe": "Rookgasklep", + "aktiviert": "Geactiveerd", + "antilegionellenfunktion": "Anti-legionella functie", + "at_abschaltung": "OT afsluiten", + "at_frostschutz": "OT vorstbescherming", + "aus": "Uitgeschakeld", "auto": "Auto", + "auto_off_cool": "AutoOffCool", + "auto_on_cool": "AutoOnCool", "automatik_aus": "Automatisch UIT", "automatik_ein": "Automatisch AAN", + "bereit_keine_ladung": "Klaar, niet laden", + "betrieb_ohne_brenner": "Werken zonder brander", "cooling": "Koelen", "deaktiviert": "Inactief", "dhw_prior": "DHWPrior", "eco": "Eco", "ein": "Ingeschakeld", + "estrichtrocknung": "Dekvloer drogen", + "externe_deaktivierung": "Externe uitschakeling", + "fernschalter_ein": "Op afstand bedienen ingeschakeld", "frost_warmwasser": "DHW vorst", "frostschutz": "Vorstbescherming", "gasdruck": "Gasdruk", @@ -20,6 +33,7 @@ "initialisierung": "Initialisatie", "kalibration": "Kalibratie", "kalibration_heizbetrieb": "Kalibratie verwarmingsmodus", + "kalibration_kombibetrieb": "Kalibratie van de combimodus", "kalibration_warmwasserbetrieb": "DHW-kalibratie", "kombibetrieb": "Combi-modus", "kombigerat": "Combiketel", From 2f159577074e98c472ca29f8c456b5f00cfd5f60 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Fri, 19 Mar 2021 09:57:11 +0100 Subject: [PATCH 476/831] Refactor Netatmo test (#48097) * Refactor webhook simulate * Update test_climate.py --- tests/components/netatmo/common.py | 20 ++ tests/components/netatmo/test_climate.py | 409 +++++++++++++++-------- 2 files changed, 290 insertions(+), 139 deletions(-) diff --git a/tests/components/netatmo/common.py b/tests/components/netatmo/common.py index c8014a9b2a9..b952d6fe790 100644 --- a/tests/components/netatmo/common.py +++ b/tests/components/netatmo/common.py @@ -1,6 +1,9 @@ """Common methods used across tests for Netatmo.""" import json +from homeassistant.components.webhook import async_handle_webhook +from homeassistant.util.aiohttp import MockRequest + from tests.common import load_fixture CLIENT_ID = "1234" @@ -19,6 +22,13 @@ ALL_SCOPES = [ "write_thermostat", ] +COMMON_RESPONSE = { + "user_id": "91763b24c43d3e344f424e8d", + "home_id": "91763b24c43d3e344f424e8b", + "home_name": "MYHOME", + "user": {"id": "91763b24c43d3e344f424e8b", "email": "john@doe.com"}, +} + def fake_post_request(**args): """Return fake data.""" @@ -42,3 +52,13 @@ def fake_post_request(**args): def fake_post_request_no_data(**args): """Fake error during requesting backend data.""" return "{}" + + +async def simulate_webhook(hass, webhook_id, response): + """Simulate a webhook event.""" + request = MockRequest( + content=bytes(json.dumps({**COMMON_RESPONSE, **response}), "utf-8"), + mock_source="test", + ) + await async_handle_webhook(hass, webhook_id, request) + await hass.async_block_till_done() diff --git a/tests/components/netatmo/test_climate.py b/tests/components/netatmo/test_climate.py index e7ad1669769..910fb32a7cf 100644 --- a/tests/components/netatmo/test_climate.py +++ b/tests/components/netatmo/test_climate.py @@ -31,16 +31,9 @@ from homeassistant.components.netatmo.const import ( ATTR_SCHEDULE_NAME, SERVICE_SET_SCHEDULE, ) -from homeassistant.components.webhook import async_handle_webhook from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_WEBHOOK_ID -from homeassistant.util.aiohttp import MockRequest - -async def simulate_webhook(hass, webhook_id, response): - """Simulate a webhook event.""" - request = MockRequest(content=response, mock_source="test") - await async_handle_webhook(hass, webhook_id, request) - await hass.async_block_till_done() +from .common import simulate_webhook async def test_webhook_event_handling_thermostats(hass, climate_entry): @@ -65,16 +58,31 @@ async def test_webhook_event_handling_thermostats(hass, climate_entry): await hass.async_block_till_done() # Fake webhook thermostat manual set point - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",' - b'"home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE",' - b'"rooms": [{ "id": "2746182631", "name": "Livingroom", "type": "livingroom",' - b'"therm_setpoint_mode": "manual", "therm_setpoint_temperature": 21,' - b'"therm_setpoint_end_time": 1612734552}], "modules": [{"id": "12:34:56:00:01:ae",' - b'"name": "Livingroom", "type": "NATherm1"}]}, "mode": "manual", "event_type": "set_point",' - b'"temperature": 21, "push_type": "display_change"}' - ) + response = { + "room_id": "2746182631", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2746182631", + "name": "Livingroom", + "type": "livingroom", + "therm_setpoint_mode": "manual", + "therm_setpoint_temperature": 21, + "therm_setpoint_end_time": 1612734552, + } + ], + "modules": [ + {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} + ], + }, + "mode": "manual", + "event_type": "set_point", + "temperature": 21, + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "heat" @@ -94,15 +102,29 @@ async def test_webhook_event_handling_thermostats(hass, climate_entry): await hass.async_block_till_done() # Fake webhook thermostat mode change to "Max" - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",' - b'"home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE",' - b'"rooms": [{"id": "2746182631", "name": "Livingroom", "type": "livingroom",' - b'"therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],' - b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"}]},' - b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}' - ) + response = { + "room_id": "2746182631", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2746182631", + "name": "Livingroom", + "type": "livingroom", + "therm_setpoint_mode": "max", + "therm_setpoint_end_time": 1612749189, + } + ], + "modules": [ + {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} + ], + }, + "mode": "max", + "event_type": "set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "heat" @@ -118,15 +140,27 @@ async def test_webhook_event_handling_thermostats(hass, climate_entry): await hass.async_block_till_done() # Fake webhook turn thermostat off - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",' - b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",' - b'"rooms": [{"id": "2746182631","name": "Livingroom","type": "livingroom",' - b'"therm_setpoint_mode": "off"}],"modules": [{"id": "12:34:56:00:01:ae",' - b'"name": "Livingroom", "type": "NATherm1"}]}, "mode": "off", "event_type": "set_point",' - b'"push_type": "display_change"}' - ) + response = { + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2746182631", + "name": "Livingroom", + "type": "livingroom", + "therm_setpoint_mode": "off", + } + ], + "modules": [ + {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} + ], + }, + "mode": "off", + "event_type": "set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "off" @@ -141,15 +175,28 @@ async def test_webhook_event_handling_thermostats(hass, climate_entry): await hass.async_block_till_done() # Fake webhook thermostat mode cancel set point - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b","room_id": "2746182631",' - b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",' - b'"rooms": [{"id": "2746182631","name": "Livingroom","type": "livingroom",' - b'"therm_setpoint_mode": "home"}], "modules": [{"id": "12:34:56:00:01:ae",' - b'"name": "Livingroom", "type": "NATherm1"}]}, "mode": "home",' - b'"event_type": "cancel_set_point", "push_type": "display_change"}' - ) + response = { + "room_id": "2746182631", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2746182631", + "name": "Livingroom", + "type": "livingroom", + "therm_setpoint_mode": "home", + } + ], + "modules": [ + {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} + ], + }, + "mode": "home", + "event_type": "cancel_set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "auto" @@ -183,13 +230,13 @@ async def test_service_preset_mode_frost_guard_thermostat(hass, climate_entry): await hass.async_block_till_done() # Fake webhook thermostat mode change to "Frost Guard" - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b","user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' - b'"event_type": "therm_mode", "home": {"id": "91763b24c43d3e344f424e8b",' - b'"therm_mode": "hg"}, "mode": "hg", "previous_mode": "schedule",' - b'"push_type":"home_event_changed"}' - ) + response = { + "event_type": "therm_mode", + "home": {"id": "91763b24c43d3e344f424e8b", "therm_mode": "hg"}, + "mode": "hg", + "previous_mode": "schedule", + "push_type": "home_event_changed", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "auto" @@ -211,13 +258,13 @@ async def test_service_preset_mode_frost_guard_thermostat(hass, climate_entry): await hass.async_block_till_done() # Test webhook thermostat mode change to "Schedule" - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b","user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' - b'"event_type": "therm_mode", "home": {"id": "91763b24c43d3e344f424e8b",' - b'"therm_mode": "schedule"}, "mode": "schedule", "previous_mode": "hg",' - b'"push_type": "home_event_changed"}' - ) + response = { + "event_type": "therm_mode", + "home": {"id": "91763b24c43d3e344f424e8b", "therm_mode": "schedule"}, + "mode": "schedule", + "previous_mode": "hg", + "push_type": "home_event_changed", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "auto" @@ -248,13 +295,13 @@ async def test_service_preset_modes_thermostat(hass, climate_entry): await hass.async_block_till_done() # Fake webhook thermostat mode change to "Away" - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b","user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", ' - b'"event_type": "therm_mode","home": {"id": "91763b24c43d3e344f424e8b",' - b'"therm_mode": "away"},"mode": "away","previous_mode": "schedule",' - b'"push_type": "home_event_changed"}' - ) + response = { + "event_type": "therm_mode", + "home": {"id": "91763b24c43d3e344f424e8b", "therm_mode": "away"}, + "mode": "away", + "previous_mode": "schedule", + "push_type": "home_event_changed", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "auto" @@ -271,16 +318,30 @@ async def test_service_preset_modes_thermostat(hass, climate_entry): ) await hass.async_block_till_done() - # TFakeest webhook thermostat mode change to "Max" - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email":"john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",' - b'"home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE",' - b'"rooms": [{"id": "2746182631", "name": "Livingroom", "type": "livingroom",' - b'"therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],' - b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"}]},' - b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}' - ) + # Test webhook thermostat mode change to "Max" + response = { + "room_id": "2746182631", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2746182631", + "name": "Livingroom", + "type": "livingroom", + "therm_setpoint_mode": "max", + "therm_setpoint_end_time": 1612749189, + } + ], + "modules": [ + {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} + ], + }, + "mode": "max", + "event_type": "set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "heat" @@ -292,31 +353,42 @@ async def test_webhook_event_handling_no_data(hass, climate_entry): # Test webhook without home entry webhook_id = climate_entry.data[CONF_WEBHOOK_ID] - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' - b'"push_type": "home_event_changed"}' - ) + response = { + "push_type": "home_event_changed", + } await simulate_webhook(hass, webhook_id, response) # Test webhook with different home id - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "3d3e344f491763b24c424e8b",' - b'"room_id": "2746182631", "home": {"id": "3d3e344f491763b24c424e8b",' - b'"name": "MYHOME","country": "DE", "rooms": [], "modules": []}, "mode": "home",' - b'"event_type": "cancel_set_point", "push_type": "display_change"}' - ) + response = { + "home_id": "3d3e344f491763b24c424e8b", + "room_id": "2746182631", + "home": { + "id": "3d3e344f491763b24c424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [], + "modules": [], + }, + "mode": "home", + "event_type": "cancel_set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) # Test webhook without room entries - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' - b'"room_id": "2746182631", "home": {"id": "91763b24c43d3e344f424e8b",' - b'"name": "MYHOME", "country": "DE", "rooms": [], "modules": []}, "mode": "home",' - b'"event_type": "cancel_set_point","push_type": "display_change"}' - ) + response = { + "room_id": "2746182631", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [], + "modules": [], + }, + "mode": "home", + "event_type": "cancel_set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) @@ -363,15 +435,27 @@ async def test_service_preset_mode_already_boost_valves(hass, climate_entry): assert hass.states.get(climate_entity_entrada).attributes["temperature"] == 7 # Test webhook valve mode change to "Max" - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' - b'"room_id": "2833524037", "home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME",' - b'"country": "DE","rooms": [{"id": "2833524037", "name": "Entrada", "type": "lobby",' - b'"therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],' - b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}]},' - b'"mode": "max","event_type": "set_point","push_type": "display_change"}' - ) + response = { + "room_id": "2833524037", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2833524037", + "name": "Entrada", + "type": "lobby", + "therm_setpoint_mode": "max", + "therm_setpoint_end_time": 1612749189, + } + ], + "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], + }, + "mode": "max", + "event_type": "set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) # Test service setting the preset mode to "boost" @@ -384,15 +468,27 @@ async def test_service_preset_mode_already_boost_valves(hass, climate_entry): await hass.async_block_till_done() # Test webhook valve mode change to "Max" - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' - b'"room_id": "2833524037", "home": {"id": "91763b24c43d3e344f424e8b",' - b'"name": "MYHOME","country": "DE","rooms": [{"id": "2833524037", "name": "Entrada",' - b'"type": "lobby", "therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],' - b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}]},' - b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}' - ) + response = { + "room_id": "2833524037", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2833524037", + "name": "Entrada", + "type": "lobby", + "therm_setpoint_mode": "max", + "therm_setpoint_end_time": 1612749189, + } + ], + "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], + }, + "mode": "max", + "event_type": "set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_entrada).state == "heat" @@ -417,15 +513,27 @@ async def test_service_preset_mode_boost_valves(hass, climate_entry): await hass.async_block_till_done() # Fake backend response - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' - b'"room_id": "2833524037", "home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME",' - b'"country": "DE", "rooms": [{"id": "2833524037","name": "Entrada","type": "lobby",' - b'"therm_setpoint_mode": "max","therm_setpoint_end_time": 1612749189}],' - b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}]},' - b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}' - ) + response = { + "room_id": "2833524037", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2833524037", + "name": "Entrada", + "type": "lobby", + "therm_setpoint_mode": "max", + "therm_setpoint_end_time": 1612749189, + } + ], + "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], + }, + "mode": "max", + "event_type": "set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_entrada).state == "heat" @@ -460,14 +568,26 @@ async def test_valves_service_turn_off(hass, climate_entry): await hass.async_block_till_done() # Fake backend response for valve being turned off - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2833524037",' - b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",' - b'"rooms": [{"id": "2833524037","name": "Entrada","type": "lobby",' - b'"therm_setpoint_mode": "off"}], "modules": [{"id": "12:34:56:00:01:ae","name": "Entrada",' - b'"type": "NRV"}]}, "mode": "off", "event_type": "set_point", "push_type":"display_change"}' - ) + response = { + "room_id": "2833524037", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2833524037", + "name": "Entrada", + "type": "lobby", + "therm_setpoint_mode": "off", + } + ], + "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], + }, + "mode": "off", + "event_type": "set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_entrada).state == "off" @@ -488,15 +608,26 @@ async def test_valves_service_turn_on(hass, climate_entry): await hass.async_block_till_done() # Fake backend response for valve being turned on - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2833524037",' - b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",' - b'"rooms": [{"id": "2833524037","name": "Entrada","type": "lobby",' - b'"therm_setpoint_mode": "home"}], "modules": [{"id": "12:34:56:00:01:ae",' - b'"name": "Entrada", "type": "NRV"}]}, "mode": "home", "event_type": "cancel_set_point",' - b'"push_type": "display_change"}' - ) + response = { + "room_id": "2833524037", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2833524037", + "name": "Entrada", + "type": "lobby", + "therm_setpoint_mode": "home", + } + ], + "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], + }, + "mode": "home", + "event_type": "cancel_set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_entrada).state == "auto" From 987c2d1612a69422fb0cf6e94cf8ed5b69b6fbd0 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 19 Mar 2021 10:12:55 +0100 Subject: [PATCH 477/831] Type check KNX integration expose (#48055) --- homeassistant/components/knx/__init__.py | 2 +- homeassistant/components/knx/expose.py | 64 +++++++++++++++--------- homeassistant/components/knx/schema.py | 18 ++++++- 3 files changed, 58 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 348eac8f40e..c252572e28e 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -195,7 +195,7 @@ SERVICE_KNX_EVENT_REGISTER_SCHEMA = vol.Schema( ) SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA = vol.Any( - ExposeSchema.SCHEMA.extend( + ExposeSchema.EXPOSE_SENSOR_SCHEMA.extend( { vol.Optional(SERVICE_KNX_ATTR_REMOVE, default=False): cv.boolean, } diff --git a/homeassistant/components/knx/expose.py b/homeassistant/components/knx/expose.py index 8bdd3d1d1d1..3d28c394140 100644 --- a/homeassistant/components/knx/expose.py +++ b/homeassistant/components/knx/expose.py @@ -1,6 +1,8 @@ """Exposures to KNX bus.""" from __future__ import annotations +from typing import Callable + from xknx import XKNX from xknx.devices import DateTime, ExposeSensor @@ -11,9 +13,14 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import callback from homeassistant.helpers.event import async_track_state_change_event -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.typing import ( + ConfigType, + EventType, + HomeAssistantType, + StateType, +) from .const import KNX_ADDRESS from .schema import ExposeSchema @@ -21,19 +28,22 @@ from .schema import ExposeSchema @callback def create_knx_exposure( - hass: HomeAssistant, xknx: XKNX, config: ConfigType + hass: HomeAssistantType, xknx: XKNX, config: ConfigType ) -> KNXExposeSensor | KNXExposeTime: """Create exposures from config.""" address = config[KNX_ADDRESS] + expose_type = config[ExposeSchema.CONF_KNX_EXPOSE_TYPE] attribute = config.get(ExposeSchema.CONF_KNX_EXPOSE_ATTRIBUTE) - entity_id = config.get(CONF_ENTITY_ID) - expose_type = config.get(ExposeSchema.CONF_KNX_EXPOSE_TYPE) default = config.get(ExposeSchema.CONF_KNX_EXPOSE_DEFAULT) exposure: KNXExposeSensor | KNXExposeTime - if expose_type.lower() in ["time", "date", "datetime"]: + if ( + isinstance(expose_type, str) + and expose_type.lower() in ExposeSchema.EXPOSE_TIME_TYPES + ): exposure = KNXExposeTime(xknx, expose_type, address) else: + entity_id = config[CONF_ENTITY_ID] exposure = KNXExposeSensor( hass, xknx, @@ -43,14 +53,22 @@ def create_knx_exposure( default, address, ) - exposure.async_register() return exposure class KNXExposeSensor: """Object to Expose Home Assistant entity to KNX bus.""" - def __init__(self, hass, xknx, expose_type, entity_id, attribute, default, address): + def __init__( + self, + hass: HomeAssistantType, + xknx: XKNX, + expose_type: int | str, + entity_id: str, + attribute: str | None, + default: StateType, + address: str, + ) -> None: """Initialize of Expose class.""" self.hass = hass self.xknx = xknx @@ -59,17 +77,17 @@ class KNXExposeSensor: self.expose_attribute = attribute self.expose_default = default self.address = address - self.device = None - self._remove_listener = None + self._remove_listener: Callable[[], None] | None = None + self.device: ExposeSensor = self.async_register() @callback - def async_register(self): + def async_register(self) -> ExposeSensor: """Register listener.""" if self.expose_attribute is not None: _name = self.entity_id + "__" + self.expose_attribute else: _name = self.entity_id - self.device = ExposeSensor( + device = ExposeSensor( self.xknx, name=_name, group_address=self.address, @@ -78,6 +96,7 @@ class KNXExposeSensor: self._remove_listener = async_track_state_change_event( self.hass, [self.entity_id], self._async_entity_changed ) + return device @callback def shutdown(self) -> None: @@ -85,10 +104,9 @@ class KNXExposeSensor: if self._remove_listener is not None: self._remove_listener() self._remove_listener = None - if self.device is not None: - self.device.shutdown() + self.device.shutdown() - async def _async_entity_changed(self, event): + async def _async_entity_changed(self, event: EventType) -> None: """Handle entity change.""" new_state = event.data.get("new_state") if new_state is None: @@ -110,8 +128,9 @@ class KNXExposeSensor: return await self._async_set_knx_value(new_attribute) - async def _async_set_knx_value(self, value): + async def _async_set_knx_value(self, value: StateType) -> None: """Set new value on xknx ExposeSensor.""" + assert self.device is not None if value is None: if self.expose_default is None: return @@ -129,17 +148,17 @@ class KNXExposeSensor: class KNXExposeTime: """Object to Expose Time/Date object to KNX bus.""" - def __init__(self, xknx: XKNX, expose_type: str, address: str): + def __init__(self, xknx: XKNX, expose_type: str, address: str) -> None: """Initialize of Expose class.""" self.xknx = xknx self.expose_type = expose_type self.address = address - self.device = None + self.device: DateTime = self.async_register() @callback - def async_register(self): + def async_register(self) -> DateTime: """Register listener.""" - self.device = DateTime( + return DateTime( self.xknx, name=self.expose_type.capitalize(), broadcast_type=self.expose_type.upper(), @@ -148,7 +167,6 @@ class KNXExposeTime: ) @callback - def shutdown(self): + def shutdown(self) -> None: """Prepare for deletion.""" - if self.device is not None: - self.device.shutdown() + self.device.shutdown() diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index 376e86cf90c..0e290311f28 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -254,16 +254,30 @@ class ExposeSchema: CONF_KNX_EXPOSE_TYPE = CONF_TYPE CONF_KNX_EXPOSE_ATTRIBUTE = "attribute" CONF_KNX_EXPOSE_DEFAULT = "default" + EXPOSE_TIME_TYPES = [ + "time", + "date", + "datetime", + ] - SCHEMA = vol.Schema( + EXPOSE_TIME_SCHEMA = vol.Schema( + { + vol.Required(CONF_KNX_EXPOSE_TYPE): vol.All( + cv.string, str.lower, vol.In(EXPOSE_TIME_TYPES) + ), + vol.Required(KNX_ADDRESS): ga_validator, + } + ) + EXPOSE_SENSOR_SCHEMA = vol.Schema( { vol.Required(CONF_KNX_EXPOSE_TYPE): sensor_type_validator, vol.Required(KNX_ADDRESS): ga_validator, - vol.Optional(CONF_ENTITY_ID): cv.entity_id, + vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Optional(CONF_KNX_EXPOSE_ATTRIBUTE): cv.string, vol.Optional(CONF_KNX_EXPOSE_DEFAULT): cv.match_all, } ) + SCHEMA = vol.Any(EXPOSE_TIME_SCHEMA, EXPOSE_SENSOR_SCHEMA) class FanSchema: From 943ce8afaff83a69ab05410a68e5321d58609d93 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 19 Mar 2021 10:16:27 +0100 Subject: [PATCH 478/831] Type check KNX integration weather, notify and scene (#48051) --- homeassistant/components/knx/notify.py | 23 +++++++++++----- homeassistant/components/knx/scene.py | 20 +++++++++++--- homeassistant/components/knx/weather.py | 35 ++++++++++++++++++------- 3 files changed, 59 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/knx/notify.py b/homeassistant/components/knx/notify.py index 9d6ed35e36b..77b89b86c4d 100644 --- a/homeassistant/components/knx/notify.py +++ b/homeassistant/components/knx/notify.py @@ -1,14 +1,25 @@ """Support for KNX/IP notification services.""" from __future__ import annotations +from typing import Any + from xknx.devices import Notification as XknxNotification from homeassistant.components.notify import BaseNotificationService +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) from .const import DOMAIN -async def async_get_service(hass, config, discovery_info=None): +async def async_get_service( + hass: HomeAssistantType, + config: ConfigType, + discovery_info: DiscoveryInfoType | None = None, +) -> KNXNotificationService | None: """Get the KNX notification service.""" notification_devices = [] for device in hass.data[DOMAIN].xknx.devices: @@ -22,31 +33,31 @@ async def async_get_service(hass, config, discovery_info=None): class KNXNotificationService(BaseNotificationService): """Implement demo notification service.""" - def __init__(self, devices: list[XknxNotification]): + def __init__(self, devices: list[XknxNotification]) -> None: """Initialize the service.""" self.devices = devices @property - def targets(self): + def targets(self) -> dict[str, str]: """Return a dictionary of registered targets.""" ret = {} for device in self.devices: ret[device.name] = device.name return ret - async def async_send_message(self, message="", **kwargs): + async def async_send_message(self, message: str = "", **kwargs: Any) -> None: """Send a notification to knx bus.""" if "target" in kwargs: await self._async_send_to_device(message, kwargs["target"]) else: await self._async_send_to_all_devices(message) - async def _async_send_to_all_devices(self, message): + async def _async_send_to_all_devices(self, message: str) -> None: """Send a notification to knx bus to all connected devices.""" for device in self.devices: await device.set(message) - async def _async_send_to_device(self, message, names): + async def _async_send_to_device(self, message: str, names: str) -> None: """Send a notification to knx bus to device with given names.""" for device in self.devices: if device.name in names: diff --git a/homeassistant/components/knx/scene.py b/homeassistant/components/knx/scene.py index 6c76fdbd199..bc235da4f35 100644 --- a/homeassistant/components/knx/scene.py +++ b/homeassistant/components/knx/scene.py @@ -1,15 +1,28 @@ """Support for KNX scenes.""" -from typing import Any +from __future__ import annotations + +from typing import Any, Callable, Iterable from xknx.devices import Scene as XknxScene from homeassistant.components.scene import Scene +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) from .const import DOMAIN from .knx_entity import KnxEntity -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up the scenes for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: @@ -21,8 +34,9 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KNXScene(KnxEntity, Scene): """Representation of a KNX scene.""" - def __init__(self, device: XknxScene): + def __init__(self, device: XknxScene) -> None: """Init KNX scene.""" + self._device: XknxScene super().__init__(device) async def async_activate(self, **kwargs: Any) -> None: diff --git a/homeassistant/components/knx/weather.py b/homeassistant/components/knx/weather.py index 031af9f5af0..c022867284e 100644 --- a/homeassistant/components/knx/weather.py +++ b/homeassistant/components/knx/weather.py @@ -1,16 +1,30 @@ """Support for KNX/IP weather station.""" +from __future__ import annotations + +from typing import Callable, Iterable from xknx.devices import Weather as XknxWeather from homeassistant.components.weather import WeatherEntity from homeassistant.const import TEMP_CELSIUS +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) from .const import DOMAIN from .knx_entity import KnxEntity -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the scenes for KNX platform.""" +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: + """Set up weather entities for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: if isinstance(device, XknxWeather): @@ -21,22 +35,23 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KNXWeather(KnxEntity, WeatherEntity): """Representation of a KNX weather device.""" - def __init__(self, device: XknxWeather): + def __init__(self, device: XknxWeather) -> None: """Initialize of a KNX sensor.""" + self._device: XknxWeather super().__init__(device) @property - def temperature(self): + def temperature(self) -> float | None: """Return current temperature.""" return self._device.temperature @property - def temperature_unit(self): + def temperature_unit(self) -> str: """Return temperature unit.""" return TEMP_CELSIUS @property - def pressure(self): + def pressure(self) -> float | None: """Return current air pressure.""" # KNX returns pA - HA requires hPa return ( @@ -46,22 +61,22 @@ class KNXWeather(KnxEntity, WeatherEntity): ) @property - def condition(self): + def condition(self) -> str: """Return current weather condition.""" return self._device.ha_current_state().value @property - def humidity(self): + def humidity(self) -> float | None: """Return current humidity.""" return self._device.humidity @property - def wind_bearing(self): + def wind_bearing(self) -> int | None: """Return current wind bearing in degrees.""" return self._device.wind_bearing @property - def wind_speed(self): + def wind_speed(self) -> float | None: """Return current wind speed in km/h.""" # KNX only supports wind speed in m/s return ( From fb1e76db8c84b06b541df659a9dae4a8dee4cf78 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 19 Mar 2021 10:21:06 +0100 Subject: [PATCH 479/831] Type check KNX integration light (#48053) * type check light * review changes --- homeassistant/components/knx/light.py | 62 +++++++++++++++++---------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/knx/light.py b/homeassistant/components/knx/light.py index 50d067bf29a..940a9333696 100644 --- a/homeassistant/components/knx/light.py +++ b/homeassistant/components/knx/light.py @@ -1,4 +1,8 @@ """Support for KNX/IP lights.""" +from __future__ import annotations + +from typing import Any, Callable, Iterable + from xknx.devices import Light as XknxLight from homeassistant.components.light import ( @@ -12,17 +16,29 @@ from homeassistant.components.light import ( SUPPORT_WHITE_VALUE, LightEntity, ) +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) import homeassistant.util.color as color_util from .const import DOMAIN from .knx_entity import KnxEntity +from .schema import LightSchema DEFAULT_COLOR = (0.0, 0.0) DEFAULT_BRIGHTNESS = 255 DEFAULT_WHITE_VALUE = 255 -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up lights for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: @@ -34,12 +50,13 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KNXLight(KnxEntity, LightEntity): """Representation of a KNX light.""" - def __init__(self, device: XknxLight): + def __init__(self, device: XknxLight) -> None: """Initialize of KNX light.""" + self._device: XknxLight super().__init__(device) - self._min_kelvin = device.min_kelvin - self._max_kelvin = device.max_kelvin + self._min_kelvin = device.min_kelvin or LightSchema.DEFAULT_MIN_KELVIN + self._max_kelvin = device.max_kelvin or LightSchema.DEFAULT_MAX_KELVIN self._min_mireds = color_util.color_temperature_kelvin_to_mired( self._max_kelvin ) @@ -48,7 +65,7 @@ class KNXLight(KnxEntity, LightEntity): ) @property - def brightness(self): + def brightness(self) -> int | None: """Return the brightness of this light between 0..255.""" if self._device.supports_brightness: return self._device.current_brightness @@ -58,31 +75,31 @@ class KNXLight(KnxEntity, LightEntity): return None @property - def hs_color(self): + def hs_color(self) -> tuple[float, float] | None: """Return the HS color value.""" - rgb = None + rgb: tuple[int, int, int] | None = None if self._device.supports_rgbw or self._device.supports_color: rgb, _ = self._device.current_color return color_util.color_RGB_to_hs(*rgb) if rgb else None @property - def _hsv_color(self): + def _hsv_color(self) -> tuple[float, float, float] | None: """Return the HSV color value.""" - rgb = None + rgb: tuple[int, int, int] | None = None if self._device.supports_rgbw or self._device.supports_color: rgb, _ = self._device.current_color return color_util.color_RGB_to_hsv(*rgb) if rgb else None @property - def white_value(self): + def white_value(self) -> int | None: """Return the white value.""" - white = None + white: int | None = None if self._device.supports_rgbw: _, white = self._device.current_color return white @property - def color_temp(self): + def color_temp(self) -> int | None: """Return the color temperature in mireds.""" if self._device.supports_color_temperature: kelvin = self._device.current_color_temperature @@ -101,32 +118,32 @@ class KNXLight(KnxEntity, LightEntity): return None @property - def min_mireds(self): + def min_mireds(self) -> int: """Return the coldest color temp this light supports in mireds.""" return self._min_mireds @property - def max_mireds(self): + def max_mireds(self) -> int: """Return the warmest color temp this light supports in mireds.""" return self._max_mireds @property - def effect_list(self): + def effect_list(self) -> list[str] | None: """Return the list of supported effects.""" return None @property - def effect(self): + def effect(self) -> str | None: """Return the current effect.""" return None @property - def is_on(self): + def is_on(self) -> bool: """Return true if light is on.""" - return self._device.state + return bool(self._device.state) @property - def supported_features(self): + def supported_features(self) -> int: """Flag supported features.""" flags = 0 if self._device.supports_brightness: @@ -142,7 +159,7 @@ class KNXLight(KnxEntity, LightEntity): flags |= SUPPORT_COLOR_TEMP return flags - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the light on.""" brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness) hs_color = kwargs.get(ATTR_HS_COLOR, self.hs_color) @@ -183,7 +200,8 @@ class KNXLight(KnxEntity, LightEntity): hs_color = DEFAULT_COLOR if white_value is None and self._device.supports_rgbw: white_value = DEFAULT_WHITE_VALUE - rgb = color_util.color_hsv_to_RGB(*hs_color, brightness * 100 / 255) + hsv_color = hs_color + (brightness * 100 / 255,) + rgb = color_util.color_hsv_to_RGB(*hsv_color) await self._device.set_color(rgb, white_value) if update_color_temp: @@ -200,6 +218,6 @@ class KNXLight(KnxEntity, LightEntity): ) await self._device.set_tunable_white(relative_ct) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the light off.""" await self._device.set_off() From e522b311ce883eb3a1c04fadd2c968454882e552 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 19 Mar 2021 10:22:18 +0100 Subject: [PATCH 480/831] Type check KNX integration binary_sensor, sensor and switch (#48050) --- homeassistant/components/knx/binary_sensor.py | 22 ++++++++++---- homeassistant/components/knx/sensor.py | 26 ++++++++++++---- homeassistant/components/knx/switch.py | 30 ++++++++++++++----- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/knx/binary_sensor.py b/homeassistant/components/knx/binary_sensor.py index 6ee37abee18..df5a367098d 100644 --- a/homeassistant/components/knx/binary_sensor.py +++ b/homeassistant/components/knx/binary_sensor.py @@ -1,17 +1,28 @@ """Support for KNX/IP binary sensors.""" from __future__ import annotations -from typing import Any +from typing import Any, Callable, Iterable from xknx.devices import BinarySensor as XknxBinarySensor from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorEntity +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) from .const import ATTR_COUNTER, DOMAIN from .knx_entity import KnxEntity -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up binary sensor(s) for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: @@ -23,19 +34,20 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KNXBinarySensor(KnxEntity, BinarySensorEntity): """Representation of a KNX binary sensor.""" - def __init__(self, device: XknxBinarySensor): + def __init__(self, device: XknxBinarySensor) -> None: """Initialize of KNX binary sensor.""" + self._device: XknxBinarySensor super().__init__(device) @property - def device_class(self): + def device_class(self) -> str | None: """Return the class of this sensor.""" if self._device.device_class in DEVICE_CLASSES: return self._device.device_class return None @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary sensor is on.""" return self._device.is_on() diff --git a/homeassistant/components/knx/sensor.py b/homeassistant/components/knx/sensor.py index 2409d7a6425..f3155eebf01 100644 --- a/homeassistant/components/knx/sensor.py +++ b/homeassistant/components/knx/sensor.py @@ -1,14 +1,29 @@ """Support for KNX/IP sensors.""" +from __future__ import annotations + +from typing import Callable, Iterable + from xknx.devices import Sensor as XknxSensor from homeassistant.components.sensor import DEVICE_CLASSES from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, + StateType, +) from .const import DOMAIN from .knx_entity import KnxEntity -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up sensor(s) for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: @@ -20,22 +35,23 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KNXSensor(KnxEntity, Entity): """Representation of a KNX sensor.""" - def __init__(self, device: XknxSensor): + def __init__(self, device: XknxSensor) -> None: """Initialize of a KNX sensor.""" + self._device: XknxSensor super().__init__(device) @property - def state(self): + def state(self) -> StateType: """Return the state of the sensor.""" return self._device.resolve_state() @property - def unit_of_measurement(self) -> str: + def unit_of_measurement(self) -> str | None: """Return the unit this state is expressed in.""" return self._device.unit_of_measurement() @property - def device_class(self): + def device_class(self) -> str | None: """Return the device class of the sensor.""" device_class = self._device.ha_device_class() if device_class in DEVICE_CLASSES: diff --git a/homeassistant/components/knx/switch.py b/homeassistant/components/knx/switch.py index ae3048e2d23..40b028267ba 100644 --- a/homeassistant/components/knx/switch.py +++ b/homeassistant/components/knx/switch.py @@ -1,13 +1,28 @@ """Support for KNX/IP switches.""" +from __future__ import annotations + +from typing import Any, Callable, Iterable + from xknx.devices import Switch as XknxSwitch from homeassistant.components.switch import SwitchEntity +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) -from . import DOMAIN +from .const import DOMAIN from .knx_entity import KnxEntity -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up switch(es) for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: @@ -19,19 +34,20 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KNXSwitch(KnxEntity, SwitchEntity): """Representation of a KNX switch.""" - def __init__(self, device: XknxSwitch): + def __init__(self, device: XknxSwitch) -> None: """Initialize of KNX switch.""" + self._device: XknxSwitch super().__init__(device) @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" - return self._device.state + return bool(self._device.state) - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" await self._device.set_on() - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" await self._device.set_off() From 66b537c0e36c68f8072dbbc3a4259b6d69e6160e Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 19 Mar 2021 10:23:50 +0100 Subject: [PATCH 481/831] Type check KNX integration factory and schema (#48045) these are used non-optional anyway get them per config[] notation --- homeassistant/components/knx/factory.py | 8 ++++---- homeassistant/components/knx/schema.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/knx/factory.py b/homeassistant/components/knx/factory.py index 0543119cadf..827ec83a8e1 100644 --- a/homeassistant/components/knx/factory.py +++ b/homeassistant/components/knx/factory.py @@ -264,9 +264,9 @@ def _create_climate(knx_module: XKNX, config: ConfigType) -> XknxClimate: max_temp=config.get(ClimateSchema.CONF_MAX_TEMP), mode=climate_mode, on_off_invert=config[ClimateSchema.CONF_ON_OFF_INVERT], - create_temperature_sensors=config.get( + create_temperature_sensors=config[ ClimateSchema.CONF_CREATE_TEMPERATURE_SENSORS - ), + ], ) @@ -277,7 +277,7 @@ def _create_switch(knx_module: XKNX, config: ConfigType) -> XknxSwitch: name=config[CONF_NAME], group_address=config[KNX_ADDRESS], group_address_state=config.get(SwitchSchema.CONF_STATE_ADDRESS), - invert=config.get(SwitchSchema.CONF_INVERT), + invert=config[SwitchSchema.CONF_INVERT], ) @@ -320,7 +320,7 @@ def _create_binary_sensor(knx_module: XKNX, config: ConfigType) -> XknxBinarySen knx_module, name=device_name, group_address_state=config[BinarySensorSchema.CONF_STATE_ADDRESS], - invert=config.get(BinarySensorSchema.CONF_INVERT), + invert=config[BinarySensorSchema.CONF_INVERT], sync_state=config[BinarySensorSchema.CONF_SYNC_STATE], device_class=config.get(CONF_DEVICE_CLASS), ignore_internal_state=config[BinarySensorSchema.CONF_IGNORE_INTERNAL_STATE], diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index 0e290311f28..6ff9d295b75 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -30,13 +30,13 @@ from .const import ( ################## ga_validator = vol.Any( - cv.matches_regex(GroupAddress.ADDRESS_RE), + cv.matches_regex(GroupAddress.ADDRESS_RE.pattern), vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)), msg="value does not match pattern for KNX group address '
//', '
/' or '' (eg.'1/2/3', '9/234', '123')", ) ia_validator = vol.Any( - cv.matches_regex(IndividualAddress.ADDRESS_RE), + cv.matches_regex(IndividualAddress.ADDRESS_RE.pattern), vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)), msg="value does not match pattern for KNX individual address '..' (eg.'1.1.100')", ) @@ -96,12 +96,12 @@ class BinarySensorSchema: vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator, vol.Optional(CONF_IGNORE_INTERNAL_STATE, default=False): cv.boolean, + vol.Optional(CONF_INVERT, default=False): cv.boolean, vol.Required(CONF_STATE_ADDRESS): ga_validator, vol.Optional(CONF_CONTEXT_TIMEOUT): vol.All( vol.Coerce(float), vol.Range(min=0, max=10) ), vol.Optional(CONF_DEVICE_CLASS): cv.string, - vol.Optional(CONF_INVERT): cv.boolean, vol.Optional(CONF_RESET_AFTER): cv.positive_float, } ), @@ -448,9 +448,9 @@ class SwitchSchema: SCHEMA = vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_INVERT, default=False): cv.boolean, vol.Required(KNX_ADDRESS): ga_validator, vol.Optional(CONF_STATE_ADDRESS): ga_validator, - vol.Optional(CONF_INVERT): cv.boolean, } ) From 7858b599449d1aa90c4f6a10c62c098a2e66deb7 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Fri, 19 Mar 2021 10:25:17 +0100 Subject: [PATCH 482/831] Use device class voltage in NUT integration (#48096) --- homeassistant/components/nut/const.py | 36 +++++++++++++++++++-------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/nut/const.py b/homeassistant/components/nut/const.py index b8b315df59e..890ac3697dd 100644 --- a/homeassistant/components/nut/const.py +++ b/homeassistant/components/nut/const.py @@ -4,6 +4,7 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, ) from homeassistant.const import ( ELECTRICAL_CURRENT_AMPERE, @@ -120,10 +121,15 @@ SENSOR_TYPES = { None, ], "battery.charger.status": ["Charging Status", "", "mdi:information-outline", None], - "battery.voltage": ["Battery Voltage", VOLT, "mdi:flash", None], - "battery.voltage.nominal": ["Nominal Battery Voltage", VOLT, "mdi:flash", None], - "battery.voltage.low": ["Low Battery Voltage", VOLT, "mdi:flash", None], - "battery.voltage.high": ["High Battery Voltage", VOLT, "mdi:flash", None], + "battery.voltage": ["Battery Voltage", VOLT, None, DEVICE_CLASS_VOLTAGE], + "battery.voltage.nominal": [ + "Nominal Battery Voltage", + VOLT, + None, + DEVICE_CLASS_VOLTAGE, + ], + "battery.voltage.low": ["Low Battery Voltage", VOLT, None, DEVICE_CLASS_VOLTAGE], + "battery.voltage.high": ["High Battery Voltage", VOLT, None, DEVICE_CLASS_VOLTAGE], "battery.capacity": ["Battery Capacity", "Ah", "mdi:flash", None], "battery.current": [ "Battery Current", @@ -178,16 +184,21 @@ SENSOR_TYPES = { "mdi:information-outline", None, ], - "input.transfer.low": ["Low Voltage Transfer", VOLT, "mdi:flash", None], - "input.transfer.high": ["High Voltage Transfer", VOLT, "mdi:flash", None], + "input.transfer.low": ["Low Voltage Transfer", VOLT, None, DEVICE_CLASS_VOLTAGE], + "input.transfer.high": ["High Voltage Transfer", VOLT, None, DEVICE_CLASS_VOLTAGE], "input.transfer.reason": [ "Voltage Transfer Reason", "", "mdi:information-outline", None, ], - "input.voltage": ["Input Voltage", VOLT, "mdi:flash", None], - "input.voltage.nominal": ["Nominal Input Voltage", VOLT, "mdi:flash", None], + "input.voltage": ["Input Voltage", VOLT, None, DEVICE_CLASS_VOLTAGE], + "input.voltage.nominal": [ + "Nominal Input Voltage", + VOLT, + None, + DEVICE_CLASS_VOLTAGE, + ], "input.frequency": ["Input Line Frequency", FREQUENCY_HERTZ, "mdi:flash", None], "input.frequency.nominal": [ "Nominal Input Line Frequency", @@ -208,8 +219,13 @@ SENSOR_TYPES = { "mdi:flash", None, ], - "output.voltage": ["Output Voltage", VOLT, "mdi:flash", None], - "output.voltage.nominal": ["Nominal Output Voltage", VOLT, "mdi:flash", None], + "output.voltage": ["Output Voltage", VOLT, None, DEVICE_CLASS_VOLTAGE], + "output.voltage.nominal": [ + "Nominal Output Voltage", + VOLT, + None, + DEVICE_CLASS_VOLTAGE, + ], "output.frequency": ["Output Frequency", FREQUENCY_HERTZ, "mdi:flash", None], "output.frequency.nominal": [ "Nominal Output Frequency", From aaafe399a1804410d1d5a227950cd19cba3aa8cc Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Fri, 19 Mar 2021 12:19:27 +0100 Subject: [PATCH 483/831] Add tests for Netatmo light (#46381) * Add tests for Netatmo light * Improve docstring * Register the camera data class for the light platform * Remove freezegun dependency * Update tests * Update tests/components/netatmo/test_light.py Co-authored-by: Erik Montnemery * Deduplicate webhook test data * Mock pytest to verify it is called * Don't test internals * Rename * Assert light still on with erroneous event data Co-authored-by: Erik Montnemery --- .coveragerc | 1 - homeassistant/components/netatmo/light.py | 6 ++ tests/components/netatmo/conftest.py | 6 ++ tests/components/netatmo/test_light.py | 79 +++++++++++++++++++++++ tests/fixtures/netatmo/ping.json | 4 ++ 5 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 tests/components/netatmo/test_light.py create mode 100644 tests/fixtures/netatmo/ping.json diff --git a/.coveragerc b/.coveragerc index 92d55e83527..71dbda8ce40 100644 --- a/.coveragerc +++ b/.coveragerc @@ -647,7 +647,6 @@ omit = homeassistant/components/netatmo/camera.py homeassistant/components/netatmo/data_handler.py homeassistant/components/netatmo/helper.py - homeassistant/components/netatmo/light.py homeassistant/components/netatmo/netatmo_entity_base.py homeassistant/components/netatmo/sensor.py homeassistant/components/netatmo/webhook.py diff --git a/homeassistant/components/netatmo/light.py b/homeassistant/components/netatmo/light.py index eed12f048c8..08744c462e8 100644 --- a/homeassistant/components/netatmo/light.py +++ b/homeassistant/components/netatmo/light.py @@ -31,6 +31,10 @@ async def async_setup_entry(hass, entry, async_add_entities): data_handler = hass.data[DOMAIN][entry.entry_id][DATA_HANDLER] + await data_handler.register_data_class( + CAMERA_DATA_CLASS_NAME, CAMERA_DATA_CLASS_NAME, None + ) + if CAMERA_DATA_CLASS_NAME not in data_handler.data: raise PlatformNotReady @@ -64,6 +68,8 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(await get_entities(), True) + await data_handler.unregister_data_class(CAMERA_DATA_CLASS_NAME, None) + class NetatmoLight(NetatmoBase, LightEntity): """Representation of a Netatmo Presence camera light.""" diff --git a/tests/components/netatmo/conftest.py b/tests/components/netatmo/conftest.py index b18b70f323e..4e59eaacb82 100644 --- a/tests/components/netatmo/conftest.py +++ b/tests/components/netatmo/conftest.py @@ -81,6 +81,8 @@ async def mock_sensor_entry_fixture(hass, config_entry): """Mock setup of sensor platform.""" with selected_platforms(["sensor"]): await hass.config_entries.async_setup(config_entry.entry_id) + + await hass.async_block_till_done() return config_entry @@ -89,6 +91,8 @@ async def mock_camera_entry_fixture(hass, config_entry): """Mock setup of camera platform.""" with selected_platforms(["camera"]): await hass.config_entries.async_setup(config_entry.entry_id) + + await hass.async_block_till_done() return config_entry @@ -97,6 +101,8 @@ async def mock_light_entry_fixture(hass, config_entry): """Mock setup of light platform.""" with selected_platforms(["light"]): await hass.config_entries.async_setup(config_entry.entry_id) + + await hass.async_block_till_done() return config_entry diff --git a/tests/components/netatmo/test_light.py b/tests/components/netatmo/test_light.py new file mode 100644 index 00000000000..8f2f371a15e --- /dev/null +++ b/tests/components/netatmo/test_light.py @@ -0,0 +1,79 @@ +"""The tests for Netatmo light.""" +from unittest.mock import patch + +from homeassistant.components.light import ( + DOMAIN as LIGHT_DOMAIN, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, +) +from homeassistant.const import ATTR_ENTITY_ID, CONF_WEBHOOK_ID + +from .common import simulate_webhook + + +async def test_light_setup_and_services(hass, light_entry): + """Test setup and services.""" + webhook_id = light_entry.data[CONF_WEBHOOK_ID] + + # Fake webhook activation + webhook_data = { + "push_type": "webhook_activation", + } + await simulate_webhook(hass, webhook_id, webhook_data) + await hass.async_block_till_done() + + light_entity = "light.netatmo_garden" + assert hass.states.get(light_entity).state == "unavailable" + + # Trigger light mode change + response = { + "event_type": "light_mode", + "device_id": "12:34:56:00:a5:a4", + "camera_id": "12:34:56:00:a5:a4", + "event_id": "601dce1560abca1ebad9b723", + "push_type": "NOC-light_mode", + "sub_type": "on", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(light_entity).state == "on" + + # Trigger light mode change with erroneous webhook data + response = { + "user_id": "91763b24c43d3e344f424e8d", + "event_type": "light_mode", + "device_id": "12:34:56:00:a5:a4", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(light_entity).state == "on" + + # Test turning light off + with patch("pyatmo.camera.CameraData.set_state") as mock_set_state: + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: light_entity}, + blocking=True, + ) + await hass.async_block_till_done() + mock_set_state.assert_called_once_with( + home_id="91763b24c43d3e344f424e8b", + camera_id="12:34:56:00:a5:a4", + floodlight="auto", + ) + + # Test turning light on + with patch("pyatmo.camera.CameraData.set_state") as mock_set_state: + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: light_entity}, + blocking=True, + ) + await hass.async_block_till_done() + mock_set_state.assert_called_once_with( + home_id="91763b24c43d3e344f424e8b", + camera_id="12:34:56:00:a5:a4", + floodlight="on", + ) diff --git a/tests/fixtures/netatmo/ping.json b/tests/fixtures/netatmo/ping.json new file mode 100644 index 00000000000..784975de5b0 --- /dev/null +++ b/tests/fixtures/netatmo/ping.json @@ -0,0 +1,4 @@ +{ + "local_url": "http://192.168.0.123/678460a0d47e5618699fb31169e2b47d", + "product_name": "Welcome Netatmo" +} \ No newline at end of file From 4ee4d674d8931927eae5222e3bf8dd6e26f3c6e5 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Fri, 19 Mar 2021 12:19:57 +0100 Subject: [PATCH 484/831] Add tests for Netatmo camera (#46380) * Add test for Netatmo camera * Improve docstrings * Remove light tests * Remove freezegun from tests * Update camera tests * Remove freezegun dependency * Update tests/components/netatmo/test_camera.py Co-authored-by: Erik Montnemery * Update tests/components/netatmo/test_camera.py Co-authored-by: Erik Montnemery * Deduplication of the fake webhook payload * Mock pyatmo instead of checking the logs * Clean up * Further deduplication * Assert function arguments * Rename mocked functions * Update .coveragerc Co-authored-by: Erik Montnemery --- .coveragerc | 1 - homeassistant/components/netatmo/camera.py | 2 +- tests/components/netatmo/conftest.py | 2 + tests/components/netatmo/test_camera.py | 207 +++++++++++++++++++++ 4 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 tests/components/netatmo/test_camera.py diff --git a/.coveragerc b/.coveragerc index 71dbda8ce40..48fb4ce756f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -644,7 +644,6 @@ omit = homeassistant/components/nello/lock.py homeassistant/components/nest/legacy/* homeassistant/components/netatmo/__init__.py - homeassistant/components/netatmo/camera.py homeassistant/components/netatmo/data_handler.py homeassistant/components/netatmo/helper.py homeassistant/components/netatmo/netatmo_entity_base.py diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index b02d77e45fb..5445231282c 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -350,7 +350,7 @@ class NetatmoCamera(NetatmoBase, Camera): def _service_set_camera_light(self, **kwargs): """Service to set light mode.""" mode = kwargs.get(ATTR_CAMERA_LIGHT_MODE) - _LOGGER.debug("Turn camera '%s' %s", self._name, mode) + _LOGGER.debug("Turn %s camera light for '%s'", mode, self._name) self._data.set_state( home_id=self._home_id, camera_id=self._id, diff --git a/tests/components/netatmo/conftest.py b/tests/components/netatmo/conftest.py index 4e59eaacb82..e0138dcc4d7 100644 --- a/tests/components/netatmo/conftest.py +++ b/tests/components/netatmo/conftest.py @@ -73,6 +73,8 @@ async def mock_entry_fixture(hass, config_entry): """Mock setup of all platforms.""" with selected_platforms(): await hass.config_entries.async_setup(config_entry.entry_id) + + await hass.async_block_till_done() return config_entry diff --git a/tests/components/netatmo/test_camera.py b/tests/components/netatmo/test_camera.py new file mode 100644 index 00000000000..a58f2f3e1aa --- /dev/null +++ b/tests/components/netatmo/test_camera.py @@ -0,0 +1,207 @@ +"""The tests for Netatmo camera.""" +from unittest.mock import patch + +from homeassistant.components import camera +from homeassistant.components.camera import STATE_STREAMING +from homeassistant.components.netatmo.const import ( + SERVICE_SET_CAMERA_LIGHT, + SERVICE_SET_PERSON_AWAY, + SERVICE_SET_PERSONS_HOME, +) +from homeassistant.const import CONF_WEBHOOK_ID + +from .common import simulate_webhook + + +async def test_setup_component_with_webhook(hass, camera_entry): + """Test setup with webhook.""" + webhook_id = camera_entry.data[CONF_WEBHOOK_ID] + await hass.async_block_till_done() + + camera_entity_indoor = "camera.netatmo_hall" + camera_entity_outdoor = "camera.netatmo_garden" + assert hass.states.get(camera_entity_indoor).state == "streaming" + response = { + "event_type": "off", + "device_id": "12:34:56:00:f1:62", + "camera_id": "12:34:56:00:f1:62", + "event_id": "601dce1560abca1ebad9b723", + "push_type": "NACamera-off", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(camera_entity_indoor).state == "idle" + + response = { + "event_type": "on", + "device_id": "12:34:56:00:f1:62", + "camera_id": "12:34:56:00:f1:62", + "event_id": "646227f1dc0dfa000ec5f350", + "push_type": "NACamera-on", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(camera_entity_indoor).state == "streaming" + + response = { + "event_type": "light_mode", + "device_id": "12:34:56:00:a5:a4", + "camera_id": "12:34:56:00:a5:a4", + "event_id": "601dce1560abca1ebad9b723", + "push_type": "NOC-light_mode", + "sub_type": "on", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(camera_entity_indoor).state == "streaming" + assert hass.states.get(camera_entity_outdoor).attributes["light_state"] == "on" + + response = { + "event_type": "light_mode", + "device_id": "12:34:56:00:a5:a4", + "camera_id": "12:34:56:00:a5:a4", + "event_id": "601dce1560abca1ebad9b723", + "push_type": "NOC-light_mode", + "sub_type": "auto", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(camera_entity_outdoor).attributes["light_state"] == "auto" + + response = { + "event_type": "light_mode", + "device_id": "12:34:56:00:a5:a4", + "event_id": "601dce1560abca1ebad9b723", + "push_type": "NOC-light_mode", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(camera_entity_indoor).state == "streaming" + assert hass.states.get(camera_entity_outdoor).attributes["light_state"] == "auto" + + +IMAGE_BYTES_FROM_STREAM = b"test stream image bytes" + + +async def test_camera_image_local(hass, camera_entry, requests_mock): + """Test retrieval or local camera image.""" + await hass.async_block_till_done() + + uri = "http://192.168.0.123/678460a0d47e5618699fb31169e2b47d" + stream_uri = uri + "/live/files/high/index.m3u8" + camera_entity_indoor = "camera.netatmo_hall" + cam = hass.states.get(camera_entity_indoor) + + assert cam is not None + assert cam.state == STATE_STREAMING + + stream_source = await camera.async_get_stream_source(hass, camera_entity_indoor) + assert stream_source == stream_uri + + requests_mock.get( + uri + "/live/snapshot_720.jpg", + content=IMAGE_BYTES_FROM_STREAM, + ) + image = await camera.async_get_image(hass, camera_entity_indoor) + assert image.content == IMAGE_BYTES_FROM_STREAM + + +async def test_camera_image_vpn(hass, camera_entry, requests_mock): + """Test retrieval of remote camera image.""" + await hass.async_block_till_done() + + uri = ( + "https://prodvpn-eu-2.netatmo.net/restricted/10.255.248.91/" + "6d278460699e56180d47ab47169efb31/MpEylTU2MDYzNjRVD-LJxUnIndumKzLboeAwMDqTTw,," + ) + stream_uri = uri + "/live/files/high/index.m3u8" + camera_entity_indoor = "camera.netatmo_garden" + cam = hass.states.get(camera_entity_indoor) + + assert cam is not None + assert cam.state == STATE_STREAMING + + stream_source = await camera.async_get_stream_source(hass, camera_entity_indoor) + assert stream_source == stream_uri + + requests_mock.get( + uri + "/live/snapshot_720.jpg", + content=IMAGE_BYTES_FROM_STREAM, + ) + image = await camera.async_get_image(hass, camera_entity_indoor) + assert image.content == IMAGE_BYTES_FROM_STREAM + + +async def test_service_set_person_away(hass, camera_entry): + """Test service to set person as away.""" + await hass.async_block_till_done() + + data = { + "entity_id": "camera.netatmo_hall", + "person": "Richard Doe", + } + + with patch("pyatmo.camera.CameraData.set_persons_away") as mock_set_persons_away: + await hass.services.async_call( + "netatmo", SERVICE_SET_PERSON_AWAY, service_data=data + ) + await hass.async_block_till_done() + mock_set_persons_away.assert_called_once_with( + person_id="91827376-7e04-5298-83af-a0cb8372dff3", + home_id="91763b24c43d3e344f424e8b", + ) + + data = { + "entity_id": "camera.netatmo_hall", + } + + with patch("pyatmo.camera.CameraData.set_persons_away") as mock_set_persons_away: + await hass.services.async_call( + "netatmo", SERVICE_SET_PERSON_AWAY, service_data=data + ) + await hass.async_block_till_done() + mock_set_persons_away.assert_called_once_with( + person_id=None, + home_id="91763b24c43d3e344f424e8b", + ) + + +async def test_service_set_persons_home(hass, camera_entry): + """Test service to set persons as home.""" + await hass.async_block_till_done() + + data = { + "entity_id": "camera.netatmo_hall", + "persons": "John Doe", + } + + with patch("pyatmo.camera.CameraData.set_persons_home") as mock_set_persons_home: + await hass.services.async_call( + "netatmo", SERVICE_SET_PERSONS_HOME, service_data=data + ) + await hass.async_block_till_done() + mock_set_persons_home.assert_called_once_with( + person_ids=["91827374-7e04-5298-83ad-a0cb8372dff1"], + home_id="91763b24c43d3e344f424e8b", + ) + + +async def test_service_set_camera_light(hass, camera_entry): + """Test service to set the outdoor camera light mode.""" + await hass.async_block_till_done() + + data = { + "entity_id": "camera.netatmo_garden", + "camera_light_mode": "on", + } + + with patch("pyatmo.camera.CameraData.set_state") as mock_set_state: + await hass.services.async_call( + "netatmo", SERVICE_SET_CAMERA_LIGHT, service_data=data + ) + await hass.async_block_till_done() + mock_set_state.assert_called_once_with( + home_id="91763b24c43d3e344f424e8b", + camera_id="12:34:56:00:a5:a4", + floodlight="on", + ) From 993261e7f5da3de48153beaed975c61867b55351 Mon Sep 17 00:00:00 2001 From: Andreas Brett Date: Fri, 19 Mar 2021 12:34:06 +0100 Subject: [PATCH 485/831] Add "timestamp" attribute to seventeentrack (#47960) * bump py17track to 3.0.1 * Make aiohttp ClientSession optional as introduced in py17track v3.0.0 (https://github.com/bachya/py17track/releases/tag/3.0.0) * Update manifest.json * add new attribute timestamp introduced in 3.1.0 * Update requirements.txt * Update requirements_all.txt * Update requirements.txt * Update requirements_test_all.txt * Update sensor.py * Update sensor.py * Update manifest.json * provide timezone configuration user config to pre-define timezone of package status timestamps * Update requirements_all.txt * Update requirements_test_all.txt * linting * use hass.config.time_zone * Update sensor.py * Update test_sensor.py * Update test_sensor.py * black * Update manifest.json * adjust changes to session param * added test against latest dev branch * make isort happy * make black happy * make flake8 happy * make black happy * bump to 3.2.1 * 3.2.1 * Update typing 15 --- .../components/seventeentrack/manifest.json | 2 +- .../components/seventeentrack/sensor.py | 25 +++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../components/seventeentrack/test_sensor.py | 128 ++++++++++++++++-- 5 files changed, 139 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/seventeentrack/manifest.json b/homeassistant/components/seventeentrack/manifest.json index 2cec9dea954..427882de91a 100644 --- a/homeassistant/components/seventeentrack/manifest.json +++ b/homeassistant/components/seventeentrack/manifest.json @@ -2,6 +2,6 @@ "domain": "seventeentrack", "name": "17TRACK", "documentation": "https://www.home-assistant.io/integrations/seventeentrack", - "requirements": ["py17track==2.2.2"], + "requirements": ["py17track==3.2.1"], "codeowners": ["@bachya"] } diff --git a/homeassistant/components/seventeentrack/sensor.py b/homeassistant/components/seventeentrack/sensor.py index 15110f6a0c3..223844dc9c7 100644 --- a/homeassistant/components/seventeentrack/sensor.py +++ b/homeassistant/components/seventeentrack/sensor.py @@ -24,6 +24,7 @@ _LOGGER = logging.getLogger(__name__) ATTR_DESTINATION_COUNTRY = "destination_country" ATTR_INFO_TEXT = "info_text" +ATTR_TIMESTAMP = "timestamp" ATTR_ORIGIN_COUNTRY = "origin_country" ATTR_PACKAGES = "packages" ATTR_PACKAGE_TYPE = "package_type" @@ -65,9 +66,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Configure the platform and add the sensors.""" - websession = aiohttp_client.async_get_clientsession(hass) + session = aiohttp_client.async_get_clientsession(hass) - client = SeventeenTrackClient(websession) + client = SeventeenTrackClient(session=session) try: login_result = await client.profile.login( @@ -89,6 +90,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= scan_interval, config[CONF_SHOW_ARCHIVED], config[CONF_SHOW_DELIVERED], + str(hass.config.time_zone), ) await data.async_update() @@ -151,6 +153,7 @@ class SeventeenTrackSummarySensor(Entity): { ATTR_FRIENDLY_NAME: package.friendly_name, ATTR_INFO_TEXT: package.info_text, + ATTR_TIMESTAMP: package.timestamp, ATTR_STATUS: package.status, ATTR_LOCATION: package.location, ATTR_TRACKING_NUMBER: package.tracking_number, @@ -172,6 +175,7 @@ class SeventeenTrackPackageSensor(Entity): ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION, ATTR_DESTINATION_COUNTRY: package.destination_country, ATTR_INFO_TEXT: package.info_text, + ATTR_TIMESTAMP: package.timestamp, ATTR_LOCATION: package.location, ATTR_ORIGIN_COUNTRY: package.origin_country, ATTR_PACKAGE_TYPE: package.package_type, @@ -237,7 +241,11 @@ class SeventeenTrackPackageSensor(Entity): return self._attrs.update( - {ATTR_INFO_TEXT: package.info_text, ATTR_LOCATION: package.location} + { + ATTR_INFO_TEXT: package.info_text, + ATTR_TIMESTAMP: package.timestamp, + ATTR_LOCATION: package.location, + } ) self._state = package.status self._friendly_name = package.friendly_name @@ -277,7 +285,13 @@ class SeventeenTrackData: """Define a data handler for 17track.net.""" def __init__( - self, client, async_add_entities, scan_interval, show_archived, show_delivered + self, + client, + async_add_entities, + scan_interval, + show_archived, + show_delivered, + timezone, ): """Initialize.""" self._async_add_entities = async_add_entities @@ -287,6 +301,7 @@ class SeventeenTrackData: self.account_id = client.profile.account_id self.packages = {} self.show_delivered = show_delivered + self.timezone = timezone self.summary = {} self.async_update = Throttle(self._scan_interval)(self._async_update) @@ -297,7 +312,7 @@ class SeventeenTrackData: try: packages = await self._client.profile.packages( - show_archived=self._show_archived + show_archived=self._show_archived, tz=self.timezone ) _LOGGER.debug("New package data received: %s", packages) diff --git a/requirements_all.txt b/requirements_all.txt index b1581b797bd..b7b07acc71a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1213,7 +1213,7 @@ py-schluter==0.1.7 py-zabbix==1.1.7 # homeassistant.components.seventeentrack -py17track==2.2.2 +py17track==3.2.1 # homeassistant.components.hdmi_cec pyCEC==0.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9c0222a12d6..4a5dbc85efe 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -629,7 +629,7 @@ py-melissa-climate==2.1.4 py-nightscout==1.2.2 # homeassistant.components.seventeentrack -py17track==2.2.2 +py17track==3.2.1 # homeassistant.components.control4 pyControl4==0.0.6 diff --git a/tests/components/seventeentrack/test_sensor.py b/tests/components/seventeentrack/test_sensor.py index 65e40d02aff..d40d2cd499b 100644 --- a/tests/components/seventeentrack/test_sensor.py +++ b/tests/components/seventeentrack/test_sensor.py @@ -71,7 +71,7 @@ NEW_SUMMARY_DATA = { class ClientMock: """Mock the py17track client to inject the ProfileMock.""" - def __init__(self, websession) -> None: + def __init__(self, session) -> None: """Mock the profile.""" self.profile = ProfileMock() @@ -101,7 +101,10 @@ class ProfileMock: return self.__class__.login_result async def packages( - self, package_state: int | str = "", show_archived: bool = False + self, + package_state: int | str = "", + show_archived: bool = False, + tz: str = "UTC", ) -> list: """Packages mock.""" return self.__class__.package_list[:] @@ -169,7 +172,14 @@ async def test_invalid_config(hass): async def test_add_package(hass): """Ensure package is added correctly when user add a new package.""" package = Package( - "456", 206, "friendly name 1", "info text 1", "location 1", 206, 2 + "456", + 206, + "friendly name 1", + "info text 1", + "location 1", + "2020-08-10 10:32", + 206, + 2, ) ProfileMock.package_list = [package] @@ -178,7 +188,14 @@ async def test_add_package(hass): assert len(hass.states.async_entity_ids()) == 1 package2 = Package( - "789", 206, "friendly name 2", "info text 2", "location 2", 206, 2 + "789", + 206, + "friendly name 2", + "info text 2", + "location 2", + "2020-08-10 14:25", + 206, + 2, ) ProfileMock.package_list = [package, package2] @@ -191,10 +208,24 @@ async def test_add_package(hass): async def test_remove_package(hass): """Ensure entity is not there anymore if package is not there.""" package1 = Package( - "456", 206, "friendly name 1", "info text 1", "location 1", 206, 2 + "456", + 206, + "friendly name 1", + "info text 1", + "location 1", + "2020-08-10 10:32", + 206, + 2, ) package2 = Package( - "789", 206, "friendly name 2", "info text 2", "location 2", 206, 2 + "789", + 206, + "friendly name 2", + "info text 2", + "location 2", + "2020-08-10 14:25", + 206, + 2, ) ProfileMock.package_list = [package1, package2] @@ -217,7 +248,14 @@ async def test_remove_package(hass): async def test_friendly_name_changed(hass): """Test friendly name change.""" package = Package( - "456", 206, "friendly name 1", "info text 1", "location 1", 206, 2 + "456", + 206, + "friendly name 1", + "info text 1", + "location 1", + "2020-08-10 10:32", + 206, + 2, ) ProfileMock.package_list = [package] @@ -227,7 +265,14 @@ async def test_friendly_name_changed(hass): assert len(hass.states.async_entity_ids()) == 1 package = Package( - "456", 206, "friendly name 2", "info text 1", "location 1", 206, 2 + "456", + 206, + "friendly name 2", + "info text 1", + "location 1", + "2020-08-10 10:32", + 206, + 2, ) ProfileMock.package_list = [package] @@ -244,7 +289,15 @@ async def test_friendly_name_changed(hass): async def test_delivered_not_shown(hass): """Ensure delivered packages are not shown.""" package = Package( - "456", 206, "friendly name 1", "info text 1", "location 1", 206, 2, 40 + "456", + 206, + "friendly name 1", + "info text 1", + "location 1", + "2020-08-10 10:32", + 206, + 2, + 40, ) ProfileMock.package_list = [package] @@ -259,7 +312,15 @@ async def test_delivered_not_shown(hass): async def test_delivered_shown(hass): """Ensure delivered packages are show when user choose to show them.""" package = Package( - "456", 206, "friendly name 1", "info text 1", "location 1", 206, 2, 40 + "456", + 206, + "friendly name 1", + "info text 1", + "location 1", + "2020-08-10 10:32", + 206, + 2, + 40, ) ProfileMock.package_list = [package] @@ -274,7 +335,14 @@ async def test_delivered_shown(hass): async def test_becomes_delivered_not_shown_notification(hass): """Ensure notification is triggered when package becomes delivered.""" package = Package( - "456", 206, "friendly name 1", "info text 1", "location 1", 206, 2 + "456", + 206, + "friendly name 1", + "info text 1", + "location 1", + "2020-08-10 10:32", + 206, + 2, ) ProfileMock.package_list = [package] @@ -284,7 +352,15 @@ async def test_becomes_delivered_not_shown_notification(hass): assert len(hass.states.async_entity_ids()) == 1 package_delivered = Package( - "456", 206, "friendly name 1", "info text 1", "location 1", 206, 2, 40 + "456", + 206, + "friendly name 1", + "info text 1", + "location 1", + "2020-08-10 10:32", + 206, + 2, + 40, ) ProfileMock.package_list = [package_delivered] @@ -310,3 +386,31 @@ async def test_summary_correctly_updated(hass): assert len(hass.states.async_entity_ids()) == 7 for state in hass.states.async_all(): assert state.state == "1" + + +async def test_utc_timestamp(hass): + """Ensure package timestamp is converted correctly from HA-defined time zone to UTC.""" + package = Package( + "456", + 206, + "friendly name 1", + "info text 1", + "location 1", + "2020-08-10 10:32", + 206, + 2, + tz="Asia/Jakarta", + ) + ProfileMock.package_list = [package] + + await _setup_seventeentrack(hass) + assert hass.states.get("sensor.seventeentrack_package_456") is not None + assert len(hass.states.async_entity_ids()) == 1 + assert ( + str( + hass.states.get("sensor.seventeentrack_package_456").attributes.get( + "timestamp" + ) + ) + == "2020-08-10 03:32:00+00:00" + ) From b1626f00917d1041d138dd8d495d1702c964d15d Mon Sep 17 00:00:00 2001 From: Michael Cicogna <44257895+miccico@users.noreply.github.com> Date: Fri, 19 Mar 2021 12:36:03 +0100 Subject: [PATCH 486/831] Fix Homematic transition function on light devices with multiple channels (#45725) * Update light.py Fix Transition function on devices with multiple channels * Update light.py fix Flake8 Warning W293 blank line contains whitespace --- homeassistant/components/homematic/light.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homematic/light.py b/homeassistant/components/homematic/light.py index 11731e2ae5f..036034bf801 100644 --- a/homeassistant/components/homematic/light.py +++ b/homeassistant/components/homematic/light.py @@ -9,6 +9,7 @@ from homeassistant.components.light import ( SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, + SUPPORT_TRANSITION, LightEntity, ) @@ -53,7 +54,8 @@ class HMLight(HMDevice, LightEntity): @property def supported_features(self): """Flag supported features.""" - features = SUPPORT_BRIGHTNESS + features = SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + if "COLOR" in self._hmdevice.WRITENODE: features |= SUPPORT_COLOR if "PROGRAM" in self._hmdevice.WRITENODE: @@ -95,7 +97,7 @@ class HMLight(HMDevice, LightEntity): def turn_on(self, **kwargs): """Turn the light on and/or change color or color effect settings.""" if ATTR_TRANSITION in kwargs: - self._hmdevice.setValue("RAMP_TIME", kwargs[ATTR_TRANSITION]) + self._hmdevice.setValue("RAMP_TIME", kwargs[ATTR_TRANSITION], self._channel) if ATTR_BRIGHTNESS in kwargs and self._state == "LEVEL": percent_bright = float(kwargs[ATTR_BRIGHTNESS]) / 255 @@ -123,6 +125,9 @@ class HMLight(HMDevice, LightEntity): def turn_off(self, **kwargs): """Turn the light off.""" + if ATTR_TRANSITION in kwargs: + self._hmdevice.setValue("RAMP_TIME", kwargs[ATTR_TRANSITION], self._channel) + self._hmdevice.off(self._channel) def _init_data_struct(self): From 703c073e53217692b8d6f86ceb7a8478986f6fd2 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 19 Mar 2021 13:30:16 +0100 Subject: [PATCH 487/831] Improve websocket debug log --- homeassistant/components/websocket_api/http.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py index 982bead296a..27af0424f3c 100644 --- a/homeassistant/components/websocket_api/http.py +++ b/homeassistant/components/websocket_api/http.py @@ -74,11 +74,11 @@ class WebSocketHandler: if message is None: break - self._logger.debug("Sending %s", message) - if not isinstance(message, str): message = message_to_json(message) + self._logger.debug("Sending %s", message) + await self.wsock.send_str(message) # Clean up the peaker checker when we shut down the writer From 6e0c0afde2811659052d56588dc86b074de0da1b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 19 Mar 2021 13:36:44 +0100 Subject: [PATCH 488/831] Upgrade RPi.GPIO to 0.7.1a4 (#48106) --- homeassistant/components/bmp280/manifest.json | 2 +- homeassistant/components/mcp23017/manifest.json | 2 +- homeassistant/components/rpi_gpio/manifest.json | 2 +- requirements_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/bmp280/manifest.json b/homeassistant/components/bmp280/manifest.json index dbd79896718..e22c275ed76 100644 --- a/homeassistant/components/bmp280/manifest.json +++ b/homeassistant/components/bmp280/manifest.json @@ -3,6 +3,6 @@ "name": "Bosch BMP280 Environmental Sensor", "documentation": "https://www.home-assistant.io/integrations/bmp280", "codeowners": ["@belidzs"], - "requirements": ["adafruit-circuitpython-bmp280==3.1.1", "RPi.GPIO==0.7.0"], + "requirements": ["adafruit-circuitpython-bmp280==3.1.1", "RPi.GPIO==0.7.1a4"], "quality_scale": "silver" } diff --git a/homeassistant/components/mcp23017/manifest.json b/homeassistant/components/mcp23017/manifest.json index aeda638710e..7460529f8fe 100644 --- a/homeassistant/components/mcp23017/manifest.json +++ b/homeassistant/components/mcp23017/manifest.json @@ -3,7 +3,7 @@ "name": "MCP23017 I/O Expander", "documentation": "https://www.home-assistant.io/integrations/mcp23017", "requirements": [ - "RPi.GPIO==0.7.0", + "RPi.GPIO==0.7.1a4", "adafruit-circuitpython-mcp230xx==2.2.2" ], "codeowners": ["@jardiamj"] diff --git a/homeassistant/components/rpi_gpio/manifest.json b/homeassistant/components/rpi_gpio/manifest.json index 523d98dfdb7..1a73c736d04 100644 --- a/homeassistant/components/rpi_gpio/manifest.json +++ b/homeassistant/components/rpi_gpio/manifest.json @@ -2,6 +2,6 @@ "domain": "rpi_gpio", "name": "Raspberry Pi GPIO", "documentation": "https://www.home-assistant.io/integrations/rpi_gpio", - "requirements": ["RPi.GPIO==0.7.0"], + "requirements": ["RPi.GPIO==0.7.1a4"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index b7b07acc71a..6a114b0e972 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -72,7 +72,7 @@ PyXiaomiGateway==0.13.4 # homeassistant.components.bmp280 # homeassistant.components.mcp23017 # homeassistant.components.rpi_gpio -# RPi.GPIO==0.7.0 +# RPi.GPIO==0.7.1a4 # homeassistant.components.remember_the_milk RtmAPI==0.7.2 From fa5ce70af1f6cbc219adf49ae15730abf090482f Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 19 Mar 2021 13:37:22 +0100 Subject: [PATCH 489/831] Improve test coverage of deCONZ config flow (#48091) --- tests/components/deconz/test_config_flow.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/components/deconz/test_config_flow.py b/tests/components/deconz/test_config_flow.py index 19f544fabc9..8f9a597e00c 100644 --- a/tests/components/deconz/test_config_flow.py +++ b/tests/components/deconz/test_config_flow.py @@ -442,6 +442,18 @@ async def test_flow_ssdp_discovery(hass, aioclient_mock): } +async def test_flow_ssdp_bad_discovery(hass, aioclient_mock): + """Test that SSDP discovery aborts if manufacturer URL is wrong.""" + result = await hass.config_entries.flow.async_init( + DECONZ_DOMAIN, + data={ATTR_UPNP_MANUFACTURER_URL: "other"}, + context={"source": SOURCE_SSDP}, + ) + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "not_deconz_bridge" + + async def test_ssdp_discovery_update_configuration(hass, aioclient_mock): """Test if a discovered bridge is configured but updates with new attributes.""" config_entry = await setup_deconz_integration(hass, aioclient_mock) From 8a56dbf58710e8a3bbcfa83ce92c427a6c793fbe Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 19 Mar 2021 13:41:09 +0100 Subject: [PATCH 490/831] Add flake8 comprehensions checks to pre-commit & CI (#48111) --- .pre-commit-config.yaml | 1 + homeassistant/components/denon/media_player.py | 2 +- homeassistant/components/emulated_hue/hue_api.py | 8 +++----- homeassistant/components/flux_led/light.py | 2 +- homeassistant/components/light/__init__.py | 2 +- homeassistant/components/nad/media_player.py | 2 +- homeassistant/components/sms/gateway.py | 4 ++-- homeassistant/components/uk_transport/sensor.py | 4 +--- homeassistant/components/unifi/config_flow.py | 2 +- homeassistant/components/webostv/media_player.py | 2 +- homeassistant/util/dt.py | 2 +- requirements_test_pre_commit.txt | 1 + tests/common.py | 2 +- tests/components/automation/test_trace.py | 2 +- tests/components/group/test_init.py | 2 +- tests/components/zwave/test_init.py | 8 ++++---- tests/helpers/test_json.py | 2 +- tests/util/test_dt.py | 8 ++++---- 18 files changed, 27 insertions(+), 29 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6629065005a..031d0659aad 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,6 +32,7 @@ repos: # default yet due to https://github.com/plinss/flake8-noqa/issues/1 # - flake8-noqa==1.1.0 - pydocstyle==5.1.1 + - flake8-comprehensions==3.4.0 files: ^(homeassistant|script|tests)/.+\.py$ - repo: https://github.com/PyCQA/bandit rev: 1.7.0 diff --git a/homeassistant/components/denon/media_player.py b/homeassistant/components/denon/media_player.py index b909dc7c070..82427f8aa16 100644 --- a/homeassistant/components/denon/media_player.py +++ b/homeassistant/components/denon/media_player.py @@ -230,7 +230,7 @@ class DenonDevice(MediaPlayerEntity): @property def source_list(self): """Return the list of available input sources.""" - return sorted(list(self._source_list)) + return sorted(self._source_list) @property def media_title(self): diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index 1630405a73e..647d9db1335 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -391,11 +391,9 @@ class HueOneLightChangeView(HomeAssistantView): return self.json_message("Bad request", HTTP_BAD_REQUEST) if HUE_API_STATE_XY in request_json: try: - parsed[STATE_XY] = tuple( - ( - float(request_json[HUE_API_STATE_XY][0]), - float(request_json[HUE_API_STATE_XY][1]), - ) + parsed[STATE_XY] = ( + float(request_json[HUE_API_STATE_XY][0]), + float(request_json[HUE_API_STATE_XY][1]), ) except ValueError: _LOGGER.error("Unable to parse data (2): %s", request_json) diff --git a/homeassistant/components/flux_led/light.py b/homeassistant/components/flux_led/light.py index 4bfd0c0a26c..2f8d2cc5536 100644 --- a/homeassistant/components/flux_led/light.py +++ b/homeassistant/components/flux_led/light.py @@ -98,7 +98,7 @@ TRANSITION_GRADUAL = "gradual" TRANSITION_JUMP = "jump" TRANSITION_STROBE = "strobe" -FLUX_EFFECT_LIST = sorted(list(EFFECT_MAP)) + [EFFECT_RANDOM] +FLUX_EFFECT_LIST = sorted(EFFECT_MAP) + [EFFECT_RANDOM] CUSTOM_EFFECT_SCHEMA = vol.Schema( { diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index c2e2fdbeaa9..7934874db0b 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -610,7 +610,7 @@ class LightEntity(ToggleEntity): data[ATTR_EFFECT_LIST] = self.effect_list data[ATTR_SUPPORTED_COLOR_MODES] = sorted( - list(self._light_internal_supported_color_modes) + self._light_internal_supported_color_modes ) return data diff --git a/homeassistant/components/nad/media_player.py b/homeassistant/components/nad/media_player.py index 782b8735e3a..e7f83c66efa 100644 --- a/homeassistant/components/nad/media_player.py +++ b/homeassistant/components/nad/media_player.py @@ -163,7 +163,7 @@ class NAD(MediaPlayerEntity): @property def source_list(self): """List of available input sources.""" - return sorted(list(self._reverse_mapping)) + return sorted(self._reverse_mapping) @property def available(self): diff --git a/homeassistant/components/sms/gateway.py b/homeassistant/components/sms/gateway.py index e28b3947e37..51667ef8f77 100644 --- a/homeassistant/components/sms/gateway.py +++ b/homeassistant/components/sms/gateway.py @@ -43,7 +43,7 @@ class Gateway: ) entries = self.get_and_delete_all_sms(state_machine) _LOGGER.debug("SMS entries:%s", entries) - data = list() + data = [] for entry in entries: decoded_entry = gammu.DecodeSMS(entry) @@ -78,7 +78,7 @@ class Gateway: start_remaining = remaining # Get all sms start = True - entries = list() + entries = [] all_parts = -1 all_parts_arrived = False _LOGGER.debug("Start remaining:%i", start_remaining) diff --git a/homeassistant/components/uk_transport/sensor.py b/homeassistant/components/uk_transport/sensor.py index 80b97b186be..f5e4b4373c0 100644 --- a/homeassistant/components/uk_transport/sensor.py +++ b/homeassistant/components/uk_transport/sensor.py @@ -32,9 +32,7 @@ CONF_DESTINATION = "destination" _QUERY_SCHEME = vol.Schema( { - vol.Required(CONF_MODE): vol.All( - cv.ensure_list, [vol.In(list(["bus", "train"]))] - ), + vol.Required(CONF_MODE): vol.All(cv.ensure_list, [vol.In(["bus", "train"])]), vol.Required(CONF_ORIGIN): cv.string, vol.Required(CONF_DESTINATION): cv.string, } diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index 6d8c37e8b04..094bae05881 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -319,7 +319,7 @@ class UnifiOptionsFlowHandler(config_entries.OptionsFlow): if "name" in wlan } ) - ssid_filter = {ssid: ssid for ssid in sorted(list(ssids))} + ssid_filter = {ssid: ssid for ssid in sorted(ssids)} return self.async_show_form( step_id="device_tracker", diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index 0fc442b37e9..75c1bdd2bcc 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -271,7 +271,7 @@ class LgWebOSMediaPlayerEntity(MediaPlayerEntity): @property def source_list(self): """List of available input sources.""" - return sorted(list(self._source_list)) + return sorted(self._source_list) @property def media_content_type(self): diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index a659d0add38..aae01c56bf3 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -227,7 +227,7 @@ def parse_time_expression(parameter: Any, min_value: int, max_value: int) -> lis elif not hasattr(parameter, "__iter__"): res = [int(parameter)] else: - res = list(sorted(int(x) for x in parameter)) + res = sorted(int(x) for x in parameter) for val in res: if val < min_value or val > max_value: diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index fd5329a8ca1..be881f09f7c 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -3,6 +3,7 @@ bandit==1.7.0 black==20.8b1 codespell==2.0.0 +flake8-comprehensions==3.4.0 flake8-docstrings==1.5.0 flake8==3.8.4 isort==5.7.0 diff --git a/tests/common.py b/tests/common.py index f1449721c29..5f8626afb4e 100644 --- a/tests/common.py +++ b/tests/common.py @@ -766,7 +766,7 @@ class MockConfigEntry(config_entries.ConfigEntry): def patch_yaml_files(files_dict, endswith=True): """Patch load_yaml with a dictionary of yaml files.""" # match using endswith, start search with longest string - matchlist = sorted(list(files_dict.keys()), key=len) if endswith else [] + matchlist = sorted(files_dict.keys(), key=len) if endswith else [] def mock_open_f(fname, **_): """Mock open() in the yaml module, used by load_yaml.""" diff --git a/tests/components/automation/test_trace.py b/tests/components/automation/test_trace.py index 818f1ee1768..612a0ccfcab 100644 --- a/tests/components/automation/test_trace.py +++ b/tests/components/automation/test_trace.py @@ -32,7 +32,7 @@ def test_json_encoder(hass): # Test serializing a set() data = {"milk", "beer"} - assert sorted(ha_json_enc.default(data)) == sorted(list(data)) + assert sorted(ha_json_enc.default(data)) == sorted(data) # Test serializong object which implements as_dict assert ha_json_enc.default(state) == state.as_dict() diff --git a/tests/components/group/test_init.py b/tests/components/group/test_init.py index 627e3c5bbe0..d68fdd7f717 100644 --- a/tests/components/group/test_init.py +++ b/tests/components/group/test_init.py @@ -579,7 +579,7 @@ async def test_service_group_set_group_remove_group(hass): assert group_state.attributes[group.ATTR_AUTO] assert group_state.attributes["friendly_name"] == "Test2" assert group_state.attributes["icon"] == "mdi:camera" - assert sorted(list(group_state.attributes["entity_id"])) == sorted( + assert sorted(group_state.attributes["entity_id"]) == sorted( ["test.entity_bla1", "test.entity_id2"] ) diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index e76754a9872..e857675c545 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -861,7 +861,7 @@ async def test_entity_discovery( assert values.primary is value_class.primary assert len(list(values)) == 3 - assert sorted(list(values), key=lambda a: id(a)) == sorted( + assert sorted(values, key=lambda a: id(a)) == sorted( [value_class.primary, None, None], key=lambda a: id(a) ) @@ -885,7 +885,7 @@ async def test_entity_discovery( assert values.secondary is value_class.secondary assert len(list(values)) == 3 - assert sorted(list(values), key=lambda a: id(a)) == sorted( + assert sorted(values, key=lambda a: id(a)) == sorted( [value_class.primary, value_class.secondary, None], key=lambda a: id(a) ) @@ -902,7 +902,7 @@ async def test_entity_discovery( assert values.optional is value_class.optional assert len(list(values)) == 3 - assert sorted(list(values), key=lambda a: id(a)) == sorted( + assert sorted(values, key=lambda a: id(a)) == sorted( [value_class.primary, value_class.secondary, value_class.optional], key=lambda a: id(a), ) @@ -961,7 +961,7 @@ async def test_entity_existing_values( assert values.secondary is value_class.secondary assert values.optional is value_class.optional assert len(list(values)) == 3 - assert sorted(list(values), key=lambda a: id(a)) == sorted( + assert sorted(values, key=lambda a: id(a)) == sorted( [value_class.primary, value_class.secondary, value_class.optional], key=lambda a: id(a), ) diff --git a/tests/helpers/test_json.py b/tests/helpers/test_json.py index 55d566f5edd..1a68f2b8da5 100644 --- a/tests/helpers/test_json.py +++ b/tests/helpers/test_json.py @@ -17,7 +17,7 @@ def test_json_encoder(hass): # Test serializing a set() data = {"milk", "beer"} - assert sorted(ha_json_enc.default(data)) == sorted(list(data)) + assert sorted(ha_json_enc.default(data)) == sorted(data) # Test serializing an object which implements as_dict assert ha_json_enc.default(state) == state.as_dict() diff --git a/tests/util/test_dt.py b/tests/util/test_dt.py index 7c4ca77fd79..1327bc51f8a 100644 --- a/tests/util/test_dt.py +++ b/tests/util/test_dt.py @@ -177,14 +177,14 @@ def test_get_age(): def test_parse_time_expression(): """Test parse_time_expression.""" - assert [x for x in range(60)] == dt_util.parse_time_expression("*", 0, 59) - assert [x for x in range(60)] == dt_util.parse_time_expression(None, 0, 59) + assert list(range(60)) == dt_util.parse_time_expression("*", 0, 59) + assert list(range(60)) == dt_util.parse_time_expression(None, 0, 59) - assert [x for x in range(0, 60, 5)] == dt_util.parse_time_expression("/5", 0, 59) + assert list(range(0, 60, 5)) == dt_util.parse_time_expression("/5", 0, 59) assert [1, 2, 3] == dt_util.parse_time_expression([2, 1, 3], 0, 59) - assert [x for x in range(24)] == dt_util.parse_time_expression("*", 0, 23) + assert list(range(24)) == dt_util.parse_time_expression("*", 0, 23) assert [42] == dt_util.parse_time_expression(42, 0, 59) assert [42] == dt_util.parse_time_expression("42", 0, 59) From 3742f175ad327a63684789531ee4a4bbad655123 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 19 Mar 2021 14:27:26 +0100 Subject: [PATCH 491/831] Add missing oauth2 error abort reason (#48112) --- homeassistant/strings.json | 1 + script/scaffold/generate.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/homeassistant/strings.json b/homeassistant/strings.json index b8e7dee2996..31693c5bba1 100644 --- a/homeassistant/strings.json +++ b/homeassistant/strings.json @@ -67,6 +67,7 @@ "already_in_progress": "Configuration flow is already in progress", "no_devices_found": "No devices found on the network", "webhook_not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive webhook messages.", + "oauth2_error": "Received invalid token data.", "oauth2_missing_configuration": "The component is not configured. Please follow the documentation.", "oauth2_authorize_url_timeout": "Timeout generating authorize URL.", "oauth2_no_url_available": "No URL available. For information about this error, [check the help section]({docs_url})", diff --git a/script/scaffold/generate.py b/script/scaffold/generate.py index 4cf1624eb4b..10de17e45ee 100644 --- a/script/scaffold/generate.py +++ b/script/scaffold/generate.py @@ -163,6 +163,9 @@ def _custom_tasks(template, info) -> None: } }, "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", + "oauth_error": "[%key:common::config_flow::abort::oauth2_error%]", "missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]", "authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]", "no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]", From c820dd4cb55ab4349f9135a7ba1b1c059536719b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Mar 2021 09:26:36 -0500 Subject: [PATCH 492/831] Have pylint warn when user visible log messages do not start with capital letter or end with a period (#48064) Co-authored-by: Martin Hjelmare --- homeassistant/components/abode/config_flow.py | 2 +- .../components/actiontec/device_tracker.py | 2 +- .../aemet/weather_update_coordinator.py | 12 +-- homeassistant/components/amcrest/camera.py | 2 +- homeassistant/components/awair/sensor.py | 2 +- homeassistant/components/axis/device.py | 2 +- .../components/azure_devops/__init__.py | 2 +- .../components/bbox/device_tracker.py | 2 +- homeassistant/components/bloomsky/__init__.py | 2 +- homeassistant/components/buienradar/sensor.py | 6 +- homeassistant/components/buienradar/util.py | 2 +- .../components/cisco_ios/device_tracker.py | 5 +- homeassistant/components/decora/light.py | 2 +- .../components/downloader/__init__.py | 2 +- homeassistant/components/duckdns/__init__.py | 2 +- .../eddystone_temperature/sensor.py | 2 +- .../components/envisalink/__init__.py | 8 +- homeassistant/components/fan/__init__.py | 4 +- homeassistant/components/fints/sensor.py | 4 +- homeassistant/components/folder/sensor.py | 2 +- .../components/folder_watcher/__init__.py | 2 +- homeassistant/components/foscam/camera.py | 2 +- .../components/foscam/config_flow.py | 8 +- homeassistant/components/gogogate2/cover.py | 4 +- .../components/growatt_server/sensor.py | 2 +- .../components/gstreamer/media_player.py | 2 +- homeassistant/components/habitica/sensor.py | 8 +- .../components/hangouts/hangouts_bot.py | 2 +- homeassistant/components/hdmi_cec/__init__.py | 2 +- .../components/hitron_coda/device_tracker.py | 4 +- .../components/homeassistant/__init__.py | 3 +- homeassistant/components/homekit/__init__.py | 2 +- homeassistant/components/homekit/img_util.py | 2 +- .../components/homekit_controller/climate.py | 8 +- .../components/horizon/media_player.py | 4 +- .../components/huawei_lte/__init__.py | 6 +- homeassistant/components/hue/bridge.py | 2 +- .../components/huisbaasje/__init__.py | 2 +- homeassistant/components/icloud/account.py | 4 +- .../components/insteon/config_flow.py | 4 +- homeassistant/components/isy994/__init__.py | 2 +- .../components/isy994/config_flow.py | 2 +- homeassistant/components/keba/__init__.py | 4 +- .../components/keenetic_ndms2/router.py | 2 +- homeassistant/components/kiwi/lock.py | 2 +- homeassistant/components/konnected/panel.py | 2 +- homeassistant/components/lifx_legacy/light.py | 2 +- .../components/meraki/device_tracker.py | 2 +- .../components/meteo_france/__init__.py | 4 +- .../components/meteo_france/weather.py | 2 +- .../components/motion_blinds/gateway.py | 2 +- homeassistant/components/mqtt/cover.py | 2 +- homeassistant/components/mystrom/light.py | 2 +- homeassistant/components/netatmo/__init__.py | 2 +- homeassistant/components/netatmo/climate.py | 4 +- .../components/nissan_leaf/__init__.py | 2 +- .../components/nmap_tracker/device_tracker.py | 2 +- homeassistant/components/onvif/config_flow.py | 2 +- homeassistant/components/onvif/device.py | 2 +- homeassistant/components/onvif/event.py | 2 +- homeassistant/components/orvibo/switch.py | 2 +- .../components/panasonic_viera/__init__.py | 2 +- homeassistant/components/plex/server.py | 6 +- .../components/plum_lightpad/__init__.py | 2 +- .../components/poolsense/__init__.py | 2 +- homeassistant/components/pushsafer/notify.py | 4 +- .../components/radiotherm/climate.py | 2 +- .../components/recollect_waste/sensor.py | 4 +- homeassistant/components/recorder/util.py | 4 +- homeassistant/components/rflink/__init__.py | 2 +- homeassistant/components/rfxtrx/__init__.py | 2 +- .../components/screenlogic/__init__.py | 2 +- .../components/screenlogic/switch.py | 4 +- .../components/screenlogic/water_heater.py | 4 +- homeassistant/components/skybeacon/sensor.py | 2 +- homeassistant/components/smarty/__init__.py | 6 +- .../components/somfy_mylink/__init__.py | 2 +- .../components/sonos/media_player.py | 2 +- homeassistant/components/starline/account.py | 2 +- .../components/subaru/config_flow.py | 2 +- homeassistant/components/syncthru/sensor.py | 2 +- .../components/system_health/__init__.py | 2 +- .../components/systemmonitor/sensor.py | 2 +- .../components/telegram_bot/webhooks.py | 2 +- .../components/tensorflow/image_processing.py | 2 +- homeassistant/components/twitter/notify.py | 2 +- homeassistant/components/upcloud/__init__.py | 2 +- homeassistant/components/vizio/config_flow.py | 3 +- homeassistant/components/webhook/__init__.py | 2 +- .../components/webostv/media_player.py | 2 +- homeassistant/components/wemo/__init__.py | 4 +- .../components/wirelesstag/__init__.py | 2 +- .../components/xiaomi_miio/switch.py | 2 +- .../components/xiaomi_miio/vacuum.py | 2 +- homeassistant/components/xs1/__init__.py | 4 +- homeassistant/components/zabbix/sensor.py | 2 +- homeassistant/components/zeroconf/usage.py | 2 +- homeassistant/components/zwave/__init__.py | 8 +- homeassistant/components/zwave_js/__init__.py | 2 +- homeassistant/components/zwave_js/migrate.py | 4 +- homeassistant/core.py | 6 +- homeassistant/helpers/template.py | 2 +- homeassistant/util/yaml/loader.py | 2 +- pylint/plugins/hass_logger.py | 85 +++++++++++++++++++ pyproject.toml | 2 + tests/components/duckdns/test_init.py | 4 +- tests/components/mqtt/test_cover.py | 2 +- 107 files changed, 243 insertions(+), 157 deletions(-) create mode 100644 pylint/plugins/hass_logger.py diff --git a/homeassistant/components/abode/config_flow.py b/homeassistant/components/abode/config_flow.py index 76c23f7f705..d1e66b3a3dc 100644 --- a/homeassistant/components/abode/config_flow.py +++ b/homeassistant/components/abode/config_flow.py @@ -163,7 +163,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_import(self, import_config): """Import a config entry from configuration.yaml.""" if self._async_current_entries(): - LOGGER.warning("Already configured. Only a single configuration possible.") + LOGGER.warning("Already configured; Only a single configuration possible") return self.async_abort(reason="single_instance_allowed") self._polling = import_config.get(CONF_POLLING, False) diff --git a/homeassistant/components/actiontec/device_tracker.py b/homeassistant/components/actiontec/device_tracker.py index e3fdeaf35f2..c88ed546b9d 100644 --- a/homeassistant/components/actiontec/device_tracker.py +++ b/homeassistant/components/actiontec/device_tracker.py @@ -53,7 +53,7 @@ class ActiontecDeviceScanner(DeviceScanner): self.last_results = [] data = self.get_actiontec_data() self.success_init = data is not None - _LOGGER.info("canner initialized") + _LOGGER.info("Scanner initialized") def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" diff --git a/homeassistant/components/aemet/weather_update_coordinator.py b/homeassistant/components/aemet/weather_update_coordinator.py index 1a70baa6765..ab098dae17c 100644 --- a/homeassistant/components/aemet/weather_update_coordinator.py +++ b/homeassistant/components/aemet/weather_update_coordinator.py @@ -90,7 +90,7 @@ def format_condition(condition: str) -> str: for key, value in CONDITIONS_MAP.items(): if condition in value: return key - _LOGGER.error('condition "%s" not found in CONDITIONS_MAP', condition) + _LOGGER.error('Condition "%s" not found in CONDITIONS_MAP', condition) return condition @@ -175,14 +175,14 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): ) if self._town: _LOGGER.debug( - "town found for coordinates [%s, %s]: %s", + "Town found for coordinates [%s, %s]: %s", self._latitude, self._longitude, self._town, ) if not self._town: _LOGGER.error( - "town not found for coordinates [%s, %s]", + "Town not found for coordinates [%s, %s]", self._latitude, self._longitude, ) @@ -197,7 +197,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): daily = self._aemet.get_specific_forecast_town_daily(self._town[AEMET_ATTR_ID]) if not daily: _LOGGER.error( - 'error fetching daily data for town "%s"', self._town[AEMET_ATTR_ID] + 'Error fetching daily data for town "%s"', self._town[AEMET_ATTR_ID] ) hourly = self._aemet.get_specific_forecast_town_hourly( @@ -205,7 +205,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): ) if not hourly: _LOGGER.error( - 'error fetching hourly data for town "%s"', self._town[AEMET_ATTR_ID] + 'Error fetching hourly data for town "%s"', self._town[AEMET_ATTR_ID] ) station = None @@ -215,7 +215,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): ) if not station: _LOGGER.error( - 'error fetching data for station "%s"', + 'Error fetching data for station "%s"', self._station[AEMET_ATTR_IDEMA], ) diff --git a/homeassistant/components/amcrest/camera.py b/homeassistant/components/amcrest/camera.py index 046da7b270d..f57b9e62bae 100644 --- a/homeassistant/components/amcrest/camera.py +++ b/homeassistant/components/amcrest/camera.py @@ -197,7 +197,7 @@ class AmcrestCam(Camera): # and before initiating shapshot. while self._snapshot_task: self._check_snapshot_ok() - _LOGGER.debug("Waiting for previous snapshot from %s ...", self._name) + _LOGGER.debug("Waiting for previous snapshot from %s", self._name) await self._snapshot_task self._check_snapshot_ok() # Run snapshot command in separate Task that can't be cancelled so diff --git a/homeassistant/components/awair/sensor.py b/homeassistant/components/awair/sensor.py index 81dc0562fc4..33ecaf46660 100644 --- a/homeassistant/components/awair/sensor.py +++ b/homeassistant/components/awair/sensor.py @@ -42,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Import Awair configuration from YAML.""" LOGGER.warning( - "Loading Awair via platform setup is deprecated. Please remove it from your configuration." + "Loading Awair via platform setup is deprecated; Please remove it from your configuration" ) hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/homeassistant/components/axis/device.py b/homeassistant/components/axis/device.py index bd7b5e442ad..8c2a43c44ed 100644 --- a/homeassistant/components/axis/device.py +++ b/homeassistant/components/axis/device.py @@ -310,7 +310,7 @@ async def get_device(hass, host, port, username, password): return device except axis.Unauthorized as err: - LOGGER.warning("Connected to device at %s but not registered.", host) + LOGGER.warning("Connected to device at %s but not registered", host) raise AuthenticationRequired from err except (asyncio.TimeoutError, axis.RequestError) as err: diff --git a/homeassistant/components/azure_devops/__init__.py b/homeassistant/components/azure_devops/__init__.py index b856dc5aa00..e10bb4df886 100644 --- a/homeassistant/components/azure_devops/__init__.py +++ b/homeassistant/components/azure_devops/__init__.py @@ -102,7 +102,7 @@ class AzureDevOpsEntity(Entity): else: if self._available: _LOGGER.debug( - "An error occurred while updating Azure DevOps sensor.", + "An error occurred while updating Azure DevOps sensor", exc_info=True, ) self._available = False diff --git a/homeassistant/components/bbox/device_tracker.py b/homeassistant/components/bbox/device_tracker.py index 130d315197b..9dac635dd2f 100644 --- a/homeassistant/components/bbox/device_tracker.py +++ b/homeassistant/components/bbox/device_tracker.py @@ -75,7 +75,7 @@ class BboxDeviceScanner(DeviceScanner): Returns boolean if scanning successful. """ - _LOGGER.info("Scanning...") + _LOGGER.info("Scanning") box = pybbox.Bbox(ip=self.host) result = box.get_all_connected_devices() diff --git a/homeassistant/components/bloomsky/__init__.py b/homeassistant/components/bloomsky/__init__.py index 76ed9cdd12a..fa8d3160dc8 100644 --- a/homeassistant/components/bloomsky/__init__.py +++ b/homeassistant/components/bloomsky/__init__.py @@ -60,7 +60,7 @@ class BloomSky: self._endpoint_argument = "unit=intl" if is_metric else "" self.devices = {} self.is_metric = is_metric - _LOGGER.debug("Initial BloomSky device load...") + _LOGGER.debug("Initial BloomSky device load") self.refresh_devices() @Throttle(MIN_TIME_BETWEEN_UPDATES) diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index ae57ed3c43c..fbfa23f1e74 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -309,7 +309,7 @@ class BrSensor(Entity): try: condition = data.get(FORECAST)[fcday].get(CONDITION) except IndexError: - _LOGGER.warning("No forecast for fcday=%s...", fcday) + _LOGGER.warning("No forecast for fcday=%s", fcday) return False if condition: @@ -339,7 +339,7 @@ class BrSensor(Entity): self._state = round(self._state * 3.6, 1) return True except IndexError: - _LOGGER.warning("No forecast for fcday=%s...", fcday) + _LOGGER.warning("No forecast for fcday=%s", fcday) return False # update all other sensors @@ -347,7 +347,7 @@ class BrSensor(Entity): self._state = data.get(FORECAST)[fcday].get(self.type[:-3]) return True except IndexError: - _LOGGER.warning("No forecast for fcday=%s...", fcday) + _LOGGER.warning("No forecast for fcday=%s", fcday) return False if self.type == SYMBOL or self.type.startswith(CONDITION): diff --git a/homeassistant/components/buienradar/util.py b/homeassistant/components/buienradar/util.py index b4f2314eee5..83c511713d0 100644 --- a/homeassistant/components/buienradar/util.py +++ b/homeassistant/components/buienradar/util.py @@ -82,7 +82,7 @@ class BrData: async def get_data(self, url): """Load data from specified url.""" - _LOGGER.debug("Calling url: %s...", url) + _LOGGER.debug("Calling url: %s", url) result = {SUCCESS: False, MESSAGE: None} resp = None try: diff --git a/homeassistant/components/cisco_ios/device_tracker.py b/homeassistant/components/cisco_ios/device_tracker.py index 8bf2b77fa25..0c77fc6fd7e 100644 --- a/homeassistant/components/cisco_ios/device_tracker.py +++ b/homeassistant/components/cisco_ios/device_tracker.py @@ -47,7 +47,7 @@ class CiscoDeviceScanner(DeviceScanner): self.last_results = {} self.success_init = self._update_info() - _LOGGER.info("cisco_ios scanner initialized") + _LOGGER.info("Initialized cisco_ios scanner") def get_device_name(self, device): """Get the firmware doesn't save the name of the wireless device.""" @@ -131,8 +131,7 @@ class CiscoDeviceScanner(DeviceScanner): return devices_result.decode("utf-8") except pxssh.ExceptionPxssh as px_e: - _LOGGER.error("pxssh failed on login") - _LOGGER.error(px_e) + _LOGGER.error("Failed to login via pxssh: %s", px_e) return None diff --git a/homeassistant/components/decora/light.py b/homeassistant/components/decora/light.py index 96126bfcc98..45c42c4bb1c 100644 --- a/homeassistant/components/decora/light.py +++ b/homeassistant/components/decora/light.py @@ -62,7 +62,7 @@ def retry(method): return method(device, *args, **kwargs) except (decora.decoraException, AttributeError, BTLEException): _LOGGER.warning( - "Decora connect error for device %s. Reconnecting...", + "Decora connect error for device %s. Reconnecting", device.name, ) # pylint: disable=protected-access diff --git a/homeassistant/components/downloader/__init__.py b/homeassistant/components/downloader/__init__.py index 3856df696ad..89aa4a465cf 100644 --- a/homeassistant/components/downloader/__init__.py +++ b/homeassistant/components/downloader/__init__.py @@ -80,7 +80,7 @@ def setup(hass, config): if req.status_code != HTTP_OK: _LOGGER.warning( - "downloading '%s' failed, status_code=%d", url, req.status_code + "Downloading '%s' failed, status_code=%d", url, req.status_code ) hass.bus.fire( f"{DOMAIN}_{DOWNLOAD_FAILED_EVENT}", diff --git a/homeassistant/components/duckdns/__init__.py b/homeassistant/components/duckdns/__init__.py index 3e08d0e7b34..76353415d4f 100644 --- a/homeassistant/components/duckdns/__init__.py +++ b/homeassistant/components/duckdns/__init__.py @@ -103,7 +103,7 @@ async def _update_duckdns(session, domain, token, *, txt=_SENTINEL, clear=False) def async_track_time_interval_backoff(hass, action, intervals) -> CALLBACK_TYPE: """Add a listener that fires repetitively at every timedelta interval.""" if not iscoroutinefunction: - _LOGGER.error("action needs to be a coroutine and return True/False") + _LOGGER.error("Action needs to be a coroutine and return True/False") return if not isinstance(intervals, (list, tuple)): diff --git a/homeassistant/components/eddystone_temperature/sensor.py b/homeassistant/components/eddystone_temperature/sensor.py index 1d6ff61bf59..f8a7254b6fa 100644 --- a/homeassistant/components/eddystone_temperature/sensor.py +++ b/homeassistant/components/eddystone_temperature/sensor.py @@ -179,7 +179,7 @@ class Monitor: def stop(self): """Signal runner to stop and join thread.""" if self.scanning: - _LOGGER.debug("Stopping...") + _LOGGER.debug("Stopping") self.scanner.stop() _LOGGER.debug("Stopped") self.scanning = False diff --git a/homeassistant/components/envisalink/__init__.py b/homeassistant/components/envisalink/__init__.py index 73e20eea92c..75d4bff3dd1 100644 --- a/homeassistant/components/envisalink/__init__.py +++ b/homeassistant/components/envisalink/__init__.py @@ -144,9 +144,7 @@ async def async_setup(hass, config): @callback def connection_fail_callback(data): """Network failure callback.""" - _LOGGER.error( - "Could not establish a connection with the Envisalink- retrying..." - ) + _LOGGER.error("Could not establish a connection with the Envisalink- retrying") if not sync_connect.done(): hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink) sync_connect.set_result(True) @@ -162,13 +160,13 @@ async def async_setup(hass, config): @callback def zones_updated_callback(data): """Handle zone timer updates.""" - _LOGGER.debug("Envisalink sent a zone update event. Updating zones...") + _LOGGER.debug("Envisalink sent a zone update event. Updating zones") async_dispatcher_send(hass, SIGNAL_ZONE_UPDATE, data) @callback def alarm_data_updated_callback(data): """Handle non-alarm based info updates.""" - _LOGGER.debug("Envisalink sent new alarm info. Updating alarms...") + _LOGGER.debug("Envisalink sent new alarm info. Updating alarms") async_dispatcher_send(hass, SIGNAL_KEYPAD_UPDATE, data) @callback diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index e707de5f543..85b31c31576 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -230,7 +230,7 @@ class FanEntity(ToggleEntity): async def async_set_speed_deprecated(self, speed: str): """Set the speed of the fan.""" _LOGGER.warning( - "fan.set_speed is deprecated, use fan.set_percentage or fan.set_preset_mode instead." + "The fan.set_speed service is deprecated, use fan.set_percentage or fan.set_preset_mode instead" ) await self.async_set_speed(speed) @@ -368,7 +368,7 @@ class FanEntity(ToggleEntity): percentage = None elif speed is not None: _LOGGER.warning( - "Calling fan.turn_on with the speed argument is deprecated, use percentage or preset_mode instead." + "Calling fan.turn_on with the speed argument is deprecated, use percentage or preset_mode instead" ) if speed in self.preset_modes: preset_mode = speed diff --git a/homeassistant/components/fints/sensor.py b/homeassistant/components/fints/sensor.py index 4ccd2b6f848..96d6ecc2166 100644 --- a/homeassistant/components/fints/sensor.py +++ b/homeassistant/components/fints/sensor.py @@ -75,7 +75,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for account in balance_accounts: if config[CONF_ACCOUNTS] and account.iban not in account_config: - _LOGGER.info("skipping account %s for bank %s", account.iban, fints_name) + _LOGGER.info("Skipping account %s for bank %s", account.iban, fints_name) continue account_name = account_config.get(account.iban) @@ -87,7 +87,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for account in holdings_accounts: if config[CONF_HOLDINGS] and account.accountnumber not in holdings_config: _LOGGER.info( - "skipping holdings %s for bank %s", account.accountnumber, fints_name + "Skipping holdings %s for bank %s", account.accountnumber, fints_name ) continue diff --git a/homeassistant/components/folder/sensor.py b/homeassistant/components/folder/sensor.py index cfbfd670d05..a6dccf57602 100644 --- a/homeassistant/components/folder/sensor.py +++ b/homeassistant/components/folder/sensor.py @@ -45,7 +45,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): path = config.get(CONF_FOLDER_PATHS) if not hass.config.is_allowed_path(path): - _LOGGER.error("folder %s is not valid or allowed", path) + _LOGGER.error("Folder %s is not valid or allowed", path) else: folder = Folder(path, config.get(CONF_FILTER)) add_entities([folder], True) diff --git a/homeassistant/components/folder_watcher/__init__.py b/homeassistant/components/folder_watcher/__init__.py index d99e4928cc5..7d3fd5e77a7 100644 --- a/homeassistant/components/folder_watcher/__init__.py +++ b/homeassistant/components/folder_watcher/__init__.py @@ -43,7 +43,7 @@ def setup(hass, config): path = watcher[CONF_FOLDER] patterns = watcher[CONF_PATTERNS] if not hass.config.is_allowed_path(path): - _LOGGER.error("folder %s is not valid or allowed", path) + _LOGGER.error("Folder %s is not valid or allowed", path) return False Watcher(path, patterns, hass) diff --git a/homeassistant/components/foscam/camera.py b/homeassistant/components/foscam/camera.py index d600546c3b0..ea20f0a07fb 100644 --- a/homeassistant/components/foscam/camera.py +++ b/homeassistant/components/foscam/camera.py @@ -68,7 +68,7 @@ PTZ_GOTO_PRESET_COMMAND = "ptz_goto_preset" async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up a Foscam IP Camera.""" LOGGER.warning( - "Loading foscam via platform config is deprecated, it will be automatically imported. Please remove it afterwards." + "Loading foscam via platform config is deprecated, it will be automatically imported; Please remove it afterwards" ) config_new = { diff --git a/homeassistant/components/foscam/config_flow.py b/homeassistant/components/foscam/config_flow.py index bfd13c730f8..5ec36c97fa0 100644 --- a/homeassistant/components/foscam/config_flow.py +++ b/homeassistant/components/foscam/config_flow.py @@ -127,16 +127,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self._validate_and_create(import_config) except CannotConnect: - LOGGER.error("Error importing foscam platform config: cannot connect.") + LOGGER.error("Error importing foscam platform config: cannot connect") return self.async_abort(reason="cannot_connect") except InvalidAuth: - LOGGER.error("Error importing foscam platform config: invalid auth.") + LOGGER.error("Error importing foscam platform config: invalid auth") return self.async_abort(reason="invalid_auth") except InvalidResponse: LOGGER.exception( - "Error importing foscam platform config: invalid response from camera." + "Error importing foscam platform config: invalid response from camera" ) return self.async_abort(reason="invalid_response") @@ -145,7 +145,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): except Exception: # pylint: disable=broad-except LOGGER.exception( - "Error importing foscam platform config: unexpected exception." + "Error importing foscam platform config: unexpected exception" ) return self.async_abort(reason="unknown") diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index 62302e8f669..05fcb639e47 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -33,8 +33,8 @@ async def async_setup_platform( ) -> None: """Convert old style file configs to new style configs.""" _LOGGER.warning( - "Loading gogogate2 via platform config is deprecated. The configuration" - " has been migrated to a config entry and can be safely removed." + "Loading gogogate2 via platform config is deprecated; The configuration" + " has been migrated to a config entry and can be safely removed" ) hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/homeassistant/components/growatt_server/sensor.py b/homeassistant/components/growatt_server/sensor.py index e6ed422db0f..50d269207de 100644 --- a/homeassistant/components/growatt_server/sensor.py +++ b/homeassistant/components/growatt_server/sensor.py @@ -399,7 +399,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): sensors = STORAGE_SENSOR_TYPES else: _LOGGER.debug( - "Device type %s was found but is not supported right now.", + "Device type %s was found but is not supported right now", device["deviceType"], ) diff --git a/homeassistant/components/gstreamer/media_player.py b/homeassistant/components/gstreamer/media_player.py index ea211ccd748..c927b04de25 100644 --- a/homeassistant/components/gstreamer/media_player.py +++ b/homeassistant/components/gstreamer/media_player.py @@ -82,7 +82,7 @@ class GstreamerDevice(MediaPlayerEntity): def play_media(self, media_type, media_id, **kwargs): """Play media.""" if media_type != MEDIA_TYPE_MUSIC: - _LOGGER.error("invalid media type") + _LOGGER.error("Invalid media type") return self._player.queue(media_id) diff --git a/homeassistant/components/habitica/sensor.py b/homeassistant/components/habitica/sensor.py index 4d61a86de75..dd17243cc9c 100644 --- a/homeassistant/components/habitica/sensor.py +++ b/homeassistant/components/habitica/sensor.py @@ -96,8 +96,8 @@ class HabitipyData: except ClientResponseError as error: if error.status == HTTP_TOO_MANY_REQUESTS: _LOGGER.warning( - "Sensor data update for %s has too many API requests." - " Skipping the update.", + "Sensor data update for %s has too many API requests;" + " Skipping the update", DOMAIN, ) else: @@ -113,8 +113,8 @@ class HabitipyData: except ClientResponseError as error: if error.status == HTTP_TOO_MANY_REQUESTS: _LOGGER.warning( - "Sensor data update for %s has too many API requests." - " Skipping the update.", + "Sensor data update for %s has too many API requests;" + " Skipping the update", DOMAIN, ) else: diff --git a/homeassistant/components/hangouts/hangouts_bot.py b/homeassistant/components/hangouts/hangouts_bot.py index 56045f0eb1c..10b983fb034 100644 --- a/homeassistant/components/hangouts/hangouts_bot.py +++ b/homeassistant/components/hangouts/hangouts_bot.py @@ -221,7 +221,7 @@ class HangoutsBot: async def _on_disconnect(self): """Handle disconnecting.""" if self._connected: - _LOGGER.debug("Connection lost! Reconnect...") + _LOGGER.debug("Connection lost! Reconnect") await self.async_connect() else: dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_DISCONNECTED) diff --git a/homeassistant/components/hdmi_cec/__init__.py b/homeassistant/components/hdmi_cec/__init__.py index d92342c1fb0..c7dfd335c32 100644 --- a/homeassistant/components/hdmi_cec/__init__.py +++ b/homeassistant/components/hdmi_cec/__init__.py @@ -218,7 +218,7 @@ def setup(hass: HomeAssistant, base_config): _LOGGER.debug("Reached _adapter_watchdog") event.async_call_later(hass, WATCHDOG_INTERVAL, _adapter_watchdog) if not adapter.initialized: - _LOGGER.info("Adapter not initialized. Trying to restart.") + _LOGGER.info("Adapter not initialized; Trying to restart") hass.bus.fire(EVENT_HDMI_CEC_UNAVAILABLE) adapter.init() diff --git a/homeassistant/components/hitron_coda/device_tracker.py b/homeassistant/components/hitron_coda/device_tracker.py index ace6540fe71..4634c6e378a 100644 --- a/homeassistant/components/hitron_coda/device_tracker.py +++ b/homeassistant/components/hitron_coda/device_tracker.py @@ -81,7 +81,7 @@ class HitronCODADeviceScanner(DeviceScanner): def _login(self): """Log in to the router. This is required for subsequent api calls.""" - _LOGGER.info("Logging in to CODA...") + _LOGGER.info("Logging in to CODA") try: data = [("user", self._username), (self._type, self._password)] @@ -101,7 +101,7 @@ class HitronCODADeviceScanner(DeviceScanner): def _update_info(self): """Get ARP from router.""" - _LOGGER.info("Fetching...") + _LOGGER.info("Fetching") if self._userid is None: if not self._login(): diff --git a/homeassistant/components/homeassistant/__init__.py b/homeassistant/components/homeassistant/__init__.py index 309f98e6095..67eb94a97e7 100644 --- a/homeassistant/components/homeassistant/__init__.py +++ b/homeassistant/components/homeassistant/__init__.py @@ -58,7 +58,8 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> bool: # Generic turn on/off method requires entity id if not all_referenced: _LOGGER.error( - "homeassistant.%s cannot be called without a target", service.service + "The service homeassistant.%s cannot be called without a target", + service.service, ) return diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 8b606036a48..4fea31e7238 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -536,7 +536,7 @@ class HomeKit: "The bridge %s has entity %s. For best performance, " "and to prevent unexpected unavailability, create and " "pair a separate HomeKit instance in accessory mode for " - "this entity.", + "this entity", self._name, state.entity_id, ) diff --git a/homeassistant/components/homekit/img_util.py b/homeassistant/components/homekit/img_util.py index 2baede8d957..860d798f113 100644 --- a/homeassistant/components/homekit/img_util.py +++ b/homeassistant/components/homekit/img_util.py @@ -63,6 +63,6 @@ class TurboJPEGSingleton: TurboJPEGSingleton.__instance = TurboJPEG() except Exception: # pylint: disable=broad-except _LOGGER.exception( - "libturbojpeg is not installed, cameras may impact HomeKit performance" + "Error loading libturbojpeg; Cameras may impact HomeKit performance" ) TurboJPEGSingleton.__instance = False diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py index cb0feb6ba77..2c251d41fb3 100644 --- a/homeassistant/components/homekit_controller/climate.py +++ b/homeassistant/components/homekit_controller/climate.py @@ -132,8 +132,8 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): else: hvac_mode = TARGET_HEATER_COOLER_STATE_HOMEKIT_TO_HASS.get(state) _LOGGER.warning( - "HomeKit device %s: Setting temperature in %s mode is not supported yet." - " Consider raising a ticket if you have this device and want to help us implement this feature.", + "HomeKit device %s: Setting temperature in %s mode is not supported yet;" + " Consider raising a ticket if you have this device and want to help us implement this feature", self.entity_id, hvac_mode, ) @@ -147,8 +147,8 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): return if hvac_mode not in {HVAC_MODE_HEAT, HVAC_MODE_COOL}: _LOGGER.warning( - "HomeKit device %s: Setting temperature in %s mode is not supported yet." - " Consider raising a ticket if you have this device and want to help us implement this feature.", + "HomeKit device %s: Setting temperature in %s mode is not supported yet;" + " Consider raising a ticket if you have this device and want to help us implement this feature", self.entity_id, hvac_mode, ) diff --git a/homeassistant/components/horizon/media_player.py b/homeassistant/components/horizon/media_player.py index 5b9fb656938..e6eb211206d 100644 --- a/homeassistant/components/horizon/media_player.py +++ b/homeassistant/components/horizon/media_player.py @@ -184,9 +184,7 @@ class HorizonDevice(MediaPlayerEntity): elif channel: self._client.select_channel(channel) except OSError as msg: - _LOGGER.error( - "%s disconnected: %s. Trying to reconnect...", self._name, msg - ) + _LOGGER.error("%s disconnected: %s. Trying to reconnect", self._name, msg) # for reconnect, first gracefully disconnect self._client.disconnect() diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index d82e7e03e40..5ceb252dfa9 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -197,14 +197,14 @@ class Router: self.subscriptions.pop(key) except ResponseErrorLoginRequiredException: if isinstance(self.connection, AuthorizedConnection): - _LOGGER.debug("Trying to authorize again...") + _LOGGER.debug("Trying to authorize again") if self.connection.enforce_authorized_connection(): _LOGGER.debug( - "...success, %s will be updated by a future periodic run", + "success, %s will be updated by a future periodic run", key, ) else: - _LOGGER.debug("...failed") + _LOGGER.debug("failed") return _LOGGER.info( "%s requires authorization, excluding from future updates", key diff --git a/homeassistant/components/hue/bridge.py b/homeassistant/components/hue/bridge.py index dc9b56fcdfe..c14caa89620 100644 --- a/homeassistant/components/hue/bridge.py +++ b/homeassistant/components/hue/bridge.py @@ -252,7 +252,7 @@ class HueBridge: # we already created a new config flow, no need to do it again return LOGGER.error( - "Unable to authorize to bridge %s, setup the linking again.", self.host + "Unable to authorize to bridge %s, setup the linking again", self.host ) self.authorized = False create_config_flow(self.hass, self.host) diff --git a/homeassistant/components/huisbaasje/__init__.py b/homeassistant/components/huisbaasje/__init__.py index 23dc3cb7eda..8cd2681c8da 100644 --- a/homeassistant/components/huisbaasje/__init__.py +++ b/homeassistant/components/huisbaasje/__init__.py @@ -100,7 +100,7 @@ async def async_update_huisbaasje(huisbaasje): # handled by the data update coordinator. async with async_timeout.timeout(FETCH_TIMEOUT): if not huisbaasje.is_authenticated(): - _LOGGER.warning("Huisbaasje is unauthenticated. Reauthenticating...") + _LOGGER.warning("Huisbaasje is unauthenticated. Reauthenticating") await huisbaasje.authenticate() current_measurements = await huisbaasje.current_measurements() diff --git a/homeassistant/components/icloud/account.py b/homeassistant/components/icloud/account.py index 5c7f448668d..97bba3c5ca6 100644 --- a/homeassistant/components/icloud/account.py +++ b/homeassistant/components/icloud/account.py @@ -125,9 +125,9 @@ class IcloudAccount: # Login failed which means credentials need to be updated. _LOGGER.error( ( - "Your password for '%s' is no longer working. Go to the " + "Your password for '%s' is no longer working; Go to the " "Integrations menu and click on Configure on the discovered Apple " - "iCloud card to login again." + "iCloud card to login again" ), self._config_entry.data[CONF_USERNAME], ) diff --git a/homeassistant/components/insteon/config_flow.py b/homeassistant/components/insteon/config_flow.py index d8e17cfa03f..7af396baa1e 100644 --- a/homeassistant/components/insteon/config_flow.py +++ b/homeassistant/components/insteon/config_flow.py @@ -65,10 +65,10 @@ async def _async_connect(**kwargs): """Connect to the Insteon modem.""" try: await async_connect(**kwargs) - _LOGGER.info("Connected to Insteon modem.") + _LOGGER.info("Connected to Insteon modem") return True except ConnectionError: - _LOGGER.error("Could not connect to Insteon modem.") + _LOGGER.error("Could not connect to Insteon modem") return False diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py index a2648d0dbc4..de43407c371 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -144,7 +144,7 @@ async def async_setup_entry( https = True port = host.port or 443 else: - _LOGGER.error("isy994 host value in configuration is invalid") + _LOGGER.error("The isy994 host value in configuration is invalid") return False # Connect to ISY controller. diff --git a/homeassistant/components/isy994/config_flow.py b/homeassistant/components/isy994/config_flow.py index 3d52687bced..95c8a3664fb 100644 --- a/homeassistant/components/isy994/config_flow.py +++ b/homeassistant/components/isy994/config_flow.py @@ -60,7 +60,7 @@ async def validate_input(hass: core.HomeAssistant, data): https = True port = host.port or 443 else: - _LOGGER.error("isy994 host value in configuration is invalid") + _LOGGER.error("The isy994 host value in configuration is invalid") raise InvalidHost # Connect to ISY controller. diff --git a/homeassistant/components/keba/__init__.py b/homeassistant/components/keba/__init__.py index 764110f94b9..e1cf9bfd3ea 100644 --- a/homeassistant/components/keba/__init__.py +++ b/homeassistant/components/keba/__init__.py @@ -233,7 +233,7 @@ class KebaHandler(KebaKeContact): self._set_fast_polling() except (KeyError, ValueError) as ex: _LOGGER.warning( - "failsafe_timeout, failsafe_fallback and/or " - "failsafe_persist value are not correct. %s", + "Values are not correct for: failsafe_timeout, failsafe_fallback and/or " + "failsafe_persist: %s", ex, ) diff --git a/homeassistant/components/keenetic_ndms2/router.py b/homeassistant/components/keenetic_ndms2/router.py index 0066b49223b..049d9aab0de 100644 --- a/homeassistant/components/keenetic_ndms2/router.py +++ b/homeassistant/components/keenetic_ndms2/router.py @@ -167,7 +167,7 @@ class KeeneticRouter: def _update_devices(self): """Get ARP from keenetic router.""" - _LOGGER.debug("Fetching devices from router...") + _LOGGER.debug("Fetching devices from router") try: _response = self._client.get_devices( diff --git a/homeassistant/components/kiwi/lock.py b/homeassistant/components/kiwi/lock.py index 6a94cc5a393..8a0eeed83f0 100644 --- a/homeassistant/components/kiwi/lock.py +++ b/homeassistant/components/kiwi/lock.py @@ -102,7 +102,7 @@ class KiwiLock(LockEntity): try: self._client.open_door(self.lock_id) except KiwiException: - _LOGGER.error("failed to open door") + _LOGGER.error("Failed to open door") else: self._state = STATE_UNLOCKED self.hass.add_job( diff --git a/homeassistant/components/konnected/panel.py b/homeassistant/components/konnected/panel.py index 18f2ed64a1d..cf2f33de332 100644 --- a/homeassistant/components/konnected/panel.py +++ b/homeassistant/components/konnected/panel.py @@ -376,7 +376,7 @@ class AlarmPanel: self.async_desired_settings_payload() != self.async_current_settings_payload() ): - _LOGGER.info("pushing settings to device %s", self.device_id) + _LOGGER.info("Pushing settings to device %s", self.device_id) await self.client.put_settings(**self.async_desired_settings_payload()) diff --git a/homeassistant/components/lifx_legacy/light.py b/homeassistant/components/lifx_legacy/light.py index 4d50ecbecf2..795f3e17793 100644 --- a/homeassistant/components/lifx_legacy/light.py +++ b/homeassistant/components/lifx_legacy/light.py @@ -59,7 +59,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the LIFX platform.""" _LOGGER.warning( "The LIFX Legacy platform is deprecated and will be removed in " - "Home Assistant Core 2021.6.0. Use the LIFX integration instead." + "Home Assistant Core 2021.6.0; Use the LIFX integration instead" ) server_addr = config.get(CONF_SERVER) diff --git a/homeassistant/components/meraki/device_tracker.py b/homeassistant/components/meraki/device_tracker.py index 55186d63146..13644c1d341 100644 --- a/homeassistant/components/meraki/device_tracker.py +++ b/homeassistant/components/meraki/device_tracker.py @@ -56,7 +56,7 @@ class MerakiView(HomeAssistantView): return self.json_message("Invalid JSON", HTTP_BAD_REQUEST) _LOGGER.debug("Meraki Data from Post: %s", json.dumps(data)) if not data.get("secret", False): - _LOGGER.error("secret invalid") + _LOGGER.error("The secret is invalid") return self.json_message("No secret", HTTP_UNPROCESSABLE_ENTITY) if data["secret"] != self.secret: _LOGGER.error("Invalid Secret received from Meraki") diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py index 3034135f847..1229a4e43af 100644 --- a/homeassistant/components/meteo_france/__init__.py +++ b/homeassistant/components/meteo_france/__init__.py @@ -159,7 +159,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool ) else: _LOGGER.warning( - "Weather alert not available: The city %s is not in metropolitan France or Andorre.", + "Weather alert not available: The city %s is not in metropolitan France or Andorre", entry.title, ) @@ -189,7 +189,7 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): ].data.position.get("dept") hass.data[DOMAIN][department] = False _LOGGER.debug( - "Weather alert for depatment %s unloaded and released. It can be added now by another city.", + "Weather alert for depatment %s unloaded and released. It can be added now by another city", department, ) diff --git a/homeassistant/components/meteo_france/weather.py b/homeassistant/components/meteo_france/weather.py index 09e062cc715..08d5c1c4f6a 100644 --- a/homeassistant/components/meteo_france/weather.py +++ b/homeassistant/components/meteo_france/weather.py @@ -59,7 +59,7 @@ async def async_setup_entry( True, ) _LOGGER.debug( - "Weather entity (%s) added for %s.", + "Weather entity (%s) added for %s", entry.options.get(CONF_MODE, FORECAST_MODE_DAILY), coordinator.data.position["name"], ) diff --git a/homeassistant/components/motion_blinds/gateway.py b/homeassistant/components/motion_blinds/gateway.py index 14dd36ce5b0..6f8032e5a65 100644 --- a/homeassistant/components/motion_blinds/gateway.py +++ b/homeassistant/components/motion_blinds/gateway.py @@ -30,7 +30,7 @@ class ConnectMotionGateway: async def async_connect_gateway(self, host, key): """Connect to the Motion Gateway.""" - _LOGGER.debug("Initializing with host %s (key %s...)", host, key[:3]) + _LOGGER.debug("Initializing with host %s (key %s)", host, key[:3]) self._gateway_device = MotionGateway( ip=host, key=key, multicast=self._multicast ) diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 7f810501e1a..010f751dad4 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -120,7 +120,7 @@ def validate_options(value): and CONF_VALUE_TEMPLATE in value ): _LOGGER.warning( - "using 'value_template' for 'position_topic' is deprecated " + "Using 'value_template' for 'position_topic' is deprecated " "and will be removed from Home Assistant in version 2021.6, " "please replace it with 'position_template'" ) diff --git a/homeassistant/components/mystrom/light.py b/homeassistant/components/mystrom/light.py index 510245ea859..145bdadcc2a 100644 --- a/homeassistant/components/mystrom/light.py +++ b/homeassistant/components/mystrom/light.py @@ -145,7 +145,7 @@ class MyStromLight(LightEntity): try: await self._bulb.set_off() except MyStromConnectionError: - _LOGGER.warning("myStrom bulb not online") + _LOGGER.warning("The myStrom bulb not online") async def async_update(self): """Fetch new state data for this light.""" diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py index d76133c91ab..b9b04a08feb 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -207,7 +207,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): await hass.async_add_executor_job( hass.data[DOMAIN][entry.entry_id][AUTH].dropwebhook ) - _LOGGER.info("Unregister Netatmo webhook.") + _LOGGER.info("Unregister Netatmo webhook") await hass.data[DOMAIN][entry.entry_id][DATA_HANDLER].async_cleanup() diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index 6558717b847..caa4aebe376 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -123,10 +123,10 @@ async def async_setup_entry(hass, entry, async_add_entities): entities = [] for home_id in get_all_home_ids(home_data): - _LOGGER.debug("Setting up home %s ...", home_id) + _LOGGER.debug("Setting up home %s", home_id) for room_id in home_data.rooms[home_id].keys(): room_name = home_data.rooms[home_id][room_id]["name"] - _LOGGER.debug("Setting up room %s (%s) ...", room_name, room_id) + _LOGGER.debug("Setting up room %s (%s)", room_name, room_id) signal_name = f"{HOMESTATUS_DATA_CLASS_NAME}-{home_id}" await data_handler.register_data_class( HOMESTATUS_DATA_CLASS_NAME, signal_name, None, home_id=home_id diff --git a/homeassistant/components/nissan_leaf/__init__.py b/homeassistant/components/nissan_leaf/__init__.py index 967857aedc5..24adf223719 100644 --- a/homeassistant/components/nissan_leaf/__init__.py +++ b/homeassistant/components/nissan_leaf/__init__.py @@ -135,7 +135,7 @@ def setup(hass, config): def setup_leaf(car_config): """Set up a car.""" - _LOGGER.debug("Logging into You+Nissan...") + _LOGGER.debug("Logging into You+Nissan") username = car_config[CONF_USERNAME] password = car_config[CONF_PASSWORD] diff --git a/homeassistant/components/nmap_tracker/device_tracker.py b/homeassistant/components/nmap_tracker/device_tracker.py index 608f90d5421..69c65873e51 100644 --- a/homeassistant/components/nmap_tracker/device_tracker.py +++ b/homeassistant/components/nmap_tracker/device_tracker.py @@ -89,7 +89,7 @@ class NmapDeviceScanner(DeviceScanner): Returns boolean if scanning successful. """ - _LOGGER.debug("Scanning...") + _LOGGER.debug("Scanning") scanner = PortScanner() diff --git a/homeassistant/components/onvif/config_flow.py b/homeassistant/components/onvif/config_flow.py index 6c6e155a046..9bd6f629459 100644 --- a/homeassistant/components/onvif/config_flow.py +++ b/homeassistant/components/onvif/config_flow.py @@ -50,7 +50,7 @@ def wsdiscovery() -> list[Service]: async def async_discovery(hass) -> bool: """Return if there are devices that can be discovered.""" - LOGGER.debug("Starting ONVIF discovery...") + LOGGER.debug("Starting ONVIF discovery") services = await hass.async_add_executor_job(wsdiscovery) devices = [] diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index 07be1fbfd03..761eb2fc2dc 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -439,7 +439,7 @@ class ONVIFDevice: await ptz_service.Stop(req) except ONVIFError as err: if "Bad Request" in err.reason: - LOGGER.warning("Device '%s' doesn't support PTZ.", self.name) + LOGGER.warning("Device '%s' doesn't support PTZ", self.name) else: LOGGER.error("Error trying to perform PTZ action: %s", err) diff --git a/homeassistant/components/onvif/event.py b/homeassistant/components/onvif/event.py index 064f0dcfa0f..91db2a90e57 100644 --- a/homeassistant/components/onvif/event.py +++ b/homeassistant/components/onvif/event.py @@ -132,7 +132,7 @@ class EventManager: if not restarted: LOGGER.warning( - "Failed to restart ONVIF PullPoint subscription for '%s'. Retrying...", + "Failed to restart ONVIF PullPoint subscription for '%s'. Retrying", self.unique_id, ) # Try again in a minute diff --git a/homeassistant/components/orvibo/switch.py b/homeassistant/components/orvibo/switch.py index a03d9fc5ff0..f7a16036f00 100644 --- a/homeassistant/components/orvibo/switch.py +++ b/homeassistant/components/orvibo/switch.py @@ -44,7 +44,7 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None): switch_conf = config.get(CONF_SWITCHES, [config]) if config.get(CONF_DISCOVERY): - _LOGGER.info("Discovering S20 switches ...") + _LOGGER.info("Discovering S20 switches") switch_data.update(discover()) for switch in switch_conf: diff --git a/homeassistant/components/panasonic_viera/__init__.py b/homeassistant/components/panasonic_viera/__init__.py index 449515802b9..67cf07dc433 100644 --- a/homeassistant/components/panasonic_viera/__init__.py +++ b/homeassistant/components/panasonic_viera/__init__.py @@ -94,7 +94,7 @@ async def async_setup_entry(hass, config_entry): unique_id = config_entry.unique_id if device_info is None: _LOGGER.error( - "Couldn't gather device info. Please restart Home Assistant with your TV turned on and connected to your network." + "Couldn't gather device info; Please restart Home Assistant with your TV turned on and connected to your network" ) else: unique_id = device_info[ATTR_UDN] diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index c8d383867cf..6e1a83297b3 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -418,9 +418,11 @@ class PlexServer: """Connect to a plex.tv resource and return a Plex client.""" try: client = resource.connect(timeout=3) - _LOGGER.debug("plex.tv resource connection successful: %s", client) + _LOGGER.debug("Resource connection successful to plex.tv: %s", client) except NotFound: - _LOGGER.error("plex.tv resource connection failed: %s", resource.name) + _LOGGER.error( + "Resource connection failed to plex.tv: %s", resource.name + ) else: client.proxyThroughServer(value=False, server=self._plex_server) self._client_device_cache[client.machineIdentifier] = client diff --git a/homeassistant/components/plum_lightpad/__init__.py b/homeassistant/components/plum_lightpad/__init__.py index 858f87e74f8..aeabe8634f8 100644 --- a/homeassistant/components/plum_lightpad/__init__.py +++ b/homeassistant/components/plum_lightpad/__init__.py @@ -38,7 +38,7 @@ async def async_setup(hass: HomeAssistant, config: dict): conf = config[DOMAIN] - _LOGGER.info("Found Plum Lightpad configuration in config, importing...") + _LOGGER.info("Found Plum Lightpad configuration in config, importing") hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=conf diff --git a/homeassistant/components/poolsense/__init__.py b/homeassistant/components/poolsense/__init__.py index 3dc9ace1b8f..b11b7732e19 100644 --- a/homeassistant/components/poolsense/__init__.py +++ b/homeassistant/components/poolsense/__init__.py @@ -118,7 +118,7 @@ class PoolSenseDataUpdateCoordinator(DataUpdateCoordinator): try: data = await self.poolsense.get_poolsense_data() except (PoolSenseError) as error: - _LOGGER.error("PoolSense query did not complete.") + _LOGGER.error("PoolSense query did not complete") raise UpdateFailed(error) from error return data diff --git a/homeassistant/components/pushsafer/notify.py b/homeassistant/components/pushsafer/notify.py index bec85409010..3337af0f8b0 100644 --- a/homeassistant/components/pushsafer/notify.py +++ b/homeassistant/components/pushsafer/notify.py @@ -93,7 +93,7 @@ class PushsaferNotificationService(BaseNotificationService): _LOGGER.debug("Loading image from file %s", local_path) picture1_encoded = self.load_from_file(local_path) else: - _LOGGER.warning("missing url or local_path for picture1") + _LOGGER.warning("Missing url or local_path for picture1") else: _LOGGER.debug("picture1 is not specified") @@ -143,7 +143,7 @@ class PushsaferNotificationService(BaseNotificationService): else: response = requests.get(url, timeout=CONF_TIMEOUT) return self.get_base64(response.content, response.headers["content-type"]) - _LOGGER.warning("url not found in param") + _LOGGER.warning("No url was found in param") return None diff --git a/homeassistant/components/radiotherm/climate.py b/homeassistant/components/radiotherm/climate.py index d7bca1175cb..aad6bf3989e 100644 --- a/homeassistant/components/radiotherm/climate.py +++ b/homeassistant/components/radiotherm/climate.py @@ -372,6 +372,6 @@ class RadioThermostat(ClimateEntity): self.device.program_mode = PRESET_MODE_TO_CODE[preset_mode] else: _LOGGER.error( - "preset_mode %s not in PRESET_MODES", + "Preset_mode %s not in PRESET_MODES", preset_mode, ) diff --git a/homeassistant/components/recollect_waste/sensor.py b/homeassistant/components/recollect_waste/sensor.py index b6a99f4ebea..000d76b54c7 100644 --- a/homeassistant/components/recollect_waste/sensor.py +++ b/homeassistant/components/recollect_waste/sensor.py @@ -57,8 +57,8 @@ async def async_setup_platform( ): """Import Recollect Waste configuration from YAML.""" LOGGER.warning( - "Loading ReCollect Waste via platform setup is deprecated. " - "Please remove it from your configuration." + "Loading ReCollect Waste via platform setup is deprecated; " + "Please remove it from your configuration" ) hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/homeassistant/components/recorder/util.py b/homeassistant/components/recorder/util.py index b04a4fb7f1f..c17fb33d365 100644 --- a/homeassistant/components/recorder/util.py +++ b/homeassistant/components/recorder/util.py @@ -175,7 +175,7 @@ def validate_sqlite_database(dbpath: str, db_integrity_check: bool) -> bool: run_checks_on_open_db(dbpath, conn.cursor(), db_integrity_check) conn.close() except sqlite3.DatabaseError: - _LOGGER.exception("The database at %s is corrupt or malformed.", dbpath) + _LOGGER.exception("The database at %s is corrupt or malformed", dbpath) return False return True @@ -210,7 +210,7 @@ def run_checks_on_open_db(dbpath, cursor, db_integrity_check): if not last_run_was_clean: _LOGGER.warning( - "The system could not validate that the sqlite3 database at %s was shutdown cleanly.", + "The system could not validate that the sqlite3 database at %s was shutdown cleanly", dbpath, ) diff --git a/homeassistant/components/rflink/__init__.py b/homeassistant/components/rflink/__init__.py index 18f02d66a31..81d44806c5e 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -246,7 +246,7 @@ async def async_setup(hass, config): # If HA is not stopping, initiate new connection if hass.state != CoreState.stopping: - _LOGGER.warning("disconnected from Rflink, reconnecting") + _LOGGER.warning("Disconnected from Rflink, reconnecting") hass.async_create_task(connect()) async def connect(): diff --git a/homeassistant/components/rfxtrx/__init__.py b/homeassistant/components/rfxtrx/__init__.py index 649a573c5b4..d23a3e4e6ff 100644 --- a/homeassistant/components/rfxtrx/__init__.py +++ b/homeassistant/components/rfxtrx/__init__.py @@ -428,7 +428,7 @@ def find_possible_pt2262_device(device_ids, device_id): if size is not None: size = len(dev_id) - size - 1 _LOGGER.info( - "rfxtrx: found possible device %s for %s " + "Found possible device %s for %s " "with the following configuration:\n" "data_bits=%d\n" "command_on=0x%s\n" diff --git a/homeassistant/components/screenlogic/__init__.py b/homeassistant/components/screenlogic/__init__.py index 720b59f80b9..a5e0f248bc6 100644 --- a/homeassistant/components/screenlogic/__init__.py +++ b/homeassistant/components/screenlogic/__init__.py @@ -46,7 +46,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): if mac in discovered_gateways: connect_info = discovered_gateways[mac] else: - _LOGGER.warning("Gateway rediscovery failed.") + _LOGGER.warning("Gateway rediscovery failed") # Static connection defined or fallback from discovery connect_info = { SL_GATEWAY_NAME: name_for_mac(mac), diff --git a/homeassistant/components/screenlogic/switch.py b/homeassistant/components/screenlogic/switch.py index aa1e643681e..7c1a468df7d 100644 --- a/homeassistant/components/screenlogic/switch.py +++ b/homeassistant/components/screenlogic/switch.py @@ -47,10 +47,10 @@ class ScreenLogicSwitch(ScreenlogicEntity, SwitchEntity): if await self.hass.async_add_executor_job( self.gateway.set_circuit, self._data_key, circuit_value ): - _LOGGER.info("screenlogic turn %s %s", circuit_value, self._data_key) + _LOGGER.debug("Screenlogic turn %s %s", circuit_value, self._data_key) await self.coordinator.async_request_refresh() else: - _LOGGER.info("screenlogic turn %s %s error", circuit_value, self._data_key) + _LOGGER.info("Screenlogic turn %s %s error", circuit_value, self._data_key) @property def circuit(self): diff --git a/homeassistant/components/screenlogic/water_heater.py b/homeassistant/components/screenlogic/water_heater.py index 2a0a8e82c80..6b16f68e141 100644 --- a/homeassistant/components/screenlogic/water_heater.py +++ b/homeassistant/components/screenlogic/water_heater.py @@ -105,7 +105,7 @@ class ScreenLogicWaterHeater(ScreenlogicEntity, WaterHeaterEntity): ): await self.coordinator.async_request_refresh() else: - _LOGGER.error("screenlogic set_temperature error") + _LOGGER.error("Screenlogic set_temperature error") async def async_set_operation_mode(self, operation_mode) -> None: """Set the operation mode.""" @@ -115,7 +115,7 @@ class ScreenLogicWaterHeater(ScreenlogicEntity, WaterHeaterEntity): ): await self.coordinator.async_request_refresh() else: - _LOGGER.error("screenlogic set_operation_mode error") + _LOGGER.error("Screenlogic set_operation_mode error") @property def body(self): diff --git a/homeassistant/components/skybeacon/sensor.py b/homeassistant/components/skybeacon/sensor.py index bff9e311844..3308ec80b8f 100644 --- a/homeassistant/components/skybeacon/sensor.py +++ b/homeassistant/components/skybeacon/sensor.py @@ -47,7 +47,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Skybeacon sensor.""" name = config.get(CONF_NAME) mac = config.get(CONF_MAC) - _LOGGER.debug("Setting up...") + _LOGGER.debug("Setting up") mon = Monitor(hass, mac, name) add_entities([SkybeaconTemp(name, mon)]) diff --git a/homeassistant/components/smarty/__init__.py b/homeassistant/components/smarty/__init__.py index 22987673005..72d5071882f 100644 --- a/homeassistant/components/smarty/__init__.py +++ b/homeassistant/components/smarty/__init__.py @@ -59,12 +59,12 @@ def setup(hass, config): def poll_device_update(event_time): """Update Smarty device.""" - _LOGGER.debug("Updating Smarty device...") + _LOGGER.debug("Updating Smarty device") if smarty.update(): - _LOGGER.debug("Update success...") + _LOGGER.debug("Update success") dispatcher_send(hass, SIGNAL_UPDATE_SMARTY) else: - _LOGGER.debug("Update failed...") + _LOGGER.debug("Update failed") track_time_interval(hass, poll_device_update, timedelta(seconds=30)) diff --git a/homeassistant/components/somfy_mylink/__init__.py b/homeassistant/components/somfy_mylink/__init__.py index a2ade387c01..40240306dc4 100644 --- a/homeassistant/components/somfy_mylink/__init__.py +++ b/homeassistant/components/somfy_mylink/__init__.py @@ -101,7 +101,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): if not mylink_status or "error" in mylink_status: _LOGGER.error( - "mylink failed to setup because of an error: %s", + "Somfy Mylink failed to setup because of an error: %s", mylink_status.get("error", {}).get( "message", "Empty response from mylink device" ), diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 0b01ff94462..9b2342e5e1b 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -1330,7 +1330,7 @@ class SonosEntity(MediaPlayerEntity): if one_alarm._alarm_id == str(alarm_id): alarm = one_alarm if alarm is None: - _LOGGER.warning("did not find alarm with id %s", alarm_id) + _LOGGER.warning("Did not find alarm with id %s", alarm_id) return if time is not None: alarm.start_time = time diff --git a/homeassistant/components/starline/account.py b/homeassistant/components/starline/account.py index 8d967dc2ea7..3f82b816cd5 100644 --- a/homeassistant/components/starline/account.py +++ b/homeassistant/components/starline/account.py @@ -116,7 +116,7 @@ class StarlineAccount: def unload(self): """Unload StarLine API.""" - _LOGGER.debug("Unloading StarLine API.") + _LOGGER.debug("Unloading StarLine API") if self._unsubscribe_auto_updater is not None: self._unsubscribe_auto_updater() self._unsubscribe_auto_updater = None diff --git a/homeassistant/components/subaru/config_flow.py b/homeassistant/components/subaru/config_flow.py index 4c5c476a402..a3586b32974 100644 --- a/homeassistant/components/subaru/config_flow.py +++ b/homeassistant/components/subaru/config_flow.py @@ -107,7 +107,7 @@ class SubaruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) _LOGGER.debug("Using subarulink %s", self.controller.version) _LOGGER.debug( - "Setting up first time connection to Subuaru API. This may take up to 20 seconds." + "Setting up first time connection to Subuaru API; This may take up to 20 seconds" ) if await self.controller.connect(): _LOGGER.debug("Successfully authenticated and authorized with Subaru API") diff --git a/homeassistant/components/syncthru/sensor.py b/homeassistant/components/syncthru/sensor.py index bfada33bf38..edec503bc25 100644 --- a/homeassistant/components/syncthru/sensor.py +++ b/homeassistant/components/syncthru/sensor.py @@ -41,7 +41,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= """Set up the SyncThru component.""" _LOGGER.warning( "Loading syncthru via platform config is deprecated and no longer " - "necessary as of 0.113. Please remove it from your configuration YAML." + "necessary as of 0.113; Please remove it from your configuration YAML" ) hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/homeassistant/components/system_health/__init__.py b/homeassistant/components/system_health/__init__.py index a5756ce9ddc..2ad4863dbec 100644 --- a/homeassistant/components/system_health/__init__.py +++ b/homeassistant/components/system_health/__init__.py @@ -36,7 +36,7 @@ def async_register_info( Deprecated. """ _LOGGER.warning( - "system_health.async_register_info is deprecated. Add a system_health platform instead." + "Calling system_health.async_register_info is deprecated; Add a system_health platform instead" ) hass.data.setdefault(DOMAIN, {}) SystemHealthRegistration(hass, domain).async_register_info(info_callback) diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py index ce856c04c64..805ffcdba5d 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -174,7 +174,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # If not, do not create the entity and add a warning to the log if resource[CONF_TYPE] == "processor_temperature": if SystemMonitorSensor.read_cpu_temperature() is None: - _LOGGER.warning("Cannot read CPU / processor temperature information.") + _LOGGER.warning("Cannot read CPU / processor temperature information") continue dev.append(SystemMonitorSensor(resource[CONF_TYPE], resource[CONF_ARG])) diff --git a/homeassistant/components/telegram_bot/webhooks.py b/homeassistant/components/telegram_bot/webhooks.py index f772e2411e5..7fd6cb24efd 100644 --- a/homeassistant/components/telegram_bot/webhooks.py +++ b/homeassistant/components/telegram_bot/webhooks.py @@ -42,7 +42,7 @@ async def async_setup_platform(hass, config): if (last_error_date is not None) and (isinstance(last_error_date, int)): last_error_date = dt.datetime.fromtimestamp(last_error_date) _LOGGER.info( - "telegram webhook last_error_date: %s. Status: %s", + "Telegram webhook last_error_date: %s. Status: %s", last_error_date, current_status, ) diff --git a/homeassistant/components/tensorflow/image_processing.py b/homeassistant/components/tensorflow/image_processing.py index 0b8fafd57a8..dad83005512 100644 --- a/homeassistant/components/tensorflow/image_processing.py +++ b/homeassistant/components/tensorflow/image_processing.py @@ -336,7 +336,7 @@ class TensorFlowImageProcessor(ImageProcessingEntity): """Process the image.""" model = self.hass.data[DOMAIN][CONF_MODEL] if not model: - _LOGGER.debug("Model not yet ready.") + _LOGGER.debug("Model not yet ready") return start = time.perf_counter() diff --git a/homeassistant/components/twitter/notify.py b/homeassistant/components/twitter/notify.py index 62e8fc17dff..ac7de89a61b 100644 --- a/homeassistant/components/twitter/notify.py +++ b/homeassistant/components/twitter/notify.py @@ -201,7 +201,7 @@ class TwitterNotificationService(BaseNotificationService): method_override="GET", ) if resp.status_code != HTTP_OK: - _LOGGER.error("media processing error: %s", resp.json()) + _LOGGER.error("Media processing error: %s", resp.json()) processing_info = resp.json()["processing_info"] _LOGGER.debug("media processing %s status: %s", media_id, processing_info) diff --git a/homeassistant/components/upcloud/__init__.py b/homeassistant/components/upcloud/__init__.py index 0f463aec666..756862456f0 100644 --- a/homeassistant/components/upcloud/__init__.py +++ b/homeassistant/components/upcloud/__init__.py @@ -127,7 +127,7 @@ async def async_setup(hass: HomeAssistantType, config) -> bool: _LOGGER.warning( "Loading upcloud via top level config is deprecated and no longer " - "necessary as of 0.117. Please remove it from your YAML configuration." + "necessary as of 0.117; Please remove it from your YAML configuration" ) hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/homeassistant/components/vizio/config_flow.py b/homeassistant/components/vizio/config_flow.py index c6632868ae3..2c3c365b15a 100644 --- a/homeassistant/components/vizio/config_flow.py +++ b/homeassistant/components/vizio/config_flow.py @@ -273,7 +273,8 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if errors and self.context["source"] == SOURCE_IMPORT: # Log an error message if import config flow fails since otherwise failure is silent _LOGGER.error( - "configuration.yaml import failure: %s", ", ".join(errors.values()) + "Importing from configuration.yaml failed: %s", + ", ".join(errors.values()), ) return self.async_show_form(step_id="user", data_schema=schema, errors=errors) diff --git a/homeassistant/components/webhook/__init__.py b/homeassistant/components/webhook/__init__.py index 9c6dfe45e74..6d61f5d62dc 100644 --- a/homeassistant/components/webhook/__init__.py +++ b/homeassistant/components/webhook/__init__.py @@ -89,7 +89,7 @@ async def async_handle_webhook(hass, webhook_id, request): # Look at content to provide some context for received webhook # Limit to 64 chars to avoid flooding the log content = await request.content.read(64) - _LOGGER.debug("%s...", content) + _LOGGER.debug("%s", content) return Response(status=HTTP_OK) try: diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index 75c1bdd2bcc..f4d4b67b8b0 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -386,7 +386,7 @@ class LgWebOSMediaPlayerEntity(MediaPlayerEntity): _LOGGER.debug("Call play media type <%s>, Id <%s>", media_type, media_id) if media_type == MEDIA_TYPE_CHANNEL: - _LOGGER.debug("Searching channel...") + _LOGGER.debug("Searching channel") partial_match_channel_id = None perfect_match_channel_id = None diff --git a/homeassistant/components/wemo/__init__.py b/homeassistant/components/wemo/__init__.py index db380ae11ca..a013d1fdd34 100644 --- a/homeassistant/components/wemo/__init__.py +++ b/homeassistant/components/wemo/__init__.py @@ -113,7 +113,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): static_conf = config.get(CONF_STATIC, []) if static_conf: - _LOGGER.debug("Adding statically configured WeMo devices...") + _LOGGER.debug("Adding statically configured WeMo devices") for device in await asyncio.gather( *[ hass.async_add_executor_job(validate_static_config, host, port) @@ -190,7 +190,7 @@ class WemoDiscovery: async def async_discover_and_schedule(self, *_) -> None: """Periodically scan the network looking for WeMo devices.""" - _LOGGER.debug("Scanning network for WeMo devices...") + _LOGGER.debug("Scanning network for WeMo devices") try: for device in await self._hass.async_add_executor_job( pywemo.discover_devices diff --git a/homeassistant/components/wirelesstag/__init__.py b/homeassistant/components/wirelesstag/__init__.py index 0efbc80f13c..5da19f54dcf 100644 --- a/homeassistant/components/wirelesstag/__init__.py +++ b/homeassistant/components/wirelesstag/__init__.py @@ -150,7 +150,7 @@ class WirelessTagPlatform: def handle_update_tags_event(self, event): """Handle push event from wireless tag manager.""" - _LOGGER.info("push notification for update arrived: %s", event) + _LOGGER.info("Push notification for update arrived: %s", event) try: tag_id = event.data.get("id") mac = event.data.get("mac") diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index adb18b0de5a..09a9786c372 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -127,7 +127,7 @@ SERVICE_TO_METHOD = { async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Import Miio configuration from YAML.""" _LOGGER.warning( - "Loading Xiaomi Miio Switch via platform setup is deprecated. Please remove it from your configuration." + "Loading Xiaomi Miio Switch via platform setup is deprecated; Please remove it from your configuration" ) hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py index b6cb2b76ae6..8551a80ff89 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -122,7 +122,7 @@ STATE_CODE_TO_STATE = { async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Import Miio configuration from YAML.""" _LOGGER.warning( - "Loading Xiaomi Miio Vacuum via platform setup is deprecated. Please remove it from your configuration." + "Loading Xiaomi Miio Vacuum via platform setup is deprecated; Please remove it from your configuration" ) hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/homeassistant/components/xs1/__init__.py b/homeassistant/components/xs1/__init__.py index 9392b9be403..1d65b2bcfd1 100644 --- a/homeassistant/components/xs1/__init__.py +++ b/homeassistant/components/xs1/__init__.py @@ -68,7 +68,7 @@ def setup(hass, config): ) return False - _LOGGER.debug("Establishing connection to XS1 gateway and retrieving data...") + _LOGGER.debug("Establishing connection to XS1 gateway and retrieving data") hass.data[DOMAIN] = {} @@ -78,7 +78,7 @@ def setup(hass, config): hass.data[DOMAIN][ACTUATORS] = actuators hass.data[DOMAIN][SENSORS] = sensors - _LOGGER.debug("Loading platforms for XS1 integration...") + _LOGGER.debug("Loading platforms for XS1 integration") # Load platforms for supported devices for platform in PLATFORMS: discovery.load_platform(hass, platform, DOMAIN, {}, config) diff --git a/homeassistant/components/zabbix/sensor.py b/homeassistant/components/zabbix/sensor.py index 4ef0da85daa..536709e5a83 100644 --- a/homeassistant/components/zabbix/sensor.py +++ b/homeassistant/components/zabbix/sensor.py @@ -37,7 +37,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): zapi = hass.data[zabbix.DOMAIN] if not zapi: - _LOGGER.error("zapi is None. Zabbix integration hasn't been loaded?") + _LOGGER.error("Zabbix integration hasn't been loaded? zapi is None") return False _LOGGER.info("Connected to Zabbix API Version %s", zapi.api_version()) diff --git a/homeassistant/components/zeroconf/usage.py b/homeassistant/components/zeroconf/usage.py index 1303412249c..1af6e3e1f3c 100644 --- a/homeassistant/components/zeroconf/usage.py +++ b/homeassistant/components/zeroconf/usage.py @@ -43,7 +43,7 @@ def _report(what: str) -> None: if not integration_frame: _LOGGER.warning( - "Detected code that %s. Please report this issue.", what, stack_info=True + "Detected code that %s; Please report this issue", what, stack_info=True ) return diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index b8265f1e089..12ea668dce8 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -889,9 +889,7 @@ async def async_setup_entry(hass, config_entry): continue network.manager.pressButton(value.value_id) network.manager.releaseButton(value.value_id) - _LOGGER.info( - "Resetting meters on node %s instance %s....", node_id, instance - ) + _LOGGER.info("Resetting meters on node %s instance %s", node_id, instance) return _LOGGER.info( "Node %s on instance %s does not have resettable meters", node_id, instance @@ -915,7 +913,7 @@ async def async_setup_entry(hass, config_entry): def start_zwave(_service_or_event): """Startup Z-Wave network.""" - _LOGGER.info("Starting Z-Wave network...") + _LOGGER.info("Starting Z-Wave network") network.start() hass.bus.fire(const.EVENT_NETWORK_START) @@ -939,7 +937,7 @@ async def async_setup_entry(hass, config_entry): "Z-Wave not ready after %d seconds, continuing anyway", waited ) _LOGGER.info( - "final network state: %d %s", network.state, network.state_str + "Final network state: %d %s", network.state, network.state_str ) break diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 64dcf9614b2..3de4eac0461 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -143,7 +143,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async_on_node_ready(node) return # if node is not yet ready, register one-time callback for ready state - LOGGER.debug("Node added: %s - waiting for it to become ready.", node.node_id) + LOGGER.debug("Node added: %s - waiting for it to become ready", node.node_id) node.once( "ready", lambda event: async_on_node_ready(event["node"]), diff --git a/homeassistant/components/zwave_js/migrate.py b/homeassistant/components/zwave_js/migrate.py index 016aa3066d7..997d34c8445 100644 --- a/homeassistant/components/zwave_js/migrate.py +++ b/homeassistant/components/zwave_js/migrate.py @@ -36,8 +36,8 @@ def async_migrate_entity( except ValueError: _LOGGER.debug( ( - "Entity %s can't be migrated because the unique ID is taken. " - "Cleaning it up since it is likely no longer valid." + "Entity %s can't be migrated because the unique ID is taken; " + "Cleaning it up since it is likely no longer valid" ), entity_id, ) diff --git a/homeassistant/core.py b/homeassistant/core.py index 64edf5742ce..debbbeeb263 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -515,11 +515,13 @@ class HomeAssistant: if self.state == CoreState.not_running: # just ignore return if self.state in [CoreState.stopping, CoreState.final_write]: - _LOGGER.info("async_stop called twice: ignored") + _LOGGER.info("Additional call to async_stop was ignored") return if self.state == CoreState.starting: # This may not work - _LOGGER.warning("async_stop called before startup is complete") + _LOGGER.warning( + "Stopping Home Assistant before startup has completed may fail" + ) # stage 1 self.state = CoreState.stopping diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index e9752645804..671009b8846 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -982,7 +982,7 @@ def distance(hass, *args): else: if not loc_helper.has_location(point_state): _LOGGER.warning( - "distance:State does not contain valid location: %s", point_state + "Distance:State does not contain valid location: %s", point_state ) return None diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index 386f14ac157..b03e93f17df 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -77,7 +77,7 @@ class Secrets: _LOGGER.setLevel(logging.DEBUG) else: _LOGGER.error( - "secrets.yaml: 'logger: debug' expected, but 'logger: %s' found", + "Error in secrets.yaml: 'logger: debug' expected, but 'logger: %s' found", logger, ) del secrets["logger"] diff --git a/pylint/plugins/hass_logger.py b/pylint/plugins/hass_logger.py new file mode 100644 index 00000000000..b771b07aa5e --- /dev/null +++ b/pylint/plugins/hass_logger.py @@ -0,0 +1,85 @@ +import astroid +from pylint.checkers import BaseChecker +from pylint.interfaces import IAstroidChecker + +LOGGER_NAMES = ("LOGGER", "_LOGGER") +LOG_LEVEL_ALLOWED_LOWER_START = ("debug",) + +# This is our checker class. +# Checkers should always inherit from `BaseChecker`. +class HassLoggerFormatChecker(BaseChecker): + """Add class member attributes to the class locals dictionary.""" + + __implements__ = IAstroidChecker + + # The name defines a custom section of the config for this checker. + name = "hass_logger" + priority = -1 + msgs = { + "W0001": ( + "User visible logger messages must not end with a period", + "hass-logger-period", + "Periods are not permitted at the end of logger messages", + ), + "W0002": ( + "User visible logger messages must start with a capital letter or downgrade to debug", + "hass-logger-capital", + "All logger messages must start with a capital letter", + ), + } + options = ( + ( + "hass-logger", + { + "default": "properties", + "help": ( + "Validate _LOGGER or LOGGER messages conform to Home Assistant standards." + ), + }, + ), + ) + + def visit_call(self, node): + """Called when a :class:`.astroid.node_classes.Call` node is visited. + See :mod:`astroid` for the description of available nodes. + :param node: The node to check. + :type node: astroid.node_classes.Call + """ + if not isinstance(node.func, astroid.Attribute) or not isinstance( + node.func.expr, astroid.Name + ): + return + + if not node.func.expr.name in LOGGER_NAMES: + return + + if not node.args: + return + + first_arg = node.args[0] + + if not isinstance(first_arg, astroid.Const) or not first_arg.value: + return + + log_message = first_arg.value + + if len(log_message) < 1: + return + + if log_message[-1] == ".": + self.add_message("hass-logger-period", args=node.args, node=node) + + if ( + isinstance(node.func.attrname, str) + and node.func.attrname not in LOG_LEVEL_ALLOWED_LOWER_START + and log_message[0].upper() != log_message[0] + ): + self.add_message("hass-logger-capital", args=node.args, node=node) + + +def register(linter): + """This required method auto registers the checker. + :param linter: The linter to register the checker to. + :type linter: pylint.lint.PyLinter + """ + linter.register_checker(HassLoggerFormatChecker(linter)) diff --git a/pyproject.toml b/pyproject.toml index 731b7f15730..80c182cc3b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,8 +23,10 @@ ignore = [ # Use a conservative default here; 2 should speed up most setups and not hurt # any too bad. Override on command line as appropriate. jobs = 2 +init-hook='from pylint.config.find_default_config_files import find_default_config_files; from pathlib import Path; import sys; sys.path.append(str(Path(Path(list(find_default_config_files())[0]).parent, "pylint/plugins")))' load-plugins = [ "pylint_strict_informational", + "hass_logger" ] persistent = false extension-pkg-whitelist = [ diff --git a/tests/components/duckdns/test_init.py b/tests/components/duckdns/test_init.py index 03fce0df20e..a78d9d280d0 100644 --- a/tests/components/duckdns/test_init.py +++ b/tests/components/duckdns/test_init.py @@ -87,7 +87,7 @@ async def test_setup_backoff(hass, aioclient_mock): tme = utcnow() await hass.async_block_till_done() - _LOGGER.debug("Backoff...") + _LOGGER.debug("Backoff") for idx in range(1, len(intervals)): tme += intervals[idx] async_fire_time_changed(hass, tme) @@ -156,7 +156,7 @@ async def test_async_track_time_interval_backoff(hass): assert call_count == 1 - _LOGGER.debug("Backoff...") + _LOGGER.debug("Backoff") for idx in range(1, len(intervals)): tme += intervals[idx] async_fire_time_changed(hass, tme + timedelta(seconds=0.1)) diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index d6899d5149a..2a29bf9e15e 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -2159,7 +2159,7 @@ async def test_deprecated_value_template_for_position_topic_warning( await hass.async_block_till_done() assert ( - "using 'value_template' for 'position_topic' is deprecated " + "Using 'value_template' for 'position_topic' is deprecated " "and will be removed from Home Assistant in version 2021.6, " "please replace it with 'position_template'" ) in caplog.text From e798f415a490fbe09ff5d17baa9ce93fc8130dd7 Mon Sep 17 00:00:00 2001 From: javicalle <31999997+javicalle@users.noreply.github.com> Date: Fri, 19 Mar 2021 15:42:45 +0100 Subject: [PATCH 493/831] Wait for switch startup in generic_thermostat (#45253) * Better status control on restore * Better status control on restore * fix code coverage * Rollback hvac_mode initialization I think I have better understood the handling of the `hvac_mode`. I change the approach. Now the thermostat doesn't initialize until the switch is available. * fix pyupgrade * fix black * Delete test_turn_on_while_restarting HVAC mode should not be modified by the switch. IMHO, this test does not make sense because if the switch is turned on the thermostat is not turning on (and not changing HVAC_MODE) * Re add turn off if HVAC is off If HVAC_MODE is off thermostat will not control heater switch. This can be because `initial_hvac_mode`, because state defaults to or because old_state. IMHO it is preferable to be excessively cautious. * Update climate.py * Change warning message * Fix black * Fix black --- .../components/generic_thermostat/climate.py | 17 +++- .../generic_thermostat/test_climate.py | 86 +++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index 7062267de19..3c7959dbf4d 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -266,6 +266,14 @@ class GenericThermostat(ClimateEntity, RestoreEntity): if not self._hvac_mode: self._hvac_mode = HVAC_MODE_OFF + # Prevent the device from keep running if HVAC_MODE_OFF + if self._hvac_mode == HVAC_MODE_OFF and self._is_device_active: + await self._async_heater_turn_off() + _LOGGER.warning( + "The climate mode is OFF, but the switch device is ON. Turning off device %s", + self.heater_entity_id, + ) + @property def should_poll(self): """Return the polling state.""" @@ -418,7 +426,11 @@ class GenericThermostat(ClimateEntity, RestoreEntity): async def _async_control_heating(self, time=None, force=False): """Check if we need to turn heating on or off.""" async with self._temp_lock: - if not self._active and None not in (self._cur_temp, self._target_temp): + if not self._active and None not in ( + self._cur_temp, + self._target_temp, + self._is_device_active, + ): self._active = True _LOGGER.info( "Obtained current and target temperature. " @@ -480,6 +492,9 @@ class GenericThermostat(ClimateEntity, RestoreEntity): @property def _is_device_active(self): """If the toggleable device is currently active.""" + if not self.hass.states.get(self.heater_entity_id): + return None + return self.hass.states.is_state(self.heater_entity_id, STATE_ON) @property diff --git a/tests/components/generic_thermostat/test_climate.py b/tests/components/generic_thermostat/test_climate.py index b83828c86c7..dc5353971b7 100644 --- a/tests/components/generic_thermostat/test_climate.py +++ b/tests/components/generic_thermostat/test_climate.py @@ -1249,6 +1249,92 @@ async def test_no_restore_state(hass): assert state.state == HVAC_MODE_OFF +async def test_initial_hvac_off_force_heater_off(hass): + """Ensure that restored state is coherent with real situation. + + 'initial_hvac_mode: off' will force HVAC status, but we must be sure + that heater don't keep on. + """ + # switch is on + calls = _setup_switch(hass, True) + assert hass.states.get(ENT_SWITCH).state == STATE_ON + + _setup_sensor(hass, 16) + + await async_setup_component( + hass, + DOMAIN, + { + "climate": { + "platform": "generic_thermostat", + "name": "test_thermostat", + "heater": ENT_SWITCH, + "target_sensor": ENT_SENSOR, + "target_temp": 20, + "initial_hvac_mode": HVAC_MODE_OFF, + } + }, + ) + await hass.async_block_till_done() + state = hass.states.get("climate.test_thermostat") + # 'initial_hvac_mode' will force state but must prevent heather keep working + assert state.state == HVAC_MODE_OFF + # heater must be switched off + assert len(calls) == 1 + call = calls[0] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH + + +async def test_restore_will_turn_off_(hass): + """Ensure that restored state is coherent with real situation. + + Thermostat status must trigger heater event if temp raises the target . + """ + heater_switch = "input_boolean.test" + mock_restore_cache( + hass, + ( + State( + "climate.test_thermostat", + HVAC_MODE_HEAT, + {ATTR_TEMPERATURE: "18", ATTR_PRESET_MODE: PRESET_NONE}, + ), + State(heater_switch, STATE_ON, {}), + ), + ) + + hass.state = CoreState.starting + + assert await async_setup_component( + hass, input_boolean.DOMAIN, {"input_boolean": {"test": None}} + ) + await hass.async_block_till_done() + assert hass.states.get(heater_switch).state == STATE_ON + + _setup_sensor(hass, 22) + + await async_setup_component( + hass, + DOMAIN, + { + "climate": { + "platform": "generic_thermostat", + "name": "test_thermostat", + "heater": heater_switch, + "target_sensor": ENT_SENSOR, + "target_temp": 20, + } + }, + ) + await hass.async_block_till_done() + state = hass.states.get("climate.test_thermostat") + assert state.attributes[ATTR_TEMPERATURE] == 20 + assert state.state == HVAC_MODE_HEAT + assert hass.states.get(heater_switch).state == STATE_ON + + async def test_restore_state_uncoherence_case(hass): """ Test restore from a strange state. From bc0eb9bf32f6493847e291e2506f99e0a0d2ce24 Mon Sep 17 00:00:00 2001 From: sycx2 Date: Fri, 19 Mar 2021 15:54:07 +0100 Subject: [PATCH 494/831] Improve uvc test camera (#41438) * Improve uvc test camera * Clean setup full config * Clean setup partial config * Set more camera defaults * Clean setup partial config v31x * Clean setup incomplete config * Clean setup nvr errors during indexing * Clean setup nvr errors during initialization * Clean properties * Fix motion recording mode properties * Clean stream * Clean login * Clean login v31x * Clean login tries both addres and caches * Clean login fails both properly * Remove not needed test * Clean camera image error * Test snapshot login retry * Clean up * Test enable and disable motion detection * Times must be UTC Co-authored-by: Martin Hjelmare --- homeassistant/components/uvc/camera.py | 3 +- tests/components/uvc/test_camera.py | 905 +++++++++++++++---------- 2 files changed, 565 insertions(+), 343 deletions(-) diff --git a/homeassistant/components/uvc/camera.py b/homeassistant/components/uvc/camera.py index 94181de37c4..367a3915e6d 100644 --- a/homeassistant/components/uvc/camera.py +++ b/homeassistant/components/uvc/camera.py @@ -13,6 +13,7 @@ from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_STREAM, Cam from homeassistant.const import CONF_PASSWORD, CONF_PORT, CONF_SSL from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv +from homeassistant.util.dt import utc_from_timestamp _LOGGER = logging.getLogger(__name__) @@ -259,5 +260,5 @@ class UnifiVideoCamera(Camera): def timestamp_ms_to_date(epoch_ms: int) -> datetime | None: """Convert millisecond timestamp to datetime.""" if epoch_ms: - return datetime.fromtimestamp(epoch_ms / 1000) + return utc_from_timestamp(epoch_ms / 1000) return None diff --git a/tests/components/uvc/test_camera.py b/tests/components/uvc/test_camera.py index 24d73272f18..75d0d40c9f3 100644 --- a/tests/components/uvc/test_camera.py +++ b/tests/components/uvc/test_camera.py @@ -1,381 +1,602 @@ """The tests for UVC camera module.""" -from datetime import datetime -import socket -import unittest -from unittest import mock +from datetime import datetime, timedelta, timezone +from unittest.mock import call, patch import pytest import requests -from uvcclient import camera, nvr +from uvcclient import camera as camera, nvr -from homeassistant.components.camera import SUPPORT_STREAM -from homeassistant.components.uvc import camera as uvc -from homeassistant.exceptions import PlatformNotReady -from homeassistant.setup import setup_component +from homeassistant.components.camera import ( + DEFAULT_CONTENT_TYPE, + SERVICE_DISABLE_MOTION, + SERVICE_ENABLE_MOTION, + STATE_RECORDING, + SUPPORT_STREAM, + async_get_image, + async_get_stream_source, +) +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry +from homeassistant.setup import async_setup_component +from homeassistant.util.dt import utcnow -from tests.common import get_test_home_assistant +from tests.common import async_fire_time_changed -class TestUVCSetup(unittest.TestCase): - """Test the UVC camera platform.""" +@pytest.fixture(name="mock_remote") +def mock_remote_fixture(camera_info): + """Mock the nvr.UVCRemote class.""" + with patch("homeassistant.components.uvc.camera.nvr.UVCRemote") as mock_remote: - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.addCleanup(self.hass.stop) + def setup(host, port, apikey, ssl=False): + """Set instance attributes.""" + mock_remote.return_value._host = host + mock_remote.return_value._port = port + mock_remote.return_value._apikey = apikey + mock_remote.return_value._ssl = ssl + return mock_remote.return_value - @mock.patch("uvcclient.nvr.UVCRemote") - @mock.patch.object(uvc, "UnifiVideoCamera") - def test_setup_full_config(self, mock_uvc, mock_remote): - """Test the setup with full configuration.""" - config = { - "platform": "uvc", - "nvr": "foo", - "password": "bar", - "port": 123, - "key": "secret", - } + mock_remote.side_effect = setup + mock_remote.return_value.get_camera.return_value = camera_info mock_cameras = [ {"uuid": "one", "name": "Front", "id": "id1"}, {"uuid": "two", "name": "Back", "id": "id2"}, - {"uuid": "three", "name": "Old AirCam", "id": "id3"}, ] - - def mock_get_camera(uuid): - """Create a mock camera.""" - if uuid == "id3": - return {"model": "airCam"} - return {"model": "UVC"} - mock_remote.return_value.index.return_value = mock_cameras - mock_remote.return_value.get_camera.side_effect = mock_get_camera mock_remote.return_value.server_version = (3, 2, 0) - - assert setup_component(self.hass, "camera", {"camera": config}) - self.hass.block_till_done() - - assert mock_remote.call_count == 1 - assert mock_remote.call_args == mock.call("foo", 123, "secret", ssl=False) - mock_uvc.assert_has_calls( - [ - mock.call(mock_remote.return_value, "id1", "Front", "bar"), - mock.call(mock_remote.return_value, "id2", "Back", "bar"), - ] - ) - - @mock.patch("uvcclient.nvr.UVCRemote") - @mock.patch.object(uvc, "UnifiVideoCamera") - def test_setup_partial_config(self, mock_uvc, mock_remote): - """Test the setup with partial configuration.""" - config = {"platform": "uvc", "nvr": "foo", "key": "secret"} - mock_cameras = [ - {"uuid": "one", "name": "Front", "id": "id1"}, - {"uuid": "two", "name": "Back", "id": "id2"}, - ] - mock_remote.return_value.index.return_value = mock_cameras - mock_remote.return_value.get_camera.return_value = {"model": "UVC"} - mock_remote.return_value.server_version = (3, 2, 0) - - assert setup_component(self.hass, "camera", {"camera": config}) - self.hass.block_till_done() - - assert mock_remote.call_count == 1 - assert mock_remote.call_args == mock.call("foo", 7080, "secret", ssl=False) - mock_uvc.assert_has_calls( - [ - mock.call(mock_remote.return_value, "id1", "Front", "ubnt"), - mock.call(mock_remote.return_value, "id2", "Back", "ubnt"), - ] - ) - - @mock.patch("uvcclient.nvr.UVCRemote") - @mock.patch.object(uvc, "UnifiVideoCamera") - def test_setup_partial_config_v31x(self, mock_uvc, mock_remote): - """Test the setup with a v3.1.x server.""" - config = {"platform": "uvc", "nvr": "foo", "key": "secret"} - mock_cameras = [ - {"uuid": "one", "name": "Front", "id": "id1"}, - {"uuid": "two", "name": "Back", "id": "id2"}, - ] - mock_remote.return_value.index.return_value = mock_cameras - mock_remote.return_value.get_camera.return_value = {"model": "UVC"} - mock_remote.return_value.server_version = (3, 1, 3) - - assert setup_component(self.hass, "camera", {"camera": config}) - self.hass.block_till_done() - - assert mock_remote.call_count == 1 - assert mock_remote.call_args == mock.call("foo", 7080, "secret", ssl=False) - mock_uvc.assert_has_calls( - [ - mock.call(mock_remote.return_value, "one", "Front", "ubnt"), - mock.call(mock_remote.return_value, "two", "Back", "ubnt"), - ] - ) - - @mock.patch.object(uvc, "UnifiVideoCamera") - def test_setup_incomplete_config(self, mock_uvc): - """Test the setup with incomplete configuration.""" - assert setup_component(self.hass, "camera", {"platform": "uvc", "nvr": "foo"}) - self.hass.block_till_done() - - assert not mock_uvc.called - assert setup_component( - self.hass, "camera", {"platform": "uvc", "key": "secret"} - ) - self.hass.block_till_done() - - assert not mock_uvc.called - assert setup_component( - self.hass, "camera", {"platform": "uvc", "port": "invalid"} - ) - self.hass.block_till_done() - - assert not mock_uvc.called - - @mock.patch.object(uvc, "UnifiVideoCamera") - @mock.patch("uvcclient.nvr.UVCRemote") - def setup_nvr_errors_during_indexing(self, error, mock_remote, mock_uvc): - """Set up test for NVR errors during indexing.""" - config = {"platform": "uvc", "nvr": "foo", "key": "secret"} - mock_remote.return_value.index.side_effect = error - assert setup_component(self.hass, "camera", {"camera": config}) - self.hass.block_till_done() - - assert not mock_uvc.called - - def test_setup_nvr_error_during_indexing_notauthorized(self): - """Test for error: nvr.NotAuthorized.""" - self.setup_nvr_errors_during_indexing(nvr.NotAuthorized) - - def test_setup_nvr_error_during_indexing_nvrerror(self): - """Test for error: nvr.NvrError.""" - self.setup_nvr_errors_during_indexing(nvr.NvrError) - pytest.raises(PlatformNotReady) - - def test_setup_nvr_error_during_indexing_connectionerror(self): - """Test for error: requests.exceptions.ConnectionError.""" - self.setup_nvr_errors_during_indexing(requests.exceptions.ConnectionError) - pytest.raises(PlatformNotReady) - - @mock.patch.object(uvc, "UnifiVideoCamera") - @mock.patch("uvcclient.nvr.UVCRemote.__init__") - def setup_nvr_errors_during_initialization(self, error, mock_remote, mock_uvc): - """Set up test for NVR errors during initialization.""" - config = {"platform": "uvc", "nvr": "foo", "key": "secret"} - mock_remote.return_value = None - mock_remote.side_effect = error - assert setup_component(self.hass, "camera", {"camera": config}) - self.hass.block_till_done() - - assert not mock_remote.index.called - assert not mock_uvc.called - - def test_setup_nvr_error_during_initialization_notauthorized(self): - """Test for error: nvr.NotAuthorized.""" - self.setup_nvr_errors_during_initialization(nvr.NotAuthorized) - - def test_setup_nvr_error_during_initialization_nvrerror(self): - """Test for error: nvr.NvrError.""" - self.setup_nvr_errors_during_initialization(nvr.NvrError) - pytest.raises(PlatformNotReady) - - def test_setup_nvr_error_during_initialization_connectionerror(self): - """Test for error: requests.exceptions.ConnectionError.""" - self.setup_nvr_errors_during_initialization(requests.exceptions.ConnectionError) - pytest.raises(PlatformNotReady) + yield mock_remote -class TestUVC(unittest.TestCase): - """Test class for UVC.""" - - def setup_method(self, method): - """Set up the mock camera.""" - self.nvr = mock.MagicMock() - self.uuid = "uuid" - self.name = "name" - self.password = "seekret" - self.uvc = uvc.UnifiVideoCamera(self.nvr, self.uuid, self.name, self.password) - self.nvr.get_camera.return_value = { - "model": "UVC Fake", - "uuid": "06e3ff29-8048-31c2-8574-0852d1bd0e03", - "recordingSettings": { - "fullTimeRecordEnabled": True, - "motionRecordEnabled": False, +@pytest.fixture(name="camera_info") +def camera_info_fixture(): + """Mock the camera info of a camera.""" + return { + "model": "UVC", + "recordingSettings": { + "fullTimeRecordEnabled": True, + "motionRecordEnabled": False, + }, + "host": "host-a", + "internalHost": "host-b", + "username": "admin", + "lastRecordingStartTime": 1610070992367, + "channels": [ + { + "id": "0", + "width": 1920, + "height": 1080, + "fps": 25, + "bitrate": 6000000, + "isRtspEnabled": True, + "rtspUris": [ + "rtsp://host-a:7447/uuid_rtspchannel_0", + "rtsp://foo:7447/uuid_rtspchannel_0", + ], }, - "host": "host-a", - "internalHost": "host-b", - "username": "admin", - "lastRecordingStartTime": 1610070992367, - "channels": [ - { - "id": "0", - "width": 1920, - "height": 1080, - "fps": 25, - "bitrate": 6000000, - "isRtspEnabled": True, - "rtspUris": [ - "rtsp://host-a:7447/uuid_rtspchannel_0", - "rtsp://foo:7447/uuid_rtspchannel_0", - ], - }, - { - "id": "1", - "width": 1024, - "height": 576, - "fps": 15, - "bitrate": 1200000, - "isRtspEnabled": False, - "rtspUris": [ - "rtsp://host-a:7447/uuid_rtspchannel_1", - "rtsp://foo:7447/uuid_rtspchannel_1", - ], - }, - ], - } - self.nvr.server_version = (3, 2, 0) - self.uvc.update() + { + "id": "1", + "width": 1024, + "height": 576, + "fps": 15, + "bitrate": 1200000, + "isRtspEnabled": False, + "rtspUris": [ + "rtsp://host-a:7447/uuid_rtspchannel_1", + "rtsp://foo:7447/uuid_rtspchannel_1", + ], + }, + ], + } - def test_properties(self): - """Test the properties.""" - assert self.name == self.uvc.name - assert self.uvc.is_recording - assert "Ubiquiti" == self.uvc.brand - assert "UVC Fake" == self.uvc.model - assert SUPPORT_STREAM == self.uvc.supported_features - assert "uuid" == self.uvc.unique_id - def test_motion_recording_mode_properties(self): - """Test the properties.""" - self.nvr.get_camera.return_value["recordingSettings"][ - "fullTimeRecordEnabled" - ] = False - self.nvr.get_camera.return_value["recordingSettings"][ - "motionRecordEnabled" - ] = True - assert not self.uvc.is_recording - assert ( - datetime(2021, 1, 8, 1, 56, 32, 367000) - == self.uvc.extra_state_attributes["last_recording_start_time"] - ) +@pytest.fixture(name="camera_v320") +def camera_v320_fixture(): + """Mock the v320 camera.""" + with patch( + "homeassistant.components.uvc.camera.uvc_camera.UVCCameraClientV320" + ) as camera: + camera.return_value.get_snapshot.return_value = "test_image" + yield camera - self.nvr.get_camera.return_value["recordingIndicator"] = "DISABLED" - assert not self.uvc.is_recording - self.nvr.get_camera.return_value["recordingIndicator"] = "MOTION_INPROGRESS" - assert self.uvc.is_recording +@pytest.fixture(name="camera_v313") +def camera_v313_fixture(): + """Mock the v320 camera.""" + with patch( + "homeassistant.components.uvc.camera.uvc_camera.UVCCameraClient" + ) as camera: + camera.return_value.get_snapshot.return_value = "test_image" + yield camera - self.nvr.get_camera.return_value["recordingIndicator"] = "MOTION_FINISHED" - assert self.uvc.is_recording - def test_stream(self): - """Test the RTSP stream URI.""" - stream_source = yield from self.uvc.stream_source() - assert stream_source == "rtsp://foo:7447/uuid_rtspchannel_0" +async def test_setup_full_config(hass, mock_remote, camera_info): + """Test the setup with full configuration.""" + config = { + "platform": "uvc", + "nvr": "foo", + "password": "bar", + "port": 123, + "key": "secret", + } - @mock.patch("uvcclient.store.get_info_store") - @mock.patch("uvcclient.camera.UVCCameraClientV320") - def test_login(self, mock_camera, mock_store): - """Test the login.""" - self.uvc._login() - assert mock_camera.call_count == 1 - assert mock_camera.call_args == mock.call("host-a", "admin", "seekret") - assert mock_camera.return_value.login.call_count == 1 - assert mock_camera.return_value.login.call_args == mock.call() + def mock_get_camera(uuid): + """Create a mock camera.""" + if uuid == "id3": + camera_info["model"] = "airCam" - @mock.patch("uvcclient.store.get_info_store") - @mock.patch("uvcclient.camera.UVCCameraClient") - def test_login_v31x(self, mock_camera, mock_store): - """Test login with v3.1.x server.""" - self.nvr.server_version = (3, 1, 3) - self.uvc._login() - assert mock_camera.call_count == 1 - assert mock_camera.call_args == mock.call("host-a", "admin", "seekret") - assert mock_camera.return_value.login.call_count == 1 - assert mock_camera.return_value.login.call_args == mock.call() + return camera_info - @mock.patch("uvcclient.store.get_info_store") - @mock.patch("uvcclient.camera.UVCCameraClientV320") - def test_login_tries_both_addrs_and_caches(self, mock_camera, mock_store): - """Test the login tries.""" - responses = [0] + mock_remote.return_value.index.return_value.append( + {"uuid": "three", "name": "Old AirCam", "id": "id3"} + ) + mock_remote.return_value.get_camera.side_effect = mock_get_camera - def mock_login(*a): - """Mock login.""" - try: - responses.pop(0) - raise OSError - except IndexError: - pass + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() - mock_store.return_value.get_camera_password.return_value = None - mock_camera.return_value.login.side_effect = mock_login - self.uvc._login() - assert 2 == mock_camera.call_count - assert "host-b" == self.uvc._connect_addr + assert mock_remote.call_count == 1 + assert mock_remote.call_args == call("foo", 123, "secret", ssl=False) - mock_camera.reset_mock() - self.uvc._login() - assert mock_camera.call_count == 1 - assert mock_camera.call_args == mock.call("host-b", "admin", "seekret") - assert mock_camera.return_value.login.call_count == 1 - assert mock_camera.return_value.login.call_args == mock.call() + camera_states = hass.states.async_all("camera") - @mock.patch("uvcclient.store.get_info_store") - @mock.patch("uvcclient.camera.UVCCameraClientV320") - def test_login_fails_both_properly(self, mock_camera, mock_store): - """Test if login fails properly.""" - mock_camera.return_value.login.side_effect = socket.error - assert self.uvc._login() is None - assert self.uvc._connect_addr is None + assert len(camera_states) == 2 - def test_camera_image_tries_login_bails_on_failure(self): - """Test retrieving failure.""" - with mock.patch.object(self.uvc, "_login") as mock_login: - mock_login.return_value = False - assert self.uvc.camera_image() is None - assert mock_login.call_count == 1 - assert mock_login.call_args == mock.call() + state = hass.states.get("camera.front") - def test_camera_image_logged_in(self): - """Test the login state.""" - self.uvc._camera = mock.MagicMock() - assert self.uvc._camera.get_snapshot.return_value == self.uvc.camera_image() + assert state + assert state.name == "Front" - def test_camera_image_error(self): - """Test the camera image error.""" - self.uvc._camera = mock.MagicMock() - self.uvc._camera.get_snapshot.side_effect = camera.CameraConnectError - assert self.uvc.camera_image() is None + state = hass.states.get("camera.back") - def test_camera_image_reauths(self): - """Test the re-authentication.""" - responses = [0] + assert state + assert state.name == "Back" - def mock_snapshot(): - """Mock snapshot.""" - try: - responses.pop() - raise camera.CameraAuthError() - except IndexError: - pass - return "image" + entity_registry = async_get_entity_registry(hass) + entity_entry = entity_registry.async_get("camera.front") - self.uvc._camera = mock.MagicMock() - self.uvc._camera.get_snapshot.side_effect = mock_snapshot - with mock.patch.object(self.uvc, "_login") as mock_login: - assert "image" == self.uvc.camera_image() - assert mock_login.call_count == 1 - assert mock_login.call_args == mock.call() - assert [] == responses + assert entity_entry.unique_id == "id1" - def test_camera_image_reauths_only_once(self): - """Test if the re-authentication only happens once.""" - self.uvc._camera = mock.MagicMock() - self.uvc._camera.get_snapshot.side_effect = camera.CameraAuthError - with mock.patch.object(self.uvc, "_login") as mock_login: - with pytest.raises(camera.CameraAuthError): - self.uvc.camera_image() - assert mock_login.call_count == 1 - assert mock_login.call_args == mock.call() + entity_entry = entity_registry.async_get("camera.back") + + assert entity_entry.unique_id == "id2" + + +async def test_setup_partial_config(hass, mock_remote): + """Test the setup with partial configuration.""" + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + assert mock_remote.call_count == 1 + assert mock_remote.call_args == call("foo", 7080, "secret", ssl=False) + + camera_states = hass.states.async_all("camera") + + assert len(camera_states) == 2 + + state = hass.states.get("camera.front") + + assert state + assert state.name == "Front" + + state = hass.states.get("camera.back") + + assert state + assert state.name == "Back" + + entity_registry = async_get_entity_registry(hass) + entity_entry = entity_registry.async_get("camera.front") + + assert entity_entry.unique_id == "id1" + + entity_entry = entity_registry.async_get("camera.back") + + assert entity_entry.unique_id == "id2" + + +async def test_setup_partial_config_v31x(hass, mock_remote): + """Test the setup with a v3.1.x server.""" + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + mock_remote.return_value.server_version = (3, 1, 3) + + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + assert mock_remote.call_count == 1 + assert mock_remote.call_args == call("foo", 7080, "secret", ssl=False) + + camera_states = hass.states.async_all("camera") + + assert len(camera_states) == 2 + + state = hass.states.get("camera.front") + + assert state + assert state.name == "Front" + + state = hass.states.get("camera.back") + + assert state + assert state.name == "Back" + + entity_registry = async_get_entity_registry(hass) + entity_entry = entity_registry.async_get("camera.front") + + assert entity_entry.unique_id == "one" + + entity_entry = entity_registry.async_get("camera.back") + + assert entity_entry.unique_id == "two" + + +@pytest.mark.parametrize( + "config", + [ + {"platform": "uvc", "nvr": "foo"}, + {"platform": "uvc", "key": "secret"}, + {"platform": "uvc", "nvr": "foo", "key": "secret", "port": "invalid"}, + ], +) +async def test_setup_incomplete_config(hass, mock_remote, config): + """Test the setup with incomplete or invalid configuration.""" + assert await async_setup_component(hass, "camera", config) + await hass.async_block_till_done() + + camera_states = hass.states.async_all("camera") + + assert not camera_states + + +@pytest.mark.parametrize( + "error, ready_states", + [ + (nvr.NotAuthorized, 0), + (nvr.NvrError, 2), + (requests.exceptions.ConnectionError, 2), + ], +) +async def test_setup_nvr_errors_during_indexing(hass, mock_remote, error, ready_states): + """Set up test for NVR errors during indexing.""" + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + now = utcnow() + mock_remote.return_value.index.side_effect = error + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + camera_states = hass.states.async_all("camera") + + assert not camera_states + + # resolve the error + mock_remote.return_value.index.side_effect = None + + async_fire_time_changed(hass, now + timedelta(seconds=31)) + await hass.async_block_till_done() + + camera_states = hass.states.async_all("camera") + + assert len(camera_states) == ready_states + + +@pytest.mark.parametrize( + "error, ready_states", + [ + (nvr.NotAuthorized, 0), + (nvr.NvrError, 2), + (requests.exceptions.ConnectionError, 2), + ], +) +async def test_setup_nvr_errors_during_initialization( + hass, mock_remote, error, ready_states +): + """Set up test for NVR errors during initialization.""" + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + now = utcnow() + mock_remote.side_effect = error + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + assert not mock_remote.index.called + + camera_states = hass.states.async_all("camera") + + assert not camera_states + + # resolve the error + mock_remote.side_effect = None + + async_fire_time_changed(hass, now + timedelta(seconds=31)) + await hass.async_block_till_done() + + camera_states = hass.states.async_all("camera") + + assert len(camera_states) == ready_states + + +async def test_properties(hass, mock_remote): + """Test the properties.""" + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + camera_states = hass.states.async_all("camera") + + assert len(camera_states) == 2 + + state = hass.states.get("camera.front") + + assert state + assert state.name == "Front" + assert state.state == STATE_RECORDING + assert state.attributes["brand"] == "Ubiquiti" + assert state.attributes["model_name"] == "UVC" + assert state.attributes["supported_features"] == SUPPORT_STREAM + + +async def test_motion_recording_mode_properties(hass, mock_remote): + """Test the properties.""" + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + now = utcnow() + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert state.state == STATE_RECORDING + + mock_remote.return_value.get_camera.return_value["recordingSettings"][ + "fullTimeRecordEnabled" + ] = False + mock_remote.return_value.get_camera.return_value["recordingSettings"][ + "motionRecordEnabled" + ] = True + + async_fire_time_changed(hass, now + timedelta(seconds=31)) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert state.state != STATE_RECORDING + assert state.attributes["last_recording_start_time"] == datetime( + 2021, 1, 8, 1, 56, 32, 367000, tzinfo=timezone.utc + ) + + mock_remote.return_value.get_camera.return_value["recordingIndicator"] = "DISABLED" + + async_fire_time_changed(hass, now + timedelta(seconds=61)) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert state.state != STATE_RECORDING + + mock_remote.return_value.get_camera.return_value[ + "recordingIndicator" + ] = "MOTION_INPROGRESS" + + async_fire_time_changed(hass, now + timedelta(seconds=91)) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert state.state == STATE_RECORDING + + mock_remote.return_value.get_camera.return_value[ + "recordingIndicator" + ] = "MOTION_FINISHED" + + async_fire_time_changed(hass, now + timedelta(seconds=121)) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert state.state == STATE_RECORDING + + +async def test_stream(hass, mock_remote): + """Test the RTSP stream URI.""" + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + stream_source = await async_get_stream_source(hass, "camera.front") + + assert stream_source == "rtsp://foo:7447/uuid_rtspchannel_0" + + +async def test_login(hass, mock_remote, camera_v320): + """Test the login.""" + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + image = await async_get_image(hass, "camera.front") + + assert camera_v320.call_count == 1 + assert camera_v320.call_args == call("host-a", "admin", "ubnt") + assert camera_v320.return_value.login.call_count == 1 + assert image.content_type == DEFAULT_CONTENT_TYPE + assert image.content == "test_image" + + +async def test_login_v31x(hass, mock_remote, camera_v313): + """Test login with v3.1.x server.""" + mock_remote.return_value.server_version = (3, 1, 3) + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + image = await async_get_image(hass, "camera.front") + + assert camera_v313.call_count == 1 + assert camera_v313.call_args == call("host-a", "admin", "ubnt") + assert camera_v313.return_value.login.call_count == 1 + assert image.content_type == DEFAULT_CONTENT_TYPE + assert image.content == "test_image" + + +@pytest.mark.parametrize( + "error", [OSError, camera.CameraConnectError, camera.CameraAuthError] +) +async def test_login_tries_both_addrs_and_caches(hass, mock_remote, camera_v320, error): + """Test the login tries.""" + responses = [0] + + def mock_login(*a): + """Mock login.""" + try: + responses.pop(0) + raise error + except IndexError: + pass + + snapshots = [0] + + def mock_snapshots(*a): + """Mock get snapshots.""" + try: + snapshots.pop(0) + raise camera.CameraAuthError() + except IndexError: + pass + return "test_image" + + camera_v320.return_value.login.side_effect = mock_login + + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + image = await async_get_image(hass, "camera.front") + + assert camera_v320.call_count == 2 + assert camera_v320.call_args == call("host-b", "admin", "ubnt") + assert image.content_type == DEFAULT_CONTENT_TYPE + assert image.content == "test_image" + + camera_v320.reset_mock() + camera_v320.return_value.get_snapshot.side_effect = mock_snapshots + + image = await async_get_image(hass, "camera.front") + + assert camera_v320.call_count == 1 + assert camera_v320.call_args == call("host-b", "admin", "ubnt") + assert camera_v320.return_value.login.call_count == 1 + assert image.content_type == DEFAULT_CONTENT_TYPE + assert image.content == "test_image" + + +async def test_login_fails_both_properly(hass, mock_remote, camera_v320): + """Test if login fails properly.""" + camera_v320.return_value.login.side_effect = OSError + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + with pytest.raises(HomeAssistantError): + await async_get_image(hass, "camera.front") + + assert camera_v320.return_value.get_snapshot.call_count == 0 + + +@pytest.mark.parametrize( + "source_error, raised_error, snapshot_calls", + [ + (camera.CameraConnectError, HomeAssistantError, 1), + (camera.CameraAuthError, camera.CameraAuthError, 2), + ], +) +async def test_camera_image_error( + hass, mock_remote, camera_v320, source_error, raised_error, snapshot_calls +): + """Test the camera image error.""" + camera_v320.return_value.get_snapshot.side_effect = source_error + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + with pytest.raises(raised_error): + await async_get_image(hass, "camera.front") + + assert camera_v320.return_value.get_snapshot.call_count == snapshot_calls + + +async def test_enable_disable_motion_detection(hass, mock_remote, camera_info): + """Test enable and disable motion detection.""" + + def set_recordmode(uuid, mode): + """Set record mode.""" + motion_record_enabled = mode == "motion" + camera_info["recordingSettings"]["motionRecordEnabled"] = motion_record_enabled + + mock_remote.return_value.set_recordmode.side_effect = set_recordmode + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert "motion_detection" not in state.attributes + + await hass.services.async_call( + "camera", SERVICE_ENABLE_MOTION, {"entity_id": "camera.front"}, True + ) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert state.attributes["motion_detection"] + + await hass.services.async_call( + "camera", SERVICE_DISABLE_MOTION, {"entity_id": "camera.front"}, True + ) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert "motion_detection" not in state.attributes + + mock_remote.return_value.set_recordmode.side_effect = nvr.NvrError + + await hass.services.async_call( + "camera", SERVICE_ENABLE_MOTION, {"entity_id": "camera.front"}, True + ) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert "motion_detection" not in state.attributes + + mock_remote.return_value.set_recordmode.side_effect = set_recordmode + + await hass.services.async_call( + "camera", SERVICE_ENABLE_MOTION, {"entity_id": "camera.front"}, True + ) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert state.attributes["motion_detection"] + + mock_remote.return_value.set_recordmode.side_effect = nvr.NvrError + + await hass.services.async_call( + "camera", SERVICE_DISABLE_MOTION, {"entity_id": "camera.front"}, True + ) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert state.attributes["motion_detection"] From 24e067782a9c28fe131fd4c896127178fb13378e Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 19 Mar 2021 16:51:44 +0100 Subject: [PATCH 495/831] Improve sensor coverage by verifying daylight sensor attributes (#48090) --- tests/components/deconz/test_sensor.py | 68 ++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/tests/components/deconz/test_sensor.py b/tests/components/deconz/test_sensor.py index e9a35337aef..a4d4e006366 100644 --- a/tests/components/deconz/test_sensor.py +++ b/tests/components/deconz/test_sensor.py @@ -1,10 +1,12 @@ """deCONZ sensor platform tests.""" +from datetime import timedelta from unittest.mock import patch from homeassistant.components.deconz.const import CONF_ALLOW_CLIP_SENSOR from homeassistant.components.deconz.sensor import ATTR_DAYLIGHT from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY from homeassistant.const import ( ATTR_DEVICE_CLASS, DEVICE_CLASS_BATTERY, @@ -13,9 +15,13 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) +from homeassistant.helpers import entity_registry as er +from homeassistant.util import dt from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from tests.common import async_fire_time_changed + async def test_no_sensors(hass, aioclient_mock): """Test that no sensors in deconz results in no sensor entities.""" @@ -56,27 +62,20 @@ async def test_sensors(hass, aioclient_mock, mock_deconz_websocket): "uniqueid": "00:00:00:00:00:00:00:03-00", }, "5": { - "name": "Daylight sensor", - "type": "Daylight", - "state": {"daylight": True, "status": 130}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:04-00", - }, - "6": { "name": "Power sensor", "type": "ZHAPower", "state": {"current": 2, "power": 6, "voltage": 3}, "config": {"reachable": True}, "uniqueid": "00:00:00:00:00:00:00:05-00", }, - "7": { + "6": { "name": "Consumption sensor", "type": "ZHAConsumption", "state": {"consumption": 2, "power": 6}, "config": {"reachable": True}, "uniqueid": "00:00:00:00:00:00:00:06-00", }, - "8": { + "7": { "id": "CLIP light sensor id", "name": "CLIP light level sensor", "type": "CLIPLightLevel", @@ -442,6 +441,57 @@ async def test_air_quality_sensor(hass, aioclient_mock): assert hass.states.get("sensor.air_quality").state == "poor" +async def test_daylight_sensor(hass, aioclient_mock): + """Test daylight sensor is disabled by default and when created has expected attributes.""" + data = { + "sensors": { + "0": { + "config": { + "configured": True, + "on": True, + "sunriseoffset": 30, + "sunsetoffset": -30, + }, + "etag": "55047cf652a7e594d0ee7e6fae01dd38", + "manufacturername": "Philips", + "modelid": "PHDL00", + "name": "Daylight sensor", + "state": { + "daylight": True, + "lastupdated": "2018-03-24T17:26:12", + "status": 170, + }, + "swversion": "1.0", + "type": "Daylight", + "uniqueid": "00:00:00:00:00:00:00:00-00", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) + + assert len(hass.states.async_all()) == 0 + assert not hass.states.get("sensor.daylight_sensor") + + # Enable in entity registry + + entity_registry = er.async_get(hass) + entity_registry.async_update_entity( + entity_id="sensor.daylight_sensor", disabled_by=None + ) + await hass.async_block_till_done() + + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(seconds=RELOAD_AFTER_UPDATE_DELAY + 1), + ) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 + assert hass.states.get("sensor.daylight_sensor") + assert hass.states.get("sensor.daylight_sensor").attributes[ATTR_DAYLIGHT] + + async def test_time_sensor(hass, aioclient_mock): """Test successful creation of time sensor entities.""" data = { From b03c97cdd09f329c6860f295d0f110f19d52a37a Mon Sep 17 00:00:00 2001 From: Greg Dowling Date: Fri, 19 Mar 2021 19:27:56 +0000 Subject: [PATCH 496/831] Make Vera should_poll static rather than dynamic (#47854) * Make should_poll static. * Address review comments. * Fix black error. --- CODEOWNERS | 2 +- homeassistant/components/vera/__init__.py | 4 +++- homeassistant/components/vera/manifest.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 1b0f2747dee..2afbc288b0f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -510,7 +510,7 @@ homeassistant/components/usgs_earthquakes_feed/* @exxamalte homeassistant/components/utility_meter/* @dgomes homeassistant/components/velbus/* @Cereal2nd @brefra homeassistant/components/velux/* @Julius2342 -homeassistant/components/vera/* @vangorra +homeassistant/components/vera/* @pavoni homeassistant/components/verisure/* @frenck homeassistant/components/versasense/* @flamm3blemuff1n homeassistant/components/version/* @fabaff @ludeeus diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index 929e4424d80..e544ddb925f 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -236,7 +236,9 @@ class VeraDevice(Generic[DeviceType], Entity): def update(self): """Force a refresh from the device if the device is unavailable.""" - if not self.available: + refresh_needed = self.vera_device.should_poll or not self.available + _LOGGER.debug("%s: update called (refresh=%s)", self._name, refresh_needed) + if refresh_needed: self.vera_device.refresh() @property diff --git a/homeassistant/components/vera/manifest.json b/homeassistant/components/vera/manifest.json index 1f180b39750..76d6bda5c7b 100644 --- a/homeassistant/components/vera/manifest.json +++ b/homeassistant/components/vera/manifest.json @@ -4,5 +4,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/vera", "requirements": ["pyvera==0.3.13"], - "codeowners": ["@vangorra"] + "codeowners": ["@pavoni"] } From 16a4f05e27f583fe5d05c66516487cdc2765b385 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 19 Mar 2021 20:55:08 +0100 Subject: [PATCH 497/831] Type check KNX integration fan (#48056) --- homeassistant/components/knx/fan.py | 33 +++++++++++++++++++---------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/knx/fan.py b/homeassistant/components/knx/fan.py index 2d9f48fe804..7f68c2a7d51 100644 --- a/homeassistant/components/knx/fan.py +++ b/homeassistant/components/knx/fan.py @@ -2,12 +2,17 @@ from __future__ import annotations import math -from typing import Any +from typing import Any, Callable, Iterable from xknx.devices import Fan as XknxFan -from xknx.devices.fan import FanSpeedMode from homeassistant.components.fan import SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) from homeassistant.util.percentage import ( int_states_in_range, percentage_to_ranged_value, @@ -20,7 +25,12 @@ from .knx_entity import KnxEntity DEFAULT_PERCENTAGE = 50 -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up fans for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: @@ -32,18 +42,19 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KNXFan(KnxEntity, FanEntity): """Representation of a KNX fan.""" - def __init__(self, device: XknxFan): + def __init__(self, device: XknxFan) -> None: """Initialize of KNX fan.""" + self._device: XknxFan super().__init__(device) - if self._device.mode == FanSpeedMode.STEP: + self._step_range: tuple[int, int] | None = None + if device.max_step: + # FanSpeedMode.STEP: self._step_range = (1, device.max_step) - else: - self._step_range = None async def async_set_percentage(self, percentage: int) -> None: """Set the speed of the fan, as a percentage.""" - if self._device.mode == FanSpeedMode.STEP: + if self._step_range: step = math.ceil(percentage_to_ranged_value(self._step_range, percentage)) await self._device.set_speed(step) else: @@ -65,7 +76,7 @@ class KNXFan(KnxEntity, FanEntity): if self._device.current_speed is None: return None - if self._device.mode == FanSpeedMode.STEP: + if self._step_range: return ranged_value_to_percentage( self._step_range, self._device.current_speed ) @@ -83,7 +94,7 @@ class KNXFan(KnxEntity, FanEntity): speed: str | None = None, percentage: int | None = None, preset_mode: str | None = None, - **kwargs, + **kwargs: Any, ) -> None: """Turn on the fan.""" if percentage is None: @@ -100,6 +111,6 @@ class KNXFan(KnxEntity, FanEntity): await self._device.set_oscillation(oscillating) @property - def oscillating(self): + def oscillating(self) -> bool | None: """Return whether or not the fan is currently oscillating.""" return self._device.current_oscillation From 70bebc51f2c8366efd7c38cc50d7ab32261630aa Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 19 Mar 2021 22:25:20 +0100 Subject: [PATCH 498/831] Type check KNX integration cover (#48046) --- homeassistant/components/knx/cover.py | 76 ++++++++++++++++----------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/knx/cover.py b/homeassistant/components/knx/cover.py index 4c08612926b..a0f35a30636 100644 --- a/homeassistant/components/knx/cover.py +++ b/homeassistant/components/knx/cover.py @@ -1,5 +1,10 @@ """Support for KNX/IP covers.""" -from xknx.devices import Cover as XknxCover +from __future__ import annotations + +from datetime import datetime +from typing import Any, Callable, Iterable + +from xknx.devices import Cover as XknxCover, Device as XknxDevice from homeassistant.components.cover import ( ATTR_POSITION, @@ -17,13 +22,24 @@ from homeassistant.components.cover import ( CoverEntity, ) from homeassistant.core import callback +from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_utc_time_change +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) from .const import DOMAIN from .knx_entity import KnxEntity -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up cover(s) for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: @@ -37,19 +53,20 @@ class KNXCover(KnxEntity, CoverEntity): def __init__(self, device: XknxCover): """Initialize the cover.""" + self._device: XknxCover super().__init__(device) - self._unsubscribe_auto_updater = None + self._unsubscribe_auto_updater: Callable[[], None] | None = None @callback - async def after_update_callback(self, device): + async def after_update_callback(self, device: XknxDevice) -> None: """Call after device was updated.""" self.async_write_ha_state() if self._device.is_traveling(): self.start_auto_updater() @property - def device_class(self): + def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" if self._device.device_class in DEVICE_CLASSES: return self._device.device_class @@ -58,7 +75,7 @@ class KNXCover(KnxEntity, CoverEntity): return None @property - def supported_features(self): + def supported_features(self) -> int: """Flag supported features.""" supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION if self._device.supports_stop: @@ -73,19 +90,17 @@ class KNXCover(KnxEntity, CoverEntity): return supported_features @property - def current_cover_position(self): + def current_cover_position(self) -> int | None: """Return the current position of the cover. None is unknown, 0 is closed, 100 is fully open. """ # In KNX 0 is open, 100 is closed. - try: - return 100 - self._device.current_position() - except TypeError: - return None + pos = self._device.current_position() + return 100 - pos if pos is not None else None @property - def is_closed(self): + def is_closed(self) -> bool | None: """Return if the cover is closed.""" # state shall be "unknown" when xknx travelcalculator is not initialized if self._device.current_position() is None: @@ -93,79 +108,76 @@ class KNXCover(KnxEntity, CoverEntity): return self._device.is_closed() @property - def is_opening(self): + def is_opening(self) -> bool: """Return if the cover is opening or not.""" return self._device.is_opening() @property - def is_closing(self): + def is_closing(self) -> bool: """Return if the cover is closing or not.""" return self._device.is_closing() - async def async_close_cover(self, **kwargs): + async def async_close_cover(self, **kwargs: Any) -> None: """Close the cover.""" await self._device.set_down() - async def async_open_cover(self, **kwargs): + async def async_open_cover(self, **kwargs: Any) -> None: """Open the cover.""" await self._device.set_up() - async def async_set_cover_position(self, **kwargs): + async def async_set_cover_position(self, **kwargs: Any) -> None: """Move the cover to a specific position.""" knx_position = 100 - kwargs[ATTR_POSITION] await self._device.set_position(knx_position) - async def async_stop_cover(self, **kwargs): + async def async_stop_cover(self, **kwargs: Any) -> None: """Stop the cover.""" await self._device.stop() self.stop_auto_updater() @property - def current_cover_tilt_position(self): + def current_cover_tilt_position(self) -> int | None: """Return current tilt position of cover.""" if not self._device.supports_angle: return None - try: - return 100 - self._device.current_angle() - except TypeError: - return None + ang = self._device.current_angle() + return 100 - ang if ang is not None else None - async def async_set_cover_tilt_position(self, **kwargs): + async def async_set_cover_tilt_position(self, **kwargs: Any) -> None: """Move the cover tilt to a specific position.""" knx_tilt_position = 100 - kwargs[ATTR_TILT_POSITION] await self._device.set_angle(knx_tilt_position) - async def async_open_cover_tilt(self, **kwargs): + async def async_open_cover_tilt(self, **kwargs: Any) -> None: """Open the cover tilt.""" await self._device.set_short_up() - async def async_close_cover_tilt(self, **kwargs): + async def async_close_cover_tilt(self, **kwargs: Any) -> None: """Close the cover tilt.""" await self._device.set_short_down() - async def async_stop_cover_tilt(self, **kwargs): + async def async_stop_cover_tilt(self, **kwargs: Any) -> None: """Stop the cover tilt.""" await self._device.stop() self.stop_auto_updater() - def start_auto_updater(self): + def start_auto_updater(self) -> None: """Start the autoupdater to update Home Assistant while cover is moving.""" if self._unsubscribe_auto_updater is None: self._unsubscribe_auto_updater = async_track_utc_time_change( self.hass, self.auto_updater_hook ) - def stop_auto_updater(self): + def stop_auto_updater(self) -> None: """Stop the autoupdater.""" if self._unsubscribe_auto_updater is not None: self._unsubscribe_auto_updater() self._unsubscribe_auto_updater = None @callback - def auto_updater_hook(self, now): + def auto_updater_hook(self, now: datetime) -> None: """Call for the autoupdater.""" self.async_write_ha_state() if self._device.position_reached(): + self.hass.async_create_task(self._device.auto_stop_if_necessary()) self.stop_auto_updater() - - self.hass.add_job(self._device.auto_stop_if_necessary()) From e47d8da3b4a1b6bcd7dff11a8d5c1b603bc67a41 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 19 Mar 2021 23:16:45 +0100 Subject: [PATCH 499/831] Remove defunct test from percentage util (#48125) --- tests/util/test_percentage.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/util/test_percentage.py b/tests/util/test_percentage.py index 4ad28f8567c..31420d3c076 100644 --- a/tests/util/test_percentage.py +++ b/tests/util/test_percentage.py @@ -28,15 +28,6 @@ SMALL_ORDERED_LIST = [SPEED_1, SPEED_2, SPEED_3, SPEED_4] LARGE_ORDERED_LIST = [SPEED_1, SPEED_2, SPEED_3, SPEED_4, SPEED_5, SPEED_6, SPEED_7] -async def test_ordered_list_percentage_round_trip(): - """Test we can round trip.""" - for ordered_list in (SMALL_ORDERED_LIST, LARGE_ORDERED_LIST): - for i in range(1, 100): - ordered_list_item_to_percentage( - ordered_list, percentage_to_ordered_list_item(ordered_list, i) - ) == i - - async def test_ordered_list_item_to_percentage(): """Test percentage of an item in an ordered list.""" From 098c53e8b5cddbea1be05c54de90db1cb9cd8539 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sat, 20 Mar 2021 00:04:10 +0000 Subject: [PATCH 500/831] [ci skip] Translation update --- .../components/airly/translations/nl.json | 2 +- .../components/almond/translations/nl.json | 2 +- .../components/august/translations/es.json | 11 +++++ .../components/august/translations/fr.json | 11 +++++ .../components/august/translations/it.json | 16 +++++++ .../components/brother/translations/nl.json | 2 +- .../components/canary/translations/nl.json | 1 + .../components/denonavr/translations/nl.json | 1 + .../device_tracker/translations/it.json | 2 +- .../components/eafm/translations/nl.json | 7 ++- .../garmin_connect/translations/nl.json | 4 +- .../components/gdacs/translations/nl.json | 2 +- .../components/gios/translations/nl.json | 2 +- .../components/goalzero/translations/nl.json | 3 +- .../components/hive/translations/es.json | 44 +++++++++++++++++++ .../homekit_controller/translations/nl.json | 6 +++ .../huawei_lte/translations/nl.json | 2 +- .../components/insteon/translations/nl.json | 9 ++++ .../components/kmtronic/translations/es.json | 9 ++++ .../components/konnected/translations/nl.json | 1 + .../meteo_france/translations/nl.json | 4 +- .../components/mikrotik/translations/nl.json | 4 +- .../mobile_app/translations/es.json | 3 +- .../components/neato/translations/nl.json | 4 +- .../components/netatmo/translations/nl.json | 2 +- .../nightscout/translations/nl.json | 5 ++- .../opentherm_gw/translations/ca.json | 4 +- .../opentherm_gw/translations/es.json | 4 +- .../opentherm_gw/translations/et.json | 4 +- .../opentherm_gw/translations/fr.json | 4 +- .../opentherm_gw/translations/it.json | 4 +- .../opentherm_gw/translations/ko.json | 4 +- .../opentherm_gw/translations/no.json | 4 +- .../opentherm_gw/translations/ru.json | 4 +- .../opentherm_gw/translations/zh-Hant.json | 4 +- .../ovo_energy/translations/nl.json | 1 + .../philips_js/translations/es.json | 4 ++ .../philips_js/translations/fr.json | 4 ++ .../philips_js/translations/it.json | 7 +++ .../philips_js/translations/ko.json | 2 +- .../components/plugwise/translations/nl.json | 3 +- .../components/rfxtrx/translations/nl.json | 4 ++ .../components/rpi_power/translations/nl.json | 1 + .../components/samsungtv/translations/nl.json | 6 +-- .../screenlogic/translations/es.json | 29 ++++++++++++ .../screenlogic/translations/fr.json | 33 ++++++++++++++ .../screenlogic/translations/it.json | 39 ++++++++++++++++ .../components/sensor/translations/es.json | 4 ++ .../components/sensor/translations/nl.json | 4 +- .../components/sentry/translations/nl.json | 12 +++++ .../components/smappee/translations/nl.json | 10 +++++ .../components/somfy/translations/nl.json | 2 +- .../components/spotify/translations/nl.json | 2 +- .../components/tag/translations/nl.json | 3 ++ .../components/tasmota/translations/nl.json | 3 ++ .../components/tuya/translations/nl.json | 18 +++++++- .../components/verisure/translations/es.json | 35 +++++++++++++++ .../components/verisure/translations/fr.json | 5 +++ .../components/vizio/translations/nl.json | 9 ++-- .../water_heater/translations/es.json | 10 +++++ .../components/withings/translations/nl.json | 6 +-- .../wolflink/translations/sensor.nl.json | 26 +++++++++++ .../zodiac/translations/sensor.nl.json | 1 + 63 files changed, 432 insertions(+), 46 deletions(-) create mode 100644 homeassistant/components/hive/translations/es.json create mode 100644 homeassistant/components/screenlogic/translations/es.json create mode 100644 homeassistant/components/screenlogic/translations/fr.json create mode 100644 homeassistant/components/screenlogic/translations/it.json create mode 100644 homeassistant/components/tag/translations/nl.json create mode 100644 homeassistant/components/verisure/translations/es.json diff --git a/homeassistant/components/airly/translations/nl.json b/homeassistant/components/airly/translations/nl.json index e89f769cd83..14cbaf1711e 100644 --- a/homeassistant/components/airly/translations/nl.json +++ b/homeassistant/components/airly/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Airly-integratie voor deze co\u00f6rdinaten is al geconfigureerd." + "already_configured": "Locatie is al geconfigureerd." }, "error": { "invalid_api_key": "Ongeldige API-sleutel", diff --git a/homeassistant/components/almond/translations/nl.json b/homeassistant/components/almond/translations/nl.json index 6d3aaf29e97..43d90100e93 100644 --- a/homeassistant/components/almond/translations/nl.json +++ b/homeassistant/components/almond/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "cannot_connect": "Kan geen verbinding maken met de Almond-server.", + "cannot_connect": "Kan geen verbinding maken", "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." diff --git a/homeassistant/components/august/translations/es.json b/homeassistant/components/august/translations/es.json index a8f7bc6af23..bb343e6da97 100644 --- a/homeassistant/components/august/translations/es.json +++ b/homeassistant/components/august/translations/es.json @@ -10,6 +10,10 @@ "unknown": "Error inesperado" }, "step": { + "reauth_validate": { + "description": "Introduzca la contrase\u00f1a de {username}.", + "title": "Reautorizar una cuenta de August" + }, "user": { "data": { "login_method": "M\u00e9todo de inicio de sesi\u00f3n", @@ -20,6 +24,13 @@ "description": "Si el M\u00e9todo de Inicio de Sesi\u00f3n es 'correo electr\u00f3nico', Usuario es la direcci\u00f3n de correo electr\u00f3nico. Si el M\u00e9todo de Inicio de Sesi\u00f3n es 'tel\u00e9fono', Usuario es el n\u00famero de tel\u00e9fono en formato '+NNNNNNNNN'.", "title": "Configurar una cuenta de August" }, + "user_validate": { + "data": { + "login_method": "M\u00e9todo de inicio de sesi\u00f3n" + }, + "description": "Si el m\u00e9todo de inicio de sesi\u00f3n es \"correo electr\u00f3nico\", el nombre de usuario es la direcci\u00f3n de correo electr\u00f3nico. Si el m\u00e9todo de inicio de sesi\u00f3n es \"tel\u00e9fono\", el nombre de usuario es el n\u00famero de tel\u00e9fono en el formato \"+NNNNNNN\".", + "title": "Configurar una cuenta de August" + }, "validation": { "data": { "code": "C\u00f3digo de verificaci\u00f3n" diff --git a/homeassistant/components/august/translations/fr.json b/homeassistant/components/august/translations/fr.json index 82568b681fd..530fda4dc9f 100644 --- a/homeassistant/components/august/translations/fr.json +++ b/homeassistant/components/august/translations/fr.json @@ -10,6 +10,10 @@ "unknown": "Erreur inattendue" }, "step": { + "reauth_validate": { + "description": "Saisissez le mot de passe de {username} .", + "title": "R\u00e9authentifier un compte August" + }, "user": { "data": { "login_method": "M\u00e9thode de connexion", @@ -20,6 +24,13 @@ "description": "Si la m\u00e9thode de connexion est \u00abe-mail\u00bb, le nom d'utilisateur est l'adresse e-mail. Si la m\u00e9thode de connexion est \u00abt\u00e9l\u00e9phone\u00bb, le nom d'utilisateur est le num\u00e9ro de t\u00e9l\u00e9phone au format \u00ab+ NNNNNNNNN\u00bb.", "title": "Configurer un compte August" }, + "user_validate": { + "data": { + "login_method": "M\u00e9thode de connexion" + }, + "description": "Si la m\u00e9thode de connexion est \u00abemail\u00bb, le nom d'utilisateur est l'adresse e-mail. Si la m\u00e9thode de connexion est \u00abt\u00e9l\u00e9phone\u00bb, le nom d'utilisateur est le num\u00e9ro de t\u00e9l\u00e9phone au format \u00ab+ NNNNNNNNN\u00bb.", + "title": "Cr\u00e9er un compte August" + }, "validation": { "data": { "code": "Code de v\u00e9rification" diff --git a/homeassistant/components/august/translations/it.json b/homeassistant/components/august/translations/it.json index adc9017a275..c20f95b90ad 100644 --- a/homeassistant/components/august/translations/it.json +++ b/homeassistant/components/august/translations/it.json @@ -10,6 +10,13 @@ "unknown": "Errore imprevisto" }, "step": { + "reauth_validate": { + "data": { + "password": "Password" + }, + "description": "Inserisci la password per {username}.", + "title": "Riautentica un account di August" + }, "user": { "data": { "login_method": "Metodo di accesso", @@ -20,6 +27,15 @@ "description": "Se il metodo di accesso \u00e8 \"e-mail\", il nome utente \u00e8 l'indirizzo e-mail. Se il metodo di accesso \u00e8 \"telefono\", il nome utente \u00e8 il numero di telefono nel formato \"+NNNNNNNNN\".", "title": "Configura un account di August" }, + "user_validate": { + "data": { + "login_method": "Metodo di accesso", + "password": "Password", + "username": "Nome utente" + }, + "description": "Se il metodo di accesso \u00e8 'email', il nome utente \u00e8 l'indirizzo email. Se il metodo di accesso \u00e8 'phone', il nome utente \u00e8 il numero di telefono nel formato '+NNNNNNNNNN'.", + "title": "Configura un account di August" + }, "validation": { "data": { "code": "Codice di verifica" diff --git a/homeassistant/components/brother/translations/nl.json b/homeassistant/components/brother/translations/nl.json index d754b2df9c1..531038d827b 100644 --- a/homeassistant/components/brother/translations/nl.json +++ b/homeassistant/components/brother/translations/nl.json @@ -13,7 +13,7 @@ "step": { "user": { "data": { - "host": "Printerhostnaam of IP-adres", + "host": "Host", "type": "Type printer" }, "description": "Zet Brother printerintegratie op. Als u problemen heeft met de configuratie ga dan naar: https://www.home-assistant.io/integrations/brother" diff --git a/homeassistant/components/canary/translations/nl.json b/homeassistant/components/canary/translations/nl.json index 9681bcd7c37..fbe642bbc96 100644 --- a/homeassistant/components/canary/translations/nl.json +++ b/homeassistant/components/canary/translations/nl.json @@ -22,6 +22,7 @@ "step": { "init": { "data": { + "ffmpeg_arguments": "Argumenten doorgegeven aan ffmpeg voor camera's", "timeout": "Time-out verzoek (seconden)" } } diff --git a/homeassistant/components/denonavr/translations/nl.json b/homeassistant/components/denonavr/translations/nl.json index a109df0f125..04f067c2f2a 100644 --- a/homeassistant/components/denonavr/translations/nl.json +++ b/homeassistant/components/denonavr/translations/nl.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang", + "cannot_connect": "Verbinding mislukt, probeer het opnieuw, de stroom- en ethernetkabels loskoppelen en opnieuw aansluiten kan helpen", "not_denonavr_manufacturer": "Geen Denon AVR Netwerk Receiver, ontdekte fabrikant komt niet overeen", "not_denonavr_missing": "Geen Denon AVR netwerkontvanger, zoekinformatie niet compleet" }, diff --git a/homeassistant/components/device_tracker/translations/it.json b/homeassistant/components/device_tracker/translations/it.json index 92ccce1c1c5..646f0732cd8 100644 --- a/homeassistant/components/device_tracker/translations/it.json +++ b/homeassistant/components/device_tracker/translations/it.json @@ -15,5 +15,5 @@ "not_home": "Fuori casa" } }, - "title": "Tracciatore dispositivo" + "title": "Localizzatore di dispositivi" } \ No newline at end of file diff --git a/homeassistant/components/eafm/translations/nl.json b/homeassistant/components/eafm/translations/nl.json index 0973f9ebd1a..ed67ed8f982 100644 --- a/homeassistant/components/eafm/translations/nl.json +++ b/homeassistant/components/eafm/translations/nl.json @@ -1,13 +1,16 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "no_stations": "Geen meetstations voor overstromingen gevonden." }, "step": { "user": { "data": { "station": "Station" - } + }, + "description": "Selecteer het station dat u wilt monitoren", + "title": "Volg een station voor overstromingen" } } } diff --git a/homeassistant/components/garmin_connect/translations/nl.json b/homeassistant/components/garmin_connect/translations/nl.json index e9b71c49c71..e751aaf1b5c 100644 --- a/homeassistant/components/garmin_connect/translations/nl.json +++ b/homeassistant/components/garmin_connect/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Dit account is al geconfigureerd." + "already_configured": "Account is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw.", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "too_many_requests": "Te veel aanvragen, probeer het later opnieuw.", "unknown": "Onverwachte fout" diff --git a/homeassistant/components/gdacs/translations/nl.json b/homeassistant/components/gdacs/translations/nl.json index f2a09892a66..6dd3e5aa196 100644 --- a/homeassistant/components/gdacs/translations/nl.json +++ b/homeassistant/components/gdacs/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Locatie is al geconfigureerd." + "already_configured": "Service is al geconfigureerd" }, "step": { "user": { diff --git a/homeassistant/components/gios/translations/nl.json b/homeassistant/components/gios/translations/nl.json index f6ac11f8724..7224c29f318 100644 --- a/homeassistant/components/gios/translations/nl.json +++ b/homeassistant/components/gios/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "GIO\u015a-integratie voor dit meetstation is al geconfigureerd." + "already_configured": "Locatie is al geconfigureerd." }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/goalzero/translations/nl.json b/homeassistant/components/goalzero/translations/nl.json index 4d9b5a397dd..c84ef7adb1f 100644 --- a/homeassistant/components/goalzero/translations/nl.json +++ b/homeassistant/components/goalzero/translations/nl.json @@ -14,7 +14,8 @@ "host": "Host", "name": "Naam" }, - "description": "Eerst moet u de Goal Zero-app downloaden: https://www.goalzero.com/product-features/yeti-app/ \n\n Volg de instructies om je Yeti te verbinden met je wifi-netwerk. Haal dan de host-ip van uw router. DHCP moet zijn ingesteld in uw routerinstellingen voor het apparaat om ervoor te zorgen dat het host-ip niet verandert. Raadpleeg de gebruikershandleiding van uw router." + "description": "Eerst moet u de Goal Zero-app downloaden: https://www.goalzero.com/product-features/yeti-app/ \n\n Volg de instructies om je Yeti te verbinden met je wifi-netwerk. Haal dan de host-ip van uw router. DHCP moet zijn ingesteld in uw routerinstellingen voor het apparaat om ervoor te zorgen dat het host-ip niet verandert. Raadpleeg de gebruikershandleiding van uw router.", + "title": "Goal Zero Yeti" } } } diff --git a/homeassistant/components/hive/translations/es.json b/homeassistant/components/hive/translations/es.json new file mode 100644 index 00000000000..eb5ef0fd6eb --- /dev/null +++ b/homeassistant/components/hive/translations/es.json @@ -0,0 +1,44 @@ +{ + "config": { + "abort": { + "unknown_entry": "No se puede encontrar una entrada existente." + }, + "error": { + "invalid_code": "No se ha podido iniciar la sesi\u00f3n en Hive. Tu c\u00f3digo de autenticaci\u00f3n de dos factores era incorrecto.", + "invalid_password": "No se ha podido iniciar la sesi\u00f3n en Hive. Contrase\u00f1a incorrecta, por favor, int\u00e9ntelo de nuevo.", + "invalid_username": "No se ha podido iniciar la sesi\u00f3n en Hive. No se reconoce su direcci\u00f3n de correo electr\u00f3nico.", + "no_internet_available": "Se requiere una conexi\u00f3n a Internet para conectarse a Hive." + }, + "step": { + "2fa": { + "data": { + "2fa": "C\u00f3digo de dos factores" + }, + "description": "Introduzca su c\u00f3digo de autentificaci\u00f3n Hive. \n \n Introduzca el c\u00f3digo 0000 para solicitar otro c\u00f3digo.", + "title": "Autenticaci\u00f3n de dos factores de Hive." + }, + "reauth": { + "description": "Vuelva a introducir sus datos de acceso a Hive.", + "title": "Inicio de sesi\u00f3n en Hive" + }, + "user": { + "data": { + "scan_interval": "Intervalo de exploraci\u00f3n (segundos)" + }, + "description": "Ingrese su configuraci\u00f3n e informaci\u00f3n de inicio de sesi\u00f3n de Hive.", + "title": "Inicio de sesi\u00f3n en Hive" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Intervalo de exploraci\u00f3n (segundos)" + }, + "description": "Actualice el intervalo de escaneo para buscar datos m\u00e1s a menudo.", + "title": "Opciones para Hive" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/nl.json b/homeassistant/components/homekit_controller/translations/nl.json index 5c23159e21f..57692426ce0 100644 --- a/homeassistant/components/homekit_controller/translations/nl.json +++ b/homeassistant/components/homekit_controller/translations/nl.json @@ -19,7 +19,12 @@ }, "flow_title": "{name} via HomeKit-accessoireprotocol", "step": { + "busy_error": { + "description": "Onderbreek het koppelen op alle controllers, of probeer het apparaat opnieuw op te starten, en ga dan verder om het koppelen te hervatten.", + "title": "Het apparaat is al aan het koppelen met een andere controller" + }, "max_tries_error": { + "description": "Het apparaat heeft meer dan 100 mislukte verificatiepogingen ontvangen. Probeer het apparaat opnieuw op te starten en ga dan verder om het koppelen te hervatten.", "title": "Maximum aantal authenticatiepogingen overschreden" }, "pair": { @@ -30,6 +35,7 @@ "title": "Koppel met HomeKit accessoire" }, "protocol_error": { + "description": "Het apparaat staat mogelijk niet in de koppelingsmodus en vereist mogelijk een fysieke of virtuele druk op de knop. Zorg ervoor dat het apparaat in de koppelingsmodus staat of probeer het apparaat opnieuw op te starten en ga dan verder om het koppelen te hervatten.", "title": "Fout bij het communiceren met de accessoire" }, "user": { diff --git a/homeassistant/components/huawei_lte/translations/nl.json b/homeassistant/components/huawei_lte/translations/nl.json index c1584b56330..799a9ce50af 100644 --- a/homeassistant/components/huawei_lte/translations/nl.json +++ b/homeassistant/components/huawei_lte/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "Dit apparaat wordt al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "not_huawei_lte": "Geen Huawei LTE-apparaat" }, "error": { diff --git a/homeassistant/components/insteon/translations/nl.json b/homeassistant/components/insteon/translations/nl.json index 20b29287862..0c9191e8077 100644 --- a/homeassistant/components/insteon/translations/nl.json +++ b/homeassistant/components/insteon/translations/nl.json @@ -87,12 +87,21 @@ "remove_override": "Verwijder een apparaatoverschrijving.", "remove_x10": "Verwijder een X10-apparaat." }, + "description": "Selecteer een optie om te configureren.", "title": "Insteon" }, "remove_override": { + "data": { + "address": "Selecteer een apparaatadres om te verwijderen" + }, + "description": "Verwijder een apparaatoverschrijving", "title": "Insteon" }, "remove_x10": { + "data": { + "address": "Selecteer een apparaatadres om te verwijderen" + }, + "description": "Een X10 apparaat verwijderen", "title": "Insteon" } } diff --git a/homeassistant/components/kmtronic/translations/es.json b/homeassistant/components/kmtronic/translations/es.json index 4fb0fc0e0a5..f7c20f7805b 100644 --- a/homeassistant/components/kmtronic/translations/es.json +++ b/homeassistant/components/kmtronic/translations/es.json @@ -15,5 +15,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "L\u00f3gica de conmutaci\u00f3n inversa (utilizar NC)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/konnected/translations/nl.json b/homeassistant/components/konnected/translations/nl.json index 75b11338016..635c064807b 100644 --- a/homeassistant/components/konnected/translations/nl.json +++ b/homeassistant/components/konnected/translations/nl.json @@ -87,6 +87,7 @@ "data": { "api_host": "API host-URL overschrijven (optioneel)", "blink": "Led knipperen bij het verzenden van statuswijziging", + "discovery": "Reageer op detectieverzoeken op uw netwerk", "override_api_host": "Overschrijf standaard Home Assistant API hostpaneel-URL" }, "description": "Selecteer het gewenste gedrag voor uw paneel", diff --git a/homeassistant/components/meteo_france/translations/nl.json b/homeassistant/components/meteo_france/translations/nl.json index c8f7258e100..f69db3ed47e 100644 --- a/homeassistant/components/meteo_france/translations/nl.json +++ b/homeassistant/components/meteo_france/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Stad al geconfigureerd", - "unknown": "Onbekende fout: probeer het later nog eens" + "already_configured": "Locatie is al geconfigureerd.", + "unknown": "Onverwachte fout" }, "error": { "empty": "Geen resultaat bij het zoeken naar een stad: controleer de invoer: stad" diff --git a/homeassistant/components/mikrotik/translations/nl.json b/homeassistant/components/mikrotik/translations/nl.json index 53e05b5cf5f..78e143ddadb 100644 --- a/homeassistant/components/mikrotik/translations/nl.json +++ b/homeassistant/components/mikrotik/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Mikrotik is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding niet geslaagd", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "name_exists": "Naam bestaat al" }, diff --git a/homeassistant/components/mobile_app/translations/es.json b/homeassistant/components/mobile_app/translations/es.json index 43ba004ac72..8ac5c909e17 100644 --- a/homeassistant/components/mobile_app/translations/es.json +++ b/homeassistant/components/mobile_app/translations/es.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Enviar una notificaci\u00f3n" } - } + }, + "title": "Aplicaci\u00f3n m\u00f3vil" } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/nl.json b/homeassistant/components/neato/translations/nl.json index 563e6500c16..d03bc1d216a 100644 --- a/homeassistant/components/neato/translations/nl.json +++ b/homeassistant/components/neato/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", "invalid_auth": "Ongeldige authenticatie", "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", @@ -9,7 +9,7 @@ "reauth_successful": "Herauthenticatie was succesvol" }, "create_entry": { - "default": "Zie [Neato-documentatie] ({docs_url})." + "default": "Succesvol geauthenticeerd" }, "error": { "invalid_auth": "Ongeldige authenticatie", diff --git a/homeassistant/components/netatmo/translations/nl.json b/homeassistant/components/netatmo/translations/nl.json index a0f9682fd74..dc811b63534 100644 --- a/homeassistant/components/netatmo/translations/nl.json +++ b/homeassistant/components/netatmo/translations/nl.json @@ -7,7 +7,7 @@ "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "create_entry": { - "default": "Succesvol geauthenticeerd met Netatmo." + "default": "Succesvol geauthenticeerd" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/nightscout/translations/nl.json b/homeassistant/components/nightscout/translations/nl.json index 0146996dce5..a9b81e9403e 100644 --- a/homeassistant/components/nightscout/translations/nl.json +++ b/homeassistant/components/nightscout/translations/nl.json @@ -8,12 +8,15 @@ "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, + "flow_title": "Nightscout", "step": { "user": { "data": { "api_key": "API-sleutel", "url": "URL" - } + }, + "description": "- URL: het adres van uw nightscout instantie. Bijv.: https://myhomeassistant.duckdns.org:5423\n- API-sleutel (optioneel): Alleen gebruiken als uw instantie beveiligd is (auth_default_roles != readable).", + "title": "Voer uw Nightscout-serverinformatie in." } } } diff --git a/homeassistant/components/opentherm_gw/translations/ca.json b/homeassistant/components/opentherm_gw/translations/ca.json index 1da9bbb584e..d63dfa91ce8 100644 --- a/homeassistant/components/opentherm_gw/translations/ca.json +++ b/homeassistant/components/opentherm_gw/translations/ca.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "Temperatura de la planta", - "precision": "Precisi\u00f3" + "precision": "Precisi\u00f3", + "read_precision": "Llegeix precisi\u00f3", + "set_precision": "Defineix precisi\u00f3" }, "description": "Opcions del la passarel\u00b7la d'enlla\u00e7 d'OpenTherm" } diff --git a/homeassistant/components/opentherm_gw/translations/es.json b/homeassistant/components/opentherm_gw/translations/es.json index 44b6c6dfabc..7a85b685e89 100644 --- a/homeassistant/components/opentherm_gw/translations/es.json +++ b/homeassistant/components/opentherm_gw/translations/es.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "Temperatura del suelo", - "precision": "Precisi\u00f3n" + "precision": "Precisi\u00f3n", + "read_precision": "Leer precisi\u00f3n", + "set_precision": "Establecer precisi\u00f3n" }, "description": "Opciones para OpenTherm Gateway" } diff --git a/homeassistant/components/opentherm_gw/translations/et.json b/homeassistant/components/opentherm_gw/translations/et.json index 4ab500e5531..7bcb754afaf 100644 --- a/homeassistant/components/opentherm_gw/translations/et.json +++ b/homeassistant/components/opentherm_gw/translations/et.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "P\u00f5randa temperatuur", - "precision": "T\u00e4psus" + "precision": "T\u00e4psus", + "read_precision": "Lugemi t\u00e4psus", + "set_precision": "M\u00e4\u00e4ra lugemi t\u00e4psus" }, "description": "OpenTherm Gateway suvandid" } diff --git a/homeassistant/components/opentherm_gw/translations/fr.json b/homeassistant/components/opentherm_gw/translations/fr.json index f060503ea23..804a7c8fe9c 100644 --- a/homeassistant/components/opentherm_gw/translations/fr.json +++ b/homeassistant/components/opentherm_gw/translations/fr.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "Temp\u00e9rature du sol", - "precision": "Pr\u00e9cision" + "precision": "Pr\u00e9cision", + "read_precision": "Lire la pr\u00e9cision", + "set_precision": "D\u00e9finir la pr\u00e9cision" }, "description": "Options pour la passerelle OpenTherm" } diff --git a/homeassistant/components/opentherm_gw/translations/it.json b/homeassistant/components/opentherm_gw/translations/it.json index df1c36cd8d5..bfe55c54bbd 100644 --- a/homeassistant/components/opentherm_gw/translations/it.json +++ b/homeassistant/components/opentherm_gw/translations/it.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "Temperatura del pavimento", - "precision": "Precisione" + "precision": "Precisione", + "read_precision": "Leggi la precisione", + "set_precision": "Imposta la precisione" }, "description": "Opzioni per OpenTherm Gateway" } diff --git a/homeassistant/components/opentherm_gw/translations/ko.json b/homeassistant/components/opentherm_gw/translations/ko.json index 6f3ac939ad1..00f2902a4f3 100644 --- a/homeassistant/components/opentherm_gw/translations/ko.json +++ b/homeassistant/components/opentherm_gw/translations/ko.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "\uc628\ub3c4 \uc18c\uc218\uc810 \ubc84\ub9bc", - "precision": "\uc815\ubc00\ub3c4" + "precision": "\uc815\ubc00\ub3c4", + "read_precision": "\uc77d\uae30 \uc815\ubc00\ub3c4", + "set_precision": "\uc815\ubc00\ub3c4 \uc124\uc815\ud558\uae30" }, "description": "OpenTherm Gateway \uc635\uc158" } diff --git a/homeassistant/components/opentherm_gw/translations/no.json b/homeassistant/components/opentherm_gw/translations/no.json index 76118924e0a..07b7c77c5cc 100644 --- a/homeassistant/components/opentherm_gw/translations/no.json +++ b/homeassistant/components/opentherm_gw/translations/no.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "Etasje Temperatur", - "precision": "Presisjon" + "precision": "Presisjon", + "read_precision": "Les presisjon", + "set_precision": "Angi presisjon" }, "description": "Alternativer for OpenTherm Gateway" } diff --git a/homeassistant/components/opentherm_gw/translations/ru.json b/homeassistant/components/opentherm_gw/translations/ru.json index e63bfb58d95..df1166a7def 100644 --- a/homeassistant/components/opentherm_gw/translations/ru.json +++ b/homeassistant/components/opentherm_gw/translations/ru.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "\u0422\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 \u043f\u043e\u043b\u0430", - "precision": "\u0422\u043e\u0447\u043d\u043e\u0441\u0442\u044c" + "precision": "\u0422\u043e\u0447\u043d\u043e\u0441\u0442\u044c", + "read_precision": "\u0422\u043e\u0447\u043d\u043e\u0441\u0442\u044c \u0447\u0442\u0435\u043d\u0438\u044f", + "set_precision": "\u0422\u043e\u0447\u043d\u043e\u0441\u0442\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438" }, "description": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043b\u044f \u0448\u043b\u044e\u0437\u0430 Opentherm" } diff --git a/homeassistant/components/opentherm_gw/translations/zh-Hant.json b/homeassistant/components/opentherm_gw/translations/zh-Hant.json index ea138287c78..500c47ac46f 100644 --- a/homeassistant/components/opentherm_gw/translations/zh-Hant.json +++ b/homeassistant/components/opentherm_gw/translations/zh-Hant.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "\u6a13\u5c64\u6eab\u5ea6", - "precision": "\u6e96\u78ba\u5ea6" + "precision": "\u6e96\u78ba\u5ea6", + "read_precision": "\u8b80\u53d6\u7cbe\u6e96\u5ea6", + "set_precision": "\u8a2d\u5b9a\u7cbe\u6e96\u5ea6" }, "description": "OpenTherm \u9598\u9053\u5668\u9078\u9805" } diff --git a/homeassistant/components/ovo_energy/translations/nl.json b/homeassistant/components/ovo_energy/translations/nl.json index d3909b0e44e..d598e17d93b 100644 --- a/homeassistant/components/ovo_energy/translations/nl.json +++ b/homeassistant/components/ovo_energy/translations/nl.json @@ -19,6 +19,7 @@ "password": "Wachtwoord", "username": "Gebruikersnaam" }, + "description": "Stel een OVO Energy instance in om toegang te krijgen tot je energieverbruik.", "title": "Voeg OVO Energie Account toe" } } diff --git a/homeassistant/components/philips_js/translations/es.json b/homeassistant/components/philips_js/translations/es.json index 66d3b85c3bd..c8d34e9ea9d 100644 --- a/homeassistant/components/philips_js/translations/es.json +++ b/homeassistant/components/philips_js/translations/es.json @@ -5,6 +5,10 @@ "pairing_failure": "No se ha podido emparejar: {error_id}" }, "step": { + "pair": { + "description": "Introduzca el PIN que se muestra en el televisor", + "title": "Par" + }, "user": { "data": { "api_version": "Versi\u00f3n del API", diff --git a/homeassistant/components/philips_js/translations/fr.json b/homeassistant/components/philips_js/translations/fr.json index 25c28edcf1d..326b81dc6a3 100644 --- a/homeassistant/components/philips_js/translations/fr.json +++ b/homeassistant/components/philips_js/translations/fr.json @@ -10,6 +10,10 @@ "unknown": "Erreur inattendue" }, "step": { + "pair": { + "description": "Entrez le code PIN affich\u00e9 sur votre t\u00e9l\u00e9viseur", + "title": "Paire" + }, "user": { "data": { "api_version": "Version de l'API", diff --git a/homeassistant/components/philips_js/translations/it.json b/homeassistant/components/philips_js/translations/it.json index 03b5340a6bc..6ff668dbea8 100644 --- a/homeassistant/components/philips_js/translations/it.json +++ b/homeassistant/components/philips_js/translations/it.json @@ -10,6 +10,13 @@ "unknown": "Errore imprevisto" }, "step": { + "pair": { + "data": { + "pin": "Codice PIN" + }, + "description": "Inserire il PIN visualizzato sul televisore", + "title": "Associa" + }, "user": { "data": { "api_version": "Versione API", diff --git a/homeassistant/components/philips_js/translations/ko.json b/homeassistant/components/philips_js/translations/ko.json index a76fd70418c..04ba5eff601 100644 --- a/homeassistant/components/philips_js/translations/ko.json +++ b/homeassistant/components/philips_js/translations/ko.json @@ -15,7 +15,7 @@ "pin": "PIN \ucf54\ub4dc" }, "description": "TV\uc5d0 \ud45c\uc2dc\ub41c PIN \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694", - "title": "\ud398\uc5b4\ub9c1" + "title": "\ud398\uc5b4\ub9c1\ud558\uae30" }, "user": { "data": { diff --git a/homeassistant/components/plugwise/translations/nl.json b/homeassistant/components/plugwise/translations/nl.json index efd679a1ce3..408ddf967f4 100644 --- a/homeassistant/components/plugwise/translations/nl.json +++ b/homeassistant/components/plugwise/translations/nl.json @@ -34,7 +34,8 @@ "init": { "data": { "scan_interval": "Scaninterval (seconden)" - } + }, + "description": "Plugwise opties aanpassen" } } } diff --git a/homeassistant/components/rfxtrx/translations/nl.json b/homeassistant/components/rfxtrx/translations/nl.json index d727fc9c1b8..53441859d72 100644 --- a/homeassistant/components/rfxtrx/translations/nl.json +++ b/homeassistant/components/rfxtrx/translations/nl.json @@ -39,6 +39,8 @@ "error": { "already_configured_device": "Apparaat is al geconfigureerd", "invalid_event_code": "Ongeldige gebeurteniscode", + "invalid_input_2262_off": "Ongeldige invoer voor commando uit", + "invalid_input_2262_on": "Ongeldige invoer voor commando aan", "invalid_input_off_delay": "Ongeldige invoer voor uitschakelvertraging", "unknown": "Onverwachte fout" }, @@ -55,6 +57,8 @@ }, "set_device_options": { "data": { + "command_off": "Waarde gegevensbits voor commando uit", + "command_on": "Waarde gegevensbits voor commando aan", "data_bit": "Aantal databits", "fire_event": "Schakel apparaatgebeurtenis in", "off_delay": "Uitschakelvertraging", diff --git a/homeassistant/components/rpi_power/translations/nl.json b/homeassistant/components/rpi_power/translations/nl.json index 8c15279dca8..5529aa39f20 100644 --- a/homeassistant/components/rpi_power/translations/nl.json +++ b/homeassistant/components/rpi_power/translations/nl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "no_devices_found": "Kan de systeemklasse die nodig is voor dit onderdeel niet vinden, controleer of uw kernel recent is en of de hardware ondersteund wordt", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { diff --git a/homeassistant/components/samsungtv/translations/nl.json b/homeassistant/components/samsungtv/translations/nl.json index d1e2a9abaa2..2a6cca466ea 100644 --- a/homeassistant/components/samsungtv/translations/nl.json +++ b/homeassistant/components/samsungtv/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Deze Samsung TV is al geconfigureerd.", - "already_in_progress": "Samsung TV configuratie is al in uitvoering.", + "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "auth_missing": "Home Assistant is niet geautoriseerd om verbinding te maken met deze Samsung TV.", "cannot_connect": "Kan geen verbinding maken", "not_supported": "Deze Samsung TV wordt momenteel niet ondersteund." @@ -15,7 +15,7 @@ }, "user": { "data": { - "host": "Hostnaam of IP-adres", + "host": "Host", "name": "Naam" }, "description": "Voer uw Samsung TV informatie in. Als u nooit eerder Home Assistant hebt verbonden dan zou u een popup op uw TV moeten zien waarin u om toestemming wordt vraagt." diff --git a/homeassistant/components/screenlogic/translations/es.json b/homeassistant/components/screenlogic/translations/es.json new file mode 100644 index 00000000000..8e9513d4f75 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/es.json @@ -0,0 +1,29 @@ +{ + "config": { + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "description": "Introduzca la informaci\u00f3n de su ScreenLogic Gateway.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "Puerta de enlace" + }, + "description": "Se han descubierto las siguientes puertas de enlace ScreenLogic. Seleccione una para configurarla o elija configurar manualmente una puerta de enlace ScreenLogic.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Segundos entre exploraciones" + }, + "description": "Especificar la configuraci\u00f3n de {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/fr.json b/homeassistant/components/screenlogic/translations/fr.json new file mode 100644 index 00000000000..d651f4f1c98 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/fr.json @@ -0,0 +1,33 @@ +{ + "config": { + "flow_title": "ScreenLogic {nom}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "Adresse IP", + "port": "Port" + }, + "description": "Entrez vos informations de passerelle ScreenLogic.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "passerelle" + }, + "description": "Les passerelles ScreenLogic suivantes ont \u00e9t\u00e9 d\u00e9couvertes. S\u2019il vous pla\u00eet s\u00e9lectionner un \u00e0 configurer, ou choisissez de configurer manuellement une passerelle ScreenLogic.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Secondes entre les scans" + }, + "description": "Sp\u00e9cifiez les param\u00e8tres pour {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/it.json b/homeassistant/components/screenlogic/translations/it.json new file mode 100644 index 00000000000..8fc3c346c0f --- /dev/null +++ b/homeassistant/components/screenlogic/translations/it.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "Indirizzo IP", + "port": "Porta" + }, + "description": "Inserisci le informazioni del tuo gateway ScreenLogic.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "Gateway" + }, + "description": "Sono stati individuati i gateway ScreenLogic seguenti. Selezionarne uno da configurare oppure scegliere di configurare manualmente un gateway ScreenLogic.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Secondi tra le scansioni" + }, + "description": "Specifica le impostazioni per {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sensor/translations/es.json b/homeassistant/components/sensor/translations/es.json index b2db3151abf..b810a3f0eb1 100644 --- a/homeassistant/components/sensor/translations/es.json +++ b/homeassistant/components/sensor/translations/es.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "Nivel de bater\u00eda actual de {entity_name}", + "is_carbon_dioxide": "Nivel actual de concentraci\u00f3n de di\u00f3xido de carbono {entity_name}", + "is_carbon_monoxide": "Nivel actual de concentraci\u00f3n de mon\u00f3xido de carbono {entity_name}", "is_current": "Corriente actual de {entity_name}", "is_energy": "Energ\u00eda actual de {entity_name}", "is_humidity": "Humedad actual de {entity_name}", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "Cambios de nivel de bater\u00eda de {entity_name}", + "carbon_dioxide": "{entity_name} cambios en la concentraci\u00f3n de di\u00f3xido de carbono", + "carbon_monoxide": "{entity_name} cambios en la concentraci\u00f3n de mon\u00f3xido de carbono", "current": "Cambio de corriente en {entity_name}", "energy": "Cambio de energ\u00eda en {entity_name}", "humidity": "Cambios de humedad de {entity_name}", diff --git a/homeassistant/components/sensor/translations/nl.json b/homeassistant/components/sensor/translations/nl.json index 94eb0374adf..411ebf3cefd 100644 --- a/homeassistant/components/sensor/translations/nl.json +++ b/homeassistant/components/sensor/translations/nl.json @@ -26,11 +26,13 @@ "humidity": "{entity_name} vochtigheidsgraad gewijzigd", "illuminance": "{entity_name} verlichtingssterkte gewijzigd", "power": "{entity_name} vermogen gewijzigd", + "power_factor": "{entity_name} power factor verandert", "pressure": "{entity_name} druk gewijzigd", "signal_strength": "{entity_name} signaalsterkte gewijzigd", "temperature": "{entity_name} temperatuur gewijzigd", "timestamp": "{entity_name} tijdstip gewijzigd", - "value": "{entity_name} waarde gewijzigd" + "value": "{entity_name} waarde gewijzigd", + "voltage": "{entity_name} voltage verandert" } }, "state": { diff --git a/homeassistant/components/sentry/translations/nl.json b/homeassistant/components/sentry/translations/nl.json index 64b7f1b73f7..682517c15db 100644 --- a/homeassistant/components/sentry/translations/nl.json +++ b/homeassistant/components/sentry/translations/nl.json @@ -16,5 +16,17 @@ "title": "Sentry" } } + }, + "options": { + "step": { + "init": { + "data": { + "environment": "Optionele naam van de omgeving.", + "event_custom_components": "Gebeurtenissen verzenden vanuit aangepaste onderdelen", + "event_handled": "Stuur afgehandelde gebeurtenissen", + "event_third_party_packages": "Gebeurtenissen verzenden vanuit pakketten van derden" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/smappee/translations/nl.json b/homeassistant/components/smappee/translations/nl.json index 373ff6ecd2e..66ede5e8c14 100644 --- a/homeassistant/components/smappee/translations/nl.json +++ b/homeassistant/components/smappee/translations/nl.json @@ -11,6 +11,12 @@ }, "flow_title": "Smappee: {name}", "step": { + "environment": { + "data": { + "environment": "Omgeving" + }, + "description": "Stel uw Smappee in om te integreren met Home Assistant." + }, "local": { "data": { "host": "Host" @@ -19,6 +25,10 @@ }, "pick_implementation": { "title": "Kies een authenticatie methode" + }, + "zeroconf_confirm": { + "description": "Wilt u het Smappee apparaat met serienummer `{serialnumber}` toevoegen aan Home Assistant?", + "title": "Ontdekt Smappee apparaat" } } } diff --git a/homeassistant/components/somfy/translations/nl.json b/homeassistant/components/somfy/translations/nl.json index 3fe61e5eaab..94305c7ae6f 100644 --- a/homeassistant/components/somfy/translations/nl.json +++ b/homeassistant/components/somfy/translations/nl.json @@ -11,7 +11,7 @@ }, "step": { "pick_implementation": { - "title": "Kies de authenticatie methode" + "title": "Kies een authenticatie methode" } } } diff --git a/homeassistant/components/spotify/translations/nl.json b/homeassistant/components/spotify/translations/nl.json index afccb637145..0d1e63fde5d 100644 --- a/homeassistant/components/spotify/translations/nl.json +++ b/homeassistant/components/spotify/translations/nl.json @@ -11,7 +11,7 @@ }, "step": { "pick_implementation": { - "title": "Kies Authenticatiemethode" + "title": "Kies een authenticatie methode" }, "reauth_confirm": { "description": "De Spotify integratie moet opnieuw worden geverifieerd met Spotify voor account: {account}", diff --git a/homeassistant/components/tag/translations/nl.json b/homeassistant/components/tag/translations/nl.json new file mode 100644 index 00000000000..fdac700612d --- /dev/null +++ b/homeassistant/components/tag/translations/nl.json @@ -0,0 +1,3 @@ +{ + "title": "Tag" +} \ No newline at end of file diff --git a/homeassistant/components/tasmota/translations/nl.json b/homeassistant/components/tasmota/translations/nl.json index 561439362c7..c099d376920 100644 --- a/homeassistant/components/tasmota/translations/nl.json +++ b/homeassistant/components/tasmota/translations/nl.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "Is al geconfigureerd. Er is maar een configuratie mogelijk" }, + "error": { + "invalid_discovery_topic": "Ongeldig onderwerpvoorvoegsel voor ontdekken" + }, "step": { "config": { "data": { diff --git a/homeassistant/components/tuya/translations/nl.json b/homeassistant/components/tuya/translations/nl.json index c9f681c5ecb..8f811714411 100644 --- a/homeassistant/components/tuya/translations/nl.json +++ b/homeassistant/components/tuya/translations/nl.json @@ -35,11 +35,27 @@ "device": { "data": { "brightness_range_mode": "Helderheidsbereik gebruikt door apparaat", - "temp_step_override": "Doeltemperatuur stap" + "curr_temp_divider": "Huidige temperatuurwaarde deler (0 = standaardwaarde)", + "max_kelvin": "Max ondersteunde kleurtemperatuur in kelvin", + "max_temp": "Maximale doeltemperatuur (gebruik min en max = 0 voor standaardwaarde)", + "min_kelvin": "Minimaal ondersteunde kleurtemperatuur in kelvin", + "min_temp": "Min. gewenste temperatuur (gebruik min en max = 0 voor standaard)", + "support_color": "Forceer kleurenondersteuning", + "temp_divider": "Temperatuurwaarde deler (0 = standaardwaarde)", + "temp_step_override": "Doeltemperatuur stap", + "tuya_max_coltemp": "Max. Kleurtemperatuur gerapporteerd door apparaat", + "unit_of_measurement": "Temperatuureenheid gebruikt door apparaat" }, + "description": "Configureer opties om weergegeven informatie aan te passen voor {device_type} apparaat `{device_name}`", "title": "Configureer Tuya Apparaat" }, "init": { + "data": { + "discovery_interval": "Polling-interval van ontdekt apparaat in seconden", + "list_devices": "Selecteer de te configureren apparaten of laat leeg om de configuratie op te slaan", + "query_device": "Selecteer apparaat dat query-methode zal gebruiken voor snellere statusupdate", + "query_interval": "Peilinginterval van het apparaat in seconden" + }, "title": "Configureer Tuya opties" } } diff --git a/homeassistant/components/verisure/translations/es.json b/homeassistant/components/verisure/translations/es.json new file mode 100644 index 00000000000..38605e4f86b --- /dev/null +++ b/homeassistant/components/verisure/translations/es.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "installation": { + "data": { + "giid": "Instalaci\u00f3n" + }, + "description": "Home Assistant encontr\u00f3 varias instalaciones de Verisure en su cuenta de Mis p\u00e1ginas. Por favor, seleccione la instalaci\u00f3n para agregar a Home Assistant." + }, + "reauth_confirm": { + "data": { + "description": "Vuelva a autenticarse con su cuenta Verisure My Pages." + } + }, + "user": { + "data": { + "description": "Inicia sesi\u00f3n con tu cuenta Verisure My Pages." + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "El c\u00f3digo PIN predeterminado no coincide con el n\u00famero necesario de d\u00edgitos" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "N\u00famero de d\u00edgitos del c\u00f3digo PIN de las cerraduras", + "lock_default_code": "C\u00f3digo PIN por defecto para las cerraduras, utilizado si no se indica ninguno" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/fr.json b/homeassistant/components/verisure/translations/fr.json index df2640e10ba..1991120ab8f 100644 --- a/homeassistant/components/verisure/translations/fr.json +++ b/homeassistant/components/verisure/translations/fr.json @@ -14,6 +14,11 @@ }, "description": "Home Assistant a trouv\u00e9 plusieurs installations Verisure dans votre compte My Pages. Veuillez s\u00e9lectionner l'installation \u00e0 ajouter \u00e0 Home Assistant." }, + "reauth_confirm": { + "data": { + "description": "R\u00e9-authentifiez-vous avec votre compte Verisure My Pages." + } + }, "user": { "data": { "description": "Connectez-vous avec votre compte Verisure My Pages.", diff --git a/homeassistant/components/vizio/translations/nl.json b/homeassistant/components/vizio/translations/nl.json index 4472a9cb9c0..48a7a7d353d 100644 --- a/homeassistant/components/vizio/translations/nl.json +++ b/homeassistant/components/vizio/translations/nl.json @@ -7,7 +7,8 @@ }, "error": { "cannot_connect": "Verbinding mislukt", - "complete_pairing_failed": "Kan het koppelen niet voltooien. Zorg ervoor dat de door u opgegeven pincode correct is en dat de tv nog steeds van stroom wordt voorzien en is verbonden met het netwerk voordat u opnieuw verzendt." + "complete_pairing_failed": "Kan het koppelen niet voltooien. Zorg ervoor dat de door u opgegeven pincode correct is en dat de tv nog steeds van stroom wordt voorzien en is verbonden met het netwerk voordat u opnieuw verzendt.", + "existing_config_entry_found": "Een bestaande VIZIO SmartCast-apparaat config entry met hetzelfde serienummer is reeds geconfigureerd. U moet de bestaande invoer verwijderen om deze te kunnen configureren." }, "step": { "pair_tv": { @@ -29,11 +30,11 @@ "data": { "access_token": "Toegangstoken", "device_class": "Apparaattype", - "host": ":", + "host": "Host", "name": "Naam" }, "description": "Een toegangstoken is alleen nodig voor tv's. Als u een TV configureert en nog geen toegangstoken heeft, laat dit dan leeg en doorloop het koppelingsproces.", - "title": "Vizio SmartCast Client instellen" + "title": "VIZIO SmartCast-apparaat" } } }, @@ -46,7 +47,7 @@ "volume_step": "Volume Stapgrootte" }, "description": "Als je een Smart TV hebt, kun je optioneel je bronnenlijst filteren door te kiezen welke apps je in je bronnenlijst wilt opnemen of uitsluiten.", - "title": "Update Vizo SmartCast Opties" + "title": "Update VIZIO SmartCast-apparaat opties" } } } diff --git a/homeassistant/components/water_heater/translations/es.json b/homeassistant/components/water_heater/translations/es.json index 46be0201ba5..f11f9592b81 100644 --- a/homeassistant/components/water_heater/translations/es.json +++ b/homeassistant/components/water_heater/translations/es.json @@ -4,5 +4,15 @@ "turn_off": "Apagar {entity_name}", "turn_on": "Encender {entity_name}" } + }, + "state": { + "_": { + "eco": "Eco", + "electric": "El\u00e9ctrico", + "gas": "Gas", + "heat_pump": "Bomba de calor", + "high_demand": "Alta demanda", + "performance": "Rendimiento" + } } } \ No newline at end of file diff --git a/homeassistant/components/withings/translations/nl.json b/homeassistant/components/withings/translations/nl.json index 9f7810a8378..b20323347e4 100644 --- a/homeassistant/components/withings/translations/nl.json +++ b/homeassistant/components/withings/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Configuratie bijgewerkt voor profiel.", "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", - "missing_configuration": "De Withings integratie is niet geconfigureerd. Gelieve de documentatie te volgen.", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" }, "create_entry": { @@ -15,13 +15,13 @@ "flow_title": "Withings: {profile}", "step": { "pick_implementation": { - "title": "Kies Authenticatiemethode" + "title": "Kies een authenticatie methode" }, "profile": { "data": { "profile": "Profiel" }, - "description": "Welk profiel hebt u op de website van Withings selecteren? Het is belangrijk dat de profielen overeenkomen, anders worden gegevens verkeerd gelabeld.", + "description": "Geef een unieke profielnaam op voor deze gegevens. Meestal is dit de naam van het profiel dat u in de vorige stap hebt geselecteerd.", "title": "Gebruikersprofiel." }, "reauth": { diff --git a/homeassistant/components/wolflink/translations/sensor.nl.json b/homeassistant/components/wolflink/translations/sensor.nl.json index 4056e4762ec..f050fe4f629 100644 --- a/homeassistant/components/wolflink/translations/sensor.nl.json +++ b/homeassistant/components/wolflink/translations/sensor.nl.json @@ -3,6 +3,8 @@ "wolflink__state": { "1_x_warmwasser": "1 x DHW", "abgasklappe": "Rookgasklep", + "absenkbetrieb": "Setback modus", + "absenkstop": "Setback stop", "aktiviert": "Geactiveerd", "antilegionellenfunktion": "Anti-legionella functie", "at_abschaltung": "OT afsluiten", @@ -23,10 +25,12 @@ "estrichtrocknung": "Dekvloer drogen", "externe_deaktivierung": "Externe uitschakeling", "fernschalter_ein": "Op afstand bedienen ingeschakeld", + "frost_heizkreis": "Verwarmengscircuit ontdooien", "frost_warmwasser": "DHW vorst", "frostschutz": "Vorstbescherming", "gasdruck": "Gasdruk", "glt_betrieb": "BMS-modus", + "gradienten_uberwachung": "Gradient monitoring", "heizbetrieb": "Verwarmingsmodus", "heizgerat_mit_speicher": "Boiler met cilinder", "heizung": "Verwarmen", @@ -35,26 +39,48 @@ "kalibration_heizbetrieb": "Kalibratie verwarmingsmodus", "kalibration_kombibetrieb": "Kalibratie van de combimodus", "kalibration_warmwasserbetrieb": "DHW-kalibratie", + "kaskadenbetrieb": "Cascade operation", "kombibetrieb": "Combi-modus", "kombigerat": "Combiketel", + "kombigerat_mit_solareinbindung": "Combiketel met zonne-integratie", + "mindest_kombizeit": "Minimale combitijd", + "nachlauf_heizkreispumpe": "De pomp van het verwarmingscircuit gaat aan", + "nachspulen": "Post-flush", "nur_heizgerat": "Alleen ketel", + "parallelbetrieb": "Parallelle modus", "partymodus": "Feestmodus", + "perm_cooling": "PermCooling", "permanent": "Permanent", "permanentbetrieb": "Permanente modus", "reduzierter_betrieb": "Beperkte modus", + "rt_abschaltung": "RT afsluiten", + "rt_frostschutz": "RT vorstbescherming", + "ruhekontakt": "Rest contact", "schornsteinfeger": "Emissietest", "smart_grid": "SmartGrid", "smart_home": "SmartHome", + "softstart": "Zachte start", + "solarbetrieb": "Zonnemodus", "sparbetrieb": "Spaarstand", + "sparen": "Spaarstand", + "spreizung_hoch": "dT te breed", + "spreizung_kf": "Spreid KF", + "stabilisierung": "Stablisatie", "standby": "Stand-by", "start": "Start", "storung": "Fout", + "taktsperre": "Anti-cyclus", + "telefonfernschalter": "Telefoon schakelaar op afstand", "test": "Test", "tpw": "TPW", "urlaubsmodus": "Vakantiemodus", "ventilprufung": "Kleptest", + "vorspulen": "Invoer spoelen", "warmwasser": "DHW", + "warmwasser_schnellstart": "DHW Snel starten", "warmwasserbetrieb": "DHW-modus", + "warmwassernachlauf": "DHW aanloop", + "warmwasservorrang": "DHW prioriteit", "zunden": "Ontsteking" } } diff --git a/homeassistant/components/zodiac/translations/sensor.nl.json b/homeassistant/components/zodiac/translations/sensor.nl.json index c07b20de21b..6dba645ed83 100644 --- a/homeassistant/components/zodiac/translations/sensor.nl.json +++ b/homeassistant/components/zodiac/translations/sensor.nl.json @@ -3,6 +3,7 @@ "zodiac__sign": { "aquarius": "Waterman", "aries": "Ram", + "cancer": "Kreeft", "capricorn": "Steenbok", "gemini": "Tweelingen", "leo": "Leo", From 26bceae99d6ebfed3d76715113c6891a71d31fe6 Mon Sep 17 00:00:00 2001 From: Thiago Oliveira Date: Fri, 19 Mar 2021 17:20:09 -0700 Subject: [PATCH 501/831] Set zwave_js climate precision to tenths for F (#48133) --- homeassistant/components/zwave_js/climate.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 78d76423378..fbe54e42d78 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -41,7 +41,12 @@ from homeassistant.components.climate.const import ( SUPPORT_TARGET_TEMPERATURE_RANGE, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.const import ( + ATTR_TEMPERATURE, + PRECISION_TENTHS, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -228,6 +233,11 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): return TEMP_FAHRENHEIT return TEMP_CELSIUS + @property + def precision(self) -> float: + """Return the precision of 0.1.""" + return PRECISION_TENTHS + @property def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode.""" From fb849b81b5307a25112b89f8994f83fb8fc1f4f1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 20 Mar 2021 01:27:04 +0100 Subject: [PATCH 502/831] Rewrite of not a == b occurances (#48132) --- homeassistant/components/glances/sensor.py | 2 +- homeassistant/components/isy994/helpers.py | 4 ++-- homeassistant/components/isy994/services.py | 8 ++++---- .../components/openhome/media_player.py | 2 +- homeassistant/components/proxy/camera.py | 2 +- .../components/pulseaudio_loopback/switch.py | 2 +- homeassistant/components/roon/media_player.py | 2 +- homeassistant/components/vlc/media_player.py | 2 +- homeassistant/components/zha/device_tracker.py | 2 +- homeassistant/components/zwave_js/sensor.py | 4 ++-- homeassistant/helpers/template.py | 2 +- homeassistant/util/timeout.py | 2 +- script/hassfest/coverage.py | 4 ++-- tests/components/mqtt/test_cover.py | 18 +++++++++--------- tests/components/pilight/test_init.py | 2 +- tests/components/recorder/test_purge.py | 2 +- .../components/universal/test_media_player.py | 2 +- 17 files changed, 31 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/glances/sensor.py b/homeassistant/components/glances/sensor.py index 4c534a90ae1..006333b321a 100644 --- a/homeassistant/components/glances/sensor.py +++ b/homeassistant/components/glances/sensor.py @@ -15,7 +15,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): dev = [] for sensor_type, sensor_details in SENSOR_TYPES.items(): - if not sensor_details[0] in client.api.data: + if sensor_details[0] not in client.api.data: continue if sensor_details[0] in client.api.data: if sensor_details[0] == "fs": diff --git a/homeassistant/components/isy994/helpers.py b/homeassistant/components/isy994/helpers.py index 780e24843bd..81a74430d3a 100644 --- a/homeassistant/components/isy994/helpers.py +++ b/homeassistant/components/isy994/helpers.py @@ -326,7 +326,7 @@ def _categorize_programs(hass_isy_data: dict, programs: Programs) -> None: actions = None status = entity_folder.get_by_name(KEY_STATUS) - if not status or not status.protocol == PROTO_PROGRAM: + if not status or status.protocol != PROTO_PROGRAM: _LOGGER.warning( "Program %s entity '%s' not loaded, invalid/missing status program", platform, @@ -336,7 +336,7 @@ def _categorize_programs(hass_isy_data: dict, programs: Programs) -> None: if platform != BINARY_SENSOR: actions = entity_folder.get_by_name(KEY_ACTIONS) - if not actions or not actions.protocol == PROTO_PROGRAM: + if not actions or actions.protocol != PROTO_PROGRAM: _LOGGER.warning( "Program %s entity '%s' not loaded, invalid/missing actions program", platform, diff --git a/homeassistant/components/isy994/services.py b/homeassistant/components/isy994/services.py index b9c4bcdeef2..39966a9d994 100644 --- a/homeassistant/components/isy994/services.py +++ b/homeassistant/components/isy994/services.py @@ -174,7 +174,7 @@ def async_setup_services(hass: HomeAssistantType): for config_entry_id in hass.data[DOMAIN]: isy = hass.data[DOMAIN][config_entry_id][ISY994_ISY] - if isy_name and not isy_name == isy.configuration["name"]: + if isy_name and isy_name != isy.configuration["name"]: continue # If an address is provided, make sure we query the correct ISY. # Otherwise, query the whole system on all ISY's connected. @@ -199,7 +199,7 @@ def async_setup_services(hass: HomeAssistantType): for config_entry_id in hass.data[DOMAIN]: isy = hass.data[DOMAIN][config_entry_id][ISY994_ISY] - if isy_name and not isy_name == isy.configuration["name"]: + if isy_name and isy_name != isy.configuration["name"]: continue if not hasattr(isy, "networking") or isy.networking is None: continue @@ -224,7 +224,7 @@ def async_setup_services(hass: HomeAssistantType): for config_entry_id in hass.data[DOMAIN]: isy = hass.data[DOMAIN][config_entry_id][ISY994_ISY] - if isy_name and not isy_name == isy.configuration["name"]: + if isy_name and isy_name != isy.configuration["name"]: continue program = None if address: @@ -247,7 +247,7 @@ def async_setup_services(hass: HomeAssistantType): for config_entry_id in hass.data[DOMAIN]: isy = hass.data[DOMAIN][config_entry_id][ISY994_ISY] - if isy_name and not isy_name == isy.configuration["name"]: + if isy_name and isy_name != isy.configuration["name"]: continue variable = None if name: diff --git a/homeassistant/components/openhome/media_player.py b/homeassistant/components/openhome/media_player.py index f9dbd4272ba..270eb22ebda 100644 --- a/homeassistant/components/openhome/media_player.py +++ b/homeassistant/components/openhome/media_player.py @@ -139,7 +139,7 @@ class OpenhomeDevice(MediaPlayerEntity): def play_media(self, media_type, media_id, **kwargs): """Send the play_media command to the media player.""" - if not media_type == MEDIA_TYPE_MUSIC: + if media_type != MEDIA_TYPE_MUSIC: _LOGGER.error( "Invalid media type %s. Only %s is supported", media_type, diff --git a/homeassistant/components/proxy/camera.py b/homeassistant/components/proxy/camera.py index c3f7151431a..8fda507ace2 100644 --- a/homeassistant/components/proxy/camera.py +++ b/homeassistant/components/proxy/camera.py @@ -77,7 +77,7 @@ def _precheck_image(image, opts): if imgfmt not in ("PNG", "JPEG"): _LOGGER.warning("Image is of unsupported type: %s", imgfmt) raise ValueError() - if not img.mode == "RGB": + if img.mode != "RGB": img = img.convert("RGB") return img diff --git a/homeassistant/components/pulseaudio_loopback/switch.py b/homeassistant/components/pulseaudio_loopback/switch.py index 9c27ab4e027..260fbe65c1b 100644 --- a/homeassistant/components/pulseaudio_loopback/switch.py +++ b/homeassistant/components/pulseaudio_loopback/switch.py @@ -73,7 +73,7 @@ class PALoopbackSwitch(SwitchEntity): self._pa_svr.connect() for module in self._pa_svr.module_list(): - if not module.name == "module-loopback": + if module.name != "module-loopback": continue if f"sink={self._sink_name}" not in module.argument: diff --git a/homeassistant/components/roon/media_player.py b/homeassistant/components/roon/media_player.py index b2ae62ec250..773028da2d3 100644 --- a/homeassistant/components/roon/media_player.py +++ b/homeassistant/components/roon/media_player.py @@ -465,7 +465,7 @@ class RoonDevice(MediaPlayerEntity): return for source in self.player_data["source_controls"]: - if source["supports_standby"] and not source["status"] == "indeterminate": + if source["supports_standby"] and source["status"] != "indeterminate": self._server.roonapi.standby(self.output_id, source["control_key"]) return diff --git a/homeassistant/components/vlc/media_player.py b/homeassistant/components/vlc/media_player.py index fe387dd4adc..db5c26f4a0c 100644 --- a/homeassistant/components/vlc/media_player.py +++ b/homeassistant/components/vlc/media_player.py @@ -159,7 +159,7 @@ class VlcDevice(MediaPlayerEntity): def play_media(self, media_type, media_id, **kwargs): """Play media from a URL or file.""" - if not media_type == MEDIA_TYPE_MUSIC: + if media_type != MEDIA_TYPE_MUSIC: _LOGGER.error( "Invalid media type %s. Only %s is supported", media_type, diff --git a/homeassistant/components/zha/device_tracker.py b/homeassistant/components/zha/device_tracker.py index 53191789eba..ffb37e33b0f 100644 --- a/homeassistant/components/zha/device_tracker.py +++ b/homeassistant/components/zha/device_tracker.py @@ -83,7 +83,7 @@ class ZHADeviceScannerEntity(ScannerEntity, ZhaEntity): @callback def async_battery_percentage_remaining_updated(self, attr_id, attr_name, value): """Handle tracking.""" - if not attr_name == "battery_percentage_remaining": + if attr_name != "battery_percentage_remaining": return self.debug("battery_percentage_remaining updated: %s", value) self._connected = True diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index ada6f6b5d06..5f116f0790c 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -197,8 +197,8 @@ class ZWaveListSensor(ZwaveSensorBase): if self.info.primary_value.value is None: return None if ( - not str(self.info.primary_value.value) - in self.info.primary_value.metadata.states + str(self.info.primary_value.value) + not in self.info.primary_value.metadata.states ): return str(self.info.primary_value.value) return str( diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 671009b8846..cfb7223752e 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1439,7 +1439,7 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): def is_safe_attribute(self, obj, attr, value): """Test if attribute is safe.""" if isinstance(obj, (AllStates, DomainStates, TemplateState)): - return not attr[0] == "_" + return attr[0] != "_" if isinstance(obj, Namespace): return True diff --git a/homeassistant/util/timeout.py b/homeassistant/util/timeout.py index 64208d775ea..5821f89659e 100644 --- a/homeassistant/util/timeout.py +++ b/homeassistant/util/timeout.py @@ -243,7 +243,7 @@ class _GlobalTaskContext: """Wait until zones are done.""" await self._wait_zone.wait() await asyncio.sleep(self._cool_down) # Allow context switch - if not self.state == _State.TIMEOUT: + if self.state != _State.TIMEOUT: return self._cancel_task() diff --git a/script/hassfest/coverage.py b/script/hassfest/coverage.py index 1a0c684cfab..06e38902060 100644 --- a/script/hassfest/coverage.py +++ b/script/hassfest/coverage.py @@ -106,8 +106,8 @@ def validate(integrations: dict[str, Integration], config: Config): if ( not line.startswith("homeassistant/components/") - or not len(path.parts) == 4 - or not path.parts[-1] == "*" + or len(path.parts) != 4 + or path.parts[-1] != "*" ): continue diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index 2a29bf9e15e..508869c7b51 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -525,9 +525,9 @@ async def test_current_cover_position(hass, mqtt_mock): await hass.async_block_till_done() state_attributes_dict = hass.states.get("cover.test").attributes - assert not (ATTR_CURRENT_POSITION in state_attributes_dict) - assert not (ATTR_CURRENT_TILT_POSITION in state_attributes_dict) - assert not (4 & hass.states.get("cover.test").attributes["supported_features"] == 4) + assert ATTR_CURRENT_POSITION not in state_attributes_dict + assert ATTR_CURRENT_TILT_POSITION not in state_attributes_dict + assert 4 & hass.states.get("cover.test").attributes["supported_features"] != 4 async_fire_mqtt_message(hass, "get-position-topic", "0") current_cover_position = hass.states.get("cover.test").attributes[ @@ -576,9 +576,9 @@ async def test_current_cover_position_inverted(hass, mqtt_mock): await hass.async_block_till_done() state_attributes_dict = hass.states.get("cover.test").attributes - assert not (ATTR_CURRENT_POSITION in state_attributes_dict) - assert not (ATTR_CURRENT_TILT_POSITION in state_attributes_dict) - assert not (4 & hass.states.get("cover.test").attributes["supported_features"] == 4) + assert ATTR_CURRENT_POSITION not in state_attributes_dict + assert ATTR_CURRENT_TILT_POSITION not in state_attributes_dict + assert 4 & hass.states.get("cover.test").attributes["supported_features"] != 4 async_fire_mqtt_message(hass, "get-position-topic", "100") current_percentage_cover_position = hass.states.get("cover.test").attributes[ @@ -659,14 +659,14 @@ async def test_position_update(hass, mqtt_mock): await hass.async_block_till_done() state_attributes_dict = hass.states.get("cover.test").attributes - assert not (ATTR_CURRENT_POSITION in state_attributes_dict) - assert not (ATTR_CURRENT_TILT_POSITION in state_attributes_dict) + assert ATTR_CURRENT_POSITION not in state_attributes_dict + assert ATTR_CURRENT_TILT_POSITION not in state_attributes_dict assert 4 & hass.states.get("cover.test").attributes["supported_features"] == 4 async_fire_mqtt_message(hass, "get-position-topic", "22") state_attributes_dict = hass.states.get("cover.test").attributes assert ATTR_CURRENT_POSITION in state_attributes_dict - assert not (ATTR_CURRENT_TILT_POSITION in state_attributes_dict) + assert ATTR_CURRENT_TILT_POSITION not in state_attributes_dict current_cover_position = hass.states.get("cover.test").attributes[ ATTR_CURRENT_POSITION ] diff --git a/tests/components/pilight/test_init.py b/tests/components/pilight/test_init.py index b69e03058bf..4b8b017f7fd 100644 --- a/tests/components/pilight/test_init.py +++ b/tests/components/pilight/test_init.py @@ -360,7 +360,7 @@ async def test_whitelist_no_match(mock_debug, hass): await hass.async_block_till_done() debug_log_call = mock_debug.call_args_list[-3] - assert not ("Event pilight_received" in debug_log_call) + assert "Event pilight_received" not in debug_log_call async def test_call_rate_delay_throttle_enabled(hass): diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index bbc16cef825..e487e542958 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -157,7 +157,7 @@ async def test_purge_method( assert runs[1] == runs_before_purge[5] assert runs[2] == runs_before_purge[6] - assert not ("EVENT_TEST_PURGE" in (event.event_type for event in events.all())) + assert "EVENT_TEST_PURGE" not in (event.event_type for event in events.all()) # run purge method - correct service data, with repack service_data["repack"] = True diff --git a/tests/components/universal/test_media_player.py b/tests/components/universal/test_media_player.py index 8d8bc80234e..3914f580724 100644 --- a/tests/components/universal/test_media_player.py +++ b/tests/components/universal/test_media_player.py @@ -333,7 +333,7 @@ class TestMediaPlayer(unittest.TestCase): config = {"name": "test", "asdf": 5, "platform": "universal"} config = validate_config(config) - assert not ("asdf" in config) + assert "asdf" not in config def test_platform_setup(self): """Test platform setup.""" From 0f16d4f1e7f27de78c0201ba3368eebad99906e3 Mon Sep 17 00:00:00 2001 From: Jonathan Keslin Date: Fri, 19 Mar 2021 17:50:52 -0700 Subject: [PATCH 503/831] Update pyvesync to 1.3.1 (#48128) --- homeassistant/components/vesync/manifest.json | 4 ++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/vesync/manifest.json b/homeassistant/components/vesync/manifest.json index 667cb16d128..6aa7a5774fd 100644 --- a/homeassistant/components/vesync/manifest.json +++ b/homeassistant/components/vesync/manifest.json @@ -8,7 +8,7 @@ "@thegardenmonkey" ], "requirements": [ - "pyvesync==1.2.0" + "pyvesync==1.3.1" ], "config_flow": true -} \ No newline at end of file +} diff --git a/requirements_all.txt b/requirements_all.txt index 6a114b0e972..6c3793e6c1f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1892,7 +1892,7 @@ pyvera==0.3.13 pyversasense==0.0.6 # homeassistant.components.vesync -pyvesync==1.2.0 +pyvesync==1.3.1 # homeassistant.components.vizio pyvizio==0.1.57 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4a5dbc85efe..04a5c645087 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -980,7 +980,7 @@ pytradfri[async]==7.0.6 pyvera==0.3.13 # homeassistant.components.vesync -pyvesync==1.2.0 +pyvesync==1.3.1 # homeassistant.components.vizio pyvizio==0.1.57 From 365e8a74ee639381d8625daa2f501052c4b2a2e9 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Sat, 20 Mar 2021 08:26:11 +0100 Subject: [PATCH 504/831] Add tests for Netatmo webhook handler (#46396) * Add tests for Netatmo webhook handler * Add async prefix * Remove freezegun dependency * Clean up --- .coveragerc | 1 - tests/components/netatmo/test_webhook.py | 126 +++++++++++++++++++++++ 2 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 tests/components/netatmo/test_webhook.py diff --git a/.coveragerc b/.coveragerc index 48fb4ce756f..33f4071c8f4 100644 --- a/.coveragerc +++ b/.coveragerc @@ -648,7 +648,6 @@ omit = homeassistant/components/netatmo/helper.py homeassistant/components/netatmo/netatmo_entity_base.py homeassistant/components/netatmo/sensor.py - homeassistant/components/netatmo/webhook.py homeassistant/components/netdata/sensor.py homeassistant/components/netgear/device_tracker.py homeassistant/components/netgear_lte/* diff --git a/tests/components/netatmo/test_webhook.py b/tests/components/netatmo/test_webhook.py new file mode 100644 index 00000000000..a56bd2f0fde --- /dev/null +++ b/tests/components/netatmo/test_webhook.py @@ -0,0 +1,126 @@ +"""The tests for Netatmo webhook events.""" +from homeassistant.components.netatmo.const import DATA_DEVICE_IDS, DATA_PERSONS +from homeassistant.components.netatmo.webhook import async_handle_webhook +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util.aiohttp import MockRequest + + +async def test_webhook(hass): + """Test that webhook events are processed.""" + webhook_called = False + + async def handle_event(_): + nonlocal webhook_called + webhook_called = True + + response = ( + b'{"user_id": "123", "user": {"id": "123", "email": "foo@bar.com"},' + b'"push_type": "webhook_activation"}' + ) + request = MockRequest(content=response, mock_source="test") + + async_dispatcher_connect( + hass, + "signal-netatmo-webhook-None", + handle_event, + ) + + await async_handle_webhook(hass, "webhook_id", request) + await hass.async_block_till_done() + + assert webhook_called + + +async def test_webhook_error_in_data(hass): + """Test that errors in webhook data are handled.""" + webhook_called = False + + async def handle_event(_): + nonlocal webhook_called + webhook_called = True + + response = b'""webhook_activation"}' + request = MockRequest(content=response, mock_source="test") + + async_dispatcher_connect( + hass, + "signal-netatmo-webhook-None", + handle_event, + ) + + await async_handle_webhook(hass, "webhook_id", request) + await hass.async_block_till_done() + + assert not webhook_called + + +async def test_webhook_climate_event(hass): + """Test that climate events are handled.""" + webhook_called = False + + async def handle_event(_): + nonlocal webhook_called + webhook_called = True + + response = ( + b'{"user_id": "123", "user": {"id": "123", "email": "foo@bar.com"},' + b'"home_id": "456", "event_type": "therm_mode",' + b'"home": {"id": "456", "therm_mode": "away"},' + b'"mode": "away", "previous_mode": "schedule", "push_type": "home_event_changed"}' + ) + request = MockRequest(content=response, mock_source="test") + + hass.data["netatmo"] = { + DATA_DEVICE_IDS: {}, + } + + async_dispatcher_connect( + hass, + "signal-netatmo-webhook-therm_mode", + handle_event, + ) + + await async_handle_webhook(hass, "webhook_id", request) + await hass.async_block_till_done() + + assert webhook_called + + +async def test_webhook_person_event(hass): + """Test that person events are handled.""" + webhook_called = False + + async def handle_event(_): + nonlocal webhook_called + webhook_called = True + + response = ( + b'{"user_id": "5c81004xxxxxxxxxx45f4",' + b'"persons": [{"id": "e2bf7xxxxxxxxxxxxea3", "face_id": "5d66xxxxxx9b9",' + b'"face_key": "89dxxxxx22", "is_known": true,' + b'"face_url": "https://netatmocameraimage.blob.core.windows.net/production/5xxx"}],' + b'"snapshot_id": "5d19bae867368a59e81cca89", "snapshot_key": "d3b3ae0229f7xb74cf8",' + b'"snapshot_url": "https://netatmocameraimage.blob.core.windows.net/production/5xxxx",' + b'"event_type": "person", "camera_id": "70:xxxxxx:a7", "device_id": "70:xxxxxx:a7",' + b'"home_id": "5c5dxxxxxxxd594", "home_name": "Boulogne Billan.",' + b'"event_id": "5d19bxxxxxxxxcca88",' + b'"message": "Boulogne Billan.: Benoit has been seen by Indoor Camera ",' + b'"push_type": "NACamera-person"}' + ) + request = MockRequest(content=response, mock_source="test") + + hass.data["netatmo"] = { + DATA_DEVICE_IDS: {}, + DATA_PERSONS: {}, + } + + async_dispatcher_connect( + hass, + "signal-netatmo-webhook-person", + handle_event, + ) + + await async_handle_webhook(hass, "webhook_id", request) + await hass.async_block_till_done() + + assert webhook_called From 5a2b5fe7c5bab74d1720073112e795b8dc89ff25 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 20 Mar 2021 13:55:10 +0100 Subject: [PATCH 505/831] Yoda assertion style removed is (#48142) --- tests/components/alert/test_init.py | 20 +- tests/components/apns/test_notify.py | 24 +- .../components/bayesian/test_binary_sensor.py | 10 +- tests/components/binary_sensor/test_init.py | 6 +- .../components/blackbird/test_media_player.py | 54 +-- tests/components/configurator/test_init.py | 26 +- tests/components/counter/test_init.py | 50 +-- tests/components/datadog/test_init.py | 4 +- tests/components/demo/test_climate.py | 2 +- tests/components/device_tracker/test_init.py | 8 +- tests/components/ecobee/test_climate.py | 4 +- tests/components/efergy/test_sensor.py | 16 +- tests/components/emulated_hue/test_hue_api.py | 6 +- tests/components/filter/test_sensor.py | 20 +- tests/components/flux/test_switch.py | 38 +-- .../generic_thermostat/test_climate.py | 320 +++++++++--------- tests/components/geofency/test_init.py | 24 +- tests/components/google_pubsub/test_init.py | 4 +- tests/components/google_wifi/test_sensor.py | 22 +- tests/components/gpslogger/test_init.py | 8 +- tests/components/group/test_init.py | 32 +- tests/components/homeassistant/test_init.py | 8 +- .../homeassistant/triggers/test_state.py | 2 +- tests/components/honeywell/test_climate.py | 54 +-- .../imap_email_content/test_sensor.py | 30 +- tests/components/influxdb/test_init.py | 6 +- tests/components/input_boolean/test_init.py | 16 +- tests/components/input_number/test_init.py | 34 +- tests/components/input_select/test_init.py | 52 +-- tests/components/logentries/test_init.py | 4 +- tests/components/mailbox/test_init.py | 3 +- .../manual/test_alarm_control_panel.py | 234 ++++++------- .../manual_mqtt/test_alarm_control_panel.py | 240 ++++++------- tests/components/melissa/test_climate.py | 34 +- .../components/meraki/test_device_tracker.py | 4 +- tests/components/mfi/test_sensor.py | 2 +- tests/components/min_max/test_sensor.py | 52 +-- tests/components/minio/test_minio.py | 18 +- tests/components/mochad/test_switch.py | 2 +- .../components/mold_indicator/test_sensor.py | 2 +- tests/components/mqtt/test_cover.py | 8 +- tests/components/mqtt/test_trigger.py | 2 +- .../components/mqtt_eventstream/test_init.py | 2 +- .../nsw_fuel_station/test_sensor.py | 4 +- tests/components/nx584/test_binary_sensor.py | 6 +- tests/components/plant/test_init.py | 18 +- tests/components/prometheus/test_init.py | 4 +- tests/components/radarr/test_sensor.py | 80 ++--- tests/components/remote/test_init.py | 2 +- tests/components/rest/test_switch.py | 2 +- tests/components/scene/test_init.py | 6 +- tests/components/script/test_init.py | 12 +- tests/components/shell_command/test_init.py | 2 +- .../components/sleepiq/test_binary_sensor.py | 16 +- tests/components/sleepiq/test_sensor.py | 16 +- tests/components/statistics/test_sensor.py | 22 +- tests/components/statsd/test_init.py | 2 +- tests/components/template/test_trigger.py | 4 +- .../threshold/test_binary_sensor.py | 100 +++--- tests/components/timer/test_init.py | 32 +- .../totalconnect/test_alarm_control_panel.py | 32 +- tests/components/traccar/test_init.py | 8 +- tests/components/uk_transport/test_sensor.py | 24 +- .../unifi_direct/test_device_tracker.py | 6 +- .../components/universal/test_media_player.py | 96 +++--- tests/components/vultr/test_binary_sensor.py | 38 +-- tests/components/vultr/test_sensor.py | 32 +- tests/components/wake_on_lan/test_switch.py | 26 +- .../components/xiaomi/test_device_tracker.py | 12 +- tests/components/zone/test_init.py | 6 +- tests/components/zone/test_trigger.py | 2 +- tests/helpers/test_config_validation.py | 2 +- tests/helpers/test_template.py | 154 ++++----- tests/util/test_dt.py | 2 +- tests/util/test_unit_system.py | 10 +- 75 files changed, 1137 insertions(+), 1148 deletions(-) diff --git a/tests/components/alert/test_init.py b/tests/components/alert/test_init.py index 3796322a5b5..199be9845ca 100644 --- a/tests/components/alert/test_init.py +++ b/tests/components/alert/test_init.py @@ -120,7 +120,7 @@ async def test_is_on(hass): async def test_setup(hass): """Test setup method.""" assert await async_setup_component(hass, alert.DOMAIN, TEST_CONFIG) - assert STATE_IDLE == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_IDLE async def test_fire(hass, mock_notifier): @@ -128,7 +128,7 @@ async def test_fire(hass, mock_notifier): assert await async_setup_component(hass, alert.DOMAIN, TEST_CONFIG) hass.states.async_set("sensor.test", STATE_ON) await hass.async_block_till_done() - assert STATE_ON == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ON async def test_silence(hass, mock_notifier): @@ -138,15 +138,15 @@ async def test_silence(hass, mock_notifier): await hass.async_block_till_done() async_turn_off(hass, ENTITY_ID) await hass.async_block_till_done() - assert STATE_OFF == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_OFF # alert should not be silenced on next fire hass.states.async_set("sensor.test", STATE_OFF) await hass.async_block_till_done() - assert STATE_IDLE == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_IDLE hass.states.async_set("sensor.test", STATE_ON) await hass.async_block_till_done() - assert STATE_ON == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ON async def test_reset(hass, mock_notifier): @@ -156,10 +156,10 @@ async def test_reset(hass, mock_notifier): await hass.async_block_till_done() async_turn_off(hass, ENTITY_ID) await hass.async_block_till_done() - assert STATE_OFF == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_OFF async_turn_on(hass, ENTITY_ID) await hass.async_block_till_done() - assert STATE_ON == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ON async def test_toggle(hass, mock_notifier): @@ -167,13 +167,13 @@ async def test_toggle(hass, mock_notifier): assert await async_setup_component(hass, alert.DOMAIN, TEST_CONFIG) hass.states.async_set("sensor.test", STATE_ON) await hass.async_block_till_done() - assert STATE_ON == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ON async_toggle(hass, ENTITY_ID) await hass.async_block_till_done() - assert STATE_OFF == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_OFF async_toggle(hass, ENTITY_ID) await hass.async_block_till_done() - assert STATE_ON == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ON async def test_notification_no_done_message(hass): diff --git a/tests/components/apns/test_notify.py b/tests/components/apns/test_notify.py index 22d0a30ab16..301ef1362fa 100644 --- a/tests/components/apns/test_notify.py +++ b/tests/components/apns/test_notify.py @@ -199,7 +199,7 @@ async def test_update_existing_device(mock_write, hass): assert test_device_1 is not None assert test_device_2 is not None - assert "updated device 1" == test_device_1.name + assert test_device_1.name == "updated device 1" @patch("homeassistant.components.apns.notify._write_device") @@ -239,8 +239,8 @@ async def test_update_existing_device_with_tracking_id(mock_write, hass): assert test_device_1 is not None assert test_device_2 is not None - assert "tracking123" == test_device_1.tracking_device_id - assert "tracking456" == test_device_2.tracking_device_id + assert test_device_1.tracking_device_id == "tracking123" + assert test_device_2.tracking_device_id == "tracking456" @patch("homeassistant.components.apns.notify.APNsClient") @@ -267,16 +267,16 @@ async def test_send(mock_client, hass): ) assert send.called - assert 1 == len(send.mock_calls) + assert len(send.mock_calls) == 1 target = send.mock_calls[0][1][0] payload = send.mock_calls[0][1][1] - assert "1234" == target - assert "Hello" == payload.alert - assert 1 == payload.badge - assert "test.mp3" == payload.sound - assert "testing" == payload.category + assert target == "1234" + assert payload.alert == "Hello" + assert payload.badge == 1 + assert payload.sound == "test.mp3" + assert payload.category == "testing" @patch("homeassistant.components.apns.notify.APNsClient") @@ -337,13 +337,13 @@ async def test_send_with_state(mock_client, hass): notify_service.send_message(message="Hello", target="home") assert send.called - assert 1 == len(send.mock_calls) + assert len(send.mock_calls) == 1 target = send.mock_calls[0][1][0] payload = send.mock_calls[0][1][1] - assert "5678" == target - assert "Hello" == payload.alert + assert target == "5678" + assert payload.alert == "Hello" @patch("homeassistant.components.apns.notify.APNsClient") diff --git a/tests/components/bayesian/test_binary_sensor.py b/tests/components/bayesian/test_binary_sensor.py index 01f2664ea67..9c181e90deb 100644 --- a/tests/components/bayesian/test_binary_sensor.py +++ b/tests/components/bayesian/test_binary_sensor.py @@ -119,7 +119,7 @@ async def test_sensor_numeric_state(hass): state = hass.states.get("binary_sensor.test_binary") assert [] == state.attributes.get("observations") - assert 0.2 == state.attributes.get("probability") + assert state.attributes.get("probability") == 0.2 assert state.state == "off" @@ -146,7 +146,7 @@ async def test_sensor_numeric_state(hass): await hass.async_block_till_done() state = hass.states.get("binary_sensor.test_binary") - assert 0.2 == state.attributes.get("probability") + assert state.attributes.get("probability") == 0.2 assert state.state == "off" @@ -186,7 +186,7 @@ async def test_sensor_state(hass): state = hass.states.get("binary_sensor.test_binary") assert [] == state.attributes.get("observations") - assert 0.2 == state.attributes.get("probability") + assert state.attributes.get("probability") == 0.2 assert state.state == "off" @@ -242,7 +242,7 @@ async def test_sensor_value_template(hass): state = hass.states.get("binary_sensor.test_binary") assert [] == state.attributes.get("observations") - assert 0.2 == state.attributes.get("probability") + assert state.attributes.get("probability") == 0.2 assert state.state == "off" @@ -339,7 +339,7 @@ async def test_multiple_observations(hass): for key, attrs in state.attributes.items(): json.dumps(attrs) assert [] == state.attributes.get("observations") - assert 0.2 == state.attributes.get("probability") + assert state.attributes.get("probability") == 0.2 assert state.state == "off" diff --git a/tests/components/binary_sensor/test_init.py b/tests/components/binary_sensor/test_init.py index 99af954cd4d..0c574df1569 100644 --- a/tests/components/binary_sensor/test_init.py +++ b/tests/components/binary_sensor/test_init.py @@ -8,17 +8,17 @@ from homeassistant.const import STATE_OFF, STATE_ON def test_state(): """Test binary sensor state.""" sensor = binary_sensor.BinarySensorEntity() - assert STATE_OFF == sensor.state + assert sensor.state == STATE_OFF with mock.patch( "homeassistant.components.binary_sensor.BinarySensorEntity.is_on", new=False, ): - assert STATE_OFF == binary_sensor.BinarySensorEntity().state + assert binary_sensor.BinarySensorEntity().state == STATE_OFF with mock.patch( "homeassistant.components.binary_sensor.BinarySensorEntity.is_on", new=True, ): - assert STATE_ON == binary_sensor.BinarySensorEntity().state + assert binary_sensor.BinarySensorEntity().state == STATE_ON def test_deprecated_base_class(caplog): diff --git a/tests/components/blackbird/test_media_player.py b/tests/components/blackbird/test_media_player.py index 316ed681fa0..73b40fdec97 100644 --- a/tests/components/blackbird/test_media_player.py +++ b/tests/components/blackbird/test_media_player.py @@ -219,9 +219,9 @@ class TestBlackbirdMediaPlayer(unittest.TestCase): def test_setallzones_service_call_with_entity_id(self): """Test set all zone source service call with entity id.""" self.media_player.update() - assert "Zone name" == self.media_player.name - assert STATE_ON == self.media_player.state - assert "one" == self.media_player.source + assert self.media_player.name == "Zone name" + assert self.media_player.state == STATE_ON + assert self.media_player.source == "one" # Call set all zones service self.hass.services.call( @@ -232,16 +232,16 @@ class TestBlackbirdMediaPlayer(unittest.TestCase): ) # Check that source was changed - assert 3 == self.blackbird.zones[3].av + assert self.blackbird.zones[3].av == 3 self.media_player.update() - assert "three" == self.media_player.source + assert self.media_player.source == "three" def test_setallzones_service_call_without_entity_id(self): """Test set all zone source service call without entity id.""" self.media_player.update() - assert "Zone name" == self.media_player.name - assert STATE_ON == self.media_player.state - assert "one" == self.media_player.source + assert self.media_player.name == "Zone name" + assert self.media_player.state == STATE_ON + assert self.media_player.source == "one" # Call set all zones service self.hass.services.call( @@ -249,9 +249,9 @@ class TestBlackbirdMediaPlayer(unittest.TestCase): ) # Check that source was changed - assert 3 == self.blackbird.zones[3].av + assert self.blackbird.zones[3].av == 3 self.media_player.update() - assert "three" == self.media_player.source + assert self.media_player.source == "three" def test_update(self): """Test updating values from blackbird.""" @@ -260,23 +260,23 @@ class TestBlackbirdMediaPlayer(unittest.TestCase): self.media_player.update() - assert STATE_ON == self.media_player.state - assert "one" == self.media_player.source + assert self.media_player.state == STATE_ON + assert self.media_player.source == "one" def test_name(self): """Test name property.""" - assert "Zone name" == self.media_player.name + assert self.media_player.name == "Zone name" def test_state(self): """Test state property.""" assert self.media_player.state is None self.media_player.update() - assert STATE_ON == self.media_player.state + assert self.media_player.state == STATE_ON self.blackbird.zones[3].power = False self.media_player.update() - assert STATE_OFF == self.media_player.state + assert self.media_player.state == STATE_OFF def test_supported_features(self): """Test supported features property.""" @@ -289,54 +289,54 @@ class TestBlackbirdMediaPlayer(unittest.TestCase): """Test source property.""" assert self.media_player.source is None self.media_player.update() - assert "one" == self.media_player.source + assert self.media_player.source == "one" def test_media_title(self): """Test media title property.""" assert self.media_player.media_title is None self.media_player.update() - assert "one" == self.media_player.media_title + assert self.media_player.media_title == "one" def test_source_list(self): """Test source list property.""" # Note, the list is sorted! - assert ["one", "two", "three"] == self.media_player.source_list + assert self.media_player.source_list == ["one", "two", "three"] def test_select_source(self): """Test source selection methods.""" self.media_player.update() - assert "one" == self.media_player.source + assert self.media_player.source == "one" self.media_player.select_source("two") - assert 2 == self.blackbird.zones[3].av + assert self.blackbird.zones[3].av == 2 self.media_player.update() - assert "two" == self.media_player.source + assert self.media_player.source == "two" # Trying to set unknown source. self.media_player.select_source("no name") - assert 2 == self.blackbird.zones[3].av + assert self.blackbird.zones[3].av == 2 self.media_player.update() - assert "two" == self.media_player.source + assert self.media_player.source == "two" def test_turn_on(self): """Testing turning on the zone.""" self.blackbird.zones[3].power = False self.media_player.update() - assert STATE_OFF == self.media_player.state + assert self.media_player.state == STATE_OFF self.media_player.turn_on() assert self.blackbird.zones[3].power self.media_player.update() - assert STATE_ON == self.media_player.state + assert self.media_player.state == STATE_ON def test_turn_off(self): """Testing turning off the zone.""" self.blackbird.zones[3].power = True self.media_player.update() - assert STATE_ON == self.media_player.state + assert self.media_player.state == STATE_ON self.media_player.turn_off() assert not self.blackbird.zones[3].power self.media_player.update() - assert STATE_OFF == self.media_player.state + assert self.media_player.state == STATE_OFF diff --git a/tests/components/configurator/test_init.py b/tests/components/configurator/test_init.py index 8d116c30105..65701cbd139 100644 --- a/tests/components/configurator/test_init.py +++ b/tests/components/configurator/test_init.py @@ -8,18 +8,18 @@ async def test_request_least_info(hass): """Test request config with least amount of data.""" request_id = configurator.async_request_config(hass, "Test Request", lambda _: None) - assert 1 == len( - hass.services.async_services().get(configurator.DOMAIN, []) + assert ( + len(hass.services.async_services().get(configurator.DOMAIN, [])) == 1 ), "No new service registered" states = hass.states.async_all() - assert 1 == len(states), "Expected a new state registered" + assert len(states) == 1, "Expected a new state registered" state = states[0] - assert configurator.STATE_CONFIGURE == state.state - assert request_id == state.attributes.get(configurator.ATTR_CONFIGURE_ID) + assert state.state == configurator.STATE_CONFIGURE + assert state.attributes.get(configurator.ATTR_CONFIGURE_ID) == request_id async def test_request_all_info(hass): @@ -49,11 +49,11 @@ async def test_request_all_info(hass): } states = hass.states.async_all() - assert 1 == len(states) + assert len(states) == 1 state = states[0] - assert configurator.STATE_CONFIGURE == state.state - assert exp_attr == state.attributes + assert state.state == configurator.STATE_CONFIGURE + assert state.attributes == exp_attr async def test_callback_called_on_configure(hass): @@ -70,7 +70,7 @@ async def test_callback_called_on_configure(hass): ) await hass.async_block_till_done() - assert 1 == len(calls), "Callback not called" + assert len(calls) == 1, "Callback not called" async def test_state_change_on_notify_errors(hass): @@ -80,9 +80,9 @@ async def test_state_change_on_notify_errors(hass): configurator.async_notify_errors(hass, request_id, error) states = hass.states.async_all() - assert 1 == len(states) + assert len(states) == 1 state = states[0] - assert error == state.attributes.get(configurator.ATTR_ERRORS) + assert state.attributes.get(configurator.ATTR_ERRORS) == error async def test_notify_errors_fail_silently_on_bad_request_id(hass): @@ -94,11 +94,11 @@ async def test_request_done_works(hass): """Test if calling request done works.""" request_id = configurator.async_request_config(hass, "Test Request", lambda _: None) configurator.async_request_done(hass, request_id) - assert 1 == len(hass.states.async_all()) + assert len(hass.states.async_all()) == 1 hass.bus.async_fire(EVENT_TIME_CHANGED) await hass.async_block_till_done() - assert 0 == len(hass.states.async_all()) + assert len(hass.states.async_all()) == 0 async def test_request_done_fail_silently_on_bad_request_id(hass): diff --git a/tests/components/counter/test_init.py b/tests/components/counter/test_init.py index 7e5859497c5..107dd97924d 100644 --- a/tests/components/counter/test_init.py +++ b/tests/components/counter/test_init.py @@ -114,16 +114,16 @@ async def test_config_options(hass): assert state_2 is not None assert state_3 is not None - assert 0 == int(state_1.state) + assert int(state_1.state) == 0 assert ATTR_ICON not in state_1.attributes assert ATTR_FRIENDLY_NAME not in state_1.attributes - assert 10 == int(state_2.state) - assert "Hello World" == state_2.attributes.get(ATTR_FRIENDLY_NAME) - assert "mdi:work" == state_2.attributes.get(ATTR_ICON) + assert int(state_2.state) == 10 + assert state_2.attributes.get(ATTR_FRIENDLY_NAME) == "Hello World" + assert state_2.attributes.get(ATTR_ICON) == "mdi:work" - assert DEFAULT_INITIAL == state_3.attributes.get(ATTR_INITIAL) - assert DEFAULT_STEP == state_3.attributes.get(ATTR_STEP) + assert state_3.attributes.get(ATTR_INITIAL) == DEFAULT_INITIAL + assert state_3.attributes.get(ATTR_STEP) == DEFAULT_STEP async def test_methods(hass): @@ -135,31 +135,31 @@ async def test_methods(hass): entity_id = "counter.test_1" state = hass.states.get(entity_id) - assert 0 == int(state.state) + assert int(state.state) == 0 async_increment(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 1 == int(state.state) + assert int(state.state) == 1 async_increment(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 2 == int(state.state) + assert int(state.state) == 2 async_decrement(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 1 == int(state.state) + assert int(state.state) == 1 async_reset(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 0 == int(state.state) + assert int(state.state) == 0 async def test_methods_with_config(hass): @@ -173,25 +173,25 @@ async def test_methods_with_config(hass): entity_id = "counter.test" state = hass.states.get(entity_id) - assert 10 == int(state.state) + assert int(state.state) == 10 async_increment(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 15 == int(state.state) + assert int(state.state) == 15 async_increment(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 20 == int(state.state) + assert int(state.state) == 20 async_decrement(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 15 == int(state.state) + assert int(state.state) == 15 async def test_initial_state_overrules_restore_state(hass): @@ -370,7 +370,7 @@ async def test_configure(hass, hass_admin_user): state = hass.states.get("counter.test") assert state is not None assert state.state == "10" - assert 10 == state.attributes.get("maximum") + assert state.attributes.get("maximum") == 10 # update max await hass.services.async_call( @@ -384,7 +384,7 @@ async def test_configure(hass, hass_admin_user): state = hass.states.get("counter.test") assert state is not None assert state.state == "0" - assert 0 == state.attributes.get("maximum") + assert state.attributes.get("maximum") == 0 # disable max await hass.services.async_call( @@ -413,7 +413,7 @@ async def test_configure(hass, hass_admin_user): state = hass.states.get("counter.test") assert state is not None assert state.state == "5" - assert 5 == state.attributes.get("minimum") + assert state.attributes.get("minimum") == 5 # disable min await hass.services.async_call( @@ -430,7 +430,7 @@ async def test_configure(hass, hass_admin_user): assert state.attributes.get("minimum") is None # update step - assert 1 == state.attributes.get("step") + assert state.attributes.get("step") == 1 await hass.services.async_call( "counter", "configure", @@ -442,7 +442,7 @@ async def test_configure(hass, hass_admin_user): state = hass.states.get("counter.test") assert state is not None assert state.state == "5" - assert 3 == state.attributes.get("step") + assert state.attributes.get("step") == 3 # update value await hass.services.async_call( @@ -469,7 +469,7 @@ async def test_configure(hass, hass_admin_user): state = hass.states.get("counter.test") assert state is not None assert state.state == "6" - assert 5 == state.attributes.get("initial") + assert state.attributes.get("initial") == 5 # update all await hass.services.async_call( @@ -490,10 +490,10 @@ async def test_configure(hass, hass_admin_user): state = hass.states.get("counter.test") assert state is not None assert state.state == "5" - assert 5 == state.attributes.get("step") - assert 0 == state.attributes.get("minimum") - assert 9 == state.attributes.get("maximum") - assert 6 == state.attributes.get("initial") + assert state.attributes.get("step") == 5 + assert state.attributes.get("minimum") == 0 + assert state.attributes.get("maximum") == 9 + assert state.attributes.get("initial") == 6 async def test_load_from_storage(hass, storage_setup): diff --git a/tests/components/datadog/test_init.py b/tests/components/datadog/test_init.py index 087e0f4b884..13d99289bb8 100644 --- a/tests/components/datadog/test_init.py +++ b/tests/components/datadog/test_init.py @@ -37,8 +37,8 @@ async def test_datadog_setup_full(hass): assert mock_init.call_args == mock.call(statsd_host="host", statsd_port=123) assert hass.bus.listen.called - assert EVENT_LOGBOOK_ENTRY == hass.bus.listen.call_args_list[0][0][0] - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[1][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_LOGBOOK_ENTRY + assert hass.bus.listen.call_args_list[1][0][0] == EVENT_STATE_CHANGED async def test_datadog_setup_defaults(hass): diff --git a/tests/components/demo/test_climate.py b/tests/components/demo/test_climate.py index aa6ff39cb0e..2f4317c49c5 100644 --- a/tests/components/demo/test_climate.py +++ b/tests/components/demo/test_climate.py @@ -69,7 +69,7 @@ def test_setup_params(hass): assert state.attributes.get(ATTR_HUMIDITY) == 67 assert state.attributes.get(ATTR_CURRENT_HUMIDITY) == 54 assert state.attributes.get(ATTR_SWING_MODE) == "Off" - assert STATE_OFF == state.attributes.get(ATTR_AUX_HEAT) + assert state.attributes.get(ATTR_AUX_HEAT) == STATE_OFF assert state.attributes.get(ATTR_HVAC_MODES) == [ "off", "heat", diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index c7aba405ccd..d62e46255d7 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -251,7 +251,7 @@ async def test_update_stale(hass, mock_device_tracker_conf): ) await hass.async_block_till_done() - assert STATE_HOME == hass.states.get("device_tracker.dev1").state + assert hass.states.get("device_tracker.dev1").state == STATE_HOME scanner.leave_home("DEV1") @@ -262,7 +262,7 @@ async def test_update_stale(hass, mock_device_tracker_conf): async_fire_time_changed(hass, scan_time) await hass.async_block_till_done() - assert STATE_NOT_HOME == hass.states.get("device_tracker.dev1").state + assert hass.states.get("device_tracker.dev1").state == STATE_NOT_HOME async def test_entity_attributes(hass, mock_device_tracker_conf): @@ -474,7 +474,7 @@ async def test_see_passive_zone_state(hass, mock_device_tracker_conf): state = hass.states.get("device_tracker.dev1") attrs = state.attributes - assert STATE_HOME == state.state + assert state.state == STATE_HOME assert state.object_id == "dev1" assert state.name == "dev1" assert attrs.get("friendly_name") == "dev1" @@ -494,7 +494,7 @@ async def test_see_passive_zone_state(hass, mock_device_tracker_conf): state = hass.states.get("device_tracker.dev1") attrs = state.attributes - assert STATE_NOT_HOME == state.state + assert state.state == STATE_NOT_HOME assert state.object_id == "dev1" assert state.name == "dev1" assert attrs.get("friendly_name") == "dev1" diff --git a/tests/components/ecobee/test_climate.py b/tests/components/ecobee/test_climate.py index 92ad310f68d..95b4b290b70 100644 --- a/tests/components/ecobee/test_climate.py +++ b/tests/components/ecobee/test_climate.py @@ -117,9 +117,9 @@ async def test_fan(ecobee_fixture, thermostat): """Test fan property.""" assert const.STATE_ON == thermostat.fan ecobee_fixture["equipmentStatus"] = "" - assert STATE_OFF == thermostat.fan + assert thermostat.fan == STATE_OFF ecobee_fixture["equipmentStatus"] = "heatPump, heatPump2" - assert STATE_OFF == thermostat.fan + assert thermostat.fan == STATE_OFF async def test_hvac_mode(ecobee_fixture, thermostat): diff --git a/tests/components/efergy/test_sensor.py b/tests/components/efergy/test_sensor.py index 3dfbfc354f8..a56e28cb3ef 100644 --- a/tests/components/efergy/test_sensor.py +++ b/tests/components/efergy/test_sensor.py @@ -63,11 +63,11 @@ async def test_single_sensor_readings(hass, requests_mock): assert await async_setup_component(hass, "sensor", {"sensor": ONE_SENSOR_CONFIG}) await hass.async_block_till_done() - assert "38.21" == hass.states.get("sensor.energy_consumed").state - assert "1580" == hass.states.get("sensor.energy_usage").state - assert "ok" == hass.states.get("sensor.energy_budget").state - assert "5.27" == hass.states.get("sensor.energy_cost").state - assert "1628" == hass.states.get("sensor.efergy_728386").state + assert hass.states.get("sensor.energy_consumed").state == "38.21" + assert hass.states.get("sensor.energy_usage").state == "1580" + assert hass.states.get("sensor.energy_budget").state == "ok" + assert hass.states.get("sensor.energy_cost").state == "5.27" + assert hass.states.get("sensor.efergy_728386").state == "1628" async def test_multi_sensor_readings(hass, requests_mock): @@ -76,6 +76,6 @@ async def test_multi_sensor_readings(hass, requests_mock): assert await async_setup_component(hass, "sensor", {"sensor": MULTI_SENSOR_CONFIG}) await hass.async_block_till_done() - assert "218" == hass.states.get("sensor.efergy_728386").state - assert "1808" == hass.states.get("sensor.efergy_0").state - assert "312" == hass.states.get("sensor.efergy_728387").state + assert hass.states.get("sensor.efergy_728386").state == "218" + assert hass.states.get("sensor.efergy_0").state == "1808" + assert hass.states.get("sensor.efergy_728387").state == "312" diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 3c322c0b613..f10786d36de 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -363,7 +363,7 @@ async def test_light_without_brightness_can_be_turned_off(hass_hue, hue_client): call = turn_off_calls[-1] assert light.DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service + assert call.service == SERVICE_TURN_OFF assert "light.no_brightness" in call.data[ATTR_ENTITY_ID] @@ -401,11 +401,11 @@ async def test_light_without_brightness_can_be_turned_on(hass_hue, hue_client): # Verify that SERVICE_TURN_ON has been called await hass_hue.async_block_till_done() - assert 1 == len(turn_on_calls) + assert len(turn_on_calls) == 1 call = turn_on_calls[-1] assert light.DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service + assert call.service == SERVICE_TURN_ON assert "light.no_brightness" in call.data[ATTR_ENTITY_ID] diff --git a/tests/components/filter/test_sensor.py b/tests/components/filter/test_sensor.py index 417c84e8ea4..b787fc0235c 100644 --- a/tests/components/filter/test_sensor.py +++ b/tests/components/filter/test_sensor.py @@ -75,7 +75,7 @@ async def test_chain(hass, values): await hass.async_block_till_done() state = hass.states.get("sensor.test") - assert "18.05" == state.state + assert state.state == "18.05" async def test_chain_history(hass, values, missing=False): @@ -131,9 +131,9 @@ async def test_chain_history(hass, values, missing=False): state = hass.states.get("sensor.test") if missing: - assert "18.05" == state.state + assert state.state == "18.05" else: - assert "17.05" == state.state + assert state.state == "17.05" async def test_source_state_none(hass, values): @@ -245,7 +245,7 @@ async def test_history_time(hass): await hass.async_block_till_done() state = hass.states.get("sensor.test") - assert "18.0" == state.state + assert state.state == "18.0" async def test_setup(hass): @@ -316,7 +316,7 @@ async def test_outlier(values): filt = OutlierFilter(window_size=3, precision=2, entity=None, radius=4.0) for state in values: filtered = filt.filter_state(state) - assert 21 == filtered.state + assert filtered.state == 21 def test_outlier_step(values): @@ -331,7 +331,7 @@ def test_outlier_step(values): values[-1].state = 22 for state in values: filtered = filt.filter_state(state) - assert 22 == filtered.state + assert filtered.state == 22 def test_initial_outlier(values): @@ -340,7 +340,7 @@ def test_initial_outlier(values): out = ha.State("sensor.test_monitored", 4000) for state in [out] + values: filtered = filt.filter_state(state) - assert 21 == filtered.state + assert filtered.state == 21 def test_unknown_state_outlier(values): @@ -352,7 +352,7 @@ def test_unknown_state_outlier(values): filtered = filt.filter_state(state) except ValueError: assert state.state == "unknown" - assert 21 == filtered.state + assert filtered.state == 21 def test_precision_zero(values): @@ -372,7 +372,7 @@ def test_lowpass(values): filtered = filt.filter_state(state) except ValueError: assert state.state == "unknown" - assert 18.05 == filtered.state + assert filtered.state == 18.05 def test_range(values): @@ -438,7 +438,7 @@ def test_time_sma(values): ) for state in values: filtered = filt.filter_state(state) - assert 21.5 == filtered.state + assert filtered.state == 21.5 async def test_reload(hass): diff --git a/tests/components/flux/test_switch.py b/tests/components/flux/test_switch.py index 0d4e4f0595e..7438b690ab5 100644 --- a/tests/components/flux/test_switch.py +++ b/tests/components/flux/test_switch.py @@ -140,7 +140,7 @@ async def test_flux_when_switch_is_off(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -191,7 +191,7 @@ async def test_flux_before_sunrise(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -250,7 +250,7 @@ async def test_flux_before_sunrise_known_location(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -309,7 +309,7 @@ async def test_flux_after_sunrise_before_sunset(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -368,7 +368,7 @@ async def test_flux_after_sunset_before_stop(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -428,7 +428,7 @@ async def test_flux_after_stop_before_sunrise(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -487,7 +487,7 @@ async def test_flux_with_custom_start_stop_times(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -550,7 +550,7 @@ async def test_flux_before_sunrise_stop_next_day(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -616,7 +616,7 @@ async def test_flux_after_sunrise_before_sunset_stop_next_day( # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -682,7 +682,7 @@ async def test_flux_after_sunset_before_midnight_stop_next_day( # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -747,7 +747,7 @@ async def test_flux_after_sunset_after_midnight_stop_next_day( # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -812,7 +812,7 @@ async def test_flux_after_stop_before_sunrise_stop_next_day( # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -872,7 +872,7 @@ async def test_flux_with_custom_colortemps(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -934,7 +934,7 @@ async def test_flux_with_custom_brightness(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -1001,17 +1001,17 @@ async def test_flux_with_multiple_lights(hass, legacy_patchable_time): await hass.async_block_till_done() state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None state = hass.states.get(ent2.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None state = hass.states.get(ent3.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -1077,7 +1077,7 @@ async def test_flux_with_mired(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("color_temp") is None test_time = dt_util.utcnow().replace(hour=8, minute=30, second=0) @@ -1134,7 +1134,7 @@ async def test_flux_with_rgb(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("color_temp") is None test_time = dt_util.utcnow().replace(hour=8, minute=30, second=0) diff --git a/tests/components/generic_thermostat/test_climate.py b/tests/components/generic_thermostat/test_climate.py index dc5353971b7..c2c1435464e 100644 --- a/tests/components/generic_thermostat/test_climate.py +++ b/tests/components/generic_thermostat/test_climate.py @@ -116,13 +116,13 @@ async def test_heater_input_boolean(hass, setup_comp_1): ) await hass.async_block_till_done() - assert STATE_OFF == hass.states.get(heater_switch).state + assert hass.states.get(heater_switch).state == STATE_OFF _setup_sensor(hass, 18) await hass.async_block_till_done() await common.async_set_temperature(hass, 23) - assert STATE_ON == hass.states.get(heater_switch).state + assert hass.states.get(heater_switch).state == STATE_ON async def test_heater_switch(hass, setup_comp_1): @@ -151,13 +151,13 @@ async def test_heater_switch(hass, setup_comp_1): ) await hass.async_block_till_done() - assert STATE_OFF == hass.states.get(heater_switch).state + assert hass.states.get(heater_switch).state == STATE_OFF _setup_sensor(hass, 18) await common.async_set_temperature(hass, 23) await hass.async_block_till_done() - assert STATE_ON == hass.states.get(heater_switch).state + assert hass.states.get(heater_switch).state == STATE_ON async def test_unique_id(hass, setup_comp_1): @@ -234,7 +234,7 @@ async def test_setup_defaults_to_unknown(hass): }, ) await hass.async_block_till_done() - assert HVAC_MODE_OFF == hass.states.get(ENTITY).state + assert hass.states.get(ENTITY).state == HVAC_MODE_OFF async def test_setup_gets_current_temp_from_sensor(hass): @@ -264,27 +264,27 @@ async def test_setup_gets_current_temp_from_sensor(hass): async def test_default_setup_params(hass, setup_comp_2): """Test the setup with default parameters.""" state = hass.states.get(ENTITY) - assert 7 == state.attributes.get("min_temp") - assert 35 == state.attributes.get("max_temp") - assert 7 == state.attributes.get("temperature") + assert state.attributes.get("min_temp") == 7 + assert state.attributes.get("max_temp") == 35 + assert state.attributes.get("temperature") == 7 async def test_get_hvac_modes(hass, setup_comp_2): """Test that the operation list returns the correct modes.""" state = hass.states.get(ENTITY) modes = state.attributes.get("hvac_modes") - assert [HVAC_MODE_HEAT, HVAC_MODE_OFF] == modes + assert modes == [HVAC_MODE_HEAT, HVAC_MODE_OFF] async def test_set_target_temp(hass, setup_comp_2): """Test the setting of the target temperature.""" await common.async_set_temperature(hass, 30) state = hass.states.get(ENTITY) - assert 30.0 == state.attributes.get("temperature") + assert state.attributes.get("temperature") == 30.0 with pytest.raises(vol.Invalid): await common.async_set_temperature(hass, None) state = hass.states.get(ENTITY) - assert 30.0 == state.attributes.get("temperature") + assert state.attributes.get("temperature") == 30.0 async def test_set_away_mode(hass, setup_comp_2): @@ -292,7 +292,7 @@ async def test_set_away_mode(hass, setup_comp_2): await common.async_set_temperature(hass, 23) await common.async_set_preset_mode(hass, PRESET_AWAY) state = hass.states.get(ENTITY) - assert 16 == state.attributes.get("temperature") + assert state.attributes.get("temperature") == 16 async def test_set_away_mode_and_restore_prev_temp(hass, setup_comp_2): @@ -303,10 +303,10 @@ async def test_set_away_mode_and_restore_prev_temp(hass, setup_comp_2): await common.async_set_temperature(hass, 23) await common.async_set_preset_mode(hass, PRESET_AWAY) state = hass.states.get(ENTITY) - assert 16 == state.attributes.get("temperature") + assert state.attributes.get("temperature") == 16 await common.async_set_preset_mode(hass, PRESET_NONE) state = hass.states.get(ENTITY) - assert 23 == state.attributes.get("temperature") + assert state.attributes.get("temperature") == 23 async def test_set_away_mode_twice_and_restore_prev_temp(hass, setup_comp_2): @@ -318,10 +318,10 @@ async def test_set_away_mode_twice_and_restore_prev_temp(hass, setup_comp_2): await common.async_set_preset_mode(hass, PRESET_AWAY) await common.async_set_preset_mode(hass, PRESET_AWAY) state = hass.states.get(ENTITY) - assert 16 == state.attributes.get("temperature") + assert state.attributes.get("temperature") == 16 await common.async_set_preset_mode(hass, PRESET_NONE) state = hass.states.get(ENTITY) - assert 23 == state.attributes.get("temperature") + assert state.attributes.get("temperature") == 23 async def test_sensor_bad_value(hass, setup_comp_2): @@ -382,11 +382,11 @@ async def test_set_target_temp_heater_on(hass, setup_comp_2): _setup_sensor(hass, 25) await hass.async_block_till_done() await common.async_set_temperature(hass, 30) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_set_target_temp_heater_off(hass, setup_comp_2): @@ -395,11 +395,11 @@ async def test_set_target_temp_heater_off(hass, setup_comp_2): _setup_sensor(hass, 30) await hass.async_block_till_done() await common.async_set_temperature(hass, 25) - assert 2 == len(calls) + assert len(calls) == 2 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_temp_change_heater_on_within_tolerance(hass, setup_comp_2): @@ -408,7 +408,7 @@ async def test_temp_change_heater_on_within_tolerance(hass, setup_comp_2): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 29) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_temp_change_heater_on_outside_tolerance(hass, setup_comp_2): @@ -417,11 +417,11 @@ async def test_temp_change_heater_on_outside_tolerance(hass, setup_comp_2): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 27) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_temp_change_heater_off_within_tolerance(hass, setup_comp_2): @@ -430,7 +430,7 @@ async def test_temp_change_heater_off_within_tolerance(hass, setup_comp_2): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 33) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_temp_change_heater_off_outside_tolerance(hass, setup_comp_2): @@ -439,11 +439,11 @@ async def test_temp_change_heater_off_outside_tolerance(hass, setup_comp_2): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 35) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_running_when_hvac_mode_is_off(hass, setup_comp_2): @@ -451,11 +451,11 @@ async def test_running_when_hvac_mode_is_off(hass, setup_comp_2): calls = _setup_switch(hass, True) await common.async_set_temperature(hass, 30) await common.async_set_hvac_mode(hass, HVAC_MODE_OFF) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_no_state_change_when_hvac_mode_off(hass, setup_comp_2): @@ -465,7 +465,7 @@ async def test_no_state_change_when_hvac_mode_off(hass, setup_comp_2): await common.async_set_hvac_mode(hass, HVAC_MODE_OFF) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_hvac_mode_heat(hass, setup_comp_2): @@ -479,11 +479,11 @@ async def test_hvac_mode_heat(hass, setup_comp_2): await hass.async_block_till_done() calls = _setup_switch(hass, False) await common.async_set_hvac_mode(hass, HVAC_MODE_HEAT) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH def _setup_switch(hass, is_on): @@ -532,11 +532,11 @@ async def test_set_target_temp_ac_off(hass, setup_comp_3): _setup_sensor(hass, 25) await hass.async_block_till_done() await common.async_set_temperature(hass, 30) - assert 2 == len(calls) + assert len(calls) == 2 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_turn_away_mode_on_cooling(hass, setup_comp_3): @@ -547,7 +547,7 @@ async def test_turn_away_mode_on_cooling(hass, setup_comp_3): await common.async_set_temperature(hass, 19) await common.async_set_preset_mode(hass, PRESET_AWAY) state = hass.states.get(ENTITY) - assert 30 == state.attributes.get("temperature") + assert state.attributes.get("temperature") == 30 async def test_hvac_mode_cool(hass, setup_comp_3): @@ -561,11 +561,11 @@ async def test_hvac_mode_cool(hass, setup_comp_3): await hass.async_block_till_done() calls = _setup_switch(hass, False) await common.async_set_hvac_mode(hass, HVAC_MODE_COOL) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_set_target_temp_ac_on(hass, setup_comp_3): @@ -574,11 +574,11 @@ async def test_set_target_temp_ac_on(hass, setup_comp_3): _setup_sensor(hass, 30) await hass.async_block_till_done() await common.async_set_temperature(hass, 25) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_temp_change_ac_off_within_tolerance(hass, setup_comp_3): @@ -587,7 +587,7 @@ async def test_temp_change_ac_off_within_tolerance(hass, setup_comp_3): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 29.8) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_set_temp_change_ac_off_outside_tolerance(hass, setup_comp_3): @@ -596,11 +596,11 @@ async def test_set_temp_change_ac_off_outside_tolerance(hass, setup_comp_3): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 27) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_temp_change_ac_on_within_tolerance(hass, setup_comp_3): @@ -609,7 +609,7 @@ async def test_temp_change_ac_on_within_tolerance(hass, setup_comp_3): await common.async_set_temperature(hass, 25) _setup_sensor(hass, 25.2) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_temp_change_ac_on_outside_tolerance(hass, setup_comp_3): @@ -618,11 +618,11 @@ async def test_temp_change_ac_on_outside_tolerance(hass, setup_comp_3): await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_running_when_operating_mode_is_off_2(hass, setup_comp_3): @@ -630,11 +630,11 @@ async def test_running_when_operating_mode_is_off_2(hass, setup_comp_3): calls = _setup_switch(hass, True) await common.async_set_temperature(hass, 30) await common.async_set_hvac_mode(hass, HVAC_MODE_OFF) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_no_state_change_when_operation_mode_off_2(hass, setup_comp_3): @@ -644,7 +644,7 @@ async def test_no_state_change_when_operation_mode_off_2(hass, setup_comp_3): await common.async_set_hvac_mode(hass, HVAC_MODE_OFF) _setup_sensor(hass, 35) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 @pytest.fixture @@ -677,7 +677,7 @@ async def test_temp_change_ac_trigger_on_not_long_enough(hass, setup_comp_4): await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_temp_change_ac_trigger_on_long_enough(hass, setup_comp_4): @@ -692,11 +692,11 @@ async def test_temp_change_ac_trigger_on_long_enough(hass, setup_comp_4): await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_temp_change_ac_trigger_off_not_long_enough(hass, setup_comp_4): @@ -705,7 +705,7 @@ async def test_temp_change_ac_trigger_off_not_long_enough(hass, setup_comp_4): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_temp_change_ac_trigger_off_long_enough(hass, setup_comp_4): @@ -720,11 +720,11 @@ async def test_temp_change_ac_trigger_off_long_enough(hass, setup_comp_4): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_mode_change_ac_trigger_off_not_long_enough(hass, setup_comp_4): @@ -733,13 +733,13 @@ async def test_mode_change_ac_trigger_off_not_long_enough(hass, setup_comp_4): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 await common.async_set_hvac_mode(hass, HVAC_MODE_OFF) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert "homeassistant" == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == "homeassistant" + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_mode_change_ac_trigger_on_not_long_enough(hass, setup_comp_4): @@ -748,13 +748,13 @@ async def test_mode_change_ac_trigger_on_not_long_enough(hass, setup_comp_4): await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 await common.async_set_hvac_mode(hass, HVAC_MODE_HEAT) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert "homeassistant" == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == "homeassistant" + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH @pytest.fixture @@ -787,7 +787,7 @@ async def test_temp_change_ac_trigger_on_not_long_enough_2(hass, setup_comp_5): await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_temp_change_ac_trigger_on_long_enough_2(hass, setup_comp_5): @@ -802,11 +802,11 @@ async def test_temp_change_ac_trigger_on_long_enough_2(hass, setup_comp_5): await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_temp_change_ac_trigger_off_not_long_enough_2(hass, setup_comp_5): @@ -815,7 +815,7 @@ async def test_temp_change_ac_trigger_off_not_long_enough_2(hass, setup_comp_5): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_temp_change_ac_trigger_off_long_enough_2(hass, setup_comp_5): @@ -830,11 +830,11 @@ async def test_temp_change_ac_trigger_off_long_enough_2(hass, setup_comp_5): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_mode_change_ac_trigger_off_not_long_enough_2(hass, setup_comp_5): @@ -843,13 +843,13 @@ async def test_mode_change_ac_trigger_off_not_long_enough_2(hass, setup_comp_5): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 await common.async_set_hvac_mode(hass, HVAC_MODE_OFF) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert "homeassistant" == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == "homeassistant" + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_mode_change_ac_trigger_on_not_long_enough_2(hass, setup_comp_5): @@ -858,13 +858,13 @@ async def test_mode_change_ac_trigger_on_not_long_enough_2(hass, setup_comp_5): await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 await common.async_set_hvac_mode(hass, HVAC_MODE_HEAT) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert "homeassistant" == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == "homeassistant" + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH @pytest.fixture @@ -896,7 +896,7 @@ async def test_temp_change_heater_trigger_off_not_long_enough(hass, setup_comp_6 await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_temp_change_heater_trigger_on_not_long_enough(hass, setup_comp_6): @@ -905,7 +905,7 @@ async def test_temp_change_heater_trigger_on_not_long_enough(hass, setup_comp_6) await common.async_set_temperature(hass, 30) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_temp_change_heater_trigger_on_long_enough(hass, setup_comp_6): @@ -920,11 +920,11 @@ async def test_temp_change_heater_trigger_on_long_enough(hass, setup_comp_6): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_temp_change_heater_trigger_off_long_enough(hass, setup_comp_6): @@ -939,11 +939,11 @@ async def test_temp_change_heater_trigger_off_long_enough(hass, setup_comp_6): await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_mode_change_heater_trigger_off_not_long_enough(hass, setup_comp_6): @@ -952,13 +952,13 @@ async def test_mode_change_heater_trigger_off_not_long_enough(hass, setup_comp_6 await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 await common.async_set_hvac_mode(hass, HVAC_MODE_OFF) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert "homeassistant" == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == "homeassistant" + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_mode_change_heater_trigger_on_not_long_enough(hass, setup_comp_6): @@ -967,13 +967,13 @@ async def test_mode_change_heater_trigger_on_not_long_enough(hass, setup_comp_6) await common.async_set_temperature(hass, 30) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 await common.async_set_hvac_mode(hass, HVAC_MODE_HEAT) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert "homeassistant" == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == "homeassistant" + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH @pytest.fixture @@ -1013,17 +1013,17 @@ async def test_temp_change_ac_trigger_on_long_enough_3(hass, setup_comp_7): test_time = datetime.datetime.now(pytz.UTC) async_fire_time_changed(hass, test_time) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=5)) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=10)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_temp_change_ac_trigger_off_long_enough_3(hass, setup_comp_7): @@ -1036,17 +1036,17 @@ async def test_temp_change_ac_trigger_off_long_enough_3(hass, setup_comp_7): test_time = datetime.datetime.now(pytz.UTC) async_fire_time_changed(hass, test_time) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=5)) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=10)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH @pytest.fixture @@ -1084,17 +1084,17 @@ async def test_temp_change_heater_trigger_on_long_enough_2(hass, setup_comp_8): test_time = datetime.datetime.now(pytz.UTC) async_fire_time_changed(hass, test_time) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=5)) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=10)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_temp_change_heater_trigger_off_long_enough_2(hass, setup_comp_8): @@ -1107,17 +1107,17 @@ async def test_temp_change_heater_trigger_off_long_enough_2(hass, setup_comp_8): test_time = datetime.datetime.now(pytz.UTC) async_fire_time_changed(hass, test_time) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=5)) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=10)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH @pytest.fixture @@ -1149,7 +1149,7 @@ async def test_precision(hass, setup_comp_9): """Test that setting precision to tenths works as intended.""" await common.async_set_temperature(hass, 23.27) state = hass.states.get(ENTITY) - assert 23.3 == state.attributes.get("temperature") + assert state.attributes.get("temperature") == 23.3 async def test_custom_setup_params(hass): @@ -1350,14 +1350,14 @@ async def test_restore_state_uncoherence_case(hass): await hass.async_block_till_done() state = hass.states.get(ENTITY) - assert 20 == state.attributes[ATTR_TEMPERATURE] - assert HVAC_MODE_OFF == state.state - assert 0 == len(calls) + assert state.attributes[ATTR_TEMPERATURE] == 20 + assert state.state == HVAC_MODE_OFF + assert len(calls) == 0 calls = _setup_switch(hass, False) await hass.async_block_till_done() state = hass.states.get(ENTITY) - assert HVAC_MODE_OFF == state.state + assert state.state == HVAC_MODE_OFF async def _setup_climate(hass): diff --git a/tests/components/geofency/test_init.py b/tests/components/geofency/test_init.py index 92a2f5b19f2..b84b6b681ae 100644 --- a/tests/components/geofency/test_init.py +++ b/tests/components/geofency/test_init.py @@ -196,7 +196,7 @@ async def test_gps_enter_and_exit_home(hass, geofency_client, webhook_id): assert req.status == HTTP_OK device_name = slugify(GPS_ENTER_HOME["device"]) state_name = hass.states.get(f"device_tracker.{device_name}").state - assert STATE_HOME == state_name + assert state_name == STATE_HOME # Exit the Home zone req = await geofency_client.post(url, data=GPS_EXIT_HOME) @@ -204,7 +204,7 @@ async def test_gps_enter_and_exit_home(hass, geofency_client, webhook_id): assert req.status == HTTP_OK device_name = slugify(GPS_EXIT_HOME["device"]) state_name = hass.states.get(f"device_tracker.{device_name}").state - assert STATE_NOT_HOME == state_name + assert state_name == STATE_NOT_HOME # Exit the Home zone with "Send Current Position" enabled data = GPS_EXIT_HOME.copy() @@ -218,11 +218,11 @@ async def test_gps_enter_and_exit_home(hass, geofency_client, webhook_id): current_latitude = hass.states.get(f"device_tracker.{device_name}").attributes[ "latitude" ] - assert NOT_HOME_LATITUDE == current_latitude + assert current_latitude == NOT_HOME_LATITUDE current_longitude = hass.states.get(f"device_tracker.{device_name}").attributes[ "longitude" ] - assert NOT_HOME_LONGITUDE == current_longitude + assert current_longitude == NOT_HOME_LONGITUDE dev_reg = dr.async_get(hass) assert len(dev_reg.devices) == 1 @@ -241,7 +241,7 @@ async def test_beacon_enter_and_exit_home(hass, geofency_client, webhook_id): assert req.status == HTTP_OK device_name = slugify(f"beacon_{BEACON_ENTER_HOME['name']}") state_name = hass.states.get(f"device_tracker.{device_name}").state - assert STATE_HOME == state_name + assert state_name == STATE_HOME # Exit the Home zone req = await geofency_client.post(url, data=BEACON_EXIT_HOME) @@ -249,7 +249,7 @@ async def test_beacon_enter_and_exit_home(hass, geofency_client, webhook_id): assert req.status == HTTP_OK device_name = slugify(f"beacon_{BEACON_ENTER_HOME['name']}") state_name = hass.states.get(f"device_tracker.{device_name}").state - assert STATE_NOT_HOME == state_name + assert state_name == STATE_NOT_HOME async def test_beacon_enter_and_exit_car(hass, geofency_client, webhook_id): @@ -262,7 +262,7 @@ async def test_beacon_enter_and_exit_car(hass, geofency_client, webhook_id): assert req.status == HTTP_OK device_name = slugify(f"beacon_{BEACON_ENTER_CAR['name']}") state_name = hass.states.get(f"device_tracker.{device_name}").state - assert STATE_NOT_HOME == state_name + assert state_name == STATE_NOT_HOME # Exit the Car away from Home zone req = await geofency_client.post(url, data=BEACON_EXIT_CAR) @@ -270,7 +270,7 @@ async def test_beacon_enter_and_exit_car(hass, geofency_client, webhook_id): assert req.status == HTTP_OK device_name = slugify(f"beacon_{BEACON_ENTER_CAR['name']}") state_name = hass.states.get(f"device_tracker.{device_name}").state - assert STATE_NOT_HOME == state_name + assert state_name == STATE_NOT_HOME # Enter the Car in the Home zone data = BEACON_ENTER_CAR.copy() @@ -281,7 +281,7 @@ async def test_beacon_enter_and_exit_car(hass, geofency_client, webhook_id): assert req.status == HTTP_OK device_name = slugify(f"beacon_{data['name']}") state_name = hass.states.get(f"device_tracker.{device_name}").state - assert STATE_HOME == state_name + assert state_name == STATE_HOME # Exit the Car in the Home zone req = await geofency_client.post(url, data=data) @@ -289,7 +289,7 @@ async def test_beacon_enter_and_exit_car(hass, geofency_client, webhook_id): assert req.status == HTTP_OK device_name = slugify(f"beacon_{data['name']}") state_name = hass.states.get(f"device_tracker.{device_name}").state - assert STATE_HOME == state_name + assert state_name == STATE_HOME async def test_load_unload_entry(hass, geofency_client, webhook_id): @@ -302,7 +302,7 @@ async def test_load_unload_entry(hass, geofency_client, webhook_id): assert req.status == HTTP_OK device_name = slugify(GPS_ENTER_HOME["device"]) state_1 = hass.states.get(f"device_tracker.{device_name}") - assert STATE_HOME == state_1.state + assert state_1.state == STATE_HOME assert len(hass.data[DOMAIN]["devices"]) == 1 entry = hass.config_entries.async_entries(DOMAIN)[0] @@ -318,6 +318,6 @@ async def test_load_unload_entry(hass, geofency_client, webhook_id): assert state_2 is not None assert state_1 is not state_2 - assert STATE_HOME == state_2.state + assert state_2.state == STATE_HOME assert state_2.attributes[ATTR_LATITUDE] == HOME_LATITUDE assert state_2.attributes[ATTR_LONGITUDE] == HOME_LONGITUDE diff --git a/tests/components/google_pubsub/test_init.py b/tests/components/google_pubsub/test_init.py index c174e454701..fc1fecb04ed 100644 --- a/tests/components/google_pubsub/test_init.py +++ b/tests/components/google_pubsub/test_init.py @@ -82,7 +82,7 @@ async def test_minimal_config(hass, mock_client): assert await async_setup_component(hass, google_pubsub.DOMAIN, config) await hass.async_block_till_done() assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED assert mock_client.PublisherClient.from_service_account_json.call_count == 1 assert ( mock_client.PublisherClient.from_service_account_json.call_args[0][0] == "path" @@ -109,7 +109,7 @@ async def test_full_config(hass, mock_client): assert await async_setup_component(hass, google_pubsub.DOMAIN, config) await hass.async_block_till_done() assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED assert mock_client.PublisherClient.from_service_account_json.call_count == 1 assert ( mock_client.PublisherClient.from_service_account_json.call_args[0][0] == "path" diff --git a/tests/components/google_wifi/test_sensor.py b/tests/components/google_wifi/test_sensor.py index 06ad5e0c3ea..6f4b4652e76 100644 --- a/tests/components/google_wifi/test_sensor.py +++ b/tests/components/google_wifi/test_sensor.py @@ -129,13 +129,13 @@ def test_state(hass, requests_mock): fake_delay(hass, 2) sensor.update() if name == google_wifi.ATTR_LAST_RESTART: - assert "1969-12-31 00:00:00" == sensor.state + assert sensor.state == "1969-12-31 00:00:00" elif name == google_wifi.ATTR_UPTIME: - assert 1 == sensor.state + assert sensor.state == 1 elif name == google_wifi.ATTR_STATUS: - assert "Online" == sensor.state + assert sensor.state == "Online" else: - assert "initial" == sensor.state + assert sensor.state == "initial" def test_update_when_value_is_none(hass, requests_mock): @@ -158,17 +158,17 @@ def test_update_when_value_changed(hass, requests_mock): fake_delay(hass, 2) sensor.update() if name == google_wifi.ATTR_LAST_RESTART: - assert "1969-12-30 00:00:00" == sensor.state + assert sensor.state == "1969-12-30 00:00:00" elif name == google_wifi.ATTR_UPTIME: - assert 2 == sensor.state + assert sensor.state == 2 elif name == google_wifi.ATTR_STATUS: - assert "Offline" == sensor.state + assert sensor.state == "Offline" elif name == google_wifi.ATTR_NEW_VERSION: - assert "Latest" == sensor.state + assert sensor.state == "Latest" elif name == google_wifi.ATTR_LOCAL_IP: - assert STATE_UNKNOWN == sensor.state + assert sensor.state == STATE_UNKNOWN else: - assert "next" == sensor.state + assert sensor.state == "next" def test_when_api_data_missing(hass, requests_mock): @@ -180,7 +180,7 @@ def test_when_api_data_missing(hass, requests_mock): sensor = sensor_dict[name]["sensor"] fake_delay(hass, 2) sensor.update() - assert STATE_UNKNOWN == sensor.state + assert sensor.state == STATE_UNKNOWN def test_update_when_unavailable(requests_mock): diff --git a/tests/components/gpslogger/test_init.py b/tests/components/gpslogger/test_init.py index 8a4880c4878..1dad262a285 100644 --- a/tests/components/gpslogger/test_init.py +++ b/tests/components/gpslogger/test_init.py @@ -117,14 +117,14 @@ async def test_enter_and_exit(hass, gpslogger_client, webhook_id): await hass.async_block_till_done() assert req.status == HTTP_OK state_name = hass.states.get(f"{DEVICE_TRACKER_DOMAIN}.{data['device']}").state - assert STATE_HOME == state_name + assert state_name == STATE_HOME # Enter Home again req = await gpslogger_client.post(url, data=data) await hass.async_block_till_done() assert req.status == HTTP_OK state_name = hass.states.get(f"{DEVICE_TRACKER_DOMAIN}.{data['device']}").state - assert STATE_HOME == state_name + assert state_name == STATE_HOME data["longitude"] = 0 data["latitude"] = 0 @@ -134,7 +134,7 @@ async def test_enter_and_exit(hass, gpslogger_client, webhook_id): await hass.async_block_till_done() assert req.status == HTTP_OK state_name = hass.states.get(f"{DEVICE_TRACKER_DOMAIN}.{data['device']}").state - assert STATE_NOT_HOME == state_name + assert state_name == STATE_NOT_HOME dev_reg = dr.async_get(hass) assert len(dev_reg.devices) == 1 @@ -213,7 +213,7 @@ async def test_load_unload_entry(hass, gpslogger_client, webhook_id): await hass.async_block_till_done() assert req.status == HTTP_OK state_name = hass.states.get(f"{DEVICE_TRACKER_DOMAIN}.{data['device']}").state - assert STATE_HOME == state_name + assert state_name == STATE_HOME assert len(hass.data[DATA_DISPATCHER][TRACKER_UPDATE]) == 1 entry = hass.config_entries.async_entries(DOMAIN)[0] diff --git a/tests/components/group/test_init.py b/tests/components/group/test_init.py index d68fdd7f717..fff1526b711 100644 --- a/tests/components/group/test_init.py +++ b/tests/components/group/test_init.py @@ -38,7 +38,7 @@ async def test_setup_group_with_mixed_groupable_states(hass): await hass.async_block_till_done() - assert STATE_ON == hass.states.get(f"{group.DOMAIN}.person_and_light").state + assert hass.states.get(f"{group.DOMAIN}.person_and_light").state == STATE_ON async def test_setup_group_with_a_non_existing_state(hass): @@ -51,7 +51,7 @@ async def test_setup_group_with_a_non_existing_state(hass): hass, "light_and_nothing", ["light.Bowl", "non.existing"] ) - assert STATE_ON == grp.state + assert grp.state == STATE_ON async def test_setup_group_with_non_groupable_states(hass): @@ -90,7 +90,7 @@ async def test_monitor_group(hass): assert test_group.entity_id in hass.states.async_entity_ids() group_state = hass.states.get(test_group.entity_id) - assert STATE_ON == group_state.state + assert group_state.state == STATE_ON assert group_state.attributes.get(group.ATTR_AUTO) @@ -108,7 +108,7 @@ async def test_group_turns_off_if_all_off(hass): await hass.async_block_till_done() group_state = hass.states.get(test_group.entity_id) - assert STATE_OFF == group_state.state + assert group_state.state == STATE_OFF async def test_group_turns_on_if_all_are_off_and_one_turns_on(hass): @@ -127,7 +127,7 @@ async def test_group_turns_on_if_all_are_off_and_one_turns_on(hass): await hass.async_block_till_done() group_state = hass.states.get(test_group.entity_id) - assert STATE_ON == group_state.state + assert group_state.state == STATE_ON async def test_allgroup_stays_off_if_all_are_off_and_one_turns_on(hass): @@ -146,7 +146,7 @@ async def test_allgroup_stays_off_if_all_are_off_and_one_turns_on(hass): await hass.async_block_till_done() group_state = hass.states.get(test_group.entity_id) - assert STATE_OFF == group_state.state + assert group_state.state == STATE_OFF async def test_allgroup_turn_on_if_last_turns_on(hass): @@ -165,7 +165,7 @@ async def test_allgroup_turn_on_if_last_turns_on(hass): await hass.async_block_till_done() group_state = hass.states.get(test_group.entity_id) - assert STATE_ON == group_state.state + assert group_state.state == STATE_ON async def test_expand_entity_ids(hass): @@ -287,7 +287,7 @@ async def test_group_being_init_before_first_tracked_state_is_set_to_on(hass): await hass.async_block_till_done() group_state = hass.states.get(test_group.entity_id) - assert STATE_ON == group_state.state + assert group_state.state == STATE_ON async def test_group_being_init_before_first_tracked_state_is_set_to_off(hass): @@ -306,7 +306,7 @@ async def test_group_being_init_before_first_tracked_state_is_set_to_off(hass): await hass.async_block_till_done() group_state = hass.states.get(test_group.entity_id) - assert STATE_OFF == group_state.state + assert group_state.state == STATE_OFF async def test_groups_get_unique_names(hass): @@ -385,7 +385,7 @@ async def test_group_updated_after_device_tracker_zone_change(hass): hass.states.async_set("device_tracker.Adam", "cool_state_not_home") await hass.async_block_till_done() - assert STATE_NOT_HOME == hass.states.get(f"{group.DOMAIN}.peeps").state + assert hass.states.get(f"{group.DOMAIN}.peeps").state == STATE_NOT_HOME async def test_is_on(hass): @@ -517,20 +517,20 @@ async def test_setup(hass): await hass.async_block_till_done() group_state = hass.states.get(f"{group.DOMAIN}.created_group") - assert STATE_ON == group_state.state + assert group_state.state == STATE_ON assert {test_group.entity_id, "light.bowl"} == set( group_state.attributes["entity_id"] ) assert group_state.attributes.get(group.ATTR_AUTO) is None - assert "mdi:work" == group_state.attributes.get(ATTR_ICON) - assert 3 == group_state.attributes.get(group.ATTR_ORDER) + assert group_state.attributes.get(ATTR_ICON) == "mdi:work" + assert group_state.attributes.get(group.ATTR_ORDER) == 3 group_state = hass.states.get(f"{group.DOMAIN}.test_group") - assert STATE_UNKNOWN == group_state.state - assert {"sensor.happy", "hello.world"} == set(group_state.attributes["entity_id"]) + assert group_state.state == STATE_UNKNOWN + assert set(group_state.attributes["entity_id"]) == {"sensor.happy", "hello.world"} assert group_state.attributes.get(group.ATTR_AUTO) is None assert group_state.attributes.get(ATTR_ICON) is None - assert 0 == group_state.attributes.get(group.ATTR_ORDER) + assert group_state.attributes.get(group.ATTR_ORDER) == 0 async def test_service_group_services(hass): diff --git a/tests/components/homeassistant/test_init.py b/tests/components/homeassistant/test_init.py index 51646ae7139..2e2eaf991af 100644 --- a/tests/components/homeassistant/test_init.py +++ b/tests/components/homeassistant/test_init.py @@ -137,28 +137,28 @@ class TestComponentsCore(unittest.TestCase): calls = mock_service(self.hass, "light", SERVICE_TURN_ON) turn_on(self.hass) self.hass.block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 def test_turn_on(self): """Test turn_on method.""" calls = mock_service(self.hass, "light", SERVICE_TURN_ON) turn_on(self.hass, "light.Ceiling") self.hass.block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 def test_turn_off(self): """Test turn_off method.""" calls = mock_service(self.hass, "light", SERVICE_TURN_OFF) turn_off(self.hass, "light.Bowl") self.hass.block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 def test_toggle(self): """Test toggle method.""" calls = mock_service(self.hass, "light", SERVICE_TOGGLE) toggle(self.hass, "light.Bowl") self.hass.block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 @patch("homeassistant.config.os.path.isfile", Mock(return_value=True)) def test_reload_core_conf(self): diff --git a/tests/components/homeassistant/triggers/test_state.py b/tests/components/homeassistant/triggers/test_state.py index 2cf2081f018..14120fe94df 100644 --- a/tests/components/homeassistant/triggers/test_state.py +++ b/tests/components/homeassistant/triggers/test_state.py @@ -506,7 +506,7 @@ async def test_if_fires_on_entity_change_with_for(hass, calls): await hass.async_block_till_done() async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_entity_change_with_for_without_to(hass, calls): diff --git a/tests/components/honeywell/test_climate.py b/tests/components/honeywell/test_climate.py index ee107d4985e..d97bbc86ed6 100644 --- a/tests/components/honeywell/test_climate.py +++ b/tests/components/honeywell/test_climate.py @@ -181,7 +181,7 @@ class TestHoneywell(unittest.TestCase): mock.call(mock_evo.return_value, "bar", False, 20.0), ] ) - assert 2 == add_entities.call_count + assert add_entities.call_count == 2 @mock.patch("evohomeclient.EvohomeClient") @mock.patch("homeassistant.components.honeywell.climate.HoneywellUSThermostat") @@ -269,15 +269,15 @@ class TestHoneywellRound(unittest.TestCase): def test_attributes(self): """Test the attributes.""" - assert "House" == self.round1.name - assert TEMP_CELSIUS == self.round1.temperature_unit - assert 20 == self.round1.current_temperature - assert 21 == self.round1.target_temperature + assert self.round1.name == "House" + assert self.round1.temperature_unit == TEMP_CELSIUS + assert self.round1.current_temperature == 20 + assert self.round1.target_temperature == 21 assert not self.round1.is_away_mode_on - assert "Hot Water" == self.round2.name - assert TEMP_CELSIUS == self.round2.temperature_unit - assert 21 == self.round2.current_temperature + assert self.round2.name == "Hot Water" + assert self.round2.temperature_unit == TEMP_CELSIUS + assert self.round2.current_temperature == 21 assert self.round2.target_temperature is None assert not self.round2.is_away_mode_on @@ -304,12 +304,12 @@ class TestHoneywellRound(unittest.TestCase): def test_set_hvac_mode(self) -> None: """Test setting the system operation.""" self.round1.set_hvac_mode("cool") - assert "cool" == self.round1.current_operation - assert "cool" == self.device.system_mode + assert self.round1.current_operation == "cool" + assert self.device.system_mode == "cool" self.round1.set_hvac_mode("heat") - assert "heat" == self.round1.current_operation - assert "heat" == self.device.system_mode + assert self.round1.current_operation == "heat" + assert self.device.system_mode == "heat" class TestHoneywellUS(unittest.TestCase): @@ -342,40 +342,40 @@ class TestHoneywellUS(unittest.TestCase): def test_properties(self): """Test the properties.""" assert self.honeywell.is_fan_on - assert "test" == self.honeywell.name - assert 72 == self.honeywell.current_temperature + assert self.honeywell.name == "test" + assert self.honeywell.current_temperature == 72 def test_unit_of_measurement(self): """Test the unit of measurement.""" - assert TEMP_FAHRENHEIT == self.honeywell.temperature_unit + assert self.honeywell.temperature_unit == TEMP_FAHRENHEIT self.device.temperature_unit = "C" - assert TEMP_CELSIUS == self.honeywell.temperature_unit + assert self.honeywell.temperature_unit == TEMP_CELSIUS def test_target_temp(self): """Test the target temperature.""" - assert 65 == self.honeywell.target_temperature + assert self.honeywell.target_temperature == 65 self.device.system_mode = "cool" - assert 78 == self.honeywell.target_temperature + assert self.honeywell.target_temperature == 78 def test_set_temp(self): """Test setting the temperature.""" self.honeywell.set_temperature(temperature=70) - assert 70 == self.device.setpoint_heat - assert 70 == self.honeywell.target_temperature + assert self.device.setpoint_heat == 70 + assert self.honeywell.target_temperature == 70 self.device.system_mode = "cool" - assert 78 == self.honeywell.target_temperature + assert self.honeywell.target_temperature == 78 self.honeywell.set_temperature(temperature=74) - assert 74 == self.device.setpoint_cool - assert 74 == self.honeywell.target_temperature + assert self.device.setpoint_cool == 74 + assert self.honeywell.target_temperature == 74 def test_set_hvac_mode(self) -> None: """Test setting the operation mode.""" self.honeywell.set_hvac_mode("cool") - assert "cool" == self.device.system_mode + assert self.device.system_mode == "cool" self.honeywell.set_hvac_mode("heat") - assert "heat" == self.device.system_mode + assert self.device.system_mode == "heat" def test_set_temp_fail(self): """Test if setting the temperature fails.""" @@ -395,7 +395,7 @@ class TestHoneywellUS(unittest.TestCase): assert expected == self.honeywell.extra_state_attributes expected["fan"] = "idle" self.device.fan_running = False - assert expected == self.honeywell.extra_state_attributes + assert self.honeywell.extra_state_attributes == expected def test_with_no_fan(self): """Test if there is on fan.""" @@ -407,7 +407,7 @@ class TestHoneywellUS(unittest.TestCase): ATTR_FAN_MODES: somecomfort.FAN_MODES, ATTR_HVAC_MODES: somecomfort.SYSTEM_MODES, } - assert expected == self.honeywell.extra_state_attributes + assert self.honeywell.extra_state_attributes == expected def test_heat_away_mode(self): """Test setting the heat away mode.""" diff --git a/tests/components/imap_email_content/test_sensor.py b/tests/components/imap_email_content/test_sensor.py index 101896f4a33..aa25d4f85ff 100644 --- a/tests/components/imap_email_content/test_sensor.py +++ b/tests/components/imap_email_content/test_sensor.py @@ -47,10 +47,10 @@ async def test_allowed_sender(hass): sensor.entity_id = "sensor.emailtest" sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() - assert "Test" == sensor.state - assert "Test Message" == sensor.extra_state_attributes["body"] - assert "sender@test.com" == sensor.extra_state_attributes["from"] - assert "Test" == sensor.extra_state_attributes["subject"] + assert sensor.state == "Test" + assert sensor.extra_state_attributes["body"] == "Test Message" + assert sensor.extra_state_attributes["from"] == "sender@test.com" + assert sensor.extra_state_attributes["subject"] == "Test" assert ( datetime.datetime(2016, 1, 1, 12, 44, 57) == sensor.extra_state_attributes["date"] @@ -83,8 +83,8 @@ async def test_multi_part_with_text(hass): sensor.entity_id = "sensor.emailtest" sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() - assert "Link" == sensor.state - assert "Test Message" == sensor.extra_state_attributes["body"] + assert sensor.state == "Link" + assert sensor.extra_state_attributes["body"] == "Test Message" async def test_multi_part_only_html(hass): @@ -110,10 +110,10 @@ async def test_multi_part_only_html(hass): sensor.entity_id = "sensor.emailtest" sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() - assert "Link" == sensor.state + assert sensor.state == "Link" assert ( - "Test Message" - == sensor.extra_state_attributes["body"] + sensor.extra_state_attributes["body"] + == "Test Message" ) @@ -140,8 +140,8 @@ async def test_multi_part_only_other_text(hass): sensor.entity_id = "sensor.emailtest" sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() - assert "Link" == sensor.state - assert "Test Message" == sensor.extra_state_attributes["body"] + assert sensor.state == "Link" + assert sensor.extra_state_attributes["body"] == "Test Message" async def test_multiple_emails(hass): @@ -180,10 +180,10 @@ async def test_multiple_emails(hass): sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() - assert "Test" == states[0].state - assert "Test 2" == states[1].state + assert states[0].state == "Test" + assert states[1].state == "Test 2" - assert "Test Message 2" == sensor.extra_state_attributes["body"] + assert sensor.extra_state_attributes["body"] == "Test Message 2" async def test_sender_not_allowed(hass): @@ -227,4 +227,4 @@ async def test_template(hass): sensor.entity_id = "sensor.emailtest" sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() - assert "Test from sender@test.com with message Test Message" == sensor.state + assert sensor.state == "Test from sender@test.com with message Test Message" diff --git a/tests/components/influxdb/test_init.py b/tests/components/influxdb/test_init.py index fd43091f457..6c560eec5e7 100644 --- a/tests/components/influxdb/test_init.py +++ b/tests/components/influxdb/test_init.py @@ -127,7 +127,7 @@ async def test_setup_config_full(hass, mock_client, config_ext, get_write_api): assert await async_setup_component(hass, influxdb.DOMAIN, config) await hass.async_block_till_done() assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED assert get_write_api(mock_client).call_count == 1 @@ -260,7 +260,7 @@ async def test_setup_config_ssl( await hass.async_block_till_done() assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED assert expected_client_args.items() <= mock_client.call_args.kwargs.items() @@ -280,7 +280,7 @@ async def test_setup_minimal_config(hass, mock_client, config_ext, get_write_api assert await async_setup_component(hass, influxdb.DOMAIN, config) await hass.async_block_till_done() assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED assert get_write_api(mock_client).call_count == 1 diff --git a/tests/components/input_boolean/test_init.py b/tests/components/input_boolean/test_init.py index 6534006dd81..2b7a1f88ef1 100644 --- a/tests/components/input_boolean/test_init.py +++ b/tests/components/input_boolean/test_init.py @@ -109,13 +109,13 @@ async def test_config_options(hass): assert state_1 is not None assert state_2 is not None - assert STATE_OFF == state_1.state + assert state_1.state == STATE_OFF assert ATTR_ICON not in state_1.attributes assert ATTR_FRIENDLY_NAME not in state_1.attributes - assert STATE_ON == state_2.state - assert "Hello World" == state_2.attributes.get(ATTR_FRIENDLY_NAME) - assert "mdi:work" == state_2.attributes.get(ATTR_ICON) + assert state_2.state == STATE_ON + assert state_2.attributes.get(ATTR_FRIENDLY_NAME) == "Hello World" + assert state_2.attributes.get(ATTR_ICON) == "mdi:work" async def test_restore_state(hass): @@ -218,7 +218,7 @@ async def test_reload(hass, hass_admin_user): assert state_1 is not None assert state_2 is not None assert state_3 is None - assert STATE_ON == state_2.state + assert state_2.state == STATE_ON assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_1") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None @@ -259,9 +259,9 @@ async def test_reload(hass, hass_admin_user): assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is not None - assert STATE_ON == state_2.state # reload is not supposed to change entity state - assert "Hello World reloaded" == state_2.attributes.get(ATTR_FRIENDLY_NAME) - assert "mdi:work_reloaded" == state_2.attributes.get(ATTR_ICON) + assert state_2.state == STATE_ON # reload is not supposed to change entity state + assert state_2.attributes.get(ATTR_FRIENDLY_NAME) == "Hello World reloaded" + assert state_2.attributes.get(ATTR_ICON) == "mdi:work_reloaded" async def test_load_from_storage(hass, storage_setup): diff --git a/tests/components/input_number/test_init.py b/tests/components/input_number/test_init.py index 78fa54b03be..d6d80a9ad87 100644 --- a/tests/components/input_number/test_init.py +++ b/tests/components/input_number/test_init.py @@ -116,17 +116,17 @@ async def test_set_value(hass, caplog): entity_id = "input_number.test_1" state = hass.states.get(entity_id) - assert 50 == float(state.state) + assert float(state.state) == 50 await set_value(hass, entity_id, "30.4") state = hass.states.get(entity_id) - assert 30.4 == float(state.state) + assert float(state.state) == 30.4 await set_value(hass, entity_id, "70") state = hass.states.get(entity_id) - assert 70 == float(state.state) + assert float(state.state) == 70 with pytest.raises(vol.Invalid) as excinfo: await set_value(hass, entity_id, "110") @@ -136,7 +136,7 @@ async def test_set_value(hass, caplog): ) state = hass.states.get(entity_id) - assert 70 == float(state.state) + assert float(state.state) == 70 async def test_increment(hass): @@ -147,19 +147,19 @@ async def test_increment(hass): entity_id = "input_number.test_2" state = hass.states.get(entity_id) - assert 50 == float(state.state) + assert float(state.state) == 50 await increment(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 51 == float(state.state) + assert float(state.state) == 51 await increment(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 51 == float(state.state) + assert float(state.state) == 51 async def test_decrement(hass): @@ -170,19 +170,19 @@ async def test_decrement(hass): entity_id = "input_number.test_3" state = hass.states.get(entity_id) - assert 50 == float(state.state) + assert float(state.state) == 50 await decrement(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 49 == float(state.state) + assert float(state.state) == 49 await decrement(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 49 == float(state.state) + assert float(state.state) == 49 async def test_mode(hass): @@ -201,15 +201,15 @@ async def test_mode(hass): state = hass.states.get("input_number.test_default_slider") assert state - assert "slider" == state.attributes["mode"] + assert state.attributes["mode"] == "slider" state = hass.states.get("input_number.test_explicit_box") assert state - assert "box" == state.attributes["mode"] + assert state.attributes["mode"] == "box" state = hass.states.get("input_number.test_explicit_slider") assert state - assert "slider" == state.attributes["mode"] + assert state.attributes["mode"] == "slider" async def test_restore_state(hass): @@ -322,8 +322,8 @@ async def test_reload(hass, hass_admin_user, hass_read_only_user): assert state_1 is not None assert state_2 is None assert state_3 is not None - assert 50 == float(state_1.state) - assert 10 == float(state_3.state) + assert float(state_1.state) == 50 + assert float(state_3.state) == 10 assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_1") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is not None @@ -362,8 +362,8 @@ async def test_reload(hass, hass_admin_user, hass_read_only_user): assert state_1 is not None assert state_2 is not None assert state_3 is None - assert 50 == float(state_1.state) - assert 20 == float(state_2.state) + assert float(state_1.state) == 50 + assert float(state_2.state) == 20 assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_1") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is None diff --git a/tests/components/input_select/test_init.py b/tests/components/input_select/test_init.py index 70663f71e7a..f5f7956e9c5 100644 --- a/tests/components/input_select/test_init.py +++ b/tests/components/input_select/test_init.py @@ -156,19 +156,19 @@ async def test_select_option(hass): entity_id = "input_select.test_1" state = hass.states.get(entity_id) - assert "some option" == state.state + assert state.state == "some option" select_option(hass, entity_id, "another option") await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "another option" == state.state + assert state.state == "another option" select_option(hass, entity_id, "non existing option") await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "another option" == state.state + assert state.state == "another option" async def test_select_next(hass): @@ -188,19 +188,19 @@ async def test_select_next(hass): entity_id = "input_select.test_1" state = hass.states.get(entity_id) - assert "middle option" == state.state + assert state.state == "middle option" select_next(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "last option" == state.state + assert state.state == "last option" select_next(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "first option" == state.state + assert state.state == "first option" async def test_select_previous(hass): @@ -220,19 +220,19 @@ async def test_select_previous(hass): entity_id = "input_select.test_1" state = hass.states.get(entity_id) - assert "middle option" == state.state + assert state.state == "middle option" select_previous(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "first option" == state.state + assert state.state == "first option" select_previous(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "last option" == state.state + assert state.state == "last option" async def test_select_first_last(hass): @@ -252,19 +252,19 @@ async def test_select_first_last(hass): entity_id = "input_select.test_1" state = hass.states.get(entity_id) - assert "middle option" == state.state + assert state.state == "middle option" select_first(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "first option" == state.state + assert state.state == "first option" select_last(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "last option" == state.state + assert state.state == "last option" async def test_config_options(hass): @@ -297,14 +297,14 @@ async def test_config_options(hass): assert state_1 is not None assert state_2 is not None - assert "1" == state_1.state - assert ["1", "2"] == state_1.attributes.get(ATTR_OPTIONS) + assert state_1.state == "1" + assert state_1.attributes.get(ATTR_OPTIONS) == ["1", "2"] assert ATTR_ICON not in state_1.attributes - assert "Better Option" == state_2.state - assert test_2_options == state_2.attributes.get(ATTR_OPTIONS) - assert "Hello World" == state_2.attributes.get(ATTR_FRIENDLY_NAME) - assert "mdi:work" == state_2.attributes.get(ATTR_ICON) + assert state_2.state == "Better Option" + assert state_2.attributes.get(ATTR_OPTIONS) == test_2_options + assert state_2.attributes.get(ATTR_FRIENDLY_NAME) == "Hello World" + assert state_2.attributes.get(ATTR_ICON) == "mdi:work" async def test_set_options_service(hass): @@ -324,24 +324,24 @@ async def test_set_options_service(hass): entity_id = "input_select.test_1" state = hass.states.get(entity_id) - assert "middle option" == state.state + assert state.state == "middle option" data = {ATTR_OPTIONS: ["test1", "test2"], "entity_id": entity_id} await hass.services.async_call(DOMAIN, SERVICE_SET_OPTIONS, data) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "test1" == state.state + assert state.state == "test1" select_option(hass, entity_id, "first option") await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "test1" == state.state + assert state.state == "test1" select_option(hass, entity_id, "test2") await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "test2" == state.state + assert state.state == "test2" async def test_restore_state(hass): @@ -453,8 +453,8 @@ async def test_reload(hass, hass_admin_user, hass_read_only_user): assert state_1 is not None assert state_2 is not None assert state_3 is None - assert "middle option" == state_1.state - assert "an option" == state_2.state + assert state_1.state == "middle option" + assert state_2.state == "an option" assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_1") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is None @@ -499,8 +499,8 @@ async def test_reload(hass, hass_admin_user, hass_read_only_user): assert state_1 is None assert state_2 is not None assert state_3 is not None - assert "an option" == state_2.state - assert "newer option" == state_3.state + assert state_2.state == "an option" + assert state_3.state == "newer option" assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_1") is None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is not None diff --git a/tests/components/logentries/test_init.py b/tests/components/logentries/test_init.py index 3ae25d521cb..96632865af0 100644 --- a/tests/components/logentries/test_init.py +++ b/tests/components/logentries/test_init.py @@ -15,7 +15,7 @@ async def test_setup_config_full(hass): hass.bus.listen = MagicMock() assert await async_setup_component(hass, logentries.DOMAIN, config) assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED async def test_setup_config_defaults(hass): @@ -24,7 +24,7 @@ async def test_setup_config_defaults(hass): hass.bus.listen = MagicMock() assert await async_setup_component(hass, logentries.DOMAIN, config) assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED @pytest.fixture diff --git a/tests/components/mailbox/test_init.py b/tests/components/mailbox/test_init.py index a2b2583bdb5..75ecc8d9db3 100644 --- a/tests/components/mailbox/test_init.py +++ b/tests/components/mailbox/test_init.py @@ -23,7 +23,8 @@ async def test_get_platforms_from_mailbox(mock_http_client): req = await mock_http_client.get(url) assert req.status == 200 result = await req.json() - assert len(result) == 1 and "DemoMailbox" == result[0].get("name", None) + assert len(result) == 1 + assert result[0].get("name") == "DemoMailbox" async def test_get_messages_from_mailbox(mock_http_client): diff --git a/tests/components/manual/test_alarm_control_panel.py b/tests/components/manual/test_alarm_control_panel.py index ee3d97e52d7..f3cf3ccce39 100644 --- a/tests/components/manual/test_alarm_control_panel.py +++ b/tests/components/manual/test_alarm_control_panel.py @@ -51,11 +51,11 @@ async def test_arm_home_no_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, CODE) - assert STATE_ALARM_ARMED_HOME == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_HOME async def test_arm_home_no_pending_when_code_not_req(hass): @@ -78,11 +78,11 @@ async def test_arm_home_no_pending_when_code_not_req(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, 0) - assert STATE_ALARM_ARMED_HOME == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_HOME async def test_arm_home_with_pending(hass): @@ -104,11 +104,11 @@ async def test_arm_home_with_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, CODE, entity_id) - assert STATE_ALARM_ARMING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMING state = hass.states.get(entity_id) assert state.attributes["next_state"] == STATE_ALARM_ARMED_HOME @@ -144,11 +144,11 @@ async def test_arm_home_with_invalid_code(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, CODE + "2") - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_arm_away_no_pending(hass): @@ -170,11 +170,11 @@ async def test_arm_away_no_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE, entity_id) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_arm_away_no_pending_when_code_not_req(hass): @@ -197,11 +197,11 @@ async def test_arm_away_no_pending_when_code_not_req(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, 0, entity_id) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_arm_home_with_template_code(hass): @@ -223,12 +223,12 @@ async def test_arm_home_with_template_code(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, "abc") state = hass.states.get(entity_id) - assert STATE_ALARM_ARMED_HOME == state.state + assert state.state == STATE_ALARM_ARMED_HOME async def test_arm_away_with_pending(hass): @@ -250,11 +250,11 @@ async def test_arm_away_with_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) - assert STATE_ALARM_ARMING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMING state = hass.states.get(entity_id) assert state.attributes["next_state"] == STATE_ALARM_ARMED_AWAY @@ -290,11 +290,11 @@ async def test_arm_away_with_invalid_code(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE + "2") - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_arm_night_no_pending(hass): @@ -316,11 +316,11 @@ async def test_arm_night_no_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_night(hass, CODE) - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT async def test_arm_night_no_pending_when_code_not_req(hass): @@ -343,11 +343,11 @@ async def test_arm_night_no_pending_when_code_not_req(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_night(hass, 0) - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT async def test_arm_night_with_pending(hass): @@ -369,11 +369,11 @@ async def test_arm_night_with_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_night(hass, CODE, entity_id) - assert STATE_ALARM_ARMING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMING state = hass.states.get(entity_id) assert state.attributes["next_state"] == STATE_ALARM_ARMED_NIGHT @@ -392,7 +392,7 @@ async def test_arm_night_with_pending(hass): # Do not go to the pending state when updating to the same state await common.async_alarm_arm_night(hass, CODE, entity_id) - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT async def test_arm_night_with_invalid_code(hass): @@ -414,11 +414,11 @@ async def test_arm_night_with_invalid_code(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_night(hass, CODE + "2") - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_no_pending(hass): @@ -439,11 +439,11 @@ async def test_trigger_no_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING future = dt_util.utcnow() + timedelta(seconds=60) with patch( @@ -453,7 +453,7 @@ async def test_trigger_no_pending(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED async def test_trigger_with_delay(hass): @@ -476,17 +476,17 @@ async def test_trigger_with_delay(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_TRIGGERED == state.attributes["next_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["next_state"] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -497,7 +497,7 @@ async def test_trigger_with_delay(hass): await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_TRIGGERED == state.state + assert state.state == STATE_ALARM_TRIGGERED async def test_trigger_zero_trigger_time(hass): @@ -519,11 +519,11 @@ async def test_trigger_zero_trigger_time(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_zero_trigger_time_with_pending(hass): @@ -545,11 +545,11 @@ async def test_trigger_zero_trigger_time_with_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_pending(hass): @@ -571,11 +571,11 @@ async def test_trigger_with_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING state = hass.states.get(entity_id) assert state.attributes["next_state"] == STATE_ALARM_TRIGGERED @@ -624,17 +624,17 @@ async def test_trigger_with_unused_specific_delay(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_TRIGGERED == state.attributes["next_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["next_state"] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -669,17 +669,17 @@ async def test_trigger_with_specific_delay(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_TRIGGERED == state.attributes["next_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["next_state"] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -713,11 +713,11 @@ async def test_trigger_with_pending_and_delay(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) @@ -770,11 +770,11 @@ async def test_trigger_with_pending_and_specific_delay(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) @@ -826,7 +826,7 @@ async def test_armed_home_with_specific_pending(hass): await common.async_alarm_arm_home(hass) - assert STATE_ALARM_ARMING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMING future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -836,7 +836,7 @@ async def test_armed_home_with_specific_pending(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_HOME == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_HOME async def test_armed_away_with_specific_pending(hass): @@ -859,7 +859,7 @@ async def test_armed_away_with_specific_pending(hass): await common.async_alarm_arm_away(hass) - assert STATE_ALARM_ARMING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMING future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -869,7 +869,7 @@ async def test_armed_away_with_specific_pending(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_armed_night_with_specific_pending(hass): @@ -892,7 +892,7 @@ async def test_armed_night_with_specific_pending(hass): await common.async_alarm_arm_night(hass) - assert STATE_ALARM_ARMING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMING future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -902,7 +902,7 @@ async def test_armed_night_with_specific_pending(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT async def test_trigger_with_specific_pending(hass): @@ -927,7 +927,7 @@ async def test_trigger_with_specific_pending(hass): await common.async_alarm_trigger(hass) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -937,7 +937,7 @@ async def test_trigger_with_specific_pending(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -947,7 +947,7 @@ async def test_trigger_with_specific_pending(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_disarm_after_trigger(hass): @@ -969,11 +969,11 @@ async def test_trigger_with_disarm_after_trigger(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -983,7 +983,7 @@ async def test_trigger_with_disarm_after_trigger(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_zero_specific_trigger_time(hass): @@ -1006,11 +1006,11 @@ async def test_trigger_with_zero_specific_trigger_time(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_unused_zero_specific_trigger_time(hass): @@ -1033,11 +1033,11 @@ async def test_trigger_with_unused_zero_specific_trigger_time(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -1047,7 +1047,7 @@ async def test_trigger_with_unused_zero_specific_trigger_time(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_specific_trigger_time(hass): @@ -1069,11 +1069,11 @@ async def test_trigger_with_specific_trigger_time(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -1083,7 +1083,7 @@ async def test_trigger_with_specific_trigger_time(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_no_disarm_after_trigger(hass): @@ -1106,15 +1106,15 @@ async def test_trigger_with_no_disarm_after_trigger(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE, entity_id) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -1124,7 +1124,7 @@ async def test_trigger_with_no_disarm_after_trigger(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass): @@ -1147,15 +1147,15 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE, entity_id) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -1165,11 +1165,11 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -1179,7 +1179,7 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_disarm_while_pending_trigger(hass): @@ -1200,15 +1200,15 @@ async def test_disarm_while_pending_trigger(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING await common.async_alarm_disarm(hass, entity_id=entity_id) - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -1218,7 +1218,7 @@ async def test_disarm_while_pending_trigger(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_disarm_during_trigger_with_invalid_code(hass): @@ -1240,15 +1240,15 @@ async def test_disarm_during_trigger_with_invalid_code(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING await common.async_alarm_disarm(hass, entity_id=entity_id) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -1258,7 +1258,7 @@ async def test_disarm_during_trigger_with_invalid_code(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED async def test_disarm_with_template_code(hass): @@ -1280,22 +1280,22 @@ async def test_disarm_with_template_code(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, "def") state = hass.states.get(entity_id) - assert STATE_ALARM_ARMED_HOME == state.state + assert state.state == STATE_ALARM_ARMED_HOME await common.async_alarm_disarm(hass, "def") state = hass.states.get(entity_id) - assert STATE_ALARM_ARMED_HOME == state.state + assert state.state == STATE_ALARM_ARMED_HOME await common.async_alarm_disarm(hass, "abc") state = hass.states.get(entity_id) - assert STATE_ALARM_DISARMED == state.state + assert state.state == STATE_ALARM_DISARMED async def test_arm_custom_bypass_no_pending(hass): @@ -1317,11 +1317,11 @@ async def test_arm_custom_bypass_no_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_custom_bypass(hass, CODE) - assert STATE_ALARM_ARMED_CUSTOM_BYPASS == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_CUSTOM_BYPASS async def test_arm_custom_bypass_no_pending_when_code_not_req(hass): @@ -1344,11 +1344,11 @@ async def test_arm_custom_bypass_no_pending_when_code_not_req(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_custom_bypass(hass, 0) - assert STATE_ALARM_ARMED_CUSTOM_BYPASS == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_CUSTOM_BYPASS async def test_arm_custom_bypass_with_pending(hass): @@ -1370,11 +1370,11 @@ async def test_arm_custom_bypass_with_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_custom_bypass(hass, CODE, entity_id) - assert STATE_ALARM_ARMING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMING state = hass.states.get(entity_id) assert state.attributes["next_state"] == STATE_ALARM_ARMED_CUSTOM_BYPASS @@ -1410,11 +1410,11 @@ async def test_arm_custom_bypass_with_invalid_code(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_custom_bypass(hass, CODE + "2") - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_armed_custom_bypass_with_specific_pending(hass): @@ -1437,7 +1437,7 @@ async def test_armed_custom_bypass_with_specific_pending(hass): await common.async_alarm_arm_custom_bypass(hass) - assert STATE_ALARM_ARMING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMING future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -1447,7 +1447,7 @@ async def test_armed_custom_bypass_with_specific_pending(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_CUSTOM_BYPASS == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_CUSTOM_BYPASS async def test_arm_away_after_disabled_disarmed(hass, legacy_patchable_time): @@ -1472,21 +1472,21 @@ async def test_arm_away_after_disabled_disarmed(hass, legacy_patchable_time): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) state = hass.states.get(entity_id) - assert STATE_ALARM_ARMING == state.state - assert STATE_ALARM_DISARMED == state.attributes["previous_state"] - assert STATE_ALARM_ARMED_AWAY == state.attributes["next_state"] + assert state.state == STATE_ALARM_ARMING + assert state.attributes["previous_state"] == STATE_ALARM_DISARMED + assert state.attributes["next_state"] == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) state = hass.states.get(entity_id) - assert STATE_ALARM_ARMING == state.state - assert STATE_ALARM_DISARMED == state.attributes["previous_state"] - assert STATE_ALARM_ARMED_AWAY == state.attributes["next_state"] + assert state.state == STATE_ALARM_ARMING + assert state.attributes["previous_state"] == STATE_ALARM_DISARMED + assert state.attributes["next_state"] == STATE_ALARM_ARMED_AWAY future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -1497,14 +1497,14 @@ async def test_arm_away_after_disabled_disarmed(hass, legacy_patchable_time): await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_ARMED_AWAY == state.state + assert state.state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_ARMED_AWAY == state.attributes["previous_state"] - assert STATE_ALARM_TRIGGERED == state.attributes["next_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["previous_state"] == STATE_ALARM_ARMED_AWAY + assert state.attributes["next_state"] == STATE_ALARM_TRIGGERED future += timedelta(seconds=1) with patch( @@ -1515,7 +1515,7 @@ async def test_arm_away_after_disabled_disarmed(hass, legacy_patchable_time): await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_TRIGGERED == state.state + assert state.state == STATE_ALARM_TRIGGERED async def test_restore_armed_state(hass): diff --git a/tests/components/manual_mqtt/test_alarm_control_panel.py b/tests/components/manual_mqtt/test_alarm_control_panel.py index 9a98af127ea..05033bb3347 100644 --- a/tests/components/manual_mqtt/test_alarm_control_panel.py +++ b/tests/components/manual_mqtt/test_alarm_control_panel.py @@ -76,12 +76,12 @@ async def test_arm_home_no_pending(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, CODE) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_HOME == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_HOME async def test_arm_home_no_pending_when_code_not_req(hass, mqtt_mock): @@ -106,12 +106,12 @@ async def test_arm_home_no_pending_when_code_not_req(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, 0) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_HOME == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_HOME async def test_arm_home_with_pending(hass, mqtt_mock): @@ -135,12 +135,12 @@ async def test_arm_home_with_pending(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, CODE, entity_id) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING state = hass.states.get(entity_id) assert state.attributes["post_pending_state"] == STATE_ALARM_ARMED_HOME @@ -153,7 +153,7 @@ async def test_arm_home_with_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_HOME == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_HOME async def test_arm_home_with_invalid_code(hass, mqtt_mock): @@ -177,12 +177,12 @@ async def test_arm_home_with_invalid_code(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, f"{CODE}2") await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_arm_away_no_pending(hass, mqtt_mock): @@ -206,12 +206,12 @@ async def test_arm_away_no_pending(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE, entity_id) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_arm_away_no_pending_when_code_not_req(hass, mqtt_mock): @@ -236,12 +236,12 @@ async def test_arm_away_no_pending_when_code_not_req(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, 0, entity_id) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_arm_home_with_template_code(hass, mqtt_mock): @@ -265,13 +265,13 @@ async def test_arm_home_with_template_code(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, "abc") await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_ARMED_HOME == state.state + assert state.state == STATE_ALARM_ARMED_HOME async def test_arm_away_with_pending(hass, mqtt_mock): @@ -295,12 +295,12 @@ async def test_arm_away_with_pending(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING state = hass.states.get(entity_id) assert state.attributes["post_pending_state"] == STATE_ALARM_ARMED_AWAY @@ -313,7 +313,7 @@ async def test_arm_away_with_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_arm_away_with_invalid_code(hass, mqtt_mock): @@ -337,12 +337,12 @@ async def test_arm_away_with_invalid_code(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, f"{CODE}2") await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_arm_night_no_pending(hass, mqtt_mock): @@ -366,12 +366,12 @@ async def test_arm_night_no_pending(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_night(hass, CODE, entity_id) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT async def test_arm_night_no_pending_when_code_not_req(hass, mqtt_mock): @@ -396,12 +396,12 @@ async def test_arm_night_no_pending_when_code_not_req(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_night(hass, 0, entity_id) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT async def test_arm_night_with_pending(hass, mqtt_mock): @@ -425,12 +425,12 @@ async def test_arm_night_with_pending(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_night(hass, CODE) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING state = hass.states.get(entity_id) assert state.attributes["post_pending_state"] == STATE_ALARM_ARMED_NIGHT @@ -443,13 +443,13 @@ async def test_arm_night_with_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT # Do not go to the pending state when updating to the same state await common.async_alarm_arm_night(hass, CODE, entity_id) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT async def test_arm_night_with_invalid_code(hass, mqtt_mock): @@ -473,12 +473,12 @@ async def test_arm_night_with_invalid_code(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_night(hass, f"{CODE}2") await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_no_pending(hass, mqtt_mock): @@ -501,12 +501,12 @@ async def test_trigger_no_pending(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING future = dt_util.utcnow() + timedelta(seconds=60) with patch( @@ -516,7 +516,7 @@ async def test_trigger_no_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED async def test_trigger_with_delay(hass, mqtt_mock): @@ -541,19 +541,19 @@ async def test_trigger_with_delay(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_TRIGGERED == state.attributes["post_pending_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["post_pending_state"] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -564,7 +564,7 @@ async def test_trigger_with_delay(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_TRIGGERED == state.state + assert state.state == STATE_ALARM_TRIGGERED async def test_trigger_zero_trigger_time(hass, mqtt_mock): @@ -588,12 +588,12 @@ async def test_trigger_zero_trigger_time(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_zero_trigger_time_with_pending(hass, mqtt_mock): @@ -617,12 +617,12 @@ async def test_trigger_zero_trigger_time_with_pending(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_pending(hass, mqtt_mock): @@ -646,12 +646,12 @@ async def test_trigger_with_pending(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING state = hass.states.get(entity_id) assert state.attributes["post_pending_state"] == STATE_ALARM_TRIGGERED @@ -664,7 +664,7 @@ async def test_trigger_with_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -674,7 +674,7 @@ async def test_trigger_with_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_disarm_after_trigger(hass, mqtt_mock): @@ -698,12 +698,12 @@ async def test_trigger_with_disarm_after_trigger(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -713,7 +713,7 @@ async def test_trigger_with_disarm_after_trigger(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_zero_specific_trigger_time(hass, mqtt_mock): @@ -738,12 +738,12 @@ async def test_trigger_with_zero_specific_trigger_time(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_unused_zero_specific_trigger_time(hass, mqtt_mock): @@ -768,12 +768,12 @@ async def test_trigger_with_unused_zero_specific_trigger_time(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -783,7 +783,7 @@ async def test_trigger_with_unused_zero_specific_trigger_time(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_specific_trigger_time(hass, mqtt_mock): @@ -807,12 +807,12 @@ async def test_trigger_with_specific_trigger_time(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -822,7 +822,7 @@ async def test_trigger_with_specific_trigger_time(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass, mqtt_mock): @@ -846,17 +846,17 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass, mqtt_mock entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE, entity_id) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -866,12 +866,12 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass, mqtt_mock async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -881,7 +881,7 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass, mqtt_mock async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_disarm_while_pending_trigger(hass, mqtt_mock): @@ -904,17 +904,17 @@ async def test_disarm_while_pending_trigger(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING await common.async_alarm_disarm(hass, entity_id=entity_id) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -924,7 +924,7 @@ async def test_disarm_while_pending_trigger(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_disarm_during_trigger_with_invalid_code(hass, mqtt_mock): @@ -948,17 +948,17 @@ async def test_disarm_during_trigger_with_invalid_code(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING await common.async_alarm_disarm(hass, entity_id=entity_id) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -968,7 +968,7 @@ async def test_disarm_during_trigger_with_invalid_code(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED async def test_trigger_with_unused_specific_delay(hass, mqtt_mock): @@ -994,19 +994,19 @@ async def test_trigger_with_unused_specific_delay(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_TRIGGERED == state.attributes["post_pending_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["post_pending_state"] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -1043,19 +1043,19 @@ async def test_trigger_with_specific_delay(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_TRIGGERED == state.attributes["post_pending_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["post_pending_state"] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -1092,12 +1092,12 @@ async def test_trigger_with_pending_and_delay(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() @@ -1154,12 +1154,12 @@ async def test_trigger_with_pending_and_specific_delay(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() @@ -1215,7 +1215,7 @@ async def test_armed_home_with_specific_pending(hass, mqtt_mock): await common.async_alarm_arm_home(hass) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -1225,7 +1225,7 @@ async def test_armed_home_with_specific_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_HOME == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_HOME async def test_armed_away_with_specific_pending(hass, mqtt_mock): @@ -1251,7 +1251,7 @@ async def test_armed_away_with_specific_pending(hass, mqtt_mock): await common.async_alarm_arm_away(hass) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -1261,7 +1261,7 @@ async def test_armed_away_with_specific_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_armed_night_with_specific_pending(hass, mqtt_mock): @@ -1287,7 +1287,7 @@ async def test_armed_night_with_specific_pending(hass, mqtt_mock): await common.async_alarm_arm_night(hass) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -1297,7 +1297,7 @@ async def test_armed_night_with_specific_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT async def test_trigger_with_specific_pending(hass, mqtt_mock): @@ -1325,7 +1325,7 @@ async def test_trigger_with_specific_pending(hass, mqtt_mock): await common.async_alarm_trigger(hass) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -1335,7 +1335,7 @@ async def test_trigger_with_specific_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -1345,7 +1345,7 @@ async def test_trigger_with_specific_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_arm_away_after_disabled_disarmed(hass, legacy_patchable_time, mqtt_mock): @@ -1372,23 +1372,23 @@ async def test_arm_away_after_disabled_disarmed(hass, legacy_patchable_time, mqt entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_DISARMED == state.attributes["pre_pending_state"] - assert STATE_ALARM_ARMED_AWAY == state.attributes["post_pending_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["pre_pending_state"] == STATE_ALARM_DISARMED + assert state.attributes["post_pending_state"] == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_DISARMED == state.attributes["pre_pending_state"] - assert STATE_ALARM_ARMED_AWAY == state.attributes["post_pending_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["pre_pending_state"] == STATE_ALARM_DISARMED + assert state.attributes["post_pending_state"] == STATE_ALARM_ARMED_AWAY future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -1399,15 +1399,15 @@ async def test_arm_away_after_disabled_disarmed(hass, legacy_patchable_time, mqt await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_ARMED_AWAY == state.state + assert state.state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_ARMED_AWAY == state.attributes["pre_pending_state"] - assert STATE_ALARM_TRIGGERED == state.attributes["post_pending_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["pre_pending_state"] == STATE_ALARM_ARMED_AWAY + assert state.attributes["post_pending_state"] == STATE_ALARM_TRIGGERED future += timedelta(seconds=1) with patch( @@ -1418,7 +1418,7 @@ async def test_arm_away_after_disabled_disarmed(hass, legacy_patchable_time, mqt await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_TRIGGERED == state.state + assert state.state == STATE_ALARM_TRIGGERED async def test_disarm_with_template_code(hass, mqtt_mock): @@ -1442,25 +1442,25 @@ async def test_disarm_with_template_code(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, "def") await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_ARMED_HOME == state.state + assert state.state == STATE_ALARM_ARMED_HOME await common.async_alarm_disarm(hass, "def") await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_ARMED_HOME == state.state + assert state.state == STATE_ALARM_ARMED_HOME await common.async_alarm_disarm(hass, "abc") await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_DISARMED == state.state + assert state.state == STATE_ALARM_DISARMED async def test_arm_home_via_command_topic(hass, mqtt_mock): @@ -1483,12 +1483,12 @@ async def test_arm_home_via_command_topic(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED # Fire the arm command via MQTT; ensure state changes to pending async_fire_mqtt_message(hass, "alarm/command", "ARM_HOME") await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) @@ -1499,7 +1499,7 @@ async def test_arm_home_via_command_topic(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_HOME == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_HOME async def test_arm_away_via_command_topic(hass, mqtt_mock): @@ -1522,12 +1522,12 @@ async def test_arm_away_via_command_topic(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED # Fire the arm command via MQTT; ensure state changes to pending async_fire_mqtt_message(hass, "alarm/command", "ARM_AWAY") await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) @@ -1538,7 +1538,7 @@ async def test_arm_away_via_command_topic(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_arm_night_via_command_topic(hass, mqtt_mock): @@ -1561,12 +1561,12 @@ async def test_arm_night_via_command_topic(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED # Fire the arm command via MQTT; ensure state changes to pending async_fire_mqtt_message(hass, "alarm/command", "ARM_NIGHT") await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) @@ -1577,7 +1577,7 @@ async def test_arm_night_via_command_topic(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT async def test_disarm_pending_via_command_topic(hass, mqtt_mock): @@ -1600,18 +1600,18 @@ async def test_disarm_pending_via_command_topic(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING # Now that we're pending, receive a command to disarm async_fire_mqtt_message(hass, "alarm/command", "DISARM") await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_state_changes_are_published_to_mqtt(hass, mqtt_mock): diff --git a/tests/components/melissa/test_climate.py b/tests/components/melissa/test_climate.py index ccb5f951fa2..1b53e2f8334 100644 --- a/tests/components/melissa/test_climate.py +++ b/tests/components/melissa/test_climate.py @@ -94,7 +94,7 @@ async def test_current_fan_mode(hass): device = (await api.async_fetch_devices())[_SERIAL] thermostat = MelissaClimate(api, _SERIAL, device) await thermostat.async_update() - assert SPEED_LOW == thermostat.fan_mode + assert thermostat.fan_mode == SPEED_LOW thermostat._cur_settings = None assert thermostat.fan_mode is None @@ -185,7 +185,7 @@ async def test_state(hass): device = (await api.async_fetch_devices())[_SERIAL] thermostat = MelissaClimate(api, _SERIAL, device) await thermostat.async_update() - assert HVAC_MODE_HEAT == thermostat.state + assert thermostat.state == HVAC_MODE_HEAT thermostat._cur_settings = None assert thermostat.state is None @@ -197,7 +197,7 @@ async def test_temperature_unit(hass): api = melissa_mock() device = (await api.async_fetch_devices())[_SERIAL] thermostat = MelissaClimate(api, _SERIAL, device) - assert TEMP_CELSIUS == thermostat.temperature_unit + assert thermostat.temperature_unit == TEMP_CELSIUS async def test_min_temp(hass): @@ -225,7 +225,7 @@ async def test_supported_features(hass): device = (await api.async_fetch_devices())[_SERIAL] thermostat = MelissaClimate(api, _SERIAL, device) features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE - assert features == thermostat.supported_features + assert thermostat.supported_features == features async def test_set_temperature(hass): @@ -249,7 +249,7 @@ async def test_fan_mode(hass): await hass.async_block_till_done() await thermostat.async_set_fan_mode(SPEED_HIGH) await hass.async_block_till_done() - assert SPEED_HIGH == thermostat.fan_mode + assert thermostat.fan_mode == SPEED_HIGH async def test_set_operation_mode(hass): @@ -262,7 +262,7 @@ async def test_set_operation_mode(hass): await hass.async_block_till_done() await thermostat.async_set_hvac_mode(HVAC_MODE_COOL) await hass.async_block_till_done() - assert HVAC_MODE_COOL == thermostat.hvac_mode + assert thermostat.hvac_mode == HVAC_MODE_COOL async def test_send(hass): @@ -275,7 +275,7 @@ async def test_send(hass): await hass.async_block_till_done() await thermostat.async_send({"fan": api.FAN_MEDIUM}) await hass.async_block_till_done() - assert SPEED_MEDIUM == thermostat.fan_mode + assert thermostat.fan_mode == SPEED_MEDIUM api.async_send.return_value = AsyncMock(return_value=False) thermostat._cur_settings = None await thermostat.async_send({"fan": api.FAN_LOW}) @@ -294,8 +294,8 @@ async def test_update(hass): device = (await api.async_fetch_devices())[_SERIAL] thermostat = MelissaClimate(api, _SERIAL, device) await thermostat.async_update() - assert SPEED_LOW == thermostat.fan_mode - assert HVAC_MODE_HEAT == thermostat.state + assert thermostat.fan_mode == SPEED_LOW + assert thermostat.state == HVAC_MODE_HEAT api.async_status = AsyncMock(side_effect=KeyError("boom")) await thermostat.async_update() mocked_warning.assert_called_once_with( @@ -309,10 +309,10 @@ async def test_melissa_op_to_hass(hass): api = melissa_mock() device = (await api.async_fetch_devices())[_SERIAL] thermostat = MelissaClimate(api, _SERIAL, device) - assert HVAC_MODE_FAN_ONLY == thermostat.melissa_op_to_hass(1) - assert HVAC_MODE_HEAT == thermostat.melissa_op_to_hass(2) - assert HVAC_MODE_COOL == thermostat.melissa_op_to_hass(3) - assert HVAC_MODE_DRY == thermostat.melissa_op_to_hass(4) + assert thermostat.melissa_op_to_hass(1) == HVAC_MODE_FAN_ONLY + assert thermostat.melissa_op_to_hass(2) == HVAC_MODE_HEAT + assert thermostat.melissa_op_to_hass(3) == HVAC_MODE_COOL + assert thermostat.melissa_op_to_hass(4) == HVAC_MODE_DRY assert thermostat.melissa_op_to_hass(5) is None @@ -322,10 +322,10 @@ async def test_melissa_fan_to_hass(hass): api = melissa_mock() device = (await api.async_fetch_devices())[_SERIAL] thermostat = MelissaClimate(api, _SERIAL, device) - assert "auto" == thermostat.melissa_fan_to_hass(0) - assert SPEED_LOW == thermostat.melissa_fan_to_hass(1) - assert SPEED_MEDIUM == thermostat.melissa_fan_to_hass(2) - assert SPEED_HIGH == thermostat.melissa_fan_to_hass(3) + assert thermostat.melissa_fan_to_hass(0) == "auto" + assert thermostat.melissa_fan_to_hass(1) == SPEED_LOW + assert thermostat.melissa_fan_to_hass(2) == SPEED_MEDIUM + assert thermostat.melissa_fan_to_hass(3) == SPEED_HIGH assert thermostat.melissa_fan_to_hass(4) is None diff --git a/tests/components/meraki/test_device_tracker.py b/tests/components/meraki/test_device_tracker.py index 0fa83eec9c8..1c22b24411a 100644 --- a/tests/components/meraki/test_device_tracker.py +++ b/tests/components/meraki/test_device_tracker.py @@ -125,9 +125,9 @@ async def test_data_will_be_saved(mock_device_tracker_conf, hass, meraki_client) state_name = hass.states.get( "{}.{}".format("device_tracker", "00_26_ab_b8_a9_a4") ).state - assert "home" == state_name + assert state_name == "home" state_name = hass.states.get( "{}.{}".format("device_tracker", "00_26_ab_b8_a9_a5") ).state - assert "home" == state_name + assert state_name == "home" diff --git a/tests/components/mfi/test_sensor.py b/tests/components/mfi/test_sensor.py index 610aa91cd4c..6103c43d3a4 100644 --- a/tests/components/mfi/test_sensor.py +++ b/tests/components/mfi/test_sensor.py @@ -132,7 +132,7 @@ async def test_name(port, sensor): async def test_uom_temp(port, sensor): """Test the UOM temperature.""" port.tag = "temperature" - assert TEMP_CELSIUS == sensor.unit_of_measurement + assert sensor.unit_of_measurement == TEMP_CELSIUS async def test_uom_power(port, sensor): diff --git a/tests/components/min_max/test_sensor.py b/tests/components/min_max/test_sensor.py index 93360d57786..c6c352a8c3e 100644 --- a/tests/components/min_max/test_sensor.py +++ b/tests/components/min_max/test_sensor.py @@ -50,10 +50,10 @@ async def test_min_sensor(hass): assert str(float(MIN_VALUE)) == state.state assert entity_ids[2] == state.attributes.get("min_entity_id") - assert MAX_VALUE == state.attributes.get("max_value") + assert state.attributes.get("max_value") == MAX_VALUE assert entity_ids[1] == state.attributes.get("max_entity_id") - assert MEAN == state.attributes.get("mean") - assert MEDIAN == state.attributes.get("median") + assert state.attributes.get("mean") == MEAN + assert state.attributes.get("median") == MEDIAN async def test_max_sensor(hass): @@ -80,10 +80,10 @@ async def test_max_sensor(hass): assert str(float(MAX_VALUE)) == state.state assert entity_ids[2] == state.attributes.get("min_entity_id") - assert MIN_VALUE == state.attributes.get("min_value") + assert state.attributes.get("min_value") == MIN_VALUE assert entity_ids[1] == state.attributes.get("max_entity_id") - assert MEAN == state.attributes.get("mean") - assert MEDIAN == state.attributes.get("median") + assert state.attributes.get("mean") == MEAN + assert state.attributes.get("median") == MEDIAN async def test_mean_sensor(hass): @@ -109,11 +109,11 @@ async def test_mean_sensor(hass): state = hass.states.get("sensor.test_mean") assert str(float(MEAN)) == state.state - assert MIN_VALUE == state.attributes.get("min_value") + assert state.attributes.get("min_value") == MIN_VALUE assert entity_ids[2] == state.attributes.get("min_entity_id") - assert MAX_VALUE == state.attributes.get("max_value") + assert state.attributes.get("max_value") == MAX_VALUE assert entity_ids[1] == state.attributes.get("max_entity_id") - assert MEDIAN == state.attributes.get("median") + assert state.attributes.get("median") == MEDIAN async def test_mean_1_digit_sensor(hass): @@ -140,11 +140,11 @@ async def test_mean_1_digit_sensor(hass): state = hass.states.get("sensor.test_mean") assert str(float(MEAN_1_DIGIT)) == state.state - assert MIN_VALUE == state.attributes.get("min_value") + assert state.attributes.get("min_value") == MIN_VALUE assert entity_ids[2] == state.attributes.get("min_entity_id") - assert MAX_VALUE == state.attributes.get("max_value") + assert state.attributes.get("max_value") == MAX_VALUE assert entity_ids[1] == state.attributes.get("max_entity_id") - assert MEDIAN == state.attributes.get("median") + assert state.attributes.get("median") == MEDIAN async def test_mean_4_digit_sensor(hass): @@ -171,11 +171,11 @@ async def test_mean_4_digit_sensor(hass): state = hass.states.get("sensor.test_mean") assert str(float(MEAN_4_DIGITS)) == state.state - assert MIN_VALUE == state.attributes.get("min_value") + assert state.attributes.get("min_value") == MIN_VALUE assert entity_ids[2] == state.attributes.get("min_entity_id") - assert MAX_VALUE == state.attributes.get("max_value") + assert state.attributes.get("max_value") == MAX_VALUE assert entity_ids[1] == state.attributes.get("max_entity_id") - assert MEDIAN == state.attributes.get("median") + assert state.attributes.get("median") == MEDIAN async def test_median_sensor(hass): @@ -201,11 +201,11 @@ async def test_median_sensor(hass): state = hass.states.get("sensor.test_median") assert str(float(MEDIAN)) == state.state - assert MIN_VALUE == state.attributes.get("min_value") + assert state.attributes.get("min_value") == MIN_VALUE assert entity_ids[2] == state.attributes.get("min_entity_id") - assert MAX_VALUE == state.attributes.get("max_value") + assert state.attributes.get("max_value") == MAX_VALUE assert entity_ids[1] == state.attributes.get("max_entity_id") - assert MEAN == state.attributes.get("mean") + assert state.attributes.get("mean") == MEAN async def test_not_enough_sensor_value(hass): @@ -228,7 +228,7 @@ async def test_not_enough_sensor_value(hass): await hass.async_block_till_done() state = hass.states.get("sensor.test_max") - assert STATE_UNKNOWN == state.state + assert state.state == STATE_UNKNOWN assert state.attributes.get("min_entity_id") is None assert state.attributes.get("min_value") is None assert state.attributes.get("max_entity_id") is None @@ -259,7 +259,7 @@ async def test_not_enough_sensor_value(hass): await hass.async_block_till_done() state = hass.states.get("sensor.test_max") - assert STATE_UNKNOWN == state.state + assert state.state == STATE_UNKNOWN assert state.attributes.get("min_entity_id") is None assert state.attributes.get("min_value") is None assert state.attributes.get("max_entity_id") is None @@ -299,7 +299,7 @@ async def test_different_unit_of_measurement(hass): state = hass.states.get("sensor.test") - assert STATE_UNKNOWN == state.state + assert state.state == STATE_UNKNOWN assert state.attributes.get("unit_of_measurement") == "ERR" hass.states.async_set( @@ -309,7 +309,7 @@ async def test_different_unit_of_measurement(hass): state = hass.states.get("sensor.test") - assert STATE_UNKNOWN == state.state + assert state.state == STATE_UNKNOWN assert state.attributes.get("unit_of_measurement") == "ERR" @@ -336,10 +336,10 @@ async def test_last_sensor(hass): assert str(float(value)) == state.state assert entity_id == state.attributes.get("last_entity_id") - assert MIN_VALUE == state.attributes.get("min_value") - assert MAX_VALUE == state.attributes.get("max_value") - assert MEAN == state.attributes.get("mean") - assert MEDIAN == state.attributes.get("median") + assert state.attributes.get("min_value") == MIN_VALUE + assert state.attributes.get("max_value") == MAX_VALUE + assert state.attributes.get("mean") == MEAN + assert state.attributes.get("median") == MEDIAN async def test_reload(hass): diff --git a/tests/components/minio/test_minio.py b/tests/components/minio/test_minio.py index de66ee2fafa..d6668470b55 100644 --- a/tests/components/minio/test_minio.py +++ b/tests/components/minio/test_minio.py @@ -141,16 +141,16 @@ async def test_minio_listen(hass, caplog, minio_client_event): while not events: await asyncio.sleep(0) - assert 1 == len(events) + assert len(events) == 1 event = events[0] - assert DOMAIN == event.event_type - assert "s3:ObjectCreated:Put" == event.data["event_name"] - assert "5jJkTAo.jpg" == event.data["file_name"] - assert "test" == event.data["bucket"] - assert "5jJkTAo.jpg" == event.data["key"] - assert "http://url" == event.data["presigned_url"] - assert 0 == len(event.data["metadata"]) + assert event.event_type == DOMAIN + assert event.data["event_name"] == "s3:ObjectCreated:Put" + assert event.data["file_name"] == "5jJkTAo.jpg" + assert event.data["bucket"] == "test" + assert event.data["key"] == "5jJkTAo.jpg" + assert event.data["presigned_url"] == "http://url" + assert len(event.data["metadata"]) == 0 async def test_queue_listener(): @@ -183,7 +183,7 @@ async def test_queue_listener(): "metadata": {}, } - assert DOMAIN == call_domain + assert call_domain == DOMAIN assert json.dumps(expected_event, sort_keys=True) == json.dumps( call_event, sort_keys=True ) diff --git a/tests/components/mochad/test_switch.py b/tests/components/mochad/test_switch.py index 218248c3442..0242417cb71 100644 --- a/tests/components/mochad/test_switch.py +++ b/tests/components/mochad/test_switch.py @@ -39,7 +39,7 @@ async def test_setup_adds_proper_devices(hass): async def test_name(switch_mock): """Test the name.""" - assert "fake_switch" == switch_mock.name + assert switch_mock.name == "fake_switch" async def test_turn_on(switch_mock): diff --git a/tests/components/mold_indicator/test_sensor.py b/tests/components/mold_indicator/test_sensor.py index 2496bddc57e..04305b724d8 100644 --- a/tests/components/mold_indicator/test_sensor.py +++ b/tests/components/mold_indicator/test_sensor.py @@ -47,7 +47,7 @@ async def test_setup(hass): await hass.async_block_till_done() moldind = hass.states.get("sensor.mold_indicator") assert moldind - assert PERCENTAGE == moldind.attributes.get("unit_of_measurement") + assert moldind.attributes.get("unit_of_measurement") == PERCENTAGE async def test_invalidcalib(hass): diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index 508869c7b51..e28cd697457 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -333,7 +333,7 @@ async def test_optimistic_state_change(hass, mqtt_mock): mqtt_mock.async_publish.assert_called_once_with("command-topic", "CLOSE", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("cover.test") - assert STATE_CLOSED == state.state + assert state.state == STATE_CLOSED await hass.services.async_call( cover.DOMAIN, SERVICE_TOGGLE, {ATTR_ENTITY_ID: "cover.test"}, blocking=True @@ -342,7 +342,7 @@ async def test_optimistic_state_change(hass, mqtt_mock): mqtt_mock.async_publish.assert_called_once_with("command-topic", "OPEN", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("cover.test") - assert STATE_OPEN == state.state + assert state.state == STATE_OPEN await hass.services.async_call( cover.DOMAIN, SERVICE_TOGGLE, {ATTR_ENTITY_ID: "cover.test"}, blocking=True @@ -393,7 +393,7 @@ async def test_optimistic_state_change_with_position(hass, mqtt_mock): mqtt_mock.async_publish.assert_called_once_with("command-topic", "CLOSE", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("cover.test") - assert STATE_CLOSED == state.state + assert state.state == STATE_CLOSED assert state.attributes.get(ATTR_CURRENT_POSITION) == 0 await hass.services.async_call( @@ -403,7 +403,7 @@ async def test_optimistic_state_change_with_position(hass, mqtt_mock): mqtt_mock.async_publish.assert_called_once_with("command-topic", "OPEN", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("cover.test") - assert STATE_OPEN == state.state + assert state.state == STATE_OPEN assert state.attributes.get(ATTR_CURRENT_POSITION) == 100 await hass.services.async_call( diff --git a/tests/components/mqtt/test_trigger.py b/tests/components/mqtt/test_trigger.py index 70ee5e9327c..332ed9a04d2 100644 --- a/tests/components/mqtt/test_trigger.py +++ b/tests/components/mqtt/test_trigger.py @@ -46,7 +46,7 @@ async def test_if_fires_on_topic_match(hass, calls): async_fire_mqtt_message(hass, "test-topic", '{ "hello": "world" }') await hass.async_block_till_done() assert len(calls) == 1 - assert 'mqtt - test-topic - { "hello": "world" } - world' == calls[0].data["some"] + assert calls[0].data["some"] == 'mqtt - test-topic - { "hello": "world" } - world' await hass.services.async_call( automation.DOMAIN, diff --git a/tests/components/mqtt_eventstream/test_init.py b/tests/components/mqtt_eventstream/test_init.py index da37489a130..7f6b22bda90 100644 --- a/tests/components/mqtt_eventstream/test_init.py +++ b/tests/components/mqtt_eventstream/test_init.py @@ -138,7 +138,7 @@ async def test_receiving_remote_event_fires_hass_event(hass, mqtt_mock): async_fire_mqtt_message(hass, sub_topic, payload) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_ignored_event_doesnt_send_over_stream(hass, mqtt_mock): diff --git a/tests/components/nsw_fuel_station/test_sensor.py b/tests/components/nsw_fuel_station/test_sensor.py index 28ff7a7ed95..40143a67bac 100644 --- a/tests/components/nsw_fuel_station/test_sensor.py +++ b/tests/components/nsw_fuel_station/test_sensor.py @@ -99,5 +99,5 @@ async def test_sensor_values(hass): assert await async_setup_component(hass, sensor.DOMAIN, {"sensor": VALID_CONFIG}) await hass.async_block_till_done() - assert "140.0" == hass.states.get("sensor.my_fake_station_e10").state - assert "150.0" == hass.states.get("sensor.my_fake_station_p95").state + assert hass.states.get("sensor.my_fake_station_e10").state == "140.0" + assert hass.states.get("sensor.my_fake_station_p95").state == "150.0" diff --git a/tests/components/nx584/test_binary_sensor.py b/tests/components/nx584/test_binary_sensor.py index d6d410b3700..fd2e5b30bac 100644 --- a/tests/components/nx584/test_binary_sensor.py +++ b/tests/components/nx584/test_binary_sensor.py @@ -143,7 +143,7 @@ def test_nx584_zone_sensor_normal(): """Test for the NX584 zone sensor.""" zone = {"number": 1, "name": "foo", "state": True} sensor = nx584.NX584ZoneSensor(zone, "motion") - assert "foo" == sensor.name + assert sensor.name == "foo" assert not sensor.should_poll assert sensor.is_on assert sensor.extra_state_attributes["zone_number"] == 1 @@ -204,7 +204,7 @@ def test_nx584_watcher_run_with_zone_events(): assert fake_process.call_args == mock.call(fake_events[0]) run() - assert 3 == client.get_events.call_count + assert client.get_events.call_count == 3 @mock.patch("time.sleep") @@ -224,5 +224,5 @@ def test_nx584_watcher_run_retries_failures(mock_sleep): mock_inner.side_effect = fake_run with pytest.raises(StopMe): watcher.run() - assert 3 == mock_inner.call_count + assert mock_inner.call_count == 3 mock_sleep.assert_has_calls([mock.call(10), mock.call(10)]) diff --git a/tests/components/plant/test_init.py b/tests/components/plant/test_init.py index f30350141c4..494206d81a3 100644 --- a/tests/components/plant/test_init.py +++ b/tests/components/plant/test_init.py @@ -88,7 +88,7 @@ async def test_initial_states(hass): ) await hass.async_block_till_done() state = hass.states.get(f"plant.{plant_name}") - assert 5 == state.attributes[plant.READING_MOISTURE] + assert state.attributes[plant.READING_MOISTURE] == 5 async def test_update_states(hass): @@ -103,8 +103,8 @@ async def test_update_states(hass): hass.states.async_set(MOISTURE_ENTITY, 5, {ATTR_UNIT_OF_MEASUREMENT: CONDUCTIVITY}) await hass.async_block_till_done() state = hass.states.get(f"plant.{plant_name}") - assert STATE_PROBLEM == state.state - assert 5 == state.attributes[plant.READING_MOISTURE] + assert state.state == STATE_PROBLEM + assert state.attributes[plant.READING_MOISTURE] == 5 async def test_unavailable_state(hass): @@ -177,9 +177,9 @@ async def test_load_from_db(hass): await hass.async_block_till_done() state = hass.states.get(f"plant.{plant_name}") - assert STATE_UNKNOWN == state.state + assert state.state == STATE_UNKNOWN max_brightness = state.attributes.get(plant.ATTR_MAX_BRIGHTNESS_HISTORY) - assert 30 == max_brightness + assert max_brightness == 30 async def test_brightness_history(hass): @@ -191,17 +191,17 @@ async def test_brightness_history(hass): hass.states.async_set(BRIGHTNESS_ENTITY, 100, {ATTR_UNIT_OF_MEASUREMENT: LIGHT_LUX}) await hass.async_block_till_done() state = hass.states.get(f"plant.{plant_name}") - assert STATE_PROBLEM == state.state + assert state.state == STATE_PROBLEM hass.states.async_set(BRIGHTNESS_ENTITY, 600, {ATTR_UNIT_OF_MEASUREMENT: LIGHT_LUX}) await hass.async_block_till_done() state = hass.states.get(f"plant.{plant_name}") - assert STATE_OK == state.state + assert state.state == STATE_OK hass.states.async_set(BRIGHTNESS_ENTITY, 100, {ATTR_UNIT_OF_MEASUREMENT: LIGHT_LUX}) await hass.async_block_till_done() state = hass.states.get(f"plant.{plant_name}") - assert STATE_OK == state.state + assert state.state == STATE_OK def test_daily_history_no_data(hass): @@ -217,7 +217,7 @@ def test_daily_history_one_day(hass): for i in range(len(values)): dh.add_measurement(values[i]) max_value = max(values[0 : i + 1]) - assert 1 == len(dh._days) + assert len(dh._days) == 1 assert dh.max == max_value diff --git a/tests/components/prometheus/test_init.py b/tests/components/prometheus/test_init.py index bea42cc0888..fe91c7c0002 100644 --- a/tests/components/prometheus/test_init.py +++ b/tests/components/prometheus/test_init.py @@ -224,7 +224,7 @@ async def test_minimal_config(hass, mock_client): assert await async_setup_component(hass, prometheus.DOMAIN, config) await hass.async_block_till_done() assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED @pytest.mark.usefixtures("mock_bus") @@ -251,7 +251,7 @@ async def test_full_config(hass, mock_client): assert await async_setup_component(hass, prometheus.DOMAIN, config) await hass.async_block_till_done() assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED def make_event(entity_id): diff --git a/tests/components/radarr/test_sensor.py b/tests/components/radarr/test_sensor.py index 514b8f1b817..aa8ccd74667 100644 --- a/tests/components/radarr/test_sensor.py +++ b/tests/components/radarr/test_sensor.py @@ -211,11 +211,11 @@ async def test_diskspace_no_paths(hass): entity = hass.states.get("sensor.radarr_disk_space") assert entity is not None - assert "263.10" == entity.state - assert "mdi:harddisk" == entity.attributes["icon"] - assert DATA_GIGABYTES == entity.attributes["unit_of_measurement"] - assert "Radarr Disk Space" == entity.attributes["friendly_name"] - assert "263.10/465.42GB (56.53%)" == entity.attributes["/data"] + assert entity.state == "263.10" + assert entity.attributes["icon"] == "mdi:harddisk" + assert entity.attributes["unit_of_measurement"] == DATA_GIGABYTES + assert entity.attributes["friendly_name"] == "Radarr Disk Space" + assert entity.attributes["/data"] == "263.10/465.42GB (56.53%)" async def test_diskspace_paths(hass): @@ -240,11 +240,11 @@ async def test_diskspace_paths(hass): entity = hass.states.get("sensor.radarr_disk_space") assert entity is not None - assert "263.10" == entity.state - assert "mdi:harddisk" == entity.attributes["icon"] - assert DATA_GIGABYTES == entity.attributes["unit_of_measurement"] - assert "Radarr Disk Space" == entity.attributes["friendly_name"] - assert "263.10/465.42GB (56.53%)" == entity.attributes["/data"] + assert entity.state == "263.10" + assert entity.attributes["icon"] == "mdi:harddisk" + assert entity.attributes["unit_of_measurement"] == DATA_GIGABYTES + assert entity.attributes["friendly_name"] == "Radarr Disk Space" + assert entity.attributes["/data"] == "263.10/465.42GB (56.53%)" async def test_commands(hass): @@ -269,11 +269,11 @@ async def test_commands(hass): entity = hass.states.get("sensor.radarr_commands") assert entity is not None - assert 1 == int(entity.state) - assert "mdi:code-braces" == entity.attributes["icon"] - assert "Commands" == entity.attributes["unit_of_measurement"] - assert "Radarr Commands" == entity.attributes["friendly_name"] - assert "pending" == entity.attributes["RescanMovie"] + assert int(entity.state) == 1 + assert entity.attributes["icon"] == "mdi:code-braces" + assert entity.attributes["unit_of_measurement"] == "Commands" + assert entity.attributes["friendly_name"] == "Radarr Commands" + assert entity.attributes["RescanMovie"] == "pending" async def test_movies(hass): @@ -298,11 +298,11 @@ async def test_movies(hass): entity = hass.states.get("sensor.radarr_movies") assert entity is not None - assert 1 == int(entity.state) - assert "mdi:television" == entity.attributes["icon"] - assert "Movies" == entity.attributes["unit_of_measurement"] - assert "Radarr Movies" == entity.attributes["friendly_name"] - assert "false" == entity.attributes["Assassin's Creed (2016)"] + assert int(entity.state) == 1 + assert entity.attributes["icon"] == "mdi:television" + assert entity.attributes["unit_of_measurement"] == "Movies" + assert entity.attributes["friendly_name"] == "Radarr Movies" + assert entity.attributes["Assassin's Creed (2016)"] == "false" async def test_upcoming_multiple_days(hass): @@ -327,11 +327,11 @@ async def test_upcoming_multiple_days(hass): entity = hass.states.get("sensor.radarr_upcoming") assert entity is not None - assert 1 == int(entity.state) - assert "mdi:television" == entity.attributes["icon"] - assert "Movies" == entity.attributes["unit_of_measurement"] - assert "Radarr Upcoming" == entity.attributes["friendly_name"] - assert "2017-01-27T00:00:00Z" == entity.attributes["Resident Evil (2017)"] + assert int(entity.state) == 1 + assert entity.attributes["icon"] == "mdi:television" + assert entity.attributes["unit_of_measurement"] == "Movies" + assert entity.attributes["friendly_name"] == "Radarr Upcoming" + assert entity.attributes["Resident Evil (2017)"] == "2017-01-27T00:00:00Z" @pytest.mark.skip @@ -357,11 +357,11 @@ async def test_upcoming_today(hass): assert await async_setup_component(hass, "sensor", config) await hass.async_block_till_done() entity = hass.states.get("sensor.radarr_upcoming") - assert 1 == int(entity.state) - assert "mdi:television" == entity.attributes["icon"] - assert "Movies" == entity.attributes["unit_of_measurement"] - assert "Radarr Upcoming" == entity.attributes["friendly_name"] - assert "2017-01-27T00:00:00Z" == entity.attributes["Resident Evil (2017)"] + assert int(entity.state) == 1 + assert entity.attributes["icon"] == "mdi:television" + assert entity.attributes["unit_of_measurement"] == "Movies" + assert entity.attributes["friendly_name"] == "Radarr Upcoming" + assert entity.attributes["Resident Evil (2017)"] == "2017-01-27T00:00:00Z" async def test_system_status(hass): @@ -384,10 +384,10 @@ async def test_system_status(hass): await hass.async_block_till_done() entity = hass.states.get("sensor.radarr_status") assert entity is not None - assert "0.2.0.210" == entity.state - assert "mdi:information" == entity.attributes["icon"] - assert "Radarr Status" == entity.attributes["friendly_name"] - assert "4.8.13.1" == entity.attributes["osVersion"] + assert entity.state == "0.2.0.210" + assert entity.attributes["icon"] == "mdi:information" + assert entity.attributes["friendly_name"] == "Radarr Status" + assert entity.attributes["osVersion"] == "4.8.13.1" async def test_ssl(hass): @@ -411,11 +411,11 @@ async def test_ssl(hass): await hass.async_block_till_done() entity = hass.states.get("sensor.radarr_upcoming") assert entity is not None - assert 1 == int(entity.state) - assert "mdi:television" == entity.attributes["icon"] - assert "Movies" == entity.attributes["unit_of_measurement"] - assert "Radarr Upcoming" == entity.attributes["friendly_name"] - assert "2017-01-27T00:00:00Z" == entity.attributes["Resident Evil (2017)"] + assert int(entity.state) == 1 + assert entity.attributes["icon"] == "mdi:television" + assert entity.attributes["unit_of_measurement"] == "Movies" + assert entity.attributes["friendly_name"] == "Radarr Upcoming" + assert entity.attributes["Resident Evil (2017)"] == "2017-01-27T00:00:00Z" async def test_exception_handling(hass): @@ -438,4 +438,4 @@ async def test_exception_handling(hass): await hass.async_block_till_done() entity = hass.states.get("sensor.radarr_upcoming") assert entity is not None - assert "unavailable" == entity.state + assert entity.state == "unavailable" diff --git a/tests/components/remote/test_init.py b/tests/components/remote/test_init.py index 1f57b97884c..2c2005a7fee 100644 --- a/tests/components/remote/test_init.py +++ b/tests/components/remote/test_init.py @@ -48,7 +48,7 @@ async def test_turn_on(hass): assert len(turn_on_calls) == 1 call = turn_on_calls[-1] - assert DOMAIN == call.domain + assert call.domain == DOMAIN async def test_turn_off(hass): diff --git a/tests/components/rest/test_switch.py b/tests/components/rest/test_switch.py index 7141a34203a..62fc30d9e4b 100644 --- a/tests/components/rest/test_switch.py +++ b/tests/components/rest/test_switch.py @@ -179,7 +179,7 @@ def _setup_test_switch(hass): def test_name(hass): """Test the name.""" switch, body_on, body_off = _setup_test_switch(hass) - assert NAME == switch.name + assert switch.name == NAME def test_is_on_before_update(hass): diff --git a/tests/components/scene/test_init.py b/tests/components/scene/test_init.py index 44696e6f370..a3d50d0214f 100644 --- a/tests/components/scene/test_init.py +++ b/tests/components/scene/test_init.py @@ -60,8 +60,8 @@ async def test_config_yaml_alias_anchor(hass, entities): assert light.is_on(hass, light_1.entity_id) assert light.is_on(hass, light_2.entity_id) - assert 100 == light_1.last_call("turn_on")[1].get("brightness") - assert 100 == light_2.last_call("turn_on")[1].get("brightness") + assert light_1.last_call("turn_on")[1].get("brightness") == 100 + assert light_2.last_call("turn_on")[1].get("brightness") == 100 async def test_config_yaml_bool(hass, entities): @@ -88,7 +88,7 @@ async def test_config_yaml_bool(hass, entities): assert light.is_on(hass, light_1.entity_id) assert light.is_on(hass, light_2.entity_id) - assert 100 == light_2.last_call("turn_on")[1].get("brightness") + assert light_2.last_call("turn_on")[1].get("brightness") == 100 async def test_activate_scene(hass, entities): diff --git a/tests/components/script/test_init.py b/tests/components/script/test_init.py index 95703949525..c1485a0c6ab 100644 --- a/tests/components/script/test_init.py +++ b/tests/components/script/test_init.py @@ -173,7 +173,7 @@ async def test_turn_on_off_toggle(hass, toggle): assert not script.is_on(hass, ENTITY_ID) assert was_on - assert 1 == event_mock.call_count + assert event_mock.call_count == 1 invalid_configs = [ @@ -190,7 +190,7 @@ async def test_setup_with_invalid_configs(hass, value): hass, "script", {"script": value} ), f"Script loaded with wrong config {value}" - assert 0 == len(hass.states.async_entity_ids("script")) + assert len(hass.states.async_entity_ids("script")) == 0 @pytest.mark.parametrize("running", ["no", "same", "different"]) @@ -587,7 +587,7 @@ async def test_concurrent_script(hass, concurrently): await asyncio.wait_for(service_called.wait(), 1) service_called.clear() - assert "script2a" == service_values[-1] + assert service_values[-1] == "script2a" assert script.is_on(hass, "script.script1") assert script.is_on(hass, "script.script2") @@ -596,13 +596,13 @@ async def test_concurrent_script(hass, concurrently): await asyncio.wait_for(service_called.wait(), 1) service_called.clear() - assert "script2b" == service_values[-1] + assert service_values[-1] == "script2b" hass.states.async_set("input_boolean.test1", "on") await asyncio.wait_for(service_called.wait(), 1) service_called.clear() - assert "script1" == service_values[-1] + assert service_values[-1] == "script1" assert concurrently == script.is_on(hass, "script.script2") if concurrently: @@ -610,7 +610,7 @@ async def test_concurrent_script(hass, concurrently): await asyncio.wait_for(service_called.wait(), 1) service_called.clear() - assert "script2b" == service_values[-1] + assert service_values[-1] == "script2b" await hass.async_block_till_done() diff --git a/tests/components/shell_command/test_init.py b/tests/components/shell_command/test_init.py index d4581ae1fc7..f3a5d46f64b 100644 --- a/tests/components/shell_command/test_init.py +++ b/tests/components/shell_command/test_init.py @@ -79,7 +79,7 @@ async def test_template_render_no_template(mock_call, hass): cmd = mock_call.mock_calls[0][1][0] assert mock_call.call_count == 1 - assert "ls /bin" == cmd + assert cmd == "ls /bin" @patch( diff --git a/tests/components/sleepiq/test_binary_sensor.py b/tests/components/sleepiq/test_binary_sensor.py index c158554b278..9b4092d9d48 100644 --- a/tests/components/sleepiq/test_binary_sensor.py +++ b/tests/components/sleepiq/test_binary_sensor.py @@ -18,15 +18,15 @@ async def test_sensor_setup(hass, requests_mock): device_mock = MagicMock() sleepiq.setup_platform(hass, CONFIG, device_mock, MagicMock()) devices = device_mock.call_args[0][0] - assert 2 == len(devices) + assert len(devices) == 2 left_side = devices[1] - assert "SleepNumber ILE Test1 Is In Bed" == left_side.name - assert "on" == left_side.state + assert left_side.name == "SleepNumber ILE Test1 Is In Bed" + assert left_side.state == "on" right_side = devices[0] - assert "SleepNumber ILE Test2 Is In Bed" == right_side.name - assert "off" == right_side.state + assert right_side.name == "SleepNumber ILE Test2 Is In Bed" + assert right_side.state == "off" async def test_setup_single(hass, requests_mock): @@ -38,8 +38,8 @@ async def test_setup_single(hass, requests_mock): device_mock = MagicMock() sleepiq.setup_platform(hass, CONFIG, device_mock, MagicMock()) devices = device_mock.call_args[0][0] - assert 1 == len(devices) + assert len(devices) == 1 right_side = devices[0] - assert "SleepNumber ILE Test1 Is In Bed" == right_side.name - assert "on" == right_side.state + assert right_side.name == "SleepNumber ILE Test1 Is In Bed" + assert right_side.state == "on" diff --git a/tests/components/sleepiq/test_sensor.py b/tests/components/sleepiq/test_sensor.py index 559e808f554..c6a584802b9 100644 --- a/tests/components/sleepiq/test_sensor.py +++ b/tests/components/sleepiq/test_sensor.py @@ -18,15 +18,15 @@ async def test_setup(hass, requests_mock): device_mock = MagicMock() sleepiq.setup_platform(hass, CONFIG, device_mock, MagicMock()) devices = device_mock.call_args[0][0] - assert 2 == len(devices) + assert len(devices) == 2 left_side = devices[1] - assert "SleepNumber ILE Test1 SleepNumber" == left_side.name - assert 40 == left_side.state + assert left_side.name == "SleepNumber ILE Test1 SleepNumber" + assert left_side.state == 40 right_side = devices[0] - assert "SleepNumber ILE Test2 SleepNumber" == right_side.name - assert 80 == right_side.state + assert right_side.name == "SleepNumber ILE Test2 SleepNumber" + assert right_side.state == 80 async def test_setup_sigle(hass, requests_mock): @@ -38,8 +38,8 @@ async def test_setup_sigle(hass, requests_mock): device_mock = MagicMock() sleepiq.setup_platform(hass, CONFIG, device_mock, MagicMock()) devices = device_mock.call_args[0][0] - assert 1 == len(devices) + assert len(devices) == 1 right_side = devices[0] - assert "SleepNumber ILE Test1 SleepNumber" == right_side.name - assert 40 == right_side.state + assert right_side.name == "SleepNumber ILE Test1 SleepNumber" + assert right_side.state == 40 diff --git a/tests/components/statistics/test_sensor.py b/tests/components/statistics/test_sensor.py index b6bebcfeeda..60de732cf79 100644 --- a/tests/components/statistics/test_sensor.py +++ b/tests/components/statistics/test_sensor.py @@ -115,7 +115,7 @@ class TestStatisticsSensor(unittest.TestCase): assert self.mean == state.attributes.get("mean") assert self.count == state.attributes.get("count") assert self.total == state.attributes.get("total") - assert TEMP_CELSIUS == state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS assert self.change == state.attributes.get("change") assert self.average_change == state.attributes.get("average_change") @@ -146,8 +146,8 @@ class TestStatisticsSensor(unittest.TestCase): state = self.hass.states.get("sensor.test") - assert 3.8 == state.attributes.get("min_value") - assert 14 == state.attributes.get("max_value") + assert state.attributes.get("min_value") == 3.8 + assert state.attributes.get("max_value") == 14 def test_sampling_size_1(self): """Test validity of stats requiring only one sample.""" @@ -182,12 +182,12 @@ class TestStatisticsSensor(unittest.TestCase): assert self.values[-1] == state.attributes.get("mean") assert self.values[-1] == state.attributes.get("median") assert self.values[-1] == state.attributes.get("total") - assert 0 == state.attributes.get("change") - assert 0 == state.attributes.get("average_change") + assert state.attributes.get("change") == 0 + assert state.attributes.get("average_change") == 0 # require at least two data points - assert STATE_UNKNOWN == state.attributes.get("variance") - assert STATE_UNKNOWN == state.attributes.get("standard_deviation") + assert state.attributes.get("variance") == STATE_UNKNOWN + assert state.attributes.get("standard_deviation") == STATE_UNKNOWN def test_max_age(self): """Test value deprecation.""" @@ -231,8 +231,8 @@ class TestStatisticsSensor(unittest.TestCase): state = self.hass.states.get("sensor.test") - assert 6 == state.attributes.get("min_value") - assert 14 == state.attributes.get("max_value") + assert state.attributes.get("min_value") == 6 + assert state.attributes.get("max_value") == 14 def test_max_age_without_sensor_change(self): """Test value deprecation.""" @@ -276,8 +276,8 @@ class TestStatisticsSensor(unittest.TestCase): state = self.hass.states.get("sensor.test") - assert 3.8 == state.attributes.get("min_value") - assert 15.2 == state.attributes.get("max_value") + assert state.attributes.get("min_value") == 3.8 + assert state.attributes.get("max_value") == 15.2 # wait for 3 minutes (max_age). mock_data["return_time"] += timedelta(minutes=3) diff --git a/tests/components/statsd/test_init.py b/tests/components/statsd/test_init.py index 17e49b09951..a0e5fd51669 100644 --- a/tests/components/statsd/test_init.py +++ b/tests/components/statsd/test_init.py @@ -39,7 +39,7 @@ async def test_statsd_setup_full(hass): assert mock_init.call_args == mock.call(host="host", port=123, prefix="foo") assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED async def test_statsd_setup_defaults(hass): diff --git a/tests/components/template/test_trigger.py b/tests/components/template/test_trigger.py index b40eee7cab3..c85600571e8 100644 --- a/tests/components/template/test_trigger.py +++ b/tests/components/template/test_trigger.py @@ -395,7 +395,7 @@ async def test_if_fires_on_change_with_template_advanced(hass, calls): await hass.async_block_till_done() assert len(calls) == 1 assert calls[0].context.parent_id == context.id - assert "template - test.entity - hello - world - None" == calls[0].data["some"] + assert calls[0].data["some"] == "template - test.entity - hello - world - None" async def test_if_fires_on_no_change_with_template_advanced(hass, calls): @@ -657,7 +657,7 @@ async def test_if_fires_on_change_with_for_advanced(hass, calls): await hass.async_block_till_done() assert len(calls) == 1 assert calls[0].context.parent_id == context.id - assert "template - test.entity - hello - world - 0:00:05" == calls[0].data["some"] + assert calls[0].data["some"] == "template - test.entity - hello - world - 0:00:05" async def test_if_fires_on_change_with_for_0(hass, calls): diff --git a/tests/components/threshold/test_binary_sensor.py b/tests/components/threshold/test_binary_sensor.py index 66ab9e6cac1..af8c32a1549 100644 --- a/tests/components/threshold/test_binary_sensor.py +++ b/tests/components/threshold/test_binary_sensor.py @@ -24,12 +24,12 @@ async def test_sensor_upper(hass): state = hass.states.get("binary_sensor.threshold") - assert "sensor.test_monitored" == state.attributes.get("entity_id") - assert 16 == state.attributes.get("sensor_value") - assert "above" == state.attributes.get("position") - assert float(config["binary_sensor"]["upper"]) == state.attributes.get("upper") - assert 0.0 == state.attributes.get("hysteresis") - assert "upper" == state.attributes.get("type") + assert state.attributes.get("entity_id") == "sensor.test_monitored" + assert state.attributes.get("sensor_value") == 16 + assert state.attributes.get("position") == "above" + assert state.attributes.get("upper") == float(config["binary_sensor"]["upper"]) + assert state.attributes.get("hysteresis") == 0.0 + assert state.attributes.get("type") == "upper" assert state.state == "on" @@ -66,10 +66,10 @@ async def test_sensor_lower(hass): state = hass.states.get("binary_sensor.threshold") - assert "above" == state.attributes.get("position") - assert float(config["binary_sensor"]["lower"]) == state.attributes.get("lower") - assert 0.0 == state.attributes.get("hysteresis") - assert "lower" == state.attributes.get("type") + assert state.attributes.get("position") == "above" + assert state.attributes.get("lower") == float(config["binary_sensor"]["lower"]) + assert state.attributes.get("hysteresis") == 0.0 + assert state.attributes.get("type") == "lower" assert state.state == "off" @@ -100,10 +100,10 @@ async def test_sensor_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "above" == state.attributes.get("position") - assert float(config["binary_sensor"]["upper"]) == state.attributes.get("upper") - assert 2.5 == state.attributes.get("hysteresis") - assert "upper" == state.attributes.get("type") + assert state.attributes.get("position") == "above" + assert state.attributes.get("upper") == float(config["binary_sensor"]["upper"]) + assert state.attributes.get("hysteresis") == 2.5 + assert state.attributes.get("type") == "upper" assert state.state == "on" @@ -158,12 +158,12 @@ async def test_sensor_in_range_no_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") assert state.attributes.get("entity_id") == "sensor.test_monitored" - assert 16 == state.attributes.get("sensor_value") - assert "in_range" == state.attributes.get("position") - assert float(config["binary_sensor"]["lower"]) == state.attributes.get("lower") - assert float(config["binary_sensor"]["upper"]) == state.attributes.get("upper") - assert 0.0 == state.attributes.get("hysteresis") - assert "range" == state.attributes.get("type") + assert state.attributes.get("sensor_value") == 16 + assert state.attributes.get("position") == "in_range" + assert state.attributes.get("lower") == float(config["binary_sensor"]["lower"]) + assert state.attributes.get("upper") == float(config["binary_sensor"]["upper"]) + assert state.attributes.get("hysteresis") == 0.0 + assert state.attributes.get("type") == "range" assert state.state == "on" @@ -172,7 +172,7 @@ async def test_sensor_in_range_no_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "below" == state.attributes.get("position") + assert state.attributes.get("position") == "below" assert state.state == "off" hass.states.async_set("sensor.test_monitored", 21) @@ -180,7 +180,7 @@ async def test_sensor_in_range_no_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "above" == state.attributes.get("position") + assert state.attributes.get("position") == "above" assert state.state == "off" @@ -206,15 +206,15 @@ async def test_sensor_in_range_with_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "sensor.test_monitored" == state.attributes.get("entity_id") - assert 16 == state.attributes.get("sensor_value") - assert "in_range" == state.attributes.get("position") - assert float(config["binary_sensor"]["lower"]) == state.attributes.get("lower") - assert float(config["binary_sensor"]["upper"]) == state.attributes.get("upper") - assert float(config["binary_sensor"]["hysteresis"]) == state.attributes.get( - "hysteresis" + assert state.attributes.get("entity_id") == "sensor.test_monitored" + assert state.attributes.get("sensor_value") == 16 + assert state.attributes.get("position") == "in_range" + assert state.attributes.get("lower") == float(config["binary_sensor"]["lower"]) + assert state.attributes.get("upper") == float(config["binary_sensor"]["upper"]) + assert state.attributes.get("hysteresis") == float( + config["binary_sensor"]["hysteresis"] ) - assert "range" == state.attributes.get("type") + assert state.attributes.get("type") == "range" assert state.state == "on" @@ -223,7 +223,7 @@ async def test_sensor_in_range_with_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "in_range" == state.attributes.get("position") + assert state.attributes.get("position") == "in_range" assert state.state == "on" hass.states.async_set("sensor.test_monitored", 7) @@ -231,7 +231,7 @@ async def test_sensor_in_range_with_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "below" == state.attributes.get("position") + assert state.attributes.get("position") == "below" assert state.state == "off" hass.states.async_set("sensor.test_monitored", 12) @@ -239,7 +239,7 @@ async def test_sensor_in_range_with_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "below" == state.attributes.get("position") + assert state.attributes.get("position") == "below" assert state.state == "off" hass.states.async_set("sensor.test_monitored", 13) @@ -247,7 +247,7 @@ async def test_sensor_in_range_with_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "in_range" == state.attributes.get("position") + assert state.attributes.get("position") == "in_range" assert state.state == "on" hass.states.async_set("sensor.test_monitored", 22) @@ -255,7 +255,7 @@ async def test_sensor_in_range_with_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "in_range" == state.attributes.get("position") + assert state.attributes.get("position") == "in_range" assert state.state == "on" hass.states.async_set("sensor.test_monitored", 23) @@ -263,7 +263,7 @@ async def test_sensor_in_range_with_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "above" == state.attributes.get("position") + assert state.attributes.get("position") == "above" assert state.state == "off" hass.states.async_set("sensor.test_monitored", 18) @@ -271,7 +271,7 @@ async def test_sensor_in_range_with_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "above" == state.attributes.get("position") + assert state.attributes.get("position") == "above" assert state.state == "off" hass.states.async_set("sensor.test_monitored", 17) @@ -279,7 +279,7 @@ async def test_sensor_in_range_with_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "in_range" == state.attributes.get("position") + assert state.attributes.get("position") == "in_range" assert state.state == "on" @@ -304,13 +304,13 @@ async def test_sensor_in_range_unknown_state(hass): state = hass.states.get("binary_sensor.threshold") - assert "sensor.test_monitored" == state.attributes.get("entity_id") - assert 16 == state.attributes.get("sensor_value") - assert "in_range" == state.attributes.get("position") - assert float(config["binary_sensor"]["lower"]) == state.attributes.get("lower") - assert float(config["binary_sensor"]["upper"]) == state.attributes.get("upper") - assert 0.0 == state.attributes.get("hysteresis") - assert "range" == state.attributes.get("type") + assert state.attributes.get("entity_id") == "sensor.test_monitored" + assert state.attributes.get("sensor_value") == 16 + assert state.attributes.get("position") == "in_range" + assert state.attributes.get("lower") == float(config["binary_sensor"]["lower"]) + assert state.attributes.get("upper") == float(config["binary_sensor"]["upper"]) + assert state.attributes.get("hysteresis") == 0.0 + assert state.attributes.get("type") == "range" assert state.state == "on" @@ -319,7 +319,7 @@ async def test_sensor_in_range_unknown_state(hass): state = hass.states.get("binary_sensor.threshold") - assert "unknown" == state.attributes.get("position") + assert state.attributes.get("position") == "unknown" assert state.state == "off" @@ -341,8 +341,8 @@ async def test_sensor_lower_zero_threshold(hass): state = hass.states.get("binary_sensor.threshold") - assert "lower" == state.attributes.get("type") - assert float(config["binary_sensor"]["lower"]) == state.attributes.get("lower") + assert state.attributes.get("type") == "lower" + assert state.attributes.get("lower") == float(config["binary_sensor"]["lower"]) assert state.state == "off" @@ -372,8 +372,8 @@ async def test_sensor_upper_zero_threshold(hass): state = hass.states.get("binary_sensor.threshold") - assert "upper" == state.attributes.get("type") - assert float(config["binary_sensor"]["upper"]) == state.attributes.get("upper") + assert state.attributes.get("type") == "upper" + assert state.attributes.get("upper") == float(config["binary_sensor"]["upper"]) assert state.state == "off" diff --git a/tests/components/timer/test_init.py b/tests/components/timer/test_init.py index 21f48ed6147..92958a7bce6 100644 --- a/tests/components/timer/test_init.py +++ b/tests/components/timer/test_init.py @@ -120,16 +120,16 @@ async def test_config_options(hass): assert state_2 is not None assert state_3 is not None - assert STATUS_IDLE == state_1.state + assert state_1.state == STATUS_IDLE assert ATTR_ICON not in state_1.attributes assert ATTR_FRIENDLY_NAME not in state_1.attributes - assert STATUS_IDLE == state_2.state - assert "Hello World" == state_2.attributes.get(ATTR_FRIENDLY_NAME) - assert "mdi:work" == state_2.attributes.get(ATTR_ICON) - assert "0:00:10" == state_2.attributes.get(ATTR_DURATION) + assert state_2.state == STATUS_IDLE + assert state_2.attributes.get(ATTR_FRIENDLY_NAME) == "Hello World" + assert state_2.attributes.get(ATTR_ICON) == "mdi:work" + assert state_2.attributes.get(ATTR_DURATION) == "0:00:10" - assert STATUS_IDLE == state_3.state + assert state_3.state == STATUS_IDLE assert str(cv.time_period(DEFAULT_DURATION)) == state_3.attributes.get( CONF_DURATION ) @@ -280,14 +280,14 @@ async def test_config_reload(hass, hass_admin_user, hass_read_only_user): assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is None - assert STATUS_IDLE == state_1.state + assert state_1.state == STATUS_IDLE assert ATTR_ICON not in state_1.attributes assert ATTR_FRIENDLY_NAME not in state_1.attributes - assert STATUS_IDLE == state_2.state - assert "Hello World" == state_2.attributes.get(ATTR_FRIENDLY_NAME) - assert "mdi:work" == state_2.attributes.get(ATTR_ICON) - assert "0:00:10" == state_2.attributes.get(ATTR_DURATION) + assert state_2.state == STATUS_IDLE + assert state_2.attributes.get(ATTR_FRIENDLY_NAME) == "Hello World" + assert state_2.attributes.get(ATTR_ICON) == "mdi:work" + assert state_2.attributes.get(ATTR_DURATION) == "0:00:10" with patch( "homeassistant.config.load_yaml_config_file", @@ -331,12 +331,12 @@ async def test_config_reload(hass, hass_admin_user, hass_read_only_user): assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is not None - assert STATUS_IDLE == state_2.state - assert "Hello World reloaded" == state_2.attributes.get(ATTR_FRIENDLY_NAME) - assert "mdi:work-reloaded" == state_2.attributes.get(ATTR_ICON) - assert "0:00:20" == state_2.attributes.get(ATTR_DURATION) + assert state_2.state == STATUS_IDLE + assert state_2.attributes.get(ATTR_FRIENDLY_NAME) == "Hello World reloaded" + assert state_2.attributes.get(ATTR_ICON) == "mdi:work-reloaded" + assert state_2.attributes.get(ATTR_DURATION) == "0:00:20" - assert STATUS_IDLE == state_3.state + assert state_3.state == STATUS_IDLE assert ATTR_ICON not in state_3.attributes assert ATTR_FRIENDLY_NAME not in state_3.attributes diff --git a/tests/components/totalconnect/test_alarm_control_panel.py b/tests/components/totalconnect/test_alarm_control_panel.py index ba929c0bc54..12ad53733b5 100644 --- a/tests/components/totalconnect/test_alarm_control_panel.py +++ b/tests/components/totalconnect/test_alarm_control_panel.py @@ -54,14 +54,14 @@ async def test_arm_home_success(hass): side_effect=responses, ): await setup_platform(hass, ALARM_DOMAIN) - assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED await hass.services.async_call( ALARM_DOMAIN, SERVICE_ALARM_ARM_HOME, DATA, blocking=True ) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_HOME == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_HOME async def test_arm_home_failure(hass): @@ -72,7 +72,7 @@ async def test_arm_home_failure(hass): side_effect=responses, ): await setup_platform(hass, ALARM_DOMAIN) - assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED with pytest.raises(HomeAssistantError) as err: await hass.services.async_call( @@ -80,7 +80,7 @@ async def test_arm_home_failure(hass): ) await hass.async_block_till_done() assert f"{err.value}" == "TotalConnect failed to arm home test." - assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED async def test_arm_home_invalid_usercode(hass): @@ -91,7 +91,7 @@ async def test_arm_home_invalid_usercode(hass): side_effect=responses, ): await setup_platform(hass, ALARM_DOMAIN) - assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED with pytest.raises(HomeAssistantError) as err: await hass.services.async_call( @@ -99,7 +99,7 @@ async def test_arm_home_invalid_usercode(hass): ) await hass.async_block_till_done() assert f"{err.value}" == "TotalConnect failed to arm home test." - assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED async def test_arm_away_success(hass): @@ -110,13 +110,13 @@ async def test_arm_away_success(hass): side_effect=responses, ): await setup_platform(hass, ALARM_DOMAIN) - assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED await hass.services.async_call( ALARM_DOMAIN, SERVICE_ALARM_ARM_AWAY, DATA, blocking=True ) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_AWAY async def test_arm_away_failure(hass): @@ -127,7 +127,7 @@ async def test_arm_away_failure(hass): side_effect=responses, ): await setup_platform(hass, ALARM_DOMAIN) - assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED with pytest.raises(HomeAssistantError) as err: await hass.services.async_call( @@ -135,7 +135,7 @@ async def test_arm_away_failure(hass): ) await hass.async_block_till_done() assert f"{err.value}" == "TotalConnect failed to arm away test." - assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED async def test_disarm_success(hass): @@ -146,13 +146,13 @@ async def test_disarm_success(hass): side_effect=responses, ): await setup_platform(hass, ALARM_DOMAIN) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_AWAY await hass.services.async_call( ALARM_DOMAIN, SERVICE_ALARM_DISARM, DATA, blocking=True ) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED async def test_disarm_failure(hass): @@ -163,7 +163,7 @@ async def test_disarm_failure(hass): side_effect=responses, ): await setup_platform(hass, ALARM_DOMAIN) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_AWAY with pytest.raises(HomeAssistantError) as err: await hass.services.async_call( @@ -171,7 +171,7 @@ async def test_disarm_failure(hass): ) await hass.async_block_till_done() assert f"{err.value}" == "TotalConnect failed to disarm test." - assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_AWAY async def test_disarm_invalid_usercode(hass): @@ -182,7 +182,7 @@ async def test_disarm_invalid_usercode(hass): side_effect=responses, ): await setup_platform(hass, ALARM_DOMAIN) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_AWAY with pytest.raises(HomeAssistantError) as err: await hass.services.async_call( @@ -190,4 +190,4 @@ async def test_disarm_invalid_usercode(hass): ) await hass.async_block_till_done() assert f"{err.value}" == "TotalConnect failed to disarm test." - assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_AWAY diff --git a/tests/components/traccar/test_init.py b/tests/components/traccar/test_init.py index 0e741751b8a..f372358ea1d 100644 --- a/tests/components/traccar/test_init.py +++ b/tests/components/traccar/test_init.py @@ -114,7 +114,7 @@ async def test_enter_and_exit(hass, client, webhook_id): state_name = hass.states.get( "{}.{}".format(DEVICE_TRACKER_DOMAIN, data["id"]) ).state - assert STATE_HOME == state_name + assert state_name == STATE_HOME # Enter Home again req = await client.post(url, params=data) @@ -123,7 +123,7 @@ async def test_enter_and_exit(hass, client, webhook_id): state_name = hass.states.get( "{}.{}".format(DEVICE_TRACKER_DOMAIN, data["id"]) ).state - assert STATE_HOME == state_name + assert state_name == STATE_HOME data["lon"] = 0 data["lat"] = 0 @@ -135,7 +135,7 @@ async def test_enter_and_exit(hass, client, webhook_id): state_name = hass.states.get( "{}.{}".format(DEVICE_TRACKER_DOMAIN, data["id"]) ).state - assert STATE_NOT_HOME == state_name + assert state_name == STATE_NOT_HOME dev_reg = dr.async_get(hass) assert len(dev_reg.devices) == 1 @@ -237,7 +237,7 @@ async def test_load_unload_entry(hass, client, webhook_id): state_name = hass.states.get( "{}.{}".format(DEVICE_TRACKER_DOMAIN, data["id"]) ).state - assert STATE_HOME == state_name + assert state_name == STATE_HOME assert len(hass.data[DATA_DISPATCHER][TRACKER_UPDATE]) == 1 entry = hass.config_entries.async_entries(DOMAIN)[0] diff --git a/tests/components/uk_transport/test_sensor.py b/tests/components/uk_transport/test_sensor.py index 54bc122aa56..9d179b5b06b 100644 --- a/tests/components/uk_transport/test_sensor.py +++ b/tests/components/uk_transport/test_sensor.py @@ -53,11 +53,11 @@ async def test_bus(hass): bus_state = hass.states.get("sensor.next_bus_to_wantage") assert None is not bus_state - assert f"Next bus to {BUS_DIRECTION}" == bus_state.name - assert BUS_ATCOCODE == bus_state.attributes[ATTR_ATCOCODE] - assert "Harwell Campus" == bus_state.attributes[ATTR_LOCALITY] - assert "Bus Station" == bus_state.attributes[ATTR_STOP_NAME] - assert 2 == len(bus_state.attributes.get(ATTR_NEXT_BUSES)) + assert bus_state.name == f"Next bus to {BUS_DIRECTION}" + assert bus_state.attributes[ATTR_ATCOCODE] == BUS_ATCOCODE + assert bus_state.attributes[ATTR_LOCALITY] == "Harwell Campus" + assert bus_state.attributes[ATTR_STOP_NAME] == "Bus Station" + assert len(bus_state.attributes.get(ATTR_NEXT_BUSES)) == 2 direction_re = re.compile(BUS_DIRECTION) for bus in bus_state.attributes.get(ATTR_NEXT_BUSES): @@ -77,13 +77,13 @@ async def test_train(hass): train_state = hass.states.get("sensor.next_train_to_WAT") assert None is not train_state - assert f"Next train to {TRAIN_DESTINATION_NAME}" == train_state.name - assert TRAIN_STATION_CODE == train_state.attributes[ATTR_STATION_CODE] - assert TRAIN_DESTINATION_NAME == train_state.attributes[ATTR_CALLING_AT] - assert 25 == len(train_state.attributes.get(ATTR_NEXT_TRAINS)) + assert train_state.name == f"Next train to {TRAIN_DESTINATION_NAME}" + assert train_state.attributes[ATTR_STATION_CODE] == TRAIN_STATION_CODE + assert train_state.attributes[ATTR_CALLING_AT] == TRAIN_DESTINATION_NAME + assert len(train_state.attributes.get(ATTR_NEXT_TRAINS)) == 25 assert ( - "London Waterloo" - == train_state.attributes[ATTR_NEXT_TRAINS][0]["destination_name"] + train_state.attributes[ATTR_NEXT_TRAINS][0]["destination_name"] + == "London Waterloo" ) - assert "06:13" == train_state.attributes[ATTR_NEXT_TRAINS][0]["estimated"] + assert train_state.attributes[ATTR_NEXT_TRAINS][0]["estimated"] == "06:13" diff --git a/tests/components/unifi_direct/test_device_tracker.py b/tests/components/unifi_direct/test_device_tracker.py index 1f96e3fd84b..9f594901e94 100644 --- a/tests/components/unifi_direct/test_device_tracker.py +++ b/tests/components/unifi_direct/test_device_tracker.py @@ -77,9 +77,9 @@ async def test_get_device_name(mock_ssh, hass): mock_ssh.return_value.before = load_fixture("unifi_direct.txt") scanner = get_scanner(hass, conf_dict) devices = scanner.scan_devices() - assert 23 == len(devices) - assert "iPhone" == scanner.get_device_name("98:00:c6:56:34:12") - assert "iPhone" == scanner.get_device_name("98:00:C6:56:34:12") + assert len(devices) == 23 + assert scanner.get_device_name("98:00:c6:56:34:12") == "iPhone" + assert scanner.get_device_name("98:00:C6:56:34:12") == "iPhone" @patch("pexpect.pxssh.pxssh.logout") diff --git a/tests/components/universal/test_media_player.py b/tests/components/universal/test_media_player.py index 3914f580724..054c3b89541 100644 --- a/tests/components/universal/test_media_player.py +++ b/tests/components/universal/test_media_player.py @@ -357,7 +357,7 @@ class TestMediaPlayer(unittest.TestCase): except MultipleInvalid: setup_ok = False assert not setup_ok - assert 0 == len(entities) + assert len(entities) == 0 asyncio.run_coroutine_threadsafe( universal.async_setup_platform( @@ -365,8 +365,8 @@ class TestMediaPlayer(unittest.TestCase): ), self.hass.loop, ).result() - assert 1 == len(entities) - assert "test" == entities[0].name + assert len(entities) == 1 + assert entities[0].name == "test" def test_master_state(self): """Test master state property.""" @@ -382,9 +382,9 @@ class TestMediaPlayer(unittest.TestCase): ump = universal.UniversalMediaPlayer(self.hass, **config) - assert STATE_OFF == ump.master_state + assert ump.master_state == STATE_OFF self.hass.states.set(self.mock_state_switch_id, STATE_ON) - assert STATE_ON == ump.master_state + assert ump.master_state == STATE_ON def test_master_state_with_bad_attrs(self): """Test master state property.""" @@ -394,7 +394,7 @@ class TestMediaPlayer(unittest.TestCase): ump = universal.UniversalMediaPlayer(self.hass, **config) - assert STATE_OFF == ump.master_state + assert ump.master_state == STATE_OFF def test_active_child_state(self): """Test active child state property.""" @@ -454,7 +454,7 @@ class TestMediaPlayer(unittest.TestCase): self.mock_mp_1.schedule_update_ha_state() self.hass.block_till_done() asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - assert STATE_PLAYING == ump.state + assert ump.state == STATE_PLAYING def test_state_with_children_and_attrs(self): """Test media player with children and master state.""" @@ -464,21 +464,21 @@ class TestMediaPlayer(unittest.TestCase): ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"]) asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - assert STATE_OFF == ump.state + assert ump.state == STATE_OFF self.hass.states.set(self.mock_state_switch_id, STATE_ON) asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - assert STATE_ON == ump.state + assert ump.state == STATE_ON self.mock_mp_1._state = STATE_PLAYING self.mock_mp_1.schedule_update_ha_state() self.hass.block_till_done() asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - assert STATE_PLAYING == ump.state + assert ump.state == STATE_PLAYING self.hass.states.set(self.mock_state_switch_id, STATE_OFF) asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - assert STATE_OFF == ump.state + assert ump.state == STATE_OFF def test_volume_level(self): """Test volume level property.""" @@ -494,13 +494,13 @@ class TestMediaPlayer(unittest.TestCase): self.mock_mp_1.schedule_update_ha_state() self.hass.block_till_done() asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - assert 0 == ump.volume_level + assert ump.volume_level == 0 self.mock_mp_1._volume_level = 1 self.mock_mp_1.schedule_update_ha_state() self.hass.block_till_done() asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - assert 1 == ump.volume_level + assert ump.volume_level == 1 def test_media_image_url(self): """Test media_image_url property.""" @@ -550,10 +550,10 @@ class TestMediaPlayer(unittest.TestCase): ump = universal.UniversalMediaPlayer(self.hass, **config) - assert "['music', 'movie']" == ump.sound_mode_list + assert ump.sound_mode_list == "['music', 'movie']" self.hass.states.set(self.mock_sound_mode_list_id, ["music", "movie", "game"]) - assert "['music', 'movie', 'game']" == ump.sound_mode_list + assert ump.sound_mode_list == "['music', 'movie', 'game']" def test_source_list_children_and_attr(self): """Test source list property w/ children and attrs.""" @@ -561,10 +561,10 @@ class TestMediaPlayer(unittest.TestCase): ump = universal.UniversalMediaPlayer(self.hass, **config) - assert "['dvd', 'htpc']" == ump.source_list + assert ump.source_list == "['dvd', 'htpc']" self.hass.states.set(self.mock_source_list_id, ["dvd", "htpc", "game"]) - assert "['dvd', 'htpc', 'game']" == ump.source_list + assert ump.source_list == "['dvd', 'htpc', 'game']" def test_sound_mode_children_and_attr(self): """Test sound modeproperty w/ children and attrs.""" @@ -572,10 +572,10 @@ class TestMediaPlayer(unittest.TestCase): ump = universal.UniversalMediaPlayer(self.hass, **config) - assert "music" == ump.sound_mode + assert ump.sound_mode == "music" self.hass.states.set(self.mock_sound_mode_id, "movie") - assert "movie" == ump.sound_mode + assert ump.sound_mode == "movie" def test_source_children_and_attr(self): """Test source property w/ children and attrs.""" @@ -583,10 +583,10 @@ class TestMediaPlayer(unittest.TestCase): ump = universal.UniversalMediaPlayer(self.hass, **config) - assert "dvd" == ump.source + assert ump.source == "dvd" self.hass.states.set(self.mock_source_id, "htpc") - assert "htpc" == ump.source + assert ump.source == "htpc" def test_volume_level_children_and_attr(self): """Test volume level property w/ children and attrs.""" @@ -594,10 +594,10 @@ class TestMediaPlayer(unittest.TestCase): ump = universal.UniversalMediaPlayer(self.hass, **config) - assert 0 == ump.volume_level + assert ump.volume_level == 0 self.hass.states.set(self.mock_volume_id, 100) - assert 100 == ump.volume_level + assert ump.volume_level == 100 def test_is_volume_muted_children_and_attr(self): """Test is volume muted property w/ children and attrs.""" @@ -618,14 +618,14 @@ class TestMediaPlayer(unittest.TestCase): ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"]) asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - assert 0 == ump.supported_features + assert ump.supported_features == 0 self.mock_mp_1._supported_features = 512 self.mock_mp_1._state = STATE_PLAYING self.mock_mp_1.schedule_update_ha_state() self.hass.block_till_done() asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - assert 512 == ump.supported_features + assert ump.supported_features == 512 def test_supported_features_children_and_cmds(self): """Test supported media commands with children and attrs.""" @@ -717,8 +717,8 @@ class TestMediaPlayer(unittest.TestCase): asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() asyncio.run_coroutine_threadsafe(ump.async_turn_off(), self.hass.loop).result() - assert 0 == len(self.mock_mp_1.service_calls["turn_off"]) - assert 0 == len(self.mock_mp_2.service_calls["turn_off"]) + assert len(self.mock_mp_1.service_calls["turn_off"]) == 0 + assert len(self.mock_mp_2.service_calls["turn_off"]) == 0 def test_service_call_to_child(self): """Test service calls that should be routed to a child.""" @@ -734,96 +734,96 @@ class TestMediaPlayer(unittest.TestCase): asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() asyncio.run_coroutine_threadsafe(ump.async_turn_off(), self.hass.loop).result() - assert 1 == len(self.mock_mp_2.service_calls["turn_off"]) + assert len(self.mock_mp_2.service_calls["turn_off"]) == 1 asyncio.run_coroutine_threadsafe(ump.async_turn_on(), self.hass.loop).result() - assert 1 == len(self.mock_mp_2.service_calls["turn_on"]) + assert len(self.mock_mp_2.service_calls["turn_on"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_mute_volume(True), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["mute_volume"]) + assert len(self.mock_mp_2.service_calls["mute_volume"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_set_volume_level(0.5), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["set_volume_level"]) + assert len(self.mock_mp_2.service_calls["set_volume_level"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_media_play(), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["media_play"]) + assert len(self.mock_mp_2.service_calls["media_play"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_media_pause(), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["media_pause"]) + assert len(self.mock_mp_2.service_calls["media_pause"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_media_stop(), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["media_stop"]) + assert len(self.mock_mp_2.service_calls["media_stop"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_media_previous_track(), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["media_previous_track"]) + assert len(self.mock_mp_2.service_calls["media_previous_track"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_media_next_track(), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["media_next_track"]) + assert len(self.mock_mp_2.service_calls["media_next_track"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_media_seek(100), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["media_seek"]) + assert len(self.mock_mp_2.service_calls["media_seek"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_play_media("movie", "batman"), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["play_media"]) + assert len(self.mock_mp_2.service_calls["play_media"]) == 1 asyncio.run_coroutine_threadsafe(ump.async_volume_up(), self.hass.loop).result() - assert 1 == len(self.mock_mp_2.service_calls["volume_up"]) + assert len(self.mock_mp_2.service_calls["volume_up"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_volume_down(), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["volume_down"]) + assert len(self.mock_mp_2.service_calls["volume_down"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_media_play_pause(), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["media_play_pause"]) + assert len(self.mock_mp_2.service_calls["media_play_pause"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_select_sound_mode("music"), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["select_sound_mode"]) + assert len(self.mock_mp_2.service_calls["select_sound_mode"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_select_source("dvd"), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["select_source"]) + assert len(self.mock_mp_2.service_calls["select_source"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_clear_playlist(), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["clear_playlist"]) + assert len(self.mock_mp_2.service_calls["clear_playlist"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_set_repeat(True), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["repeat_set"]) + assert len(self.mock_mp_2.service_calls["repeat_set"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_set_shuffle(True), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["shuffle_set"]) + assert len(self.mock_mp_2.service_calls["shuffle_set"]) == 1 asyncio.run_coroutine_threadsafe(ump.async_toggle(), self.hass.loop).result() - assert 1 == len(self.mock_mp_2.service_calls["toggle"]) + assert len(self.mock_mp_2.service_calls["toggle"]) == 1 def test_service_call_to_command(self): """Test service call to command.""" @@ -843,7 +843,7 @@ class TestMediaPlayer(unittest.TestCase): asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() asyncio.run_coroutine_threadsafe(ump.async_turn_off(), self.hass.loop).result() - assert 1 == len(service) + assert len(service) == 1 async def test_state_template(hass): diff --git a/tests/components/vultr/test_binary_sensor.py b/tests/components/vultr/test_binary_sensor.py index 7fb0c90362a..e0d71bf20a8 100644 --- a/tests/components/vultr/test_binary_sensor.py +++ b/tests/components/vultr/test_binary_sensor.py @@ -73,35 +73,35 @@ class TestVultrBinarySensorSetup(unittest.TestCase): # Test pre data retrieval if device.subscription == "555555": - assert "Vultr {}" == device.name + assert device.name == "Vultr {}" device.update() device_attrs = device.extra_state_attributes if device.subscription == "555555": - assert "Vultr Another Server" == device.name + assert device.name == "Vultr Another Server" if device.name == "A Server": assert device.is_on is True - assert "power" == device.device_class - assert "on" == device.state - assert "mdi:server" == device.icon - assert "1000" == device_attrs[ATTR_ALLOWED_BANDWIDTH] - assert "yes" == device_attrs[ATTR_AUTO_BACKUPS] - assert "123.123.123.123" == device_attrs[ATTR_IPV4_ADDRESS] - assert "10.05" == device_attrs[ATTR_COST_PER_MONTH] - assert "2013-12-19 14:45:41" == device_attrs[ATTR_CREATED_AT] - assert "576965" == device_attrs[ATTR_SUBSCRIPTION_ID] + assert device.device_class == "power" + assert device.state == "on" + assert device.icon == "mdi:server" + assert device_attrs[ATTR_ALLOWED_BANDWIDTH] == "1000" + assert device_attrs[ATTR_AUTO_BACKUPS] == "yes" + assert device_attrs[ATTR_IPV4_ADDRESS] == "123.123.123.123" + assert device_attrs[ATTR_COST_PER_MONTH] == "10.05" + assert device_attrs[ATTR_CREATED_AT] == "2013-12-19 14:45:41" + assert device_attrs[ATTR_SUBSCRIPTION_ID] == "576965" elif device.name == "Failed Server": assert device.is_on is False - assert "off" == device.state - assert "mdi:server-off" == device.icon - assert "1000" == device_attrs[ATTR_ALLOWED_BANDWIDTH] - assert "no" == device_attrs[ATTR_AUTO_BACKUPS] - assert "192.168.100.50" == device_attrs[ATTR_IPV4_ADDRESS] - assert "73.25" == device_attrs[ATTR_COST_PER_MONTH] - assert "2014-10-13 14:45:41" == device_attrs[ATTR_CREATED_AT] - assert "123456" == device_attrs[ATTR_SUBSCRIPTION_ID] + assert device.state == "off" + assert device.icon == "mdi:server-off" + assert device_attrs[ATTR_ALLOWED_BANDWIDTH] == "1000" + assert device_attrs[ATTR_AUTO_BACKUPS] == "no" + assert device_attrs[ATTR_IPV4_ADDRESS] == "192.168.100.50" + assert device_attrs[ATTR_COST_PER_MONTH] == "73.25" + assert device_attrs[ATTR_CREATED_AT] == "2014-10-13 14:45:41" + assert device_attrs[ATTR_SUBSCRIPTION_ID] == "123456" def test_invalid_sensor_config(self): """Test config type failures.""" diff --git a/tests/components/vultr/test_sensor.py b/tests/components/vultr/test_sensor.py index 2b5e07c80b5..4449859ddb2 100644 --- a/tests/components/vultr/test_sensor.py +++ b/tests/components/vultr/test_sensor.py @@ -73,7 +73,7 @@ class TestVultrSensorSetup(unittest.TestCase): assert setup is None - assert 5 == len(self.DEVICES) + assert len(self.DEVICES) == 5 tested = 0 @@ -87,34 +87,34 @@ class TestVultrSensorSetup(unittest.TestCase): if device.unit_of_measurement == DATA_GIGABYTES: # Test Bandwidth Used if device.subscription == "576965": - assert "Vultr my new server Current Bandwidth Used" == device.name - assert "mdi:chart-histogram" == device.icon - assert 131.51 == device.state - assert "mdi:chart-histogram" == device.icon + assert device.name == "Vultr my new server Current Bandwidth Used" + assert device.icon == "mdi:chart-histogram" + assert device.state == 131.51 + assert device.icon == "mdi:chart-histogram" tested += 1 elif device.subscription == "123456": - assert "Server Current Bandwidth Used" == device.name - assert 957.46 == device.state + assert device.name == "Server Current Bandwidth Used" + assert device.state == 957.46 tested += 1 elif device.unit_of_measurement == "US$": # Test Pending Charges if device.subscription == "576965": # Default 'Vultr {} {}' - assert "Vultr my new server Pending Charges" == device.name - assert "mdi:currency-usd" == device.icon - assert 46.67 == device.state - assert "mdi:currency-usd" == device.icon + assert device.name == "Vultr my new server Pending Charges" + assert device.icon == "mdi:currency-usd" + assert device.state == 46.67 + assert device.icon == "mdi:currency-usd" tested += 1 elif device.subscription == "123456": # Custom name with 1 {} - assert "Server Pending Charges" == device.name - assert "not a number" == device.state + assert device.name == "Server Pending Charges" + assert device.state == "not a number" tested += 1 elif device.subscription == "555555": # No {} in name - assert "VPS Charges" == device.name - assert 5.45 == device.state + assert device.name == "VPS Charges" + assert device.state == 5.45 tested += 1 assert tested == 5 @@ -161,4 +161,4 @@ class TestVultrSensorSetup(unittest.TestCase): ) assert no_sub_setup is None - assert 0 == len(self.DEVICES) + assert len(self.DEVICES) == 0 diff --git a/tests/components/wake_on_lan/test_switch.py b/tests/components/wake_on_lan/test_switch.py index 7b41fd4d75c..1396ae80f1e 100644 --- a/tests/components/wake_on_lan/test_switch.py +++ b/tests/components/wake_on_lan/test_switch.py @@ -41,7 +41,7 @@ async def test_valid_hostname(hass): await hass.async_block_till_done() state = hass.states.get("switch.wake_on_lan") - assert STATE_OFF == state.state + assert state.state == STATE_OFF with patch.object(subprocess, "call", return_value=0): @@ -53,7 +53,7 @@ async def test_valid_hostname(hass): ) state = hass.states.get("switch.wake_on_lan") - assert STATE_ON == state.state + assert state.state == STATE_ON await hass.services.async_call( switch.DOMAIN, @@ -63,7 +63,7 @@ async def test_valid_hostname(hass): ) state = hass.states.get("switch.wake_on_lan") - assert STATE_ON == state.state + assert state.state == STATE_ON async def test_valid_hostname_windows(hass): @@ -82,7 +82,7 @@ async def test_valid_hostname_windows(hass): await hass.async_block_till_done() state = hass.states.get("switch.wake_on_lan") - assert STATE_OFF == state.state + assert state.state == STATE_OFF with patch.object(subprocess, "call", return_value=0), patch.object( platform, "system", return_value="Windows" @@ -95,7 +95,7 @@ async def test_valid_hostname_windows(hass): ) state = hass.states.get("switch.wake_on_lan") - assert STATE_ON == state.state + assert state.state == STATE_ON async def test_broadcast_config_ip_and_port(hass, mock_send_magic_packet): @@ -119,7 +119,7 @@ async def test_broadcast_config_ip_and_port(hass, mock_send_magic_packet): await hass.async_block_till_done() state = hass.states.get("switch.wake_on_lan") - assert STATE_OFF == state.state + assert state.state == STATE_OFF with patch.object(subprocess, "call", return_value=0): @@ -155,7 +155,7 @@ async def test_broadcast_config_ip(hass, mock_send_magic_packet): await hass.async_block_till_done() state = hass.states.get("switch.wake_on_lan") - assert STATE_OFF == state.state + assert state.state == STATE_OFF with patch.object(subprocess, "call", return_value=0): @@ -183,7 +183,7 @@ async def test_broadcast_config_port(hass, mock_send_magic_packet): await hass.async_block_till_done() state = hass.states.get("switch.wake_on_lan") - assert STATE_OFF == state.state + assert state.state == STATE_OFF with patch.object(subprocess, "call", return_value=0): @@ -216,7 +216,7 @@ async def test_off_script(hass): calls = async_mock_service(hass, "shell_command", "turn_off_target") state = hass.states.get("switch.wake_on_lan") - assert STATE_OFF == state.state + assert state.state == STATE_OFF with patch.object(subprocess, "call", return_value=0): @@ -228,7 +228,7 @@ async def test_off_script(hass): ) state = hass.states.get("switch.wake_on_lan") - assert STATE_ON == state.state + assert state.state == STATE_ON assert len(calls) == 0 with patch.object(subprocess, "call", return_value=2): @@ -241,7 +241,7 @@ async def test_off_script(hass): ) state = hass.states.get("switch.wake_on_lan") - assert STATE_OFF == state.state + assert state.state == STATE_OFF assert len(calls) == 1 @@ -262,7 +262,7 @@ async def test_invalid_hostname_windows(hass): await hass.async_block_till_done() state = hass.states.get("switch.wake_on_lan") - assert STATE_OFF == state.state + assert state.state == STATE_OFF with patch.object(subprocess, "call", return_value=2): @@ -274,7 +274,7 @@ async def test_invalid_hostname_windows(hass): ) state = hass.states.get("switch.wake_on_lan") - assert STATE_OFF == state.state + assert state.state == STATE_OFF async def test_no_hostname_state(hass): diff --git a/tests/components/xiaomi/test_device_tracker.py b/tests/components/xiaomi/test_device_tracker.py index af5e192b9cf..b5764fac089 100644 --- a/tests/components/xiaomi/test_device_tracker.py +++ b/tests/components/xiaomi/test_device_tracker.py @@ -226,9 +226,9 @@ async def test_valid_credential(mock_get, mock_post, hass): } scanner = get_scanner(hass, config) assert scanner is not None - assert 2 == len(scanner.scan_devices()) - assert "Device1" == scanner.get_device_name("23:83:BF:F6:38:A0") - assert "Device2" == scanner.get_device_name("1D:98:EC:5E:D5:A6") + assert len(scanner.scan_devices()) == 2 + assert scanner.get_device_name("23:83:BF:F6:38:A0") == "Device1" + assert scanner.get_device_name("1D:98:EC:5E:D5:A6") == "Device2" @patch("requests.get", side_effect=mocked_requests) @@ -250,6 +250,6 @@ async def test_token_timed_out(mock_get, mock_post, hass): } scanner = get_scanner(hass, config) assert scanner is not None - assert 2 == len(scanner.scan_devices()) - assert "Device1" == scanner.get_device_name("23:83:BF:F6:38:A0") - assert "Device2" == scanner.get_device_name("1D:98:EC:5E:D5:A6") + assert len(scanner.scan_devices()) == 2 + assert scanner.get_device_name("23:83:BF:F6:38:A0") == "Device1" + assert scanner.get_device_name("1D:98:EC:5E:D5:A6") == "Device2" diff --git a/tests/components/zone/test_init.py b/tests/components/zone/test_init.py index 38846050643..8d0fddb921c 100644 --- a/tests/components/zone/test_init.py +++ b/tests/components/zone/test_init.py @@ -144,7 +144,7 @@ async def test_active_zone_skips_passive_zones_2(hass): ) await hass.async_block_till_done() active = zone.async_active_zone(hass, 32.880700, -117.237561) - assert "zone.active_zone" == active.entity_id + assert active.entity_id == "zone.active_zone" async def test_active_zone_prefers_smaller_zone_if_same_distance(hass): @@ -173,7 +173,7 @@ async def test_active_zone_prefers_smaller_zone_if_same_distance(hass): ) active = zone.async_active_zone(hass, latitude, longitude) - assert "zone.small_zone" == active.entity_id + assert active.entity_id == "zone.small_zone" async def test_active_zone_prefers_smaller_zone_if_same_distance_2(hass): @@ -196,7 +196,7 @@ async def test_active_zone_prefers_smaller_zone_if_same_distance_2(hass): ) active = zone.async_active_zone(hass, latitude, longitude) - assert "zone.smallest_zone" == active.entity_id + assert active.entity_id == "zone.smallest_zone" async def test_in_zone_works_for_passive_zones(hass): diff --git a/tests/components/zone/test_trigger.py b/tests/components/zone/test_trigger.py index 52fbb55ba97..2b0d2f27afd 100644 --- a/tests/components/zone/test_trigger.py +++ b/tests/components/zone/test_trigger.py @@ -84,7 +84,7 @@ async def test_if_fires_on_zone_enter(hass, calls): assert len(calls) == 1 assert calls[0].context.parent_id == context.id - assert "zone - test.entity - hello - hello - test" == calls[0].data["some"] + assert calls[0].data["some"] == "zone - test.entity - hello - hello - test" # Set out of zone again so we can trigger call hass.states.async_set( diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index f58fd1b22f9..232d7bbb8b6 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -934,7 +934,7 @@ def test_socket_timeout(): # pylint: disable=invalid-name with pytest.raises(vol.Invalid): schema(-1) - assert _GLOBAL_DEFAULT_TIMEOUT == schema(None) + assert schema(None) == _GLOBAL_DEFAULT_TIMEOUT assert schema(1) == 1.0 diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 4259e7302ed..193c7e2aa55 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -880,41 +880,38 @@ def test_relative_time(mock_is_safe, hass): """Test relative_time method.""" now = datetime.strptime("2000-01-01 10:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z") with patch("homeassistant.util.dt.now", return_value=now): - assert ( - "1 hour" - == template.Template( - '{{relative_time(strptime("2000-01-01 09:00:00", "%Y-%m-%d %H:%M:%S"))}}', - hass, - ).async_render() - ) - assert ( - "2 hours" - == template.Template( - '{{relative_time(strptime("2000-01-01 09:00:00 +01:00", "%Y-%m-%d %H:%M:%S %z"))}}', - hass, - ).async_render() - ) - assert ( - "1 hour" - == template.Template( - '{{relative_time(strptime("2000-01-01 03:00:00 -06:00", "%Y-%m-%d %H:%M:%S %z"))}}', - hass, - ).async_render() - ) - assert ( - str(template.strptime("2000-01-01 11:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z")) - == template.Template( - '{{relative_time(strptime("2000-01-01 11:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z"))}}', - hass, - ).async_render() - ) - assert ( - "string" - == template.Template( - '{{relative_time("string")}}', - hass, - ).async_render() + result = template.Template( + '{{relative_time(strptime("2000-01-01 09:00:00", "%Y-%m-%d %H:%M:%S"))}}', + hass, + ).async_render() + assert result == "1 hour" + + result = template.Template( + '{{relative_time(strptime("2000-01-01 09:00:00 +01:00", "%Y-%m-%d %H:%M:%S %z"))}}', + hass, + ).async_render() + assert result == "2 hours" + + result = template.Template( + '{{relative_time(strptime("2000-01-01 03:00:00 -06:00", "%Y-%m-%d %H:%M:%S %z"))}}', + hass, + ).async_render() + assert result == "1 hour" + + result1 = str( + template.strptime("2000-01-01 11:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z") ) + result2 = template.Template( + '{{relative_time(strptime("2000-01-01 11:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z"))}}', + hass, + ).async_render() + assert result1 == result2 + + result = template.Template( + '{{relative_time("string")}}', + hass, + ).async_render() + assert result == "string" @patch( @@ -925,55 +922,46 @@ def test_timedelta(mock_is_safe, hass): """Test relative_time method.""" now = datetime.strptime("2000-01-01 10:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z") with patch("homeassistant.util.dt.now", return_value=now): - assert ( - "0:02:00" - == template.Template( - "{{timedelta(seconds=120)}}", - hass, - ).async_render() - ) - assert ( - "1 day, 0:00:00" - == template.Template( - "{{timedelta(seconds=86400)}}", - hass, - ).async_render() - ) - assert ( - "1 day, 4:00:00" - == template.Template( - "{{timedelta(days=1, hours=4)}}", - hass, - ).async_render() - ) - assert ( - "1 hour" - == template.Template( - "{{relative_time(now() - timedelta(seconds=3600))}}", - hass, - ).async_render() - ) - assert ( - "1 day" - == template.Template( - "{{relative_time(now() - timedelta(seconds=86400))}}", - hass, - ).async_render() - ) - assert ( - "1 day" - == template.Template( - "{{relative_time(now() - timedelta(seconds=86401))}}", - hass, - ).async_render() - ) - assert ( - "15 days" - == template.Template( - "{{relative_time(now() - timedelta(weeks=2, days=1))}}", - hass, - ).async_render() - ) + result = template.Template( + "{{timedelta(seconds=120)}}", + hass, + ).async_render() + assert result == "0:02:00" + + result = template.Template( + "{{timedelta(seconds=86400)}}", + hass, + ).async_render() + assert result == "1 day, 0:00:00" + + result = template.Template( + "{{timedelta(days=1, hours=4)}}", hass + ).async_render() + assert result == "1 day, 4:00:00" + + result = template.Template( + "{{relative_time(now() - timedelta(seconds=3600))}}", + hass, + ).async_render() + assert result == "1 hour" + + result = template.Template( + "{{relative_time(now() - timedelta(seconds=86400))}}", + hass, + ).async_render() + assert result == "1 day" + + result = template.Template( + "{{relative_time(now() - timedelta(seconds=86401))}}", + hass, + ).async_render() + assert result == "1 day" + + result = template.Template( + "{{relative_time(now() - timedelta(weeks=2, days=1))}}", + hass, + ).async_render() + assert result == "15 days" def test_regex_match(hass): diff --git a/tests/util/test_dt.py b/tests/util/test_dt.py index 1327bc51f8a..50013012201 100644 --- a/tests/util/test_dt.py +++ b/tests/util/test_dt.py @@ -19,7 +19,7 @@ def test_get_time_zone_retrieves_valid_time_zone(): time_zone = dt_util.get_time_zone(TEST_TIME_ZONE) assert time_zone is not None - assert TEST_TIME_ZONE == time_zone.zone + assert time_zone.zone == TEST_TIME_ZONE def test_get_time_zone_returns_none_for_garbage_time_zone(): diff --git a/tests/util/test_unit_system.py b/tests/util/test_unit_system.py index e0e4524a2f2..74abfef452f 100644 --- a/tests/util/test_unit_system.py +++ b/tests/util/test_unit_system.py @@ -171,11 +171,11 @@ def test_pressure_to_imperial(): def test_properties(): """Test the unit properties are returned as expected.""" - assert LENGTH_KILOMETERS == METRIC_SYSTEM.length_unit - assert TEMP_CELSIUS == METRIC_SYSTEM.temperature_unit - assert MASS_GRAMS == METRIC_SYSTEM.mass_unit - assert VOLUME_LITERS == METRIC_SYSTEM.volume_unit - assert PRESSURE_PA == METRIC_SYSTEM.pressure_unit + assert METRIC_SYSTEM.length_unit == LENGTH_KILOMETERS + assert METRIC_SYSTEM.temperature_unit == TEMP_CELSIUS + assert METRIC_SYSTEM.mass_unit == MASS_GRAMS + assert METRIC_SYSTEM.volume_unit == VOLUME_LITERS + assert METRIC_SYSTEM.pressure_unit == PRESSURE_PA def test_is_metric(): From 863f75e65ef12750fd6e11ba32731c5150291499 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 20 Mar 2021 14:22:01 +0100 Subject: [PATCH 506/831] Improve test coverage of deconz_device (#48141) --- .../components/deconz/deconz_event.py | 5 +- tests/components/deconz/test_deconz_event.py | 30 +++++++++ tests/components/deconz/test_services.py | 67 +++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/deconz/deconz_event.py b/homeassistant/components/deconz/deconz_event.py index 81d3aa94d31..706850477d8 100644 --- a/homeassistant/components/deconz/deconz_event.py +++ b/homeassistant/components/deconz/deconz_event.py @@ -106,8 +106,11 @@ class DeconzEvent(DeconzBase): self.gateway.hass.bus.async_fire(CONF_DECONZ_EVENT, data) - async def async_update_device_registry(self): + async def async_update_device_registry(self) -> None: """Update device registry.""" + if not self.device_info: + return + device_registry = ( await self.gateway.hass.helpers.device_registry.async_get_registry() ) diff --git a/tests/components/deconz/test_deconz_event.py b/tests/components/deconz/test_deconz_event.py index 8a2e6a1d465..fc7544f3918 100644 --- a/tests/components/deconz/test_deconz_event.py +++ b/tests/components/deconz/test_deconz_event.py @@ -171,3 +171,33 @@ async def test_deconz_events(hass, aioclient_mock, mock_deconz_websocket): await hass.config_entries.async_remove(config_entry.entry_id) await hass.async_block_till_done() assert len(hass.states.async_all()) == 0 + + +async def test_deconz_events_bad_unique_id(hass, aioclient_mock, mock_deconz_websocket): + """Verify no devices are created if unique id is bad or missing.""" + data = { + "sensors": { + "1": { + "name": "Switch 1 no unique id", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {}, + }, + "2": { + "name": "Switch 2 bad unique id", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {"battery": 100}, + "uniqueid": "00:00-00", + }, + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + device_registry = await hass.helpers.device_registry.async_get_registry() + + assert len(hass.states.async_all()) == 1 + assert ( + len(async_entries_for_config_entry(device_registry, config_entry.entry_id)) == 2 + ) diff --git a/tests/components/deconz/test_services.py b/tests/components/deconz/test_services.py index a631327351f..249f4dbbb57 100644 --- a/tests/components/deconz/test_services.py +++ b/tests/components/deconz/test_services.py @@ -8,6 +8,7 @@ from homeassistant.components.deconz.const import ( CONF_BRIDGE_ID, DOMAIN as DECONZ_DOMAIN, ) +from homeassistant.components.deconz.deconz_event import CONF_DECONZ_EVENT from homeassistant.components.deconz.services import ( DECONZ_SERVICES, SERVICE_CONFIGURE_DEVICE, @@ -31,6 +32,8 @@ from .test_gateway import ( setup_deconz_integration, ) +from tests.common import async_capture_events + async def test_service_setup(hass): """Verify service setup works.""" @@ -229,6 +232,70 @@ async def test_service_refresh_devices(hass, aioclient_mock): assert len(hass.states.async_all()) == 4 +async def test_service_refresh_devices_trigger_no_state_update(hass, aioclient_mock): + """Verify that gateway.ignore_state_updates are honored.""" + data = { + "sensors": { + "1": { + "name": "Switch 1", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {"battery": 100}, + "uniqueid": "00:00:00:00:00:00:00:01-00", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + assert len(hass.states.async_all()) == 1 + + captured_events = async_capture_events(hass, CONF_DECONZ_EVENT) + + aioclient_mock.clear_requests() + + data = { + "groups": { + "1": { + "id": "Group 1 id", + "name": "Group 1 name", + "type": "LightGroup", + "state": {}, + "action": {}, + "scenes": [{"id": "1", "name": "Scene 1"}], + "lights": ["1"], + } + }, + "lights": { + "1": { + "name": "Light 1 name", + "state": {"reachable": True}, + "type": "Light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + } + }, + "sensors": { + "1": { + "name": "Switch 1", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {"battery": 100}, + "uniqueid": "00:00:00:00:00:00:00:01-00", + } + }, + } + + mock_deconz_request(aioclient_mock, config_entry.data, data) + + await hass.services.async_call( + DECONZ_DOMAIN, SERVICE_DEVICE_REFRESH, service_data={CONF_BRIDGE_ID: BRIDGEID} + ) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 4 + assert len(captured_events) == 0 + + async def test_remove_orphaned_entries_service(hass, aioclient_mock): """Test service works and also don't remove more than expected.""" data = { From f8755a52c2c2a548f8e6c48f94b3662082fa4213 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 20 Mar 2021 15:16:04 +0100 Subject: [PATCH 507/831] Warn on undefined variables in templates (#48140) * Warn on undefined variables in templates * Add test * fix tests * fix tests --- homeassistant/helpers/template.py | 2 +- tests/components/influxdb/test_sensor.py | 16 +++++++++++++--- tests/helpers/test_script.py | 3 +-- tests/helpers/test_template.py | 7 +++++++ 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index cfb7223752e..24c91ec5468 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1318,7 +1318,7 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): def __init__(self, hass, limited=False): """Initialise template environment.""" - super().__init__() + super().__init__(undefined=jinja2.make_logging_undefined(logger=_LOGGER)) self.hass = hass self.template_cache = weakref.WeakValueDictionary() self.filters["round"] = forgiving_round diff --git a/tests/components/influxdb/test_sensor.py b/tests/components/influxdb/test_sensor.py index 55172011f4f..9a353f59e42 100644 --- a/tests/components/influxdb/test_sensor.py +++ b/tests/components/influxdb/test_sensor.py @@ -442,7 +442,7 @@ async def test_error_querying_influx( @pytest.mark.parametrize( - "mock_client, config_ext, queries, set_query_mock, make_resultset", + "mock_client, config_ext, queries, set_query_mock, make_resultset, key", [ ( DEFAULT_API_VERSION, @@ -459,6 +459,7 @@ async def test_error_querying_influx( }, _set_query_mock_v1, _make_v1_resultset, + "where", ), ( API_VERSION_2, @@ -466,12 +467,13 @@ async def test_error_querying_influx( {"queries_flux": [{"name": "test", "query": "{{ illegal.template }}"}]}, _set_query_mock_v2, _make_v2_resultset, + "query", ), ], indirect=["mock_client"], ) async def test_error_rendering_template( - hass, caplog, mock_client, config_ext, queries, set_query_mock, make_resultset + hass, caplog, mock_client, config_ext, queries, set_query_mock, make_resultset, key ): """Test behavior of sensor with error rendering template.""" set_query_mock(mock_client, return_value=make_resultset(42)) @@ -479,7 +481,15 @@ async def test_error_rendering_template( sensors = await _setup(hass, config_ext, queries, ["sensor.test"]) assert sensors[0].state == STATE_UNKNOWN assert ( - len([record for record in caplog.records if record.levelname == "ERROR"]) == 1 + len( + [ + record + for record in caplog.records + if record.levelname == "ERROR" + and f"Could not render {key} template" in record.msg + ] + ) + == 1 ) diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 1f47aa9dbba..8a93d769775 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -1277,8 +1277,7 @@ async def test_repeat_condition_warning(hass, caplog, condition): hass.async_create_task(script_obj.async_run(context=Context())) await asyncio.wait_for(hass.async_block_till_done(), 1) - assert len(caplog.record_tuples) == 1 - assert caplog.record_tuples[0][1] == logging.WARNING + assert f"Error in '{condition}[0]' evaluation" in caplog.text assert len(events) == count diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 193c7e2aa55..da6a8663cc3 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -2497,3 +2497,10 @@ async def test_parse_result(hass): ("0011101.00100001010001", "0011101.00100001010001"), ): assert template.Template(tpl, hass).async_render() == result + + +async def test_undefined_variable(hass, caplog): + """Test a warning is logged on undefined variables.""" + tpl = template.Template("{{ no_such_variable }}", hass) + assert tpl.async_render() == "" + assert "Template variable warning: no_such_variable is undefined" in caplog.text From 08870690a6ed746601110ea2aec7a9fadc3c3350 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 20 Mar 2021 17:23:55 +0100 Subject: [PATCH 508/831] Fix a collection of tests with missing asserts (#48127) Co-authored-by: Martin Hjelmare --- tests/components/hassio/test_http.py | 8 ++++---- tests/components/logger/test_init.py | 6 +++--- tests/components/recorder/test_purge.py | 12 ++++++------ tests/components/recorder/test_util.py | 6 ++---- tests/components/tod/test_binary_sensor.py | 6 +++--- tests/components/universal/test_media_player.py | 8 ++++---- tests/helpers/test_device_registry.py | 2 +- 7 files changed, 23 insertions(+), 25 deletions(-) diff --git a/tests/components/hassio/test_http.py b/tests/components/hassio/test_http.py index 2ec964d8e8b..ff1c348a37b 100644 --- a/tests/components/hassio/test_http.py +++ b/tests/components/hassio/test_http.py @@ -128,8 +128,8 @@ async def test_forwarding_user_info(hassio_client, hass_admin_user, aioclient_mo assert len(aioclient_mock.mock_calls) == 1 req_headers = aioclient_mock.mock_calls[0][-1] - req_headers["X-Hass-User-ID"] == hass_admin_user.id - req_headers["X-Hass-Is-Admin"] == "1" + assert req_headers["X-Hass-User-ID"] == hass_admin_user.id + assert req_headers["X-Hass-Is-Admin"] == "1" async def test_snapshot_upload_headers(hassio_client, aioclient_mock): @@ -147,7 +147,7 @@ async def test_snapshot_upload_headers(hassio_client, aioclient_mock): assert len(aioclient_mock.mock_calls) == 1 req_headers = aioclient_mock.mock_calls[0][-1] - req_headers["Content-Type"] == content_type + assert req_headers["Content-Type"] == content_type async def test_snapshot_download_headers(hassio_client, aioclient_mock): @@ -168,7 +168,7 @@ async def test_snapshot_download_headers(hassio_client, aioclient_mock): assert len(aioclient_mock.mock_calls) == 1 - resp.headers["Content-Disposition"] == content_disposition + assert resp.headers["Content-Disposition"] == content_disposition def test_need_auth(hass): diff --git a/tests/components/logger/test_init.py b/tests/components/logger/test_init.py index abb8c9b0de8..61818d57df9 100644 --- a/tests/components/logger/test_init.py +++ b/tests/components/logger/test_init.py @@ -165,9 +165,9 @@ async def test_can_set_level(hass): logger.DOMAIN, "set_level", {f"{UNCONFIG_NS}.any": "debug"}, blocking=True ) - logging.getLogger(UNCONFIG_NS).level == logging.NOTSET - logging.getLogger(f"{UNCONFIG_NS}.any").level == logging.DEBUG - logging.getLogger(UNCONFIG_NS).level == logging.NOTSET + assert logging.getLogger(UNCONFIG_NS).level == logging.NOTSET + assert logging.getLogger(f"{UNCONFIG_NS}.any").level == logging.DEBUG + assert logging.getLogger(UNCONFIG_NS).level == logging.NOTSET await hass.services.async_call( logger.DOMAIN, "set_default_level", {"level": "debug"}, blocking=True diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index e487e542958..f2fa9bf6400 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -364,9 +364,9 @@ async def test_purge_filtered_states( ) assert states_sensor_excluded.count() == 0 - session.query(States).get(71).old_state_id is None - session.query(States).get(72).old_state_id is None - session.query(States).get(73).old_state_id == 62 # should have been keeped + assert session.query(States).get(72).old_state_id is None + assert session.query(States).get(73).old_state_id is None + assert session.query(States).get(74).old_state_id == 62 # should have been kept async def test_purge_filtered_events( @@ -550,9 +550,9 @@ async def test_purge_filtered_events_state_changed( assert events_purge.count() == 0 assert states.count() == 3 - session.query(States).get(61).old_state_id is None - session.query(States).get(62).old_state_id is None - session.query(States).get(63).old_state_id == 62 # should have been keeped + assert session.query(States).get(61).old_state_id is None + assert session.query(States).get(62).old_state_id is None + assert session.query(States).get(63).old_state_id == 62 # should have been kept async def _add_test_states(hass: HomeAssistantType, instance: recorder.Recorder): diff --git a/tests/components/recorder/test_util.py b/tests/components/recorder/test_util.py index 4aba6569a41..c814570416c 100644 --- a/tests/components/recorder/test_util.py +++ b/tests/components/recorder/test_util.py @@ -88,8 +88,7 @@ def test_validate_or_move_away_sqlite_database_with_integrity_check( test_db_file = f"{test_dir}/broken.db" dburl = f"{SQLITE_URL_PREFIX}{test_db_file}" - util.validate_sqlite_database(test_db_file, db_integrity_check) is True - + assert util.validate_sqlite_database(test_db_file, db_integrity_check) is False assert os.path.exists(test_db_file) is True assert ( util.validate_or_move_away_sqlite_database(dburl, db_integrity_check) is False @@ -125,8 +124,7 @@ def test_validate_or_move_away_sqlite_database_without_integrity_check( test_db_file = f"{test_dir}/broken.db" dburl = f"{SQLITE_URL_PREFIX}{test_db_file}" - util.validate_sqlite_database(test_db_file, db_integrity_check) is True - + assert util.validate_sqlite_database(test_db_file, db_integrity_check) is False assert os.path.exists(test_db_file) is True assert ( util.validate_or_move_away_sqlite_database(dburl, db_integrity_check) is False diff --git a/tests/components/tod/test_binary_sensor.py b/tests/components/tod/test_binary_sensor.py index dff1e208ab6..363d159b811 100644 --- a/tests/components/tod/test_binary_sensor.py +++ b/tests/components/tod/test_binary_sensor.py @@ -903,7 +903,7 @@ async def test_dst(hass): await hass.async_block_till_done() state = hass.states.get(entity_id) - state.attributes["after"] == "2019-03-31T03:30:00+02:00" - state.attributes["before"] == "2019-03-31T03:40:00+02:00" - state.attributes["next_update"] == "2019-03-31T03:30:00+02:00" + assert state.attributes["after"] == "2019-03-31T03:30:00+02:00" + assert state.attributes["before"] == "2019-03-31T03:40:00+02:00" + assert state.attributes["next_update"] == "2019-03-31T03:30:00+02:00" assert state.state == STATE_OFF diff --git a/tests/components/universal/test_media_player.py b/tests/components/universal/test_media_player.py index 054c3b89541..7bf19116d93 100644 --- a/tests/components/universal/test_media_player.py +++ b/tests/components/universal/test_media_player.py @@ -944,7 +944,7 @@ async def test_master_state_with_template(hass): await hass.async_start() await hass.async_block_till_done() - hass.states.get("media_player.tv").state == STATE_ON + assert hass.states.get("media_player.tv").state == STATE_ON events = [] @@ -956,7 +956,7 @@ async def test_master_state_with_template(hass): hass.states.async_set("input_boolean.test", STATE_ON, context=context) await hass.async_block_till_done() - hass.states.get("media_player.tv").state == STATE_OFF + assert hass.states.get("media_player.tv").state == STATE_OFF assert events[0].context == context @@ -987,12 +987,12 @@ async def test_reload(hass): await hass.async_start() await hass.async_block_till_done() - hass.states.get("media_player.tv").state == STATE_ON + assert hass.states.get("media_player.tv").state == STATE_ON hass.states.async_set("input_boolean.test", STATE_ON) await hass.async_block_till_done() - hass.states.get("media_player.tv").state == STATE_OFF + assert hass.states.get("media_player.tv").state == STATE_OFF hass.states.async_set("media_player.master_bedroom_2", STATE_OFF) hass.states.async_set( diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index 1d1b1a3cca3..c5328000269 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -573,7 +573,7 @@ async def test_loading_saving_data(hass, registry, area_registry): orig_kitchen_light_witout_suggested_area = registry.async_update_device( orig_kitchen_light.id, suggested_area=None ) - orig_kitchen_light_witout_suggested_area.suggested_area is None + assert orig_kitchen_light_witout_suggested_area.suggested_area is None assert orig_kitchen_light_witout_suggested_area == new_kitchen_light From 01fcc41aa067bce649721fa1d04c800b6576eb02 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Sat, 20 Mar 2021 17:26:23 -0400 Subject: [PATCH 509/831] only block coord removal if it is active (#48147) --- homeassistant/components/zha/api.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index 45f7d540052..c8839acf856 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -60,6 +60,7 @@ from .core.helpers import ( get_matched_clusters, qr_to_install_code, ) +from .core.typing import ZhaDeviceType, ZhaGatewayType _LOGGER = logging.getLogger(__name__) @@ -892,9 +893,12 @@ def async_load_api(hass): async def remove(service): """Remove a node from the network.""" ieee = service.data[ATTR_IEEE] - zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] - zha_device = zha_gateway.get_device(ieee) - if zha_device is not None and zha_device.is_coordinator: + zha_gateway: ZhaGatewayType = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] + zha_device: ZhaDeviceType = zha_gateway.get_device(ieee) + if zha_device is not None and ( + zha_device.is_coordinator + and zha_device.ieee == zha_gateway.application_controller.ieee + ): _LOGGER.info("Removing the coordinator (%s) is not allowed", ieee) return _LOGGER.info("Removing node %s", ieee) From 3ae946013190954fd9885ec386daa00b10cec9dc Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Sun, 21 Mar 2021 00:34:46 +0100 Subject: [PATCH 510/831] Use domain const in config_flow (#48168) --- homeassistant/components/ambiclimate/config_flow.py | 2 +- homeassistant/components/daikin/__init__.py | 4 +--- homeassistant/components/daikin/config_flow.py | 4 ++-- homeassistant/components/daikin/const.py | 2 ++ homeassistant/components/mqtt/config_flow.py | 3 ++- homeassistant/components/point/config_flow.py | 2 +- homeassistant/components/tellduslive/config_flow.py | 2 +- homeassistant/components/tradfri/config_flow.py | 3 ++- 8 files changed, 12 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/ambiclimate/config_flow.py b/homeassistant/components/ambiclimate/config_flow.py index 85504971489..a6dbe60a761 100644 --- a/homeassistant/components/ambiclimate/config_flow.py +++ b/homeassistant/components/ambiclimate/config_flow.py @@ -38,7 +38,7 @@ def register_flow_implementation(hass, client_id, client_secret): } -@config_entries.HANDLERS.register("ambiclimate") +@config_entries.HANDLERS.register(DOMAIN) class AmbiclimateFlowHandler(config_entries.ConfigFlow): """Handle a config flow.""" diff --git a/homeassistant/components/daikin/__init__.py b/homeassistant/components/daikin/__init__.py index d0bc109c082..092bbf8866d 100644 --- a/homeassistant/components/daikin/__init__.py +++ b/homeassistant/components/daikin/__init__.py @@ -16,12 +16,10 @@ from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import Throttle -from .const import CONF_UUID, KEY_MAC, TIMEOUT +from .const import CONF_UUID, DOMAIN, KEY_MAC, TIMEOUT _LOGGER = logging.getLogger(__name__) -DOMAIN = "daikin" - PARALLEL_UPDATES = 0 MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) diff --git a/homeassistant/components/daikin/config_flow.py b/homeassistant/components/daikin/config_flow.py index 155fdd18376..619f9c8d1d8 100644 --- a/homeassistant/components/daikin/config_flow.py +++ b/homeassistant/components/daikin/config_flow.py @@ -12,12 +12,12 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PASSWORD -from .const import CONF_UUID, KEY_MAC, TIMEOUT +from .const import CONF_UUID, DOMAIN, KEY_MAC, TIMEOUT _LOGGER = logging.getLogger(__name__) -@config_entries.HANDLERS.register("daikin") +@config_entries.HANDLERS.register(DOMAIN) class FlowHandler(config_entries.ConfigFlow): """Handle a config flow.""" diff --git a/homeassistant/components/daikin/const.py b/homeassistant/components/daikin/const.py index 00bbbefd051..5b4bdd28331 100644 --- a/homeassistant/components/daikin/const.py +++ b/homeassistant/components/daikin/const.py @@ -14,6 +14,8 @@ from homeassistant.const import ( TEMP_CELSIUS, ) +DOMAIN = "daikin" + ATTR_TARGET_TEMPERATURE = "target_temperature" ATTR_INSIDE_TEMPERATURE = "inside_temperature" ATTR_OUTSIDE_TEMPERATURE = "outside_temperature" diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index e19aaecc3db..5e5b8c54cf2 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -27,11 +27,12 @@ from .const import ( DEFAULT_BIRTH, DEFAULT_DISCOVERY, DEFAULT_WILL, + DOMAIN, ) from .util import MQTT_WILL_BIRTH_SCHEMA -@config_entries.HANDLERS.register("mqtt") +@config_entries.HANDLERS.register(DOMAIN) class FlowHandler(config_entries.ConfigFlow): """Handle a config flow.""" diff --git a/homeassistant/components/point/config_flow.py b/homeassistant/components/point/config_flow.py index aaefc45bc9c..1f3cf2a751d 100644 --- a/homeassistant/components/point/config_flow.py +++ b/homeassistant/components/point/config_flow.py @@ -40,7 +40,7 @@ def register_flow_implementation(hass, domain, client_id, client_secret): } -@config_entries.HANDLERS.register("point") +@config_entries.HANDLERS.register(DOMAIN) class PointFlowHandler(config_entries.ConfigFlow): """Handle a config flow.""" diff --git a/homeassistant/components/tellduslive/config_flow.py b/homeassistant/components/tellduslive/config_flow.py index aabbf88ee1c..33a02cd1f16 100644 --- a/homeassistant/components/tellduslive/config_flow.py +++ b/homeassistant/components/tellduslive/config_flow.py @@ -29,7 +29,7 @@ KEY_TOKEN_SECRET = "token_secret" _LOGGER = logging.getLogger(__name__) -@config_entries.HANDLERS.register("tellduslive") +@config_entries.HANDLERS.register(DOMAIN) class FlowHandler(config_entries.ConfigFlow): """Handle a config flow.""" diff --git a/homeassistant/components/tradfri/config_flow.py b/homeassistant/components/tradfri/config_flow.py index 7947c3ad6de..e02ac69de36 100644 --- a/homeassistant/components/tradfri/config_flow.py +++ b/homeassistant/components/tradfri/config_flow.py @@ -15,6 +15,7 @@ from .const import ( CONF_IDENTITY, CONF_IMPORT_GROUPS, CONF_KEY, + DOMAIN, KEY_SECURITY_CODE, ) @@ -28,7 +29,7 @@ class AuthError(Exception): self.code = code -@config_entries.HANDLERS.register("tradfri") +@config_entries.HANDLERS.register(DOMAIN) class FlowHandler(config_entries.ConfigFlow): """Handle a config flow.""" From 46a3b80a2dbe217d35d906ce7a29c5ae0aa47523 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sun, 21 Mar 2021 00:05:03 +0000 Subject: [PATCH 511/831] [ci skip] Translation update --- homeassistant/components/accuweather/translations/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/accuweather/translations/de.json b/homeassistant/components/accuweather/translations/de.json index 814e57d1d6c..330f2850d26 100644 --- a/homeassistant/components/accuweather/translations/de.json +++ b/homeassistant/components/accuweather/translations/de.json @@ -5,7 +5,8 @@ }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", - "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel" + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel", + "requests_exceeded": "Die zul\u00e4ssige Anzahl von Anforderungen an die Accuweather-API wurde \u00fcberschritten. Sie m\u00fcssen warten oder den API-Schl\u00fcssel \u00e4ndern." }, "step": { "user": { From 0193f16ae98a2783715208c25a98c22830e1df26 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 21 Mar 2021 01:49:03 +0100 Subject: [PATCH 512/831] ESPHome trigger reconnect immediately when mDNS record received (#48129) --- homeassistant/components/esphome/__init__.py | 270 ++++++++++++++---- .../components/esphome/entry_data.py | 1 - 2 files changed, 207 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index d895fc9216d..0cb6cd94e08 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -18,6 +18,7 @@ from aioesphomeapi import ( UserServiceArgType, ) import voluptuous as vol +from zeroconf import DNSPointer, DNSRecord, RecordUpdateListener, Zeroconf from homeassistant import const from homeassistant.components import zeroconf @@ -199,7 +200,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool # Re-connection logic will trigger after this await cli.disconnect() - try_connect = await _setup_auto_reconnect_logic(hass, cli, entry, host, on_login) + reconnect_logic = ReconnectLogic( + hass, cli, entry, host, on_login, zeroconf_instance + ) async def complete_setup() -> None: """Complete the config entry setup.""" @@ -207,85 +210,228 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool await entry_data.async_update_static_infos(hass, entry, infos) await _setup_services(hass, entry_data, services) - # Create connection attempt outside of HA's tracked task in order - # not to delay startup. - hass.loop.create_task(try_connect(is_disconnect=False)) + await reconnect_logic.start() + entry_data.cleanup_callbacks.append(reconnect_logic.stop_callback) hass.async_create_task(complete_setup()) return True -async def _setup_auto_reconnect_logic( - hass: HomeAssistantType, cli: APIClient, entry: ConfigEntry, host: str, on_login -): - """Set up the re-connect logic for the API client.""" +class ReconnectLogic(RecordUpdateListener): + """Reconnectiong logic handler for ESPHome config entries. - async def try_connect(tries: int = 0, is_disconnect: bool = True) -> None: - """Try connecting to the API client. Will retry if not successful.""" - if entry.entry_id not in hass.data[DOMAIN]: + Contains two reconnect strategies: + - Connect with increasing time between connection attempts. + - Listen to zeroconf mDNS records, if any records are found for this device, try reconnecting immediately. + """ + + def __init__( + self, + hass: HomeAssistantType, + cli: APIClient, + entry: ConfigEntry, + host: str, + on_login, + zc: Zeroconf, + ): + """Initialize ReconnectingLogic.""" + self._hass = hass + self._cli = cli + self._entry = entry + self._host = host + self._on_login = on_login + self._zc = zc + # Flag to check if the device is connected + self._connected = True + self._connected_lock = asyncio.Lock() + # Event the different strategies use for issuing a reconnect attempt. + self._reconnect_event = asyncio.Event() + # The task containing the infinite reconnect loop while running + self._loop_task: asyncio.Task | None = None + # How many reconnect attempts have there been already, used for exponential wait time + self._tries = 0 + self._tries_lock = asyncio.Lock() + # Track the wait task to cancel it on HA shutdown + self._wait_task: asyncio.Task | None = None + self._wait_task_lock = asyncio.Lock() + + @property + def _entry_data(self) -> RuntimeEntryData | None: + return self._hass.data[DOMAIN].get(self._entry.entry_id) + + async def _on_disconnect(self): + """Log and issue callbacks when disconnecting.""" + if self._entry_data is None: + return + # This can happen often depending on WiFi signal strength. + # So therefore all these connection warnings are logged + # as infos. The "unavailable" logic will still trigger so the + # user knows if the device is not connected. + _LOGGER.info("Disconnected from ESPHome API for %s", self._host) + + # Run disconnect hooks + for disconnect_cb in self._entry_data.disconnect_callbacks: + disconnect_cb() + self._entry_data.disconnect_callbacks = [] + self._entry_data.available = False + self._entry_data.async_update_device_state(self._hass) + + # Reset tries + async with self._tries_lock: + self._tries = 0 + # Connected needs to be reset before the reconnect event (opposite order of check) + async with self._connected_lock: + self._connected = False + self._reconnect_event.set() + + async def _wait_and_start_reconnect(self): + """Wait for exponentially increasing time to issue next reconnect event.""" + async with self._tries_lock: + tries = self._tries + # If not first re-try, wait and print message + # Cap wait time at 1 minute. This is because while working on the + # device (e.g. soldering stuff), users don't want to have to wait + # a long time for their device to show up in HA again (this was + # mentioned a lot in early feedback) + tries = min(tries, 10) # prevent OverflowError + wait_time = int(round(min(1.8 ** tries, 60.0))) + if tries == 1: + _LOGGER.info("Trying to reconnect to %s in the background", self._host) + _LOGGER.debug("Retrying %s in %d seconds", self._host, wait_time) + await asyncio.sleep(wait_time) + async with self._wait_task_lock: + self._wait_task = None + self._reconnect_event.set() + + async def _try_connect(self): + """Try connecting to the API client.""" + async with self._tries_lock: + tries = self._tries + self._tries += 1 + + try: + await self._cli.connect(on_stop=self._on_disconnect, login=True) + except APIConnectionError as error: + level = logging.WARNING if tries == 0 else logging.DEBUG + _LOGGER.log( + level, + "Can't connect to ESPHome API for %s (%s): %s", + self._entry.unique_id, + self._host, + error, + ) + # Schedule re-connect in event loop in order not to delay HA + # startup. First connect is scheduled in tracked tasks. + async with self._wait_task_lock: + # Allow only one wait task at a time + # can happen if mDNS record received while waiting, then use existing wait task + if self._wait_task is not None: + return + + self._wait_task = self._hass.loop.create_task( + self._wait_and_start_reconnect() + ) + else: + _LOGGER.info("Successfully connected to %s", self._host) + async with self._tries_lock: + self._tries = 0 + async with self._connected_lock: + self._connected = True + self._hass.async_create_task(self._on_login()) + + async def _reconnect_once(self): + # Wait and clear reconnection event + await self._reconnect_event.wait() + self._reconnect_event.clear() + + # If in connected state, do not try to connect again. + async with self._connected_lock: + if self._connected: + return False + + # Check if the entry got removed or disabled, in which case we shouldn't reconnect + if self._entry.entry_id not in self._hass.data[DOMAIN]: # When removing/disconnecting manually return - device_registry = await hass.helpers.device_registry.async_get_registry() - devices = dr.async_entries_for_config_entry(device_registry, entry.entry_id) + device_registry = self._hass.helpers.device_registry.async_get(self._hass) + devices = dr.async_entries_for_config_entry( + device_registry, self._entry.entry_id + ) for device in devices: # There is only one device in ESPHome if device.disabled: # Don't attempt to connect if it's disabled return - data: RuntimeEntryData = hass.data[DOMAIN][entry.entry_id] - for disconnect_cb in data.disconnect_callbacks: - disconnect_cb() - data.disconnect_callbacks = [] - data.available = False - data.async_update_device_state(hass) + await self._try_connect() - if is_disconnect: - # This can happen often depending on WiFi signal strength. - # So therefore all these connection warnings are logged - # as infos. The "unavailable" logic will still trigger so the - # user knows if the device is not connected. - _LOGGER.info("Disconnected from ESPHome API for %s", host) + async def _reconnect_loop(self): + while True: + try: + await self._reconnect_once() + except asyncio.CancelledError: # pylint: disable=try-except-raise + raise + except Exception: # pylint: disable=broad-except + _LOGGER.error("Caught exception while reconnecting", exc_info=True) - if tries != 0: - # If not first re-try, wait and print message - # Cap wait time at 1 minute. This is because while working on the - # device (e.g. soldering stuff), users don't want to have to wait - # a long time for their device to show up in HA again (this was - # mentioned a lot in early feedback) - # - # In the future another API will be set up so that the ESP can - # notify HA of connectivity directly, but for new we'll use a - # really short reconnect interval. - tries = min(tries, 10) # prevent OverflowError - wait_time = int(round(min(1.8 ** tries, 60.0))) - if tries == 1: - _LOGGER.info("Trying to reconnect to %s in the background", host) - _LOGGER.debug("Retrying %s in %d seconds", host, wait_time) - await asyncio.sleep(wait_time) + async def start(self): + """Start the reconnecting logic background task.""" + # Create reconnection loop outside of HA's tracked tasks in order + # not to delay startup. + self._loop_task = self._hass.loop.create_task(self._reconnect_loop()) + # Listen for mDNS records so we can reconnect directly if a received mDNS record + # indicates the node is up again + await self._hass.async_add_executor_job(self._zc.add_listener, self, None) - try: - await cli.connect(on_stop=try_connect, login=True) - except APIConnectionError as error: - level = logging.WARNING if tries == 0 else logging.DEBUG - _LOGGER.log( - level, - "Can't connect to ESPHome API for %s (%s): %s", - entry.unique_id, - host, - error, - ) - # Schedule re-connect in event loop in order not to delay HA - # startup. First connect is scheduled in tracked tasks. - data.reconnect_task = hass.loop.create_task( - try_connect(tries + 1, is_disconnect=False) - ) - else: - _LOGGER.info("Successfully connected to %s", host) - hass.async_create_task(on_login()) + async with self._connected_lock: + self._connected = False + self._reconnect_event.set() - return try_connect + async def stop(self): + """Stop the reconnecting logic background task. Does not disconnect the client.""" + if self._loop_task is not None: + self._loop_task.cancel() + self._loop_task = None + await self._hass.async_add_executor_job(self._zc.remove_listener, self) + async with self._wait_task_lock: + if self._wait_task is not None: + self._wait_task.cancel() + self._wait_task = None + + @callback + def stop_callback(self): + """Stop as an async callback function.""" + self._hass.async_create_task(self.stop()) + + @callback + def _set_reconnect(self): + self._reconnect_event.set() + + def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None: + """Listen to zeroconf updated mDNS records.""" + if not isinstance(record, DNSPointer): + # We only consider PTR records and match using the alias name + return + if self._entry_data is None or self._entry_data.device_info is None: + # Either the entry was already teared down or we haven't received device info yet + return + filter_alias = f"{self._entry_data.device_info.name}._esphomelib._tcp.local." + if record.alias != filter_alias: + return + + # This is a mDNS record from the device and could mean it just woke up + # Check if already connected, no lock needed for this access + if self._connected: + return + + # Tell reconnection logic to retry connection attempt now (even before reconnect timer finishes) + _LOGGER.debug( + "%s: Triggering reconnect because of received mDNS record %s", + self._host, + record, + ) + self._hass.add_job(self._set_reconnect) async def _async_setup_device_registry( @@ -421,8 +567,6 @@ async def _cleanup_instance( ) -> RuntimeEntryData: """Cleanup the esphome client if it exists.""" data: RuntimeEntryData = hass.data[DOMAIN].pop(entry.entry_id) - if data.reconnect_task is not None: - data.reconnect_task.cancel() for disconnect_cb in data.disconnect_callbacks: disconnect_cb() for cleanup_callback in data.cleanup_callbacks: diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index 0923c84acd7..4fada10a3d1 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -54,7 +54,6 @@ class RuntimeEntryData: entry_id: str = attr.ib() client: APIClient = attr.ib() store: Store = attr.ib() - reconnect_task: asyncio.Task | None = attr.ib(default=None) state: dict[str, dict[str, Any]] = attr.ib(factory=dict) info: dict[str, dict[str, Any]] = attr.ib(factory=dict) From 99d2d72d13719730ccb65a00871ad80cb1ed10bb Mon Sep 17 00:00:00 2001 From: javicalle <31999997+javicalle@users.noreply.github.com> Date: Sun, 21 Mar 2021 03:27:24 +0100 Subject: [PATCH 513/831] Update RFLink tests (#48149) --- tests/components/rflink/test_cover.py | 152 +++++++++---------------- tests/components/rflink/test_init.py | 44 +++---- tests/components/rflink/test_light.py | 68 ++++------- tests/components/rflink/test_switch.py | 12 +- 4 files changed, 102 insertions(+), 174 deletions(-) diff --git a/tests/components/rflink/test_cover.py b/tests/components/rflink/test_cover.py index fd3f5b84314..1dac064d778 100644 --- a/tests/components/rflink/test_cover.py +++ b/tests/components/rflink/test_cover.py @@ -89,20 +89,16 @@ async def test_default_setup(hass, monkeypatch): assert hass.states.get(f"{DOMAIN}.test").state == STATE_OPEN # test changing state from HA propagates to RFLink - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) await hass.async_block_till_done() assert hass.states.get(f"{DOMAIN}.test").state == STATE_CLOSED assert protocol.send_command_ack.call_args_list[0][0][0] == "protocol_0_0" assert protocol.send_command_ack.call_args_list[0][0][1] == "DOWN" - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) await hass.async_block_till_done() assert hass.states.get(f"{DOMAIN}.test").state == STATE_OPEN @@ -162,10 +158,8 @@ async def test_signal_repetitions(hass, monkeypatch): _, _, protocol, _ = await mock_rflink(hass, config, DOMAIN, monkeypatch) # test if signal repetition is performed according to configuration - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) # wait for commands and repetitions to finish @@ -174,10 +168,8 @@ async def test_signal_repetitions(hass, monkeypatch): assert protocol.send_command_ack.call_count == 2 # test if default apply to configured devices - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test1"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test1"} ) # wait for commands and repetitions to finish @@ -202,15 +194,11 @@ async def test_signal_repetitions_alternation(hass, monkeypatch): # setup mocking rflink module _, _, protocol, _ = await mock_rflink(hass, config, DOMAIN, monkeypatch) - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test1"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test1"} ) await hass.async_block_till_done() @@ -234,16 +222,12 @@ async def test_signal_repetitions_cancelling(hass, monkeypatch): # setup mocking rflink module _, _, protocol, _ = await mock_rflink(hass, config, DOMAIN, monkeypatch) - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) await hass.async_block_till_done() @@ -606,12 +590,10 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the close command from HA should result # in an 'DOWN' command sent to a non-newkaku device # that has its type set to 'standard'. - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_CLOSE_COVER, - {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_standard"}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_CLOSE_COVER, + {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_standard"}, ) await hass.async_block_till_done() @@ -623,12 +605,10 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the open command from HA should result # in an 'UP' command sent to a non-newkaku device # that has its type set to 'standard'. - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_OPEN_COVER, - {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_standard"}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_OPEN_COVER, + {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_standard"}, ) await hass.async_block_till_done() @@ -640,10 +620,8 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the close command from HA should result # in an 'DOWN' command sent to a non-newkaku device # that has its type not specified. - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_none"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_none"} ) await hass.async_block_till_done() @@ -655,10 +633,8 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the open command from HA should result # in an 'UP' command sent to a non-newkaku device # that has its type not specified. - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_none"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_none"} ) await hass.async_block_till_done() @@ -670,12 +646,10 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the close command from HA should result # in an 'UP' command sent to a non-newkaku device # that has its type set to 'inverted'. - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_CLOSE_COVER, - {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_inverted"}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_CLOSE_COVER, + {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_inverted"}, ) await hass.async_block_till_done() @@ -687,12 +661,10 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the open command from HA should result # in an 'DOWN' command sent to a non-newkaku device # that has its type set to 'inverted'. - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_OPEN_COVER, - {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_inverted"}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_OPEN_COVER, + {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_inverted"}, ) await hass.async_block_till_done() @@ -704,12 +676,10 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the close command from HA should result # in an 'DOWN' command sent to a newkaku device # that has its type set to 'standard'. - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_CLOSE_COVER, - {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_standard"}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_CLOSE_COVER, + {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_standard"}, ) await hass.async_block_till_done() @@ -721,12 +691,10 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the open command from HA should result # in an 'UP' command sent to a newkaku device # that has its type set to 'standard'. - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_OPEN_COVER, - {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_standard"}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_OPEN_COVER, + {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_standard"}, ) await hass.async_block_till_done() @@ -738,10 +706,8 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the close command from HA should result # in an 'UP' command sent to a newkaku device # that has its type not specified. - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_none"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_none"} ) await hass.async_block_till_done() @@ -753,10 +719,8 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the open command from HA should result # in an 'DOWN' command sent to a newkaku device # that has its type not specified. - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_none"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_none"} ) await hass.async_block_till_done() @@ -768,12 +732,10 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the close command from HA should result # in an 'UP' command sent to a newkaku device # that has its type set to 'inverted'. - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_CLOSE_COVER, - {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_inverted"}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_CLOSE_COVER, + {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_inverted"}, ) await hass.async_block_till_done() @@ -785,12 +747,10 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the open command from HA should result # in an 'DOWN' command sent to a newkaku device # that has its type set to 'inverted'. - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_OPEN_COVER, - {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_inverted"}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_OPEN_COVER, + {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_inverted"}, ) await hass.async_block_till_done() diff --git a/tests/components/rflink/test_init.py b/tests/components/rflink/test_init.py index 233170d8cd2..f93c9703d30 100644 --- a/tests/components/rflink/test_init.py +++ b/tests/components/rflink/test_init.py @@ -107,10 +107,8 @@ async def test_send_no_wait(hass, monkeypatch): # setup mocking rflink module _, _, protocol, _ = await mock_rflink(hass, config, domain, monkeypatch) - hass.async_create_task( - hass.services.async_call( - domain, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "switch.test"} - ) + await hass.services.async_call( + domain, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "switch.test"} ) await hass.async_block_till_done() assert protocol.send_command.call_args_list[0][0][0] == "protocol_0_0" @@ -133,10 +131,8 @@ async def test_cover_send_no_wait(hass, monkeypatch): # setup mocking rflink module _, _, protocol, _ = await mock_rflink(hass, config, domain, monkeypatch) - hass.async_create_task( - hass.services.async_call( - domain, SERVICE_STOP_COVER, {ATTR_ENTITY_ID: "cover.test"} - ) + await hass.services.async_call( + domain, SERVICE_STOP_COVER, {ATTR_ENTITY_ID: "cover.test"} ) await hass.async_block_till_done() assert protocol.send_command.call_args_list[0][0][0] == "RTS_0100F2_0" @@ -151,12 +147,10 @@ async def test_send_command(hass, monkeypatch): # setup mocking rflink module _, _, protocol, _ = await mock_rflink(hass, config, domain, monkeypatch) - hass.async_create_task( - hass.services.async_call( - domain, - SERVICE_SEND_COMMAND, - {"device_id": "newkaku_0000c6c2_1", "command": "on"}, - ) + await hass.services.async_call( + domain, + SERVICE_SEND_COMMAND, + {"device_id": "newkaku_0000c6c2_1", "command": "on"}, ) await hass.async_block_till_done() assert protocol.send_command_ack.call_args_list[0][0][0] == "newkaku_0000c6c2_1" @@ -215,24 +209,22 @@ async def test_send_command_event_propagation(hass, monkeypatch): # default value = 'off' assert hass.states.get(f"{domain}.test1").state == "off" - hass.async_create_task( - hass.services.async_call( - "rflink", - SERVICE_SEND_COMMAND, - {"device_id": "protocol_0_1", "command": "on"}, - ) + await hass.services.async_call( + "rflink", + SERVICE_SEND_COMMAND, + {"device_id": "protocol_0_1", "command": "on"}, + blocking=True, ) await hass.async_block_till_done() assert protocol.send_command_ack.call_args_list[0][0][0] == "protocol_0_1" assert protocol.send_command_ack.call_args_list[0][0][1] == "on" assert hass.states.get(f"{domain}.test1").state == "on" - hass.async_create_task( - hass.services.async_call( - "rflink", - SERVICE_SEND_COMMAND, - {"device_id": "protocol_0_1", "command": "alloff"}, - ) + await hass.services.async_call( + "rflink", + SERVICE_SEND_COMMAND, + {"device_id": "protocol_0_1", "command": "alloff"}, + blocking=True, ) await hass.async_block_till_done() assert protocol.send_command_ack.call_args_list[1][0][0] == "protocol_0_1" diff --git a/tests/components/rflink/test_light.py b/tests/components/rflink/test_light.py index f3ea5e30375..79fa752e54c 100644 --- a/tests/components/rflink/test_light.py +++ b/tests/components/rflink/test_light.py @@ -96,20 +96,16 @@ async def test_default_setup(hass, monkeypatch): assert hass.states.get(f"{DOMAIN}.protocol2_0_1").state == "on" # test changing state from HA propagates to RFLink - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) await hass.async_block_till_done() assert hass.states.get(f"{DOMAIN}.test").state == "off" assert protocol.send_command_ack.call_args_list[0][0][0] == "protocol_0_0" assert protocol.send_command_ack.call_args_list[0][0][1] == "off" - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) await hass.async_block_till_done() assert hass.states.get(f"{DOMAIN}.test").state == "on" @@ -118,10 +114,8 @@ async def test_default_setup(hass, monkeypatch): # protocols supporting dimming and on/off should create hybrid light entity event_callback({"id": "newkaku_0_1", "command": "off"}) await hass.async_block_till_done() - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_0_1"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_0_1"} ) await hass.async_block_till_done() @@ -131,23 +125,19 @@ async def test_default_setup(hass, monkeypatch): # and send on command for fallback assert protocol.send_command_ack.call_args_list[3][0][1] == "on" - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_0_1", ATTR_BRIGHTNESS: 128}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_0_1", ATTR_BRIGHTNESS: 128}, ) await hass.async_block_till_done() assert protocol.send_command_ack.call_args_list[4][0][1] == "7" - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: f"{DOMAIN}.dim_test", ATTR_BRIGHTNESS: 128}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: f"{DOMAIN}.dim_test", ATTR_BRIGHTNESS: 128}, ) await hass.async_block_till_done() @@ -210,10 +200,8 @@ async def test_signal_repetitions(hass, monkeypatch): ) # test if signal repetition is performed according to configuration - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) # wait for commands and repetitions to finish @@ -222,10 +210,8 @@ async def test_signal_repetitions(hass, monkeypatch): assert protocol.send_command_ack.call_count == 2 # test if default apply to configured devices - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.test1"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.test1"} ) # wait for commands and repetitions to finish @@ -239,10 +225,8 @@ async def test_signal_repetitions(hass, monkeypatch): # make sure entity is created before setting state await hass.async_block_till_done() - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.protocol_0_2"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.protocol_0_2"} ) # wait for commands and repetitions to finish @@ -340,20 +324,16 @@ async def test_type_toggle(hass, monkeypatch): assert hass.states.get(f"{DOMAIN}.toggle_test").state == "off" # test async_turn_off, must set state = 'on' ('off' + toggle) - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.toggle_test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.toggle_test"} ) await hass.async_block_till_done() assert hass.states.get(f"{DOMAIN}.toggle_test").state == "on" # test async_turn_on, must set state = 'off' (yes, sounds crazy) - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: f"{DOMAIN}.toggle_test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: f"{DOMAIN}.toggle_test"} ) await hass.async_block_till_done() diff --git a/tests/components/rflink/test_switch.py b/tests/components/rflink/test_switch.py index d696e8933be..ef6bac55f21 100644 --- a/tests/components/rflink/test_switch.py +++ b/tests/components/rflink/test_switch.py @@ -76,20 +76,16 @@ async def test_default_setup(hass, monkeypatch): # events because every new unknown device is added as a light by default. # test changing state from HA propagates to Rflink - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) await hass.async_block_till_done() assert hass.states.get(f"{DOMAIN}.test").state == "off" assert protocol.send_command_ack.call_args_list[0][0][0] == "protocol_0_0" assert protocol.send_command_ack.call_args_list[0][0][1] == "off" - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) await hass.async_block_till_done() assert hass.states.get(f"{DOMAIN}.test").state == "on" From 87499989a041ab83bc1b33d16e1947d4005479f7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 21 Mar 2021 04:08:49 +0100 Subject: [PATCH 514/831] Small code styling tweaks for HomeKit (#48163) --- .../components/homekit/config_flow.py | 8 +- .../components/homekit/type_covers.py | 21 ++-- .../components/homekit/type_humidifiers.py | 8 +- .../components/homekit/type_locks.py | 8 +- .../components/homekit/type_thermostats.py | 114 ++++++++++-------- homeassistant/components/homekit/util.py | 16 ++- 6 files changed, 92 insertions(+), 83 deletions(-) diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index 2eabdd98e79..758e25b97bc 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -247,10 +247,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Determine is a name or port is already used.""" name = user_input[CONF_NAME] port = user_input[CONF_PORT] - for entry in self._async_current_entries(): - if entry.data[CONF_NAME] == name or entry.data[CONF_PORT] == port: - return False - return True + return not any( + entry.data[CONF_NAME] == name or entry.data[CONF_PORT] == port + for entry in self._async_current_entries() + ) @staticmethod @callback diff --git a/homeassistant/components/homekit/type_covers.py b/homeassistant/components/homekit/type_covers.py index ca375bb6f37..f21287b3bf8 100644 --- a/homeassistant/components/homekit/type_covers.py +++ b/homeassistant/components/homekit/type_covers.py @@ -364,18 +364,17 @@ class WindowCoveringBasic(OpeningDeviceBase, HomeAccessory): """Move cover to value if call came from HomeKit.""" _LOGGER.debug("%s: Set position to %d", self.entity_id, value) - if self._supports_stop: - if value > 70: - service, position = (SERVICE_OPEN_COVER, 100) - elif value < 30: - service, position = (SERVICE_CLOSE_COVER, 0) - else: - service, position = (SERVICE_STOP_COVER, 50) + if ( + self._supports_stop + and value > 70 + or not self._supports_stop + and value >= 50 + ): + service, position = (SERVICE_OPEN_COVER, 100) + elif value < 30 or not self._supports_stop: + service, position = (SERVICE_CLOSE_COVER, 0) else: - if value >= 50: - service, position = (SERVICE_OPEN_COVER, 100) - else: - service, position = (SERVICE_CLOSE_COVER, 0) + service, position = (SERVICE_STOP_COVER, 50) params = {ATTR_ENTITY_ID: self.entity_id} self.async_call_service(DOMAIN, service, params) diff --git a/homeassistant/components/homekit/type_humidifiers.py b/homeassistant/components/homekit/type_humidifiers.py index 6e1978d9499..a4a73abf998 100644 --- a/homeassistant/components/homekit/type_humidifiers.py +++ b/homeassistant/components/homekit/type_humidifiers.py @@ -240,6 +240,8 @@ class HumidifierDehumidifier(HomeAccessory): # Update target humidity target_humidity = new_state.attributes.get(ATTR_HUMIDITY) - if isinstance(target_humidity, (int, float)): - if self.char_target_humidity.value != target_humidity: - self.char_target_humidity.set_value(target_humidity) + if ( + isinstance(target_humidity, (int, float)) + and self.char_target_humidity.value != target_humidity + ): + self.char_target_humidity.set_value(target_humidity) diff --git a/homeassistant/components/homekit/type_locks.py b/homeassistant/components/homekit/type_locks.py index 140940dda47..17e2eee46e8 100644 --- a/homeassistant/components/homekit/type_locks.py +++ b/homeassistant/components/homekit/type_locks.py @@ -78,9 +78,11 @@ class Lock(HomeAccessory): # LockTargetState only supports locked and unlocked # Must set lock target state before current state # or there will be no notification - if hass_state in (STATE_LOCKED, STATE_UNLOCKED): - if self.char_target_state.value != current_lock_state: - self.char_target_state.set_value(current_lock_state) + if ( + hass_state in (STATE_LOCKED, STATE_UNLOCKED) + and self.char_target_state.value != current_lock_state + ): + self.char_target_state.set_value(current_lock_state) # Set lock current state ONLY after ensuring that # target state is correct or there will be no diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index 2eb63f4c840..fb3063704c2 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -253,42 +253,44 @@ class Thermostat(HomeAccessory): hvac_mode = state.state homekit_hvac_mode = HC_HASS_TO_HOMEKIT[hvac_mode] - if CHAR_TARGET_HEATING_COOLING in char_values: - # Homekit will reset the mode when VIEWING the temp - # Ignore it if its the same mode - if char_values[CHAR_TARGET_HEATING_COOLING] != homekit_hvac_mode: - target_hc = char_values[CHAR_TARGET_HEATING_COOLING] - if target_hc not in self.hc_homekit_to_hass: - # If the target heating cooling state we want does not - # exist on the device, we have to sort it out - # based on the the current and target temperature since - # siri will always send HC_HEAT_COOL_AUTO in this case - # and hope for the best. - hc_target_temp = char_values.get(CHAR_TARGET_TEMPERATURE) - hc_current_temp = _get_current_temperature(state, self._unit) - hc_fallback_order = HC_HEAT_COOL_PREFER_HEAT - if ( - hc_target_temp is not None - and hc_current_temp is not None - and hc_target_temp < hc_current_temp - ): - hc_fallback_order = HC_HEAT_COOL_PREFER_COOL - for hc_fallback in hc_fallback_order: - if hc_fallback in self.hc_homekit_to_hass: - _LOGGER.debug( - "Siri requested target mode: %s and the device does not support, falling back to %s", - target_hc, - hc_fallback, - ) - target_hc = hc_fallback - break + # Homekit will reset the mode when VIEWING the temp + # Ignore it if its the same mode + if ( + CHAR_TARGET_HEATING_COOLING in char_values + and char_values[CHAR_TARGET_HEATING_COOLING] != homekit_hvac_mode + ): + target_hc = char_values[CHAR_TARGET_HEATING_COOLING] + if target_hc not in self.hc_homekit_to_hass: + # If the target heating cooling state we want does not + # exist on the device, we have to sort it out + # based on the the current and target temperature since + # siri will always send HC_HEAT_COOL_AUTO in this case + # and hope for the best. + hc_target_temp = char_values.get(CHAR_TARGET_TEMPERATURE) + hc_current_temp = _get_current_temperature(state, self._unit) + hc_fallback_order = HC_HEAT_COOL_PREFER_HEAT + if ( + hc_target_temp is not None + and hc_current_temp is not None + and hc_target_temp < hc_current_temp + ): + hc_fallback_order = HC_HEAT_COOL_PREFER_COOL + for hc_fallback in hc_fallback_order: + if hc_fallback in self.hc_homekit_to_hass: + _LOGGER.debug( + "Siri requested target mode: %s and the device does not support, falling back to %s", + target_hc, + hc_fallback, + ) + target_hc = hc_fallback + break - service = SERVICE_SET_HVAC_MODE_THERMOSTAT - hass_value = self.hc_homekit_to_hass[target_hc] - params = {ATTR_HVAC_MODE: hass_value} - events.append( - f"{CHAR_TARGET_HEATING_COOLING} to {char_values[CHAR_TARGET_HEATING_COOLING]}" - ) + service = SERVICE_SET_HVAC_MODE_THERMOSTAT + hass_value = self.hc_homekit_to_hass[target_hc] + params = {ATTR_HVAC_MODE: hass_value} + events.append( + f"{CHAR_TARGET_HEATING_COOLING} to {char_values[CHAR_TARGET_HEATING_COOLING]}" + ) if CHAR_TARGET_TEMPERATURE in char_values: hc_target_temp = char_values[CHAR_TARGET_TEMPERATURE] @@ -462,23 +464,26 @@ class Thermostat(HomeAccessory): # Update current temperature current_temp = _get_current_temperature(new_state, self._unit) - if current_temp is not None: - if self.char_current_temp.value != current_temp: - self.char_current_temp.set_value(current_temp) + if current_temp is not None and self.char_current_temp.value != current_temp: + self.char_current_temp.set_value(current_temp) # Update current humidity if CHAR_CURRENT_HUMIDITY in self.chars: current_humdity = new_state.attributes.get(ATTR_CURRENT_HUMIDITY) - if isinstance(current_humdity, (int, float)): - if self.char_current_humidity.value != current_humdity: - self.char_current_humidity.set_value(current_humdity) + if ( + isinstance(current_humdity, (int, float)) + and self.char_current_humidity.value != current_humdity + ): + self.char_current_humidity.set_value(current_humdity) # Update target humidity if CHAR_TARGET_HUMIDITY in self.chars: target_humdity = new_state.attributes.get(ATTR_HUMIDITY) - if isinstance(target_humdity, (int, float)): - if self.char_target_humidity.value != target_humdity: - self.char_target_humidity.set_value(target_humdity) + if ( + isinstance(target_humdity, (int, float)) + and self.char_target_humidity.value != target_humdity + ): + self.char_target_humidity.set_value(target_humdity) # Update cooling threshold temperature if characteristic exists if self.char_cooling_thresh_temp: @@ -575,9 +580,8 @@ class WaterHeater(HomeAccessory): """Change operation mode to value if call came from HomeKit.""" _LOGGER.debug("%s: Set heat-cool to %d", self.entity_id, value) hass_value = HC_HOMEKIT_TO_HASS[value] - if hass_value != HVAC_MODE_HEAT: - if self.char_target_heat_cool.value != 1: - self.char_target_heat_cool.set_value(1) # Heat + if hass_value != HVAC_MODE_HEAT and self.char_target_heat_cool.value != 1: + self.char_target_heat_cool.set_value(1) # Heat def set_target_temperature(self, value): """Set target temperature to value if call came from HomeKit.""" @@ -596,14 +600,18 @@ class WaterHeater(HomeAccessory): """Update water_heater state after state change.""" # Update current and target temperature target_temperature = _get_target_temperature(new_state, self._unit) - if target_temperature is not None: - if target_temperature != self.char_target_temp.value: - self.char_target_temp.set_value(target_temperature) + if ( + target_temperature is not None + and target_temperature != self.char_target_temp.value + ): + self.char_target_temp.set_value(target_temperature) current_temperature = _get_current_temperature(new_state, self._unit) - if current_temperature is not None: - if current_temperature != self.char_current_temp.value: - self.char_current_temp.set_value(current_temperature) + if ( + current_temperature is not None + and current_temperature != self.char_current_temp.value + ): + self.char_current_temp.set_value(current_temperature) # Update display units if self._unit and self._unit in UNIT_HASS_TO_HOMEKIT: diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 10550cf4a11..cab1a28892a 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -444,10 +444,11 @@ def port_is_available(port: int) -> bool: async def async_find_next_available_port(hass: HomeAssistant, start_port: int) -> int: """Find the next available port not assigned to a config entry.""" - exclude_ports = set() - for entry in hass.config_entries.async_entries(DOMAIN): - if CONF_PORT in entry.data: - exclude_ports.add(entry.data[CONF_PORT]) + exclude_ports = { + entry.data[CONF_PORT] + for entry in hass.config_entries.async_entries(DOMAIN) + if CONF_PORT in entry.data + } return await hass.async_add_executor_job( _find_next_available_port, start_port, exclude_ports @@ -499,10 +500,7 @@ def state_needs_accessory_mode(state): if state.domain == CAMERA_DOMAIN: return True - if ( + return ( state.domain == MEDIA_PLAYER_DOMAIN and state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TV - ): - return True - - return False + ) From 668d018e9c0a9b5171fad49beaf8af2f60a0a737 Mon Sep 17 00:00:00 2001 From: javicalle <31999997+javicalle@users.noreply.github.com> Date: Sun, 21 Mar 2021 08:43:38 +0100 Subject: [PATCH 515/831] Make Rflink handle set_level command for dimmable devices (#46499) * Added handle_event for set_level command in dimmable devices * refactor common code for dimmable devices * Force tests Silly change to force tests execution * fix super() * add rflink dim utils --- homeassistant/components/rflink/__init__.py | 4 +- homeassistant/components/rflink/light.py | 71 +++++--------- homeassistant/components/rflink/utils.py | 11 +++ tests/components/rflink/test_light.py | 101 +++++++++++++++++++- tests/components/rflink/test_utils.py | 33 +++++++ 5 files changed, 167 insertions(+), 53 deletions(-) create mode 100644 homeassistant/components/rflink/utils.py create mode 100644 tests/components/rflink/test_utils.py diff --git a/homeassistant/components/rflink/__init__.py b/homeassistant/components/rflink/__init__.py index 81d44806c5e..c78b0c6f944 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -28,6 +28,8 @@ from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.entity import Entity from homeassistant.helpers.restore_state import RestoreEntity +from .utils import brightness_to_rflink + _LOGGER = logging.getLogger(__name__) ATTR_EVENT = "event" @@ -509,7 +511,7 @@ class RflinkCommand(RflinkDevice): elif command == "dim": # convert brightness to rflink dim level - cmd = str(int(args[0] / 17)) + cmd = str(brightness_to_rflink(args[0])) self._state = True elif command == "toggle": diff --git a/homeassistant/components/rflink/light.py b/homeassistant/components/rflink/light.py index 2a97472f000..5a0d6766179 100644 --- a/homeassistant/components/rflink/light.py +++ b/homeassistant/components/rflink/light.py @@ -1,5 +1,6 @@ """Support for Rflink lights.""" import logging +import re import voluptuous as vol @@ -27,6 +28,7 @@ from . import ( EVENT_KEY_ID, SwitchableRflinkDevice, ) +from .utils import brightness_to_rflink, rflink_to_brightness _LOGGER = logging.getLogger(__name__) @@ -183,30 +185,39 @@ class DimmableRflinkLight(SwitchableRflinkDevice, LightEntity): """Turn the device on.""" if ATTR_BRIGHTNESS in kwargs: # rflink only support 16 brightness levels - self._brightness = int(kwargs[ATTR_BRIGHTNESS] / 17) * 17 + self._brightness = rflink_to_brightness( + brightness_to_rflink(kwargs[ATTR_BRIGHTNESS]) + ) # Turn on light at the requested dim level await self._async_handle_command("dim", self._brightness) + def _handle_event(self, event): + """Adjust state if Rflink picks up a remote command for this device.""" + self.cancel_queued_send_commands() + + command = event["command"] + if command in ["on", "allon"]: + self._state = True + elif command in ["off", "alloff"]: + self._state = False + # dimmable device accept 'set_level=(0-15)' commands + elif re.search("^set_level=(0?[0-9]|1[0-5])$", command, re.IGNORECASE): + self._brightness = rflink_to_brightness(int(command.split("=")[1])) + self._state = True + @property def brightness(self): """Return the brightness of this light between 0..255.""" return self._brightness - @property - def extra_state_attributes(self): - """Return the device state attributes.""" - if self._brightness is None: - return {} - return {ATTR_BRIGHTNESS: self._brightness} - @property def supported_features(self): """Flag supported features.""" return SUPPORT_BRIGHTNESS -class HybridRflinkLight(SwitchableRflinkDevice, LightEntity): +class HybridRflinkLight(DimmableRflinkLight, LightEntity): """Rflink light device that sends out both dim and on/off commands. Used for protocols which support lights that are not exclusively on/off @@ -221,52 +232,14 @@ class HybridRflinkLight(SwitchableRflinkDevice, LightEntity): Which results in a nice house disco :) """ - _brightness = 255 - - async def async_added_to_hass(self): - """Restore RFLink light brightness attribute.""" - await super().async_added_to_hass() - - old_state = await self.async_get_last_state() - if ( - old_state is not None - and old_state.attributes.get(ATTR_BRIGHTNESS) is not None - ): - # restore also brightness in dimmables devices - self._brightness = int(old_state.attributes[ATTR_BRIGHTNESS]) - async def async_turn_on(self, **kwargs): """Turn the device on and set dim level.""" - if ATTR_BRIGHTNESS in kwargs: - # rflink only support 16 brightness levels - self._brightness = int(kwargs[ATTR_BRIGHTNESS] / 17) * 17 - - # if receiver supports dimming this will turn on the light - # at the requested dim level - await self._async_handle_command("dim", self._brightness) - + await super().async_turn_on(**kwargs) # if the receiving device does not support dimlevel this # will ensure it is turned on when full brightness is set - if self._brightness == 255: + if self.brightness == 255: await self._async_handle_command("turn_on") - @property - def brightness(self): - """Return the brightness of this light between 0..255.""" - return self._brightness - - @property - def extra_state_attributes(self): - """Return the device state attributes.""" - if self._brightness is None: - return {} - return {ATTR_BRIGHTNESS: self._brightness} - - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_BRIGHTNESS - class ToggleRflinkLight(SwitchableRflinkDevice, LightEntity): """Rflink light device which sends out only 'on' commands. diff --git a/homeassistant/components/rflink/utils.py b/homeassistant/components/rflink/utils.py new file mode 100644 index 00000000000..9738d9f74fa --- /dev/null +++ b/homeassistant/components/rflink/utils.py @@ -0,0 +1,11 @@ +"""RFLink integration utils.""" + + +def brightness_to_rflink(brightness: int) -> int: + """Convert 0-255 brightness to RFLink dim level (0-15).""" + return int(brightness / 17) + + +def rflink_to_brightness(dim_level: int) -> int: + """Convert RFLink dim level (0-15) to 0-255 brightness.""" + return int(dim_level * 17) diff --git a/tests/components/rflink/test_light.py b/tests/components/rflink/test_light.py index 79fa752e54c..5f9672ac9fc 100644 --- a/tests/components/rflink/test_light.py +++ b/tests/components/rflink/test_light.py @@ -340,6 +340,93 @@ async def test_type_toggle(hass, monkeypatch): assert hass.states.get(f"{DOMAIN}.toggle_test").state == "off" +async def test_set_level_command(hass, monkeypatch): + """Test 'set_level=XX' events.""" + config = { + "rflink": {"port": "/dev/ttyABC0"}, + DOMAIN: { + "platform": "rflink", + "devices": { + "newkaku_12345678_0": {"name": "l1"}, + "test_no_dimmable": {"name": "l2"}, + "test_dimmable": {"name": "l3", "type": "dimmable"}, + "test_hybrid": {"name": "l4", "type": "hybrid"}, + }, + }, + } + + # setup mocking rflink module + event_callback, _, _, _ = await mock_rflink(hass, config, DOMAIN, monkeypatch) + + # test sending command to a newkaku device + event_callback({"id": "newkaku_12345678_0", "command": "set_level=10"}) + await hass.async_block_till_done() + # should affect state + state = hass.states.get(f"{DOMAIN}.l1") + assert state + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 170 + # turn off + event_callback({"id": "newkaku_12345678_0", "command": "off"}) + await hass.async_block_till_done() + state = hass.states.get(f"{DOMAIN}.l1") + assert state + assert state.state == STATE_OFF + # off light shouldn't have brightness + assert not state.attributes.get(ATTR_BRIGHTNESS) + # turn on + event_callback({"id": "newkaku_12345678_0", "command": "on"}) + await hass.async_block_till_done() + state = hass.states.get(f"{DOMAIN}.l1") + assert state + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 170 + + # test sending command to a no dimmable device + event_callback({"id": "test_no_dimmable", "command": "set_level=10"}) + await hass.async_block_till_done() + # should NOT affect state + state = hass.states.get(f"{DOMAIN}.l2") + assert state + assert state.state == STATE_OFF + assert not state.attributes.get(ATTR_BRIGHTNESS) + + # test sending command to a dimmable device + event_callback({"id": "test_dimmable", "command": "set_level=5"}) + await hass.async_block_till_done() + # should affect state + state = hass.states.get(f"{DOMAIN}.l3") + assert state + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 85 + + # test sending command to a hybrid device + event_callback({"id": "test_hybrid", "command": "set_level=15"}) + await hass.async_block_till_done() + # should affect state + state = hass.states.get(f"{DOMAIN}.l4") + assert state + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 255 + + event_callback({"id": "test_hybrid", "command": "off"}) + await hass.async_block_till_done() + # should affect state + state = hass.states.get(f"{DOMAIN}.l4") + assert state + assert state.state == STATE_OFF + # off light shouldn't have brightness + assert not state.attributes.get(ATTR_BRIGHTNESS) + + event_callback({"id": "test_hybrid", "command": "set_level=0"}) + await hass.async_block_till_done() + # should affect state + state = hass.states.get(f"{DOMAIN}.l4") + assert state + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 0 + + async def test_group_alias(hass, monkeypatch): """Group aliases should only respond to group commands (allon/alloff).""" config = { @@ -347,7 +434,12 @@ async def test_group_alias(hass, monkeypatch): DOMAIN: { "platform": "rflink", "devices": { - "protocol_0_0": {"name": "test", "group_aliases": ["test_group_0_0"]} + "protocol_0_0": {"name": "test", "group_aliases": ["test_group_0_0"]}, + "protocol_0_1": { + "name": "test2", + "type": "dimmable", + "group_aliases": ["test_group_0_0"], + }, }, }, } @@ -362,12 +454,14 @@ async def test_group_alias(hass, monkeypatch): await hass.async_block_till_done() assert hass.states.get(f"{DOMAIN}.test").state == "on" + assert hass.states.get(f"{DOMAIN}.test2").state == "on" # test sending group command to group alias event_callback({"id": "test_group_0_0", "command": "off"}) await hass.async_block_till_done() assert hass.states.get(f"{DOMAIN}.test").state == "on" + assert hass.states.get(f"{DOMAIN}.test2").state == "on" async def test_nogroup_alias(hass, monkeypatch): @@ -396,7 +490,7 @@ async def test_nogroup_alias(hass, monkeypatch): # should not affect state assert hass.states.get(f"{DOMAIN}.test").state == "off" - # test sending group command to nogroup alias + # test sending group commands to nogroup alias event_callback({"id": "test_nogroup_0_0", "command": "on"}) await hass.async_block_till_done() # should affect state @@ -501,7 +595,8 @@ async def test_restore_state(hass, monkeypatch): state = hass.states.get(f"{DOMAIN}.l4") assert state assert state.state == STATE_OFF - assert state.attributes[ATTR_BRIGHTNESS] == 255 + # off light shouldn't have brightness + assert not state.attributes.get(ATTR_BRIGHTNESS) assert state.attributes["assumed_state"] # test coverage for dimmable light diff --git a/tests/components/rflink/test_utils.py b/tests/components/rflink/test_utils.py new file mode 100644 index 00000000000..50dd8100e8e --- /dev/null +++ b/tests/components/rflink/test_utils.py @@ -0,0 +1,33 @@ +"""Test for RFLink utils methods.""" +from homeassistant.components.rflink.utils import ( + brightness_to_rflink, + rflink_to_brightness, +) + + +async def test_utils(hass, monkeypatch): + """Test all utils methods.""" + # test brightness_to_rflink + assert brightness_to_rflink(0) == 0 + assert brightness_to_rflink(17) == 1 + assert brightness_to_rflink(34) == 2 + assert brightness_to_rflink(85) == 5 + assert brightness_to_rflink(170) == 10 + assert brightness_to_rflink(255) == 15 + + assert brightness_to_rflink(10) == 0 + assert brightness_to_rflink(20) == 1 + assert brightness_to_rflink(30) == 1 + assert brightness_to_rflink(40) == 2 + assert brightness_to_rflink(50) == 2 + assert brightness_to_rflink(60) == 3 + assert brightness_to_rflink(70) == 4 + assert brightness_to_rflink(80) == 4 + + # test rflink_to_brightness + assert rflink_to_brightness(0) == 0 + assert rflink_to_brightness(1) == 17 + assert rflink_to_brightness(5) == 85 + assert rflink_to_brightness(10) == 170 + assert rflink_to_brightness(12) == 204 + assert rflink_to_brightness(15) == 255 From 346a724ac389e7976ea134eb6da6b847a11e7fcb Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 21 Mar 2021 10:38:24 +0100 Subject: [PATCH 516/831] Mark base components' state_attribute @final, rename others to extra_state_attributes (#48161) * Mark base state_attributes @final, rename others to extra_state_attributes * Fix calendar, update tests --- .../components/acer_projector/switch.py | 2 +- .../components/air_quality/__init__.py | 2 ++ .../alarm_control_panel/__init__.py | 2 ++ homeassistant/components/arwn/sensor.py | 2 +- .../components/automation/__init__.py | 2 +- homeassistant/components/calendar/__init__.py | 5 +-- homeassistant/components/camera/__init__.py | 2 ++ homeassistant/components/climate/__init__.py | 5 +-- homeassistant/components/counter/__init__.py | 2 +- homeassistant/components/cover/__init__.py | 5 +-- .../components/device_tracker/config_entry.py | 8 +++-- .../components/device_tracker/legacy.py | 5 +-- homeassistant/components/fail2ban/sensor.py | 2 +- homeassistant/components/fan/__init__.py | 4 ++- .../components/fritzbox_netmonitor/sensor.py | 2 +- .../components/geo_location/__init__.py | 4 ++- homeassistant/components/group/__init__.py | 2 +- homeassistant/components/homematic/entity.py | 2 +- .../components/humidifier/__init__.py | 5 +-- homeassistant/components/icloud/account.py | 2 +- .../components/image_processing/__init__.py | 2 ++ .../components/input_boolean/__init__.py | 2 +- .../components/input_datetime/__init__.py | 2 +- .../components/input_number/__init__.py | 2 +- .../components/input_select/__init__.py | 2 +- .../components/input_text/__init__.py | 2 +- homeassistant/components/light/__init__.py | 5 +-- homeassistant/components/lock/__init__.py | 4 ++- .../components/media_player/__init__.py | 2 ++ homeassistant/components/netio/switch.py | 2 +- .../openalpr_local/image_processing.py | 2 +- .../components/opencv/image_processing.py | 2 +- .../components/openhardwaremonitor/sensor.py | 4 +-- homeassistant/components/person/__init__.py | 2 +- homeassistant/components/plant/__init__.py | 2 +- .../components/proximity/__init__.py | 2 +- homeassistant/components/remote/__init__.py | 5 +-- .../components/rmvtransport/sensor.py | 2 +- homeassistant/components/script/__init__.py | 2 +- .../components/sony_projector/switch.py | 2 +- homeassistant/components/sun/__init__.py | 2 +- homeassistant/components/switch/__init__.py | 4 ++- homeassistant/components/timer/__init__.py | 2 +- homeassistant/components/twinkly/light.py | 2 +- .../components/utility_meter/__init__.py | 2 +- homeassistant/components/vacuum/__init__.py | 2 ++ .../components/water_heater/__init__.py | 4 ++- homeassistant/components/weather/__init__.py | 2 ++ .../components/workday/binary_sensor.py | 2 +- homeassistant/components/zone/__init__.py | 2 +- tests/components/fail2ban/test_sensor.py | 32 +++++++++---------- tests/components/plant/test_init.py | 6 ++-- 52 files changed, 106 insertions(+), 71 deletions(-) diff --git a/homeassistant/components/acer_projector/switch.py b/homeassistant/components/acer_projector/switch.py index 101f7cbd615..4a61ec793db 100644 --- a/homeassistant/components/acer_projector/switch.py +++ b/homeassistant/components/acer_projector/switch.py @@ -132,7 +132,7 @@ class AcerSwitch(SwitchEntity): return self._state @property - def state_attributes(self): + def extra_state_attributes(self): """Return state attributes.""" return self._attributes diff --git a/homeassistant/components/air_quality/__init__.py b/homeassistant/components/air_quality/__init__.py index 52c9208854a..d69a02f83bd 100644 --- a/homeassistant/components/air_quality/__init__.py +++ b/homeassistant/components/air_quality/__init__.py @@ -1,6 +1,7 @@ """Component for handling Air Quality data for your location.""" from datetime import timedelta import logging +from typing import final from homeassistant.const import ( ATTR_ATTRIBUTION, @@ -131,6 +132,7 @@ class AirQualityEntity(Entity): """Return the NO2 (nitrogen dioxide) level.""" return None + @final @property def state_attributes(self): """Return the state attributes.""" diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index 114abfa9cd6..7d9e47fbcbe 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -2,6 +2,7 @@ from abc import abstractmethod from datetime import timedelta import logging +from typing import final import voluptuous as vol @@ -172,6 +173,7 @@ class AlarmControlPanelEntity(Entity): def supported_features(self) -> int: """Return the list of supported features.""" + @final @property def state_attributes(self): """Return the state attributes.""" diff --git a/homeassistant/components/arwn/sensor.py b/homeassistant/components/arwn/sensor.py index 18186e8b871..d68549ea95c 100644 --- a/homeassistant/components/arwn/sensor.py +++ b/homeassistant/components/arwn/sensor.py @@ -154,7 +154,7 @@ class ArwnSensor(Entity): return self._uid @property - def state_attributes(self): + def extra_state_attributes(self): """Return all the state attributes.""" return self.event diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 425ffe17979..4c278cbf5f0 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -274,7 +274,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity): return False @property - def state_attributes(self): + def extra_state_attributes(self): """Return the entity state attributes.""" attrs = { ATTR_LAST_TRIGGERED: self.action_script.last_triggered, diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index 81ce49e9c94..11a6916ba83 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -4,7 +4,7 @@ from __future__ import annotations from datetime import timedelta import logging import re -from typing import cast +from typing import cast, final from aiohttp import web @@ -129,13 +129,14 @@ def is_offset_reached(event): class CalendarEventDevice(Entity): - """A calendar event device.""" + """Base class for calendar event entities.""" @property def event(self): """Return the next upcoming event.""" raise NotImplementedError() + @final @property def state_attributes(self): """Return the entity state attributes.""" diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 99b5cebc2a3..70739857587 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -8,6 +8,7 @@ import hashlib import logging import os from random import SystemRandom +from typing import final from aiohttp import web import async_timeout @@ -441,6 +442,7 @@ class Camera(Entity): """Call the job and disable motion detection.""" await self.hass.async_add_executor_job(self.disable_motion_detection) + @final @property def state_attributes(self): """Return the camera state attributes.""" diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 93041a6bb33..ef2d34aba9b 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -5,7 +5,7 @@ from abc import abstractmethod from datetime import timedelta import functools as ft import logging -from typing import Any +from typing import Any, final import voluptuous as vol @@ -167,7 +167,7 @@ async def async_unload_entry(hass: HomeAssistantType, entry): class ClimateEntity(Entity): - """Representation of a climate entity.""" + """Base class for climate entities.""" @property def state(self) -> str: @@ -213,6 +213,7 @@ class ClimateEntity(Entity): return data + @final @property def state_attributes(self) -> dict[str, Any]: """Return the optional state attributes.""" diff --git a/homeassistant/components/counter/__init__.py b/homeassistant/components/counter/__init__.py index a4e04825ef6..39cca0527eb 100644 --- a/homeassistant/components/counter/__init__.py +++ b/homeassistant/components/counter/__init__.py @@ -208,7 +208,7 @@ class Counter(RestoreEntity): return self._state @property - def state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" ret = { ATTR_EDITABLE: self.editable, diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py index c63963c87dc..034beb7f9db 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -2,7 +2,7 @@ from datetime import timedelta import functools as ft import logging -from typing import Any +from typing import Any, final import voluptuous as vol @@ -165,7 +165,7 @@ async def async_unload_entry(hass, entry): class CoverEntity(Entity): - """Representation of a cover.""" + """Base class for cover entities.""" @property def current_cover_position(self): @@ -196,6 +196,7 @@ class CoverEntity(Entity): return STATE_CLOSED if closed else STATE_OPEN + @final @property def state_attributes(self): """Return the state attributes.""" diff --git a/homeassistant/components/device_tracker/config_entry.py b/homeassistant/components/device_tracker/config_entry.py index 0d8970e087b..05fa4b4a60d 100644 --- a/homeassistant/components/device_tracker/config_entry.py +++ b/homeassistant/components/device_tracker/config_entry.py @@ -1,6 +1,8 @@ """Code to set up a device tracker platform using a config entry.""" from __future__ import annotations +from typing import final + from homeassistant.components import zone from homeassistant.const import ( ATTR_BATTERY_LEVEL, @@ -59,7 +61,7 @@ class BaseTrackerEntity(Entity): class TrackerEntity(BaseTrackerEntity): - """Represent a tracked device.""" + """Base class for a tracked device.""" @property def should_poll(self): @@ -114,6 +116,7 @@ class TrackerEntity(BaseTrackerEntity): return None + @final @property def state_attributes(self): """Return the device state attributes.""" @@ -128,7 +131,7 @@ class TrackerEntity(BaseTrackerEntity): class ScannerEntity(BaseTrackerEntity): - """Represent a tracked device that is on a scanned network.""" + """Base class for a tracked device that is on a scanned network.""" @property def ip_address(self) -> str: @@ -157,6 +160,7 @@ class ScannerEntity(BaseTrackerEntity): """Return true if the device is connected to the network.""" raise NotImplementedError + @final @property def state_attributes(self): """Return the device state attributes.""" diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index 1d3e428b46a..7ab8df1de87 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -5,7 +5,7 @@ import asyncio from datetime import timedelta import hashlib from types import ModuleType -from typing import Any, Callable, Sequence +from typing import Any, Callable, Sequence, final import attr import voluptuous as vol @@ -588,7 +588,7 @@ class DeviceTracker: class Device(RestoreEntity): - """Represent a tracked device.""" + """Base class for a tracked device.""" host_name: str = None location_name: str = None @@ -661,6 +661,7 @@ class Device(RestoreEntity): """Return the picture of the device.""" return self.config_picture + @final @property def state_attributes(self): """Return the device state attributes.""" diff --git a/homeassistant/components/fail2ban/sensor.py b/homeassistant/components/fail2ban/sensor.py index 2f206dca737..30aa0c69838 100644 --- a/homeassistant/components/fail2ban/sensor.py +++ b/homeassistant/components/fail2ban/sensor.py @@ -66,7 +66,7 @@ class BanSensor(Entity): return self._name @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the fail2ban sensor.""" return self.ban_dict diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 85b31c31576..da2224aaf33 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -5,6 +5,7 @@ from datetime import timedelta import functools as ft import logging import math +from typing import final import voluptuous as vol @@ -220,7 +221,7 @@ def _fan_native(method): class FanEntity(ToggleEntity): - """Representation of a fan.""" + """Base class for fan entities.""" @_fan_native def set_speed(self, speed: str) -> None: @@ -586,6 +587,7 @@ class FanEntity(ToggleEntity): f"The speed_list {speed_list} does not contain any valid speeds." ) from ex + @final @property def state_attributes(self) -> dict: """Return optional state attributes.""" diff --git a/homeassistant/components/fritzbox_netmonitor/sensor.py b/homeassistant/components/fritzbox_netmonitor/sensor.py index 13b822ae8a4..320144ae163 100644 --- a/homeassistant/components/fritzbox_netmonitor/sensor.py +++ b/homeassistant/components/fritzbox_netmonitor/sensor.py @@ -93,7 +93,7 @@ class FritzboxMonitorSensor(Entity): return self._state @property - def state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" # Don't return attributes if FritzBox is unreachable if self._state == STATE_UNAVAILABLE: diff --git a/homeassistant/components/geo_location/__init__.py b/homeassistant/components/geo_location/__init__.py index 2041c147b2b..11294e73f63 100644 --- a/homeassistant/components/geo_location/__init__.py +++ b/homeassistant/components/geo_location/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations from datetime import timedelta import logging +from typing import final from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE from homeassistant.helpers.config_validation import ( # noqa: F401 @@ -46,7 +47,7 @@ async def async_unload_entry(hass, entry): class GeolocationEvent(Entity): - """This represents an external event with an associated geolocation.""" + """Base class for an external event with an associated geolocation.""" @property def state(self): @@ -75,6 +76,7 @@ class GeolocationEvent(Entity): """Return longitude value of this external event.""" return None + @final @property def state_attributes(self): """Return the state attributes of this external event.""" diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index 96822b9f993..5e8a2e679f6 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -547,7 +547,7 @@ class Group(Entity): self._icon = value @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes for the group.""" data = {ATTR_ENTITY_ID: self.tracking, ATTR_ORDER: self._order} if not self.user_defined: diff --git a/homeassistant/components/homematic/entity.py b/homeassistant/components/homematic/entity.py index 3643871c607..50b9bcb2bfc 100644 --- a/homeassistant/components/homematic/entity.py +++ b/homeassistant/components/homematic/entity.py @@ -231,7 +231,7 @@ class HMHub(Entity): return self._state @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._variables.copy() diff --git a/homeassistant/components/humidifier/__init__.py b/homeassistant/components/humidifier/__init__.py index 01a9fc2ada0..f6a4ebcccd9 100644 --- a/homeassistant/components/humidifier/__init__.py +++ b/homeassistant/components/humidifier/__init__.py @@ -3,7 +3,7 @@ from __future__ import annotations from datetime import timedelta import logging -from typing import Any +from typing import Any, final import voluptuous as vol @@ -99,7 +99,7 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo class HumidifierEntity(ToggleEntity): - """Representation of a humidifier device.""" + """Base class for humidifier entities.""" @property def capability_attributes(self) -> dict[str, Any]: @@ -115,6 +115,7 @@ class HumidifierEntity(ToggleEntity): return data + @final @property def state_attributes(self) -> dict[str, Any]: """Return the optional state attributes.""" diff --git a/homeassistant/components/icloud/account.py b/homeassistant/components/icloud/account.py index 97bba3c5ca6..2fc8f7124d0 100644 --- a/homeassistant/components/icloud/account.py +++ b/homeassistant/components/icloud/account.py @@ -502,6 +502,6 @@ class IcloudDevice: return self._location @property - def state_attributes(self) -> dict[str, any]: + def exta_state_attributes(self) -> dict[str, any]: """Return the attributes.""" return self._attrs diff --git a/homeassistant/components/image_processing/__init__.py b/homeassistant/components/image_processing/__init__.py index e885a9ca7a9..1320629aeb4 100644 --- a/homeassistant/components/image_processing/__init__.py +++ b/homeassistant/components/image_processing/__init__.py @@ -2,6 +2,7 @@ import asyncio from datetime import timedelta import logging +from typing import final import voluptuous as vol @@ -175,6 +176,7 @@ class ImageProcessingFaceEntity(ImageProcessingEntity): """Return the class of this device, from component DEVICE_CLASSES.""" return "face" + @final @property def state_attributes(self): """Return device specific state attributes.""" diff --git a/homeassistant/components/input_boolean/__init__.py b/homeassistant/components/input_boolean/__init__.py index f1e4ebd57dc..b46135d934a 100644 --- a/homeassistant/components/input_boolean/__init__.py +++ b/homeassistant/components/input_boolean/__init__.py @@ -169,7 +169,7 @@ class InputBoolean(ToggleEntity, RestoreEntity): return self._config.get(CONF_NAME) @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the entity.""" return {ATTR_EDITABLE: self.editable} diff --git a/homeassistant/components/input_datetime/__init__.py b/homeassistant/components/input_datetime/__init__.py index 8273652be43..c408c73f869 100644 --- a/homeassistant/components/input_datetime/__init__.py +++ b/homeassistant/components/input_datetime/__init__.py @@ -319,7 +319,7 @@ class InputDatetime(RestoreEntity): return self._current_datetime.strftime(FMT_TIME) @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = { ATTR_EDITABLE: self.editable, diff --git a/homeassistant/components/input_number/__init__.py b/homeassistant/components/input_number/__init__.py index 5f73dec0192..d22a43c3330 100644 --- a/homeassistant/components/input_number/__init__.py +++ b/homeassistant/components/input_number/__init__.py @@ -256,7 +256,7 @@ class InputNumber(RestoreEntity): return self._config[CONF_ID] @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_INITIAL: self._config.get(CONF_INITIAL), diff --git a/homeassistant/components/input_select/__init__.py b/homeassistant/components/input_select/__init__.py index f14d23124e7..92defe2ded9 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -253,7 +253,7 @@ class InputSelect(RestoreEntity): return self._current_option @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_OPTIONS: self._config[ATTR_OPTIONS], ATTR_EDITABLE: self.editable} diff --git a/homeassistant/components/input_text/__init__.py b/homeassistant/components/input_text/__init__.py index d4e0fc705f2..3eef18f01b1 100644 --- a/homeassistant/components/input_text/__init__.py +++ b/homeassistant/components/input_text/__init__.py @@ -245,7 +245,7 @@ class InputText(RestoreEntity): return self._config[CONF_ID] @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_EDITABLE: self.editable, diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 7934874db0b..06b6829d31c 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -6,7 +6,7 @@ import dataclasses from datetime import timedelta import logging import os -from typing import cast +from typing import cast, final import voluptuous as vol @@ -478,7 +478,7 @@ class Profiles: class LightEntity(ToggleEntity): - """Representation of a light.""" + """Base class for light entities.""" @property def brightness(self) -> int | None: @@ -634,6 +634,7 @@ class LightEntity(ToggleEntity): data[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color) return data + @final @property def state_attributes(self): """Return state attributes.""" diff --git a/homeassistant/components/lock/__init__.py b/homeassistant/components/lock/__init__.py index bc7dc10ba8d..237daedae80 100644 --- a/homeassistant/components/lock/__init__.py +++ b/homeassistant/components/lock/__init__.py @@ -2,6 +2,7 @@ from datetime import timedelta import functools as ft import logging +from typing import final import voluptuous as vol @@ -76,7 +77,7 @@ async def async_unload_entry(hass, entry): class LockEntity(Entity): - """Representation of a lock.""" + """Base class for lock entities.""" @property def changed_by(self): @@ -117,6 +118,7 @@ class LockEntity(Entity): """Open the door latch.""" await self.hass.async_add_executor_job(ft.partial(self.open, **kwargs)) + @final @property def state_attributes(self): """Return the state attributes.""" diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 8e3ffe5dd0d..faa2c488216 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -9,6 +9,7 @@ import functools as ft import hashlib import logging import secrets +from typing import final from urllib.parse import urlparse from aiohttp import web @@ -853,6 +854,7 @@ class MediaPlayerEntity(Entity): return data + @final @property def state_attributes(self): """Return the state attributes.""" diff --git a/homeassistant/components/netio/switch.py b/homeassistant/components/netio/switch.py index 3c1482844ad..a254d06fc06 100644 --- a/homeassistant/components/netio/switch.py +++ b/homeassistant/components/netio/switch.py @@ -169,7 +169,7 @@ class NetioSwitch(SwitchEntity): self.netio.update() @property - def state_attributes(self): + def extra_state_attributes(self): """Return optional state attributes.""" return { ATTR_TOTAL_CONSUMPTION_KWH: self.cumulated_consumption_kwh, diff --git a/homeassistant/components/openalpr_local/image_processing.py b/homeassistant/components/openalpr_local/image_processing.py index ee913070a11..d098edba5b2 100644 --- a/homeassistant/components/openalpr_local/image_processing.py +++ b/homeassistant/components/openalpr_local/image_processing.py @@ -99,7 +99,7 @@ class ImageProcessingAlprEntity(ImageProcessingEntity): return "alpr" @property - def state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return {ATTR_PLATES: self.plates, ATTR_VEHICLES: self.vehicles} diff --git a/homeassistant/components/opencv/image_processing.py b/homeassistant/components/opencv/image_processing.py index 028d6eacf24..bf63ec0bfff 100644 --- a/homeassistant/components/opencv/image_processing.py +++ b/homeassistant/components/opencv/image_processing.py @@ -152,7 +152,7 @@ class OpenCVImageProcessor(ImageProcessingEntity): return self._total_matches @property - def state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return {ATTR_MATCHES: self._matches, ATTR_TOTAL_MATCHES: self._total_matches} diff --git a/homeassistant/components/openhardwaremonitor/sensor.py b/homeassistant/components/openhardwaremonitor/sensor.py index 115366dac66..3254f0824f1 100644 --- a/homeassistant/components/openhardwaremonitor/sensor.py +++ b/homeassistant/components/openhardwaremonitor/sensor.py @@ -73,8 +73,8 @@ class OpenHardwareMonitorDevice(Entity): return self.value @property - def state_attributes(self): - """Return the state attributes of the sun.""" + def extra_state_attributes(self): + """Return the state attributes of the entity.""" return self.attributes @classmethod diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index cf6403b7334..7774694ff4e 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -400,7 +400,7 @@ class Person(RestoreEntity): return self._state @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the person.""" data = {ATTR_EDITABLE: self.editable, ATTR_ID: self.unique_id} if self._latitude is not None: diff --git a/homeassistant/components/plant/__init__.py b/homeassistant/components/plant/__init__.py index 2d829187560..7c0c8e9b46f 100644 --- a/homeassistant/components/plant/__init__.py +++ b/homeassistant/components/plant/__init__.py @@ -348,7 +348,7 @@ class Plant(Entity): return self._state @property - def state_attributes(self): + def extra_state_attributes(self): """Return the attributes of the entity. Provide the individual measurements from the diff --git a/homeassistant/components/proximity/__init__.py b/homeassistant/components/proximity/__init__.py index 6df0f50b720..de9d6247f9f 100644 --- a/homeassistant/components/proximity/__init__.py +++ b/homeassistant/components/proximity/__init__.py @@ -148,7 +148,7 @@ class Proximity(Entity): return self._unit_of_measurement @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_DIR_OF_TRAVEL: self.dir_of_travel, ATTR_NEAREST: self.nearest} diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index fe220dd46a8..ecde6f67b67 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -4,7 +4,7 @@ from __future__ import annotations from datetime import timedelta import functools as ft import logging -from typing import Any, Iterable, cast +from typing import Any, Iterable, cast, final import voluptuous as vol @@ -141,7 +141,7 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo class RemoteEntity(ToggleEntity): - """Representation of a remote.""" + """Base class for remote entities.""" @property def supported_features(self) -> int: @@ -158,6 +158,7 @@ class RemoteEntity(ToggleEntity): """List of available activities.""" return None + @final @property def state_attributes(self) -> dict[str, Any] | None: """Return optional state attributes.""" diff --git a/homeassistant/components/rmvtransport/sensor.py b/homeassistant/components/rmvtransport/sensor.py index ad1ceea3d86..555f545d0c7 100644 --- a/homeassistant/components/rmvtransport/sensor.py +++ b/homeassistant/components/rmvtransport/sensor.py @@ -151,7 +151,7 @@ class RMVDepartureSensor(Entity): return self._state @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" try: return { diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index ec0bee73528..243cdaddd81 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -284,7 +284,7 @@ class ScriptEntity(ToggleEntity): return self.script.name @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = { ATTR_LAST_TRIGGERED: self.script.last_triggered, diff --git a/homeassistant/components/sony_projector/switch.py b/homeassistant/components/sony_projector/switch.py index 723478ac34b..935b33cc5df 100644 --- a/homeassistant/components/sony_projector/switch.py +++ b/homeassistant/components/sony_projector/switch.py @@ -65,7 +65,7 @@ class SonyProjector(SwitchEntity): return self._state @property - def state_attributes(self): + def extra_state_attributes(self): """Return state attributes.""" return self._attributes diff --git a/homeassistant/components/sun/__init__.py b/homeassistant/components/sun/__init__.py index 2d921da4a46..dfe3b15c110 100644 --- a/homeassistant/components/sun/__init__.py +++ b/homeassistant/components/sun/__init__.py @@ -124,7 +124,7 @@ class Sun(Entity): return STATE_BELOW_HORIZON @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sun.""" return { STATE_ATTR_NEXT_DAWN: self.next_dawn.isoformat(), diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 1d9b54a0424..c585fdc22d3 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -1,6 +1,7 @@ """Component to interface with switches that can be controlled remotely.""" from datetime import timedelta import logging +from typing import final import voluptuous as vol @@ -79,7 +80,7 @@ async def async_unload_entry(hass, entry): class SwitchEntity(ToggleEntity): - """Representation of a switch.""" + """Base class for switch entities.""" @property def current_power_w(self): @@ -96,6 +97,7 @@ class SwitchEntity(ToggleEntity): """Return true if device is in standby.""" return None + @final @property def state_attributes(self): """Return the optional state attributes.""" diff --git a/homeassistant/components/timer/__init__.py b/homeassistant/components/timer/__init__.py index 05955b46b5c..216ab3217a5 100644 --- a/homeassistant/components/timer/__init__.py +++ b/homeassistant/components/timer/__init__.py @@ -232,7 +232,7 @@ class Timer(RestoreEntity): return self._state @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = { ATTR_DURATION: _format_timedelta(self._duration), diff --git a/homeassistant/components/twinkly/light.py b/homeassistant/components/twinkly/light.py index ece3e0b048c..4353aa2707b 100644 --- a/homeassistant/components/twinkly/light.py +++ b/homeassistant/components/twinkly/light.py @@ -129,7 +129,7 @@ class TwinklyLight(LightEntity): return self._brightness @property - def state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return device specific state attributes.""" attributes = self._attributes diff --git a/homeassistant/components/utility_meter/__init__.py b/homeassistant/components/utility_meter/__init__.py index 24bfd77f762..5442cd583e2 100644 --- a/homeassistant/components/utility_meter/__init__.py +++ b/homeassistant/components/utility_meter/__init__.py @@ -165,7 +165,7 @@ class TariffSelect(RestoreEntity): return self._current_tariff @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_TARIFFS: self._tariffs} diff --git a/homeassistant/components/vacuum/__init__.py b/homeassistant/components/vacuum/__init__.py index ca5caec7ac1..d8803931f38 100644 --- a/homeassistant/components/vacuum/__init__.py +++ b/homeassistant/components/vacuum/__init__.py @@ -2,6 +2,7 @@ from datetime import timedelta from functools import partial import logging +from typing import final import voluptuous as vol @@ -271,6 +272,7 @@ class VacuumEntity(_BaseVacuum, ToggleEntity): battery_level=self.battery_level, charging=charging ) + @final @property def state_attributes(self): """Return the state attributes of the vacuum cleaner.""" diff --git a/homeassistant/components/water_heater/__init__.py b/homeassistant/components/water_heater/__init__.py index 0763c552075..5ae22c77b5e 100644 --- a/homeassistant/components/water_heater/__init__.py +++ b/homeassistant/components/water_heater/__init__.py @@ -2,6 +2,7 @@ from datetime import timedelta import functools as ft import logging +from typing import final import voluptuous as vol @@ -129,7 +130,7 @@ async def async_unload_entry(hass, entry): class WaterHeaterEntity(Entity): - """Representation of a water_heater device.""" + """Base class for water heater entities.""" @property def state(self): @@ -162,6 +163,7 @@ class WaterHeaterEntity(Entity): return data + @final @property def state_attributes(self): """Return the optional state attributes.""" diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 5127dae1102..da66c354d5a 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -1,6 +1,7 @@ """Weather component that handles meteorological data for your location.""" from datetime import timedelta import logging +from typing import final from homeassistant.const import PRECISION_TENTHS, PRECISION_WHOLE, TEMP_CELSIUS from homeassistant.helpers.config_validation import ( # noqa: F401 @@ -138,6 +139,7 @@ class WeatherEntity(Entity): else PRECISION_WHOLE ) + @final @property def state_attributes(self): """Return the state attributes.""" diff --git a/homeassistant/components/workday/binary_sensor.py b/homeassistant/components/workday/binary_sensor.py index 7a414035900..ed3822b9698 100644 --- a/homeassistant/components/workday/binary_sensor.py +++ b/homeassistant/components/workday/binary_sensor.py @@ -171,7 +171,7 @@ class IsWorkdaySensor(BinarySensorEntity): return False @property - def state_attributes(self): + def extra_state_attributes(self): """Return the attributes of the entity.""" # return self._attributes return { diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index 7e329127d03..4866c278074 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -315,7 +315,7 @@ class Zone(entity.Entity): return self._config.get(CONF_ICON) @property - def state_attributes(self) -> dict | None: + def extra_state_attributes(self) -> dict | None: """Return the state attributes of the zone.""" return self._attrs diff --git a/tests/components/fail2ban/test_sensor.py b/tests/components/fail2ban/test_sensor.py index e43064c54ab..f9c78e14888 100644 --- a/tests/components/fail2ban/test_sensor.py +++ b/tests/components/fail2ban/test_sensor.py @@ -89,8 +89,8 @@ async def test_single_ban(hass): sensor.update() assert sensor.state == "111.111.111.111" - assert sensor.state_attributes[STATE_CURRENT_BANS] == ["111.111.111.111"] - assert sensor.state_attributes[STATE_ALL_BANS] == ["111.111.111.111"] + assert sensor.extra_state_attributes[STATE_CURRENT_BANS] == ["111.111.111.111"] + assert sensor.extra_state_attributes[STATE_ALL_BANS] == ["111.111.111.111"] async def test_ipv6_ban(hass): @@ -103,8 +103,8 @@ async def test_ipv6_ban(hass): sensor.update() assert sensor.state == "2607:f0d0:1002:51::4" - assert sensor.state_attributes[STATE_CURRENT_BANS] == ["2607:f0d0:1002:51::4"] - assert sensor.state_attributes[STATE_ALL_BANS] == ["2607:f0d0:1002:51::4"] + assert sensor.extra_state_attributes[STATE_CURRENT_BANS] == ["2607:f0d0:1002:51::4"] + assert sensor.extra_state_attributes[STATE_ALL_BANS] == ["2607:f0d0:1002:51::4"] async def test_multiple_ban(hass): @@ -117,11 +117,11 @@ async def test_multiple_ban(hass): sensor.update() assert sensor.state == "222.222.222.222" - assert sensor.state_attributes[STATE_CURRENT_BANS] == [ + assert sensor.extra_state_attributes[STATE_CURRENT_BANS] == [ "111.111.111.111", "222.222.222.222", ] - assert sensor.state_attributes[STATE_ALL_BANS] == [ + assert sensor.extra_state_attributes[STATE_ALL_BANS] == [ "111.111.111.111", "222.222.222.222", ] @@ -137,8 +137,8 @@ async def test_unban_all(hass): sensor.update() assert sensor.state == "None" - assert sensor.state_attributes[STATE_CURRENT_BANS] == [] - assert sensor.state_attributes[STATE_ALL_BANS] == [ + assert sensor.extra_state_attributes[STATE_CURRENT_BANS] == [] + assert sensor.extra_state_attributes[STATE_ALL_BANS] == [ "111.111.111.111", "222.222.222.222", ] @@ -154,8 +154,8 @@ async def test_unban_one(hass): sensor.update() assert sensor.state == "222.222.222.222" - assert sensor.state_attributes[STATE_CURRENT_BANS] == ["222.222.222.222"] - assert sensor.state_attributes[STATE_ALL_BANS] == [ + assert sensor.extra_state_attributes[STATE_CURRENT_BANS] == ["222.222.222.222"] + assert sensor.extra_state_attributes[STATE_ALL_BANS] == [ "111.111.111.111", "222.222.222.222", ] @@ -174,11 +174,11 @@ async def test_multi_jail(hass): sensor2.update() assert sensor1.state == "111.111.111.111" - assert sensor1.state_attributes[STATE_CURRENT_BANS] == ["111.111.111.111"] - assert sensor1.state_attributes[STATE_ALL_BANS] == ["111.111.111.111"] + assert sensor1.extra_state_attributes[STATE_CURRENT_BANS] == ["111.111.111.111"] + assert sensor1.extra_state_attributes[STATE_ALL_BANS] == ["111.111.111.111"] assert sensor2.state == "222.222.222.222" - assert sensor2.state_attributes[STATE_CURRENT_BANS] == ["222.222.222.222"] - assert sensor2.state_attributes[STATE_ALL_BANS] == ["222.222.222.222"] + assert sensor2.extra_state_attributes[STATE_CURRENT_BANS] == ["222.222.222.222"] + assert sensor2.extra_state_attributes[STATE_ALL_BANS] == ["222.222.222.222"] async def test_ban_active_after_update(hass): @@ -192,5 +192,5 @@ async def test_ban_active_after_update(hass): assert sensor.state == "111.111.111.111" sensor.update() assert sensor.state == "111.111.111.111" - assert sensor.state_attributes[STATE_CURRENT_BANS] == ["111.111.111.111"] - assert sensor.state_attributes[STATE_ALL_BANS] == ["111.111.111.111"] + assert sensor.extra_state_attributes[STATE_CURRENT_BANS] == ["111.111.111.111"] + assert sensor.extra_state_attributes[STATE_ALL_BANS] == ["111.111.111.111"] diff --git a/tests/components/plant/test_init.py b/tests/components/plant/test_init.py index 494206d81a3..14e0f3668b0 100644 --- a/tests/components/plant/test_init.py +++ b/tests/components/plant/test_init.py @@ -58,7 +58,7 @@ async def test_valid_data(hass): State(GOOD_CONFIG["sensors"][reading], value), ) assert sensor.state == "ok" - attrib = sensor.state_attributes + attrib = sensor.extra_state_attributes for reading, value in GOOD_DATA.items(): # battery level has a different name in # the JSON format than in hass @@ -70,13 +70,13 @@ async def test_low_battery(hass): sensor = plant.Plant("other plant", GOOD_CONFIG) sensor.entity_id = "sensor.mqtt_plant_battery" sensor.hass = hass - assert sensor.state_attributes["problem"] == "none" + assert sensor.extra_state_attributes["problem"] == "none" sensor.state_changed( "sensor.mqtt_plant_battery", State("sensor.mqtt_plant_battery", 10), ) assert sensor.state == "problem" - assert sensor.state_attributes["problem"] == "battery low" + assert sensor.extra_state_attributes["problem"] == "battery low" async def test_initial_states(hass): From fb48fd7d10a18c322634aa9f0821f2619f7b4ca6 Mon Sep 17 00:00:00 2001 From: Kevin Worrel <37058192+dieselrabbit@users.noreply.github.com> Date: Sun, 21 Mar 2021 03:56:46 -0700 Subject: [PATCH 517/831] ScreenLogic cleanups (#48136) * ScreenLogic cleanup. Bump screenlogicpy to 0.2.0. Move heating functions from water_heater to climate platform. Address notes from original PR. * Fix temperature attribute * Addressing notes. Bump screenlogicpy to 0.2.1. Made device_types constants. Made (known) equipment flags constants. Used dict.get() in places where None is the default. Return fast with good _last_preset. * Update homeassistant/components/screenlogic/climate.py Let base entity handle state property. Co-authored-by: J. Nick Koston * Patch integration setup functions. * Exception, ATTR_TEMPERATURE notes Co-authored-by: J. Nick Koston --- .coveragerc | 2 +- .../components/screenlogic/__init__.py | 21 +- .../components/screenlogic/binary_sensor.py | 24 +- .../components/screenlogic/climate.py | 209 ++++++++++++++++++ .../components/screenlogic/config_flow.py | 4 +- .../components/screenlogic/manifest.json | 2 +- .../components/screenlogic/sensor.py | 38 ++-- .../components/screenlogic/switch.py | 15 +- .../components/screenlogic/water_heater.py | 128 ----------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../screenlogic/test_config_flow.py | 101 ++++++++- 12 files changed, 354 insertions(+), 194 deletions(-) create mode 100644 homeassistant/components/screenlogic/climate.py delete mode 100644 homeassistant/components/screenlogic/water_heater.py diff --git a/.coveragerc b/.coveragerc index 33f4071c8f4..48f71f5a998 100644 --- a/.coveragerc +++ b/.coveragerc @@ -840,9 +840,9 @@ omit = homeassistant/components/scrape/sensor.py homeassistant/components/screenlogic/__init__.py homeassistant/components/screenlogic/binary_sensor.py + homeassistant/components/screenlogic/climate.py homeassistant/components/screenlogic/sensor.py homeassistant/components/screenlogic/switch.py - homeassistant/components/screenlogic/water_heater.py homeassistant/components/scsgate/* homeassistant/components/scsgate/cover.py homeassistant/components/sendgrid/notify.py diff --git a/homeassistant/components/screenlogic/__init__.py b/homeassistant/components/screenlogic/__init__.py index a5e0f248bc6..cca3e7e8785 100644 --- a/homeassistant/components/screenlogic/__init__.py +++ b/homeassistant/components/screenlogic/__init__.py @@ -6,7 +6,7 @@ import logging from screenlogicpy import ScreenLogicError, ScreenLogicGateway from screenlogicpy.const import ( - CONTROLLER_HARDWARE, + EQUIPMENT, SL_GATEWAY_IP, SL_GATEWAY_NAME, SL_GATEWAY_PORT, @@ -28,7 +28,7 @@ from .const import DEFAULT_SCAN_INTERVAL, DISCOVERED_GATEWAYS, DOMAIN _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["switch", "sensor", "binary_sensor", "water_heater"] +PLATFORMS = ["switch", "sensor", "binary_sensor", "climate"] async def async_setup(hass: HomeAssistant, config: dict): @@ -59,11 +59,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): except ScreenLogicError as ex: _LOGGER.error("Error while connecting to the gateway %s: %s", connect_info, ex) raise ConfigEntryNotReady from ex - except AttributeError as ex: - _LOGGER.exception( - "Unexpected error while connecting to the gateway %s", connect_info - ) - raise ConfigEntryNotReady from ex coordinator = ScreenlogicDataUpdateCoordinator( hass, config_entry=entry, gateway=gateway @@ -91,7 +86,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): device_data["pump"].append(pump) for body in coordinator.data["bodies"]: - device_data["water_heater"].append(body) + device_data["body"].append(body) hass.data[DOMAIN][entry.entry_id] = { "coordinator": coordinator, @@ -151,18 +146,18 @@ class ScreenlogicDataUpdateCoordinator(DataUpdateCoordinator): """Fetch data from the Screenlogic gateway.""" try: await self.hass.async_add_executor_job(self.gateway.update) - return self.gateway.get_data() except ScreenLogicError as error: raise UpdateFailed(error) from error + return self.gateway.get_data() class ScreenlogicEntity(CoordinatorEntity): """Base class for all ScreenLogic entities.""" - def __init__(self, coordinator, datakey): + def __init__(self, coordinator, data_key): """Initialize of the entity.""" super().__init__(coordinator) - self._data_key = datakey + self._data_key = data_key @property def mac(self): @@ -192,11 +187,11 @@ class ScreenlogicEntity(CoordinatorEntity): @property def device_info(self): """Return device information for the controller.""" - controller_type = self.config_data["controler_type"] + controller_type = self.config_data["controller_type"] hardware_type = self.config_data["hardware_type"] return { "connections": {(dr.CONNECTION_NETWORK_MAC, self.mac)}, "name": self.gateway_name, "manufacturer": "Pentair", - "model": CONTROLLER_HARDWARE[controller_type][hardware_type], + "model": EQUIPMENT.CONTROLLER_HARDWARE[controller_type][hardware_type], } diff --git a/homeassistant/components/screenlogic/binary_sensor.py b/homeassistant/components/screenlogic/binary_sensor.py index fa8d63ee5e6..0001223030a 100644 --- a/homeassistant/components/screenlogic/binary_sensor.py +++ b/homeassistant/components/screenlogic/binary_sensor.py @@ -1,15 +1,20 @@ """Support for a ScreenLogic Binary Sensor.""" import logging -from screenlogicpy.const import ON_OFF +from screenlogicpy.const import DEVICE_TYPE, ON_OFF -from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_PROBLEM, + BinarySensorEntity, +) from . import ScreenlogicEntity from .const import DOMAIN _LOGGER = logging.getLogger(__name__) +SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS = {DEVICE_TYPE.ALARM: DEVICE_CLASS_PROBLEM} + async def async_setup_entry(hass, config_entry, async_add_entities): """Set up entry.""" @@ -19,7 +24,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): for binary_sensor in data["devices"]["binary_sensor"]: entities.append(ScreenLogicBinarySensor(coordinator, binary_sensor)) - async_add_entities(entities, True) + async_add_entities(entities) class ScreenLogicBinarySensor(ScreenlogicEntity, BinarySensorEntity): @@ -33,10 +38,8 @@ class ScreenLogicBinarySensor(ScreenlogicEntity, BinarySensorEntity): @property def device_class(self): """Return the device class.""" - device_class = self.sensor.get("hass_type") - if device_class in DEVICE_CLASSES: - return device_class - return None + device_class = self.sensor.get("device_type") + return SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS.get(device_class) @property def is_on(self) -> bool: @@ -46,9 +49,4 @@ class ScreenLogicBinarySensor(ScreenlogicEntity, BinarySensorEntity): @property def sensor(self): """Shortcut to access the sensor data.""" - return self.sensor_data[self._data_key] - - @property - def sensor_data(self): - """Shortcut to access the sensors data.""" - return self.coordinator.data["sensors"] + return self.coordinator.data["sensors"][self._data_key] diff --git a/homeassistant/components/screenlogic/climate.py b/homeassistant/components/screenlogic/climate.py new file mode 100644 index 00000000000..d289c00228c --- /dev/null +++ b/homeassistant/components/screenlogic/climate.py @@ -0,0 +1,209 @@ +"""Support for a ScreenLogic heating device.""" +import logging + +from screenlogicpy.const import EQUIPMENT, HEAT_MODE + +from homeassistant.components.climate import ClimateEntity +from homeassistant.components.climate.const import ( + ATTR_PRESET_MODE, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, +) +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.restore_state import RestoreEntity + +from . import ScreenlogicEntity +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +SUPPORTED_FEATURES = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE + +SUPPORTED_MODES = [HVAC_MODE_OFF, HVAC_MODE_HEAT] + +SUPPORTED_PRESETS = [ + HEAT_MODE.SOLAR, + HEAT_MODE.SOLAR_PREFERRED, + HEAT_MODE.HEATER, +] + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up entry.""" + entities = [] + data = hass.data[DOMAIN][config_entry.entry_id] + coordinator = data["coordinator"] + + for body in data["devices"]["body"]: + entities.append(ScreenLogicClimate(coordinator, body)) + async_add_entities(entities) + + +class ScreenLogicClimate(ScreenlogicEntity, ClimateEntity, RestoreEntity): + """Represents a ScreenLogic climate entity.""" + + def __init__(self, coordinator, body): + """Initialize a ScreenLogic climate entity.""" + super().__init__(coordinator, body) + self._configured_heat_modes = [] + # Is solar listed as available equipment? + if self.coordinator.data["config"]["equipment_flags"] & EQUIPMENT.FLAG_SOLAR: + self._configured_heat_modes.extend( + [HEAT_MODE.SOLAR, HEAT_MODE.SOLAR_PREFERRED] + ) + self._configured_heat_modes.append(HEAT_MODE.HEATER) + self._last_preset = None + + @property + def name(self) -> str: + """Name of the heater.""" + ent_name = self.body["heat_status"]["name"] + return f"{self.gateway_name} {ent_name}" + + @property + def min_temp(self) -> float: + """Minimum allowed temperature.""" + return self.body["min_set_point"]["value"] + + @property + def max_temp(self) -> float: + """Maximum allowed temperature.""" + return self.body["max_set_point"]["value"] + + @property + def current_temperature(self) -> float: + """Return water temperature.""" + return self.body["last_temperature"]["value"] + + @property + def target_temperature(self) -> float: + """Target temperature.""" + return self.body["heat_set_point"]["value"] + + @property + def temperature_unit(self) -> str: + """Return the unit of measurement.""" + if self.config_data["is_celcius"]["value"] == 1: + return TEMP_CELSIUS + return TEMP_FAHRENHEIT + + @property + def hvac_mode(self) -> str: + """Return the current hvac mode.""" + if self.body["heat_mode"]["value"] > 0: + return HVAC_MODE_HEAT + return HVAC_MODE_OFF + + @property + def hvac_modes(self): + """Return th supported hvac modes.""" + return SUPPORTED_MODES + + @property + def hvac_action(self) -> str: + """Return the current action of the heater.""" + if self.body["heat_status"]["value"] > 0: + return CURRENT_HVAC_HEAT + if self.hvac_mode == HVAC_MODE_HEAT: + return CURRENT_HVAC_IDLE + return CURRENT_HVAC_OFF + + @property + def preset_mode(self) -> str: + """Return current/last preset mode.""" + if self.hvac_mode == HVAC_MODE_OFF: + return HEAT_MODE.NAME_FOR_NUM[self._last_preset] + return HEAT_MODE.NAME_FOR_NUM[self.body["heat_mode"]["value"]] + + @property + def preset_modes(self): + """All available presets.""" + return [ + HEAT_MODE.NAME_FOR_NUM[mode_num] for mode_num in self._configured_heat_modes + ] + + @property + def supported_features(self): + """Supported features of the heater.""" + return SUPPORTED_FEATURES + + async def async_set_temperature(self, **kwargs) -> None: + """Change the setpoint of the heater.""" + if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None: + raise ValueError(f"Expected attribute {ATTR_TEMPERATURE}") + + if await self.hass.async_add_executor_job( + self.gateway.set_heat_temp, int(self._data_key), int(temperature) + ): + await self.coordinator.async_request_refresh() + else: + raise HomeAssistantError( + f"Failed to set_temperature {temperature} on body {self.body['body_type']['value']}" + ) + + async def async_set_hvac_mode(self, hvac_mode) -> None: + """Set the operation mode.""" + if hvac_mode == HVAC_MODE_OFF: + mode = HEAT_MODE.OFF + else: + mode = HEAT_MODE.NUM_FOR_NAME[self.preset_mode] + if await self.hass.async_add_executor_job( + self.gateway.set_heat_mode, int(self._data_key), int(mode) + ): + await self.coordinator.async_request_refresh() + else: + raise HomeAssistantError( + f"Failed to set_hvac_mode {mode} on body {self.body['body_type']['value']}" + ) + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set the preset mode.""" + _LOGGER.debug("Setting last_preset to %s", HEAT_MODE.NUM_FOR_NAME[preset_mode]) + self._last_preset = mode = HEAT_MODE.NUM_FOR_NAME[preset_mode] + if self.hvac_mode == HVAC_MODE_OFF: + return + if await self.hass.async_add_executor_job( + self.gateway.set_heat_mode, int(self._data_key), int(mode) + ): + await self.coordinator.async_request_refresh() + else: + raise HomeAssistantError( + f"Failed to set_preset_mode {mode} on body {self.body['body_type']['value']}" + ) + + async def async_added_to_hass(self): + """Run when entity is about to be added.""" + await super().async_added_to_hass() + + _LOGGER.debug("Startup last preset is %s", self._last_preset) + if self._last_preset is not None: + return + prev_state = await self.async_get_last_state() + if ( + prev_state is not None + and prev_state.attributes.get(ATTR_PRESET_MODE) is not None + ): + _LOGGER.debug( + "Startup setting last_preset to %s from prev_state", + HEAT_MODE.NUM_FOR_NAME[prev_state.attributes.get(ATTR_PRESET_MODE)], + ) + self._last_preset = HEAT_MODE.NUM_FOR_NAME[ + prev_state.attributes.get(ATTR_PRESET_MODE) + ] + else: + _LOGGER.debug( + "Startup setting last_preset to default (%s)", + self._configured_heat_modes[0], + ) + self._last_preset = self._configured_heat_modes[0] + + @property + def body(self): + """Shortcut to access body data.""" + return self.coordinator.data["bodies"][self._data_key] diff --git a/homeassistant/components/screenlogic/config_flow.py b/homeassistant/components/screenlogic/config_flow.py index 865c0fdbbf4..32a29564844 100644 --- a/homeassistant/components/screenlogic/config_flow.py +++ b/homeassistant/components/screenlogic/config_flow.py @@ -120,7 +120,7 @@ class ScreenlogicConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): mac = user_input[GATEWAY_SELECT_KEY] selected_gateway = self.discovered_gateways[mac] - await self.async_set_unique_id(mac) + await self.async_set_unique_id(mac, raise_on_progress=False) self._abort_if_unique_id_configured() return self.async_create_entry( title=name_for_mac(mac), @@ -164,7 +164,7 @@ class ScreenlogicConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors[CONF_IP_ADDRESS] = "cannot_connect" if not errors: - await self.async_set_unique_id(mac) + await self.async_set_unique_id(mac, raise_on_progress=False) self._abort_if_unique_id_configured() return self.async_create_entry( title=name_for_mac(mac), diff --git a/homeassistant/components/screenlogic/manifest.json b/homeassistant/components/screenlogic/manifest.json index 2ff3f2c683d..ab3d08a0702 100644 --- a/homeassistant/components/screenlogic/manifest.json +++ b/homeassistant/components/screenlogic/manifest.json @@ -3,7 +3,7 @@ "name": "Pentair ScreenLogic", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/screenlogic", - "requirements": ["screenlogicpy==0.1.2"], + "requirements": ["screenlogicpy==0.2.1"], "codeowners": [ "@dieselrabbit" ], diff --git a/homeassistant/components/screenlogic/sensor.py b/homeassistant/components/screenlogic/sensor.py index f4f86cdd2aa..c9c66a75681 100644 --- a/homeassistant/components/screenlogic/sensor.py +++ b/homeassistant/components/screenlogic/sensor.py @@ -1,7 +1,9 @@ """Support for a ScreenLogic Sensor.""" import logging -from homeassistant.components.sensor import DEVICE_CLASSES +from screenlogicpy.const import DEVICE_TYPE + +from homeassistant.components.sensor import DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE from . import ScreenlogicEntity from .const import DOMAIN @@ -10,6 +12,11 @@ _LOGGER = logging.getLogger(__name__) PUMP_SENSORS = ("currentWatts", "currentRPM", "currentGPM") +SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS = { + DEVICE_TYPE.TEMPERATURE: DEVICE_CLASS_TEMPERATURE, + DEVICE_TYPE.ENERGY: DEVICE_CLASS_POWER, +} + async def async_setup_entry(hass, config_entry, async_add_entities): """Set up entry.""" @@ -19,11 +26,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities): # Generic sensors for sensor in data["devices"]["sensor"]: entities.append(ScreenLogicSensor(coordinator, sensor)) + # Pump sensors for pump in data["devices"]["pump"]: for pump_key in PUMP_SENSORS: entities.append(ScreenLogicPumpSensor(coordinator, pump, pump_key)) - async_add_entities(entities, True) + async_add_entities(entities) class ScreenLogicSensor(ScreenlogicEntity): @@ -42,10 +50,8 @@ class ScreenLogicSensor(ScreenlogicEntity): @property def device_class(self): """Device class of the sensor.""" - device_class = self.sensor.get("hass_type") - if device_class in DEVICE_CLASSES: - return device_class - return None + device_class = self.sensor.get("device_type") + return SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS.get(device_class) @property def state(self): @@ -56,12 +62,7 @@ class ScreenLogicSensor(ScreenlogicEntity): @property def sensor(self): """Shortcut to access the sensor data.""" - return self.sensor_data[self._data_key] - - @property - def sensor_data(self): - """Shortcut to access the sensors data.""" - return self.coordinator.data["sensors"] + return self.coordinator.data["sensors"][self._data_key] class ScreenLogicPumpSensor(ScreenlogicEntity): @@ -86,10 +87,8 @@ class ScreenLogicPumpSensor(ScreenlogicEntity): @property def device_class(self): """Return the device class.""" - device_class = self.pump_sensor.get("hass_type") - if device_class in DEVICE_CLASSES: - return device_class - return None + device_class = self.pump_sensor.get("device_type") + return SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS.get(device_class) @property def state(self): @@ -99,9 +98,4 @@ class ScreenLogicPumpSensor(ScreenlogicEntity): @property def pump_sensor(self): """Shortcut to access the pump sensor data.""" - return self.pumps_data[self._pump_id][self._key] - - @property - def pumps_data(self): - """Shortcut to access the pump data.""" - return self.coordinator.data["pumps"] + return self.coordinator.data["pumps"][self._pump_id][self._key] diff --git a/homeassistant/components/screenlogic/switch.py b/homeassistant/components/screenlogic/switch.py index 7c1a468df7d..d1ef9397bfc 100644 --- a/homeassistant/components/screenlogic/switch.py +++ b/homeassistant/components/screenlogic/switch.py @@ -19,7 +19,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): for switch in data["devices"]["switch"]: entities.append(ScreenLogicSwitch(coordinator, switch)) - async_add_entities(entities, True) + async_add_entities(entities) class ScreenLogicSwitch(ScreenlogicEntity, SwitchEntity): @@ -47,17 +47,14 @@ class ScreenLogicSwitch(ScreenlogicEntity, SwitchEntity): if await self.hass.async_add_executor_job( self.gateway.set_circuit, self._data_key, circuit_value ): - _LOGGER.debug("Screenlogic turn %s %s", circuit_value, self._data_key) + _LOGGER.debug("Turn %s %s", self._data_key, circuit_value) await self.coordinator.async_request_refresh() else: - _LOGGER.info("Screenlogic turn %s %s error", circuit_value, self._data_key) + _LOGGER.warning( + "Failed to set_circuit %s %s", self._data_key, circuit_value + ) @property def circuit(self): """Shortcut to access the circuit.""" - return self.circuits_data[self._data_key] - - @property - def circuits_data(self): - """Shortcut to access the circuits data.""" - return self.coordinator.data["circuits"] + return self.coordinator.data["circuits"][self._data_key] diff --git a/homeassistant/components/screenlogic/water_heater.py b/homeassistant/components/screenlogic/water_heater.py deleted file mode 100644 index 6b16f68e141..00000000000 --- a/homeassistant/components/screenlogic/water_heater.py +++ /dev/null @@ -1,128 +0,0 @@ -"""Support for a ScreenLogic Water Heater.""" -import logging - -from screenlogicpy.const import HEAT_MODE - -from homeassistant.components.water_heater import ( - SUPPORT_OPERATION_MODE, - SUPPORT_TARGET_TEMPERATURE, - WaterHeaterEntity, -) -from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT - -from . import ScreenlogicEntity -from .const import DOMAIN - -_LOGGER = logging.getLogger(__name__) - -SUPPORTED_FEATURES = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE - -HEAT_MODE_NAMES = HEAT_MODE.Names - -MODE_NAME_TO_MODE_NUM = { - HEAT_MODE_NAMES[num]: num for num in range(len(HEAT_MODE_NAMES)) -} - - -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up entry.""" - entities = [] - data = hass.data[DOMAIN][config_entry.entry_id] - coordinator = data["coordinator"] - - for body in data["devices"]["water_heater"]: - entities.append(ScreenLogicWaterHeater(coordinator, body)) - async_add_entities(entities, True) - - -class ScreenLogicWaterHeater(ScreenlogicEntity, WaterHeaterEntity): - """Represents the heating functions for a body of water.""" - - @property - def name(self) -> str: - """Name of the water heater.""" - ent_name = self.body["heat_status"]["name"] - return f"{self.gateway_name} {ent_name}" - - @property - def state(self) -> str: - """State of the water heater.""" - return HEAT_MODE.GetFriendlyName(self.body["heat_status"]["value"]) - - @property - def min_temp(self) -> float: - """Minimum allowed temperature.""" - return self.body["min_set_point"]["value"] - - @property - def max_temp(self) -> float: - """Maximum allowed temperature.""" - return self.body["max_set_point"]["value"] - - @property - def current_temperature(self) -> float: - """Return water temperature.""" - return self.body["last_temperature"]["value"] - - @property - def target_temperature(self) -> float: - """Target temperature.""" - return self.body["heat_set_point"]["value"] - - @property - def temperature_unit(self) -> str: - """Return the unit of measurement.""" - if self.config_data["is_celcius"]["value"] == 1: - return TEMP_CELSIUS - return TEMP_FAHRENHEIT - - @property - def current_operation(self) -> str: - """Return operation.""" - return HEAT_MODE_NAMES[self.body["heat_mode"]["value"]] - - @property - def operation_list(self): - """All available operations.""" - supported_heat_modes = [HEAT_MODE.OFF] - # Is solar listed as available equipment? - if self.coordinator.data["config"]["equipment_flags"] & 0x1: - supported_heat_modes.extend([HEAT_MODE.SOLAR, HEAT_MODE.SOLAR_PREFERED]) - supported_heat_modes.append(HEAT_MODE.HEATER) - - return [HEAT_MODE_NAMES[mode_num] for mode_num in supported_heat_modes] - - @property - def supported_features(self): - """Supported features of the water heater.""" - return SUPPORTED_FEATURES - - async def async_set_temperature(self, **kwargs) -> None: - """Change the setpoint of the heater.""" - temperature = kwargs.get(ATTR_TEMPERATURE) - if await self.hass.async_add_executor_job( - self.gateway.set_heat_temp, int(self._data_key), int(temperature) - ): - await self.coordinator.async_request_refresh() - else: - _LOGGER.error("Screenlogic set_temperature error") - - async def async_set_operation_mode(self, operation_mode) -> None: - """Set the operation mode.""" - mode = MODE_NAME_TO_MODE_NUM[operation_mode] - if await self.hass.async_add_executor_job( - self.gateway.set_heat_mode, int(self._data_key), int(mode) - ): - await self.coordinator.async_request_refresh() - else: - _LOGGER.error("Screenlogic set_operation_mode error") - - @property - def body(self): - """Shortcut to access body data.""" - return self.bodies_data[self._data_key] - - @property - def bodies_data(self): - """Shortcut to access bodies data.""" - return self.coordinator.data["bodies"] diff --git a/requirements_all.txt b/requirements_all.txt index 6c3793e6c1f..0ee7a4edb72 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2009,7 +2009,7 @@ scapy==2.4.4 schiene==0.23 # homeassistant.components.screenlogic -screenlogicpy==0.1.2 +screenlogicpy==0.2.1 # homeassistant.components.scsgate scsgate==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 04a5c645087..6ceeccec6b5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1040,7 +1040,7 @@ samsungtvws==1.6.0 scapy==2.4.4 # homeassistant.components.screenlogic -screenlogicpy==0.1.2 +screenlogicpy==0.2.1 # homeassistant.components.emulated_kasa # homeassistant.components.sense diff --git a/tests/components/screenlogic/test_config_flow.py b/tests/components/screenlogic/test_config_flow.py index 6d2c1ee2595..71dc4935001 100644 --- a/tests/components/screenlogic/test_config_flow.py +++ b/tests/components/screenlogic/test_config_flow.py @@ -101,9 +101,40 @@ async def test_flow_discover_error(hass): assert result["errors"] == {} assert result["step_id"] == "gateway_entry" + with patch( + "homeassistant.components.screenlogic.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.screenlogic.async_setup_entry", + return_value=True, + ) as mock_setup_entry, patch( + "homeassistant.components.screenlogic.config_flow.login.create_socket", + return_value=True, + ), patch( + "homeassistant.components.screenlogic.config_flow.login.gateway_connect", + return_value="00-C0-33-01-01-01", + ): + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_PORT: 80, + }, + ) + await hass.async_block_till_done() + + assert result3["type"] == "create_entry" + assert result3["title"] == "Pentair: 01-01-01" + assert result3["data"] == { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_PORT: 80, + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + async def test_dhcp(hass): """Test DHCP discovery flow.""" + await setup.async_setup_component(hass, "persistent_notification", {}) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": "dhcp"}, @@ -116,6 +147,36 @@ async def test_dhcp(hass): assert result["type"] == "form" assert result["step_id"] == "gateway_entry" + with patch( + "homeassistant.components.screenlogic.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.screenlogic.async_setup_entry", + return_value=True, + ) as mock_setup_entry, patch( + "homeassistant.components.screenlogic.config_flow.login.create_socket", + return_value=True, + ), patch( + "homeassistant.components.screenlogic.config_flow.login.gateway_connect", + return_value="00-C0-33-01-01-01", + ): + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_PORT: 80, + }, + ) + await hass.async_block_till_done() + + assert result3["type"] == "create_entry" + assert result3["title"] == "Pentair: 01-01-01" + assert result3["data"] == { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_PORT: 80, + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + async def test_form_manual_entry(hass): """Test we get the form.""" @@ -148,6 +209,11 @@ async def test_form_manual_entry(hass): assert result2["step_id"] == "gateway_entry" with patch( + "homeassistant.components.screenlogic.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.screenlogic.async_setup_entry", + return_value=True, + ) as mock_setup_entry, patch( "homeassistant.components.screenlogic.config_flow.login.create_socket", return_value=True, ), patch( @@ -169,6 +235,8 @@ async def test_form_manual_entry(hass): CONF_IP_ADDRESS: "1.1.1.1", CONF_PORT: 80, } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 async def test_form_cannot_connect(hass): @@ -195,9 +263,18 @@ async def test_form_cannot_connect(hass): async def test_option_flow(hass): """Test config flow options.""" - entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) + entry = MockConfigEntry(domain=DOMAIN) entry.add_to_hass(hass) + with patch( + "homeassistant.components.screenlogic.async_setup", return_value=True + ), patch( + "homeassistant.components.screenlogic.async_setup_entry", + return_value=True, + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + result = await hass.config_entries.options.async_init(entry.entry_id) assert result["type"] == "form" @@ -213,9 +290,18 @@ async def test_option_flow(hass): async def test_option_flow_defaults(hass): """Test config flow options.""" - entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) + entry = MockConfigEntry(domain=DOMAIN) entry.add_to_hass(hass) + with patch( + "homeassistant.components.screenlogic.async_setup", return_value=True + ), patch( + "homeassistant.components.screenlogic.async_setup_entry", + return_value=True, + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + result = await hass.config_entries.options.async_init(entry.entry_id) assert result["type"] == "form" @@ -232,9 +318,18 @@ async def test_option_flow_defaults(hass): async def test_option_flow_input_floor(hass): """Test config flow options.""" - entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) + entry = MockConfigEntry(domain=DOMAIN) entry.add_to_hass(hass) + with patch( + "homeassistant.components.screenlogic.async_setup", return_value=True + ), patch( + "homeassistant.components.screenlogic.async_setup_entry", + return_value=True, + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + result = await hass.config_entries.options.async_init(entry.entry_id) assert result["type"] == "form" From 7473f25afd175c7e9cf1350aa4db7340088cd58b Mon Sep 17 00:00:00 2001 From: Ikko Ashimine Date: Sun, 21 Mar 2021 20:39:33 +0900 Subject: [PATCH 518/831] Fix typo in homekit strings.json (#48176) Co-authored-by: J. Nick Koston --- homeassistant/components/homekit/strings.json | 2 +- .../components/homekit/translations/en.json | 23 +++---------------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/homekit/strings.json b/homeassistant/components/homekit/strings.json index a9b7c1c6cc1..4e3437a1fa0 100644 --- a/homeassistant/components/homekit/strings.json +++ b/homeassistant/components/homekit/strings.json @@ -18,7 +18,7 @@ "mode": "[%key:common::config_flow::data::mode%]", "entities": "Entities" }, - "description": "Choose the entities to be included. In accessory mode, only a single entity is included. In bridge include mode, all entities in the domain will be included unless specific entities are selected. In bridge exclude mode, all entities in the domain will be included except for the excluded entities. For best performance, a seperate HomeKit accessory will beeach tv media player and camera.", + "description": "Choose the entities to be included. In accessory mode, only a single entity is included. In bridge include mode, all entities in the domain will be included unless specific entities are selected. In bridge exclude mode, all entities in the domain will be included except for the excluded entities. For best performance, a separate HomeKit accessory will be created for each tv media player and camera.", "title": "Select entities to be included" }, "cameras": { diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json index 3b0129567c4..bc516eebf15 100644 --- a/homeassistant/components/homekit/translations/en.json +++ b/homeassistant/components/homekit/translations/en.json @@ -4,29 +4,13 @@ "port_name_in_use": "An accessory or bridge with the same name or port is already configured." }, "step": { - "accessory_mode": { - "data": { - "entity_id": "Entity" - }, - "description": "Choose the entity to be included. In accessory mode, only a single entity is included.", - "title": "Select entity to be included" - }, - "bridge_mode": { - "data": { - "include_domains": "Domains to include" - }, - "description": "Choose the domains to be included. All supported entities in the domain will be included.", - "title": "Select domains to be included" - }, "pairing": { "description": "To complete pairing following the instructions in \u201cNotifications\u201d under \u201cHomeKit Pairing\u201d.", "title": "Pair HomeKit" }, "user": { "data": { - "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", - "include_domains": "Domains to include", - "mode": "Mode" + "include_domains": "Domains to include" }, "description": "Choose the domains to be included. All supported entities in the domain will be included. A separate HomeKit instance in accessory mode will be created for each tv media player and camera.", "title": "Select domains to be included" @@ -37,8 +21,7 @@ "step": { "advanced": { "data": { - "auto_start": "Autostart (disable if you are calling the homekit.start service manually)", - "safe_mode": "Safe Mode (enable only if pairing fails)" + "auto_start": "Autostart (disable if you are calling the homekit.start service manually)" }, "description": "These settings only need to be adjusted if HomeKit is not functional.", "title": "Advanced Configuration" @@ -55,7 +38,7 @@ "entities": "Entities", "mode": "Mode" }, - "description": "Choose the entities to be included. In accessory mode, only a single entity is included. In bridge include mode, all entities in the domain will be included unless specific entities are selected. In bridge exclude mode, all entities in the domain will be included except for the excluded entities. For best performance, a seperate HomeKit accessory will beeach tv media player and camera.", + "description": "Choose the entities to be included. In accessory mode, only a single entity is included. In bridge include mode, all entities in the domain will be included unless specific entities are selected. In bridge exclude mode, all entities in the domain will be included except for the excluded entities. For best performance, a separate HomeKit accessory will be created for each tv media player and camera.", "title": "Select entities to be included" }, "init": { From 9739707f62cc394154a4ec407a863d17792d6d8e Mon Sep 17 00:00:00 2001 From: xonestonex <51967236+xonestonex@users.noreply.github.com> Date: Sun, 21 Mar 2021 23:03:23 +0100 Subject: [PATCH 519/831] Preset support for MOES thermostat valves (#48178) --- homeassistant/components/zha/climate.py | 90 ++++++++++ homeassistant/components/zha/core/const.py | 3 + tests/components/zha/test_climate.py | 183 +++++++++++++++++++++ 3 files changed, 276 insertions(+) diff --git a/homeassistant/components/zha/climate.py b/homeassistant/components/zha/climate.py index e2c4faa8539..8292335ce23 100644 --- a/homeassistant/components/zha/climate.py +++ b/homeassistant/components/zha/climate.py @@ -31,6 +31,9 @@ from homeassistant.components.climate.const import ( HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, PRESET_AWAY, + PRESET_BOOST, + PRESET_COMFORT, + PRESET_ECO, PRESET_NONE, SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE, @@ -49,6 +52,8 @@ from .core.const import ( CHANNEL_THERMOSTAT, DATA_ZHA, DATA_ZHA_DISPATCHERS, + PRESET_COMPLEX, + PRESET_SCHEDULE, SIGNAL_ADD_ENTITIES, SIGNAL_ATTR_UPDATED, ) @@ -595,3 +600,88 @@ class ZenWithinThermostat(Thermostat): ) class CentralitePearl(ZenWithinThermostat): """Centralite Pearl Thermostat implementation.""" + + +@STRICT_MATCH( + channel_names=CHANNEL_THERMOSTAT, + manufacturers={ + "_TZE200_ckud7u2l", + "_TZE200_ywdxldoj", + "_TYST11_ckud7u2l", + "_TYST11_ywdxldoj", + }, +) +class MoesThermostat(Thermostat): + """Moes Thermostat implementation.""" + + def __init__(self, unique_id, zha_device, channels, **kwargs): + """Initialize ZHA Thermostat instance.""" + super().__init__(unique_id, zha_device, channels, **kwargs) + self._presets = [ + PRESET_NONE, + PRESET_AWAY, + PRESET_SCHEDULE, + PRESET_COMFORT, + PRESET_ECO, + PRESET_BOOST, + PRESET_COMPLEX, + ] + self._supported_flags |= SUPPORT_PRESET_MODE + + @property + def hvac_modes(self) -> tuple[str, ...]: + """Return only the heat mode, because the device can't be turned off.""" + return (HVAC_MODE_HEAT,) + + async def async_attribute_updated(self, record): + """Handle attribute update from device.""" + if record.attr_name == "operation_preset": + if record.value == 0: + self._preset = PRESET_AWAY + if record.value == 1: + self._preset = PRESET_SCHEDULE + if record.value == 2: + self._preset = PRESET_NONE + if record.value == 3: + self._preset = PRESET_COMFORT + if record.value == 4: + self._preset = PRESET_ECO + if record.value == 5: + self._preset = PRESET_BOOST + if record.value == 6: + self._preset = PRESET_COMPLEX + await super().async_attribute_updated(record) + + async def async_preset_handler(self, preset: str, enable: bool = False) -> bool: + """Set the preset mode.""" + mfg_code = self._zha_device.manufacturer_code + if not enable: + return await self._thrm.write_attributes( + {"operation_preset": 2}, manufacturer=mfg_code + ) + if preset == PRESET_AWAY: + return await self._thrm.write_attributes( + {"operation_preset": 0}, manufacturer=mfg_code + ) + if preset == PRESET_SCHEDULE: + return await self._thrm.write_attributes( + {"operation_preset": 1}, manufacturer=mfg_code + ) + if preset == PRESET_COMFORT: + return await self._thrm.write_attributes( + {"operation_preset": 3}, manufacturer=mfg_code + ) + if preset == PRESET_ECO: + return await self._thrm.write_attributes( + {"operation_preset": 4}, manufacturer=mfg_code + ) + if preset == PRESET_BOOST: + return await self._thrm.write_attributes( + {"operation_preset": 5}, manufacturer=mfg_code + ) + if preset == PRESET_COMPLEX: + return await self._thrm.write_attributes( + {"operation_preset": 6}, manufacturer=mfg_code + ) + + return False diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 45453ba7545..ed45400861a 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -173,6 +173,9 @@ MFG_CLUSTER_ID_START = 0xFC00 POWER_MAINS_POWERED = "Mains" POWER_BATTERY_OR_UNKNOWN = "Battery or Unknown" +PRESET_SCHEDULE = "schedule" +PRESET_COMPLEX = "complex" + class RadioType(enum.Enum): # pylint: disable=invalid-name diff --git a/tests/components/zha/test_climate.py b/tests/components/zha/test_climate.py index 05412ddb64d..ea2d6dfb7e3 100644 --- a/tests/components/zha/test_climate.py +++ b/tests/components/zha/test_climate.py @@ -33,6 +33,9 @@ from homeassistant.components.climate.const import ( HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, PRESET_AWAY, + PRESET_BOOST, + PRESET_COMFORT, + PRESET_ECO, PRESET_NONE, SERVICE_SET_FAN_MODE, SERVICE_SET_HVAC_MODE, @@ -44,6 +47,7 @@ from homeassistant.components.zha.climate import ( HVAC_MODE_2_SYSTEM, SEQ_OF_OPERATION, ) +from homeassistant.components.zha.core.const import PRESET_COMPLEX, PRESET_SCHEDULE from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_UNKNOWN from .common import async_enable_traffic, find_entity_id, send_attributes_report @@ -103,8 +107,23 @@ CLIMATE_ZEN = { "out_clusters": [zigpy.zcl.clusters.general.Ota.cluster_id], } } + +CLIMATE_MOES = { + 1: { + "device_type": zigpy.profiles.zha.DeviceType.THERMOSTAT, + "in_clusters": [ + zigpy.zcl.clusters.general.Basic.cluster_id, + zigpy.zcl.clusters.general.Identify.cluster_id, + zigpy.zcl.clusters.hvac.Thermostat.cluster_id, + zigpy.zcl.clusters.hvac.UserInterface.cluster_id, + 61148, + ], + "out_clusters": [zigpy.zcl.clusters.general.Ota.cluster_id], + } +} MANUF_SINOPE = "Sinope Technologies" MANUF_ZEN = "Zen Within" +MANUF_MOES = "_TZE200_ckud7u2l" ZCL_ATTR_PLUG = { "abs_min_heat_setpoint_limit": 800, @@ -183,6 +202,13 @@ async def device_climate_zen(device_climate_mock): return await device_climate_mock(CLIMATE_ZEN, manuf=MANUF_ZEN) +@pytest.fixture +async def device_climate_moes(device_climate_mock): + """MOES thermostat.""" + + return await device_climate_mock(CLIMATE_MOES, manuf=MANUF_MOES) + + def test_sequence_mappings(): """Test correct mapping between control sequence -> HVAC Mode -> Sysmode.""" @@ -1106,3 +1132,160 @@ async def test_set_fan_mode(hass, device_climate_fan): ) assert fan_cluster.write_attributes.await_count == 1 assert fan_cluster.write_attributes.call_args[0][0] == {"fan_mode": 5} + + +async def test_set_moes_preset(hass, device_climate_moes): + """Test setting preset for moes trv.""" + + entity_id = await find_entity_id(DOMAIN, device_climate_moes, hass) + thrm_cluster = device_climate_moes.device.endpoints[1].thermostat + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY}, + blocking=True, + ) + + assert thrm_cluster.write_attributes.await_count == 1 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "operation_preset": 0 + } + + thrm_cluster.write_attributes.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_SCHEDULE}, + blocking=True, + ) + + assert thrm_cluster.write_attributes.await_count == 2 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "operation_preset": 2 + } + assert thrm_cluster.write_attributes.call_args_list[1][0][0] == { + "operation_preset": 1 + } + + thrm_cluster.write_attributes.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_COMFORT}, + blocking=True, + ) + + assert thrm_cluster.write_attributes.await_count == 2 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "operation_preset": 2 + } + assert thrm_cluster.write_attributes.call_args_list[1][0][0] == { + "operation_preset": 3 + } + + thrm_cluster.write_attributes.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_ECO}, + blocking=True, + ) + + assert thrm_cluster.write_attributes.await_count == 2 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "operation_preset": 2 + } + assert thrm_cluster.write_attributes.call_args_list[1][0][0] == { + "operation_preset": 4 + } + + thrm_cluster.write_attributes.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_BOOST}, + blocking=True, + ) + + assert thrm_cluster.write_attributes.await_count == 2 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "operation_preset": 2 + } + assert thrm_cluster.write_attributes.call_args_list[1][0][0] == { + "operation_preset": 5 + } + + thrm_cluster.write_attributes.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_COMPLEX}, + blocking=True, + ) + + assert thrm_cluster.write_attributes.await_count == 2 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "operation_preset": 2 + } + assert thrm_cluster.write_attributes.call_args_list[1][0][0] == { + "operation_preset": 6 + } + + thrm_cluster.write_attributes.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_NONE}, + blocking=True, + ) + + assert thrm_cluster.write_attributes.await_count == 1 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "operation_preset": 2 + } + + +async def test_set_moes_operation_mode(hass, device_climate_moes): + """Test setting preset for moes trv.""" + + entity_id = await find_entity_id(DOMAIN, device_climate_moes, hass) + thrm_cluster = device_climate_moes.device.endpoints[1].thermostat + + await send_attributes_report(hass, thrm_cluster, {"operation_preset": 0}) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_AWAY + + await send_attributes_report(hass, thrm_cluster, {"operation_preset": 1}) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_SCHEDULE + + await send_attributes_report(hass, thrm_cluster, {"operation_preset": 2}) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + + await send_attributes_report(hass, thrm_cluster, {"operation_preset": 3}) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_COMFORT + + await send_attributes_report(hass, thrm_cluster, {"operation_preset": 4}) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_ECO + + await send_attributes_report(hass, thrm_cluster, {"operation_preset": 5}) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_BOOST + + await send_attributes_report(hass, thrm_cluster, {"operation_preset": 6}) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_COMPLEX From 2912db84d735527e0b04f01ed934362b7ed42e2b Mon Sep 17 00:00:00 2001 From: Nate Clark Date: Sun, 21 Mar 2021 19:16:34 -0400 Subject: [PATCH 520/831] Handle switch state updates from Konnected device (#48167) Co-authored-by: Martin Hjelmare --- homeassistant/components/konnected/__init__.py | 10 ++++++++-- homeassistant/components/konnected/handlers.py | 2 +- homeassistant/components/konnected/switch.py | 14 +++++++++++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/konnected/__init__.py b/homeassistant/components/konnected/__init__.py index 7ed4d5c1ac6..db1e20204cd 100644 --- a/homeassistant/components/konnected/__init__.py +++ b/homeassistant/components/konnected/__init__.py @@ -360,8 +360,14 @@ class KonnectedView(HomeAssistantView): try: zone_num = str(payload.get(CONF_ZONE) or PIN_TO_ZONE[payload[CONF_PIN]]) payload[CONF_ZONE] = zone_num - zone_data = device[CONF_BINARY_SENSORS].get(zone_num) or next( - (s for s in device[CONF_SENSORS] if s[CONF_ZONE] == zone_num), None + zone_data = ( + device[CONF_BINARY_SENSORS].get(zone_num) + or next( + (s for s in device[CONF_SWITCHES] if s[CONF_ZONE] == zone_num), None + ) + or next( + (s for s in device[CONF_SENSORS] if s[CONF_ZONE] == zone_num), None + ) ) except KeyError: zone_data = None diff --git a/homeassistant/components/konnected/handlers.py b/homeassistant/components/konnected/handlers.py index 923e5d63899..879d0d4cf8f 100644 --- a/homeassistant/components/konnected/handlers.py +++ b/homeassistant/components/konnected/handlers.py @@ -18,7 +18,7 @@ HANDLERS = decorator.Registry() @HANDLERS.register("state") async def async_handle_state_update(hass, context, msg): - """Handle a binary sensor state update.""" + """Handle a binary sensor or switch state update.""" _LOGGER.debug("[state handler] context: %s msg: %s", context, msg) entity_id = context.get(ATTR_ENTITY_ID) state = bool(int(msg.get(ATTR_STATE))) diff --git a/homeassistant/components/konnected/switch.py b/homeassistant/components/konnected/switch.py index b599fe55242..9c9f8193dcd 100644 --- a/homeassistant/components/konnected/switch.py +++ b/homeassistant/components/konnected/switch.py @@ -9,6 +9,8 @@ from homeassistant.const import ( CONF_SWITCHES, CONF_ZONE, ) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import ToggleEntity from .const import ( @@ -130,6 +132,16 @@ class KonnectedSwitch(ToggleEntity): state, ) + @callback + def async_set_state(self, state): + """Update the switch state.""" + self._set_state(state) + async def async_added_to_hass(self): - """Store entity_id.""" + """Store entity_id and register state change callback.""" self._data["entity_id"] = self.entity_id + self.async_on_remove( + async_dispatcher_connect( + self.hass, f"konnected.{self.entity_id}.update", self.async_set_state + ) + ) From 6fab4a2c82d94582b7dedabaa3813dbc1af781e9 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Mon, 22 Mar 2021 00:08:34 +0000 Subject: [PATCH 521/831] [ci skip] Translation update --- .../components/foscam/translations/de.json | 1 + .../components/homekit/translations/ca.json | 2 +- .../components/homekit/translations/en.json | 21 +++++++++++++++++-- .../keenetic_ndms2/translations/de.json | 1 + .../lutron_caseta/translations/de.json | 3 ++- .../mobile_app/translations/de.json | 3 ++- .../components/mysensors/translations/de.json | 4 ++++ .../opentherm_gw/translations/nl.json | 3 ++- .../squeezebox/translations/de.json | 1 + 9 files changed, 33 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/foscam/translations/de.json b/homeassistant/components/foscam/translations/de.json index e3011f61615..d87044b579a 100644 --- a/homeassistant/components/foscam/translations/de.json +++ b/homeassistant/components/foscam/translations/de.json @@ -15,6 +15,7 @@ "host": "Host", "password": "Passwort", "port": "Port", + "rtsp_port": "RTSP-Port", "username": "Benutzername" } } diff --git a/homeassistant/components/homekit/translations/ca.json b/homeassistant/components/homekit/translations/ca.json index dbd83622d8a..1ad6d63a0ff 100644 --- a/homeassistant/components/homekit/translations/ca.json +++ b/homeassistant/components/homekit/translations/ca.json @@ -55,7 +55,7 @@ "entities": "Entitats", "mode": "Mode" }, - "description": "Tria les entitats que vulguis incloure. En mode accessori, nom\u00e9s s'inclou una sola entitat. En mode enlla\u00e7 inclusiu, s'exposaran totes les entitats del domini tret de que se'n seleccionin algunes en concret. En mode enlla\u00e7 excusiu, s'inclouran totes les entitats del domini excepte les entitats excloses. Per obtenir el millor rendiment, es crea una inst\u00e0ncia HomeKit per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", + "description": "Tria les entitats a incloure. En mode accessori, nom\u00e9s s'inclou una sola entitat. En mode enlla\u00e7 inclusiu, s'exposaran totes les entitats del domini tret de que se'n seleccionin algunes en concret. En mode enlla\u00e7 excusiu, s'inclouran totes les entitats del domini excepte les entitats excloses. Per obtenir el millor rendiment, es crea una inst\u00e0ncia HomeKit per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", "title": "Selecciona les entitats a incloure" }, "init": { diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json index bc516eebf15..42bcb5bd0f2 100644 --- a/homeassistant/components/homekit/translations/en.json +++ b/homeassistant/components/homekit/translations/en.json @@ -4,13 +4,29 @@ "port_name_in_use": "An accessory or bridge with the same name or port is already configured." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Entity" + }, + "description": "Choose the entity to be included. In accessory mode, only a single entity is included.", + "title": "Select entity to be included" + }, + "bridge_mode": { + "data": { + "include_domains": "Domains to include" + }, + "description": "Choose the domains to be included. All supported entities in the domain will be included.", + "title": "Select domains to be included" + }, "pairing": { "description": "To complete pairing following the instructions in \u201cNotifications\u201d under \u201cHomeKit Pairing\u201d.", "title": "Pair HomeKit" }, "user": { "data": { - "include_domains": "Domains to include" + "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", + "include_domains": "Domains to include", + "mode": "Mode" }, "description": "Choose the domains to be included. All supported entities in the domain will be included. A separate HomeKit instance in accessory mode will be created for each tv media player and camera.", "title": "Select domains to be included" @@ -21,7 +37,8 @@ "step": { "advanced": { "data": { - "auto_start": "Autostart (disable if you are calling the homekit.start service manually)" + "auto_start": "Autostart (disable if you are calling the homekit.start service manually)", + "safe_mode": "Safe Mode (enable only if pairing fails)" }, "description": "These settings only need to be adjusted if HomeKit is not functional.", "title": "Advanced Configuration" diff --git a/homeassistant/components/keenetic_ndms2/translations/de.json b/homeassistant/components/keenetic_ndms2/translations/de.json index 5994df16940..cc9a3630ab1 100644 --- a/homeassistant/components/keenetic_ndms2/translations/de.json +++ b/homeassistant/components/keenetic_ndms2/translations/de.json @@ -22,6 +22,7 @@ "step": { "user": { "data": { + "interfaces": "Schnittstellen zum Scannen ausw\u00e4hlen", "scan_interval": "Scanintervall" } } diff --git a/homeassistant/components/lutron_caseta/translations/de.json b/homeassistant/components/lutron_caseta/translations/de.json index ce771f52acd..392648136bd 100644 --- a/homeassistant/components/lutron_caseta/translations/de.json +++ b/homeassistant/components/lutron_caseta/translations/de.json @@ -29,7 +29,8 @@ "button_3": "Dritte Taste", "button_4": "Vierte Taste", "off": "Aus", - "on": "An" + "on": "An", + "stop_all": "Alle anhalten" } } } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/de.json b/homeassistant/components/mobile_app/translations/de.json index 493ceb4dfd1..721cbc09f8d 100644 --- a/homeassistant/components/mobile_app/translations/de.json +++ b/homeassistant/components/mobile_app/translations/de.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Sende eine Benachrichtigung" } - } + }, + "title": "Mobile App" } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/de.json b/homeassistant/components/mysensors/translations/de.json index 98a1ca9cd32..d05e2bdb47b 100644 --- a/homeassistant/components/mysensors/translations/de.json +++ b/homeassistant/components/mysensors/translations/de.json @@ -6,6 +6,7 @@ "invalid_auth": "Ung\u00fcltige Authentifizierung", "invalid_device": "Ung\u00fcltiges Ger\u00e4t", "invalid_ip": "Ung\u00fcltige IP-Adresse", + "invalid_serial": "Ung\u00fcltiger Serieller Port", "invalid_version": "Ung\u00fcltige MySensors Version", "not_a_number": "Bitte eine Nummer eingeben", "unknown": "Unerwarteter Fehler" @@ -16,6 +17,7 @@ "invalid_auth": "Ung\u00fcltige Authentifizierung", "invalid_device": "Ung\u00fcltiges Ger\u00e4t", "invalid_ip": "Ung\u00fcltige IP-Adresse", + "invalid_serial": "Ung\u00fcltiger Serieller Port", "invalid_version": "Ung\u00fcltige MySensors Version", "not_a_number": "Bitte eine Nummer eingeben", "unknown": "Unerwarteter Fehler" @@ -23,6 +25,7 @@ "step": { "gw_mqtt": { "data": { + "retain": "MQTT behalten", "version": "MySensors Version" }, "description": "MQTT-Gateway einrichten" @@ -38,6 +41,7 @@ "gw_tcp": { "data": { "device": "IP-Adresse des Gateways", + "tcp_port": "Port", "version": "MySensors Version" }, "description": "Einrichtung des Ethernet-Gateways" diff --git a/homeassistant/components/opentherm_gw/translations/nl.json b/homeassistant/components/opentherm_gw/translations/nl.json index e832e790c1e..785d09fdfeb 100644 --- a/homeassistant/components/opentherm_gw/translations/nl.json +++ b/homeassistant/components/opentherm_gw/translations/nl.json @@ -21,7 +21,8 @@ "init": { "data": { "floor_temperature": "Vloertemperatuur", - "precision": "Precisie" + "precision": "Precisie", + "set_precision": "Precisie instellen" }, "description": "Opties voor de OpenTherm Gateway" } diff --git a/homeassistant/components/squeezebox/translations/de.json b/homeassistant/components/squeezebox/translations/de.json index 742210f3dc6..cdbc5c1426a 100644 --- a/homeassistant/components/squeezebox/translations/de.json +++ b/homeassistant/components/squeezebox/translations/de.json @@ -8,6 +8,7 @@ "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, + "flow_title": "Logitech Squeezebox", "step": { "edit": { "data": { From 3f2ca16ad74bddbd19d01e37e78a05948811a778 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Mar 2021 18:44:29 -1000 Subject: [PATCH 522/831] Index config entries by id (#48199) --- homeassistant/config_entries.py | 31 ++++++------ tests/common.py | 10 ++-- .../components/config/test_config_entries.py | 4 +- tests/components/hue/conftest.py | 15 +++--- tests/components/huisbaasje/test_init.py | 50 +++++++++---------- tests/components/huisbaasje/test_sensor.py | 35 ++++++------- tests/test_config_entries.py | 35 ++++++++++++- 7 files changed, 106 insertions(+), 74 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 157acb545c1..b5491d83800 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -619,7 +619,7 @@ class ConfigEntries: self.flow = ConfigEntriesFlowManager(hass, self, hass_config) self.options = OptionsFlowManager(hass) self._hass_config = hass_config - self._entries: list[ConfigEntry] = [] + self._entries: dict[str, ConfigEntry] = {} self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) EntityRegistryDisabledHandler(hass).async_setup() @@ -629,7 +629,7 @@ class ConfigEntries: seen: set[str] = set() result = [] - for entry in self._entries: + for entry in self._entries.values(): if entry.domain not in seen: seen.add(entry.domain) result.append(entry.domain) @@ -639,21 +639,22 @@ class ConfigEntries: @callback def async_get_entry(self, entry_id: str) -> ConfigEntry | None: """Return entry with matching entry_id.""" - for entry in self._entries: - if entry_id == entry.entry_id: - return entry - return None + return self._entries.get(entry_id) @callback def async_entries(self, domain: str | None = None) -> list[ConfigEntry]: """Return all entries or entries for a specific domain.""" if domain is None: - return list(self._entries) - return [entry for entry in self._entries if entry.domain == domain] + return list(self._entries.values()) + return [entry for entry in self._entries.values() if entry.domain == domain] async def async_add(self, entry: ConfigEntry) -> None: """Add and setup an entry.""" - self._entries.append(entry) + if entry.entry_id in self._entries: + raise HomeAssistantError( + f"An entry with the id {entry.entry_id} already exists." + ) + self._entries[entry.entry_id] = entry await self.async_setup(entry.entry_id) self._async_schedule_save() @@ -671,7 +672,7 @@ class ConfigEntries: await entry.async_remove(self.hass) - self._entries.remove(entry) + del self._entries[entry.entry_id] self._async_schedule_save() dev_reg, ent_reg = await asyncio.gather( @@ -707,11 +708,11 @@ class ConfigEntries: ) if config is None: - self._entries = [] + self._entries = {} return - self._entries = [ - ConfigEntry( + self._entries = { + entry["entry_id"]: ConfigEntry( version=entry["version"], domain=entry["domain"], entry_id=entry["entry_id"], @@ -730,7 +731,7 @@ class ConfigEntries: disabled_by=entry.get("disabled_by"), ) for entry in config["entries"] - ] + } async def async_setup(self, entry_id: str) -> bool: """Set up a config entry. @@ -920,7 +921,7 @@ class ConfigEntries: @callback def _data_to_save(self) -> dict[str, list[dict[str, Any]]]: """Return data to save.""" - return {"entries": [entry.as_dict() for entry in self._entries]} + return {"entries": [entry.as_dict() for entry in self._entries.values()]} async def _old_conf_migrator(old_config: dict[str, Any]) -> dict[str, Any]: diff --git a/tests/common.py b/tests/common.py index 5f8626afb4e..984b35716f0 100644 --- a/tests/common.py +++ b/tests/common.py @@ -18,7 +18,6 @@ from time import monotonic import types from typing import Any, Awaitable, Collection from unittest.mock import AsyncMock, Mock, patch -import uuid from aiohttp.test_utils import unused_port as get_test_instance_port # noqa: F401 @@ -61,6 +60,7 @@ from homeassistant.setup import async_setup_component, setup_component from homeassistant.util.async_ import run_callback_threadsafe import homeassistant.util.dt as date_util from homeassistant.util.unit_system import METRIC_SYSTEM +import homeassistant.util.uuid as uuid_util import homeassistant.util.yaml.loader as yaml_loader _LOGGER = logging.getLogger(__name__) @@ -276,7 +276,7 @@ async def async_test_home_assistant(loop, load_registries=True): hass.config.skip_pip = True hass.config_entries = config_entries.ConfigEntries(hass, {}) - hass.config_entries._entries = [] + hass.config_entries._entries = {} hass.config_entries._store._async_ensure_stop_listener = lambda: None # Load the registries @@ -737,7 +737,7 @@ class MockConfigEntry(config_entries.ConfigEntry): ): """Initialize a mock config entry.""" kwargs = { - "entry_id": entry_id or uuid.uuid4().hex, + "entry_id": entry_id or uuid_util.random_uuid_hex(), "domain": domain, "data": data or {}, "system_options": system_options, @@ -756,11 +756,11 @@ class MockConfigEntry(config_entries.ConfigEntry): def add_to_hass(self, hass): """Test helper to add entry to hass.""" - hass.config_entries._entries.append(self) + hass.config_entries._entries[self.entry_id] = self def add_to_manager(self, manager): """Test helper to add entry to entry manager.""" - manager._entries.append(self) + manager._entries[self.entry_id] = self def patch_yaml_files(files_dict, endswith=True): diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index 6bb1f1885eb..d6cc474fa8b 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -569,7 +569,7 @@ async def test_options_flow(hass, client): source="bla", connection_class=core_ce.CONN_CLASS_LOCAL_POLL, ).add_to_hass(hass) - entry = hass.config_entries._entries[0] + entry = hass.config_entries.async_entries()[0] with patch.dict(HANDLERS, {"test": TestFlow}): url = "/api/config/config_entries/options/flow" @@ -618,7 +618,7 @@ async def test_two_step_options_flow(hass, client): source="bla", connection_class=core_ce.CONN_CLASS_LOCAL_POLL, ).add_to_hass(hass) - entry = hass.config_entries._entries[0] + entry = hass.config_entries.async_entries()[0] with patch.dict(HANDLERS, {"test": TestFlow}): url = "/api/config/config_entries/options/flow" diff --git a/tests/components/hue/conftest.py b/tests/components/hue/conftest.py index db45b9fcd4d..3fc55692cc4 100644 --- a/tests/components/hue/conftest.py +++ b/tests/components/hue/conftest.py @@ -12,6 +12,7 @@ from homeassistant import config_entries from homeassistant.components import hue from homeassistant.components.hue import sensor_base as hue_sensor_base +from tests.common import MockConfigEntry from tests.components.light.conftest import mock_light_profiles # noqa: F401 @@ -111,13 +112,11 @@ async def setup_bridge_for_sensors(hass, mock_bridge, hostname=None): if hostname is None: hostname = "mock-host" hass.config.components.add(hue.DOMAIN) - config_entry = config_entries.ConfigEntry( - 1, - hue.DOMAIN, - "Mock Title", - {"host": hostname}, - "test", - config_entries.CONN_CLASS_LOCAL_POLL, + config_entry = MockConfigEntry( + domain=hue.DOMAIN, + title="Mock Title", + data={"host": hostname}, + connection_class=config_entries.CONN_CLASS_LOCAL_POLL, system_options={}, ) mock_bridge.config_entry = config_entry @@ -125,7 +124,7 @@ async def setup_bridge_for_sensors(hass, mock_bridge, hostname=None): await hass.config_entries.async_forward_entry_setup(config_entry, "binary_sensor") await hass.config_entries.async_forward_entry_setup(config_entry, "sensor") # simulate a full setup by manually adding the bridge config entry - hass.config_entries._entries.append(config_entry) + config_entry.add_to_hass(hass) # and make sure it completes before going further await hass.async_block_till_done() diff --git a/tests/components/huisbaasje/test_init.py b/tests/components/huisbaasje/test_init.py index 3de6af83e46..2d68cdf8a11 100644 --- a/tests/components/huisbaasje/test_init.py +++ b/tests/components/huisbaasje/test_init.py @@ -9,12 +9,12 @@ from homeassistant.config_entries import ( ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED, ENTRY_STATE_SETUP_ERROR, - ConfigEntry, ) from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from tests.common import MockConfigEntry from tests.components.huisbaasje.test_data import MOCK_CURRENT_MEASUREMENTS @@ -36,20 +36,20 @@ async def test_setup_entry(hass: HomeAssistant): return_value=MOCK_CURRENT_MEASUREMENTS, ) as mock_current_measurements: hass.config.components.add(huisbaasje.DOMAIN) - config_entry = ConfigEntry( - 1, - huisbaasje.DOMAIN, - "userId", - { + config_entry = MockConfigEntry( + version=1, + domain=huisbaasje.DOMAIN, + title="userId", + data={ CONF_ID: "userId", CONF_USERNAME: "username", CONF_PASSWORD: "password", }, - "test", - CONN_CLASS_CLOUD_POLL, + source="test", + connection_class=CONN_CLASS_CLOUD_POLL, system_options={}, ) - hass.config_entries._entries.append(config_entry) + config_entry.add_to_hass(hass) assert config_entry.state == ENTRY_STATE_NOT_LOADED assert await hass.config_entries.async_setup(config_entry.entry_id) @@ -77,20 +77,20 @@ async def test_setup_entry_error(hass: HomeAssistant): "huisbaasje.Huisbaasje.authenticate", side_effect=HuisbaasjeException ) as mock_authenticate: hass.config.components.add(huisbaasje.DOMAIN) - config_entry = ConfigEntry( - 1, - huisbaasje.DOMAIN, - "userId", - { + config_entry = MockConfigEntry( + version=1, + domain=huisbaasje.DOMAIN, + title="userId", + data={ CONF_ID: "userId", CONF_USERNAME: "username", CONF_PASSWORD: "password", }, - "test", - CONN_CLASS_CLOUD_POLL, + source="test", + connection_class=CONN_CLASS_CLOUD_POLL, system_options={}, ) - hass.config_entries._entries.append(config_entry) + config_entry.add_to_hass(hass) assert config_entry.state == ENTRY_STATE_NOT_LOADED await hass.config_entries.async_setup(config_entry.entry_id) @@ -119,20 +119,20 @@ async def test_unload_entry(hass: HomeAssistant): return_value=MOCK_CURRENT_MEASUREMENTS, ) as mock_current_measurements: hass.config.components.add(huisbaasje.DOMAIN) - config_entry = ConfigEntry( - 1, - huisbaasje.DOMAIN, - "userId", - { + config_entry = MockConfigEntry( + version=1, + domain=huisbaasje.DOMAIN, + title="userId", + data={ CONF_ID: "userId", CONF_USERNAME: "username", CONF_PASSWORD: "password", }, - "test", - CONN_CLASS_CLOUD_POLL, + source="test", + connection_class=CONN_CLASS_CLOUD_POLL, system_options={}, ) - hass.config_entries._entries.append(config_entry) + config_entry.add_to_hass(hass) # Load config entry assert await hass.config_entries.async_setup(config_entry.entry_id) diff --git a/tests/components/huisbaasje/test_sensor.py b/tests/components/huisbaasje/test_sensor.py index d1ffe565c84..cfb17cd5f2d 100644 --- a/tests/components/huisbaasje/test_sensor.py +++ b/tests/components/huisbaasje/test_sensor.py @@ -2,10 +2,11 @@ from unittest.mock import patch from homeassistant.components import huisbaasje -from homeassistant.config_entries import CONN_CLASS_CLOUD_POLL, ConfigEntry +from homeassistant.config_entries import CONN_CLASS_CLOUD_POLL from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant +from tests.common import MockConfigEntry from tests.components.huisbaasje.test_data import ( MOCK_CURRENT_MEASUREMENTS, MOCK_LIMITED_CURRENT_MEASUREMENTS, @@ -24,20 +25,20 @@ async def test_setup_entry(hass: HomeAssistant): ) as mock_current_measurements: hass.config.components.add(huisbaasje.DOMAIN) - config_entry = ConfigEntry( - 1, - huisbaasje.DOMAIN, - "userId", - { + config_entry = MockConfigEntry( + version=1, + domain=huisbaasje.DOMAIN, + title="userId", + data={ CONF_ID: "userId", CONF_USERNAME: "username", CONF_PASSWORD: "password", }, - "test", - CONN_CLASS_CLOUD_POLL, + source="test", + connection_class=CONN_CLASS_CLOUD_POLL, system_options={}, ) - hass.config_entries._entries.append(config_entry) + config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -81,20 +82,20 @@ async def test_setup_entry_absent_measurement(hass: HomeAssistant): ) as mock_current_measurements: hass.config.components.add(huisbaasje.DOMAIN) - config_entry = ConfigEntry( - 1, - huisbaasje.DOMAIN, - "userId", - { + config_entry = MockConfigEntry( + version=1, + domain=huisbaasje.DOMAIN, + title="userId", + data={ CONF_ID: "userId", CONF_USERNAME: "username", CONF_PASSWORD: "password", }, - "test", - CONN_CLASS_CLOUD_POLL, + source="test", + connection_class=CONN_CLASS_CLOUD_POLL, system_options={}, ) - hass.config_entries._entries.append(config_entry) + config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 9abb68e97c9..09ab60e4300 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -7,7 +7,7 @@ import pytest from homeassistant import config_entries, data_entry_flow, loader from homeassistant.core import callback -from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util import dt @@ -44,7 +44,7 @@ def mock_handlers(): def manager(hass): """Fixture of a loaded config manager.""" manager = config_entries.ConfigEntries(hass, {}) - manager._entries = [] + manager._entries = {} manager._store._async_ensure_stop_listener = lambda: None hass.config_entries = manager return manager @@ -1383,6 +1383,37 @@ async def test_unique_id_existing_entry(hass, manager): assert len(async_remove_entry.mock_calls) == 1 +async def test_entry_id_existing_entry(hass, manager): + """Test that we throw when the entry id collides.""" + collide_entry_id = "collide" + hass.config.components.add("comp") + MockConfigEntry( + entry_id=collide_entry_id, + domain="comp", + state=config_entries.ENTRY_STATE_LOADED, + unique_id="mock-unique-id", + ).add_to_hass(hass) + + class TestFlow(config_entries.ConfigFlow): + """Test flow.""" + + VERSION = 1 + + async def async_step_user(self, user_input=None): + """Test user step.""" + return self.async_create_entry(title="mock-title", data={"via": "flow"}) + + with pytest.raises(HomeAssistantError), patch.dict( + config_entries.HANDLERS, {"comp": TestFlow} + ), patch( + "homeassistant.config_entries.uuid_util.random_uuid_hex", + return_value=collide_entry_id, + ): + await manager.flow.async_init( + "comp", context={"source": config_entries.SOURCE_USER} + ) + + async def test_unique_id_update_existing_entry_without_reload(hass, manager): """Test that we update an entry if there already is an entry with unique ID.""" hass.config.components.add("comp") From fd310e1f4133d567513a09fe22b144c74a46faed Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Mar 2021 18:55:20 -1000 Subject: [PATCH 523/831] Update homekit to improve representation of activity based remotes (#47261) --- homeassistant/components/homekit/__init__.py | 1 + .../components/homekit/accessories.py | 6 +- .../components/homekit/config_flow.py | 6 +- .../components/homekit/type_media_players.py | 128 ++--------- .../components/homekit/type_remotes.py | 214 ++++++++++++++++++ homeassistant/components/homekit/util.py | 3 + script/hassfest/dependencies.py | 1 + tests/components/homekit/test_type_remote.py | 148 ++++++++++++ 8 files changed, 398 insertions(+), 109 deletions(-) create mode 100644 homeassistant/components/homekit/type_remotes.py create mode 100644 tests/components/homekit/test_type_remote.py diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 4fea31e7238..0e4bcc28aab 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -50,6 +50,7 @@ from . import ( # noqa: F401 type_lights, type_locks, type_media_players, + type_remotes, type_security_systems, type_sensors, type_switches, diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index b6ff11aa26d..307dbf0e806 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -12,6 +12,7 @@ from homeassistant.components.cover import ( DEVICE_CLASS_WINDOW, ) from homeassistant.components.media_player import DEVICE_CLASS_TV +from homeassistant.components.remote import SUPPORT_ACTIVITY from homeassistant.const import ( ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, @@ -103,6 +104,7 @@ def get_accessory(hass, driver, state, aid, config): a_type = None name = config.get(CONF_NAME, state.name) + features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) if state.domain == "alarm_control_panel": a_type = "SecuritySystem" @@ -115,7 +117,6 @@ def get_accessory(hass, driver, state, aid, config): elif state.domain == "cover": device_class = state.attributes.get(ATTR_DEVICE_CLASS) - features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) if device_class in (DEVICE_CLASS_GARAGE, DEVICE_CLASS_GATE) and features & ( cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE @@ -179,6 +180,9 @@ def get_accessory(hass, driver, state, aid, config): elif state.domain == "vacuum": a_type = "Vacuum" + elif state.domain == "remote" and features & SUPPORT_ACTIVITY: + a_type = "ActivityRemote" + elif state.domain in ("automation", "input_boolean", "remote", "scene", "script"): a_type = "Switch" diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index 758e25b97bc..1fc99bb8585 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -8,6 +8,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER_DOMAIN +from homeassistant.components.remote import DOMAIN as REMOTE_DOMAIN from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_FRIENDLY_NAME, @@ -53,7 +54,7 @@ MODE_EXCLUDE = "exclude" INCLUDE_EXCLUDE_MODES = [MODE_EXCLUDE, MODE_INCLUDE] -DOMAINS_NEED_ACCESSORY_MODE = [CAMERA_DOMAIN, MEDIA_PLAYER_DOMAIN] +DOMAINS_NEED_ACCESSORY_MODE = [CAMERA_DOMAIN, MEDIA_PLAYER_DOMAIN, REMOTE_DOMAIN] NEVER_BRIDGED_DOMAINS = [CAMERA_DOMAIN] CAMERA_ENTITY_PREFIX = f"{CAMERA_DOMAIN}." @@ -74,7 +75,7 @@ SUPPORTED_DOMAINS = [ "lock", MEDIA_PLAYER_DOMAIN, "person", - "remote", + REMOTE_DOMAIN, "scene", "script", "sensor", @@ -93,6 +94,7 @@ DEFAULT_DOMAINS = [ "light", "lock", MEDIA_PLAYER_DOMAIN, + REMOTE_DOMAIN, "switch", "vacuum", "water_heater", diff --git a/homeassistant/components/homekit/type_media_players.py b/homeassistant/components/homekit/type_media_players.py index b54b62372f9..5cd27109bd8 100644 --- a/homeassistant/components/homekit/type_media_players.py +++ b/homeassistant/components/homekit/type_media_players.py @@ -1,7 +1,7 @@ """Class to hold all media player accessories.""" import logging -from pyhap.const import CATEGORY_SWITCH, CATEGORY_TELEVISION +from pyhap.const import CATEGORY_SWITCH from homeassistant.components.media_player import ( ATTR_INPUT_SOURCE, @@ -42,17 +42,9 @@ from .accessories import TYPES, HomeAccessory from .const import ( ATTR_KEY_NAME, CHAR_ACTIVE, - CHAR_ACTIVE_IDENTIFIER, - CHAR_CONFIGURED_NAME, - CHAR_CURRENT_VISIBILITY_STATE, - CHAR_IDENTIFIER, - CHAR_INPUT_SOURCE_TYPE, - CHAR_IS_CONFIGURED, CHAR_MUTE, CHAR_NAME, CHAR_ON, - CHAR_REMOTE_KEY, - CHAR_SLEEP_DISCOVER_MODE, CHAR_VOLUME, CHAR_VOLUME_CONTROL_TYPE, CHAR_VOLUME_SELECTOR, @@ -62,43 +54,15 @@ from .const import ( FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, - KEY_ARROW_DOWN, - KEY_ARROW_LEFT, - KEY_ARROW_RIGHT, - KEY_ARROW_UP, - KEY_BACK, - KEY_EXIT, - KEY_FAST_FORWARD, - KEY_INFORMATION, - KEY_NEXT_TRACK, KEY_PLAY_PAUSE, - KEY_PREVIOUS_TRACK, - KEY_REWIND, - KEY_SELECT, - SERV_INPUT_SOURCE, SERV_SWITCH, - SERV_TELEVISION, SERV_TELEVISION_SPEAKER, ) +from .type_remotes import REMOTE_KEYS, RemoteInputSelectAccessory from .util import get_media_player_features _LOGGER = logging.getLogger(__name__) -MEDIA_PLAYER_KEYS = { - 0: KEY_REWIND, - 1: KEY_FAST_FORWARD, - 2: KEY_NEXT_TRACK, - 3: KEY_PREVIOUS_TRACK, - 4: KEY_ARROW_UP, - 5: KEY_ARROW_DOWN, - 6: KEY_ARROW_LEFT, - 7: KEY_ARROW_RIGHT, - 8: KEY_SELECT, - 9: KEY_BACK, - 10: KEY_EXIT, - 11: KEY_PLAY_PAUSE, - 15: KEY_INFORMATION, -} # Names may not contain special characters # or emjoi (/ is a special character for Apple) @@ -250,22 +214,22 @@ class MediaPlayer(HomeAccessory): @TYPES.register("TelevisionMediaPlayer") -class TelevisionMediaPlayer(HomeAccessory): +class TelevisionMediaPlayer(RemoteInputSelectAccessory): """Generate a Television Media Player accessory.""" def __init__(self, *args): - """Initialize a Switch accessory object.""" - super().__init__(*args, category=CATEGORY_TELEVISION) + """Initialize a Television Media Player accessory object.""" + super().__init__( + SUPPORT_SELECT_SOURCE, + ATTR_INPUT_SOURCE, + ATTR_INPUT_SOURCE_LIST, + *args, + ) state = self.hass.states.get(self.entity_id) - - self.support_select_source = False - - self.sources = [] - - self.chars_tv = [CHAR_REMOTE_KEY] - self.chars_speaker = [] features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) + self.chars_speaker = [] + self._supports_play_pause = features & (SUPPORT_PLAY | SUPPORT_PAUSE) if features & SUPPORT_VOLUME_MUTE or features & SUPPORT_VOLUME_STEP: self.chars_speaker.extend( @@ -274,27 +238,11 @@ class TelevisionMediaPlayer(HomeAccessory): if features & SUPPORT_VOLUME_SET: self.chars_speaker.append(CHAR_VOLUME) - source_list = state.attributes.get(ATTR_INPUT_SOURCE_LIST, []) - if source_list and features & SUPPORT_SELECT_SOURCE: - self.support_select_source = True - - serv_tv = self.add_preload_service(SERV_TELEVISION, self.chars_tv) - self.set_primary_service(serv_tv) - serv_tv.configure_char(CHAR_CONFIGURED_NAME, value=self.display_name) - serv_tv.configure_char(CHAR_SLEEP_DISCOVER_MODE, value=True) - self.char_active = serv_tv.configure_char( - CHAR_ACTIVE, setter_callback=self.set_on_off - ) - - self.char_remote_key = serv_tv.configure_char( - CHAR_REMOTE_KEY, setter_callback=self.set_remote_key - ) - if CHAR_VOLUME_SELECTOR in self.chars_speaker: serv_speaker = self.add_preload_service( SERV_TELEVISION_SPEAKER, self.chars_speaker ) - serv_tv.add_linked_service(serv_speaker) + self.serv_tv.add_linked_service(serv_speaker) name = f"{self.display_name} Volume" serv_speaker.configure_char(CHAR_NAME, value=name) @@ -318,25 +266,6 @@ class TelevisionMediaPlayer(HomeAccessory): CHAR_VOLUME, setter_callback=self.set_volume ) - if self.support_select_source: - self.sources = source_list - self.char_input_source = serv_tv.configure_char( - CHAR_ACTIVE_IDENTIFIER, setter_callback=self.set_input_source - ) - for index, source in enumerate(self.sources): - serv_input = self.add_preload_service( - SERV_INPUT_SOURCE, [CHAR_IDENTIFIER, CHAR_NAME] - ) - serv_tv.add_linked_service(serv_input) - serv_input.configure_char(CHAR_CONFIGURED_NAME, value=source) - serv_input.configure_char(CHAR_NAME, value=source) - serv_input.configure_char(CHAR_IDENTIFIER, value=index) - serv_input.configure_char(CHAR_IS_CONFIGURED, value=True) - input_type = 3 if "hdmi" in source.lower() else 0 - serv_input.configure_char(CHAR_INPUT_SOURCE_TYPE, value=input_type) - serv_input.configure_char(CHAR_CURRENT_VISIBILITY_STATE, value=False) - _LOGGER.debug("%s: Added source %s", self.entity_id, source) - self.async_update_state(state) def set_on_off(self, value): @@ -377,7 +306,7 @@ class TelevisionMediaPlayer(HomeAccessory): def set_remote_key(self, value): """Send remote key value if call came from HomeKit.""" _LOGGER.debug("%s: Set remote key to %s", self.entity_id, value) - key_name = MEDIA_PLAYER_KEYS.get(value) + key_name = REMOTE_KEYS.get(value) if key_name is None: _LOGGER.warning("%s: Unhandled key press for %s", self.entity_id, value) return @@ -393,12 +322,13 @@ class TelevisionMediaPlayer(HomeAccessory): service = SERVICE_MEDIA_PLAY_PAUSE params = {ATTR_ENTITY_ID: self.entity_id} self.async_call_service(DOMAIN, service, params) - else: - # Unhandled keys can be handled by listening to the event bus - self.hass.bus.fire( - EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, - {ATTR_KEY_NAME: key_name, ATTR_ENTITY_ID: self.entity_id}, - ) + return + + # Unhandled keys can be handled by listening to the event bus + self.hass.bus.async_fire( + EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, + {ATTR_KEY_NAME: key_name, ATTR_ENTITY_ID: self.entity_id}, + ) @callback def async_update_state(self, new_state): @@ -424,18 +354,4 @@ class TelevisionMediaPlayer(HomeAccessory): if self.char_mute.value != current_mute_state: self.char_mute.set_value(current_mute_state) - # Set active input - if self.support_select_source and self.sources: - source_name = new_state.attributes.get(ATTR_INPUT_SOURCE) - _LOGGER.debug("%s: Set current input to %s", self.entity_id, source_name) - if source_name in self.sources: - index = self.sources.index(source_name) - if self.char_input_source.value != index: - self.char_input_source.set_value(index) - elif hk_state: - _LOGGER.warning( - "%s: Sources out of sync. Restart Home Assistant", - self.entity_id, - ) - if self.char_input_source.value != 0: - self.char_input_source.set_value(0) + self._async_update_input_state(hk_state, new_state) diff --git a/homeassistant/components/homekit/type_remotes.py b/homeassistant/components/homekit/type_remotes.py new file mode 100644 index 00000000000..e4f18a7c16f --- /dev/null +++ b/homeassistant/components/homekit/type_remotes.py @@ -0,0 +1,214 @@ +"""Class to hold remote accessories.""" +from abc import abstractmethod +import logging + +from pyhap.const import CATEGORY_TELEVISION + +from homeassistant.components.remote import ( + ATTR_ACTIVITY, + ATTR_ACTIVITY_LIST, + ATTR_CURRENT_ACTIVITY, + DOMAIN as REMOTE_DOMAIN, + SUPPORT_ACTIVITY, +) +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_ON, +) +from homeassistant.core import callback + +from .accessories import TYPES, HomeAccessory +from .const import ( + ATTR_KEY_NAME, + CHAR_ACTIVE, + CHAR_ACTIVE_IDENTIFIER, + CHAR_CONFIGURED_NAME, + CHAR_CURRENT_VISIBILITY_STATE, + CHAR_IDENTIFIER, + CHAR_INPUT_SOURCE_TYPE, + CHAR_IS_CONFIGURED, + CHAR_NAME, + CHAR_REMOTE_KEY, + CHAR_SLEEP_DISCOVER_MODE, + EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, + KEY_ARROW_DOWN, + KEY_ARROW_LEFT, + KEY_ARROW_RIGHT, + KEY_ARROW_UP, + KEY_BACK, + KEY_EXIT, + KEY_FAST_FORWARD, + KEY_INFORMATION, + KEY_NEXT_TRACK, + KEY_PLAY_PAUSE, + KEY_PREVIOUS_TRACK, + KEY_REWIND, + KEY_SELECT, + SERV_INPUT_SOURCE, + SERV_TELEVISION, +) + +_LOGGER = logging.getLogger(__name__) + +REMOTE_KEYS = { + 0: KEY_REWIND, + 1: KEY_FAST_FORWARD, + 2: KEY_NEXT_TRACK, + 3: KEY_PREVIOUS_TRACK, + 4: KEY_ARROW_UP, + 5: KEY_ARROW_DOWN, + 6: KEY_ARROW_LEFT, + 7: KEY_ARROW_RIGHT, + 8: KEY_SELECT, + 9: KEY_BACK, + 10: KEY_EXIT, + 11: KEY_PLAY_PAUSE, + 15: KEY_INFORMATION, +} + + +class RemoteInputSelectAccessory(HomeAccessory): + """Generate a InputSelect accessory.""" + + def __init__( + self, + required_feature, + source_key, + source_list_key, + *args, + **kwargs, + ): + """Initialize a InputSelect accessory object.""" + super().__init__(*args, category=CATEGORY_TELEVISION, **kwargs) + state = self.hass.states.get(self.entity_id) + features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) + + self.source_key = source_key + self.sources = [] + self.support_select_source = False + if features & required_feature: + self.sources = state.attributes.get(source_list_key, []) + if self.sources: + self.support_select_source = True + + self.chars_tv = [CHAR_REMOTE_KEY] + serv_tv = self.serv_tv = self.add_preload_service( + SERV_TELEVISION, self.chars_tv + ) + self.char_remote_key = self.serv_tv.configure_char( + CHAR_REMOTE_KEY, setter_callback=self.set_remote_key + ) + self.set_primary_service(serv_tv) + serv_tv.configure_char(CHAR_CONFIGURED_NAME, value=self.display_name) + serv_tv.configure_char(CHAR_SLEEP_DISCOVER_MODE, value=True) + self.char_active = serv_tv.configure_char( + CHAR_ACTIVE, setter_callback=self.set_on_off + ) + + if not self.support_select_source: + return + + self.char_input_source = serv_tv.configure_char( + CHAR_ACTIVE_IDENTIFIER, setter_callback=self.set_input_source + ) + for index, source in enumerate(self.sources): + serv_input = self.add_preload_service( + SERV_INPUT_SOURCE, [CHAR_IDENTIFIER, CHAR_NAME] + ) + serv_tv.add_linked_service(serv_input) + serv_input.configure_char(CHAR_CONFIGURED_NAME, value=source) + serv_input.configure_char(CHAR_NAME, value=source) + serv_input.configure_char(CHAR_IDENTIFIER, value=index) + serv_input.configure_char(CHAR_IS_CONFIGURED, value=True) + input_type = 3 if "hdmi" in source.lower() else 0 + serv_input.configure_char(CHAR_INPUT_SOURCE_TYPE, value=input_type) + serv_input.configure_char(CHAR_CURRENT_VISIBILITY_STATE, value=False) + _LOGGER.debug("%s: Added source %s", self.entity_id, source) + + @abstractmethod + def set_on_off(self, value): + """Move switch state to value if call came from HomeKit.""" + + @abstractmethod + def set_input_source(self, value): + """Send input set value if call came from HomeKit.""" + + @abstractmethod + def set_remote_key(self, value): + """Send remote key value if call came from HomeKit.""" + + @callback + def _async_update_input_state(self, hk_state, new_state): + """Update input state after state changed.""" + # Set active input + if not self.support_select_source or not self.sources: + return + source_name = new_state.attributes.get(self.source_key) + _LOGGER.debug("%s: Set current input to %s", self.entity_id, source_name) + if source_name in self.sources: + index = self.sources.index(source_name) + if self.char_input_source.value != index: + self.char_input_source.set_value(index) + elif hk_state: + _LOGGER.warning( + "%s: Sources out of sync. Restart Home Assistant", + self.entity_id, + ) + if self.char_input_source.value != 0: + self.char_input_source.set_value(0) + + +@TYPES.register("ActivityRemote") +class ActivityRemote(RemoteInputSelectAccessory): + """Generate a Activity Remote accessory.""" + + def __init__(self, *args): + """Initialize a Activity Remote accessory object.""" + super().__init__( + SUPPORT_ACTIVITY, + ATTR_CURRENT_ACTIVITY, + ATTR_ACTIVITY_LIST, + *args, + ) + self.async_update_state(self.hass.states.get(self.entity_id)) + + def set_on_off(self, value): + """Move switch state to value if call came from HomeKit.""" + _LOGGER.debug('%s: Set switch state for "on_off" to %s', self.entity_id, value) + service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF + params = {ATTR_ENTITY_ID: self.entity_id} + self.async_call_service(REMOTE_DOMAIN, service, params) + + def set_input_source(self, value): + """Send input set value if call came from HomeKit.""" + _LOGGER.debug("%s: Set current input to %s", self.entity_id, value) + source = self.sources[value] + params = {ATTR_ENTITY_ID: self.entity_id, ATTR_ACTIVITY: source} + self.async_call_service(REMOTE_DOMAIN, SERVICE_TURN_ON, params) + + def set_remote_key(self, value): + """Send remote key value if call came from HomeKit.""" + _LOGGER.debug("%s: Set remote key to %s", self.entity_id, value) + key_name = REMOTE_KEYS.get(value) + if key_name is None: + _LOGGER.warning("%s: Unhandled key press for %s", self.entity_id, value) + return + self.hass.bus.async_fire( + EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, + {ATTR_KEY_NAME: key_name, ATTR_ENTITY_ID: self.entity_id}, + ) + + @callback + def async_update_state(self, new_state): + """Update Television remote state after state changed.""" + current_state = new_state.state + # Power state remote + hk_state = 1 if current_state == STATE_ON else 0 + _LOGGER.debug("%s: Set current active state to %s", self.entity_id, hk_state) + if self.char_active.value != hk_state: + self.char_active.set_value(hk_state) + + self._async_update_input_state(hk_state, new_state) diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index cab1a28892a..d0a83b1a12a 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -16,6 +16,7 @@ from homeassistant.components.media_player import ( DEVICE_CLASS_TV, DOMAIN as MEDIA_PLAYER_DOMAIN, ) +from homeassistant.components.remote import DOMAIN as REMOTE_DOMAIN, SUPPORT_ACTIVITY from homeassistant.const import ( ATTR_CODE, ATTR_DEVICE_CLASS, @@ -503,4 +504,6 @@ def state_needs_accessory_mode(state): return ( state.domain == MEDIA_PLAYER_DOMAIN and state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TV + or state.domain == REMOTE_DOMAIN + and state.attributes.get(ATTR_SUPPORTED_FEATURES) & SUPPORT_ACTIVITY ) diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py index ed780ed067b..5f885c59a1d 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -107,6 +107,7 @@ ALLOWED_USED_COMPONENTS = { "onboarding", "persistent_notification", "person", + "remote", "script", "shopping_list", "sun", diff --git a/tests/components/homekit/test_type_remote.py b/tests/components/homekit/test_type_remote.py new file mode 100644 index 00000000000..e69ebfb29fb --- /dev/null +++ b/tests/components/homekit/test_type_remote.py @@ -0,0 +1,148 @@ +"""Test different accessory types: Remotes.""" + +from homeassistant.components.homekit.const import ( + ATTR_KEY_NAME, + ATTR_VALUE, + EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, + KEY_ARROW_RIGHT, +) +from homeassistant.components.homekit.type_remotes import ActivityRemote +from homeassistant.components.remote import ( + ATTR_ACTIVITY, + ATTR_ACTIVITY_LIST, + ATTR_CURRENT_ACTIVITY, + DOMAIN, + SUPPORT_ACTIVITY, +) +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + STATE_OFF, + STATE_ON, + STATE_STANDBY, +) + +from tests.common import async_mock_service + + +async def test_activity_remote(hass, hk_driver, events, caplog): + """Test if remote accessory and HA are updated accordingly.""" + entity_id = "remote.harmony" + hass.states.async_set( + entity_id, + None, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_ACTIVITY, + ATTR_CURRENT_ACTIVITY: "Apple TV", + ATTR_ACTIVITY_LIST: ["TV", "Apple TV"], + }, + ) + await hass.async_block_till_done() + acc = ActivityRemote(hass, hk_driver, "ActivityRemote", entity_id, 2, None) + await acc.run() + await hass.async_block_till_done() + + assert acc.aid == 2 + assert acc.category == 31 # Television + + assert acc.char_active.value == 0 + assert acc.char_remote_key.value == 0 + assert acc.char_input_source.value == 1 + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_ACTIVITY, + ATTR_CURRENT_ACTIVITY: "Apple TV", + ATTR_ACTIVITY_LIST: ["TV", "Apple TV"], + }, + ) + await hass.async_block_till_done() + assert acc.char_active.value == 1 + + hass.states.async_set(entity_id, STATE_OFF) + await hass.async_block_till_done() + assert acc.char_active.value == 0 + + hass.states.async_set(entity_id, STATE_ON) + await hass.async_block_till_done() + assert acc.char_active.value == 1 + + hass.states.async_set(entity_id, STATE_STANDBY) + await hass.async_block_till_done() + assert acc.char_active.value == 0 + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_ACTIVITY, + ATTR_CURRENT_ACTIVITY: "TV", + ATTR_ACTIVITY_LIST: ["TV", "Apple TV"], + }, + ) + await hass.async_block_till_done() + assert acc.char_input_source.value == 0 + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_ACTIVITY, + ATTR_CURRENT_ACTIVITY: "Apple TV", + ATTR_ACTIVITY_LIST: ["TV", "Apple TV"], + }, + ) + await hass.async_block_till_done() + assert acc.char_input_source.value == 1 + + # Set from HomeKit + call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") + call_turn_off = async_mock_service(hass, DOMAIN, "turn_off") + + acc.char_active.client_update_value(1) + await hass.async_block_till_done() + assert call_turn_on + assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id + assert len(events) == 1 + assert events[-1].data[ATTR_VALUE] is None + + acc.char_active.client_update_value(0) + await hass.async_block_till_done() + assert call_turn_off + assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id + assert len(events) == 2 + assert events[-1].data[ATTR_VALUE] is None + + acc.char_input_source.client_update_value(1) + await hass.async_block_till_done() + assert call_turn_on + assert call_turn_on[1].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[1].data[ATTR_ACTIVITY] == "Apple TV" + assert len(events) == 3 + assert events[-1].data[ATTR_VALUE] is None + + acc.char_input_source.client_update_value(0) + await hass.async_block_till_done() + assert call_turn_on + assert call_turn_on[2].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[2].data[ATTR_ACTIVITY] == "TV" + assert len(events) == 4 + assert events[-1].data[ATTR_VALUE] is None + + events = [] + + def listener(event): + events.append(event) + + hass.bus.async_listen(EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, listener) + + acc.char_remote_key.client_update_value(20) + await hass.async_block_till_done() + + acc.char_remote_key.client_update_value(7) + await hass.async_block_till_done() + + assert len(events) == 1 + assert events[0].data[ATTR_KEY_NAME] == KEY_ARROW_RIGHT From f35641ae8e05e2a4797fbe62eb7ed68919bbbe0f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Mar 2021 18:57:49 -1000 Subject: [PATCH 524/831] Make sure include_ignore=False always works with _async_current_entries (#48196) If the step was anything other than SOURCE_USER, include_ignore=False would not be honored --- homeassistant/config_entries.py | 10 ++- tests/test_config_entries.py | 109 ++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index b5491d83800..6bdb850e643 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1021,14 +1021,20 @@ class ConfigFlow(data_entry_flow.FlowHandler): self.context["confirm_only"] = True @callback - def _async_current_entries(self, include_ignore: bool = False) -> list[ConfigEntry]: + def _async_current_entries( + self, include_ignore: bool | None = None + ) -> list[ConfigEntry]: """Return current entries. If the flow is user initiated, filter out ignored entries unless include_ignore is True. """ config_entries = self.hass.config_entries.async_entries(self.handler) - if include_ignore or self.source != SOURCE_USER: + if ( + include_ignore is True + or include_ignore is None + and self.source != SOURCE_USER + ): return config_entries return [entry for entry in config_entries if entry.source != SOURCE_IGNORE] diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 09ab60e4300..5d774f7877d 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1760,6 +1760,115 @@ async def test_manual_add_overrides_ignored_entry_singleton(hass, manager): assert p_entry.data == {"token": "supersecret"} +async def test__async_current_entries_does_not_skip_ignore_non_user(hass, manager): + """Test that _async_current_entries does not skip ignore by default for non user step.""" + hass.config.components.add("comp") + entry = MockConfigEntry( + domain="comp", + state=config_entries.ENTRY_STATE_LOADED, + source=config_entries.SOURCE_IGNORE, + ) + entry.add_to_hass(hass) + + mock_setup_entry = AsyncMock(return_value=True) + + mock_integration(hass, MockModule("comp", async_setup_entry=mock_setup_entry)) + mock_entity_platform(hass, "config_flow.comp", None) + + class TestFlow(config_entries.ConfigFlow): + """Test flow.""" + + VERSION = 1 + + async def async_step_import(self, user_input=None): + """Test not the user step.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + return self.async_create_entry(title="title", data={"token": "supersecret"}) + + with patch.dict(config_entries.HANDLERS, {"comp": TestFlow, "beer": 5}): + await manager.flow.async_init( + "comp", context={"source": config_entries.SOURCE_IMPORT} + ) + await hass.async_block_till_done() + + assert len(mock_setup_entry.mock_calls) == 0 + + +async def test__async_current_entries_explict_skip_ignore(hass, manager): + """Test that _async_current_entries can explicitly include ignore.""" + hass.config.components.add("comp") + entry = MockConfigEntry( + domain="comp", + state=config_entries.ENTRY_STATE_LOADED, + source=config_entries.SOURCE_IGNORE, + ) + entry.add_to_hass(hass) + + mock_setup_entry = AsyncMock(return_value=True) + + mock_integration(hass, MockModule("comp", async_setup_entry=mock_setup_entry)) + mock_entity_platform(hass, "config_flow.comp", None) + + class TestFlow(config_entries.ConfigFlow): + """Test flow.""" + + VERSION = 1 + + async def async_step_import(self, user_input=None): + """Test not the user step.""" + if self._async_current_entries(include_ignore=False): + return self.async_abort(reason="single_instance_allowed") + return self.async_create_entry(title="title", data={"token": "supersecret"}) + + with patch.dict(config_entries.HANDLERS, {"comp": TestFlow, "beer": 5}): + await manager.flow.async_init( + "comp", context={"source": config_entries.SOURCE_IMPORT} + ) + await hass.async_block_till_done() + + assert len(mock_setup_entry.mock_calls) == 1 + p_hass, p_entry = mock_setup_entry.mock_calls[0][1] + + assert p_hass is hass + assert p_entry.data == {"token": "supersecret"} + + +async def test__async_current_entries_explict_include_ignore(hass, manager): + """Test that _async_current_entries can explicitly include ignore.""" + hass.config.components.add("comp") + entry = MockConfigEntry( + domain="comp", + state=config_entries.ENTRY_STATE_LOADED, + source=config_entries.SOURCE_IGNORE, + ) + entry.add_to_hass(hass) + + mock_setup_entry = AsyncMock(return_value=True) + + mock_integration(hass, MockModule("comp", async_setup_entry=mock_setup_entry)) + mock_entity_platform(hass, "config_flow.comp", None) + + class TestFlow(config_entries.ConfigFlow): + """Test flow.""" + + VERSION = 1 + + async def async_step_import(self, user_input=None): + """Test not the user step.""" + if self._async_current_entries(include_ignore=True): + return self.async_abort(reason="single_instance_allowed") + return self.async_create_entry(title="title", data={"token": "supersecret"}) + + with patch.dict(config_entries.HANDLERS, {"comp": TestFlow, "beer": 5}): + await manager.flow.async_init( + "comp", context={"source": config_entries.SOURCE_IMPORT} + ) + await hass.async_block_till_done() + + assert len(mock_setup_entry.mock_calls) == 0 + + async def test_unignore_step_form(hass, manager): """Test that we can ignore flows that are in progress and have a unique ID, then rediscover them.""" async_setup_entry = AsyncMock(return_value=True) From 8557b856a4072c092e45dcfa629d70daf6910881 Mon Sep 17 00:00:00 2001 From: Emily Mills Date: Mon, 22 Mar 2021 00:08:09 -0500 Subject: [PATCH 525/831] Fix Kulersky and Zerproc config unloading (#47572) --- homeassistant/components/kulersky/__init__.py | 20 +++++++++++++------ homeassistant/components/kulersky/const.py | 3 +++ homeassistant/components/kulersky/light.py | 20 ++++++------------- homeassistant/components/zerproc/__init__.py | 12 ++++++++--- homeassistant/components/zerproc/const.py | 3 +++ homeassistant/components/zerproc/light.py | 12 +++++++---- tests/components/kulersky/test_light.py | 10 +++++++++- tests/components/zerproc/test_light.py | 9 +++++++-- 8 files changed, 59 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/kulersky/__init__.py b/homeassistant/components/kulersky/__init__.py index 666239ad22b..951e2a5353f 100644 --- a/homeassistant/components/kulersky/__init__.py +++ b/homeassistant/components/kulersky/__init__.py @@ -4,7 +4,7 @@ import asyncio from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from .const import DOMAIN +from .const import DATA_ADDRESSES, DATA_DISCOVERY_SUBSCRIPTION, DOMAIN PLATFORMS = ["light"] @@ -16,6 +16,11 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Kuler Sky from a config entry.""" + if DOMAIN not in hass.data: + hass.data[DOMAIN] = {} + if DATA_ADDRESSES not in hass.data[DOMAIN]: + hass.data[DOMAIN][DATA_ADDRESSES] = set() + for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform) @@ -26,7 +31,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" - unload_ok = all( + # Stop discovery + unregister_discovery = hass.data[DOMAIN].pop(DATA_DISCOVERY_SUBSCRIPTION, None) + if unregister_discovery: + unregister_discovery() + + hass.data.pop(DOMAIN, None) + + return all( await asyncio.gather( *[ hass.config_entries.async_forward_entry_unload(entry, platform) @@ -34,7 +46,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): ] ) ) - if unload_ok: - hass.data[DOMAIN].pop(entry.entry_id) - - return unload_ok diff --git a/homeassistant/components/kulersky/const.py b/homeassistant/components/kulersky/const.py index ae1e7a435dc..8b314d7bde9 100644 --- a/homeassistant/components/kulersky/const.py +++ b/homeassistant/components/kulersky/const.py @@ -1,2 +1,5 @@ """Constants for the Kuler Sky integration.""" DOMAIN = "kulersky" + +DATA_ADDRESSES = "addresses" +DATA_DISCOVERY_SUBSCRIPTION = "discovery_subscription" diff --git a/homeassistant/components/kulersky/light.py b/homeassistant/components/kulersky/light.py index 599f99a83e8..980d4612ce9 100644 --- a/homeassistant/components/kulersky/light.py +++ b/homeassistant/components/kulersky/light.py @@ -18,13 +18,12 @@ from homeassistant.components.light import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP -from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import HomeAssistantType import homeassistant.util.color as color_util -from .const import DOMAIN +from .const import DATA_ADDRESSES, DATA_DISCOVERY_SUBSCRIPTION, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -39,10 +38,6 @@ async def async_setup_entry( async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Kuler sky light devices.""" - if DOMAIN not in hass.data: - hass.data[DOMAIN] = {} - if "addresses" not in hass.data[DOMAIN]: - hass.data[DOMAIN]["addresses"] = set() async def discover(*args): """Attempt to discover new lights.""" @@ -52,12 +47,12 @@ async def async_setup_entry( new_lights = [ light for light in lights - if light.address not in hass.data[DOMAIN]["addresses"] + if light.address not in hass.data[DOMAIN][DATA_ADDRESSES] ] new_entities = [] for light in new_lights: - hass.data[DOMAIN]["addresses"].add(light.address) + hass.data[DOMAIN][DATA_ADDRESSES].add(light.address) new_entities.append(KulerskyLight(light)) async_add_entities(new_entities, update_before_add=True) @@ -66,12 +61,9 @@ async def async_setup_entry( hass.async_create_task(discover()) # Perform recurring discovery of new devices - async_track_time_interval(hass, discover, DISCOVERY_INTERVAL) - - -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): - """Cleanup the Kuler sky integration.""" - hass.data.pop(DOMAIN, None) + hass.data[DOMAIN][DATA_DISCOVERY_SUBSCRIPTION] = async_track_time_interval( + hass, discover, DISCOVERY_INTERVAL + ) class KulerskyLight(LightEntity): diff --git a/homeassistant/components/zerproc/__init__.py b/homeassistant/components/zerproc/__init__.py index d6faaf7a981..12953afeb2d 100644 --- a/homeassistant/components/zerproc/__init__.py +++ b/homeassistant/components/zerproc/__init__.py @@ -4,7 +4,7 @@ import asyncio from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.core import HomeAssistant -from .const import DOMAIN +from .const import DATA_ADDRESSES, DATA_DISCOVERY_SUBSCRIPTION, DOMAIN PLATFORMS = ["light"] @@ -22,8 +22,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Zerproc from a config entry.""" if DOMAIN not in hass.data: hass.data[DOMAIN] = {} - if "addresses" not in hass.data[DOMAIN]: - hass.data[DOMAIN]["addresses"] = set() + if DATA_ADDRESSES not in hass.data[DOMAIN]: + hass.data[DOMAIN][DATA_ADDRESSES] = set() for platform in PLATFORMS: hass.async_create_task( @@ -35,7 +35,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" + # Stop discovery + unregister_discovery = hass.data[DOMAIN].pop(DATA_DISCOVERY_SUBSCRIPTION, None) + if unregister_discovery: + unregister_discovery() + hass.data.pop(DOMAIN, None) + return all( await asyncio.gather( *[ diff --git a/homeassistant/components/zerproc/const.py b/homeassistant/components/zerproc/const.py index a5481bd4c34..69d5fcfb740 100644 --- a/homeassistant/components/zerproc/const.py +++ b/homeassistant/components/zerproc/const.py @@ -1,2 +1,5 @@ """Constants for the Zerproc integration.""" DOMAIN = "zerproc" + +DATA_ADDRESSES = "addresses" +DATA_DISCOVERY_SUBSCRIPTION = "discovery_subscription" diff --git a/homeassistant/components/zerproc/light.py b/homeassistant/components/zerproc/light.py index 16fe5607925..627358ab971 100644 --- a/homeassistant/components/zerproc/light.py +++ b/homeassistant/components/zerproc/light.py @@ -22,7 +22,7 @@ from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import HomeAssistantType import homeassistant.util.color as color_util -from .const import DOMAIN +from .const import DATA_ADDRESSES, DATA_DISCOVERY_SUBSCRIPTION, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -37,12 +37,14 @@ async def discover_entities(hass: HomeAssistant) -> list[Entity]: # Filter out already discovered lights new_lights = [ - light for light in lights if light.address not in hass.data[DOMAIN]["addresses"] + light + for light in lights + if light.address not in hass.data[DOMAIN][DATA_ADDRESSES] ] entities = [] for light in new_lights: - hass.data[DOMAIN]["addresses"].add(light.address) + hass.data[DOMAIN][DATA_ADDRESSES].add(light.address) entities.append(ZerprocLight(light)) return entities @@ -72,7 +74,9 @@ async def async_setup_entry( hass.async_create_task(discover()) # Perform recurring discovery of new devices - async_track_time_interval(hass, discover, DISCOVERY_INTERVAL) + hass.data[DOMAIN][DATA_DISCOVERY_SUBSCRIPTION] = async_track_time_interval( + hass, discover, DISCOVERY_INTERVAL + ) class ZerprocLight(LightEntity): diff --git a/tests/components/kulersky/test_light.py b/tests/components/kulersky/test_light.py index c08dd10d597..ea5eeb5a690 100644 --- a/tests/components/kulersky/test_light.py +++ b/tests/components/kulersky/test_light.py @@ -5,7 +5,11 @@ import pykulersky import pytest from homeassistant import setup -from homeassistant.components.kulersky.light import DOMAIN +from homeassistant.components.kulersky.const import ( + DATA_ADDRESSES, + DATA_DISCOVERY_SUBSCRIPTION, + DOMAIN, +) from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_MODE, @@ -85,9 +89,13 @@ async def test_init(hass, mock_light): async def test_remove_entry(hass, mock_light, mock_entry): """Test platform setup.""" + assert hass.data[DOMAIN][DATA_ADDRESSES] == {"AA:BB:CC:11:22:33"} + assert DATA_DISCOVERY_SUBSCRIPTION in hass.data[DOMAIN] + await hass.config_entries.async_remove(mock_entry.entry_id) assert mock_light.disconnect.called + assert DOMAIN not in hass.data async def test_remove_entry_exceptions_caught(hass, mock_light, mock_entry): diff --git a/tests/components/zerproc/test_light.py b/tests/components/zerproc/test_light.py index 9fac5085986..0fec6be6451 100644 --- a/tests/components/zerproc/test_light.py +++ b/tests/components/zerproc/test_light.py @@ -17,7 +17,11 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, ) -from homeassistant.components.zerproc.light import DOMAIN +from homeassistant.components.zerproc.const import ( + DATA_ADDRESSES, + DATA_DISCOVERY_SUBSCRIPTION, + DOMAIN, +) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, @@ -146,7 +150,8 @@ async def test_discovery_exception(hass, mock_entry): async def test_remove_entry(hass, mock_light, mock_entry): """Test platform setup.""" - assert hass.data[DOMAIN]["addresses"] == {"AA:BB:CC:DD:EE:FF"} + assert hass.data[DOMAIN][DATA_ADDRESSES] == {"AA:BB:CC:DD:EE:FF"} + assert DATA_DISCOVERY_SUBSCRIPTION in hass.data[DOMAIN] with patch.object(mock_light, "disconnect") as mock_disconnect: await hass.config_entries.async_remove(mock_entry.entry_id) From 73e546e2b838533b25154cb6dbc90550378a2e88 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 06:09:59 +0100 Subject: [PATCH 526/831] Improve condition trace tests (#48152) --- tests/helpers/test_condition.py | 112 +++++++++++++++++++++++++------- 1 file changed, 88 insertions(+), 24 deletions(-) diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index c1bf727bc0f..7c32f2c1192 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -13,10 +13,18 @@ from homeassistant.util import dt def assert_element(trace_element, expected_element, path): """Assert a trace element is as expected. - Note: Unused variable path is passed to get helpful errors from pytest. + Note: Unused variable 'path' is passed to get helpful errors from pytest. """ - for result_key, result in expected_element.get("result", {}).items(): + expected_result = expected_element.get("result", {}) + # Check that every item in expected_element is present and equal in trace_element + # The redundant set operation gives helpful errors from pytest + assert not set(expected_result) - set(trace_element._result or {}) + for result_key, result in expected_result.items(): assert trace_element._result[result_key] == result + + # Check for unexpected items in trace_element + assert not set(trace_element._result or {}) - set(expected_result) + if "error_type" in expected_element: assert isinstance(trace_element._error, expected_element["error_type"]) else: @@ -94,7 +102,9 @@ async def test_and_condition(hass): { "": [{"result": {"result": False}}], "conditions/0": [{"result": {"result": False}}], - "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [ + {"result": {"result": False, "state": "120", "wanted_state": "100"}} + ], } ) @@ -104,7 +114,9 @@ async def test_and_condition(hass): { "": [{"result": {"result": False}}], "conditions/0": [{"result": {"result": False}}], - "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [ + {"result": {"result": False, "state": "105", "wanted_state": "100"}} + ], } ) @@ -114,9 +126,11 @@ async def test_and_condition(hass): { "": [{"result": {"result": True}}], "conditions/0": [{"result": {"result": True}}], - "conditions/0/entity_id/0": [{"result": {"result": True}}], + "conditions/0/entity_id/0": [ + {"result": {"result": True, "state": "100", "wanted_state": "100"}} + ], "conditions/1": [{"result": {"result": True}}], - "conditions/1/entity_id/0": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True, "state": 100.0}}], } ) @@ -167,7 +181,7 @@ async def test_and_condition_raises(hass): "conditions/0": [{"error_type": ConditionError}], "conditions/0/entity_id/0": [{"error_type": ConditionError}], "conditions/1": [{"result": {"result": True}}], - "conditions/1/entity_id/0": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True, "state": 120.0}}], } ) @@ -181,7 +195,15 @@ async def test_and_condition_raises(hass): "conditions/0": [{"error_type": ConditionError}], "conditions/0/entity_id/0": [{"error_type": ConditionError}], "conditions/1": [{"result": {"result": False}}], - "conditions/1/entity_id/0": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [ + { + "result": { + "result": False, + "state": 90.0, + "wanted_state_above": 110.0, + } + } + ], } ) @@ -257,9 +279,19 @@ async def test_or_condition(hass): { "": [{"result": {"result": False}}], "conditions/0": [{"result": {"result": False}}], - "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [ + {"result": {"result": False, "state": "120", "wanted_state": "100"}} + ], "conditions/1": [{"result": {"result": False}}], - "conditions/1/entity_id/0": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [ + { + "result": { + "result": False, + "state": 120.0, + "wanted_state_below": 110.0, + } + } + ], } ) @@ -269,9 +301,11 @@ async def test_or_condition(hass): { "": [{"result": {"result": True}}], "conditions/0": [{"result": {"result": False}}], - "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [ + {"result": {"result": False, "state": "105", "wanted_state": "100"}} + ], "conditions/1": [{"result": {"result": True}}], - "conditions/1/entity_id/0": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True, "state": 105.0}}], } ) @@ -281,7 +315,9 @@ async def test_or_condition(hass): { "": [{"result": {"result": True}}], "conditions/0": [{"result": {"result": True}}], - "conditions/0/entity_id/0": [{"result": {"result": True}}], + "conditions/0/entity_id/0": [ + {"result": {"result": True, "state": "100", "wanted_state": "100"}} + ], } ) @@ -332,7 +368,15 @@ async def test_or_condition_raises(hass): "conditions/0": [{"error_type": ConditionError}], "conditions/0/entity_id/0": [{"error_type": ConditionError}], "conditions/1": [{"result": {"result": False}}], - "conditions/1/entity_id/0": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [ + { + "result": { + "result": False, + "state": 100.0, + "wanted_state_above": 110.0, + } + } + ], } ) @@ -346,7 +390,7 @@ async def test_or_condition_raises(hass): "conditions/0": [{"error_type": ConditionError}], "conditions/0/entity_id/0": [{"error_type": ConditionError}], "conditions/1": [{"result": {"result": True}}], - "conditions/1/entity_id/0": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True, "state": 120.0}}], } ) @@ -418,9 +462,19 @@ async def test_not_condition(hass): { "": [{"result": {"result": True}}], "conditions/0": [{"result": {"result": False}}], - "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [ + {"result": {"result": False, "state": "101", "wanted_state": "100"}} + ], "conditions/1": [{"result": {"result": False}}], - "conditions/1/entity_id/0": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [ + { + "result": { + "result": False, + "state": 101.0, + "wanted_state_below": 50.0, + } + } + ], } ) @@ -430,9 +484,13 @@ async def test_not_condition(hass): { "": [{"result": {"result": True}}], "conditions/0": [{"result": {"result": False}}], - "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [ + {"result": {"result": False, "state": "50", "wanted_state": "100"}} + ], "conditions/1": [{"result": {"result": False}}], - "conditions/1/entity_id/0": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [ + {"result": {"result": False, "state": 50.0, "wanted_state_below": 50.0}} + ], } ) @@ -442,9 +500,11 @@ async def test_not_condition(hass): { "": [{"result": {"result": False}}], "conditions/0": [{"result": {"result": False}}], - "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [ + {"result": {"result": False, "state": "49", "wanted_state": "100"}} + ], "conditions/1": [{"result": {"result": True}}], - "conditions/1/entity_id/0": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True, "state": 49.0}}], } ) @@ -454,7 +514,9 @@ async def test_not_condition(hass): { "": [{"result": {"result": False}}], "conditions/0": [{"result": {"result": True}}], - "conditions/0/entity_id/0": [{"result": {"result": True}}], + "conditions/0/entity_id/0": [ + {"result": {"result": True, "state": "100", "wanted_state": "100"}} + ], } ) @@ -505,7 +567,9 @@ async def test_not_condition_raises(hass): "conditions/0": [{"error_type": ConditionError}], "conditions/0/entity_id/0": [{"error_type": ConditionError}], "conditions/1": [{"result": {"result": False}}], - "conditions/1/entity_id/0": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [ + {"result": {"result": False, "state": 90.0, "wanted_state_below": 50.0}} + ], } ) @@ -519,7 +583,7 @@ async def test_not_condition_raises(hass): "conditions/0": [{"error_type": ConditionError}], "conditions/0/entity_id/0": [{"error_type": ConditionError}], "conditions/1": [{"result": {"result": True}}], - "conditions/1/entity_id/0": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True, "state": 40.0}}], } ) From 40ce25800ca4649ebb3047f57bd91e4f1b991213 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 06:12:56 +0100 Subject: [PATCH 527/831] Test that homeassistant stop and restart do not block WS (#48081) --- .../components/websocket_api/test_commands.py | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index a83f9509d01..b9e1e149dd1 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -1,5 +1,8 @@ """Tests for WebSocket API commands.""" +from unittest.mock import ANY, patch + from async_timeout import timeout +import pytest import voluptuous as vol from homeassistant.components.websocket_api import const @@ -47,6 +50,83 @@ async def test_call_service(hass, websocket_client): assert call.context.as_dict() == msg["result"]["context"] +@pytest.mark.parametrize("command", ("call_service", "call_service_action")) +async def test_call_service_blocking(hass, websocket_client, command): + """Test call service commands block, except for homeassistant restart / stop.""" + with patch( + "homeassistant.core.ServiceRegistry.async_call", autospec=True + ) as mock_call: + await websocket_client.send_json( + { + "id": 5, + "type": "call_service", + "domain": "domain_test", + "service": "test_service", + "service_data": {"hello": "world"}, + }, + ) + msg = await websocket_client.receive_json() + + assert msg["id"] == 5 + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + mock_call.assert_called_once_with( + ANY, + "domain_test", + "test_service", + {"hello": "world"}, + blocking=True, + context=ANY, + target=ANY, + ) + + with patch( + "homeassistant.core.ServiceRegistry.async_call", autospec=True + ) as mock_call: + await websocket_client.send_json( + { + "id": 6, + "type": "call_service", + "domain": "homeassistant", + "service": "test_service", + }, + ) + msg = await websocket_client.receive_json() + + assert msg["id"] == 6 + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + mock_call.assert_called_once_with( + ANY, + "homeassistant", + "test_service", + ANY, + blocking=True, + context=ANY, + target=ANY, + ) + + with patch( + "homeassistant.core.ServiceRegistry.async_call", autospec=True + ) as mock_call: + await websocket_client.send_json( + { + "id": 7, + "type": "call_service", + "domain": "homeassistant", + "service": "restart", + }, + ) + msg = await websocket_client.receive_json() + + assert msg["id"] == 7 + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + mock_call.assert_called_once_with( + ANY, "homeassistant", "restart", ANY, blocking=False, context=ANY, target=ANY + ) + + async def test_call_service_target(hass, websocket_client): """Test call service command with target.""" calls = async_mock_service(hass, "domain_test", "test_service") From e5893ca42caa6ee101d5ca15c9529776c233c8ec Mon Sep 17 00:00:00 2001 From: jjlawren Date: Mon, 22 Mar 2021 00:14:09 -0500 Subject: [PATCH 528/831] Trigger Plex GDM scans regularly (#48041) --- homeassistant/components/plex/__init__.py | 14 +++++++------- homeassistant/components/plex/server.py | 3 +++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 6b403150e9c..d84a86984e2 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -61,12 +61,16 @@ async def async_setup(hass, config): gdm = hass.data[PLEX_DOMAIN][GDM_SCANNER] = GDM() + def gdm_scan(): + _LOGGER.debug("Scanning for GDM clients") + gdm.scan(scan_for_clients=True) + hass.data[PLEX_DOMAIN][GDM_DEBOUNCER] = Debouncer( hass, _LOGGER, cooldown=10, immediate=True, - function=partial(gdm.scan, scan_for_clients=True), + function=gdm_scan, ).async_call return True @@ -145,14 +149,10 @@ async def async_setup_entry(hass, entry): entry.add_update_listener(async_options_updated) - async def async_update_plex(): - await hass.data[PLEX_DOMAIN][GDM_DEBOUNCER]() - await plex_server.async_update_platforms() - unsub = async_dispatcher_connect( hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id), - async_update_plex, + plex_server.async_update_platforms, ) hass.data[PLEX_DOMAIN][DISPATCHERS].setdefault(server_id, []) hass.data[PLEX_DOMAIN][DISPATCHERS][server_id].append(unsub) @@ -164,7 +164,7 @@ async def async_setup_entry(hass, entry): if data == STATE_CONNECTED: _LOGGER.debug("Websocket to %s successful", entry.data[CONF_SERVER]) - hass.async_create_task(async_update_plex()) + hass.async_create_task(plex_server.async_update_platforms()) elif data == STATE_DISCONNECTED: _LOGGER.debug( "Websocket to %s disconnected, retrying", entry.data[CONF_SERVER] diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 6e1a83297b3..27954cdbd9f 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -34,6 +34,7 @@ from .const import ( DEBOUNCE_TIMEOUT, DEFAULT_VERIFY_SSL, DOMAIN, + GDM_DEBOUNCER, GDM_SCANNER, PLAYER_SOURCE, PLEX_NEW_MP_SIGNAL, @@ -323,6 +324,8 @@ class PlexServer: """Update the platform entities.""" _LOGGER.debug("Updating devices") + await self.hass.data[DOMAIN][GDM_DEBOUNCER]() + available_clients = {} ignored_clients = set() new_clients = set() From 7a447c42091582b56cfdb603c25dfe0cafffc73d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Mar 2021 19:18:24 -1000 Subject: [PATCH 529/831] Exclude homekit accessories created by the homekit integration from homekit_controller (#48201) --- .../homekit_controller/config_flow.py | 18 ++++++---- .../homekit_controller/test_config_flow.py | 35 ++++++++++++++----- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 38a41617c6a..fcf83918fda 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -18,8 +18,6 @@ from .const import DOMAIN, KNOWN_DEVICES HOMEKIT_DIR = ".homekit" HOMEKIT_BRIDGE_DOMAIN = "homekit" -HOMEKIT_BRIDGE_SERIAL_NUMBER = "homekit.bridge" -HOMEKIT_BRIDGE_MODEL = "Home Assistant HomeKit Bridge" HOMEKIT_IGNORE = [ # eufy Indoor Cam 2K and 2K Pan & Tilt @@ -181,8 +179,8 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): return self.async_abort(reason="no_devices") - async def _hkid_is_homekit_bridge(self, hkid): - """Determine if the device is a homekit bridge.""" + async def _hkid_is_homekit(self, hkid): + """Determine if the device is a homekit bridge or accessory.""" dev_reg = await async_get_device_registry(self.hass) device = dev_reg.async_get_device( identifiers=set(), connections={(CONNECTION_NETWORK_MAC, hkid)} @@ -190,7 +188,13 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): if device is None: return False - return device.model == HOMEKIT_BRIDGE_MODEL + + for entry_id in device.config_entries: + entry = self.hass.config_entries.async_get_entry(entry_id) + if entry and entry.domain == HOMEKIT_BRIDGE_DOMAIN: + return True + + return False async def async_step_zeroconf(self, discovery_info): """Handle a discovered HomeKit accessory. @@ -266,8 +270,8 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): if model in HOMEKIT_IGNORE: return self.async_abort(reason="ignored_model") - # If this is a HomeKit bridge exported by *this* HA instance ignore it. - if await self._hkid_is_homekit_bridge(hkid): + # If this is a HomeKit bridge/accessory exported by *this* HA instance ignore it. + if await self._hkid_is_homekit(hkid): return self.async_abort(reason="ignored_model") self.name = name diff --git a/tests/components/homekit_controller/test_config_flow.py b/tests/components/homekit_controller/test_config_flow.py index 9cc785f85fb..42903a53062 100644 --- a/tests/components/homekit_controller/test_config_flow.py +++ b/tests/components/homekit_controller/test_config_flow.py @@ -269,25 +269,18 @@ async def test_discovery_ignored_model(hass, controller): async def test_discovery_ignored_hk_bridge(hass, controller): - """Already paired.""" + """Ensure we ignore homekit bridges and accessories created by the homekit integration.""" device = setup_mock_accessory(controller) discovery_info = get_device_discovery_info(device) config_entry = MockConfigEntry(domain=config_flow.HOMEKIT_BRIDGE_DOMAIN, data={}) + config_entry.add_to_hass(hass) formatted_mac = device_registry.format_mac("AA:BB:CC:DD:EE:FF") dev_reg = mock_device_registry(hass) dev_reg.async_get_or_create( config_entry_id=config_entry.entry_id, - identifiers={ - ( - config_flow.HOMEKIT_BRIDGE_DOMAIN, - config_entry.entry_id, - config_flow.HOMEKIT_BRIDGE_SERIAL_NUMBER, - ) - }, connections={(device_registry.CONNECTION_NETWORK_MAC, formatted_mac)}, - model=config_flow.HOMEKIT_BRIDGE_MODEL, ) discovery_info["properties"]["id"] = "AA:BB:CC:DD:EE:FF" @@ -300,6 +293,30 @@ async def test_discovery_ignored_hk_bridge(hass, controller): assert result["reason"] == "ignored_model" +async def test_discovery_does_not_ignore_non_homekit(hass, controller): + """Do not ignore devices that are not from the homekit integration.""" + device = setup_mock_accessory(controller) + discovery_info = get_device_discovery_info(device) + + config_entry = MockConfigEntry(domain="not_homekit", data={}) + config_entry.add_to_hass(hass) + formatted_mac = device_registry.format_mac("AA:BB:CC:DD:EE:FF") + + dev_reg = mock_device_registry(hass) + dev_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(device_registry.CONNECTION_NETWORK_MAC, formatted_mac)}, + ) + + discovery_info["properties"]["id"] = "AA:BB:CC:DD:EE:FF" + + # Device is discovered + result = await hass.config_entries.flow.async_init( + "homekit_controller", context={"source": "zeroconf"}, data=discovery_info + ) + assert result["type"] == "form" + + async def test_discovery_invalid_config_entry(hass, controller): """There is already a config entry for the pairing id but it's invalid.""" MockConfigEntry( From 8e4c0e3ff746f685367c9f2ffa99abc434e303e1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Mar 2021 19:29:48 -1000 Subject: [PATCH 530/831] Increase config entries test coverage (#48203) --- tests/test_config_entries.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 5d774f7877d..fbcc9d1bf14 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1394,6 +1394,12 @@ async def test_entry_id_existing_entry(hass, manager): unique_id="mock-unique-id", ).add_to_hass(hass) + mock_integration( + hass, + MockModule("comp"), + ) + mock_entity_platform(hass, "config_flow.comp", None) + class TestFlow(config_entries.ConfigFlow): """Test flow.""" From a2c4b438ea8ff795cfa7854d919d3981ac60de89 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Mar 2021 19:35:12 -1000 Subject: [PATCH 531/831] Convert august to be push instead of poll (#47544) --- homeassistant/components/august/__init__.py | 149 ++++++++++++----- homeassistant/components/august/activity.py | 157 ++++++++++++------ .../components/august/binary_sensor.py | 99 ++++++----- homeassistant/components/august/camera.py | 6 +- .../components/august/config_flow.py | 2 +- homeassistant/components/august/gateway.py | 4 +- homeassistant/components/august/lock.py | 16 +- homeassistant/components/august/manifest.json | 2 +- homeassistant/components/august/sensor.py | 4 +- requirements_all.txt | 6 +- requirements_test_all.txt | 6 +- tests/components/august/mocks.py | 60 ++++--- tests/components/august/test_binary_sensor.py | 125 ++++++++++++++ tests/components/august/test_config_flow.py | 2 +- tests/components/august/test_gateway.py | 2 +- tests/components/august/test_init.py | 32 +++- tests/components/august/test_lock.py | 121 ++++++++++++++ .../august/get_activity.bridge_offline.json | 34 ++++ .../august/get_activity.bridge_online.json | 34 ++++ tests/fixtures/august/get_doorbell.json | 2 +- .../get_lock.online_with_doorsense.json | 3 +- 21 files changed, 683 insertions(+), 183 deletions(-) create mode 100644 tests/fixtures/august/get_activity.bridge_offline.json create mode 100644 tests/fixtures/august/get_activity.bridge_online.json diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index 73f1cc6a1b5..70c88d02901 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -1,14 +1,16 @@ """Support for August devices.""" import asyncio -import itertools +from itertools import chain import logging from aiohttp import ClientError, ClientResponseError -from august.exceptions import AugustApiAIOHTTPError +from yalexs.exceptions import AugustApiAIOHTTPError +from yalexs.pubnub_activity import activities_from_pubnub_message +from yalexs.pubnub_async import AugustPubNub, async_create_pubnub from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, HTTP_UNAUTHORIZED -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from .activity import ActivityStream @@ -19,6 +21,13 @@ from .subscriber import AugustSubscriberMixin _LOGGER = logging.getLogger(__name__) +API_CACHED_ATTRS = ( + "door_state", + "door_state_datetime", + "lock_status", + "lock_status_datetime", +) + async def async_setup(hass: HomeAssistant, config: dict): """Set up the August component from YAML.""" @@ -60,6 +69,9 @@ def _async_start_reauth(hass: HomeAssistant, entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" + + hass.data[DOMAIN][entry.entry_id][DATA_AUGUST].async_stop() + unload_ok = all( await asyncio.gather( *[ @@ -114,25 +126,27 @@ class AugustData(AugustSubscriberMixin): self._doorbells_by_id = {} self._locks_by_id = {} self._house_ids = set() + self._pubnub_unsub = None async def async_setup(self): """Async setup of august device data and activities.""" - locks = ( - await self._api.async_get_operable_locks(self._august_gateway.access_token) - or [] - ) - doorbells = ( - await self._api.async_get_doorbells(self._august_gateway.access_token) or [] + token = self._august_gateway.access_token + user_data, locks, doorbells = await asyncio.gather( + self._api.async_get_user(token), + self._api.async_get_operable_locks(token), + self._api.async_get_doorbells(token), ) + if not doorbells: + doorbells = [] + if not locks: + locks = [] self._doorbells_by_id = {device.device_id: device for device in doorbells} self._locks_by_id = {device.device_id: device for device in locks} - self._house_ids = { - device.house_id for device in itertools.chain(locks, doorbells) - } + self._house_ids = {device.house_id for device in chain(locks, doorbells)} await self._async_refresh_device_detail_by_ids( - [device.device_id for device in itertools.chain(locks, doorbells)] + [device.device_id for device in chain(locks, doorbells)] ) # We remove all devices that we are missing @@ -142,10 +156,32 @@ class AugustData(AugustSubscriberMixin): self._remove_inoperative_locks() self._remove_inoperative_doorbells() + pubnub = AugustPubNub() + for device in self._device_detail_by_id.values(): + pubnub.register_device(device) + self.activity_stream = ActivityStream( - self._hass, self._api, self._august_gateway, self._house_ids + self._hass, self._api, self._august_gateway, self._house_ids, pubnub ) await self.activity_stream.async_setup() + pubnub.subscribe(self.async_pubnub_message) + self._pubnub_unsub = async_create_pubnub(user_data["UserID"], pubnub) + + @callback + def async_pubnub_message(self, device_id, date_time, message): + """Process a pubnub message.""" + device = self.get_device_detail(device_id) + activities = activities_from_pubnub_message(device, date_time, message) + if activities: + self.activity_stream.async_process_newer_device_activities(activities) + self.async_signal_device_id_update(device.device_id) + self.activity_stream.async_schedule_house_id_refresh(device.house_id) + + @callback + def async_stop(self): + """Stop the subscriptions.""" + self._pubnub_unsub() + self.activity_stream.async_stop() @property def doorbells(self): @@ -165,27 +201,38 @@ class AugustData(AugustSubscriberMixin): await self._async_refresh_device_detail_by_ids(self._subscriptions.keys()) async def _async_refresh_device_detail_by_ids(self, device_ids_list): - for device_id in device_ids_list: - if device_id in self._locks_by_id: - await self._async_update_device_detail( - self._locks_by_id[device_id], self._api.async_get_lock_detail - ) - # keypads are always attached to locks - if ( - device_id in self._device_detail_by_id - and self._device_detail_by_id[device_id].keypad is not None - ): - keypad = self._device_detail_by_id[device_id].keypad - self._device_detail_by_id[keypad.device_id] = keypad - elif device_id in self._doorbells_by_id: - await self._async_update_device_detail( - self._doorbells_by_id[device_id], - self._api.async_get_doorbell_detail, - ) - _LOGGER.debug( - "async_signal_device_id_update (from detail updates): %s", device_id + await asyncio.gather( + *[ + self._async_refresh_device_detail_by_id(device_id) + for device_id in device_ids_list + ] + ) + + async def _async_refresh_device_detail_by_id(self, device_id): + if device_id in self._locks_by_id: + if self.activity_stream and self.activity_stream.pubnub.connected: + saved_attrs = _save_live_attrs(self._device_detail_by_id[device_id]) + await self._async_update_device_detail( + self._locks_by_id[device_id], self._api.async_get_lock_detail ) - self.async_signal_device_id_update(device_id) + if self.activity_stream and self.activity_stream.pubnub.connected: + _restore_live_attrs(self._device_detail_by_id[device_id], saved_attrs) + # keypads are always attached to locks + if ( + device_id in self._device_detail_by_id + and self._device_detail_by_id[device_id].keypad is not None + ): + keypad = self._device_detail_by_id[device_id].keypad + self._device_detail_by_id[keypad.device_id] = keypad + elif device_id in self._doorbells_by_id: + await self._async_update_device_detail( + self._doorbells_by_id[device_id], + self._api.async_get_doorbell_detail, + ) + _LOGGER.debug( + "async_signal_device_id_update (from detail updates): %s", device_id + ) + self.async_signal_device_id_update(device_id) async def _async_update_device_detail(self, device, api_call): _LOGGER.debug( @@ -213,9 +260,9 @@ class AugustData(AugustSubscriberMixin): def _get_device_name(self, device_id): """Return doorbell or lock name as August has it stored.""" - if self._locks_by_id.get(device_id): + if device_id in self._locks_by_id: return self._locks_by_id[device_id].device_name - if self._doorbells_by_id.get(device_id): + if device_id in self._doorbells_by_id: return self._doorbells_by_id[device_id].device_name async def async_lock(self, device_id): @@ -252,8 +299,7 @@ class AugustData(AugustSubscriberMixin): return ret def _remove_inoperative_doorbells(self): - doorbells = list(self.doorbells) - for doorbell in doorbells: + for doorbell in list(self.doorbells): device_id = doorbell.device_id doorbell_is_operative = False doorbell_detail = self._device_detail_by_id.get(device_id) @@ -273,9 +319,7 @@ class AugustData(AugustSubscriberMixin): # Remove non-operative locks as there must # be a bridge (August Connect) for them to # be usable - locks = list(self.locks) - - for lock in locks: + for lock in list(self.locks): device_id = lock.device_id lock_is_operative = False lock_detail = self._device_detail_by_id.get(device_id) @@ -289,14 +333,27 @@ class AugustData(AugustSubscriberMixin): "The lock %s could not be setup because it does not have a bridge (Connect)", lock.device_name, ) - elif not lock_detail.bridge.operative: - _LOGGER.info( - "The lock %s could not be setup because the bridge (Connect) is not operative", - lock.device_name, - ) + # Bridge may come back online later so we still add the device since we will + # have a pubnub subscription to tell use when it recovers else: lock_is_operative = True if not lock_is_operative: del self._locks_by_id[device_id] del self._device_detail_by_id[device_id] + + +def _save_live_attrs(lock_detail): + """Store the attributes that the lock detail api may have an invalid cache for. + + Since we are connected to pubnub we may have more current data + then the api so we want to restore the most current data after + updating battery state etc. + """ + return {attr: getattr(lock_detail, attr) for attr in API_CACHED_ATTRS} + + +def _restore_live_attrs(lock_detail, attrs): + """Restore the non-cache attributes after a cached update.""" + for attr, value in attrs.items(): + setattr(lock_detail, attr, value) diff --git a/homeassistant/components/august/activity.py b/homeassistant/components/august/activity.py index d972fbf5281..18f390b4f8f 100644 --- a/homeassistant/components/august/activity.py +++ b/homeassistant/components/august/activity.py @@ -1,8 +1,12 @@ """Consume the august activity stream.""" +import asyncio import logging from aiohttp import ClientError +from homeassistant.core import callback +from homeassistant.helpers.debounce import Debouncer +from homeassistant.helpers.event import async_call_later from homeassistant.util.dt import utcnow from .const import ACTIVITY_UPDATE_INTERVAL @@ -17,27 +21,58 @@ ACTIVITY_CATCH_UP_FETCH_LIMIT = 2500 class ActivityStream(AugustSubscriberMixin): """August activity stream handler.""" - def __init__(self, hass, api, august_gateway, house_ids): + def __init__(self, hass, api, august_gateway, house_ids, pubnub): """Init August activity stream object.""" super().__init__(hass, ACTIVITY_UPDATE_INTERVAL) self._hass = hass + self._schedule_updates = {} self._august_gateway = august_gateway self._api = api self._house_ids = house_ids - self._latest_activities_by_id_type = {} + self._latest_activities = {} self._last_update_time = None self._abort_async_track_time_interval = None + self.pubnub = pubnub + self._update_debounce = {} async def async_setup(self): """Token refresh check and catch up the activity stream.""" - await self._async_refresh(utcnow) + for house_id in self._house_ids: + self._update_debounce[house_id] = self._async_create_debouncer(house_id) + + await self._async_refresh(utcnow()) + + @callback + def _async_create_debouncer(self, house_id): + """Create a debouncer for the house id.""" + + async def _async_update_house_id(): + await self._async_update_house_id(house_id) + + return Debouncer( + self._hass, + _LOGGER, + cooldown=ACTIVITY_UPDATE_INTERVAL.seconds, + immediate=True, + function=_async_update_house_id, + ) + + @callback + def async_stop(self): + """Cleanup any debounces.""" + for debouncer in self._update_debounce.values(): + debouncer.async_cancel() + for house_id in self._schedule_updates: + if self._schedule_updates[house_id] is not None: + self._schedule_updates[house_id]() + self._schedule_updates[house_id] = None def get_latest_device_activity(self, device_id, activity_types): """Return latest activity that is one of the acitivty_types.""" - if device_id not in self._latest_activities_by_id_type: + if device_id not in self._latest_activities: return None - latest_device_activities = self._latest_activities_by_id_type[device_id] + latest_device_activities = self._latest_activities[device_id] latest_activity = None for activity_type in activity_types: @@ -54,62 +89,86 @@ class ActivityStream(AugustSubscriberMixin): async def _async_refresh(self, time): """Update the activity stream from August.""" - # This is the only place we refresh the api token await self._august_gateway.async_refresh_access_token_if_needed() + if self.pubnub.connected: + _LOGGER.debug("Skipping update because pubnub is connected") + return await self._async_update_device_activities(time) async def _async_update_device_activities(self, time): _LOGGER.debug("Start retrieving device activities") - - limit = ( - ACTIVITY_STREAM_FETCH_LIMIT - if self._last_update_time - else ACTIVITY_CATCH_UP_FETCH_LIMIT + await asyncio.gather( + *[ + self._update_debounce[house_id].async_call() + for house_id in self._house_ids + ] ) - - for house_id in self._house_ids: - _LOGGER.debug("Updating device activity for house id %s", house_id) - try: - activities = await self._api.async_get_house_activities( - self._august_gateway.access_token, house_id, limit=limit - ) - except ClientError as ex: - _LOGGER.error( - "Request error trying to retrieve activity for house id %s: %s", - house_id, - ex, - ) - # Make sure we process the next house if one of them fails - continue - - _LOGGER.debug( - "Completed retrieving device activities for house id %s", house_id - ) - - updated_device_ids = self._process_newer_device_activities(activities) - - if updated_device_ids: - for device_id in updated_device_ids: - _LOGGER.debug( - "async_signal_device_id_update (from activity stream): %s", - device_id, - ) - self.async_signal_device_id_update(device_id) - self._last_update_time = time - def _process_newer_device_activities(self, activities): + @callback + def async_schedule_house_id_refresh(self, house_id): + """Update for a house activities now and once in the future.""" + if self._schedule_updates.get(house_id): + self._schedule_updates[house_id]() + self._schedule_updates[house_id] = None + + async def _update_house_activities(_): + await self._update_debounce[house_id].async_call() + + self._hass.async_create_task(self._update_debounce[house_id].async_call()) + # Schedule an update past the debounce to ensure + # we catch the case where the lock operator is + # not updated or the lock failed + self._schedule_updates[house_id] = async_call_later( + self._hass, ACTIVITY_UPDATE_INTERVAL.seconds + 1, _update_house_activities + ) + + async def _async_update_house_id(self, house_id): + """Update device activities for a house.""" + if self._last_update_time: + limit = ACTIVITY_STREAM_FETCH_LIMIT + else: + limit = ACTIVITY_CATCH_UP_FETCH_LIMIT + + _LOGGER.debug("Updating device activity for house id %s", house_id) + try: + activities = await self._api.async_get_house_activities( + self._august_gateway.access_token, house_id, limit=limit + ) + except ClientError as ex: + _LOGGER.error( + "Request error trying to retrieve activity for house id %s: %s", + house_id, + ex, + ) + # Make sure we process the next house if one of them fails + return + + _LOGGER.debug( + "Completed retrieving device activities for house id %s", house_id + ) + + updated_device_ids = self.async_process_newer_device_activities(activities) + + if not updated_device_ids: + return + + for device_id in updated_device_ids: + _LOGGER.debug( + "async_signal_device_id_update (from activity stream): %s", + device_id, + ) + self.async_signal_device_id_update(device_id) + + def async_process_newer_device_activities(self, activities): + """Process activities if they are newer than the last one.""" updated_device_ids = set() for activity in activities: device_id = activity.device_id activity_type = activity.activity_type - - self._latest_activities_by_id_type.setdefault(device_id, {}) - - lastest_activity = self._latest_activities_by_id_type[device_id].get( - activity_type - ) + device_activities = self._latest_activities.setdefault(device_id, {}) + lastest_activity = device_activities.get(activity_type) # Ignore activities that are older than the latest one if ( @@ -118,7 +177,7 @@ class ActivityStream(AugustSubscriberMixin): ): continue - self._latest_activities_by_id_type[device_id][activity_type] = activity + device_activities[activity_type] = activity updated_device_ids.add(device_id) diff --git a/homeassistant/components/august/binary_sensor.py b/homeassistant/components/august/binary_sensor.py index 226cbf655f9..6dccec57a09 100644 --- a/homeassistant/components/august/binary_sensor.py +++ b/homeassistant/components/august/binary_sensor.py @@ -2,9 +2,9 @@ from datetime import datetime, timedelta import logging -from august.activity import ActivityType -from august.lock import LockDoorStatus -from august.util import update_lock_detail_from_activity +from yalexs.activity import ACTION_DOORBELL_CALL_MISSED, ActivityType +from yalexs.lock import LockDoorStatus +from yalexs.util import update_lock_detail_from_activity from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, @@ -14,15 +14,15 @@ from homeassistant.components.binary_sensor import ( BinarySensorEntity, ) from homeassistant.core import callback -from homeassistant.helpers.event import async_track_point_in_utc_time -from homeassistant.util.dt import utcnow +from homeassistant.helpers.event import async_call_later -from .const import DATA_AUGUST, DOMAIN +from .const import ACTIVITY_UPDATE_INTERVAL, DATA_AUGUST, DOMAIN from .entity import AugustEntityMixin _LOGGER = logging.getLogger(__name__) -TIME_TO_DECLARE_DETECTION = timedelta(seconds=60) +TIME_TO_DECLARE_DETECTION = timedelta(seconds=ACTIVITY_UPDATE_INTERVAL.seconds) +TIME_TO_RECHECK_DETECTION = timedelta(seconds=ACTIVITY_UPDATE_INTERVAL.seconds * 3) def _retrieve_online_state(data, detail): @@ -35,30 +35,43 @@ def _retrieve_online_state(data, detail): def _retrieve_motion_state(data, detail): - - return _activity_time_based_state( - data, - detail.device_id, - [ActivityType.DOORBELL_MOTION, ActivityType.DOORBELL_DING], + latest = data.activity_stream.get_latest_device_activity( + detail.device_id, {ActivityType.DOORBELL_MOTION} ) + if latest is None: + return False + + return _activity_time_based_state(latest) + def _retrieve_ding_state(data, detail): - - return _activity_time_based_state( - data, detail.device_id, [ActivityType.DOORBELL_DING] + latest = data.activity_stream.get_latest_device_activity( + detail.device_id, {ActivityType.DOORBELL_DING} ) + if latest is None: + return False -def _activity_time_based_state(data, device_id, activity_types): + if ( + data.activity_stream.pubnub.connected + and latest.action == ACTION_DOORBELL_CALL_MISSED + ): + return False + + return _activity_time_based_state(latest) + + +def _activity_time_based_state(latest): """Get the latest state of the sensor.""" - latest = data.activity_stream.get_latest_device_activity(device_id, activity_types) + start = latest.activity_start_time + end = latest.activity_end_time + TIME_TO_DECLARE_DETECTION + return start <= _native_datetime() <= end - if latest is not None: - start = latest.activity_start_time - end = latest.activity_end_time + TIME_TO_DECLARE_DETECTION - return start <= datetime.now() <= end - return None + +def _native_datetime(): + """Return time in the format august uses without timezone.""" + return datetime.now() SENSOR_NAME = 0 @@ -143,12 +156,19 @@ class AugustDoorBinarySensor(AugustEntityMixin, BinarySensorEntity): def _update_from_data(self): """Get the latest state of the sensor and update activity.""" door_activity = self._data.activity_stream.get_latest_device_activity( - self._device_id, [ActivityType.DOOR_OPERATION] + self._device_id, {ActivityType.DOOR_OPERATION} ) if door_activity is not None: update_lock_detail_from_activity(self._detail, door_activity) + bridge_activity = self._data.activity_stream.get_latest_device_activity( + self._device_id, {ActivityType.BRIDGE_OPERATION} + ) + + if bridge_activity is not None: + update_lock_detail_from_activity(self._detail, bridge_activity) + @property def unique_id(self) -> str: """Get the unique of the door open binary sensor.""" @@ -179,25 +199,30 @@ class AugustDoorbellBinarySensor(AugustEntityMixin, BinarySensorEntity): """Return true if the binary sensor is on.""" return self._state + @property + def _sensor_config(self): + """Return the config for the sensor.""" + return SENSOR_TYPES_DOORBELL[self._sensor_type] + @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - return SENSOR_TYPES_DOORBELL[self._sensor_type][SENSOR_DEVICE_CLASS] + return self._sensor_config[SENSOR_DEVICE_CLASS] @property def name(self): """Return the name of the binary sensor.""" - return f"{self._device.device_name} {SENSOR_TYPES_DOORBELL[self._sensor_type][SENSOR_NAME]}" + return f"{self._device.device_name} {self._sensor_config[SENSOR_NAME]}" @property def _state_provider(self): """Return the state provider for the binary sensor.""" - return SENSOR_TYPES_DOORBELL[self._sensor_type][SENSOR_STATE_PROVIDER] + return self._sensor_config[SENSOR_STATE_PROVIDER] @property def _is_time_based(self): """Return true of false if the sensor is time based.""" - return SENSOR_TYPES_DOORBELL[self._sensor_type][SENSOR_STATE_IS_TIME_BASED] + return self._sensor_config[SENSOR_STATE_IS_TIME_BASED] @callback def _update_from_data(self): @@ -228,17 +253,20 @@ class AugustDoorbellBinarySensor(AugustEntityMixin, BinarySensorEntity): """Timer callback for sensor update.""" self._check_for_off_update_listener = None self._update_from_data() + if not self._state: + self.async_write_ha_state() - self._check_for_off_update_listener = async_track_point_in_utc_time( - self.hass, _scheduled_update, utcnow() + TIME_TO_DECLARE_DETECTION + self._check_for_off_update_listener = async_call_later( + self.hass, TIME_TO_RECHECK_DETECTION.seconds, _scheduled_update ) def _cancel_any_pending_updates(self): """Cancel any updates to recheck a sensor to see if it is ready to turn off.""" - if self._check_for_off_update_listener: - _LOGGER.debug("%s: canceled pending update", self.entity_id) - self._check_for_off_update_listener() - self._check_for_off_update_listener = None + if not self._check_for_off_update_listener: + return + _LOGGER.debug("%s: canceled pending update", self.entity_id) + self._check_for_off_update_listener() + self._check_for_off_update_listener = None async def async_added_to_hass(self): """Call the mixin to subscribe and setup an async_track_point_in_utc_time to turn off the sensor if needed.""" @@ -248,7 +276,4 @@ class AugustDoorbellBinarySensor(AugustEntityMixin, BinarySensorEntity): @property def unique_id(self) -> str: """Get the unique id of the doorbell sensor.""" - return ( - f"{self._device_id}_" - f"{SENSOR_TYPES_DOORBELL[self._sensor_type][SENSOR_NAME].lower()}" - ) + return f"{self._device_id}_{self._sensor_config[SENSOR_NAME].lower()}" diff --git a/homeassistant/components/august/camera.py b/homeassistant/components/august/camera.py index 4037489fa22..e002e0b2517 100644 --- a/homeassistant/components/august/camera.py +++ b/homeassistant/components/august/camera.py @@ -1,7 +1,7 @@ """Support for August doorbell camera.""" -from august.activity import ActivityType -from august.util import update_doorbell_image_from_activity +from yalexs.activity import ActivityType +from yalexs.util import update_doorbell_image_from_activity from homeassistant.components.camera import Camera from homeassistant.core import callback @@ -63,7 +63,7 @@ class AugustCamera(AugustEntityMixin, Camera): def _update_from_data(self): """Get the latest state of the sensor.""" doorbell_activity = self._data.activity_stream.get_latest_device_activity( - self._device_id, [ActivityType.DOORBELL_MOTION] + self._device_id, {ActivityType.DOORBELL_MOTION} ) if doorbell_activity is not None: diff --git a/homeassistant/components/august/config_flow.py b/homeassistant/components/august/config_flow.py index 29bb41947a0..7176592e37e 100644 --- a/homeassistant/components/august/config_flow.py +++ b/homeassistant/components/august/config_flow.py @@ -1,8 +1,8 @@ """Config flow for August integration.""" import logging -from august.authenticator import ValidationResult import voluptuous as vol +from yalexs.authenticator import ValidationResult from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_USERNAME diff --git a/homeassistant/components/august/gateway.py b/homeassistant/components/august/gateway.py index f71541e82fc..5499246a187 100644 --- a/homeassistant/components/august/gateway.py +++ b/homeassistant/components/august/gateway.py @@ -5,8 +5,8 @@ import logging import os from aiohttp import ClientError, ClientResponseError -from august.api_async import ApiAsync -from august.authenticator_async import AuthenticationState, AuthenticatorAsync +from yalexs.api_async import ApiAsync +from yalexs.authenticator_async import AuthenticationState, AuthenticatorAsync from homeassistant.const import ( CONF_PASSWORD, diff --git a/homeassistant/components/august/lock.py b/homeassistant/components/august/lock.py index 4b4ae906190..59c97190d7f 100644 --- a/homeassistant/components/august/lock.py +++ b/homeassistant/components/august/lock.py @@ -1,9 +1,9 @@ """Support for August lock.""" import logging -from august.activity import ActivityType -from august.lock import LockStatus -from august.util import update_lock_detail_from_activity +from yalexs.activity import ActivityType +from yalexs.lock import LockStatus +from yalexs.util import update_lock_detail_from_activity from homeassistant.components.lock import ATTR_CHANGED_BY, LockEntity from homeassistant.const import ATTR_BATTERY_LEVEL @@ -73,13 +73,21 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity): def _update_from_data(self): """Get the latest state of the sensor and update activity.""" lock_activity = self._data.activity_stream.get_latest_device_activity( - self._device_id, [ActivityType.LOCK_OPERATION] + self._device_id, + {ActivityType.LOCK_OPERATION, ActivityType.LOCK_OPERATION_WITHOUT_OPERATOR}, ) if lock_activity is not None: self._changed_by = lock_activity.operated_by update_lock_detail_from_activity(self._detail, lock_activity) + bridge_activity = self._data.activity_stream.get_latest_device_activity( + self._device_id, {ActivityType.BRIDGE_OPERATION} + ) + + if bridge_activity is not None: + update_lock_detail_from_activity(self._detail, bridge_activity) + self._update_lock_status_from_detail() @property diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index 91733b6822e..3a156a189c7 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -2,7 +2,7 @@ "domain": "august", "name": "August", "documentation": "https://www.home-assistant.io/integrations/august", - "requirements": ["py-august==0.25.2"], + "requirements": ["yalexs==1.1.4"], "codeowners": ["@bdraco"], "dhcp": [ {"hostname":"connect","macaddress":"D86162*"}, diff --git a/homeassistant/components/august/sensor.py b/homeassistant/components/august/sensor.py index 9dba5bb2766..1841b2cef56 100644 --- a/homeassistant/components/august/sensor.py +++ b/homeassistant/components/august/sensor.py @@ -1,7 +1,7 @@ """Support for August sensors.""" import logging -from august.activity import ActivityType +from yalexs.activity import ActivityType from homeassistant.components.sensor import DEVICE_CLASS_BATTERY from homeassistant.const import ATTR_ENTITY_PICTURE, PERCENTAGE, STATE_UNAVAILABLE @@ -154,7 +154,7 @@ class AugustOperatorSensor(AugustEntityMixin, RestoreEntity, Entity): def _update_from_data(self): """Get the latest state of the sensor and update activity.""" lock_activity = self._data.activity_stream.get_latest_device_activity( - self._device_id, [ActivityType.LOCK_OPERATION] + self._device_id, {ActivityType.LOCK_OPERATION} ) self._available = True diff --git a/requirements_all.txt b/requirements_all.txt index 0ee7a4edb72..dbaee574184 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1191,9 +1191,6 @@ pushover_complete==1.1.1 # homeassistant.components.rpi_gpio_pwm pwmled==1.6.7 -# homeassistant.components.august -py-august==0.25.2 - # homeassistant.components.canary py-canary==0.5.1 @@ -2347,6 +2344,9 @@ xs1-api-client==3.0.0 # homeassistant.components.yale_smart_alarm yalesmartalarmclient==0.1.6 +# homeassistant.components.august +yalexs==1.1.4 + # homeassistant.components.yeelight yeelight==0.5.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6ceeccec6b5..20bd6547b05 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -616,9 +616,6 @@ pure-python-adb[async]==0.3.0.dev0 # homeassistant.components.pushbullet pushbullet.py==0.11.0 -# homeassistant.components.august -py-august==0.25.2 - # homeassistant.components.canary py-canary==0.5.1 @@ -1208,6 +1205,9 @@ xbox-webapi==2.0.8 # homeassistant.components.zestimate xmltodict==0.12.0 +# homeassistant.components.august +yalexs==1.1.4 + # homeassistant.components.yeelight yeelight==0.5.4 diff --git a/tests/components/august/mocks.py b/tests/components/august/mocks.py index 928b753af52..9a54a708a4f 100644 --- a/tests/components/august/mocks.py +++ b/tests/components/august/mocks.py @@ -6,21 +6,26 @@ import time # from unittest.mock import AsyncMock from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch -from august.activity import ( +from yalexs.activity import ( + ACTIVITY_ACTIONS_BRIDGE_OPERATION, ACTIVITY_ACTIONS_DOOR_OPERATION, ACTIVITY_ACTIONS_DOORBELL_DING, ACTIVITY_ACTIONS_DOORBELL_MOTION, ACTIVITY_ACTIONS_DOORBELL_VIEW, ACTIVITY_ACTIONS_LOCK_OPERATION, + SOURCE_LOCK_OPERATE, + SOURCE_LOG, + BridgeOperationActivity, DoorbellDingActivity, DoorbellMotionActivity, DoorbellViewActivity, DoorOperationActivity, LockOperationActivity, ) -from august.authenticator import AuthenticationState -from august.doorbell import Doorbell, DoorbellDetail -from august.lock import Lock, LockDetail +from yalexs.authenticator import AuthenticationState +from yalexs.doorbell import Doorbell, DoorbellDetail +from yalexs.lock import Lock, LockDetail +from yalexs.pubnub_async import AugustPubNub from homeassistant.components.august.const import CONF_LOGIN_METHOD, DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME @@ -48,7 +53,9 @@ def _mock_authenticator(auth_state): @patch("homeassistant.components.august.gateway.ApiAsync") @patch("homeassistant.components.august.gateway.AuthenticatorAsync.async_authenticate") -async def _mock_setup_august(hass, api_instance, authenticate_mock, api_mock): +async def _mock_setup_august( + hass, api_instance, pubnub_mock, authenticate_mock, api_mock +): """Set up august integration.""" authenticate_mock.side_effect = MagicMock( return_value=_mock_august_authentication( @@ -62,16 +69,21 @@ async def _mock_setup_august(hass, api_instance, authenticate_mock, api_mock): options={}, ) entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - return True + with patch("homeassistant.components.august.async_create_pubnub"), patch( + "homeassistant.components.august.AugustPubNub", return_value=pubnub_mock + ): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + return entry async def _create_august_with_devices( - hass, devices, api_call_side_effects=None, activities=None + hass, devices, api_call_side_effects=None, activities=None, pubnub=None ): if api_call_side_effects is None: api_call_side_effects = {} + if pubnub is None: + pubnub = AugustPubNub() device_data = {"doorbells": [], "locks": []} for device in devices: @@ -152,10 +164,12 @@ async def _create_august_with_devices( "unlock_return_activities" ] = unlock_return_activities_side_effect - return await _mock_setup_august_with_api_side_effects(hass, api_call_side_effects) + return await _mock_setup_august_with_api_side_effects( + hass, api_call_side_effects, pubnub + ) -async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects): +async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects, pubnub): api_instance = MagicMock(name="Api") if api_call_side_effects["get_lock_detail"]: @@ -193,11 +207,13 @@ async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects): side_effect=api_call_side_effects["unlock_return_activities"] ) - return await _mock_setup_august(hass, api_instance) + api_instance.async_get_user = AsyncMock(return_value={"UserID": "abc"}) + + return await _mock_setup_august(hass, api_instance, pubnub) def _mock_august_authentication(token_text, token_timestamp, state): - authentication = MagicMock(name="august.authentication") + authentication = MagicMock(name="yalexs.authentication") type(authentication).state = PropertyMock(return_value=state) type(authentication).access_token = PropertyMock(return_value=token_text) type(authentication).access_token_expires = PropertyMock( @@ -301,23 +317,25 @@ async def _mock_doorsense_missing_august_lock_detail(hass): def _mock_lock_operation_activity(lock, action, offset): return LockOperationActivity( + SOURCE_LOCK_OPERATE, { "dateTime": (time.time() + offset) * 1000, "deviceID": lock.device_id, "deviceType": "lock", "action": action, - } + }, ) def _mock_door_operation_activity(lock, action, offset): return DoorOperationActivity( + SOURCE_LOCK_OPERATE, { "dateTime": (time.time() + offset) * 1000, "deviceID": lock.device_id, "deviceType": "lock", "action": action, - } + }, ) @@ -327,13 +345,15 @@ def _activity_from_dict(activity_dict): activity_dict["dateTime"] = time.time() * 1000 if action in ACTIVITY_ACTIONS_DOORBELL_DING: - return DoorbellDingActivity(activity_dict) + return DoorbellDingActivity(SOURCE_LOG, activity_dict) if action in ACTIVITY_ACTIONS_DOORBELL_MOTION: - return DoorbellMotionActivity(activity_dict) + return DoorbellMotionActivity(SOURCE_LOG, activity_dict) if action in ACTIVITY_ACTIONS_DOORBELL_VIEW: - return DoorbellViewActivity(activity_dict) + return DoorbellViewActivity(SOURCE_LOG, activity_dict) if action in ACTIVITY_ACTIONS_LOCK_OPERATION: - return LockOperationActivity(activity_dict) + return LockOperationActivity(SOURCE_LOG, activity_dict) if action in ACTIVITY_ACTIONS_DOOR_OPERATION: - return DoorOperationActivity(activity_dict) + return DoorOperationActivity(SOURCE_LOG, activity_dict) + if action in ACTIVITY_ACTIONS_BRIDGE_OPERATION: + return BridgeOperationActivity(SOURCE_LOG, activity_dict) return None diff --git a/tests/components/august/test_binary_sensor.py b/tests/components/august/test_binary_sensor.py index 0e337813f52..0912b05bec1 100644 --- a/tests/components/august/test_binary_sensor.py +++ b/tests/components/august/test_binary_sensor.py @@ -1,4 +1,9 @@ """The binary_sensor tests for the august platform.""" +import datetime +from unittest.mock import Mock, patch + +from yalexs.pubnub_async import AugustPubNub + from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, @@ -9,7 +14,9 @@ from homeassistant.const import ( STATE_UNAVAILABLE, ) from homeassistant.helpers import device_registry as dr +import homeassistant.util.dt as dt_util +from tests.common import async_fire_time_changed from tests.components.august.mocks import ( _create_august_with_devices, _mock_activities_from_fixture, @@ -52,6 +59,22 @@ async def test_doorsense(hass): assert binary_sensor_online_with_doorsense_name.state == STATE_OFF +async def test_lock_bridge_offline(hass): + """Test creation of a lock with doorsense and bridge that goes offline.""" + lock_one = await _mock_lock_from_fixture( + hass, "get_lock.online_with_doorsense.json" + ) + activities = await _mock_activities_from_fixture( + hass, "get_activity.bridge_offline.json" + ) + await _create_august_with_devices(hass, [lock_one], activities=activities) + + binary_sensor_online_with_doorsense_name = hass.states.get( + "binary_sensor.online_with_doorsense_name_open" + ) + assert binary_sensor_online_with_doorsense_name.state == STATE_UNAVAILABLE + + async def test_create_doorbell(hass): """Test creation of a doorbell.""" doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.json") @@ -112,6 +135,108 @@ async def test_create_doorbell_with_motion(hass): "binary_sensor.k98gidt45gul_name_ding" ) assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF + new_time = dt_util.utcnow() + datetime.timedelta(seconds=40) + native_time = datetime.datetime.now() + datetime.timedelta(seconds=40) + with patch( + "homeassistant.components.august.binary_sensor._native_datetime", + return_value=native_time, + ): + async_fire_time_changed(hass, new_time) + await hass.async_block_till_done() + binary_sensor_k98gidt45gul_name_motion = hass.states.get( + "binary_sensor.k98gidt45gul_name_motion" + ) + assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF + + +async def test_doorbell_update_via_pubnub(hass): + """Test creation of a doorbell that can be updated via pubnub.""" + doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.json") + pubnub = AugustPubNub() + + await _create_august_with_devices(hass, [doorbell_one], pubnub=pubnub) + assert doorbell_one.pubsub_channel == "7c7a6672-59c8-3333-ffff-dcd98705cccc" + + binary_sensor_k98gidt45gul_name_motion = hass.states.get( + "binary_sensor.k98gidt45gul_name_motion" + ) + assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF + binary_sensor_k98gidt45gul_name_ding = hass.states.get( + "binary_sensor.k98gidt45gul_name_ding" + ) + assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF + + pubnub.message( + pubnub, + Mock( + channel=doorbell_one.pubsub_channel, + timetoken=dt_util.utcnow().timestamp() * 10000000, + message={ + "status": "imagecapture", + "data": { + "result": { + "created_at": "2021-03-16T01:07:08.817Z", + "secure_url": "https://dyu7azbnaoi74.cloudfront.net/zip/images/zip.jpeg", + }, + }, + }, + ), + ) + + await hass.async_block_till_done() + + binary_sensor_k98gidt45gul_name_motion = hass.states.get( + "binary_sensor.k98gidt45gul_name_motion" + ) + assert binary_sensor_k98gidt45gul_name_motion.state == STATE_ON + binary_sensor_k98gidt45gul_name_ding = hass.states.get( + "binary_sensor.k98gidt45gul_name_ding" + ) + assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF + + new_time = dt_util.utcnow() + datetime.timedelta(seconds=40) + native_time = datetime.datetime.now() + datetime.timedelta(seconds=40) + with patch( + "homeassistant.components.august.binary_sensor._native_datetime", + return_value=native_time, + ): + async_fire_time_changed(hass, new_time) + await hass.async_block_till_done() + + binary_sensor_k98gidt45gul_name_motion = hass.states.get( + "binary_sensor.k98gidt45gul_name_motion" + ) + assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF + + pubnub.message( + pubnub, + Mock( + channel=doorbell_one.pubsub_channel, + timetoken=dt_util.utcnow().timestamp() * 10000000, + message={ + "status": "buttonpush", + }, + ), + ) + await hass.async_block_till_done() + + binary_sensor_k98gidt45gul_name_ding = hass.states.get( + "binary_sensor.k98gidt45gul_name_ding" + ) + assert binary_sensor_k98gidt45gul_name_ding.state == STATE_ON + new_time = dt_util.utcnow() + datetime.timedelta(seconds=40) + native_time = datetime.datetime.now() + datetime.timedelta(seconds=40) + with patch( + "homeassistant.components.august.binary_sensor._native_datetime", + return_value=native_time, + ): + async_fire_time_changed(hass, new_time) + await hass.async_block_till_done() + + binary_sensor_k98gidt45gul_name_ding = hass.states.get( + "binary_sensor.k98gidt45gul_name_ding" + ) + assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF async def test_doorbell_device_registry(hass): diff --git a/tests/components/august/test_config_flow.py b/tests/components/august/test_config_flow.py index 205c5c70689..c87291e0f79 100644 --- a/tests/components/august/test_config_flow.py +++ b/tests/components/august/test_config_flow.py @@ -1,7 +1,7 @@ """Test the August config flow.""" from unittest.mock import patch -from august.authenticator import ValidationResult +from yalexs.authenticator import ValidationResult from homeassistant import config_entries, setup from homeassistant.components.august.const import ( diff --git a/tests/components/august/test_gateway.py b/tests/components/august/test_gateway.py index ced07360008..54a5e9321f2 100644 --- a/tests/components/august/test_gateway.py +++ b/tests/components/august/test_gateway.py @@ -1,7 +1,7 @@ """The gateway tests for the august platform.""" from unittest.mock import MagicMock, patch -from august.authenticator_common import AuthenticationState +from yalexs.authenticator_common import AuthenticationState from homeassistant.components.august.const import DOMAIN from homeassistant.components.august.gateway import AugustGateway diff --git a/tests/components/august/test_init.py b/tests/components/august/test_init.py index d0116d2c586..8b0885f7341 100644 --- a/tests/components/august/test_init.py +++ b/tests/components/august/test_init.py @@ -3,13 +3,14 @@ import asyncio from unittest.mock import patch from aiohttp import ClientResponseError -from august.authenticator_common import AuthenticationState -from august.exceptions import AugustApiAIOHTTPError +from yalexs.authenticator_common import AuthenticationState +from yalexs.exceptions import AugustApiAIOHTTPError from homeassistant import setup from homeassistant.components.august.const import DOMAIN from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.config_entries import ( + ENTRY_STATE_LOADED, ENTRY_STATE_SETUP_ERROR, ENTRY_STATE_SETUP_RETRY, ) @@ -46,7 +47,7 @@ async def test_august_is_offline(hass): await setup.async_setup_component(hass, "persistent_notification", {}) with patch( - "august.authenticator_async.AuthenticatorAsync.async_authenticate", + "yalexs.authenticator_async.AuthenticatorAsync.async_authenticate", side_effect=asyncio.TimeoutError, ): await hass.config_entries.async_setup(config_entry.entry_id) @@ -152,7 +153,7 @@ async def test_auth_fails(hass): await setup.async_setup_component(hass, "persistent_notification", {}) with patch( - "august.authenticator_async.AuthenticatorAsync.async_authenticate", + "yalexs.authenticator_async.AuthenticatorAsync.async_authenticate", side_effect=ClientResponseError(None, None, status=401), ): await hass.config_entries.async_setup(config_entry.entry_id) @@ -178,7 +179,7 @@ async def test_bad_password(hass): await setup.async_setup_component(hass, "persistent_notification", {}) with patch( - "august.authenticator_async.AuthenticatorAsync.async_authenticate", + "yalexs.authenticator_async.AuthenticatorAsync.async_authenticate", return_value=_mock_august_authentication( "original_token", 1234, AuthenticationState.BAD_PASSWORD ), @@ -206,7 +207,7 @@ async def test_http_failure(hass): await setup.async_setup_component(hass, "persistent_notification", {}) with patch( - "august.authenticator_async.AuthenticatorAsync.async_authenticate", + "yalexs.authenticator_async.AuthenticatorAsync.async_authenticate", side_effect=ClientResponseError(None, None, status=500), ): await hass.config_entries.async_setup(config_entry.entry_id) @@ -230,7 +231,7 @@ async def test_unknown_auth_state(hass): await setup.async_setup_component(hass, "persistent_notification", {}) with patch( - "august.authenticator_async.AuthenticatorAsync.async_authenticate", + "yalexs.authenticator_async.AuthenticatorAsync.async_authenticate", return_value=_mock_august_authentication("original_token", 1234, None), ): await hass.config_entries.async_setup(config_entry.entry_id) @@ -256,7 +257,7 @@ async def test_requires_validation_state(hass): await setup.async_setup_component(hass, "persistent_notification", {}) with patch( - "august.authenticator_async.AuthenticatorAsync.async_authenticate", + "yalexs.authenticator_async.AuthenticatorAsync.async_authenticate", return_value=_mock_august_authentication( "original_token", 1234, AuthenticationState.REQUIRES_VALIDATION ), @@ -268,3 +269,18 @@ async def test_requires_validation_state(hass): assert len(hass.config_entries.flow.async_progress()) == 1 assert hass.config_entries.flow.async_progress()[0]["context"]["source"] == "reauth" + + +async def test_load_unload(hass): + """Config entry can be unloaded.""" + + august_operative_lock = await _mock_operative_august_lock_detail(hass) + august_inoperative_lock = await _mock_inoperative_august_lock_detail(hass) + config_entry = await _create_august_with_devices( + hass, [august_operative_lock, august_inoperative_lock] + ) + + assert config_entry.state == ENTRY_STATE_LOADED + + await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/components/august/test_lock.py b/tests/components/august/test_lock.py index dadd6de2d4f..5b3c163780f 100644 --- a/tests/components/august/test_lock.py +++ b/tests/components/august/test_lock.py @@ -1,15 +1,23 @@ """The lock tests for the august platform.""" +import datetime +from unittest.mock import Mock + +from yalexs.pubnub_async import AugustPubNub + from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_LOCK, SERVICE_UNLOCK, STATE_LOCKED, + STATE_UNAVAILABLE, STATE_UNKNOWN, STATE_UNLOCKED, ) from homeassistant.helpers import device_registry as dr, entity_registry as er +import homeassistant.util.dt as dt_util +from tests.common import async_fire_time_changed from tests.components.august.mocks import ( _create_august_with_devices, _mock_activities_from_fixture, @@ -112,3 +120,116 @@ async def test_one_lock_unknown_state(hass): lock_brokenid_name = hass.states.get("lock.brokenid_name") assert lock_brokenid_name.state == STATE_UNKNOWN + + +async def test_lock_bridge_offline(hass): + """Test creation of a lock with doorsense and bridge that goes offline.""" + lock_one = await _mock_doorsense_enabled_august_lock_detail(hass) + + activities = await _mock_activities_from_fixture( + hass, "get_activity.bridge_offline.json" + ) + await _create_august_with_devices(hass, [lock_one], activities=activities) + + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + + assert lock_online_with_doorsense_name.state == STATE_UNAVAILABLE + + +async def test_lock_bridge_online(hass): + """Test creation of a lock with doorsense and bridge that goes offline.""" + lock_one = await _mock_doorsense_enabled_august_lock_detail(hass) + + activities = await _mock_activities_from_fixture( + hass, "get_activity.bridge_online.json" + ) + await _create_august_with_devices(hass, [lock_one], activities=activities) + + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + + assert lock_online_with_doorsense_name.state == STATE_LOCKED + + +async def test_lock_update_via_pubnub(hass): + """Test creation of a lock with doorsense and bridge.""" + lock_one = await _mock_doorsense_enabled_august_lock_detail(hass) + assert lock_one.pubsub_channel == "pubsub" + pubnub = AugustPubNub() + + activities = await _mock_activities_from_fixture(hass, "get_activity.lock.json") + config_entry = await _create_august_with_devices( + hass, [lock_one], activities=activities, pubnub=pubnub + ) + + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + + assert lock_online_with_doorsense_name.state == STATE_LOCKED + + pubnub.message( + pubnub, + Mock( + channel=lock_one.pubsub_channel, + timetoken=dt_util.utcnow().timestamp() * 10000000, + message={ + "status": "kAugLockState_Unlocking", + }, + ), + ) + + await hass.async_block_till_done() + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + assert lock_online_with_doorsense_name.state == STATE_UNLOCKED + + pubnub.message( + pubnub, + Mock( + channel=lock_one.pubsub_channel, + timetoken=dt_util.utcnow().timestamp() * 10000000, + message={ + "status": "kAugLockState_Locking", + }, + ), + ) + + await hass.async_block_till_done() + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + assert lock_online_with_doorsense_name.state == STATE_LOCKED + + async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(seconds=30)) + await hass.async_block_till_done() + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + assert lock_online_with_doorsense_name.state == STATE_LOCKED + + pubnub.connected = True + async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(seconds=30)) + await hass.async_block_till_done() + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + assert lock_online_with_doorsense_name.state == STATE_LOCKED + + # Ensure pubnub status is always preserved + async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(hours=2)) + await hass.async_block_till_done() + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + assert lock_online_with_doorsense_name.state == STATE_LOCKED + + pubnub.message( + pubnub, + Mock( + channel=lock_one.pubsub_channel, + timetoken=dt_util.utcnow().timestamp() * 10000000, + message={ + "status": "kAugLockState_Unlocking", + }, + ), + ) + await hass.async_block_till_done() + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + assert lock_online_with_doorsense_name.state == STATE_UNLOCKED + + async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(hours=4)) + await hass.async_block_till_done() + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + assert lock_online_with_doorsense_name.state == STATE_UNLOCKED + + await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/fixtures/august/get_activity.bridge_offline.json b/tests/fixtures/august/get_activity.bridge_offline.json new file mode 100644 index 00000000000..ed4aaadaf73 --- /dev/null +++ b/tests/fixtures/august/get_activity.bridge_offline.json @@ -0,0 +1,34 @@ +[{ + "entities" : { + "activity" : "mockActivity2", + "house" : "123", + "device" : "online_with_doorsense", + "callingUser" : "mockUserId2", + "otherUser" : "deleted" + }, + "callingUser" : { + "LastName" : "elven princess", + "UserID" : "mockUserId2", + "FirstName" : "Your favorite" + }, + "otherUser" : { + "LastName" : "User", + "UserName" : "deleteduser", + "FirstName" : "Unknown", + "UserID" : "deleted", + "PhoneNo" : "deleted" + }, + "deviceType" : "lock", + "deviceName" : "MockHouseTDoor", + "action" : "associated_bridge_offline", + "dateTime" : 1582007218000, + "info" : { + "remote" : true, + "DateLogActionID" : "ABC+Time" + }, + "deviceID" : "online_with_doorsense", + "house" : { + "houseName" : "MockHouse", + "houseID" : "123" + } +}] diff --git a/tests/fixtures/august/get_activity.bridge_online.json b/tests/fixtures/august/get_activity.bridge_online.json new file mode 100644 index 00000000000..db14f06cfe9 --- /dev/null +++ b/tests/fixtures/august/get_activity.bridge_online.json @@ -0,0 +1,34 @@ +[{ + "entities" : { + "activity" : "mockActivity2", + "house" : "123", + "device" : "online_with_doorsense", + "callingUser" : "mockUserId2", + "otherUser" : "deleted" + }, + "callingUser" : { + "LastName" : "elven princess", + "UserID" : "mockUserId2", + "FirstName" : "Your favorite" + }, + "otherUser" : { + "LastName" : "User", + "UserName" : "deleteduser", + "FirstName" : "Unknown", + "UserID" : "deleted", + "PhoneNo" : "deleted" + }, + "deviceType" : "lock", + "deviceName" : "MockHouseTDoor", + "action" : "associated_bridge_online", + "dateTime" : 1582007218000, + "info" : { + "remote" : true, + "DateLogActionID" : "ABC+Time" + }, + "deviceID" : "online_with_doorsense", + "house" : { + "houseName" : "MockHouse", + "houseID" : "123" + } +}] diff --git a/tests/fixtures/august/get_doorbell.json b/tests/fixtures/august/get_doorbell.json index abe6e37b1e3..fb2cd5780c9 100644 --- a/tests/fixtures/august/get_doorbell.json +++ b/tests/fixtures/august/get_doorbell.json @@ -55,7 +55,7 @@ "reconnect" ], "doorbellID" : "K98GiDT45GUL", - "HouseID" : "3dd2accaea08", + "HouseID" : "mockhouseid1", "telemetry" : { "signal_level" : -56, "date" : "2017-12-10 08:05:12", diff --git a/tests/fixtures/august/get_lock.online_with_doorsense.json b/tests/fixtures/august/get_lock.online_with_doorsense.json index f7376570482..e29614c9e48 100644 --- a/tests/fixtures/august/get_lock.online_with_doorsense.json +++ b/tests/fixtures/august/get_lock.online_with_doorsense.json @@ -13,9 +13,10 @@ "updated" : "2000-00-00T00:00:00.447Z" } }, + "pubsubChannel":"pubsub", "Calibrated" : false, "Created" : "2000-00-00T00:00:00.447Z", - "HouseID" : "123", + "HouseID" : "mockhouseid1", "HouseName" : "Test", "LockID" : "online_with_doorsense", "LockName" : "Online door with doorsense", From 6b93c4073d0f782c95e8159a8a532fdc98a7c8ca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Mar 2021 21:17:04 -1000 Subject: [PATCH 532/831] Ensure homekit yaml config works when there is an ignored config entry (#48175) --- homeassistant/components/homekit/config_flow.py | 4 ++-- tests/components/homekit/test_config_flow.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index 1fc99bb8585..683e533d2df 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -223,7 +223,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Return a set of bridge names.""" return { entry.data[CONF_NAME] - for entry in self._async_current_entries() + for entry in self._async_current_entries(include_ignore=False) if CONF_NAME in entry.data } @@ -251,7 +251,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): port = user_input[CONF_PORT] return not any( entry.data[CONF_NAME] == name or entry.data[CONF_PORT] == port - for entry in self._async_current_entries() + for entry in self._async_current_entries(include_ignore=False) ) @staticmethod diff --git a/tests/components/homekit/test_config_flow.py b/tests/components/homekit/test_config_flow.py index 3d94672cd8a..26804675265 100644 --- a/tests/components/homekit/test_config_flow.py +++ b/tests/components/homekit/test_config_flow.py @@ -5,7 +5,7 @@ import pytest from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.homekit.const import DOMAIN, SHORT_BRIDGE_NAME -from homeassistant.config_entries import SOURCE_IMPORT +from homeassistant.config_entries import SOURCE_IGNORE, SOURCE_IMPORT from homeassistant.const import CONF_NAME, CONF_PORT from tests.common import MockConfigEntry @@ -229,6 +229,8 @@ async def test_import(hass): """Test we can import instance.""" await setup.async_setup_component(hass, "persistent_notification", {}) + ignored_entry = MockConfigEntry(domain=DOMAIN, data={}, source=SOURCE_IGNORE) + ignored_entry.add_to_hass(hass) entry = MockConfigEntry( domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} ) From f67e8b43690b0836ff8ee60ce90b9e015123badc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 22 Mar 2021 00:22:32 -0700 Subject: [PATCH 533/831] Populate trigger variable when manually triggering automation (#48202) * Populate trigger variable when manually triggering automation * Update tests/components/automation/test_init.py Co-authored-by: Erik Montnemery --- .../components/automation/__init__.py | 2 +- tests/components/automation/test_init.py | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 4c278cbf5f0..79c6dcc2312 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -189,7 +189,7 @@ async def async_setup(hass, config): async def trigger_service_handler(entity, service_call): """Handle forced automation trigger, e.g. from frontend.""" await entity.async_trigger( - service_call.data[ATTR_VARIABLES], + {**service_call.data[ATTR_VARIABLES], "trigger": {"platform": None}}, skip_condition=service_call.data[CONF_SKIP_CONDITION], context=service_call.context, ) diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 91531481a99..71727258fcc 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1355,3 +1355,33 @@ async def test_blueprint_automation(hass, calls): assert automation.entities_in_automation(hass, "automation.automation_0") == [ "light.kitchen" ] + + +async def test_trigger_service(hass, calls): + """Test the automation trigger service.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "alias": "hello", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": { + "service": "test.automation", + "data_template": {"trigger": "{{ trigger }}"}, + }, + } + }, + ) + context = Context() + await hass.services.async_call( + "automation", + "trigger", + {"entity_id": "automation.hello"}, + blocking=True, + context=context, + ) + + assert len(calls) == 1 + assert calls[0].data.get("trigger") == {"platform": None} + assert calls[0].context.parent_id is context.id From 136ac88bed6bc1214e2f4350674450467e7c0087 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Mar 2021 00:19:05 -1000 Subject: [PATCH 534/831] Bump yalexs to 1.1.5 for august (#48205) Turns on auto-reconnect support --- homeassistant/components/august/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index 3a156a189c7..feadf8f6218 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -2,7 +2,7 @@ "domain": "august", "name": "August", "documentation": "https://www.home-assistant.io/integrations/august", - "requirements": ["yalexs==1.1.4"], + "requirements": ["yalexs==1.1.5"], "codeowners": ["@bdraco"], "dhcp": [ {"hostname":"connect","macaddress":"D86162*"}, diff --git a/requirements_all.txt b/requirements_all.txt index dbaee574184..7cef3f49064 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2345,7 +2345,7 @@ xs1-api-client==3.0.0 yalesmartalarmclient==0.1.6 # homeassistant.components.august -yalexs==1.1.4 +yalexs==1.1.5 # homeassistant.components.yeelight yeelight==0.5.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 20bd6547b05..fb42375de7b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1206,7 +1206,7 @@ xbox-webapi==2.0.8 xmltodict==0.12.0 # homeassistant.components.august -yalexs==1.1.4 +yalexs==1.1.5 # homeassistant.components.yeelight yeelight==0.5.4 From 834fc1ae141731e9b3041a9236d7ff36a6d88e78 Mon Sep 17 00:00:00 2001 From: Greg Dowling Date: Mon, 22 Mar 2021 11:01:17 +0000 Subject: [PATCH 535/831] Remove vera should_poll (#48209) --- homeassistant/components/vera/__init__.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index e544ddb925f..3654db5072d 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -246,11 +246,6 @@ class VeraDevice(Generic[DeviceType], Entity): """Return the name of the device.""" return self._name - @property - def should_poll(self) -> bool: - """Get polling requirement from vera device.""" - return self.vera_device.should_poll - @property def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the device.""" From 1bb29bffbb27fa9f3cc5954f33338daab76d0ade Mon Sep 17 00:00:00 2001 From: Sean Wilson Date: Mon, 22 Mar 2021 07:12:14 -0400 Subject: [PATCH 536/831] Update aqualogic library to v2.6 (#48119) --- homeassistant/components/aqualogic/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/aqualogic/manifest.json b/homeassistant/components/aqualogic/manifest.json index 2a8e2a78cac..5a753342b2b 100644 --- a/homeassistant/components/aqualogic/manifest.json +++ b/homeassistant/components/aqualogic/manifest.json @@ -2,6 +2,6 @@ "domain": "aqualogic", "name": "AquaLogic", "documentation": "https://www.home-assistant.io/integrations/aqualogic", - "requirements": ["aqualogic==1.0"], + "requirements": ["aqualogic==2.6"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 7cef3f49064..23c66f79e59 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -269,7 +269,7 @@ apprise==0.8.9 aprslib==0.6.46 # homeassistant.components.aqualogic -aqualogic==1.0 +aqualogic==2.6 # homeassistant.components.arcam_fmj arcam-fmj==0.5.3 From e0cd7072d6480ff314e4cf1935decc9a22d46a9e Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 12:37:16 +0100 Subject: [PATCH 537/831] Migrate integrations a-c to extend SensorEntity (#48210) --- homeassistant/components/abode/sensor.py | 3 ++- homeassistant/components/accuweather/sensor.py | 3 ++- homeassistant/components/acmeda/sensor.py | 3 ++- homeassistant/components/adguard/sensor.py | 3 ++- homeassistant/components/ads/sensor.py | 4 ++-- homeassistant/components/advantage_air/sensor.py | 7 ++++--- homeassistant/components/aemet/sensor.py | 6 ++++-- homeassistant/components/aftership/sensor.py | 5 ++--- homeassistant/components/airly/sensor.py | 3 ++- homeassistant/components/airnow/sensor.py | 3 ++- homeassistant/components/airvisual/sensor.py | 5 +++-- homeassistant/components/alarmdecoder/sensor.py | 4 ++-- homeassistant/components/alpha_vantage/sensor.py | 7 +++---- homeassistant/components/ambient_station/sensor.py | 3 ++- homeassistant/components/amcrest/sensor.py | 4 ++-- homeassistant/components/android_ip_webcam/sensor.py | 3 ++- homeassistant/components/apcupsd/sensor.py | 5 ++--- homeassistant/components/aqualogic/sensor.py | 5 ++--- homeassistant/components/arduino/sensor.py | 5 ++--- homeassistant/components/arest/sensor.py | 5 ++--- homeassistant/components/arlo/sensor.py | 5 ++--- homeassistant/components/arwn/sensor.py | 4 ++-- homeassistant/components/asuswrt/sensor.py | 3 ++- homeassistant/components/atag/sensor.py | 3 ++- homeassistant/components/atome/sensor.py | 5 ++--- homeassistant/components/august/sensor.py | 7 +++---- homeassistant/components/aurora/sensor.py | 3 ++- homeassistant/components/aurora_abb_powerone/sensor.py | 5 ++--- homeassistant/components/awair/sensor.py | 4 ++-- homeassistant/components/azure_devops/sensor.py | 3 ++- homeassistant/components/bbox/sensor.py | 7 +++---- homeassistant/components/beewi_smartclim/sensor.py | 5 ++--- homeassistant/components/bh1750/sensor.py | 5 ++--- homeassistant/components/bitcoin/sensor.py | 5 ++--- homeassistant/components/bizkaibus/sensor.py | 5 ++--- homeassistant/components/blebox/sensor.py | 4 ++-- homeassistant/components/blink/sensor.py | 4 ++-- homeassistant/components/blockchain/sensor.py | 5 ++--- homeassistant/components/bloomsky/sensor.py | 5 ++--- homeassistant/components/bme280/sensor.py | 5 ++--- homeassistant/components/bme680/sensor.py | 5 ++--- homeassistant/components/bmp280/sensor.py | 4 ++-- homeassistant/components/bmw_connected_drive/sensor.py | 4 ++-- homeassistant/components/broadlink/sensor.py | 4 ++-- homeassistant/components/brother/sensor.py | 3 ++- homeassistant/components/brottsplatskartan/sensor.py | 5 ++--- homeassistant/components/buienradar/sensor.py | 5 ++--- homeassistant/components/canary/sensor.py | 3 ++- homeassistant/components/cert_expiry/sensor.py | 4 ++-- homeassistant/components/citybikes/sensor.py | 10 +++++++--- homeassistant/components/co2signal/sensor.py | 5 ++--- homeassistant/components/coinbase/sensor.py | 6 +++--- .../components/comed_hourly_pricing/sensor.py | 5 ++--- homeassistant/components/comfoconnect/sensor.py | 5 ++--- homeassistant/components/command_line/sensor.py | 5 ++--- homeassistant/components/coronavirus/sensor.py | 3 ++- homeassistant/components/cpuspeed/sensor.py | 5 ++--- homeassistant/components/cups/sensor.py | 9 ++++----- homeassistant/components/currencylayer/sensor.py | 5 ++--- homeassistant/components/sensor/__init__.py | 5 +++++ 60 files changed, 139 insertions(+), 139 deletions(-) diff --git a/homeassistant/components/abode/sensor.py b/homeassistant/components/abode/sensor.py index 6ecc5c871cd..993228c36ac 100644 --- a/homeassistant/components/abode/sensor.py +++ b/homeassistant/components/abode/sensor.py @@ -1,6 +1,7 @@ """Support for Abode Security System sensors.""" import abodepy.helpers.constants as CONST +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, @@ -33,7 +34,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class AbodeSensor(AbodeDevice): +class AbodeSensor(SensorEntity, AbodeDevice): """A sensor implementation for Abode devices.""" def __init__(self, data, device, sensor_type): diff --git a/homeassistant/components/accuweather/sensor.py b/homeassistant/components/accuweather/sensor.py index aa7f604e27f..89147cc7104 100644 --- a/homeassistant/components/accuweather/sensor.py +++ b/homeassistant/components/accuweather/sensor.py @@ -1,4 +1,5 @@ """Support for the AccuWeather service.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_DEVICE_CLASS, @@ -48,7 +49,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class AccuWeatherSensor(CoordinatorEntity): +class AccuWeatherSensor(SensorEntity, CoordinatorEntity): """Define an AccuWeather entity.""" def __init__(self, name, kind, coordinator, forecast_day=None): diff --git a/homeassistant/components/acmeda/sensor.py b/homeassistant/components/acmeda/sensor.py index f427548ab94..f1961b85589 100644 --- a/homeassistant/components/acmeda/sensor.py +++ b/homeassistant/components/acmeda/sensor.py @@ -1,4 +1,5 @@ """Support for Acmeda Roller Blind Batteries.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -29,7 +30,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class AcmedaBattery(AcmedaBase): +class AcmedaBattery(SensorEntity, AcmedaBase): """Representation of a Acmeda cover device.""" device_class = DEVICE_CLASS_BATTERY diff --git a/homeassistant/components/adguard/sensor.py b/homeassistant/components/adguard/sensor.py index 3a93d9fbb35..6f2f0f54beb 100644 --- a/homeassistant/components/adguard/sensor.py +++ b/homeassistant/components/adguard/sensor.py @@ -6,6 +6,7 @@ from typing import Callable from adguardhome import AdGuardHome, AdGuardHomeConnectionError +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE, TIME_MILLISECONDS from homeassistant.core import HomeAssistant @@ -48,7 +49,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class AdGuardHomeSensor(AdGuardHomeDeviceEntity): +class AdGuardHomeSensor(SensorEntity, AdGuardHomeDeviceEntity): """Defines a AdGuard Home sensor.""" def __init__( diff --git a/homeassistant/components/ads/sensor.py b/homeassistant/components/ads/sensor.py index 2f411b27723..6e7c69d3c2d 100644 --- a/homeassistant/components/ads/sensor.py +++ b/homeassistant/components/ads/sensor.py @@ -2,7 +2,7 @@ import voluptuous as vol from homeassistant.components import ads -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT import homeassistant.helpers.config_validation as cv @@ -43,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([entity]) -class AdsSensor(AdsEntity): +class AdsSensor(SensorEntity, AdsEntity): """Representation of an ADS sensor entity.""" def __init__(self, ads_hub, ads_var, ads_type, name, unit_of_measurement, factor): diff --git a/homeassistant/components/advantage_air/sensor.py b/homeassistant/components/advantage_air/sensor.py index d25ce4888fc..cef70ebb245 100644 --- a/homeassistant/components/advantage_air/sensor.py +++ b/homeassistant/components/advantage_air/sensor.py @@ -1,6 +1,7 @@ """Sensor platform for Advantage Air integration.""" import voluptuous as vol +from homeassistant.components.sensor import SensorEntity from homeassistant.const import PERCENTAGE from homeassistant.helpers import config_validation as cv, entity_platform @@ -40,7 +41,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class AdvantageAirTimeTo(AdvantageAirEntity): +class AdvantageAirTimeTo(SensorEntity, AdvantageAirEntity): """Representation of Advantage Air timer control.""" def __init__(self, instance, ac_key, action): @@ -82,7 +83,7 @@ class AdvantageAirTimeTo(AdvantageAirEntity): await self.async_change({self.ac_key: {"info": {self._time_key: value}}}) -class AdvantageAirZoneVent(AdvantageAirEntity): +class AdvantageAirZoneVent(SensorEntity, AdvantageAirEntity): """Representation of Advantage Air Zone Vent Sensor.""" @property @@ -115,7 +116,7 @@ class AdvantageAirZoneVent(AdvantageAirEntity): return "mdi:fan-off" -class AdvantageAirZoneSignal(AdvantageAirEntity): +class AdvantageAirZoneSignal(SensorEntity, AdvantageAirEntity): """Representation of Advantage Air Zone wireless signal sensor.""" @property diff --git a/homeassistant/components/aemet/sensor.py b/homeassistant/components/aemet/sensor.py index b57de1ce890..400952830cc 100644 --- a/homeassistant/components/aemet/sensor.py +++ b/homeassistant/components/aemet/sensor.py @@ -1,4 +1,6 @@ """Support for the AEMET OpenData service.""" +from homeassistant.components.sensor import SensorEntity + from .abstract_aemet_sensor import AbstractAemetSensor from .const import ( DOMAIN, @@ -56,7 +58,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class AemetSensor(AbstractAemetSensor): +class AemetSensor(SensorEntity, AbstractAemetSensor): """Implementation of an AEMET OpenData sensor.""" def __init__( @@ -79,7 +81,7 @@ class AemetSensor(AbstractAemetSensor): return self._weather_coordinator.data.get(self._sensor_type) -class AemetForecastSensor(AbstractAemetSensor): +class AemetForecastSensor(SensorEntity, AbstractAemetSensor): """Implementation of an AEMET OpenData forecast sensor.""" def __init__( diff --git a/homeassistant/components/aftership/sensor.py b/homeassistant/components/aftership/sensor.py index 754b3ce72eb..a5ffc511a26 100644 --- a/homeassistant/components/aftership/sensor.py +++ b/homeassistant/components/aftership/sensor.py @@ -5,12 +5,11 @@ import logging from pyaftership.tracker import Tracking import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME, HTTP_OK from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from .const import DOMAIN @@ -108,7 +107,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class AfterShipSensor(Entity): +class AfterShipSensor(SensorEntity): """Representation of a AfterShip sensor.""" def __init__(self, aftership, name): diff --git a/homeassistant/components/airly/sensor.py b/homeassistant/components/airly/sensor.py index dae167559e0..efadeac94e1 100644 --- a/homeassistant/components/airly/sensor.py +++ b/homeassistant/components/airly/sensor.py @@ -1,4 +1,5 @@ """Support for the Airly sensor service.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_DEVICE_CLASS, @@ -72,7 +73,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class AirlySensor(CoordinatorEntity): +class AirlySensor(SensorEntity, CoordinatorEntity): """Define an Airly sensor.""" def __init__(self, coordinator, name, kind): diff --git a/homeassistant/components/airnow/sensor.py b/homeassistant/components/airnow/sensor.py index ea2ff9856fb..f8f5eff6cc7 100644 --- a/homeassistant/components/airnow/sensor.py +++ b/homeassistant/components/airnow/sensor.py @@ -1,4 +1,5 @@ """Support for the AirNow sensor service.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_DEVICE_CLASS, @@ -59,7 +60,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class AirNowSensor(CoordinatorEntity): +class AirNowSensor(SensorEntity, CoordinatorEntity): """Define an AirNow sensor.""" def __init__(self, coordinator, kind): diff --git a/homeassistant/components/airvisual/sensor.py b/homeassistant/components/airvisual/sensor.py index 3c1aef128ab..c4cbf3af22e 100644 --- a/homeassistant/components/airvisual/sensor.py +++ b/homeassistant/components/airvisual/sensor.py @@ -1,4 +1,5 @@ """Support for AirVisual air quality sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_LATITUDE, ATTR_LONGITUDE, @@ -138,7 +139,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, True) -class AirVisualGeographySensor(AirVisualEntity): +class AirVisualGeographySensor(SensorEntity, AirVisualEntity): """Define an AirVisual sensor related to geography data via the Cloud API.""" def __init__(self, coordinator, config_entry, kind, name, icon, unit, locale): @@ -236,7 +237,7 @@ class AirVisualGeographySensor(AirVisualEntity): self._attrs.pop(ATTR_LONGITUDE, None) -class AirVisualNodeProSensor(AirVisualEntity): +class AirVisualNodeProSensor(SensorEntity, AirVisualEntity): """Define an AirVisual sensor related to a Node/Pro unit.""" def __init__(self, coordinator, kind, name, device_class, unit): diff --git a/homeassistant/components/alarmdecoder/sensor.py b/homeassistant/components/alarmdecoder/sensor.py index a8aed8dac73..80b9c1261a3 100644 --- a/homeassistant/components/alarmdecoder/sensor.py +++ b/homeassistant/components/alarmdecoder/sensor.py @@ -1,6 +1,6 @@ """Support for AlarmDecoder sensors (Shows Panel Display).""" +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType from .const import SIGNAL_PANEL_MESSAGE @@ -16,7 +16,7 @@ async def async_setup_entry( return True -class AlarmDecoderSensor(Entity): +class AlarmDecoderSensor(SensorEntity): """Representation of an AlarmDecoder keypad.""" def __init__(self): diff --git a/homeassistant/components/alpha_vantage/sensor.py b/homeassistant/components/alpha_vantage/sensor.py index cd161b1870c..0788772a45b 100644 --- a/homeassistant/components/alpha_vantage/sensor.py +++ b/homeassistant/components/alpha_vantage/sensor.py @@ -6,10 +6,9 @@ from alpha_vantage.foreignexchange import ForeignExchange from alpha_vantage.timeseries import TimeSeries import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_CURRENCY, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -105,7 +104,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.debug("Setup completed") -class AlphaVantageSensor(Entity): +class AlphaVantageSensor(SensorEntity): """Representation of a Alpha Vantage sensor.""" def __init__(self, timeseries, symbol): @@ -156,7 +155,7 @@ class AlphaVantageSensor(Entity): _LOGGER.debug("Received new values for symbol %s", self._symbol) -class AlphaVantageForeignExchange(Entity): +class AlphaVantageForeignExchange(SensorEntity): """Sensor for foreign exchange rates.""" def __init__(self, foreign_exchange, config): diff --git a/homeassistant/components/ambient_station/sensor.py b/homeassistant/components/ambient_station/sensor.py index 184927a1ef5..7e8f3d26ec6 100644 --- a/homeassistant/components/ambient_station/sensor.py +++ b/homeassistant/components/ambient_station/sensor.py @@ -1,5 +1,6 @@ """Support for Ambient Weather Station sensors.""" from homeassistant.components.binary_sensor import DOMAIN as SENSOR +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_NAME from homeassistant.core import callback @@ -36,7 +37,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensor_list, True) -class AmbientWeatherSensor(AmbientWeatherEntity): +class AmbientWeatherSensor(SensorEntity, AmbientWeatherEntity): """Define an Ambient sensor.""" def __init__( diff --git a/homeassistant/components/amcrest/sensor.py b/homeassistant/components/amcrest/sensor.py index 15a610b23b4..a30de62494e 100644 --- a/homeassistant/components/amcrest/sensor.py +++ b/homeassistant/components/amcrest/sensor.py @@ -4,9 +4,9 @@ import logging from amcrest import AmcrestError +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME, CONF_SENSORS, PERCENTAGE from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from .const import DATA_AMCREST, DEVICES, SENSOR_SCAN_INTERVAL_SECS, SERVICE_UPDATE from .helpers import log_update_error, service_signal @@ -40,7 +40,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class AmcrestSensor(Entity): +class AmcrestSensor(SensorEntity): """A sensor implementation for Amcrest IP camera.""" def __init__(self, name, device, sensor_type): diff --git a/homeassistant/components/android_ip_webcam/sensor.py b/homeassistant/components/android_ip_webcam/sensor.py index 05c1fe16c61..de029c39aba 100644 --- a/homeassistant/components/android_ip_webcam/sensor.py +++ b/homeassistant/components/android_ip_webcam/sensor.py @@ -1,4 +1,5 @@ """Support for Android IP Webcam sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.helpers.icon import icon_for_battery_level from . import ( @@ -30,7 +31,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(all_sensors, True) -class IPWebcamSensor(AndroidIPCamEntity): +class IPWebcamSensor(SensorEntity, AndroidIPCamEntity): """Representation of a IP Webcam sensor.""" def __init__(self, name, host, ipcam, sensor): diff --git a/homeassistant/components/apcupsd/sensor.py b/homeassistant/components/apcupsd/sensor.py index 55e6d8c7a56..36dc1155b7f 100644 --- a/homeassistant/components/apcupsd/sensor.py +++ b/homeassistant/components/apcupsd/sensor.py @@ -4,7 +4,7 @@ import logging from apcaccess.status import ALL_UNITS import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_RESOURCES, ELECTRICAL_CURRENT_AMPERE, @@ -18,7 +18,6 @@ from homeassistant.const import ( VOLT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from . import DOMAIN @@ -156,7 +155,7 @@ def infer_unit(value): return value, None -class APCUPSdSensor(Entity): +class APCUPSdSensor(SensorEntity): """Representation of a sensor entity for APCUPSd status values.""" def __init__(self, data, sensor_type): diff --git a/homeassistant/components/aqualogic/sensor.py b/homeassistant/components/aqualogic/sensor.py index f92cd11c011..315b039f778 100644 --- a/homeassistant/components/aqualogic/sensor.py +++ b/homeassistant/components/aqualogic/sensor.py @@ -2,7 +2,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_CONDITIONS, PERCENTAGE, @@ -12,7 +12,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from . import DOMAIN, UPDATE_TOPIC @@ -56,7 +55,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors) -class AquaLogicSensor(Entity): +class AquaLogicSensor(SensorEntity): """Sensor implementation for the AquaLogic component.""" def __init__(self, processor, sensor_type): diff --git a/homeassistant/components/arduino/sensor.py b/homeassistant/components/arduino/sensor.py index f31272b3a20..588a652660a 100644 --- a/homeassistant/components/arduino/sensor.py +++ b/homeassistant/components/arduino/sensor.py @@ -1,10 +1,9 @@ """Support for getting information from Arduino pins.""" import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from . import DOMAIN @@ -30,7 +29,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class ArduinoSensor(Entity): +class ArduinoSensor(SensorEntity): """Representation of an Arduino Sensor.""" def __init__(self, name, pin, pin_type, board): diff --git a/homeassistant/components/arest/sensor.py b/homeassistant/components/arest/sensor.py index d213a3d2903..061c15eafb0 100644 --- a/homeassistant/components/arest/sensor.py +++ b/homeassistant/components/arest/sensor.py @@ -5,7 +5,7 @@ import logging import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_VARIABLES, CONF_NAME, @@ -16,7 +16,6 @@ from homeassistant.const import ( ) from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -124,7 +123,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class ArestSensor(Entity): +class ArestSensor(SensorEntity): """Implementation of an aREST sensor for exposed variables.""" def __init__( diff --git a/homeassistant/components/arlo/sensor.py b/homeassistant/components/arlo/sensor.py index 1e0c55c5d31..c794bf1ef5e 100644 --- a/homeassistant/components/arlo/sensor.py +++ b/homeassistant/components/arlo/sensor.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONCENTRATION_PARTS_PER_MILLION, @@ -16,7 +16,6 @@ from homeassistant.const import ( from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from . import ATTRIBUTION, DATA_ARLO, DEFAULT_BRAND, SIGNAL_UPDATE_ARLO @@ -73,7 +72,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class ArloSensor(Entity): +class ArloSensor(SensorEntity): """An implementation of a Netgear Arlo IP sensor.""" def __init__(self, name, device, sensor_type): diff --git a/homeassistant/components/arwn/sensor.py b/homeassistant/components/arwn/sensor.py index d68549ea95c..ba9166d1af5 100644 --- a/homeassistant/components/arwn/sensor.py +++ b/homeassistant/components/arwn/sensor.py @@ -3,9 +3,9 @@ import json import logging from homeassistant.components import mqtt +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEGREE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from homeassistant.util import slugify _LOGGER = logging.getLogger(__name__) @@ -114,7 +114,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= return True -class ArwnSensor(Entity): +class ArwnSensor(SensorEntity): """Representation of an ARWN sensor.""" def __init__(self, topic, name, state_key, units, icon=None): diff --git a/homeassistant/components/asuswrt/sensor.py b/homeassistant/components/asuswrt/sensor.py index 7dc8208ee67..30f4163d01b 100644 --- a/homeassistant/components/asuswrt/sensor.py +++ b/homeassistant/components/asuswrt/sensor.py @@ -4,6 +4,7 @@ from __future__ import annotations import logging from numbers import Number +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import DATA_GIGABYTES, DATA_RATE_MEGABITS_PER_SECOND from homeassistant.helpers.typing import HomeAssistantType @@ -97,7 +98,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class AsusWrtSensor(CoordinatorEntity): +class AsusWrtSensor(SensorEntity, CoordinatorEntity): """Representation of a AsusWrt sensor.""" def __init__( diff --git a/homeassistant/components/atag/sensor.py b/homeassistant/components/atag/sensor.py index 3123e245711..163ed669be4 100644 --- a/homeassistant/components/atag/sensor.py +++ b/homeassistant/components/atag/sensor.py @@ -1,4 +1,5 @@ """Initialization of ATAG One sensor platform.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, @@ -29,7 +30,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities([AtagSensor(coordinator, sensor) for sensor in SENSORS]) -class AtagSensor(AtagEntity): +class AtagSensor(SensorEntity, AtagEntity): """Representation of a AtagOne Sensor.""" def __init__(self, coordinator, sensor): diff --git a/homeassistant/components/atome/sensor.py b/homeassistant/components/atome/sensor.py index e50f7cd5de6..d10024f64c2 100644 --- a/homeassistant/components/atome/sensor.py +++ b/homeassistant/components/atome/sensor.py @@ -5,7 +5,7 @@ import logging from pyatome.client import AtomeClient, PyAtomeError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, @@ -15,7 +15,6 @@ from homeassistant.const import ( POWER_WATT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -215,7 +214,7 @@ class AtomeData: _LOGGER.error("Missing last value in values: %s: %s", values, error) -class AtomeSensor(Entity): +class AtomeSensor(SensorEntity): """Representation of a sensor entity for Atome.""" def __init__(self, data, name, sensor_type): diff --git a/homeassistant/components/august/sensor.py b/homeassistant/components/august/sensor.py index 1841b2cef56..9b8352a0b96 100644 --- a/homeassistant/components/august/sensor.py +++ b/homeassistant/components/august/sensor.py @@ -3,10 +3,9 @@ import logging from yalexs.activity import ActivityType -from homeassistant.components.sensor import DEVICE_CLASS_BATTERY +from homeassistant.components.sensor import DEVICE_CLASS_BATTERY, SensorEntity from homeassistant.const import ATTR_ENTITY_PICTURE, PERCENTAGE, STATE_UNAVAILABLE from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.helpers.restore_state import RestoreEntity @@ -118,7 +117,7 @@ async def _async_migrate_old_unique_ids(hass, devices): registry.async_update_entity(old_entity_id, new_unique_id=device.unique_id) -class AugustOperatorSensor(AugustEntityMixin, RestoreEntity, Entity): +class AugustOperatorSensor(SensorEntity, AugustEntityMixin, RestoreEntity): """Representation of an August lock operation sensor.""" def __init__(self, data, device): @@ -217,7 +216,7 @@ class AugustOperatorSensor(AugustEntityMixin, RestoreEntity, Entity): return f"{self._device_id}_lock_operator" -class AugustBatterySensor(AugustEntityMixin, Entity): +class AugustBatterySensor(SensorEntity, AugustEntityMixin): """Representation of an August sensor.""" def __init__(self, data, sensor_type, device, old_device): diff --git a/homeassistant/components/aurora/sensor.py b/homeassistant/components/aurora/sensor.py index 731c6d08afd..1a34bb604da 100644 --- a/homeassistant/components/aurora/sensor.py +++ b/homeassistant/components/aurora/sensor.py @@ -1,4 +1,5 @@ """Support for Aurora Forecast sensor.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import PERCENTAGE from . import AuroraEntity @@ -18,7 +19,7 @@ async def async_setup_entry(hass, entry, async_add_entries): async_add_entries([entity]) -class AuroraSensor(AuroraEntity): +class AuroraSensor(SensorEntity, AuroraEntity): """Implementation of an aurora sensor.""" @property diff --git a/homeassistant/components/aurora_abb_powerone/sensor.py b/homeassistant/components/aurora_abb_powerone/sensor.py index 69a513dd8fb..f4640e7c014 100644 --- a/homeassistant/components/aurora_abb_powerone/sensor.py +++ b/homeassistant/components/aurora_abb_powerone/sensor.py @@ -5,7 +5,7 @@ import logging from aurorapy.client import AuroraError, AuroraSerialClient import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_ADDRESS, CONF_DEVICE, @@ -14,7 +14,6 @@ from homeassistant.const import ( POWER_WATT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -44,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class AuroraABBSolarPVMonitorSensor(Entity): +class AuroraABBSolarPVMonitorSensor(SensorEntity): """Representation of a Sensor.""" def __init__(self, client, name, typename): diff --git a/homeassistant/components/awair/sensor.py b/homeassistant/components/awair/sensor.py index 33ecaf46660..de39c20eb60 100644 --- a/homeassistant/components/awair/sensor.py +++ b/homeassistant/components/awair/sensor.py @@ -7,7 +7,7 @@ from python_awair.devices import AwairDevice import voluptuous as vol from homeassistant.components.awair import AwairDataUpdateCoordinator, AwairResult -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ATTR_ATTRIBUTION, ATTR_DEVICE_CLASS, CONF_ACCESS_TOKEN from homeassistant.helpers import device_registry as dr @@ -84,7 +84,7 @@ async def async_setup_entry( async_add_entities(sensors) -class AwairSensor(CoordinatorEntity): +class AwairSensor(SensorEntity, CoordinatorEntity): """Defines an Awair sensor entity.""" def __init__( diff --git a/homeassistant/components/azure_devops/sensor.py b/homeassistant/components/azure_devops/sensor.py index 1d30bfcb9f9..a0701ac9839 100644 --- a/homeassistant/components/azure_devops/sensor.py +++ b/homeassistant/components/azure_devops/sensor.py @@ -17,6 +17,7 @@ from homeassistant.components.azure_devops.const import ( DATA_PROJECT, DOMAIN, ) +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.typing import HomeAssistantType @@ -55,7 +56,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class AzureDevOpsSensor(AzureDevOpsDeviceEntity): +class AzureDevOpsSensor(SensorEntity, AzureDevOpsDeviceEntity): """Defines a Azure DevOps sensor.""" def __init__( diff --git a/homeassistant/components/bbox/sensor.py b/homeassistant/components/bbox/sensor.py index 8d6708c6342..5256c2a61a0 100644 --- a/homeassistant/components/bbox/sensor.py +++ b/homeassistant/components/bbox/sensor.py @@ -6,7 +6,7 @@ import pybbox import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_MONITORED_VARIABLES, @@ -15,7 +15,6 @@ from homeassistant.const import ( DEVICE_CLASS_TIMESTAMP, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.dt import utcnow @@ -86,7 +85,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class BboxUptimeSensor(Entity): +class BboxUptimeSensor(SensorEntity): """Bbox uptime sensor.""" def __init__(self, bbox_data, sensor_type, name): @@ -133,7 +132,7 @@ class BboxUptimeSensor(Entity): self._state = uptime.replace(microsecond=0).isoformat() -class BboxSensor(Entity): +class BboxSensor(SensorEntity): """Implementation of a Bbox sensor.""" def __init__(self, bbox_data, sensor_type, name): diff --git a/homeassistant/components/beewi_smartclim/sensor.py b/homeassistant/components/beewi_smartclim/sensor.py index e8a37d51be4..9bf935f3c4f 100644 --- a/homeassistant/components/beewi_smartclim/sensor.py +++ b/homeassistant/components/beewi_smartclim/sensor.py @@ -2,7 +2,7 @@ from beewi_smartclim import BeewiSmartClimPoller # pylint: disable=import-error import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MAC, CONF_NAME, @@ -13,7 +13,6 @@ from homeassistant.const import ( TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity # Default values DEFAULT_NAME = "BeeWi SmartClim" @@ -56,7 +55,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class BeewiSmartclimSensor(Entity): +class BeewiSmartclimSensor(SensorEntity): """Representation of a Sensor.""" def __init__(self, poller, name, mac, device, unit): diff --git a/homeassistant/components/bh1750/sensor.py b/homeassistant/components/bh1750/sensor.py index eb9bbc2dccc..5b708ae2630 100644 --- a/homeassistant/components/bh1750/sensor.py +++ b/homeassistant/components/bh1750/sensor.py @@ -6,10 +6,9 @@ from i2csense.bh1750 import BH1750 # pylint: disable=import-error import smbus import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, DEVICE_CLASS_ILLUMINANCE, LIGHT_LUX import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -94,7 +93,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(dev, True) -class BH1750Sensor(Entity): +class BH1750Sensor(SensorEntity): """Implementation of the BH1750 sensor.""" def __init__(self, bh1750_sensor, name, unit, multiplier=1.0): diff --git a/homeassistant/components/bitcoin/sensor.py b/homeassistant/components/bitcoin/sensor.py index f6f5a24d22f..4acce03d6fa 100644 --- a/homeassistant/components/bitcoin/sensor.py +++ b/homeassistant/components/bitcoin/sensor.py @@ -5,7 +5,7 @@ import logging from blockchain import exchangerates, statistics import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_CURRENCY, @@ -14,7 +14,6 @@ from homeassistant.const import ( TIME_SECONDS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -77,7 +76,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class BitcoinSensor(Entity): +class BitcoinSensor(SensorEntity): """Representation of a Bitcoin sensor.""" def __init__(self, data, option_type, currency): diff --git a/homeassistant/components/bizkaibus/sensor.py b/homeassistant/components/bizkaibus/sensor.py index c1cee98208c..a905498328b 100644 --- a/homeassistant/components/bizkaibus/sensor.py +++ b/homeassistant/components/bizkaibus/sensor.py @@ -2,10 +2,9 @@ from bizkaibus.bizkaibus import BizkaibusData import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, TIME_MINUTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity ATTR_DUE_IN = "Due in" @@ -33,7 +32,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([BizkaibusSensor(data, stop, route, name)], True) -class BizkaibusSensor(Entity): +class BizkaibusSensor(SensorEntity): """The class for handling the data.""" def __init__(self, data, stop, route, name): diff --git a/homeassistant/components/blebox/sensor.py b/homeassistant/components/blebox/sensor.py index 84f4c19371d..688d2931461 100644 --- a/homeassistant/components/blebox/sensor.py +++ b/homeassistant/components/blebox/sensor.py @@ -1,6 +1,6 @@ """BleBox sensor entities.""" -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from . import BleBoxEntity, create_blebox_entities from .const import BLEBOX_TO_HASS_DEVICE_CLASSES, BLEBOX_TO_UNIT_MAP @@ -14,7 +14,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class BleBoxSensorEntity(BleBoxEntity, Entity): +class BleBoxSensorEntity(SensorEntity, BleBoxEntity): """Representation of a BleBox sensor feature.""" @property diff --git a/homeassistant/components/blink/sensor.py b/homeassistant/components/blink/sensor.py index 3c3adf6d990..1ec61900091 100644 --- a/homeassistant/components/blink/sensor.py +++ b/homeassistant/components/blink/sensor.py @@ -1,13 +1,13 @@ """Support for Blink system camera sensors.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_TEMPERATURE, SIGNAL_STRENGTH_DECIBELS_MILLIWATT, TEMP_FAHRENHEIT, ) -from homeassistant.helpers.entity import Entity from .const import DOMAIN, TYPE_TEMPERATURE, TYPE_WIFI_STRENGTH @@ -34,7 +34,7 @@ async def async_setup_entry(hass, config, async_add_entities): async_add_entities(entities) -class BlinkSensor(Entity): +class BlinkSensor(SensorEntity): """A Blink camera sensor.""" def __init__(self, data, camera, sensor_type): diff --git a/homeassistant/components/blockchain/sensor.py b/homeassistant/components/blockchain/sensor.py index 790051dad96..3ecf4bee319 100644 --- a/homeassistant/components/blockchain/sensor.py +++ b/homeassistant/components/blockchain/sensor.py @@ -5,10 +5,9 @@ import logging from pyblockchain import get_balance, validate_address import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -44,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([BlockchainSensor(name, addresses)], True) -class BlockchainSensor(Entity): +class BlockchainSensor(SensorEntity): """Representation of a Blockchain.com sensor.""" def __init__(self, name, addresses): diff --git a/homeassistant/components/bloomsky/sensor.py b/homeassistant/components/bloomsky/sensor.py index df06e39db7e..4dc52e1a85c 100644 --- a/homeassistant/components/bloomsky/sensor.py +++ b/homeassistant/components/bloomsky/sensor.py @@ -1,7 +1,7 @@ """Support the sensor of a BloomSky weather station.""" import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( AREA_SQUARE_METERS, CONF_MONITORED_CONDITIONS, @@ -12,7 +12,6 @@ from homeassistant.const import ( TEMP_FAHRENHEIT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from . import DOMAIN @@ -70,7 +69,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([BloomSkySensor(bloomsky, device, variable)], True) -class BloomSkySensor(Entity): +class BloomSkySensor(SensorEntity): """Representation of a single sensor in a BloomSky device.""" def __init__(self, bs, device, sensor_name): diff --git a/homeassistant/components/bme280/sensor.py b/homeassistant/components/bme280/sensor.py index d3d97881035..ffe68479341 100644 --- a/homeassistant/components/bme280/sensor.py +++ b/homeassistant/components/bme280/sensor.py @@ -7,7 +7,7 @@ from i2csense.bme280 import BME280 # pylint: disable=import-error import smbus import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_CONDITIONS, CONF_NAME, @@ -15,7 +15,6 @@ from homeassistant.const import ( TEMP_FAHRENHEIT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.temperature import celsius_to_fahrenheit @@ -136,7 +135,7 @@ class BME280Handler: self.sensor.update(first_reading) -class BME280Sensor(Entity): +class BME280Sensor(SensorEntity): """Implementation of the BME280 sensor.""" def __init__(self, bme280_client, sensor_type, temp_unit, name): diff --git a/homeassistant/components/bme680/sensor.py b/homeassistant/components/bme680/sensor.py index 6af9d46c0bc..f3d6b9428ea 100644 --- a/homeassistant/components/bme680/sensor.py +++ b/homeassistant/components/bme680/sensor.py @@ -7,7 +7,7 @@ import bme680 # pylint: disable=import-error from smbus import SMBus import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_CONDITIONS, CONF_NAME, @@ -15,7 +15,6 @@ from homeassistant.const import ( TEMP_FAHRENHEIT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util.temperature import celsius_to_fahrenheit _LOGGER = logging.getLogger(__name__) @@ -316,7 +315,7 @@ class BME680Handler: return hum_score + gas_score -class BME680Sensor(Entity): +class BME680Sensor(SensorEntity): """Implementation of the BME680 sensor.""" def __init__(self, bme680_client, sensor_type, temp_unit, name): diff --git a/homeassistant/components/bmp280/sensor.py b/homeassistant/components/bmp280/sensor.py index 3c34408cb62..60cbdb75d41 100644 --- a/homeassistant/components/bmp280/sensor.py +++ b/homeassistant/components/bmp280/sensor.py @@ -11,11 +11,11 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, PLATFORM_SCHEMA, + SensorEntity, ) from homeassistant.const import CONF_NAME, PRESSURE_HPA, TEMP_CELSIUS from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -65,7 +65,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class Bmp280Sensor(Entity): +class Bmp280Sensor(SensorEntity): """Base class for BMP280 entities.""" def __init__( diff --git a/homeassistant/components/bmw_connected_drive/sensor.py b/homeassistant/components/bmw_connected_drive/sensor.py index 480aac34eb3..5c85312d959 100644 --- a/homeassistant/components/bmw_connected_drive/sensor.py +++ b/homeassistant/components/bmw_connected_drive/sensor.py @@ -3,6 +3,7 @@ import logging from bimmer_connected.state import ChargingState +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONF_UNIT_SYSTEM_IMPERIAL, LENGTH_KILOMETERS, @@ -12,7 +13,6 @@ from homeassistant.const import ( VOLUME_GALLONS, VOLUME_LITERS, ) -from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from . import DOMAIN as BMW_DOMAIN, BMWConnectedDriveBaseEntity @@ -67,7 +67,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, Entity): +class BMWConnectedDriveSensor(SensorEntity, BMWConnectedDriveBaseEntity): """Representation of a BMW vehicle sensor.""" def __init__(self, account, vehicle, attribute: str, attribute_info): diff --git a/homeassistant/components/broadlink/sensor.py b/homeassistant/components/broadlink/sensor.py index af350329e8c..0e42d8c438f 100644 --- a/homeassistant/components/broadlink/sensor.py +++ b/homeassistant/components/broadlink/sensor.py @@ -8,11 +8,11 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, PLATFORM_SCHEMA, + SensorEntity, ) from homeassistant.const import CONF_HOST, PERCENTAGE, TEMP_CELSIUS from homeassistant.core import callback from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity import Entity from .const import DOMAIN from .helpers import import_device @@ -56,7 +56,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -class BroadlinkSensor(Entity): +class BroadlinkSensor(SensorEntity): """Representation of a Broadlink sensor.""" def __init__(self, device, monitored_condition): diff --git a/homeassistant/components/brother/sensor.py b/homeassistant/components/brother/sensor.py index dcbf92fba7d..10cc3979207 100644 --- a/homeassistant/components/brother/sensor.py +++ b/homeassistant/components/brother/sensor.py @@ -1,4 +1,5 @@ """Support for the Brother service.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_TIMESTAMP from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -56,7 +57,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class BrotherPrinterSensor(CoordinatorEntity): +class BrotherPrinterSensor(SensorEntity, CoordinatorEntity): """Define an Brother Printer sensor.""" def __init__(self, coordinator, kind, device_info): diff --git a/homeassistant/components/brottsplatskartan/sensor.py b/homeassistant/components/brottsplatskartan/sensor.py index b75edcc7f4a..32af96dfe60 100644 --- a/homeassistant/components/brottsplatskartan/sensor.py +++ b/homeassistant/components/brottsplatskartan/sensor.py @@ -7,7 +7,7 @@ import uuid import brottsplatskartan import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_LATITUDE, @@ -15,7 +15,6 @@ from homeassistant.const import ( CONF_NAME, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -78,7 +77,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([BrottsplatskartanSensor(bpk, name)], True) -class BrottsplatskartanSensor(Entity): +class BrottsplatskartanSensor(SensorEntity): """Representation of a Brottsplatskartan Sensor.""" def __init__(self, bpk, name): diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index fbfa23f1e74..170493969f8 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -20,7 +20,7 @@ from buienradar.constants import ( ) import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_LATITUDE, @@ -39,7 +39,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import dt as dt_util from .const import DEFAULT_TIMEFRAME @@ -236,7 +235,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= await data.schedule_update(1) -class BrSensor(Entity): +class BrSensor(SensorEntity): """Representation of an Buienradar sensor.""" def __init__(self, sensor_type, client_name, coordinates): diff --git a/homeassistant/components/canary/sensor.py b/homeassistant/components/canary/sensor.py index 87e40d268bd..e4c6190ff99 100644 --- a/homeassistant/components/canary/sensor.py +++ b/homeassistant/components/canary/sensor.py @@ -5,6 +5,7 @@ from typing import Callable from canary.api import SensorType +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DEVICE_CLASS_BATTERY, @@ -77,7 +78,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class CanarySensor(CoordinatorEntity, Entity): +class CanarySensor(SensorEntity, CoordinatorEntity): """Representation of a Canary sensor.""" def __init__(self, coordinator, sensor_type, location, device): diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py index 582205f9c0d..bf80f8b8fb5 100644 --- a/homeassistant/components/cert_expiry/sensor.py +++ b/homeassistant/components/cert_expiry/sensor.py @@ -3,7 +3,7 @@ from datetime import timedelta import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( CONF_HOST, @@ -76,7 +76,7 @@ class CertExpiryEntity(CoordinatorEntity): } -class SSLCertificateTimestamp(CertExpiryEntity): +class SSLCertificateTimestamp(SensorEntity, CertExpiryEntity): """Implementation of the Cert Expiry timestamp sensor.""" @property diff --git a/homeassistant/components/citybikes/sensor.py b/homeassistant/components/citybikes/sensor.py index 2dd60078bd5..bc323a51151 100644 --- a/homeassistant/components/citybikes/sensor.py +++ b/homeassistant/components/citybikes/sensor.py @@ -7,7 +7,11 @@ import aiohttp import async_timeout import voluptuous as vol -from homeassistant.components.sensor import ENTITY_ID_FORMAT, PLATFORM_SCHEMA +from homeassistant.components.sensor import ( + ENTITY_ID_FORMAT, + PLATFORM_SCHEMA, + SensorEntity, +) from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_ID, @@ -25,7 +29,7 @@ from homeassistant.const import ( from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity, async_generate_entity_id +from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.event import async_track_time_interval from homeassistant.util import distance, location @@ -258,7 +262,7 @@ class CityBikesNetwork: raise PlatformNotReady from err -class CityBikesStation(Entity): +class CityBikesStation(SensorEntity): """CityBikes API Sensor.""" def __init__(self, network, station_id, entity_id): diff --git a/homeassistant/components/co2signal/sensor.py b/homeassistant/components/co2signal/sensor.py index 2153a8a7af5..c7d2a64d6b0 100644 --- a/homeassistant/components/co2signal/sensor.py +++ b/homeassistant/components/co2signal/sensor.py @@ -4,7 +4,7 @@ import logging import CO2Signal import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_LATITUDE, @@ -13,7 +13,6 @@ from homeassistant.const import ( ENERGY_KILO_WATT_HOUR, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity CONF_COUNTRY_CODE = "country_code" @@ -52,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devs, True) -class CO2Sensor(Entity): +class CO2Sensor(SensorEntity): """Implementation of the CO2Signal sensor.""" def __init__(self, token, country_code, lat, lon): diff --git a/homeassistant/components/coinbase/sensor.py b/homeassistant/components/coinbase/sensor.py index 1d239ba457e..e4e4e719c9e 100644 --- a/homeassistant/components/coinbase/sensor.py +++ b/homeassistant/components/coinbase/sensor.py @@ -1,6 +1,6 @@ """Support for Coinbase sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION -from homeassistant.helpers.entity import Entity ATTR_NATIVE_BALANCE = "Balance in native currency" @@ -38,7 +38,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([sensor], True) -class AccountSensor(Entity): +class AccountSensor(SensorEntity): """Representation of a Coinbase.com sensor.""" def __init__(self, coinbase_data, name, currency): @@ -88,7 +88,7 @@ class AccountSensor(Entity): self._native_currency = account["native_balance"]["currency"] -class ExchangeRateSensor(Entity): +class ExchangeRateSensor(SensorEntity): """Representation of a Coinbase.com sensor.""" def __init__(self, coinbase_data, exchange_currency, native_currency): diff --git a/homeassistant/components/comed_hourly_pricing/sensor.py b/homeassistant/components/comed_hourly_pricing/sensor.py index c1a784f619c..5d4ec6eec13 100644 --- a/homeassistant/components/comed_hourly_pricing/sensor.py +++ b/homeassistant/components/comed_hourly_pricing/sensor.py @@ -8,11 +8,10 @@ import aiohttp import async_timeout import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, CONF_OFFSET from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) _RESOURCE = "https://hourlypricing.comed.com/api" @@ -65,7 +64,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(dev, True) -class ComedHourlyPricingSensor(Entity): +class ComedHourlyPricingSensor(SensorEntity): """Implementation of a ComEd Hourly Pricing sensor.""" def __init__(self, loop, websession, sensor_type, offset, name): diff --git a/homeassistant/components/comfoconnect/sensor.py b/homeassistant/components/comfoconnect/sensor.py index 87fa8f4a1a6..728bc13b76b 100644 --- a/homeassistant/components/comfoconnect/sensor.py +++ b/homeassistant/components/comfoconnect/sensor.py @@ -26,7 +26,7 @@ from pycomfoconnect import ( ) import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_ICON, @@ -45,7 +45,6 @@ from homeassistant.const import ( ) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from . import DOMAIN, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, ComfoConnectBridge @@ -258,7 +257,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class ComfoConnectSensor(Entity): +class ComfoConnectSensor(SensorEntity): """Representation of a ComfoConnect sensor.""" def __init__(self, name, ccb: ComfoConnectBridge, sensor_type) -> None: diff --git a/homeassistant/components/command_line/sensor.py b/homeassistant/components/command_line/sensor.py index fc99befb821..10c5a16f60b 100644 --- a/homeassistant/components/command_line/sensor.py +++ b/homeassistant/components/command_line/sensor.py @@ -6,7 +6,7 @@ import logging import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_COMMAND, CONF_NAME, @@ -17,7 +17,6 @@ from homeassistant.const import ( from homeassistant.exceptions import TemplateError from homeassistant.helpers import template import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.reload import setup_reload_service from . import check_output_or_log @@ -63,7 +62,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class CommandSensor(Entity): +class CommandSensor(SensorEntity): """Representation of a sensor that is using shell commands.""" def __init__( diff --git a/homeassistant/components/coronavirus/sensor.py b/homeassistant/components/coronavirus/sensor.py index acfc5569f34..58e6760a85d 100644 --- a/homeassistant/components/coronavirus/sensor.py +++ b/homeassistant/components/coronavirus/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for the Corona virus.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -23,7 +24,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class CoronavirusSensor(CoordinatorEntity): +class CoronavirusSensor(SensorEntity, CoordinatorEntity): """Sensor representing corona virus data.""" name = None diff --git a/homeassistant/components/cpuspeed/sensor.py b/homeassistant/components/cpuspeed/sensor.py index f21513f616e..01938344694 100644 --- a/homeassistant/components/cpuspeed/sensor.py +++ b/homeassistant/components/cpuspeed/sensor.py @@ -2,10 +2,9 @@ from cpuinfo import cpuinfo import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, FREQUENCY_GIGAHERTZ import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity ATTR_BRAND = "brand" ATTR_HZ = "ghz_advertised" @@ -29,7 +28,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([CpuSpeedSensor(name)], True) -class CpuSpeedSensor(Entity): +class CpuSpeedSensor(SensorEntity): """Representation of a CPU sensor.""" def __init__(self, name): diff --git a/homeassistant/components/cups/sensor.py b/homeassistant/components/cups/sensor.py index 54e674b1811..6a3fc7b4215 100644 --- a/homeassistant/components/cups/sensor.py +++ b/homeassistant/components/cups/sensor.py @@ -5,11 +5,10 @@ import logging import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_HOST, CONF_PORT, PERCENTAGE from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -96,7 +95,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class CupsSensor(Entity): +class CupsSensor(SensorEntity): """Representation of a CUPS sensor.""" def __init__(self, data, printer): @@ -155,7 +154,7 @@ class CupsSensor(Entity): self._available = self.data.available -class IPPSensor(Entity): +class IPPSensor(SensorEntity): """Implementation of the IPPSensor. This sensor represents the status of the printer. @@ -232,7 +231,7 @@ class IPPSensor(Entity): self._available = self.data.available -class MarkerSensor(Entity): +class MarkerSensor(SensorEntity): """Implementation of the MarkerSensor. This sensor represents the percentage of ink or toner. diff --git a/homeassistant/components/currencylayer/sensor.py b/homeassistant/components/currencylayer/sensor.py index 66b26eb0692..f42534f509b 100644 --- a/homeassistant/components/currencylayer/sensor.py +++ b/homeassistant/components/currencylayer/sensor.py @@ -5,7 +5,7 @@ import logging import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -14,7 +14,6 @@ from homeassistant.const import ( CONF_QUOTE, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) _RESOURCE = "http://apilayer.net/api/live" @@ -55,7 +54,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class CurrencylayerSensor(Entity): +class CurrencylayerSensor(SensorEntity): """Implementing the Currencylayer sensor.""" def __init__(self, rest, base, quote): diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 187cb0c410d..0012b1a3aa2 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -25,6 +25,7 @@ from homeassistant.helpers.config_validation import ( # noqa: F401 PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, ) +from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent # mypy: allow-untyped-defs, no-check-untyped-defs @@ -74,3 +75,7 @@ async def async_setup_entry(hass, entry): async def async_unload_entry(hass, entry): """Unload a config entry.""" return await hass.data[DOMAIN].async_unload_entry(entry) + + +class SensorEntity(Entity): + """Base class for sensor entities.""" From 23b562386f798c23bd45b343f44fdb8226aba468 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 12:52:29 +0100 Subject: [PATCH 538/831] Migrate integrations d-e to extend SensorEntity (#48211) --- homeassistant/components/daikin/sensor.py | 4 ++-- homeassistant/components/danfoss_air/sensor.py | 4 ++-- homeassistant/components/darksky/sensor.py | 11 +++++++---- homeassistant/components/deconz/sensor.py | 6 +++--- homeassistant/components/delijn/sensor.py | 5 ++--- homeassistant/components/deluge/sensor.py | 5 ++--- homeassistant/components/demo/sensor.py | 4 ++-- homeassistant/components/derivative/sensor.py | 4 ++-- homeassistant/components/deutsche_bahn/sensor.py | 5 ++--- .../components/devolo_home_control/sensor.py | 3 ++- homeassistant/components/dexcom/sensor.py | 5 +++-- homeassistant/components/dht/sensor.py | 5 ++--- homeassistant/components/discogs/sensor.py | 5 ++--- homeassistant/components/dnsip/sensor.py | 5 ++--- homeassistant/components/dovado/sensor.py | 5 ++--- homeassistant/components/dsmr/sensor.py | 5 ++--- homeassistant/components/dsmr_reader/sensor.py | 4 ++-- homeassistant/components/dte_energy_bridge/sensor.py | 5 ++--- .../components/dublin_bus_transport/sensor.py | 5 ++--- .../components/dwd_weather_warnings/sensor.py | 5 ++--- homeassistant/components/dweet/sensor.py | 5 ++--- homeassistant/components/dyson/sensor.py | 4 ++-- homeassistant/components/eafm/sensor.py | 3 ++- homeassistant/components/ebox/sensor.py | 5 ++--- homeassistant/components/ebusd/sensor.py | 4 ++-- homeassistant/components/ecoal_boiler/sensor.py | 4 ++-- homeassistant/components/ecobee/sensor.py | 4 ++-- homeassistant/components/econet/sensor.py | 3 ++- .../components/eddystone_temperature/sensor.py | 5 ++--- homeassistant/components/edl21/sensor.py | 5 ++--- homeassistant/components/efergy/sensor.py | 5 ++--- homeassistant/components/eight_sleep/sensor.py | 7 ++++--- homeassistant/components/eliqonline/sensor.py | 5 ++--- homeassistant/components/elkm1/sensor.py | 3 ++- homeassistant/components/emoncms/sensor.py | 5 ++--- homeassistant/components/enocean/sensor.py | 4 ++-- homeassistant/components/enphase_envoy/sensor.py | 4 ++-- .../components/entur_public_transport/sensor.py | 5 ++--- homeassistant/components/environment_canada/sensor.py | 5 ++--- homeassistant/components/envirophat/sensor.py | 5 ++--- homeassistant/components/envisalink/sensor.py | 4 ++-- homeassistant/components/epsonworkforce/sensor.py | 5 ++--- homeassistant/components/esphome/sensor.py | 6 +++--- homeassistant/components/essent/sensor.py | 5 ++--- homeassistant/components/etherscan/sensor.py | 5 ++--- 45 files changed, 100 insertions(+), 115 deletions(-) diff --git a/homeassistant/components/daikin/sensor.py b/homeassistant/components/daikin/sensor.py index 80de4ed34a5..a5b515ea918 100644 --- a/homeassistant/components/daikin/sensor.py +++ b/homeassistant/components/daikin/sensor.py @@ -1,4 +1,5 @@ """Support for Daikin AC sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONF_DEVICE_CLASS, CONF_ICON, @@ -6,7 +7,6 @@ from homeassistant.const import ( CONF_TYPE, CONF_UNIT_OF_MEASUREMENT, ) -from homeassistant.helpers.entity import Entity from . import DOMAIN as DAIKIN_DOMAIN, DaikinApi from .const import ( @@ -49,7 +49,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities([DaikinSensor.factory(daikin_api, sensor) for sensor in sensors]) -class DaikinSensor(Entity): +class DaikinSensor(SensorEntity): """Representation of a Sensor.""" @staticmethod diff --git a/homeassistant/components/danfoss_air/sensor.py b/homeassistant/components/danfoss_air/sensor.py index 251c9692021..792a95e8ac4 100644 --- a/homeassistant/components/danfoss_air/sensor.py +++ b/homeassistant/components/danfoss_air/sensor.py @@ -3,6 +3,7 @@ import logging from pydanfossair.commands import ReadCommand +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, @@ -10,7 +11,6 @@ from homeassistant.const import ( PERCENTAGE, TEMP_CELSIUS, ) -from homeassistant.helpers.entity import Entity from . import DOMAIN as DANFOSS_AIR_DOMAIN @@ -77,7 +77,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class DanfossAir(Entity): +class DanfossAir(SensorEntity): """Representation of a Sensor.""" def __init__(self, data, name, sensor_unit, sensor_type, device_class): diff --git a/homeassistant/components/darksky/sensor.py b/homeassistant/components/darksky/sensor.py index 7e6463dc08e..058969d96f9 100644 --- a/homeassistant/components/darksky/sensor.py +++ b/homeassistant/components/darksky/sensor.py @@ -6,7 +6,11 @@ import forecastio from requests.exceptions import ConnectionError as ConnectError, HTTPError, Timeout import voluptuous as vol -from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE, PLATFORM_SCHEMA +from homeassistant.components.sensor import ( + DEVICE_CLASS_TEMPERATURE, + PLATFORM_SCHEMA, + SensorEntity, +) from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -29,7 +33,6 @@ from homeassistant.const import ( UV_INDEX, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -544,7 +547,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class DarkSkySensor(Entity): +class DarkSkySensor(SensorEntity): """Implementation of a Dark Sky sensor.""" def __init__( @@ -708,7 +711,7 @@ class DarkSkySensor(Entity): return state -class DarkSkyAlertSensor(Entity): +class DarkSkyAlertSensor(SensorEntity): """Implementation of a Dark Sky sensor.""" def __init__(self, forecast_data, sensor_type, name): diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index b08ec0d091b..8f23b47a1b9 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -12,7 +12,7 @@ from pydeconz.sensor import ( Thermostat, ) -from homeassistant.components.sensor import DOMAIN +from homeassistant.components.sensor import DOMAIN, SensorEntity from homeassistant.const import ( ATTR_TEMPERATURE, ATTR_VOLTAGE, @@ -122,7 +122,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class DeconzSensor(DeconzDevice): +class DeconzSensor(SensorEntity, DeconzDevice): """Representation of a deCONZ sensor.""" TYPE = DOMAIN @@ -186,7 +186,7 @@ class DeconzSensor(DeconzDevice): return attr -class DeconzBattery(DeconzDevice): +class DeconzBattery(SensorEntity, DeconzDevice): """Battery class for when a device is only represented as an event.""" TYPE = DOMAIN diff --git a/homeassistant/components/delijn/sensor.py b/homeassistant/components/delijn/sensor.py index 44b70c4e7a8..cff93c89954 100644 --- a/homeassistant/components/delijn/sensor.py +++ b/homeassistant/components/delijn/sensor.py @@ -5,11 +5,10 @@ from pydelijn.api import Passages from pydelijn.common import HttpException import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, DEVICE_CLASS_TIMESTAMP from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -58,7 +57,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors, True) -class DeLijnPublicTransportSensor(Entity): +class DeLijnPublicTransportSensor(SensorEntity): """Representation of a Ruter sensor.""" def __init__(self, line): diff --git a/homeassistant/components/deluge/sensor.py b/homeassistant/components/deluge/sensor.py index 5e8df89c20d..0c79e6f835e 100644 --- a/homeassistant/components/deluge/sensor.py +++ b/homeassistant/components/deluge/sensor.py @@ -4,7 +4,7 @@ import logging from deluge_client import DelugeRPCClient, FailedToReconnectException import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_MONITORED_VARIABLES, @@ -17,7 +17,6 @@ from homeassistant.const import ( ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) _THROTTLED_REFRESH = None @@ -68,7 +67,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev) -class DelugeSensor(Entity): +class DelugeSensor(SensorEntity): """Representation of a Deluge sensor.""" def __init__(self, sensor_type, deluge_client, client_name): diff --git a/homeassistant/components/demo/sensor.py b/homeassistant/components/demo/sensor.py index 9c005d2d7f5..7607bad4e1c 100644 --- a/homeassistant/components/demo/sensor.py +++ b/homeassistant/components/demo/sensor.py @@ -1,4 +1,5 @@ """Demo platform that has a couple of fake sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_BATTERY_LEVEL, CONCENTRATION_PARTS_PER_MILLION, @@ -9,7 +10,6 @@ from homeassistant.const import ( PERCENTAGE, TEMP_CELSIUS, ) -from homeassistant.helpers.entity import Entity from . import DOMAIN @@ -59,7 +59,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): await async_setup_platform(hass, {}, async_add_entities) -class DemoSensor(Entity): +class DemoSensor(SensorEntity): """Representation of a Demo sensor.""" def __init__( diff --git a/homeassistant/components/derivative/sensor.py b/homeassistant/components/derivative/sensor.py index ddbe1d19e31..72d0186d94d 100644 --- a/homeassistant/components/derivative/sensor.py +++ b/homeassistant/components/derivative/sensor.py @@ -4,7 +4,7 @@ import logging import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, @@ -86,7 +86,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([derivative]) -class DerivativeSensor(RestoreEntity): +class DerivativeSensor(SensorEntity, RestoreEntity): """Representation of an derivative sensor.""" def __init__( diff --git a/homeassistant/components/deutsche_bahn/sensor.py b/homeassistant/components/deutsche_bahn/sensor.py index 1de6609c756..33fd9a8224f 100644 --- a/homeassistant/components/deutsche_bahn/sensor.py +++ b/homeassistant/components/deutsche_bahn/sensor.py @@ -4,10 +4,9 @@ from datetime import timedelta import schiene import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_OFFSET import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util CONF_DESTINATION = "to" @@ -40,7 +39,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([DeutscheBahnSensor(start, destination, offset, only_direct)], True) -class DeutscheBahnSensor(Entity): +class DeutscheBahnSensor(SensorEntity): """Implementation of a Deutsche Bahn sensor.""" def __init__(self, start, goal, offset, only_direct): diff --git a/homeassistant/components/devolo_home_control/sensor.py b/homeassistant/components/devolo_home_control/sensor.py index 9fa3bdcf809..f125448b4cc 100644 --- a/homeassistant/components/devolo_home_control/sensor.py +++ b/homeassistant/components/devolo_home_control/sensor.py @@ -6,6 +6,7 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, + SensorEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE @@ -65,7 +66,7 @@ async def async_setup_entry( async_add_entities(entities, False) -class DevoloMultiLevelDeviceEntity(DevoloDeviceEntity): +class DevoloMultiLevelDeviceEntity(SensorEntity, DevoloDeviceEntity): """Abstract representation of a multi level sensor within devolo Home Control.""" @property diff --git a/homeassistant/components/dexcom/sensor.py b/homeassistant/components/dexcom/sensor.py index afb3feeec4d..09c7ff4e051 100644 --- a/homeassistant/components/dexcom/sensor.py +++ b/homeassistant/components/dexcom/sensor.py @@ -1,4 +1,5 @@ """Support for Dexcom sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_UNIT_OF_MEASUREMENT, CONF_USERNAME from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -16,7 +17,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class DexcomGlucoseValueSensor(CoordinatorEntity): +class DexcomGlucoseValueSensor(SensorEntity, CoordinatorEntity): """Representation of a Dexcom glucose value sensor.""" def __init__(self, coordinator, username, unit_of_measurement): @@ -58,7 +59,7 @@ class DexcomGlucoseValueSensor(CoordinatorEntity): return self._unique_id -class DexcomGlucoseTrendSensor(CoordinatorEntity): +class DexcomGlucoseTrendSensor(SensorEntity, CoordinatorEntity): """Representation of a Dexcom glucose trend sensor.""" def __init__(self, coordinator, username): diff --git a/homeassistant/components/dht/sensor.py b/homeassistant/components/dht/sensor.py index 0bddd5a187e..c1fa32e71d1 100644 --- a/homeassistant/components/dht/sensor.py +++ b/homeassistant/components/dht/sensor.py @@ -5,7 +5,7 @@ import logging import Adafruit_DHT # pylint: disable=import-error import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_CONDITIONS, CONF_NAME, @@ -14,7 +14,6 @@ from homeassistant.const import ( TEMP_FAHRENHEIT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.temperature import celsius_to_fahrenheit @@ -93,7 +92,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class DHTSensor(Entity): +class DHTSensor(SensorEntity): """Implementation of the DHT sensor.""" def __init__( diff --git a/homeassistant/components/discogs/sensor.py b/homeassistant/components/discogs/sensor.py index 6f45a4ab959..81beec0e60e 100644 --- a/homeassistant/components/discogs/sensor.py +++ b/homeassistant/components/discogs/sensor.py @@ -6,7 +6,7 @@ import random import discogs_client import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS, @@ -15,7 +15,6 @@ from homeassistant.const import ( ) from homeassistant.helpers.aiohttp_client import SERVER_SOFTWARE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -89,7 +88,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class DiscogsSensor(Entity): +class DiscogsSensor(SensorEntity): """Create a new Discogs sensor for a specific type.""" def __init__(self, discogs_data, name, sensor_type): diff --git a/homeassistant/components/dnsip/sensor.py b/homeassistant/components/dnsip/sensor.py index b202ff8485c..01d6e2f4f2a 100644 --- a/homeassistant/components/dnsip/sensor.py +++ b/homeassistant/components/dnsip/sensor.py @@ -6,10 +6,9 @@ import aiodns from aiodns.error import DNSError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -55,7 +54,7 @@ async def async_setup_platform(hass, config, async_add_devices, discovery_info=N async_add_devices([WanIpSensor(hass, name, hostname, resolver, ipv6)], True) -class WanIpSensor(Entity): +class WanIpSensor(SensorEntity): """Implementation of a DNS IP sensor.""" def __init__(self, hass, name, hostname, resolver, ipv6): diff --git a/homeassistant/components/dovado/sensor.py b/homeassistant/components/dovado/sensor.py index 8c8dffbb8b6..e7b3dbdd363 100644 --- a/homeassistant/components/dovado/sensor.py +++ b/homeassistant/components/dovado/sensor.py @@ -4,10 +4,9 @@ import re import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_SENSORS, DATA_GIGABYTES, PERCENTAGE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from . import DOMAIN as DOVADO_DOMAIN @@ -53,7 +52,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities) -class DovadoSensor(Entity): +class DovadoSensor(SensorEntity): """Representation of a Dovado sensor.""" def __init__(self, data, sensor): diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index 0d2c55051e8..12304fa51d4 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -12,7 +12,7 @@ from dsmr_parser.clients.protocol import create_dsmr_reader, create_tcp_dsmr_rea import serial import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_HOST, @@ -22,7 +22,6 @@ from homeassistant.const import ( ) from homeassistant.core import CoreState, callback from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import Throttle @@ -286,7 +285,7 @@ async def async_setup_entry( hass.data[DOMAIN][entry.entry_id][DATA_TASK] = task -class DSMREntity(Entity): +class DSMREntity(SensorEntity): """Entity reading values from DSMR telegram.""" def __init__(self, name, device_name, device_serial, obis, config, force_update): diff --git a/homeassistant/components/dsmr_reader/sensor.py b/homeassistant/components/dsmr_reader/sensor.py index 14234b49dbe..0ee5932c1bb 100644 --- a/homeassistant/components/dsmr_reader/sensor.py +++ b/homeassistant/components/dsmr_reader/sensor.py @@ -1,7 +1,7 @@ """Support for DSMR Reader through MQTT.""" from homeassistant.components import mqtt +from homeassistant.components.sensor import SensorEntity from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from homeassistant.util import slugify from .definitions import DEFINITIONS @@ -19,7 +19,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors) -class DSMRSensor(Entity): +class DSMRSensor(SensorEntity): """Representation of a DSMR sensor that is updated via MQTT.""" def __init__(self, topic): diff --git a/homeassistant/components/dte_energy_bridge/sensor.py b/homeassistant/components/dte_energy_bridge/sensor.py index efd00b3da1e..27475990de0 100644 --- a/homeassistant/components/dte_energy_bridge/sensor.py +++ b/homeassistant/components/dte_energy_bridge/sensor.py @@ -4,10 +4,9 @@ import logging import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, HTTP_OK import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -39,7 +38,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([DteEnergyBridgeSensor(ip_address, name, version)], True) -class DteEnergyBridgeSensor(Entity): +class DteEnergyBridgeSensor(SensorEntity): """Implementation of the DTE Energy Bridge sensors.""" def __init__(self, ip_address, name, version): diff --git a/homeassistant/components/dublin_bus_transport/sensor.py b/homeassistant/components/dublin_bus_transport/sensor.py index 5b0cbaf9f18..909eed09b9a 100644 --- a/homeassistant/components/dublin_bus_transport/sensor.py +++ b/homeassistant/components/dublin_bus_transport/sensor.py @@ -9,10 +9,9 @@ from datetime import datetime, timedelta import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, HTTP_OK, TIME_MINUTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util _RESOURCE = "https://data.dublinked.ie/cgi-bin/rtpi/realtimebusinformation" @@ -65,7 +64,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([DublinPublicTransportSensor(data, stop, route, name)], True) -class DublinPublicTransportSensor(Entity): +class DublinPublicTransportSensor(SensorEntity): """Implementation of an Dublin public transport sensor.""" def __init__(self, data, stop, route, name): diff --git a/homeassistant/components/dwd_weather_warnings/sensor.py b/homeassistant/components/dwd_weather_warnings/sensor.py index af91e279600..78fa9bd8552 100644 --- a/homeassistant/components/dwd_weather_warnings/sensor.py +++ b/homeassistant/components/dwd_weather_warnings/sensor.py @@ -15,10 +15,9 @@ import logging from dwdwfsapi import DwdWeatherWarningsAPI import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -87,7 +86,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class DwdWeatherWarningsSensor(Entity): +class DwdWeatherWarningsSensor(SensorEntity): """Representation of a DWD-Weather-Warnings sensor.""" def __init__(self, api, name, sensor_type): diff --git a/homeassistant/components/dweet/sensor.py b/homeassistant/components/dweet/sensor.py index f3f604ff369..f1243cd5407 100644 --- a/homeassistant/components/dweet/sensor.py +++ b/homeassistant/components/dweet/sensor.py @@ -6,7 +6,7 @@ import logging import dweepy import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_DEVICE, CONF_NAME, @@ -14,7 +14,6 @@ from homeassistant.const import ( CONF_VALUE_TEMPLATE, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -56,7 +55,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([DweetSensor(hass, dweet, name, value_template, unit)], True) -class DweetSensor(Entity): +class DweetSensor(SensorEntity): """Representation of a Dweet sensor.""" def __init__(self, hass, dweet, name, value_template, unit_of_measurement): diff --git a/homeassistant/components/dyson/sensor.py b/homeassistant/components/dyson/sensor.py index 80a64e787f0..356bfc61cb2 100644 --- a/homeassistant/components/dyson/sensor.py +++ b/homeassistant/components/dyson/sensor.py @@ -2,6 +2,7 @@ from libpurecool.dyson_pure_cool import DysonPureCool from libpurecool.dyson_pure_cool_link import DysonPureCoolLink +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_ICON, @@ -13,7 +14,6 @@ from homeassistant.const import ( TEMP_CELSIUS, TIME_HOURS, ) -from homeassistant.helpers.entity import Entity from . import DYSON_DEVICES, DysonEntity @@ -101,7 +101,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class DysonSensor(DysonEntity, Entity): +class DysonSensor(SensorEntity, DysonEntity): """Representation of a generic Dyson sensor.""" def __init__(self, device, sensor_type): diff --git a/homeassistant/components/eafm/sensor.py b/homeassistant/components/eafm/sensor.py index 9e2d4e5cef9..8fa2a47c157 100644 --- a/homeassistant/components/eafm/sensor.py +++ b/homeassistant/components/eafm/sensor.py @@ -5,6 +5,7 @@ import logging from aioeafm import get_station import async_timeout +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, LENGTH_METERS from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import ( @@ -77,7 +78,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): await coordinator.async_refresh() -class Measurement(CoordinatorEntity): +class Measurement(SensorEntity, CoordinatorEntity): """A gauge at a flood monitoring station.""" attribution = "This uses Environment Agency flood and river level data from the real-time data API" diff --git a/homeassistant/components/ebox/sensor.py b/homeassistant/components/ebox/sensor.py index aa6945bac99..72d169f389e 100644 --- a/homeassistant/components/ebox/sensor.py +++ b/homeassistant/components/ebox/sensor.py @@ -10,7 +10,7 @@ from pyebox import EboxClient from pyebox.client import PyEboxError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_VARIABLES, CONF_NAME, @@ -22,7 +22,6 @@ from homeassistant.const import ( ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -90,7 +89,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors, True) -class EBoxSensor(Entity): +class EBoxSensor(SensorEntity): """Implementation of a EBox sensor.""" def __init__(self, ebox_data, sensor_type, name): diff --git a/homeassistant/components/ebusd/sensor.py b/homeassistant/components/ebusd/sensor.py index 53dac4eb4c0..00f6a6b2b3e 100644 --- a/homeassistant/components/ebusd/sensor.py +++ b/homeassistant/components/ebusd/sensor.py @@ -2,7 +2,7 @@ import datetime import logging -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from homeassistant.util import Throttle import homeassistant.util.dt as dt_util @@ -34,7 +34,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class EbusdSensor(Entity): +class EbusdSensor(SensorEntity): """Ebusd component sensor methods definition.""" def __init__(self, data, sensor, name): diff --git a/homeassistant/components/ecoal_boiler/sensor.py b/homeassistant/components/ecoal_boiler/sensor.py index 963f547283f..e1c9308b5a9 100644 --- a/homeassistant/components/ecoal_boiler/sensor.py +++ b/homeassistant/components/ecoal_boiler/sensor.py @@ -1,6 +1,6 @@ """Allows reading temperatures from ecoal/esterownik.pl controller.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import TEMP_CELSIUS -from homeassistant.helpers.entity import Entity from . import AVAILABLE_SENSORS, DATA_ECOAL_BOILER @@ -17,7 +17,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class EcoalTempSensor(Entity): +class EcoalTempSensor(SensorEntity): """Representation of a temperature sensor using ecoal status data.""" def __init__(self, ecoal_contr, name, status_attr): diff --git a/homeassistant/components/ecobee/sensor.py b/homeassistant/components/ecobee/sensor.py index cfbaa7a4516..5abe809e59d 100644 --- a/homeassistant/components/ecobee/sensor.py +++ b/homeassistant/components/ecobee/sensor.py @@ -1,13 +1,13 @@ """Support for Ecobee sensors.""" from pyecobee.const import ECOBEE_STATE_CALIBRATING, ECOBEE_STATE_UNKNOWN +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, PERCENTAGE, TEMP_FAHRENHEIT, ) -from homeassistant.helpers.entity import Entity from .const import _LOGGER, DOMAIN, ECOBEE_MODEL_TO_NAME, MANUFACTURER @@ -32,7 +32,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(dev, True) -class EcobeeSensor(Entity): +class EcobeeSensor(SensorEntity): """Representation of an Ecobee sensor.""" def __init__(self, data, sensor_name, sensor_type, sensor_index): diff --git a/homeassistant/components/econet/sensor.py b/homeassistant/components/econet/sensor.py index e0ef7dc6ce9..8198eb75045 100644 --- a/homeassistant/components/econet/sensor.py +++ b/homeassistant/components/econet/sensor.py @@ -1,6 +1,7 @@ """Support for Rheem EcoNet water heaters.""" from pyeconet.equipment import EquipmentType +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ENERGY_KILO_WATT_HOUR, PERCENTAGE, @@ -47,7 +48,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensors) -class EcoNetSensor(EcoNetEntity): +class EcoNetSensor(SensorEntity, EcoNetEntity): """Define a Econet sensor.""" def __init__(self, econet_device, device_name): diff --git a/homeassistant/components/eddystone_temperature/sensor.py b/homeassistant/components/eddystone_temperature/sensor.py index f8a7254b6fa..70afde4ffb1 100644 --- a/homeassistant/components/eddystone_temperature/sensor.py +++ b/homeassistant/components/eddystone_temperature/sensor.py @@ -10,7 +10,7 @@ import logging from beacontools import BeaconScanner, EddystoneFilter, EddystoneTLMFrame import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_NAME, EVENT_HOMEASSISTANT_START, @@ -19,7 +19,6 @@ from homeassistant.const import ( TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -97,7 +96,7 @@ def get_from_conf(config, config_key, length): return string -class EddystoneTemp(Entity): +class EddystoneTemp(SensorEntity): """Representation of a temperature sensor.""" def __init__(self, name, namespace, instance): diff --git a/homeassistant/components/edl21/sensor.py b/homeassistant/components/edl21/sensor.py index 1f21eaa4004..090b2780ec4 100644 --- a/homeassistant/components/edl21/sensor.py +++ b/homeassistant/components/edl21/sensor.py @@ -7,7 +7,7 @@ from sml import SmlGetListResponse from sml.asyncio import SmlProtocol import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv @@ -15,7 +15,6 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, ) -from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.helpers.typing import Optional from homeassistant.util.dt import utcnow @@ -193,7 +192,7 @@ class EDL21: self._async_add_entities(new_entities, update_before_add=True) -class EDL21Entity(Entity): +class EDL21Entity(SensorEntity): """Entity reading values from EDL21 telegram.""" def __init__(self, electricity_id, obis, name, telegram): diff --git a/homeassistant/components/efergy/sensor.py b/homeassistant/components/efergy/sensor.py index 02bc8fa0ccb..6e2ac1c01c7 100644 --- a/homeassistant/components/efergy/sensor.py +++ b/homeassistant/components/efergy/sensor.py @@ -4,7 +4,7 @@ import logging import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_CURRENCY, CONF_MONITORED_VARIABLES, @@ -13,7 +13,6 @@ from homeassistant.const import ( POWER_WATT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) _RESOURCE = "https://engage.efergy.com/mobile_proxy/" @@ -94,7 +93,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class EfergySensor(Entity): +class EfergySensor(SensorEntity): """Implementation of an Efergy sensor.""" def __init__(self, sensor_type, app_token, utc_offset, period, currency, sid=None): diff --git a/homeassistant/components/eight_sleep/sensor.py b/homeassistant/components/eight_sleep/sensor.py index f858309d02c..f5a0d1b1f8d 100644 --- a/homeassistant/components/eight_sleep/sensor.py +++ b/homeassistant/components/eight_sleep/sensor.py @@ -1,6 +1,7 @@ """Support for Eight Sleep sensors.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import PERCENTAGE, TEMP_CELSIUS, TEMP_FAHRENHEIT from . import ( @@ -66,7 +67,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(all_sensors, True) -class EightHeatSensor(EightSleepHeatEntity): +class EightHeatSensor(SensorEntity, EightSleepHeatEntity): """Representation of an eight sleep heat-based sensor.""" def __init__(self, name, eight, sensor): @@ -119,7 +120,7 @@ class EightHeatSensor(EightSleepHeatEntity): } -class EightUserSensor(EightSleepUserEntity): +class EightUserSensor(SensorEntity, EightSleepUserEntity): """Representation of an eight sleep user-based sensor.""" def __init__(self, name, eight, sensor, units): @@ -289,7 +290,7 @@ class EightUserSensor(EightSleepUserEntity): return state_attr -class EightRoomSensor(EightSleepUserEntity): +class EightRoomSensor(SensorEntity, EightSleepUserEntity): """Representation of an eight sleep room sensor.""" def __init__(self, name, eight, sensor, units): diff --git a/homeassistant/components/eliqonline/sensor.py b/homeassistant/components/eliqonline/sensor.py index b3d56e42325..a4d812850f7 100644 --- a/homeassistant/components/eliqonline/sensor.py +++ b/homeassistant/components/eliqonline/sensor.py @@ -6,11 +6,10 @@ import logging import eliqonline import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_ACCESS_TOKEN, CONF_NAME, POWER_WATT from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -52,7 +51,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([EliqSensor(api, channel_id, name)], True) -class EliqSensor(Entity): +class EliqSensor(SensorEntity): """Implementation of an ELIQ Online sensor.""" def __init__(self, api, channel_id, name): diff --git a/homeassistant/components/elkm1/sensor.py b/homeassistant/components/elkm1/sensor.py index 7d63f283f0b..c8d4fd722bb 100644 --- a/homeassistant/components/elkm1/sensor.py +++ b/homeassistant/components/elkm1/sensor.py @@ -8,6 +8,7 @@ from elkm1_lib.const import ( from elkm1_lib.util import pretty_const, username import voluptuous as vol +from homeassistant.components.sensor import SensorEntity from homeassistant.const import VOLT from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_platform @@ -67,7 +68,7 @@ def temperature_to_state(temperature, undefined_temperature): return temperature if temperature > undefined_temperature else None -class ElkSensor(ElkAttachedEntity): +class ElkSensor(SensorEntity, ElkAttachedEntity): """Base representation of Elk-M1 sensor.""" def __init__(self, element, elk, elk_data): diff --git a/homeassistant/components/emoncms/sensor.py b/homeassistant/components/emoncms/sensor.py index 523350532a0..4913f6340cc 100644 --- a/homeassistant/components/emoncms/sensor.py +++ b/homeassistant/components/emoncms/sensor.py @@ -5,7 +5,7 @@ import logging import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_API_KEY, CONF_ID, @@ -19,7 +19,6 @@ from homeassistant.const import ( ) from homeassistant.helpers import template import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -125,7 +124,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class EmonCmsSensor(Entity): +class EmonCmsSensor(SensorEntity): """Implementation of an Emoncms sensor.""" def __init__( diff --git a/homeassistant/components/enocean/sensor.py b/homeassistant/components/enocean/sensor.py index 011dcdafdb6..18362a3c6ac 100644 --- a/homeassistant/components/enocean/sensor.py +++ b/homeassistant/components/enocean/sensor.py @@ -1,7 +1,7 @@ """Support for EnOcean sensors.""" import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_DEVICE_CLASS, CONF_ID, @@ -101,7 +101,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([EnOceanWindowHandle(dev_id, dev_name)]) -class EnOceanSensor(EnOceanEntity, RestoreEntity): +class EnOceanSensor(SensorEntity, EnOceanEntity, RestoreEntity): """Representation of an EnOcean sensor device such as a power meter.""" def __init__(self, dev_id, dev_name, sensor_type): diff --git a/homeassistant/components/enphase_envoy/sensor.py b/homeassistant/components/enphase_envoy/sensor.py index e40c8e94372..794c0ca2dbd 100644 --- a/homeassistant/components/enphase_envoy/sensor.py +++ b/homeassistant/components/enphase_envoy/sensor.py @@ -8,7 +8,7 @@ from envoy_reader.envoy_reader import EnvoyReader import httpx import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_IP_ADDRESS, CONF_MONITORED_CONDITIONS, @@ -156,7 +156,7 @@ async def async_setup_platform( async_add_entities(entities) -class Envoy(CoordinatorEntity): +class Envoy(SensorEntity, CoordinatorEntity): """Envoy entity.""" def __init__(self, sensor_type, name, serial_number, unit, coordinator): diff --git a/homeassistant/components/entur_public_transport/sensor.py b/homeassistant/components/entur_public_transport/sensor.py index 23c895c58a1..c9c530c6b08 100644 --- a/homeassistant/components/entur_public_transport/sensor.py +++ b/homeassistant/components/entur_public_transport/sensor.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta from enturclient import EnturPublicTransportData import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_LATITUDE, @@ -15,7 +15,6 @@ from homeassistant.const import ( ) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.util.dt as dt_util @@ -148,7 +147,7 @@ class EnturProxy: return self._api.get_stop_info(stop_id) -class EnturPublicTransportSensor(Entity): +class EnturPublicTransportSensor(SensorEntity): """Implementation of a Entur public transport sensor.""" def __init__(self, api: EnturProxy, name: str, stop: str, show_on_map: bool): diff --git a/homeassistant/components/environment_canada/sensor.py b/homeassistant/components/environment_canada/sensor.py index f178b3c6275..0f0fb04fd00 100644 --- a/homeassistant/components/environment_canada/sensor.py +++ b/homeassistant/components/environment_canada/sensor.py @@ -6,7 +6,7 @@ import re from env_canada import ECData import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LOCATION, @@ -15,7 +15,6 @@ from homeassistant.const import ( TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -65,7 +64,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([ECSensor(sensor_type, ec_data) for sensor_type in sensor_list], True) -class ECSensor(Entity): +class ECSensor(SensorEntity): """Implementation of an Environment Canada sensor.""" def __init__(self, sensor_type, ec_data): diff --git a/homeassistant/components/envirophat/sensor.py b/homeassistant/components/envirophat/sensor.py index 873d9935ee8..137d6aee853 100644 --- a/homeassistant/components/envirophat/sensor.py +++ b/homeassistant/components/envirophat/sensor.py @@ -5,7 +5,7 @@ import logging import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_DISPLAY_OPTIONS, CONF_NAME, @@ -14,7 +14,6 @@ from homeassistant.const import ( VOLT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -71,7 +70,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class EnvirophatSensor(Entity): +class EnvirophatSensor(SensorEntity): """Representation of an Enviro pHAT sensor.""" def __init__(self, data, sensor_types): diff --git a/homeassistant/components/envisalink/sensor.py b/homeassistant/components/envisalink/sensor.py index 9551b51b3e9..ff4c73500f9 100644 --- a/homeassistant/components/envisalink/sensor.py +++ b/homeassistant/components/envisalink/sensor.py @@ -1,9 +1,9 @@ """Support for Envisalink sensors (shows panel info).""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from . import ( CONF_PARTITIONNAME, @@ -37,7 +37,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devices) -class EnvisalinkSensor(EnvisalinkDevice, Entity): +class EnvisalinkSensor(SensorEntity, EnvisalinkDevice): """Representation of an Envisalink keypad.""" def __init__(self, hass, partition_name, partition_number, info, controller): diff --git a/homeassistant/components/epsonworkforce/sensor.py b/homeassistant/components/epsonworkforce/sensor.py index 7c6042c8959..22f74e1c0b1 100644 --- a/homeassistant/components/epsonworkforce/sensor.py +++ b/homeassistant/components/epsonworkforce/sensor.py @@ -4,11 +4,10 @@ from datetime import timedelta from epsonprinter_pkg.epsonprinterapi import EpsonPrinterAPI import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_HOST, CONF_MONITORED_CONDITIONS, PERCENTAGE from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity MONITORED_CONDITIONS = { "black": ["Ink level Black", PERCENTAGE, "mdi:water"], @@ -45,7 +44,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(sensors, True) -class EpsonPrinterCartridge(Entity): +class EpsonPrinterCartridge(SensorEntity): """Representation of a cartridge sensor.""" def __init__(self, api, cartridgeidx): diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index 17799e4484f..99f66f48029 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -6,7 +6,7 @@ import math from aioesphomeapi import SensorInfo, SensorState, TextSensorInfo, TextSensorState import voluptuous as vol -from homeassistant.components.sensor import DEVICE_CLASSES +from homeassistant.components.sensor import DEVICE_CLASSES, SensorEntity from homeassistant.config_entries import ConfigEntry import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import HomeAssistantType @@ -42,7 +42,7 @@ async def async_setup_entry( # pylint: disable=invalid-overridden-method -class EsphomeSensor(EsphomeEntity): +class EsphomeSensor(SensorEntity, EsphomeEntity): """A sensor implementation for esphome.""" @property @@ -89,7 +89,7 @@ class EsphomeSensor(EsphomeEntity): return self._static_info.device_class -class EsphomeTextSensor(EsphomeEntity): +class EsphomeTextSensor(SensorEntity, EsphomeEntity): """A text sensor implementation for ESPHome.""" @property diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index 26f7c930241..f0dc70d7be4 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -6,10 +6,9 @@ from datetime import timedelta from pyessent import PyEssent import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, ENERGY_KILO_WATT_HOUR import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle SCAN_INTERVAL = timedelta(hours=1) @@ -82,7 +81,7 @@ class EssentBase: self._meter_data[possible_meter] = meter_data -class EssentMeter(Entity): +class EssentMeter(SensorEntity): """Representation of Essent measurements.""" def __init__(self, essent_base, meter, meter_type, tariff, unit): diff --git a/homeassistant/components/etherscan/sensor.py b/homeassistant/components/etherscan/sensor.py index e56b49181d4..1fa2edbf2e8 100644 --- a/homeassistant/components/etherscan/sensor.py +++ b/homeassistant/components/etherscan/sensor.py @@ -4,10 +4,9 @@ from datetime import timedelta from pyetherscan import get_balance import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_ADDRESS, CONF_NAME, CONF_TOKEN import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity ATTRIBUTION = "Data provided by etherscan.io" @@ -42,7 +41,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([EtherscanSensor(name, address, token, token_address)], True) -class EtherscanSensor(Entity): +class EtherscanSensor(SensorEntity): """Representation of an Etherscan.io sensor.""" def __init__(self, name, address, token, token_address): From 6cead320de1d3ba1e620edb140b6b81c8a2136e1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 22 Mar 2021 13:10:01 +0100 Subject: [PATCH 539/831] Bump colorlog to 4.8.0 (#48218) --- homeassistant/scripts/check_config.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index 40ce9a521df..893351c7715 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -20,7 +20,7 @@ import homeassistant.util.yaml.loader as yaml_loader # mypy: allow-untyped-calls, allow-untyped-defs -REQUIREMENTS = ("colorlog==4.7.2",) +REQUIREMENTS = ("colorlog==4.8.0",) _LOGGER = logging.getLogger(__name__) # pylint: disable=protected-access diff --git a/requirements_all.txt b/requirements_all.txt index 23c66f79e59..9c3251c9681 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -429,7 +429,7 @@ co2signal==0.4.2 coinbase==2.1.0 # homeassistant.scripts.check_config -colorlog==4.7.2 +colorlog==4.8.0 # homeassistant.components.color_extractor colorthief==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fb42375de7b..53f7b69d52d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -226,7 +226,7 @@ buienradar==1.0.4 caldav==0.7.1 # homeassistant.scripts.check_config -colorlog==4.7.2 +colorlog==4.8.0 # homeassistant.components.color_extractor colorthief==0.2.1 From b7ad5ff032f8f7baba882e25ae9ef31b53e281d6 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 22 Mar 2021 13:11:06 +0100 Subject: [PATCH 540/831] Upgrade pre-commit to 2.11.1 (#48219) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 79203fba763..21930782002 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -9,7 +9,7 @@ coverage==5.5 jsonpickle==1.4.1 mock-open==1.4.0 mypy==0.812 -pre-commit==2.11.0 +pre-commit==2.11.1 pylint==2.7.2 astroid==2.5.1 pipdeptree==1.0.0 From 3fb323b745f2d075fa6405b151c0afe142afe706 Mon Sep 17 00:00:00 2001 From: unaiur Date: Mon, 22 Mar 2021 13:13:06 +0100 Subject: [PATCH 541/831] Fix maxcube temperature for thermostat auto mode (#48047) maxcube-api dependency now supports using None as the target temperature: in that case, it uses the scheduled temperature in auto mode and current temperature in all other modes. We will use that feature when changing hvac_mode to auto and selecting PRESET_NONE. --- homeassistant/components/maxcube/climate.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/maxcube/climate.py b/homeassistant/components/maxcube/climate.py index 87da5953f26..7c114a927d6 100644 --- a/homeassistant/components/maxcube/climate.py +++ b/homeassistant/components/maxcube/climate.py @@ -160,13 +160,7 @@ class MaxCubeClimate(ClimateEntity): elif hvac_mode == HVAC_MODE_HEAT: temp = max(temp, self.min_temp) else: - # Reset the temperature to a sane value. - # Ideally, we should send 0 and the device will set its - # temperature according to the schedule. However, current - # version of the library has a bug which causes an - # exception when setting values below 8. - if temp in [OFF_TEMPERATURE, ON_TEMPERATURE]: - temp = device.eco_temperature + temp = None mode = MAX_DEVICE_MODE_AUTOMATIC cube = self._cubehandle.cube @@ -264,7 +258,7 @@ class MaxCubeClimate(ClimateEntity): def set_preset_mode(self, preset_mode): """Set new operation mode.""" device = self._cubehandle.cube.device_by_rf(self._rf_address) - temp = device.target_temperature + temp = None mode = MAX_DEVICE_MODE_AUTOMATIC if preset_mode in [PRESET_COMFORT, PRESET_ECO, PRESET_ON]: From 272dffc3843fbca32fbb4f92639c0a815753a888 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 13:15:45 +0100 Subject: [PATCH 542/831] Improve script tracing (#48100) * Improve script tracing * Fix test --- homeassistant/helpers/script.py | 28 +- .../automation/test_websocket_api.py | 18 +- tests/helpers/test_script.py | 647 +++++++++++++++++- 3 files changed, 645 insertions(+), 48 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 1824bb9e2d6..4cc2c1975e8 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -427,11 +427,12 @@ class _ScriptRun: delay = delay.total_seconds() self._changed() + trace_set_result(delay=delay, done=False) try: async with async_timeout.timeout(delay): await self._stop.wait() except asyncio.TimeoutError: - pass + trace_set_result(delay=delay, done=True) async def _async_wait_template_step(self): """Handle a wait template.""" @@ -443,6 +444,7 @@ class _ScriptRun: self._step_log("wait template", timeout) self._variables["wait"] = {"remaining": timeout, "completed": False} + trace_set_result(wait=self._variables["wait"]) wait_template = self._action[CONF_WAIT_TEMPLATE] wait_template.hass = self._hass @@ -455,10 +457,9 @@ class _ScriptRun: @callback def async_script_wait(entity_id, from_s, to_s): """Handle script after template condition is true.""" - self._variables["wait"] = { - "remaining": to_context.remaining if to_context else timeout, - "completed": True, - } + wait_var = self._variables["wait"] + wait_var["remaining"] = to_context.remaining if to_context else timeout + wait_var["completed"] = True done.set() to_context = None @@ -475,10 +476,11 @@ class _ScriptRun: async with async_timeout.timeout(timeout) as to_context: await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) except asyncio.TimeoutError as ex: + self._variables["wait"]["remaining"] = 0.0 if not self._action.get(CONF_CONTINUE_ON_TIMEOUT, True): self._log(_TIMEOUT_MSG) + trace_set_result(wait=self._variables["wait"], timeout=True) raise _StopScript from ex - self._variables["wait"]["remaining"] = 0.0 finally: for task in tasks: task.cancel() @@ -539,6 +541,7 @@ class _ScriptRun: else: limit = SERVICE_CALL_LIMIT + trace_set_result(params=params, running_script=running_script, limit=limit) service_task = self._hass.async_create_task( self._hass.services.async_call( **params, @@ -567,6 +570,7 @@ class _ScriptRun: async def _async_scene_step(self): """Activate the scene specified in the action.""" self._step_log("activate scene") + trace_set_result(scene=self._action[CONF_SCENE]) await self._hass.services.async_call( scene.DOMAIN, SERVICE_TURN_ON, @@ -592,6 +596,7 @@ class _ScriptRun: "Error rendering event data template: %s", ex, level=logging.ERROR ) + trace_set_result(event=self._action[CONF_EVENT], event_data=event_data) self._hass.bus.async_fire( self._action[CONF_EVENT], event_data, context=self._context ) @@ -748,14 +753,14 @@ class _ScriptRun: variables = {**self._variables} self._variables["wait"] = {"remaining": timeout, "trigger": None} + trace_set_result(wait=self._variables["wait"]) done = asyncio.Event() async def async_done(variables, context=None): - self._variables["wait"] = { - "remaining": to_context.remaining if to_context else timeout, - "trigger": variables["trigger"], - } + wait_var = self._variables["wait"] + wait_var["remaining"] = to_context.remaining if to_context else timeout + wait_var["trigger"] = variables["trigger"] done.set() def log_cb(level, msg, **kwargs): @@ -782,10 +787,11 @@ class _ScriptRun: async with async_timeout.timeout(timeout) as to_context: await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) except asyncio.TimeoutError as ex: + self._variables["wait"]["remaining"] = 0.0 if not self._action.get(CONF_CONTINUE_ON_TIMEOUT, True): self._log(_TIMEOUT_MSG) + trace_set_result(wait=self._variables["wait"], timeout=True) raise _StopScript from ex - self._variables["wait"]["remaining"] = 0.0 finally: for task in tasks: task.cancel() diff --git a/tests/components/automation/test_websocket_api.py b/tests/components/automation/test_websocket_api.py index ab69e89416a..6f630c27470 100644 --- a/tests/components/automation/test_websocket_api.py +++ b/tests/components/automation/test_websocket_api.py @@ -50,6 +50,18 @@ async def test_get_automation_trace(hass, hass_ws_client): "action": {"event": "another_event"}, } + sun_action = { + "limit": 10, + "params": { + "domain": "test", + "service": "automation", + "service_data": {}, + "target": {}, + }, + "running_script": False, + } + moon_action = {"event": "another_event", "event_data": {}} + assert await async_setup_component( hass, "automation", @@ -94,7 +106,7 @@ async def test_get_automation_trace(hass, hass_ws_client): assert len(trace["action_trace"]) == 1 assert len(trace["action_trace"]["action/0"]) == 1 assert trace["action_trace"]["action/0"][0]["error"] - assert "result" not in trace["action_trace"]["action/0"][0] + assert trace["action_trace"]["action/0"][0]["result"] == sun_action assert trace["condition_trace"] == {} assert trace["config"] == sun_config assert trace["context"] @@ -133,7 +145,7 @@ async def test_get_automation_trace(hass, hass_ws_client): assert len(trace["action_trace"]) == 1 assert len(trace["action_trace"]["action/0"]) == 1 assert "error" not in trace["action_trace"]["action/0"][0] - assert "result" not in trace["action_trace"]["action/0"][0] + assert trace["action_trace"]["action/0"][0]["result"] == moon_action assert len(trace["condition_trace"]) == 1 assert len(trace["condition_trace"]["condition/0"]) == 1 assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} @@ -212,7 +224,7 @@ async def test_get_automation_trace(hass, hass_ws_client): assert len(trace["action_trace"]) == 1 assert len(trace["action_trace"]["action/0"]) == 1 assert "error" not in trace["action_trace"]["action/0"][0] - assert "result" not in trace["action_trace"]["action/0"][0] + assert trace["action_trace"]["action/0"][0]["result"] == moon_action assert len(trace["condition_trace"]) == 1 assert len(trace["condition_trace"]["condition/0"]) == 1 assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 8a93d769775..917fc64b0e7 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -16,7 +16,8 @@ import voluptuous as vol from homeassistant import exceptions import homeassistant.components.scene as scene from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON -from homeassistant.core import Context, CoreState, callback +from homeassistant.core import SERVICE_CALL_LIMIT, Context, CoreState, callback +from homeassistant.exceptions import ConditionError, ServiceNotFound from homeassistant.helpers import config_validation as cv, script, trace from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.setup import async_setup_component @@ -31,18 +32,59 @@ from tests.common import ( ENTITY_ID = "script.test" +@pytest.fixture(autouse=True) +def prepare_tracing(): + """Prepare tracing.""" + trace.trace_get() + + +def compare_trigger_item(actual_trigger, expected_trigger): + """Compare trigger data description.""" + assert actual_trigger["description"] == expected_trigger["description"] + + +def compare_result_item(key, actual, expected): + """Compare an item in the result dict.""" + if key == "wait" and (expected.get("trigger") is not None): + assert "trigger" in actual + expected_trigger = expected.pop("trigger") + actual_trigger = actual.pop("trigger") + compare_trigger_item(actual_trigger, expected_trigger) + + assert actual == expected + + def assert_element(trace_element, expected_element, path): """Assert a trace element is as expected. Note: Unused variable 'path' is passed to get helpful errors from pytest. """ - for result_key, result in expected_element.get("result", {}).items(): + expected_result = expected_element.get("result", {}) + + # Check that every item in expected_element is present and equal in trace_element + # The redundant set operation gives helpful errors from pytest + assert not set(expected_result) - set(trace_element._result or {}) + for result_key, result in expected_result.items(): + compare_result_item(result_key, trace_element._result[result_key], result) assert trace_element._result[result_key] == result + + # Check for unexpected items in trace_element + assert not set(trace_element._result or {}) - set(expected_result) + if "error_type" in expected_element: assert isinstance(trace_element._error, expected_element["error_type"]) else: assert trace_element._error is None + # Don't check variables when script starts + if trace_element.path == "0": + return + + if "variables" in expected_element: + assert expected_element["variables"] == trace_element._variables + else: + assert not trace_element._variables + def assert_action_trace(expected): """Assert a trace condition sequence is as expected.""" @@ -82,9 +124,6 @@ async def test_firing_event_basic(hass, caplog): {"alias": alias, "event": event, "event_data": {"hello": "world"}} ) - # Prepare tracing - trace.trace_get() - script_obj = script.Script( hass, sequence, @@ -102,9 +141,12 @@ async def test_firing_event_basic(hass, caplog): assert ".test_name:" in caplog.text assert "Test Name: Running test script" in caplog.text assert f"Executing step {alias}" in caplog.text + assert_action_trace( { - "0": [{}], + "0": [ + {"result": {"event": "test_event", "event_data": {"hello": "world"}}}, + ], } ) @@ -150,6 +192,24 @@ async def test_firing_event_template(hass): "list2": ["yes", "yesyes"], } + assert_action_trace( + { + "0": [ + { + "result": { + "event": "test_event", + "event_data": { + "dict": {1: "yes", 2: "yesyes", 3: "yesyesyes"}, + "dict2": {1: "yes", 2: "yesyes", 3: "yesyesyes"}, + "list": ["yes", "yesyes"], + "list2": ["yes", "yesyes"], + }, + } + } + ], + } + ) + async def test_calling_service_basic(hass, caplog): """Test the calling of a service.""" @@ -170,6 +230,25 @@ async def test_calling_service_basic(hass, caplog): assert calls[0].data.get("hello") == "world" assert f"Executing step {alias}" in caplog.text + assert_action_trace( + { + "0": [ + { + "result": { + "limit": SERVICE_CALL_LIMIT, + "params": { + "domain": "test", + "service": "script", + "service_data": {"hello": "world"}, + "target": {}, + }, + "running_script": False, + } + } + ], + } + ) + async def test_calling_service_template(hass): """Test the calling of a service.""" @@ -204,6 +283,25 @@ async def test_calling_service_template(hass): assert calls[0].context is context assert calls[0].data.get("hello") == "world" + assert_action_trace( + { + "0": [ + { + "result": { + "limit": SERVICE_CALL_LIMIT, + "params": { + "domain": "test", + "service": "script", + "service_data": {"hello": "world"}, + "target": {}, + }, + "running_script": False, + } + } + ], + } + ) + async def test_data_template_with_templated_key(hass): """Test the calling of a service with a data_template with a templated key.""" @@ -222,7 +320,26 @@ async def test_data_template_with_templated_key(hass): assert len(calls) == 1 assert calls[0].context is context - assert "hello" in calls[0].data + assert calls[0].data.get("hello") == "world" + + assert_action_trace( + { + "0": [ + { + "result": { + "limit": SERVICE_CALL_LIMIT, + "params": { + "domain": "test", + "service": "script", + "service_data": {"hello": "world"}, + "target": {}, + }, + "running_script": False, + } + } + ], + } + ) async def test_multiple_runs_no_wait(hass): @@ -315,6 +432,12 @@ async def test_activating_scene(hass, caplog): assert calls[0].data.get(ATTR_ENTITY_ID) == "scene.hello" assert f"Executing step {alias}" in caplog.text + assert_action_trace( + { + "0": [{"result": {"scene": "scene.hello"}}], + } + ) + @pytest.mark.parametrize("count", [1, 3]) async def test_stop_no_wait(hass, count): @@ -390,6 +513,12 @@ async def test_delay_basic(hass): assert not script_obj.is_running assert script_obj.last_action is None + assert_action_trace( + { + "0": [{"result": {"delay": 5.0, "done": True}}], + } + ) + async def test_multiple_runs_delay(hass): """Test multiple runs with delay in script.""" @@ -454,6 +583,12 @@ async def test_delay_template_ok(hass): assert not script_obj.is_running + assert_action_trace( + { + "0": [{"result": {"delay": 5.0, "done": True}}], + } + ) + async def test_delay_template_invalid(hass, caplog): """Test the delay as a template that fails.""" @@ -481,6 +616,13 @@ async def test_delay_template_invalid(hass, caplog): assert not script_obj.is_running assert len(events) == 1 + assert_action_trace( + { + "0": [{"result": {"event": "test_event", "event_data": {}}}], + "1": [{"error_type": script._StopScript}], + } + ) + async def test_delay_template_complex_ok(hass): """Test the delay with a working complex template.""" @@ -501,6 +643,12 @@ async def test_delay_template_complex_ok(hass): assert not script_obj.is_running + assert_action_trace( + { + "0": [{"result": {"delay": 5.0, "done": True}}], + } + ) + async def test_delay_template_complex_invalid(hass, caplog): """Test the delay with a complex template that fails.""" @@ -528,6 +676,13 @@ async def test_delay_template_complex_invalid(hass, caplog): assert not script_obj.is_running assert len(events) == 1 + assert_action_trace( + { + "0": [{"result": {"event": "test_event", "event_data": {}}}], + "1": [{"error_type": script._StopScript}], + } + ) + async def test_cancel_delay(hass): """Test the cancelling while the delay is present.""" @@ -559,6 +714,12 @@ async def test_cancel_delay(hass): assert not script_obj.is_running assert len(events) == 0 + assert_action_trace( + { + "0": [{"result": {"delay": 5.0, "done": False}}], + } + ) + @pytest.mark.parametrize("action_type", ["template", "trigger"]) async def test_wait_basic(hass, action_type): @@ -594,6 +755,28 @@ async def test_wait_basic(hass, action_type): assert not script_obj.is_running assert script_obj.last_action is None + if action_type == "template": + assert_action_trace( + { + "0": [{"result": {"wait": {"completed": True, "remaining": None}}}], + } + ) + else: + assert_action_trace( + { + "0": [ + { + "result": { + "wait": { + "trigger": {"description": "state of switch.test"}, + "remaining": None, + } + } + } + ], + } + ) + async def test_wait_for_trigger_variables(hass): """Test variables are passed to wait_for_trigger action.""" @@ -672,6 +855,19 @@ async def test_wait_basic_times_out(hass, action_type): assert timed_out + if action_type == "template": + assert_action_trace( + { + "0": [{"result": {"wait": {"completed": False, "remaining": None}}}], + } + ) + else: + assert_action_trace( + { + "0": [{"result": {"wait": {"trigger": None, "remaining": None}}}], + } + ) + @pytest.mark.parametrize("action_type", ["template", "trigger"]) async def test_multiple_runs_wait(hass, action_type): @@ -769,6 +965,19 @@ async def test_cancel_wait(hass, action_type): assert not script_obj.is_running assert len(events) == 0 + if action_type == "template": + assert_action_trace( + { + "0": [{"result": {"wait": {"completed": False, "remaining": None}}}], + } + ) + else: + assert_action_trace( + { + "0": [{"result": {"wait": {"trigger": None, "remaining": None}}}], + } + ) + async def test_wait_template_not_schedule(hass): """Test the wait template with correct condition.""" @@ -790,6 +999,19 @@ async def test_wait_template_not_schedule(hass): assert not script_obj.is_running assert len(events) == 2 + assert_action_trace( + { + "0": [{"result": {"event": "test_event", "event_data": {}}}], + "1": [{"result": {"wait": {"completed": True, "remaining": None}}}], + "2": [ + { + "result": {"event": "test_event", "event_data": {}}, + "variables": {"wait": {"completed": True, "remaining": None}}, + } + ], + } + ) + @pytest.mark.parametrize( "timeout_param", [5, "{{ 5 }}", {"seconds": 5}, {"seconds": "{{ 5 }}"}] @@ -839,6 +1061,21 @@ async def test_wait_timeout(hass, caplog, timeout_param, action_type): assert len(events) == 1 assert "(timeout: 0:00:05)" in caplog.text + if action_type == "template": + variable_wait = {"wait": {"completed": False, "remaining": 0.0}} + else: + variable_wait = {"wait": {"trigger": None, "remaining": 0.0}} + expected_trace = { + "0": [{"result": variable_wait}], + "1": [ + { + "result": {"event": "test_event", "event_data": {}}, + "variables": variable_wait, + } + ], + } + assert_action_trace(expected_trace) + @pytest.mark.parametrize( "continue_on_timeout,n_events", [(False, 0), (True, 1), (None, 1)] @@ -884,6 +1121,25 @@ async def test_wait_continue_on_timeout( assert not script_obj.is_running assert len(events) == n_events + if action_type == "template": + variable_wait = {"wait": {"completed": False, "remaining": 0.0}} + else: + variable_wait = {"wait": {"trigger": None, "remaining": 0.0}} + expected_trace = { + "0": [{"result": variable_wait}], + } + if continue_on_timeout is False: + expected_trace["0"][0]["result"]["timeout"] = True + expected_trace["0"][0]["error_type"] = script._StopScript + else: + expected_trace["1"] = [ + { + "result": {"event": "test_event", "event_data": {}}, + "variables": variable_wait, + } + ] + assert_action_trace(expected_trace) + async def test_wait_template_variables_in(hass): """Test the wait template with input variables.""" @@ -908,6 +1164,12 @@ async def test_wait_template_variables_in(hass): assert not script_obj.is_running + assert_action_trace( + { + "0": [{"result": {"wait": {"completed": True, "remaining": None}}}], + } + ) + async def test_wait_template_with_utcnow(hass): """Test the wait template with utcnow.""" @@ -933,6 +1195,12 @@ async def test_wait_template_with_utcnow(hass): await hass.async_block_till_done() assert not script_obj.is_running + assert_action_trace( + { + "0": [{"result": {"wait": {"completed": True, "remaining": None}}}], + } + ) + async def test_wait_template_with_utcnow_no_match(hass): """Test the wait template with utcnow that does not match.""" @@ -963,6 +1231,12 @@ async def test_wait_template_with_utcnow_no_match(hass): assert timed_out + assert_action_trace( + { + "0": [{"result": {"wait": {"completed": False, "remaining": None}}}], + } + ) + @pytest.mark.parametrize("mode", ["no_timeout", "timeout_finish", "timeout_not_finish"]) @pytest.mark.parametrize("action_type", ["template", "trigger"]) @@ -1056,6 +1330,12 @@ async def test_wait_for_trigger_bad(hass, caplog): assert "Unknown error while setting up trigger" in caplog.text + assert_action_trace( + { + "0": [{"result": {"wait": {"trigger": None, "remaining": None}}}], + } + ) + async def test_wait_for_trigger_generated_exception(hass, caplog): """Test bad wait_for_trigger.""" @@ -1082,6 +1362,12 @@ async def test_wait_for_trigger_generated_exception(hass, caplog): assert "ValueError" in caplog.text assert "something bad" in caplog.text + assert_action_trace( + { + "0": [{"result": {"wait": {"trigger": None, "remaining": None}}}], + } + ) + async def test_condition_warning(hass, caplog): """Test warning on condition.""" @@ -1112,6 +1398,15 @@ async def test_condition_warning(hass, caplog): assert len(events) == 1 + assert_action_trace( + { + "0": [{"result": {"event": "test_event", "event_data": {}}}], + "1": [{"error_type": script._StopScript, "result": {"result": False}}], + "1/condition": [{"error_type": ConditionError}], + "1/condition/entity_id/0": [{"error_type": ConditionError}], + } + ) + async def test_condition_basic(hass, caplog): """Test if we can use conditions in a script.""" @@ -1139,6 +1434,15 @@ async def test_condition_basic(hass, caplog): caplog.clear() assert len(events) == 2 + assert_action_trace( + { + "0": [{"result": {"event": "test_event", "event_data": {}}}], + "1": [{"result": {"result": True}}], + "1/condition": [{"result": {"result": True}}], + "2": [{"result": {"event": "test_event", "event_data": {}}}], + } + ) + hass.states.async_set("test.entity", "goodbye") await script_obj.async_run(context=Context()) @@ -1147,6 +1451,14 @@ async def test_condition_basic(hass, caplog): assert f"Test condition {alias}: False" in caplog.text assert len(events) == 3 + assert_action_trace( + { + "0": [{"result": {"event": "test_event", "event_data": {}}}], + "1": [{"error_type": script._StopScript, "result": {"result": False}}], + "1/condition": [{"result": {"result": False}}], + } + ) + @patch("homeassistant.helpers.script.condition.async_from_config") async def test_condition_created_once(async_from_config, hass): @@ -1219,9 +1531,6 @@ async def test_repeat_count(hass, caplog, count): } ) - # Prepare tracing - trace.trace_get() - script_obj = script.Script(hass, sequence, "Test Name", "test_domain") await script_obj.async_run(context=Context()) @@ -1233,10 +1542,31 @@ async def test_repeat_count(hass, caplog, count): assert event.data.get("index") == index + 1 assert event.data.get("last") == (index == count - 1) assert caplog.text.count(f"Repeating {alias}") == count + first_index = max(1, count - script.ACTION_TRACE_NODE_MAX_LEN + 1) + last_index = count + 1 assert_action_trace( { "0": [{}], - "0/repeat/sequence/0": [{}] * min(count, script.ACTION_TRACE_NODE_MAX_LEN), + "0/repeat/sequence/0": [ + { + "result": { + "event": "test_event", + "event_data": { + "first": index == 1, + "index": index, + "last": index == count, + }, + }, + "variables": { + "repeat": { + "first": index == 1, + "index": index, + "last": index == count, + } + }, + } + for index in range(first_index, last_index) + ], } ) @@ -1281,6 +1611,26 @@ async def test_repeat_condition_warning(hass, caplog, condition): assert len(events) == count + expected_trace = {"0": [{}]} + if condition == "until": + expected_trace["0/repeat/sequence/0"] = [ + { + "result": {"event": "test_event", "event_data": {}}, + "variables": {"repeat": {"first": True, "index": 1}}, + } + ] + expected_trace["0/repeat"] = [ + { + "result": {"result": None}, + "variables": {"repeat": {"first": True, "index": 1}}, + } + ] + expected_trace[f"0/repeat/{condition}/0"] = [{"error_type": ConditionError}] + expected_trace[f"0/repeat/{condition}/0/entity_id/0"] = [ + {"error_type": ConditionError} + ] + assert_action_trace(expected_trace) + @pytest.mark.parametrize("condition", ["while", "until"]) @pytest.mark.parametrize("direct_template", [False, True]) @@ -1364,15 +1714,14 @@ async def test_repeat_var_in_condition(hass, condition): sequence = {"repeat": {"sequence": {"event": event}}} if condition == "while": - sequence["repeat"]["while"] = { - "condition": "template", - "value_template": "{{ repeat.index <= 2 }}", - } + value_template = "{{ repeat.index <= 2 }}" else: - sequence["repeat"]["until"] = { - "condition": "template", - "value_template": "{{ repeat.index == 2 }}", - } + value_template = "{{ repeat.index == 2 }}" + sequence["repeat"][condition] = { + "condition": "template", + "value_template": value_template, + } + script_obj = script.Script( hass, cv.SCRIPT_SCHEMA(sequence), "Test Name", "test_domain" ) @@ -1385,6 +1734,63 @@ async def test_repeat_var_in_condition(hass, condition): assert len(events) == 2 + if condition == "while": + expected_trace = { + "0": [{}], + "0/repeat": [ + { + "result": {"result": True}, + "variables": {"repeat": {"first": True, "index": 1}}, + }, + { + "result": {"result": True}, + "variables": {"repeat": {"first": False, "index": 2}}, + }, + { + "result": {"result": False}, + "variables": {"repeat": {"first": False, "index": 3}}, + }, + ], + "0/repeat/while/0": [ + {"result": {"result": True}}, + {"result": {"result": True}}, + {"result": {"result": False}}, + ], + "0/repeat/sequence/0": [ + {"result": {"event": "test_event", "event_data": {}}} + ] + * 2, + } + else: + expected_trace = { + "0": [{}], + "0/repeat/sequence/0": [ + { + "result": {"event": "test_event", "event_data": {}}, + "variables": {"repeat": {"first": True, "index": 1}}, + }, + { + "result": {"event": "test_event", "event_data": {}}, + "variables": {"repeat": {"first": False, "index": 2}}, + }, + ], + "0/repeat": [ + { + "result": {"result": False}, + "variables": {"repeat": {"first": True, "index": 1}}, + }, + { + "result": {"result": True}, + "variables": {"repeat": {"first": False, "index": 2}}, + }, + ], + "0/repeat/until/0": [ + {"result": {"result": False}}, + {"result": {"result": True}}, + ], + } + assert_action_trace(expected_trace) + @pytest.mark.parametrize( "variables,first_last,inside_x", @@ -1486,6 +1892,49 @@ async def test_repeat_nested(hass, variables, first_last, inside_x): "x": result[3], } + event_data1 = {"repeat": None, "x": inside_x} + event_data2 = [ + {"first": True, "index": 1, "last": False, "x": inside_x}, + {"first": False, "index": 2, "last": True, "x": inside_x}, + ] + variable_repeat = [ + {"repeat": {"first": True, "index": 1, "last": False}}, + {"repeat": {"first": False, "index": 2, "last": True}}, + ] + expected_trace = { + "0": [{"result": {"event": "test_event", "event_data": event_data1}}], + "1": [{}], + "1/repeat/sequence/0": [ + { + "result": {"event": "test_event", "event_data": event_data2[0]}, + "variables": variable_repeat[0], + }, + { + "result": {"event": "test_event", "event_data": event_data2[1]}, + "variables": variable_repeat[1], + }, + ], + "1/repeat/sequence/1": [{}, {}], + "1/repeat/sequence/1/repeat/sequence/0": [ + {"result": {"event": "test_event", "event_data": event_data2[0]}}, + { + "result": {"event": "test_event", "event_data": event_data2[1]}, + "variables": variable_repeat[1], + }, + { + "result": {"event": "test_event", "event_data": event_data2[0]}, + "variables": variable_repeat[0], + }, + {"result": {"event": "test_event", "event_data": event_data2[1]}}, + ], + "1/repeat/sequence/2": [ + {"result": {"event": "test_event", "event_data": event_data2[0]}}, + {"result": {"event": "test_event", "event_data": event_data2[1]}}, + ], + "2": [{"result": {"event": "test_event", "event_data": event_data1}}], + } + assert_action_trace(expected_trace) + async def test_choose_warning(hass, caplog): """Test warning on choose.""" @@ -1578,9 +2027,6 @@ async def test_choose(hass, caplog, var, result): } ) - # Prepare tracing - trace.trace_get() - script_obj = script.Script(hass, sequence, "Test Name", "test_domain") await script_obj.async_run(MappingProxyType({"var": var}), Context()) @@ -1593,19 +2039,29 @@ async def test_choose(hass, caplog, var, result): expected_choice = "default" assert f"{alias}: {expected_choice}: Executing step {aliases[var]}" in caplog.text - expected_trace = {"0": [{}]} - if var >= 1: - expected_trace["0/choose/0"] = [{}] - expected_trace["0/choose/0/conditions/0"] = [{}] - if var >= 2: - expected_trace["0/choose/1"] = [{}] - expected_trace["0/choose/1/conditions/0"] = [{}] - if var == 1: - expected_trace["0/choose/0/sequence/0"] = [{}] - if var == 2: - expected_trace["0/choose/1/sequence/0"] = [{}] + expected_choice = var - 1 if var == 3: - expected_trace["0/default/sequence/0"] = [{}] + expected_choice = "default" + + expected_trace = {"0": [{"result": {"choice": expected_choice}}]} + if var >= 1: + expected_trace["0/choose/0"] = [{"result": {"result": var == 1}}] + expected_trace["0/choose/0/conditions/0"] = [{"result": {"result": var == 1}}] + if var >= 2: + expected_trace["0/choose/1"] = [{"result": {"result": var == 2}}] + expected_trace["0/choose/1/conditions/0"] = [{"result": {"result": var == 2}}] + if var == 1: + expected_trace["0/choose/0/sequence/0"] = [ + {"result": {"event": "test_event", "event_data": {"choice": "first"}}} + ] + if var == 2: + expected_trace["0/choose/1/sequence/0"] = [ + {"result": {"event": "test_event", "event_data": {"choice": "second"}}} + ] + if var == 3: + expected_trace["0/default/sequence/0"] = [ + {"result": {"event": "test_event", "event_data": {"choice": "default"}}} + ] assert_action_trace(expected_trace) @@ -1668,6 +2124,25 @@ async def test_propagate_error_service_not_found(hass): assert len(events) == 0 assert not script_obj.is_running + expected_trace = { + "0": [ + { + "error_type": ServiceNotFound, + "result": { + "limit": 10, + "params": { + "domain": "test", + "service": "script", + "service_data": {}, + "target": {}, + }, + "running_script": False, + }, + } + ], + } + assert_action_trace(expected_trace) + async def test_propagate_error_invalid_service_data(hass): """Test that a script aborts when we send invalid service data.""" @@ -1686,6 +2161,25 @@ async def test_propagate_error_invalid_service_data(hass): assert len(calls) == 0 assert not script_obj.is_running + expected_trace = { + "0": [ + { + "error_type": vol.MultipleInvalid, + "result": { + "limit": 10, + "params": { + "domain": "test", + "service": "script", + "service_data": {"text": 1}, + "target": {}, + }, + "running_script": False, + }, + } + ], + } + assert_action_trace(expected_trace) + async def test_propagate_error_service_exception(hass): """Test that a script aborts when a service throws an exception.""" @@ -1708,6 +2202,25 @@ async def test_propagate_error_service_exception(hass): assert len(events) == 0 assert not script_obj.is_running + expected_trace = { + "0": [ + { + "error_type": ValueError, + "result": { + "limit": 10, + "params": { + "domain": "test", + "service": "script", + "service_data": {}, + "target": {}, + }, + "running_script": False, + }, + } + ], + } + assert_action_trace(expected_trace) + async def test_referenced_entities(hass): """Test referenced entities.""" @@ -2151,6 +2664,11 @@ async def test_shutdown_at(hass, caplog): assert not script_obj.is_running assert "Stopping scripts running at shutdown: test script" in caplog.text + expected_trace = { + "0": [{"result": {"delay": 120.0, "done": False}}], + } + assert_action_trace(expected_trace) + async def test_shutdown_after(hass, caplog): """Test stopping scripts at shutdown.""" @@ -2182,6 +2700,11 @@ async def test_shutdown_after(hass, caplog): in caplog.text ) + expected_trace = { + "0": [{"result": {"delay": 120.0, "done": False}}], + } + assert_action_trace(expected_trace) + async def test_update_logger(hass, caplog): """Test updating logger.""" @@ -2240,6 +2763,26 @@ async def test_set_variable(hass, caplog): assert mock_calls[0].data["value"] == "value" assert f"Executing step {alias}" in caplog.text + expected_trace = { + "0": [{}], + "1": [ + { + "result": { + "limit": SERVICE_CALL_LIMIT, + "params": { + "domain": "test", + "service": "script", + "service_data": {"value": "value"}, + "target": {}, + }, + "running_script": False, + }, + "variables": {"variable": "value"}, + } + ], + } + assert_action_trace(expected_trace) + async def test_set_redefines_variable(hass, caplog): """Test setting variables based on their current value.""" @@ -2261,6 +2804,42 @@ async def test_set_redefines_variable(hass, caplog): assert mock_calls[0].data["value"] == 1 assert mock_calls[1].data["value"] == 2 + expected_trace = { + "0": [{}], + "1": [ + { + "result": { + "limit": SERVICE_CALL_LIMIT, + "params": { + "domain": "test", + "service": "script", + "service_data": {"value": 1}, + "target": {}, + }, + "running_script": False, + }, + "variables": {"variable": "1"}, + } + ], + "2": [{}], + "3": [ + { + "result": { + "limit": SERVICE_CALL_LIMIT, + "params": { + "domain": "test", + "service": "script", + "service_data": {"value": 2}, + "target": {}, + }, + "running_script": False, + }, + "variables": {"variable": 2}, + } + ], + } + assert_action_trace(expected_trace) + async def test_validate_action_config(hass): """Validate action config.""" From dc15f243e6913a0dc0049cd508e1a21dea97f168 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 22 Mar 2021 13:29:39 +0100 Subject: [PATCH 543/831] Upgrade pyupgrade to v2.11.0 (#48220) --- .pre-commit-config.yaml | 2 +- homeassistant/auth/models.py | 4 ++-- homeassistant/components/esphome/entry_data.py | 2 +- homeassistant/components/http/web_runner.py | 2 +- homeassistant/components/wunderground/sensor.py | 2 +- homeassistant/components/zha/core/store.py | 2 +- homeassistant/exceptions.py | 2 +- homeassistant/helpers/device_registry.py | 2 +- homeassistant/helpers/entity_registry.py | 2 +- homeassistant/helpers/sun.py | 2 +- requirements_test_pre_commit.txt | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 031d0659aad..2ed5a8bd8d3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.10.1 + rev: v2.11.0 hooks: - id: pyupgrade args: [--py38-plus] diff --git a/homeassistant/auth/models.py b/homeassistant/auth/models.py index aaef7e02174..758bbdb78e2 100644 --- a/homeassistant/auth/models.py +++ b/homeassistant/auth/models.py @@ -46,7 +46,7 @@ class User: credentials: list[Credentials] = attr.ib(factory=list, eq=False, order=False) # Tokens associated with a user. - refresh_tokens: dict[str, "RefreshToken"] = attr.ib( + refresh_tokens: dict[str, RefreshToken] = attr.ib( factory=dict, eq=False, order=False ) @@ -109,7 +109,7 @@ class RefreshToken: last_used_at: datetime | None = attr.ib(default=None) last_used_ip: str | None = attr.ib(default=None) - credential: "Credentials" | None = attr.ib(default=None) + credential: Credentials | None = attr.ib(default=None) version: str | None = attr.ib(default=__version__) diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index 4fada10a3d1..34ed6ffee46 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -63,7 +63,7 @@ class RuntimeEntryData: # If an entity can't find anything in the info array, it will look for info here. old_info: dict[str, dict[str, Any]] = attr.ib(factory=dict) - services: dict[int, "UserService"] = attr.ib(factory=dict) + services: dict[int, UserService] = attr.ib(factory=dict) available: bool = attr.ib(default=False) device_info: DeviceInfo | None = attr.ib(default=None) cleanup_callbacks: list[Callable[[], None]] = attr.ib(factory=list) diff --git a/homeassistant/components/http/web_runner.py b/homeassistant/components/http/web_runner.py index 74410026f94..87468d40954 100644 --- a/homeassistant/components/http/web_runner.py +++ b/homeassistant/components/http/web_runner.py @@ -25,7 +25,7 @@ class HomeAssistantTCPSite(web.BaseSite): def __init__( self, - runner: "web.BaseRunner", + runner: web.BaseRunner, host: None | str | list[str], port: int, *, diff --git a/homeassistant/components/wunderground/sensor.py b/homeassistant/components/wunderground/sensor.py index 9e4b764f34c..38d6bba0b1f 100644 --- a/homeassistant/components/wunderground/sensor.py +++ b/homeassistant/components/wunderground/sensor.py @@ -68,7 +68,7 @@ class WUSensorConfig: self, friendly_name: str | Callable, feature: str, - value: Callable[["WUndergroundData"], Any], + value: Callable[[WUndergroundData], Any], unit_of_measurement: str | None = None, entity_picture=None, icon: str = "mdi:gauge", diff --git a/homeassistant/components/zha/core/store.py b/homeassistant/components/zha/core/store.py index 43b41153657..9381c529187 100644 --- a/homeassistant/components/zha/core/store.py +++ b/homeassistant/components/zha/core/store.py @@ -93,7 +93,7 @@ class ZhaStorage: """Load the registry of zha device entries.""" data = await self._store.async_load() - devices: "OrderedDict[str, ZhaDeviceEntry]" = OrderedDict() + devices: OrderedDict[str, ZhaDeviceEntry] = OrderedDict() if data is not None: for device in data["devices"]: diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index 499d4052849..375db789618 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -115,7 +115,7 @@ class Unauthorized(HomeAssistantError): def __init__( self, - context: "Context" | None = None, + context: Context | None = None, user_id: str | None = None, entity_id: str | None = None, config_entry_id: str | None = None, diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index fac937c4afb..bfa04706d60 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -688,7 +688,7 @@ def async_config_entry_disabled_by_changed( def async_cleanup( hass: HomeAssistantType, dev_reg: DeviceRegistry, - ent_reg: "entity_registry.EntityRegistry", + ent_reg: entity_registry.EntityRegistry, ) -> None: """Clean up device registry.""" # Find all devices that are referenced by a config_entry. diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 265dcd9d878..832838798ca 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -222,7 +222,7 @@ class EntityRegistry: # To disable an entity if it gets created disabled_by: str | None = None, # Data that we want entry to have - config_entry: "ConfigEntry" | None = None, + config_entry: ConfigEntry | None = None, device_id: str | None = None, area_id: str | None = None, capabilities: dict[str, Any] | None = None, diff --git a/homeassistant/helpers/sun.py b/homeassistant/helpers/sun.py index ded1b3a7123..5b23b3d0251 100644 --- a/homeassistant/helpers/sun.py +++ b/homeassistant/helpers/sun.py @@ -54,7 +54,7 @@ def get_astral_event_next( @callback def get_location_astral_event_next( - location: "astral.Location", + location: astral.Location, event: str, utc_point_in_time: datetime.datetime | None = None, offset: datetime.timedelta | None = None, diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index be881f09f7c..b8fabc685b1 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -9,5 +9,5 @@ flake8==3.8.4 isort==5.7.0 pydocstyle==5.1.1 python-typing-update==0.3.0 -pyupgrade==2.10.1 +pyupgrade==2.11.0 yamllint==1.24.2 From a583f56bd8a461ad48eb273ca5d99f6d7c8fb5ca Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 22 Mar 2021 14:35:24 +0100 Subject: [PATCH 544/831] Add identification for YAML imports (#48162) --- homeassistant/components/xiaomi_miio/config_flow.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py index efb668f0196..12e299879b2 100644 --- a/homeassistant/components/xiaomi_miio/config_flow.py +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -46,6 +46,8 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_import(self, conf: dict): """Import a configuration from config.yaml.""" + host = conf[CONF_HOST] + self.context.update({"title_placeholders": {"name": f"YAML import {host}"}}) return await self.async_step_device(user_input=conf) async def async_step_user(self, user_input=None): From 53a9c117ee3d622ba9d75626381b088514cc813c Mon Sep 17 00:00:00 2001 From: MatsNl <37705266+MatsNl@users.noreply.github.com> Date: Mon, 22 Mar 2021 14:43:46 +0100 Subject: [PATCH 545/831] Add jobstate parser to Onvif integration (#46589) --- homeassistant/components/onvif/parsers.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/homeassistant/components/onvif/parsers.py b/homeassistant/components/onvif/parsers.py index cad2b3c8cab..9574d44edea 100644 --- a/homeassistant/components/onvif/parsers.py +++ b/homeassistant/components/onvif/parsers.py @@ -387,3 +387,25 @@ async def async_parse_last_clock_sync(uid: str, msg) -> Event: ) except (AttributeError, KeyError, ValueError): return None + + +@PARSERS.register("tns1:RecordingConfig/JobState") +# pylint: disable=protected-access +async def async_parse_jobstate(uid: str, msg) -> Event: + """Handle parsing event message. + + Topic: tns1:RecordingConfig/JobState* + """ + + try: + source = msg.Message._value_1.Source.SimpleItem[0].Value + return Event( + f"{uid}_{msg.Topic._value_1}_{source}", + f"{source} JobState", + "binary_sensor", + None, + None, + msg.Message._value_1.Data.SimpleItem[0].Value == "Active", + ) + except (AttributeError, KeyError): + return None From 286217f7718d1bc6ebbaa19c8340ea386974d3f0 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Mon, 22 Mar 2021 14:59:40 +0100 Subject: [PATCH 546/831] Fix condition extra fields for climate and humidifier (#48184) --- homeassistant/components/climate/device_condition.py | 2 +- homeassistant/components/cover/device_action.py | 2 +- homeassistant/components/humidifier/device_condition.py | 2 +- tests/components/climate/test_device_condition.py | 2 +- tests/components/humidifier/test_device_condition.py | 6 ++---- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/climate/device_condition.py b/homeassistant/components/climate/device_condition.py index 10f3b5069b9..d20c202e93b 100644 --- a/homeassistant/components/climate/device_condition.py +++ b/homeassistant/components/climate/device_condition.py @@ -119,6 +119,6 @@ async def async_get_condition_capabilities(hass, config): else: preset_modes = [] - fields[vol.Required(const.ATTR_PRESET_MODES)] = vol.In(preset_modes) + fields[vol.Required(const.ATTR_PRESET_MODE)] = vol.In(preset_modes) return {"extra_fields": vol.Schema(fields)} diff --git a/homeassistant/components/cover/device_action.py b/homeassistant/components/cover/device_action.py index 4f92e7f09bd..6981f87c492 100644 --- a/homeassistant/components/cover/device_action.py +++ b/homeassistant/components/cover/device_action.py @@ -153,7 +153,7 @@ async def async_get_action_capabilities(hass: HomeAssistant, config: dict) -> di return { "extra_fields": vol.Schema( { - vol.Optional("position", default=0): vol.All( + vol.Optional(ATTR_POSITION, default=0): vol.All( vol.Coerce(int), vol.Range(min=0, max=100) ) } diff --git a/homeassistant/components/humidifier/device_condition.py b/homeassistant/components/humidifier/device_condition.py index 137fd6af73d..02a667f2f68 100644 --- a/homeassistant/components/humidifier/device_condition.py +++ b/homeassistant/components/humidifier/device_condition.py @@ -98,7 +98,7 @@ async def async_get_condition_capabilities(hass, config): else: modes = [] - fields[vol.Required(const.ATTR_AVAILABLE_MODES)] = vol.In(modes) + fields[vol.Required(ATTR_MODE)] = vol.In(modes) return {"extra_fields": vol.Schema(fields)} diff --git a/tests/components/climate/test_device_condition.py b/tests/components/climate/test_device_condition.py index 009d29dab39..27341b6c2c9 100644 --- a/tests/components/climate/test_device_condition.py +++ b/tests/components/climate/test_device_condition.py @@ -260,7 +260,7 @@ async def test_capabilities(hass): capabilities["extra_fields"], custom_serializer=cv.custom_serializer ) == [ { - "name": "preset_modes", + "name": "preset_mode", "options": [("home", "home"), ("away", "away")], "required": True, "type": "select", diff --git a/tests/components/humidifier/test_device_condition.py b/tests/components/humidifier/test_device_condition.py index 8b356552233..59887f65a33 100644 --- a/tests/components/humidifier/test_device_condition.py +++ b/tests/components/humidifier/test_device_condition.py @@ -256,7 +256,7 @@ async def test_capabilities(hass): capabilities["extra_fields"], custom_serializer=cv.custom_serializer ) == [ { - "name": "available_modes", + "name": "mode", "options": [("home", "home"), ("away", "away")], "required": True, "type": "select", @@ -282,9 +282,7 @@ async def test_capabilities_no_state(hass): assert voluptuous_serialize.convert( capabilities["extra_fields"], custom_serializer=cv.custom_serializer - ) == [ - {"name": "available_modes", "options": [], "required": True, "type": "select"} - ] + ) == [{"name": "mode", "options": [], "required": True, "type": "select"}] async def test_get_condition_capabilities(hass, device_reg, entity_reg): From 781084880b0bc8d1e73d3ec56b81f4717e520dc7 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Mon, 22 Mar 2021 07:59:12 -0700 Subject: [PATCH 547/831] Add an option to hide selected Hyperion effects (#45689) --- .../components/hyperion/config_flow.py | 38 ++++ homeassistant/components/hyperion/const.py | 2 + homeassistant/components/hyperion/light.py | 20 +- .../components/hyperion/strings.json | 5 +- tests/components/hyperion/__init__.py | 9 +- tests/components/hyperion/test_config_flow.py | 199 +++++++++++++++++- tests/components/hyperion/test_light.py | 24 ++- 7 files changed, 275 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/hyperion/config_flow.py b/homeassistant/components/hyperion/config_flow.py index bea8971cfa6..2b8d0ee8d8f 100644 --- a/homeassistant/components/hyperion/config_flow.py +++ b/homeassistant/components/hyperion/config_flow.py @@ -26,6 +26,7 @@ from homeassistant.const import ( CONF_TOKEN, ) from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType from . import create_hyperion_client @@ -34,6 +35,8 @@ from . import create_hyperion_client from .const import ( CONF_AUTH_ID, CONF_CREATE_TOKEN, + CONF_EFFECT_HIDE_LIST, + CONF_EFFECT_SHOW_LIST, CONF_PRIORITY, DEFAULT_ORIGIN, DEFAULT_PRIORITY, @@ -439,13 +442,44 @@ class HyperionOptionsFlow(OptionsFlow): """Initialize a Hyperion options flow.""" self._config_entry = config_entry + def _create_client(self) -> client.HyperionClient: + """Create and connect a client instance.""" + return create_hyperion_client( + self._config_entry.data[CONF_HOST], + self._config_entry.data[CONF_PORT], + token=self._config_entry.data.get(CONF_TOKEN), + ) + async def async_step_init( self, user_input: dict[str, Any] | None = None ) -> dict[str, Any]: """Manage the options.""" + + effects = {source: source for source in const.KEY_COMPONENTID_EXTERNAL_SOURCES} + async with self._create_client() as hyperion_client: + if not hyperion_client: + return self.async_abort(reason="cannot_connect") + for effect in hyperion_client.effects or []: + if const.KEY_NAME in effect: + effects[effect[const.KEY_NAME]] = effect[const.KEY_NAME] + + # If a new effect is added to Hyperion, we always want it to show by default. So + # rather than store a 'show list' in the config entry, we store a 'hide list'. + # However, it's more intuitive to ask the user to select which effects to show, + # so we inverse the meaning prior to storage. + if user_input is not None: + effect_show_list = user_input.pop(CONF_EFFECT_SHOW_LIST) + user_input[CONF_EFFECT_HIDE_LIST] = sorted( + set(effects) - set(effect_show_list) + ) return self.async_create_entry(title="", data=user_input) + default_effect_show_list = list( + set(effects) + - set(self._config_entry.options.get(CONF_EFFECT_HIDE_LIST, [])) + ) + return self.async_show_form( step_id="init", data_schema=vol.Schema( @@ -456,6 +490,10 @@ class HyperionOptionsFlow(OptionsFlow): CONF_PRIORITY, DEFAULT_PRIORITY ), ): vol.All(vol.Coerce(int), vol.Range(min=0, max=255)), + vol.Optional( + CONF_EFFECT_SHOW_LIST, + default=default_effect_show_list, + ): cv.multi_select(effects), } ), ) diff --git a/homeassistant/components/hyperion/const.py b/homeassistant/components/hyperion/const.py index 64c2f20052b..994ef580c91 100644 --- a/homeassistant/components/hyperion/const.py +++ b/homeassistant/components/hyperion/const.py @@ -31,6 +31,8 @@ CONF_INSTANCE_CLIENTS = "INSTANCE_CLIENTS" CONF_ON_UNLOAD = "ON_UNLOAD" CONF_PRIORITY = "priority" CONF_ROOT_CLIENT = "ROOT_CLIENT" +CONF_EFFECT_HIDE_LIST = "effect_hide_list" +CONF_EFFECT_SHOW_LIST = "effect_show_list" DEFAULT_NAME = "Hyperion" DEFAULT_ORIGIN = "Home Assistant" diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index d322362e959..36c3d836bf3 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -28,6 +28,7 @@ import homeassistant.util.color as color_util from . import get_hyperion_unique_id, listen_for_instance_updates from .const import ( + CONF_EFFECT_HIDE_LIST, CONF_INSTANCE_CLIENTS, CONF_PRIORITY, DEFAULT_ORIGIN, @@ -217,7 +218,10 @@ class HyperionBaseLight(LightEntity): def _get_option(self, key: str) -> Any: """Get a value from the provided options.""" - defaults = {CONF_PRIORITY: DEFAULT_PRIORITY} + defaults = { + CONF_PRIORITY: DEFAULT_PRIORITY, + CONF_EFFECT_HIDE_LIST: [], + } return self._options.get(key, defaults[key]) async def async_turn_on(self, **kwargs: Any) -> None: @@ -366,12 +370,18 @@ class HyperionBaseLight(LightEntity): if not self._client.effects: return effect_list: list[str] = [] + hide_effects = self._get_option(CONF_EFFECT_HIDE_LIST) + for effect in self._client.effects or []: if const.KEY_NAME in effect: - effect_list.append(effect[const.KEY_NAME]) - if effect_list: - self._effect_list = self._static_effect_list + effect_list - self.async_write_ha_state() + effect_name = effect[const.KEY_NAME] + if effect_name not in hide_effects: + effect_list.append(effect_name) + + self._effect_list = [ + effect for effect in self._static_effect_list if effect not in hide_effects + ] + effect_list + self.async_write_ha_state() @callback def _update_full_state(self) -> None: diff --git a/homeassistant/components/hyperion/strings.json b/homeassistant/components/hyperion/strings.json index ca7ed238f0b..54beb7704c9 100644 --- a/homeassistant/components/hyperion/strings.json +++ b/homeassistant/components/hyperion/strings.json @@ -45,9 +45,10 @@ "step": { "init": { "data": { - "priority": "Hyperion priority to use for colors and effects" + "priority": "Hyperion priority to use for colors and effects", + "effect_show_list": "Hyperion effects to show" } } } } -} \ No newline at end of file +} diff --git a/tests/components/hyperion/__init__.py b/tests/components/hyperion/__init__.py index e811a4dde32..d0653f88b83 100644 --- a/tests/components/hyperion/__init__.py +++ b/tests/components/hyperion/__init__.py @@ -118,7 +118,9 @@ def create_mock_client() -> Mock: def add_test_config_entry( - hass: HomeAssistantType, data: dict[str, Any] | None = None + hass: HomeAssistantType, + data: dict[str, Any] | None = None, + options: dict[str, Any] | None = None, ) -> ConfigEntry: """Add a test config entry.""" config_entry: MockConfigEntry = MockConfigEntry( # type: ignore[no-untyped-call] @@ -131,7 +133,7 @@ def add_test_config_entry( }, title=f"Hyperion {TEST_SYSINFO_ID}", unique_id=TEST_SYSINFO_ID, - options=TEST_CONFIG_ENTRY_OPTIONS, + options=options or TEST_CONFIG_ENTRY_OPTIONS, ) config_entry.add_to_hass(hass) # type: ignore[no-untyped-call] return config_entry @@ -141,9 +143,10 @@ async def setup_test_config_entry( hass: HomeAssistantType, config_entry: ConfigEntry | None = None, hyperion_client: Mock | None = None, + options: dict[str, Any] | None = None, ) -> ConfigEntry: """Add a test Hyperion entity to hass.""" - config_entry = config_entry or add_test_config_entry(hass) + config_entry = config_entry or add_test_config_entry(hass, options=options) hyperion_client = hyperion_client or create_mock_client() # pylint: disable=attribute-defined-outside-init diff --git a/tests/components/hyperion/test_config_flow.py b/tests/components/hyperion/test_config_flow.py index 15bca12b03f..7cf0556eddf 100644 --- a/tests/components/hyperion/test_config_flow.py +++ b/tests/components/hyperion/test_config_flow.py @@ -1,8 +1,9 @@ """Tests for the Hyperion config flow.""" from __future__ import annotations +import asyncio from typing import Any -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock, Mock, patch from hyperion import const @@ -10,6 +11,8 @@ from homeassistant import data_entry_flow from homeassistant.components.hyperion.const import ( CONF_AUTH_ID, CONF_CREATE_TOKEN, + CONF_EFFECT_HIDE_LIST, + CONF_EFFECT_SHOW_LIST, CONF_PRIORITY, DOMAIN, ) @@ -309,23 +312,42 @@ async def test_auth_static_token_success(hass: HomeAssistantType) -> None: } -async def test_auth_static_token_login_fail(hass: HomeAssistantType) -> None: - """Test correct behavior with a bad static token.""" +async def test_auth_static_token_login_connect_fail(hass: HomeAssistantType) -> None: + """Test correct behavior with a static token that cannot connect.""" result = await _init_flow(hass) assert result["step_id"] == "user" client = create_mock_client() client.async_is_auth_required = AsyncMock(return_value=TEST_AUTH_REQUIRED_RESP) - # Fail the login call. - client.async_login = AsyncMock( - return_value={"command": "authorize-login", "success": False, "tan": 0} - ) + with patch( + "homeassistant.components.hyperion.client.HyperionClient", return_value=client + ): + result = await _configure_flow(hass, result, user_input=TEST_HOST_PORT) + client.async_client_connect = AsyncMock(return_value=False) + result = await _configure_flow( + hass, result, user_input={CONF_CREATE_TOKEN: False, CONF_TOKEN: TEST_TOKEN} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "cannot_connect" + + +async def test_auth_static_token_login_fail(hass: HomeAssistantType) -> None: + """Test correct behavior with a static token that cannot login.""" + result = await _init_flow(hass) + assert result["step_id"] == "user" + + client = create_mock_client() + client.async_is_auth_required = AsyncMock(return_value=TEST_AUTH_REQUIRED_RESP) with patch( "homeassistant.components.hyperion.client.HyperionClient", return_value=client ): result = await _configure_flow(hass, result, user_input=TEST_HOST_PORT) + client.async_login = AsyncMock( + return_value={"command": "authorize-login", "success": False, "tan": 0} + ) result = await _configure_flow( hass, result, user_input={CONF_CREATE_TOKEN: False, CONF_TOKEN: TEST_TOKEN} ) @@ -377,6 +399,66 @@ async def test_auth_create_token_approval_declined(hass: HomeAssistantType) -> N assert result["reason"] == "auth_new_token_not_granted_error" +async def test_auth_create_token_approval_declined_task_canceled( + hass: HomeAssistantType, +) -> None: + """Verify correct behaviour when a token request is declined.""" + result = await _init_flow(hass) + + client = create_mock_client() + client.async_is_auth_required = AsyncMock(return_value=TEST_AUTH_REQUIRED_RESP) + + with patch( + "homeassistant.components.hyperion.client.HyperionClient", return_value=client + ): + result = await _configure_flow(hass, result, user_input=TEST_HOST_PORT) + assert result["step_id"] == "auth" + + client.async_request_token = AsyncMock(return_value=TEST_REQUEST_TOKEN_FAIL) + + class CanceledAwaitableMock(AsyncMock): + """A canceled awaitable mock.""" + + def __await__(self): + raise asyncio.CancelledError + + mock_task = CanceledAwaitableMock() + task_coro = None + + def create_task(arg): + nonlocal task_coro + task_coro = arg + return mock_task + + with patch( + "homeassistant.components.hyperion.client.HyperionClient", return_value=client + ), patch( + "homeassistant.components.hyperion.config_flow.client.generate_random_auth_id", + return_value=TEST_AUTH_ID, + ), patch.object( + hass, "async_create_task", side_effect=create_task + ): + result = await _configure_flow( + hass, result, user_input={CONF_CREATE_TOKEN: True} + ) + assert result["step_id"] == "create_token" + + result = await _configure_flow(hass, result) + assert result["step_id"] == "create_token_external" + + # Leave the task running, to ensure it is canceled. + mock_task.done = Mock(return_value=False) + mock_task.cancel = Mock() + + result = await _configure_flow(hass, result) + + # This await will advance to the next step. + await task_coro + + # Assert that cancel is called on the task. + assert mock_task.cancel.called + + async def test_auth_create_token_when_issued_token_fails( hass: HomeAssistantType, ) -> None: @@ -468,6 +550,47 @@ async def test_auth_create_token_success(hass: HomeAssistantType) -> None: } +async def test_auth_create_token_success_but_login_fail( + hass: HomeAssistantType, +) -> None: + """Verify correct behaviour when a token is successfully created but the login fails.""" + result = await _init_flow(hass) + + client = create_mock_client() + client.async_is_auth_required = AsyncMock(return_value=TEST_AUTH_REQUIRED_RESP) + + with patch( + "homeassistant.components.hyperion.client.HyperionClient", return_value=client + ): + result = await _configure_flow(hass, result, user_input=TEST_HOST_PORT) + assert result["step_id"] == "auth" + + client.async_request_token = AsyncMock(return_value=TEST_REQUEST_TOKEN_SUCCESS) + with patch( + "homeassistant.components.hyperion.client.HyperionClient", return_value=client + ), patch( + "homeassistant.components.hyperion.config_flow.client.generate_random_auth_id", + return_value=TEST_AUTH_ID, + ): + result = await _configure_flow( + hass, result, user_input={CONF_CREATE_TOKEN: True} + ) + assert result["step_id"] == "create_token" + + result = await _configure_flow(hass, result) + assert result["step_id"] == "create_token_external" + + client.async_login = AsyncMock( + return_value={"command": "authorize-login", "success": False, "tan": 0} + ) + + # The flow will be automatically advanced by the auth token response. + result = await _configure_flow(hass, result) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "auth_new_token_not_work_error" + + async def test_ssdp_success(hass: HomeAssistantType) -> None: """Check an SSDP flow.""" @@ -599,8 +722,8 @@ async def test_ssdp_abort_duplicates(hass: HomeAssistantType) -> None: assert result_2["reason"] == "already_in_progress" -async def test_options(hass: HomeAssistantType) -> None: - """Check an options flow.""" +async def test_options_priority(hass: HomeAssistantType) -> None: + """Check an options flow priority option.""" config_entry = add_test_config_entry(hass) @@ -618,11 +741,12 @@ async def test_options(hass: HomeAssistantType) -> None: new_priority = 1 result = await hass.config_entries.options.async_configure( - result["flow_id"], user_input={CONF_PRIORITY: new_priority} + result["flow_id"], + user_input={CONF_PRIORITY: new_priority}, ) await hass.async_block_till_done() assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["data"] == {CONF_PRIORITY: new_priority} + assert result["data"][CONF_PRIORITY] == new_priority # Turn the light on and ensure the new priority is used. client.async_send_set_color = AsyncMock(return_value=True) @@ -636,6 +760,59 @@ async def test_options(hass: HomeAssistantType) -> None: assert client.async_send_set_color.call_args[1][CONF_PRIORITY] == new_priority +async def test_options_effect_show_list(hass: HomeAssistantType) -> None: + """Check an options flow effect show list.""" + + config_entry = add_test_config_entry(hass) + + client = create_mock_client() + client.effects = [ + {const.KEY_NAME: "effect1"}, + {const.KEY_NAME: "effect2"}, + {const.KEY_NAME: "effect3"}, + ] + + with patch( + "homeassistant.components.hyperion.client.HyperionClient", return_value=client + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init(config_entry.entry_id) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_EFFECT_SHOW_LIST: ["effect1", "effect3"]}, + ) + await hass.async_block_till_done() + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + + # effect1 and effect3 only, so effect2 & external sources are hidden. + assert result["data"][CONF_EFFECT_HIDE_LIST] == sorted( + ["effect2"] + const.KEY_COMPONENTID_EXTERNAL_SOURCES + ) + + +async def test_options_effect_hide_list_cannot_connect(hass: HomeAssistantType) -> None: + """Check an options flow effect hide list with a failed connection.""" + + config_entry = add_test_config_entry(hass) + client = create_mock_client() + + with patch( + "homeassistant.components.hyperion.client.HyperionClient", return_value=client + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + client.async_client_connect = AsyncMock(return_value=False) + + result = await hass.config_entries.options.async_init(config_entry.entry_id) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "cannot_connect" + + async def test_reauth_success(hass: HomeAssistantType) -> None: """Check a reauth flow that succeeds.""" diff --git a/tests/components/hyperion/test_light.py b/tests/components/hyperion/test_light.py index e83f5059939..bb8fe8d0814 100644 --- a/tests/components/hyperion/test_light.py +++ b/tests/components/hyperion/test_light.py @@ -6,7 +6,11 @@ from unittest.mock import AsyncMock, Mock, call, patch from hyperion import const from homeassistant.components.hyperion import light as hyperion_light -from homeassistant.components.hyperion.const import DEFAULT_ORIGIN, DOMAIN +from homeassistant.components.hyperion.const import ( + CONF_EFFECT_HIDE_LIST, + DEFAULT_ORIGIN, + DOMAIN, +) from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_EFFECT, @@ -1129,3 +1133,21 @@ async def test_priority_light_has_no_external_sources(hass: HomeAssistantType) - entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1) assert entity_state assert entity_state.attributes["effect_list"] == [hyperion_light.KEY_EFFECT_SOLID] + + +async def test_light_option_effect_hide_list(hass: HomeAssistantType) -> None: + """Test the effect_hide_list option.""" + client = create_mock_client() + client.effects = [{const.KEY_NAME: "One"}, {const.KEY_NAME: "Two"}] + + await setup_test_config_entry( + hass, hyperion_client=client, options={CONF_EFFECT_HIDE_LIST: ["Two", "V4L"]} + ) + + entity_state = hass.states.get(TEST_ENTITY_ID_1) + assert entity_state.attributes["effect_list"] == [ + "Solid", + "BOBLIGHTSERVER", + "GRABBER", + "One", + ] From a49989241aee9a42d6b6347661ce8006b518bfb9 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 19:19:38 +0100 Subject: [PATCH 548/831] Refactor tracing: Move trace support to its own integration (#48224) --- CODEOWNERS | 1 + .../components/automation/__init__.py | 6 +- .../components/automation/manifest.json | 2 +- homeassistant/components/automation/trace.py | 87 ++----------------- homeassistant/components/trace/__init__.py | 12 +++ homeassistant/components/trace/const.py | 4 + homeassistant/components/trace/manifest.json | 9 ++ homeassistant/components/trace/trace.py | 35 ++++++++ homeassistant/components/trace/utils.py | 43 +++++++++ .../{automation => trace}/websocket_api.py | 10 +-- tests/components/trace/__init__.py | 1 + .../test_trace.py => trace/test_utils.py} | 6 +- .../test_websocket_api.py | 2 +- 13 files changed, 120 insertions(+), 98 deletions(-) create mode 100644 homeassistant/components/trace/__init__.py create mode 100644 homeassistant/components/trace/const.py create mode 100644 homeassistant/components/trace/manifest.json create mode 100644 homeassistant/components/trace/trace.py create mode 100644 homeassistant/components/trace/utils.py rename homeassistant/components/{automation => trace}/websocket_api.py (97%) create mode 100644 tests/components/trace/__init__.py rename tests/components/{automation/test_trace.py => trace/test_utils.py} (88%) rename tests/components/{automation => trace}/test_websocket_api.py (99%) diff --git a/CODEOWNERS b/CODEOWNERS index 2afbc288b0f..31ce9706baa 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -490,6 +490,7 @@ homeassistant/components/toon/* @frenck homeassistant/components/totalconnect/* @austinmroczek homeassistant/components/tplink/* @rytilahti @thegardenmonkey homeassistant/components/traccar/* @ludeeus +homeassistant/components/trace/* @home-assistant/core homeassistant/components/trafikverket_train/* @endor-force homeassistant/components/trafikverket_weatherstation/* @endor-force homeassistant/components/transmission/* @engrbm87 @JPHutchins diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 79c6dcc2312..1dd81afaa70 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -61,7 +61,6 @@ from homeassistant.helpers.typing import TemplateVarsType from homeassistant.loader import bind_hass from homeassistant.util.dt import parse_datetime -from . import websocket_api from .config import AutomationConfig, async_validate_config_item # Not used except by packages to check config structure @@ -76,7 +75,7 @@ from .const import ( LOGGER, ) from .helpers import async_get_blueprints -from .trace import DATA_AUTOMATION_TRACE, trace_automation +from .trace import trace_automation # mypy: allow-untyped-calls, allow-untyped-defs # mypy: no-check-untyped-defs, no-warn-return-any @@ -176,9 +175,6 @@ async def async_setup(hass, config): """Set up all automations.""" # Local import to avoid circular import hass.data[DOMAIN] = component = EntityComponent(LOGGER, DOMAIN, hass) - hass.data.setdefault(DATA_AUTOMATION_TRACE, {}) - - websocket_api.async_setup(hass) # To register the automation blueprints async_get_blueprints(hass) diff --git a/homeassistant/components/automation/manifest.json b/homeassistant/components/automation/manifest.json index 2db56eb597f..2483f57de8e 100644 --- a/homeassistant/components/automation/manifest.json +++ b/homeassistant/components/automation/manifest.json @@ -2,7 +2,7 @@ "domain": "automation", "name": "Automation", "documentation": "https://www.home-assistant.io/integrations/automation", - "dependencies": ["blueprint"], + "dependencies": ["blueprint", "trace"], "after_dependencies": [ "device_automation", "webhook" diff --git a/homeassistant/components/automation/trace.py b/homeassistant/components/automation/trace.py index 79fa4c844bc..de199ad9310 100644 --- a/homeassistant/components/automation/trace.py +++ b/homeassistant/components/automation/trace.py @@ -1,26 +1,17 @@ """Trace support for automation.""" from __future__ import annotations -from collections import OrderedDict from contextlib import contextmanager import datetime as dt -from datetime import timedelta from itertools import count -import logging -from typing import Any, Awaitable, Callable, Deque +from typing import Any, Deque -from homeassistant.core import Context, HomeAssistant, callback -from homeassistant.helpers.json import JSONEncoder as HAJSONEncoder +from homeassistant.components.trace.const import DATA_TRACE, STORED_TRACES +from homeassistant.components.trace.utils import LimitedSizeDict +from homeassistant.core import Context from homeassistant.helpers.trace import TraceElement, trace_id_set -from homeassistant.helpers.typing import TemplateVarsType from homeassistant.util import dt as dt_util -DATA_AUTOMATION_TRACE = "automation_trace" -STORED_TRACES = 5 # Stored traces per automation - -_LOGGER = logging.getLogger(__name__) -AutomationActionType = Callable[[HomeAssistant, TemplateVarsType], Awaitable[None]] - # mypy: allow-untyped-calls, allow-untyped-defs # mypy: no-check-untyped-defs, no-warn-return-any @@ -134,27 +125,6 @@ class AutomationTrace: return result -class LimitedSizeDict(OrderedDict): - """OrderedDict limited in size.""" - - def __init__(self, *args, **kwds): - """Initialize OrderedDict limited in size.""" - self.size_limit = kwds.pop("size_limit", None) - OrderedDict.__init__(self, *args, **kwds) - self._check_size_limit() - - def __setitem__(self, key, value): - """Set item and check dict size.""" - OrderedDict.__setitem__(self, key, value) - self._check_size_limit() - - def _check_size_limit(self): - """Check dict size and evict items in FIFO order if needed.""" - if self.size_limit is not None: - while len(self) > self.size_limit: - self.popitem(last=False) - - @contextmanager def trace_automation(hass, unique_id, config, context): """Trace action execution of automation with automation_id.""" @@ -162,7 +132,7 @@ def trace_automation(hass, unique_id, config, context): trace_id_set((unique_id, automation_trace.run_id)) if unique_id: - automation_traces = hass.data[DATA_AUTOMATION_TRACE] + automation_traces = hass.data[DATA_TRACE] if unique_id not in automation_traces: automation_traces[unique_id] = LimitedSizeDict(size_limit=STORED_TRACES) automation_traces[unique_id][automation_trace.run_id] = automation_trace @@ -176,50 +146,3 @@ def trace_automation(hass, unique_id, config, context): finally: if unique_id: automation_trace.finished() - - -@callback -def get_debug_trace(hass, automation_id, run_id): - """Return a serializable debug trace.""" - return hass.data[DATA_AUTOMATION_TRACE][automation_id][run_id] - - -@callback -def get_debug_traces_for_automation(hass, automation_id, summary=False): - """Return a serializable list of debug traces for an automation.""" - traces = [] - - for trace in hass.data[DATA_AUTOMATION_TRACE].get(automation_id, {}).values(): - if summary: - traces.append(trace.as_short_dict()) - else: - traces.append(trace.as_dict()) - - return traces - - -@callback -def get_debug_traces(hass, summary=False): - """Return a serializable list of debug traces.""" - traces = [] - - for automation_id in hass.data[DATA_AUTOMATION_TRACE]: - traces.extend(get_debug_traces_for_automation(hass, automation_id, summary)) - - return traces - - -class TraceJSONEncoder(HAJSONEncoder): - """JSONEncoder that supports Home Assistant objects and falls back to repr(o).""" - - def default(self, o: Any) -> Any: - """Convert certain objects. - - Fall back to repr(o). - """ - if isinstance(o, timedelta): - return {"__type": str(type(o)), "total_seconds": o.total_seconds()} - try: - return super().default(o) - except TypeError: - return {"__type": str(type(o)), "repr": repr(o)} diff --git a/homeassistant/components/trace/__init__.py b/homeassistant/components/trace/__init__.py new file mode 100644 index 00000000000..0dc8cda6664 --- /dev/null +++ b/homeassistant/components/trace/__init__.py @@ -0,0 +1,12 @@ +"""Support for automation and script tracing and debugging.""" +from . import websocket_api +from .const import DATA_TRACE + +DOMAIN = "trace" + + +async def async_setup(hass, config): + """Initialize the trace integration.""" + hass.data.setdefault(DATA_TRACE, {}) + websocket_api.async_setup(hass) + return True diff --git a/homeassistant/components/trace/const.py b/homeassistant/components/trace/const.py new file mode 100644 index 00000000000..547bdb35c77 --- /dev/null +++ b/homeassistant/components/trace/const.py @@ -0,0 +1,4 @@ +"""Shared constants for automation and script tracing and debugging.""" + +DATA_TRACE = "trace" +STORED_TRACES = 5 # Stored traces per automation diff --git a/homeassistant/components/trace/manifest.json b/homeassistant/components/trace/manifest.json new file mode 100644 index 00000000000..cdd857d00d3 --- /dev/null +++ b/homeassistant/components/trace/manifest.json @@ -0,0 +1,9 @@ +{ + "domain": "trace", + "name": "Trace", + "documentation": "https://www.home-assistant.io/integrations/automation", + "codeowners": [ + "@home-assistant/core" + ], + "quality_scale": "internal" +} diff --git a/homeassistant/components/trace/trace.py b/homeassistant/components/trace/trace.py new file mode 100644 index 00000000000..cc51e3269ab --- /dev/null +++ b/homeassistant/components/trace/trace.py @@ -0,0 +1,35 @@ +"""Support for automation and script tracing and debugging.""" +from homeassistant.core import callback + +from .const import DATA_TRACE + + +@callback +def get_debug_trace(hass, automation_id, run_id): + """Return a serializable debug trace.""" + return hass.data[DATA_TRACE][automation_id][run_id] + + +@callback +def get_debug_traces_for_automation(hass, automation_id, summary=False): + """Return a serializable list of debug traces for an automation.""" + traces = [] + + for trace in hass.data[DATA_TRACE].get(automation_id, {}).values(): + if summary: + traces.append(trace.as_short_dict()) + else: + traces.append(trace.as_dict()) + + return traces + + +@callback +def get_debug_traces(hass, summary=False): + """Return a serializable list of debug traces.""" + traces = [] + + for automation_id in hass.data[DATA_TRACE]: + traces.extend(get_debug_traces_for_automation(hass, automation_id, summary)) + + return traces diff --git a/homeassistant/components/trace/utils.py b/homeassistant/components/trace/utils.py new file mode 100644 index 00000000000..59bf8c98498 --- /dev/null +++ b/homeassistant/components/trace/utils.py @@ -0,0 +1,43 @@ +"""Helpers for automation and script tracing and debugging.""" +from collections import OrderedDict +from datetime import timedelta +from typing import Any + +from homeassistant.helpers.json import JSONEncoder as HAJSONEncoder + + +class LimitedSizeDict(OrderedDict): + """OrderedDict limited in size.""" + + def __init__(self, *args, **kwds): + """Initialize OrderedDict limited in size.""" + self.size_limit = kwds.pop("size_limit", None) + OrderedDict.__init__(self, *args, **kwds) + self._check_size_limit() + + def __setitem__(self, key, value): + """Set item and check dict size.""" + OrderedDict.__setitem__(self, key, value) + self._check_size_limit() + + def _check_size_limit(self): + """Check dict size and evict items in FIFO order if needed.""" + if self.size_limit is not None: + while len(self) > self.size_limit: + self.popitem(last=False) + + +class TraceJSONEncoder(HAJSONEncoder): + """JSONEncoder that supports Home Assistant objects and falls back to repr(o).""" + + def default(self, o: Any) -> Any: + """Convert certain objects. + + Fall back to repr(o). + """ + if isinstance(o, timedelta): + return {"__type": str(type(o)), "total_seconds": o.total_seconds()} + try: + return super().default(o) + except TypeError: + return {"__type": str(type(o)), "repr": repr(o)} diff --git a/homeassistant/components/automation/websocket_api.py b/homeassistant/components/trace/websocket_api.py similarity index 97% rename from homeassistant/components/automation/websocket_api.py rename to homeassistant/components/trace/websocket_api.py index 7bd1b57d064..9ac1828de14 100644 --- a/homeassistant/components/automation/websocket_api.py +++ b/homeassistant/components/trace/websocket_api.py @@ -24,12 +24,12 @@ from homeassistant.helpers.script import ( ) from .trace import ( - DATA_AUTOMATION_TRACE, - TraceJSONEncoder, + DATA_TRACE, get_debug_trace, get_debug_traces, get_debug_traces_for_automation, ) +from .utils import TraceJSONEncoder # mypy: allow-untyped-calls, allow-untyped-defs @@ -101,11 +101,9 @@ def websocket_automation_trace_contexts(hass, connection, msg): automation_id = msg.get("automation_id") if automation_id is not None: - values = { - automation_id: hass.data[DATA_AUTOMATION_TRACE].get(automation_id, {}) - } + values = {automation_id: hass.data[DATA_TRACE].get(automation_id, {})} else: - values = hass.data[DATA_AUTOMATION_TRACE] + values = hass.data[DATA_TRACE] contexts = { trace.context.id: {"run_id": trace.run_id, "automation_id": automation_id} diff --git a/tests/components/trace/__init__.py b/tests/components/trace/__init__.py new file mode 100644 index 00000000000..13937105ae3 --- /dev/null +++ b/tests/components/trace/__init__.py @@ -0,0 +1 @@ +"""The tests for Trace.""" diff --git a/tests/components/automation/test_trace.py b/tests/components/trace/test_utils.py similarity index 88% rename from tests/components/automation/test_trace.py rename to tests/components/trace/test_utils.py index 612a0ccfcab..ce0f09bfdd8 100644 --- a/tests/components/automation/test_trace.py +++ b/tests/components/trace/test_utils.py @@ -1,14 +1,14 @@ -"""Test Automation trace helpers.""" +"""Test trace helpers.""" from datetime import timedelta from homeassistant import core -from homeassistant.components import automation +from homeassistant.components import trace from homeassistant.util import dt as dt_util def test_json_encoder(hass): """Test the Trace JSON Encoder.""" - ha_json_enc = automation.trace.TraceJSONEncoder() + ha_json_enc = trace.utils.TraceJSONEncoder() state = core.State("test.test", "hello") # Test serializing a datetime diff --git a/tests/components/automation/test_websocket_api.py b/tests/components/trace/test_websocket_api.py similarity index 99% rename from tests/components/automation/test_websocket_api.py rename to tests/components/trace/test_websocket_api.py index 6f630c27470..882d857c014 100644 --- a/tests/components/automation/test_websocket_api.py +++ b/tests/components/trace/test_websocket_api.py @@ -1,4 +1,4 @@ -"""Test Automation config panel.""" +"""Test Trace websocket API.""" from unittest.mock import patch from homeassistant.bootstrap import async_setup_component From 339a56e434c466f4dc814f667358ae11ff89ff30 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 19:45:17 +0100 Subject: [PATCH 549/831] Migrate integrations f-h to extend SensorEntity (#48212) --- homeassistant/components/fail2ban/sensor.py | 5 ++--- homeassistant/components/fastdotcom/sensor.py | 3 ++- homeassistant/components/fibaro/sensor.py | 5 ++--- homeassistant/components/fido/sensor.py | 5 ++--- homeassistant/components/file/sensor.py | 5 ++--- homeassistant/components/filesize/sensor.py | 5 ++--- homeassistant/components/filter/sensor.py | 16 ++++++++-------- homeassistant/components/fints/sensor.py | 7 +++---- .../components/fireservicerota/sensor.py | 3 ++- homeassistant/components/firmata/sensor.py | 4 ++-- homeassistant/components/fitbit/sensor.py | 5 ++--- homeassistant/components/fixer/sensor.py | 5 ++--- .../components/flick_electric/sensor.py | 4 ++-- homeassistant/components/flo/sensor.py | 15 ++++++++------- homeassistant/components/flume/sensor.py | 4 ++-- homeassistant/components/flunearyou/sensor.py | 3 ++- homeassistant/components/folder/sensor.py | 5 ++--- homeassistant/components/foobot/sensor.py | 3 ++- homeassistant/components/freebox/sensor.py | 4 ++-- homeassistant/components/fritzbox/sensor.py | 4 ++-- .../components/fritzbox_callmonitor/sensor.py | 5 ++--- .../components/fritzbox_netmonitor/sensor.py | 5 ++--- homeassistant/components/fronius/sensor.py | 5 ++--- .../components/garmin_connect/sensor.py | 4 ++-- homeassistant/components/gdacs/sensor.py | 4 ++-- homeassistant/components/geizhals/sensor.py | 5 ++--- homeassistant/components/geniushub/sensor.py | 5 +++-- .../components/geo_rss_events/sensor.py | 5 ++--- .../components/geonetnz_quakes/sensor.py | 4 ++-- .../components/geonetnz_volcano/sensor.py | 4 ++-- homeassistant/components/github/sensor.py | 5 ++--- homeassistant/components/gitlab_ci/sensor.py | 5 ++--- homeassistant/components/gitter/sensor.py | 5 ++--- homeassistant/components/glances/sensor.py | 4 ++-- homeassistant/components/gogogate2/sensor.py | 5 +++-- .../components/google_travel_time/sensor.py | 5 ++--- .../components/google_wifi/sensor.py | 5 ++--- homeassistant/components/gpsd/sensor.py | 5 ++--- .../components/greeneye_monitor/sensor.py | 4 ++-- .../components/growatt_server/sensor.py | 5 ++--- homeassistant/components/gtfs/sensor.py | 5 ++--- homeassistant/components/guardian/sensor.py | 5 +++-- homeassistant/components/habitica/sensor.py | 6 +++--- homeassistant/components/hassio/sensor.py | 5 +++-- .../components/haveibeenpwned/sensor.py | 5 ++--- homeassistant/components/hddtemp/sensor.py | 5 ++--- .../components/here_travel_time/sensor.py | 5 ++--- .../components/history_stats/sensor.py | 5 ++--- homeassistant/components/hive/sensor.py | 5 ++--- .../components/home_connect/sensor.py | 3 ++- .../components/homekit_controller/sensor.py | 13 +++++++------ homeassistant/components/homematic/sensor.py | 3 ++- .../components/homematicip_cloud/sensor.py | 19 ++++++++++--------- homeassistant/components/hp_ilo/sensor.py | 5 ++--- homeassistant/components/htu21d/sensor.py | 5 ++--- homeassistant/components/huawei_lte/sensor.py | 3 ++- homeassistant/components/hue/sensor.py | 6 +++--- homeassistant/components/huisbaasje/sensor.py | 3 ++- .../hunterdouglas_powerview/sensor.py | 3 ++- .../components/hvv_departures/sensor.py | 4 ++-- homeassistant/components/hydrawise/sensor.py | 4 ++-- 61 files changed, 154 insertions(+), 167 deletions(-) diff --git a/homeassistant/components/fail2ban/sensor.py b/homeassistant/components/fail2ban/sensor.py index 30aa0c69838..87a8460f149 100644 --- a/homeassistant/components/fail2ban/sensor.py +++ b/homeassistant/components/fail2ban/sensor.py @@ -6,10 +6,9 @@ import re import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_FILE_PATH, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -45,7 +44,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(device_list, True) -class BanSensor(Entity): +class BanSensor(SensorEntity): """Implementation of a fail2ban sensor.""" def __init__(self, name, jail, log_parser): diff --git a/homeassistant/components/fastdotcom/sensor.py b/homeassistant/components/fastdotcom/sensor.py index ad3711b1319..b4406a4de95 100644 --- a/homeassistant/components/fastdotcom/sensor.py +++ b/homeassistant/components/fastdotcom/sensor.py @@ -1,4 +1,5 @@ """Support for Fast.com internet speed testing sensor.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DATA_RATE_MEGABITS_PER_SECOND from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -14,7 +15,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([SpeedtestSensor(hass.data[FASTDOTCOM_DOMAIN])]) -class SpeedtestSensor(RestoreEntity): +class SpeedtestSensor(RestoreEntity, SensorEntity): """Implementation of a FAst.com sensor.""" def __init__(self, speedtest_data): diff --git a/homeassistant/components/fibaro/sensor.py b/homeassistant/components/fibaro/sensor.py index 40a843bc7bb..fd25a3ce7eb 100644 --- a/homeassistant/components/fibaro/sensor.py +++ b/homeassistant/components/fibaro/sensor.py @@ -1,5 +1,5 @@ """Support for Fibaro sensors.""" -from homeassistant.components.sensor import DOMAIN +from homeassistant.components.sensor import DOMAIN, SensorEntity from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, DEVICE_CLASS_CO2, @@ -11,7 +11,6 @@ from homeassistant.const import ( TEMP_CELSIUS, TEMP_FAHRENHEIT, ) -from homeassistant.helpers.entity import Entity from . import FIBARO_DEVICES, FibaroDevice @@ -55,7 +54,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class FibaroSensor(FibaroDevice, Entity): +class FibaroSensor(FibaroDevice, SensorEntity): """Representation of a Fibaro Sensor.""" def __init__(self, fibaro_device): diff --git a/homeassistant/components/fido/sensor.py b/homeassistant/components/fido/sensor.py index fe9c60c85a6..55ec455d8f1 100644 --- a/homeassistant/components/fido/sensor.py +++ b/homeassistant/components/fido/sensor.py @@ -11,7 +11,7 @@ from pyfido import FidoClient from pyfido.client import PyFidoError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_VARIABLES, CONF_NAME, @@ -21,7 +21,6 @@ from homeassistant.const import ( TIME_MINUTES, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -90,7 +89,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors, True) -class FidoSensor(Entity): +class FidoSensor(SensorEntity): """Implementation of a Fido sensor.""" def __init__(self, fido_data, sensor_type, name, number): diff --git a/homeassistant/components/file/sensor.py b/homeassistant/components/file/sensor.py index 3368bd878d5..5d8a9475235 100644 --- a/homeassistant/components/file/sensor.py +++ b/homeassistant/components/file/sensor.py @@ -4,7 +4,7 @@ import os import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_FILE_PATH, CONF_NAME, @@ -12,7 +12,6 @@ from homeassistant.const import ( CONF_VALUE_TEMPLATE, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -46,7 +45,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= _LOGGER.error("'%s' is not an allowed directory", file_path) -class FileSensor(Entity): +class FileSensor(SensorEntity): """Implementation of a file sensor.""" def __init__(self, name, file_path, unit_of_measurement, value_template): diff --git a/homeassistant/components/filesize/sensor.py b/homeassistant/components/filesize/sensor.py index 312805e4942..856b29364ae 100644 --- a/homeassistant/components/filesize/sensor.py +++ b/homeassistant/components/filesize/sensor.py @@ -5,10 +5,9 @@ import os import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import DATA_MEGABYTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.reload import setup_reload_service from . import DOMAIN, PLATFORMS @@ -40,7 +39,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class Filesize(Entity): +class Filesize(SensorEntity): """Encapsulates file size information.""" def __init__(self, path): diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py index d74491dd9c6..2f1705f5f4d 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -18,6 +18,7 @@ from homeassistant.components.sensor import ( DEVICE_CLASSES as SENSOR_DEVICE_CLASSES, DOMAIN as SENSOR_DOMAIN, PLATFORM_SCHEMA, + SensorEntity, ) from homeassistant.const import ( ATTR_DEVICE_CLASS, @@ -31,7 +32,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.util.decorator import Registry @@ -179,7 +179,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([SensorFilter(name, entity_id, filters)]) -class SensorFilter(Entity): +class SensorFilter(SensorEntity): """Representation of a Filter Sensor.""" def __init__(self, name, entity_id, filters): @@ -454,7 +454,7 @@ class Filter: @FILTERS.register(FILTER_NAME_RANGE) -class RangeFilter(Filter): +class RangeFilter(Filter, SensorEntity): """Range filter. Determines if new state is in the range of upper_bound and lower_bound. @@ -509,7 +509,7 @@ class RangeFilter(Filter): @FILTERS.register(FILTER_NAME_OUTLIER) -class OutlierFilter(Filter): +class OutlierFilter(Filter, SensorEntity): """BASIC outlier filter. Determines if new state is in a band around the median. @@ -547,7 +547,7 @@ class OutlierFilter(Filter): @FILTERS.register(FILTER_NAME_LOWPASS) -class LowPassFilter(Filter): +class LowPassFilter(Filter, SensorEntity): """BASIC Low Pass Filter.""" def __init__(self, window_size, precision, entity, time_constant: int): @@ -571,7 +571,7 @@ class LowPassFilter(Filter): @FILTERS.register(FILTER_NAME_TIME_SMA) -class TimeSMAFilter(Filter): +class TimeSMAFilter(Filter, SensorEntity): """Simple Moving Average (SMA) Filter. The window_size is determined by time, and SMA is time weighted. @@ -617,7 +617,7 @@ class TimeSMAFilter(Filter): @FILTERS.register(FILTER_NAME_THROTTLE) -class ThrottleFilter(Filter): +class ThrottleFilter(Filter, SensorEntity): """Throttle Filter. One sample per window. @@ -640,7 +640,7 @@ class ThrottleFilter(Filter): @FILTERS.register(FILTER_NAME_TIME_THROTTLE) -class TimeThrottleFilter(Filter): +class TimeThrottleFilter(Filter, SensorEntity): """Time Throttle Filter. One sample per time period. diff --git a/homeassistant/components/fints/sensor.py b/homeassistant/components/fints/sensor.py index 96d6ecc2166..e7faff46155 100644 --- a/homeassistant/components/fints/sensor.py +++ b/homeassistant/components/fints/sensor.py @@ -8,10 +8,9 @@ from fints.client import FinTS3PinTanClient from fints.dialog import FinTSDialogError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_PIN, CONF_URL, CONF_USERNAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -154,7 +153,7 @@ class FinTsClient: return balance_accounts, holdings_accounts -class FinTsAccount(Entity): +class FinTsAccount(SensorEntity): """Sensor for a FinTS balance account. A balance account contains an amount of money (=balance). The amount may @@ -206,7 +205,7 @@ class FinTsAccount(Entity): return ICON -class FinTsHoldingsAccount(Entity): +class FinTsHoldingsAccount(SensorEntity): """Sensor for a FinTS holdings account. A holdings account does not contain money but rather some financial diff --git a/homeassistant/components/fireservicerota/sensor.py b/homeassistant/components/fireservicerota/sensor.py index 7dc8f546c98..04d8c97a4a5 100644 --- a/homeassistant/components/fireservicerota/sensor.py +++ b/homeassistant/components/fireservicerota/sensor.py @@ -1,6 +1,7 @@ """Sensor platform for FireServiceRota integration.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -21,7 +22,7 @@ async def async_setup_entry( async_add_entities([IncidentsSensor(client)]) -class IncidentsSensor(RestoreEntity): +class IncidentsSensor(RestoreEntity, SensorEntity): """Representation of FireServiceRota incidents sensor.""" def __init__(self, client): diff --git a/homeassistant/components/firmata/sensor.py b/homeassistant/components/firmata/sensor.py index cb9db1f11e5..fedac6f76d9 100644 --- a/homeassistant/components/firmata/sensor.py +++ b/homeassistant/components/firmata/sensor.py @@ -2,10 +2,10 @@ import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME, CONF_PIN from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import Entity from .const import CONF_DIFFERENTIAL, CONF_PIN_MODE, DOMAIN from .entity import FirmataPinEntity @@ -42,7 +42,7 @@ async def async_setup_entry( async_add_entities(new_entities) -class FirmataSensor(FirmataPinEntity, Entity): +class FirmataSensor(FirmataPinEntity, SensorEntity): """Representation of a sensor on a Firmata board.""" async def async_added_to_hass(self) -> None: diff --git a/homeassistant/components/fitbit/sensor.py b/homeassistant/components/fitbit/sensor.py index b90d33adaff..8571d31bc8a 100644 --- a/homeassistant/components/fitbit/sensor.py +++ b/homeassistant/components/fitbit/sensor.py @@ -10,7 +10,7 @@ from oauthlib.oauth2.rfc6749.errors import MismatchingStateError, MissingTokenEr import voluptuous as vol from homeassistant.components.http import HomeAssistantView -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_CLIENT_ID, @@ -25,7 +25,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.helpers.network import get_url from homeassistant.util.json import load_json, save_json @@ -403,7 +402,7 @@ class FitbitAuthCallbackView(HomeAssistantView): return html_response -class FitbitSensor(Entity): +class FitbitSensor(SensorEntity): """Implementation of a Fitbit sensor.""" def __init__( diff --git a/homeassistant/components/fixer/sensor.py b/homeassistant/components/fixer/sensor.py index 9641f596e61..9214dd6907e 100644 --- a/homeassistant/components/fixer/sensor.py +++ b/homeassistant/components/fixer/sensor.py @@ -6,10 +6,9 @@ from fixerio import Fixerio from fixerio.exceptions import FixerioException import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME, CONF_TARGET import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -49,7 +48,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([ExchangeRateSensor(data, name, target)], True) -class ExchangeRateSensor(Entity): +class ExchangeRateSensor(SensorEntity): """Representation of a Exchange sensor.""" def __init__(self, data, name, target): diff --git a/homeassistant/components/flick_electric/sensor.py b/homeassistant/components/flick_electric/sensor.py index 4f152c54e3c..e6880600212 100644 --- a/homeassistant/components/flick_electric/sensor.py +++ b/homeassistant/components/flick_electric/sensor.py @@ -5,10 +5,10 @@ import logging import async_timeout from pyflick import FlickAPI, FlickPrice +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ATTRIBUTION, ATTR_FRIENDLY_NAME from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import Entity from homeassistant.util.dt import utcnow from .const import ATTR_COMPONENTS, ATTR_END_AT, ATTR_START_AT, DOMAIN @@ -33,7 +33,7 @@ async def async_setup_entry( async_add_entities([FlickPricingSensor(api)], True) -class FlickPricingSensor(Entity): +class FlickPricingSensor(SensorEntity): """Entity object for Flick Electric sensor.""" def __init__(self, api: FlickAPI): diff --git a/homeassistant/components/flo/sensor.py b/homeassistant/components/flo/sensor.py index 18ee067af5a..1e362e75f8c 100644 --- a/homeassistant/components/flo/sensor.py +++ b/homeassistant/components/flo/sensor.py @@ -1,6 +1,7 @@ """Support for Flo Water Monitor sensors.""" from __future__ import annotations +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, @@ -56,7 +57,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class FloDailyUsageSensor(FloEntity): +class FloDailyUsageSensor(FloEntity, SensorEntity): """Monitors the daily water usage.""" def __init__(self, device): @@ -82,7 +83,7 @@ class FloDailyUsageSensor(FloEntity): return VOLUME_GALLONS -class FloSystemModeSensor(FloEntity): +class FloSystemModeSensor(FloEntity, SensorEntity): """Monitors the current Flo system mode.""" def __init__(self, device): @@ -98,7 +99,7 @@ class FloSystemModeSensor(FloEntity): return self._device.current_system_mode -class FloCurrentFlowRateSensor(FloEntity): +class FloCurrentFlowRateSensor(FloEntity, SensorEntity): """Monitors the current water flow rate.""" def __init__(self, device): @@ -124,7 +125,7 @@ class FloCurrentFlowRateSensor(FloEntity): return "gpm" -class FloTemperatureSensor(FloEntity): +class FloTemperatureSensor(FloEntity, SensorEntity): """Monitors the temperature.""" def __init__(self, name, device): @@ -150,7 +151,7 @@ class FloTemperatureSensor(FloEntity): return DEVICE_CLASS_TEMPERATURE -class FloHumiditySensor(FloEntity): +class FloHumiditySensor(FloEntity, SensorEntity): """Monitors the humidity.""" def __init__(self, device): @@ -176,7 +177,7 @@ class FloHumiditySensor(FloEntity): return DEVICE_CLASS_HUMIDITY -class FloPressureSensor(FloEntity): +class FloPressureSensor(FloEntity, SensorEntity): """Monitors the water pressure.""" def __init__(self, device): @@ -202,7 +203,7 @@ class FloPressureSensor(FloEntity): return DEVICE_CLASS_PRESSURE -class FloBatterySensor(FloEntity): +class FloBatterySensor(FloEntity, SensorEntity): """Monitors the battery level for battery-powered leak detectors.""" def __init__(self, device): diff --git a/homeassistant/components/flume/sensor.py b/homeassistant/components/flume/sensor.py index 65584239910..d890443d238 100644 --- a/homeassistant/components/flume/sensor.py +++ b/homeassistant/components/flume/sensor.py @@ -6,7 +6,7 @@ from numbers import Number from pyflume import FlumeData import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( CONF_CLIENT_ID, @@ -108,7 +108,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(flume_entity_list) -class FlumeSensor(CoordinatorEntity): +class FlumeSensor(CoordinatorEntity, SensorEntity): """Representation of the Flume sensor.""" def __init__(self, coordinator, flume_device, flume_query_sensor, name, device_id): diff --git a/homeassistant/components/flunearyou/sensor.py b/homeassistant/components/flunearyou/sensor.py index 225ed15d98b..066126c390e 100644 --- a/homeassistant/components/flunearyou/sensor.py +++ b/homeassistant/components/flunearyou/sensor.py @@ -1,4 +1,5 @@ """Support for user- and CDC-based flu info sensors from Flu Near You.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_STATE, @@ -85,7 +86,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -class FluNearYouSensor(CoordinatorEntity): +class FluNearYouSensor(CoordinatorEntity, SensorEntity): """Define a base Flu Near You sensor.""" def __init__(self, coordinator, config_entry, sensor_type, name, icon, unit): diff --git a/homeassistant/components/folder/sensor.py b/homeassistant/components/folder/sensor.py index a6dccf57602..707f22f98ba 100644 --- a/homeassistant/components/folder/sensor.py +++ b/homeassistant/components/folder/sensor.py @@ -6,10 +6,9 @@ import os import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import DATA_MEGABYTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -51,7 +50,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([folder], True) -class Folder(Entity): +class Folder(SensorEntity): """Representation of a folder.""" ICON = "mdi:folder" diff --git a/homeassistant/components/foobot/sensor.py b/homeassistant/components/foobot/sensor.py index 9ca265ba9ef..996ac1b1049 100644 --- a/homeassistant/components/foobot/sensor.py +++ b/homeassistant/components/foobot/sensor.py @@ -7,6 +7,7 @@ import aiohttp from foobot_async import FoobotClient import voluptuous as vol +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_TEMPERATURE, ATTR_TIME, @@ -91,7 +92,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(dev, True) -class FoobotSensor(Entity): +class FoobotSensor(SensorEntity): """Implementation of a Foobot sensor.""" def __init__(self, data, device, sensor_type): diff --git a/homeassistant/components/freebox/sensor.py b/homeassistant/components/freebox/sensor.py index 9f125ff69bc..fd0685f7667 100644 --- a/homeassistant/components/freebox/sensor.py +++ b/homeassistant/components/freebox/sensor.py @@ -3,11 +3,11 @@ from __future__ import annotations import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import DATA_RATE_KILOBYTES_PER_SECOND from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType import homeassistant.util.dt as dt_util @@ -74,7 +74,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class FreeboxSensor(Entity): +class FreeboxSensor(SensorEntity): """Representation of a Freebox sensor.""" def __init__( diff --git a/homeassistant/components/fritzbox/sensor.py b/homeassistant/components/fritzbox/sensor.py index 470d110429c..4d9c1693c1f 100644 --- a/homeassistant/components/fritzbox/sensor.py +++ b/homeassistant/components/fritzbox/sensor.py @@ -1,8 +1,8 @@ """Support for AVM Fritz!Box smarthome temperature sensor only devices.""" import requests +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_DEVICES, TEMP_CELSIUS -from homeassistant.helpers.entity import Entity from .const import ( ATTR_STATE_DEVICE_LOCKED, @@ -32,7 +32,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class FritzBoxTempSensor(Entity): +class FritzBoxTempSensor(SensorEntity): """The entity class for Fritzbox temperature sensors.""" def __init__(self, device, fritz): diff --git a/homeassistant/components/fritzbox_callmonitor/sensor.py b/homeassistant/components/fritzbox_callmonitor/sensor.py index 82ce075268b..a325c0ca71d 100644 --- a/homeassistant/components/fritzbox_callmonitor/sensor.py +++ b/homeassistant/components/fritzbox_callmonitor/sensor.py @@ -8,7 +8,7 @@ from time import sleep from fritzconnection.core.fritzmonitor import FritzMonitor import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( CONF_HOST, @@ -19,7 +19,6 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from .const import ( ATTR_PREFIXES, @@ -102,7 +101,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities([sensor]) -class FritzBoxCallSensor(Entity): +class FritzBoxCallSensor(SensorEntity): """Implementation of a Fritz!Box call monitor.""" def __init__(self, name, unique_id, fritzbox_phonebook, prefixes, host, port): diff --git a/homeassistant/components/fritzbox_netmonitor/sensor.py b/homeassistant/components/fritzbox_netmonitor/sensor.py index 320144ae163..3c37de7664c 100644 --- a/homeassistant/components/fritzbox_netmonitor/sensor.py +++ b/homeassistant/components/fritzbox_netmonitor/sensor.py @@ -7,10 +7,9 @@ from fritzconnection.lib.fritzstatus import FritzStatus from requests.exceptions import RequestException import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_HOST, CONF_NAME, STATE_UNAVAILABLE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -62,7 +61,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([FritzboxMonitorSensor(name, fstatus)], True) -class FritzboxMonitorSensor(Entity): +class FritzboxMonitorSensor(SensorEntity): """Implementation of a fritzbox monitor sensor.""" def __init__(self, name, fstatus): diff --git a/homeassistant/components/fronius/sensor.py b/homeassistant/components/fronius/sensor.py index 7e578701e2c..a908f2605f8 100644 --- a/homeassistant/components/fronius/sensor.py +++ b/homeassistant/components/fronius/sensor.py @@ -8,7 +8,7 @@ import logging from pyfronius import Fronius import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_DEVICE, CONF_MONITORED_CONDITIONS, @@ -18,7 +18,6 @@ from homeassistant.const import ( ) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval _LOGGER = logging.getLogger(__name__) @@ -252,7 +251,7 @@ class FroniusPowerFlow(FroniusAdapter): return await self.bridge.current_power_flow() -class FroniusTemplateSensor(Entity): +class FroniusTemplateSensor(SensorEntity): """Sensor for the single values (e.g. pv power, ac power).""" def __init__(self, parent: FroniusAdapter, name): diff --git a/homeassistant/components/garmin_connect/sensor.py b/homeassistant/components/garmin_connect/sensor.py index ba43a894cfe..46db6e615f1 100644 --- a/homeassistant/components/garmin_connect/sensor.py +++ b/homeassistant/components/garmin_connect/sensor.py @@ -10,9 +10,9 @@ from garminconnect import ( GarminConnectTooManyRequestsError, ) +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ATTRIBUTION, CONF_ID -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType from .alarm_util import calculate_next_active_alarms @@ -70,7 +70,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class GarminConnectSensor(Entity): +class GarminConnectSensor(SensorEntity): """Representation of a Garmin Connect Sensor.""" def __init__( diff --git a/homeassistant/components/gdacs/sensor.py b/homeassistant/components/gdacs/sensor.py index 7ab63fd6c6a..2e4759088fc 100644 --- a/homeassistant/components/gdacs/sensor.py +++ b/homeassistant/components/gdacs/sensor.py @@ -3,9 +3,9 @@ from __future__ import annotations import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from homeassistant.util import dt from .const import DEFAULT_ICON, DOMAIN, FEED @@ -34,7 +34,7 @@ async def async_setup_entry(hass, entry, async_add_entities): _LOGGER.debug("Sensor setup done") -class GdacsSensor(Entity): +class GdacsSensor(SensorEntity): """This is a status sensor for the GDACS integration.""" def __init__(self, config_entry_id, config_unique_id, config_title, manager): diff --git a/homeassistant/components/geizhals/sensor.py b/homeassistant/components/geizhals/sensor.py index 5f11ae7d0ca..04267e696ea 100644 --- a/homeassistant/components/geizhals/sensor.py +++ b/homeassistant/components/geizhals/sensor.py @@ -4,10 +4,9 @@ from datetime import timedelta from geizhals import Device, Geizhals import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle CONF_DESCRIPTION = "description" @@ -38,7 +37,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([Geizwatch(name, description, product_id, domain)], True) -class Geizwatch(Entity): +class Geizwatch(SensorEntity): """Implementation of Geizwatch.""" def __init__(self, name, description, product_id, domain): diff --git a/homeassistant/components/geniushub/sensor.py b/homeassistant/components/geniushub/sensor.py index 10c53994529..3234ccd577f 100644 --- a/homeassistant/components/geniushub/sensor.py +++ b/homeassistant/components/geniushub/sensor.py @@ -4,6 +4,7 @@ from __future__ import annotations from datetime import timedelta from typing import Any +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE from homeassistant.helpers.typing import ConfigType, HomeAssistantType import homeassistant.util.dt as dt_util @@ -38,7 +39,7 @@ async def async_setup_platform( async_add_entities(sensors + issues, update_before_add=True) -class GeniusBattery(GeniusDevice): +class GeniusBattery(GeniusDevice, SensorEntity): """Representation of a Genius Hub sensor.""" def __init__(self, broker, device, state_attr) -> None: @@ -88,7 +89,7 @@ class GeniusBattery(GeniusDevice): return level if level != 255 else 0 -class GeniusIssue(GeniusEntity): +class GeniusIssue(GeniusEntity, SensorEntity): """Representation of a Genius Hub sensor.""" def __init__(self, broker, level) -> None: diff --git a/homeassistant/components/geo_rss_events/sensor.py b/homeassistant/components/geo_rss_events/sensor.py index 00ff63982fd..df5f11850fd 100644 --- a/homeassistant/components/geo_rss_events/sensor.py +++ b/homeassistant/components/geo_rss_events/sensor.py @@ -12,7 +12,7 @@ from georss_client import UPDATE_OK, UPDATE_OK_NO_DATA from georss_generic_client import GenericFeed import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, @@ -23,7 +23,6 @@ from homeassistant.const import ( LENGTH_KILOMETERS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -96,7 +95,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class GeoRssServiceSensor(Entity): +class GeoRssServiceSensor(SensorEntity): """Representation of a Sensor.""" def __init__( diff --git a/homeassistant/components/geonetnz_quakes/sensor.py b/homeassistant/components/geonetnz_quakes/sensor.py index 565e022f036..94c7965663a 100644 --- a/homeassistant/components/geonetnz_quakes/sensor.py +++ b/homeassistant/components/geonetnz_quakes/sensor.py @@ -3,9 +3,9 @@ from __future__ import annotations import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from homeassistant.util import dt from .const import DOMAIN, FEED @@ -35,7 +35,7 @@ async def async_setup_entry(hass, entry, async_add_entities): _LOGGER.debug("Sensor setup done") -class GeonetnzQuakesSensor(Entity): +class GeonetnzQuakesSensor(SensorEntity): """This is a status sensor for the GeoNet NZ Quakes integration.""" def __init__(self, config_entry_id, config_unique_id, config_title, manager): diff --git a/homeassistant/components/geonetnz_volcano/sensor.py b/homeassistant/components/geonetnz_volcano/sensor.py index 2ec4940f88d..c0cc6801437 100644 --- a/homeassistant/components/geonetnz_volcano/sensor.py +++ b/homeassistant/components/geonetnz_volcano/sensor.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, @@ -12,7 +13,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from homeassistant.util import dt from homeassistant.util.unit_system import IMPERIAL_SYSTEM @@ -54,7 +54,7 @@ async def async_setup_entry(hass, entry, async_add_entities): _LOGGER.debug("Sensor setup done") -class GeonetnzVolcanoSensor(Entity): +class GeonetnzVolcanoSensor(SensorEntity): """This represents an external event with GeoNet NZ Volcano feed data.""" def __init__(self, config_entry_id, feed_manager, external_id, unit_system): diff --git a/homeassistant/components/github/sensor.py b/homeassistant/components/github/sensor.py index f5f40c270a8..c7812fa621d 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -5,7 +5,7 @@ import logging import github import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_NAME, CONF_ACCESS_TOKEN, @@ -14,7 +14,6 @@ from homeassistant.const import ( CONF_URL, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -72,7 +71,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class GitHubSensor(Entity): +class GitHubSensor(SensorEntity): """Representation of a GitHub sensor.""" def __init__(self, github_data): diff --git a/homeassistant/components/gitlab_ci/sensor.py b/homeassistant/components/gitlab_ci/sensor.py index 5eaa62e601d..0b619853348 100644 --- a/homeassistant/components/gitlab_ci/sensor.py +++ b/homeassistant/components/gitlab_ci/sensor.py @@ -5,7 +5,7 @@ import logging from gitlab import Gitlab, GitlabAuthenticationError, GitlabGetError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_NAME, @@ -14,7 +14,6 @@ from homeassistant.const import ( CONF_URL, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -66,7 +65,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([GitLabSensor(_gitlab_data, _name)], True) -class GitLabSensor(Entity): +class GitLabSensor(SensorEntity): """Representation of a GitLab sensor.""" def __init__(self, gitlab_data, name): diff --git a/homeassistant/components/gitter/sensor.py b/homeassistant/components/gitter/sensor.py index 3f7888d8593..20b68b2e5a9 100644 --- a/homeassistant/components/gitter/sensor.py +++ b/homeassistant/components/gitter/sensor.py @@ -5,10 +5,9 @@ from gitterpy.client import GitterClient from gitterpy.errors import GitterRoomError, GitterTokenError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_API_KEY, CONF_NAME, CONF_ROOM import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -47,7 +46,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([GitterSensor(gitter, room, name, username)], True) -class GitterSensor(Entity): +class GitterSensor(SensorEntity): """Representation of a Gitter sensor.""" def __init__(self, data, room, name, username): diff --git a/homeassistant/components/glances/sensor.py b/homeassistant/components/glances/sensor.py index 006333b321a..3e663621625 100644 --- a/homeassistant/components/glances/sensor.py +++ b/homeassistant/components/glances/sensor.py @@ -1,8 +1,8 @@ """Support gathering system information of hosts which are running glances.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME, STATE_UNAVAILABLE from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from .const import DATA_UPDATED, DOMAIN, SENSOR_TYPES @@ -60,7 +60,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(dev, True) -class GlancesSensor(Entity): +class GlancesSensor(SensorEntity): """Implementation of a Glances sensor.""" def __init__( diff --git a/homeassistant/components/gogogate2/sensor.py b/homeassistant/components/gogogate2/sensor.py index 2bdb1b3170c..9062bc0b352 100644 --- a/homeassistant/components/gogogate2/sensor.py +++ b/homeassistant/components/gogogate2/sensor.py @@ -6,6 +6,7 @@ from typing import Callable from gogogate2_api.common import AbstractDoor, get_configured_doors +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DEVICE_CLASS_BATTERY, @@ -48,7 +49,7 @@ async def async_setup_entry( async_add_entities(sensors) -class DoorSensorBattery(GoGoGate2Entity): +class DoorSensorBattery(GoGoGate2Entity, SensorEntity): """Battery sensor entity for gogogate2 door sensor.""" def __init__( @@ -86,7 +87,7 @@ class DoorSensorBattery(GoGoGate2Entity): return None -class DoorSensorTemperature(GoGoGate2Entity): +class DoorSensorTemperature(GoGoGate2Entity, SensorEntity): """Temperature sensor entity for gogogate2 door sensor.""" def __init__( diff --git a/homeassistant/components/google_travel_time/sensor.py b/homeassistant/components/google_travel_time/sensor.py index 109b65a4225..11bfb871a1b 100644 --- a/homeassistant/components/google_travel_time/sensor.py +++ b/homeassistant/components/google_travel_time/sensor.py @@ -5,7 +5,7 @@ import logging import googlemaps import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, @@ -18,7 +18,6 @@ from homeassistant.const import ( ) from homeassistant.helpers import location import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -180,7 +179,7 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None): hass.bus.listen_once(EVENT_HOMEASSISTANT_START, run_setup) -class GoogleTravelTimeSensor(Entity): +class GoogleTravelTimeSensor(SensorEntity): """Representation of a Google travel time sensor.""" def __init__(self, hass, name, api_key, origin, destination, options): diff --git a/homeassistant/components/google_wifi/sensor.py b/homeassistant/components/google_wifi/sensor.py index 9dfa26fab75..cf5a804e5a5 100644 --- a/homeassistant/components/google_wifi/sensor.py +++ b/homeassistant/components/google_wifi/sensor.py @@ -5,7 +5,7 @@ import logging import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_MONITORED_CONDITIONS, @@ -14,7 +14,6 @@ from homeassistant.const import ( TIME_DAYS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle, dt _LOGGER = logging.getLogger(__name__) @@ -71,7 +70,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class GoogleWifiSensor(Entity): +class GoogleWifiSensor(SensorEntity): """Representation of a Google Wifi sensor.""" def __init__(self, api, name, variable): diff --git a/homeassistant/components/gpsd/sensor.py b/homeassistant/components/gpsd/sensor.py index 978589e9a74..2f97f62337c 100644 --- a/homeassistant/components/gpsd/sensor.py +++ b/homeassistant/components/gpsd/sensor.py @@ -5,7 +5,7 @@ import socket from gps3.agps3threaded import AGPS3mechanism import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_LATITUDE, ATTR_LONGITUDE, @@ -15,7 +15,6 @@ from homeassistant.const import ( CONF_PORT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -65,7 +64,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([GpsdSensor(hass, name, host, port)]) -class GpsdSensor(Entity): +class GpsdSensor(SensorEntity): """Representation of a GPS receiver available via GPSD.""" def __init__(self, hass, name, host, port): diff --git a/homeassistant/components/greeneye_monitor/sensor.py b/homeassistant/components/greeneye_monitor/sensor.py index 84352328312..4e792bf56e4 100644 --- a/homeassistant/components/greeneye_monitor/sensor.py +++ b/homeassistant/components/greeneye_monitor/sensor.py @@ -1,4 +1,5 @@ """Support for the sensors in a GreenEye Monitor.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONF_NAME, CONF_SENSOR_TYPE, @@ -9,7 +10,6 @@ from homeassistant.const import ( TIME_SECONDS, VOLT, ) -from homeassistant.helpers.entity import Entity from . import ( CONF_COUNTED_QUANTITY, @@ -85,7 +85,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(entities) -class GEMSensor(Entity): +class GEMSensor(SensorEntity): """Base class for GreenEye Monitor sensors.""" def __init__(self, monitor_serial_number, name, sensor_type, number): diff --git a/homeassistant/components/growatt_server/sensor.py b/homeassistant/components/growatt_server/sensor.py index 50d269207de..86b88872a8a 100644 --- a/homeassistant/components/growatt_server/sensor.py +++ b/homeassistant/components/growatt_server/sensor.py @@ -7,7 +7,7 @@ import re import growattServer import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, @@ -28,7 +28,6 @@ from homeassistant.const import ( VOLT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -416,7 +415,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities, True) -class GrowattInverter(Entity): +class GrowattInverter(SensorEntity): """Representation of a Growatt Sensor.""" def __init__(self, probe, name, sensor, unique_id): diff --git a/homeassistant/components/gtfs/sensor.py b/homeassistant/components/gtfs/sensor.py index 737348548a8..46a31f464a1 100644 --- a/homeassistant/components/gtfs/sensor.py +++ b/homeassistant/components/gtfs/sensor.py @@ -11,7 +11,7 @@ import pygtfs from sqlalchemy.sql import text import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_NAME, @@ -20,7 +20,6 @@ from homeassistant.const import ( STATE_UNKNOWN, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ( ConfigType, DiscoveryInfoType, @@ -519,7 +518,7 @@ def setup_platform( ) -class GTFSDepartureSensor(Entity): +class GTFSDepartureSensor(SensorEntity): """Implementation of a GTFS departure sensor.""" def __init__( diff --git a/homeassistant/components/guardian/sensor.py b/homeassistant/components/guardian/sensor.py index 9b778128d19..48807c9cfeb 100644 --- a/homeassistant/components/guardian/sensor.py +++ b/homeassistant/components/guardian/sensor.py @@ -3,6 +3,7 @@ from __future__ import annotations from typing import Callable +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DEVICE_CLASS_BATTERY, @@ -110,7 +111,7 @@ async def async_setup_entry( async_add_entities(sensors) -class PairedSensorSensor(PairedSensorEntity): +class PairedSensorSensor(PairedSensorEntity, SensorEntity): """Define a binary sensor related to a Guardian valve controller.""" def __init__( @@ -153,7 +154,7 @@ class PairedSensorSensor(PairedSensorEntity): self._state = self.coordinator.data["temperature"] -class ValveControllerSensor(ValveControllerEntity): +class ValveControllerSensor(ValveControllerEntity, SensorEntity): """Define a generic Guardian sensor.""" def __init__( diff --git a/homeassistant/components/habitica/sensor.py b/homeassistant/components/habitica/sensor.py index dd17243cc9c..52748ddadad 100644 --- a/homeassistant/components/habitica/sensor.py +++ b/homeassistant/components/habitica/sensor.py @@ -5,8 +5,8 @@ import logging from aiohttp import ClientResponseError +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME, HTTP_TOO_MANY_REQUESTS -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from .const import DOMAIN @@ -125,7 +125,7 @@ class HabitipyData: ) -class HabitipySensor(Entity): +class HabitipySensor(SensorEntity): """A generic Habitica sensor.""" def __init__(self, name, sensor_name, updater): @@ -165,7 +165,7 @@ class HabitipySensor(Entity): return self._sensor_type.unit -class HabitipyTaskSensor(Entity): +class HabitipyTaskSensor(SensorEntity): """A Habitica task sensor.""" def __init__(self, name, task_name, updater): diff --git a/homeassistant/components/hassio/sensor.py b/homeassistant/components/hassio/sensor.py index ae2bd20d6ed..c41c0dc5090 100644 --- a/homeassistant/components/hassio/sensor.py +++ b/homeassistant/components/hassio/sensor.py @@ -3,6 +3,7 @@ from __future__ import annotations from typing import Callable +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity @@ -36,7 +37,7 @@ async def async_setup_entry( async_add_entities(entities) -class HassioAddonSensor(HassioAddonEntity): +class HassioAddonSensor(HassioAddonEntity, SensorEntity): """Sensor to track a Hass.io add-on attribute.""" @property @@ -45,7 +46,7 @@ class HassioAddonSensor(HassioAddonEntity): return self.addon_info[self.attribute_name] -class HassioOSSensor(HassioOSEntity): +class HassioOSSensor(HassioOSEntity, SensorEntity): """Sensor to track a Hass.io add-on attribute.""" @property diff --git a/homeassistant/components/haveibeenpwned/sensor.py b/homeassistant/components/haveibeenpwned/sensor.py index 5aa336905e9..55b369c2fde 100644 --- a/homeassistant/components/haveibeenpwned/sensor.py +++ b/homeassistant/components/haveibeenpwned/sensor.py @@ -6,7 +6,7 @@ from aiohttp.hdrs import USER_AGENT import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -15,7 +15,6 @@ from homeassistant.const import ( HTTP_OK, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import track_point_in_time from homeassistant.util import Throttle import homeassistant.util.dt as dt_util @@ -54,7 +53,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class HaveIBeenPwnedSensor(Entity): +class HaveIBeenPwnedSensor(SensorEntity): """Implementation of a HaveIBeenPwned sensor.""" def __init__(self, data, email): diff --git a/homeassistant/components/hddtemp/sensor.py b/homeassistant/components/hddtemp/sensor.py index e5f6621defe..4376c7f1289 100644 --- a/homeassistant/components/hddtemp/sensor.py +++ b/homeassistant/components/hddtemp/sensor.py @@ -6,7 +6,7 @@ from telnetlib import Telnet import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_DISKS, CONF_HOST, @@ -16,7 +16,6 @@ from homeassistant.const import ( TEMP_FAHRENHEIT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -60,7 +59,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class HddTempSensor(Entity): +class HddTempSensor(SensorEntity): """Representation of a HDDTemp sensor.""" def __init__(self, name, disk, hddtemp): diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index 976c591f810..e4fbc0b2a89 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -8,7 +8,7 @@ from typing import Callable import herepy import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, @@ -26,7 +26,6 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant, State, callback from homeassistant.helpers import location import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import DiscoveryInfoType import homeassistant.util.dt as dt @@ -215,7 +214,7 @@ def _are_valid_client_credentials(here_client: herepy.RoutingApi) -> bool: return True -class HERETravelTimeSensor(Entity): +class HERETravelTimeSensor(SensorEntity): """Representation of a HERE travel time sensor.""" def __init__( diff --git a/homeassistant/components/history_stats/sensor.py b/homeassistant/components/history_stats/sensor.py index 7658146d102..b8d3dc39187 100644 --- a/homeassistant/components/history_stats/sensor.py +++ b/homeassistant/components/history_stats/sensor.py @@ -6,7 +6,7 @@ import math import voluptuous as vol from homeassistant.components import history -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_ENTITY_ID, CONF_NAME, @@ -19,7 +19,6 @@ from homeassistant.const import ( from homeassistant.core import CoreState, callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.reload import setup_reload_service import homeassistant.util.dt as dt_util @@ -102,7 +101,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class HistoryStatsSensor(Entity): +class HistoryStatsSensor(SensorEntity): """Representation of a HistoryStats sensor.""" def __init__( diff --git a/homeassistant/components/hive/sensor.py b/homeassistant/components/hive/sensor.py index 53cc643250c..518f3286231 100644 --- a/homeassistant/components/hive/sensor.py +++ b/homeassistant/components/hive/sensor.py @@ -2,8 +2,7 @@ from datetime import timedelta -from homeassistant.components.sensor import DEVICE_CLASS_BATTERY -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import DEVICE_CLASS_BATTERY, SensorEntity from . import HiveEntity from .const import DOMAIN @@ -27,7 +26,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities, True) -class HiveSensorEntity(HiveEntity, Entity): +class HiveSensorEntity(HiveEntity, SensorEntity): """Hive Sensor Entity.""" @property diff --git a/homeassistant/components/home_connect/sensor.py b/homeassistant/components/home_connect/sensor.py index 064ae033fb0..463de6cda51 100644 --- a/homeassistant/components/home_connect/sensor.py +++ b/homeassistant/components/home_connect/sensor.py @@ -3,6 +3,7 @@ from datetime import timedelta import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_ENTITIES, DEVICE_CLASS_TIMESTAMP import homeassistant.util.dt as dt_util @@ -27,7 +28,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(await hass.async_add_executor_job(get_entities), True) -class HomeConnectSensor(HomeConnectEntity): +class HomeConnectSensor(HomeConnectEntity, SensorEntity): """Sensor class for Home Connect.""" def __init__(self, device, desc, key, unit, icon, device_class, sign=1): diff --git a/homeassistant/components/homekit_controller/sensor.py b/homeassistant/components/homekit_controller/sensor.py index 094d0a500d1..2ae264fabb9 100644 --- a/homeassistant/components/homekit_controller/sensor.py +++ b/homeassistant/components/homekit_controller/sensor.py @@ -2,6 +2,7 @@ from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, DEVICE_CLASS_BATTERY, @@ -39,7 +40,7 @@ SIMPLE_SENSOR = { } -class HomeKitHumiditySensor(HomeKitEntity): +class HomeKitHumiditySensor(HomeKitEntity, SensorEntity): """Representation of a Homekit humidity sensor.""" def get_characteristic_types(self): @@ -72,7 +73,7 @@ class HomeKitHumiditySensor(HomeKitEntity): return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT) -class HomeKitTemperatureSensor(HomeKitEntity): +class HomeKitTemperatureSensor(HomeKitEntity, SensorEntity): """Representation of a Homekit temperature sensor.""" def get_characteristic_types(self): @@ -105,7 +106,7 @@ class HomeKitTemperatureSensor(HomeKitEntity): return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT) -class HomeKitLightSensor(HomeKitEntity): +class HomeKitLightSensor(HomeKitEntity, SensorEntity): """Representation of a Homekit light level sensor.""" def get_characteristic_types(self): @@ -138,7 +139,7 @@ class HomeKitLightSensor(HomeKitEntity): return self.service.value(CharacteristicsTypes.LIGHT_LEVEL_CURRENT) -class HomeKitCarbonDioxideSensor(HomeKitEntity): +class HomeKitCarbonDioxideSensor(HomeKitEntity, SensorEntity): """Representation of a Homekit Carbon Dioxide sensor.""" def get_characteristic_types(self): @@ -166,7 +167,7 @@ class HomeKitCarbonDioxideSensor(HomeKitEntity): return self.service.value(CharacteristicsTypes.CARBON_DIOXIDE_LEVEL) -class HomeKitBatterySensor(HomeKitEntity): +class HomeKitBatterySensor(HomeKitEntity, SensorEntity): """Representation of a Homekit battery sensor.""" def get_characteristic_types(self): @@ -233,7 +234,7 @@ class HomeKitBatterySensor(HomeKitEntity): return self.service.value(CharacteristicsTypes.BATTERY_LEVEL) -class SimpleSensor(CharacteristicEntity): +class SimpleSensor(CharacteristicEntity, SensorEntity): """ A simple sensor for a single characteristic. diff --git a/homeassistant/components/homematic/sensor.py b/homeassistant/components/homematic/sensor.py index e6439c451c1..4525d5a48fc 100644 --- a/homeassistant/components/homematic/sensor.py +++ b/homeassistant/components/homematic/sensor.py @@ -1,6 +1,7 @@ """Support for HomeMatic sensors.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEGREE, DEVICE_CLASS_HUMIDITY, @@ -97,7 +98,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class HMSensor(HMDevice): +class HMSensor(HMDevice, SensorEntity): """Representation of a HomeMatic sensor.""" @property diff --git a/homeassistant/components/homematicip_cloud/sensor.py b/homeassistant/components/homematicip_cloud/sensor.py index 6f4acc6137a..9e6e96232b4 100644 --- a/homeassistant/components/homematicip_cloud/sensor.py +++ b/homeassistant/components/homematicip_cloud/sensor.py @@ -26,6 +26,7 @@ from homematicip.aio.device import ( ) from homematicip.base.enums import ValveState +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, @@ -123,7 +124,7 @@ async def async_setup_entry( async_add_entities(entities) -class HomematicipAccesspointDutyCycle(HomematicipGenericEntity): +class HomematicipAccesspointDutyCycle(HomematicipGenericEntity, SensorEntity): """Representation of then HomeMaticIP access point.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -146,7 +147,7 @@ class HomematicipAccesspointDutyCycle(HomematicipGenericEntity): return PERCENTAGE -class HomematicipHeatingThermostat(HomematicipGenericEntity): +class HomematicipHeatingThermostat(HomematicipGenericEntity, SensorEntity): """Representation of the HomematicIP heating thermostat.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -175,7 +176,7 @@ class HomematicipHeatingThermostat(HomematicipGenericEntity): return PERCENTAGE -class HomematicipHumiditySensor(HomematicipGenericEntity): +class HomematicipHumiditySensor(HomematicipGenericEntity, SensorEntity): """Representation of the HomematicIP humidity sensor.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -198,7 +199,7 @@ class HomematicipHumiditySensor(HomematicipGenericEntity): return PERCENTAGE -class HomematicipTemperatureSensor(HomematicipGenericEntity): +class HomematicipTemperatureSensor(HomematicipGenericEntity, SensorEntity): """Representation of the HomematicIP thermometer.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -235,7 +236,7 @@ class HomematicipTemperatureSensor(HomematicipGenericEntity): return state_attr -class HomematicipIlluminanceSensor(HomematicipGenericEntity): +class HomematicipIlluminanceSensor(HomematicipGenericEntity, SensorEntity): """Representation of the HomematicIP Illuminance sensor.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -273,7 +274,7 @@ class HomematicipIlluminanceSensor(HomematicipGenericEntity): return state_attr -class HomematicipPowerSensor(HomematicipGenericEntity): +class HomematicipPowerSensor(HomematicipGenericEntity, SensorEntity): """Representation of the HomematicIP power measuring sensor.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -296,7 +297,7 @@ class HomematicipPowerSensor(HomematicipGenericEntity): return POWER_WATT -class HomematicipWindspeedSensor(HomematicipGenericEntity): +class HomematicipWindspeedSensor(HomematicipGenericEntity, SensorEntity): """Representation of the HomematicIP wind speed sensor.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -329,7 +330,7 @@ class HomematicipWindspeedSensor(HomematicipGenericEntity): return state_attr -class HomematicipTodayRainSensor(HomematicipGenericEntity): +class HomematicipTodayRainSensor(HomematicipGenericEntity, SensorEntity): """Representation of the HomematicIP rain counter of a day sensor.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -347,7 +348,7 @@ class HomematicipTodayRainSensor(HomematicipGenericEntity): return LENGTH_MILLIMETERS -class HomematicipPassageDetectorDeltaCounter(HomematicipGenericEntity): +class HomematicipPassageDetectorDeltaCounter(HomematicipGenericEntity, SensorEntity): """Representation of the HomematicIP passage detector delta counter.""" @property diff --git a/homeassistant/components/hp_ilo/sensor.py b/homeassistant/components/hp_ilo/sensor.py index 0126ab780ce..297bfa5264f 100644 --- a/homeassistant/components/hp_ilo/sensor.py +++ b/homeassistant/components/hp_ilo/sensor.py @@ -5,7 +5,7 @@ import logging import hpilo import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_MONITORED_VARIABLES, @@ -18,7 +18,6 @@ from homeassistant.const import ( CONF_VALUE_TEMPLATE, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -100,7 +99,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class HpIloSensor(Entity): +class HpIloSensor(SensorEntity): """Representation of a HP iLO sensor.""" def __init__( diff --git a/homeassistant/components/htu21d/sensor.py b/homeassistant/components/htu21d/sensor.py index 615177ed6af..d32eebf6d5f 100644 --- a/homeassistant/components/htu21d/sensor.py +++ b/homeassistant/components/htu21d/sensor.py @@ -7,10 +7,9 @@ from i2csense.htu21d import HTU21D # pylint: disable=import-error import smbus import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, PERCENTAGE, TEMP_FAHRENHEIT import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.temperature import celsius_to_fahrenheit @@ -70,7 +69,7 @@ class HTU21DHandler: self.sensor.update() -class HTU21DSensor(Entity): +class HTU21DSensor(SensorEntity): """Implementation of the HTU21D sensor.""" def __init__(self, htu21d_client, name, variable, unit): diff --git a/homeassistant/components/huawei_lte/sensor.py b/homeassistant/components/huawei_lte/sensor.py index c0773fdf808..c6cb93f0e67 100644 --- a/homeassistant/components/huawei_lte/sensor.py +++ b/homeassistant/components/huawei_lte/sensor.py @@ -12,6 +12,7 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_SIGNAL_STRENGTH, DOMAIN as SENSOR_DOMAIN, + SensorEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -373,7 +374,7 @@ def format_default(value: StateType) -> tuple[StateType, str | None]: @attr.s -class HuaweiLteSensor(HuaweiLteBaseEntity): +class HuaweiLteSensor(HuaweiLteBaseEntity, SensorEntity): """Huawei LTE sensor entity.""" key: str = attr.ib() diff --git a/homeassistant/components/hue/sensor.py b/homeassistant/components/hue/sensor.py index a3c5fcc3fa7..6ac8d134327 100644 --- a/homeassistant/components/hue/sensor.py +++ b/homeassistant/components/hue/sensor.py @@ -6,6 +6,7 @@ from aiohue.sensors import ( TYPE_ZLL_TEMPERATURE, ) +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, @@ -14,7 +15,6 @@ from homeassistant.const import ( PERCENTAGE, TEMP_CELSIUS, ) -from homeassistant.helpers.entity import Entity from .const import DOMAIN as HUE_DOMAIN from .sensor_base import SENSOR_CONFIG_MAP, GenericHueSensor, GenericZLLSensor @@ -31,7 +31,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ].sensor_manager.async_register_component("sensor", async_add_entities) -class GenericHueGaugeSensorEntity(GenericZLLSensor, Entity): +class GenericHueGaugeSensorEntity(GenericZLLSensor, SensorEntity): """Parent class for all 'gauge' Hue device sensors.""" async def _async_update_ha_state(self, *args, **kwargs): @@ -88,7 +88,7 @@ class HueTemperature(GenericHueGaugeSensorEntity): return self.sensor.temperature / 100 -class HueBattery(GenericHueSensor): +class HueBattery(GenericHueSensor, SensorEntity): """Battery class for when a batt-powered device is only represented as an event.""" @property diff --git a/homeassistant/components/huisbaasje/sensor.py b/homeassistant/components/huisbaasje/sensor.py index e84052fe029..3acf39a140d 100644 --- a/homeassistant/components/huisbaasje/sensor.py +++ b/homeassistant/components/huisbaasje/sensor.py @@ -1,4 +1,5 @@ """Platform for sensor integration.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ID, POWER_WATT from homeassistant.core import HomeAssistant @@ -23,7 +24,7 @@ async def async_setup_entry( ) -class HuisbaasjeSensor(CoordinatorEntity): +class HuisbaasjeSensor(CoordinatorEntity, SensorEntity): """Defines a Huisbaasje sensor.""" def __init__( diff --git a/homeassistant/components/hunterdouglas_powerview/sensor.py b/homeassistant/components/hunterdouglas_powerview/sensor.py index 130e8dd507a..d66671fe1ea 100644 --- a/homeassistant/components/hunterdouglas_powerview/sensor.py +++ b/homeassistant/components/hunterdouglas_powerview/sensor.py @@ -1,6 +1,7 @@ """Support for hunterdouglass_powerview sensors.""" from aiopvapi.resources.shade import factory as PvShade +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE from homeassistant.core import callback @@ -45,7 +46,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class PowerViewShadeBatterySensor(ShadeEntity): +class PowerViewShadeBatterySensor(ShadeEntity, SensorEntity): """Representation of an shade battery charge sensor.""" @property diff --git a/homeassistant/components/hvv_departures/sensor.py b/homeassistant/components/hvv_departures/sensor.py index bbabb1b8caa..35fc137a0f0 100644 --- a/homeassistant/components/hvv_departures/sensor.py +++ b/homeassistant/components/hvv_departures/sensor.py @@ -6,9 +6,9 @@ from aiohttp import ClientConnectorError from pygti.exceptions import InvalidAuth from pytz import timezone +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ID, DEVICE_CLASS_TIMESTAMP from homeassistant.helpers import aiohttp_client -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.dt import utcnow @@ -43,7 +43,7 @@ async def async_setup_entry(hass, config_entry, async_add_devices): async_add_devices([sensor], True) -class HVVDepartureSensor(Entity): +class HVVDepartureSensor(SensorEntity): """HVVDepartureSensor class.""" def __init__(self, hass, config_entry, session, hub): diff --git a/homeassistant/components/hydrawise/sensor.py b/homeassistant/components/hydrawise/sensor.py index 6a0c6ab0d80..7cd521296f6 100644 --- a/homeassistant/components/hydrawise/sensor.py +++ b/homeassistant/components/hydrawise/sensor.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv from homeassistant.util import dt @@ -36,7 +36,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class HydrawiseSensor(HydrawiseEntity): +class HydrawiseSensor(HydrawiseEntity, SensorEntity): """A sensor implementation for Hydrawise device.""" @property From c900e3030b81ed25859911c3c3100713b24cf52b Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 19:46:46 +0100 Subject: [PATCH 550/831] Migrate integrations n-q to extend SensorEntity (#48214) --- homeassistant/components/n26/sensor.py | 8 ++++---- homeassistant/components/neato/sensor.py | 5 ++--- .../components/nederlandse_spoorwegen/sensor.py | 5 ++--- homeassistant/components/nest/legacy/sensor.py | 5 +++-- homeassistant/components/netatmo/sensor.py | 5 +++-- homeassistant/components/netdata/sensor.py | 7 +++---- homeassistant/components/netgear_lte/sensor.py | 4 ++-- homeassistant/components/neurio_energy/sensor.py | 5 ++--- homeassistant/components/nexia/sensor.py | 5 +++-- homeassistant/components/nextbus/sensor.py | 5 ++--- homeassistant/components/nextcloud/sensor.py | 4 ++-- homeassistant/components/nightscout/sensor.py | 3 ++- homeassistant/components/nissan_leaf/sensor.py | 5 +++-- homeassistant/components/nmbs/sensor.py | 7 +++---- homeassistant/components/noaa_tides/sensor.py | 5 ++--- homeassistant/components/notion/sensor.py | 3 ++- homeassistant/components/nsw_fuel_station/sensor.py | 5 ++--- homeassistant/components/numato/sensor.py | 4 ++-- homeassistant/components/nut/sensor.py | 3 ++- homeassistant/components/nzbget/sensor.py | 3 ++- homeassistant/components/oasa_telematics/sensor.py | 5 ++--- homeassistant/components/obihai/sensor.py | 5 ++--- homeassistant/components/octoprint/sensor.py | 4 ++-- homeassistant/components/ohmconnect/sensor.py | 5 ++--- homeassistant/components/ombi/sensor.py | 4 ++-- homeassistant/components/omnilogic/sensor.py | 4 ++-- homeassistant/components/ondilo_ico/sensor.py | 3 ++- homeassistant/components/onewire/sensor.py | 8 ++++---- homeassistant/components/onvif/sensor.py | 3 ++- homeassistant/components/openerz/sensor.py | 4 ++-- homeassistant/components/openevse/sensor.py | 5 ++--- homeassistant/components/openexchangerates/sensor.py | 5 ++--- homeassistant/components/openhardwaremonitor/sensor.py | 5 ++--- homeassistant/components/opensky/sensor.py | 5 ++--- homeassistant/components/opentherm_gw/sensor.py | 6 +++--- homeassistant/components/openuv/sensor.py | 3 ++- .../components/openweathermap/abstract_owm_sensor.py | 4 ++-- homeassistant/components/oru/sensor.py | 5 ++--- homeassistant/components/otp/sensor.py | 5 ++--- homeassistant/components/ovo_energy/sensor.py | 3 ++- homeassistant/components/ozw/sensor.py | 3 ++- homeassistant/components/pi_hole/sensor.py | 3 ++- homeassistant/components/pilight/sensor.py | 5 ++--- homeassistant/components/plaato/sensor.py | 4 ++-- homeassistant/components/plex/sensor.py | 4 ++-- homeassistant/components/plugwise/sensor.py | 10 +++++----- homeassistant/components/pocketcasts/sensor.py | 5 ++--- homeassistant/components/point/sensor.py | 4 ++-- homeassistant/components/poolsense/sensor.py | 4 ++-- homeassistant/components/powerwall/sensor.py | 5 +++-- homeassistant/components/pushbullet/sensor.py | 5 ++--- homeassistant/components/pvoutput/sensor.py | 5 ++--- homeassistant/components/pvpc_hourly_pricing/sensor.py | 3 ++- homeassistant/components/pyload/sensor.py | 5 ++--- homeassistant/components/qbittorrent/sensor.py | 5 ++--- homeassistant/components/qnap/sensor.py | 5 ++--- homeassistant/components/qwikswitch/sensor.py | 3 ++- 57 files changed, 129 insertions(+), 136 deletions(-) diff --git a/homeassistant/components/n26/sensor.py b/homeassistant/components/n26/sensor.py index df687d9689a..98d86194b86 100644 --- a/homeassistant/components/n26/sensor.py +++ b/homeassistant/components/n26/sensor.py @@ -1,5 +1,5 @@ """Support for N26 bank account sensors.""" -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from . import DEFAULT_SCAN_INTERVAL, DOMAIN, timestamp_ms_to_date from .const import DATA @@ -43,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensor_entities) -class N26Account(Entity): +class N26Account(SensorEntity): """Sensor for a N26 balance account. A balance account contains an amount of money (=balance). The amount may @@ -117,7 +117,7 @@ class N26Account(Entity): return ICON_ACCOUNT -class N26Card(Entity): +class N26Card(SensorEntity): """Sensor for a N26 card.""" def __init__(self, api_data, card) -> None: @@ -186,7 +186,7 @@ class N26Card(Entity): return ICON_CARD -class N26Space(Entity): +class N26Space(SensorEntity): """Sensor for a N26 space.""" def __init__(self, api_data, space) -> None: diff --git a/homeassistant/components/neato/sensor.py b/homeassistant/components/neato/sensor.py index 50af42e7007..83add4ff3f7 100644 --- a/homeassistant/components/neato/sensor.py +++ b/homeassistant/components/neato/sensor.py @@ -4,9 +4,8 @@ import logging from pybotvac.exceptions import NeatoRobotException -from homeassistant.components.sensor import DEVICE_CLASS_BATTERY +from homeassistant.components.sensor import DEVICE_CLASS_BATTERY, SensorEntity from homeassistant.const import PERCENTAGE -from homeassistant.helpers.entity import Entity from .const import NEATO_DOMAIN, NEATO_LOGIN, NEATO_ROBOTS, SCAN_INTERVAL_MINUTES @@ -31,7 +30,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(dev, True) -class NeatoSensor(Entity): +class NeatoSensor(SensorEntity): """Neato sensor.""" def __init__(self, neato, robot): diff --git a/homeassistant/components/nederlandse_spoorwegen/sensor.py b/homeassistant/components/nederlandse_spoorwegen/sensor.py index 7d90c2c3c0d..de8a85f44fd 100644 --- a/homeassistant/components/nederlandse_spoorwegen/sensor.py +++ b/homeassistant/components/nederlandse_spoorwegen/sensor.py @@ -7,11 +7,10 @@ from ns_api import RequestParametersError import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -94,7 +93,7 @@ def valid_stations(stations, given_stations): return True -class NSDepartureSensor(Entity): +class NSDepartureSensor(SensorEntity): """Implementation of a NS Departure Sensor.""" def __init__(self, nsapi, name, departure, heading, via, time): diff --git a/homeassistant/components/nest/legacy/sensor.py b/homeassistant/components/nest/legacy/sensor.py index 34f525ca7a6..9bea3b34ad4 100644 --- a/homeassistant/components/nest/legacy/sensor.py +++ b/homeassistant/components/nest/legacy/sensor.py @@ -1,6 +1,7 @@ """Support for Nest Thermostat sensors for the legacy API.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONF_MONITORED_CONDITIONS, CONF_SENSORS, @@ -149,7 +150,7 @@ async def async_setup_legacy_entry(hass, entry, async_add_entities): async_add_entities(await hass.async_add_executor_job(get_sensors), True) -class NestBasicSensor(NestSensorDevice): +class NestBasicSensor(NestSensorDevice, SensorEntity): """Representation a basic Nest sensor.""" @property @@ -179,7 +180,7 @@ class NestBasicSensor(NestSensorDevice): self._state = getattr(self.device, self.variable) -class NestTempSensor(NestSensorDevice): +class NestTempSensor(NestSensorDevice, SensorEntity): """Representation of a Nest Temperature sensor.""" @property diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index ac86e86b960..cccd3865e54 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -1,6 +1,7 @@ """Support for the Netatmo Weather Service.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_LATITUDE, @@ -260,7 +261,7 @@ async def async_config_entry_updated(hass: HomeAssistant, entry: ConfigEntry) -> async_dispatcher_send(hass, f"signal-{DOMAIN}-public-update-{entry.entry_id}") -class NetatmoSensor(NetatmoBase): +class NetatmoSensor(NetatmoBase, SensorEntity): """Implementation of a Netatmo sensor.""" def __init__(self, data_handler, data_class_name, module_info, sensor_type): @@ -489,7 +490,7 @@ def process_wifi(strength): return "Full" -class NetatmoPublicSensor(NetatmoBase): +class NetatmoPublicSensor(NetatmoBase, SensorEntity): """Represent a single sensor in a Netatmo.""" def __init__(self, data_handler, area, sensor_type): diff --git a/homeassistant/components/netdata/sensor.py b/homeassistant/components/netdata/sensor.py index 64c8d789fc7..21e4cd1b005 100644 --- a/homeassistant/components/netdata/sensor.py +++ b/homeassistant/components/netdata/sensor.py @@ -6,7 +6,7 @@ from netdata import Netdata from netdata.exceptions import NetdataError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_ICON, @@ -18,7 +18,6 @@ from homeassistant.const import ( from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -97,7 +96,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(dev, True) -class NetdataSensor(Entity): +class NetdataSensor(SensorEntity): """Implementation of a Netdata sensor.""" def __init__(self, netdata, name, sensor, sensor_name, element, icon, unit, invert): @@ -146,7 +145,7 @@ class NetdataSensor(Entity): ) -class NetdataAlarms(Entity): +class NetdataAlarms(SensorEntity): """Implementation of a Netdata alarm sensor.""" def __init__(self, netdata, name, host, port): diff --git a/homeassistant/components/netgear_lte/sensor.py b/homeassistant/components/netgear_lte/sensor.py index abbedf79d64..c8f07301e98 100644 --- a/homeassistant/components/netgear_lte/sensor.py +++ b/homeassistant/components/netgear_lte/sensor.py @@ -1,5 +1,5 @@ """Support for Netgear LTE sensors.""" -from homeassistant.components.sensor import DOMAIN +from homeassistant.components.sensor import DOMAIN, SensorEntity from homeassistant.exceptions import PlatformNotReady from . import CONF_MONITORED_CONDITIONS, DATA_KEY, LTEEntity @@ -33,7 +33,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info) async_add_entities(sensors) -class LTESensor(LTEEntity): +class LTESensor(LTEEntity, SensorEntity): """Base LTE sensor entity.""" @property diff --git a/homeassistant/components/neurio_energy/sensor.py b/homeassistant/components/neurio_energy/sensor.py index 264d7508347..2bc17fbecb2 100644 --- a/homeassistant/components/neurio_energy/sensor.py +++ b/homeassistant/components/neurio_energy/sensor.py @@ -6,10 +6,9 @@ import neurio import requests.exceptions import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_API_KEY, ENERGY_KILO_WATT_HOUR, POWER_WATT import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.util.dt as dt_util @@ -123,7 +122,7 @@ class NeurioData: self._daily_usage = round(kwh, 2) -class NeurioEnergy(Entity): +class NeurioEnergy(SensorEntity): """Implementation of a Neurio energy sensor.""" def __init__(self, data, name, sensor_type, update_call): diff --git a/homeassistant/components/nexia/sensor.py b/homeassistant/components/nexia/sensor.py index eff15d443bc..a14931e41ee 100644 --- a/homeassistant/components/nexia/sensor.py +++ b/homeassistant/components/nexia/sensor.py @@ -2,6 +2,7 @@ from nexia.const import UNIT_CELSIUS +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, @@ -149,7 +150,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class NexiaThermostatSensor(NexiaThermostatEntity): +class NexiaThermostatSensor(NexiaThermostatEntity, SensorEntity): """Provides Nexia thermostat sensor support.""" def __init__( @@ -196,7 +197,7 @@ class NexiaThermostatSensor(NexiaThermostatEntity): return self._unit_of_measurement -class NexiaThermostatZoneSensor(NexiaThermostatZoneEntity): +class NexiaThermostatZoneSensor(NexiaThermostatZoneEntity, SensorEntity): """Nexia Zone Sensor Support.""" def __init__( diff --git a/homeassistant/components/nextbus/sensor.py b/homeassistant/components/nextbus/sensor.py index 3357d84fd69..67d0a4a81d7 100644 --- a/homeassistant/components/nextbus/sensor.py +++ b/homeassistant/components/nextbus/sensor.py @@ -5,10 +5,9 @@ import logging from py_nextbus import NextBusClient import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, DEVICE_CLASS_TIMESTAMP import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util.dt import utc_from_timestamp _LOGGER = logging.getLogger(__name__) @@ -104,7 +103,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([NextBusDepartureSensor(client, agency, route, stop, name)], True) -class NextBusDepartureSensor(Entity): +class NextBusDepartureSensor(SensorEntity): """Sensor class that displays upcoming NextBus times. To function, this requires knowing the agency tag as well as the tags for diff --git a/homeassistant/components/nextcloud/sensor.py b/homeassistant/components/nextcloud/sensor.py index e0be9dde69e..5cd02f124e9 100644 --- a/homeassistant/components/nextcloud/sensor.py +++ b/homeassistant/components/nextcloud/sensor.py @@ -1,5 +1,5 @@ """Summary data from Nextcoud.""" -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from . import DOMAIN, SENSORS @@ -15,7 +15,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class NextcloudSensor(Entity): +class NextcloudSensor(SensorEntity): """Represents a Nextcloud sensor.""" def __init__(self, item): diff --git a/homeassistant/components/nightscout/sensor.py b/homeassistant/components/nightscout/sensor.py index d190da48a16..ea2ea549cec 100644 --- a/homeassistant/components/nightscout/sensor.py +++ b/homeassistant/components/nightscout/sensor.py @@ -9,6 +9,7 @@ from typing import Callable from aiohttp import ClientError from py_nightscout import Api as NightscoutAPI +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_DATE from homeassistant.core import HomeAssistant @@ -33,7 +34,7 @@ async def async_setup_entry( async_add_entities([NightscoutSensor(api, "Blood Sugar", entry.unique_id)], True) -class NightscoutSensor(Entity): +class NightscoutSensor(SensorEntity): """Implementation of a Nightscout sensor.""" def __init__(self, api: NightscoutAPI, name, unique_id): diff --git a/homeassistant/components/nissan_leaf/sensor.py b/homeassistant/components/nissan_leaf/sensor.py index 368db17ab4b..936d607a84e 100644 --- a/homeassistant/components/nissan_leaf/sensor.py +++ b/homeassistant/components/nissan_leaf/sensor.py @@ -1,6 +1,7 @@ """Battery Charge and Range Support for the Nissan Leaf.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.util.distance import LENGTH_KILOMETERS, LENGTH_MILES @@ -35,7 +36,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(devices, True) -class LeafBatterySensor(LeafEntity): +class LeafBatterySensor(LeafEntity, SensorEntity): """Nissan Leaf Battery Sensor.""" @property @@ -65,7 +66,7 @@ class LeafBatterySensor(LeafEntity): return icon_for_battery_level(battery_level=self.state, charging=chargestate) -class LeafRangeSensor(LeafEntity): +class LeafRangeSensor(LeafEntity, SensorEntity): """Nissan Leaf Range Sensor.""" def __init__(self, car, ac_on): diff --git a/homeassistant/components/nmbs/sensor.py b/homeassistant/components/nmbs/sensor.py index ac6753ce0d6..32e4fd87e29 100644 --- a/homeassistant/components/nmbs/sensor.py +++ b/homeassistant/components/nmbs/sensor.py @@ -4,7 +4,7 @@ import logging from pyrail import iRail import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, @@ -14,7 +14,6 @@ from homeassistant.const import ( TIME_MINUTES, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -88,7 +87,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class NMBSLiveBoard(Entity): +class NMBSLiveBoard(SensorEntity): """Get the next train from a station's liveboard.""" def __init__(self, api_client, live_station, station_from, station_to): @@ -164,7 +163,7 @@ class NMBSLiveBoard(Entity): ) -class NMBSSensor(Entity): +class NMBSSensor(SensorEntity): """Get the the total travel time for a given connection.""" def __init__( diff --git a/homeassistant/components/noaa_tides/sensor.py b/homeassistant/components/noaa_tides/sensor.py index b6771d9293a..e637e953173 100644 --- a/homeassistant/components/noaa_tides/sensor.py +++ b/homeassistant/components/noaa_tides/sensor.py @@ -6,7 +6,7 @@ import noaa_coops as coops import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_NAME, @@ -15,7 +15,6 @@ from homeassistant.const import ( ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -72,7 +71,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([noaa_sensor], True) -class NOAATidesAndCurrentsSensor(Entity): +class NOAATidesAndCurrentsSensor(SensorEntity): """Representation of a NOAA Tides and Currents sensor.""" def __init__(self, name, station_id, timezone, unit_system, station): diff --git a/homeassistant/components/notion/sensor.py b/homeassistant/components/notion/sensor.py index 978e0aac46a..4f034408fe2 100644 --- a/homeassistant/components/notion/sensor.py +++ b/homeassistant/components/notion/sensor.py @@ -1,6 +1,7 @@ """Support for Notion sensors.""" from typing import Callable +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import TEMP_CELSIUS from homeassistant.core import HomeAssistant, callback @@ -42,7 +43,7 @@ async def async_setup_entry( async_add_entities(sensor_list) -class NotionSensor(NotionEntity): +class NotionSensor(NotionEntity, SensorEntity): """Define a Notion sensor.""" def __init__( diff --git a/homeassistant/components/nsw_fuel_station/sensor.py b/homeassistant/components/nsw_fuel_station/sensor.py index 92a071ec439..6c8061294e9 100644 --- a/homeassistant/components/nsw_fuel_station/sensor.py +++ b/homeassistant/components/nsw_fuel_station/sensor.py @@ -7,10 +7,9 @@ import logging from nsw_fuel import FuelCheckClient, FuelCheckError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CURRENCY_CENT, VOLUME_LITERS import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -146,7 +145,7 @@ class StationPriceData: return self._station_name -class StationPriceSensor(Entity): +class StationPriceSensor(SensorEntity): """Implementation of a sensor that reports the fuel price for a station.""" def __init__(self, station_data: StationPriceData, fuel_type: str): diff --git a/homeassistant/components/numato/sensor.py b/homeassistant/components/numato/sensor.py index e268d32a293..19372de5258 100644 --- a/homeassistant/components/numato/sensor.py +++ b/homeassistant/components/numato/sensor.py @@ -3,8 +3,8 @@ import logging from numato_gpio import NumatoGpioError +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_ID, CONF_NAME, CONF_SENSORS -from homeassistant.helpers.entity import Entity from . import ( CONF_DEVICES, @@ -58,7 +58,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class NumatoGpioAdc(Entity): +class NumatoGpioAdc(SensorEntity): """Represents an ADC port of a Numato USB GPIO expander.""" def __init__(self, name, device_id, port, src_range, dst_range, dst_unit, api): diff --git a/homeassistant/components/nut/sensor.py b/homeassistant/components/nut/sensor.py index d4fdd03adc8..2e3826935fe 100644 --- a/homeassistant/components/nut/sensor.py +++ b/homeassistant/components/nut/sensor.py @@ -1,6 +1,7 @@ """Provides a sensor to track various status aspects of a UPS.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_STATE, CONF_RESOURCES, STATE_UNKNOWN from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -76,7 +77,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class NUTSensor(CoordinatorEntity): +class NUTSensor(CoordinatorEntity, SensorEntity): """Representation of a sensor entity for NUT status values.""" def __init__( diff --git a/homeassistant/components/nzbget/sensor.py b/homeassistant/components/nzbget/sensor.py index a8870db52a3..54a88c89f53 100644 --- a/homeassistant/components/nzbget/sensor.py +++ b/homeassistant/components/nzbget/sensor.py @@ -5,6 +5,7 @@ from datetime import timedelta import logging from typing import Callable +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_NAME, @@ -66,7 +67,7 @@ async def async_setup_entry( async_add_entities(sensors) -class NZBGetSensor(NZBGetEntity): +class NZBGetSensor(NZBGetEntity, SensorEntity): """Representation of a NZBGet sensor.""" def __init__( diff --git a/homeassistant/components/oasa_telematics/sensor.py b/homeassistant/components/oasa_telematics/sensor.py index 8af74b5cd0e..71af8dacba2 100644 --- a/homeassistant/components/oasa_telematics/sensor.py +++ b/homeassistant/components/oasa_telematics/sensor.py @@ -6,10 +6,9 @@ from operator import itemgetter import oasatelematics import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, DEVICE_CLASS_TIMESTAMP import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -52,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([OASATelematicsSensor(data, stop_id, route_id, name)], True) -class OASATelematicsSensor(Entity): +class OASATelematicsSensor(SensorEntity): """Implementation of the OASA Telematics sensor.""" def __init__(self, data, stop_id, route_id, name): diff --git a/homeassistant/components/obihai/sensor.py b/homeassistant/components/obihai/sensor.py index c105b91971d..9aacaa84193 100644 --- a/homeassistant/components/obihai/sensor.py +++ b/homeassistant/components/obihai/sensor.py @@ -5,7 +5,7 @@ import logging from pyobihai import PyObihai import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -13,7 +13,6 @@ from homeassistant.const import ( DEVICE_CLASS_TIMESTAMP, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -69,7 +68,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class ObihaiServiceSensors(Entity): +class ObihaiServiceSensors(SensorEntity): """Get the status of each Obihai Lines.""" def __init__(self, pyobihai, serial, service_name): diff --git a/homeassistant/components/octoprint/sensor.py b/homeassistant/components/octoprint/sensor.py index 921f355edbe..f2c5c56c58a 100644 --- a/homeassistant/components/octoprint/sensor.py +++ b/homeassistant/components/octoprint/sensor.py @@ -3,8 +3,8 @@ import logging import requests +from homeassistant.components.sensor import SensorEntity from homeassistant.const import PERCENTAGE, TEMP_CELSIUS -from homeassistant.helpers.entity import Entity from . import DOMAIN as COMPONENT_DOMAIN, SENSOR_TYPES @@ -71,7 +71,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class OctoPrintSensor(Entity): +class OctoPrintSensor(SensorEntity): """Representation of an OctoPrint sensor.""" def __init__( diff --git a/homeassistant/components/ohmconnect/sensor.py b/homeassistant/components/ohmconnect/sensor.py index 6c7c04b25cb..b53c35e17b5 100644 --- a/homeassistant/components/ohmconnect/sensor.py +++ b/homeassistant/components/ohmconnect/sensor.py @@ -6,10 +6,9 @@ import defusedxml.ElementTree as ET import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_ID, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -34,7 +33,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([OhmconnectSensor(name, ohmid)], True) -class OhmconnectSensor(Entity): +class OhmconnectSensor(SensorEntity): """Representation of a OhmConnect sensor.""" def __init__(self, name, ohmid): diff --git a/homeassistant/components/ombi/sensor.py b/homeassistant/components/ombi/sensor.py index 2a2f50532b4..8c08b026b28 100644 --- a/homeassistant/components/ombi/sensor.py +++ b/homeassistant/components/ombi/sensor.py @@ -4,7 +4,7 @@ import logging from pyombi import OmbiError -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from .const import DOMAIN, SENSOR_TYPES @@ -31,7 +31,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class OmbiSensor(Entity): +class OmbiSensor(SensorEntity): """Representation of an Ombi sensor.""" def __init__(self, label, sensor_type, ombi, icon): diff --git a/homeassistant/components/omnilogic/sensor.py b/homeassistant/components/omnilogic/sensor.py index e1b4b387a46..25457224e9f 100644 --- a/homeassistant/components/omnilogic/sensor.py +++ b/homeassistant/components/omnilogic/sensor.py @@ -1,5 +1,5 @@ """Definition and setup of the Omnilogic Sensors for Home Assistant.""" -from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE +from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE, SensorEntity from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, MASS_GRAMS, @@ -59,7 +59,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class OmnilogicSensor(OmniLogicEntity): +class OmnilogicSensor(OmniLogicEntity, SensorEntity): """Defines an Omnilogic sensor entity.""" def __init__( diff --git a/homeassistant/components/ondilo_ico/sensor.py b/homeassistant/components/ondilo_ico/sensor.py index b34ee4eae35..3af2bb7c326 100644 --- a/homeassistant/components/ondilo_ico/sensor.py +++ b/homeassistant/components/ondilo_ico/sensor.py @@ -4,6 +4,7 @@ import logging from ondilo import OndiloError +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, DEVICE_CLASS_BATTERY, @@ -83,7 +84,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class OndiloICO(CoordinatorEntity): +class OndiloICO(CoordinatorEntity, SensorEntity): """Representation of a Sensor.""" def __init__( diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index 4888383fa42..ba988aba226 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -6,7 +6,7 @@ import os from pi1wire import InvalidCRCException, UnsupportResponseException import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE import homeassistant.helpers.config_validation as cv @@ -394,7 +394,7 @@ def get_entities(onewirehub: OneWireHub, config): return entities -class OneWireProxySensor(OneWireProxyEntity): +class OneWireProxySensor(OneWireProxyEntity, SensorEntity): """Implementation of a 1-Wire sensor connected through owserver.""" @property @@ -403,7 +403,7 @@ class OneWireProxySensor(OneWireProxyEntity): return self._state -class OneWireDirectSensor(OneWireBaseEntity): +class OneWireDirectSensor(OneWireBaseEntity, SensorEntity): """Implementation of a 1-Wire sensor directly connected to RPI GPIO.""" def __init__(self, name, device_file, device_info, owsensor): @@ -431,7 +431,7 @@ class OneWireDirectSensor(OneWireBaseEntity): self._state = value -class OneWireOWFSSensor(OneWireBaseEntity): # pragma: no cover +class OneWireOWFSSensor(OneWireBaseEntity, SensorEntity): # pragma: no cover """Implementation of a 1-Wire sensor through owfs. This part of the implementation does not conform to policy regarding 3rd-party libraries, and will not longer be updated. diff --git a/homeassistant/components/onvif/sensor.py b/homeassistant/components/onvif/sensor.py index 3895e8a4abb..1c5766e3969 100644 --- a/homeassistant/components/onvif/sensor.py +++ b/homeassistant/components/onvif/sensor.py @@ -1,6 +1,7 @@ """Support for ONVIF binary sensors.""" from __future__ import annotations +from homeassistant.components.sensor import SensorEntity from homeassistant.core import callback from .base import ONVIFBaseEntity @@ -33,7 +34,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): return True -class ONVIFSensor(ONVIFBaseEntity): +class ONVIFSensor(ONVIFBaseEntity, SensorEntity): """Representation of a ONVIF sensor event.""" def __init__(self, uid, device): diff --git a/homeassistant/components/openerz/sensor.py b/homeassistant/components/openerz/sensor.py index 9a5bf3a9813..33305b677de 100644 --- a/homeassistant/components/openerz/sensor.py +++ b/homeassistant/components/openerz/sensor.py @@ -4,9 +4,9 @@ from datetime import timedelta from openerz_api.main import OpenERZConnector import voluptuous as vol +from homeassistant.components.sensor import SensorEntity import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import PLATFORM_SCHEMA -from homeassistant.helpers.entity import Entity SCAN_INTERVAL = timedelta(hours=12) @@ -29,7 +29,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([OpenERZSensor(api_connector, config.get(CONF_NAME))], True) -class OpenERZSensor(Entity): +class OpenERZSensor(SensorEntity): """Representation of a Sensor.""" def __init__(self, api_connector, name): diff --git a/homeassistant/components/openevse/sensor.py b/homeassistant/components/openevse/sensor.py index e0f21f6946d..d7d4149e26d 100644 --- a/homeassistant/components/openevse/sensor.py +++ b/homeassistant/components/openevse/sensor.py @@ -5,7 +5,7 @@ import openevsewifi from requests import RequestException import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_MONITORED_VARIABLES, @@ -14,7 +14,6 @@ from homeassistant.const import ( TIME_MINUTES, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -52,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class OpenEVSESensor(Entity): +class OpenEVSESensor(SensorEntity): """Implementation of an OpenEVSE sensor.""" def __init__(self, sensor_type, charger): diff --git a/homeassistant/components/openexchangerates/sensor.py b/homeassistant/components/openexchangerates/sensor.py index f3f0d825ff6..8474cdab131 100644 --- a/homeassistant/components/openexchangerates/sensor.py +++ b/homeassistant/components/openexchangerates/sensor.py @@ -5,7 +5,7 @@ import logging import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -15,7 +15,6 @@ from homeassistant.const import ( HTTP_OK, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -58,7 +57,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([OpenexchangeratesSensor(rest, name, quote)], True) -class OpenexchangeratesSensor(Entity): +class OpenexchangeratesSensor(SensorEntity): """Representation of an Open Exchange Rates sensor.""" def __init__(self, rest, name, quote): diff --git a/homeassistant/components/openhardwaremonitor/sensor.py b/homeassistant/components/openhardwaremonitor/sensor.py index 3254f0824f1..70d0d36176c 100644 --- a/homeassistant/components/openhardwaremonitor/sensor.py +++ b/homeassistant/components/openhardwaremonitor/sensor.py @@ -5,11 +5,10 @@ import logging import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.dt import utcnow @@ -44,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(data.devices, True) -class OpenHardwareMonitorDevice(Entity): +class OpenHardwareMonitorDevice(SensorEntity): """Device used to display information from OpenHardwareMonitor.""" def __init__(self, data, name, path, unit_of_measurement): diff --git a/homeassistant/components/opensky/sensor.py b/homeassistant/components/opensky/sensor.py index e5ffb2384f5..122388b85b7 100644 --- a/homeassistant/components/opensky/sensor.py +++ b/homeassistant/components/opensky/sensor.py @@ -4,7 +4,7 @@ from datetime import timedelta import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, @@ -17,7 +17,6 @@ from homeassistant.const import ( LENGTH_METERS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import distance as util_distance, location as util_location CONF_ALTITUDE = "altitude" @@ -87,7 +86,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class OpenSkySensor(Entity): +class OpenSkySensor(SensorEntity): """Open Sky Network Sensor.""" def __init__(self, hass, name, latitude, longitude, radius, altitude): diff --git a/homeassistant/components/opentherm_gw/sensor.py b/homeassistant/components/opentherm_gw/sensor.py index 4a20aa651cd..1d9904ea59f 100644 --- a/homeassistant/components/opentherm_gw/sensor.py +++ b/homeassistant/components/opentherm_gw/sensor.py @@ -2,11 +2,11 @@ import logging from pprint import pformat -from homeassistant.components.sensor import ENTITY_ID_FORMAT +from homeassistant.components.sensor import ENTITY_ID_FORMAT, SensorEntity from homeassistant.const import CONF_ID from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity, async_generate_entity_id +from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.entity_registry import async_get_registry from . import DOMAIN @@ -77,7 +77,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -class OpenThermSensor(Entity): +class OpenThermSensor(SensorEntity): """Representation of an OpenTherm Gateway sensor.""" def __init__(self, gw_dev, var, source, device_class, unit, friendly_name_format): diff --git a/homeassistant/components/openuv/sensor.py b/homeassistant/components/openuv/sensor.py index b9c73023c11..654a89cfcf9 100644 --- a/homeassistant/components/openuv/sensor.py +++ b/homeassistant/components/openuv/sensor.py @@ -1,4 +1,5 @@ """Support for OpenUV sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import TIME_MINUTES, UV_INDEX from homeassistant.core import callback from homeassistant.util.dt import as_local, parse_datetime @@ -87,7 +88,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensors, True) -class OpenUvSensor(OpenUvEntity): +class OpenUvSensor(OpenUvEntity, SensorEntity): """Define a binary sensor for OpenUV.""" def __init__(self, openuv, sensor_type, name, icon, unit, entry_id): diff --git a/homeassistant/components/openweathermap/abstract_owm_sensor.py b/homeassistant/components/openweathermap/abstract_owm_sensor.py index a69f542589b..30a21a057f0 100644 --- a/homeassistant/components/openweathermap/abstract_owm_sensor.py +++ b/homeassistant/components/openweathermap/abstract_owm_sensor.py @@ -1,12 +1,12 @@ """Abstraction form OWM sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION -from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ATTRIBUTION, SENSOR_DEVICE_CLASS, SENSOR_NAME, SENSOR_UNIT -class AbstractOpenWeatherMapSensor(Entity): +class AbstractOpenWeatherMapSensor(SensorEntity): """Abstract class for an OpenWeatherMap sensor.""" def __init__( diff --git a/homeassistant/components/oru/sensor.py b/homeassistant/components/oru/sensor.py index d6620ed39e5..063c0c6169d 100644 --- a/homeassistant/components/oru/sensor.py +++ b/homeassistant/components/oru/sensor.py @@ -5,10 +5,9 @@ import logging from oru import Meter, MeterError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ENERGY_KILO_WATT_HOUR import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -39,7 +38,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.debug("Oru meter_number = %s", meter_number) -class CurrentEnergyUsageSensor(Entity): +class CurrentEnergyUsageSensor(SensorEntity): """Representation of the sensor.""" def __init__(self, meter): diff --git a/homeassistant/components/otp/sensor.py b/homeassistant/components/otp/sensor.py index 9f23f5ae6fa..7aee9d99208 100644 --- a/homeassistant/components/otp/sensor.py +++ b/homeassistant/components/otp/sensor.py @@ -4,11 +4,10 @@ import time import pyotp import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_TOKEN from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity DEFAULT_NAME = "OTP Sensor" @@ -34,7 +33,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= # Only TOTP supported at the moment, HOTP might be added later -class TOTPSensor(Entity): +class TOTPSensor(SensorEntity): """Representation of a TOTP sensor.""" def __init__(self, name, token): diff --git a/homeassistant/components/ovo_energy/sensor.py b/homeassistant/components/ovo_energy/sensor.py index 3171b3231dc..d03f7c49f96 100644 --- a/homeassistant/components/ovo_energy/sensor.py +++ b/homeassistant/components/ovo_energy/sensor.py @@ -4,6 +4,7 @@ from datetime import timedelta from ovoenergy import OVODailyUsage from ovoenergy.ovoenergy import OVOEnergy +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator @@ -53,7 +54,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class OVOEnergySensor(OVOEnergyDeviceEntity): +class OVOEnergySensor(OVOEnergyDeviceEntity, SensorEntity): """Defines a OVO Energy sensor.""" def __init__( diff --git a/homeassistant/components/ozw/sensor.py b/homeassistant/components/ozw/sensor.py index db695bcf6bc..3c3d4c3ca36 100644 --- a/homeassistant/components/ozw/sensor.py +++ b/homeassistant/components/ozw/sensor.py @@ -12,6 +12,7 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, DOMAIN as SENSOR_DOMAIN, + SensorEntity, ) from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import callback @@ -57,7 +58,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class ZwaveSensorBase(ZWaveDeviceEntity): +class ZwaveSensorBase(ZWaveDeviceEntity, SensorEntity): """Basic Representation of a Z-Wave sensor.""" @property diff --git a/homeassistant/components/pi_hole/sensor.py b/homeassistant/components/pi_hole/sensor.py index 4bd4c7b7f6f..517e8cfcf17 100644 --- a/homeassistant/components/pi_hole/sensor.py +++ b/homeassistant/components/pi_hole/sensor.py @@ -1,5 +1,6 @@ """Support for getting statistical data from a Pi-hole system.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME from . import PiHoleEntity @@ -30,7 +31,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensors, True) -class PiHoleSensor(PiHoleEntity): +class PiHoleSensor(PiHoleEntity, SensorEntity): """Representation of a Pi-hole sensor.""" def __init__(self, api, coordinator, name, sensor_name, server_unique_id): diff --git a/homeassistant/components/pilight/sensor.py b/homeassistant/components/pilight/sensor.py index e8c7b4bd4b6..97458acd5fc 100644 --- a/homeassistant/components/pilight/sensor.py +++ b/homeassistant/components/pilight/sensor.py @@ -4,10 +4,9 @@ import logging import voluptuous as vol from homeassistant.components import pilight -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_PAYLOAD, CONF_UNIT_OF_MEASUREMENT import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -39,7 +38,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class PilightSensor(Entity): +class PilightSensor(SensorEntity): """Representation of a sensor that can be updated using Pilight.""" def __init__(self, hass, name, variable, payload, unit_of_measurement): diff --git a/homeassistant/components/plaato/sensor.py b/homeassistant/components/plaato/sensor.py index c2c59d347f3..0812a2fd585 100644 --- a/homeassistant/components/plaato/sensor.py +++ b/homeassistant/components/plaato/sensor.py @@ -4,7 +4,7 @@ from __future__ import annotations from pyplaato.models.device import PlaatoDevice from pyplaato.plaato import PlaatoKeg -from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE +from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE, SensorEntity from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, @@ -59,7 +59,7 @@ async def async_setup_entry(hass, entry, async_add_entities): ) -class PlaatoSensor(PlaatoEntity): +class PlaatoSensor(PlaatoEntity, SensorEntity): """Representation of a Plaato Sensor.""" @property diff --git a/homeassistant/components/plex/sensor.py b/homeassistant/components/plex/sensor.py index 47dd05a557b..d79c4120dc7 100644 --- a/homeassistant/components/plex/sensor.py +++ b/homeassistant/components/plex/sensor.py @@ -1,9 +1,9 @@ """Support for Plex media server monitoring.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from .const import ( CONF_SERVER_IDENTIFIER, @@ -25,7 +25,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities([sensor]) -class PlexSensor(Entity): +class PlexSensor(SensorEntity): """Representation of a Plex now playing sensor.""" def __init__(self, hass, plex_server): diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py index f57ff2b2a91..4152f9fdabd 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -2,6 +2,7 @@ import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, @@ -17,7 +18,6 @@ from homeassistant.const import ( VOLUME_CUBIC_METERS, ) from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from .const import ( COOL_ICON, @@ -236,7 +236,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class SmileSensor(SmileGateway): +class SmileSensor(SmileGateway, SensorEntity): """Represent Smile Sensors.""" def __init__(self, api, coordinator, name, dev_id, sensor): @@ -282,7 +282,7 @@ class SmileSensor(SmileGateway): return self._unit_of_measurement -class PwThermostatSensor(SmileSensor, Entity): +class PwThermostatSensor(SmileSensor): """Thermostat (or generic) sensor devices.""" def __init__(self, api, coordinator, name, dev_id, sensor, sensor_type): @@ -311,7 +311,7 @@ class PwThermostatSensor(SmileSensor, Entity): self.async_write_ha_state() -class PwAuxDeviceSensor(SmileSensor, Entity): +class PwAuxDeviceSensor(SmileSensor): """Auxiliary Device Sensors.""" def __init__(self, api, coordinator, name, dev_id, sensor): @@ -348,7 +348,7 @@ class PwAuxDeviceSensor(SmileSensor, Entity): self.async_write_ha_state() -class PwPowerSensor(SmileSensor, Entity): +class PwPowerSensor(SmileSensor): """Power sensor entities.""" def __init__(self, api, coordinator, name, dev_id, sensor, sensor_type, model): diff --git a/homeassistant/components/pocketcasts/sensor.py b/homeassistant/components/pocketcasts/sensor.py index 19f7e265438..55ae4a524fc 100644 --- a/homeassistant/components/pocketcasts/sensor.py +++ b/homeassistant/components/pocketcasts/sensor.py @@ -5,10 +5,9 @@ import logging from pycketcasts import pocketcasts import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_PASSWORD, CONF_USERNAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -37,7 +36,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return False -class PocketCastsSensor(Entity): +class PocketCastsSensor(SensorEntity): """Representation of a pocket casts sensor.""" def __init__(self, api): diff --git a/homeassistant/components/point/sensor.py b/homeassistant/components/point/sensor.py index 87f9a8ab2fe..338ed275f50 100644 --- a/homeassistant/components/point/sensor.py +++ b/homeassistant/components/point/sensor.py @@ -1,7 +1,7 @@ """Support for Minut Point sensors.""" import logging -from homeassistant.components.sensor import DOMAIN +from homeassistant.components.sensor import DOMAIN, SensorEntity from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PRESSURE, @@ -47,7 +47,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class MinutPointSensor(MinutPointEntity): +class MinutPointSensor(MinutPointEntity, SensorEntity): """The platform class required by Home Assistant.""" def __init__(self, point_client, device_id, device_class): diff --git a/homeassistant/components/poolsense/sensor.py b/homeassistant/components/poolsense/sensor.py index bf5c3eb0163..ca79fde6b08 100644 --- a/homeassistant/components/poolsense/sensor.py +++ b/homeassistant/components/poolsense/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for the PoolSense sensor.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_EMAIL, @@ -8,7 +9,6 @@ from homeassistant.const import ( PERCENTAGE, TEMP_CELSIUS, ) -from homeassistant.helpers.entity import Entity from . import PoolSenseEntity from .const import ATTRIBUTION, DOMAIN @@ -79,7 +79,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors_list, False) -class PoolSenseSensor(PoolSenseEntity, Entity): +class PoolSenseSensor(PoolSenseEntity, SensorEntity): """Sensor representing poolsense data.""" @property diff --git a/homeassistant/components/powerwall/sensor.py b/homeassistant/components/powerwall/sensor.py index 3b4d7918cf7..36f803e66d7 100644 --- a/homeassistant/components/powerwall/sensor.py +++ b/homeassistant/components/powerwall/sensor.py @@ -3,6 +3,7 @@ import logging from tesla_powerwall import MeterType +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_BATTERY, DEVICE_CLASS_POWER, PERCENTAGE from .const import ( @@ -59,7 +60,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class PowerWallChargeSensor(PowerWallEntity): +class PowerWallChargeSensor(PowerWallEntity, SensorEntity): """Representation of an Powerwall charge sensor.""" @property @@ -88,7 +89,7 @@ class PowerWallChargeSensor(PowerWallEntity): return round(self.coordinator.data[POWERWALL_API_CHARGE]) -class PowerWallEnergySensor(PowerWallEntity): +class PowerWallEnergySensor(PowerWallEntity, SensorEntity): """Representation of an Powerwall Energy sensor.""" def __init__( diff --git a/homeassistant/components/pushbullet/sensor.py b/homeassistant/components/pushbullet/sensor.py index f7aaa693c67..4f8ec6a1700 100644 --- a/homeassistant/components/pushbullet/sensor.py +++ b/homeassistant/components/pushbullet/sensor.py @@ -5,10 +5,9 @@ import threading from pushbullet import InvalidKeyError, Listener, PushBullet import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_API_KEY, CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -52,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class PushBulletNotificationSensor(Entity): +class PushBulletNotificationSensor(SensorEntity): """Representation of a Pushbullet Sensor.""" def __init__(self, pb, element): diff --git a/homeassistant/components/pvoutput/sensor.py b/homeassistant/components/pvoutput/sensor.py index 9e88cb0a664..eb461061dcc 100644 --- a/homeassistant/components/pvoutput/sensor.py +++ b/homeassistant/components/pvoutput/sensor.py @@ -6,7 +6,7 @@ import logging import voluptuous as vol from homeassistant.components.rest.data import RestData -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_DATE, ATTR_TEMPERATURE, @@ -17,7 +17,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) _ENDPOINT = "http://pvoutput.org/service/r2/getstatus.jsp" @@ -64,7 +63,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([PvoutputSensor(rest, name)]) -class PvoutputSensor(Entity): +class PvoutputSensor(SensorEntity): """Representation of a PVOutput sensor.""" def __init__(self, rest, name): diff --git a/homeassistant/components/pvpc_hourly_pricing/sensor.py b/homeassistant/components/pvpc_hourly_pricing/sensor.py index 3aca4db67e7..5fe65e3dc65 100644 --- a/homeassistant/components/pvpc_hourly_pricing/sensor.py +++ b/homeassistant/components/pvpc_hourly_pricing/sensor.py @@ -7,6 +7,7 @@ from random import randint from aiopvpc import PVPCData from homeassistant import config_entries +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME, CURRENCY_EURO, ENERGY_KILO_WATT_HOUR from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -42,7 +43,7 @@ async def async_setup_entry( ) -class ElecPriceSensor(RestoreEntity): +class ElecPriceSensor(RestoreEntity, SensorEntity): """Class to hold the prices of electricity as a sensor.""" unit_of_measurement = UNIT diff --git a/homeassistant/components/pyload/sensor.py b/homeassistant/components/pyload/sensor.py index 6539479d2cd..c439d5181be 100644 --- a/homeassistant/components/pyload/sensor.py +++ b/homeassistant/components/pyload/sensor.py @@ -6,7 +6,7 @@ from aiohttp.hdrs import CONTENT_TYPE import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_MONITORED_VARIABLES, @@ -19,7 +19,6 @@ from homeassistant.const import ( DATA_RATE_MEGABYTES_PER_SECOND, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -77,7 +76,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class PyLoadSensor(Entity): +class PyLoadSensor(SensorEntity): """Representation of a pyLoad sensor.""" def __init__(self, api, sensor_type, client_name): diff --git a/homeassistant/components/qbittorrent/sensor.py b/homeassistant/components/qbittorrent/sensor.py index cd67355d883..251407099b1 100644 --- a/homeassistant/components/qbittorrent/sensor.py +++ b/homeassistant/components/qbittorrent/sensor.py @@ -5,7 +5,7 @@ from qbittorrent.client import Client, LoginRequired from requests.exceptions import RequestException import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, @@ -16,7 +16,6 @@ from homeassistant.const import ( ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -71,7 +70,7 @@ def format_speed(speed): return round(kb_spd, 2 if kb_spd < 0.1 else 1) -class QBittorrentSensor(Entity): +class QBittorrentSensor(SensorEntity): """Representation of an qBittorrent sensor.""" def __init__(self, sensor_type, qbittorrent_client, client_name, exception): diff --git a/homeassistant/components/qnap/sensor.py b/homeassistant/components/qnap/sensor.py index 5f7695e5a60..5759713e80c 100644 --- a/homeassistant/components/qnap/sensor.py +++ b/homeassistant/components/qnap/sensor.py @@ -5,7 +5,7 @@ import logging from qnapstats import QNAPStats import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_NAME, CONF_HOST, @@ -23,7 +23,6 @@ from homeassistant.const import ( ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -200,7 +199,7 @@ class QNAPStatsAPI: _LOGGER.exception("Failed to fetch QNAP stats from the NAS") -class QNAPSensor(Entity): +class QNAPSensor(SensorEntity): """Base class for a QNAP sensor.""" def __init__(self, api, variable, variable_info, monitor_device=None): diff --git a/homeassistant/components/qwikswitch/sensor.py b/homeassistant/components/qwikswitch/sensor.py index 53cf68ccdba..f6d0ce7ec28 100644 --- a/homeassistant/components/qwikswitch/sensor.py +++ b/homeassistant/components/qwikswitch/sensor.py @@ -3,6 +3,7 @@ import logging from pyqwikswitch.qwikswitch import SENSORS +from homeassistant.components.sensor import SensorEntity from homeassistant.core import callback from . import DOMAIN as QWIKSWITCH, QSEntity @@ -21,7 +22,7 @@ async def async_setup_platform(hass, _, add_entities, discovery_info=None): add_entities(devs) -class QSSensor(QSEntity): +class QSSensor(QSEntity, SensorEntity): """Sensor based on a Qwikswitch relay/dimmer module.""" _val = None From 783b453bbe4fb07e812ee03174a7c87789ca13f6 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 19:47:44 +0100 Subject: [PATCH 551/831] Migrate integrations t-v to extend SensorEntity (#48216) --- homeassistant/components/tado/sensor.py | 6 +++--- homeassistant/components/tahoma/sensor.py | 4 ++-- homeassistant/components/tank_utility/sensor.py | 5 ++--- homeassistant/components/tankerkoenig/sensor.py | 3 ++- homeassistant/components/tasmota/sensor.py | 4 ++-- homeassistant/components/tautulli/sensor.py | 5 ++--- homeassistant/components/tcp/sensor.py | 5 ++--- homeassistant/components/ted5000/sensor.py | 5 ++--- homeassistant/components/tellduslive/sensor.py | 3 ++- homeassistant/components/tellstick/sensor.py | 5 ++--- homeassistant/components/temper/sensor.py | 5 ++--- homeassistant/components/template/sensor.py | 5 +++-- homeassistant/components/tesla/sensor.py | 5 ++--- homeassistant/components/thermoworks_smoke/sensor.py | 5 ++--- homeassistant/components/thethingsnetwork/sensor.py | 5 ++--- homeassistant/components/thinkingcleaner/sensor.py | 5 ++--- homeassistant/components/tibber/sensor.py | 5 ++--- homeassistant/components/time_date/sensor.py | 5 ++--- homeassistant/components/tmb/sensor.py | 5 ++--- homeassistant/components/tof/sensor.py | 5 ++--- homeassistant/components/toon/sensor.py | 3 ++- homeassistant/components/torque/sensor.py | 5 ++--- homeassistant/components/tradfri/sensor.py | 3 ++- homeassistant/components/trafikverket_train/sensor.py | 5 ++--- .../components/trafikverket_weatherstation/sensor.py | 5 ++--- homeassistant/components/transmission/sensor.py | 4 ++-- homeassistant/components/transport_nsw/sensor.py | 5 ++--- homeassistant/components/travisci/sensor.py | 5 ++--- homeassistant/components/twentemilieu/sensor.py | 3 ++- homeassistant/components/twitch/sensor.py | 5 ++--- homeassistant/components/uk_transport/sensor.py | 5 ++--- homeassistant/components/unifi/sensor.py | 6 +++--- homeassistant/components/upnp/sensor.py | 3 ++- homeassistant/components/uptime/sensor.py | 9 ++++++--- homeassistant/components/uscis/sensor.py | 5 ++--- homeassistant/components/utility_meter/sensor.py | 3 ++- homeassistant/components/vallox/sensor.py | 4 ++-- homeassistant/components/vasttrafik/sensor.py | 5 ++--- homeassistant/components/velbus/sensor.py | 3 ++- homeassistant/components/vera/sensor.py | 8 ++++++-- homeassistant/components/verisure/sensor.py | 7 ++++--- homeassistant/components/versasense/sensor.py | 4 ++-- homeassistant/components/version/sensor.py | 5 ++--- homeassistant/components/viaggiatreno/sensor.py | 5 ++--- homeassistant/components/vicare/sensor.py | 4 ++-- homeassistant/components/vilfo/sensor.py | 4 ++-- homeassistant/components/volkszaehler/sensor.py | 5 ++--- homeassistant/components/volvooncall/sensor.py | 4 +++- homeassistant/components/vultr/sensor.py | 5 ++--- 49 files changed, 112 insertions(+), 120 deletions(-) diff --git a/homeassistant/components/tado/sensor.py b/homeassistant/components/tado/sensor.py index cfafeb14ddf..87d2170eb75 100644 --- a/homeassistant/components/tado/sensor.py +++ b/homeassistant/components/tado/sensor.py @@ -1,6 +1,7 @@ """Support for Tado sensors for each zone.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, @@ -10,7 +11,6 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from .const import ( CONDITIONS_MAP, @@ -86,7 +86,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class TadoHomeSensor(TadoHomeEntity, Entity): +class TadoHomeSensor(TadoHomeEntity, SensorEntity): """Representation of a Tado Sensor.""" def __init__(self, tado, home_variable): @@ -191,7 +191,7 @@ class TadoHomeSensor(TadoHomeEntity, Entity): } -class TadoZoneSensor(TadoZoneEntity, Entity): +class TadoZoneSensor(TadoZoneEntity, SensorEntity): """Representation of a tado Sensor.""" def __init__(self, tado, zone_name, zone_id, zone_variable): diff --git a/homeassistant/components/tahoma/sensor.py b/homeassistant/components/tahoma/sensor.py index 91629137318..47e6d300414 100644 --- a/homeassistant/components/tahoma/sensor.py +++ b/homeassistant/components/tahoma/sensor.py @@ -2,8 +2,8 @@ from datetime import timedelta import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_BATTERY_LEVEL, LIGHT_LUX, PERCENTAGE, TEMP_CELSIUS -from homeassistant.helpers.entity import Entity from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice @@ -25,7 +25,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class TahomaSensor(TahomaDevice, Entity): +class TahomaSensor(TahomaDevice, SensorEntity): """Representation of a Tahoma Sensor.""" def __init__(self, tahoma_device, controller): diff --git a/homeassistant/components/tank_utility/sensor.py b/homeassistant/components/tank_utility/sensor.py index d0566ec7c9e..379819cf65e 100644 --- a/homeassistant/components/tank_utility/sensor.py +++ b/homeassistant/components/tank_utility/sensor.py @@ -7,10 +7,9 @@ import requests from tank_utility import auth, device as tank_monitor import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_DEVICES, CONF_EMAIL, CONF_PASSWORD, PERCENTAGE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -62,7 +61,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(all_sensors, True) -class TankUtilitySensor(Entity): +class TankUtilitySensor(SensorEntity): """Representation of a Tank Utility sensor.""" def __init__(self, email, password, token, device): diff --git a/homeassistant/components/tankerkoenig/sensor.py b/homeassistant/components/tankerkoenig/sensor.py index a1cbed2ba11..5c1898e02a9 100644 --- a/homeassistant/components/tankerkoenig/sensor.py +++ b/homeassistant/components/tankerkoenig/sensor.py @@ -2,6 +2,7 @@ import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, @@ -79,7 +80,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(entities) -class FuelPriceSensor(CoordinatorEntity): +class FuelPriceSensor(CoordinatorEntity, SensorEntity): """Contains prices for fuel in a given station.""" def __init__(self, fuel_type, station, coordinator, name, show_on_map): diff --git a/homeassistant/components/tasmota/sensor.py b/homeassistant/components/tasmota/sensor.py index 9d7195a98e5..432fc2266f3 100644 --- a/homeassistant/components/tasmota/sensor.py +++ b/homeassistant/components/tasmota/sensor.py @@ -4,6 +4,7 @@ from __future__ import annotations from hatasmota import const as hc, status_sensor from homeassistant.components import sensor +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONCENTRATION_PARTS_PER_BILLION, @@ -38,7 +39,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from .const import DATA_REMOVE_DISCOVER_COMPONENT from .discovery import TASMOTA_DISCOVERY_ENTITY_NEW @@ -145,7 +145,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class TasmotaSensor(TasmotaAvailability, TasmotaDiscoveryUpdate, Entity): +class TasmotaSensor(TasmotaAvailability, TasmotaDiscoveryUpdate, SensorEntity): """Representation of a Tasmota sensor.""" def __init__(self, **kwds): diff --git a/homeassistant/components/tautulli/sensor.py b/homeassistant/components/tautulli/sensor.py index 2d2d005e95d..c50efb00ed7 100644 --- a/homeassistant/components/tautulli/sensor.py +++ b/homeassistant/components/tautulli/sensor.py @@ -4,7 +4,7 @@ from datetime import timedelta from pytautulli import Tautulli import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_API_KEY, CONF_HOST, @@ -18,7 +18,6 @@ from homeassistant.const import ( from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle CONF_MONITORED_USERS = "monitored_users" @@ -72,7 +71,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensor, True) -class TautulliSensor(Entity): +class TautulliSensor(SensorEntity): """Representation of a Tautulli sensor.""" def __init__(self, tautulli, name, monitored_conditions, users): diff --git a/homeassistant/components/tcp/sensor.py b/homeassistant/components/tcp/sensor.py index 9b7e1539fb4..54cf4d120f1 100644 --- a/homeassistant/components/tcp/sensor.py +++ b/homeassistant/components/tcp/sensor.py @@ -5,7 +5,7 @@ import socket import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -17,7 +17,6 @@ from homeassistant.const import ( ) from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -48,7 +47,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([TcpSensor(hass, config)]) -class TcpSensor(Entity): +class TcpSensor(SensorEntity): """Implementation of a TCP socket based sensor.""" required = () diff --git a/homeassistant/components/ted5000/sensor.py b/homeassistant/components/ted5000/sensor.py index 3ea26286b18..86c9b9ace95 100644 --- a/homeassistant/components/ted5000/sensor.py +++ b/homeassistant/components/ted5000/sensor.py @@ -6,10 +6,9 @@ import requests import voluptuous as vol import xmltodict -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, POWER_WATT, VOLT from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -49,7 +48,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class Ted5000Sensor(Entity): +class Ted5000Sensor(SensorEntity): """Implementation of a Ted5000 sensor.""" def __init__(self, gateway, name, mtu, unit): diff --git a/homeassistant/components/tellduslive/sensor.py b/homeassistant/components/tellduslive/sensor.py index 1b06fd6ed97..a86b487afd2 100644 --- a/homeassistant/components/tellduslive/sensor.py +++ b/homeassistant/components/tellduslive/sensor.py @@ -1,5 +1,6 @@ """Support for Tellstick Net/Telstick Live sensors.""" from homeassistant.components import sensor, tellduslive +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, @@ -71,7 +72,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class TelldusLiveSensor(TelldusLiveEntity): +class TelldusLiveSensor(TelldusLiveEntity, SensorEntity): """Representation of a Telldus Live sensor.""" @property diff --git a/homeassistant/components/tellstick/sensor.py b/homeassistant/components/tellstick/sensor.py index 444cabd0180..f58c5916bfb 100644 --- a/homeassistant/components/tellstick/sensor.py +++ b/homeassistant/components/tellstick/sensor.py @@ -6,7 +6,7 @@ from tellcore import telldus import tellcore.constants as tellcore_constants import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_ID, CONF_NAME, @@ -15,7 +15,6 @@ from homeassistant.const import ( TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -126,7 +125,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class TellstickSensor(Entity): +class TellstickSensor(SensorEntity): """Representation of a Tellstick sensor.""" def __init__(self, name, tellcore_sensor, datatype, sensor_info): diff --git a/homeassistant/components/temper/sensor.py b/homeassistant/components/temper/sensor.py index c47aa1878fc..7edbd3ba812 100644 --- a/homeassistant/components/temper/sensor.py +++ b/homeassistant/components/temper/sensor.py @@ -4,14 +4,13 @@ import logging from temperusb.temper import TemperHandler import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_NAME, CONF_OFFSET, DEVICE_DEFAULT_NAME, TEMP_FAHRENHEIT, ) -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -58,7 +57,7 @@ def reset_devices(): sensor.set_temper_device(device) -class TemperSensor(Entity): +class TemperSensor(SensorEntity): """Representation of a Temper temperature sensor.""" def __init__(self, temper_device, temp_unit, name, scaling): diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index 073924c51b6..b587fe3bd82 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -7,6 +7,7 @@ from homeassistant.components.sensor import ( DEVICE_CLASSES_SCHEMA, ENTITY_ID_FORMAT, PLATFORM_SCHEMA, + SensorEntity, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -23,7 +24,7 @@ from homeassistant.const import ( from homeassistant.core import callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity, async_generate_entity_id +from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.reload import async_setup_reload_service from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS @@ -99,7 +100,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(await _async_create_entities(hass, config)) -class SensorTemplate(TemplateEntity, Entity): +class SensorTemplate(TemplateEntity, SensorEntity): """Representation of a Template Sensor.""" def __init__( diff --git a/homeassistant/components/tesla/sensor.py b/homeassistant/components/tesla/sensor.py index 40bf68bfa15..40c7aa8548d 100644 --- a/homeassistant/components/tesla/sensor.py +++ b/homeassistant/components/tesla/sensor.py @@ -1,14 +1,13 @@ """Support for the Tesla sensors.""" from __future__ import annotations -from homeassistant.components.sensor import DEVICE_CLASSES +from homeassistant.components.sensor import DEVICE_CLASSES, SensorEntity from homeassistant.const import ( LENGTH_KILOMETERS, LENGTH_MILES, TEMP_CELSIUS, TEMP_FAHRENHEIT, ) -from homeassistant.helpers.entity import Entity from homeassistant.util.distance import convert from . import DOMAIN as TESLA_DOMAIN, TeslaDevice @@ -27,7 +26,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class TeslaSensor(TeslaDevice, Entity): +class TeslaSensor(TeslaDevice, SensorEntity): """Representation of Tesla sensors.""" def __init__(self, tesla_device, coordinator, sensor_type=None): diff --git a/homeassistant/components/thermoworks_smoke/sensor.py b/homeassistant/components/thermoworks_smoke/sensor.py index d768f009364..86427349f31 100644 --- a/homeassistant/components/thermoworks_smoke/sensor.py +++ b/homeassistant/components/thermoworks_smoke/sensor.py @@ -11,7 +11,7 @@ from stringcase import camelcase, snakecase import thermoworks_smoke import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_BATTERY_LEVEL, CONF_EMAIL, @@ -21,7 +21,6 @@ from homeassistant.const import ( TEMP_FAHRENHEIT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -91,7 +90,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.error(msg) -class ThermoworksSmokeSensor(Entity): +class ThermoworksSmokeSensor(SensorEntity): """Implementation of a thermoworks smoke sensor.""" def __init__(self, sensor_type, serial, mgr): diff --git a/homeassistant/components/thethingsnetwork/sensor.py b/homeassistant/components/thethingsnetwork/sensor.py index d758a20f384..2e139eae63d 100644 --- a/homeassistant/components/thethingsnetwork/sensor.py +++ b/homeassistant/components/thethingsnetwork/sensor.py @@ -7,7 +7,7 @@ from aiohttp.hdrs import ACCEPT, AUTHORIZATION import async_timeout import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_DEVICE_ID, ATTR_TIME, @@ -18,7 +18,6 @@ from homeassistant.const import ( ) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from . import DATA_TTN, TTN_ACCESS_KEY, TTN_APP_ID, TTN_DATA_STORAGE_URL @@ -59,7 +58,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devices, True) -class TtnDataSensor(Entity): +class TtnDataSensor(SensorEntity): """Representation of a The Things Network Data Storage sensor.""" def __init__(self, ttn_data_storage, device_id, value, unit_of_measurement): diff --git a/homeassistant/components/thinkingcleaner/sensor.py b/homeassistant/components/thinkingcleaner/sensor.py index 966930f1eb1..56cf272f7d1 100644 --- a/homeassistant/components/thinkingcleaner/sensor.py +++ b/homeassistant/components/thinkingcleaner/sensor.py @@ -5,10 +5,9 @@ from pythinkingcleaner import Discovery, ThinkingCleaner import voluptuous as vol from homeassistant import util -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_HOST, PERCENTAGE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100) @@ -73,7 +72,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev) -class ThinkingCleanerSensor(Entity): +class ThinkingCleanerSensor(SensorEntity): """Representation of a ThinkingCleaner Sensor.""" def __init__(self, tc_object, sensor_type, update_devices): diff --git a/homeassistant/components/tibber/sensor.py b/homeassistant/components/tibber/sensor.py index 01e050fdd24..5ab85013a25 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -6,10 +6,9 @@ from random import randrange import aiohttp -from homeassistant.components.sensor import DEVICE_CLASS_POWER +from homeassistant.components.sensor import DEVICE_CLASS_POWER, SensorEntity from homeassistant.const import POWER_WATT from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle, dt as dt_util from .const import DOMAIN as TIBBER_DOMAIN, MANUFACTURER @@ -45,7 +44,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(dev, True) -class TibberSensor(Entity): +class TibberSensor(SensorEntity): """Representation of a generic Tibber sensor.""" def __init__(self, tibber_home): diff --git a/homeassistant/components/time_date/sensor.py b/homeassistant/components/time_date/sensor.py index 4615e9e046c..08195e6dd3d 100644 --- a/homeassistant/components/time_date/sensor.py +++ b/homeassistant/components/time_date/sensor.py @@ -4,11 +4,10 @@ import logging import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_DISPLAY_OPTIONS from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_point_in_utc_time import homeassistant.util.dt as dt_util @@ -47,7 +46,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class TimeDateSensor(Entity): +class TimeDateSensor(SensorEntity): """Implementation of a Time and Date sensor.""" def __init__(self, hass, option_type): diff --git a/homeassistant/components/tmb/sensor.py b/homeassistant/components/tmb/sensor.py index c3f813d796a..88471a86c27 100644 --- a/homeassistant/components/tmb/sensor.py +++ b/homeassistant/components/tmb/sensor.py @@ -6,10 +6,9 @@ from requests import HTTPError from tmb import IBus import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, TIME_MINUTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -63,7 +62,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class TMBSensor(Entity): +class TMBSensor(SensorEntity): """Implementation of a TMB line/stop Sensor.""" def __init__(self, ibus_client, stop, line, name): diff --git a/homeassistant/components/tof/sensor.py b/homeassistant/components/tof/sensor.py index 17b2d1010bf..45713dd8f77 100644 --- a/homeassistant/components/tof/sensor.py +++ b/homeassistant/components/tof/sensor.py @@ -7,10 +7,9 @@ from VL53L1X2 import VL53L1X # pylint: disable=import-error import voluptuous as vol from homeassistant.components import rpi_gpio -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, LENGTH_MILLIMETERS import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity CONF_I2C_ADDRESS = "i2c_address" CONF_I2C_BUS = "i2c_bus" @@ -65,7 +64,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(dev, True) -class VL53L1XSensor(Entity): +class VL53L1XSensor(SensorEntity): """Implementation of VL53L1X sensor.""" def __init__(self, vl53l1x_sensor, name, unit, i2c_address): diff --git a/homeassistant/components/toon/sensor.py b/homeassistant/components/toon/sensor.py index 583683e53ae..36f5dedde3d 100644 --- a/homeassistant/components/toon/sensor.py +++ b/homeassistant/components/toon/sensor.py @@ -1,6 +1,7 @@ """Support for Toon sensors.""" from __future__ import annotations +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -109,7 +110,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class ToonSensor(ToonEntity): +class ToonSensor(ToonEntity, SensorEntity): """Defines a Toon sensor.""" def __init__(self, coordinator: ToonDataUpdateCoordinator, *, key: str) -> None: diff --git a/homeassistant/components/torque/sensor.py b/homeassistant/components/torque/sensor.py index 4b52a565d87..156259adccb 100644 --- a/homeassistant/components/torque/sensor.py +++ b/homeassistant/components/torque/sensor.py @@ -4,11 +4,10 @@ import re import voluptuous as vol from homeassistant.components.http import HomeAssistantView -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_EMAIL, CONF_NAME, DEGREE from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity API_PATH = "/api/torque" @@ -106,7 +105,7 @@ class TorqueReceiveDataView(HomeAssistantView): return "OK!" -class TorqueSensor(Entity): +class TorqueSensor(SensorEntity): """Representation of a Torque sensor.""" def __init__(self, name, unit): diff --git a/homeassistant/components/tradfri/sensor.py b/homeassistant/components/tradfri/sensor.py index c2bf640e2aa..455ca69147d 100644 --- a/homeassistant/components/tradfri/sensor.py +++ b/homeassistant/components/tradfri/sensor.py @@ -1,5 +1,6 @@ """Support for IKEA Tradfri sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE from .base_class import TradfriBaseDevice @@ -25,7 +26,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(TradfriSensor(sensor, api, gateway_id) for sensor in sensors) -class TradfriSensor(TradfriBaseDevice): +class TradfriSensor(TradfriBaseDevice, SensorEntity): """The platform class required by Home Assistant.""" def __init__(self, device, api, gateway_id): diff --git a/homeassistant/components/trafikverket_train/sensor.py b/homeassistant/components/trafikverket_train/sensor.py index 45899422564..37e3bd52cdc 100644 --- a/homeassistant/components/trafikverket_train/sensor.py +++ b/homeassistant/components/trafikverket_train/sensor.py @@ -6,7 +6,7 @@ import logging from pytrafikverket import TrafikverketTrain import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_API_KEY, CONF_NAME, @@ -16,7 +16,6 @@ from homeassistant.const import ( ) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -116,7 +115,7 @@ def next_departuredate(departure): return next_weekday(today_date, WEEKDAYS.index(departure[0])) -class TrainSensor(Entity): +class TrainSensor(SensorEntity): """Contains data about a train depature.""" def __init__(self, train_api, name, from_station, to_station, weekday, time): diff --git a/homeassistant/components/trafikverket_weatherstation/sensor.py b/homeassistant/components/trafikverket_weatherstation/sensor.py index f2e2521a90b..1ae090ea231 100644 --- a/homeassistant/components/trafikverket_weatherstation/sensor.py +++ b/homeassistant/components/trafikverket_weatherstation/sensor.py @@ -8,7 +8,7 @@ import aiohttp from pytrafikverket.trafikverket_weather import TrafikverketWeather import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -24,7 +24,6 @@ from homeassistant.const import ( ) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -145,7 +144,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(dev, True) -class TrafikverketWeatherStation(Entity): +class TrafikverketWeatherStation(SensorEntity): """Representation of a Trafikverket sensor.""" def __init__(self, weather_api, name, sensor_type, sensor_station): diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index cd9e57c50d7..a82adac6160 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -3,10 +3,10 @@ from __future__ import annotations from transmissionrpc.torrent import Torrent +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME, DATA_RATE_MEGABYTES_PER_SECOND, STATE_IDLE from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from . import TransmissionClient from .const import ( @@ -38,7 +38,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(dev, True) -class TransmissionSensor(Entity): +class TransmissionSensor(SensorEntity): """A base class for all Transmission sensors.""" def __init__(self, tm_client, client_name, sensor_name, sub_type=None): diff --git a/homeassistant/components/transport_nsw/sensor.py b/homeassistant/components/transport_nsw/sensor.py index f6d4e40e4e8..be76999ec3f 100644 --- a/homeassistant/components/transport_nsw/sensor.py +++ b/homeassistant/components/transport_nsw/sensor.py @@ -4,7 +4,7 @@ from datetime import timedelta from TransportNSW import TransportNSW import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_MODE, @@ -13,7 +13,6 @@ from homeassistant.const import ( TIME_MINUTES, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity ATTR_STOP_ID = "stop_id" ATTR_ROUTE = "route" @@ -65,7 +64,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([TransportNSWSensor(data, stop_id, name)], True) -class TransportNSWSensor(Entity): +class TransportNSWSensor(SensorEntity): """Implementation of an Transport NSW sensor.""" def __init__(self, data, stop_id, name): diff --git a/homeassistant/components/travisci/sensor.py b/homeassistant/components/travisci/sensor.py index 6464667bffe..94a6ba3a48f 100644 --- a/homeassistant/components/travisci/sensor.py +++ b/homeassistant/components/travisci/sensor.py @@ -6,7 +6,7 @@ from travispy import TravisPy from travispy.errors import TravisError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -15,7 +15,6 @@ from homeassistant.const import ( TIME_SECONDS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -94,7 +93,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class TravisCISensor(Entity): +class TravisCISensor(SensorEntity): """Representation of a Travis CI sensor.""" def __init__(self, data, repo_name, user, branch, sensor_type): diff --git a/homeassistant/components/twentemilieu/sensor.py b/homeassistant/components/twentemilieu/sensor.py index 34da746cf06..ad552a4b341 100644 --- a/homeassistant/components/twentemilieu/sensor.py +++ b/homeassistant/components/twentemilieu/sensor.py @@ -12,6 +12,7 @@ from twentemilieu import ( TwenteMilieuConnectionError, ) +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ID from homeassistant.core import HomeAssistant, callback @@ -71,7 +72,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class TwenteMilieuSensor(Entity): +class TwenteMilieuSensor(SensorEntity): """Defines a Twente Milieu sensor.""" def __init__( diff --git a/homeassistant/components/twitch/sensor.py b/homeassistant/components/twitch/sensor.py index 0e5abb58747..cfabcf1045f 100644 --- a/homeassistant/components/twitch/sensor.py +++ b/homeassistant/components/twitch/sensor.py @@ -5,10 +5,9 @@ from requests.exceptions import HTTPError from twitch import TwitchClient import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_CLIENT_ID, CONF_TOKEN import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -56,7 +55,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([TwitchSensor(channel_id, client) for channel_id in channel_ids], True) -class TwitchSensor(Entity): +class TwitchSensor(SensorEntity): """Representation of an Twitch channel.""" def __init__(self, channel, client): diff --git a/homeassistant/components/uk_transport/sensor.py b/homeassistant/components/uk_transport/sensor.py index f5e4b4373c0..f5cb21edcf7 100644 --- a/homeassistant/components/uk_transport/sensor.py +++ b/homeassistant/components/uk_transport/sensor.py @@ -6,10 +6,9 @@ import re import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_MODE, HTTP_OK, TIME_MINUTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.util.dt as dt_util @@ -83,7 +82,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class UkTransportSensor(Entity): +class UkTransportSensor(SensorEntity): """ Sensor that reads the UK transport web API. diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index 77e8f5afbe4..755d95a061b 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -6,7 +6,7 @@ Support for uptime sensors of network clients. from datetime import datetime, timedelta -from homeassistant.components.sensor import DEVICE_CLASS_TIMESTAMP, DOMAIN +from homeassistant.components.sensor import DEVICE_CLASS_TIMESTAMP, DOMAIN, SensorEntity from homeassistant.const import DATA_MEGABYTES from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -79,7 +79,7 @@ def add_uptime_entities(controller, async_add_entities, clients): async_add_entities(sensors) -class UniFiBandwidthSensor(UniFiClient): +class UniFiBandwidthSensor(UniFiClient, SensorEntity): """UniFi bandwidth sensor base class.""" DOMAIN = DOMAIN @@ -126,7 +126,7 @@ class UniFiTxBandwidthSensor(UniFiBandwidthSensor): return self.client.tx_bytes / 1000000 -class UniFiUpTimeSensor(UniFiClient): +class UniFiUpTimeSensor(UniFiClient, SensorEntity): """UniFi uptime sensor.""" DOMAIN = DOMAIN diff --git a/homeassistant/components/upnp/sensor.py b/homeassistant/components/upnp/sensor.py index 76bb1d023b7..0e95b6106a3 100644 --- a/homeassistant/components/upnp/sensor.py +++ b/homeassistant/components/upnp/sensor.py @@ -4,6 +4,7 @@ from __future__ import annotations from datetime import timedelta from typing import Any, Mapping +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import DATA_BYTES, DATA_RATE_KIBIBYTES_PER_SECOND from homeassistant.helpers import device_registry as dr @@ -117,7 +118,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class UpnpSensor(CoordinatorEntity): +class UpnpSensor(CoordinatorEntity, SensorEntity): """Base class for UPnP/IGD sensors.""" def __init__( diff --git a/homeassistant/components/uptime/sensor.py b/homeassistant/components/uptime/sensor.py index 8363d2da2cb..7e79c2fbb5e 100644 --- a/homeassistant/components/uptime/sensor.py +++ b/homeassistant/components/uptime/sensor.py @@ -2,10 +2,13 @@ import voluptuous as vol -from homeassistant.components.sensor import DEVICE_CLASS_TIMESTAMP, PLATFORM_SCHEMA +from homeassistant.components.sensor import ( + DEVICE_CLASS_TIMESTAMP, + PLATFORM_SCHEMA, + SensorEntity, +) from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util DEFAULT_NAME = "Uptime" @@ -30,7 +33,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([UptimeSensor(name)], True) -class UptimeSensor(Entity): +class UptimeSensor(SensorEntity): """Representation of an uptime sensor.""" def __init__(self, name): diff --git a/homeassistant/components/uscis/sensor.py b/homeassistant/components/uscis/sensor.py index 5f26a0ff82e..bd261aba4fb 100644 --- a/homeassistant/components/uscis/sensor.py +++ b/homeassistant/components/uscis/sensor.py @@ -5,10 +5,9 @@ import logging import uscisstatus import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -33,7 +32,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.error("Setup USCIS Sensor Fail check if your Case ID is Valid") -class UscisSensor(Entity): +class UscisSensor(SensorEntity): """USCIS Sensor will check case status on daily basis.""" MIN_TIME_BETWEEN_UPDATES = timedelta(hours=24) diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index 09f788806f6..d28819a38cb 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -5,6 +5,7 @@ import logging import voluptuous as vol +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, @@ -102,7 +103,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class UtilityMeterSensor(RestoreEntity): +class UtilityMeterSensor(RestoreEntity, SensorEntity): """Representation of an utility meter sensor.""" def __init__( diff --git a/homeassistant/components/vallox/sensor.py b/homeassistant/components/vallox/sensor.py index 6f6755a05e7..b4269ac4451 100644 --- a/homeassistant/components/vallox/sensor.py +++ b/homeassistant/components/vallox/sensor.py @@ -3,6 +3,7 @@ from datetime import datetime, timedelta import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, @@ -12,7 +13,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from . import DOMAIN, METRIC_KEY_MODE, SIGNAL_VALLOX_STATE_UPDATE @@ -96,7 +96,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors, update_before_add=False) -class ValloxSensor(Entity): +class ValloxSensor(SensorEntity): """Representation of a Vallox sensor.""" def __init__( diff --git a/homeassistant/components/vasttrafik/sensor.py b/homeassistant/components/vasttrafik/sensor.py index d1a461d94fa..31c5da097ff 100644 --- a/homeassistant/components/vasttrafik/sensor.py +++ b/homeassistant/components/vasttrafik/sensor.py @@ -5,10 +5,9 @@ import logging import vasttrafik import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_DELAY, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.dt import now @@ -71,7 +70,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class VasttrafikDepartureSensor(Entity): +class VasttrafikDepartureSensor(SensorEntity): """Implementation of a Vasttrafik Departure Sensor.""" def __init__(self, planner, name, departure, heading, lines, delay): diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py index 095108b4401..9d9b68dd4eb 100644 --- a/homeassistant/components/velbus/sensor.py +++ b/homeassistant/components/velbus/sensor.py @@ -1,4 +1,5 @@ """Support for Velbus sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_POWER, ENERGY_KILO_WATT_HOUR from . import VelbusEntity @@ -18,7 +19,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class VelbusSensor(VelbusEntity): +class VelbusSensor(VelbusEntity, SensorEntity): """Representation of a sensor.""" def __init__(self, module, channel, counter=False): diff --git a/homeassistant/components/vera/sensor.py b/homeassistant/components/vera/sensor.py index d52d49c2546..516801b57c6 100644 --- a/homeassistant/components/vera/sensor.py +++ b/homeassistant/components/vera/sensor.py @@ -6,7 +6,11 @@ from typing import Callable, cast import pyvera as veraApi -from homeassistant.components.sensor import DOMAIN as PLATFORM_DOMAIN, ENTITY_ID_FORMAT +from homeassistant.components.sensor import ( + DOMAIN as PLATFORM_DOMAIN, + ENTITY_ID_FORMAT, + SensorEntity, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import LIGHT_LUX, PERCENTAGE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import HomeAssistant @@ -35,7 +39,7 @@ async def async_setup_entry( ) -class VeraSensor(VeraDevice[veraApi.VeraSensor], Entity): +class VeraSensor(VeraDevice[veraApi.VeraSensor], SensorEntity): """Representation of a Vera Sensor.""" def __init__( diff --git a/homeassistant/components/verisure/sensor.py b/homeassistant/components/verisure/sensor.py index f7f2212149b..93e1793da8d 100644 --- a/homeassistant/components/verisure/sensor.py +++ b/homeassistant/components/verisure/sensor.py @@ -6,6 +6,7 @@ from typing import Any, Callable, Iterable from homeassistant.components.sensor import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + SensorEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE, TEMP_CELSIUS @@ -45,7 +46,7 @@ async def async_setup_entry( async_add_entities(sensors) -class VerisureThermometer(CoordinatorEntity, Entity): +class VerisureThermometer(CoordinatorEntity, SensorEntity): """Representation of a Verisure thermometer.""" coordinator: VerisureDataUpdateCoordinator @@ -109,7 +110,7 @@ class VerisureThermometer(CoordinatorEntity, Entity): return TEMP_CELSIUS -class VerisureHygrometer(CoordinatorEntity, Entity): +class VerisureHygrometer(CoordinatorEntity, SensorEntity): """Representation of a Verisure hygrometer.""" coordinator: VerisureDataUpdateCoordinator @@ -173,7 +174,7 @@ class VerisureHygrometer(CoordinatorEntity, Entity): return PERCENTAGE -class VerisureMouseDetection(CoordinatorEntity, Entity): +class VerisureMouseDetection(CoordinatorEntity, SensorEntity): """Representation of a Verisure mouse detector.""" coordinator: VerisureDataUpdateCoordinator diff --git a/homeassistant/components/versasense/sensor.py b/homeassistant/components/versasense/sensor.py index e598093cd37..d29032af399 100644 --- a/homeassistant/components/versasense/sensor.py +++ b/homeassistant/components/versasense/sensor.py @@ -1,7 +1,7 @@ """Support for VersaSense sensor peripheral.""" import logging -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from . import DOMAIN from .const import ( @@ -40,7 +40,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensor_list) -class VSensor(Entity): +class VSensor(SensorEntity): """Representation of a Sensor.""" def __init__(self, peripheral, parent_name, unit, measurement, consumer): diff --git a/homeassistant/components/version/sensor.py b/homeassistant/components/version/sensor.py index 645cde63459..3e5d235b5d7 100644 --- a/homeassistant/components/version/sensor.py +++ b/homeassistant/components/version/sensor.py @@ -10,11 +10,10 @@ from pyhaversion import ( ) import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_SOURCE from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle ALL_IMAGES = [ @@ -94,7 +93,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([VersionSensor(haversion, name)], True) -class VersionSensor(Entity): +class VersionSensor(SensorEntity): """Representation of a Home Assistant version sensor.""" def __init__(self, haversion, name): diff --git a/homeassistant/components/viaggiatreno/sensor.py b/homeassistant/components/viaggiatreno/sensor.py index e886b9d9728..10821859f9a 100644 --- a/homeassistant/components/viaggiatreno/sensor.py +++ b/homeassistant/components/viaggiatreno/sensor.py @@ -6,10 +6,9 @@ import aiohttp import async_timeout import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, HTTP_OK, TIME_MINUTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -82,7 +81,7 @@ async def async_http_request(hass, uri): _LOGGER.error("Received non-JSON data from ViaggiaTreno API endpoint") -class ViaggiaTrenoSensor(Entity): +class ViaggiaTrenoSensor(SensorEntity): """Implementation of a ViaggiaTreno sensor.""" def __init__(self, train_id, station_id, name): diff --git a/homeassistant/components/vicare/sensor.py b/homeassistant/components/vicare/sensor.py index a14e00923c2..79e7391ca70 100644 --- a/homeassistant/components/vicare/sensor.py +++ b/homeassistant/components/vicare/sensor.py @@ -3,6 +3,7 @@ import logging import requests +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONF_DEVICE_CLASS, CONF_ICON, @@ -14,7 +15,6 @@ from homeassistant.const import ( TEMP_CELSIUS, TIME_HOURS, ) -from homeassistant.helpers.entity import Entity from . import ( DOMAIN as VICARE_DOMAIN, @@ -269,7 +269,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class ViCareSensor(Entity): +class ViCareSensor(SensorEntity): """Representation of a ViCare sensor.""" def __init__(self, name, api, sensor_type): diff --git a/homeassistant/components/vilfo/sensor.py b/homeassistant/components/vilfo/sensor.py index 80a14354913..90527c60458 100644 --- a/homeassistant/components/vilfo/sensor.py +++ b/homeassistant/components/vilfo/sensor.py @@ -1,6 +1,6 @@ """Support for Vilfo Router sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ICON -from homeassistant.helpers.entity import Entity from .const import ( ATTR_API_DATA_FIELD, @@ -27,7 +27,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, True) -class VilfoRouterSensor(Entity): +class VilfoRouterSensor(SensorEntity): """Define a Vilfo Router Sensor.""" def __init__(self, sensor_type, api): diff --git a/homeassistant/components/volkszaehler/sensor.py b/homeassistant/components/volkszaehler/sensor.py index a418780c165..61fcf1d2969 100644 --- a/homeassistant/components/volkszaehler/sensor.py +++ b/homeassistant/components/volkszaehler/sensor.py @@ -6,7 +6,7 @@ from volkszaehler import Volkszaehler from volkszaehler.exceptions import VolkszaehlerApiConnectionError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_MONITORED_CONDITIONS, @@ -18,7 +18,6 @@ from homeassistant.const import ( from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -77,7 +76,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(dev, True) -class VolkszaehlerSensor(Entity): +class VolkszaehlerSensor(SensorEntity): """Implementation of a Volkszaehler sensor.""" def __init__(self, vz_api, name, sensor_type): diff --git a/homeassistant/components/volvooncall/sensor.py b/homeassistant/components/volvooncall/sensor.py index 0915408860d..ad6571576b4 100644 --- a/homeassistant/components/volvooncall/sensor.py +++ b/homeassistant/components/volvooncall/sensor.py @@ -1,4 +1,6 @@ """Support for Volvo On Call sensors.""" +from homeassistant.components.sensor import SensorEntity + from . import DATA_KEY, VolvoEntity @@ -9,7 +11,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([VolvoSensor(hass.data[DATA_KEY], *discovery_info)]) -class VolvoSensor(VolvoEntity): +class VolvoSensor(VolvoEntity, SensorEntity): """Representation of a Volvo sensor.""" @property diff --git a/homeassistant/components/vultr/sensor.py b/homeassistant/components/vultr/sensor.py index 0bcdcf9d4c1..5e6815944d7 100644 --- a/homeassistant/components/vultr/sensor.py +++ b/homeassistant/components/vultr/sensor.py @@ -3,10 +3,9 @@ import logging import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_MONITORED_CONDITIONS, CONF_NAME, DATA_GIGABYTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from . import ( ATTR_CURRENT_BANDWIDTH_USED, @@ -58,7 +57,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class VultrSensor(Entity): +class VultrSensor(SensorEntity): """Representation of a Vultr subscription sensor.""" def __init__(self, vultr, subscription, condition, name): From 0c086b5067858155e935e5486dfd149d9d1e9e20 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 19:50:29 +0100 Subject: [PATCH 552/831] Migrate integrations w-z to extend SensorEntity (#48217) --- homeassistant/components/waqi/sensor.py | 4 ++-- homeassistant/components/waterfurnace/sensor.py | 5 ++--- homeassistant/components/waze_travel_time/sensor.py | 5 ++--- homeassistant/components/websocket_api/sensor.py | 4 ++-- homeassistant/components/whois/sensor.py | 5 ++--- homeassistant/components/wiffi/sensor.py | 5 +++-- homeassistant/components/wink/sensor.py | 11 ++++++----- homeassistant/components/wirelesstag/sensor.py | 4 ++-- homeassistant/components/withings/sensor.py | 4 ++-- homeassistant/components/wled/sensor.py | 4 ++-- homeassistant/components/wolflink/sensor.py | 3 ++- homeassistant/components/worldclock/sensor.py | 5 ++--- homeassistant/components/worldtidesinfo/sensor.py | 5 ++--- homeassistant/components/worxlandroid/sensor.py | 5 ++--- homeassistant/components/wsdot/sensor.py | 5 ++--- homeassistant/components/wunderground/sensor.py | 5 ++--- homeassistant/components/xbee/sensor.py | 4 ++-- homeassistant/components/xbox/sensor.py | 3 ++- homeassistant/components/xbox_live/sensor.py | 5 ++--- homeassistant/components/xiaomi_aqara/sensor.py | 5 +++-- homeassistant/components/xiaomi_miio/sensor.py | 9 ++++----- homeassistant/components/xs1/sensor.py | 4 ++-- homeassistant/components/yandex_transport/sensor.py | 5 ++--- homeassistant/components/zabbix/sensor.py | 5 ++--- homeassistant/components/zamg/sensor.py | 4 ++-- homeassistant/components/zestimate/sensor.py | 5 ++--- homeassistant/components/zha/sensor.py | 3 ++- homeassistant/components/zodiac/sensor.py | 4 ++-- homeassistant/components/zoneminder/sensor.py | 9 ++++----- homeassistant/components/zwave/sensor.py | 4 ++-- homeassistant/components/zwave_js/sensor.py | 3 ++- 31 files changed, 72 insertions(+), 79 deletions(-) diff --git a/homeassistant/components/waqi/sensor.py b/homeassistant/components/waqi/sensor.py index ac43da68641..ef01c057a9e 100644 --- a/homeassistant/components/waqi/sensor.py +++ b/homeassistant/components/waqi/sensor.py @@ -7,6 +7,7 @@ import aiohttp import voluptuous as vol from waqiasync import WaqiClient +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_TEMPERATURE, @@ -17,7 +18,6 @@ from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import PLATFORM_SCHEMA -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -93,7 +93,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(dev, True) -class WaqiSensor(Entity): +class WaqiSensor(SensorEntity): """Implementation of a WAQI sensor.""" def __init__(self, client, station): diff --git a/homeassistant/components/waterfurnace/sensor.py b/homeassistant/components/waterfurnace/sensor.py index dfb960fe819..91e455d03d6 100644 --- a/homeassistant/components/waterfurnace/sensor.py +++ b/homeassistant/components/waterfurnace/sensor.py @@ -1,9 +1,8 @@ """Support for Waterfurnace.""" -from homeassistant.components.sensor import ENTITY_ID_FORMAT +from homeassistant.components.sensor import ENTITY_ID_FORMAT, SensorEntity from homeassistant.const import PERCENTAGE, POWER_WATT, TEMP_FAHRENHEIT from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from homeassistant.util import slugify from . import DOMAIN as WF_DOMAIN, UPDATE_TOPIC @@ -61,7 +60,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class WaterFurnaceSensor(Entity): +class WaterFurnaceSensor(SensorEntity): """Implementing the Waterfurnace sensor.""" def __init__(self, client, config): diff --git a/homeassistant/components/waze_travel_time/sensor.py b/homeassistant/components/waze_travel_time/sensor.py index be2cf7ca2da..327a0769b50 100644 --- a/homeassistant/components/waze_travel_time/sensor.py +++ b/homeassistant/components/waze_travel_time/sensor.py @@ -6,7 +6,7 @@ import re import WazeRouteCalculator import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, @@ -20,7 +20,6 @@ from homeassistant.const import ( ) from homeassistant.helpers import location import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -126,7 +125,7 @@ def _get_location_from_attributes(state): return "{},{}".format(attr.get(ATTR_LATITUDE), attr.get(ATTR_LONGITUDE)) -class WazeTravelTime(Entity): +class WazeTravelTime(SensorEntity): """Representation of a Waze travel time sensor.""" def __init__(self, name, origin, destination, waze_data): diff --git a/homeassistant/components/websocket_api/sensor.py b/homeassistant/components/websocket_api/sensor.py index c026978634f..dfcdc57842e 100644 --- a/homeassistant/components/websocket_api/sensor.py +++ b/homeassistant/components/websocket_api/sensor.py @@ -1,7 +1,7 @@ """Entity to track connections to websocket API.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from .const import ( DATA_CONNECTIONS, @@ -19,7 +19,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([entity]) -class APICount(Entity): +class APICount(SensorEntity): """Entity to represent how many people are connected to the stream API.""" def __init__(self): diff --git a/homeassistant/components/whois/sensor.py b/homeassistant/components/whois/sensor.py index 72c992456a7..6d97037a4ee 100644 --- a/homeassistant/components/whois/sensor.py +++ b/homeassistant/components/whois/sensor.py @@ -5,10 +5,9 @@ import logging import voluptuous as vol import whois -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_DOMAIN, CONF_NAME, TIME_DAYS import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -47,7 +46,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return -class WhoisSensor(Entity): +class WhoisSensor(SensorEntity): """Implementation of a WHOIS sensor.""" def __init__(self, name, domain): diff --git a/homeassistant/components/wiffi/sensor.py b/homeassistant/components/wiffi/sensor.py index f207e3be3ac..800a420f8f0 100644 --- a/homeassistant/components/wiffi/sensor.py +++ b/homeassistant/components/wiffi/sensor.py @@ -5,6 +5,7 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, + SensorEntity, ) from homeassistant.const import DEGREE, PRESSURE_MBAR, TEMP_CELSIUS from homeassistant.core import callback @@ -58,7 +59,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_dispatcher_connect(hass, CREATE_ENTITY_SIGNAL, _create_entity) -class NumberEntity(WiffiEntity): +class NumberEntity(WiffiEntity, SensorEntity): """Entity for wiffi metrics which have a number value.""" def __init__(self, device, metric, options): @@ -100,7 +101,7 @@ class NumberEntity(WiffiEntity): self.async_write_ha_state() -class StringEntity(WiffiEntity): +class StringEntity(WiffiEntity, SensorEntity): """Entity for wiffi metrics which have a string value.""" def __init__(self, device, metric, options): diff --git a/homeassistant/components/wink/sensor.py b/homeassistant/components/wink/sensor.py index d2de4c43945..88f9588750e 100644 --- a/homeassistant/components/wink/sensor.py +++ b/homeassistant/components/wink/sensor.py @@ -3,6 +3,7 @@ import logging import pywink +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEGREE, TEMP_CELSIUS from . import DOMAIN, WinkDevice @@ -19,29 +20,29 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _id = sensor.object_id() + sensor.name() if _id not in hass.data[DOMAIN]["unique_ids"]: if sensor.capability() in SENSOR_TYPES: - add_entities([WinkSensorDevice(sensor, hass)]) + add_entities([WinkSensorEntity(sensor, hass)]) for eggtray in pywink.get_eggtrays(): _id = eggtray.object_id() + eggtray.name() if _id not in hass.data[DOMAIN]["unique_ids"]: - add_entities([WinkSensorDevice(eggtray, hass)]) + add_entities([WinkSensorEntity(eggtray, hass)]) for tank in pywink.get_propane_tanks(): _id = tank.object_id() + tank.name() if _id not in hass.data[DOMAIN]["unique_ids"]: - add_entities([WinkSensorDevice(tank, hass)]) + add_entities([WinkSensorEntity(tank, hass)]) for piggy_bank in pywink.get_piggy_banks(): _id = piggy_bank.object_id() + piggy_bank.name() if _id not in hass.data[DOMAIN]["unique_ids"]: try: if piggy_bank.capability() in SENSOR_TYPES: - add_entities([WinkSensorDevice(piggy_bank, hass)]) + add_entities([WinkSensorEntity(piggy_bank, hass)]) except AttributeError: _LOGGER.info("Device is not a sensor") -class WinkSensorDevice(WinkDevice): +class WinkSensorEntity(WinkDevice, SensorEntity): """Representation of a Wink sensor.""" def __init__(self, wink, hass): diff --git a/homeassistant/components/wirelesstag/sensor.py b/homeassistant/components/wirelesstag/sensor.py index 2a845249028..cc0ce0cb888 100644 --- a/homeassistant/components/wirelesstag/sensor.py +++ b/homeassistant/components/wirelesstag/sensor.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_MONITORED_CONDITIONS from homeassistant.core import callback import homeassistant.helpers.config_validation as cv @@ -44,7 +44,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class WirelessTagSensor(WirelessTagBaseSensor): +class WirelessTagSensor(WirelessTagBaseSensor, SensorEntity): """Representation of a Sensor.""" def __init__(self, api, tag, sensor_type, config): diff --git a/homeassistant/components/withings/sensor.py b/homeassistant/components/withings/sensor.py index a7a7947dec5..89bb81eb607 100644 --- a/homeassistant/components/withings/sensor.py +++ b/homeassistant/components/withings/sensor.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Callable -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity @@ -28,7 +28,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class WithingsHealthSensor(BaseWithingsSensor): +class WithingsHealthSensor(BaseWithingsSensor, SensorEntity): """Implementation of a Withings sensor.""" @property diff --git a/homeassistant/components/wled/sensor.py b/homeassistant/components/wled/sensor.py index 4bfca885613..7e91f81dea0 100644 --- a/homeassistant/components/wled/sensor.py +++ b/homeassistant/components/wled/sensor.py @@ -4,7 +4,7 @@ from __future__ import annotations from datetime import timedelta from typing import Any, Callable -from homeassistant.components.sensor import DEVICE_CLASS_CURRENT +from homeassistant.components.sensor import DEVICE_CLASS_CURRENT, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DATA_BYTES, @@ -42,7 +42,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class WLEDSensor(WLEDDeviceEntity): +class WLEDSensor(WLEDDeviceEntity, SensorEntity): """Defines a WLED sensor.""" def __init__( diff --git a/homeassistant/components/wolflink/sensor.py b/homeassistant/components/wolflink/sensor.py index 9ea7f9d1163..f243160ff59 100644 --- a/homeassistant/components/wolflink/sensor.py +++ b/homeassistant/components/wolflink/sensor.py @@ -9,6 +9,7 @@ from wolf_smartset.models import ( Temperature, ) +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, @@ -46,7 +47,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class WolfLinkSensor(CoordinatorEntity): +class WolfLinkSensor(CoordinatorEntity, SensorEntity): """Base class for all Wolf entities.""" def __init__(self, coordinator, wolf_object: Parameter, device_id): diff --git a/homeassistant/components/worldclock/sensor.py b/homeassistant/components/worldclock/sensor.py index e02dc3a0d5c..de5b3991e3f 100644 --- a/homeassistant/components/worldclock/sensor.py +++ b/homeassistant/components/worldclock/sensor.py @@ -1,10 +1,9 @@ """Support for showing the time in a different time zone.""" import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_TIME_ZONE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util CONF_TIME_FORMAT = "time_format" @@ -39,7 +38,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class WorldClockSensor(Entity): +class WorldClockSensor(SensorEntity): """Representation of a World clock sensor.""" def __init__(self, time_zone, name, time_format): diff --git a/homeassistant/components/worldtidesinfo/sensor.py b/homeassistant/components/worldtidesinfo/sensor.py index 43c9446b6ce..0fa65957e40 100644 --- a/homeassistant/components/worldtidesinfo/sensor.py +++ b/homeassistant/components/worldtidesinfo/sensor.py @@ -6,7 +6,7 @@ import time import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -15,7 +15,6 @@ from homeassistant.const import ( CONF_NAME, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -55,7 +54,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([tides]) -class WorldTidesInfoSensor(Entity): +class WorldTidesInfoSensor(SensorEntity): """Representation of a WorldTidesInfo sensor.""" def __init__(self, name, lat, lon, key): diff --git a/homeassistant/components/worxlandroid/sensor.py b/homeassistant/components/worxlandroid/sensor.py index e4fe33f62f1..9be3afabc9f 100644 --- a/homeassistant/components/worxlandroid/sensor.py +++ b/homeassistant/components/worxlandroid/sensor.py @@ -6,11 +6,10 @@ import aiohttp import async_timeout import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_HOST, CONF_PIN, CONF_TIMEOUT, PERCENTAGE from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -50,7 +49,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([WorxLandroidSensor(typ, config)]) -class WorxLandroidSensor(Entity): +class WorxLandroidSensor(SensorEntity): """Implementation of a Worx Landroid sensor.""" def __init__(self, sensor, config): diff --git a/homeassistant/components/wsdot/sensor.py b/homeassistant/components/wsdot/sensor.py index 34ad5a37ec8..9e4d957d028 100644 --- a/homeassistant/components/wsdot/sensor.py +++ b/homeassistant/components/wsdot/sensor.py @@ -6,7 +6,7 @@ import re import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_NAME, @@ -17,7 +17,6 @@ from homeassistant.const import ( TIME_MINUTES, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -65,7 +64,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class WashingtonStateTransportSensor(Entity): +class WashingtonStateTransportSensor(SensorEntity): """ Sensor that reads the WSDOT web API. diff --git a/homeassistant/components/wunderground/sensor.py b/homeassistant/components/wunderground/sensor.py index 38d6bba0b1f..358e305dc47 100644 --- a/homeassistant/components/wunderground/sensor.py +++ b/homeassistant/components/wunderground/sensor.py @@ -12,7 +12,7 @@ import async_timeout import voluptuous as vol from homeassistant.components import sensor -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -36,7 +36,6 @@ from homeassistant.const import ( from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.util import Throttle @@ -1117,7 +1116,7 @@ async def async_setup_platform( async_add_entities(sensors, True) -class WUndergroundSensor(Entity): +class WUndergroundSensor(SensorEntity): """Implementing the WUnderground sensor.""" def __init__(self, hass: HomeAssistantType, rest, condition, unique_id_base: str): diff --git a/homeassistant/components/xbee/sensor.py b/homeassistant/components/xbee/sensor.py index 4d9f9ca518b..78cfe964277 100644 --- a/homeassistant/components/xbee/sensor.py +++ b/homeassistant/components/xbee/sensor.py @@ -5,8 +5,8 @@ import logging import voluptuous as vol from xbee_helper.exceptions import ZigBeeException, ZigBeeTxFailure +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_TYPE, TEMP_CELSIUS -from homeassistant.helpers.entity import Entity from . import DOMAIN, PLATFORM_SCHEMA, XBeeAnalogIn, XBeeAnalogInConfig, XBeeConfig @@ -43,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([sensor_class(config_class(config), zigbee_device)], True) -class XBeeTemperatureSensor(Entity): +class XBeeTemperatureSensor(SensorEntity): """Representation of XBee Pro temperature sensor.""" def __init__(self, config, device): diff --git a/homeassistant/components/xbox/sensor.py b/homeassistant/components/xbox/sensor.py index 88bf112b728..ac19a4be193 100644 --- a/homeassistant/components/xbox/sensor.py +++ b/homeassistant/components/xbox/sensor.py @@ -3,6 +3,7 @@ from __future__ import annotations from functools import partial +from homeassistant.components.sensor import SensorEntity from homeassistant.core import callback from homeassistant.helpers.entity_registry import ( async_get_registry as async_get_entity_registry, @@ -29,7 +30,7 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry, async_add_ent update_friends() -class XboxSensorEntity(XboxBaseSensorEntity): +class XboxSensorEntity(XboxBaseSensorEntity, SensorEntity): """Representation of a Xbox presence state.""" @property diff --git a/homeassistant/components/xbox_live/sensor.py b/homeassistant/components/xbox_live/sensor.py index 780051b2d87..2717bc1ad62 100644 --- a/homeassistant/components/xbox_live/sensor.py +++ b/homeassistant/components/xbox_live/sensor.py @@ -5,11 +5,10 @@ import logging import voluptuous as vol from xboxapi import Client -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_API_KEY, CONF_SCAN_INTERVAL from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval _LOGGER = logging.getLogger(__name__) @@ -73,7 +72,7 @@ def get_user_gamercard(api, xuid): return None -class XboxSensor(Entity): +class XboxSensor(SensorEntity): """A class for the Xbox account.""" def __init__(self, api, xuid, gamercard, interval): diff --git a/homeassistant/components/xiaomi_aqara/sensor.py b/homeassistant/components/xiaomi_aqara/sensor.py index 969980bf7c8..fa3d265f12f 100644 --- a/homeassistant/components/xiaomi_aqara/sensor.py +++ b/homeassistant/components/xiaomi_aqara/sensor.py @@ -1,6 +1,7 @@ """Support for Xiaomi Aqara sensors.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_BATTERY_LEVEL, DEVICE_CLASS_BATTERY, @@ -107,7 +108,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class XiaomiSensor(XiaomiDevice): +class XiaomiSensor(XiaomiDevice, SensorEntity): """Representation of a XiaomiSensor.""" def __init__(self, device, name, data_key, xiaomi_hub, config_entry): @@ -171,7 +172,7 @@ class XiaomiSensor(XiaomiDevice): return True -class XiaomiBatterySensor(XiaomiDevice): +class XiaomiBatterySensor(XiaomiDevice, SensorEntity): """Representation of a XiaomiSensor.""" @property diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index e2b645a10ea..ac9a7ab4543 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -12,7 +12,7 @@ from miio.gateway.gateway import ( ) import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_BATTERY_LEVEL, @@ -30,7 +30,6 @@ from homeassistant.const import ( TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from .const import CONF_DEVICE, CONF_FLOW_TYPE, CONF_GATEWAY, DOMAIN, KEY_COORDINATOR from .device import XiaomiMiioEntity @@ -147,7 +146,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, update_before_add=True) -class XiaomiAirQualityMonitor(XiaomiMiioEntity): +class XiaomiAirQualityMonitor(XiaomiMiioEntity, SensorEntity): """Representation of a Xiaomi Air Quality Monitor.""" def __init__(self, name, device, entry, unique_id): @@ -221,7 +220,7 @@ class XiaomiAirQualityMonitor(XiaomiMiioEntity): _LOGGER.error("Got exception while fetching the state: %s", ex) -class XiaomiGatewaySensor(XiaomiGatewayDevice): +class XiaomiGatewaySensor(XiaomiGatewayDevice, SensorEntity): """Representation of a XiaomiGatewaySensor.""" def __init__(self, coordinator, sub_device, entry, data_key): @@ -252,7 +251,7 @@ class XiaomiGatewaySensor(XiaomiGatewayDevice): return self._sub_device.status[self._data_key] -class XiaomiGatewayIlluminanceSensor(Entity): +class XiaomiGatewayIlluminanceSensor(SensorEntity): """Representation of the gateway device's illuminance sensor.""" def __init__(self, gateway_device, gateway_name, gateway_device_id): diff --git a/homeassistant/components/xs1/sensor.py b/homeassistant/components/xs1/sensor.py index 9e6afa40fa4..f158e7d74b8 100644 --- a/homeassistant/components/xs1/sensor.py +++ b/homeassistant/components/xs1/sensor.py @@ -1,7 +1,7 @@ """Support for XS1 sensors.""" from xs1_api_client.api_constants import ActuatorType -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity @@ -28,7 +28,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensor_entities) -class XS1Sensor(XS1DeviceEntity, Entity): +class XS1Sensor(XS1DeviceEntity, SensorEntity): """Representation of a Sensor.""" @property diff --git a/homeassistant/components/yandex_transport/sensor.py b/homeassistant/components/yandex_transport/sensor.py index 0df073f581d..08e856a721e 100644 --- a/homeassistant/components/yandex_transport/sensor.py +++ b/homeassistant/components/yandex_transport/sensor.py @@ -6,11 +6,10 @@ import logging from aioymaps import YandexMapsRequester import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, DEVICE_CLASS_TIMESTAMP from homeassistant.helpers.aiohttp_client import async_create_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -47,7 +46,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([DiscoverYandexTransport(data, stop_id, routes, name)], True) -class DiscoverYandexTransport(Entity): +class DiscoverYandexTransport(SensorEntity): """Implementation of yandex_transport sensor.""" def __init__(self, requester: YandexMapsRequester, stop_id, routes, name): diff --git a/homeassistant/components/zabbix/sensor.py b/homeassistant/components/zabbix/sensor.py index 536709e5a83..a2644287690 100644 --- a/homeassistant/components/zabbix/sensor.py +++ b/homeassistant/components/zabbix/sensor.py @@ -4,10 +4,9 @@ import logging import voluptuous as vol from homeassistant.components import zabbix -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -79,7 +78,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class ZabbixTriggerCountSensor(Entity): +class ZabbixTriggerCountSensor(SensorEntity): """Get the active trigger count for all Zabbix monitored hosts.""" def __init__(self, zApi, name="Zabbix"): diff --git a/homeassistant/components/zamg/sensor.py b/homeassistant/components/zamg/sensor.py index ef0a476f612..2e2d07cea62 100644 --- a/homeassistant/components/zamg/sensor.py +++ b/homeassistant/components/zamg/sensor.py @@ -11,6 +11,7 @@ import pytz import requests import voluptuous as vol +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( AREA_SQUARE_METERS, ATTR_ATTRIBUTION, @@ -27,7 +28,6 @@ from homeassistant.const import ( __version__, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -132,7 +132,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class ZamgSensor(Entity): +class ZamgSensor(SensorEntity): """Implementation of a ZAMG sensor.""" def __init__(self, probe, variable, name): diff --git a/homeassistant/components/zestimate/sensor.py b/homeassistant/components/zestimate/sensor.py index ed15d42b7e3..0333bb76a20 100644 --- a/homeassistant/components/zestimate/sensor.py +++ b/homeassistant/components/zestimate/sensor.py @@ -6,10 +6,9 @@ import requests import voluptuous as vol import xmltodict -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) _RESOURCE = "http://www.zillow.com/webservice/GetZestimate.htm" @@ -56,7 +55,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class ZestimateDataSensor(Entity): +class ZestimateDataSensor(SensorEntity): """Implementation of a Zestimate sensor.""" def __init__(self, name, params): diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 425a41a1340..41dce816e86 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -15,6 +15,7 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, DOMAIN, + SensorEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -89,7 +90,7 @@ async def async_setup_entry( hass.data[DATA_ZHA][DATA_ZHA_DISPATCHERS].append(unsub) -class Sensor(ZhaEntity): +class Sensor(ZhaEntity, SensorEntity): """Base ZHA sensor.""" SENSOR_ATTR: int | str | None = None diff --git a/homeassistant/components/zodiac/sensor.py b/homeassistant/components/zodiac/sensor.py index b602d7a50c4..4c037a7aa02 100644 --- a/homeassistant/components/zodiac/sensor.py +++ b/homeassistant/components/zodiac/sensor.py @@ -1,5 +1,5 @@ """Support for tracking the zodiac sign.""" -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from homeassistant.util.dt import as_local, utcnow from .const import ( @@ -162,7 +162,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([ZodiacSensor()], True) -class ZodiacSensor(Entity): +class ZodiacSensor(SensorEntity): """Representation of a Zodiac sensor.""" def __init__(self): diff --git a/homeassistant/components/zoneminder/sensor.py b/homeassistant/components/zoneminder/sensor.py index 75531e79e13..701f4b490d3 100644 --- a/homeassistant/components/zoneminder/sensor.py +++ b/homeassistant/components/zoneminder/sensor.py @@ -4,10 +4,9 @@ import logging import voluptuous as vol from zoneminder.monitor import TimePeriod -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from . import DOMAIN as ZONEMINDER_DOMAIN @@ -57,7 +56,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class ZMSensorMonitors(Entity): +class ZMSensorMonitors(SensorEntity): """Get the status of each ZoneMinder monitor.""" def __init__(self, monitor): @@ -91,7 +90,7 @@ class ZMSensorMonitors(Entity): self._is_available = self._monitor.is_available -class ZMSensorEvents(Entity): +class ZMSensorEvents(SensorEntity): """Get the number of events for each monitor.""" def __init__(self, monitor, include_archived, sensor_type): @@ -122,7 +121,7 @@ class ZMSensorEvents(Entity): self._state = self._monitor.get_events(self.time_period, self._include_archived) -class ZMSensorRunState(Entity): +class ZMSensorRunState(SensorEntity): """Get the ZoneMinder run state.""" def __init__(self, client): diff --git a/homeassistant/components/zwave/sensor.py b/homeassistant/components/zwave/sensor.py index aae38382f2e..a3183ba8927 100644 --- a/homeassistant/components/zwave/sensor.py +++ b/homeassistant/components/zwave/sensor.py @@ -1,5 +1,5 @@ """Support for Z-Wave sensors.""" -from homeassistant.components.sensor import DEVICE_CLASS_BATTERY, DOMAIN +from homeassistant.components.sensor import DEVICE_CLASS_BATTERY, DOMAIN, SensorEntity from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -37,7 +37,7 @@ def get_device(node, values, **kwargs): return None -class ZWaveSensor(ZWaveDeviceEntity): +class ZWaveSensor(ZWaveDeviceEntity, SensorEntity): """Representation of a Z-Wave sensor.""" def __init__(self, values): diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index 5f116f0790c..574e1af658c 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -13,6 +13,7 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, DOMAIN as SENSOR_DOMAIN, + SensorEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -67,7 +68,7 @@ async def async_setup_entry( ) -class ZwaveSensorBase(ZWaveBaseEntity): +class ZwaveSensorBase(ZWaveBaseEntity, SensorEntity): """Basic Representation of a Z-Wave sensor.""" def __init__( From 64bc9a81965904040ceb3a3b25b1202212167a72 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 19:54:14 +0100 Subject: [PATCH 553/831] Migrate integrations r-s to extend SensorEntity (#48215) --- homeassistant/components/radarr/sensor.py | 5 ++--- homeassistant/components/rainbird/sensor.py | 4 ++-- homeassistant/components/raincloud/sensor.py | 4 ++-- homeassistant/components/rainforest_eagle/sensor.py | 7 +++---- homeassistant/components/rainmachine/sensor.py | 3 ++- homeassistant/components/random/sensor.py | 5 ++--- homeassistant/components/recollect_waste/sensor.py | 4 ++-- homeassistant/components/reddit/sensor.py | 5 ++--- homeassistant/components/rejseplanen/sensor.py | 5 ++--- homeassistant/components/repetier/sensor.py | 4 ++-- homeassistant/components/rest/sensor.py | 8 ++++++-- homeassistant/components/rflink/sensor.py | 4 ++-- homeassistant/components/rfxtrx/sensor.py | 3 ++- homeassistant/components/ring/sensor.py | 4 ++-- homeassistant/components/ripple/sensor.py | 5 ++--- homeassistant/components/risco/sensor.py | 3 ++- homeassistant/components/rmvtransport/sensor.py | 5 ++--- homeassistant/components/roomba/sensor.py | 3 ++- homeassistant/components/rova/sensor.py | 5 ++--- homeassistant/components/rtorrent/sensor.py | 5 ++--- homeassistant/components/sabnzbd/sensor.py | 4 ++-- homeassistant/components/saj/sensor.py | 5 ++--- homeassistant/components/scrape/sensor.py | 5 ++--- homeassistant/components/screenlogic/sensor.py | 10 +++++++--- homeassistant/components/season/sensor.py | 5 ++--- homeassistant/components/sense/sensor.py | 10 +++++----- homeassistant/components/sensehat/sensor.py | 5 ++--- homeassistant/components/serial/sensor.py | 5 ++--- homeassistant/components/serial_pm/sensor.py | 5 ++--- homeassistant/components/seventeentrack/sensor.py | 7 +++---- homeassistant/components/shelly/sensor.py | 7 ++++--- homeassistant/components/shodan/sensor.py | 5 ++--- homeassistant/components/sht31/sensor.py | 5 ++--- homeassistant/components/sigfox/sensor.py | 5 ++--- homeassistant/components/simplisafe/sensor.py | 3 ++- homeassistant/components/simulated/sensor.py | 5 ++--- homeassistant/components/skybeacon/sensor.py | 9 ++++----- homeassistant/components/skybell/sensor.py | 4 ++-- homeassistant/components/sleepiq/sensor.py | 4 +++- homeassistant/components/sma/sensor.py | 5 ++--- homeassistant/components/smappee/sensor.py | 4 ++-- homeassistant/components/smart_meter_texas/sensor.py | 3 ++- homeassistant/components/smartthings/sensor.py | 5 +++-- homeassistant/components/smarttub/sensor.py | 4 +++- homeassistant/components/smarty/sensor.py | 4 ++-- homeassistant/components/sms/sensor.py | 4 ++-- homeassistant/components/snmp/sensor.py | 5 ++--- homeassistant/components/sochain/sensor.py | 5 ++--- homeassistant/components/socialblade/sensor.py | 5 ++--- homeassistant/components/solaredge/sensor.py | 4 ++-- homeassistant/components/solaredge_local/sensor.py | 5 ++--- homeassistant/components/solarlog/sensor.py | 4 ++-- homeassistant/components/solax/sensor.py | 5 ++--- homeassistant/components/soma/sensor.py | 4 ++-- homeassistant/components/somfy/sensor.py | 3 ++- homeassistant/components/sonarr/sensor.py | 3 ++- homeassistant/components/speedtestdotnet/sensor.py | 3 ++- homeassistant/components/spotcrime/sensor.py | 5 ++--- homeassistant/components/sql/sensor.py | 5 ++--- homeassistant/components/srp_energy/sensor.py | 4 ++-- homeassistant/components/starline/sensor.py | 5 ++--- homeassistant/components/starlingbank/sensor.py | 5 ++--- homeassistant/components/startca/sensor.py | 5 ++--- homeassistant/components/statistics/sensor.py | 5 ++--- homeassistant/components/steam_online/sensor.py | 5 ++--- homeassistant/components/streamlabswater/sensor.py | 4 ++-- homeassistant/components/subaru/sensor.py | 4 ++-- homeassistant/components/suez_water/sensor.py | 5 ++--- homeassistant/components/supervisord/sensor.py | 5 ++--- homeassistant/components/surepetcare/sensor.py | 4 ++-- .../components/swiss_hydrological_data/sensor.py | 5 ++--- .../components/swiss_public_transport/sensor.py | 5 ++--- homeassistant/components/syncthru/sensor.py | 5 ++--- homeassistant/components/synology_dsm/sensor.py | 7 ++++--- homeassistant/components/systemmonitor/sensor.py | 5 ++--- 75 files changed, 173 insertions(+), 189 deletions(-) diff --git a/homeassistant/components/radarr/sensor.py b/homeassistant/components/radarr/sensor.py index 9baed6c41c7..542ff285261 100644 --- a/homeassistant/components/radarr/sensor.py +++ b/homeassistant/components/radarr/sensor.py @@ -7,7 +7,7 @@ from pytz import timezone import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_API_KEY, CONF_HOST, @@ -26,7 +26,6 @@ from homeassistant.const import ( HTTP_OK, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -95,7 +94,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([RadarrSensor(hass, config, sensor) for sensor in conditions], True) -class RadarrSensor(Entity): +class RadarrSensor(SensorEntity): """Implementation of the Radarr sensor.""" def __init__(self, hass, conf, sensor_type): diff --git a/homeassistant/components/rainbird/sensor.py b/homeassistant/components/rainbird/sensor.py index 501566de682..2c542dc12a9 100644 --- a/homeassistant/components/rainbird/sensor.py +++ b/homeassistant/components/rainbird/sensor.py @@ -3,7 +3,7 @@ import logging from pyrainbird import RainbirdController -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from . import ( DATA_RAINBIRD, @@ -28,7 +28,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class RainBirdSensor(Entity): +class RainBirdSensor(SensorEntity): """A sensor implementation for Rain Bird device.""" def __init__(self, controller: RainbirdController, sensor_type): diff --git a/homeassistant/components/raincloud/sensor.py b/homeassistant/components/raincloud/sensor.py index b819b51365e..efff3677009 100644 --- a/homeassistant/components/raincloud/sensor.py +++ b/homeassistant/components/raincloud/sensor.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv from homeassistant.helpers.icon import icon_for_battery_level @@ -38,7 +38,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class RainCloudSensor(RainCloudEntity): +class RainCloudSensor(RainCloudEntity, SensorEntity): """A sensor implementation for raincloud device.""" @property diff --git a/homeassistant/components/rainforest_eagle/sensor.py b/homeassistant/components/rainforest_eagle/sensor.py index 99751e63f5b..80475b4c21b 100644 --- a/homeassistant/components/rainforest_eagle/sensor.py +++ b/homeassistant/components/rainforest_eagle/sensor.py @@ -7,14 +7,13 @@ from requests.exceptions import ConnectionError as ConnectError, HTTPError, Time from uEagle import Eagle as LegacyReader import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_IP_ADDRESS, DEVICE_CLASS_POWER, ENERGY_KILO_WATT_HOUR, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle CONF_CLOUD_ID = "cloud_id" @@ -95,7 +94,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class EagleSensor(Entity): +class EagleSensor(SensorEntity): """Implementation of the Rainforest Eagle-200 sensor.""" def __init__(self, eagle_data, sensor_type, name, unit): @@ -160,7 +159,7 @@ class EagleData: return state -class LeagleReader(LegacyReader): +class LeagleReader(LegacyReader, SensorEntity): """Wraps uEagle to make it behave like eagle_reader, offering update().""" def update(self): diff --git a/homeassistant/components/rainmachine/sensor.py b/homeassistant/components/rainmachine/sensor.py index 4533397fb54..20912809cb1 100644 --- a/homeassistant/components/rainmachine/sensor.py +++ b/homeassistant/components/rainmachine/sensor.py @@ -4,6 +4,7 @@ from typing import Callable from regenmaschine.controller import Controller +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import TEMP_CELSIUS, VOLUME_CUBIC_METERS from homeassistant.core import HomeAssistant, callback @@ -108,7 +109,7 @@ async def async_setup_entry( ) -class RainMachineSensor(RainMachineEntity): +class RainMachineSensor(RainMachineEntity, SensorEntity): """Define a general RainMachine sensor.""" def __init__( diff --git a/homeassistant/components/random/sensor.py b/homeassistant/components/random/sensor.py index 7584fe17405..6465b828be1 100644 --- a/homeassistant/components/random/sensor.py +++ b/homeassistant/components/random/sensor.py @@ -3,7 +3,7 @@ from random import randrange import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MAXIMUM, CONF_MINIMUM, @@ -11,7 +11,6 @@ from homeassistant.const import ( CONF_UNIT_OF_MEASUREMENT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity ATTR_MAXIMUM = "maximum" ATTR_MINIMUM = "minimum" @@ -42,7 +41,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([RandomSensor(name, minimum, maximum, unit)], True) -class RandomSensor(Entity): +class RandomSensor(SensorEntity): """Representation of a Random number sensor.""" def __init__(self, name, minimum, maximum, unit_of_measurement): diff --git a/homeassistant/components/recollect_waste/sensor.py b/homeassistant/components/recollect_waste/sensor.py index 000d76b54c7..1c3dabc2c87 100644 --- a/homeassistant/components/recollect_waste/sensor.py +++ b/homeassistant/components/recollect_waste/sensor.py @@ -6,7 +6,7 @@ from typing import Callable from aiorecollect.client import PickupType import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ATTR_ATTRIBUTION, CONF_FRIENDLY_NAME, CONF_NAME from homeassistant.core import HomeAssistant, callback @@ -77,7 +77,7 @@ async def async_setup_entry( async_add_entities([ReCollectWasteSensor(coordinator, entry)]) -class ReCollectWasteSensor(CoordinatorEntity): +class ReCollectWasteSensor(CoordinatorEntity, SensorEntity): """ReCollect Waste Sensor.""" def __init__(self, coordinator: DataUpdateCoordinator, entry: ConfigEntry) -> None: diff --git a/homeassistant/components/reddit/sensor.py b/homeassistant/components/reddit/sensor.py index 153b6636cc4..a88de916009 100644 --- a/homeassistant/components/reddit/sensor.py +++ b/homeassistant/components/reddit/sensor.py @@ -5,7 +5,7 @@ import logging import praw import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ID, CONF_CLIENT_ID, @@ -15,7 +15,6 @@ from homeassistant.const import ( CONF_USERNAME, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -82,7 +81,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class RedditSensor(Entity): +class RedditSensor(SensorEntity): """Representation of a Reddit sensor.""" def __init__(self, reddit, subreddit: str, limit: int, sort_by: str): diff --git a/homeassistant/components/rejseplanen/sensor.py b/homeassistant/components/rejseplanen/sensor.py index ea55d56b8df..685dc548338 100644 --- a/homeassistant/components/rejseplanen/sensor.py +++ b/homeassistant/components/rejseplanen/sensor.py @@ -11,10 +11,9 @@ from operator import itemgetter import rjpl import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, TIME_MINUTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -87,7 +86,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): ) -class RejseplanenTransportSensor(Entity): +class RejseplanenTransportSensor(SensorEntity): """Implementation of Rejseplanen transport sensor.""" def __init__(self, data, stop_id, route, direction, name): diff --git a/homeassistant/components/repetier/sensor.py b/homeassistant/components/repetier/sensor.py index a2b86792aa7..77a3c51e9cf 100644 --- a/homeassistant/components/repetier/sensor.py +++ b/homeassistant/components/repetier/sensor.py @@ -3,10 +3,10 @@ from datetime import datetime import logging import time +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_TIMESTAMP from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from . import REPETIER_API, SENSOR_TYPES, UPDATE_SIGNAL @@ -46,7 +46,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities, True) -class RepetierSensor(Entity): +class RepetierSensor(SensorEntity): """Class to create and populate a Repetier Sensor.""" def __init__(self, api, temp_id, name, printer_id, sensor_type): diff --git a/homeassistant/components/rest/sensor.py b/homeassistant/components/rest/sensor.py index 5ff5e87c3e6..d303f7a57b3 100644 --- a/homeassistant/components/rest/sensor.py +++ b/homeassistant/components/rest/sensor.py @@ -7,7 +7,11 @@ from jsonpath import jsonpath import voluptuous as vol import xmltodict -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, PLATFORM_SCHEMA +from homeassistant.components.sensor import ( + DOMAIN as SENSOR_DOMAIN, + PLATFORM_SCHEMA, + SensorEntity, +) from homeassistant.const import ( CONF_DEVICE_CLASS, CONF_FORCE_UPDATE, @@ -81,7 +85,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class RestSensor(RestEntity): +class RestSensor(RestEntity, SensorEntity): """Implementation of a REST sensor.""" def __init__( diff --git a/homeassistant/components/rflink/sensor.py b/homeassistant/components/rflink/sensor.py index 1a616c2ed90..497c9b8cee6 100644 --- a/homeassistant/components/rflink/sensor.py +++ b/homeassistant/components/rflink/sensor.py @@ -2,7 +2,7 @@ from rflink.parser import PACKET_FIELDS, UNITS import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_DEVICES, @@ -98,7 +98,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= hass.data[DATA_DEVICE_REGISTER][EVENT_KEY_SENSOR] = add_new_device -class RflinkSensor(RflinkDevice): +class RflinkSensor(RflinkDevice, SensorEntity): """Representation of a Rflink sensor.""" def __init__( diff --git a/homeassistant/components/rfxtrx/sensor.py b/homeassistant/components/rfxtrx/sensor.py index c897e164119..72cd9f6bbf6 100644 --- a/homeassistant/components/rfxtrx/sensor.py +++ b/homeassistant/components/rfxtrx/sensor.py @@ -8,6 +8,7 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_TEMPERATURE, + SensorEntity, ) from homeassistant.const import ( CONF_DEVICES, @@ -129,7 +130,7 @@ async def async_setup_entry( connect_auto_add(hass, discovery_info, sensor_update) -class RfxtrxSensor(RfxtrxEntity): +class RfxtrxSensor(RfxtrxEntity, SensorEntity): """Representation of a RFXtrx sensor.""" def __init__(self, device, device_id, data_type, event=None): diff --git a/homeassistant/components/ring/sensor.py b/homeassistant/components/ring/sensor.py index 276d3839438..a20d484d3fe 100644 --- a/homeassistant/components/ring/sensor.py +++ b/homeassistant/components/ring/sensor.py @@ -1,7 +1,7 @@ """This component provides HA sensor support for Ring Door Bell/Chimes.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import PERCENTAGE, SIGNAL_STRENGTH_DECIBELS_MILLIWATT from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from . import DOMAIN @@ -32,7 +32,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -class RingSensor(RingEntityMixin, Entity): +class RingSensor(RingEntityMixin, SensorEntity): """A sensor implementation for Ring device.""" def __init__(self, config_entry_id, device, sensor_type): diff --git a/homeassistant/components/ripple/sensor.py b/homeassistant/components/ripple/sensor.py index 97cf8b4a794..f36e2c58ec8 100644 --- a/homeassistant/components/ripple/sensor.py +++ b/homeassistant/components/ripple/sensor.py @@ -4,10 +4,9 @@ from datetime import timedelta from pyripple import get_balance import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_ADDRESS, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity ATTRIBUTION = "Data provided by ripple.com" @@ -31,7 +30,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([RippleSensor(name, address)], True) -class RippleSensor(Entity): +class RippleSensor(SensorEntity): """Representation of an Ripple.com sensor.""" def __init__(self, name, address): diff --git a/homeassistant/components/risco/sensor.py b/homeassistant/components/risco/sensor.py index 846444e5fbd..b39655949b2 100644 --- a/homeassistant/components/risco/sensor.py +++ b/homeassistant/components/risco/sensor.py @@ -1,5 +1,6 @@ """Sensor for Risco Events.""" from homeassistant.components.binary_sensor import DOMAIN as BS_DOMAIN +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_TIMESTAMP from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -42,7 +43,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -class RiscoSensor(CoordinatorEntity): +class RiscoSensor(CoordinatorEntity, SensorEntity): """Sensor for Risco events.""" def __init__(self, coordinator, category_id, excludes, name, entry_id) -> None: diff --git a/homeassistant/components/rmvtransport/sensor.py b/homeassistant/components/rmvtransport/sensor.py index 555f545d0c7..d85dae53303 100644 --- a/homeassistant/components/rmvtransport/sensor.py +++ b/homeassistant/components/rmvtransport/sensor.py @@ -10,11 +10,10 @@ from RMVtransport.rmvtransport import ( ) import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, CONF_TIMEOUT, TIME_MINUTES from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -104,7 +103,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors) -class RMVDepartureSensor(Entity): +class RMVDepartureSensor(SensorEntity): """Implementation of an RMV departure sensor.""" def __init__( diff --git a/homeassistant/components/roomba/sensor.py b/homeassistant/components/roomba/sensor.py index f2d08f08772..4a99d9f71af 100644 --- a/homeassistant/components/roomba/sensor.py +++ b/homeassistant/components/roomba/sensor.py @@ -1,4 +1,5 @@ """Sensor for checking the battery level of Roomba.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.components.vacuum import STATE_DOCKED from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE from homeassistant.helpers.icon import icon_for_battery_level @@ -16,7 +17,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities([roomba_vac], True) -class RoombaBattery(IRobotEntity): +class RoombaBattery(IRobotEntity, SensorEntity): """Class to hold Roomba Sensor basic info.""" @property diff --git a/homeassistant/components/rova/sensor.py b/homeassistant/components/rova/sensor.py index a2dafae9317..13f8fffb8d1 100644 --- a/homeassistant/components/rova/sensor.py +++ b/homeassistant/components/rova/sensor.py @@ -7,14 +7,13 @@ from requests.exceptions import ConnectTimeout, HTTPError from rova.rova import Rova import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_CONDITIONS, CONF_NAME, DEVICE_CLASS_TIMESTAMP, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle # Config for rova requests. @@ -80,7 +79,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities, True) -class RovaSensor(Entity): +class RovaSensor(SensorEntity): """Representation of a Rova sensor.""" def __init__(self, platform_name, sensor_key, data_service): diff --git a/homeassistant/components/rtorrent/sensor.py b/homeassistant/components/rtorrent/sensor.py index 3976d8985cd..4c02f49d86a 100644 --- a/homeassistant/components/rtorrent/sensor.py +++ b/homeassistant/components/rtorrent/sensor.py @@ -4,7 +4,7 @@ import xmlrpc.client import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_VARIABLES, CONF_NAME, @@ -14,7 +14,6 @@ from homeassistant.const import ( ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -75,7 +74,7 @@ def format_speed(speed): return round(kb_spd, 2 if kb_spd < 0.1 else 1) -class RTorrentSensor(Entity): +class RTorrentSensor(SensorEntity): """Representation of an rtorrent sensor.""" def __init__(self, sensor_type, rtorrent_client, client_name): diff --git a/homeassistant/components/sabnzbd/sensor.py b/homeassistant/components/sabnzbd/sensor.py index 5e437c41b23..c0930f2c114 100644 --- a/homeassistant/components/sabnzbd/sensor.py +++ b/homeassistant/components/sabnzbd/sensor.py @@ -1,6 +1,6 @@ """Support for monitoring an SABnzbd NZB client.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from . import DATA_SABNZBD, SENSOR_TYPES, SIGNAL_SABNZBD_UPDATED @@ -18,7 +18,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class SabnzbdSensor(Entity): +class SabnzbdSensor(SensorEntity): """Representation of an SABnzbd sensor.""" def __init__(self, sensor_type, sabnzbd_api_data, client_name): diff --git a/homeassistant/components/saj/sensor.py b/homeassistant/components/saj/sensor.py index 1e7b3dd5061..ef69513db43 100644 --- a/homeassistant/components/saj/sensor.py +++ b/homeassistant/components/saj/sensor.py @@ -5,7 +5,7 @@ import logging import pysaj import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -26,7 +26,6 @@ from homeassistant.const import ( from homeassistant.core import CALLBACK_TYPE, callback from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_call_later _LOGGER = logging.getLogger(__name__) @@ -160,7 +159,7 @@ def async_track_time_interval_backoff(hass, action) -> CALLBACK_TYPE: return remove_listener -class SAJsensor(Entity): +class SAJsensor(SensorEntity): """Representation of a SAJ sensor.""" def __init__(self, serialnumber, pysaj_sensor, inverter_name=None): diff --git a/homeassistant/components/scrape/sensor.py b/homeassistant/components/scrape/sensor.py index e8b6fcfd2c3..3bf070a7d79 100644 --- a/homeassistant/components/scrape/sensor.py +++ b/homeassistant/components/scrape/sensor.py @@ -6,7 +6,7 @@ from requests.auth import HTTPBasicAuth, HTTPDigestAuth import voluptuous as vol from homeassistant.components.rest.data import RestData -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_AUTHENTICATION, CONF_HEADERS, @@ -22,7 +22,6 @@ from homeassistant.const import ( ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -89,7 +88,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class ScrapeSensor(Entity): +class ScrapeSensor(SensorEntity): """Representation of a web scrape sensor.""" def __init__(self, rest, name, select, attr, index, value_template, unit): diff --git a/homeassistant/components/screenlogic/sensor.py b/homeassistant/components/screenlogic/sensor.py index c9c66a75681..38bde2afd76 100644 --- a/homeassistant/components/screenlogic/sensor.py +++ b/homeassistant/components/screenlogic/sensor.py @@ -3,7 +3,11 @@ import logging from screenlogicpy.const import DEVICE_TYPE -from homeassistant.components.sensor import DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE +from homeassistant.components.sensor import ( + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + SensorEntity, +) from . import ScreenlogicEntity from .const import DOMAIN @@ -34,7 +38,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class ScreenLogicSensor(ScreenlogicEntity): +class ScreenLogicSensor(ScreenlogicEntity, SensorEntity): """Representation of a ScreenLogic sensor entity.""" @property @@ -65,7 +69,7 @@ class ScreenLogicSensor(ScreenlogicEntity): return self.coordinator.data["sensors"][self._data_key] -class ScreenLogicPumpSensor(ScreenlogicEntity): +class ScreenLogicPumpSensor(ScreenlogicEntity, SensorEntity): """Representation of a ScreenLogic pump sensor entity.""" def __init__(self, coordinator, pump, key): diff --git a/homeassistant/components/season/sensor.py b/homeassistant/components/season/sensor.py index e116fb0e861..165920dd8e5 100644 --- a/homeassistant/components/season/sensor.py +++ b/homeassistant/components/season/sensor.py @@ -6,10 +6,9 @@ import ephem import voluptuous as vol from homeassistant import util -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_TYPE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util.dt import utcnow _LOGGER = logging.getLogger(__name__) @@ -109,7 +108,7 @@ def get_season(date, hemisphere, season_tracking_type): return HEMISPHERE_SEASON_SWAP.get(season) -class Season(Entity): +class Season(SensorEntity): """Representation of the current season.""" def __init__(self, hass, hemisphere, season_tracking_type, name): diff --git a/homeassistant/components/sense/sensor.py b/homeassistant/components/sense/sensor.py index 7b6e415d4a3..0af64f3f17d 100644 --- a/homeassistant/components/sense/sensor.py +++ b/homeassistant/components/sense/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring a Sense energy sensor.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, DEVICE_CLASS_POWER, @@ -8,7 +9,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from .const import ( ACTIVE_NAME, @@ -118,7 +118,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(devices) -class SenseActiveSensor(Entity): +class SenseActiveSensor(SensorEntity): """Implementation of a Sense energy sensor.""" def __init__( @@ -207,7 +207,7 @@ class SenseActiveSensor(Entity): self.async_write_ha_state() -class SenseVoltageSensor(Entity): +class SenseVoltageSensor(SensorEntity): """Implementation of a Sense energy voltage sensor.""" def __init__( @@ -287,7 +287,7 @@ class SenseVoltageSensor(Entity): self.async_write_ha_state() -class SenseTrendsSensor(Entity): +class SenseTrendsSensor(SensorEntity): """Implementation of a Sense energy sensor.""" def __init__( @@ -370,7 +370,7 @@ class SenseTrendsSensor(Entity): self.async_on_remove(self._coordinator.async_add_listener(self._async_update)) -class SenseEnergyDevice(Entity): +class SenseEnergyDevice(SensorEntity): """Implementation of a Sense energy device.""" def __init__(self, sense_devices_data, device, sense_monitor_id): diff --git a/homeassistant/components/sensehat/sensor.py b/homeassistant/components/sensehat/sensor.py index 67beb021d89..6ba00baae77 100644 --- a/homeassistant/components/sensehat/sensor.py +++ b/homeassistant/components/sensehat/sensor.py @@ -6,7 +6,7 @@ from pathlib import Path from sense_hat import SenseHat import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_DISPLAY_OPTIONS, CONF_NAME, @@ -14,7 +14,6 @@ from homeassistant.const import ( TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -68,7 +67,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class SenseHatSensor(Entity): +class SenseHatSensor(SensorEntity): """Representation of a Sense HAT sensor.""" def __init__(self, data, sensor_types): diff --git a/homeassistant/components/serial/sensor.py b/homeassistant/components/serial/sensor.py index 02590ccfe8f..1e73ae9ac83 100644 --- a/homeassistant/components/serial/sensor.py +++ b/homeassistant/components/serial/sensor.py @@ -7,11 +7,10 @@ from serial import SerialException import serial_asyncio import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_STOP from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -103,7 +102,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([sensor], True) -class SerialSensor(Entity): +class SerialSensor(SensorEntity): """Representation of a Serial sensor.""" def __init__( diff --git a/homeassistant/components/serial_pm/sensor.py b/homeassistant/components/serial_pm/sensor.py index 2e7604ee97d..b81c60e0a19 100644 --- a/homeassistant/components/serial_pm/sensor.py +++ b/homeassistant/components/serial_pm/sensor.py @@ -4,10 +4,9 @@ import logging from pmsensor import serial_pm as pm import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -56,7 +55,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev) -class ParticulateMatterSensor(Entity): +class ParticulateMatterSensor(SensorEntity): """Representation of an Particulate matter sensor.""" def __init__(self, pmDataCollector, name, pmname): diff --git a/homeassistant/components/seventeentrack/sensor.py b/homeassistant/components/seventeentrack/sensor.py index 223844dc9c7..e856f71b008 100644 --- a/homeassistant/components/seventeentrack/sensor.py +++ b/homeassistant/components/seventeentrack/sensor.py @@ -6,7 +6,7 @@ from py17track import Client as SeventeenTrackClient from py17track.errors import SeventeenTrackError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_FRIENDLY_NAME, @@ -16,7 +16,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.helpers import aiohttp_client, config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_call_later from homeassistant.util import Throttle, slugify @@ -95,7 +94,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= await data.async_update() -class SeventeenTrackSummarySensor(Entity): +class SeventeenTrackSummarySensor(SensorEntity): """Define a summary sensor.""" def __init__(self, data, status, initial_state): @@ -166,7 +165,7 @@ class SeventeenTrackSummarySensor(Entity): self._state = self._data.summary.get(self._status) -class SeventeenTrackPackageSensor(Entity): +class SeventeenTrackPackageSensor(SensorEntity): """Define an individual package sensor.""" def __init__(self, data, package): diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index aac5ec81ec3..ae9e2d740d3 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -1,5 +1,6 @@ """Sensor for Shelly.""" from homeassistant.components import sensor +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, DEGREE, @@ -203,7 +204,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class ShellySensor(ShellyBlockAttributeEntity): +class ShellySensor(ShellyBlockAttributeEntity, SensorEntity): """Represent a shelly sensor.""" @property @@ -212,7 +213,7 @@ class ShellySensor(ShellyBlockAttributeEntity): return self.attribute_value -class ShellyRestSensor(ShellyRestAttributeEntity): +class ShellyRestSensor(ShellyRestAttributeEntity, SensorEntity): """Represent a shelly REST sensor.""" @property @@ -221,7 +222,7 @@ class ShellyRestSensor(ShellyRestAttributeEntity): return self.attribute_value -class ShellySleepingSensor(ShellySleepingBlockAttributeEntity): +class ShellySleepingSensor(ShellySleepingBlockAttributeEntity, SensorEntity): """Represent a shelly sleeping sensor.""" @property diff --git a/homeassistant/components/shodan/sensor.py b/homeassistant/components/shodan/sensor.py index 397e05a35ca..fa0fc2d3906 100644 --- a/homeassistant/components/shodan/sensor.py +++ b/homeassistant/components/shodan/sensor.py @@ -5,10 +5,9 @@ import logging import shodan import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -47,7 +46,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([ShodanSensor(data, name)], True) -class ShodanSensor(Entity): +class ShodanSensor(SensorEntity): """Representation of the Shodan sensor.""" def __init__(self, data, name): diff --git a/homeassistant/components/sht31/sensor.py b/homeassistant/components/sht31/sensor.py index 277039b3ba6..fd5506ee513 100644 --- a/homeassistant/components/sht31/sensor.py +++ b/homeassistant/components/sht31/sensor.py @@ -7,7 +7,7 @@ import math from Adafruit_SHT31 import SHT31 import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_CONDITIONS, CONF_NAME, @@ -16,7 +16,6 @@ from homeassistant.const import ( TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.temperature import display_temp from homeassistant.util import Throttle @@ -93,7 +92,7 @@ class SHTClient: self.humidity = humidity -class SHTSensor(Entity): +class SHTSensor(SensorEntity): """An abstract SHTSensor, can be either temperature or humidity.""" def __init__(self, sensor, name): diff --git a/homeassistant/components/sigfox/sensor.py b/homeassistant/components/sigfox/sensor.py index 3bf0f084e51..75c2a4f0f63 100644 --- a/homeassistant/components/sigfox/sensor.py +++ b/homeassistant/components/sigfox/sensor.py @@ -7,10 +7,9 @@ from urllib.parse import urljoin import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, HTTP_OK, HTTP_UNAUTHORIZED import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -109,7 +108,7 @@ class SigfoxAPI: return self._devices -class SigfoxDevice(Entity): +class SigfoxDevice(SensorEntity): """Class for single sigfox device.""" def __init__(self, device_id, auth, name): diff --git a/homeassistant/components/simplisafe/sensor.py b/homeassistant/components/simplisafe/sensor.py index 7e927ee942e..9f93a6f9e87 100644 --- a/homeassistant/components/simplisafe/sensor.py +++ b/homeassistant/components/simplisafe/sensor.py @@ -1,6 +1,7 @@ """Support for SimpliSafe freeze sensor.""" from simplipy.entity import EntityTypes +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_FAHRENHEIT from homeassistant.core import callback @@ -25,7 +26,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensors) -class SimplisafeFreezeSensor(SimpliSafeBaseSensor): +class SimplisafeFreezeSensor(SimpliSafeBaseSensor, SensorEntity): """Define a SimpliSafe freeze sensor entity.""" def __init__(self, simplisafe, system, sensor): diff --git a/homeassistant/components/simulated/sensor.py b/homeassistant/components/simulated/sensor.py index dc4872cb2aa..3fe7aedfbb0 100644 --- a/homeassistant/components/simulated/sensor.py +++ b/homeassistant/components/simulated/sensor.py @@ -5,10 +5,9 @@ from random import Random import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util CONF_AMP = "amplitude" @@ -67,7 +66,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([sensor], True) -class SimulatedSensor(Entity): +class SimulatedSensor(SensorEntity): """Class for simulated sensor.""" def __init__( diff --git a/homeassistant/components/skybeacon/sensor.py b/homeassistant/components/skybeacon/sensor.py index 3308ec80b8f..3fdd2e55b0d 100644 --- a/homeassistant/components/skybeacon/sensor.py +++ b/homeassistant/components/skybeacon/sensor.py @@ -8,7 +8,7 @@ from pygatt.backends import Characteristic, GATTToolBackend from pygatt.exceptions import BLEError, NotConnectedError, NotificationTimeout import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MAC, CONF_NAME, @@ -18,7 +18,6 @@ from homeassistant.const import ( TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -62,7 +61,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): mon.start() -class SkybeaconHumid(Entity): +class SkybeaconHumid(SensorEntity): """Representation of a Skybeacon humidity sensor.""" def __init__(self, name, mon): @@ -91,7 +90,7 @@ class SkybeaconHumid(Entity): return {ATTR_DEVICE: "SKYBEACON", ATTR_MODEL: 1} -class SkybeaconTemp(Entity): +class SkybeaconTemp(SensorEntity): """Representation of a Skybeacon temperature sensor.""" def __init__(self, name, mon): @@ -120,7 +119,7 @@ class SkybeaconTemp(Entity): return {ATTR_DEVICE: "SKYBEACON", ATTR_MODEL: 1} -class Monitor(threading.Thread): +class Monitor(threading.Thread, SensorEntity): """Connection handling.""" def __init__(self, hass, mac, name): diff --git a/homeassistant/components/skybell/sensor.py b/homeassistant/components/skybell/sensor.py index 09a7400a035..8dc13814c67 100644 --- a/homeassistant/components/skybell/sensor.py +++ b/homeassistant/components/skybell/sensor.py @@ -3,7 +3,7 @@ from datetime import timedelta import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv @@ -38,7 +38,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class SkybellSensor(SkybellDevice): +class SkybellSensor(SkybellDevice, SensorEntity): """A sensor implementation for Skybell devices.""" def __init__(self, device, sensor_type): diff --git a/homeassistant/components/sleepiq/sensor.py b/homeassistant/components/sleepiq/sensor.py index ae48c059bb8..8f5c17dad89 100644 --- a/homeassistant/components/sleepiq/sensor.py +++ b/homeassistant/components/sleepiq/sensor.py @@ -1,4 +1,6 @@ """Support for SleepIQ sensors.""" +from homeassistant.components.sensor import SensorEntity + from . import SleepIQSensor from .const import DOMAIN, SENSOR_TYPES, SIDES, SLEEP_NUMBER @@ -21,7 +23,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev) -class SleepNumberSensor(SleepIQSensor): +class SleepNumberSensor(SleepIQSensor, SensorEntity): """Implementation of a SleepIQ sensor.""" def __init__(self, sleepiq_data, bed_id, side): diff --git a/homeassistant/components/sma/sensor.py b/homeassistant/components/sma/sensor.py index bc4457e2838..2290f3a330f 100644 --- a/homeassistant/components/sma/sensor.py +++ b/homeassistant/components/sma/sensor.py @@ -5,7 +5,7 @@ import logging import pysma import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -19,7 +19,6 @@ from homeassistant.const import ( from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval _LOGGER = logging.getLogger(__name__) @@ -169,7 +168,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_track_time_interval(hass, async_sma, interval) -class SMAsensor(Entity): +class SMAsensor(SensorEntity): """Representation of a SMA sensor.""" def __init__(self, pysma_sensor, sub_sensors): diff --git a/homeassistant/components/smappee/sensor.py b/homeassistant/components/smappee/sensor.py index 41041c1a002..43483dbdb1e 100644 --- a/homeassistant/components/smappee/sensor.py +++ b/homeassistant/components/smappee/sensor.py @@ -1,6 +1,6 @@ """Support for monitoring a Smappee energy sensor.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_POWER, ENERGY_WATT_HOUR, POWER_WATT, VOLT -from homeassistant.helpers.entity import Entity from .const import DOMAIN @@ -239,7 +239,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class SmappeeSensor(Entity): +class SmappeeSensor(SensorEntity): """Implementation of a Smappee sensor.""" def __init__(self, smappee_base, service_location, sensor, attributes): diff --git a/homeassistant/components/smart_meter_texas/sensor.py b/homeassistant/components/smart_meter_texas/sensor.py index 42084fff836..54e5969f8ea 100644 --- a/homeassistant/components/smart_meter_texas/sensor.py +++ b/homeassistant/components/smart_meter_texas/sensor.py @@ -1,6 +1,7 @@ """Support for Smart Meter Texas sensors.""" from smart_meter_texas import Meter +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_ADDRESS, ENERGY_KILO_WATT_HOUR from homeassistant.core import callback from homeassistant.helpers.restore_state import RestoreEntity @@ -29,7 +30,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class SmartMeterTexasSensor(CoordinatorEntity, RestoreEntity): +class SmartMeterTexasSensor(CoordinatorEntity, RestoreEntity, SensorEntity): """Representation of an Smart Meter Texas sensor.""" def __init__(self, meter: Meter, coordinator: DataUpdateCoordinator): diff --git a/homeassistant/components/smartthings/sensor.py b/homeassistant/components/smartthings/sensor.py index 4f924786e49..86377e32e23 100644 --- a/homeassistant/components/smartthings/sensor.py +++ b/homeassistant/components/smartthings/sensor.py @@ -6,6 +6,7 @@ from typing import Sequence from pysmartthings import Attribute, Capability +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( AREA_SQUARE_METERS, CONCENTRATION_PARTS_PER_MILLION, @@ -306,7 +307,7 @@ def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: ] -class SmartThingsSensor(SmartThingsEntity): +class SmartThingsSensor(SmartThingsEntity, SensorEntity): """Define a SmartThings Sensor.""" def __init__( @@ -346,7 +347,7 @@ class SmartThingsSensor(SmartThingsEntity): return UNITS.get(unit, unit) if unit else self._default_unit -class SmartThingsThreeAxisSensor(SmartThingsEntity): +class SmartThingsThreeAxisSensor(SmartThingsEntity, SensorEntity): """Define a SmartThings Three Axis Sensor.""" def __init__(self, device, index): diff --git a/homeassistant/components/smarttub/sensor.py b/homeassistant/components/smarttub/sensor.py index 99b2e80262d..ea803c9862b 100644 --- a/homeassistant/components/smarttub/sensor.py +++ b/homeassistant/components/smarttub/sensor.py @@ -2,6 +2,8 @@ from enum import Enum import logging +from homeassistant.components.sensor import SensorEntity + from .const import DOMAIN, SMARTTUB_CONTROLLER from .entity import SmartTubSensorBase @@ -44,7 +46,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class SmartTubSensor(SmartTubSensorBase): +class SmartTubSensor(SmartTubSensorBase, SensorEntity): """Generic class for SmartTub status sensors.""" @property diff --git a/homeassistant/components/smarty/sensor.py b/homeassistant/components/smarty/sensor.py index f5cd1fbb404..b958185f9bd 100644 --- a/homeassistant/components/smarty/sensor.py +++ b/homeassistant/components/smarty/sensor.py @@ -3,6 +3,7 @@ import datetime as dt import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TIMESTAMP, @@ -10,7 +11,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util from . import DOMAIN, SIGNAL_UPDATE_SMARTY @@ -35,7 +35,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors, True) -class SmartySensor(Entity): +class SmartySensor(SensorEntity): """Representation of a Smarty Sensor.""" def __init__( diff --git a/homeassistant/components/sms/sensor.py b/homeassistant/components/sms/sensor.py index 660b1a70c01..fc2310426e3 100644 --- a/homeassistant/components/sms/sensor.py +++ b/homeassistant/components/sms/sensor.py @@ -3,8 +3,8 @@ import logging import gammu # pylint: disable=import-error +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_SIGNAL_STRENGTH, SIGNAL_STRENGTH_DECIBELS -from homeassistant.helpers.entity import Entity from .const import DOMAIN, SMS_GATEWAY @@ -27,7 +27,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class GSMSignalSensor(Entity): +class GSMSignalSensor(SensorEntity): """Implementation of a GSM Signal sensor.""" def __init__( diff --git a/homeassistant/components/snmp/sensor.py b/homeassistant/components/snmp/sensor.py index a60183a1a0f..7de2bfb91e2 100644 --- a/homeassistant/components/snmp/sensor.py +++ b/homeassistant/components/snmp/sensor.py @@ -15,7 +15,7 @@ from pysnmp.hlapi.asyncio import ( ) import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -26,7 +26,6 @@ from homeassistant.const import ( STATE_UNKNOWN, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from .const import ( CONF_ACCEPT_ERRORS, @@ -139,7 +138,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([SnmpSensor(data, name, unit, value_template)], True) -class SnmpSensor(Entity): +class SnmpSensor(SensorEntity): """Representation of a SNMP sensor.""" def __init__(self, data, name, unit_of_measurement, value_template): diff --git a/homeassistant/components/sochain/sensor.py b/homeassistant/components/sochain/sensor.py index 5acc8e8432a..1f735da4995 100644 --- a/homeassistant/components/sochain/sensor.py +++ b/homeassistant/components/sochain/sensor.py @@ -4,11 +4,10 @@ from datetime import timedelta from pysochain import ChainSo import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_ADDRESS, CONF_NAME from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity ATTRIBUTION = "Data provided by chain.so" @@ -40,7 +39,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([SochainSensor(name, network.upper(), chainso)], True) -class SochainSensor(Entity): +class SochainSensor(SensorEntity): """Representation of a Sochain sensor.""" def __init__(self, name, unit_of_measurement, chainso): diff --git a/homeassistant/components/socialblade/sensor.py b/homeassistant/components/socialblade/sensor.py index 3d3331f8af2..e38c45d10b4 100644 --- a/homeassistant/components/socialblade/sensor.py +++ b/homeassistant/components/socialblade/sensor.py @@ -5,10 +5,9 @@ import logging import socialbladeclient import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -42,7 +41,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([social_blade]) -class SocialBladeSensor(Entity): +class SocialBladeSensor(SensorEntity): """Representation of a Social Blade Sensor.""" def __init__(self, case, name): diff --git a/homeassistant/components/solaredge/sensor.py b/homeassistant/components/solaredge/sensor.py index 24932618195..7835fa9aee4 100644 --- a/homeassistant/components/solaredge/sensor.py +++ b/homeassistant/components/solaredge/sensor.py @@ -7,10 +7,10 @@ from requests.exceptions import ConnectTimeout, HTTPError import solaredge from stringcase import snakecase +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_API_KEY, DEVICE_CLASS_BATTERY, DEVICE_CLASS_POWER from homeassistant.core import callback from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, @@ -117,7 +117,7 @@ class SolarEdgeSensorFactory: return sensor_class(self.platform_name, sensor_key, service) -class SolarEdgeSensor(CoordinatorEntity, Entity): +class SolarEdgeSensor(CoordinatorEntity, SensorEntity): """Abstract class for a solaredge sensor.""" def __init__(self, platform_name, sensor_key, data_service): diff --git a/homeassistant/components/solaredge_local/sensor.py b/homeassistant/components/solaredge_local/sensor.py index 9d5eca149cf..a34fb5a3afc 100644 --- a/homeassistant/components/solaredge_local/sensor.py +++ b/homeassistant/components/solaredge_local/sensor.py @@ -8,7 +8,7 @@ from requests.exceptions import ConnectTimeout, HTTPError from solaredge_local import SolarEdge import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_IP_ADDRESS, CONF_NAME, @@ -21,7 +21,6 @@ from homeassistant.const import ( VOLT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle DOMAIN = "solaredge_local" @@ -231,7 +230,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities, True) -class SolarEdgeSensor(Entity): +class SolarEdgeSensor(SensorEntity): """Representation of an SolarEdge Monitoring API sensor.""" def __init__(self, platform_name, data, json_key, name, unit, icon, attr): diff --git a/homeassistant/components/solarlog/sensor.py b/homeassistant/components/solarlog/sensor.py index 6073d12815b..85a1531090d 100644 --- a/homeassistant/components/solarlog/sensor.py +++ b/homeassistant/components/solarlog/sensor.py @@ -5,8 +5,8 @@ from urllib.parse import ParseResult, urlparse from requests.exceptions import HTTPError, Timeout from sunwatcher.solarlog.solarlog import SolarLog +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_HOST -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from .const import DOMAIN, SCAN_INTERVAL, SENSOR_TYPES @@ -55,7 +55,7 @@ async def async_setup_entry(hass, entry, async_add_entities): return True -class SolarlogSensor(Entity): +class SolarlogSensor(SensorEntity): """Representation of a Sensor.""" def __init__(self, entry_id, device_name, sensor_key, data): diff --git a/homeassistant/components/solax/sensor.py b/homeassistant/components/solax/sensor.py index bca507c4391..e47f5c57802 100644 --- a/homeassistant/components/solax/sensor.py +++ b/homeassistant/components/solax/sensor.py @@ -6,11 +6,10 @@ from solax import real_time_api from solax.inverter import InverterError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, TEMP_CELSIUS from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval DEFAULT_PORT = 80 @@ -73,7 +72,7 @@ class RealTimeDataEndpoint: sensor.async_schedule_update_ha_state() -class Inverter(Entity): +class Inverter(SensorEntity): """Class for a sensor.""" def __init__(self, uid, serial, key, unit): diff --git a/homeassistant/components/soma/sensor.py b/homeassistant/components/soma/sensor.py index 9430a929e1e..436a92a1087 100644 --- a/homeassistant/components/soma/sensor.py +++ b/homeassistant/components/soma/sensor.py @@ -4,8 +4,8 @@ import logging from requests import RequestException +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from . import DEVICES, SomaEntity @@ -26,7 +26,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class SomaSensor(SomaEntity, Entity): +class SomaSensor(SomaEntity, SensorEntity): """Representation of a Soma cover device.""" @property diff --git a/homeassistant/components/somfy/sensor.py b/homeassistant/components/somfy/sensor.py index 996a95348a4..34283a1271c 100644 --- a/homeassistant/components/somfy/sensor.py +++ b/homeassistant/components/somfy/sensor.py @@ -3,6 +3,7 @@ from pymfy.api.devices.category import Category from pymfy.api.devices.thermostat import Thermostat +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE from . import SomfyEntity @@ -26,7 +27,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -class SomfyThermostatBatterySensor(SomfyEntity): +class SomfyThermostatBatterySensor(SomfyEntity, SensorEntity): """Representation of a Somfy thermostat battery.""" def __init__(self, coordinator, device_id, api): diff --git a/homeassistant/components/sonarr/sensor.py b/homeassistant/components/sonarr/sensor.py index 017d9b0f0d0..3446130433e 100644 --- a/homeassistant/components/sonarr/sensor.py +++ b/homeassistant/components/sonarr/sensor.py @@ -7,6 +7,7 @@ from typing import Any, Callable from sonarr import Sonarr, SonarrConnectionError, SonarrError +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import DATA_GIGABYTES from homeassistant.helpers.entity import Entity @@ -65,7 +66,7 @@ def sonarr_exception_handler(func): return handler -class SonarrSensor(SonarrEntity): +class SonarrSensor(SonarrEntity, SensorEntity): """Implementation of the Sonarr sensor.""" def __init__( diff --git a/homeassistant/components/speedtestdotnet/sensor.py b/homeassistant/components/speedtestdotnet/sensor.py index 6f0cb4124fe..c49a5691cec 100644 --- a/homeassistant/components/speedtestdotnet/sensor.py +++ b/homeassistant/components/speedtestdotnet/sensor.py @@ -1,4 +1,5 @@ """Support for Speedtest.net internet speed testing sensor.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.core import callback from homeassistant.helpers.restore_state import RestoreEntity @@ -30,7 +31,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class SpeedtestSensor(CoordinatorEntity, RestoreEntity): +class SpeedtestSensor(CoordinatorEntity, RestoreEntity, SensorEntity): """Implementation of a speedtest.net sensor.""" def __init__(self, coordinator, sensor_type): diff --git a/homeassistant/components/spotcrime/sensor.py b/homeassistant/components/spotcrime/sensor.py index e44bd81ed51..72a6fec84e9 100644 --- a/homeassistant/components/spotcrime/sensor.py +++ b/homeassistant/components/spotcrime/sensor.py @@ -6,7 +6,7 @@ from datetime import timedelta import spotcrime import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, @@ -20,7 +20,6 @@ from homeassistant.const import ( CONF_RADIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import slugify CONF_DAYS = "days" @@ -66,7 +65,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class SpotCrimeSensor(Entity): +class SpotCrimeSensor(SensorEntity): """Representation of a Spot Crime Sensor.""" def __init__( diff --git a/homeassistant/components/sql/sensor.py b/homeassistant/components/sql/sensor.py index ccec918832e..a537b160d0b 100644 --- a/homeassistant/components/sql/sensor.py +++ b/homeassistant/components/sql/sensor.py @@ -8,10 +8,9 @@ from sqlalchemy.orm import scoped_session, sessionmaker import voluptuous as vol from homeassistant.components.recorder import CONF_DB_URL, DEFAULT_DB_FILE, DEFAULT_URL -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -90,7 +89,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(queries, True) -class SQLSensor(Entity): +class SQLSensor(SensorEntity): """Representation of an SQL sensor.""" def __init__(self, name, sessmaker, query, column, unit, value_template): diff --git a/homeassistant/components/srp_energy/sensor.py b/homeassistant/components/srp_energy/sensor.py index cc39bf0b898..6973c58600e 100644 --- a/homeassistant/components/srp_energy/sensor.py +++ b/homeassistant/components/srp_energy/sensor.py @@ -5,8 +5,8 @@ import logging import async_timeout from requests.exceptions import ConnectionError as ConnectError, HTTPError, Timeout +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, ENERGY_KILO_WATT_HOUR -from homeassistant.helpers import entity from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import ( @@ -71,7 +71,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities([SrpEntity(coordinator)]) -class SrpEntity(entity.Entity): +class SrpEntity(SensorEntity): """Implementation of a Srp Energy Usage sensor.""" def __init__(self, coordinator): diff --git a/homeassistant/components/starline/sensor.py b/homeassistant/components/starline/sensor.py index a8782a87892..29deacee428 100644 --- a/homeassistant/components/starline/sensor.py +++ b/homeassistant/components/starline/sensor.py @@ -1,5 +1,5 @@ """Reads vehicle status from StarLine API.""" -from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE +from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE, SensorEntity from homeassistant.const import ( LENGTH_KILOMETERS, PERCENTAGE, @@ -7,7 +7,6 @@ from homeassistant.const import ( VOLT, VOLUME_LITERS, ) -from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level, icon_for_signal_level from .account import StarlineAccount, StarlineDevice @@ -38,7 +37,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class StarlineSensor(StarlineEntity, Entity): +class StarlineSensor(StarlineEntity, SensorEntity): """Representation of a StarLine sensor.""" def __init__( diff --git a/homeassistant/components/starlingbank/sensor.py b/homeassistant/components/starlingbank/sensor.py index 20fa646ce41..77f5ab307cb 100644 --- a/homeassistant/components/starlingbank/sensor.py +++ b/homeassistant/components/starlingbank/sensor.py @@ -5,10 +5,9 @@ import requests from starlingbank import StarlingAccount import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_ACCESS_TOKEN, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -62,7 +61,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(sensors, True) -class StarlingBalanceSensor(Entity): +class StarlingBalanceSensor(SensorEntity): """Representation of a Starling balance sensor.""" def __init__(self, starling_account, account_name, balance_data_type): diff --git a/homeassistant/components/startca/sensor.py b/homeassistant/components/startca/sensor.py index f6867d83212..661e00ed494 100644 --- a/homeassistant/components/startca/sensor.py +++ b/homeassistant/components/startca/sensor.py @@ -7,7 +7,7 @@ import async_timeout import voluptuous as vol import xmltodict -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_API_KEY, CONF_MONITORED_VARIABLES, @@ -18,7 +18,6 @@ from homeassistant.const import ( ) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -75,7 +74,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors, True) -class StartcaSensor(Entity): +class StartcaSensor(SensorEntity): """Representation of Start.ca Bandwidth sensor.""" def __init__(self, startcadata, sensor_type, name): diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py index 3bf70060da9..e32ae0debaf 100644 --- a/homeassistant/components/statistics/sensor.py +++ b/homeassistant/components/statistics/sensor.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.components.recorder.models import States from homeassistant.components.recorder.util import execute, session_scope -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_ENTITY_ID, @@ -18,7 +18,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import ( async_track_point_in_utc_time, async_track_state_change_event, @@ -85,7 +84,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= return True -class StatisticsSensor(Entity): +class StatisticsSensor(SensorEntity): """Representation of a Statistics sensor.""" def __init__(self, entity_id, name, sampling_size, max_age, precision): diff --git a/homeassistant/components/steam_online/sensor.py b/homeassistant/components/steam_online/sensor.py index e62922c67f4..45ae1a6c70a 100644 --- a/homeassistant/components/steam_online/sensor.py +++ b/homeassistant/components/steam_online/sensor.py @@ -6,11 +6,10 @@ from time import mktime import steam import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_API_KEY from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import track_time_interval from homeassistant.util.dt import utc_from_timestamp @@ -71,7 +70,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): track_time_interval(hass, do_update, BASE_INTERVAL) -class SteamSensor(Entity): +class SteamSensor(SensorEntity): """A class for the Steam account.""" def __init__(self, account, steamod): diff --git a/homeassistant/components/streamlabswater/sensor.py b/homeassistant/components/streamlabswater/sensor.py index e7168f8ec0b..ba722d0a4f2 100644 --- a/homeassistant/components/streamlabswater/sensor.py +++ b/homeassistant/components/streamlabswater/sensor.py @@ -2,9 +2,9 @@ from datetime import timedelta +from homeassistant.components.sensor import SensorEntity from homeassistant.components.streamlabswater import DOMAIN as STREAMLABSWATER_DOMAIN from homeassistant.const import VOLUME_GALLONS -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle DEPENDENCIES = ["streamlabswater"] @@ -67,7 +67,7 @@ class StreamlabsUsageData: return self._this_year -class StreamLabsDailyUsage(Entity): +class StreamLabsDailyUsage(SensorEntity): """Monitors the daily water usage.""" def __init__(self, location_name, streamlabs_usage_data): diff --git a/homeassistant/components/subaru/sensor.py b/homeassistant/components/subaru/sensor.py index 594d18028e6..b8362202a3d 100644 --- a/homeassistant/components/subaru/sensor.py +++ b/homeassistant/components/subaru/sensor.py @@ -1,7 +1,7 @@ """Support for Subaru sensors.""" import subarulink.const as sc -from homeassistant.components.sensor import DEVICE_CLASSES +from homeassistant.components.sensor import DEVICE_CLASSES, SensorEntity from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_TEMPERATURE, @@ -170,7 +170,7 @@ def create_vehicle_sensors(vehicle_info, coordinator): ] -class SubaruSensor(SubaruEntity): +class SubaruSensor(SubaruEntity, SensorEntity): """Class for Subaru sensors.""" def __init__( diff --git a/homeassistant/components/suez_water/sensor.py b/homeassistant/components/suez_water/sensor.py index 53f6b3e9c14..7170e0b8a67 100644 --- a/homeassistant/components/suez_water/sensor.py +++ b/homeassistant/components/suez_water/sensor.py @@ -6,10 +6,9 @@ from pysuez import SuezClient from pysuez.client import PySuezError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, VOLUME_LITERS import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) CONF_COUNTER_ID = "counter_id" @@ -47,7 +46,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([SuezSensor(client)], True) -class SuezSensor(Entity): +class SuezSensor(SensorEntity): """Representation of a Sensor.""" def __init__(self, client): diff --git a/homeassistant/components/supervisord/sensor.py b/homeassistant/components/supervisord/sensor.py index 0817f10ed5c..f701df2d6c3 100644 --- a/homeassistant/components/supervisord/sensor.py +++ b/homeassistant/components/supervisord/sensor.py @@ -4,10 +4,9 @@ import xmlrpc.client import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_URL import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -36,7 +35,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class SupervisorProcessSensor(Entity): +class SupervisorProcessSensor(SensorEntity): """Representation of a supervisor-monitored process.""" def __init__(self, info, server): diff --git a/homeassistant/components/surepetcare/sensor.py b/homeassistant/components/surepetcare/sensor.py index 92f90faff2c..0a49781767b 100644 --- a/homeassistant/components/surepetcare/sensor.py +++ b/homeassistant/components/surepetcare/sensor.py @@ -6,6 +6,7 @@ from typing import Any from surepy import SureLockStateID, SurepyProduct +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_VOLTAGE, CONF_ID, @@ -15,7 +16,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from . import SurePetcareAPI from .const import ( @@ -54,7 +54,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(entities, True) -class SurePetcareSensor(Entity): +class SurePetcareSensor(SensorEntity): """A binary sensor implementation for Sure Petcare Entities.""" def __init__(self, _id: int, sure_type: SurepyProduct, spc: SurePetcareAPI): diff --git a/homeassistant/components/swiss_hydrological_data/sensor.py b/homeassistant/components/swiss_hydrological_data/sensor.py index 27071afd112..47a8d3e5589 100644 --- a/homeassistant/components/swiss_hydrological_data/sensor.py +++ b/homeassistant/components/swiss_hydrological_data/sensor.py @@ -5,10 +5,9 @@ import logging from swisshydrodata import SwissHydroData import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -83,7 +82,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities, True) -class SwissHydrologicalDataSensor(Entity): +class SwissHydrologicalDataSensor(SensorEntity): """Implementation of a Swiss hydrological sensor.""" def __init__(self, hydro_data, station, condition): diff --git a/homeassistant/components/swiss_public_transport/sensor.py b/homeassistant/components/swiss_public_transport/sensor.py index 1a7b97ce439..a971524c22b 100644 --- a/homeassistant/components/swiss_public_transport/sensor.py +++ b/homeassistant/components/swiss_public_transport/sensor.py @@ -6,11 +6,10 @@ from opendata_transport import OpendataTransport from opendata_transport.exceptions import OpendataTransportError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -68,7 +67,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([SwissPublicTransportSensor(opendata, start, destination, name)]) -class SwissPublicTransportSensor(Entity): +class SwissPublicTransportSensor(SensorEntity): """Implementation of an Swiss public transport sensor.""" def __init__(self, opendata, start, destination, name): diff --git a/homeassistant/components/syncthru/sensor.py b/homeassistant/components/syncthru/sensor.py index edec503bc25..8277bd69467 100644 --- a/homeassistant/components/syncthru/sensor.py +++ b/homeassistant/components/syncthru/sensor.py @@ -5,11 +5,10 @@ import logging from pysyncthru import SYNCTHRU_STATE_HUMAN, SyncThru import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import CONF_NAME, CONF_RESOURCE, CONF_URL, PERCENTAGE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from . import device_identifiers from .const import DEFAULT_MODEL, DEFAULT_NAME_TEMPLATE, DOMAIN @@ -81,7 +80,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(devices, True) -class SyncThruSensor(Entity): +class SyncThruSensor(SensorEntity): """Implementation of an abstract Samsung Printer sensor platform.""" def __init__(self, syncthru, name): diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index 8cfab92975c..56eb56df07d 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -3,6 +3,7 @@ from __future__ import annotations from datetime import timedelta +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_DISKS, @@ -86,7 +87,7 @@ async def async_setup_entry( async_add_entities(entities) -class SynoDSMUtilSensor(SynologyDSMBaseEntity): +class SynoDSMUtilSensor(SynologyDSMBaseEntity, SensorEntity): """Representation a Synology Utilisation sensor.""" @property @@ -118,7 +119,7 @@ class SynoDSMUtilSensor(SynologyDSMBaseEntity): return bool(self._api.utilisation) -class SynoDSMStorageSensor(SynologyDSMDeviceEntity): +class SynoDSMStorageSensor(SynologyDSMDeviceEntity, SensorEntity): """Representation a Synology Storage sensor.""" @property @@ -139,7 +140,7 @@ class SynoDSMStorageSensor(SynologyDSMDeviceEntity): return attr -class SynoDSMInfoSensor(SynologyDSMBaseEntity): +class SynoDSMInfoSensor(SynologyDSMBaseEntity, SensorEntity): """Representation a Synology information sensor.""" def __init__( diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py index 805ffcdba5d..3e69939cbf3 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -7,7 +7,7 @@ import sys import psutil import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_RESOURCES, CONF_TYPE, @@ -20,7 +20,6 @@ from homeassistant.const import ( TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import slugify import homeassistant.util.dt as dt_util @@ -182,7 +181,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class SystemMonitorSensor(Entity): +class SystemMonitorSensor(SensorEntity): """Implementation of a system monitor sensor.""" def __init__(self, sensor_type, argument=""): From fdf97eaca34f56ea19c3c73758641233e10056b5 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 19:59:03 +0100 Subject: [PATCH 554/831] Migrate integrations i-m to extend SensorEntity (#48213) --- homeassistant/components/iammeter/sensor.py | 4 ++-- homeassistant/components/iaqualink/sensor.py | 4 ++-- homeassistant/components/icloud/sensor.py | 4 ++-- homeassistant/components/ihc/sensor.py | 4 ++-- homeassistant/components/imap/sensor.py | 5 ++--- .../components/imap_email_content/sensor.py | 5 ++--- homeassistant/components/incomfort/sensor.py | 4 ++-- homeassistant/components/influxdb/sensor.py | 8 +++++--- homeassistant/components/integration/sensor.py | 4 ++-- homeassistant/components/ios/sensor.py | 4 ++-- homeassistant/components/iota/sensor.py | 5 +++-- homeassistant/components/iperf3/sensor.py | 3 ++- homeassistant/components/ipp/sensor.py | 3 ++- homeassistant/components/iqvia/sensor.py | 5 +++-- .../components/irish_rail_transport/sensor.py | 5 ++--- .../components/islamic_prayer_times/sensor.py | 4 ++-- homeassistant/components/isy994/sensor.py | 6 +++--- .../components/jewish_calendar/sensor.py | 4 ++-- homeassistant/components/juicenet/sensor.py | 4 ++-- homeassistant/components/kaiterra/sensor.py | 4 ++-- homeassistant/components/keba/sensor.py | 4 ++-- homeassistant/components/kira/sensor.py | 4 ++-- homeassistant/components/knx/sensor.py | 4 ++-- homeassistant/components/konnected/sensor.py | 4 ++-- homeassistant/components/kwb/sensor.py | 5 ++--- homeassistant/components/lacrosse/sensor.py | 10 +++++++--- homeassistant/components/lastfm/sensor.py | 5 ++--- homeassistant/components/launch_library/sensor.py | 5 ++--- homeassistant/components/lcn/sensor.py | 6 +++--- homeassistant/components/lightwave/sensor.py | 4 ++-- homeassistant/components/linux_battery/sensor.py | 5 ++--- homeassistant/components/litterrobot/sensor.py | 8 ++++---- homeassistant/components/local_ip/sensor.py | 4 ++-- homeassistant/components/logi_circle/sensor.py | 4 ++-- homeassistant/components/london_air/sensor.py | 5 ++--- .../components/london_underground/sensor.py | 5 ++--- homeassistant/components/loopenergy/sensor.py | 9 ++++----- homeassistant/components/luftdaten/sensor.py | 4 ++-- homeassistant/components/lyft/sensor.py | 5 ++--- homeassistant/components/lyric/sensor.py | 3 ++- homeassistant/components/magicseaweed/sensor.py | 5 ++--- homeassistant/components/mazda/sensor.py | 15 ++++++++------- homeassistant/components/melcloud/sensor.py | 4 ++-- homeassistant/components/meteo_france/sensor.py | 3 ++- homeassistant/components/metoffice/sensor.py | 4 ++-- homeassistant/components/mfi/sensor.py | 5 ++--- homeassistant/components/mhz19/sensor.py | 5 ++--- homeassistant/components/miflora/sensor.py | 5 ++--- homeassistant/components/min_max/sensor.py | 5 ++--- .../components/minecraft_server/sensor.py | 3 ++- homeassistant/components/mitemp_bt/sensor.py | 5 ++--- homeassistant/components/mobile_app/sensor.py | 3 ++- homeassistant/components/modbus/sensor.py | 8 ++++++-- homeassistant/components/modem_callerid/sensor.py | 5 ++--- homeassistant/components/mold_indicator/sensor.py | 5 ++--- homeassistant/components/moon/sensor.py | 5 ++--- homeassistant/components/motion_blinds/sensor.py | 6 +++--- homeassistant/components/mqtt/sensor.py | 5 ++--- homeassistant/components/mqtt_room/sensor.py | 5 ++--- homeassistant/components/mvglive/sensor.py | 5 ++--- homeassistant/components/mychevy/sensor.py | 7 +++---- homeassistant/components/mysensors/sensor.py | 4 ++-- 62 files changed, 152 insertions(+), 157 deletions(-) diff --git a/homeassistant/components/iammeter/sensor.py b/homeassistant/components/iammeter/sensor.py index f885c2c49d3..b1882619fda 100644 --- a/homeassistant/components/iammeter/sensor.py +++ b/homeassistant/components/iammeter/sensor.py @@ -8,7 +8,7 @@ from iammeter import real_time_api from iammeter.power_meter import IamMeterError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers import debounce @@ -74,7 +74,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(entities) -class IamMeter(CoordinatorEntity): +class IamMeter(CoordinatorEntity, SensorEntity): """Class for a sensor.""" def __init__(self, coordinator, uid, sensor_name, unit, dev_name): diff --git a/homeassistant/components/iaqualink/sensor.py b/homeassistant/components/iaqualink/sensor.py index 565cfcca667..eac6e2b7851 100644 --- a/homeassistant/components/iaqualink/sensor.py +++ b/homeassistant/components/iaqualink/sensor.py @@ -1,7 +1,7 @@ """Support for Aqualink temperature sensors.""" from __future__ import annotations -from homeassistant.components.sensor import DOMAIN +from homeassistant.components.sensor import DOMAIN, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.helpers.typing import HomeAssistantType @@ -22,7 +22,7 @@ async def async_setup_entry( async_add_entities(devs, True) -class HassAqualinkSensor(AqualinkEntity): +class HassAqualinkSensor(AqualinkEntity, SensorEntity): """Representation of a sensor.""" @property diff --git a/homeassistant/components/icloud/sensor.py b/homeassistant/components/icloud/sensor.py index e75c9aa3854..ddd3d54c556 100644 --- a/homeassistant/components/icloud/sensor.py +++ b/homeassistant/components/icloud/sensor.py @@ -1,11 +1,11 @@ """Support for iCloud sensors.""" from __future__ import annotations +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.helpers.typing import HomeAssistantType @@ -48,7 +48,7 @@ def add_entities(account, async_add_entities, tracked): async_add_entities(new_tracked, True) -class IcloudDeviceBatterySensor(Entity): +class IcloudDeviceBatterySensor(SensorEntity): """Representation of a iCloud device battery sensor.""" def __init__(self, account: IcloudAccount, device: IcloudDevice): diff --git a/homeassistant/components/ihc/sensor.py b/homeassistant/components/ihc/sensor.py index cb1688bc7be..3348e857f51 100644 --- a/homeassistant/components/ihc/sensor.py +++ b/homeassistant/components/ihc/sensor.py @@ -1,6 +1,6 @@ """Support for IHC sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_UNIT_OF_MEASUREMENT -from homeassistant.helpers.entity import Entity from . import IHC_CONTROLLER, IHC_INFO from .ihcdevice import IHCDevice @@ -26,7 +26,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class IHCSensor(IHCDevice, Entity): +class IHCSensor(IHCDevice, SensorEntity): """Implementation of the IHC sensor.""" def __init__( diff --git a/homeassistant/components/imap/sensor.py b/homeassistant/components/imap/sensor.py index 4917abc6028..4158d1be801 100644 --- a/homeassistant/components/imap/sensor.py +++ b/homeassistant/components/imap/sensor.py @@ -6,7 +6,7 @@ from aioimaplib import IMAP4_SSL, AioImapException import async_timeout import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, @@ -16,7 +16,6 @@ from homeassistant.const import ( ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -62,7 +61,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([sensor], True) -class ImapSensor(Entity): +class ImapSensor(SensorEntity): """Representation of an IMAP sensor.""" def __init__(self, name, user, password, server, port, charset, folder, search): diff --git a/homeassistant/components/imap_email_content/sensor.py b/homeassistant/components/imap_email_content/sensor.py index 84dc527ac52..22c395a7c8f 100644 --- a/homeassistant/components/imap_email_content/sensor.py +++ b/homeassistant/components/imap_email_content/sensor.py @@ -7,7 +7,7 @@ import logging import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_DATE, CONF_NAME, @@ -18,7 +18,6 @@ from homeassistant.const import ( CONTENT_TYPE_TEXT_PLAIN, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -145,7 +144,7 @@ class EmailReader: return None -class EmailContentSensor(Entity): +class EmailContentSensor(SensorEntity): """Representation of an EMail sensor.""" def __init__(self, hass, email_reader, name, allowed_senders, value_template): diff --git a/homeassistant/components/incomfort/sensor.py b/homeassistant/components/incomfort/sensor.py index 8cb07fce14b..a9e1faaba10 100644 --- a/homeassistant/components/incomfort/sensor.py +++ b/homeassistant/components/incomfort/sensor.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Any -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, SensorEntity from homeassistant.const import ( DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, @@ -40,7 +40,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class IncomfortSensor(IncomfortChild): +class IncomfortSensor(IncomfortChild, SensorEntity): """Representation of an InComfort/InTouch sensor device.""" def __init__(self, client, heater, name) -> None: diff --git a/homeassistant/components/influxdb/sensor.py b/homeassistant/components/influxdb/sensor.py index cfabd3ee099..299fc595f4b 100644 --- a/homeassistant/components/influxdb/sensor.py +++ b/homeassistant/components/influxdb/sensor.py @@ -5,7 +5,10 @@ import logging import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA +from homeassistant.components.sensor import ( + PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA, + SensorEntity, +) from homeassistant.const import ( CONF_API_VERSION, CONF_NAME, @@ -16,7 +19,6 @@ from homeassistant.const import ( ) from homeassistant.exceptions import PlatformNotReady, TemplateError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from . import create_influx_url, get_influx_connection, validate_version_specific_config @@ -169,7 +171,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lambda _: influx.close()) -class InfluxSensor(Entity): +class InfluxSensor(SensorEntity): """Implementation of a Influxdb sensor.""" def __init__(self, hass, influx, query): diff --git a/homeassistant/components/integration/sensor.py b/homeassistant/components/integration/sensor.py index 3be60dcd7f7..0ab4ac0d2c4 100644 --- a/homeassistant/components/integration/sensor.py +++ b/homeassistant/components/integration/sensor.py @@ -4,7 +4,7 @@ import logging import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_METHOD, @@ -83,7 +83,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([integral]) -class IntegrationSensor(RestoreEntity): +class IntegrationSensor(RestoreEntity, SensorEntity): """Representation of an integration sensor.""" def __init__( diff --git a/homeassistant/components/ios/sensor.py b/homeassistant/components/ios/sensor.py index a8cffacabe9..c1442f0de9f 100644 --- a/homeassistant/components/ios/sensor.py +++ b/homeassistant/components/ios/sensor.py @@ -1,9 +1,9 @@ """Support for Home Assistant iOS app sensors.""" from homeassistant.components import ios +from homeassistant.components.sensor import SensorEntity from homeassistant.const import PERCENTAGE from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from .const import DOMAIN @@ -32,7 +32,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(dev, True) -class IOSSensor(Entity): +class IOSSensor(SensorEntity): """Representation of an iOS sensor.""" def __init__(self, sensor_type, device_name, device): diff --git a/homeassistant/components/iota/sensor.py b/homeassistant/components/iota/sensor.py index 751324d61b1..62260be2410 100644 --- a/homeassistant/components/iota/sensor.py +++ b/homeassistant/components/iota/sensor.py @@ -1,6 +1,7 @@ """Support for IOTA wallet sensors.""" from datetime import timedelta +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME from . import CONF_WALLETS, IotaDevice @@ -27,7 +28,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class IotaBalanceSensor(IotaDevice): +class IotaBalanceSensor(IotaDevice, SensorEntity): """Implement an IOTA sensor for displaying wallets balance.""" def __init__(self, wallet_config, iota_config): @@ -60,7 +61,7 @@ class IotaBalanceSensor(IotaDevice): self._state = self.api.get_inputs()["totalBalance"] -class IotaNodeSensor(IotaDevice): +class IotaNodeSensor(IotaDevice, SensorEntity): """Implement an IOTA sensor for displaying attributes of node.""" def __init__(self, iota_config): diff --git a/homeassistant/components/iperf3/sensor.py b/homeassistant/components/iperf3/sensor.py index a8a79008817..610ff91250f 100644 --- a/homeassistant/components/iperf3/sensor.py +++ b/homeassistant/components/iperf3/sensor.py @@ -1,4 +1,5 @@ """Support for Iperf3 sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -23,7 +24,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info) async_add_entities(sensors, True) -class Iperf3Sensor(RestoreEntity): +class Iperf3Sensor(RestoreEntity, SensorEntity): """A Iperf3 sensor implementation.""" def __init__(self, iperf3_data, sensor_type): diff --git a/homeassistant/components/ipp/sensor.py b/homeassistant/components/ipp/sensor.py index 1ff98df7a5f..83826409ed8 100644 --- a/homeassistant/components/ipp/sensor.py +++ b/homeassistant/components/ipp/sensor.py @@ -4,6 +4,7 @@ from __future__ import annotations from datetime import timedelta from typing import Any, Callable +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_LOCATION, DEVICE_CLASS_TIMESTAMP, PERCENTAGE from homeassistant.helpers.entity import Entity @@ -52,7 +53,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class IPPSensor(IPPEntity): +class IPPSensor(IPPEntity, SensorEntity): """Defines an IPP sensor.""" def __init__( diff --git a/homeassistant/components/iqvia/sensor.py b/homeassistant/components/iqvia/sensor.py index 48ec1cf97b1..169ff405d6d 100644 --- a/homeassistant/components/iqvia/sensor.py +++ b/homeassistant/components/iqvia/sensor.py @@ -3,6 +3,7 @@ from statistics import mean import numpy as np +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_STATE from homeassistant.core import callback @@ -98,7 +99,7 @@ def calculate_trend(indices): return TREND_FLAT -class ForecastSensor(IQVIAEntity): +class ForecastSensor(IQVIAEntity, SensorEntity): """Define sensor related to forecast data.""" @callback @@ -137,7 +138,7 @@ class ForecastSensor(IQVIAEntity): self._state = average -class IndexSensor(IQVIAEntity): +class IndexSensor(IQVIAEntity, SensorEntity): """Define sensor related to indices.""" @callback diff --git a/homeassistant/components/irish_rail_transport/sensor.py b/homeassistant/components/irish_rail_transport/sensor.py index bd277020125..b5ba16f8541 100644 --- a/homeassistant/components/irish_rail_transport/sensor.py +++ b/homeassistant/components/irish_rail_transport/sensor.py @@ -4,10 +4,9 @@ from datetime import timedelta from pyirishrail.pyirishrail import IrishRailRTPI import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, TIME_MINUTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity ATTR_STATION = "Station" ATTR_ORIGIN = "Origin" @@ -64,7 +63,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class IrishRailTransportSensor(Entity): +class IrishRailTransportSensor(SensorEntity): """Implementation of an irish rail public transport sensor.""" def __init__(self, data, station, direction, destination, stops_at, name): diff --git a/homeassistant/components/islamic_prayer_times/sensor.py b/homeassistant/components/islamic_prayer_times/sensor.py index 41b3cf4c667..3133320d978 100644 --- a/homeassistant/components/islamic_prayer_times/sensor.py +++ b/homeassistant/components/islamic_prayer_times/sensor.py @@ -1,8 +1,8 @@ """Platform to retrieve Islamic prayer times information for Home Assistant.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_TIMESTAMP from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util from .const import DATA_UPDATED, DOMAIN, PRAYER_TIMES_ICON, SENSOR_TYPES @@ -20,7 +20,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class IslamicPrayerTimeSensor(Entity): +class IslamicPrayerTimeSensor(SensorEntity): """Representation of an Islamic prayer time sensor.""" def __init__(self, sensor_type, client): diff --git a/homeassistant/components/isy994/sensor.py b/homeassistant/components/isy994/sensor.py index 4d3c4c72763..2927fbb62b1 100644 --- a/homeassistant/components/isy994/sensor.py +++ b/homeassistant/components/isy994/sensor.py @@ -5,7 +5,7 @@ from typing import Callable from pyisy.constants import ISY_VALUE_UNKNOWN -from homeassistant.components.sensor import DOMAIN as SENSOR +from homeassistant.components.sensor import DOMAIN as SENSOR, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.helpers.typing import HomeAssistantType @@ -45,7 +45,7 @@ async def async_setup_entry( async_add_entities(devices) -class ISYSensorEntity(ISYNodeEntity): +class ISYSensorEntity(ISYNodeEntity, SensorEntity): """Representation of an ISY994 sensor device.""" @property @@ -105,7 +105,7 @@ class ISYSensorEntity(ISYNodeEntity): return raw_units -class ISYSensorVariableEntity(ISYEntity): +class ISYSensorVariableEntity(ISYEntity, SensorEntity): """Representation of an ISY994 variable as a sensor device.""" def __init__(self, vname: str, vobj: object) -> None: diff --git a/homeassistant/components/jewish_calendar/sensor.py b/homeassistant/components/jewish_calendar/sensor.py index 14336b1f935..5690cd35a03 100644 --- a/homeassistant/components/jewish_calendar/sensor.py +++ b/homeassistant/components/jewish_calendar/sensor.py @@ -3,8 +3,8 @@ import logging import hdate +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_TIMESTAMP, SUN_EVENT_SUNSET -from homeassistant.helpers.entity import Entity from homeassistant.helpers.sun import get_astral_event_date import homeassistant.util.dt as dt_util @@ -30,7 +30,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors) -class JewishCalendarSensor(Entity): +class JewishCalendarSensor(SensorEntity): """Representation of an Jewish calendar sensor.""" def __init__(self, data, sensor, sensor_info): diff --git a/homeassistant/components/juicenet/sensor.py b/homeassistant/components/juicenet/sensor.py index 10008f30e7c..d908dc069ef 100644 --- a/homeassistant/components/juicenet/sensor.py +++ b/homeassistant/components/juicenet/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring juicenet/juicepoint/juicebox based EVSE sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ELECTRICAL_CURRENT_AMPERE, ENERGY_WATT_HOUR, @@ -7,7 +8,6 @@ from homeassistant.const import ( TIME_SECONDS, VOLT, ) -from homeassistant.helpers.entity import Entity from .const import DOMAIN, JUICENET_API, JUICENET_COORDINATOR from .entity import JuiceNetDevice @@ -36,7 +36,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class JuiceNetSensorDevice(JuiceNetDevice, Entity): +class JuiceNetSensorDevice(JuiceNetDevice, SensorEntity): """Implementation of a JuiceNet sensor.""" def __init__(self, device, sensor_type, coordinator): diff --git a/homeassistant/components/kaiterra/sensor.py b/homeassistant/components/kaiterra/sensor.py index d9500c7a000..1e4dd0cbbca 100644 --- a/homeassistant/components/kaiterra/sensor.py +++ b/homeassistant/components/kaiterra/sensor.py @@ -1,7 +1,7 @@ """Support for Kaiterra Temperature ahn Humidity Sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_DEVICE_ID, CONF_NAME, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from .const import DISPATCHER_KAITERRA, DOMAIN @@ -25,7 +25,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class KaiterraSensor(Entity): +class KaiterraSensor(SensorEntity): """Implementation of a Kaittera sensor.""" def __init__(self, api, name, device_id, sensor): diff --git a/homeassistant/components/keba/sensor.py b/homeassistant/components/keba/sensor.py index ed85ccd06a6..836785490e8 100644 --- a/homeassistant/components/keba/sensor.py +++ b/homeassistant/components/keba/sensor.py @@ -1,10 +1,10 @@ """Support for KEBA charging station sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_POWER, ELECTRICAL_CURRENT_AMPERE, ENERGY_KILO_WATT_HOUR, ) -from homeassistant.helpers.entity import Entity from . import DOMAIN @@ -62,7 +62,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors) -class KebaSensor(Entity): +class KebaSensor(SensorEntity): """The entity class for KEBA charging stations sensors.""" def __init__(self, keba, key, name, entity_type, icon, unit, device_class=None): diff --git a/homeassistant/components/kira/sensor.py b/homeassistant/components/kira/sensor.py index a8c49d1c04b..a6b1b9ada22 100644 --- a/homeassistant/components/kira/sensor.py +++ b/homeassistant/components/kira/sensor.py @@ -1,8 +1,8 @@ """KIRA interface to receive UDP packets from an IR-IP bridge.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_DEVICE, CONF_NAME, STATE_UNKNOWN -from homeassistant.helpers.entity import Entity from . import CONF_SENSOR, DOMAIN @@ -21,7 +21,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([KiraReceiver(device, kira)]) -class KiraReceiver(Entity): +class KiraReceiver(SensorEntity): """Implementation of a Kira Receiver.""" def __init__(self, name, kira): diff --git a/homeassistant/components/knx/sensor.py b/homeassistant/components/knx/sensor.py index f3155eebf01..4397f8bcc0b 100644 --- a/homeassistant/components/knx/sensor.py +++ b/homeassistant/components/knx/sensor.py @@ -5,7 +5,7 @@ from typing import Callable, Iterable from xknx.devices import Sensor as XknxSensor -from homeassistant.components.sensor import DEVICE_CLASSES +from homeassistant.components.sensor import DEVICE_CLASSES, SensorEntity from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ( ConfigType, @@ -32,7 +32,7 @@ async def async_setup_platform( async_add_entities(entities) -class KNXSensor(KnxEntity, Entity): +class KNXSensor(KnxEntity, SensorEntity): """Representation of a KNX sensor.""" def __init__(self, device: XknxSensor) -> None: diff --git a/homeassistant/components/konnected/sensor.py b/homeassistant/components/konnected/sensor.py index dece8d06c87..18975bdb467 100644 --- a/homeassistant/components/konnected/sensor.py +++ b/homeassistant/components/konnected/sensor.py @@ -1,4 +1,5 @@ """Support for DHT and DS18B20 sensors attached to a Konnected device.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONF_DEVICES, CONF_NAME, @@ -12,7 +13,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from .const import DOMAIN as KONNECTED_DOMAIN, SIGNAL_DS18B20_NEW @@ -70,7 +70,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_dispatcher_connect(hass, SIGNAL_DS18B20_NEW, async_add_ds18b20) -class KonnectedSensor(Entity): +class KonnectedSensor(SensorEntity): """Represents a Konnected DHT Sensor.""" def __init__(self, device_id, data, sensor_type, addr=None, initial_state=None): diff --git a/homeassistant/components/kwb/sensor.py b/homeassistant/components/kwb/sensor.py index bd0430f9786..eb96b206653 100644 --- a/homeassistant/components/kwb/sensor.py +++ b/homeassistant/components/kwb/sensor.py @@ -2,7 +2,7 @@ from pykwb import kwb import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_DEVICE, CONF_HOST, @@ -11,7 +11,6 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity DEFAULT_RAW = False DEFAULT_NAME = "KWB" @@ -74,7 +73,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class KWBSensor(Entity): +class KWBSensor(SensorEntity): """Representation of a KWB Easyfire sensor.""" def __init__(self, easyfire, sensor, client_name): diff --git a/homeassistant/components/lacrosse/sensor.py b/homeassistant/components/lacrosse/sensor.py index 35cdcecddba..32090797f11 100644 --- a/homeassistant/components/lacrosse/sensor.py +++ b/homeassistant/components/lacrosse/sensor.py @@ -6,7 +6,11 @@ import pylacrosse from serial import SerialException import voluptuous as vol -from homeassistant.components.sensor import ENTITY_ID_FORMAT, PLATFORM_SCHEMA +from homeassistant.components.sensor import ( + ENTITY_ID_FORMAT, + PLATFORM_SCHEMA, + SensorEntity, +) from homeassistant.const import ( CONF_DEVICE, CONF_ID, @@ -19,7 +23,7 @@ from homeassistant.const import ( ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity, async_generate_entity_id +from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util import dt as dt_util @@ -108,7 +112,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class LaCrosseSensor(Entity): +class LaCrosseSensor(SensorEntity): """Implementation of a Lacrosse sensor.""" _temperature = None diff --git a/homeassistant/components/lastfm/sensor.py b/homeassistant/components/lastfm/sensor.py index 2f599ad37fd..128450826d6 100644 --- a/homeassistant/components/lastfm/sensor.py +++ b/homeassistant/components/lastfm/sensor.py @@ -7,10 +7,9 @@ import pylast as lastfm from pylast import WSError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -52,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities, True) -class LastfmSensor(Entity): +class LastfmSensor(SensorEntity): """A class for the Last.fm account.""" def __init__(self, user, lastfm_api): diff --git a/homeassistant/components/launch_library/sensor.py b/homeassistant/components/launch_library/sensor.py index 7663bf86fc5..831e44dca8f 100644 --- a/homeassistant/components/launch_library/sensor.py +++ b/homeassistant/components/launch_library/sensor.py @@ -7,11 +7,10 @@ import logging from pylaunches import PyLaunches, PyLaunchesException import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from .const import ( ATTR_AGENCY, @@ -40,7 +39,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([LaunchLibrarySensor(launches, name)], True) -class LaunchLibrarySensor(Entity): +class LaunchLibrarySensor(SensorEntity): """Representation of a launch_library Sensor.""" def __init__(self, launches: PyLaunches, name: str) -> None: diff --git a/homeassistant/components/lcn/sensor.py b/homeassistant/components/lcn/sensor.py index 510df46cd1e..64870e22e4c 100644 --- a/homeassistant/components/lcn/sensor.py +++ b/homeassistant/components/lcn/sensor.py @@ -2,7 +2,7 @@ import pypck -from homeassistant.components.sensor import DOMAIN as DOMAIN_SENSOR +from homeassistant.components.sensor import DOMAIN as DOMAIN_SENSOR, SensorEntity from homeassistant.const import ( CONF_ADDRESS, CONF_DOMAIN, @@ -51,7 +51,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class LcnVariableSensor(LcnEntity): +class LcnVariableSensor(LcnEntity, SensorEntity): """Representation of a LCN sensor for variables.""" def __init__(self, config, entry_id, device_connection): @@ -99,7 +99,7 @@ class LcnVariableSensor(LcnEntity): self.async_write_ha_state() -class LcnLedLogicSensor(LcnEntity): +class LcnLedLogicSensor(LcnEntity, SensorEntity): """Representation of a LCN sensor for leds and logicops.""" def __init__(self, config, entry_id, device_connection): diff --git a/homeassistant/components/lightwave/sensor.py b/homeassistant/components/lightwave/sensor.py index 2144979106a..1128078f8bc 100644 --- a/homeassistant/components/lightwave/sensor.py +++ b/homeassistant/components/lightwave/sensor.py @@ -1,6 +1,6 @@ """Support for LightwaveRF TRV - Associated Battery.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME, DEVICE_CLASS_BATTERY, PERCENTAGE -from homeassistant.helpers.entity import Entity from . import CONF_SERIAL, LIGHTWAVE_LINK @@ -22,7 +22,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(batteries) -class LightwaveBattery(Entity): +class LightwaveBattery(SensorEntity): """Lightwave TRV Battery.""" def __init__(self, name, lwlink, serial): diff --git a/homeassistant/components/linux_battery/sensor.py b/homeassistant/components/linux_battery/sensor.py index feefa34a7a7..b7746392cee 100644 --- a/homeassistant/components/linux_battery/sensor.py +++ b/homeassistant/components/linux_battery/sensor.py @@ -5,10 +5,9 @@ import os from batinfo import Batteries import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_NAME, CONF_NAME, DEVICE_CLASS_BATTERY, PERCENTAGE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -68,7 +67,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([LinuxBatterySensor(name, battery_id, system)], True) -class LinuxBatterySensor(Entity): +class LinuxBatterySensor(SensorEntity): """Representation of a Linux Battery sensor.""" def __init__(self, name, battery_id, system): diff --git a/homeassistant/components/litterrobot/sensor.py b/homeassistant/components/litterrobot/sensor.py index 8ae512fa801..8038fdbb2cb 100644 --- a/homeassistant/components/litterrobot/sensor.py +++ b/homeassistant/components/litterrobot/sensor.py @@ -3,8 +3,8 @@ from __future__ import annotations from pylitterbot.robot import Robot +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_TIMESTAMP, PERCENTAGE -from homeassistant.helpers.entity import Entity from .const import DOMAIN from .hub import LitterRobotEntity, LitterRobotHub @@ -21,7 +21,7 @@ def icon_for_gauge_level(gauge_level: int | None = None, offset: int = 0) -> str return "mdi:gauge-low" -class LitterRobotPropertySensor(LitterRobotEntity, Entity): +class LitterRobotPropertySensor(LitterRobotEntity, SensorEntity): """Litter-Robot property sensors.""" def __init__( @@ -37,7 +37,7 @@ class LitterRobotPropertySensor(LitterRobotEntity, Entity): return getattr(self.robot, self.sensor_attribute) -class LitterRobotWasteSensor(LitterRobotPropertySensor, Entity): +class LitterRobotWasteSensor(LitterRobotPropertySensor): """Litter-Robot sensors.""" @property @@ -51,7 +51,7 @@ class LitterRobotWasteSensor(LitterRobotPropertySensor, Entity): return icon_for_gauge_level(self.state, 10) -class LitterRobotSleepTimeSensor(LitterRobotPropertySensor, Entity): +class LitterRobotSleepTimeSensor(LitterRobotPropertySensor): """Litter-Robot sleep time sensors.""" @property diff --git a/homeassistant/components/local_ip/sensor.py b/homeassistant/components/local_ip/sensor.py index d159b641fa2..1d2cce72105 100644 --- a/homeassistant/components/local_ip/sensor.py +++ b/homeassistant/components/local_ip/sensor.py @@ -1,7 +1,7 @@ """Sensor platform for local_ip.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME -from homeassistant.helpers.entity import Entity from homeassistant.util import get_local_ip from .const import DOMAIN, SENSOR @@ -13,7 +13,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities([IPSensor(name)], True) -class IPSensor(Entity): +class IPSensor(SensorEntity): """A simple sensor.""" def __init__(self, name): diff --git a/homeassistant/components/logi_circle/sensor.py b/homeassistant/components/logi_circle/sensor.py index 3d980b1dae3..29cd6e28e1c 100644 --- a/homeassistant/components/logi_circle/sensor.py +++ b/homeassistant/components/logi_circle/sensor.py @@ -1,6 +1,7 @@ """Support for Logi Circle sensors.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_BATTERY_CHARGING, @@ -9,7 +10,6 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, ) -from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.util.dt import as_local @@ -42,7 +42,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensors, True) -class LogiSensor(Entity): +class LogiSensor(SensorEntity): """A sensor implementation for a Logi Circle camera.""" def __init__(self, camera, time_zone, sensor_type): diff --git a/homeassistant/components/london_air/sensor.py b/homeassistant/components/london_air/sensor.py index c39d77585a8..23eea5c00e0 100644 --- a/homeassistant/components/london_air/sensor.py +++ b/homeassistant/components/london_air/sensor.py @@ -5,10 +5,9 @@ import logging import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import HTTP_OK import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -90,7 +89,7 @@ class APIData: self.data = parse_api_response(response.json()) -class AirSensor(Entity): +class AirSensor(SensorEntity): """Single authority air sensor.""" ICON = "mdi:cloud-outline" diff --git a/homeassistant/components/london_underground/sensor.py b/homeassistant/components/london_underground/sensor.py index acb605901c9..b7f2cb50cbf 100644 --- a/homeassistant/components/london_underground/sensor.py +++ b/homeassistant/components/london_underground/sensor.py @@ -4,10 +4,9 @@ from datetime import timedelta from london_tube_status import TubeData import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity ATTRIBUTION = "Powered by TfL Open Data" @@ -51,7 +50,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class LondonTubeSensor(Entity): +class LondonTubeSensor(SensorEntity): """Sensor that reads the status of a line from Tube Data.""" def __init__(self, name, data): diff --git a/homeassistant/components/loopenergy/sensor.py b/homeassistant/components/loopenergy/sensor.py index 85494d354b5..78e55f22eb8 100644 --- a/homeassistant/components/loopenergy/sensor.py +++ b/homeassistant/components/loopenergy/sensor.py @@ -4,14 +4,13 @@ import logging import pyloopenergy import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC, EVENT_HOMEASSISTANT_STOP, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -82,7 +81,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class LoopEnergyDevice(Entity): +class LoopEnergySensor(SensorEntity): """Implementation of an Loop Energy base sensor.""" def __init__(self, controller): @@ -116,7 +115,7 @@ class LoopEnergyDevice(Entity): self.schedule_update_ha_state(True) -class LoopEnergyElec(LoopEnergyDevice): +class LoopEnergyElec(LoopEnergySensor): """Implementation of an Loop Energy Electricity sensor.""" def __init__(self, controller): @@ -133,7 +132,7 @@ class LoopEnergyElec(LoopEnergyDevice): self._state = round(self._controller.electricity_useage, 2) -class LoopEnergyGas(LoopEnergyDevice): +class LoopEnergyGas(LoopEnergySensor): """Implementation of an Loop Energy Gas sensor.""" def __init__(self, controller): diff --git a/homeassistant/components/luftdaten/sensor.py b/homeassistant/components/luftdaten/sensor.py index 5985c63801e..aec77961b94 100644 --- a/homeassistant/components/luftdaten/sensor.py +++ b/homeassistant/components/luftdaten/sensor.py @@ -1,6 +1,7 @@ """Support for Luftdaten sensors.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, @@ -9,7 +10,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from . import ( DATA_LUFTDATEN, @@ -45,7 +45,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensors, True) -class LuftdatenSensor(Entity): +class LuftdatenSensor(SensorEntity): """Implementation of a Luftdaten sensor.""" def __init__(self, luftdaten, sensor_type, name, icon, unit, show): diff --git a/homeassistant/components/lyft/sensor.py b/homeassistant/components/lyft/sensor.py index 872281f685c..c979231a216 100644 --- a/homeassistant/components/lyft/sensor.py +++ b/homeassistant/components/lyft/sensor.py @@ -7,10 +7,9 @@ from lyft_rides.client import LyftRidesClient from lyft_rides.errors import APIError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, TIME_MINUTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -74,7 +73,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class LyftSensor(Entity): +class LyftSensor(SensorEntity): """Implementation of an Lyft sensor.""" def __init__(self, sensorType, products, product_id, product): diff --git a/homeassistant/components/lyric/sensor.py b/homeassistant/components/lyric/sensor.py index c4950f119d9..db90f474124 100644 --- a/homeassistant/components/lyric/sensor.py +++ b/homeassistant/components/lyric/sensor.py @@ -4,6 +4,7 @@ from datetime import datetime, timedelta from aiolyric.objects.device import LyricDevice from aiolyric.objects.location import LyricLocation +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, @@ -67,7 +68,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class LyricSensor(LyricDeviceEntity): +class LyricSensor(LyricDeviceEntity, SensorEntity): """Defines a Honeywell Lyric sensor.""" def __init__( diff --git a/homeassistant/components/magicseaweed/sensor.py b/homeassistant/components/magicseaweed/sensor.py index 406f5e53955..0dd27a60ae0 100644 --- a/homeassistant/components/magicseaweed/sensor.py +++ b/homeassistant/components/magicseaweed/sensor.py @@ -5,7 +5,7 @@ import logging import magicseaweed import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -13,7 +13,6 @@ from homeassistant.const import ( CONF_NAME, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.util.dt as dt_util @@ -90,7 +89,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class MagicSeaweedSensor(Entity): +class MagicSeaweedSensor(SensorEntity): """Implementation of a MagicSeaweed sensor.""" def __init__(self, forecast_data, sensor_type, name, unit_system, hour=None): diff --git a/homeassistant/components/mazda/sensor.py b/homeassistant/components/mazda/sensor.py index a05291673e8..7382347e6de 100644 --- a/homeassistant/components/mazda/sensor.py +++ b/homeassistant/components/mazda/sensor.py @@ -1,4 +1,5 @@ """Platform for Mazda sensor integration.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONF_UNIT_SYSTEM_IMPERIAL, LENGTH_KILOMETERS, @@ -29,7 +30,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class MazdaFuelRemainingSensor(MazdaEntity): +class MazdaFuelRemainingSensor(MazdaEntity, SensorEntity): """Class for the fuel remaining sensor.""" @property @@ -59,7 +60,7 @@ class MazdaFuelRemainingSensor(MazdaEntity): return self.coordinator.data[self.index]["status"]["fuelRemainingPercent"] -class MazdaFuelDistanceSensor(MazdaEntity): +class MazdaFuelDistanceSensor(MazdaEntity, SensorEntity): """Class for the fuel distance sensor.""" @property @@ -100,7 +101,7 @@ class MazdaFuelDistanceSensor(MazdaEntity): ) -class MazdaOdometerSensor(MazdaEntity): +class MazdaOdometerSensor(MazdaEntity, SensorEntity): """Class for the odometer sensor.""" @property @@ -137,7 +138,7 @@ class MazdaOdometerSensor(MazdaEntity): ) -class MazdaFrontLeftTirePressureSensor(MazdaEntity): +class MazdaFrontLeftTirePressureSensor(MazdaEntity, SensorEntity): """Class for the front left tire pressure sensor.""" @property @@ -170,7 +171,7 @@ class MazdaFrontLeftTirePressureSensor(MazdaEntity): return None if tire_pressure is None else round(tire_pressure) -class MazdaFrontRightTirePressureSensor(MazdaEntity): +class MazdaFrontRightTirePressureSensor(MazdaEntity, SensorEntity): """Class for the front right tire pressure sensor.""" @property @@ -203,7 +204,7 @@ class MazdaFrontRightTirePressureSensor(MazdaEntity): return None if tire_pressure is None else round(tire_pressure) -class MazdaRearLeftTirePressureSensor(MazdaEntity): +class MazdaRearLeftTirePressureSensor(MazdaEntity, SensorEntity): """Class for the rear left tire pressure sensor.""" @property @@ -236,7 +237,7 @@ class MazdaRearLeftTirePressureSensor(MazdaEntity): return None if tire_pressure is None else round(tire_pressure) -class MazdaRearRightTirePressureSensor(MazdaEntity): +class MazdaRearRightTirePressureSensor(MazdaEntity, SensorEntity): """Class for the rear right tire pressure sensor.""" @property diff --git a/homeassistant/components/melcloud/sensor.py b/homeassistant/components/melcloud/sensor.py index bd85d1b13bc..356992ece11 100644 --- a/homeassistant/components/melcloud/sensor.py +++ b/homeassistant/components/melcloud/sensor.py @@ -2,6 +2,7 @@ from pymelcloud import DEVICE_TYPE_ATA, DEVICE_TYPE_ATW from pymelcloud.atw_device import Zone +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_ICON, @@ -9,7 +10,6 @@ from homeassistant.const import ( ENERGY_KILO_WATT_HOUR, TEMP_CELSIUS, ) -from homeassistant.helpers.entity import Entity from . import MelCloudDevice from .const import DOMAIN @@ -110,7 +110,7 @@ async def async_setup_entry(hass, entry, async_add_entities): ) -class MelDeviceSensor(Entity): +class MelDeviceSensor(SensorEntity): """Representation of a Sensor.""" def __init__(self, api: MelCloudDevice, measurement, definition): diff --git a/homeassistant/components/meteo_france/sensor.py b/homeassistant/components/meteo_france/sensor.py index 74b4ab5a6d1..b6ec221a97e 100644 --- a/homeassistant/components/meteo_france/sensor.py +++ b/homeassistant/components/meteo_france/sensor.py @@ -6,6 +6,7 @@ from meteofrance_api.helpers import ( readeable_phenomenoms_dict, ) +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.helpers.typing import HomeAssistantType @@ -74,7 +75,7 @@ async def async_setup_entry( ) -class MeteoFranceSensor(CoordinatorEntity): +class MeteoFranceSensor(CoordinatorEntity, SensorEntity): """Representation of a Meteo-France sensor.""" def __init__(self, sensor_type: str, coordinator: DataUpdateCoordinator): diff --git a/homeassistant/components/metoffice/sensor.py b/homeassistant/components/metoffice/sensor.py index 2f4dd72b3a1..6a7cf5254a6 100644 --- a/homeassistant/components/metoffice/sensor.py +++ b/homeassistant/components/metoffice/sensor.py @@ -1,4 +1,5 @@ """Support for UK Met Office weather service.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, DEVICE_CLASS_HUMIDITY, @@ -10,7 +11,6 @@ from homeassistant.const import ( UV_INDEX, ) from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType, HomeAssistantType from .const import ( @@ -92,7 +92,7 @@ async def async_setup_entry( ) -class MetOfficeCurrentSensor(Entity): +class MetOfficeCurrentSensor(SensorEntity): """Implementation of a Met Office current weather condition sensor.""" def __init__(self, entry_data, hass_data, sensor_type): diff --git a/homeassistant/components/mfi/sensor.py b/homeassistant/components/mfi/sensor.py index 671a52bbf01..c7a64f17bd6 100644 --- a/homeassistant/components/mfi/sensor.py +++ b/homeassistant/components/mfi/sensor.py @@ -5,7 +5,7 @@ from mficlient.client import FailedToLogin, MFiClient import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -18,7 +18,6 @@ from homeassistant.const import ( TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -74,7 +73,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class MfiSensor(Entity): +class MfiSensor(SensorEntity): """Representation of a mFi sensor.""" def __init__(self, port, hass): diff --git a/homeassistant/components/mhz19/sensor.py b/homeassistant/components/mhz19/sensor.py index f26481f9d25..0f0735dd5da 100644 --- a/homeassistant/components/mhz19/sensor.py +++ b/homeassistant/components/mhz19/sensor.py @@ -5,7 +5,7 @@ import logging from pmsensor import co2sensor import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_TEMPERATURE, CONCENTRATION_PARTS_PER_MILLION, @@ -14,7 +14,6 @@ from homeassistant.const import ( TEMP_FAHRENHEIT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.temperature import celsius_to_fahrenheit @@ -69,7 +68,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class MHZ19Sensor(Entity): +class MHZ19Sensor(SensorEntity): """Representation of an CO2 sensor.""" def __init__(self, mhz_client, sensor_type, temp_unit, name): diff --git a/homeassistant/components/miflora/sensor.py b/homeassistant/components/miflora/sensor.py index 8db81ecf5dd..0c22a943bb3 100644 --- a/homeassistant/components/miflora/sensor.py +++ b/homeassistant/components/miflora/sensor.py @@ -8,7 +8,7 @@ from btlewrap import BluetoothBackendException from miflora import miflora_poller import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONDUCTIVITY, CONF_FORCE_UPDATE, @@ -27,7 +27,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util from homeassistant.util.temperature import celsius_to_fahrenheit @@ -130,7 +129,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devs) -class MiFloraSensor(Entity): +class MiFloraSensor(SensorEntity): """Implementing the MiFlora sensor.""" def __init__( diff --git a/homeassistant/components/min_max/sensor.py b/homeassistant/components/min_max/sensor.py index 5bea9379690..a42291b3f67 100644 --- a/homeassistant/components/min_max/sensor.py +++ b/homeassistant/components/min_max/sensor.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, @@ -13,7 +13,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.reload import async_setup_reload_service @@ -131,7 +130,7 @@ def calc_median(sensor_values, round_digits): return round(median, round_digits) -class MinMaxSensor(Entity): +class MinMaxSensor(SensorEntity): """Representation of a min/max sensor.""" def __init__(self, entity_ids, name, sensor_type, round_digits): diff --git a/homeassistant/components/minecraft_server/sensor.py b/homeassistant/components/minecraft_server/sensor.py index 4cc9e9746bb..ded21f2935f 100644 --- a/homeassistant/components/minecraft_server/sensor.py +++ b/homeassistant/components/minecraft_server/sensor.py @@ -3,6 +3,7 @@ from __future__ import annotations from typing import Any +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import TIME_MILLISECONDS from homeassistant.helpers.typing import HomeAssistantType @@ -47,7 +48,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class MinecraftServerSensorEntity(MinecraftServerEntity): +class MinecraftServerSensorEntity(MinecraftServerEntity, SensorEntity): """Representation of a Minecraft Server sensor base entity.""" def __init__( diff --git a/homeassistant/components/mitemp_bt/sensor.py b/homeassistant/components/mitemp_bt/sensor.py index 244a0c410d5..670a6daf3d3 100644 --- a/homeassistant/components/mitemp_bt/sensor.py +++ b/homeassistant/components/mitemp_bt/sensor.py @@ -6,7 +6,7 @@ from btlewrap.base import BluetoothBackendException from mitemp_bt import mitemp_bt_poller import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_FORCE_UPDATE, CONF_MAC, @@ -20,7 +20,6 @@ from homeassistant.const import ( TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity try: import bluepy.btle # noqa: F401 pylint: disable=unused-import @@ -104,7 +103,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devs) -class MiTempBtSensor(Entity): +class MiTempBtSensor(SensorEntity): """Implementing the MiTempBt sensor.""" def __init__(self, poller, parameter, device, name, unit, force_update, median): diff --git a/homeassistant/components/mobile_app/sensor.py b/homeassistant/components/mobile_app/sensor.py index b09ef86453b..3f4c7d56f3f 100644 --- a/homeassistant/components/mobile_app/sensor.py +++ b/homeassistant/components/mobile_app/sensor.py @@ -1,6 +1,7 @@ """Sensor platform for mobile_app.""" from functools import partial +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID, CONF_WEBHOOK_ID from homeassistant.core import callback from homeassistant.helpers import entity_registry as er @@ -71,7 +72,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class MobileAppSensor(MobileAppEntity): +class MobileAppSensor(MobileAppEntity, SensorEntity): """Representation of an mobile app sensor.""" @property diff --git a/homeassistant/components/modbus/sensor.py b/homeassistant/components/modbus/sensor.py index d0fad973802..c1f5487bd5d 100644 --- a/homeassistant/components/modbus/sensor.py +++ b/homeassistant/components/modbus/sensor.py @@ -9,7 +9,11 @@ from pymodbus.exceptions import ConnectionException, ModbusException from pymodbus.pdu import ExceptionResponse import voluptuous as vol -from homeassistant.components.sensor import DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA +from homeassistant.components.sensor import ( + DEVICE_CLASSES_SCHEMA, + PLATFORM_SCHEMA, + SensorEntity, +) from homeassistant.const import ( CONF_DEVICE_CLASS, CONF_NAME, @@ -158,7 +162,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class ModbusRegisterSensor(RestoreEntity): +class ModbusRegisterSensor(RestoreEntity, SensorEntity): """Modbus register sensor.""" def __init__( diff --git a/homeassistant/components/modem_callerid/sensor.py b/homeassistant/components/modem_callerid/sensor.py index f91b6d7a169..080e077a457 100644 --- a/homeassistant/components/modem_callerid/sensor.py +++ b/homeassistant/components/modem_callerid/sensor.py @@ -4,7 +4,7 @@ import logging from basicmodem.basicmodem import BasicModem as bm import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_DEVICE, CONF_NAME, @@ -12,7 +12,6 @@ from homeassistant.const import ( STATE_IDLE, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Modem CallerID" @@ -44,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([ModemCalleridSensor(hass, name, port, modem)]) -class ModemCalleridSensor(Entity): +class ModemCalleridSensor(SensorEntity): """Implementation of USB modem caller ID sensor.""" def __init__(self, hass, name, port, modem): diff --git a/homeassistant/components/mold_indicator/sensor.py b/homeassistant/components/mold_indicator/sensor.py index 126b57cb412..7bfa161f9ec 100644 --- a/homeassistant/components/mold_indicator/sensor.py +++ b/homeassistant/components/mold_indicator/sensor.py @@ -5,7 +5,7 @@ import math import voluptuous as vol from homeassistant import util -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, @@ -17,7 +17,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change_event _LOGGER = logging.getLogger(__name__) @@ -69,7 +68,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class MoldIndicator(Entity): +class MoldIndicator(SensorEntity): """Represents a MoldIndication sensor.""" def __init__( diff --git a/homeassistant/components/moon/sensor.py b/homeassistant/components/moon/sensor.py index 9e0f8ef51d6..4b373469cc6 100644 --- a/homeassistant/components/moon/sensor.py +++ b/homeassistant/components/moon/sensor.py @@ -2,10 +2,9 @@ from astral import Astral import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util DEFAULT_NAME = "Moon" @@ -42,7 +41,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([MoonSensor(name)], True) -class MoonSensor(Entity): +class MoonSensor(SensorEntity): """Representation of a Moon sensor.""" def __init__(self, name): diff --git a/homeassistant/components/motion_blinds/sensor.py b/homeassistant/components/motion_blinds/sensor.py index 9f80c85b959..5d7a29bf9da 100644 --- a/homeassistant/components/motion_blinds/sensor.py +++ b/homeassistant/components/motion_blinds/sensor.py @@ -1,13 +1,13 @@ """Support for Motion Blinds sensors.""" from motionblinds import BlindType +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_SIGNAL_STRENGTH, PERCENTAGE, SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ) -from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN, KEY_COORDINATOR, KEY_GATEWAY @@ -39,7 +39,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class MotionBatterySensor(CoordinatorEntity, Entity): +class MotionBatterySensor(CoordinatorEntity, SensorEntity): """ Representation of a Motion Battery Sensor. @@ -144,7 +144,7 @@ class MotionTDBUBatterySensor(MotionBatterySensor): return attributes -class MotionSignalStrengthSensor(CoordinatorEntity, Entity): +class MotionSignalStrengthSensor(CoordinatorEntity, SensorEntity): """Representation of a Motion Signal Strength Sensor.""" def __init__(self, coordinator, device, device_type): diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index b20595922cd..65c9e0550e0 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -7,7 +7,7 @@ import functools import voluptuous as vol from homeassistant.components import sensor -from homeassistant.components.sensor import DEVICE_CLASSES_SCHEMA +from homeassistant.components.sensor import DEVICE_CLASSES_SCHEMA, SensorEntity from homeassistant.const import ( CONF_DEVICE_CLASS, CONF_FORCE_UPDATE, @@ -17,7 +17,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.typing import ConfigType, HomeAssistantType @@ -71,7 +70,7 @@ async def _async_setup_entity( async_add_entities([MqttSensor(hass, config, config_entry, discovery_data)]) -class MqttSensor(MqttEntity, Entity): +class MqttSensor(MqttEntity, SensorEntity): """Representation of a sensor that can be updated using MQTT.""" def __init__(self, hass, config, config_entry, discovery_data): diff --git a/homeassistant/components/mqtt_room/sensor.py b/homeassistant/components/mqtt_room/sensor.py index e15dfd179da..e446ab8ba7a 100644 --- a/homeassistant/components/mqtt_room/sensor.py +++ b/homeassistant/components/mqtt_room/sensor.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.components import mqtt from homeassistant.components.mqtt import CONF_STATE_TOPIC -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_DEVICE_ID, ATTR_ID, @@ -18,7 +18,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import dt, slugify _LOGGER = logging.getLogger(__name__) @@ -71,7 +70,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class MQTTRoomSensor(Entity): +class MQTTRoomSensor(SensorEntity): """Representation of a room sensor that is updated via MQTT.""" def __init__(self, name, state_topic, device_id, timeout, consider_home): diff --git a/homeassistant/components/mvglive/sensor.py b/homeassistant/components/mvglive/sensor.py index 15b0d2d218e..16b061a3346 100644 --- a/homeassistant/components/mvglive/sensor.py +++ b/homeassistant/components/mvglive/sensor.py @@ -6,10 +6,9 @@ import logging import MVGLive import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, TIME_MINUTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -78,7 +77,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class MVGLiveSensor(Entity): +class MVGLiveSensor(SensorEntity): """Implementation of an MVG Live sensor.""" def __init__( diff --git a/homeassistant/components/mychevy/sensor.py b/homeassistant/components/mychevy/sensor.py index 832f1a03f25..18b5e95d838 100644 --- a/homeassistant/components/mychevy/sensor.py +++ b/homeassistant/components/mychevy/sensor.py @@ -1,10 +1,9 @@ """Support for MyChevy sensors.""" import logging -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, SensorEntity from homeassistant.const import PERCENTAGE from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.util import slugify @@ -46,7 +45,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class MyChevyStatus(Entity): +class MyChevyStatus(SensorEntity): """A string representing the charge mode.""" _name = "MyChevy Status" @@ -109,7 +108,7 @@ class MyChevyStatus(Entity): return False -class EVSensor(Entity): +class EVSensor(SensorEntity): """Base EVSensor class. The only real difference between sensors is which units and what diff --git a/homeassistant/components/mysensors/sensor.py b/homeassistant/components/mysensors/sensor.py index a09f8af1394..a62318aea53 100644 --- a/homeassistant/components/mysensors/sensor.py +++ b/homeassistant/components/mysensors/sensor.py @@ -4,7 +4,7 @@ from typing import Callable from homeassistant.components import mysensors from homeassistant.components.mysensors import on_unload from homeassistant.components.mysensors.const import MYSENSORS_DISCOVERY -from homeassistant.components.sensor import DOMAIN +from homeassistant.components.sensor import DOMAIN, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONDUCTIVITY, @@ -87,7 +87,7 @@ async def async_setup_entry( ) -class MySensorsSensor(mysensors.device.MySensorsEntity): +class MySensorsSensor(mysensors.device.MySensorsEntity, SensorEntity): """Representation of a MySensors Sensor child node.""" @property From 18e6816373cf9dbc6fecef4f6bcb81656679a8a1 Mon Sep 17 00:00:00 2001 From: plomosits Date: Mon, 22 Mar 2021 20:03:57 +0100 Subject: [PATCH 555/831] Improve Docker and Kubernetes support for KNX (#48065) Co-authored-by: Matthias Alphart --- homeassistant/components/knx/__init__.py | 4 ++++ homeassistant/components/knx/schema.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index c252572e28e..8f363ac70d1 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -368,11 +368,15 @@ class KNXModule: local_ip = self.config[DOMAIN][CONF_KNX_TUNNELING].get( ConnectionSchema.CONF_KNX_LOCAL_IP ) + route_back = self.config[DOMAIN][CONF_KNX_TUNNELING][ + ConnectionSchema.CONF_KNX_ROUTE_BACK + ] return ConnectionConfig( connection_type=ConnectionType.TUNNELING, gateway_ip=gateway_ip, gateway_port=gateway_port, local_ip=local_ip, + route_back=route_back, auto_reconnect=True, ) diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index 6ff9d295b75..bfb8ba62c39 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -59,12 +59,14 @@ class ConnectionSchema: """Voluptuous schema for KNX connection.""" CONF_KNX_LOCAL_IP = "local_ip" + CONF_KNX_ROUTE_BACK = "route_back" TUNNELING_SCHEMA = vol.Schema( { vol.Optional(CONF_PORT, default=DEFAULT_MCAST_PORT): cv.port, vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_KNX_LOCAL_IP): cv.string, + vol.Optional(CONF_KNX_ROUTE_BACK, default=False): cv.boolean, } ) From 9e9ba53f0ec7f1e6dbda2c96186104ec571a3e91 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 20:05:13 +0100 Subject: [PATCH 556/831] Move SensorEntity last in the inheritance tree (#48230) --- homeassistant/components/abode/sensor.py | 2 +- homeassistant/components/accuweather/sensor.py | 2 +- homeassistant/components/acmeda/sensor.py | 2 +- homeassistant/components/adguard/sensor.py | 2 +- homeassistant/components/ads/sensor.py | 2 +- homeassistant/components/advantage_air/sensor.py | 6 +++--- homeassistant/components/aemet/sensor.py | 4 ++-- homeassistant/components/airly/sensor.py | 2 +- homeassistant/components/airnow/sensor.py | 2 +- homeassistant/components/airvisual/sensor.py | 4 ++-- homeassistant/components/ambient_station/sensor.py | 2 +- homeassistant/components/android_ip_webcam/sensor.py | 2 +- homeassistant/components/asuswrt/sensor.py | 2 +- homeassistant/components/atag/sensor.py | 2 +- homeassistant/components/august/sensor.py | 4 ++-- homeassistant/components/aurora/sensor.py | 2 +- homeassistant/components/awair/sensor.py | 2 +- homeassistant/components/azure_devops/sensor.py | 2 +- homeassistant/components/blebox/sensor.py | 2 +- homeassistant/components/bmw_connected_drive/sensor.py | 2 +- homeassistant/components/brother/sensor.py | 2 +- homeassistant/components/canary/sensor.py | 2 +- homeassistant/components/cert_expiry/sensor.py | 2 +- homeassistant/components/coronavirus/sensor.py | 2 +- homeassistant/components/deconz/sensor.py | 4 ++-- homeassistant/components/derivative/sensor.py | 2 +- homeassistant/components/devolo_home_control/sensor.py | 2 +- homeassistant/components/dexcom/sensor.py | 4 ++-- homeassistant/components/dyson/sensor.py | 2 +- homeassistant/components/eafm/sensor.py | 2 +- homeassistant/components/econet/sensor.py | 2 +- homeassistant/components/eight_sleep/sensor.py | 6 +++--- homeassistant/components/elkm1/sensor.py | 2 +- homeassistant/components/enocean/sensor.py | 2 +- homeassistant/components/enphase_envoy/sensor.py | 2 +- homeassistant/components/envisalink/sensor.py | 2 +- homeassistant/components/esphome/sensor.py | 4 ++-- 37 files changed, 47 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/abode/sensor.py b/homeassistant/components/abode/sensor.py index 993228c36ac..e3ececb62d9 100644 --- a/homeassistant/components/abode/sensor.py +++ b/homeassistant/components/abode/sensor.py @@ -34,7 +34,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class AbodeSensor(SensorEntity, AbodeDevice): +class AbodeSensor(AbodeDevice, SensorEntity): """A sensor implementation for Abode devices.""" def __init__(self, data, device, sensor_type): diff --git a/homeassistant/components/accuweather/sensor.py b/homeassistant/components/accuweather/sensor.py index 89147cc7104..722dd8869be 100644 --- a/homeassistant/components/accuweather/sensor.py +++ b/homeassistant/components/accuweather/sensor.py @@ -49,7 +49,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class AccuWeatherSensor(SensorEntity, CoordinatorEntity): +class AccuWeatherSensor(CoordinatorEntity, SensorEntity): """Define an AccuWeather entity.""" def __init__(self, name, kind, coordinator, forecast_day=None): diff --git a/homeassistant/components/acmeda/sensor.py b/homeassistant/components/acmeda/sensor.py index f1961b85589..4f617c5726f 100644 --- a/homeassistant/components/acmeda/sensor.py +++ b/homeassistant/components/acmeda/sensor.py @@ -30,7 +30,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class AcmedaBattery(SensorEntity, AcmedaBase): +class AcmedaBattery(AcmedaBase, SensorEntity): """Representation of a Acmeda cover device.""" device_class = DEVICE_CLASS_BATTERY diff --git a/homeassistant/components/adguard/sensor.py b/homeassistant/components/adguard/sensor.py index 6f2f0f54beb..dd0400b6592 100644 --- a/homeassistant/components/adguard/sensor.py +++ b/homeassistant/components/adguard/sensor.py @@ -49,7 +49,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class AdGuardHomeSensor(SensorEntity, AdGuardHomeDeviceEntity): +class AdGuardHomeSensor(AdGuardHomeDeviceEntity, SensorEntity): """Defines a AdGuard Home sensor.""" def __init__( diff --git a/homeassistant/components/ads/sensor.py b/homeassistant/components/ads/sensor.py index 6e7c69d3c2d..933950dcf1b 100644 --- a/homeassistant/components/ads/sensor.py +++ b/homeassistant/components/ads/sensor.py @@ -43,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([entity]) -class AdsSensor(SensorEntity, AdsEntity): +class AdsSensor(AdsEntity, SensorEntity): """Representation of an ADS sensor entity.""" def __init__(self, ads_hub, ads_var, ads_type, name, unit_of_measurement, factor): diff --git a/homeassistant/components/advantage_air/sensor.py b/homeassistant/components/advantage_air/sensor.py index cef70ebb245..19ac584ac2f 100644 --- a/homeassistant/components/advantage_air/sensor.py +++ b/homeassistant/components/advantage_air/sensor.py @@ -41,7 +41,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class AdvantageAirTimeTo(SensorEntity, AdvantageAirEntity): +class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity): """Representation of Advantage Air timer control.""" def __init__(self, instance, ac_key, action): @@ -83,7 +83,7 @@ class AdvantageAirTimeTo(SensorEntity, AdvantageAirEntity): await self.async_change({self.ac_key: {"info": {self._time_key: value}}}) -class AdvantageAirZoneVent(SensorEntity, AdvantageAirEntity): +class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity): """Representation of Advantage Air Zone Vent Sensor.""" @property @@ -116,7 +116,7 @@ class AdvantageAirZoneVent(SensorEntity, AdvantageAirEntity): return "mdi:fan-off" -class AdvantageAirZoneSignal(SensorEntity, AdvantageAirEntity): +class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity): """Representation of Advantage Air Zone wireless signal sensor.""" @property diff --git a/homeassistant/components/aemet/sensor.py b/homeassistant/components/aemet/sensor.py index 400952830cc..199b254cb1e 100644 --- a/homeassistant/components/aemet/sensor.py +++ b/homeassistant/components/aemet/sensor.py @@ -58,7 +58,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class AemetSensor(SensorEntity, AbstractAemetSensor): +class AemetSensor(AbstractAemetSensor, SensorEntity): """Implementation of an AEMET OpenData sensor.""" def __init__( @@ -81,7 +81,7 @@ class AemetSensor(SensorEntity, AbstractAemetSensor): return self._weather_coordinator.data.get(self._sensor_type) -class AemetForecastSensor(SensorEntity, AbstractAemetSensor): +class AemetForecastSensor(AbstractAemetSensor, SensorEntity): """Implementation of an AEMET OpenData forecast sensor.""" def __init__( diff --git a/homeassistant/components/airly/sensor.py b/homeassistant/components/airly/sensor.py index efadeac94e1..2a52050db15 100644 --- a/homeassistant/components/airly/sensor.py +++ b/homeassistant/components/airly/sensor.py @@ -73,7 +73,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class AirlySensor(SensorEntity, CoordinatorEntity): +class AirlySensor(CoordinatorEntity, SensorEntity): """Define an Airly sensor.""" def __init__(self, coordinator, name, kind): diff --git a/homeassistant/components/airnow/sensor.py b/homeassistant/components/airnow/sensor.py index f8f5eff6cc7..2d3adc8d1e2 100644 --- a/homeassistant/components/airnow/sensor.py +++ b/homeassistant/components/airnow/sensor.py @@ -60,7 +60,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class AirNowSensor(SensorEntity, CoordinatorEntity): +class AirNowSensor(CoordinatorEntity, SensorEntity): """Define an AirNow sensor.""" def __init__(self, coordinator, kind): diff --git a/homeassistant/components/airvisual/sensor.py b/homeassistant/components/airvisual/sensor.py index c4cbf3af22e..1febcec68f4 100644 --- a/homeassistant/components/airvisual/sensor.py +++ b/homeassistant/components/airvisual/sensor.py @@ -139,7 +139,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, True) -class AirVisualGeographySensor(SensorEntity, AirVisualEntity): +class AirVisualGeographySensor(AirVisualEntity, SensorEntity): """Define an AirVisual sensor related to geography data via the Cloud API.""" def __init__(self, coordinator, config_entry, kind, name, icon, unit, locale): @@ -237,7 +237,7 @@ class AirVisualGeographySensor(SensorEntity, AirVisualEntity): self._attrs.pop(ATTR_LONGITUDE, None) -class AirVisualNodeProSensor(SensorEntity, AirVisualEntity): +class AirVisualNodeProSensor(AirVisualEntity, SensorEntity): """Define an AirVisual sensor related to a Node/Pro unit.""" def __init__(self, coordinator, kind, name, device_class, unit): diff --git a/homeassistant/components/ambient_station/sensor.py b/homeassistant/components/ambient_station/sensor.py index 7e8f3d26ec6..732c28c8dc5 100644 --- a/homeassistant/components/ambient_station/sensor.py +++ b/homeassistant/components/ambient_station/sensor.py @@ -37,7 +37,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensor_list, True) -class AmbientWeatherSensor(SensorEntity, AmbientWeatherEntity): +class AmbientWeatherSensor(AmbientWeatherEntity, SensorEntity): """Define an Ambient sensor.""" def __init__( diff --git a/homeassistant/components/android_ip_webcam/sensor.py b/homeassistant/components/android_ip_webcam/sensor.py index de029c39aba..adedb297cd1 100644 --- a/homeassistant/components/android_ip_webcam/sensor.py +++ b/homeassistant/components/android_ip_webcam/sensor.py @@ -31,7 +31,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(all_sensors, True) -class IPWebcamSensor(SensorEntity, AndroidIPCamEntity): +class IPWebcamSensor(AndroidIPCamEntity, SensorEntity): """Representation of a IP Webcam sensor.""" def __init__(self, name, host, ipcam, sensor): diff --git a/homeassistant/components/asuswrt/sensor.py b/homeassistant/components/asuswrt/sensor.py index 30f4163d01b..7e38243e3d6 100644 --- a/homeassistant/components/asuswrt/sensor.py +++ b/homeassistant/components/asuswrt/sensor.py @@ -98,7 +98,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class AsusWrtSensor(SensorEntity, CoordinatorEntity): +class AsusWrtSensor(CoordinatorEntity, SensorEntity): """Representation of a AsusWrt sensor.""" def __init__( diff --git a/homeassistant/components/atag/sensor.py b/homeassistant/components/atag/sensor.py index 163ed669be4..88ccbdc899f 100644 --- a/homeassistant/components/atag/sensor.py +++ b/homeassistant/components/atag/sensor.py @@ -30,7 +30,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities([AtagSensor(coordinator, sensor) for sensor in SENSORS]) -class AtagSensor(SensorEntity, AtagEntity): +class AtagSensor(AtagEntity, SensorEntity): """Representation of a AtagOne Sensor.""" def __init__(self, coordinator, sensor): diff --git a/homeassistant/components/august/sensor.py b/homeassistant/components/august/sensor.py index 9b8352a0b96..44597a6485e 100644 --- a/homeassistant/components/august/sensor.py +++ b/homeassistant/components/august/sensor.py @@ -117,7 +117,7 @@ async def _async_migrate_old_unique_ids(hass, devices): registry.async_update_entity(old_entity_id, new_unique_id=device.unique_id) -class AugustOperatorSensor(SensorEntity, AugustEntityMixin, RestoreEntity): +class AugustOperatorSensor(AugustEntityMixin, RestoreEntity, SensorEntity): """Representation of an August lock operation sensor.""" def __init__(self, data, device): @@ -216,7 +216,7 @@ class AugustOperatorSensor(SensorEntity, AugustEntityMixin, RestoreEntity): return f"{self._device_id}_lock_operator" -class AugustBatterySensor(SensorEntity, AugustEntityMixin): +class AugustBatterySensor(AugustEntityMixin, SensorEntity): """Representation of an August sensor.""" def __init__(self, data, sensor_type, device, old_device): diff --git a/homeassistant/components/aurora/sensor.py b/homeassistant/components/aurora/sensor.py index 1a34bb604da..d7024cc630a 100644 --- a/homeassistant/components/aurora/sensor.py +++ b/homeassistant/components/aurora/sensor.py @@ -19,7 +19,7 @@ async def async_setup_entry(hass, entry, async_add_entries): async_add_entries([entity]) -class AuroraSensor(SensorEntity, AuroraEntity): +class AuroraSensor(AuroraEntity, SensorEntity): """Implementation of an aurora sensor.""" @property diff --git a/homeassistant/components/awair/sensor.py b/homeassistant/components/awair/sensor.py index de39c20eb60..502fa3dc626 100644 --- a/homeassistant/components/awair/sensor.py +++ b/homeassistant/components/awair/sensor.py @@ -84,7 +84,7 @@ async def async_setup_entry( async_add_entities(sensors) -class AwairSensor(SensorEntity, CoordinatorEntity): +class AwairSensor(CoordinatorEntity, SensorEntity): """Defines an Awair sensor entity.""" def __init__( diff --git a/homeassistant/components/azure_devops/sensor.py b/homeassistant/components/azure_devops/sensor.py index a0701ac9839..01018d34c78 100644 --- a/homeassistant/components/azure_devops/sensor.py +++ b/homeassistant/components/azure_devops/sensor.py @@ -56,7 +56,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class AzureDevOpsSensor(SensorEntity, AzureDevOpsDeviceEntity): +class AzureDevOpsSensor(AzureDevOpsDeviceEntity, SensorEntity): """Defines a Azure DevOps sensor.""" def __init__( diff --git a/homeassistant/components/blebox/sensor.py b/homeassistant/components/blebox/sensor.py index 688d2931461..c1b9d8501c1 100644 --- a/homeassistant/components/blebox/sensor.py +++ b/homeassistant/components/blebox/sensor.py @@ -14,7 +14,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class BleBoxSensorEntity(SensorEntity, BleBoxEntity): +class BleBoxSensorEntity(BleBoxEntity, SensorEntity): """Representation of a BleBox sensor feature.""" @property diff --git a/homeassistant/components/bmw_connected_drive/sensor.py b/homeassistant/components/bmw_connected_drive/sensor.py index 5c85312d959..38415d0006f 100644 --- a/homeassistant/components/bmw_connected_drive/sensor.py +++ b/homeassistant/components/bmw_connected_drive/sensor.py @@ -67,7 +67,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class BMWConnectedDriveSensor(SensorEntity, BMWConnectedDriveBaseEntity): +class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, SensorEntity): """Representation of a BMW vehicle sensor.""" def __init__(self, account, vehicle, attribute: str, attribute_info): diff --git a/homeassistant/components/brother/sensor.py b/homeassistant/components/brother/sensor.py index 10cc3979207..0b614ffa582 100644 --- a/homeassistant/components/brother/sensor.py +++ b/homeassistant/components/brother/sensor.py @@ -57,7 +57,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class BrotherPrinterSensor(SensorEntity, CoordinatorEntity): +class BrotherPrinterSensor(CoordinatorEntity, SensorEntity): """Define an Brother Printer sensor.""" def __init__(self, coordinator, kind, device_info): diff --git a/homeassistant/components/canary/sensor.py b/homeassistant/components/canary/sensor.py index e4c6190ff99..d7a6648857a 100644 --- a/homeassistant/components/canary/sensor.py +++ b/homeassistant/components/canary/sensor.py @@ -78,7 +78,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class CanarySensor(SensorEntity, CoordinatorEntity): +class CanarySensor(CoordinatorEntity, SensorEntity): """Representation of a Canary sensor.""" def __init__(self, coordinator, sensor_type, location, device): diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py index bf80f8b8fb5..a05acdb5d77 100644 --- a/homeassistant/components/cert_expiry/sensor.py +++ b/homeassistant/components/cert_expiry/sensor.py @@ -76,7 +76,7 @@ class CertExpiryEntity(CoordinatorEntity): } -class SSLCertificateTimestamp(SensorEntity, CertExpiryEntity): +class SSLCertificateTimestamp(CertExpiryEntity, SensorEntity): """Implementation of the Cert Expiry timestamp sensor.""" @property diff --git a/homeassistant/components/coronavirus/sensor.py b/homeassistant/components/coronavirus/sensor.py index 58e6760a85d..472b8bc8d1c 100644 --- a/homeassistant/components/coronavirus/sensor.py +++ b/homeassistant/components/coronavirus/sensor.py @@ -24,7 +24,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class CoronavirusSensor(SensorEntity, CoordinatorEntity): +class CoronavirusSensor(CoordinatorEntity, SensorEntity): """Sensor representing corona virus data.""" name = None diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index 8f23b47a1b9..0646ad09a31 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -122,7 +122,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class DeconzSensor(SensorEntity, DeconzDevice): +class DeconzSensor(DeconzDevice, SensorEntity): """Representation of a deCONZ sensor.""" TYPE = DOMAIN @@ -186,7 +186,7 @@ class DeconzSensor(SensorEntity, DeconzDevice): return attr -class DeconzBattery(SensorEntity, DeconzDevice): +class DeconzBattery(DeconzDevice, SensorEntity): """Battery class for when a device is only represented as an event.""" TYPE = DOMAIN diff --git a/homeassistant/components/derivative/sensor.py b/homeassistant/components/derivative/sensor.py index 72d0186d94d..12fc4ddd7ba 100644 --- a/homeassistant/components/derivative/sensor.py +++ b/homeassistant/components/derivative/sensor.py @@ -86,7 +86,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([derivative]) -class DerivativeSensor(SensorEntity, RestoreEntity): +class DerivativeSensor(RestoreEntity, SensorEntity): """Representation of an derivative sensor.""" def __init__( diff --git a/homeassistant/components/devolo_home_control/sensor.py b/homeassistant/components/devolo_home_control/sensor.py index f125448b4cc..e3c16670dfd 100644 --- a/homeassistant/components/devolo_home_control/sensor.py +++ b/homeassistant/components/devolo_home_control/sensor.py @@ -66,7 +66,7 @@ async def async_setup_entry( async_add_entities(entities, False) -class DevoloMultiLevelDeviceEntity(SensorEntity, DevoloDeviceEntity): +class DevoloMultiLevelDeviceEntity(DevoloDeviceEntity, SensorEntity): """Abstract representation of a multi level sensor within devolo Home Control.""" @property diff --git a/homeassistant/components/dexcom/sensor.py b/homeassistant/components/dexcom/sensor.py index 09c7ff4e051..730a1824e1a 100644 --- a/homeassistant/components/dexcom/sensor.py +++ b/homeassistant/components/dexcom/sensor.py @@ -17,7 +17,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class DexcomGlucoseValueSensor(SensorEntity, CoordinatorEntity): +class DexcomGlucoseValueSensor(CoordinatorEntity, SensorEntity): """Representation of a Dexcom glucose value sensor.""" def __init__(self, coordinator, username, unit_of_measurement): @@ -59,7 +59,7 @@ class DexcomGlucoseValueSensor(SensorEntity, CoordinatorEntity): return self._unique_id -class DexcomGlucoseTrendSensor(SensorEntity, CoordinatorEntity): +class DexcomGlucoseTrendSensor(CoordinatorEntity, SensorEntity): """Representation of a Dexcom glucose trend sensor.""" def __init__(self, coordinator, username): diff --git a/homeassistant/components/dyson/sensor.py b/homeassistant/components/dyson/sensor.py index 356bfc61cb2..cff4b8f5501 100644 --- a/homeassistant/components/dyson/sensor.py +++ b/homeassistant/components/dyson/sensor.py @@ -101,7 +101,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class DysonSensor(SensorEntity, DysonEntity): +class DysonSensor(DysonEntity, SensorEntity): """Representation of a generic Dyson sensor.""" def __init__(self, device, sensor_type): diff --git a/homeassistant/components/eafm/sensor.py b/homeassistant/components/eafm/sensor.py index 8fa2a47c157..b3d726f9cd3 100644 --- a/homeassistant/components/eafm/sensor.py +++ b/homeassistant/components/eafm/sensor.py @@ -78,7 +78,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): await coordinator.async_refresh() -class Measurement(SensorEntity, CoordinatorEntity): +class Measurement(CoordinatorEntity, SensorEntity): """A gauge at a flood monitoring station.""" attribution = "This uses Environment Agency flood and river level data from the real-time data API" diff --git a/homeassistant/components/econet/sensor.py b/homeassistant/components/econet/sensor.py index 8198eb75045..05fa1b734ea 100644 --- a/homeassistant/components/econet/sensor.py +++ b/homeassistant/components/econet/sensor.py @@ -48,7 +48,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensors) -class EcoNetSensor(SensorEntity, EcoNetEntity): +class EcoNetSensor(EcoNetEntity, SensorEntity): """Define a Econet sensor.""" def __init__(self, econet_device, device_name): diff --git a/homeassistant/components/eight_sleep/sensor.py b/homeassistant/components/eight_sleep/sensor.py index f5a0d1b1f8d..ae0854ec244 100644 --- a/homeassistant/components/eight_sleep/sensor.py +++ b/homeassistant/components/eight_sleep/sensor.py @@ -67,7 +67,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(all_sensors, True) -class EightHeatSensor(SensorEntity, EightSleepHeatEntity): +class EightHeatSensor(EightSleepHeatEntity, SensorEntity): """Representation of an eight sleep heat-based sensor.""" def __init__(self, name, eight, sensor): @@ -120,7 +120,7 @@ class EightHeatSensor(SensorEntity, EightSleepHeatEntity): } -class EightUserSensor(SensorEntity, EightSleepUserEntity): +class EightUserSensor(EightSleepUserEntity, SensorEntity): """Representation of an eight sleep user-based sensor.""" def __init__(self, name, eight, sensor, units): @@ -290,7 +290,7 @@ class EightUserSensor(SensorEntity, EightSleepUserEntity): return state_attr -class EightRoomSensor(SensorEntity, EightSleepUserEntity): +class EightRoomSensor(EightSleepUserEntity, SensorEntity): """Representation of an eight sleep room sensor.""" def __init__(self, name, eight, sensor, units): diff --git a/homeassistant/components/elkm1/sensor.py b/homeassistant/components/elkm1/sensor.py index c8d4fd722bb..e33196a08c0 100644 --- a/homeassistant/components/elkm1/sensor.py +++ b/homeassistant/components/elkm1/sensor.py @@ -68,7 +68,7 @@ def temperature_to_state(temperature, undefined_temperature): return temperature if temperature > undefined_temperature else None -class ElkSensor(SensorEntity, ElkAttachedEntity): +class ElkSensor(ElkAttachedEntity, SensorEntity): """Base representation of Elk-M1 sensor.""" def __init__(self, element, elk, elk_data): diff --git a/homeassistant/components/enocean/sensor.py b/homeassistant/components/enocean/sensor.py index 18362a3c6ac..1814efb9c87 100644 --- a/homeassistant/components/enocean/sensor.py +++ b/homeassistant/components/enocean/sensor.py @@ -101,7 +101,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([EnOceanWindowHandle(dev_id, dev_name)]) -class EnOceanSensor(SensorEntity, EnOceanEntity, RestoreEntity): +class EnOceanSensor(EnOceanEntity, RestoreEntity, SensorEntity): """Representation of an EnOcean sensor device such as a power meter.""" def __init__(self, dev_id, dev_name, sensor_type): diff --git a/homeassistant/components/enphase_envoy/sensor.py b/homeassistant/components/enphase_envoy/sensor.py index 794c0ca2dbd..dd1b10c870b 100644 --- a/homeassistant/components/enphase_envoy/sensor.py +++ b/homeassistant/components/enphase_envoy/sensor.py @@ -156,7 +156,7 @@ async def async_setup_platform( async_add_entities(entities) -class Envoy(SensorEntity, CoordinatorEntity): +class Envoy(CoordinatorEntity, SensorEntity): """Envoy entity.""" def __init__(self, sensor_type, name, serial_number, unit, coordinator): diff --git a/homeassistant/components/envisalink/sensor.py b/homeassistant/components/envisalink/sensor.py index ff4c73500f9..6fd7f32c6fe 100644 --- a/homeassistant/components/envisalink/sensor.py +++ b/homeassistant/components/envisalink/sensor.py @@ -37,7 +37,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devices) -class EnvisalinkSensor(SensorEntity, EnvisalinkDevice): +class EnvisalinkSensor(EnvisalinkDevice, SensorEntity): """Representation of an Envisalink keypad.""" def __init__(self, hass, partition_name, partition_number, info, controller): diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index 99f66f48029..d751109c159 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -42,7 +42,7 @@ async def async_setup_entry( # pylint: disable=invalid-overridden-method -class EsphomeSensor(SensorEntity, EsphomeEntity): +class EsphomeSensor(EsphomeEntity, SensorEntity): """A sensor implementation for esphome.""" @property @@ -89,7 +89,7 @@ class EsphomeSensor(SensorEntity, EsphomeEntity): return self._static_info.device_class -class EsphomeTextSensor(SensorEntity, EsphomeEntity): +class EsphomeTextSensor(EsphomeEntity, SensorEntity): """A text sensor implementation for ESPHome.""" @property From 19ab7306ecd19dc69f12ad397e365ec9b086b06d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 22 Mar 2021 15:21:33 -0700 Subject: [PATCH 557/831] Clean up AsusWRT (#48012) --- .../components/asuswrt/device_tracker.py | 56 +++++++------------ 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/asuswrt/device_tracker.py b/homeassistant/components/asuswrt/device_tracker.py index 1ef68f9d65d..bd86dd21edd 100644 --- a/homeassistant/components/asuswrt/device_tracker.py +++ b/homeassistant/components/asuswrt/device_tracker.py @@ -56,66 +56,51 @@ class AsusWrtDevice(ScannerEntity): def __init__(self, router: AsusWrtRouter, device) -> None: """Initialize a AsusWrt device.""" self._router = router - self._mac = device.mac - self._name = device.name or DEFAULT_DEVICE_NAME - self._active = False - self._icon = None - self._attrs = {} - - @callback - def async_update_state(self) -> None: - """Update the AsusWrt device.""" - device = self._router.devices[self._mac] - self._active = device.is_connected - - self._attrs = { - "mac": device.mac, - "ip_address": device.ip_address, - } - if device.last_activity: - self._attrs["last_time_reachable"] = device.last_activity.isoformat( - timespec="seconds" - ) + self._device = device @property def unique_id(self) -> str: """Return a unique ID.""" - return self._mac + return self._device.mac @property def name(self) -> str: """Return the name.""" - return self._name + return self._device.name or DEFAULT_DEVICE_NAME @property def is_connected(self): """Return true if the device is connected to the network.""" - return self._active + return self._device.is_connected @property def source_type(self) -> str: """Return the source type.""" return SOURCE_TYPE_ROUTER - @property - def icon(self) -> str: - """Return the icon.""" - return self._icon - @property def extra_state_attributes(self) -> dict[str, any]: """Return the attributes.""" - return self._attrs + attrs = { + "mac": self._device.mac, + "ip_address": self._device.ip_address, + } + if self._device.last_activity: + attrs["last_time_reachable"] = self._device.last_activity.isoformat( + timespec="seconds" + ) + return attrs @property def device_info(self) -> dict[str, any]: """Return the device information.""" - return { - "connections": {(CONNECTION_NETWORK_MAC, self._mac)}, - "identifiers": {(DOMAIN, self.unique_id)}, - "name": self.name, - "manufacturer": "AsusWRT Tracked device", + data = { + "connections": {(CONNECTION_NETWORK_MAC, self._device.mac)}, } + if self._device.name: + data["default_name"] = self._device.name + + return data @property def should_poll(self) -> bool: @@ -125,12 +110,11 @@ class AsusWrtDevice(ScannerEntity): @callback def async_on_demand_update(self): """Update state.""" - self.async_update_state() + self._device = self._router.devices[self._device.mac] self.async_write_ha_state() async def async_added_to_hass(self): """Register state update callback.""" - self.async_update_state() self.async_on_remove( async_dispatcher_connect( self.hass, From 55b689b4649a7bb916618b70bffa42296bdb41cf Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Tue, 23 Mar 2021 00:03:29 +0000 Subject: [PATCH 558/831] [ci skip] Translation update --- .../components/aemet/translations/lb.json | 3 ++ .../components/august/translations/el.json | 22 ++++++++ .../components/climacell/translations/el.json | 31 +++++++++++ .../device_tracker/translations/nl.json | 2 +- .../emulated_roku/translations/nl.json | 2 +- .../flunearyou/translations/nl.json | 2 +- .../components/hive/translations/el.json | 53 +++++++++++++++++++ .../components/homekit/translations/et.json | 2 +- .../components/homekit/translations/it.json | 2 +- .../components/homekit/translations/no.json | 2 +- .../components/homekit/translations/ru.json | 2 +- .../components/hyperion/translations/ca.json | 1 + .../components/hyperion/translations/el.json | 11 ++++ .../components/hyperion/translations/en.json | 1 + .../components/hyperion/translations/et.json | 1 + .../components/hyperion/translations/it.json | 1 + .../components/hyperion/translations/ru.json | 1 + .../components/konnected/translations/nl.json | 10 ++-- .../components/melcloud/translations/nl.json | 6 +-- .../minecraft_server/translations/nl.json | 2 +- .../components/mqtt/translations/nl.json | 13 ++++- .../components/mullvad/translations/el.json | 22 ++++++++ .../components/myq/translations/nl.json | 4 +- .../components/netatmo/translations/el.json | 19 +++++++ .../components/nexia/translations/nl.json | 2 +- .../components/notify/translations/nl.json | 2 +- .../components/nws/translations/nl.json | 2 +- .../opentherm_gw/translations/el.json | 12 +++++ .../philips_js/translations/el.json | 13 +++++ .../screenlogic/translations/el.json | 39 ++++++++++++++ .../components/subaru/translations/el.json | 35 ++++++++++++ .../components/verisure/translations/el.json | 12 +++++ .../water_heater/translations/el.json | 13 +++++ 33 files changed, 323 insertions(+), 22 deletions(-) create mode 100644 homeassistant/components/august/translations/el.json create mode 100644 homeassistant/components/climacell/translations/el.json create mode 100644 homeassistant/components/hive/translations/el.json create mode 100644 homeassistant/components/hyperion/translations/el.json create mode 100644 homeassistant/components/mullvad/translations/el.json create mode 100644 homeassistant/components/netatmo/translations/el.json create mode 100644 homeassistant/components/opentherm_gw/translations/el.json create mode 100644 homeassistant/components/philips_js/translations/el.json create mode 100644 homeassistant/components/screenlogic/translations/el.json create mode 100644 homeassistant/components/subaru/translations/el.json create mode 100644 homeassistant/components/verisure/translations/el.json create mode 100644 homeassistant/components/water_heater/translations/el.json diff --git a/homeassistant/components/aemet/translations/lb.json b/homeassistant/components/aemet/translations/lb.json index 5ae5fff8664..8e83c0e86d3 100644 --- a/homeassistant/components/aemet/translations/lb.json +++ b/homeassistant/components/aemet/translations/lb.json @@ -3,6 +3,9 @@ "step": { "user": { "data": { + "api_key": "API Schl\u00ebssel", + "latitude": "L\u00e4ngegrad", + "longitude": "Breedegrad", "name": "Numm vun der Integratioun" }, "title": "AEMET OpenData" diff --git a/homeassistant/components/august/translations/el.json b/homeassistant/components/august/translations/el.json new file mode 100644 index 00000000000..8a976d451be --- /dev/null +++ b/homeassistant/components/august/translations/el.json @@ -0,0 +1,22 @@ +{ + "config": { + "step": { + "reauth_validate": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {username} .", + "title": "\u0395\u03c0\u03b9\u03ba\u03c5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03ad\u03bd\u03b1\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc August" + }, + "user_validate": { + "data": { + "login_method": "\u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2 \u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03a7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, + "description": "\u0395\u03ac\u03bd \u03b7 \u03bc\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \"\u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03c4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b5\u03af\u03bf\", \u03c4\u03bf \u038c\u03bd\u03bf\u03bc\u03b1 \u03a7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03c4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b5\u03af\u03bf\u03c5. \u0395\u03ac\u03bd \u03b7 \u03bc\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \"\u03c4\u03b7\u03bb\u03ad\u03c6\u03c9\u03bd\u03bf\", \u03c4\u03bf \u038c\u03bd\u03bf\u03bc\u03b1 \u03a7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bf \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c4\u03b7\u03bb\u03b5\u03c6\u03ce\u03bd\u03bf\u03c5 \u03bc\u03b5 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae '+NNNNNNNNNN'.", + "title": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc August" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/el.json b/homeassistant/components/climacell/translations/el.json new file mode 100644 index 00000000000..45ed5d8a722 --- /dev/null +++ b/homeassistant/components/climacell/translations/el.json @@ -0,0 +1,31 @@ +{ + "config": { + "error": { + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03a0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u039c\u03ae\u03ba\u03bf\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, + "description": "\u0395\u03ac\u03bd \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c4\u03bf \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2 \u03ba\u03b1\u03b9 \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2, \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03bf\u03cd\u03bd \u03bf\u03b9 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03c4\u03b9\u03bc\u03ad\u03c2 \u03c3\u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03c4\u03bf\u03c5 Home Assistant. \u0398\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03bc\u03b9\u03b1 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b3\u03b9\u03b1 \u03ba\u03ac\u03b8\u03b5 \u03c4\u03cd\u03c0\u03bf \u03b4\u03b5\u03bb\u03c4\u03af\u03bf\u03c5, \u03b1\u03bb\u03bb\u03ac \u03bc\u03cc\u03bd\u03bf \u03b1\u03c5\u03c4\u03ad\u03c2 \u03c0\u03bf\u03c5 \u03b5\u03c0\u03b9\u03bb\u03ad\u03b3\u03b5\u03c4\u03b5 \u03b8\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03bf\u03cd\u03bd \u03b1\u03c0\u03cc \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "\u0395\u03af\u03b4\u03bf\u03c2/\u03b7 \u0394\u03b5\u03bb\u03c4\u03af\u03bf\u03c5/\u03c9\u03bd", + "timestep": "\u039b\u03b5\u03c0\u03c4\u03ac \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03c4\u03c9\u03bd \u03b4\u03b5\u03bb\u03c4\u03af\u03c9\u03bd NowCast" + }, + "description": "\u0395\u03ac\u03bd \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b4\u03b5\u03bb\u03c4\u03af\u03c9\u03bd 'nowcast', \u03bc\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03bb\u03b5\u03c0\u03c4\u03ce\u03bd \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03ba\u03ac\u03b8\u03b5 \u03b4\u03b5\u03bb\u03c4\u03af\u03bf\u03c5. \u039f \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c4\u03c9\u03bd \u03b4\u03b5\u03bb\u03c4\u03af\u03c9\u03bd \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b5\u03be\u03b1\u03c1\u03c4\u03ac\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03bb\u03b5\u03c0\u03c4\u03ce\u03bd \u03c0\u03bf\u03c5 \u03b5\u03c0\u03b9\u03bb\u03ad\u03b3\u03bf\u03bd\u03c4\u03b1\u03b9 \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03c4\u03c9\u03bd \u03b4\u03b5\u03bb\u03c4\u03af\u03c9\u03bd.", + "title": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b5 \u03c4\u03b9\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/device_tracker/translations/nl.json b/homeassistant/components/device_tracker/translations/nl.json index a28c8bdbbb8..a3f7d6cd31f 100644 --- a/homeassistant/components/device_tracker/translations/nl.json +++ b/homeassistant/components/device_tracker/translations/nl.json @@ -15,5 +15,5 @@ "not_home": "Afwezig" } }, - "title": "Apparaat tracker" + "title": "Apparaattracker" } \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/nl.json b/homeassistant/components/emulated_roku/translations/nl.json index d4f31b7bb5d..dd988985250 100644 --- a/homeassistant/components/emulated_roku/translations/nl.json +++ b/homeassistant/components/emulated_roku/translations/nl.json @@ -6,7 +6,7 @@ "step": { "user": { "data": { - "advertise_ip": "Ip adres ontdekbaar", + "advertise_ip": "IP-adres zichtbaar", "advertise_port": "Adverteer Poort", "host_ip": "Host IP", "listen_port": "Luisterpoort", diff --git a/homeassistant/components/flunearyou/translations/nl.json b/homeassistant/components/flunearyou/translations/nl.json index bca6e2a8c8b..d78abfcc187 100644 --- a/homeassistant/components/flunearyou/translations/nl.json +++ b/homeassistant/components/flunearyou/translations/nl.json @@ -12,7 +12,7 @@ "latitude": "Breedtegraad", "longitude": "Lengtegraad" }, - "description": "Bewaak op gebruikers gebaseerde en CDC-repots voor een paar co\u00f6rdinaten.", + "description": "Bewaak gebruikersgebaseerde en CDC-rapporten voor een paar co\u00f6rdinaten.", "title": "Configureer \nFlu Near You" } } diff --git a/homeassistant/components/hive/translations/el.json b/homeassistant/components/hive/translations/el.json new file mode 100644 index 00000000000..986dd52ef19 --- /dev/null +++ b/homeassistant/components/hive/translations/el.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "\u039f \u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2", + "reauth_successful": "H \u03b5\u03c0\u03b1\u03bd\u03b1\u03b5\u03c0\u03b9\u03b2\u03b5\u03b2\u03b1\u03af\u03c9\u03c3\u03b7 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2.", + "unknown_entry": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03c3\u03b1\u03c2 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7\u03c2." + }, + "error": { + "invalid_code": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03bf Hive. \u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b4\u03cd\u03bf \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd \u03ae\u03c4\u03b1\u03bd \u03bb\u03b1\u03bd\u03b8\u03b1\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2.", + "invalid_password": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03bf Hive. \u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bb\u03ac\u03b8\u03bf\u03c2, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", + "invalid_username": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03bf Hive. \u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03c4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b5\u03af\u03bf\u03c5 \u03c3\u03b1\u03c2 \u03b4\u03b5\u03bd \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9.", + "no_internet_available": "\u0391\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf \u0394\u03b9\u03b1\u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03b3\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf Hive.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "2fa": { + "data": { + "2fa": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b4\u03cd\u03bf \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd" + }, + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 Hive. \n\n \u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc 0000 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b6\u03b7\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ac\u03bb\u03bb\u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc.", + "title": "\u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b4\u03cd\u03bf \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd \u03c4\u03bf\u03c5 Hive." + }, + "reauth": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03a7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Hive.", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 Hive" + }, + "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03a3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03a7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03ba\u03b1\u03b9 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf Hive.", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 Hive" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03a3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)" + }, + "description": "\u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03c4\u03b5 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 \u03c0\u03b9\u03bf \u03c3\u03c5\u03c7\u03bd\u03ac.", + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 Hive" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/et.json b/homeassistant/components/homekit/translations/et.json index 8c24d2d2251..2ae8d651eb1 100644 --- a/homeassistant/components/homekit/translations/et.json +++ b/homeassistant/components/homekit/translations/et.json @@ -55,7 +55,7 @@ "entities": "Olemid", "mode": "Re\u017eiim" }, - "description": "Vali kaasatavad olemid. Tarvikute re\u017eiimis on kaasatav ainult \u00fcks olem. Silla re\u017eiimis, kuvatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud juhul, kui valitud on kindlad olemid. Silla v\u00e4listamisre\u017eiimis kaasatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud v\u00e4listatud olemid. Parima kasutuskogemuse jaoks on eraldi HomeKit seadmed iga meediumim\u00e4ngija ja kaamera jaoks.", + "description": "Vali kaasatavad olemid. Tarvikute re\u017eiimis on kaasatav ainult \u00fcks olem. Silla re\u017eiimis kaasatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud juhul, kui valitud on kindlad olemid. Silla v\u00e4listamisre\u017eiimis kaasatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud v\u00e4listatud olemid. Parima kasutuskogemuse jaoks on eraldi HomeKit seadmed iga meediumim\u00e4ngija ja kaamera jaoks.", "title": "Vali kaasatavd olemid" }, "init": { diff --git a/homeassistant/components/homekit/translations/it.json b/homeassistant/components/homekit/translations/it.json index fee64457652..f92c61d493f 100644 --- a/homeassistant/components/homekit/translations/it.json +++ b/homeassistant/components/homekit/translations/it.json @@ -55,7 +55,7 @@ "entities": "Entit\u00e0", "mode": "Modalit\u00e0" }, - "description": "Scegliere le entit\u00e0 da includere. In modalit\u00e0 accessorio, \u00e8 inclusa una sola entit\u00e0. In modalit\u00e0 di inclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, a meno che non siano selezionate entit\u00e0 specifiche. In modalit\u00e0 di esclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, ad eccezione delle entit\u00e0 escluse. Per prestazioni ottimali, ci sar\u00e0 una HomeKit separata in modalit\u00e0 accessorio per ogni lettore multimediale, TV e videocamera.", + "description": "Scegliere le entit\u00e0 da includere. In modalit\u00e0 accessorio, \u00e8 inclusa una sola entit\u00e0. In modalit\u00e0 di inclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, a meno che non siano selezionate entit\u00e0 specifiche. In modalit\u00e0 di esclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, ad eccezione delle entit\u00e0 escluse. Per prestazioni ottimali, sar\u00e0 creata una HomeKit separata accessoria per ogni lettore multimediale, TV e videocamera.", "title": "Seleziona le entit\u00e0 da includere" }, "init": { diff --git a/homeassistant/components/homekit/translations/no.json b/homeassistant/components/homekit/translations/no.json index 4748fe63af2..6e13907d057 100644 --- a/homeassistant/components/homekit/translations/no.json +++ b/homeassistant/components/homekit/translations/no.json @@ -55,7 +55,7 @@ "entities": "Entiteter", "mode": "Modus" }, - "description": "Velg enhetene som skal inkluderes. I tilbeh\u00f8rsmodus er bare en enkelt enhet inkludert. I bridge-inkluderingsmodus vil alle enheter i domenet bli inkludert, med mindre spesifikke enheter er valgt. I bridge-ekskluderingsmodus vil alle enheter i domenet bli inkludert, bortsett fra de ekskluderte enhetene. For best ytelse vil et eget HomeKit-tilbeh\u00f8r v\u00e6re TV-mediaspiller og kamera.", + "description": "Velg enhetene som skal inkluderes. I tilbeh\u00f8rsmodus er bare en enkelt enhet inkludert. I bridge-inkluderingsmodus vil alle enheter i domenet bli inkludert, med mindre spesifikke enheter er valgt. I bridge-ekskluderingsmodus vil alle enheter i domenet bli inkludert, bortsett fra de ekskluderte enhetene. For best ytelse opprettes et eget HomeKit-tilbeh\u00f8r for hver tv-mediaspiller og kamera.", "title": "Velg enheter som skal inkluderes" }, "init": { diff --git a/homeassistant/components/homekit/translations/ru.json b/homeassistant/components/homekit/translations/ru.json index d00744e4cb4..ffc0ac34eae 100644 --- a/homeassistant/components/homekit/translations/ru.json +++ b/homeassistant/components/homekit/translations/ru.json @@ -55,7 +55,7 @@ "entities": "\u041e\u0431\u044a\u0435\u043a\u0442\u044b", "mode": "\u0420\u0435\u0436\u0438\u043c" }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u043c\u043e\u0441\u0442\u0430 \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u043a\u0440\u043e\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445. \u0414\u043b\u044f \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b.", + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u043c\u043e\u0441\u0442\u0430 \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u043a\u0440\u043e\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445. \u0414\u043b\u044f \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u044b \u0438 \u043a\u0430\u043c\u0435\u0440\u044b \u0431\u0443\u0434\u0443\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u044b \u043a\u0430\u043a \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430.", "title": "\u0412\u044b\u0431\u043e\u0440 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit" }, "init": { diff --git a/homeassistant/components/hyperion/translations/ca.json b/homeassistant/components/hyperion/translations/ca.json index 50ac384d8ca..3a1de53102b 100644 --- a/homeassistant/components/hyperion/translations/ca.json +++ b/homeassistant/components/hyperion/translations/ca.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "Efectes d'Hyperion a mostrar", "priority": "Prioritat Hyperion a utilitzar per als colors i efectes" } } diff --git a/homeassistant/components/hyperion/translations/el.json b/homeassistant/components/hyperion/translations/el.json new file mode 100644 index 00000000000..5ba51046f21 --- /dev/null +++ b/homeassistant/components/hyperion/translations/el.json @@ -0,0 +1,11 @@ +{ + "options": { + "step": { + "init": { + "data": { + "effect_show_list": "\u0395\u03c6\u03ad Hyperion \u03b3\u03b9\u03b1 \u03b5\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/en.json b/homeassistant/components/hyperion/translations/en.json index d1277b411e0..38ab5cf7055 100644 --- a/homeassistant/components/hyperion/translations/en.json +++ b/homeassistant/components/hyperion/translations/en.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "Hyperion effects to show", "priority": "Hyperion priority to use for colors and effects" } } diff --git a/homeassistant/components/hyperion/translations/et.json b/homeassistant/components/hyperion/translations/et.json index a225b7f2c47..dba661d24c2 100644 --- a/homeassistant/components/hyperion/translations/et.json +++ b/homeassistant/components/hyperion/translations/et.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "Kuvatavad Hyperioni efektid", "priority": "V\u00e4rvide ja efektide puhul on kasutatavad Hyperioni eelistused" } } diff --git a/homeassistant/components/hyperion/translations/it.json b/homeassistant/components/hyperion/translations/it.json index b03b368d039..81f049125ed 100644 --- a/homeassistant/components/hyperion/translations/it.json +++ b/homeassistant/components/hyperion/translations/it.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "Effetti di Hyperion da mostrare", "priority": "Priorit\u00e0 Hyperion da usare per colori ed effetti" } } diff --git a/homeassistant/components/hyperion/translations/ru.json b/homeassistant/components/hyperion/translations/ru.json index 9e74680a951..a6716bb74ea 100644 --- a/homeassistant/components/hyperion/translations/ru.json +++ b/homeassistant/components/hyperion/translations/ru.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "\u042d\u0444\u0444\u0435\u043a\u0442\u044b Hyperion", "priority": "\u041f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442 Hyperion \u0434\u043b\u044f \u0446\u0432\u0435\u0442\u043e\u0432 \u0438 \u044d\u0444\u0444\u0435\u043a\u0442\u043e\u0432" } } diff --git a/homeassistant/components/konnected/translations/nl.json b/homeassistant/components/konnected/translations/nl.json index 635c064807b..0387dc8c7b0 100644 --- a/homeassistant/components/konnected/translations/nl.json +++ b/homeassistant/components/konnected/translations/nl.json @@ -2,12 +2,12 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom voor het apparaat wordt al uitgevoerd.", + "already_in_progress": "De configuratiestroom is al aan de gang", "not_konn_panel": "Geen herkend Konnected.io apparaat", - "unknown": "Onbekende fout opgetreden" + "unknown": "Onverwachte fout" }, "error": { - "cannot_connect": "Kan geen verbinding maken met een Konnected Panel op {host} : {port}" + "cannot_connect": "Kan geen verbinding maken" }, "step": { "confirm": { @@ -20,8 +20,8 @@ }, "user": { "data": { - "host": "IP-adres van Konnected apparaat", - "port": "Konnected apparaat poort" + "host": "IP-adres", + "port": "Poort" }, "description": "Voer de host-informatie in voor uw Konnected-paneel." } diff --git a/homeassistant/components/melcloud/translations/nl.json b/homeassistant/components/melcloud/translations/nl.json index 8ef8cc716b1..481027f9092 100644 --- a/homeassistant/components/melcloud/translations/nl.json +++ b/homeassistant/components/melcloud/translations/nl.json @@ -4,15 +4,15 @@ "already_configured": "MELCloud integratie is al geconfigureerd voor deze e-mail. Toegangstoken is vernieuwd." }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, "step": { "user": { "data": { - "password": "MELCloud wachtwoord.", - "username": "E-mail gebruikt om in te loggen op MELCloud." + "password": "Wachtwoord", + "username": "E-mail" }, "description": "Maak verbinding via uw MELCloud account.", "title": "Maak verbinding met MELCloud" diff --git a/homeassistant/components/minecraft_server/translations/nl.json b/homeassistant/components/minecraft_server/translations/nl.json index 1d589f942e7..0170964d5b0 100644 --- a/homeassistant/components/minecraft_server/translations/nl.json +++ b/homeassistant/components/minecraft_server/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Host is al geconfigureerd." + "already_configured": "Service is al geconfigureerd" }, "error": { "cannot_connect": "Kan geen verbinding maken met de server. Controleer de host en de poort en probeer het opnieuw. Zorg er ook voor dat u minimaal Minecraft versie 1.7 op uw server uitvoert.", diff --git a/homeassistant/components/mqtt/translations/nl.json b/homeassistant/components/mqtt/translations/nl.json index 8395b1db7e9..cac483b1bf0 100644 --- a/homeassistant/components/mqtt/translations/nl.json +++ b/homeassistant/components/mqtt/translations/nl.json @@ -50,11 +50,14 @@ }, "options": { "error": { + "bad_birth": "Ongeldig birth topic", + "bad_will": "Ongeldig will topic", "cannot_connect": "Kon niet verbinden" }, "step": { "broker": { "data": { + "broker": "Broker", "password": "Wachtwoord", "port": "Poort", "username": "Gebruikersnaam" @@ -65,7 +68,15 @@ "data": { "birth_enable": "Geboortebericht inschakelen", "birth_payload": "Birth message payload", - "birth_topic": "Birth message onderwerp" + "birth_qos": "Birth message QoS", + "birth_retain": "Birth message behouden", + "birth_topic": "Birth message onderwerp", + "discovery": "Discovery inschakelen", + "will_enable": "Will message inschakelen", + "will_payload": "Will message payload", + "will_qos": "Will message QoS", + "will_retain": "Will message behouden", + "will_topic": "Will message topic" }, "description": "Selecteer MQTT-opties." } diff --git a/homeassistant/components/mullvad/translations/el.json b/homeassistant/components/mullvad/translations/el.json new file mode 100644 index 00000000000..6f19f0039ed --- /dev/null +++ b/homeassistant/components/mullvad/translations/el.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u0386\u03ba\u03c5\u03c1\u03b7 \u03b5\u03c0\u03b1\u03bb\u03ae\u03b8\u03b5\u03c5\u03c3\u03b7", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "user": { + "data": { + "host": "\u03a0\u03ac\u03c1\u03bf\u03c7\u03bf\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03a7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, + "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 Mullvad VPN;" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/myq/translations/nl.json b/homeassistant/components/myq/translations/nl.json index fd6310cce6a..65df320a544 100644 --- a/homeassistant/components/myq/translations/nl.json +++ b/homeassistant/components/myq/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "MyQ is al geconfigureerd" + "already_configured": "Service is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/netatmo/translations/el.json b/homeassistant/components/netatmo/translations/el.json new file mode 100644 index 00000000000..03a1530be9b --- /dev/null +++ b/homeassistant/components/netatmo/translations/el.json @@ -0,0 +1,19 @@ +{ + "device_automation": { + "trigger_subtype": { + "away": "\u03b5\u03ba\u03c4\u03cc\u03c2", + "hg": "\u03c0\u03c1\u03bf\u03c3\u03c4\u03b1\u03c3\u03af\u03b1 \u03c0\u03b1\u03b3\u03b5\u03c4\u03bf\u03cd", + "schedule": "\u03c0\u03c1\u03cc\u03b3\u03c1\u03b1\u03bc\u03bc\u03b1" + }, + "trigger_type": { + "alarm_started": "{entity_name} \u03b5\u03bd\u03c4\u03cc\u03c0\u03b9\u03c3\u03b5 \u03ad\u03bd\u03b1\u03bd \u03c3\u03c5\u03bd\u03b1\u03b3\u03b5\u03c1\u03bc\u03cc", + "animal": "{entity_name} \u03b5\u03bd\u03c4\u03cc\u03c0\u03b9\u03c3\u03b5 \u03ad\u03bd\u03b1 \u03b6\u03ce\u03bf", + "cancel_set_point": "\u03a4\u03bf {entity_name} \u03c3\u03c5\u03bd\u03ad\u03c7\u03b9\u03c3\u03b5 \u03c4\u03bf \u03c0\u03c1\u03cc\u03b3\u03c1\u03b1\u03bc\u03bc\u03ac \u03c4\u03bf\u03c5", + "human": "{entity_name} \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ad\u03bd\u03b1\u03c2 \u03ac\u03bd\u03b8\u03c1\u03c9\u03c0\u03bf\u03c2", + "movement": "{entity_name} \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7", + "outdoor": "\u03a4\u03bf {entity_name} \u03b5\u03bd\u03c4\u03cc\u03c0\u03b9\u03c3\u03b5 \u03ad\u03bd\u03b1 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd \u03b5\u03be\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03bf\u03cd \u03c7\u03ce\u03c1\u03bf\u03c5", + "person": "{entity_name} \u03b5\u03bd\u03c4\u03cc\u03c0\u03b9\u03c3\u03b5 \u03ad\u03bd\u03b1 \u03ac\u03c4\u03bf\u03bc\u03bf", + "person_away": "{entity_name} \u03b5\u03bd\u03c4\u03cc\u03c0\u03b9\u03c3\u03b5 \u03cc\u03c4\u03b9 \u03ad\u03bd\u03b1 \u03ac\u03c4\u03bf\u03bc\u03bf \u03ad\u03c7\u03b5\u03b9 \u03c6\u03cd\u03b3\u03b5\u03b9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/nl.json b/homeassistant/components/nexia/translations/nl.json index a9b4d99883e..faa19d3b63c 100644 --- a/homeassistant/components/nexia/translations/nl.json +++ b/homeassistant/components/nexia/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/notify/translations/nl.json b/homeassistant/components/notify/translations/nl.json index 409692f7227..52a24cc9efd 100644 --- a/homeassistant/components/notify/translations/nl.json +++ b/homeassistant/components/notify/translations/nl.json @@ -1,3 +1,3 @@ { - "title": "Notificeer" + "title": "Meldingen" } \ No newline at end of file diff --git a/homeassistant/components/nws/translations/nl.json b/homeassistant/components/nws/translations/nl.json index c8e10dfba10..5332f43f4c7 100644 --- a/homeassistant/components/nws/translations/nl.json +++ b/homeassistant/components/nws/translations/nl.json @@ -15,7 +15,7 @@ "longitude": "Lengtegraad", "station": "METAR-zendercode" }, - "description": "Als er geen METAR-zendercode is opgegeven, worden de lengte- en breedtegraad gebruikt om het dichtstbijzijnde station te vinden.", + "description": "Als er geen METAR-stationscode is opgegeven, worden de lengte- en breedtegraad gebruikt om het dichtstbijzijnde station te vinden. Voorlopig kan een API-sleutel van alles zijn. Het wordt aanbevolen om een geldig e-mailadres te gebruiken.", "title": "Maak verbinding met de National Weather Service" } } diff --git a/homeassistant/components/opentherm_gw/translations/el.json b/homeassistant/components/opentherm_gw/translations/el.json new file mode 100644 index 00000000000..f15bc7bdc0e --- /dev/null +++ b/homeassistant/components/opentherm_gw/translations/el.json @@ -0,0 +1,12 @@ +{ + "options": { + "step": { + "init": { + "data": { + "read_precision": "\u0394\u03b9\u03ac\u03b2\u03b1\u03c3\u03b5 \u03c4\u03b7\u03bd \u03b1\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1", + "set_precision": "\u039f\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b1\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1\u03c2" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/el.json b/homeassistant/components/philips_js/translations/el.json new file mode 100644 index 00000000000..94f6b540bd0 --- /dev/null +++ b/homeassistant/components/philips_js/translations/el.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "pair": { + "data": { + "pin": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN" + }, + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf PIN \u03c0\u03bf\u03c5 \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7\u03bd \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2", + "title": "\u03a3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/el.json b/homeassistant/components/screenlogic/translations/el.json new file mode 100644 index 00000000000..26906ac3b29 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/el.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", + "port": "\u0398\u03cd\u03c1\u03b1" + }, + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03b1\u03c2 \u03c4\u03b7\u03c2 \u03c0\u03cd\u03bb\u03b7\u03c2 ScreenLogic.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "\u03a0\u03cd\u03bb\u03b7" + }, + "description": "\u0395\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b1\u03bd \u03bf\u03b9 \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b5\u03c2 \u03c0\u03cd\u03bb\u03b5\u03c2 ScreenLogic. \u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 \u03b3\u03b9\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ae \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03cd\u03bb\u03b7 ScreenLogic \u03c7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b1.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0394\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1 \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03c3\u03b1\u03c1\u03ce\u03c3\u03b5\u03c9\u03bd" + }, + "description": "\u039a\u03b1\u03b8\u03bf\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/el.json b/homeassistant/components/subaru/translations/el.json new file mode 100644 index 00000000000..17ede1af4e7 --- /dev/null +++ b/homeassistant/components/subaru/translations/el.json @@ -0,0 +1,35 @@ +{ + "config": { + "error": { + "unknown": "\u0391\u03c0\u03c1\u03bf\u03c3\u03b4\u03cc\u03ba\u03b7\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Subaru Starlink" + }, + "user": { + "data": { + "country": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c7\u03ce\u03c1\u03b1\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03a7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03ac \u03c3\u03b1\u03c2 \u03c4\u03bf\u03c5 MySubaru\n\u03a3\u0397\u039c\u0395\u0399\u03a9\u03a3\u0397: \u0397 \u03b1\u03c1\u03c7\u03b9\u03ba\u03ae \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03bd\u03b4\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03c1\u03ba\u03ad\u03c3\u03b5\u03b9 \u03ad\u03c9\u03c2 \u03ba\u03b1\u03b9 30 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Subaru Starlink" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7\u03c2 \u03bf\u03c7\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2" + }, + "description": "\u038c\u03c4\u03b1\u03bd \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af, \u03b7 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7 \u03bf\u03c7\u03b7\u03bc\u03ac\u03c4\u03c9\u03bd \u03b8\u03b1 \u03c3\u03c4\u03ad\u03bb\u03bd\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03b1\u03c0\u03bf\u03bc\u03b1\u03ba\u03c1\u03c5\u03c3\u03bc\u03ad\u03bd\u03b7 \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae \u03c3\u03c4\u03bf \u03cc\u03c7\u03b7\u03bc\u03ac \u03c3\u03b1\u03c2 \u03ba\u03ac\u03b8\u03b5 2 \u03ce\u03c1\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03c0\u03bf\u03ba\u03c4\u03ae\u03c3\u03b5\u03b9 \u03c4\u03b1 \u03bd\u03ad\u03b1 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 \u03c4\u03c9\u03bd \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03c9\u03bd. \u03a7\u03c9\u03c1\u03af\u03c2 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7 \u03bf\u03c7\u03b7\u03bc\u03ac\u03c4\u03c9\u03bd, \u03c4\u03b1 \u03bd\u03ad\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03c9\u03bd \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03bf\u03bd\u03c4\u03b1\u03b9 \u03bc\u03cc\u03bd\u03bf \u03cc\u03c4\u03b1\u03bd \u03c4\u03bf \u03cc\u03c7\u03b7\u03bc\u03b1 \u03c9\u03b8\u03b5\u03af \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b1 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 (\u03ba\u03b1\u03bd\u03bf\u03bd\u03b9\u03ba\u03ac \u03bc\u03b5\u03c4\u03ac \u03b1\u03c0\u03cc \u03c4\u03b7 \u03b4\u03b9\u03b1\u03ba\u03bf\u03c0\u03ae \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03bc\u03b7\u03c7\u03b1\u03bd\u03ce\u03bd).", + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 Subaru Starlink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/el.json b/homeassistant/components/verisure/translations/el.json new file mode 100644 index 00000000000..87313dba1d4 --- /dev/null +++ b/homeassistant/components/verisure/translations/el.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "data": { + "email": "\u0397\u03bb\u03b5\u03ba\u03c4\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03a4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b5\u03af\u03bf", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/el.json b/homeassistant/components/water_heater/translations/el.json new file mode 100644 index 00000000000..827a171a7ac --- /dev/null +++ b/homeassistant/components/water_heater/translations/el.json @@ -0,0 +1,13 @@ +{ + "state": { + "_": { + "eco": "\u039f\u03b9\u03ba\u03bf\u03bb\u03bf\u03b3\u03b9\u03ba\u03cc", + "electric": "\u0397\u03bb\u03b5\u03ba\u03c4\u03c1\u03b9\u03ba\u03cc", + "gas": "\u0391\u03ad\u03c1\u03b9\u03bf", + "heat_pump": "\u0391\u03bd\u03c4\u03bb\u03af\u03b1 \u0398\u03b5\u03c1\u03bc\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "high_demand": "\u03a5\u03c8\u03b7\u03bb\u03ae \u0396\u03ae\u03c4\u03b7\u03c3\u03b7", + "off": "\u0391\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf", + "performance": "\u0391\u03c0\u03cc\u03b4\u03bf\u03c3\u03b7" + } + } +} \ No newline at end of file From cd455e296e331af0212344563d95ded2a537b877 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Tue, 23 Mar 2021 14:30:45 +0800 Subject: [PATCH 559/831] Remove login details before logging stream source (#45398) * Remove login details before logging stream source * Convert to str before re * Use compiled RE * Add tests and filter log message in worker * Update import Co-authored-by: Erik Montnemery * isort Co-authored-by: Erik Montnemery --- homeassistant/components/stream/__init__.py | 11 +++++++++-- homeassistant/components/stream/worker.py | 5 ++++- tests/components/stream/test_recorder.py | 10 ++++++++++ tests/components/stream/test_worker.py | 15 +++++++++++++++ 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 184ed1f2719..0226bb82f6d 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -15,6 +15,7 @@ tokens are expired. Alternatively, a Stream can be configured with keepalive to always keep workers active. """ import logging +import re import secrets import threading import time @@ -38,6 +39,8 @@ from .hls import async_setup_hls _LOGGER = logging.getLogger(__name__) +STREAM_SOURCE_RE = re.compile("//(.*):(.*)@") + def create_stream(hass, stream_source, options=None): """Create a stream with the specified identfier based on the source url. @@ -173,7 +176,9 @@ class Stream: target=self._run_worker, ) self._thread.start() - _LOGGER.info("Started stream: %s", self.source) + _LOGGER.info( + "Started stream: %s", STREAM_SOURCE_RE.sub("//", str(self.source)) + ) def update_source(self, new_source): """Restart the stream with a new stream source.""" @@ -239,7 +244,9 @@ class Stream: self._thread_quit.set() self._thread.join() self._thread = None - _LOGGER.info("Stopped stream: %s", self.source) + _LOGGER.info( + "Stopped stream: %s", STREAM_SOURCE_RE.sub("//", str(self.source)) + ) async def async_record(self, video_path, duration=30, lookback=5): """Make a .mp4 recording from a provided stream.""" diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 8d1df37d039..5a129356983 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -5,6 +5,7 @@ import logging import av +from . import STREAM_SOURCE_RE from .const import ( AUDIO_CODECS, MAX_MISSING_DTS, @@ -127,7 +128,9 @@ def stream_worker(source, options, segment_buffer, quit_event): try: container = av.open(source, options=options, timeout=STREAM_TIMEOUT) except av.AVError: - _LOGGER.error("Error opening stream %s", source) + _LOGGER.error( + "Error opening stream %s", STREAM_SOURCE_RE.sub("//", str(source)) + ) return try: video_stream = container.streams.video[0] diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py index 2b44c16243b..564da4b108e 100644 --- a/tests/components/stream/test_recorder.py +++ b/tests/components/stream/test_recorder.py @@ -257,3 +257,13 @@ async def test_record_stream_audio( # Verify that the save worker was invoked, then block until its # thread completes and is shutdown completely to avoid thread leaks. await record_worker_sync.join() + + +async def test_recorder_log(hass, caplog): + """Test starting a stream to record logs the url without username and password.""" + await async_setup_component(hass, "stream", {"stream": {}}) + stream = create_stream(hass, "https://abcd:efgh@foo.bar") + with patch.object(hass.config, "is_allowed_path", return_value=True): + await stream.async_record("/example/path") + assert "https://abcd:efgh@foo.bar" not in caplog.text + assert "https://foo.bar" in caplog.text diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py index bef5d366a8f..cf72a90168b 100644 --- a/tests/components/stream/test_worker.py +++ b/tests/components/stream/test_worker.py @@ -574,3 +574,18 @@ async def test_update_stream_source(hass): # Ccleanup stream.stop() + + +async def test_worker_log(hass, caplog): + """Test that the worker logs the url without username and password.""" + stream = Stream(hass, "https://abcd:efgh@foo.bar") + stream.add_provider(STREAM_OUTPUT_FORMAT) + with patch("av.open") as av_open: + av_open.side_effect = av.error.InvalidDataError(-2, "error") + segment_buffer = SegmentBuffer(stream.outputs) + stream_worker( + "https://abcd:efgh@foo.bar", {}, segment_buffer, threading.Event() + ) + await hass.async_block_till_done() + assert "https://abcd:efgh@foo.bar" not in caplog.text + assert "https://foo.bar" in caplog.text From fb03d79daff54b51f639a04586e4ef38bfa1fe8e Mon Sep 17 00:00:00 2001 From: Dewet Diener Date: Tue, 23 Mar 2021 08:18:48 +0000 Subject: [PATCH 560/831] Bump nanoleaf to 0.1.0, add unique IDs (#48135) * bump pynanoleaf and expose model/serial as unique_id * addressed PR feedback --- homeassistant/components/nanoleaf/light.py | 6 ++++++ homeassistant/components/nanoleaf/manifest.json | 2 +- requirements_all.txt | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/nanoleaf/light.py b/homeassistant/components/nanoleaf/light.py index ed1e4877a31..02d8f4751ef 100644 --- a/homeassistant/components/nanoleaf/light.py +++ b/homeassistant/components/nanoleaf/light.py @@ -108,6 +108,7 @@ class NanoleafLight(LightEntity): def __init__(self, light, name): """Initialize an Nanoleaf light.""" + self._unique_id = light.serialNo self._available = True self._brightness = None self._color_temp = None @@ -157,6 +158,11 @@ class NanoleafLight(LightEntity): """Return the warmest color_temp that this light supports.""" return 833 + @property + def unique_id(self): + """Return a unique ID.""" + return self._unique_id + @property def name(self): """Return the display name of this light.""" diff --git a/homeassistant/components/nanoleaf/manifest.json b/homeassistant/components/nanoleaf/manifest.json index 6d953335a34..1f0fbf80983 100644 --- a/homeassistant/components/nanoleaf/manifest.json +++ b/homeassistant/components/nanoleaf/manifest.json @@ -2,6 +2,6 @@ "domain": "nanoleaf", "name": "Nanoleaf", "documentation": "https://www.home-assistant.io/integrations/nanoleaf", - "requirements": ["pynanoleaf==0.0.5"], + "requirements": ["pynanoleaf==0.1.0"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 9c3251c9681..a9ba0da56f5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1551,7 +1551,7 @@ pymyq==3.0.4 pymysensors==0.21.0 # homeassistant.components.nanoleaf -pynanoleaf==0.0.5 +pynanoleaf==0.1.0 # homeassistant.components.nello pynello==2.0.3 From 8900b38c7f9e9e2f18a0be03686da4e1494a526b Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 23 Mar 2021 04:24:42 -0400 Subject: [PATCH 561/831] Add Blink config migration (#46671) --- homeassistant/components/blink/__init__.py | 6 +++++- homeassistant/components/blink/config_flow.py | 2 +- tests/components/blink/test_config_flow.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/blink/__init__.py b/homeassistant/components/blink/__init__.py index c9c03dc654d..1cef331c0bb 100644 --- a/homeassistant/components/blink/__init__.py +++ b/homeassistant/components/blink/__init__.py @@ -67,11 +67,15 @@ async def async_setup(hass, config): async def async_migrate_entry(hass, entry): """Handle migration of a previous version config entry.""" + _LOGGER.debug("Migrating from version %s", entry.version) data = {**entry.data} if entry.version == 1: data.pop("login_response", None) await hass.async_add_executor_job(_reauth_flow_wrapper, hass, data) return False + if entry.version == 2: + await hass.async_add_executor_job(_reauth_flow_wrapper, hass, data) + return False return True @@ -148,7 +152,7 @@ async def async_unload_entry(hass, entry): return True hass.services.async_remove(DOMAIN, SERVICE_REFRESH) - hass.services.async_remove(DOMAIN, SERVICE_SAVE_VIDEO_SCHEMA) + hass.services.async_remove(DOMAIN, SERVICE_SAVE_VIDEO) hass.services.async_remove(DOMAIN, SERVICE_SEND_PIN) return True diff --git a/homeassistant/components/blink/config_flow.py b/homeassistant/components/blink/config_flow.py index 5c77add3118..c6c3f0b27be 100644 --- a/homeassistant/components/blink/config_flow.py +++ b/homeassistant/components/blink/config_flow.py @@ -44,7 +44,7 @@ def _send_blink_2fa_pin(auth, pin): class BlinkConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a Blink config flow.""" - VERSION = 2 + VERSION = 3 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL def __init__(self): diff --git a/tests/components/blink/test_config_flow.py b/tests/components/blink/test_config_flow.py index 91264997769..f7dc89b4cbb 100644 --- a/tests/components/blink/test_config_flow.py +++ b/tests/components/blink/test_config_flow.py @@ -273,7 +273,7 @@ async def test_options_flow(hass): data={"username": "blink@example.com", "password": "example"}, options={}, entry_id=1, - version=2, + version=3, ) config_entry.add_to_hass(hass) From 95370ac84baa7fb1a0494649c1d51daeaa4690ae Mon Sep 17 00:00:00 2001 From: David Keijser Date: Tue, 23 Mar 2021 10:28:19 +0100 Subject: [PATCH 562/831] Change nanoleaf name to configured name instead of hostname (#46407) * nanoleaf: Key config by device id Rather than host which is not stable * nanoleaf: Use pretty name instead of hostname --- homeassistant/components/nanoleaf/light.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/nanoleaf/light.py b/homeassistant/components/nanoleaf/light.py index 02d8f4751ef..a6f453ce2aa 100644 --- a/homeassistant/components/nanoleaf/light.py +++ b/homeassistant/components/nanoleaf/light.py @@ -62,14 +62,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): token = "" if discovery_info is not None: host = discovery_info["host"] - name = discovery_info["hostname"] + name = None + device_id = discovery_info["properties"]["id"] + # if device already exists via config, skip discovery setup if host in hass.data[DATA_NANOLEAF]: return _LOGGER.info("Discovered a new Nanoleaf: %s", discovery_info) conf = load_json(hass.config.path(CONFIG_FILE)) - if conf.get(host, {}).get("token"): - token = conf[host]["token"] + if host in conf and device_id not in conf: + conf[device_id] = conf.pop(host) + save_json(hass.config.path(CONFIG_FILE), conf) + token = conf.get(device_id, {}).get("token", "") else: host = config[CONF_HOST] name = config[CONF_NAME] @@ -94,11 +98,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): nanoleaf_light.token = token try: - nanoleaf_light.available + info = nanoleaf_light.info except Unavailable: _LOGGER.error("Could not connect to Nanoleaf Light: %s on %s", name, host) return + if name is None: + name = info.name + hass.data[DATA_NANOLEAF][host] = nanoleaf_light add_entities([NanoleafLight(nanoleaf_light, name)], True) From 6e07279257f4d29adbd85cd4e8c11806da47fafd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 23 Mar 2021 00:59:04 -1000 Subject: [PATCH 563/831] Add august doorbells to dhcp discovery (#48244) --- homeassistant/components/august/manifest.json | 3 ++- homeassistant/generated/dhcp.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index feadf8f6218..4edacbbf64a 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -6,7 +6,8 @@ "codeowners": ["@bdraco"], "dhcp": [ {"hostname":"connect","macaddress":"D86162*"}, - {"hostname":"connect","macaddress":"B8B7F1*"} + {"hostname":"connect","macaddress":"B8B7F1*"}, + {"hostname":"august*","macaddress":"E076D0*"} ], "config_flow": true } diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index b5d419662ff..c9460a67717 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -16,6 +16,11 @@ DHCP = [ "hostname": "connect", "macaddress": "B8B7F1*" }, + { + "domain": "august", + "hostname": "august*", + "macaddress": "E076D0*" + }, { "domain": "axis", "hostname": "axis-00408c*", From 7bd876beaf41646fd400c60b0bd0d92f373ecb5e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 23 Mar 2021 01:00:06 -1000 Subject: [PATCH 564/831] Add dhcp discovery support to blink (#48243) --- homeassistant/components/blink/manifest.json | 1 + homeassistant/generated/dhcp.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/homeassistant/components/blink/manifest.json b/homeassistant/components/blink/manifest.json index 1c91f1a2295..c88e13cdde7 100644 --- a/homeassistant/components/blink/manifest.json +++ b/homeassistant/components/blink/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/blink", "requirements": ["blinkpy==0.17.0"], "codeowners": ["@fronzbot"], + "dhcp": [{"hostname":"blink*","macaddress":"B85F98*"}], "config_flow": true } diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index c9460a67717..90d846377c6 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -36,6 +36,11 @@ DHCP = [ "hostname": "axis-b8a44f*", "macaddress": "B8A44F*" }, + { + "domain": "blink", + "hostname": "blink*", + "macaddress": "B85F98*" + }, { "domain": "flume", "hostname": "flume-gw-*", From e0e349584942386a26a8c44ff14a7a9781b3d54f Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 23 Mar 2021 12:18:03 +0100 Subject: [PATCH 565/831] Upgrade pylast to 4.2.0 (#48245) --- homeassistant/components/lastfm/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lastfm/manifest.json b/homeassistant/components/lastfm/manifest.json index 9fe3a182844..e732b5d7000 100644 --- a/homeassistant/components/lastfm/manifest.json +++ b/homeassistant/components/lastfm/manifest.json @@ -2,6 +2,6 @@ "domain": "lastfm", "name": "Last.fm", "documentation": "https://www.home-assistant.io/integrations/lastfm", - "requirements": ["pylast==4.1.0"], + "requirements": ["pylast==4.2.0"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index a9ba0da56f5..48ac1318b04 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1482,7 +1482,7 @@ pykwb==0.0.8 pylacrosse==0.4 # homeassistant.components.lastfm -pylast==4.1.0 +pylast==4.2.0 # homeassistant.components.launch_library pylaunches==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 53f7b69d52d..7c3297de9f1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -781,7 +781,7 @@ pykodi==0.2.1 pykulersky==0.5.2 # homeassistant.components.lastfm -pylast==4.1.0 +pylast==4.2.0 # homeassistant.components.forked_daapd pylibrespot-java==0.1.0 From 1095d93892611b40ab8daeded5a7f37b1507484f Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 23 Mar 2021 13:49:44 +0100 Subject: [PATCH 566/831] Increase test coverage of deCONZ device triggers (#48126) * Increase test coverage of deCONZ device triggers * Revert removed new line * Found a way to explicitly assert that exceptions are raised * Remove unnecessary block till done * Fix unnecessary elif * Fix review comments * Remove helper tests --- .../components/deconz/device_trigger.py | 23 +- .../components/deconz/test_device_trigger.py | 322 ++++++++++++++++-- 2 files changed, 299 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/deconz/device_trigger.py b/homeassistant/components/deconz/device_trigger.py index 5ee0a00f04f..e8e43d384b1 100644 --- a/homeassistant/components/deconz/device_trigger.py +++ b/homeassistant/components/deconz/device_trigger.py @@ -16,7 +16,6 @@ from homeassistant.const import ( ) from . import DOMAIN -from .const import LOGGER from .deconz_event import CONF_DECONZ_EVENT, CONF_GESTURE CONF_SUBTYPE = "subtype" @@ -414,16 +413,13 @@ async def async_validate_trigger_config(hass, config): trigger = (config[CONF_TYPE], config[CONF_SUBTYPE]) - if ( - not device - or device.model not in REMOTES - or trigger not in REMOTES[device.model] - ): - if not device: - raise InvalidDeviceAutomationConfig( - f"deCONZ trigger {trigger} device with id " - f"{config[CONF_DEVICE_ID]} not found" - ) + if not device: + raise InvalidDeviceAutomationConfig( + f"deCONZ trigger {trigger} device with ID " + f"{config[CONF_DEVICE_ID]} not found" + ) + + if device.model not in REMOTES or trigger not in REMOTES[device.model]: raise InvalidDeviceAutomationConfig( f"deCONZ trigger {trigger} is not valid for device " f"{device} ({config[CONF_DEVICE_ID]})" @@ -443,8 +439,9 @@ async def async_attach_trigger(hass, config, action, automation_info): deconz_event = _get_deconz_event_from_device_id(hass, device.id) if deconz_event is None: - LOGGER.error("No deconz_event tied to device %s found", device.name) - raise InvalidDeviceAutomationConfig + raise InvalidDeviceAutomationConfig( + f'No deconz_event tied to device "{device.name}" found' + ) event_id = deconz_event.serial diff --git a/tests/components/deconz/test_device_trigger.py b/tests/components/deconz/test_device_trigger.py index 7a39b6fe48f..640a4b61ce3 100644 --- a/tests/components/deconz/test_device_trigger.py +++ b/tests/components/deconz/test_device_trigger.py @@ -1,7 +1,10 @@ """deCONZ device automation tests.""" -from unittest.mock import patch +from unittest.mock import Mock, patch +import pytest + +from homeassistant.components.automation import DOMAIN as AUTOMATION_DOMAIN from homeassistant.components.deconz import device_trigger from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.device_trigger import CONF_SUBTYPE @@ -14,33 +17,23 @@ from homeassistant.const import ( CONF_PLATFORM, CONF_TYPE, ) +from homeassistant.helpers.trigger import async_initialize_triggers +from homeassistant.setup import async_setup_component from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration -from tests.common import assert_lists_same, async_get_device_automations +from tests.common import ( + assert_lists_same, + async_get_device_automations, + async_mock_service, +) from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 -SENSORS = { - "1": { - "config": { - "alert": "none", - "battery": 60, - "group": "10", - "on": True, - "reachable": True, - }, - "ep": 1, - "etag": "1b355c0b6d2af28febd7ca9165881952", - "manufacturername": "IKEA of Sweden", - "mode": 1, - "modelid": "TRADFRI on/off switch", - "name": "TRÅDFRI on/off switch ", - "state": {"buttonevent": 2002, "lastupdated": "2019-09-07T07:39:39"}, - "swversion": "1.4.018", - "type": "ZHASwitch", - "uniqueid": "d0:cf:5e:ff:fe:71:a4:3a-01-1000", - } -} + +@pytest.fixture +def automation_calls(hass): + """Track automation calls to a mock service.""" + return async_mock_service(hass, "test", "automation") async def test_get_triggers(hass, aioclient_mock): @@ -76,8 +69,6 @@ async def test_get_triggers(hass, aioclient_mock): identifiers={(DECONZ_DOMAIN, "d0:cf:5e:ff:fe:71:a4:3a")} ) - assert device_trigger._get_deconz_event_from_device_id(hass, device.id) - triggers = await async_get_device_automations(hass, "trigger", device.id) expected_triggers = [ @@ -135,14 +126,279 @@ async def test_get_triggers(hass, aioclient_mock): assert_lists_same(triggers, expected_triggers) -async def test_helper_no_match(hass, aioclient_mock): - """Verify trigger helper returns None when no event could be matched.""" +async def test_get_triggers_manage_unsupported_remotes(hass, aioclient_mock): + """Verify no triggers for an unsupported remote.""" + data = { + "sensors": { + "1": { + "config": { + "alert": "none", + "group": "10", + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "1b355c0b6d2af28febd7ca9165881952", + "manufacturername": "IKEA of Sweden", + "mode": 1, + "modelid": "Unsupported model", + "name": "TRÅDFRI on/off switch ", + "state": {"buttonevent": 2002, "lastupdated": "2019-09-07T07:39:39"}, + "swversion": "1.4.018", + "type": "ZHASwitch", + "uniqueid": "d0:cf:5e:ff:fe:71:a4:3a-01-1000", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, "d0:cf:5e:ff:fe:71:a4:3a")} + ) + + triggers = await async_get_device_automations(hass, "trigger", device.id) + + expected_triggers = [] + + assert_lists_same(triggers, expected_triggers) + + +async def test_functional_device_trigger( + hass, aioclient_mock, mock_deconz_websocket, automation_calls +): + """Test proper matching and attachment of device trigger automation.""" + await async_setup_component(hass, "persistent_notification", {}) + + data = { + "sensors": { + "1": { + "config": { + "alert": "none", + "battery": 60, + "group": "10", + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "1b355c0b6d2af28febd7ca9165881952", + "manufacturername": "IKEA of Sweden", + "mode": 1, + "modelid": "TRADFRI on/off switch", + "name": "TRÅDFRI on/off switch ", + "state": {"buttonevent": 2002, "lastupdated": "2019-09-07T07:39:39"}, + "swversion": "1.4.018", + "type": "ZHASwitch", + "uniqueid": "d0:cf:5e:ff:fe:71:a4:3a-01-1000", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, "d0:cf:5e:ff:fe:71:a4:3a")} + ) + + assert await async_setup_component( + hass, + AUTOMATION_DOMAIN, + { + AUTOMATION_DOMAIN: [ + { + "trigger": { + CONF_PLATFORM: "device", + CONF_DOMAIN: DECONZ_DOMAIN, + CONF_DEVICE_ID: device.id, + CONF_TYPE: device_trigger.CONF_SHORT_PRESS, + CONF_SUBTYPE: device_trigger.CONF_TURN_ON, + }, + "action": { + "service": "test.automation", + "data_template": {"some": "test_trigger_button_press"}, + }, + }, + ] + }, + ) + + assert len(hass.states.async_entity_ids(AUTOMATION_DOMAIN)) == 1 + + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "1", + "state": {"buttonevent": 1002}, + } + await mock_deconz_websocket(data=event_changed_sensor) + await hass.async_block_till_done() + + assert len(automation_calls) == 1 + assert automation_calls[0].data["some"] == "test_trigger_button_press" + + +async def test_validate_trigger_unknown_device( + hass, aioclient_mock, mock_deconz_websocket +): + """Test unknown device does not return a trigger config.""" await setup_deconz_integration(hass, aioclient_mock) - deconz_event = device_trigger._get_deconz_event_from_device_id(hass, "mock-id") - assert not deconz_event + + assert await async_setup_component( + hass, + AUTOMATION_DOMAIN, + { + AUTOMATION_DOMAIN: [ + { + "trigger": { + CONF_PLATFORM: "device", + CONF_DOMAIN: DECONZ_DOMAIN, + CONF_DEVICE_ID: "unknown device", + CONF_TYPE: device_trigger.CONF_SHORT_PRESS, + CONF_SUBTYPE: device_trigger.CONF_TURN_ON, + }, + "action": { + "service": "test.automation", + "data_template": {"some": "test_trigger_button_press"}, + }, + }, + ] + }, + ) + await hass.async_block_till_done() + + assert len(hass.states.async_entity_ids(AUTOMATION_DOMAIN)) == 0 -async def test_helper_no_gateway_exist(hass): - """Verify trigger helper returns None when no gateway exist.""" - deconz_event = device_trigger._get_deconz_event_from_device_id(hass, "mock-id") - assert not deconz_event +async def test_validate_trigger_unsupported_device( + hass, aioclient_mock, mock_deconz_websocket +): + """Test unsupported device doesn't return a trigger config.""" + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + identifiers={(DECONZ_DOMAIN, "d0:cf:5e:ff:fe:71:a4:3a")}, + model="unsupported", + ) + + assert await async_setup_component( + hass, + AUTOMATION_DOMAIN, + { + AUTOMATION_DOMAIN: [ + { + "trigger": { + CONF_PLATFORM: "device", + CONF_DOMAIN: DECONZ_DOMAIN, + CONF_DEVICE_ID: device.id, + CONF_TYPE: device_trigger.CONF_SHORT_PRESS, + CONF_SUBTYPE: device_trigger.CONF_TURN_ON, + }, + "action": { + "service": "test.automation", + "data_template": {"some": "test_trigger_button_press"}, + }, + }, + ] + }, + ) + await hass.async_block_till_done() + + assert len(hass.states.async_entity_ids(AUTOMATION_DOMAIN)) == 0 + + +async def test_validate_trigger_unsupported_trigger( + hass, aioclient_mock, mock_deconz_websocket +): + """Test unsupported trigger does not return a trigger config.""" + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + identifiers={(DECONZ_DOMAIN, "d0:cf:5e:ff:fe:71:a4:3a")}, + model="TRADFRI on/off switch", + ) + + trigger_config = { + CONF_PLATFORM: "device", + CONF_DOMAIN: DECONZ_DOMAIN, + CONF_DEVICE_ID: device.id, + CONF_TYPE: "unsupported", + CONF_SUBTYPE: device_trigger.CONF_TURN_ON, + } + + assert await async_setup_component( + hass, + AUTOMATION_DOMAIN, + { + AUTOMATION_DOMAIN: [ + { + "trigger": trigger_config, + "action": { + "service": "test.automation", + "data_template": {"some": "test_trigger_button_press"}, + }, + }, + ] + }, + ) + await hass.async_block_till_done() + + assert len(hass.states.async_entity_ids(AUTOMATION_DOMAIN)) == 0 + + +async def test_attach_trigger_no_matching_event( + hass, aioclient_mock, mock_deconz_websocket +): + """Test no matching event for device doesn't return a trigger config.""" + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + identifiers={(DECONZ_DOMAIN, "d0:cf:5e:ff:fe:71:a4:3a")}, + name="Tradfri switch", + model="TRADFRI on/off switch", + ) + + trigger_config = { + CONF_PLATFORM: "device", + CONF_DOMAIN: DECONZ_DOMAIN, + CONF_DEVICE_ID: device.id, + CONF_TYPE: device_trigger.CONF_SHORT_PRESS, + CONF_SUBTYPE: device_trigger.CONF_TURN_ON, + } + + assert await async_setup_component( + hass, + AUTOMATION_DOMAIN, + { + AUTOMATION_DOMAIN: [ + { + "trigger": trigger_config, + "action": { + "service": "test.automation", + "data_template": {"some": "test_trigger_button_press"}, + }, + }, + ] + }, + ) + await hass.async_block_till_done() + + assert len(hass.states.async_entity_ids(AUTOMATION_DOMAIN)) == 1 + + # Assert that deCONZ async_attach_trigger raises InvalidDeviceAutomationConfig + assert not await async_initialize_triggers( + hass, + [trigger_config], + action=Mock(), + domain=AUTOMATION_DOMAIN, + name="mock-name", + log_cb=Mock(), + ) From 9656f260a4fa12ae5deab2d1dbf7f68cc59df89d Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Tue, 23 Mar 2021 14:28:35 +0100 Subject: [PATCH 567/831] Add tests for Netatmo (#46372) Co-authored-by: Erik Montnemery Co-authored-by: Martin Hjelmare --- .coveragerc | 3 - tests/components/netatmo/test_api.py | 14 -- tests/components/netatmo/test_init.py | 215 ++++++++++++++++++++++++++ 3 files changed, 215 insertions(+), 17 deletions(-) delete mode 100644 tests/components/netatmo/test_api.py create mode 100644 tests/components/netatmo/test_init.py diff --git a/.coveragerc b/.coveragerc index 48f71f5a998..972831c4bd4 100644 --- a/.coveragerc +++ b/.coveragerc @@ -643,10 +643,7 @@ omit = homeassistant/components/nederlandse_spoorwegen/sensor.py homeassistant/components/nello/lock.py homeassistant/components/nest/legacy/* - homeassistant/components/netatmo/__init__.py homeassistant/components/netatmo/data_handler.py - homeassistant/components/netatmo/helper.py - homeassistant/components/netatmo/netatmo_entity_base.py homeassistant/components/netatmo/sensor.py homeassistant/components/netdata/sensor.py homeassistant/components/netgear/device_tracker.py diff --git a/tests/components/netatmo/test_api.py b/tests/components/netatmo/test_api.py deleted file mode 100644 index 76d16d10515..00000000000 --- a/tests/components/netatmo/test_api.py +++ /dev/null @@ -1,14 +0,0 @@ -"""The tests for the Netatmo oauth2 api.""" -from unittest.mock import patch - -from homeassistant.components.netatmo import api - - -async def test_api(hass, config_entry): - """Test auth instantiation.""" - with patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ) as fake_implementation: - auth = api.ConfigEntryNetatmoAuth(hass, config_entry, fake_implementation) - - assert isinstance(auth, api.ConfigEntryNetatmoAuth) diff --git a/tests/components/netatmo/test_init.py b/tests/components/netatmo/test_init.py new file mode 100644 index 00000000000..49e3ab14684 --- /dev/null +++ b/tests/components/netatmo/test_init.py @@ -0,0 +1,215 @@ +"""The tests for Netatmo component.""" +from time import time +from unittest.mock import patch + +from homeassistant import config_entries +from homeassistant.components.netatmo import DOMAIN +from homeassistant.const import CONF_WEBHOOK_ID +from homeassistant.setup import async_setup_component + +from .common import fake_post_request, simulate_webhook + +from tests.common import MockConfigEntry +from tests.components.cloud import mock_cloud + +# Fake webhook thermostat mode change to "Max" +FAKE_WEBHOOK = { + "room_id": "2746182631", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2746182631", + "name": "Livingroom", + "type": "livingroom", + "therm_setpoint_mode": "max", + "therm_setpoint_end_time": 1612749189, + } + ], + "modules": [ + {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} + ], + }, + "mode": "max", + "event_type": "set_point", + "push_type": "display_change", +} + +FAKE_WEBHOOK_ACTIVATION = { + "push_type": "webhook_activation", +} + + +async def test_setup_component(hass): + """Test setup and teardown of the netatmo component.""" + config_entry = MockConfigEntry( + domain="netatmo", + data={ + "auth_implementation": "cloud", + "token": { + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": 60, + "expires_at": time() + 1000, + "scope": "read_station", + }, + }, + ) + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.netatmo.api.ConfigEntryNetatmoAuth" + ) as mock_auth, patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ) as mock_impl, patch( + "homeassistant.components.webhook.async_generate_url" + ) as mock_webhook: + mock_auth.return_value.post_request.side_effect = fake_post_request + assert await async_setup_component(hass, "netatmo", {}) + + await hass.async_block_till_done() + + mock_auth.assert_called_once() + mock_impl.assert_called_once() + mock_webhook.assert_called_once() + + assert config_entry.state == config_entries.ENTRY_STATE_LOADED + assert hass.config_entries.async_entries(DOMAIN) + assert len(hass.states.async_all()) > 0 + + for config_entry in hass.config_entries.async_entries("netatmo"): + await hass.config_entries.async_remove(config_entry.entry_id) + + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 0 + assert not hass.config_entries.async_entries(DOMAIN) + + +async def test_setup_component_with_config(hass, config_entry): + """Test setup of the netatmo component with dev account.""" + with patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ) as mock_impl, patch( + "homeassistant.components.webhook.async_generate_url" + ) as mock_webhook, patch( + "pyatmo.auth.NetatmoOAuth2.post_request" + ) as fake_post_requests, patch( + "homeassistant.components.netatmo.PLATFORMS", ["sensor"] + ): + assert await async_setup_component( + hass, "netatmo", {"netatmo": {"client_id": "123", "client_secret": "abc"}} + ) + + await hass.async_block_till_done() + + fake_post_requests.assert_called() + mock_impl.assert_called_once() + mock_webhook.assert_called_once() + + assert config_entry.state == config_entries.ENTRY_STATE_LOADED + assert hass.config_entries.async_entries(DOMAIN) + assert len(hass.states.async_all()) > 0 + + +async def test_setup_component_with_webhook(hass, entry): + """Test setup and teardown of the netatmo component with webhook registration.""" + webhook_id = entry.data[CONF_WEBHOOK_ID] + await simulate_webhook(hass, webhook_id, FAKE_WEBHOOK_ACTIVATION) + + assert len(hass.states.async_all()) > 0 + + webhook_id = entry.data[CONF_WEBHOOK_ID] + await simulate_webhook(hass, webhook_id, FAKE_WEBHOOK_ACTIVATION) + + # Assert webhook is established successfully + climate_entity_livingroom = "climate.netatmo_livingroom" + assert hass.states.get(climate_entity_livingroom).state == "auto" + await simulate_webhook(hass, webhook_id, FAKE_WEBHOOK) + assert hass.states.get(climate_entity_livingroom).state == "heat" + + for config_entry in hass.config_entries.async_entries("netatmo"): + await hass.config_entries.async_remove(config_entry.entry_id) + + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 0 + assert len(hass.config_entries.async_entries(DOMAIN)) == 0 + + +async def test_setup_without_https(hass, config_entry): + """Test if set up with cloud link and without https.""" + hass.config.components.add("cloud") + with patch( + "homeassistant.helpers.network.get_url", + return_value="https://example.nabu.casa", + ), patch( + "homeassistant.components.netatmo.api.ConfigEntryNetatmoAuth" + ) as mock_auth, patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), patch( + "homeassistant.components.webhook.async_generate_url" + ) as mock_webhook: + mock_auth.return_value.post_request.side_effect = fake_post_request + mock_webhook.return_value = "http://example.com" + assert await async_setup_component( + hass, "netatmo", {"netatmo": {"client_id": "123", "client_secret": "abc"}} + ) + + await hass.async_block_till_done() + + webhook_id = config_entry.data[CONF_WEBHOOK_ID] + await simulate_webhook(hass, webhook_id, FAKE_WEBHOOK_ACTIVATION) + + # Assert webhook is established successfully + climate_entity_livingroom = "climate.netatmo_livingroom" + assert hass.states.get(climate_entity_livingroom).state == "auto" + await simulate_webhook(hass, webhook_id, FAKE_WEBHOOK) + assert hass.states.get(climate_entity_livingroom).state == "auto" + + +async def test_setup_with_cloud(hass, config_entry): + """Test if set up with active cloud subscription.""" + await mock_cloud(hass) + await hass.async_block_till_done() + + with patch( + "homeassistant.components.cloud.async_is_logged_in", return_value=True + ), patch( + "homeassistant.components.cloud.async_active_subscription", return_value=True + ), patch( + "homeassistant.components.cloud.async_create_cloudhook", + return_value="https://hooks.nabu.casa/ABCD", + ) as fake_create_cloudhook, patch( + "homeassistant.components.cloud.async_delete_cloudhook" + ) as fake_delete_cloudhook, patch( + "homeassistant.components.netatmo.api.ConfigEntryNetatmoAuth" + ) as mock_auth, patch( + "homeassistant.components.netatmo.PLATFORMS", [] + ), patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), patch( + "homeassistant.components.webhook.async_generate_url" + ): + mock_auth.return_value.post_request.side_effect = fake_post_request + assert await async_setup_component( + hass, "netatmo", {"netatmo": {"client_id": "123", "client_secret": "abc"}} + ) + assert hass.components.cloud.async_active_subscription() is True + fake_create_cloudhook.assert_called_once() + + assert ( + hass.config_entries.async_entries("netatmo")[0].data["cloudhook_url"] + == "https://hooks.nabu.casa/ABCD" + ) + + await hass.async_block_till_done() + assert hass.config_entries.async_entries(DOMAIN) + + for config_entry in hass.config_entries.async_entries("netatmo"): + await hass.config_entries.async_remove(config_entry.entry_id) + fake_delete_cloudhook.assert_called_once() + + await hass.async_block_till_done() + assert not hass.config_entries.async_entries(DOMAIN) From 6932cf9534856e2396bbe470fc7f170c27b4943e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 23 Mar 2021 14:36:43 +0100 Subject: [PATCH 568/831] Use contextlib.suppress where possible (#48189) --- .../components/acmeda/config_flow.py | 12 +++---- homeassistant/components/amcrest/__init__.py | 7 ++-- .../components/amcrest/binary_sensor.py | 7 ++-- homeassistant/components/api/__init__.py | 9 ++--- homeassistant/components/apns/notify.py | 5 ++- .../components/arcam_fmj/__init__.py | 5 ++- homeassistant/components/automation/config.py | 6 ++-- homeassistant/components/bizkaibus/sensor.py | 6 ++-- homeassistant/components/bme280/sensor.py | 5 ++- homeassistant/components/broadlink/device.py | 5 ++- homeassistant/components/cast/media_player.py | 18 ++++------ .../components/cloud/alexa_config.py | 5 ++- .../components/configurator/__init__.py | 11 ++---- .../components/denonavr/media_player.py | 5 ++- homeassistant/components/dht/sensor.py | 5 ++- homeassistant/components/dsmr/__init__.py | 5 ++- homeassistant/components/dsmr/sensor.py | 5 ++- .../components/dublin_bus_transport/sensor.py | 5 ++- .../components/emulated_hue/__init__.py | 5 ++- homeassistant/components/fibaro/sensor.py | 10 +++--- .../components/forked_daapd/config_flow.py | 5 ++- .../components/fritzbox_callmonitor/base.py | 9 ++--- homeassistant/components/graphite/__init__.py | 5 ++- .../components/hangouts/hangouts_bot.py | 5 ++- homeassistant/components/html5/notify.py | 9 ++--- homeassistant/components/http/ban.py | 5 ++- .../components/huawei_lte/__init__.py | 5 ++- .../hunterdouglas_powerview/cover.py | 7 ++-- homeassistant/components/hyperion/__init__.py | 5 ++- .../components/hyperion/config_flow.py | 5 ++- homeassistant/components/influxdb/__init__.py | 14 +++----- homeassistant/components/insteon/__init__.py | 5 ++- homeassistant/components/izone/config_flow.py | 5 ++- .../components/keyboard_remote/__init__.py | 5 ++- homeassistant/components/kodi/browse_media.py | 5 ++- homeassistant/components/logbook/__init__.py | 5 ++- .../components/media_player/__init__.py | 20 +++++------ .../minecraft_server/config_flow.py | 5 ++- .../components/mobile_app/__init__.py | 9 ++--- .../components/mobile_app/http_api.py | 5 ++- .../components/mobile_app/webhook.py | 5 ++- .../components/motion_blinds/__init__.py | 11 ++---- homeassistant/components/mpd/media_player.py | 5 ++- .../components/mqtt/light/schema_json.py | 5 ++- homeassistant/components/mqtt/trigger.py | 5 ++- .../components/mysensors/config_flow.py | 6 ++-- homeassistant/components/onvif/device.py | 13 +++---- homeassistant/components/onvif/event.py | 10 +++--- homeassistant/components/ozw/__init__.py | 5 ++- .../components/ping/binary_sensor.py | 5 ++- homeassistant/components/plant/__init__.py | 5 ++- homeassistant/components/ps4/media_player.py | 5 ++- homeassistant/components/rachio/switch.py | 5 ++- .../components/rejseplanen/sensor.py | 5 ++- homeassistant/components/sharkiq/__init__.py | 16 ++++----- .../components/shell_command/__init__.py | 5 ++- .../components/solaredge_local/sensor.py | 9 ++--- .../components/sonos/media_browser.py | 9 ++--- .../components/sonos/media_player.py | 5 ++- homeassistant/components/spaceapi/__init__.py | 34 ++++++------------- .../components/swisscom/device_tracker.py | 5 ++- homeassistant/components/ted5000/sensor.py | 5 ++- homeassistant/components/tplink/switch.py | 6 ++-- .../components/transmission/sensor.py | 6 ++-- homeassistant/components/upb/config_flow.py | 8 ++--- homeassistant/components/verisure/__init__.py | 5 ++- homeassistant/components/webostv/__init__.py | 7 ++-- .../components/webostv/media_player.py | 7 ++-- homeassistant/components/wink/sensor.py | 9 ++--- homeassistant/components/xbox/__init__.py | 5 ++- homeassistant/components/xbox/media_source.py | 6 ++-- homeassistant/components/zabbix/__init__.py | 6 ++-- homeassistant/components/zeroconf/__init__.py | 13 +++---- homeassistant/components/zeroconf/usage.py | 5 ++- .../components/zha/core/channels/lighting.py | 5 ++- homeassistant/helpers/aiohttp_client.py | 11 +++--- homeassistant/helpers/network.py | 17 +++------- homeassistant/helpers/script.py | 6 ++-- homeassistant/helpers/storage.py | 5 ++- homeassistant/helpers/template.py | 5 ++- homeassistant/loader.py | 5 ++- homeassistant/util/dt.py | 6 ++-- homeassistant/util/ruamel_yaml.py | 5 ++- tests/components/buienradar/test_camera.py | 5 ++- tests/components/demo/test_init.py | 5 ++- tests/util/test_timeout.py | 21 ++++-------- 86 files changed, 238 insertions(+), 398 deletions(-) diff --git a/homeassistant/components/acmeda/config_flow.py b/homeassistant/components/acmeda/config_flow.py index b8913e31f2a..a849e49ddf4 100644 --- a/homeassistant/components/acmeda/config_flow.py +++ b/homeassistant/components/acmeda/config_flow.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress import aiopulse import async_timeout @@ -37,13 +38,10 @@ class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): } hubs = [] - try: - with async_timeout.timeout(5): - async for hub in aiopulse.Hub.discover(): - if hub.id not in already_configured: - hubs.append(hub) - except asyncio.TimeoutError: - pass + with suppress(asyncio.TimeoutError), async_timeout.timeout(5): + async for hub in aiopulse.Hub.discover(): + if hub.id not in already_configured: + hubs.append(hub) if len(hubs) == 0: return self.async_abort(reason="no_devices_found") diff --git a/homeassistant/components/amcrest/__init__.py b/homeassistant/components/amcrest/__init__.py index 3baad1ac88e..71c277e578c 100644 --- a/homeassistant/components/amcrest/__init__.py +++ b/homeassistant/components/amcrest/__init__.py @@ -1,4 +1,5 @@ """Support for Amcrest IP cameras.""" +from contextlib import suppress from datetime import timedelta import logging import threading @@ -191,10 +192,8 @@ class AmcrestChecker(Http): def _wrap_test_online(self, now): """Test if camera is back online.""" _LOGGER.debug("Testing if %s back online", self._wrap_name) - try: - self.current_time - except AmcrestError: - pass + with suppress(AmcrestError): + self.current_time # pylint: disable=pointless-statement def _monitor_events(hass, name, api, event_codes): diff --git a/homeassistant/components/amcrest/binary_sensor.py b/homeassistant/components/amcrest/binary_sensor.py index 0824021f31e..0add382b81f 100644 --- a/homeassistant/components/amcrest/binary_sensor.py +++ b/homeassistant/components/amcrest/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Amcrest IP camera binary sensors.""" +from contextlib import suppress from datetime import timedelta import logging @@ -154,10 +155,8 @@ class AmcrestBinarySensor(BinarySensorEntity): # Send a command to the camera to test if we can still communicate with it. # Override of Http.command() in __init__.py will set self._api.available # accordingly. - try: - self._api.current_time - except AmcrestError: - pass + with suppress(AmcrestError): + self._api.current_time # pylint: disable=pointless-statement self._state = self._api.available def _update_others(self): diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index 47c6518f7b1..a91d8540286 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -1,5 +1,6 @@ """Rest API for Home Assistant.""" import asyncio +from contextlib import suppress import json import logging @@ -196,15 +197,11 @@ class APIDiscoveryView(HomeAssistantView): ATTR_VERSION: __version__, } - try: + with suppress(NoURLAvailableError): data["external_url"] = get_url(hass, allow_internal=False) - except NoURLAvailableError: - pass - try: + with suppress(NoURLAvailableError): data["internal_url"] = get_url(hass, allow_external=False) - except NoURLAvailableError: - pass # Set old base URL based on external or internal data["base_url"] = data["external_url"] or data["internal_url"] diff --git a/homeassistant/components/apns/notify.py b/homeassistant/components/apns/notify.py index 6f8de7e9c84..c9e12a20863 100644 --- a/homeassistant/components/apns/notify.py +++ b/homeassistant/components/apns/notify.py @@ -1,4 +1,5 @@ """APNS Notification platform.""" +from contextlib import suppress import logging from apns2.client import APNsClient @@ -155,7 +156,7 @@ class ApnsNotificationService(BaseNotificationService): self.device_states = {} self.topic = topic - try: + with suppress(FileNotFoundError): self.devices = { str(key): ApnsDevice( str(key), @@ -165,8 +166,6 @@ class ApnsNotificationService(BaseNotificationService): ) for (key, value) in load_yaml_config_file(self.yaml_path).items() } - except FileNotFoundError: - pass tracking_ids = [ device.full_tracking_device_id diff --git a/homeassistant/components/arcam_fmj/__init__.py b/homeassistant/components/arcam_fmj/__init__.py index 686e7c2de16..fe62c41c061 100644 --- a/homeassistant/components/arcam_fmj/__init__.py +++ b/homeassistant/components/arcam_fmj/__init__.py @@ -1,5 +1,6 @@ """Arcam component.""" import asyncio +from contextlib import suppress import logging from arcam.fmj import ConnectionFailed @@ -28,10 +29,8 @@ CONFIG_SCHEMA = cv.deprecated(DOMAIN) async def _await_cancel(task): task.cancel() - try: + with suppress(asyncio.CancelledError): await task - except asyncio.CancelledError: - pass async def async_setup(hass: HomeAssistantType, config: ConfigType): diff --git a/homeassistant/components/automation/config.py b/homeassistant/components/automation/config.py index 5abff8fe974..d8837fe03c7 100644 --- a/homeassistant/components/automation/config.py +++ b/homeassistant/components/automation/config.py @@ -1,5 +1,6 @@ """Config validation helper for the automation integration.""" import asyncio +from contextlib import suppress import voluptuous as vol @@ -88,11 +89,8 @@ class AutomationConfig(dict): async def _try_async_validate_config_item(hass, config, full_config=None): """Validate config item.""" raw_config = None - try: + with suppress(ValueError): raw_config = dict(config) - except ValueError: - # Invalid config - pass try: config = await async_validate_config_item(hass, config, full_config) diff --git a/homeassistant/components/bizkaibus/sensor.py b/homeassistant/components/bizkaibus/sensor.py index a905498328b..d0cade31a72 100644 --- a/homeassistant/components/bizkaibus/sensor.py +++ b/homeassistant/components/bizkaibus/sensor.py @@ -1,4 +1,6 @@ """Support for Bizkaibus, Biscay (Basque Country, Spain) Bus service.""" +from contextlib import suppress + from bizkaibus.bizkaibus import BizkaibusData import voluptuous as vol @@ -61,10 +63,8 @@ class BizkaibusSensor(SensorEntity): def update(self): """Get the latest data from the webservice.""" self.data.update() - try: + with suppress(TypeError): self._state = self.data.info[0][ATTR_DUE_IN] - except TypeError: - pass class Bizkaibus: diff --git a/homeassistant/components/bme280/sensor.py b/homeassistant/components/bme280/sensor.py index ffe68479341..2c3ab0303b0 100644 --- a/homeassistant/components/bme280/sensor.py +++ b/homeassistant/components/bme280/sensor.py @@ -1,4 +1,5 @@ """Support for BME280 temperature, humidity and pressure sensor.""" +from contextlib import suppress from datetime import timedelta from functools import partial import logging @@ -110,13 +111,11 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= sensor_handler = await hass.async_add_executor_job(BME280Handler, sensor) dev = [] - try: + with suppress(KeyError): for variable in config[CONF_MONITORED_CONDITIONS]: dev.append( BME280Sensor(sensor_handler, variable, SENSOR_TYPES[variable][1], name) ) - except KeyError: - pass async_add_entities(dev, True) diff --git a/homeassistant/components/broadlink/device.py b/homeassistant/components/broadlink/device.py index c460040c12b..104118a697c 100644 --- a/homeassistant/components/broadlink/device.py +++ b/homeassistant/components/broadlink/device.py @@ -1,5 +1,6 @@ """Support for Broadlink devices.""" import asyncio +from contextlib import suppress from functools import partial import logging @@ -102,10 +103,8 @@ class BroadlinkDevice: self.hass.data[DOMAIN].devices[config.entry_id] = self self.reset_jobs.append(config.add_update_listener(self.async_update)) - try: + with suppress(BroadlinkException, OSError): self.fw_version = await self.hass.async_add_executor_job(api.get_fwversion) - except (BroadlinkException, OSError): - pass # Forward entry setup to related domains. tasks = ( diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 2a1aeaacaa6..9532c25d81a 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress from datetime import timedelta import functools as ft import json @@ -330,21 +331,14 @@ class CastDevice(MediaPlayerEntity): tts_base_url = None url_description = "" if "tts" in self.hass.config.components: - try: + with suppress(KeyError): # base_url not configured tts_base_url = self.hass.components.tts.get_base_url(self.hass) - except KeyError: - # base_url not configured, ignore - pass - try: + + with suppress(NoURLAvailableError): # external_url not configured external_url = get_url(self.hass, allow_internal=False) - except NoURLAvailableError: - # external_url not configured, ignore - pass - try: + + with suppress(NoURLAvailableError): # internal_url not configured internal_url = get_url(self.hass, allow_external=False) - except NoURLAvailableError: - # internal_url not configured, ignore - pass if media_status.content_id: if tts_base_url and media_status.content_id.startswith(tts_base_url): diff --git a/homeassistant/components/cloud/alexa_config.py b/homeassistant/components/cloud/alexa_config.py index 2d4714b4c81..138b2db0b8c 100644 --- a/homeassistant/components/cloud/alexa_config.py +++ b/homeassistant/components/cloud/alexa_config.py @@ -1,5 +1,6 @@ """Alexa configuration for Home Assistant Cloud.""" import asyncio +from contextlib import suppress from datetime import timedelta import logging @@ -322,7 +323,5 @@ class AlexaConfig(alexa_config.AbstractConfig): if "old_entity_id" in event.data: to_remove.append(event.data["old_entity_id"]) - try: + with suppress(alexa_errors.NoTokenAvailable): await self._sync_helper(to_update, to_remove) - except alexa_errors.NoTokenAvailable: - pass diff --git a/homeassistant/components/configurator/__init__.py b/homeassistant/components/configurator/__init__.py index 9ddd9d76ed9..e988e58f76b 100644 --- a/homeassistant/components/configurator/__init__.py +++ b/homeassistant/components/configurator/__init__.py @@ -6,6 +6,7 @@ This will return a request id that has to be used for future calls. A callback has to be provided to `request_config` which will be called when the user has submitted configuration information. """ +from contextlib import suppress import functools as ft from homeassistant.const import ( @@ -96,11 +97,8 @@ def request_config(hass, *args, **kwargs): @async_callback def async_notify_errors(hass, request_id, error): """Add errors to a config request.""" - try: + with suppress(KeyError): # If request_id does not exist hass.data[DATA_REQUESTS][request_id].async_notify_errors(request_id, error) - except KeyError: - # If request_id does not exist - pass @bind_hass @@ -115,11 +113,8 @@ def notify_errors(hass, request_id, error): @async_callback def async_request_done(hass, request_id): """Mark a configuration request as done.""" - try: + with suppress(KeyError): # If request_id does not exist hass.data[DATA_REQUESTS].pop(request_id).async_request_done(request_id) - except KeyError: - # If request_id does not exist - pass @bind_hass diff --git a/homeassistant/components/denonavr/media_player.py b/homeassistant/components/denonavr/media_player.py index c1fbb15a263..ea484a10877 100644 --- a/homeassistant/components/denonavr/media_player.py +++ b/homeassistant/components/denonavr/media_player.py @@ -1,5 +1,6 @@ """Support for Denon AVR receivers using their HTTP interface.""" +from contextlib import suppress import logging from homeassistant.components.media_player import MediaPlayerEntity @@ -372,11 +373,9 @@ class DenonDevice(MediaPlayerEntity): volume_denon = float((volume * 100) - 80) if volume_denon > 18: volume_denon = float(18) - try: + with suppress(ValueError): if self._receiver.set_volume(volume_denon): self._volume = volume_denon - except ValueError: - pass def mute_volume(self, mute): """Send mute command.""" diff --git a/homeassistant/components/dht/sensor.py b/homeassistant/components/dht/sensor.py index c1fa32e71d1..602a0f2b76f 100644 --- a/homeassistant/components/dht/sensor.py +++ b/homeassistant/components/dht/sensor.py @@ -1,4 +1,5 @@ """Support for Adafruit DHT temperature and humidity sensor.""" +from contextlib import suppress from datetime import timedelta import logging @@ -74,7 +75,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): dev = [] name = config[CONF_NAME] - try: + with suppress(KeyError): for variable in config[CONF_MONITORED_CONDITIONS]: dev.append( DHTSensor( @@ -86,8 +87,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None): humidity_offset, ) ) - except KeyError: - pass add_entities(dev, True) diff --git a/homeassistant/components/dsmr/__init__.py b/homeassistant/components/dsmr/__init__.py index cda3838bf78..09383f8c4aa 100644 --- a/homeassistant/components/dsmr/__init__.py +++ b/homeassistant/components/dsmr/__init__.py @@ -1,6 +1,7 @@ """The dsmr component.""" import asyncio from asyncio import CancelledError +from contextlib import suppress from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -36,10 +37,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): # Cancel the reconnect task task.cancel() - try: + with suppress(CancelledError): await task - except CancelledError: - pass unload_ok = all( await asyncio.gather( diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index 12304fa51d4..d17c3b780e4 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -3,6 +3,7 @@ from __future__ import annotations import asyncio from asyncio import CancelledError +from contextlib import suppress from datetime import timedelta from functools import partial import logging @@ -342,10 +343,8 @@ class DSMREntity(SensorEntity): if self._obis == obis_ref.ELECTRICITY_ACTIVE_TARIFF: return self.translate_tariff(value, self._config[CONF_DSMR_VERSION]) - try: + with suppress(TypeError): value = round(float(value), self._config[CONF_PRECISION]) - except TypeError: - pass if value is not None: return value diff --git a/homeassistant/components/dublin_bus_transport/sensor.py b/homeassistant/components/dublin_bus_transport/sensor.py index 909eed09b9a..dbe1d10b553 100644 --- a/homeassistant/components/dublin_bus_transport/sensor.py +++ b/homeassistant/components/dublin_bus_transport/sensor.py @@ -4,6 +4,7 @@ Support for Dublin RTPI information from data.dublinked.ie. For more info on the API see : https://data.gov.ie/dataset/real-time-passenger-information-rtpi-for-dublin-bus-bus-eireann-luas-and-irish-rail/resource/4b9f2c4f-6bf5-4958-a43a-f12dab04cf61 """ +from contextlib import suppress from datetime import datetime, timedelta import requests @@ -117,10 +118,8 @@ class DublinPublicTransportSensor(SensorEntity): """Get the latest data from opendata.ch and update the states.""" self.data.update() self._times = self.data.info - try: + with suppress(TypeError): self._state = self._times[0][ATTR_DUE_IN] - except TypeError: - pass class PublicTransportData: diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index 11ad80688a3..3864a2651f8 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -1,4 +1,5 @@ """Support for local control of entities by emulating a Philips Hue bridge.""" +from contextlib import suppress import logging from aiohttp import web @@ -341,8 +342,6 @@ class Config: def _load_json(filename): """Load JSON, handling invalid syntax.""" - try: + with suppress(HomeAssistantError): return load_json(filename) - except HomeAssistantError: - pass return {} diff --git a/homeassistant/components/fibaro/sensor.py b/homeassistant/components/fibaro/sensor.py index fd25a3ce7eb..3161e173b2a 100644 --- a/homeassistant/components/fibaro/sensor.py +++ b/homeassistant/components/fibaro/sensor.py @@ -1,4 +1,6 @@ """Support for Fibaro sensors.""" +from contextlib import suppress + from homeassistant.components.sensor import DOMAIN, SensorEntity from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, @@ -71,7 +73,7 @@ class FibaroSensor(FibaroDevice, SensorEntity): self._unit = None self._icon = None self._device_class = None - try: + with suppress(KeyError, ValueError): if not self._unit: if self.fibaro_device.properties.unit == "lux": self._unit = LIGHT_LUX @@ -81,8 +83,6 @@ class FibaroSensor(FibaroDevice, SensorEntity): self._unit = TEMP_FAHRENHEIT else: self._unit = self.fibaro_device.properties.unit - except (KeyError, ValueError): - pass @property def state(self): @@ -106,7 +106,5 @@ class FibaroSensor(FibaroDevice, SensorEntity): def update(self): """Update the state.""" - try: + with suppress(KeyError, ValueError): self.current_value = float(self.fibaro_device.properties.value) - except (KeyError, ValueError): - pass diff --git a/homeassistant/components/forked_daapd/config_flow.py b/homeassistant/components/forked_daapd/config_flow.py index adc6e9b7b35..c731087d12b 100644 --- a/homeassistant/components/forked_daapd/config_flow.py +++ b/homeassistant/components/forked_daapd/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure forked-daapd devices.""" +from contextlib import suppress import logging from pyforked_daapd import ForkedDaapdAPI @@ -161,12 +162,10 @@ class ForkedDaapdFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if discovery_info.get("properties") and discovery_info["properties"].get( "Machine Name" ): - try: + with suppress(ValueError): version_num = int( discovery_info["properties"].get("mtd-version", "0").split(".")[0] ) - except ValueError: - pass if version_num < 27: return self.async_abort(reason="not_forked_daapd") await self.async_set_unique_id(discovery_info["properties"]["Machine Name"]) diff --git a/homeassistant/components/fritzbox_callmonitor/base.py b/homeassistant/components/fritzbox_callmonitor/base.py index ec62e196855..af0612d7632 100644 --- a/homeassistant/components/fritzbox_callmonitor/base.py +++ b/homeassistant/components/fritzbox_callmonitor/base.py @@ -1,4 +1,5 @@ """Base class for fritzbox_callmonitor entities.""" +from contextlib import suppress from datetime import timedelta import logging import re @@ -69,11 +70,7 @@ class FritzBoxPhonebook: return UNKOWN_NAME for prefix in self.prefixes: - try: + with suppress(KeyError): return self.number_dict[prefix + number] - except KeyError: - pass - try: + with suppress(KeyError): return self.number_dict[prefix + number.lstrip("0")] - except KeyError: - pass diff --git a/homeassistant/components/graphite/__init__.py b/homeassistant/components/graphite/__init__.py index 327e8293be7..ccb7044ad73 100644 --- a/homeassistant/components/graphite/__init__.py +++ b/homeassistant/components/graphite/__init__.py @@ -1,4 +1,5 @@ """Support for sending data to a Graphite installation.""" +from contextlib import suppress import logging import queue import socket @@ -111,10 +112,8 @@ class GraphiteFeeder(threading.Thread): """Report the attributes.""" now = time.time() things = dict(new_state.attributes) - try: + with suppress(ValueError): things["state"] = state.state_as_number(new_state) - except ValueError: - pass lines = [ "%s.%s.%s %f %i" % (self._prefix, entity_id, key.replace(" ", "_"), value, now) diff --git a/homeassistant/components/hangouts/hangouts_bot.py b/homeassistant/components/hangouts/hangouts_bot.py index 10b983fb034..65e3c3923ad 100644 --- a/homeassistant/components/hangouts/hangouts_bot.py +++ b/homeassistant/components/hangouts/hangouts_bot.py @@ -1,5 +1,6 @@ """The Hangouts Bot.""" import asyncio +from contextlib import suppress import io import logging @@ -103,12 +104,10 @@ class HangoutsBot: self._conversation_intents[conv_id][intent_type] = data - try: + with suppress(ValueError): self._conversation_list.on_event.remove_observer( self._async_handle_conversation_event ) - except ValueError: - pass self._conversation_list.on_event.add_observer( self._async_handle_conversation_event ) diff --git a/homeassistant/components/html5/notify.py b/homeassistant/components/html5/notify.py index 33dd8118ee4..b3d5a081d1b 100644 --- a/homeassistant/components/html5/notify.py +++ b/homeassistant/components/html5/notify.py @@ -1,4 +1,5 @@ """HTML5 Push Messaging notification service.""" +from contextlib import suppress from datetime import datetime, timedelta from functools import partial import json @@ -202,10 +203,8 @@ def get_service(hass, config, discovery_info=None): def _load_config(filename): """Load configuration.""" - try: + with suppress(HomeAssistantError): return load_json(filename) - except HomeAssistantError: - pass return {} @@ -325,10 +324,8 @@ class HTML5PushCallbackView(HomeAssistantView): if target_check.get(ATTR_TARGET) in self.registrations: possible_target = self.registrations[target_check[ATTR_TARGET]] key = possible_target[ATTR_SUBSCRIPTION][ATTR_KEYS][ATTR_AUTH] - try: + with suppress(jwt.exceptions.DecodeError): return jwt.decode(token, key, algorithms=["ES256", "HS256"]) - except jwt.exceptions.DecodeError: - pass return self.json_message( "No target found in JWT", status_code=HTTP_UNAUTHORIZED diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index 009a4c7afb5..5350ae5d4c8 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -2,6 +2,7 @@ from __future__ import annotations from collections import defaultdict +from contextlib import suppress from datetime import datetime from ipaddress import ip_address import logging @@ -99,12 +100,10 @@ async def process_wrong_login(request): remote_addr = ip_address(request.remote) remote_host = request.remote - try: + with suppress(herror): remote_host, _, _ = await hass.async_add_executor_job( gethostbyaddr, request.remote ) - except herror: - pass base_msg = f"Login attempt or request with invalid authentication from {remote_host} ({remote_addr})." diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index 5ceb252dfa9..67170aaf866 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations from collections import defaultdict +from contextlib import suppress from datetime import timedelta from functools import partial import ipaddress @@ -161,10 +162,8 @@ class Router: (KEY_DEVICE_BASIC_INFORMATION, "devicename"), (KEY_DEVICE_INFORMATION, "DeviceName"), ): - try: + with suppress(KeyError, TypeError): return cast(str, self.data[key][item]) - except (KeyError, TypeError): - pass return DEFAULT_DEVICE_NAME @property diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index 9ac45c6bd9a..58c7e90994c 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -1,5 +1,6 @@ """Support for hunter douglas shades.""" import asyncio +from contextlib import suppress import logging from aiopvapi.helpers.constants import ATTR_POSITION1, ATTR_POSITION_DATA @@ -65,12 +66,10 @@ async def async_setup_entry(hass, entry, async_add_entities): # possible shade = PvShade(raw_shade, pv_request) name_before_refresh = shade.name - try: + with suppress(asyncio.TimeoutError): async with async_timeout.timeout(1): await shade.refresh() - except asyncio.TimeoutError: - # Forced refresh is not required for setup - pass + if ATTR_POSITION_DATA not in shade.raw_data: _LOGGER.info( "The %s shade was skipped because it is missing position data", diff --git a/homeassistant/components/hyperion/__init__.py b/homeassistant/components/hyperion/__init__.py index ad6990c6ab4..03b892ce83b 100644 --- a/homeassistant/components/hyperion/__init__.py +++ b/homeassistant/components/hyperion/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress import logging from typing import Any, Callable, cast @@ -159,7 +160,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b raise ConfigEntryNotReady version = await hyperion_client.async_sysinfo_version() if version is not None: - try: + with suppress(ValueError): if AwesomeVersion(version) < AwesomeVersion(HYPERION_VERSION_WARN_CUTOFF): _LOGGER.warning( "Using a Hyperion server version < %s is not recommended -- " @@ -168,8 +169,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b HYPERION_VERSION_WARN_CUTOFF, HYPERION_RELEASES_URL, ) - except ValueError: - pass # Client needs authentication, but no token provided? => Reauth. auth_resp = await hyperion_client.async_is_auth_required() diff --git a/homeassistant/components/hyperion/config_flow.py b/homeassistant/components/hyperion/config_flow.py index 2b8d0ee8d8f..d8985fa41c5 100644 --- a/homeassistant/components/hyperion/config_flow.py +++ b/homeassistant/components/hyperion/config_flow.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress import logging from typing import Any from urllib.parse import urlparse @@ -257,10 +258,8 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): if not self._request_token_task.done(): self._request_token_task.cancel() - try: + with suppress(asyncio.CancelledError): await self._request_token_task - except asyncio.CancelledError: - pass self._request_token_task = None async def _request_token_task_func(self, auth_id: str) -> None: diff --git a/homeassistant/components/influxdb/__init__.py b/homeassistant/components/influxdb/__init__.py index df7a8fda786..dde10ffca76 100644 --- a/homeassistant/components/influxdb/__init__.py +++ b/homeassistant/components/influxdb/__init__.py @@ -1,6 +1,7 @@ """Support for sending data to an Influx database.""" from __future__ import annotations +from contextlib import suppress from dataclasses import dataclass import logging import math @@ -304,11 +305,9 @@ def _generate_event_to_json(conf: dict) -> Callable[[dict], str]: ) # Infinity and NaN are not valid floats in InfluxDB - try: + with suppress(KeyError, TypeError): if not math.isfinite(json[INFLUX_CONF_FIELDS][key]): del json[INFLUX_CONF_FIELDS][key] - except (KeyError, TypeError): - pass json[INFLUX_CONF_TAGS].update(tags) @@ -382,10 +381,8 @@ def get_influx_connection(conf, test_write=False, test_read=False): if test_write: # Try to write b"" to influx. If we can connect and creds are valid # Then invalid inputs is returned. Anything else is a broken config - try: + with suppress(ValueError): write_v2(b"") - except ValueError: - pass write_api = influx.write_api(write_options=ASYNCHRONOUS) if test_read: @@ -530,7 +527,7 @@ class InfluxThread(threading.Thread): dropped = 0 - try: + with suppress(queue.Empty): while len(json) < BATCH_BUFFER_SIZE and not self.shutdown: timeout = None if count == 0 else self.batch_timeout() item = self.queue.get(timeout=timeout) @@ -549,9 +546,6 @@ class InfluxThread(threading.Thread): else: dropped += 1 - except queue.Empty: - pass - if dropped: _LOGGER.warning(CATCHING_UP_MESSAGE, dropped) diff --git a/homeassistant/components/insteon/__init__.py b/homeassistant/components/insteon/__init__.py index 7fb2178def4..509878f9613 100644 --- a/homeassistant/components/insteon/__init__.py +++ b/homeassistant/components/insteon/__init__.py @@ -1,5 +1,6 @@ """Support for INSTEON Modems (PLM and Hub).""" import asyncio +from contextlib import suppress import logging from pyinsteon import async_close, async_connect, devices @@ -37,10 +38,8 @@ async def async_get_device_config(hass, config_entry): # Make a copy of addresses due to edge case where the list of devices could change during status update # Cannot be done concurrently due to issues with the underlying protocol. for address in list(devices): - try: + with suppress(AttributeError): await devices[address].async_status() - except AttributeError: - pass await devices.async_load(id_devices=1) for addr in devices: diff --git a/homeassistant/components/izone/config_flow.py b/homeassistant/components/izone/config_flow.py index a64356051d0..bb647f6273c 100644 --- a/homeassistant/components/izone/config_flow.py +++ b/homeassistant/components/izone/config_flow.py @@ -1,6 +1,7 @@ """Config flow for izone.""" import asyncio +from contextlib import suppress import logging from async_timeout import timeout @@ -28,11 +29,9 @@ async def _async_has_devices(hass): disco = await async_start_discovery_service(hass) - try: + with suppress(asyncio.TimeoutError): async with timeout(TIMEOUT_DISCOVERY): await controller_ready.wait() - except asyncio.TimeoutError: - pass if not disco.pi_disco.controllers: await async_stop_discovery_service(hass) diff --git a/homeassistant/components/keyboard_remote/__init__.py b/homeassistant/components/keyboard_remote/__init__.py index 310bd0189bd..7d9bcf621e5 100644 --- a/homeassistant/components/keyboard_remote/__init__.py +++ b/homeassistant/components/keyboard_remote/__init__.py @@ -1,6 +1,7 @@ """Receive signals from a keyboard and use it as a remote control.""" # pylint: disable=import-error import asyncio +from contextlib import suppress import logging import os @@ -255,10 +256,8 @@ class KeyboardRemote: async def async_stop_monitoring(self): """Stop event monitoring task and issue event.""" if self.monitor_task is not None: - try: + with suppress(OSError): await self.hass.async_add_executor_job(self.dev.ungrab) - except OSError: - pass # monitoring of the device form the event loop and closing of the # device has to occur before cancelling the task to avoid # triggering unhandled exceptions inside evdev coroutines diff --git a/homeassistant/components/kodi/browse_media.py b/homeassistant/components/kodi/browse_media.py index bdbac4ab762..9c06b8124b9 100644 --- a/homeassistant/components/kodi/browse_media.py +++ b/homeassistant/components/kodi/browse_media.py @@ -1,4 +1,5 @@ """Support for media browsing.""" +from contextlib import suppress import logging from homeassistant.components.media_player import BrowseError, BrowseMedia @@ -171,10 +172,8 @@ async def build_item_response(media_library, payload): children = [] for item in media: - try: + with suppress(UnknownMediaType): children.append(item_payload(item, media_library)) - except UnknownMediaType: - pass if search_type in (MEDIA_TYPE_TVSHOW, MEDIA_TYPE_MOVIE) and search_id == "": children.sort(key=lambda x: x.title.replace("The ", "", 1), reverse=False) diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 44c9171f244..8d216b5c6f0 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -1,4 +1,5 @@ """Event parser and human readable log generator.""" +from contextlib import suppress from datetime import timedelta from itertools import groupby import json @@ -384,10 +385,8 @@ def humanify(hass, events, entity_attr_cache, context_lookup): domain = event_data.get(ATTR_DOMAIN) entity_id = event_data.get(ATTR_ENTITY_ID) if domain is None and entity_id is not None: - try: + with suppress(IndexError): domain = split_entity_id(str(entity_id))[0] - except IndexError: - pass data = { "when": event.time_fired_isoformat, diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index faa2c488216..f98c6eeceaf 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -4,6 +4,7 @@ from __future__ import annotations import asyncio import base64 import collections +from contextlib import suppress from datetime import timedelta import functools as ft import hashlib @@ -935,18 +936,13 @@ class MediaPlayerEntity(Entity): """Retrieve an image.""" content, content_type = (None, None) websession = async_get_clientsession(self.hass) - try: - with async_timeout.timeout(10): - response = await websession.get(url) - - if response.status == HTTP_OK: - content = await response.read() - content_type = response.headers.get(CONTENT_TYPE) - if content_type: - content_type = content_type.split(";")[0] - - except asyncio.TimeoutError: - pass + with suppress(asyncio.TimeoutError), async_timeout.timeout(10): + response = await websession.get(url) + if response.status == HTTP_OK: + content = await response.read() + content_type = response.headers.get(CONTENT_TYPE) + if content_type: + content_type = content_type.split(";")[0] if content is None: _LOGGER.warning("Error retrieving proxied image from %s", url) diff --git a/homeassistant/components/minecraft_server/config_flow.py b/homeassistant/components/minecraft_server/config_flow.py index a7cb0371f67..8cd26d777eb 100644 --- a/homeassistant/components/minecraft_server/config_flow.py +++ b/homeassistant/components/minecraft_server/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Minecraft Server integration.""" +from contextlib import suppress from functools import partial import ipaddress @@ -40,10 +41,8 @@ class MinecraftServerConfigFlow(ConfigFlow, domain=DOMAIN): host = address_right else: host = address_left - try: + with suppress(ValueError): port = int(address_right) - except ValueError: - pass # 'port' is already set to default value. # Remove '[' and ']' in case of an IPv6 address. host = host.strip("[]") diff --git a/homeassistant/components/mobile_app/__init__.py b/homeassistant/components/mobile_app/__init__.py index 4ba6a6f2086..e63698d3eb5 100644 --- a/homeassistant/components/mobile_app/__init__.py +++ b/homeassistant/components/mobile_app/__init__.py @@ -1,5 +1,6 @@ """Integrates Native Apps to Home Assistant.""" import asyncio +from contextlib import suppress from homeassistant.components import cloud, notify as hass_notify from homeassistant.components.webhook import ( @@ -51,12 +52,10 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType): hass.http.register_view(RegistrationsView()) for deleted_id in hass.data[DOMAIN][DATA_DELETED_IDS]: - try: + with suppress(ValueError): webhook_register( hass, DOMAIN, "Deleted Webhook", deleted_id, handle_webhook ) - except ValueError: - pass hass.async_create_task( discovery.async_load_platform(hass, "notify", DOMAIN, {}, config) @@ -129,7 +128,5 @@ async def async_remove_entry(hass, entry): await store.async_save(savable_state(hass)) if CONF_CLOUDHOOK_URL in entry.data: - try: + with suppress(cloud.CloudNotAvailable): await cloud.async_delete_cloudhook(hass, entry.data[CONF_WEBHOOK_ID]) - except cloud.CloudNotAvailable: - pass diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py index 5583b7c58d1..63bf13bad5e 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -1,6 +1,7 @@ """Provides an HTTP API for mobile_app.""" from __future__ import annotations +from contextlib import suppress import secrets from aiohttp.web import Request, Response @@ -98,10 +99,8 @@ class RegistrationsView(HomeAssistantView): ) remote_ui_url = None - try: + with suppress(hass.components.cloud.CloudNotAvailable): remote_ui_url = hass.components.cloud.async_remote_ui_url() - except hass.components.cloud.CloudNotAvailable: - pass return self.json( { diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index a7b3f38a015..efef6eb1c8a 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -1,5 +1,6 @@ """Webhook handlers for mobile_app.""" import asyncio +from contextlib import suppress from functools import wraps import logging import secrets @@ -551,10 +552,8 @@ async def webhook_get_config(hass, config_entry, data): if CONF_CLOUDHOOK_URL in config_entry.data: resp[CONF_CLOUDHOOK_URL] = config_entry.data[CONF_CLOUDHOOK_URL] - try: + with suppress(hass.components.cloud.CloudNotAvailable): resp[CONF_REMOTE_UI_URL] = hass.components.cloud.async_remote_ui_url() - except hass.components.cloud.CloudNotAvailable: - pass return webhook_response(resp, registration=config_entry.data) diff --git a/homeassistant/components/motion_blinds/__init__.py b/homeassistant/components/motion_blinds/__init__.py index c6226bed910..f57d31b47d3 100644 --- a/homeassistant/components/motion_blinds/__init__.py +++ b/homeassistant/components/motion_blinds/__init__.py @@ -1,5 +1,6 @@ """The motion_blinds component.""" import asyncio +from contextlib import suppress from datetime import timedelta import logging from socket import timeout @@ -64,19 +65,13 @@ async def async_setup_entry( """Call all updates using one async_add_executor_job.""" motion_gateway.Update() for blind in motion_gateway.device_list.values(): - try: + with suppress(timeout): blind.Update() - except timeout: - # let the error be logged and handled by the motionblinds library - pass async def async_update_data(): """Fetch data from the gateway and blinds.""" - try: + with suppress(timeout): # Let the error be handled by the motionblinds await hass.async_add_executor_job(update_gateway) - except timeout: - # let the error be logged and handled by the motionblinds library - pass coordinator = DataUpdateCoordinator( hass, diff --git a/homeassistant/components/mpd/media_player.py b/homeassistant/components/mpd/media_player.py index 371d2060680..adb4bf0e810 100644 --- a/homeassistant/components/mpd/media_player.py +++ b/homeassistant/components/mpd/media_player.py @@ -1,4 +1,5 @@ """Support to interact with a Music Player Daemon.""" +from contextlib import suppress from datetime import timedelta import hashlib import logging @@ -129,10 +130,8 @@ class MpdDevice(MediaPlayerEntity): def _disconnect(self): """Disconnect from MPD.""" - try: + with suppress(mpd.ConnectionError): self._client.disconnect() - except mpd.ConnectionError: - pass self._is_connected = False self._status = None diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 8f2d1dda0a7..f23fe033f56 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -1,4 +1,5 @@ """Support for MQTT JSON lights.""" +from contextlib import suppress import json import logging @@ -245,10 +246,8 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): _LOGGER.warning("Invalid color temp value received") if self._supported_features and SUPPORT_EFFECT: - try: + with suppress(KeyError): self._effect = values["effect"] - except KeyError: - pass if self._supported_features and SUPPORT_WHITE_VALUE: try: diff --git a/homeassistant/components/mqtt/trigger.py b/homeassistant/components/mqtt/trigger.py index 82f7885b85d..856106e029d 100644 --- a/homeassistant/components/mqtt/trigger.py +++ b/homeassistant/components/mqtt/trigger.py @@ -1,4 +1,5 @@ """Offer MQTT listening automation rules.""" +from contextlib import suppress import json import logging @@ -79,10 +80,8 @@ async def async_attach_trigger(hass, config, action, automation_info): "description": f"mqtt topic {mqttmsg.topic}", } - try: + with suppress(ValueError): data["payload_json"] = json.loads(mqttmsg.payload) - except ValueError: - pass hass.async_run_hass_job(job, {"trigger": data}) diff --git a/homeassistant/components/mysensors/config_flow.py b/homeassistant/components/mysensors/config_flow.py index 3a37799106a..bdf1b9392a8 100644 --- a/homeassistant/components/mysensors/config_flow.py +++ b/homeassistant/components/mysensors/config_flow.py @@ -1,6 +1,7 @@ """Config flow for MySensors.""" from __future__ import annotations +from contextlib import suppress import logging import os from typing import Any @@ -62,15 +63,14 @@ def _get_schema_common(user_input: dict[str, str]) -> dict: def _validate_version(version: str) -> dict[str, str]: """Validate a version string from the user.""" version_okay = False - try: + with suppress(AwesomeVersionStrategyException): version_okay = bool( AwesomeVersion.ensure_strategy( version, [AwesomeVersionStrategy.SIMPLEVER, AwesomeVersionStrategy.SEMVER], ) ) - except AwesomeVersionStrategyException: - pass + if version_okay: return {} return {CONF_VERSION: "invalid_version"} diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index 761eb2fc2dc..826ff4b1a29 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress import datetime as dt import os @@ -241,25 +242,19 @@ class ONVIFDevice: async def async_get_capabilities(self): """Obtain information about the available services on the device.""" snapshot = False - try: + with suppress(ONVIFError, Fault, RequestError): media_service = self.device.create_media_service() media_capabilities = await media_service.GetServiceCapabilities() snapshot = media_capabilities and media_capabilities.SnapshotUri - except (ONVIFError, Fault, RequestError): - pass pullpoint = False - try: + with suppress(ONVIFError, Fault, RequestError): pullpoint = await self.events.async_start() - except (ONVIFError, Fault, RequestError): - pass ptz = False - try: + with suppress(ONVIFError, Fault, RequestError): self.device.get_definition("ptz") ptz = True - except (ONVIFError, Fault, RequestError): - pass return Capabilities(snapshot, pullpoint, ptz) diff --git a/homeassistant/components/onvif/event.py b/homeassistant/components/onvif/event.py index 91db2a90e57..76b18d729a8 100644 --- a/homeassistant/components/onvif/event.py +++ b/homeassistant/components/onvif/event.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress import datetime as dt from typing import Callable @@ -86,10 +87,8 @@ class EventManager: # Initialize events pullpoint = self.device.create_pullpoint_service() - try: + with suppress(*SUBSCRIPTION_ERRORS): await pullpoint.SetSynchronizationPoint() - except SUBSCRIPTION_ERRORS: - pass response = await pullpoint.PullMessages( {"MessageLimit": 100, "Timeout": dt.timedelta(seconds=5)} ) @@ -119,10 +118,9 @@ class EventManager: return if self._subscription: - try: + # Suppressed. The subscription may no longer exist. + with suppress(*SUBSCRIPTION_ERRORS): await self._subscription.Unsubscribe() - except SUBSCRIPTION_ERRORS: - pass # Ignored. The subscription may no longer exist. self._subscription = None try: diff --git a/homeassistant/components/ozw/__init__.py b/homeassistant/components/ozw/__init__.py index 3f54c731e39..ace71e4af81 100644 --- a/homeassistant/components/ozw/__init__.py +++ b/homeassistant/components/ozw/__init__.py @@ -1,5 +1,6 @@ """The ozw integration.""" import asyncio +from contextlib import suppress import json import logging @@ -280,10 +281,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): Do not unsubscribe the manager topic. """ mqtt_client_task.cancel() - try: + with suppress(asyncio.CancelledError): await mqtt_client_task - except asyncio.CancelledError: - pass ozw_data[DATA_UNSUBSCRIBE].append( hass.bus.async_listen_once( diff --git a/homeassistant/components/ping/binary_sensor.py b/homeassistant/components/ping/binary_sensor.py index e660b5af38d..48e47937e2e 100644 --- a/homeassistant/components/ping/binary_sensor.py +++ b/homeassistant/components/ping/binary_sensor.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress from datetime import timedelta from functools import partial import logging @@ -242,10 +243,8 @@ class PingDataSubProcess(PingData): self._count + PING_TIMEOUT, ) if pinger: - try: + with suppress(TypeError): await pinger.kill() - except TypeError: - pass del pinger return False diff --git a/homeassistant/components/plant/__init__.py b/homeassistant/components/plant/__init__.py index 7c0c8e9b46f..290993959b3 100644 --- a/homeassistant/components/plant/__init__.py +++ b/homeassistant/components/plant/__init__.py @@ -1,5 +1,6 @@ """Support for monitoring plants.""" from collections import deque +from contextlib import suppress from datetime import datetime, timedelta import logging @@ -324,12 +325,10 @@ class Plant(Entity): for state in states: # filter out all None, NaN and "unknown" states # only keep real values - try: + with suppress(ValueError): self._brightness_history.add_measurement( int(state.state), state.last_updated ) - except ValueError: - pass _LOGGER.debug("Initializing from database completed") @property diff --git a/homeassistant/components/ps4/media_player.py b/homeassistant/components/ps4/media_player.py index 24a1589db0d..be77ea04f1c 100644 --- a/homeassistant/components/ps4/media_player.py +++ b/homeassistant/components/ps4/media_player.py @@ -1,5 +1,6 @@ """Support for PlayStation 4 consoles.""" import asyncio +from contextlib import suppress import logging from pyps4_2ndscreen.errors import NotReady, PSDataIncomplete @@ -142,10 +143,8 @@ class PS4Device(MediaPlayerEntity): and not self._ps4.is_standby and self._ps4.is_available ): - try: + with suppress(NotReady): await self._ps4.async_connect() - except NotReady: - pass # Try to ensure correct status is set on startup for device info. if self._ps4.ddp_protocol is None: diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index 726a6e26ce5..8d87b688aa4 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -1,5 +1,6 @@ """Integration with the Rachio Iro sprinkler system controller.""" from abc import abstractmethod +from contextlib import suppress from datetime import timedelta import logging @@ -525,7 +526,7 @@ class RachioSchedule(RachioSwitch): def _async_handle_update(self, *args, **kwargs) -> None: """Handle incoming webhook schedule data.""" # Schedule ID not passed when running individual zones, so we catch that error - try: + with suppress(KeyError): if args[0][KEY_SCHEDULE_ID] == self._schedule_id: if args[0][KEY_SUBTYPE] in [SUBTYPE_SCHEDULE_STARTED]: self._state = True @@ -534,8 +535,6 @@ class RachioSchedule(RachioSwitch): SUBTYPE_SCHEDULE_COMPLETED, ]: self._state = False - except KeyError: - pass self.async_write_ha_state() diff --git a/homeassistant/components/rejseplanen/sensor.py b/homeassistant/components/rejseplanen/sensor.py index 685dc548338..78b713c286c 100644 --- a/homeassistant/components/rejseplanen/sensor.py +++ b/homeassistant/components/rejseplanen/sensor.py @@ -4,6 +4,7 @@ Support for Rejseplanen information from rejseplanen.dk. For more info on the API see: https://help.rejseplanen.dk/hc/en-us/articles/214174465-Rejseplanen-s-API """ +from contextlib import suppress from datetime import datetime, timedelta import logging from operator import itemgetter @@ -147,10 +148,8 @@ class RejseplanenTransportSensor(SensorEntity): if not self._times: self._state = None else: - try: + with suppress(TypeError): self._state = self._times[0][ATTR_DUE_IN] - except TypeError: - pass class PublicTransportData: diff --git a/homeassistant/components/sharkiq/__init__.py b/homeassistant/components/sharkiq/__init__.py index e43338cd5b0..5ccb3e00f27 100644 --- a/homeassistant/components/sharkiq/__init__.py +++ b/homeassistant/components/sharkiq/__init__.py @@ -1,6 +1,7 @@ """Shark IQ Integration.""" import asyncio +from contextlib import suppress import async_timeout from sharkiqpy import ( @@ -59,7 +60,7 @@ async def async_setup_entry(hass, config_entry): raise exceptions.ConfigEntryNotReady from exc shark_vacs = await ayla_api.async_get_devices(False) - device_names = ", ".join([d.name for d in shark_vacs]) + device_names = ", ".join(d.name for d in shark_vacs) _LOGGER.debug("Found %d Shark IQ device(s): %s", len(shark_vacs), device_names) coordinator = SharkIqUpdateCoordinator(hass, config_entry, ayla_api, shark_vacs) @@ -81,11 +82,10 @@ async def async_setup_entry(hass, config_entry): async def async_disconnect_or_timeout(coordinator: SharkIqUpdateCoordinator): """Disconnect to vacuum.""" _LOGGER.debug("Disconnecting from Ayla Api") - with async_timeout.timeout(5): - try: - await coordinator.ayla_api.async_sign_out() - except (SharkIqAuthError, SharkIqAuthExpiringError, SharkIqNotAuthedError): - pass + with async_timeout.timeout(5), suppress( + SharkIqAuthError, SharkIqAuthExpiringError, SharkIqNotAuthedError + ): + await coordinator.ayla_api.async_sign_out() async def async_update_options(hass, config_entry): @@ -105,10 +105,8 @@ async def async_unload_entry(hass, config_entry): ) if unload_ok: domain_data = hass.data[DOMAIN][config_entry.entry_id] - try: + with suppress(SharkIqAuthError): await async_disconnect_or_timeout(coordinator=domain_data) - except SharkIqAuthError: - pass hass.data[DOMAIN].pop(config_entry.entry_id) return unload_ok diff --git a/homeassistant/components/shell_command/__init__.py b/homeassistant/components/shell_command/__init__.py index 1173d0477ab..089dc36b1a8 100644 --- a/homeassistant/components/shell_command/__init__.py +++ b/homeassistant/components/shell_command/__init__.py @@ -1,5 +1,6 @@ """Expose regular shell commands as services.""" import asyncio +from contextlib import suppress import logging import shlex @@ -87,10 +88,8 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: "Timed out running command: `%s`, after: %ss", cmd, COMMAND_TIMEOUT ) if process: - try: + with suppress(TypeError): await process.kill() - except TypeError: - pass del process return diff --git a/homeassistant/components/solaredge_local/sensor.py b/homeassistant/components/solaredge_local/sensor.py index a34fb5a3afc..441a1c39e08 100644 --- a/homeassistant/components/solaredge_local/sensor.py +++ b/homeassistant/components/solaredge_local/sensor.py @@ -1,4 +1,5 @@ """Support for SolarEdge-local Monitoring API.""" +from contextlib import suppress from copy import deepcopy from datetime import timedelta import logging @@ -350,19 +351,15 @@ class SolarEdgeData: self.info["optimizers"] = status.optimizersStatus.total self.info["invertertemperature"] = INVERTER_MODES[status.status] - try: + with suppress(IndexError): if status.metersList[1]: self.data["currentPowerimport"] = status.metersList[1].currentPower self.data["totalEnergyimport"] = status.metersList[1].totalEnergy - except IndexError: - pass - try: + with suppress(IndexError): if status.metersList[0]: self.data["currentPowerexport"] = status.metersList[0].currentPower self.data["totalEnergyexport"] = status.metersList[0].totalEnergy - except IndexError: - pass if maintenance.system.name: self.data["optimizertemperature"] = round(statistics.mean(temperature), 2) diff --git a/homeassistant/components/sonos/media_browser.py b/homeassistant/components/sonos/media_browser.py index 6b6c927ca1c..179fc62e0cc 100644 --- a/homeassistant/components/sonos/media_browser.py +++ b/homeassistant/components/sonos/media_browser.py @@ -1,4 +1,5 @@ """Support for media browsing.""" +from contextlib import suppress import logging import urllib.parse @@ -75,10 +76,8 @@ def build_item_response(media_library, payload, get_thumbnail_url=None): children = [] for item in media: - try: + with suppress(UnknownMediaType): children.append(item_payload(item, get_thumbnail_url)) - except UnknownMediaType: - pass return BrowseMedia( title=title, @@ -136,10 +135,8 @@ def library_payload(media_library, get_thumbnail_url=None): children = [] for item in media_library.browse(): - try: + with suppress(UnknownMediaType): children.append(item_payload(item, get_thumbnail_url)) - except UnknownMediaType: - pass return BrowseMedia( title="Music Library", diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 9b2342e5e1b..8339be673fb 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -1,5 +1,6 @@ """Support to interface with Sonos players.""" import asyncio +from contextlib import suppress import datetime import functools as ft import logging @@ -790,7 +791,7 @@ class SonosEntity(MediaPlayerEntity): coordinator_uid = self.unique_id slave_uids = [] - try: + with suppress(SoCoException): if self.soco.group and self.soco.group.coordinator: coordinator_uid = self.soco.group.coordinator.uid slave_uids = [ @@ -798,8 +799,6 @@ class SonosEntity(MediaPlayerEntity): for p in self.soco.group.members if p.uid != coordinator_uid ] - except SoCoException: - pass return [coordinator_uid] + slave_uids diff --git a/homeassistant/components/spaceapi/__init__.py b/homeassistant/components/spaceapi/__init__.py index f6def03ec6a..66583050b20 100644 --- a/homeassistant/components/spaceapi/__init__.py +++ b/homeassistant/components/spaceapi/__init__.py @@ -1,4 +1,6 @@ """Support for the SpaceAPI.""" +from contextlib import suppress + import voluptuous as vol from homeassistant.components.http import HomeAssistantView @@ -287,13 +289,11 @@ class APISpaceApiView(HomeAssistantView): else: state = {ATTR_OPEN: "null", ATTR_LASTCHANGE: 0} - try: + with suppress(KeyError): state[ATTR_ICON] = { ATTR_OPEN: spaceapi["state"][CONF_ICON_OPEN], ATTR_CLOSE: spaceapi["state"][CONF_ICON_CLOSED], } - except KeyError: - pass data = { ATTR_API: SPACEAPI_VERSION, @@ -306,40 +306,26 @@ class APISpaceApiView(HomeAssistantView): ATTR_URL: spaceapi[CONF_URL], } - try: + with suppress(KeyError): data[ATTR_CAM] = spaceapi[CONF_CAM] - except KeyError: - pass - try: + with suppress(KeyError): data[ATTR_SPACEFED] = spaceapi[CONF_SPACEFED] - except KeyError: - pass - try: + with suppress(KeyError): data[ATTR_STREAM] = spaceapi[CONF_STREAM] - except KeyError: - pass - try: + with suppress(KeyError): data[ATTR_FEEDS] = spaceapi[CONF_FEEDS] - except KeyError: - pass - try: + with suppress(KeyError): data[ATTR_CACHE] = spaceapi[CONF_CACHE] - except KeyError: - pass - try: + with suppress(KeyError): data[ATTR_PROJECTS] = spaceapi[CONF_PROJECTS] - except KeyError: - pass - try: + with suppress(KeyError): data[ATTR_RADIO_SHOW] = spaceapi[CONF_RADIO_SHOW] - except KeyError: - pass if is_sensors is not None: sensors = {} diff --git a/homeassistant/components/swisscom/device_tracker.py b/homeassistant/components/swisscom/device_tracker.py index 5662212c9e8..1332aa46189 100644 --- a/homeassistant/components/swisscom/device_tracker.py +++ b/homeassistant/components/swisscom/device_tracker.py @@ -1,4 +1,5 @@ """Support for Swisscom routers (Internet-Box).""" +from contextlib import suppress import logging from aiohttp.hdrs import CONTENT_TYPE @@ -97,13 +98,11 @@ class SwisscomDeviceScanner(DeviceScanner): return devices for device in request.json()["status"]: - try: + with suppress(KeyError, requests.exceptions.RequestException): devices[device["Key"]] = { "ip": device["IPAddress"], "mac": device["PhysAddress"], "host": device["Name"], "status": device["Active"], } - except (KeyError, requests.exceptions.RequestException): - pass return devices diff --git a/homeassistant/components/ted5000/sensor.py b/homeassistant/components/ted5000/sensor.py index 86c9b9ace95..d618ca9c2cf 100644 --- a/homeassistant/components/ted5000/sensor.py +++ b/homeassistant/components/ted5000/sensor.py @@ -1,4 +1,5 @@ """Support gathering ted5000 information.""" +from contextlib import suppress from datetime import timedelta import logging @@ -73,10 +74,8 @@ class Ted5000Sensor(SensorEntity): @property def state(self): """Return the state of the resources.""" - try: + with suppress(KeyError): return self._gateway.data[self._mtu][self._unit] - except KeyError: - pass def update(self): """Get the latest data from REST API.""" diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py index dec20edec65..4d7dce37447 100644 --- a/homeassistant/components/tplink/switch.py +++ b/homeassistant/components/tplink/switch.py @@ -1,5 +1,6 @@ """Support for TPLink HS100/HS110/HS200 smart switch.""" import asyncio +from contextlib import suppress import logging import time @@ -151,13 +152,10 @@ class SmartPlugSwitch(SwitchEntity): ) emeter_statics = self.smartplug.get_emeter_daily() - try: + with suppress(KeyError): # Device returned no daily history self._emeter_params[ATTR_TODAY_ENERGY_KWH] = "{:.3f}".format( emeter_statics[int(time.strftime("%e"))] ) - except KeyError: - # Device returned no daily history - pass return True except (SmartDeviceException, OSError) as ex: if update_attempt == 0: diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index a82adac6160..b00ccfc68c0 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -1,6 +1,8 @@ """Support for monitoring the Transmission BitTorrent client API.""" from __future__ import annotations +from contextlib import suppress + from transmissionrpc.torrent import Torrent from homeassistant.components.sensor import SensorEntity @@ -187,8 +189,6 @@ def _torrents_info(torrents, order, limit, statuses=None): "status": torrent.status, "id": torrent.id, } - try: + with suppress(ValueError): info["eta"] = str(torrent.eta) - except ValueError: - pass return infos diff --git a/homeassistant/components/upb/config_flow.py b/homeassistant/components/upb/config_flow.py index 3af9999bd9f..70a1cddefc3 100644 --- a/homeassistant/components/upb/config_flow.py +++ b/homeassistant/components/upb/config_flow.py @@ -1,5 +1,6 @@ """Config flow for UPB PIM integration.""" import asyncio +from contextlib import suppress import logging from urllib.parse import urlparse @@ -43,11 +44,8 @@ async def _validate_input(data): upb.connect(_connected_callback) - try: - with async_timeout.timeout(VALIDATE_TIMEOUT): - await connected_event.wait() - except asyncio.TimeoutError: - pass + with suppress(asyncio.TimeoutError), async_timeout.timeout(VALIDATE_TIMEOUT): + await connected_event.wait() upb.disconnect() diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index 1a727daac73..013142504f1 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress import os from typing import Any @@ -164,10 +165,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return False cookie_file = hass.config.path(STORAGE_DIR, f"verisure_{entry.entry_id}") - try: + with suppress(FileNotFoundError): await hass.async_add_executor_job(os.unlink, cookie_file) - except FileNotFoundError: - pass del hass.data[DOMAIN][entry.entry_id] diff --git a/homeassistant/components/webostv/__init__.py b/homeassistant/components/webostv/__init__.py index 34e32dce163..681c2acfe01 100644 --- a/homeassistant/components/webostv/__init__.py +++ b/homeassistant/components/webostv/__init__.py @@ -1,5 +1,6 @@ """Support for LG webOS Smart TV.""" import asyncio +from contextlib import suppress import json import logging import os @@ -150,9 +151,7 @@ async def async_setup_tv(hass, config, conf): async def async_connect(client): """Attempt a connection, but fail gracefully if tv is off for example.""" - try: - await client.connect() - except ( + with suppress( OSError, ConnectionClosed, ConnectionRefusedError, @@ -161,7 +160,7 @@ async def async_connect(client): PyLGTVPairException, PyLGTVCmdException, ): - pass + await client.connect() async def async_setup_tv_finalize(hass, config, conf, client): diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index f4d4b67b8b0..d94ab8a7c26 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -1,5 +1,6 @@ """Support for interface with an LG webOS Smart TV.""" import asyncio +from contextlib import suppress from datetime import timedelta from functools import wraps import logging @@ -214,9 +215,7 @@ class LgWebOSMediaPlayerEntity(MediaPlayerEntity): async def async_update(self): """Connect.""" if not self._client.is_connected(): - try: - await self._client.connect() - except ( + with suppress( OSError, ConnectionClosed, ConnectionRefusedError, @@ -225,7 +224,7 @@ class LgWebOSMediaPlayerEntity(MediaPlayerEntity): PyLGTVPairException, PyLGTVCmdException, ): - pass + await self._client.connect() @property def unique_id(self): diff --git a/homeassistant/components/wink/sensor.py b/homeassistant/components/wink/sensor.py index 88f9588750e..8d60c21c118 100644 --- a/homeassistant/components/wink/sensor.py +++ b/homeassistant/components/wink/sensor.py @@ -1,4 +1,5 @@ """Support for Wink sensors.""" +from contextlib import suppress import logging import pywink @@ -87,9 +88,9 @@ class WinkSensorEntity(WinkDevice, SensorEntity): def extra_state_attributes(self): """Return the state attributes.""" super_attrs = super().extra_state_attributes - try: + + # Ignore error, this sensor isn't an eggminder + with suppress(AttributeError): super_attrs["egg_times"] = self.wink.eggs() - except AttributeError: - # Ignore error, this sensor isn't an eggminder - pass + return super_attrs diff --git a/homeassistant/components/xbox/__init__.py b/homeassistant/components/xbox/__init__.py index dd358286802..938a392dd69 100644 --- a/homeassistant/components/xbox/__init__.py +++ b/homeassistant/components/xbox/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress from dataclasses import dataclass from datetime import timedelta import logging @@ -248,12 +249,10 @@ class XboxUpdateCoordinator(DataUpdateCoordinator): def _build_presence_data(person: Person) -> PresenceData: """Build presence data from a person.""" active_app: PresenceDetail | None = None - try: + with suppress(StopIteration): active_app = next( presence for presence in person.presence_details if presence.is_primary ) - except StopIteration: - pass return PresenceData( xuid=person.xuid, diff --git a/homeassistant/components/xbox/media_source.py b/homeassistant/components/xbox/media_source.py index 1a459580d66..64a16e2c21d 100644 --- a/homeassistant/components/xbox/media_source.py +++ b/homeassistant/components/xbox/media_source.py @@ -1,6 +1,7 @@ """Xbox Media Source Implementation.""" from __future__ import annotations +from contextlib import suppress from dataclasses import dataclass from pydantic.error_wrappers import ValidationError # pylint: disable=no-name-in-module @@ -138,7 +139,7 @@ class XboxSource(MediaSource): owner, kind = category.split("#", 1) items: list[XboxMediaItem] = [] - try: + with suppress(ValidationError): # Unexpected API response if kind == "gameclips": if owner == "my": response: GameclipsResponse = ( @@ -189,9 +190,6 @@ class XboxSource(MediaSource): ) for item in response.screenshots ] - except ValidationError: - # Unexpected API response - pass return BrowseMediaSource( domain=DOMAIN, diff --git a/homeassistant/components/zabbix/__init__.py b/homeassistant/components/zabbix/__init__.py index b9d02731570..82807c4acee 100644 --- a/homeassistant/components/zabbix/__init__.py +++ b/homeassistant/components/zabbix/__init__.py @@ -1,4 +1,5 @@ """Support for Zabbix.""" +from contextlib import suppress import json import logging import math @@ -202,7 +203,7 @@ class ZabbixThread(threading.Thread): dropped = 0 - try: + with suppress(queue.Empty): while len(metrics) < BATCH_BUFFER_SIZE and not self.shutdown: timeout = None if count == 0 else BATCH_TIMEOUT item = self.queue.get(timeout=timeout) @@ -223,9 +224,6 @@ class ZabbixThread(threading.Thread): else: dropped += 1 - except queue.Empty: - pass - if dropped: _LOGGER.warning("Catching up, dropped %d old events", dropped) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index d9c8dc52860..6adbe07d867 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -1,6 +1,7 @@ """Support for exposing Home Assistant via Zeroconf.""" from __future__ import annotations +from contextlib import suppress import fnmatch from functools import partial import ipaddress @@ -187,15 +188,11 @@ def _register_hass_zc_service(hass, zeroconf, uuid): } # Get instance URL's - try: + with suppress(NoURLAvailableError): params["external_url"] = get_url(hass, allow_internal=False) - except NoURLAvailableError: - pass - try: + with suppress(NoURLAvailableError): params["internal_url"] = get_url(hass, allow_external=False) - except NoURLAvailableError: - pass # Set old base URL based on external or internal params["base_url"] = params["external_url"] or params["internal_url"] @@ -380,11 +377,9 @@ def info_from_service(service): properties["_raw"][key] = value - try: + with suppress(UnicodeDecodeError): if isinstance(value, bytes): properties[key] = value.decode("utf-8") - except UnicodeDecodeError: - pass if not service.addresses: return None diff --git a/homeassistant/components/zeroconf/usage.py b/homeassistant/components/zeroconf/usage.py index 1af6e3e1f3c..4b4af0dd79d 100644 --- a/homeassistant/components/zeroconf/usage.py +++ b/homeassistant/components/zeroconf/usage.py @@ -1,5 +1,6 @@ """Zeroconf usage utility to warn about multiple instances.""" +from contextlib import suppress import logging import zeroconf @@ -36,10 +37,8 @@ def _report(what: str) -> None: """ integration_frame = None - try: + with suppress(MissingIntegrationFrame): integration_frame = get_integration_frame(exclude_integrations={"zeroconf"}) - except MissingIntegrationFrame: - pass if not integration_frame: _LOGGER.warning( diff --git a/homeassistant/components/zha/core/channels/lighting.py b/homeassistant/components/zha/core/channels/lighting.py index 000549b0593..eef4c56e379 100644 --- a/homeassistant/components/zha/core/channels/lighting.py +++ b/homeassistant/components/zha/core/channels/lighting.py @@ -1,6 +1,7 @@ """Lighting channels module for Zigbee Home Automation.""" from __future__ import annotations +from contextlib import suppress from typing import Coroutine import zigpy.zcl.clusters.lighting as lighting @@ -39,10 +40,8 @@ class ColorChannel(ZigbeeChannel): @property def color_capabilities(self) -> int: """Return color capabilities of the light.""" - try: + with suppress(KeyError): return self.cluster["color_capabilities"] - except KeyError: - pass if self.cluster.get("color_temperature") is not None: return self.CAPABILITIES_COLOR_XY | self.CAPABILITIES_COLOR_TEMP return self.CAPABILITIES_COLOR_XY diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py index 2a362028d51..0957260c761 100644 --- a/homeassistant/helpers/aiohttp_client.py +++ b/homeassistant/helpers/aiohttp_client.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress from ssl import SSLContext import sys from typing import Any, Awaitable, cast @@ -37,10 +38,9 @@ def async_get_clientsession( This method must be run in the event loop. """ + key = DATA_CLIENTSESSION_NOTVERIFY if verify_ssl: key = DATA_CLIENTSESSION - else: - key = DATA_CLIENTSESSION_NOTVERIFY if key not in hass.data: hass.data[key] = async_create_clientsession(hass, verify_ssl) @@ -130,7 +130,8 @@ async def async_aiohttp_proxy_stream( response.content_type = content_type await response.prepare(request) - try: + # Suppressing something went wrong fetching data, closed connection + with suppress(asyncio.TimeoutError, aiohttp.ClientError): while hass.is_running: with async_timeout.timeout(timeout): data = await stream.read(buffer_size) @@ -139,10 +140,6 @@ async def async_aiohttp_proxy_stream( break await response.write(data) - except (asyncio.TimeoutError, aiohttp.ClientError): - # Something went wrong fetching data, closed connection - pass - return response diff --git a/homeassistant/helpers/network.py b/homeassistant/helpers/network.py index 6278bf8f855..6ed8084413f 100644 --- a/homeassistant/helpers/network.py +++ b/homeassistant/helpers/network.py @@ -1,6 +1,7 @@ """Network helpers.""" from __future__ import annotations +from contextlib import suppress from ipaddress import ip_address from typing import cast @@ -56,7 +57,7 @@ def get_url( for url_type in order: if allow_internal and url_type == TYPE_URL_INTERNAL: - try: + with suppress(NoURLAvailableError): return _get_internal_url( hass, allow_ip=allow_ip, @@ -64,11 +65,9 @@ def get_url( require_ssl=require_ssl, require_standard_port=require_standard_port, ) - except NoURLAvailableError: - pass if allow_external and url_type == TYPE_URL_EXTERNAL: - try: + with suppress(NoURLAvailableError): return _get_external_url( hass, allow_cloud=allow_cloud, @@ -78,8 +77,6 @@ def get_url( require_ssl=require_ssl, require_standard_port=require_standard_port, ) - except NoURLAvailableError: - pass # For current request, we accept loopback interfaces (e.g., 127.0.0.1), # the Supervisor hostname and localhost transparently @@ -177,10 +174,8 @@ def _get_external_url( ) -> str: """Get external URL of this instance.""" if prefer_cloud and allow_cloud: - try: + with suppress(NoURLAvailableError): return _get_cloud_url(hass) - except NoURLAvailableError: - pass if hass.config.external_url: external_url = yarl.URL(hass.config.external_url) @@ -201,10 +196,8 @@ def _get_external_url( return normalize_url(str(external_url)) if allow_cloud: - try: + with suppress(NoURLAvailableError): return _get_cloud_url(hass, require_current_request=require_current_request) - except NoURLAvailableError: - pass raise NoURLAvailableError diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 4cc2c1975e8..d07d963c95d 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio -from contextlib import asynccontextmanager +from contextlib import asynccontextmanager, suppress from datetime import datetime, timedelta from functools import partial import itertools @@ -492,10 +492,8 @@ class _ScriptRun: async def async_cancel_long_task() -> None: # Stop long task and wait for it to finish. long_task.cancel() - try: + with suppress(Exception): await long_task - except Exception: # pylint: disable=broad-except - pass # Wait for long task while monitoring for a stop request. stop_task = self._hass.async_create_task(self._stop.wait()) diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index c1138b54f79..5a08a97a210 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress from json import JSONEncoder import logging import os @@ -243,7 +244,5 @@ class Store: self._async_cleanup_delay_listener() self._async_cleanup_final_write_listener() - try: + with suppress(FileNotFoundError): await self.hass.async_add_executor_job(os.unlink, self.path) - except FileNotFoundError: - pass diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 24c91ec5468..2fde0e1f0b5 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -5,6 +5,7 @@ from ast import literal_eval import asyncio import base64 import collections.abc +from contextlib import suppress from datetime import datetime, timedelta from functools import partial, wraps import json @@ -513,10 +514,8 @@ class Template: variables = dict(variables or {}) variables["value"] = value - try: + with suppress(ValueError, TypeError): variables["value_json"] = json.loads(value) - except (ValueError, TypeError): - pass try: return self._compiled.render(variables).strip() diff --git a/homeassistant/loader.py b/homeassistant/loader.py index b3ef4fff271..30a5a2a7ceb 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -7,6 +7,7 @@ documentation as possible to keep it understandable. from __future__ import annotations import asyncio +from contextlib import suppress import functools as ft import importlib import json @@ -587,10 +588,8 @@ def _load_file( Only returns it if also found to be valid. Async friendly. """ - try: + with suppress(KeyError): return hass.data[DATA_COMPONENTS][comp_or_platform] # type: ignore - except KeyError: - pass cache = hass.data.get(DATA_COMPONENTS) if cache is None: diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index aae01c56bf3..b0c6cb21fec 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -1,6 +1,7 @@ """Helper methods to handle the time in Home Assistant.""" from __future__ import annotations +from contextlib import suppress import datetime as dt import re from typing import Any, cast @@ -127,10 +128,9 @@ def parse_datetime(dt_str: str) -> dt.datetime | None: Raises ValueError if the input is well formatted but not a valid datetime. Returns None if the input isn't well formatted. """ - try: + with suppress(ValueError, IndexError): return ciso8601.parse_datetime(dt_str) - except (ValueError, IndexError): - pass + match = DATETIME_RE.match(dt_str) if not match: return None diff --git a/homeassistant/util/ruamel_yaml.py b/homeassistant/util/ruamel_yaml.py index 91aad5f381c..7bb49b0545b 100644 --- a/homeassistant/util/ruamel_yaml.py +++ b/homeassistant/util/ruamel_yaml.py @@ -2,6 +2,7 @@ from __future__ import annotations from collections import OrderedDict +from contextlib import suppress import logging import os from os import O_CREAT, O_TRUNC, O_WRONLY, stat_result @@ -128,10 +129,8 @@ def save_yaml(fname: str, data: JSON_TYPE) -> None: yaml.dump(data, temp_file) os.replace(tmp_fname, fname) if hasattr(os, "chown") and file_stat.st_ctime > -1: - try: + with suppress(OSError): os.chown(fname, file_stat.st_uid, file_stat.st_gid) - except OSError: - pass except YAMLError as exc: _LOGGER.error(str(exc)) raise HomeAssistantError(exc) from exc diff --git a/tests/components/buienradar/test_camera.py b/tests/components/buienradar/test_camera.py index 9935cc19cb8..c9c6d7b4793 100644 --- a/tests/components/buienradar/test_camera.py +++ b/tests/components/buienradar/test_camera.py @@ -1,5 +1,6 @@ """The tests for generic camera component.""" import asyncio +from contextlib import suppress from aiohttp.client_exceptions import ClientResponseError @@ -214,10 +215,8 @@ async def test_retries_after_error(aioclient_mock, hass, hass_client): aioclient_mock.get(radar_map_url(), text=None, status=HTTP_INTERNAL_SERVER_ERROR) # A 404 should not return data and throw: - try: + with suppress(ClientResponseError): await client.get("/api/camera_proxy/camera.config_test") - except ClientResponseError: - pass assert aioclient_mock.call_count == 1 diff --git a/tests/components/demo/test_init.py b/tests/components/demo/test_init.py index 420707e54d0..68d4dfbf379 100644 --- a/tests/components/demo/test_init.py +++ b/tests/components/demo/test_init.py @@ -1,4 +1,5 @@ """The tests for the Demo component.""" +from contextlib import suppress import json import os @@ -20,10 +21,8 @@ def mock_history(hass): def demo_cleanup(hass): """Clean up device tracker demo file.""" yield - try: + with suppress(FileNotFoundError): os.remove(hass.config.path(YAML_DEVICES)) - except FileNotFoundError: - pass async def test_setting_up_demo(hass): diff --git a/tests/util/test_timeout.py b/tests/util/test_timeout.py index 39ec8d916bd..227dde19234 100644 --- a/tests/util/test_timeout.py +++ b/tests/util/test_timeout.py @@ -1,5 +1,6 @@ """Test Home Assistant timeout handler.""" import asyncio +from contextlib import suppress import time import pytest @@ -232,11 +233,9 @@ async def test_mix_zone_timeout(): timeout = TimeoutManager() async with timeout.async_timeout(0.1): - try: + with suppress(asyncio.TimeoutError): async with timeout.async_timeout(0.2, "test"): await asyncio.sleep(0.4) - except asyncio.TimeoutError: - pass async def test_mix_zone_timeout_trigger_global(): @@ -245,11 +244,9 @@ async def test_mix_zone_timeout_trigger_global(): with pytest.raises(asyncio.TimeoutError): async with timeout.async_timeout(0.1): - try: + with suppress(asyncio.TimeoutError): async with timeout.async_timeout(0.1, "test"): await asyncio.sleep(0.3) - except asyncio.TimeoutError: - pass await asyncio.sleep(0.3) @@ -259,11 +256,9 @@ async def test_mix_zone_timeout_trigger_global_cool_down(): timeout = TimeoutManager() async with timeout.async_timeout(0.1, cool_down=0.3): - try: + with suppress(asyncio.TimeoutError): async with timeout.async_timeout(0.1, "test"): await asyncio.sleep(0.3) - except asyncio.TimeoutError: - pass await asyncio.sleep(0.2) @@ -301,11 +296,9 @@ async def test_simple_zone_timeout_freeze_without_timeout_exeption(): with pytest.raises(asyncio.TimeoutError): async with timeout.async_timeout(0.1): - try: + with suppress(RuntimeError): async with timeout.async_freeze("test"): raise RuntimeError() - except RuntimeError: - pass await asyncio.sleep(0.4) @@ -316,10 +309,8 @@ async def test_simple_zone_timeout_zone_with_timeout_exeption(): with pytest.raises(asyncio.TimeoutError): async with timeout.async_timeout(0.1): - try: + with suppress(RuntimeError): async with timeout.async_timeout(0.3, "test"): raise RuntimeError() - except RuntimeError: - pass await asyncio.sleep(0.3) From a09c8eecb7b5ca2a2bace70c4d8460f3682c2d69 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 23 Mar 2021 15:56:33 +0100 Subject: [PATCH 569/831] Fix some sensor classes (#48254) * Fix some sensor classes * Tweak * Tweak --- .../components/aemet/abstract_aemet_sensor.py | 3 ++- homeassistant/components/hydrawise/__init__.py | 7 ------- homeassistant/components/hydrawise/sensor.py | 9 ++++++++- homeassistant/components/iqvia/__init__.py | 3 ++- homeassistant/components/iqvia/sensor.py | 5 ++--- .../components/nest/legacy/__init__.py | 5 ----- homeassistant/components/nest/legacy/sensor.py | 10 ++++++++++ homeassistant/components/nest/sensor_sdm.py | 4 ++-- .../components/onewire/onewire_entities.py | 5 ----- homeassistant/components/onewire/sensor.py | 17 ++++++++++++++--- homeassistant/components/raincloud/__init__.py | 5 ----- homeassistant/components/raincloud/sensor.py | 13 ++++++++++++- homeassistant/components/shelly/entity.py | 10 ---------- homeassistant/components/shelly/sensor.py | 15 +++++++++++++++ .../components/synology_dsm/__init__.py | 8 -------- homeassistant/components/synology_dsm/sensor.py | 17 ++++++++++++++--- homeassistant/components/withings/common.py | 5 ----- homeassistant/components/withings/sensor.py | 5 +++++ homeassistant/components/xbee/__init__.py | 3 ++- 19 files changed, 88 insertions(+), 61 deletions(-) diff --git a/homeassistant/components/aemet/abstract_aemet_sensor.py b/homeassistant/components/aemet/abstract_aemet_sensor.py index 7699a5f99a4..8847a5d094d 100644 --- a/homeassistant/components/aemet/abstract_aemet_sensor.py +++ b/homeassistant/components/aemet/abstract_aemet_sensor.py @@ -1,4 +1,5 @@ """Abstraction form AEMET OpenData sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -6,7 +7,7 @@ from .const import ATTRIBUTION, SENSOR_DEVICE_CLASS, SENSOR_NAME, SENSOR_UNIT from .weather_update_coordinator import WeatherUpdateCoordinator -class AbstractAemetSensor(CoordinatorEntity): +class AbstractAemetSensor(CoordinatorEntity, SensorEntity): """Abstract class for an AEMET OpenData sensor.""" def __init__( diff --git a/homeassistant/components/hydrawise/__init__.py b/homeassistant/components/hydrawise/__init__.py index 6fa05ebef16..1f1b2c03157 100644 --- a/homeassistant/components/hydrawise/__init__.py +++ b/homeassistant/components/hydrawise/__init__.py @@ -143,13 +143,6 @@ class HydrawiseEntity(Entity): """Call update method.""" self.async_schedule_update_ha_state(True) - @property - def unit_of_measurement(self): - """Return the units of measurement.""" - return DEVICE_MAP[self._sensor_type][ - DEVICE_MAP_INDEX.index("UNIT_OF_MEASURE_INDEX") - ] - @property def extra_state_attributes(self): """Return the state attributes.""" diff --git a/homeassistant/components/hydrawise/sensor.py b/homeassistant/components/hydrawise/sensor.py index 7cd521296f6..62108afbded 100644 --- a/homeassistant/components/hydrawise/sensor.py +++ b/homeassistant/components/hydrawise/sensor.py @@ -8,7 +8,7 @@ from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv from homeassistant.util import dt -from . import DATA_HYDRAWISE, SENSORS, HydrawiseEntity +from . import DATA_HYDRAWISE, DEVICE_MAP, DEVICE_MAP_INDEX, SENSORS, HydrawiseEntity _LOGGER = logging.getLogger(__name__) @@ -44,6 +44,13 @@ class HydrawiseSensor(HydrawiseEntity, SensorEntity): """Return the state of the sensor.""" return self._state + @property + def unit_of_measurement(self): + """Return the units of measurement.""" + return DEVICE_MAP[self._sensor_type][ + DEVICE_MAP_INDEX.index("UNIT_OF_MEASURE_INDEX") + ] + def update(self): """Get the latest data and updates the states.""" mydata = self.hass.data[DATA_HYDRAWISE].data diff --git a/homeassistant/components/iqvia/__init__.py b/homeassistant/components/iqvia/__init__.py index 962f13ca07e..c548a115e04 100644 --- a/homeassistant/components/iqvia/__init__.py +++ b/homeassistant/components/iqvia/__init__.py @@ -6,6 +6,7 @@ from functools import partial from pyiqvia import Client from pyiqvia.errors import IQVIAError +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.core import callback from homeassistant.helpers import aiohttp_client @@ -109,7 +110,7 @@ async def async_unload_entry(hass, entry): return unload_ok -class IQVIAEntity(CoordinatorEntity): +class IQVIAEntity(CoordinatorEntity, SensorEntity): """Define a base IQVIA entity.""" def __init__(self, coordinator, entry, sensor_type, name, icon): diff --git a/homeassistant/components/iqvia/sensor.py b/homeassistant/components/iqvia/sensor.py index 169ff405d6d..48ec1cf97b1 100644 --- a/homeassistant/components/iqvia/sensor.py +++ b/homeassistant/components/iqvia/sensor.py @@ -3,7 +3,6 @@ from statistics import mean import numpy as np -from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_STATE from homeassistant.core import callback @@ -99,7 +98,7 @@ def calculate_trend(indices): return TREND_FLAT -class ForecastSensor(IQVIAEntity, SensorEntity): +class ForecastSensor(IQVIAEntity): """Define sensor related to forecast data.""" @callback @@ -138,7 +137,7 @@ class ForecastSensor(IQVIAEntity, SensorEntity): self._state = average -class IndexSensor(IQVIAEntity, SensorEntity): +class IndexSensor(IQVIAEntity): """Define sensor related to indices.""" @callback diff --git a/homeassistant/components/nest/legacy/__init__.py b/homeassistant/components/nest/legacy/__init__.py index 11bc1ab33ff..60faa90e8b4 100644 --- a/homeassistant/components/nest/legacy/__init__.py +++ b/homeassistant/components/nest/legacy/__init__.py @@ -363,11 +363,6 @@ class NestSensorDevice(Entity): """Return the name of the nest, if any.""" return self._name - @property - def unit_of_measurement(self): - """Return the unit the value is expressed in.""" - return self._unit - @property def should_poll(self): """Do not need poll thanks using Nest streaming API.""" diff --git a/homeassistant/components/nest/legacy/sensor.py b/homeassistant/components/nest/legacy/sensor.py index 9bea3b34ad4..53d9c824466 100644 --- a/homeassistant/components/nest/legacy/sensor.py +++ b/homeassistant/components/nest/legacy/sensor.py @@ -153,6 +153,11 @@ async def async_setup_legacy_entry(hass, entry, async_add_entities): class NestBasicSensor(NestSensorDevice, SensorEntity): """Representation a basic Nest sensor.""" + @property + def unit_of_measurement(self): + """Return the unit the value is expressed in.""" + return self._unit + @property def state(self): """Return the state of the sensor.""" @@ -188,6 +193,11 @@ class NestTempSensor(NestSensorDevice, SensorEntity): """Return the state of the sensor.""" return self._state + @property + def unit_of_measurement(self): + """Return the unit the value is expressed in.""" + return self._unit + @property def device_class(self): """Return the device class of the sensor.""" diff --git a/homeassistant/components/nest/sensor_sdm.py b/homeassistant/components/nest/sensor_sdm.py index 946be2e95b0..06e2b68d7cf 100644 --- a/homeassistant/components/nest/sensor_sdm.py +++ b/homeassistant/components/nest/sensor_sdm.py @@ -7,6 +7,7 @@ from google_nest_sdm.device import Device from google_nest_sdm.device_traits import HumidityTrait, TemperatureTrait from google_nest_sdm.exceptions import GoogleNestException +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, @@ -15,7 +16,6 @@ from homeassistant.const import ( TEMP_CELSIUS, ) from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType from .const import DATA_SUBSCRIBER, DOMAIN @@ -53,7 +53,7 @@ async def async_setup_sdm_entry( async_add_entities(entities) -class SensorBase(Entity): +class SensorBase(SensorEntity): """Representation of a dynamically updated Sensor.""" def __init__(self, device: Device): diff --git a/homeassistant/components/onewire/onewire_entities.py b/homeassistant/components/onewire/onewire_entities.py index e8c013094f7..10c2b0c24a7 100644 --- a/homeassistant/components/onewire/onewire_entities.py +++ b/homeassistant/components/onewire/onewire_entities.py @@ -54,11 +54,6 @@ class OneWireBaseEntity(Entity): """Return the class of this device.""" return self._device_class - @property - def unit_of_measurement(self) -> str | None: - """Return the unit the value is expressed in.""" - return self._unit_of_measurement - @property def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index ba988aba226..3ea29a904db 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -1,4 +1,6 @@ """Support for 1-Wire environment sensors.""" +from __future__ import annotations + from glob import glob import logging import os @@ -394,7 +396,16 @@ def get_entities(onewirehub: OneWireHub, config): return entities -class OneWireProxySensor(OneWireProxyEntity, SensorEntity): +class OneWireSensor(OneWireBaseEntity, SensorEntity): + """Mixin for sensor specific attributes.""" + + @property + def unit_of_measurement(self) -> str | None: + """Return the unit the value is expressed in.""" + return self._unit_of_measurement + + +class OneWireProxySensor(OneWireProxyEntity, OneWireSensor): """Implementation of a 1-Wire sensor connected through owserver.""" @property @@ -403,7 +414,7 @@ class OneWireProxySensor(OneWireProxyEntity, SensorEntity): return self._state -class OneWireDirectSensor(OneWireBaseEntity, SensorEntity): +class OneWireDirectSensor(OneWireSensor): """Implementation of a 1-Wire sensor directly connected to RPI GPIO.""" def __init__(self, name, device_file, device_info, owsensor): @@ -431,7 +442,7 @@ class OneWireDirectSensor(OneWireBaseEntity, SensorEntity): self._state = value -class OneWireOWFSSensor(OneWireBaseEntity, SensorEntity): # pragma: no cover +class OneWireOWFSSensor(OneWireSensor): # pragma: no cover """Implementation of a 1-Wire sensor through owfs. This part of the implementation does not conform to policy regarding 3rd-party libraries, and will not longer be updated. diff --git a/homeassistant/components/raincloud/__init__.py b/homeassistant/components/raincloud/__init__.py index 6b0ca39df03..51e9f5de5ce 100644 --- a/homeassistant/components/raincloud/__init__.py +++ b/homeassistant/components/raincloud/__init__.py @@ -160,11 +160,6 @@ class RainCloudEntity(Entity): """Call update method.""" self.schedule_update_ha_state(True) - @property - def unit_of_measurement(self): - """Return the units of measurement.""" - return UNIT_OF_MEASUREMENT_MAP.get(self._sensor_type) - @property def extra_state_attributes(self): """Return the state attributes.""" diff --git a/homeassistant/components/raincloud/sensor.py b/homeassistant/components/raincloud/sensor.py index efff3677009..ee8f68734ad 100644 --- a/homeassistant/components/raincloud/sensor.py +++ b/homeassistant/components/raincloud/sensor.py @@ -8,7 +8,13 @@ from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv from homeassistant.helpers.icon import icon_for_battery_level -from . import DATA_RAINCLOUD, ICON_MAP, SENSORS, RainCloudEntity +from . import ( + DATA_RAINCLOUD, + ICON_MAP, + SENSORS, + UNIT_OF_MEASUREMENT_MAP, + RainCloudEntity, +) _LOGGER = logging.getLogger(__name__) @@ -46,6 +52,11 @@ class RainCloudSensor(RainCloudEntity, SensorEntity): """Return the state of the sensor.""" return self._state + @property + def unit_of_measurement(self): + """Return the units of measurement.""" + return UNIT_OF_MEASUREMENT_MAP.get(self._sensor_type) + def update(self): """Get the latest data and updates the states.""" _LOGGER.debug("Updating RainCloud sensor: %s", self._name) diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index 292a6050f9a..48d37312225 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -267,11 +267,6 @@ class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity): return self.description.value(value) - @property - def unit_of_measurement(self): - """Return unit of sensor.""" - return self._unit - @property def device_class(self): """Device class of sensor.""" @@ -348,11 +343,6 @@ class ShellyRestAttributeEntity(update_coordinator.CoordinatorEntity): ) return self._last_value - @property - def unit_of_measurement(self): - """Return unit of sensor.""" - return self.description.unit - @property def device_class(self): """Device class of sensor.""" diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index ae9e2d740d3..9e24034bf83 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -212,6 +212,11 @@ class ShellySensor(ShellyBlockAttributeEntity, SensorEntity): """Return value of sensor.""" return self.attribute_value + @property + def unit_of_measurement(self): + """Return unit of sensor.""" + return self._unit + class ShellyRestSensor(ShellyRestAttributeEntity, SensorEntity): """Represent a shelly REST sensor.""" @@ -221,6 +226,11 @@ class ShellyRestSensor(ShellyRestAttributeEntity, SensorEntity): """Return value of sensor.""" return self.attribute_value + @property + def unit_of_measurement(self): + """Return unit of sensor.""" + return self.description.unit + class ShellySleepingSensor(ShellySleepingBlockAttributeEntity, SensorEntity): """Represent a shelly sleeping sensor.""" @@ -232,3 +242,8 @@ class ShellySleepingSensor(ShellySleepingBlockAttributeEntity, SensorEntity): return self.attribute_value return self.last_state + + @property + def unit_of_measurement(self): + """Return unit of sensor.""" + return self._unit diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 673cb6717fc..960d6321c21 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -72,7 +72,6 @@ from .const import ( STORAGE_VOL_SENSORS, SYNO_API, SYSTEM_LOADED, - TEMP_SENSORS_KEYS, UNDO_UPDATE_LISTENER, UTILISATION_SENSORS, ) @@ -631,13 +630,6 @@ class SynologyDSMBaseEntity(CoordinatorEntity): """Return the icon.""" return self._icon - @property - def unit_of_measurement(self) -> str: - """Return the unit the value is expressed in.""" - if self.entity_type in TEMP_SENSORS_KEYS: - return self.hass.config.units.temperature_unit - return self._unit - @property def device_class(self) -> str: """Return the class of this device.""" diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index 56eb56df07d..22f41601e7b 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -87,7 +87,18 @@ async def async_setup_entry( async_add_entities(entities) -class SynoDSMUtilSensor(SynologyDSMBaseEntity, SensorEntity): +class SynoDSMSensor(SynologyDSMBaseEntity): + """Mixin for sensor specific attributes.""" + + @property + def unit_of_measurement(self) -> str: + """Return the unit the value is expressed in.""" + if self.entity_type in TEMP_SENSORS_KEYS: + return self.hass.config.units.temperature_unit + return self._unit + + +class SynoDSMUtilSensor(SynoDSMSensor, SensorEntity): """Representation a Synology Utilisation sensor.""" @property @@ -119,7 +130,7 @@ class SynoDSMUtilSensor(SynologyDSMBaseEntity, SensorEntity): return bool(self._api.utilisation) -class SynoDSMStorageSensor(SynologyDSMDeviceEntity, SensorEntity): +class SynoDSMStorageSensor(SynologyDSMDeviceEntity, SynoDSMSensor, SensorEntity): """Representation a Synology Storage sensor.""" @property @@ -140,7 +151,7 @@ class SynoDSMStorageSensor(SynologyDSMDeviceEntity, SensorEntity): return attr -class SynoDSMInfoSensor(SynologyDSMBaseEntity, SensorEntity): +class SynoDSMInfoSensor(SynoDSMSensor, SensorEntity): """Representation a Synology information sensor.""" def __init__( diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index 96be4fd3063..3ff869767a9 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -967,11 +967,6 @@ class BaseWithingsSensor(Entity): """Return a unique, Home Assistant friendly identifier for this entity.""" return self._unique_id - @property - def unit_of_measurement(self) -> str: - """Return the unit of measurement of this entity, if any.""" - return self._attribute.unit_of_measurement - @property def icon(self) -> str: """Icon to use in the frontend, if any.""" diff --git a/homeassistant/components/withings/sensor.py b/homeassistant/components/withings/sensor.py index 89bb81eb607..e26804f1f0a 100644 --- a/homeassistant/components/withings/sensor.py +++ b/homeassistant/components/withings/sensor.py @@ -35,3 +35,8 @@ class WithingsHealthSensor(BaseWithingsSensor, SensorEntity): def state(self) -> None | str | int | float: """Return the state of the entity.""" return self._state_data + + @property + def unit_of_measurement(self) -> str: + """Return the unit of measurement of this entity, if any.""" + return self._attribute.unit_of_measurement diff --git a/homeassistant/components/xbee/__init__.py b/homeassistant/components/xbee/__init__.py index 6373cfa7535..58ce7587070 100644 --- a/homeassistant/components/xbee/__init__.py +++ b/homeassistant/components/xbee/__init__.py @@ -9,6 +9,7 @@ import xbee_helper.const as xb_const from xbee_helper.device import convert_adc from xbee_helper.exceptions import ZigBeeException, ZigBeeTxFailure +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONF_ADDRESS, CONF_DEVICE, @@ -365,7 +366,7 @@ class XBeeDigitalOut(XBeeDigitalIn): self._state = self._config.state2bool[pin_state] -class XBeeAnalogIn(Entity): +class XBeeAnalogIn(SensorEntity): """Representation of a GPIO pin configured as an analog input.""" def __init__(self, config, device): From 269608d1af54c20a61f130ba9f0e0e8b8c2251b0 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Tue, 23 Mar 2021 11:03:16 -0400 Subject: [PATCH 570/831] Bump up ZHA dependencies (#48257) --- 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 5eee54a5c71..6493bd138bf 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -9,7 +9,7 @@ "pyserial-asyncio==0.5", "zha-quirks==0.0.54", "zigpy-cc==0.5.2", - "zigpy-deconz==0.11.1", + "zigpy-deconz==0.12.0", "zigpy==0.33.0", "zigpy-xbee==0.13.0", "zigpy-zigate==0.7.3", diff --git a/requirements_all.txt b/requirements_all.txt index 48ac1318b04..95691cd3f43 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2378,7 +2378,7 @@ ziggo-mediabox-xl==1.1.0 zigpy-cc==0.5.2 # homeassistant.components.zha -zigpy-deconz==0.11.1 +zigpy-deconz==0.12.0 # homeassistant.components.zha zigpy-xbee==0.13.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7c3297de9f1..39d823d4e18 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1224,7 +1224,7 @@ zha-quirks==0.0.54 zigpy-cc==0.5.2 # homeassistant.components.zha -zigpy-deconz==0.11.1 +zigpy-deconz==0.12.0 # homeassistant.components.zha zigpy-xbee==0.13.0 From cc73cbcace71d911b8ce2e8ddd41209bd4712d5b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 23 Mar 2021 18:45:40 +0100 Subject: [PATCH 571/831] Update issue form to use latest changes (#48250) --- .github/ISSUE_TEMPLATE/bug_report.yml | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 9a46dd82215..384cc5834cd 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,5 +1,5 @@ name: Report an issue with Home Assistant Core -about: Report an issue with Home Assistant Core. +description: Report an issue with Home Assistant Core. title: "" issue_body: true body: @@ -29,6 +29,7 @@ body: validations: required: true attributes: + id: version label: What is version of Home Assistant Core has the issue? placeholder: core- description: > @@ -52,11 +53,13 @@ body: - Home Assistant Supervised - Home Assistant Core - type: input + id: integration_name attributes: label: Integration causing the issue description: > The name of the integration, for example, Automation or Philips Hue. - type: input + id: integration_link attributes: label: Link to integration documentation on our website placeholder: "https://www.home-assistant.io/integrations/..." @@ -76,20 +79,12 @@ body: description: | If this issue has an example piece of YAML that can help reproducing this problem, please provide. This can be an piece of YAML from, e.g., an automation, script, scene or configuration. - value: | - ```yaml - # Put your YAML below this line - - ``` + render: yaml - type: textarea attributes: label: Anything in the logs that might be useful for us? description: For example, error message, or stack traces. - value: | - ```txt - # Put your logs below this line - - ``` + render: txt - type: markdown attributes: value: | From d129b8e1e1062a1d9a8085c1ef9f9600532fb172 Mon Sep 17 00:00:00 2001 From: Fredrik Erlandsson Date: Tue, 23 Mar 2021 20:03:54 +0100 Subject: [PATCH 572/831] Update pypoint to 2.1.0 (#48223) * update pypoint to 2.1.0 * Add properties and device_classes to constant * Fix unique_ids for binary_sensors * Update device icon * Fallback to device_class icon. Co-authored-by: Erik Montnemery * Just use known events * Use DEVICE_CLASS_SOUND Co-authored-by: Erik Montnemery --- .../components/point/binary_sensor.py | 87 +++++++++++-------- homeassistant/components/point/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 53 insertions(+), 40 deletions(-) diff --git a/homeassistant/components/point/binary_sensor.py b/homeassistant/components/point/binary_sensor.py index d82ecd096ee..0bcd2a33a2e 100644 --- a/homeassistant/components/point/binary_sensor.py +++ b/homeassistant/components/point/binary_sensor.py @@ -1,8 +1,16 @@ """Support for Minut Point binary sensors.""" import logging +from pypoint import EVENTS + from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_COLD, DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_HEAT, + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_MOTION, + DEVICE_CLASS_SOUND, DOMAIN, BinarySensorEntity, ) @@ -14,37 +22,22 @@ from .const import DOMAIN as POINT_DOMAIN, POINT_DISCOVERY_NEW, SIGNAL_WEBHOOK _LOGGER = logging.getLogger(__name__) -EVENTS = { - "battery": ("battery_low", ""), # On means low, Off means normal - "button_press": ( # On means the button was pressed, Off means normal - "short_button_press", - "", - ), - "cold": ( # On means cold, Off means normal - "temperature_low", - "temperature_risen_normal", - ), - "connectivity": ( # On means connected, Off means disconnected - "device_online", - "device_offline", - ), - "dry": ( # On means too dry, Off means normal - "humidity_low", - "humidity_risen_normal", - ), - "heat": ( # On means hot, Off means normal - "temperature_high", - "temperature_dropped_normal", - ), - "moisture": ( # On means wet, Off means dry - "humidity_high", - "humidity_dropped_normal", - ), - "sound": ( # On means sound detected, Off means no sound (clear) - "avg_sound_high", - "sound_level_dropped_normal", - ), - "tamper": ("tamper", ""), # On means the point was removed or attached + +DEVICES = { + "alarm": {"icon": "mdi:alarm-bell"}, + "battery": {"device_class": DEVICE_CLASS_BATTERY}, + "button_press": {"icon": "mdi:gesture-tap-button"}, + "cold": {"device_class": DEVICE_CLASS_COLD}, + "connectivity": {"device_class": DEVICE_CLASS_CONNECTIVITY}, + "dry": {"icon": "mdi:water"}, + "glass": {"icon": "mdi:window-closed-variant"}, + "heat": {"device_class": DEVICE_CLASS_HEAT}, + "moisture": {"device_class": DEVICE_CLASS_MOISTURE}, + "motion": {"device_class": DEVICE_CLASS_MOTION}, + "noise": {"icon": "mdi:volume-high"}, + "sound": {"device_class": DEVICE_CLASS_SOUND}, + "tamper_old": {"icon": "mdi:shield-alert"}, + "tamper": {"icon": "mdi:shield-alert"}, } @@ -56,8 +49,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): client = hass.data[POINT_DOMAIN][config_entry.entry_id] async_add_entities( ( - MinutPointBinarySensor(client, device_id, device_class) - for device_class in EVENTS + MinutPointBinarySensor(client, device_id, device_name) + for device_name in DEVICES + if device_name in EVENTS ), True, ) @@ -70,12 +64,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class MinutPointBinarySensor(MinutPointEntity, BinarySensorEntity): """The platform class required by Home Assistant.""" - def __init__(self, point_client, device_id, device_class): + def __init__(self, point_client, device_id, device_name): """Initialize the binary sensor.""" - super().__init__(point_client, device_id, device_class) - + super().__init__( + point_client, + device_id, + DEVICES[device_name].get("device_class"), + ) + self._device_name = device_name self._async_unsub_hook_dispatcher_connect = None - self._events = EVENTS[device_class] + self._events = EVENTS[device_name] self._is_on = None async def async_added_to_hass(self): @@ -124,3 +122,18 @@ class MinutPointBinarySensor(MinutPointEntity, BinarySensorEntity): # connectivity is the other way around. return not self._is_on return self._is_on + + @property + def name(self): + """Return the display name of this device.""" + return f"{self._name} {self._device_name.capitalize()}" + + @property + def icon(self): + """Return the icon to use in the frontend, if any.""" + return DEVICES[self._device_name].get("icon") + + @property + def unique_id(self): + """Return the unique id of the sensor.""" + return f"point.{self._id}-{self._device_name}" diff --git a/homeassistant/components/point/manifest.json b/homeassistant/components/point/manifest.json index 6c25cdef91a..899e5615b40 100644 --- a/homeassistant/components/point/manifest.json +++ b/homeassistant/components/point/manifest.json @@ -3,7 +3,7 @@ "name": "Minut Point", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/point", - "requirements": ["pypoint==2.0.0"], + "requirements": ["pypoint==2.1.0"], "dependencies": ["webhook", "http"], "codeowners": ["@fredrike"], "quality_scale": "gold" diff --git a/requirements_all.txt b/requirements_all.txt index 95691cd3f43..203cecd6525 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1622,7 +1622,7 @@ pypjlink2==1.2.1 pyplaato==0.0.15 # homeassistant.components.point -pypoint==2.0.0 +pypoint==2.1.0 # homeassistant.components.profiler pyprof2calltree==1.4.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 39d823d4e18..2ce2cac591b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -867,7 +867,7 @@ pypck==0.7.9 pyplaato==0.0.15 # homeassistant.components.point -pypoint==2.0.0 +pypoint==2.1.0 # homeassistant.components.profiler pyprof2calltree==1.4.5 From fd5916e067af38639885a6cea1ec1dfc4b33dcf5 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Tue, 23 Mar 2021 19:19:47 +0000 Subject: [PATCH 573/831] datetime must be a string (#47809) --- homeassistant/components/buienradar/weather.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/buienradar/weather.py b/homeassistant/components/buienradar/weather.py index 4b0391c3190..2ff638a2550 100644 --- a/homeassistant/components/buienradar/weather.py +++ b/homeassistant/components/buienradar/weather.py @@ -201,7 +201,7 @@ class BrWeather(WeatherEntity): # keys understood by the weather component: condcode = data_in.get(CONDITION, []).get(CONDCODE) data_out = { - ATTR_FORECAST_TIME: data_in.get(DATETIME), + ATTR_FORECAST_TIME: data_in.get(DATETIME).isoformat(), ATTR_FORECAST_CONDITION: cond[condcode], ATTR_FORECAST_TEMP_LOW: data_in.get(MIN_TEMP), ATTR_FORECAST_TEMP: data_in.get(MAX_TEMP), From 49b47fe64890fe37217c4afb1935a9307b9665f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Tue, 23 Mar 2021 22:04:15 +0100 Subject: [PATCH 574/831] Install requirements.txt while building dev Dockerfile (#48268) --- Dockerfile.dev | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index 09f8f155930..68188f16f01 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -28,10 +28,11 @@ RUN git clone --depth 1 https://github.com/home-assistant/hass-release \ WORKDIR /workspaces # Install Python dependencies from requirements -COPY requirements_test.txt requirements_test_pre_commit.txt ./ +COPY requirements.txt requirements_test.txt requirements_test_pre_commit.txt ./ COPY homeassistant/package_constraints.txt homeassistant/package_constraints.txt -RUN pip3 install -r requirements_test.txt \ - && rm -rf requirements_test.txt requirements_test_pre_commit.txt homeassistant/ +RUN pip3 install -r requirements.txt \ + && pip3 install -r requirements_test.txt \ + && rm -rf requirements.txt requirements_test.txt requirements_test_pre_commit.txt homeassistant/ # Set the default shell to bash instead of sh ENV SHELL /bin/bash From 70d9e8a582df92b26640dcb871ec7b4a5589af09 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 23 Mar 2021 22:29:55 +0100 Subject: [PATCH 575/831] Add proper percentage support to deCONZ fan integration (#48187) * Add proper percentage support to deCONZ fan integration * Properly convert speed to percentage * Remove disabled method * Replace convert_speed with a dict --- homeassistant/components/deconz/fan.py | 119 ++++++--- tests/components/deconz/test_fan.py | 327 ++++++++++++++++++++++++- 2 files changed, 402 insertions(+), 44 deletions(-) diff --git a/homeassistant/components/deconz/fan.py b/homeassistant/components/deconz/fan.py index 1ca4c8ff9c2..3d2e61fdd7b 100644 --- a/homeassistant/components/deconz/fan.py +++ b/homeassistant/components/deconz/fan.py @@ -1,4 +1,7 @@ -"""Support for deCONZ switches.""" +"""Support for deCONZ fans.""" + +from typing import Optional + from homeassistant.components.fan import ( DOMAIN, SPEED_HIGH, @@ -10,25 +13,19 @@ from homeassistant.components.fan import ( ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util.percentage import ( + ordered_list_item_to_percentage, + percentage_to_ordered_list_item, +) from .const import FANS, NEW_LIGHT from .deconz_device import DeconzDevice from .gateway import get_gateway_from_config_entry -SPEEDS = {SPEED_OFF: 0, SPEED_LOW: 1, SPEED_MEDIUM: 2, SPEED_HIGH: 4} -SUPPORTED_ON_SPEEDS = {1: SPEED_LOW, 2: SPEED_MEDIUM, 4: SPEED_HIGH} +ORDERED_NAMED_FAN_SPEEDS = [1, 2, 3, 4] - -def convert_speed(speed: int) -> str: - """Convert speed from deCONZ to HASS. - - Fallback to medium speed if unsupported by HASS fan platform. - """ - if speed in SPEEDS.values(): - for hass_speed, deconz_speed in SPEEDS.items(): - if speed == deconz_speed: - return hass_speed - return SPEED_MEDIUM +LEGACY_SPEED_TO_DECONZ = {SPEED_OFF: 0, SPEED_LOW: 1, SPEED_MEDIUM: 2, SPEED_HIGH: 4} +LEGACY_DECONZ_TO_SPEED = {0: SPEED_OFF, 1: SPEED_LOW, 2: SPEED_MEDIUM, 4: SPEED_HIGH} async def async_setup_entry(hass, config_entry, async_add_entities) -> None: @@ -67,8 +64,8 @@ class DeconzFan(DeconzDevice, FanEntity): """Set up fan.""" super().__init__(device, gateway) - self._default_on_speed = SPEEDS[SPEED_MEDIUM] - if self.speed != SPEED_OFF: + self._default_on_speed = 2 + if self._device.speed in ORDERED_NAMED_FAN_SPEEDS: self._default_on_speed = self._device.speed self._features = SUPPORT_SET_SPEED @@ -76,17 +73,58 @@ class DeconzFan(DeconzDevice, FanEntity): @property def is_on(self) -> bool: """Return true if fan is on.""" - return self.speed != SPEED_OFF + return self._device.speed != 0 @property - def speed(self) -> int: - """Return the current speed.""" - return convert_speed(self._device.speed) + def percentage(self) -> Optional[int]: + """Return the current speed percentage.""" + if self._device.speed == 0: + return 0 + if self._device.speed not in ORDERED_NAMED_FAN_SPEEDS: + return + return ordered_list_item_to_percentage( + ORDERED_NAMED_FAN_SPEEDS, self._device.speed + ) + + @property + def speed_count(self) -> int: + """Return the number of speeds the fan supports.""" + return len(ORDERED_NAMED_FAN_SPEEDS) @property def speed_list(self) -> list: - """Get the list of available speeds.""" - return list(SPEEDS) + """Get the list of available speeds. + + Legacy fan support. + """ + return list(LEGACY_SPEED_TO_DECONZ) + + def speed_to_percentage(self, speed: str) -> int: + """Convert speed to percentage. + + Legacy fan support. + """ + if speed == SPEED_OFF: + return 0 + + if speed not in LEGACY_SPEED_TO_DECONZ: + speed = SPEED_MEDIUM + + return ordered_list_item_to_percentage( + ORDERED_NAMED_FAN_SPEEDS, LEGACY_SPEED_TO_DECONZ[speed] + ) + + def percentage_to_speed(self, percentage: int) -> str: + """Convert percentage to speed. + + Legacy fan support. + """ + if percentage == 0: + return SPEED_OFF + return LEGACY_DECONZ_TO_SPEED.get( + percentage_to_ordered_list_item(ORDERED_NAMED_FAN_SPEEDS, percentage), + SPEED_MEDIUM, + ) @property def supported_features(self) -> int: @@ -96,24 +134,26 @@ class DeconzFan(DeconzDevice, FanEntity): @callback def async_update_callback(self, force_update=False) -> None: """Store latest configured speed from the device.""" - if self.speed != SPEED_OFF and self._device.speed != self._default_on_speed: + if self._device.speed in ORDERED_NAMED_FAN_SPEEDS: self._default_on_speed = self._device.speed super().async_update_callback(force_update) + async def async_set_percentage(self, percentage: int) -> None: + """Set the speed percentage of the fan.""" + await self._device.set_speed( + percentage_to_ordered_list_item(ORDERED_NAMED_FAN_SPEEDS, percentage) + ) + async def async_set_speed(self, speed: str) -> None: - """Set the speed of the fan.""" - if speed not in SPEEDS: + """Set the speed of the fan. + + Legacy fan support. + """ + if speed not in LEGACY_SPEED_TO_DECONZ: raise ValueError(f"Unsupported speed {speed}") - await self._device.set_speed(SPEEDS[speed]) + await self._device.set_speed(LEGACY_SPEED_TO_DECONZ[speed]) - # - # The fan entity model has changed to use percentages and preset_modes - # instead of speeds. - # - # Please review - # https://developers.home-assistant.io/docs/core/entity/fan/ - # async def async_turn_on( self, speed: str = None, @@ -122,10 +162,15 @@ class DeconzFan(DeconzDevice, FanEntity): **kwargs, ) -> None: """Turn on fan.""" - if not speed: - speed = convert_speed(self._default_on_speed) - await self.async_set_speed(speed) + new_speed = self._default_on_speed + + if percentage is not None: + new_speed = percentage_to_ordered_list_item( + ORDERED_NAMED_FAN_SPEEDS, percentage + ) + + await self._device.set_speed(new_speed) async def async_turn_off(self, **kwargs) -> None: """Turn off fan.""" - await self.async_set_speed(SPEED_OFF) + await self._device.set_speed(0) diff --git a/tests/components/deconz/test_fan.py b/tests/components/deconz/test_fan.py index 930645689f8..ddd4a4e46f4 100644 --- a/tests/components/deconz/test_fan.py +++ b/tests/components/deconz/test_fan.py @@ -5,8 +5,10 @@ from unittest.mock import patch import pytest from homeassistant.components.fan import ( + ATTR_PERCENTAGE, ATTR_SPEED, DOMAIN as FAN_DOMAIN, + SERVICE_SET_PERCENTAGE, SERVICE_SET_SPEED, SERVICE_TURN_OFF, SERVICE_TURN_ON, @@ -59,10 +61,62 @@ async def test_fans(hass, aioclient_mock, mock_deconz_websocket): assert len(hass.states.async_all()) == 2 # Light and fan assert hass.states.get("fan.ceiling_fan").state == STATE_ON - assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_HIGH + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 100 # Test states + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 1}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 25 + + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 2}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 50 + + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 3}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 75 + + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 4}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 100 + event_changed_light = { "t": "event", "e": "changed", @@ -74,13 +128,13 @@ async def test_fans(hass, aioclient_mock, mock_deconz_websocket): await hass.async_block_till_done() assert hass.states.get("fan.ceiling_fan").state == STATE_OFF - assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_OFF + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 0 # Test service calls mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1/state") - # Service turn on fan + # Service turn on fan using saved default_on_speed await hass.services.async_call( FAN_DOMAIN, @@ -100,6 +154,265 @@ async def test_fans(hass, aioclient_mock, mock_deconz_websocket): ) assert aioclient_mock.mock_calls[2][2] == {"speed": 0} + # Service turn on fan to 20% + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_PERCENTAGE: 20}, + blocking=True, + ) + assert aioclient_mock.mock_calls[3][2] == {"speed": 1} + + # Service set fan percentage to 20% + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_SET_PERCENTAGE, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_PERCENTAGE: 20}, + blocking=True, + ) + assert aioclient_mock.mock_calls[4][2] == {"speed": 1} + + # Service set fan percentage to 40% + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_SET_PERCENTAGE, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_PERCENTAGE: 40}, + blocking=True, + ) + assert aioclient_mock.mock_calls[5][2] == {"speed": 2} + + # Service set fan percentage to 60% + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_SET_PERCENTAGE, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_PERCENTAGE: 60}, + blocking=True, + ) + assert aioclient_mock.mock_calls[6][2] == {"speed": 3} + + # Service set fan percentage to 80% + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_SET_PERCENTAGE, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_PERCENTAGE: 80}, + blocking=True, + ) + assert aioclient_mock.mock_calls[7][2] == {"speed": 4} + + # Service set fan percentage to 0% does not equal off + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_SET_PERCENTAGE, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_PERCENTAGE: 0}, + blocking=True, + ) + assert aioclient_mock.mock_calls[8][2] == {"speed": 1} + + # Events with an unsupported speed does not get converted + + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 5}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert not hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] + + await hass.config_entries.async_unload(config_entry.entry_id) + + states = hass.states.async_all() + assert len(states) == 2 + for state in states: + assert state.state == STATE_UNAVAILABLE + + await hass.config_entries.async_remove(config_entry.entry_id) + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 0 + + +async def test_fans_legacy_speed_modes(hass, aioclient_mock, mock_deconz_websocket): + """Test that all supported fan entities are created. + + Legacy fan support. + """ + data = { + "lights": { + "1": { + "etag": "432f3de28965052961a99e3c5494daf4", + "hascolor": False, + "manufacturername": "King Of Fans, Inc.", + "modelid": "HDC52EastwindFan", + "name": "Ceiling fan", + "state": { + "alert": "none", + "bri": 254, + "on": False, + "reachable": True, + "speed": 4, + }, + "swversion": "0000000F", + "type": "Fan", + "uniqueid": "00:22:a3:00:00:27:8b:81-01", + } + } + } + + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + assert len(hass.states.async_all()) == 2 # Light and fan + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_HIGH + + # Test states + + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 1}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 25 + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_LOW + + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 2}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 50 + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_MEDIUM + + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 3}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 75 + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_MEDIUM + + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 4}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 100 + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_HIGH + + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 0}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_OFF + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 0 + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_OFF + + # Test service calls + + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1/state") + + # Service turn on fan using saved default_on_speed + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "fan.ceiling_fan"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == {"speed": 4} + + # Service turn on fan with speed_off + # async_turn_on_compat use speed_to_percentage which will return 0 + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_OFF}, + blocking=True, + ) + assert aioclient_mock.mock_calls[2][2] == {"speed": 1} + + # Service turn on fan with bad speed + # async_turn_on_compat use speed_to_percentage which will convert to SPEED_MEDIUM -> 2 + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: "bad"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[3][2] == {"speed": 2} + + # Service turn on fan to low speed + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_LOW}, + blocking=True, + ) + assert aioclient_mock.mock_calls[4][2] == {"speed": 1} + + # Service turn on fan to medium speed + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_MEDIUM}, + blocking=True, + ) + assert aioclient_mock.mock_calls[5][2] == {"speed": 2} + + # Service turn on fan to high speed + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_HIGH}, + blocking=True, + ) + assert aioclient_mock.mock_calls[6][2] == {"speed": 4} + # Service set fan speed to low await hass.services.async_call( @@ -108,7 +421,7 @@ async def test_fans(hass, aioclient_mock, mock_deconz_websocket): {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_LOW}, blocking=True, ) - assert aioclient_mock.mock_calls[3][2] == {"speed": 1} + assert aioclient_mock.mock_calls[7][2] == {"speed": 1} # Service set fan speed to medium @@ -118,7 +431,7 @@ async def test_fans(hass, aioclient_mock, mock_deconz_websocket): {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_MEDIUM}, blocking=True, ) - assert aioclient_mock.mock_calls[4][2] == {"speed": 2} + assert aioclient_mock.mock_calls[8][2] == {"speed": 2} # Service set fan speed to high @@ -128,7 +441,7 @@ async def test_fans(hass, aioclient_mock, mock_deconz_websocket): {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_HIGH}, blocking=True, ) - assert aioclient_mock.mock_calls[5][2] == {"speed": 4} + assert aioclient_mock.mock_calls[9][2] == {"speed": 4} # Service set fan speed to off @@ -138,7 +451,7 @@ async def test_fans(hass, aioclient_mock, mock_deconz_websocket): {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_OFF}, blocking=True, ) - assert aioclient_mock.mock_calls[6][2] == {"speed": 0} + assert aioclient_mock.mock_calls[10][2] == {"speed": 0} # Service set fan speed to unsupported value From 195d4de6cd349ec79b73d0e57cf9ddfad15fd9d4 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Tue, 23 Mar 2021 16:47:00 -0500 Subject: [PATCH 576/831] Bump plexapi to 4.5.0 (#48264) --- homeassistant/components/plex/manifest.json | 2 +- .../components/plex/media_browser.py | 6 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/plex/conftest.py | 6 + tests/components/plex/test_browse_media.py | 11 +- .../plex/library_movies_filtertypes.xml | 103 ++++++++++++++++++ 7 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 tests/fixtures/plex/library_movies_filtertypes.xml diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index 1319e4bbf49..647590f7cf2 100644 --- a/homeassistant/components/plex/manifest.json +++ b/homeassistant/components/plex/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/plex", "requirements": [ - "plexapi==4.4.1", + "plexapi==4.5.0", "plexauth==0.0.6", "plexwebsocket==0.0.12" ], diff --git a/homeassistant/components/plex/media_browser.py b/homeassistant/components/plex/media_browser.py index cfc5a12d6c5..f3f92880c44 100644 --- a/homeassistant/components/plex/media_browser.py +++ b/homeassistant/components/plex/media_browser.py @@ -153,7 +153,7 @@ def browse_media(entity, is_internal, media_content_type=None, media_content_id= title = entity.plex_server.friendly_name elif media_content_type == "library": library_or_section = entity.plex_server.library.sectionByID( - media_content_id + int(media_content_id) ) title = library_or_section.title try: @@ -193,7 +193,7 @@ def browse_media(entity, is_internal, media_content_type=None, media_content_id= return server_payload(entity.plex_server) if media_content_type == "library": - return library_payload(media_content_id) + return library_payload(int(media_content_id)) except UnknownMediaType as err: raise BrowseError( @@ -223,7 +223,7 @@ def library_section_payload(section): return BrowseMedia( title=section.title, media_class=MEDIA_CLASS_DIRECTORY, - media_content_id=section.key, + media_content_id=str(section.key), media_content_type="library", can_play=False, can_expand=True, diff --git a/requirements_all.txt b/requirements_all.txt index 203cecd6525..67c0988bf0d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1131,7 +1131,7 @@ pillow==8.1.2 pizzapi==0.0.3 # homeassistant.components.plex -plexapi==4.4.1 +plexapi==4.5.0 # homeassistant.components.plex plexauth==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2ce2cac591b..115f77c35dc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -577,7 +577,7 @@ pilight==0.1.1 pillow==8.1.2 # homeassistant.components.plex -plexapi==4.4.1 +plexapi==4.5.0 # homeassistant.components.plex plexauth==0.0.6 diff --git a/tests/components/plex/conftest.py b/tests/components/plex/conftest.py index 372a06f15b6..295b560f4d1 100644 --- a/tests/components/plex/conftest.py +++ b/tests/components/plex/conftest.py @@ -108,6 +108,12 @@ def library_music_sort_fixture(): return load_fixture("plex/library_music_sort.xml") +@pytest.fixture(name="library_movies_filtertypes", scope="session") +def library_movies_filtertypes_fixture(): + """Load filtertypes payload for movie library and return it.""" + return load_fixture("plex/library_movies_filtertypes.xml") + + @pytest.fixture(name="library", scope="session") def library_fixture(): """Load library payload and return it.""" diff --git a/tests/components/plex/test_browse_media.py b/tests/components/plex/test_browse_media.py index f9966a18c27..d9f02c49341 100644 --- a/tests/components/plex/test_browse_media.py +++ b/tests/components/plex/test_browse_media.py @@ -10,7 +10,9 @@ from homeassistant.components.websocket_api.const import ERR_UNKNOWN_ERROR, TYPE from .const import DEFAULT_DATA -async def test_browse_media(hass, hass_ws_client, mock_plex_server, requests_mock): +async def test_browse_media( + hass, hass_ws_client, mock_plex_server, requests_mock, library_movies_filtertypes +): """Test getting Plex clients from plex.tv.""" websocket_client = await hass_ws_client(hass) @@ -86,6 +88,11 @@ async def test_browse_media(hass, hass_ws_client, mock_plex_server, requests_moc assert len(result["children"]) == len(mock_plex_server.library.onDeck()) # Browse into a special folder (library) + requests_mock.get( + f"{mock_plex_server.url_in_use}/library/sections/1/all?includeMeta=1", + text=library_movies_filtertypes, + ) + msg_id += 1 library_section_id = next(iter(mock_plex_server.library.sections())).key await websocket_client.send_json( @@ -127,7 +134,7 @@ async def test_browse_media(hass, hass_ws_client, mock_plex_server, requests_moc assert msg["success"] result = msg["result"] assert result[ATTR_MEDIA_CONTENT_TYPE] == "library" - result_id = result[ATTR_MEDIA_CONTENT_ID] + result_id = int(result[ATTR_MEDIA_CONTENT_ID]) assert len(result["children"]) == len( mock_plex_server.library.sectionByID(result_id).all() ) + len(SPECIAL_METHODS) diff --git a/tests/fixtures/plex/library_movies_filtertypes.xml b/tests/fixtures/plex/library_movies_filtertypes.xml new file mode 100644 index 00000000000..0f305f385c2 --- /dev/null +++ b/tests/fixtures/plex/library_movies_filtertypes.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 9f8b697e6408a2dc8df2caefda316f785f334d54 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 23 Mar 2021 22:53:38 +0100 Subject: [PATCH 577/831] Refactor tracing: Prepare for tracing of scripts (#48231) --- homeassistant/components/automation/trace.py | 35 +-- homeassistant/components/trace/trace.py | 18 +- .../components/trace/websocket_api.py | 155 +++++------ homeassistant/helpers/script.py | 62 ++--- tests/components/trace/test_websocket_api.py | 243 +++++++++++------- 5 files changed, 283 insertions(+), 230 deletions(-) diff --git a/homeassistant/components/automation/trace.py b/homeassistant/components/automation/trace.py index de199ad9310..f5e93e09c38 100644 --- a/homeassistant/components/automation/trace.py +++ b/homeassistant/components/automation/trace.py @@ -23,7 +23,7 @@ class AutomationTrace: def __init__( self, - unique_id: str | None, + key: tuple[str, str], config: dict[str, Any], context: Context, ): @@ -37,7 +37,7 @@ class AutomationTrace: self.run_id: str = str(next(self._run_ids)) self._timestamp_finish: dt.datetime | None = None self._timestamp_start: dt.datetime = dt_util.utcnow() - self._unique_id: str | None = unique_id + self._key: tuple[str, str] = key self._variables: dict[str, Any] | None = None def set_action_trace(self, trace: dict[str, Deque[TraceElement]]) -> None: @@ -104,7 +104,6 @@ class AutomationTrace: trigger = self._variables.get("trigger", {}).get("description") result = { - "automation_id": self._unique_id, "last_action": last_action, "last_condition": last_condition, "run_id": self.run_id, @@ -114,7 +113,8 @@ class AutomationTrace: "finish": self._timestamp_finish, }, "trigger": trigger, - "unique_id": self._unique_id, + "domain": self._key[0], + "item_id": self._key[1], } if self._error is not None: result["error"] = str(self._error) @@ -126,23 +126,24 @@ class AutomationTrace: @contextmanager -def trace_automation(hass, unique_id, config, context): +def trace_automation(hass, item_id, config, context): """Trace action execution of automation with automation_id.""" - automation_trace = AutomationTrace(unique_id, config, context) - trace_id_set((unique_id, automation_trace.run_id)) + key = ("automation", item_id) + trace = AutomationTrace(key, config, context) + trace_id_set((key, trace.run_id)) - if unique_id: - automation_traces = hass.data[DATA_TRACE] - if unique_id not in automation_traces: - automation_traces[unique_id] = LimitedSizeDict(size_limit=STORED_TRACES) - automation_traces[unique_id][automation_trace.run_id] = automation_trace + if key: + traces = hass.data[DATA_TRACE] + if key not in traces: + traces[key] = LimitedSizeDict(size_limit=STORED_TRACES) + traces[key][trace.run_id] = trace try: - yield automation_trace + yield trace except Exception as ex: # pylint: disable=broad-except - if unique_id: - automation_trace.set_error(ex) + if key: + trace.set_error(ex) raise ex finally: - if unique_id: - automation_trace.finished() + if key: + trace.finished() diff --git a/homeassistant/components/trace/trace.py b/homeassistant/components/trace/trace.py index cc51e3269ab..e9255550ec5 100644 --- a/homeassistant/components/trace/trace.py +++ b/homeassistant/components/trace/trace.py @@ -5,17 +5,17 @@ from .const import DATA_TRACE @callback -def get_debug_trace(hass, automation_id, run_id): +def get_debug_trace(hass, key, run_id): """Return a serializable debug trace.""" - return hass.data[DATA_TRACE][automation_id][run_id] + return hass.data[DATA_TRACE][key][run_id] @callback -def get_debug_traces_for_automation(hass, automation_id, summary=False): - """Return a serializable list of debug traces for an automation.""" +def get_debug_traces(hass, key, summary=False): + """Return a serializable list of debug traces for an automation or script.""" traces = [] - for trace in hass.data[DATA_TRACE].get(automation_id, {}).values(): + for trace in hass.data[DATA_TRACE].get(key, {}).values(): if summary: traces.append(trace.as_short_dict()) else: @@ -25,11 +25,11 @@ def get_debug_traces_for_automation(hass, automation_id, summary=False): @callback -def get_debug_traces(hass, summary=False): - """Return a serializable list of debug traces.""" +def get_all_debug_traces(hass, summary=False): + """Return a serializable list of debug traces for all automations and scripts.""" traces = [] - for automation_id in hass.data[DATA_TRACE]: - traces.extend(get_debug_traces_for_automation(hass, automation_id, summary)) + for key in hass.data[DATA_TRACE]: + traces.extend(get_debug_traces(hass, key, summary)) return traces diff --git a/homeassistant/components/trace/websocket_api.py b/homeassistant/components/trace/websocket_api.py index 9ac1828de14..1f42b50671e 100644 --- a/homeassistant/components/trace/websocket_api.py +++ b/homeassistant/components/trace/websocket_api.py @@ -23,29 +23,26 @@ from homeassistant.helpers.script import ( debug_stop, ) -from .trace import ( - DATA_TRACE, - get_debug_trace, - get_debug_traces, - get_debug_traces_for_automation, -) +from .trace import DATA_TRACE, get_all_debug_traces, get_debug_trace, get_debug_traces from .utils import TraceJSONEncoder # mypy: allow-untyped-calls, allow-untyped-defs +TRACE_DOMAINS = ["automation"] + @callback def async_setup(hass: HomeAssistant) -> None: """Set up the websocket API.""" - websocket_api.async_register_command(hass, websocket_automation_trace_get) - websocket_api.async_register_command(hass, websocket_automation_trace_list) - websocket_api.async_register_command(hass, websocket_automation_trace_contexts) - websocket_api.async_register_command(hass, websocket_automation_breakpoint_clear) - websocket_api.async_register_command(hass, websocket_automation_breakpoint_list) - websocket_api.async_register_command(hass, websocket_automation_breakpoint_set) - websocket_api.async_register_command(hass, websocket_automation_debug_continue) - websocket_api.async_register_command(hass, websocket_automation_debug_step) - websocket_api.async_register_command(hass, websocket_automation_debug_stop) + websocket_api.async_register_command(hass, websocket_trace_get) + websocket_api.async_register_command(hass, websocket_trace_list) + websocket_api.async_register_command(hass, websocket_trace_contexts) + websocket_api.async_register_command(hass, websocket_breakpoint_clear) + websocket_api.async_register_command(hass, websocket_breakpoint_list) + websocket_api.async_register_command(hass, websocket_breakpoint_set) + websocket_api.async_register_command(hass, websocket_debug_continue) + websocket_api.async_register_command(hass, websocket_debug_step) + websocket_api.async_register_command(hass, websocket_debug_stop) websocket_api.async_register_command(hass, websocket_subscribe_breakpoint_events) @@ -53,17 +50,18 @@ def async_setup(hass: HomeAssistant) -> None: @websocket_api.require_admin @websocket_api.websocket_command( { - vol.Required("type"): "automation/trace/get", - vol.Required("automation_id"): str, + vol.Required("type"): "trace/get", + vol.Required("domain"): vol.In(TRACE_DOMAINS), + vol.Required("item_id"): str, vol.Required("run_id"): str, } ) -def websocket_automation_trace_get(hass, connection, msg): - """Get an automation trace.""" - automation_id = msg["automation_id"] +def websocket_trace_get(hass, connection, msg): + """Get an automation or script trace.""" + key = (msg["domain"], msg["item_id"]) run_id = msg["run_id"] - trace = get_debug_trace(hass, automation_id, run_id) + trace = get_debug_trace(hass, key, run_id) message = websocket_api.messages.result_message(msg["id"], trace) connection.send_message(json.dumps(message, cls=TraceJSONEncoder, allow_nan=False)) @@ -72,42 +70,45 @@ def websocket_automation_trace_get(hass, connection, msg): @callback @websocket_api.require_admin @websocket_api.websocket_command( - {vol.Required("type"): "automation/trace/list", vol.Optional("automation_id"): str} + { + vol.Required("type"): "trace/list", + vol.Inclusive("domain", "id"): vol.In(TRACE_DOMAINS), + vol.Inclusive("item_id", "id"): str, + } ) -def websocket_automation_trace_list(hass, connection, msg): - """Summarize automation traces.""" - automation_id = msg.get("automation_id") +def websocket_trace_list(hass, connection, msg): + """Summarize automation and script traces.""" + key = (msg["domain"], msg["item_id"]) if "item_id" in msg else None - if not automation_id: - automation_traces = get_debug_traces(hass, summary=True) + if not key: + traces = get_all_debug_traces(hass, summary=True) else: - automation_traces = get_debug_traces_for_automation( - hass, automation_id, summary=True - ) + traces = get_debug_traces(hass, key, summary=True) - connection.send_result(msg["id"], automation_traces) + connection.send_result(msg["id"], traces) @callback @websocket_api.require_admin @websocket_api.websocket_command( { - vol.Required("type"): "automation/trace/contexts", - vol.Optional("automation_id"): str, + vol.Required("type"): "trace/contexts", + vol.Inclusive("domain", "id"): vol.In(TRACE_DOMAINS), + vol.Inclusive("item_id", "id"): str, } ) -def websocket_automation_trace_contexts(hass, connection, msg): +def websocket_trace_contexts(hass, connection, msg): """Retrieve contexts we have traces for.""" - automation_id = msg.get("automation_id") + key = (msg["domain"], msg["item_id"]) if "item_id" in msg else None - if automation_id is not None: - values = {automation_id: hass.data[DATA_TRACE].get(automation_id, {})} + if key is not None: + values = {key: hass.data[DATA_TRACE].get(key, {})} else: values = hass.data[DATA_TRACE] contexts = { - trace.context.id: {"run_id": trace.run_id, "automation_id": automation_id} - for automation_id, traces in values.items() + trace.context.id: {"run_id": trace.run_id, "domain": key[0], "item_id": key[1]} + for key, traces in values.items() for trace in traces.values() } @@ -118,15 +119,16 @@ def websocket_automation_trace_contexts(hass, connection, msg): @websocket_api.require_admin @websocket_api.websocket_command( { - vol.Required("type"): "automation/debug/breakpoint/set", - vol.Required("automation_id"): str, + vol.Required("type"): "trace/debug/breakpoint/set", + vol.Required("domain"): vol.In(TRACE_DOMAINS), + vol.Required("item_id"): str, vol.Required("node"): str, vol.Optional("run_id"): str, } ) -def websocket_automation_breakpoint_set(hass, connection, msg): +def websocket_breakpoint_set(hass, connection, msg): """Set breakpoint.""" - automation_id = msg["automation_id"] + key = (msg["domain"], msg["item_id"]) node = msg["node"] run_id = msg.get("run_id") @@ -136,7 +138,7 @@ def websocket_automation_breakpoint_set(hass, connection, msg): ): raise HomeAssistantError("No breakpoint subscription") - result = breakpoint_set(hass, automation_id, run_id, node) + result = breakpoint_set(hass, key, run_id, node) connection.send_result(msg["id"], result) @@ -144,33 +146,32 @@ def websocket_automation_breakpoint_set(hass, connection, msg): @websocket_api.require_admin @websocket_api.websocket_command( { - vol.Required("type"): "automation/debug/breakpoint/clear", - vol.Required("automation_id"): str, + vol.Required("type"): "trace/debug/breakpoint/clear", + vol.Required("domain"): vol.In(TRACE_DOMAINS), + vol.Required("item_id"): str, vol.Required("node"): str, vol.Optional("run_id"): str, } ) -def websocket_automation_breakpoint_clear(hass, connection, msg): +def websocket_breakpoint_clear(hass, connection, msg): """Clear breakpoint.""" - automation_id = msg["automation_id"] + key = (msg["domain"], msg["item_id"]) node = msg["node"] run_id = msg.get("run_id") - result = breakpoint_clear(hass, automation_id, run_id, node) + result = breakpoint_clear(hass, key, run_id, node) connection.send_result(msg["id"], result) @callback @websocket_api.require_admin -@websocket_api.websocket_command( - {vol.Required("type"): "automation/debug/breakpoint/list"} -) -def websocket_automation_breakpoint_list(hass, connection, msg): +@websocket_api.websocket_command({vol.Required("type"): "trace/debug/breakpoint/list"}) +def websocket_breakpoint_list(hass, connection, msg): """List breakpoints.""" breakpoints = breakpoint_list(hass) for _breakpoint in breakpoints: - _breakpoint["automation_id"] = _breakpoint.pop("unique_id") + _breakpoint["domain"], _breakpoint["item_id"] = _breakpoint.pop("key") connection.send_result(msg["id"], breakpoints) @@ -178,19 +179,20 @@ def websocket_automation_breakpoint_list(hass, connection, msg): @callback @websocket_api.require_admin @websocket_api.websocket_command( - {vol.Required("type"): "automation/debug/breakpoint/subscribe"} + {vol.Required("type"): "trace/debug/breakpoint/subscribe"} ) def websocket_subscribe_breakpoint_events(hass, connection, msg): """Subscribe to breakpoint events.""" @callback - def breakpoint_hit(automation_id, run_id, node): + def breakpoint_hit(key, run_id, node): """Forward events to websocket.""" connection.send_message( websocket_api.event_message( msg["id"], { - "automation_id": automation_id, + "domain": key[0], + "item_id": key[1], "run_id": run_id, "node": node, }, @@ -221,17 +223,18 @@ def websocket_subscribe_breakpoint_events(hass, connection, msg): @websocket_api.require_admin @websocket_api.websocket_command( { - vol.Required("type"): "automation/debug/continue", - vol.Required("automation_id"): str, + vol.Required("type"): "trace/debug/continue", + vol.Required("domain"): vol.In(TRACE_DOMAINS), + vol.Required("item_id"): str, vol.Required("run_id"): str, } ) -def websocket_automation_debug_continue(hass, connection, msg): - """Resume execution of halted automation.""" - automation_id = msg["automation_id"] +def websocket_debug_continue(hass, connection, msg): + """Resume execution of halted automation or script.""" + key = (msg["domain"], msg["item_id"]) run_id = msg["run_id"] - result = debug_continue(hass, automation_id, run_id) + result = debug_continue(hass, key, run_id) connection.send_result(msg["id"], result) @@ -240,17 +243,18 @@ def websocket_automation_debug_continue(hass, connection, msg): @websocket_api.require_admin @websocket_api.websocket_command( { - vol.Required("type"): "automation/debug/step", - vol.Required("automation_id"): str, + vol.Required("type"): "trace/debug/step", + vol.Required("domain"): vol.In(TRACE_DOMAINS), + vol.Required("item_id"): str, vol.Required("run_id"): str, } ) -def websocket_automation_debug_step(hass, connection, msg): - """Single step a halted automation.""" - automation_id = msg["automation_id"] +def websocket_debug_step(hass, connection, msg): + """Single step a halted automation or script.""" + key = (msg["domain"], msg["item_id"]) run_id = msg["run_id"] - result = debug_step(hass, automation_id, run_id) + result = debug_step(hass, key, run_id) connection.send_result(msg["id"], result) @@ -259,16 +263,17 @@ def websocket_automation_debug_step(hass, connection, msg): @websocket_api.require_admin @websocket_api.websocket_command( { - vol.Required("type"): "automation/debug/stop", - vol.Required("automation_id"): str, + vol.Required("type"): "trace/debug/stop", + vol.Required("domain"): vol.In(TRACE_DOMAINS), + vol.Required("item_id"): str, vol.Required("run_id"): str, } ) -def websocket_automation_debug_stop(hass, connection, msg): - """Stop a halted automation.""" - automation_id = msg["automation_id"] +def websocket_debug_stop(hass, connection, msg): + """Stop a halted automation or script.""" + key = (msg["domain"], msg["item_id"]) run_id = msg["run_id"] - result = debug_stop(hass, automation_id, run_id) + result = debug_stop(hass, key, run_id) connection.send_result(msg["id"], result) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index d07d963c95d..3df5be09f13 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -143,26 +143,26 @@ async def trace_action(hass, script_run, stop, variables): trace_id = trace_id_get() if trace_id: - unique_id = trace_id[0] + key = trace_id[0] run_id = trace_id[1] breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS] - if unique_id in breakpoints and ( + if key in breakpoints and ( ( - run_id in breakpoints[unique_id] + run_id in breakpoints[key] and ( - path in breakpoints[unique_id][run_id] - or NODE_ANY in breakpoints[unique_id][run_id] + path in breakpoints[key][run_id] + or NODE_ANY in breakpoints[key][run_id] ) ) or ( - RUN_ID_ANY in breakpoints[unique_id] + RUN_ID_ANY in breakpoints[key] and ( - path in breakpoints[unique_id][RUN_ID_ANY] - or NODE_ANY in breakpoints[unique_id][RUN_ID_ANY] + path in breakpoints[key][RUN_ID_ANY] + or NODE_ANY in breakpoints[key][RUN_ID_ANY] ) ) ): - async_dispatcher_send(hass, SCRIPT_BREAKPOINT_HIT, unique_id, run_id, path) + async_dispatcher_send(hass, SCRIPT_BREAKPOINT_HIT, key, run_id, path) done = asyncio.Event() @@ -172,7 +172,7 @@ async def trace_action(hass, script_run, stop, variables): stop.set() done.set() - signal = SCRIPT_DEBUG_CONTINUE_STOP.format(unique_id, run_id) + signal = SCRIPT_DEBUG_CONTINUE_STOP.format(key, run_id) remove_signal1 = async_dispatcher_connect(hass, signal, async_continue_stop) remove_signal2 = async_dispatcher_connect( hass, SCRIPT_DEBUG_CONTINUE_ALL, async_continue_stop @@ -1281,13 +1281,13 @@ class Script: @callback -def breakpoint_clear(hass, unique_id, run_id, node): +def breakpoint_clear(hass, key, run_id, node): """Clear a breakpoint.""" run_id = run_id or RUN_ID_ANY breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS] - if unique_id not in breakpoints or run_id not in breakpoints[unique_id]: + if key not in breakpoints or run_id not in breakpoints[key]: return - breakpoints[unique_id][run_id].discard(node) + breakpoints[key][run_id].discard(node) @callback @@ -1297,15 +1297,15 @@ def breakpoint_clear_all(hass): @callback -def breakpoint_set(hass, unique_id, run_id, node): +def breakpoint_set(hass, key, run_id, node): """Set a breakpoint.""" run_id = run_id or RUN_ID_ANY breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS] - if unique_id not in breakpoints: - breakpoints[unique_id] = {} - if run_id not in breakpoints[unique_id]: - breakpoints[unique_id][run_id] = set() - breakpoints[unique_id][run_id].add(node) + if key not in breakpoints: + breakpoints[key] = {} + if run_id not in breakpoints[key]: + breakpoints[key][run_id] = set() + breakpoints[key][run_id].add(node) @callback @@ -1314,35 +1314,35 @@ def breakpoint_list(hass): breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS] return [ - {"unique_id": unique_id, "run_id": run_id, "node": node} - for unique_id in breakpoints - for run_id in breakpoints[unique_id] - for node in breakpoints[unique_id][run_id] + {"key": key, "run_id": run_id, "node": node} + for key in breakpoints + for run_id in breakpoints[key] + for node in breakpoints[key][run_id] ] @callback -def debug_continue(hass, unique_id, run_id): +def debug_continue(hass, key, run_id): """Continue execution of a halted script.""" # Clear any wildcard breakpoint - breakpoint_clear(hass, unique_id, run_id, NODE_ANY) + breakpoint_clear(hass, key, run_id, NODE_ANY) - signal = SCRIPT_DEBUG_CONTINUE_STOP.format(unique_id, run_id) + signal = SCRIPT_DEBUG_CONTINUE_STOP.format(key, run_id) async_dispatcher_send(hass, signal, "continue") @callback -def debug_step(hass, unique_id, run_id): +def debug_step(hass, key, run_id): """Single step a halted script.""" # Set a wildcard breakpoint - breakpoint_set(hass, unique_id, run_id, NODE_ANY) + breakpoint_set(hass, key, run_id, NODE_ANY) - signal = SCRIPT_DEBUG_CONTINUE_STOP.format(unique_id, run_id) + signal = SCRIPT_DEBUG_CONTINUE_STOP.format(key, run_id) async_dispatcher_send(hass, signal, "continue") @callback -def debug_stop(hass, unique_id, run_id): +def debug_stop(hass, key, run_id): """Stop execution of a running or halted script.""" - signal = SCRIPT_DEBUG_CONTINUE_STOP.format(unique_id, run_id) + signal = SCRIPT_DEBUG_CONTINUE_STOP.format(key, run_id) async_dispatcher_send(hass, signal, "stop") diff --git a/tests/components/trace/test_websocket_api.py b/tests/components/trace/test_websocket_api.py index 882d857c014..e07c042d1d0 100644 --- a/tests/components/trace/test_websocket_api.py +++ b/tests/components/trace/test_websocket_api.py @@ -2,25 +2,26 @@ from unittest.mock import patch from homeassistant.bootstrap import async_setup_component -from homeassistant.components import automation, config +from homeassistant.components import config +from homeassistant.components.trace.const import STORED_TRACES from homeassistant.core import Context from tests.common import assert_lists_same from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 -def _find_run_id(traces, automation_id): +def _find_run_id(traces, item_id): """Find newest run_id for an automation.""" for trace in reversed(traces): - if trace["automation_id"] == automation_id: + if trace["item_id"] == item_id: return trace["run_id"] return None -def _find_traces_for_automation(traces, automation_id): +def _find_traces_for_automation(traces, item_id): """Find traces for an automation.""" - return [trace for trace in traces if trace["automation_id"] == automation_id] + return [trace for trace in traces if trace["item_id"] == item_id] async def test_get_automation_trace(hass, hass_ws_client): @@ -85,7 +86,7 @@ async def test_get_automation_trace(hass, hass_ws_client): await hass.async_block_till_done() # List traces - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] run_id = _find_run_id(response["result"], "sun") @@ -94,8 +95,9 @@ async def test_get_automation_trace(hass, hass_ws_client): await client.send_json( { "id": next_id(), - "type": "automation/trace/get", - "automation_id": "sun", + "type": "trace/get", + "domain": "automation", + "item_id": "sun", "run_id": run_id, } ) @@ -113,11 +115,12 @@ async def test_get_automation_trace(hass, hass_ws_client): assert trace["error"] == "Unable to find service test.automation" assert trace["state"] == "stopped" assert trace["trigger"] == "event 'test_event'" - assert trace["unique_id"] == "sun" + assert trace["item_id"] == "sun" assert trace["variables"] contexts[trace["context"]["id"]] = { "run_id": trace["run_id"], - "automation_id": trace["automation_id"], + "domain": "automation", + "item_id": trace["item_id"], } # Trigger "moon" automation, with passing condition @@ -125,7 +128,7 @@ async def test_get_automation_trace(hass, hass_ws_client): await hass.async_block_till_done() # List traces - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] run_id = _find_run_id(response["result"], "moon") @@ -134,8 +137,9 @@ async def test_get_automation_trace(hass, hass_ws_client): await client.send_json( { "id": next_id(), - "type": "automation/trace/get", - "automation_id": "moon", + "type": "trace/get", + "domain": "automation", + "item_id": "moon", "run_id": run_id, } ) @@ -154,11 +158,12 @@ async def test_get_automation_trace(hass, hass_ws_client): assert "error" not in trace assert trace["state"] == "stopped" assert trace["trigger"] == "event 'test_event2'" - assert trace["unique_id"] == "moon" + assert trace["item_id"] == "moon" assert trace["variables"] contexts[trace["context"]["id"]] = { "run_id": trace["run_id"], - "automation_id": trace["automation_id"], + "domain": "automation", + "item_id": trace["item_id"], } # Trigger "moon" automation, with failing condition @@ -166,7 +171,7 @@ async def test_get_automation_trace(hass, hass_ws_client): await hass.async_block_till_done() # List traces - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] run_id = _find_run_id(response["result"], "moon") @@ -175,8 +180,9 @@ async def test_get_automation_trace(hass, hass_ws_client): await client.send_json( { "id": next_id(), - "type": "automation/trace/get", - "automation_id": "moon", + "type": "trace/get", + "domain": "automation", + "item_id": "moon", "run_id": run_id, } ) @@ -192,11 +198,12 @@ async def test_get_automation_trace(hass, hass_ws_client): assert "error" not in trace assert trace["state"] == "stopped" assert trace["trigger"] == "event 'test_event3'" - assert trace["unique_id"] == "moon" + assert trace["item_id"] == "moon" assert trace["variables"] contexts[trace["context"]["id"]] = { "run_id": trace["run_id"], - "automation_id": trace["automation_id"], + "domain": "automation", + "item_id": trace["item_id"], } # Trigger "moon" automation, with passing condition @@ -204,7 +211,7 @@ async def test_get_automation_trace(hass, hass_ws_client): await hass.async_block_till_done() # List traces - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] run_id = _find_run_id(response["result"], "moon") @@ -213,8 +220,9 @@ async def test_get_automation_trace(hass, hass_ws_client): await client.send_json( { "id": next_id(), - "type": "automation/trace/get", - "automation_id": "moon", + "type": "trace/get", + "domain": "automation", + "item_id": "moon", "run_id": run_id, } ) @@ -233,15 +241,16 @@ async def test_get_automation_trace(hass, hass_ws_client): assert "error" not in trace assert trace["state"] == "stopped" assert trace["trigger"] == "event 'test_event2'" - assert trace["unique_id"] == "moon" + assert trace["item_id"] == "moon" assert trace["variables"] contexts[trace["context"]["id"]] = { "run_id": trace["run_id"], - "automation_id": trace["automation_id"], + "domain": "automation", + "item_id": trace["item_id"], } # Check contexts - await client.send_json({"id": next_id(), "type": "automation/trace/contexts"}) + await client.send_json({"id": next_id(), "type": "trace/contexts"}) response = await client.receive_json() assert response["success"] assert response["result"] == contexts @@ -283,7 +292,7 @@ async def test_automation_trace_overflow(hass, hass_ws_client): client = await hass_ws_client() - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] assert response["result"] == [] @@ -294,7 +303,7 @@ async def test_automation_trace_overflow(hass, hass_ws_client): await hass.async_block_till_done() # List traces - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] assert len(_find_traces_for_automation(response["result"], "moon")) == 1 @@ -302,21 +311,18 @@ async def test_automation_trace_overflow(hass, hass_ws_client): assert len(_find_traces_for_automation(response["result"], "sun")) == 1 # Trigger "moon" automation enough times to overflow the number of stored traces - for _ in range(automation.trace.STORED_TRACES): + for _ in range(STORED_TRACES): hass.bus.async_fire("test_event2") await hass.async_block_till_done() - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] moon_traces = _find_traces_for_automation(response["result"], "moon") - assert len(moon_traces) == automation.trace.STORED_TRACES + assert len(moon_traces) == STORED_TRACES assert moon_traces[0] assert int(moon_traces[0]["run_id"]) == int(moon_run_id) + 1 - assert ( - int(moon_traces[-1]["run_id"]) - == int(moon_run_id) + automation.trace.STORED_TRACES - ) + assert int(moon_traces[-1]["run_id"]) == int(moon_run_id) + STORED_TRACES assert len(_find_traces_for_automation(response["result"], "sun")) == 1 @@ -363,13 +369,18 @@ async def test_list_automation_traces(hass, hass_ws_client): client = await hass_ws_client() - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] assert response["result"] == [] await client.send_json( - {"id": next_id(), "type": "automation/trace/list", "automation_id": "sun"} + { + "id": next_id(), + "type": "trace/list", + "domain": "automation", + "item_id": "sun", + } ) response = await client.receive_json() assert response["success"] @@ -380,14 +391,19 @@ async def test_list_automation_traces(hass, hass_ws_client): await hass.async_block_till_done() # Get trace - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] assert len(response["result"]) == 1 assert len(_find_traces_for_automation(response["result"], "sun")) == 1 await client.send_json( - {"id": next_id(), "type": "automation/trace/list", "automation_id": "sun"} + { + "id": next_id(), + "type": "trace/list", + "domain": "automation", + "item_id": "sun", + } ) response = await client.receive_json() assert response["success"] @@ -395,7 +411,12 @@ async def test_list_automation_traces(hass, hass_ws_client): assert len(_find_traces_for_automation(response["result"], "sun")) == 1 await client.send_json( - {"id": next_id(), "type": "automation/trace/list", "automation_id": "moon"} + { + "id": next_id(), + "type": "trace/list", + "domain": "automation", + "item_id": "moon", + } ) response = await client.receive_json() assert response["success"] @@ -414,7 +435,7 @@ async def test_list_automation_traces(hass, hass_ws_client): await hass.async_block_till_done() # Get trace - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] assert len(_find_traces_for_automation(response["result"], "moon")) == 3 @@ -426,7 +447,7 @@ async def test_list_automation_traces(hass, hass_ws_client): assert trace["state"] == "stopped" assert trace["timestamp"] assert trace["trigger"] == "event 'test_event'" - assert trace["unique_id"] == "sun" + assert trace["item_id"] == "sun" trace = _find_traces_for_automation(response["result"], "moon")[0] assert trace["last_action"] == "action/0" @@ -435,7 +456,7 @@ async def test_list_automation_traces(hass, hass_ws_client): assert trace["state"] == "stopped" assert trace["timestamp"] assert trace["trigger"] == "event 'test_event2'" - assert trace["unique_id"] == "moon" + assert trace["item_id"] == "moon" trace = _find_traces_for_automation(response["result"], "moon")[1] assert trace["last_action"] is None @@ -444,7 +465,7 @@ async def test_list_automation_traces(hass, hass_ws_client): assert trace["state"] == "stopped" assert trace["timestamp"] assert trace["trigger"] == "event 'test_event3'" - assert trace["unique_id"] == "moon" + assert trace["item_id"] == "moon" trace = _find_traces_for_automation(response["result"], "moon")[2] assert trace["last_action"] == "action/0" @@ -453,7 +474,7 @@ async def test_list_automation_traces(hass, hass_ws_client): assert trace["state"] == "stopped" assert trace["timestamp"] assert trace["trigger"] == "event 'test_event2'" - assert trace["unique_id"] == "moon" + assert trace["item_id"] == "moon" async def test_automation_breakpoints(hass, hass_ws_client): @@ -465,11 +486,11 @@ async def test_automation_breakpoints(hass, hass_ws_client): id += 1 return id - async def assert_last_action(automation_id, expected_action, expected_state): - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + async def assert_last_action(item_id, expected_action, expected_state): + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - trace = _find_traces_for_automation(response["result"], automation_id)[-1] + trace = _find_traces_for_automation(response["result"], item_id)[-1] assert trace["last_action"] == expected_action assert trace["state"] == expected_state return trace["run_id"] @@ -508,24 +529,23 @@ async def test_automation_breakpoints(hass, hass_ws_client): await client.send_json( { "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", + "type": "trace/debug/breakpoint/set", + "domain": "automation", + "item_id": "sun", "node": "1", } ) response = await client.receive_json() assert not response["success"] - await client.send_json( - {"id": next_id(), "type": "automation/debug/breakpoint/list"} - ) + await client.send_json({"id": next_id(), "type": "trace/debug/breakpoint/list"}) response = await client.receive_json() assert response["success"] assert response["result"] == [] subscription_id = next_id() await client.send_json( - {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} + {"id": subscription_id, "type": "trace/debug/breakpoint/subscribe"} ) response = await client.receive_json() assert response["success"] @@ -533,8 +553,9 @@ async def test_automation_breakpoints(hass, hass_ws_client): await client.send_json( { "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", + "type": "trace/debug/breakpoint/set", + "domain": "automation", + "item_id": "sun", "node": "action/1", } ) @@ -543,24 +564,33 @@ async def test_automation_breakpoints(hass, hass_ws_client): await client.send_json( { "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", + "type": "trace/debug/breakpoint/set", + "domain": "automation", + "item_id": "sun", "node": "action/5", } ) response = await client.receive_json() assert response["success"] - await client.send_json( - {"id": next_id(), "type": "automation/debug/breakpoint/list"} - ) + await client.send_json({"id": next_id(), "type": "trace/debug/breakpoint/list"}) response = await client.receive_json() assert response["success"] assert_lists_same( response["result"], [ - {"node": "action/1", "run_id": "*", "automation_id": "sun"}, - {"node": "action/5", "run_id": "*", "automation_id": "sun"}, + { + "node": "action/1", + "run_id": "*", + "domain": "automation", + "item_id": "sun", + }, + { + "node": "action/5", + "run_id": "*", + "domain": "automation", + "item_id": "sun", + }, ], ) @@ -570,7 +600,8 @@ async def test_automation_breakpoints(hass, hass_ws_client): response = await client.receive_json() run_id = await assert_last_action("sun", "action/1", "running") assert response["event"] == { - "automation_id": "sun", + "domain": "automation", + "item_id": "sun", "node": "action/1", "run_id": run_id, } @@ -578,8 +609,9 @@ async def test_automation_breakpoints(hass, hass_ws_client): await client.send_json( { "id": next_id(), - "type": "automation/debug/step", - "automation_id": "sun", + "type": "trace/debug/step", + "domain": "automation", + "item_id": "sun", "run_id": run_id, } ) @@ -589,7 +621,8 @@ async def test_automation_breakpoints(hass, hass_ws_client): response = await client.receive_json() run_id = await assert_last_action("sun", "action/2", "running") assert response["event"] == { - "automation_id": "sun", + "domain": "automation", + "item_id": "sun", "node": "action/2", "run_id": run_id, } @@ -597,8 +630,9 @@ async def test_automation_breakpoints(hass, hass_ws_client): await client.send_json( { "id": next_id(), - "type": "automation/debug/continue", - "automation_id": "sun", + "type": "trace/debug/continue", + "domain": "automation", + "item_id": "sun", "run_id": run_id, } ) @@ -608,7 +642,8 @@ async def test_automation_breakpoints(hass, hass_ws_client): response = await client.receive_json() run_id = await assert_last_action("sun", "action/5", "running") assert response["event"] == { - "automation_id": "sun", + "domain": "automation", + "item_id": "sun", "node": "action/5", "run_id": run_id, } @@ -616,8 +651,9 @@ async def test_automation_breakpoints(hass, hass_ws_client): await client.send_json( { "id": next_id(), - "type": "automation/debug/stop", - "automation_id": "sun", + "type": "trace/debug/stop", + "domain": "automation", + "item_id": "sun", "run_id": run_id, } ) @@ -636,11 +672,11 @@ async def test_automation_breakpoints_2(hass, hass_ws_client): id += 1 return id - async def assert_last_action(automation_id, expected_action, expected_state): - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + async def assert_last_action(item_id, expected_action, expected_state): + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - trace = _find_traces_for_automation(response["result"], automation_id)[-1] + trace = _find_traces_for_automation(response["result"], item_id)[-1] assert trace["last_action"] == expected_action assert trace["state"] == expected_state return trace["run_id"] @@ -678,7 +714,7 @@ async def test_automation_breakpoints_2(hass, hass_ws_client): subscription_id = next_id() await client.send_json( - {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} + {"id": subscription_id, "type": "trace/debug/breakpoint/subscribe"} ) response = await client.receive_json() assert response["success"] @@ -686,8 +722,9 @@ async def test_automation_breakpoints_2(hass, hass_ws_client): await client.send_json( { "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", + "type": "trace/debug/breakpoint/set", + "domain": "automation", + "item_id": "sun", "node": "action/1", } ) @@ -700,7 +737,8 @@ async def test_automation_breakpoints_2(hass, hass_ws_client): response = await client.receive_json() run_id = await assert_last_action("sun", "action/1", "running") assert response["event"] == { - "automation_id": "sun", + "domain": "automation", + "item_id": "sun", "node": "action/1", "run_id": run_id, } @@ -718,8 +756,9 @@ async def test_automation_breakpoints_2(hass, hass_ws_client): await client.send_json( { "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", + "type": "trace/debug/breakpoint/set", + "domain": "automation", + "item_id": "sun", "node": "1", } ) @@ -743,11 +782,11 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): id += 1 return id - async def assert_last_action(automation_id, expected_action, expected_state): - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + async def assert_last_action(item_id, expected_action, expected_state): + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - trace = _find_traces_for_automation(response["result"], automation_id)[-1] + trace = _find_traces_for_automation(response["result"], item_id)[-1] assert trace["last_action"] == expected_action assert trace["state"] == expected_state return trace["run_id"] @@ -785,7 +824,7 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): subscription_id = next_id() await client.send_json( - {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} + {"id": subscription_id, "type": "trace/debug/breakpoint/subscribe"} ) response = await client.receive_json() assert response["success"] @@ -793,8 +832,9 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): await client.send_json( { "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", + "type": "trace/debug/breakpoint/set", + "domain": "automation", + "item_id": "sun", "node": "action/1", } ) @@ -804,8 +844,9 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): await client.send_json( { "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", + "type": "trace/debug/breakpoint/set", + "domain": "automation", + "item_id": "sun", "node": "action/5", } ) @@ -818,7 +859,8 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): response = await client.receive_json() run_id = await assert_last_action("sun", "action/1", "running") assert response["event"] == { - "automation_id": "sun", + "domain": "automation", + "item_id": "sun", "node": "action/1", "run_id": run_id, } @@ -826,8 +868,9 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): await client.send_json( { "id": next_id(), - "type": "automation/debug/continue", - "automation_id": "sun", + "type": "trace/debug/continue", + "domain": "automation", + "item_id": "sun", "run_id": run_id, } ) @@ -837,7 +880,8 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): response = await client.receive_json() run_id = await assert_last_action("sun", "action/5", "running") assert response["event"] == { - "automation_id": "sun", + "domain": "automation", + "item_id": "sun", "node": "action/5", "run_id": run_id, } @@ -845,8 +889,9 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): await client.send_json( { "id": next_id(), - "type": "automation/debug/stop", - "automation_id": "sun", + "type": "trace/debug/stop", + "domain": "automation", + "item_id": "sun", "run_id": run_id, } ) @@ -859,8 +904,9 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): await client.send_json( { "id": next_id(), - "type": "automation/debug/breakpoint/clear", - "automation_id": "sun", + "type": "trace/debug/breakpoint/clear", + "domain": "automation", + "item_id": "sun", "node": "action/1", } ) @@ -873,7 +919,8 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): response = await client.receive_json() run_id = await assert_last_action("sun", "action/5", "running") assert response["event"] == { - "automation_id": "sun", + "domain": "automation", + "item_id": "sun", "node": "action/5", "run_id": run_id, } From b1d0b37d2c575a440686c9bd177862a723deea68 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 23 Mar 2021 23:04:32 +0100 Subject: [PATCH 578/831] Google assistant: disconnect user agent when not found in google (#48233) --- .../components/cloud/google_config.py | 31 ++++++-------- .../components/google_assistant/helpers.py | 18 ++++++++- .../components/google_assistant/http.py | 7 ++-- tests/components/cloud/test_google_config.py | 40 ++++++++++++++----- 4 files changed, 62 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index dffa1e2f306..26cb830e8c6 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -6,11 +6,7 @@ from hass_nabucasa import Cloud, cloud_api from hass_nabucasa.google_report_state import ErrorResponse from homeassistant.components.google_assistant.helpers import AbstractConfig -from homeassistant.const import ( - CLOUD_NEVER_EXPOSED_ENTITIES, - EVENT_HOMEASSISTANT_STARTED, - HTTP_OK, -) +from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES, HTTP_OK from homeassistant.core import CoreState, split_entity_id from homeassistant.helpers import entity_registry @@ -87,8 +83,15 @@ class CloudGoogleConfig(AbstractConfig): async def async_initialize(self): """Perform async initialization of config.""" await super().async_initialize() - # Remove bad data that was there until 0.103.6 - Jan 6, 2020 - self._store.pop_agent_user_id(self._user) + + # Remove old/wrong user agent ids + remove_agent_user_ids = [] + for agent_user_id in self._store.agent_user_ids: + if agent_user_id != self.agent_user_id: + remove_agent_user_ids.append(agent_user_id) + + for agent_user_id in remove_agent_user_ids: + await self.async_disconnect_agent_user(agent_user_id) self._prefs.async_listen_updates(self._async_prefs_updated) @@ -198,17 +201,7 @@ class CloudGoogleConfig(AbstractConfig): if not self._should_expose_entity_id(entity_id): return - if self.hass.state == CoreState.running: - self.async_schedule_google_sync_all() + if self.hass.state != CoreState.running: return - if self._sync_on_started: - return - - self._sync_on_started = True - - async def sync_google(_): - """Sync entities to Google.""" - await self.async_sync_entities_all() - - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, sync_google) + self.async_schedule_google_sync_all() diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py index 9b133ad6a30..f248202e5f0 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -15,9 +15,10 @@ from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, CLOUD_NEVER_EXPOSED_ENTITIES, CONF_NAME, + EVENT_HOMEASSISTANT_STARTED, STATE_UNAVAILABLE, ) -from homeassistant.core import Context, HomeAssistant, State, callback +from homeassistant.core import Context, CoreState, HomeAssistant, State, callback from homeassistant.helpers.area_registry import AreaEntry from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.entity_registry import RegistryEntry @@ -104,6 +105,16 @@ class AbstractConfig(ABC): self._store = GoogleConfigStore(self.hass) await self._store.async_load() + if self.hass.state == CoreState.running: + await self.async_sync_entities_all() + return + + async def sync_google(_): + """Sync entities to Google.""" + await self.async_sync_entities_all() + + self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, sync_google) + @property def enabled(self): """Return if Google is enabled.""" @@ -193,7 +204,10 @@ class AbstractConfig(ABC): """Sync all entities to Google.""" # Remove any pending sync self._google_sync_unsub.pop(agent_user_id, lambda: None)() - return await self._async_request_sync_devices(agent_user_id) + status = await self._async_request_sync_devices(agent_user_id) + if status == 404: + await self.async_disconnect_agent_user(agent_user_id) + return status async def async_sync_entities_all(self): """Sync all entities to Google for all registered agents.""" diff --git a/homeassistant/components/google_assistant/http.py b/homeassistant/components/google_assistant/http.py index 5cf1cb14379..3787a63a514 100644 --- a/homeassistant/components/google_assistant/http.py +++ b/homeassistant/components/google_assistant/http.py @@ -135,11 +135,12 @@ class GoogleConfig(AbstractConfig): async def _async_request_sync_devices(self, agent_user_id: str): if CONF_SERVICE_ACCOUNT in self._config: - await self.async_call_homegraph_api( + return await self.async_call_homegraph_api( REQUEST_SYNC_BASE_URL, {"agentUserId": agent_user_id} ) - else: - _LOGGER.error("No configuration for request_sync available") + + _LOGGER.error("No configuration for request_sync available") + return HTTP_INTERNAL_SERVER_ERROR async def _async_update_token(self, force=False): if CONF_SERVICE_ACCOUNT not in self._config: diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index e1da6bbe0a8..5f29a41c6e0 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -1,5 +1,5 @@ """Test the Cloud Google Config.""" -from unittest.mock import AsyncMock, Mock, patch +from unittest.mock import Mock, patch import pytest @@ -41,21 +41,19 @@ async def test_google_update_report_state(mock_conf, hass, cloud_prefs): assert len(mock_report_state.mock_calls) == 1 -async def test_sync_entities(aioclient_mock, hass, cloud_prefs): +async def test_sync_entities(mock_conf, hass, cloud_prefs): """Test sync devices.""" - config = CloudGoogleConfig( - hass, - GACTIONS_SCHEMA({}), - "mock-user-id", - cloud_prefs, - Mock(auth=Mock(async_check_token=AsyncMock())), - ) + await mock_conf.async_initialize() + await mock_conf.async_connect_agent_user("mock-user-id") + + assert len(mock_conf._store.agent_user_ids) == 1 with patch( "hass_nabucasa.cloud_api.async_google_actions_request_sync", return_value=Mock(status=HTTP_NOT_FOUND), ) as mock_request_sync: - assert await config.async_sync_entities("user") == HTTP_NOT_FOUND + assert await mock_conf.async_sync_entities("mock-user-id") == HTTP_NOT_FOUND + assert len(mock_conf._store.agent_user_ids) == 0 assert len(mock_request_sync.mock_calls) == 1 @@ -165,7 +163,29 @@ async def test_google_entity_registry_sync(hass, mock_cloud_login, cloud_prefs): assert len(mock_sync.mock_calls) == 3 + +async def test_sync_google_when_started(hass, mock_cloud_login, cloud_prefs): + """Test Google config syncs on init.""" + config = CloudGoogleConfig( + hass, GACTIONS_SCHEMA({}), "mock-user-id", cloud_prefs, hass.data["cloud"] + ) with patch.object(config, "async_sync_entities_all") as mock_sync: + await config.async_initialize() + await config.async_connect_agent_user("mock-user-id") + assert len(mock_sync.mock_calls) == 1 + + +async def test_sync_google_on_home_assistant_start(hass, mock_cloud_login, cloud_prefs): + """Test Google config syncs when home assistant started.""" + config = CloudGoogleConfig( + hass, GACTIONS_SCHEMA({}), "mock-user-id", cloud_prefs, hass.data["cloud"] + ) + hass.state = CoreState.starting + with patch.object(config, "async_sync_entities_all") as mock_sync: + await config.async_initialize() + await config.async_connect_agent_user("mock-user-id") + assert len(mock_sync.mock_calls) == 0 + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert len(mock_sync.mock_calls) == 1 From c4e5af8081d690718255315b8c9f8f36f5598b65 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Wed, 24 Mar 2021 00:03:09 +0000 Subject: [PATCH 579/831] [ci skip] Translation update --- .../components/hyperion/translations/ko.json | 1 + .../components/hyperion/translations/no.json | 1 + .../components/hyperion/translations/zh-Hant.json | 1 + .../islamic_prayer_times/translations/de.json | 15 +++++++++++++++ .../components/shelly/translations/ko.json | 2 +- 5 files changed, 19 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/hyperion/translations/ko.json b/homeassistant/components/hyperion/translations/ko.json index 94dc9d48a58..f42450675fb 100644 --- a/homeassistant/components/hyperion/translations/ko.json +++ b/homeassistant/components/hyperion/translations/ko.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "\ucd9c\ub825\ud560 Hyperion \ud6a8\uacfc", "priority": "\uc0c9\uc0c1 \ubc0f \ud6a8\uacfc\uc5d0 \uc0ac\uc6a9\ud560 Hyperion \uc6b0\uc120 \uc21c\uc704" } } diff --git a/homeassistant/components/hyperion/translations/no.json b/homeassistant/components/hyperion/translations/no.json index e411982b58a..8fed4ee2437 100644 --- a/homeassistant/components/hyperion/translations/no.json +++ b/homeassistant/components/hyperion/translations/no.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "Hyperion-effekter \u00e5 vise", "priority": "Hyperion-prioritet for bruke til farger og effekter" } } diff --git a/homeassistant/components/hyperion/translations/zh-Hant.json b/homeassistant/components/hyperion/translations/zh-Hant.json index bb8eacd5376..d9757ccc22a 100644 --- a/homeassistant/components/hyperion/translations/zh-Hant.json +++ b/homeassistant/components/hyperion/translations/zh-Hant.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "\u986f\u793a Hyperion \u6548\u61c9", "priority": "Hyperion \u512a\u5148\u4f7f\u7528\u4e4b\u8272\u6eab\u8207\u7279\u6548" } } diff --git a/homeassistant/components/islamic_prayer_times/translations/de.json b/homeassistant/components/islamic_prayer_times/translations/de.json index b06137bdb0e..b8097e9bd39 100644 --- a/homeassistant/components/islamic_prayer_times/translations/de.json +++ b/homeassistant/components/islamic_prayer_times/translations/de.json @@ -2,6 +2,21 @@ "config": { "abort": { "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "step": { + "user": { + "description": "M\u00f6chtest du islamische Gebetszeiten einrichten?", + "title": "Islamische Gebetszeiten einrichten" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "calculation_method": "Gebetsberechnungsmethode" + } + } } }, "title": "Islamische Gebetszeiten" diff --git a/homeassistant/components/shelly/translations/ko.json b/homeassistant/components/shelly/translations/ko.json index d422b6de86b..d4af4248578 100644 --- a/homeassistant/components/shelly/translations/ko.json +++ b/homeassistant/components/shelly/translations/ko.json @@ -12,7 +12,7 @@ "flow_title": "{name}", "step": { "confirm_discovery": { - "description": "{host}\uc5d0\uc11c {model}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\n\n\ube44\ubc00\ubc88\ud638\ub85c \ubcf4\ud638\ub41c \ubc30\ud130\ub9ac \uad6c\ub3d9 \uae30\uae30\ub294 \uc124\uc815\ud558\uae30 \uc804\uc5d0 \uc808\uc804 \ubaa8\ub4dc\ub97c \ud574\uc81c\ud574\uc57c \ud569\ub2c8\ub2e4.\n\ube44\ubc00\ubc88\ud638\ub85c \ubcf4\ud638\ub418\uc9c0 \uc54a\ub294 \ubc30\ud130\ub9ac \uad6c\ub3d9 \uae30\uae30\ub294 \uae30\uae30\uc758 \uc808\uc804 \ubaa8\ub4dc\uac00 \ud574\uc81c\ub420 \ub54c \ucd94\uac00\ub418\uba70, \uae30\uae30\uc758 \ubc84\ud2bc\uc744 \uc0ac\uc6a9\ud558\uc5ec \uc218\ub3d9\uc73c\ub85c \uae30\uae30\ub97c \uc808\uc804 \ud574\uc81c\uc2dc\ud0a4\uac70\ub098 \uae30\uae30\uc5d0\uc11c \ub2e4\uc74c \ub370\uc774\ud130\ub97c \uc5c5\ub370\uc774\ud2b8\ud560 \ub54c\uae4c\uc9c0 \uae30\ub2e4\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "description": "{host}\uc5d0\uc11c {model}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\n\n\ube44\ubc00\ubc88\ud638\ub85c \ubcf4\ud638\ub41c \ubc30\ud130\ub9ac \ubc29\uc2dd \uae30\uae30\ub294 \uc124\uc815\ud558\uae30 \uc804\uc5d0 \uc808\uc804 \ubaa8\ub4dc\ub97c \ud574\uc81c\ud574\uc57c \ud569\ub2c8\ub2e4.\n\ube44\ubc00\ubc88\ud638\ub85c \ubcf4\ud638\ub418\uc9c0 \uc54a\ub294 \ubc30\ud130\ub9ac \ubc29\uc2dd \uae30\uae30\ub294 \uae30\uae30\uc758 \uc808\uc804 \ubaa8\ub4dc\uac00 \ud574\uc81c\ub420 \ub54c \ucd94\uac00\ub418\uba70, \uae30\uae30\uc758 \ubc84\ud2bc\uc744 \uc0ac\uc6a9\ud558\uc5ec \uc218\ub3d9\uc73c\ub85c \uae30\uae30\ub97c \uc808\uc804 \ud574\uc81c\uc2dc\ud0a4\uac70\ub098 \uae30\uae30\uc5d0\uc11c \ub2e4\uc74c \ub370\uc774\ud130\ub97c \uc5c5\ub370\uc774\ud2b8\ud560 \ub54c\uae4c\uc9c0 \uae30\ub2e4\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "credentials": { "data": { From b58dd7d0477f1c076ec796a296967f0e44c5ed06 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 24 Mar 2021 00:23:29 +0000 Subject: [PATCH 580/831] Bump frontend to 20210324.0 --- 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 ed97d0718e7..215cf65291f 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20210316.0" + "home-assistant-frontend==20210324.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index a8ad4ff85f9..8502fa2c22d 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.42.0 -home-assistant-frontend==20210316.0 +home-assistant-frontend==20210324.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 67c0988bf0d..56459895f1d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210316.0 +home-assistant-frontend==20210324.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 115f77c35dc..f9d3520dcf6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210316.0 +home-assistant-frontend==20210324.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 3dec394cad4024c17566b0e93fc557bf86c12d93 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 23 Mar 2021 20:35:15 -0700 Subject: [PATCH 581/831] Migrate template to register reload service on async_setup (#48273) --- homeassistant/components/template/__init__.py | 21 +- .../template/alarm_control_panel.py | 3 - .../components/template/binary_sensor.py | 4 +- homeassistant/components/template/const.py | 2 - homeassistant/components/template/cover.py | 4 +- homeassistant/components/template/fan.py | 4 +- homeassistant/components/template/light.py | 4 +- homeassistant/components/template/lock.py | 4 +- homeassistant/components/template/sensor.py | 4 +- homeassistant/components/template/switch.py | 4 +- homeassistant/components/template/vacuum.py | 4 +- homeassistant/components/template/weather.py | 3 - .../components/template/test_binary_sensor.py | 242 +++++++++--------- tests/components/template/test_init.py | 3 +- 14 files changed, 132 insertions(+), 174 deletions(-) diff --git a/homeassistant/components/template/__init__.py b/homeassistant/components/template/__init__.py index cc8862afcf4..6292cd40fec 100644 --- a/homeassistant/components/template/__init__.py +++ b/homeassistant/components/template/__init__.py @@ -1,20 +1,11 @@ """The template component.""" -from homeassistant.const import SERVICE_RELOAD -from homeassistant.helpers.reload import async_reload_integration_platforms +from homeassistant.helpers.reload import async_setup_reload_service -from .const import DOMAIN, EVENT_TEMPLATE_RELOADED, PLATFORMS +from .const import DOMAIN, PLATFORMS -async def async_setup_reload_service(hass): - """Create the reload service for the template domain.""" - if hass.services.has_service(DOMAIN, SERVICE_RELOAD): - return +async def async_setup(hass, config): + """Set up the template integration.""" + await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - async def _reload_config(call): - """Reload the template platform config.""" - await async_reload_integration_platforms(hass, DOMAIN, PLATFORMS) - hass.bus.async_fire(EVENT_TEMPLATE_RELOADED, context=call.context) - - hass.helpers.service.async_register_admin_service( - DOMAIN, SERVICE_RELOAD, _reload_config - ) + return True diff --git a/homeassistant/components/template/alarm_control_panel.py b/homeassistant/components/template/alarm_control_panel.py index f56c5b27572..4c72c5094ef 100644 --- a/homeassistant/components/template/alarm_control_panel.py +++ b/homeassistant/components/template/alarm_control_panel.py @@ -32,10 +32,8 @@ from homeassistant.core import callback from homeassistant.exceptions import TemplateError 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 .const import DOMAIN, PLATFORMS from .template_entity import TemplateEntity _LOGGER = logging.getLogger(__name__) @@ -113,7 +111,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Template Alarm Control Panels.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/binary_sensor.py b/homeassistant/components/template/binary_sensor.py index b810c7faee1..1088652cd0a 100644 --- a/homeassistant/components/template/binary_sensor.py +++ b/homeassistant/components/template/binary_sensor.py @@ -22,10 +22,9 @@ from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.event import async_call_later -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.template import result_as_boolean -from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS +from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity CONF_DELAY_ON = "delay_on" @@ -97,7 +96,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template binary sensors.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/const.py b/homeassistant/components/template/const.py index 5b38f19eaeb..5d6bf6391df 100644 --- a/homeassistant/components/template/const.py +++ b/homeassistant/components/template/const.py @@ -6,8 +6,6 @@ DOMAIN = "template" PLATFORM_STORAGE_KEY = "template_platforms" -EVENT_TEMPLATE_RELOADED = "event_template_reloaded" - PLATFORMS = [ "alarm_control_panel", "binary_sensor", diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index 278cd1c80bb..cd552a33e5d 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -38,10 +38,9 @@ from homeassistant.core import callback from homeassistant.exceptions import TemplateError 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 .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS +from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity _LOGGER = logging.getLogger(__name__) @@ -160,7 +159,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Template cover.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index 51dce0f8d56..87b063583bf 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -36,10 +36,9 @@ from homeassistant.core import callback from homeassistant.exceptions import TemplateError 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 .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS +from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity _LOGGER = logging.getLogger(__name__) @@ -163,7 +162,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template fans.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index 0edaacbb5ca..e76ba42289b 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -31,10 +31,9 @@ from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv 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 .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS +from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity _LOGGER = logging.getLogger(__name__) @@ -137,7 +136,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template lights.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/lock.py b/homeassistant/components/template/lock.py index 692f06e28fe..c4a3977a4db 100644 --- a/homeassistant/components/template/lock.py +++ b/homeassistant/components/template/lock.py @@ -13,10 +13,9 @@ from homeassistant.const import ( from homeassistant.core import callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.script import Script -from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS +from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity CONF_LOCK = "lock" @@ -60,7 +59,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template lock.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index b587fe3bd82..9a63302044a 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -25,9 +25,8 @@ from homeassistant.core import callback from homeassistant.exceptions import TemplateError 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 .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS +from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity CONF_ATTRIBUTE_TEMPLATES = "attribute_templates" @@ -96,7 +95,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template sensors.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index 412c4507d1f..0e083df13f4 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -22,11 +22,10 @@ from homeassistant.core import callback from homeassistant.exceptions import TemplateError 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.restore_state import RestoreEntity from homeassistant.helpers.script import Script -from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS +from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity _VALID_STATES = [STATE_ON, STATE_OFF, "true", "false"] @@ -90,7 +89,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template switches.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index 171aeb7af92..ed7919d174e 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -41,10 +41,9 @@ from homeassistant.core import callback from homeassistant.exceptions import TemplateError 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 .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS +from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity _LOGGER = logging.getLogger(__name__) @@ -147,7 +146,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template vacuums.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/weather.py b/homeassistant/components/template/weather.py index 0db94520afe..27980febb56 100644 --- a/homeassistant/components/template/weather.py +++ b/homeassistant/components/template/weather.py @@ -24,9 +24,7 @@ from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID import homeassistant.helpers.config_validation as cv 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 .const import DOMAIN, PLATFORMS from .template_entity import TemplateEntity CONDITION_CLASSES = { @@ -71,7 +69,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Template weather.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) name = config[CONF_NAME] condition_template = config[CONF_CONDITION_TEMPLATE] diff --git a/tests/components/template/test_binary_sensor.py b/tests/components/template/test_binary_sensor.py index 241ac88328e..76602b39433 100644 --- a/tests/components/template/test_binary_sensor.py +++ b/tests/components/template/test_binary_sensor.py @@ -15,7 +15,7 @@ from homeassistant.const import ( from homeassistant.core import CoreState import homeassistant.util.dt as dt_util -from tests.common import assert_setup_component, async_fire_time_changed +from tests.common import async_fire_time_changed async def test_setup(hass): @@ -32,85 +32,79 @@ async def test_setup(hass): }, } } - with assert_setup_component(1): - assert await setup.async_setup_component(hass, binary_sensor.DOMAIN, config) + assert await setup.async_setup_component(hass, binary_sensor.DOMAIN, config) async def test_setup_no_sensors(hass): """Test setup with no sensors.""" - with assert_setup_component(0): - assert await setup.async_setup_component( - hass, binary_sensor.DOMAIN, {"binary_sensor": {"platform": "template"}} - ) + assert await setup.async_setup_component( + hass, binary_sensor.DOMAIN, {"binary_sensor": {"platform": "template"}} + ) async def test_setup_invalid_device(hass): """Test the setup with invalid devices.""" - with assert_setup_component(0): - assert await setup.async_setup_component( - hass, - binary_sensor.DOMAIN, - {"binary_sensor": {"platform": "template", "sensors": {"foo bar": {}}}}, - ) + assert await setup.async_setup_component( + hass, + binary_sensor.DOMAIN, + {"binary_sensor": {"platform": "template", "sensors": {"foo bar": {}}}}, + ) async def test_setup_invalid_device_class(hass): """Test setup with invalid sensor class.""" - with assert_setup_component(0): - assert await setup.async_setup_component( - hass, - binary_sensor.DOMAIN, - { - "binary_sensor": { - "platform": "template", - "sensors": { - "test": { - "value_template": "{{ foo }}", - "device_class": "foobarnotreal", - } - }, - } - }, - ) + assert await setup.async_setup_component( + hass, + binary_sensor.DOMAIN, + { + "binary_sensor": { + "platform": "template", + "sensors": { + "test": { + "value_template": "{{ foo }}", + "device_class": "foobarnotreal", + } + }, + } + }, + ) async def test_setup_invalid_missing_template(hass): """Test setup with invalid and missing template.""" - with assert_setup_component(0): - assert await setup.async_setup_component( - hass, - binary_sensor.DOMAIN, - { - "binary_sensor": { - "platform": "template", - "sensors": {"test": {"device_class": "motion"}}, - } - }, - ) + assert await setup.async_setup_component( + hass, + binary_sensor.DOMAIN, + { + "binary_sensor": { + "platform": "template", + "sensors": {"test": {"device_class": "motion"}}, + } + }, + ) async def test_icon_template(hass): """Test icon template.""" - with assert_setup_component(1): - assert await setup.async_setup_component( - hass, - binary_sensor.DOMAIN, - { - "binary_sensor": { - "platform": "template", - "sensors": { - "test_template_sensor": { - "value_template": "{{ states.sensor.xyz.state }}", - "icon_template": "{% if " - "states.binary_sensor.test_state.state == " - "'Works' %}" - "mdi:check" - "{% endif %}", - } - }, - } - }, - ) + assert await setup.async_setup_component( + hass, + binary_sensor.DOMAIN, + { + "binary_sensor": { + "platform": "template", + "sensors": { + "test_template_sensor": { + "value_template": "{{ states.sensor.xyz.state }}", + "icon_template": "{% if " + "states.binary_sensor.test_state.state == " + "'Works' %}" + "mdi:check" + "{% endif %}", + } + }, + } + }, + ) await hass.async_block_till_done() await hass.async_start() @@ -127,26 +121,25 @@ async def test_icon_template(hass): async def test_entity_picture_template(hass): """Test entity_picture template.""" - with assert_setup_component(1): - assert await setup.async_setup_component( - hass, - binary_sensor.DOMAIN, - { - "binary_sensor": { - "platform": "template", - "sensors": { - "test_template_sensor": { - "value_template": "{{ states.sensor.xyz.state }}", - "entity_picture_template": "{% if " - "states.binary_sensor.test_state.state == " - "'Works' %}" - "/local/sensor.png" - "{% endif %}", - } - }, - } - }, - ) + assert await setup.async_setup_component( + hass, + binary_sensor.DOMAIN, + { + "binary_sensor": { + "platform": "template", + "sensors": { + "test_template_sensor": { + "value_template": "{{ states.sensor.xyz.state }}", + "entity_picture_template": "{% if " + "states.binary_sensor.test_state.state == " + "'Works' %}" + "/local/sensor.png" + "{% endif %}", + } + }, + } + }, + ) await hass.async_block_till_done() await hass.async_start() @@ -163,24 +156,23 @@ async def test_entity_picture_template(hass): async def test_attribute_templates(hass): """Test attribute_templates template.""" - with assert_setup_component(1): - assert await setup.async_setup_component( - hass, - binary_sensor.DOMAIN, - { - "binary_sensor": { - "platform": "template", - "sensors": { - "test_template_sensor": { - "value_template": "{{ states.sensor.xyz.state }}", - "attribute_templates": { - "test_attribute": "It {{ states.sensor.test_state.state }}." - }, - } - }, - } - }, - ) + assert await setup.async_setup_component( + hass, + binary_sensor.DOMAIN, + { + "binary_sensor": { + "platform": "template", + "sensors": { + "test_template_sensor": { + "value_template": "{{ states.sensor.xyz.state }}", + "attribute_templates": { + "test_attribute": "It {{ states.sensor.test_state.state }}." + }, + } + }, + } + }, + ) await hass.async_block_till_done() await hass.async_start() @@ -202,35 +194,34 @@ async def test_match_all(hass): "homeassistant.components.template.binary_sensor." "BinarySensorTemplate._update_state" ) as _update_state: - with assert_setup_component(1): - assert await setup.async_setup_component( - hass, - binary_sensor.DOMAIN, - { - "binary_sensor": { - "platform": "template", - "sensors": { - "match_all_template_sensor": { - "value_template": ( - "{% for state in states %}" - "{% if state.entity_id == 'sensor.humidity' %}" - "{{ state.entity_id }}={{ state.state }}" - "{% endif %}" - "{% endfor %}" - ), - }, + assert await setup.async_setup_component( + hass, + binary_sensor.DOMAIN, + { + "binary_sensor": { + "platform": "template", + "sensors": { + "match_all_template_sensor": { + "value_template": ( + "{% for state in states %}" + "{% if state.entity_id == 'sensor.humidity' %}" + "{{ state.entity_id }}={{ state.state }}" + "{% endif %}" + "{% endfor %}" + ), }, - } - }, - ) + }, + } + }, + ) - await hass.async_start() - await hass.async_block_till_done() - init_calls = len(_update_state.mock_calls) + await hass.async_start() + await hass.async_block_till_done() + init_calls = len(_update_state.mock_calls) - hass.states.async_set("sensor.any_state", "update") - await hass.async_block_till_done() - assert len(_update_state.mock_calls) == init_calls + hass.states.async_set("sensor.any_state", "update") + await hass.async_block_till_done() + assert len(_update_state.mock_calls) == init_calls async def test_event(hass): @@ -247,8 +238,7 @@ async def test_event(hass): }, } } - with assert_setup_component(1): - assert await setup.async_setup_component(hass, binary_sensor.DOMAIN, config) + assert await setup.async_setup_component(hass, binary_sensor.DOMAIN, config) await hass.async_block_till_done() await hass.async_start() diff --git a/tests/components/template/test_init.py b/tests/components/template/test_init.py index 1c932c5af30..107c54c710e 100644 --- a/tests/components/template/test_init.py +++ b/tests/components/template/test_init.py @@ -4,7 +4,8 @@ from os import path from unittest.mock import patch from homeassistant import config -from homeassistant.components.template import DOMAIN, SERVICE_RELOAD +from homeassistant.components.template import DOMAIN +from homeassistant.helpers.reload import SERVICE_RELOAD from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util From 0d699bb768b5f35bbf34b9346166072066b30a29 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Wed, 24 Mar 2021 07:17:51 +0100 Subject: [PATCH 582/831] Add tests for Netatmo sensor (#46393) * Add tests for Netatmo sensor * Fix coveragerc * Remove freezegun dependency * Use f-strings instead of string concatenation * Update tests/components/netatmo/test_sensor.py Co-authored-by: Erik Montnemery * Address comment on config options test * Replace deprecated call to async_get_registry() * Fix public weather sensor update test * Clean up * Prevent division by zero Co-authored-by: Erik Montnemery Co-authored-by: Martin Hjelmare --- .coveragerc | 2 +- homeassistant/components/netatmo/sensor.py | 12 +- tests/components/netatmo/common.py | 2 + tests/components/netatmo/conftest.py | 13 +- tests/components/netatmo/test_sensor.py | 235 +++++++++++ tests/fixtures/netatmo/gethomecoachsdata.json | 202 +++++++++ tests/fixtures/netatmo/getpublicdata.json | 392 ++++++++++++++++++ 7 files changed, 844 insertions(+), 14 deletions(-) create mode 100644 tests/components/netatmo/test_sensor.py create mode 100644 tests/fixtures/netatmo/gethomecoachsdata.json create mode 100644 tests/fixtures/netatmo/getpublicdata.json diff --git a/.coveragerc b/.coveragerc index 972831c4bd4..10eac76421c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -644,7 +644,7 @@ omit = homeassistant/components/nello/lock.py homeassistant/components/nest/legacy/* homeassistant/components/netatmo/data_handler.py - homeassistant/components/netatmo/sensor.py + homeassistant/components/netatmo/helper.py homeassistant/components/netdata/sensor.py homeassistant/components/netgear/device_tracker.py homeassistant/components/netgear_lte/* diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index cccd3865e54..4c6facb3eca 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -641,7 +641,7 @@ class NetatmoPublicSensor(NetatmoBase, SensorEntity): elif self.type == "guststrength": data = self._data.get_latest_gust_strengths() - if not data: + if data is None: if self._state is None: return _LOGGER.debug( @@ -650,8 +650,8 @@ class NetatmoPublicSensor(NetatmoBase, SensorEntity): self._state = None return - values = [x for x in data.values() if x is not None] - if self._mode == "avg": - self._state = round(sum(values) / len(values), 1) - elif self._mode == "max": - self._state = max(values) + if values := [x for x in data.values() if x is not None]: + if self._mode == "avg": + self._state = round(sum(values) / len(values), 1) + elif self._mode == "max": + self._state = max(values) diff --git a/tests/components/netatmo/common.py b/tests/components/netatmo/common.py index b952d6fe790..6bafd12acea 100644 --- a/tests/components/netatmo/common.py +++ b/tests/components/netatmo/common.py @@ -29,6 +29,8 @@ COMMON_RESPONSE = { "user": {"id": "91763b24c43d3e344f424e8b", "email": "john@doe.com"}, } +TEST_TIME = 1559347200.0 + def fake_post_request(**args): """Return fake data.""" diff --git a/tests/components/netatmo/conftest.py b/tests/components/netatmo/conftest.py index e0138dcc4d7..9a16391d2a4 100644 --- a/tests/components/netatmo/conftest.py +++ b/tests/components/netatmo/conftest.py @@ -5,7 +5,7 @@ from unittest.mock import patch import pytest -from .common import ALL_SCOPES, fake_post_request, fake_post_request_no_data +from .common import ALL_SCOPES, TEST_TIME, fake_post_request, fake_post_request_no_data from tests.common import MockConfigEntry @@ -81,11 +81,10 @@ async def mock_entry_fixture(hass, config_entry): @pytest.fixture(name="sensor_entry") async def mock_sensor_entry_fixture(hass, config_entry): """Mock setup of sensor platform.""" - with selected_platforms(["sensor"]): + with patch("time.time", return_value=TEST_TIME), selected_platforms(["sensor"]): await hass.config_entries.async_setup(config_entry.entry_id) - - await hass.async_block_till_done() - return config_entry + await hass.async_block_till_done() + yield config_entry @pytest.fixture(name="camera_entry") @@ -131,5 +130,5 @@ async def mock_entry_error_fixture(hass, config_entry): mock_auth.return_value.post_request.side_effect = fake_post_request_no_data await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - return config_entry + await hass.async_block_till_done() + yield config_entry diff --git a/tests/components/netatmo/test_sensor.py b/tests/components/netatmo/test_sensor.py new file mode 100644 index 00000000000..fcb2ce454df --- /dev/null +++ b/tests/components/netatmo/test_sensor.py @@ -0,0 +1,235 @@ +"""The tests for the Netatmo sensor platform.""" +from datetime import timedelta +from unittest.mock import patch + +import pytest + +from homeassistant.components.netatmo import sensor +from homeassistant.components.netatmo.sensor import MODULE_TYPE_WIND +from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY +from homeassistant.helpers import entity_registry as er +from homeassistant.util import dt + +from .common import TEST_TIME +from .conftest import selected_platforms + +from tests.common import async_fire_time_changed + + +async def test_weather_sensor(hass, sensor_entry): + """Test weather sensor setup.""" + prefix = "sensor.netatmo_mystation_" + + assert hass.states.get(f"{prefix}temperature").state == "24.6" + assert hass.states.get(f"{prefix}humidity").state == "36" + assert hass.states.get(f"{prefix}co2").state == "749" + assert hass.states.get(f"{prefix}pressure").state == "1017.3" + + +async def test_public_weather_sensor(hass, sensor_entry): + """Test public weather sensor setup.""" + prefix = "sensor.netatmo_home_max_" + + assert hass.states.get(f"{prefix}temperature").state == "27.4" + assert hass.states.get(f"{prefix}humidity").state == "76" + assert hass.states.get(f"{prefix}pressure").state == "1014.4" + + prefix = "sensor.netatmo_home_avg_" + + assert hass.states.get(f"{prefix}temperature").state == "22.7" + assert hass.states.get(f"{prefix}humidity").state == "63.2" + assert hass.states.get(f"{prefix}pressure").state == "1010.3" + + assert len(hass.states.async_all()) > 0 + entities_before_change = len(hass.states.async_all()) + + valid_option = { + "lat_ne": 32.91336, + "lon_ne": -117.187429, + "lat_sw": 32.83336, + "lon_sw": -117.26743, + "show_on_map": True, + "area_name": "Home avg", + "mode": "max", + } + + result = await hass.config_entries.options.async_init(sensor_entry.entry_id) + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={"new_area": "Home avg"} + ) + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input=valid_option + ) + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={} + ) + await hass.async_block_till_done() + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(seconds=RELOAD_AFTER_UPDATE_DELAY + 1), + ) + await hass.async_block_till_done() + + assert hass.states.get(f"{prefix}temperature").state == "27.4" + assert hass.states.get(f"{prefix}humidity").state == "76" + assert hass.states.get(f"{prefix}pressure").state == "1014.4" + + assert len(hass.states.async_all()) == entities_before_change + + +@pytest.mark.parametrize( + "strength, expected", + [(50, "Full"), (60, "High"), (80, "Medium"), (90, "Low")], +) +async def test_process_wifi(strength, expected): + """Test wifi strength translation.""" + assert sensor.process_wifi(strength) == expected + + +@pytest.mark.parametrize( + "strength, expected", + [(50, "Full"), (70, "High"), (80, "Medium"), (90, "Low")], +) +async def test_process_rf(strength, expected): + """Test radio strength translation.""" + assert sensor.process_rf(strength) == expected + + +@pytest.mark.parametrize( + "health, expected", + [(4, "Unhealthy"), (3, "Poor"), (2, "Fair"), (1, "Fine"), (0, "Healthy")], +) +async def test_process_health(health, expected): + """Test health index translation.""" + assert sensor.process_health(health) == expected + + +@pytest.mark.parametrize( + "model, data, expected", + [ + (MODULE_TYPE_WIND, 5591, "Full"), + (MODULE_TYPE_WIND, 5181, "High"), + (MODULE_TYPE_WIND, 4771, "Medium"), + (MODULE_TYPE_WIND, 4361, "Low"), + (MODULE_TYPE_WIND, 4300, "Very Low"), + ], +) +async def test_process_battery(model, data, expected): + """Test battery level translation.""" + assert sensor.process_battery(data, model) == expected + + +@pytest.mark.parametrize( + "angle, expected", + [ + (0, "N"), + (40, "NE"), + (70, "E"), + (130, "SE"), + (160, "S"), + (220, "SW"), + (250, "W"), + (310, "NW"), + (340, "N"), + ], +) +async def test_process_angle(angle, expected): + """Test wind direction translation.""" + assert sensor.process_angle(angle) == expected + + +@pytest.mark.parametrize( + "angle, expected", + [(-1, 359), (-40, 320)], +) +async def test_fix_angle(angle, expected): + """Test wind angle fix.""" + assert sensor.fix_angle(angle) == expected + + +@pytest.mark.parametrize( + "uid, name, expected", + [ + ("12:34:56:37:11:ca-reachable", "netatmo_mystation_reachable", "True"), + ("12:34:56:03:1b:e4-rf_status", "netatmo_mystation_yard_radio", "Full"), + ( + "12:34:56:05:25:6e-rf_status", + "netatmo_valley_road_rain_gauge_radio", + "Medium", + ), + ( + "12:34:56:36:fc:de-rf_status_lvl", + "netatmo_mystation_netatmooutdoor_radio_level", + "65", + ), + ( + "12:34:56:37:11:ca-wifi_status_lvl", + "netatmo_mystation_wifi_level", + "45", + ), + ( + "12:34:56:37:11:ca-wifi_status", + "netatmo_mystation_wifi_status", + "Full", + ), + ( + "12:34:56:37:11:ca-temp_trend", + "netatmo_mystation_temperature_trend", + "stable", + ), + ( + "12:34:56:37:11:ca-pressure_trend", + "netatmo_mystation_pressure_trend", + "down", + ), + ("12:34:56:05:51:20-sum_rain_1", "netatmo_mystation_yard_rain_last_hour", "0"), + ("12:34:56:05:51:20-sum_rain_24", "netatmo_mystation_yard_rain_today", "0"), + ("12:34:56:03:1b:e4-windangle", "netatmo_mystation_garden_direction", "SW"), + ( + "12:34:56:03:1b:e4-windangle_value", + "netatmo_mystation_garden_angle", + "217", + ), + ("12:34:56:03:1b:e4-gustangle", "mystation_garden_gust_direction", "S"), + ( + "12:34:56:03:1b:e4-gustangle", + "netatmo_mystation_garden_gust_direction", + "S", + ), + ( + "12:34:56:03:1b:e4-gustangle_value", + "netatmo_mystation_garden_gust_angle_value", + "206", + ), + ( + "12:34:56:03:1b:e4-guststrength", + "netatmo_mystation_garden_gust_strength", + "9", + ), + ( + "12:34:56:26:68:92-health_idx", + "netatmo_baby_bedroom_health", + "Fine", + ), + ], +) +async def test_weather_sensor_enabling(hass, config_entry, uid, name, expected): + """Test enabling of by default disabled sensors.""" + with patch("time.time", return_value=TEST_TIME), selected_platforms(["sensor"]): + states_before = len(hass.states.async_all()) + assert hass.states.get(f"sensor.{name}") is None + + registry = er.async_get(hass) + registry.async_get_or_create( + "sensor", + "netatmo", + uid, + suggested_object_id=name, + disabled_by=None, + ) + await hass.config_entries.async_setup(config_entry.entry_id) + + await hass.async_block_till_done() + + assert len(hass.states.async_all()) > states_before + assert hass.states.get(f"sensor.{name}").state == expected diff --git a/tests/fixtures/netatmo/gethomecoachsdata.json b/tests/fixtures/netatmo/gethomecoachsdata.json new file mode 100644 index 00000000000..3f9de74bd1a --- /dev/null +++ b/tests/fixtures/netatmo/gethomecoachsdata.json @@ -0,0 +1,202 @@ +{ + "body": { + "devices": [ + { + "_id": "12:34:56:26:69:0c", + "cipher_id": "enc:16:1UqwQlYV5AY2pfyEi5H47dmmFOOL3mCUo+KAkchL4A2CLI5u0e45Xr5jeAswO+XO", + "date_setup": 1544560184, + "last_setup": 1544560184, + "type": "NHC", + "last_status_store": 1558268332, + "firmware": 45, + "last_upgrade": 1544560186, + "wifi_status": 58, + "reachable": false, + "co2_calibrating": false, + "station_name": "Bedroom", + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure", + "health_idx" + ], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin", + "location": [ + 52.516263, + 13.377726 + ] + } + }, + { + "_id": "12:34:56:25:cf:a8", + "cipher_id": "enc:16:A+Jm0yFWBwUyKinFDutPZK7I2PuHN1fqaE9oB/KF+McbFs3oN9CKpR/dYbqL4om2", + "date_setup": 1544562192, + "last_setup": 1544562192, + "type": "NHC", + "last_status_store": 1559198922, + "firmware": 45, + "last_upgrade": 1544562194, + "wifi_status": 41, + "reachable": true, + "co2_calibrating": false, + "station_name": "Kitchen", + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure", + "health_idx" + ], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin", + "location": [ + 52.516263, + 13.377726 + ] + } + }, + { + "_id": "12:34:56:26:65:14", + "cipher_id": "enc:16:7kK6ZzG4L7NgfZZ6+dMvNxw4l6vXu+88SEJkCUklNdPa4KYIHmsfa1moOilEK61i", + "date_setup": 1544564061, + "last_setup": 1544564061, + "type": "NHC", + "last_status_store": 1559067159, + "firmware": 45, + "last_upgrade": 1544564302, + "wifi_status": 66, + "reachable": true, + "co2_calibrating": false, + "station_name": "Livingroom", + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure", + "health_idx" + ], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin", + "location": [ + 52.516263, + 13.377726 + ] + } + }, + { + "_id": "12:34:56:3e:c5:46", + "station_name": "Parents Bedroom", + "date_setup": 1570732241, + "last_setup": 1570732241, + "type": "NHC", + "last_status_store": 1572073818, + "module_name": "Indoor", + "firmware": 45, + "wifi_status": 67, + "reachable": true, + "co2_calibrating": false, + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure", + "health_idx" + ], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin", + "location": [ + 52.516263, + 13.377726 + ] + }, + "dashboard_data": { + "time_utc": 1572073816, + "Temperature": 20.3, + "CO2": 494, + "Humidity": 63, + "Noise": 42, + "Pressure": 1014.5, + "AbsolutePressure": 1004.1, + "health_idx": 1, + "min_temp": 20.3, + "max_temp": 21.6, + "date_max_temp": 1572059333, + "date_min_temp": 1572073816 + } + }, + { + "_id": "12:34:56:26:68:92", + "station_name": "Baby Bedroom", + "date_setup": 1571342643, + "last_setup": 1571342643, + "type": "NHC", + "last_status_store": 1572073995, + "module_name": "Indoor", + "firmware": 45, + "wifi_status": 68, + "reachable": true, + "co2_calibrating": false, + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure", + "health_idx" + ], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin", + "location": [ + 52.516263, + 13.377726 + ] + }, + "dashboard_data": { + "time_utc": 1572073994, + "Temperature": 21.6, + "CO2": 1053, + "Humidity": 66, + "Noise": 45, + "Pressure": 1021.4, + "AbsolutePressure": 1011, + "health_idx": 1, + "min_temp": 20.9, + "max_temp": 21.6, + "date_max_temp": 1572073690, + "date_min_temp": 1572064254 + } + } + ], + "user": { + "mail": "john@doe.com", + "administrative": { + "lang": "de-DE", + "reg_locale": "de-DE", + "country": "DE", + "unit": 0, + "windunit": 0, + "pressureunit": 0, + "feel_like_algo": 0 + } + } + }, + "status": "ok", + "time_exec": 0.095954179763794, + "time_server": 1559463229 +} \ No newline at end of file diff --git a/tests/fixtures/netatmo/getpublicdata.json b/tests/fixtures/netatmo/getpublicdata.json new file mode 100644 index 00000000000..55202713890 --- /dev/null +++ b/tests/fixtures/netatmo/getpublicdata.json @@ -0,0 +1,392 @@ +{ + "status": "ok", + "time_server": 1560248397, + "time_exec": 0, + "body": [ + { + "_id": "70:ee:50:36:94:7c", + "place": { + "location": [ + 8.791382999999996, + 50.2136394 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 132 + }, + "mark": 14, + "measures": { + "02:00:00:36:f2:94": { + "res": { + "1560248022": [ + 21.4, + 62 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:36:94:7c": { + "res": { + "1560248030": [ + 1010.6 + ] + }, + "type": [ + "pressure" + ] + }, + "05:00:00:05:33:84": { + "rain_60min": 0.2, + "rain_24h": 12.322000000000001, + "rain_live": 0.5, + "rain_timeutc": 1560248022 + } + }, + "modules": [ + "05:00:00:05:33:84", + "02:00:00:36:f2:94" + ], + "module_types": { + "05:00:00:05:33:84": "NAModule3", + "02:00:00:36:f2:94": "NAModule1" + } + }, + { + "_id": "70:ee:50:1f:68:9e", + "place": { + "location": [ + 8.795445200000017, + 50.2130169 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 125 + }, + "mark": 14, + "measures": { + "02:00:00:1f:82:28": { + "res": { + "1560248312": [ + 21.1, + 69 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:1f:68:9e": { + "res": { + "1560248344": [ + 1007.3 + ] + }, + "type": [ + "pressure" + ] + }, + "05:00:00:02:bb:6e": { + "rain_60min": 0, + "rain_24h": 9.999, + "rain_live": 0, + "rain_timeutc": 1560248344 + } + }, + "modules": [ + "02:00:00:1f:82:28", + "05:00:00:02:bb:6e" + ], + "module_types": { + "02:00:00:1f:82:28": "NAModule1", + "05:00:00:02:bb:6e": "NAModule3" + } + }, + { + "_id": "70:ee:50:27:25:b0", + "place": { + "location": [ + 8.7807159, + 50.1946167 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 112 + }, + "mark": 14, + "measures": { + "02:00:00:27:19:b2": { + "res": { + "1560247889": [ + 23.2, + 60 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:27:25:b0": { + "res": { + "1560247907": [ + 1012.8 + ] + }, + "type": [ + "pressure" + ] + }, + "05:00:00:03:5d:2e": { + "rain_60min": 0, + "rain_24h": 11.716000000000001, + "rain_live": 0, + "rain_timeutc": 1560247896 + } + }, + "modules": [ + "02:00:00:27:19:b2", + "05:00:00:03:5d:2e" + ], + "module_types": { + "02:00:00:27:19:b2": "NAModule1", + "05:00:00:03:5d:2e": "NAModule3" + } + }, + { + "_id": "70:ee:50:04:ed:7a", + "place": { + "location": [ + 8.785034, + 50.192169 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 112 + }, + "mark": 14, + "measures": { + "02:00:00:04:c2:2e": { + "res": { + "1560248137": [ + 19.8, + 76 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:04:ed:7a": { + "res": { + "1560248152": [ + 1005.4 + ] + }, + "type": [ + "pressure" + ] + } + }, + "modules": [ + "02:00:00:04:c2:2e" + ], + "module_types": { + "02:00:00:04:c2:2e": "NAModule1" + } + }, + { + "_id": "70:ee:50:27:9f:2c", + "place": { + "location": [ + 8.785342, + 50.193573 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 116 + }, + "mark": 1, + "measures": { + "02:00:00:27:aa:70": { + "res": { + "1560247821": [ + 25.5, + 56 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:27:9f:2c": { + "res": { + "1560247853": [ + 1010.6 + ] + }, + "type": [ + "pressure" + ] + } + }, + "modules": [ + "02:00:00:27:aa:70" + ], + "module_types": { + "02:00:00:27:aa:70": "NAModule1" + } + }, + { + "_id": "70:ee:50:01:20:fa", + "place": { + "location": [ + 8.7953, + 50.195241 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 119 + }, + "mark": 1, + "measures": { + "02:00:00:00:f7:ba": { + "res": { + "1560247831": [ + 27.4, + 58 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:01:20:fa": { + "res": { + "1560247876": [ + 1014.4 + ] + }, + "type": [ + "pressure" + ] + } + }, + "modules": [ + "02:00:00:00:f7:ba" + ], + "module_types": { + "02:00:00:00:f7:ba": "NAModule1" + } + }, + { + "_id": "70:ee:50:3c:02:78", + "place": { + "location": [ + 8.795953681700666, + 50.19530139868166 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 119 + }, + "mark": 7, + "measures": { + "02:00:00:3c:21:f2": { + "res": { + "1560248225": [ + 23.3, + 58 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:3c:02:78": { + "res": { + "1560248270": [ + 1011.7 + ] + }, + "type": [ + "pressure" + ] + } + }, + "modules": [ + "02:00:00:3c:21:f2" + ], + "module_types": { + "02:00:00:3c:21:f2": "NAModule1" + } + }, + { + "_id": "70:ee:50:36:a9:fc", + "place": { + "location": [ + 8.801164269110814, + 50.19596181704958 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 113 + }, + "mark": 14, + "measures": { + "02:00:00:36:a9:50": { + "res": { + "1560248145": [ + 20.1, + 67 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:36:a9:fc": { + "res": { + "1560248191": [ + 1010 + ] + }, + "type": [ + "pressure" + ] + }, + "05:00:00:02:92:82": { + "rain_60min": 0, + "rain_24h": 11.009, + "rain_live": 0, + "rain_timeutc": 1560248184 + }, + "06:00:00:03:19:76": { + "wind_strength": 15, + "wind_angle": 17, + "gust_strength": 31, + "gust_angle": 217, + "wind_timeutc": 1560248190 + } + }, + "modules": [ + "05:00:00:02:92:82", + "02:00:00:36:a9:50", + "06:00:00:03:19:76" + ], + "module_types": { + "05:00:00:02:92:82": "NAModule3", + "02:00:00:36:a9:50": "NAModule1", + "06:00:00:03:19:76": "NAModule2" + } + } + ] +} \ No newline at end of file From 879c82ebf8518acb673821a7e28ab9de8ebcabb9 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Wed, 24 Mar 2021 01:57:45 -0500 Subject: [PATCH 583/831] Improve Plex GDM client connections (#48272) --- homeassistant/components/plex/server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 27954cdbd9f..d1210e6f3d8 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -398,9 +398,10 @@ class PlexServer: client = PlexClient( server=self._plex_server, baseurl=baseurl, + identifier=machine_identifier, token=self._plex_server.createToken(), ) - except requests.exceptions.ConnectionError: + except (NotFound, requests.exceptions.ConnectionError): _LOGGER.error( "Direct client connection failed, will try again: %s (%s)", name, From 5265aabf92c8a1bf8d15402e7355ae080d1acfdf Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Wed, 24 Mar 2021 00:39:23 -0700 Subject: [PATCH 584/831] Clean up SmartTub reminders (#48033) * remove "date" state attribute * remove unused constant --- homeassistant/components/smarttub/binary_sensor.py | 7 +------ tests/components/smarttub/test_binary_sensor.py | 5 ----- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/homeassistant/components/smarttub/binary_sensor.py b/homeassistant/components/smarttub/binary_sensor.py index 04a6bb9b72a..bbeece36655 100644 --- a/homeassistant/components/smarttub/binary_sensor.py +++ b/homeassistant/components/smarttub/binary_sensor.py @@ -1,5 +1,4 @@ """Platform for binary sensor integration.""" -from datetime import datetime, timedelta import logging from smarttub import SpaReminder @@ -17,8 +16,6 @@ _LOGGER = logging.getLogger(__name__) # whether the reminder has been snoozed (bool) ATTR_REMINDER_SNOOZED = "snoozed" -# the date at which the reminder will be activated -ATTR_REMINDER_DATE = "date" async def async_setup_entry(hass, entry, async_add_entities): @@ -83,12 +80,10 @@ class SmartTubReminder(SmartTubEntity, BinarySensorEntity): return self.reminder.remaining_days == 0 @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - when = datetime.now() + timedelta(days=self.reminder.remaining_days) return { ATTR_REMINDER_SNOOZED: self.reminder.snoozed, - ATTR_REMINDER_DATE: when.date().isoformat(), } @property diff --git a/tests/components/smarttub/test_binary_sensor.py b/tests/components/smarttub/test_binary_sensor.py index 8229372e904..5db97310c56 100644 --- a/tests/components/smarttub/test_binary_sensor.py +++ b/tests/components/smarttub/test_binary_sensor.py @@ -1,6 +1,4 @@ """Test the SmartTub binary sensor platform.""" -from datetime import date, timedelta - from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, STATE_OFF, @@ -25,7 +23,4 @@ async def test_reminders(spa, setup_entry, hass): state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_OFF - assert date.fromisoformat(state.attributes["date"]) <= date.today() + timedelta( - days=2 - ) assert state.attributes["snoozed"] is False From 0be6a868e062678682974a21ed2680eed81ba0b5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 24 Mar 2021 10:20:49 +0100 Subject: [PATCH 585/831] Fix Core bug report issue form (#48279) --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 384cc5834cd..aa81d6e4df7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -26,10 +26,10 @@ body: value: | ## Environment - type: input + id: version validations: required: true attributes: - id: version label: What is version of Home Assistant Core has the issue? placeholder: core- description: > From 8896ae0d561962820cca881bee6a09d076d1c51c Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 24 Mar 2021 17:56:22 +0100 Subject: [PATCH 586/831] Add support for tracing script execution (#48276) * Add support for tracing script execution * Tweak --- homeassistant/components/automation/trace.py | 135 +---- homeassistant/components/script/__init__.py | 20 +- homeassistant/components/script/config.py | 15 + homeassistant/components/script/manifest.json | 1 + homeassistant/components/script/trace.py | 23 + homeassistant/components/trace/__init__.py | 182 +++++- homeassistant/components/trace/const.py | 4 +- .../components/trace/websocket_api.py | 2 +- homeassistant/helpers/trace.py | 2 +- tests/components/trace/test_websocket_api.py | 538 ++++++++++-------- 10 files changed, 531 insertions(+), 391 deletions(-) create mode 100644 homeassistant/components/script/trace.py diff --git a/homeassistant/components/automation/trace.py b/homeassistant/components/automation/trace.py index f5e93e09c38..a2c2e40c80c 100644 --- a/homeassistant/components/automation/trace.py +++ b/homeassistant/components/automation/trace.py @@ -2,148 +2,25 @@ from __future__ import annotations from contextlib import contextmanager -import datetime as dt -from itertools import count -from typing import Any, Deque -from homeassistant.components.trace.const import DATA_TRACE, STORED_TRACES -from homeassistant.components.trace.utils import LimitedSizeDict -from homeassistant.core import Context -from homeassistant.helpers.trace import TraceElement, trace_id_set -from homeassistant.util import dt as dt_util +from homeassistant.components.trace import AutomationTrace, async_store_trace # mypy: allow-untyped-calls, allow-untyped-defs # mypy: no-check-untyped-defs, no-warn-return-any -class AutomationTrace: - """Container for automation trace.""" - - _run_ids = count(0) - - def __init__( - self, - key: tuple[str, str], - config: dict[str, Any], - context: Context, - ): - """Container for automation trace.""" - self._action_trace: dict[str, Deque[TraceElement]] | None = None - self._condition_trace: dict[str, Deque[TraceElement]] | None = None - self._config: dict[str, Any] = config - self.context: Context = context - self._error: Exception | None = None - self._state: str = "running" - self.run_id: str = str(next(self._run_ids)) - self._timestamp_finish: dt.datetime | None = None - self._timestamp_start: dt.datetime = dt_util.utcnow() - self._key: tuple[str, str] = key - self._variables: dict[str, Any] | None = None - - def set_action_trace(self, trace: dict[str, Deque[TraceElement]]) -> None: - """Set action trace.""" - self._action_trace = trace - - def set_condition_trace(self, trace: dict[str, Deque[TraceElement]]) -> None: - """Set condition trace.""" - self._condition_trace = trace - - def set_error(self, ex: Exception) -> None: - """Set error.""" - self._error = ex - - def set_variables(self, variables: dict[str, Any]) -> None: - """Set variables.""" - self._variables = variables - - def finished(self) -> None: - """Set finish time.""" - self._timestamp_finish = dt_util.utcnow() - self._state = "stopped" - - def as_dict(self) -> dict[str, Any]: - """Return dictionary version of this AutomationTrace.""" - - result = self.as_short_dict() - - action_traces = {} - condition_traces = {} - if self._action_trace: - for key, trace_list in self._action_trace.items(): - action_traces[key] = [item.as_dict() for item in trace_list] - - if self._condition_trace: - for key, trace_list in self._condition_trace.items(): - condition_traces[key] = [item.as_dict() for item in trace_list] - - result.update( - { - "action_trace": action_traces, - "condition_trace": condition_traces, - "config": self._config, - "context": self.context, - "variables": self._variables, - } - ) - if self._error is not None: - result["error"] = str(self._error) - return result - - def as_short_dict(self) -> dict[str, Any]: - """Return a brief dictionary version of this AutomationTrace.""" - - last_action = None - last_condition = None - trigger = None - - if self._action_trace: - last_action = list(self._action_trace)[-1] - if self._condition_trace: - last_condition = list(self._condition_trace)[-1] - if self._variables: - trigger = self._variables.get("trigger", {}).get("description") - - result = { - "last_action": last_action, - "last_condition": last_condition, - "run_id": self.run_id, - "state": self._state, - "timestamp": { - "start": self._timestamp_start, - "finish": self._timestamp_finish, - }, - "trigger": trigger, - "domain": self._key[0], - "item_id": self._key[1], - } - if self._error is not None: - result["error"] = str(self._error) - if last_action is not None: - result["last_action"] = last_action - result["last_condition"] = last_condition - - return result - - @contextmanager def trace_automation(hass, item_id, config, context): - """Trace action execution of automation with automation_id.""" - key = ("automation", item_id) - trace = AutomationTrace(key, config, context) - trace_id_set((key, trace.run_id)) - - if key: - traces = hass.data[DATA_TRACE] - if key not in traces: - traces[key] = LimitedSizeDict(size_limit=STORED_TRACES) - traces[key][trace.run_id] = trace + """Trace action execution of automation with item_id.""" + trace = AutomationTrace(item_id, config, context) + async_store_trace(hass, trace) try: yield trace except Exception as ex: # pylint: disable=broad-except - if key: + if item_id: trace.set_error(ex) raise ex finally: - if key: + if item_id: trace.finished() diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index 243cdaddd81..b7586841eb7 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -36,8 +36,11 @@ from homeassistant.helpers.script import ( make_script_schema, ) from homeassistant.helpers.service import async_set_service_schema +from homeassistant.helpers.trace import trace_get, trace_path from homeassistant.loader import bind_hass +from .trace import trace_script + _LOGGER = logging.getLogger(__name__) DOMAIN = "script" @@ -221,7 +224,7 @@ async def _async_process_config(hass, config, component): ) script_entities = [ - ScriptEntity(hass, object_id, cfg) + ScriptEntity(hass, object_id, cfg, cfg.raw_config) for object_id, cfg in config.get(DOMAIN, {}).items() ] @@ -253,7 +256,7 @@ class ScriptEntity(ToggleEntity): icon = None - def __init__(self, hass, object_id, cfg): + def __init__(self, hass, object_id, cfg, raw_config): """Initialize the script.""" self.object_id = object_id self.icon = cfg.get(CONF_ICON) @@ -272,6 +275,7 @@ class ScriptEntity(ToggleEntity): variables=cfg.get(CONF_VARIABLES), ) self._changed = asyncio.Event() + self._raw_config = raw_config @property def should_poll(self): @@ -323,7 +327,7 @@ class ScriptEntity(ToggleEntity): {ATTR_NAME: self.script.name, ATTR_ENTITY_ID: self.entity_id}, context=context, ) - coro = self.script.async_run(variables, context) + coro = self._async_run(variables, context) if wait: await coro return @@ -335,6 +339,16 @@ class ScriptEntity(ToggleEntity): self.hass.async_create_task(coro) await self._changed.wait() + async def _async_run(self, variables, context): + with trace_script( + self.hass, self.object_id, self._raw_config, context + ) as script_trace: + script_trace.set_variables(variables) + # Prepare tracing the execution of the script's sequence + script_trace.set_action_trace(trace_get()) + with trace_path("sequence"): + return await self.script.async_run(variables, context) + async def async_turn_off(self, **kwargs): """Stop running the script. diff --git a/homeassistant/components/script/config.py b/homeassistant/components/script/config.py index 3860a4d0119..5da8bec5a87 100644 --- a/homeassistant/components/script/config.py +++ b/homeassistant/components/script/config.py @@ -25,8 +25,21 @@ async def async_validate_config_item(hass, config, full_config=None): return config +class ScriptConfig(dict): + """Dummy class to allow adding attributes.""" + + raw_config = None + + async def _try_async_validate_config_item(hass, object_id, config, full_config=None): """Validate config item.""" + raw_config = None + try: + raw_config = dict(config) + except ValueError: + # Invalid config + pass + try: cv.slug(object_id) config = await async_validate_config_item(hass, config, full_config) @@ -34,6 +47,8 @@ async def _try_async_validate_config_item(hass, object_id, config, full_config=N async_log_exception(ex, DOMAIN, full_config or config, hass) return None + config = ScriptConfig(config) + config.raw_config = raw_config return config diff --git a/homeassistant/components/script/manifest.json b/homeassistant/components/script/manifest.json index b9d333ce553..ab14889a60c 100644 --- a/homeassistant/components/script/manifest.json +++ b/homeassistant/components/script/manifest.json @@ -2,6 +2,7 @@ "domain": "script", "name": "Scripts", "documentation": "https://www.home-assistant.io/integrations/script", + "dependencies": ["trace"], "codeowners": [ "@home-assistant/core" ], diff --git a/homeassistant/components/script/trace.py b/homeassistant/components/script/trace.py new file mode 100644 index 00000000000..09b22f98133 --- /dev/null +++ b/homeassistant/components/script/trace.py @@ -0,0 +1,23 @@ +"""Trace support for script.""" +from __future__ import annotations + +from contextlib import contextmanager + +from homeassistant.components.trace import ScriptTrace, async_store_trace + + +@contextmanager +def trace_script(hass, item_id, config, context): + """Trace execution of a script.""" + trace = ScriptTrace(item_id, config, context) + async_store_trace(hass, trace) + + try: + yield trace + except Exception as ex: # pylint: disable=broad-except + if item_id: + trace.set_error(ex) + raise ex + finally: + if item_id: + trace.finished() diff --git a/homeassistant/components/trace/__init__.py b/homeassistant/components/trace/__init__.py index 0dc8cda6664..43deefaa769 100644 --- a/homeassistant/components/trace/__init__.py +++ b/homeassistant/components/trace/__init__.py @@ -1,12 +1,188 @@ -"""Support for automation and script tracing and debugging.""" +"""Support for script and automation tracing and debugging.""" +from __future__ import annotations + +import datetime as dt +from itertools import count +from typing import Any, Deque + +from homeassistant.core import Context +from homeassistant.helpers.trace import TraceElement, trace_id_set +import homeassistant.util.dt as dt_util + from . import websocket_api -from .const import DATA_TRACE +from .const import DATA_TRACE, STORED_TRACES +from .utils import LimitedSizeDict DOMAIN = "trace" async def async_setup(hass, config): """Initialize the trace integration.""" - hass.data.setdefault(DATA_TRACE, {}) + hass.data[DATA_TRACE] = {} websocket_api.async_setup(hass) return True + + +def async_store_trace(hass, trace): + """Store a trace if its item_id is valid.""" + key = trace.key + if key[1]: + traces = hass.data[DATA_TRACE] + if key not in traces: + traces[key] = LimitedSizeDict(size_limit=STORED_TRACES) + traces[key][trace.run_id] = trace + + +class ActionTrace: + """Base container for an script or automation trace.""" + + _run_ids = count(0) + + def __init__( + self, + key: tuple[str, str], + config: dict[str, Any], + context: Context, + ): + """Container for script trace.""" + self._action_trace: dict[str, Deque[TraceElement]] | None = None + self._config: dict[str, Any] = config + self.context: Context = context + self._error: Exception | None = None + self._state: str = "running" + self.run_id: str = str(next(self._run_ids)) + self._timestamp_finish: dt.datetime | None = None + self._timestamp_start: dt.datetime = dt_util.utcnow() + self.key: tuple[str, str] = key + self._variables: dict[str, Any] | None = None + trace_id_set((key, self.run_id)) + + def set_action_trace(self, trace: dict[str, Deque[TraceElement]]) -> None: + """Set action trace.""" + self._action_trace = trace + + def set_error(self, ex: Exception) -> None: + """Set error.""" + self._error = ex + + def set_variables(self, variables: dict[str, Any]) -> None: + """Set variables.""" + self._variables = variables + + def finished(self) -> None: + """Set finish time.""" + self._timestamp_finish = dt_util.utcnow() + self._state = "stopped" + + def as_dict(self) -> dict[str, Any]: + """Return dictionary version of this ActionTrace.""" + + result = self.as_short_dict() + + action_traces = {} + if self._action_trace: + for key, trace_list in self._action_trace.items(): + action_traces[key] = [item.as_dict() for item in trace_list] + + result.update( + { + "action_trace": action_traces, + "config": self._config, + "context": self.context, + "variables": self._variables, + } + ) + if self._error is not None: + result["error"] = str(self._error) + return result + + def as_short_dict(self) -> dict[str, Any]: + """Return a brief dictionary version of this ActionTrace.""" + + last_action = None + + if self._action_trace: + last_action = list(self._action_trace)[-1] + + result = { + "last_action": last_action, + "run_id": self.run_id, + "state": self._state, + "timestamp": { + "start": self._timestamp_start, + "finish": self._timestamp_finish, + }, + "domain": self.key[0], + "item_id": self.key[1], + } + if self._error is not None: + result["error"] = str(self._error) + if last_action is not None: + result["last_action"] = last_action + + return result + + +class AutomationTrace(ActionTrace): + """Container for automation trace.""" + + def __init__( + self, + item_id: str, + config: dict[str, Any], + context: Context, + ): + """Container for automation trace.""" + key = ("automation", item_id) + super().__init__(key, config, context) + self._condition_trace: dict[str, Deque[TraceElement]] | None = None + + def set_condition_trace(self, trace: dict[str, Deque[TraceElement]]) -> None: + """Set condition trace.""" + self._condition_trace = trace + + def as_dict(self) -> dict[str, Any]: + """Return dictionary version of this AutomationTrace.""" + + result = super().as_dict() + + condition_traces = {} + + if self._condition_trace: + for key, trace_list in self._condition_trace.items(): + condition_traces[key] = [item.as_dict() for item in trace_list] + result["condition_trace"] = condition_traces + + return result + + def as_short_dict(self) -> dict[str, Any]: + """Return a brief dictionary version of this AutomationTrace.""" + + result = super().as_short_dict() + + last_condition = None + trigger = None + + if self._condition_trace: + last_condition = list(self._condition_trace)[-1] + if self._variables: + trigger = self._variables.get("trigger", {}).get("description") + + result["trigger"] = trigger + result["last_condition"] = last_condition + + return result + + +class ScriptTrace(ActionTrace): + """Container for automation trace.""" + + def __init__( + self, + item_id: str, + config: dict[str, Any], + context: Context, + ): + """Container for automation trace.""" + key = ("script", item_id) + super().__init__(key, config, context) diff --git a/homeassistant/components/trace/const.py b/homeassistant/components/trace/const.py index 547bdb35c77..05942d7ee4d 100644 --- a/homeassistant/components/trace/const.py +++ b/homeassistant/components/trace/const.py @@ -1,4 +1,4 @@ -"""Shared constants for automation and script tracing and debugging.""" +"""Shared constants for script and automation tracing and debugging.""" DATA_TRACE = "trace" -STORED_TRACES = 5 # Stored traces per automation +STORED_TRACES = 5 # Stored traces per script or automation diff --git a/homeassistant/components/trace/websocket_api.py b/homeassistant/components/trace/websocket_api.py index 1f42b50671e..1b5270f6253 100644 --- a/homeassistant/components/trace/websocket_api.py +++ b/homeassistant/components/trace/websocket_api.py @@ -28,7 +28,7 @@ from .utils import TraceJSONEncoder # mypy: allow-untyped-calls, allow-untyped-defs -TRACE_DOMAINS = ["automation"] +TRACE_DOMAINS = ["automation", "script"] @callback diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py index d6de845248f..ba39e19943b 100644 --- a/homeassistant/helpers/trace.py +++ b/homeassistant/helpers/trace.py @@ -71,7 +71,7 @@ trace_path_stack_cv: ContextVar[list[str] | None] = ContextVar( ) # Copy of last variables variables_cv: ContextVar[Any | None] = ContextVar("variables_cv", default=None) -# Automation ID + Run ID +# (domain, item_id) + Run ID trace_id_cv: ContextVar[tuple[str, str] | None] = ContextVar( "trace_id_cv", default=None ) diff --git a/tests/components/trace/test_websocket_api.py b/tests/components/trace/test_websocket_api.py index e07c042d1d0..8dc09731b79 100644 --- a/tests/components/trace/test_websocket_api.py +++ b/tests/components/trace/test_websocket_api.py @@ -1,31 +1,42 @@ """Test Trace websocket API.""" -from unittest.mock import patch +import pytest from homeassistant.bootstrap import async_setup_component -from homeassistant.components import config from homeassistant.components.trace.const import STORED_TRACES from homeassistant.core import Context from tests.common import assert_lists_same -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 -def _find_run_id(traces, item_id): - """Find newest run_id for an automation.""" +def _find_run_id(traces, trace_type, item_id): + """Find newest run_id for an automation or script.""" for trace in reversed(traces): - if trace["item_id"] == item_id: + if trace["domain"] == trace_type and trace["item_id"] == item_id: return trace["run_id"] return None +def _find_traces(traces, trace_type, item_id): + """Find traces for an automation or script.""" + return [ + trace + for trace in traces + if trace["domain"] == trace_type and trace["item_id"] == item_id + ] + + +# TODO: Remove def _find_traces_for_automation(traces, item_id): """Find traces for an automation.""" return [trace for trace in traces if trace["item_id"] == item_id] -async def test_get_automation_trace(hass, hass_ws_client): - """Test tracing an automation.""" +@pytest.mark.parametrize( + "domain, prefix", [("automation", "action"), ("script", "sequence")] +) +async def test_get_trace(hass, hass_ws_client, domain, prefix): + """Test tracing an automation or script.""" id = 1 def next_id(): @@ -50,6 +61,9 @@ async def test_get_automation_trace(hass, hass_ws_client): }, "action": {"event": "another_event"}, } + if domain == "script": + sun_config = {"sequence": sun_config["action"]} + moon_config = {"sequence": moon_config["action"]} sun_action = { "limit": 10, @@ -63,40 +77,38 @@ async def test_get_automation_trace(hass, hass_ws_client): } moon_action = {"event": "another_event", "event_data": {}} - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - moon_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) + if domain == "automation": + assert await async_setup_component( + hass, domain, {domain: [sun_config, moon_config]} + ) + else: + assert await async_setup_component( + hass, domain, {domain: {"sun": sun_config, "moon": moon_config}} + ) client = await hass_ws_client() contexts = {} - # Trigger "sun" automation + # Trigger "sun" automation / run "sun" script context = Context() - hass.bus.async_fire("test_event", context=context) + if domain == "automation": + hass.bus.async_fire("test_event", context=context) + else: + await hass.services.async_call("script", "sun", context=context) await hass.async_block_till_done() # List traces await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - run_id = _find_run_id(response["result"], "sun") + run_id = _find_run_id(response["result"], domain, "sun") # Get trace await client.send_json( { "id": next_id(), "type": "trace/get", - "domain": "automation", + "domain": domain, "item_id": "sun", "run_id": run_id, } @@ -104,41 +116,47 @@ async def test_get_automation_trace(hass, hass_ws_client): response = await client.receive_json() assert response["success"] trace = response["result"] - assert trace["context"]["parent_id"] == context.id assert len(trace["action_trace"]) == 1 - assert len(trace["action_trace"]["action/0"]) == 1 - assert trace["action_trace"]["action/0"][0]["error"] - assert trace["action_trace"]["action/0"][0]["result"] == sun_action - assert trace["condition_trace"] == {} + assert len(trace["action_trace"][f"{prefix}/0"]) == 1 + assert trace["action_trace"][f"{prefix}/0"][0]["error"] + assert trace["action_trace"][f"{prefix}/0"][0]["result"] == sun_action assert trace["config"] == sun_config assert trace["context"] assert trace["error"] == "Unable to find service test.automation" assert trace["state"] == "stopped" - assert trace["trigger"] == "event 'test_event'" assert trace["item_id"] == "sun" - assert trace["variables"] + assert trace["variables"] is not None + if domain == "automation": + assert trace["condition_trace"] == {} + assert trace["context"]["parent_id"] == context.id + assert trace["trigger"] == "event 'test_event'" + else: + assert trace["context"]["id"] == context.id contexts[trace["context"]["id"]] = { "run_id": trace["run_id"], - "domain": "automation", + "domain": domain, "item_id": trace["item_id"], } - # Trigger "moon" automation, with passing condition - hass.bus.async_fire("test_event2") + # Trigger "moon" automation, with passing condition / run "moon" script + if domain == "automation": + hass.bus.async_fire("test_event2") + else: + await hass.services.async_call("script", "moon") await hass.async_block_till_done() # List traces await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - run_id = _find_run_id(response["result"], "moon") + run_id = _find_run_id(response["result"], domain, "moon") # Get trace await client.send_json( { "id": next_id(), "type": "trace/get", - "domain": "automation", + "domain": domain, "item_id": "moon", "run_id": run_id, } @@ -147,26 +165,36 @@ async def test_get_automation_trace(hass, hass_ws_client): assert response["success"] trace = response["result"] assert len(trace["action_trace"]) == 1 - assert len(trace["action_trace"]["action/0"]) == 1 - assert "error" not in trace["action_trace"]["action/0"][0] - assert trace["action_trace"]["action/0"][0]["result"] == moon_action - assert len(trace["condition_trace"]) == 1 - assert len(trace["condition_trace"]["condition/0"]) == 1 - assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} + assert len(trace["action_trace"][f"{prefix}/0"]) == 1 + assert "error" not in trace["action_trace"][f"{prefix}/0"][0] + assert trace["action_trace"][f"{prefix}/0"][0]["result"] == moon_action assert trace["config"] == moon_config assert trace["context"] assert "error" not in trace assert trace["state"] == "stopped" - assert trace["trigger"] == "event 'test_event2'" assert trace["item_id"] == "moon" - assert trace["variables"] + assert trace["variables"] is not None + + if domain == "automation": + assert len(trace["condition_trace"]) == 1 + assert len(trace["condition_trace"]["condition/0"]) == 1 + assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} + assert trace["trigger"] == "event 'test_event2'" contexts[trace["context"]["id"]] = { "run_id": trace["run_id"], - "domain": "automation", + "domain": domain, "item_id": trace["item_id"], } - # Trigger "moon" automation, with failing condition + if domain == "script": + # Check contexts + await client.send_json({"id": next_id(), "type": "trace/contexts"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == contexts + return + + # Trigger "moon" automation with failing condition hass.bus.async_fire("test_event3") await hass.async_block_till_done() @@ -174,14 +202,14 @@ async def test_get_automation_trace(hass, hass_ws_client): await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - run_id = _find_run_id(response["result"], "moon") + run_id = _find_run_id(response["result"], "automation", "moon") # Get trace await client.send_json( { "id": next_id(), "type": "trace/get", - "domain": "automation", + "domain": domain, "item_id": "moon", "run_id": run_id, } @@ -202,11 +230,11 @@ async def test_get_automation_trace(hass, hass_ws_client): assert trace["variables"] contexts[trace["context"]["id"]] = { "run_id": trace["run_id"], - "domain": "automation", + "domain": domain, "item_id": trace["item_id"], } - # Trigger "moon" automation, with passing condition + # Trigger "moon" automation with passing condition hass.bus.async_fire("test_event2") await hass.async_block_till_done() @@ -214,14 +242,14 @@ async def test_get_automation_trace(hass, hass_ws_client): await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - run_id = _find_run_id(response["result"], "moon") + run_id = _find_run_id(response["result"], "automation", "moon") # Get trace await client.send_json( { "id": next_id(), "type": "trace/get", - "domain": "automation", + "domain": domain, "item_id": "moon", "run_id": run_id, } @@ -230,9 +258,9 @@ async def test_get_automation_trace(hass, hass_ws_client): assert response["success"] trace = response["result"] assert len(trace["action_trace"]) == 1 - assert len(trace["action_trace"]["action/0"]) == 1 - assert "error" not in trace["action_trace"]["action/0"][0] - assert trace["action_trace"]["action/0"][0]["result"] == moon_action + assert len(trace["action_trace"][f"{prefix}/0"]) == 1 + assert "error" not in trace["action_trace"][f"{prefix}/0"][0] + assert trace["action_trace"][f"{prefix}/0"][0]["result"] == moon_action assert len(trace["condition_trace"]) == 1 assert len(trace["condition_trace"]["condition/0"]) == 1 assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} @@ -245,7 +273,7 @@ async def test_get_automation_trace(hass, hass_ws_client): assert trace["variables"] contexts[trace["context"]["id"]] = { "run_id": trace["run_id"], - "domain": "automation", + "domain": domain, "item_id": trace["item_id"], } @@ -256,8 +284,9 @@ async def test_get_automation_trace(hass, hass_ws_client): assert response["result"] == contexts -async def test_automation_trace_overflow(hass, hass_ws_client): - """Test the number of stored traces per automation is limited.""" +@pytest.mark.parametrize("domain", ["automation", "script"]) +async def test_trace_overflow(hass, hass_ws_client, domain): + """Test the number of stored traces per automation or script is limited.""" id = 1 def next_id(): @@ -275,20 +304,18 @@ async def test_automation_trace_overflow(hass, hass_ws_client): "trigger": {"platform": "event", "event_type": "test_event2"}, "action": {"event": "another_event"}, } + if domain == "script": + sun_config = {"sequence": sun_config["action"]} + moon_config = {"sequence": moon_config["action"]} - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - moon_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) + if domain == "automation": + assert await async_setup_component( + hass, domain, {domain: [sun_config, moon_config]} + ) + else: + assert await async_setup_component( + hass, domain, {domain: {"sun": sun_config, "moon": moon_config}} + ) client = await hass_ws_client() @@ -297,37 +324,47 @@ async def test_automation_trace_overflow(hass, hass_ws_client): assert response["success"] assert response["result"] == [] - # Trigger "sun" and "moon" automation once - hass.bus.async_fire("test_event") - hass.bus.async_fire("test_event2") + # Trigger "sun" and "moon" automation / script once + if domain == "automation": + hass.bus.async_fire("test_event") + hass.bus.async_fire("test_event2") + else: + await hass.services.async_call("script", "sun") + await hass.services.async_call("script", "moon") await hass.async_block_till_done() # List traces await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - assert len(_find_traces_for_automation(response["result"], "moon")) == 1 - moon_run_id = _find_run_id(response["result"], "moon") - assert len(_find_traces_for_automation(response["result"], "sun")) == 1 + assert len(_find_traces(response["result"], domain, "moon")) == 1 + moon_run_id = _find_run_id(response["result"], domain, "moon") + assert len(_find_traces(response["result"], domain, "sun")) == 1 - # Trigger "moon" automation enough times to overflow the number of stored traces + # Trigger "moon" enough times to overflow the max number of stored traces for _ in range(STORED_TRACES): - hass.bus.async_fire("test_event2") + if domain == "automation": + hass.bus.async_fire("test_event2") + else: + await hass.services.async_call("script", "moon") await hass.async_block_till_done() await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - moon_traces = _find_traces_for_automation(response["result"], "moon") + moon_traces = _find_traces(response["result"], domain, "moon") assert len(moon_traces) == STORED_TRACES assert moon_traces[0] assert int(moon_traces[0]["run_id"]) == int(moon_run_id) + 1 assert int(moon_traces[-1]["run_id"]) == int(moon_run_id) + STORED_TRACES - assert len(_find_traces_for_automation(response["result"], "sun")) == 1 + assert len(_find_traces(response["result"], domain, "sun")) == 1 -async def test_list_automation_traces(hass, hass_ws_client): - """Test listing automation traces.""" +@pytest.mark.parametrize( + "domain, prefix", [("automation", "action"), ("script", "sequence")] +) +async def test_list_traces(hass, hass_ws_client, domain, prefix): + """Test listing automation and script traces.""" id = 1 def next_id(): @@ -352,20 +389,18 @@ async def test_list_automation_traces(hass, hass_ws_client): }, "action": {"event": "another_event"}, } + if domain == "script": + sun_config = {"sequence": sun_config["action"]} + moon_config = {"sequence": moon_config["action"]} - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - moon_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) + if domain == "automation": + assert await async_setup_component( + hass, domain, {domain: [sun_config, moon_config]} + ) + else: + assert await async_setup_component( + hass, domain, {domain: {"sun": sun_config, "moon": moon_config}} + ) client = await hass_ws_client() @@ -375,19 +410,17 @@ async def test_list_automation_traces(hass, hass_ws_client): assert response["result"] == [] await client.send_json( - { - "id": next_id(), - "type": "trace/list", - "domain": "automation", - "item_id": "sun", - } + {"id": next_id(), "type": "trace/list", "domain": domain, "item_id": "sun"} ) response = await client.receive_json() assert response["success"] assert response["result"] == [] - # Trigger "sun" automation - hass.bus.async_fire("test_event") + # Trigger "sun" automation / run "sun" script + if domain == "automation": + hass.bus.async_fire("test_event") + else: + await hass.services.async_call("script", "sun") await hass.async_block_till_done() # Get trace @@ -395,90 +428,98 @@ async def test_list_automation_traces(hass, hass_ws_client): response = await client.receive_json() assert response["success"] assert len(response["result"]) == 1 - assert len(_find_traces_for_automation(response["result"], "sun")) == 1 + assert len(_find_traces(response["result"], domain, "sun")) == 1 await client.send_json( - { - "id": next_id(), - "type": "trace/list", - "domain": "automation", - "item_id": "sun", - } + {"id": next_id(), "type": "trace/list", "domain": domain, "item_id": "sun"} ) response = await client.receive_json() assert response["success"] assert len(response["result"]) == 1 - assert len(_find_traces_for_automation(response["result"], "sun")) == 1 + assert len(_find_traces(response["result"], domain, "sun")) == 1 await client.send_json( - { - "id": next_id(), - "type": "trace/list", - "domain": "automation", - "item_id": "moon", - } + {"id": next_id(), "type": "trace/list", "domain": domain, "item_id": "moon"} ) response = await client.receive_json() assert response["success"] assert response["result"] == [] - # Trigger "moon" automation, with passing condition - hass.bus.async_fire("test_event2") + # Trigger "moon" automation, with passing condition / run "moon" script + if domain == "automation": + hass.bus.async_fire("test_event2") + else: + await hass.services.async_call("script", "moon") await hass.async_block_till_done() - # Trigger "moon" automation, with failing condition - hass.bus.async_fire("test_event3") + # Trigger "moon" automation, with failing condition / run "moon" script + if domain == "automation": + hass.bus.async_fire("test_event3") + else: + await hass.services.async_call("script", "moon") await hass.async_block_till_done() - # Trigger "moon" automation, with passing condition - hass.bus.async_fire("test_event2") + # Trigger "moon" automation, with passing condition / run "moon" script + if domain == "automation": + hass.bus.async_fire("test_event2") + else: + await hass.services.async_call("script", "moon") await hass.async_block_till_done() # Get trace await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - assert len(_find_traces_for_automation(response["result"], "moon")) == 3 - assert len(_find_traces_for_automation(response["result"], "sun")) == 1 - trace = _find_traces_for_automation(response["result"], "sun")[0] - assert trace["last_action"] == "action/0" - assert trace["last_condition"] is None + assert len(_find_traces(response["result"], domain, "moon")) == 3 + assert len(_find_traces(response["result"], domain, "sun")) == 1 + trace = _find_traces(response["result"], domain, "sun")[0] + assert trace["last_action"] == f"{prefix}/0" assert trace["error"] == "Unable to find service test.automation" assert trace["state"] == "stopped" assert trace["timestamp"] - assert trace["trigger"] == "event 'test_event'" assert trace["item_id"] == "sun" + if domain == "automation": + assert trace["last_condition"] is None + assert trace["trigger"] == "event 'test_event'" - trace = _find_traces_for_automation(response["result"], "moon")[0] - assert trace["last_action"] == "action/0" - assert trace["last_condition"] == "condition/0" + trace = _find_traces(response["result"], domain, "moon")[0] + assert trace["last_action"] == f"{prefix}/0" assert "error" not in trace assert trace["state"] == "stopped" assert trace["timestamp"] - assert trace["trigger"] == "event 'test_event2'" assert trace["item_id"] == "moon" + if domain == "automation": + assert trace["last_condition"] == "condition/0" + assert trace["trigger"] == "event 'test_event2'" - trace = _find_traces_for_automation(response["result"], "moon")[1] - assert trace["last_action"] is None - assert trace["last_condition"] == "condition/0" + trace = _find_traces(response["result"], domain, "moon")[1] assert "error" not in trace assert trace["state"] == "stopped" assert trace["timestamp"] - assert trace["trigger"] == "event 'test_event3'" assert trace["item_id"] == "moon" + if domain == "automation": + assert trace["last_action"] is None + assert trace["last_condition"] == "condition/0" + assert trace["trigger"] == "event 'test_event3'" + else: + assert trace["last_action"] == f"{prefix}/0" - trace = _find_traces_for_automation(response["result"], "moon")[2] - assert trace["last_action"] == "action/0" - assert trace["last_condition"] == "condition/0" + trace = _find_traces(response["result"], domain, "moon")[2] + assert trace["last_action"] == f"{prefix}/0" assert "error" not in trace assert trace["state"] == "stopped" assert trace["timestamp"] - assert trace["trigger"] == "event 'test_event2'" assert trace["item_id"] == "moon" + if domain == "automation": + assert trace["last_condition"] == "condition/0" + assert trace["trigger"] == "event 'test_event2'" -async def test_automation_breakpoints(hass, hass_ws_client): - """Test automation breakpoints.""" +@pytest.mark.parametrize( + "domain, prefix", [("automation", "action"), ("script", "sequence")] +) +async def test_breakpoints(hass, hass_ws_client, domain, prefix): + """Test automation and script breakpoints.""" id = 1 def next_id(): @@ -490,7 +531,7 @@ async def test_automation_breakpoints(hass, hass_ws_client): await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - trace = _find_traces_for_automation(response["result"], item_id)[-1] + trace = _find_traces(response["result"], domain, item_id)[-1] assert trace["last_action"] == expected_action assert trace["state"] == expected_state return trace["run_id"] @@ -510,19 +551,13 @@ async def test_automation_breakpoints(hass, hass_ws_client): {"event": "event8"}, ], } + if domain == "script": + sun_config = {"sequence": sun_config["action"]} - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) + if domain == "automation": + assert await async_setup_component(hass, domain, {domain: [sun_config]}) + else: + assert await async_setup_component(hass, domain, {domain: {"sun": sun_config}}) client = await hass_ws_client() @@ -530,7 +565,7 @@ async def test_automation_breakpoints(hass, hass_ws_client): { "id": next_id(), "type": "trace/debug/breakpoint/set", - "domain": "automation", + "domain": domain, "item_id": "sun", "node": "1", } @@ -554,9 +589,9 @@ async def test_automation_breakpoints(hass, hass_ws_client): { "id": next_id(), "type": "trace/debug/breakpoint/set", - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/1", + "node": f"{prefix}/1", } ) response = await client.receive_json() @@ -565,9 +600,9 @@ async def test_automation_breakpoints(hass, hass_ws_client): { "id": next_id(), "type": "trace/debug/breakpoint/set", - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/5", + "node": f"{prefix}/5", } ) response = await client.receive_json() @@ -579,30 +614,23 @@ async def test_automation_breakpoints(hass, hass_ws_client): assert_lists_same( response["result"], [ - { - "node": "action/1", - "run_id": "*", - "domain": "automation", - "item_id": "sun", - }, - { - "node": "action/5", - "run_id": "*", - "domain": "automation", - "item_id": "sun", - }, + {"node": f"{prefix}/1", "run_id": "*", "domain": domain, "item_id": "sun"}, + {"node": f"{prefix}/5", "run_id": "*", "domain": domain, "item_id": "sun"}, ], ) - # Trigger "sun" automation - hass.bus.async_fire("test_event") + # Trigger "sun" automation / run "sun" script + if domain == "automation": + hass.bus.async_fire("test_event") + else: + await hass.services.async_call("script", "sun") response = await client.receive_json() - run_id = await assert_last_action("sun", "action/1", "running") + run_id = await assert_last_action("sun", f"{prefix}/1", "running") assert response["event"] == { - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/1", + "node": f"{prefix}/1", "run_id": run_id, } @@ -610,7 +638,7 @@ async def test_automation_breakpoints(hass, hass_ws_client): { "id": next_id(), "type": "trace/debug/step", - "domain": "automation", + "domain": domain, "item_id": "sun", "run_id": run_id, } @@ -619,11 +647,11 @@ async def test_automation_breakpoints(hass, hass_ws_client): assert response["success"] response = await client.receive_json() - run_id = await assert_last_action("sun", "action/2", "running") + run_id = await assert_last_action("sun", f"{prefix}/2", "running") assert response["event"] == { - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/2", + "node": f"{prefix}/2", "run_id": run_id, } @@ -631,7 +659,7 @@ async def test_automation_breakpoints(hass, hass_ws_client): { "id": next_id(), "type": "trace/debug/continue", - "domain": "automation", + "domain": domain, "item_id": "sun", "run_id": run_id, } @@ -640,11 +668,11 @@ async def test_automation_breakpoints(hass, hass_ws_client): assert response["success"] response = await client.receive_json() - run_id = await assert_last_action("sun", "action/5", "running") + run_id = await assert_last_action("sun", f"{prefix}/5", "running") assert response["event"] == { - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/5", + "node": f"{prefix}/5", "run_id": run_id, } @@ -652,7 +680,7 @@ async def test_automation_breakpoints(hass, hass_ws_client): { "id": next_id(), "type": "trace/debug/stop", - "domain": "automation", + "domain": domain, "item_id": "sun", "run_id": run_id, } @@ -660,10 +688,13 @@ async def test_automation_breakpoints(hass, hass_ws_client): response = await client.receive_json() assert response["success"] await hass.async_block_till_done() - await assert_last_action("sun", "action/5", "stopped") + await assert_last_action("sun", f"{prefix}/5", "stopped") -async def test_automation_breakpoints_2(hass, hass_ws_client): +@pytest.mark.parametrize( + "domain, prefix", [("automation", "action"), ("script", "sequence")] +) +async def test_breakpoints_2(hass, hass_ws_client, domain, prefix): """Test execution resumes and breakpoints are removed after subscription removed.""" id = 1 @@ -676,7 +707,7 @@ async def test_automation_breakpoints_2(hass, hass_ws_client): await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - trace = _find_traces_for_automation(response["result"], item_id)[-1] + trace = _find_traces(response["result"], domain, item_id)[-1] assert trace["last_action"] == expected_action assert trace["state"] == expected_state return trace["run_id"] @@ -696,19 +727,13 @@ async def test_automation_breakpoints_2(hass, hass_ws_client): {"event": "event8"}, ], } + if domain == "script": + sun_config = {"sequence": sun_config["action"]} - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) + if domain == "automation": + assert await async_setup_component(hass, domain, {domain: [sun_config]}) + else: + assert await async_setup_component(hass, domain, {domain: {"sun": sun_config}}) client = await hass_ws_client() @@ -723,23 +748,26 @@ async def test_automation_breakpoints_2(hass, hass_ws_client): { "id": next_id(), "type": "trace/debug/breakpoint/set", - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/1", + "node": f"{prefix}/1", } ) response = await client.receive_json() assert response["success"] - # Trigger "sun" automation - hass.bus.async_fire("test_event") + # Trigger "sun" automation / run "sun" script + if domain == "automation": + hass.bus.async_fire("test_event") + else: + await hass.services.async_call("script", "sun") response = await client.receive_json() - run_id = await assert_last_action("sun", "action/1", "running") + run_id = await assert_last_action("sun", f"{prefix}/1", "running") assert response["event"] == { - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/1", + "node": f"{prefix}/1", "run_id": run_id, } @@ -750,14 +778,14 @@ async def test_automation_breakpoints_2(hass, hass_ws_client): response = await client.receive_json() assert response["success"] await hass.async_block_till_done() - await assert_last_action("sun", "action/8", "stopped") + await assert_last_action("sun", f"{prefix}/8", "stopped") # Should not be possible to set breakpoints await client.send_json( { "id": next_id(), "type": "trace/debug/breakpoint/set", - "domain": "automation", + "domain": domain, "item_id": "sun", "node": "1", } @@ -765,15 +793,21 @@ async def test_automation_breakpoints_2(hass, hass_ws_client): response = await client.receive_json() assert not response["success"] - # Trigger "sun" automation, should finish without stopping on breakpoints - hass.bus.async_fire("test_event") + # Trigger "sun" automation / script, should finish without stopping on breakpoints + if domain == "automation": + hass.bus.async_fire("test_event") + else: + await hass.services.async_call("script", "sun") await hass.async_block_till_done() - new_run_id = await assert_last_action("sun", "action/8", "stopped") + new_run_id = await assert_last_action("sun", f"{prefix}/8", "stopped") assert new_run_id != run_id -async def test_automation_breakpoints_3(hass, hass_ws_client): +@pytest.mark.parametrize( + "domain, prefix", [("automation", "action"), ("script", "sequence")] +) +async def test_breakpoints_3(hass, hass_ws_client, domain, prefix): """Test breakpoints can be cleared.""" id = 1 @@ -786,7 +820,7 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - trace = _find_traces_for_automation(response["result"], item_id)[-1] + trace = _find_traces(response["result"], domain, item_id)[-1] assert trace["last_action"] == expected_action assert trace["state"] == expected_state return trace["run_id"] @@ -806,19 +840,13 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): {"event": "event8"}, ], } + if domain == "script": + sun_config = {"sequence": sun_config["action"]} - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) + if domain == "automation": + assert await async_setup_component(hass, domain, {domain: [sun_config]}) + else: + assert await async_setup_component(hass, domain, {domain: {"sun": sun_config}}) client = await hass_ws_client() @@ -833,9 +861,9 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): { "id": next_id(), "type": "trace/debug/breakpoint/set", - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/1", + "node": f"{prefix}/1", } ) response = await client.receive_json() @@ -845,23 +873,26 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): { "id": next_id(), "type": "trace/debug/breakpoint/set", - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/5", + "node": f"{prefix}/5", } ) response = await client.receive_json() assert response["success"] - # Trigger "sun" automation - hass.bus.async_fire("test_event") + # Trigger "sun" automation / run "sun" script + if domain == "automation": + hass.bus.async_fire("test_event") + else: + await hass.services.async_call("script", "sun") response = await client.receive_json() - run_id = await assert_last_action("sun", "action/1", "running") + run_id = await assert_last_action("sun", f"{prefix}/1", "running") assert response["event"] == { - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/1", + "node": f"{prefix}/1", "run_id": run_id, } @@ -869,7 +900,7 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): { "id": next_id(), "type": "trace/debug/continue", - "domain": "automation", + "domain": domain, "item_id": "sun", "run_id": run_id, } @@ -878,11 +909,11 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): assert response["success"] response = await client.receive_json() - run_id = await assert_last_action("sun", "action/5", "running") + run_id = await assert_last_action("sun", f"{prefix}/5", "running") assert response["event"] == { - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/5", + "node": f"{prefix}/5", "run_id": run_id, } @@ -890,7 +921,7 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): { "id": next_id(), "type": "trace/debug/stop", - "domain": "automation", + "domain": domain, "item_id": "sun", "run_id": run_id, } @@ -898,29 +929,32 @@ async def test_automation_breakpoints_3(hass, hass_ws_client): response = await client.receive_json() assert response["success"] await hass.async_block_till_done() - await assert_last_action("sun", "action/5", "stopped") + await assert_last_action("sun", f"{prefix}/5", "stopped") # Clear 1st breakpoint await client.send_json( { "id": next_id(), "type": "trace/debug/breakpoint/clear", - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/1", + "node": f"{prefix}/1", } ) response = await client.receive_json() assert response["success"] - # Trigger "sun" automation - hass.bus.async_fire("test_event") + # Trigger "sun" automation / run "sun" script + if domain == "automation": + hass.bus.async_fire("test_event") + else: + await hass.services.async_call("script", "sun") response = await client.receive_json() - run_id = await assert_last_action("sun", "action/5", "running") + run_id = await assert_last_action("sun", f"{prefix}/5", "running") assert response["event"] == { - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/5", + "node": f"{prefix}/5", "run_id": run_id, } From a9ccba44ed3443e65892c18f266abf38c52bcc7d Mon Sep 17 00:00:00 2001 From: scyto Date: Wed, 24 Mar 2021 11:46:11 -0700 Subject: [PATCH 587/831] Add support for Roomba 980 discovery (#47696) Co-authored-by: J. Nick Koston --- .../components/roomba/config_flow.py | 2 +- homeassistant/components/roomba/manifest.json | 12 ++++- homeassistant/generated/dhcp.py | 5 ++ tests/components/roomba/test_config_flow.py | 48 ++++++++++++++----- 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 787382ed8b5..c7614de20ac 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -80,7 +80,7 @@ class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if self._async_host_already_configured(dhcp_discovery[IP_ADDRESS]): return self.async_abort(reason="already_configured") - if not dhcp_discovery[HOSTNAME].startswith("iRobot-"): + if not dhcp_discovery[HOSTNAME].startswith(("iRobot-", "Roomba-")): return self.async_abort(reason="not_irobot_device") blid = _async_blid_from_hostname(dhcp_discovery[HOSTNAME]) diff --git a/homeassistant/components/roomba/manifest.json b/homeassistant/components/roomba/manifest.json index 5ceb44ff780..d1858a46fdc 100644 --- a/homeassistant/components/roomba/manifest.json +++ b/homeassistant/components/roomba/manifest.json @@ -5,5 +5,15 @@ "documentation": "https://www.home-assistant.io/integrations/roomba", "requirements": ["roombapy==1.6.2"], "codeowners": ["@pschmitt", "@cyr-ius", "@shenxn"], - "dhcp": [{"hostname":"irobot-*","macaddress":"501479*"}] + "dhcp": [ + { + "hostname" : "irobot-*", + "macaddress" : "501479*" + }, + { + "hostname" : "roomba-*", + "macaddress" : "80A589*" + } + ] } + diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 90d846377c6..b10893230db 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -119,6 +119,11 @@ DHCP = [ "hostname": "irobot-*", "macaddress": "501479*" }, + { + "domain": "roomba", + "hostname": "roomba-*", + "macaddress": "80A589*" + }, { "domain": "screenlogic", "hostname": "pentair: *", diff --git a/tests/components/roomba/test_config_flow.py b/tests/components/roomba/test_config_flow.py index b597717e4a8..9313220f2a8 100644 --- a/tests/components/roomba/test_config_flow.py +++ b/tests/components/roomba/test_config_flow.py @@ -1,6 +1,7 @@ """Test the iRobot Roomba config flow.""" from unittest.mock import MagicMock, PropertyMock, patch +import pytest from roombapy import RoombaConnectionError from roombapy.roomba import RoombaInfo @@ -12,7 +13,34 @@ from homeassistant.const import CONF_DELAY, CONF_HOST, CONF_PASSWORD from tests.common import MockConfigEntry MOCK_IP = "1.2.3.4" -VALID_CONFIG = {CONF_HOST: "1.2.3.4", CONF_BLID: "blid", CONF_PASSWORD: "password"} +VALID_CONFIG = {CONF_HOST: MOCK_IP, CONF_BLID: "blid", CONF_PASSWORD: "password"} + +DHCP_DISCOVERY_DEVICES = [ + { + IP_ADDRESS: MOCK_IP, + MAC_ADDRESS: "50:14:79:DD:EE:FF", + HOSTNAME: "iRobot-blid", + }, + { + IP_ADDRESS: MOCK_IP, + MAC_ADDRESS: "80:A5:89:DD:EE:FF", + HOSTNAME: "Roomba-blid", + }, +] + + +DHCP_DISCOVERY_DEVICES_WITHOUT_MATCHING_IP = [ + { + IP_ADDRESS: "1.1.1.1", + MAC_ADDRESS: "50:14:79:DD:EE:FF", + HOSTNAME: "iRobot-blid", + }, + { + IP_ADDRESS: "1.1.1.1", + MAC_ADDRESS: "80:A5:89:DD:EE:FF", + HOSTNAME: "Roomba-blid", + }, +] def _create_mocked_roomba( @@ -577,7 +605,8 @@ async def test_form_user_discovery_and_password_fetch_gets_connection_refused(ha assert len(mock_setup_entry.mock_calls) == 1 -async def test_dhcp_discovery_and_roomba_discovery_finds(hass): +@pytest.mark.parametrize("discovery_data", DHCP_DISCOVERY_DEVICES) +async def test_dhcp_discovery_and_roomba_discovery_finds(hass, discovery_data): """Test we can process the discovery from dhcp and roomba discovery matches the device.""" await setup.async_setup_component(hass, "persistent_notification", {}) @@ -592,11 +621,7 @@ async def test_dhcp_discovery_and_roomba_discovery_finds(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, - data={ - IP_ADDRESS: MOCK_IP, - MAC_ADDRESS: "AA:BB:CC:DD:EE:FF", - HOSTNAME: "iRobot-blid", - }, + data=discovery_data, ) await hass.async_block_till_done() @@ -637,7 +662,8 @@ async def test_dhcp_discovery_and_roomba_discovery_finds(hass): assert len(mock_setup_entry.mock_calls) == 1 -async def test_dhcp_discovery_falls_back_to_manual(hass): +@pytest.mark.parametrize("discovery_data", DHCP_DISCOVERY_DEVICES_WITHOUT_MATCHING_IP) +async def test_dhcp_discovery_falls_back_to_manual(hass, discovery_data): """Test we can process the discovery from dhcp but roomba discovery cannot find the device.""" await setup.async_setup_component(hass, "persistent_notification", {}) @@ -652,11 +678,7 @@ async def test_dhcp_discovery_falls_back_to_manual(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, - data={ - IP_ADDRESS: "1.1.1.1", - MAC_ADDRESS: "AA:BB:CC:DD:EE:FF", - HOSTNAME: "iRobot-blid", - }, + data=discovery_data, ) await hass.async_block_till_done() From 6fc3406c938eb6f88ea057603a9346ec2aba3721 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 24 Mar 2021 20:05:53 +0100 Subject: [PATCH 588/831] Ignore python-typing-update for pre-commit requirements (#48292) --- requirements_test_pre_commit.txt | 1 - script/gen_requirements_all.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index b8fabc685b1..6a5768d34a1 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -8,6 +8,5 @@ flake8-docstrings==1.5.0 flake8==3.8.4 isort==5.7.0 pydocstyle==5.1.1 -python-typing-update==0.3.0 pyupgrade==2.11.0 yamllint==1.24.2 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 94365be9a50..ac1e4bc2ed9 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -96,6 +96,7 @@ IGNORE_PRE_COMMIT_HOOK_ID = ( "check-json", "no-commit-to-branch", "prettier", + "python-typing-update", ) From cc12d29f6dc7de026e672a2f834cdba0ae569509 Mon Sep 17 00:00:00 2001 From: djtimca <60706061+djtimca@users.noreply.github.com> Date: Wed, 24 Mar 2021 17:58:03 -0400 Subject: [PATCH 589/831] Bump omnilogic to 0.4.3 to fix API certificate issue (#48296) * Bump omnilogic to 0.4.3 to fix API certificate issue. * Updated requirements files. --- homeassistant/components/omnilogic/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/omnilogic/manifest.json b/homeassistant/components/omnilogic/manifest.json index d999a34f076..2b2a4a9fe3d 100644 --- a/homeassistant/components/omnilogic/manifest.json +++ b/homeassistant/components/omnilogic/manifest.json @@ -3,6 +3,6 @@ "name": "Hayward Omnilogic", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/omnilogic", - "requirements": ["omnilogic==0.4.2"], + "requirements": ["omnilogic==0.4.3"], "codeowners": ["@oliver84","@djtimca","@gentoosu"] } diff --git a/requirements_all.txt b/requirements_all.txt index 56459895f1d..531c91bb0c8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1028,7 +1028,7 @@ objgraph==3.4.1 oemthermostat==1.1.1 # homeassistant.components.omnilogic -omnilogic==0.4.2 +omnilogic==0.4.3 # homeassistant.components.ondilo_ico ondilo==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f9d3520dcf6..89c19c1bdf5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -531,7 +531,7 @@ oauth2client==4.0.0 objgraph==3.4.1 # homeassistant.components.omnilogic -omnilogic==0.4.2 +omnilogic==0.4.3 # homeassistant.components.ondilo_ico ondilo==0.2.0 From c340a39275c2eb4ffe9a410f207fca303cdb92aa Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 24 Mar 2021 12:33:44 -1000 Subject: [PATCH 590/831] Handle range conversions that do not start at 1 (#48298) --- homeassistant/util/percentage.py | 6 ++++-- tests/util/test_percentage.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/homeassistant/util/percentage.py b/homeassistant/util/percentage.py index c257ca2268c..ec05a2dc2ec 100644 --- a/homeassistant/util/percentage.py +++ b/homeassistant/util/percentage.py @@ -66,7 +66,8 @@ def ranged_value_to_percentage( (1,255), 127: 50 (1,255), 10: 4 """ - return int((value * 100) // states_in_range(low_high_range)) + offset = low_high_range[0] - 1 + return int(((value - offset) * 100) // states_in_range(low_high_range)) def percentage_to_ranged_value( @@ -83,7 +84,8 @@ def percentage_to_ranged_value( (1,255), 50: 127.5 (1,255), 4: 10.2 """ - return states_in_range(low_high_range) * percentage / 100 + offset = low_high_range[0] - 1 + return states_in_range(low_high_range) * percentage / 100 + offset def states_in_range(low_high_range: tuple[float, float]) -> float: diff --git a/tests/util/test_percentage.py b/tests/util/test_percentage.py index 31420d3c076..37e4c6d9615 100644 --- a/tests/util/test_percentage.py +++ b/tests/util/test_percentage.py @@ -147,3 +147,34 @@ async def test_percentage_to_ranged_value_small(): assert math.ceil(percentage_to_ranged_value(range, 66)) == 4 assert math.ceil(percentage_to_ranged_value(range, 83)) == 5 assert math.ceil(percentage_to_ranged_value(range, 100)) == 6 + + +async def test_ranged_value_to_percentage_starting_at_one(): + """Test a range that starts with 1.""" + range = (1, 4) + + assert ranged_value_to_percentage(range, 1) == 25 + assert ranged_value_to_percentage(range, 2) == 50 + assert ranged_value_to_percentage(range, 3) == 75 + assert ranged_value_to_percentage(range, 4) == 100 + + +async def test_ranged_value_to_percentage_starting_high(): + """Test a range that does not start with 1.""" + range = (101, 255) + + assert ranged_value_to_percentage(range, 101) == 0 + assert ranged_value_to_percentage(range, 139) == 25 + assert ranged_value_to_percentage(range, 178) == 50 + assert ranged_value_to_percentage(range, 217) == 75 + assert ranged_value_to_percentage(range, 255) == 100 + + +async def test_ranged_value_to_percentage_starting_zero(): + """Test a range that starts with 0.""" + range = (0, 3) + + assert ranged_value_to_percentage(range, 0) == 25 + assert ranged_value_to_percentage(range, 1) == 50 + assert ranged_value_to_percentage(range, 2) == 75 + assert ranged_value_to_percentage(range, 3) == 100 From 6660fb74787b6cbdfe3abc796bb71fe9d7fffe0f Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 25 Mar 2021 00:03:36 +0000 Subject: [PATCH 591/831] [ci skip] Translation update --- .../components/august/translations/fr.json | 7 +++- .../components/august/translations/pl.json | 16 ++++++++ .../components/homekit/translations/fr.json | 2 +- .../components/hyperion/translations/fr.json | 1 + .../components/hyperion/translations/pl.json | 1 + .../opentherm_gw/translations/pl.json | 4 +- .../philips_js/translations/fr.json | 3 ++ .../philips_js/translations/pl.json | 7 ++++ .../screenlogic/translations/fr.json | 6 +++ .../screenlogic/translations/pl.json | 39 +++++++++++++++++++ .../components/verisure/translations/fr.json | 7 +++- 11 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 homeassistant/components/screenlogic/translations/pl.json diff --git a/homeassistant/components/august/translations/fr.json b/homeassistant/components/august/translations/fr.json index 530fda4dc9f..967fb249d97 100644 --- a/homeassistant/components/august/translations/fr.json +++ b/homeassistant/components/august/translations/fr.json @@ -11,6 +11,9 @@ }, "step": { "reauth_validate": { + "data": { + "password": "Mot de passe" + }, "description": "Saisissez le mot de passe de {username} .", "title": "R\u00e9authentifier un compte August" }, @@ -26,7 +29,9 @@ }, "user_validate": { "data": { - "login_method": "M\u00e9thode de connexion" + "login_method": "M\u00e9thode de connexion", + "password": "Mot de passe", + "username": "Nom d'utilisateur" }, "description": "Si la m\u00e9thode de connexion est \u00abemail\u00bb, le nom d'utilisateur est l'adresse e-mail. Si la m\u00e9thode de connexion est \u00abt\u00e9l\u00e9phone\u00bb, le nom d'utilisateur est le num\u00e9ro de t\u00e9l\u00e9phone au format \u00ab+ NNNNNNNNN\u00bb.", "title": "Cr\u00e9er un compte August" diff --git a/homeassistant/components/august/translations/pl.json b/homeassistant/components/august/translations/pl.json index e76b663c307..a5539bea93a 100644 --- a/homeassistant/components/august/translations/pl.json +++ b/homeassistant/components/august/translations/pl.json @@ -10,6 +10,13 @@ "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { + "reauth_validate": { + "data": { + "password": "Has\u0142o" + }, + "description": "Wprowad\u017a has\u0142o dla {username}", + "title": "Ponownie uwierzytelnij konto August" + }, "user": { "data": { "login_method": "Metoda logowania", @@ -20,6 +27,15 @@ "description": "Je\u015bli metod\u0105 logowania jest 'e-mail', nazw\u0105 u\u017cytkownika b\u0119dzie adres e-mail. Je\u015bli metod\u0105 logowania jest 'telefon', nazw\u0105 u\u017cytkownika b\u0119dzie numer telefonu w formacie '+NNNNNNNNN'.", "title": "Konfiguracja konta August" }, + "user_validate": { + "data": { + "login_method": "Metoda logowania", + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Je\u015bli metod\u0105 logowania jest 'e-mail', nazw\u0105 u\u017cytkownika b\u0119dzie adres e-mail. Je\u015bli metod\u0105 logowania jest 'telefon', nazw\u0105 u\u017cytkownika b\u0119dzie numer telefonu w formacie '+NNNNNNNNN'.", + "title": "Konfiguracja konta August" + }, "validation": { "data": { "code": "Kod weryfikacyjny" diff --git a/homeassistant/components/homekit/translations/fr.json b/homeassistant/components/homekit/translations/fr.json index 4721514e615..dae09002c54 100644 --- a/homeassistant/components/homekit/translations/fr.json +++ b/homeassistant/components/homekit/translations/fr.json @@ -55,7 +55,7 @@ "entities": "Entit\u00e9s", "mode": "Mode" }, - "description": "Choisissez les entit\u00e9s \u00e0 exposer. En mode accessoire, une seule entit\u00e9 est expos\u00e9e. En mode d'inclusion de pont, toutes les entit\u00e9s du domaine seront expos\u00e9es \u00e0 moins que des entit\u00e9s sp\u00e9cifiques ne soient s\u00e9lectionn\u00e9es. En mode d'exclusion de pont, toutes les entit\u00e9s du domaine seront expos\u00e9es \u00e0 l'exception des entit\u00e9s exclues.", + "description": "Choisissez les entit\u00e9s \u00e0 inclure. En mode accessoire, une seule entit\u00e9 est incluse. En mode d'inclusion de pont, toutes les entit\u00e9s du domaine seront incluses \u00e0 moins que des entit\u00e9s sp\u00e9cifiques ne soient s\u00e9lectionn\u00e9es. En mode d'exclusion de pont, toutes les entit\u00e9s du domaine seront incluses \u00e0 l'exception des entit\u00e9s exclues. Pour de meilleures performances, un accessoire HomeKit distinct sera cr\u00e9\u00e9 pour chaque lecteur multim\u00e9dia TV et cam\u00e9ra.", "title": "S\u00e9lectionnez les entit\u00e9s \u00e0 exposer" }, "init": { diff --git a/homeassistant/components/hyperion/translations/fr.json b/homeassistant/components/hyperion/translations/fr.json index f69fd6acdc6..57870c3b3ef 100644 --- a/homeassistant/components/hyperion/translations/fr.json +++ b/homeassistant/components/hyperion/translations/fr.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "Effets Hyperion \u00e0 montrer", "priority": "Priorit\u00e9 Hyperion \u00e0 utiliser pour les couleurs et les effets" } } diff --git a/homeassistant/components/hyperion/translations/pl.json b/homeassistant/components/hyperion/translations/pl.json index 33b7c927520..67e89e817f0 100644 --- a/homeassistant/components/hyperion/translations/pl.json +++ b/homeassistant/components/hyperion/translations/pl.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "Efekty Hyperiona do pokazania", "priority": "Hyperion ma pierwsze\u0144stwo w u\u017cyciu dla kolor\u00f3w i efekt\u00f3w" } } diff --git a/homeassistant/components/opentherm_gw/translations/pl.json b/homeassistant/components/opentherm_gw/translations/pl.json index 3fe12393a14..dc06752e404 100644 --- a/homeassistant/components/opentherm_gw/translations/pl.json +++ b/homeassistant/components/opentherm_gw/translations/pl.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "Zaokr\u0105glanie warto\u015bci w d\u00f3\u0142", - "precision": "Precyzja" + "precision": "Precyzja", + "read_precision": "Odczytaj precyzj\u0119", + "set_precision": "Ustaw precyzj\u0119" }, "description": "Opcje dla bramki OpenTherm" } diff --git a/homeassistant/components/philips_js/translations/fr.json b/homeassistant/components/philips_js/translations/fr.json index 326b81dc6a3..86d2b2dfba3 100644 --- a/homeassistant/components/philips_js/translations/fr.json +++ b/homeassistant/components/philips_js/translations/fr.json @@ -11,6 +11,9 @@ }, "step": { "pair": { + "data": { + "pin": "Code PIN" + }, "description": "Entrez le code PIN affich\u00e9 sur votre t\u00e9l\u00e9viseur", "title": "Paire" }, diff --git a/homeassistant/components/philips_js/translations/pl.json b/homeassistant/components/philips_js/translations/pl.json index 4fc1fbc5269..a89b6136ff8 100644 --- a/homeassistant/components/philips_js/translations/pl.json +++ b/homeassistant/components/philips_js/translations/pl.json @@ -10,6 +10,13 @@ "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { + "pair": { + "data": { + "pin": "Kod PIN" + }, + "description": "Wprowad\u017a kod PIN wy\u015bwietlony na Twoim telewizorze", + "title": "Paruj" + }, "user": { "data": { "api_version": "Wersja API", diff --git a/homeassistant/components/screenlogic/translations/fr.json b/homeassistant/components/screenlogic/translations/fr.json index d651f4f1c98..968045e0597 100644 --- a/homeassistant/components/screenlogic/translations/fr.json +++ b/homeassistant/components/screenlogic/translations/fr.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion" + }, "flow_title": "ScreenLogic {nom}", "step": { "gateway_entry": { diff --git a/homeassistant/components/screenlogic/translations/pl.json b/homeassistant/components/screenlogic/translations/pl.json new file mode 100644 index 00000000000..64e2573ddb0 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/pl.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "Adres IP", + "port": "Port" + }, + "description": "Wprowad\u017a informacje o bramce ScreenLogic.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "Bramka" + }, + "description": "Wykryto nast\u0119puj\u0105ce bramki ScreenLogic. Wybierz jedn\u0105 do skonfigurowania lub wybierz opcj\u0119 r\u0119cznej konfiguracji bramki ScreenLogic.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji" + }, + "description": "Okre\u015bl ustawienia dla {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/fr.json b/homeassistant/components/verisure/translations/fr.json index 1991120ab8f..47114049dfb 100644 --- a/homeassistant/components/verisure/translations/fr.json +++ b/homeassistant/components/verisure/translations/fr.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { "invalid_auth": "Authentification invalide", @@ -16,7 +17,9 @@ }, "reauth_confirm": { "data": { - "description": "R\u00e9-authentifiez-vous avec votre compte Verisure My Pages." + "description": "R\u00e9-authentifiez-vous avec votre compte Verisure My Pages.", + "email": "Email", + "password": "Mot de passe" } }, "user": { From 058d232c57856c9baae402c1fb6d8b79bf710eed Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Wed, 24 Mar 2021 20:08:16 -0400 Subject: [PATCH 592/831] Determine zwave_js sensor device class during initialization (#48304) --- homeassistant/components/zwave_js/sensor.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index 574e1af658c..52a81a26eb9 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -80,10 +80,15 @@ class ZwaveSensorBase(ZWaveBaseEntity, SensorEntity): """Initialize a ZWaveSensorBase entity.""" super().__init__(config_entry, client, info) self._name = self.generate_name(include_value_name=True) + self._device_class = self._get_device_class() - @property - def device_class(self) -> str | None: - """Return the device class of the sensor.""" + def _get_device_class(self) -> str | None: + """ + Get the device class of the sensor. + + This should be run once during initialization so we don't have to calculate + this value on every state update. + """ if self.info.primary_value.command_class == CommandClass.BATTERY: return DEVICE_CLASS_BATTERY if self.info.primary_value.command_class == CommandClass.METER: @@ -102,6 +107,11 @@ class ZwaveSensorBase(ZWaveBaseEntity, SensorEntity): return DEVICE_CLASS_ILLUMINANCE return None + @property + def device_class(self) -> str | None: + """Return the device class of the sensor.""" + return self._device_class + @property def entity_registry_enabled_default(self) -> bool: """Return if the entity should be enabled when first added to the entity registry.""" From da7fd8a2946956bdfddee5d054dc5758a803164f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 24 Mar 2021 15:04:55 -1000 Subject: [PATCH 593/831] Listen on the default interface by default for zeroconf (#48302) --- homeassistant/components/zeroconf/__init__.py | 2 +- homeassistant/components/zeroconf/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 6adbe07d867..58dbe2125c1 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -56,7 +56,7 @@ HOMEKIT_TYPES = [ CONF_DEFAULT_INTERFACE = "default_interface" CONF_IPV6 = "ipv6" -DEFAULT_DEFAULT_INTERFACE = False +DEFAULT_DEFAULT_INTERFACE = True DEFAULT_IPV6 = True HOMEKIT_PROPERTIES = "properties" diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index 654eec820c3..d407acece57 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -2,7 +2,7 @@ "domain": "zeroconf", "name": "Zero-configuration networking (zeroconf)", "documentation": "https://www.home-assistant.io/integrations/zeroconf", - "requirements": ["zeroconf==0.28.8"], + "requirements": ["zeroconf==0.29.0"], "dependencies": ["api"], "codeowners": ["@bdraco"], "quality_scale": "internal" diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 8502fa2c22d..86269b9da06 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -32,7 +32,7 @@ sqlalchemy==1.3.23 voluptuous-serialize==2.4.0 voluptuous==0.12.1 yarl==1.6.3 -zeroconf==0.28.8 +zeroconf==0.29.0 pycryptodome>=3.6.6 diff --git a/requirements_all.txt b/requirements_all.txt index 531c91bb0c8..d8c80328947 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2363,7 +2363,7 @@ zeep[async]==4.0.0 zengge==0.2 # homeassistant.components.zeroconf -zeroconf==0.28.8 +zeroconf==0.29.0 # homeassistant.components.zha zha-quirks==0.0.54 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 89c19c1bdf5..9023a3278f3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1215,7 +1215,7 @@ yeelight==0.5.4 zeep[async]==4.0.0 # homeassistant.components.zeroconf -zeroconf==0.28.8 +zeroconf==0.29.0 # homeassistant.components.zha zha-quirks==0.0.54 From 20485eb13245837fd5497b98ccd89e18b25491cc Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 25 Mar 2021 02:41:21 -0500 Subject: [PATCH 594/831] Bump plexapi to 4.5.1 (#48307) --- homeassistant/components/plex/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index 647590f7cf2..9410fba1258 100644 --- a/homeassistant/components/plex/manifest.json +++ b/homeassistant/components/plex/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/plex", "requirements": [ - "plexapi==4.5.0", + "plexapi==4.5.1", "plexauth==0.0.6", "plexwebsocket==0.0.12" ], diff --git a/requirements_all.txt b/requirements_all.txt index d8c80328947..cf5073bdfba 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1131,7 +1131,7 @@ pillow==8.1.2 pizzapi==0.0.3 # homeassistant.components.plex -plexapi==4.5.0 +plexapi==4.5.1 # homeassistant.components.plex plexauth==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9023a3278f3..dfe1e812cb5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -577,7 +577,7 @@ pilight==0.1.1 pillow==8.1.2 # homeassistant.components.plex -plexapi==4.5.0 +plexapi==4.5.1 # homeassistant.components.plex plexauth==0.0.6 From 642bb91a9a905f1b8b19ddce911831ac9277f6c4 Mon Sep 17 00:00:00 2001 From: Boris Gulay Date: Thu, 25 Mar 2021 11:18:10 +0300 Subject: [PATCH 595/831] Add metrics upload by UDP to graphite (#43751) --- homeassistant/components/graphite/__init__.py | 48 ++++++++++++------- tests/components/graphite/test_init.py | 19 ++++++-- 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/graphite/__init__.py b/homeassistant/components/graphite/__init__.py index ccb7044ad73..9405b576b4d 100644 --- a/homeassistant/components/graphite/__init__.py +++ b/homeassistant/components/graphite/__init__.py @@ -12,6 +12,7 @@ from homeassistant.const import ( CONF_HOST, CONF_PORT, CONF_PREFIX, + CONF_PROTOCOL, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, @@ -21,8 +22,11 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) +PROTOCOL_TCP = "tcp" +PROTOCOL_UDP = "udp" DEFAULT_HOST = "localhost" DEFAULT_PORT = 2003 +DEFAULT_PROTOCOL = PROTOCOL_TCP DEFAULT_PREFIX = "ha" DOMAIN = "graphite" @@ -32,6 +36,9 @@ CONFIG_SCHEMA = vol.Schema( { vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.Any( + PROTOCOL_TCP, PROTOCOL_UDP + ), vol.Optional(CONF_PREFIX, default=DEFAULT_PREFIX): cv.string, } ) @@ -46,29 +53,34 @@ def setup(hass, config): host = conf.get(CONF_HOST) prefix = conf.get(CONF_PREFIX) port = conf.get(CONF_PORT) + protocol = conf.get(CONF_PROTOCOL) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - sock.connect((host, port)) - sock.shutdown(2) - _LOGGER.debug("Connection to Graphite possible") - except OSError: - _LOGGER.error("Not able to connect to Graphite") - return False + if protocol == PROTOCOL_TCP: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + sock.connect((host, port)) + sock.shutdown(2) + _LOGGER.debug("Connection to Graphite possible") + except OSError: + _LOGGER.error("Not able to connect to Graphite") + return False + else: + _LOGGER.debug("No connection check for UDP possible") - GraphiteFeeder(hass, host, port, prefix) + GraphiteFeeder(hass, host, port, protocol, prefix) return True class GraphiteFeeder(threading.Thread): """Feed data to Graphite.""" - def __init__(self, hass, host, port, prefix): + def __init__(self, hass, host, port, protocol, prefix): """Initialize the feeder.""" super().__init__(daemon=True) self._hass = hass self._host = host self._port = port + self._protocol = protocol # rstrip any trailing dots in case they think they need it self._prefix = prefix.rstrip(".") self._queue = queue.Queue() @@ -101,12 +113,16 @@ class GraphiteFeeder(threading.Thread): def _send_to_graphite(self, data): """Send data to Graphite.""" - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.settimeout(10) - sock.connect((self._host, self._port)) - sock.sendall(data.encode("ascii")) - sock.send(b"\n") - sock.close() + if self._protocol == PROTOCOL_TCP: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(10) + sock.connect((self._host, self._port)) + sock.sendall(data.encode("ascii")) + sock.send(b"\n") + sock.close() + else: + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.sendto(data.encode("ascii") + b"\n", (self._host, self._port)) def _report_attributes(self, entity_id, new_state): """Report the attributes.""" diff --git a/tests/components/graphite/test_init.py b/tests/components/graphite/test_init.py index 88be3723936..b7f5071813e 100644 --- a/tests/components/graphite/test_init.py +++ b/tests/components/graphite/test_init.py @@ -24,7 +24,7 @@ class TestGraphite(unittest.TestCase): def setup_method(self, method): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.gf = graphite.GraphiteFeeder(self.hass, "foo", 123, "ha") + self.gf = graphite.GraphiteFeeder(self.hass, "foo", 123, "tcp", "ha") def teardown_method(self, method): """Stop everything that was started.""" @@ -45,10 +45,23 @@ class TestGraphite(unittest.TestCase): assert setup_component(self.hass, graphite.DOMAIN, config) assert mock_gf.call_count == 1 - assert mock_gf.call_args == mock.call(self.hass, "foo", 123, "me") + assert mock_gf.call_args == mock.call(self.hass, "foo", 123, "tcp", "me") assert mock_socket.call_count == 1 assert mock_socket.call_args == mock.call(socket.AF_INET, socket.SOCK_STREAM) + @patch("socket.socket") + @patch("homeassistant.components.graphite.GraphiteFeeder") + def test_full_udp_config(self, mock_gf, mock_socket): + """Test setup with full configuration and UDP protocol.""" + config = { + "graphite": {"host": "foo", "port": 123, "protocol": "udp", "prefix": "me"} + } + + assert setup_component(self.hass, graphite.DOMAIN, config) + assert mock_gf.call_count == 1 + assert mock_gf.call_args == mock.call(self.hass, "foo", 123, "udp", "me") + assert mock_socket.call_count == 0 + @patch("socket.socket") @patch("homeassistant.components.graphite.GraphiteFeeder") def test_config_port(self, mock_gf, mock_socket): @@ -63,7 +76,7 @@ class TestGraphite(unittest.TestCase): def test_subscribe(self): """Test the subscription.""" fake_hass = mock.MagicMock() - gf = graphite.GraphiteFeeder(fake_hass, "foo", 123, "ha") + gf = graphite.GraphiteFeeder(fake_hass, "foo", 123, "tcp", "ha") fake_hass.bus.listen_once.has_calls( [ mock.call(EVENT_HOMEASSISTANT_START, gf.start_listen), From 21c72fa55935e6971c032ec15b712a1fd10c4cd6 Mon Sep 17 00:00:00 2001 From: Zixuan Wang Date: Thu, 25 Mar 2021 01:19:11 -0700 Subject: [PATCH 596/831] Fix missing glances temperature sensors (#46086) * Fix missing glances temperature sensors (#44899) * Revert matching rules for Glances * Shorter if statement Co-authored-by: J. Nick Koston * Revert long-line if statement * Update if statement Co-authored-by: J. Nick Koston --- homeassistant/components/glances/const.py | 7 ++++--- homeassistant/components/glances/sensor.py | 7 +++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/glances/const.py b/homeassistant/components/glances/const.py index 69e4ce0c016..18865a232d7 100644 --- a/homeassistant/components/glances/const.py +++ b/homeassistant/components/glances/const.py @@ -36,9 +36,10 @@ SENSOR_TYPES = { "process_thread": ["processcount", "Thread", "Count", CPU_ICON], "process_sleeping": ["processcount", "Sleeping", "Count", CPU_ICON], "cpu_use_percent": ["cpu", "CPU used", PERCENTAGE, CPU_ICON], - "temperature_core": ["sensors", "temperature", TEMP_CELSIUS, "mdi:thermometer"], - "fan_speed": ["sensors", "fan speed", "RPM", "mdi:fan"], - "battery": ["sensors", "charge", PERCENTAGE, "mdi:battery"], + "temperature_core": ["sensors", "Temperature", TEMP_CELSIUS, "mdi:thermometer"], + "temperature_hdd": ["sensors", "Temperature", TEMP_CELSIUS, "mdi:thermometer"], + "fan_speed": ["sensors", "Fan speed", "RPM", "mdi:fan"], + "battery": ["sensors", "Charge", PERCENTAGE, "mdi:battery"], "docker_active": ["docker", "Containers active", "", "mdi:docker"], "docker_cpu_use": ["docker", "Containers CPU used", PERCENTAGE, "mdi:docker"], "docker_memory_use": [ diff --git a/homeassistant/components/glances/sensor.py b/homeassistant/components/glances/sensor.py index 3e663621625..52649518b68 100644 --- a/homeassistant/components/glances/sensor.py +++ b/homeassistant/components/glances/sensor.py @@ -172,6 +172,13 @@ class GlancesSensor(SensorEntity): if sensor["type"] == "temperature_core": if sensor["label"] == self._sensor_name_prefix: self._state = sensor["value"] + elif self.type == "temperature_hdd": + for sensor in value["sensors"]: + if ( + sensor["type"] == "temperature_hdd" + and sensor["label"] == self._sensor_name_prefix + ): + self._state = sensor["value"] elif self.type == "memory_use_percent": self._state = value["mem"]["percent"] elif self.type == "memory_use": From 6b2a2740f173462173f6e711120a56838c1cf6c9 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Thu, 25 Mar 2021 09:47:49 +0100 Subject: [PATCH 597/831] Type check KNX integration climate (#48054) --- homeassistant/components/knx/climate.py | 90 ++++++++++++++++--------- 1 file changed, 57 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py index e90371e3282..1b9fea57541 100644 --- a/homeassistant/components/knx/climate.py +++ b/homeassistant/components/knx/climate.py @@ -1,6 +1,8 @@ """Support for KNX/IP climate devices.""" from __future__ import annotations +from typing import Any, Callable, Iterable + from xknx.devices import Climate as XknxClimate from xknx.dpt.dpt_hvac_mode import HVACControllerMode, HVACOperationMode @@ -13,6 +15,12 @@ from homeassistant.components.climate.const import ( SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) from .const import CONTROLLER_MODES, DOMAIN, PRESET_MODES from .knx_entity import KnxEntity @@ -21,7 +29,12 @@ CONTROLLER_MODES_INV = {value: key for key, value in CONTROLLER_MODES.items()} PRESET_MODES_INV = {value: key for key, value in PRESET_MODES.items()} -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up climate(s) for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: @@ -33,8 +46,9 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KNXClimate(KnxEntity, ClimateEntity): """Representation of a KNX climate device.""" - def __init__(self, device: XknxClimate): + def __init__(self, device: XknxClimate) -> None: """Initialize of a KNX climate device.""" + self._device: XknxClimate super().__init__(device) self._unit_of_measurement = TEMP_CELSIUS @@ -44,42 +58,45 @@ class KNXClimate(KnxEntity, ClimateEntity): """Return the list of supported features.""" return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE - async def async_update(self): + async def async_update(self) -> None: """Request a state update from KNX bus.""" await self._device.sync() - await self._device.mode.sync() + if self._device.mode is not None: + await self._device.mode.sync() @property - def temperature_unit(self): + def temperature_unit(self) -> str: """Return the unit of measurement.""" return self._unit_of_measurement @property - def current_temperature(self): + def current_temperature(self) -> float | None: """Return the current temperature.""" - return self._device.temperature.value + return self._device.temperature.value # type: ignore[no-any-return] @property - def target_temperature_step(self): + def target_temperature_step(self) -> float: """Return the supported step of target temperature.""" return self._device.temperature_step @property - def target_temperature(self): + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" - return self._device.target_temperature.value + return self._device.target_temperature.value # type: ignore[no-any-return] @property - def min_temp(self): + def min_temp(self) -> float: """Return the minimum temperature.""" - return self._device.target_temperature_min + temp = self._device.target_temperature_min + return temp if temp is not None else super().min_temp @property - def max_temp(self): + def max_temp(self) -> float: """Return the maximum temperature.""" - return self._device.target_temperature_max + temp = self._device.target_temperature_max + return temp if temp is not None else super().max_temp - async def async_set_temperature(self, **kwargs) -> None: + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" temperature = kwargs.get(ATTR_TEMPERATURE) if temperature is None: @@ -88,11 +105,11 @@ class KNXClimate(KnxEntity, ClimateEntity): self.async_write_ha_state() @property - def hvac_mode(self) -> str | None: + def hvac_mode(self) -> str: """Return current operation ie. heat, cool, idle.""" if self._device.supports_on_off and not self._device.is_on: return HVAC_MODE_OFF - if self._device.mode.supports_controller_mode: + if self._device.mode is not None and self._device.mode.supports_controller_mode: return CONTROLLER_MODES.get( self._device.mode.controller_mode.value, HVAC_MODE_HEAT ) @@ -100,21 +117,23 @@ class KNXClimate(KnxEntity, ClimateEntity): return HVAC_MODE_HEAT @property - def hvac_modes(self) -> list[str] | None: + def hvac_modes(self) -> list[str]: """Return the list of available operation/controller modes.""" - _controller_modes = [ - CONTROLLER_MODES.get(controller_mode.value) - for controller_mode in self._device.mode.controller_modes - ] + ha_controller_modes: list[str | None] = [] + if self._device.mode is not None: + for knx_controller_mode in self._device.mode.controller_modes: + ha_controller_modes.append( + CONTROLLER_MODES.get(knx_controller_mode.value) + ) if self._device.supports_on_off: - if not _controller_modes: - _controller_modes.append(HVAC_MODE_HEAT) - _controller_modes.append(HVAC_MODE_OFF) + if not ha_controller_modes: + ha_controller_modes.append(HVAC_MODE_HEAT) + ha_controller_modes.append(HVAC_MODE_OFF) - _modes = list(set(filter(None, _controller_modes))) + hvac_modes = list(set(filter(None, ha_controller_modes))) # default to ["heat"] - return _modes if _modes else [HVAC_MODE_HEAT] + return hvac_modes if hvac_modes else [HVAC_MODE_HEAT] async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set operation mode.""" @@ -123,7 +142,10 @@ class KNXClimate(KnxEntity, ClimateEntity): else: if self._device.supports_on_off and not self._device.is_on: await self._device.turn_on() - if self._device.mode.supports_controller_mode: + if ( + self._device.mode is not None + and self._device.mode.supports_controller_mode + ): knx_controller_mode = HVACControllerMode( CONTROLLER_MODES_INV.get(hvac_mode) ) @@ -136,7 +158,7 @@ class KNXClimate(KnxEntity, ClimateEntity): Requires SUPPORT_PRESET_MODE. """ - if self._device.mode.supports_operation_mode: + if self._device.mode is not None and self._device.mode.supports_operation_mode: return PRESET_MODES.get(self._device.mode.operation_mode.value, PRESET_AWAY) return None @@ -146,16 +168,18 @@ class KNXClimate(KnxEntity, ClimateEntity): Requires SUPPORT_PRESET_MODE. """ - _presets = [ + if self._device.mode is None: + return None + + presets = [ PRESET_MODES.get(operation_mode.value) for operation_mode in self._device.mode.operation_modes ] - - return list(filter(None, _presets)) + return list(filter(None, presets)) async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" - if self._device.mode.supports_operation_mode: + if self._device.mode is not None and self._device.mode.supports_operation_mode: knx_operation_mode = HVACOperationMode(PRESET_MODES_INV.get(preset_mode)) await self._device.mode.set_operation_mode(knx_operation_mode) self.async_write_ha_state() From 3188f796f9deed2310ba372b2e8d0ad35dc1e3ef Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 25 Mar 2021 14:06:01 +0100 Subject: [PATCH 598/831] Add allowed UUIDs and ignore CEC to Google Cast options flow (#47269) --- homeassistant/components/cast/__init__.py | 28 +++- homeassistant/components/cast/config_flow.py | 88 +++++++++--- homeassistant/components/cast/const.py | 5 +- homeassistant/components/cast/discovery.py | 7 +- homeassistant/components/cast/media_player.py | 55 ++------ homeassistant/components/cast/strings.json | 4 +- .../components/cast/translations/en.json | 4 +- tests/components/cast/conftest.py | 1 + .../cast/test_home_assistant_cast.py | 6 +- tests/components/cast/test_init.py | 111 +++++++++++++-- tests/components/cast/test_media_player.py | 130 ++++++------------ 11 files changed, 253 insertions(+), 186 deletions(-) diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py index 49cec207764..43b6b77ebd2 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -1,20 +1,42 @@ """Component to embed Google Cast.""" +import logging + +import voluptuous as vol + from homeassistant import config_entries +from homeassistant.helpers import config_validation as cv from . import home_assistant_cast from .const import DOMAIN +from .media_player import ENTITY_SCHEMA + +# Deprecated from 2021.4, remove in 2021.6 +CONFIG_SCHEMA = cv.deprecated(DOMAIN) + +_LOGGER = logging.getLogger(__name__) async def async_setup(hass, config): """Set up the Cast component.""" conf = config.get(DOMAIN) - hass.data[DOMAIN] = conf or {} - if conf is not None: + media_player_config_validated = [] + media_player_config = conf.get("media_player", {}) + if not isinstance(media_player_config, list): + media_player_config = [media_player_config] + for cfg in media_player_config: + try: + cfg = ENTITY_SCHEMA(cfg) + media_player_config_validated.append(cfg) + except vol.Error as ex: + _LOGGER.warning("Invalid config '%s': %s", cfg, ex) + hass.async_create_task( hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_IMPORT} + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=media_player_config_validated, ) ) diff --git a/homeassistant/components/cast/config_flow.py b/homeassistant/components/cast/config_flow.py index 4a4426a5db1..464283e07f3 100644 --- a/homeassistant/components/cast/config_flow.py +++ b/homeassistant/components/cast/config_flow.py @@ -4,9 +4,11 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.helpers import config_validation as cv -from .const import CONF_KNOWN_HOSTS, DOMAIN +from .const import CONF_IGNORE_CEC, CONF_KNOWN_HOSTS, CONF_UUID, DOMAIN +IGNORE_CEC_SCHEMA = vol.Schema(vol.All(cv.ensure_list, [cv.string])) KNOWN_HOSTS_SCHEMA = vol.Schema(vol.All(cv.ensure_list, [cv.string])) +WANTED_UUID_SCHEMA = vol.Schema(vol.All(cv.ensure_list, [cv.string])) class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @@ -17,7 +19,9 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize flow.""" - self._known_hosts = None + self._ignore_cec = set() + self._known_hosts = set() + self._wanted_uuid = set() @staticmethod def async_get_options_flow(config_entry): @@ -28,7 +32,15 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Import data.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") - data = {CONF_KNOWN_HOSTS: self._known_hosts} + + media_player_config = import_data or [] + for cfg in media_player_config: + if CONF_IGNORE_CEC in cfg: + self._ignore_cec.update(set(cfg[CONF_IGNORE_CEC])) + if CONF_UUID in cfg: + self._wanted_uuid.add(cfg[CONF_UUID]) + + data = self._get_data() return self.async_create_entry(title="Google Cast", data=data) async def async_step_user(self, user_input=None): @@ -62,7 +74,8 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors["base"] = "invalid_known_hosts" bad_hosts = True else: - data[CONF_KNOWN_HOSTS] = known_hosts + self._known_hosts = known_hosts + data = self._get_data() if not bad_hosts: return self.async_create_entry(title="Google Cast", data=data) @@ -76,13 +89,20 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm(self, user_input=None): """Confirm the setup.""" - data = {CONF_KNOWN_HOSTS: self._known_hosts} + data = self._get_data() if user_input is not None: return self.async_create_entry(title="Google Cast", data=data) return self.async_show_form(step_id="confirm") + def _get_data(self): + return { + CONF_IGNORE_CEC: list(self._ignore_cec), + CONF_KNOWN_HOSTS: list(self._known_hosts), + CONF_UUID: list(self._wanted_uuid), + } + class CastOptionsFlowHandler(config_entries.OptionsFlow): """Handle Google Cast options.""" @@ -102,35 +122,59 @@ class CastOptionsFlowHandler(config_entries.OptionsFlow): errors = {} current_config = self.config_entry.data if user_input is not None: - bad_hosts = False + bad_cec, ignore_cec = _string_to_list( + user_input.get(CONF_IGNORE_CEC, ""), IGNORE_CEC_SCHEMA + ) + bad_hosts, known_hosts = _string_to_list( + user_input.get(CONF_KNOWN_HOSTS, ""), KNOWN_HOSTS_SCHEMA + ) + bad_uuid, wanted_uuid = _string_to_list( + user_input.get(CONF_UUID, ""), WANTED_UUID_SCHEMA + ) - known_hosts = user_input.get(CONF_KNOWN_HOSTS, "") - known_hosts = [x.strip() for x in known_hosts.split(",") if x.strip()] - try: - known_hosts = KNOWN_HOSTS_SCHEMA(known_hosts) - except vol.Invalid: - errors["base"] = "invalid_known_hosts" - bad_hosts = True - if not bad_hosts: + if not bad_cec and not bad_hosts and not bad_uuid: updated_config = {} + updated_config[CONF_IGNORE_CEC] = ignore_cec updated_config[CONF_KNOWN_HOSTS] = known_hosts + updated_config[CONF_UUID] = wanted_uuid self.hass.config_entries.async_update_entry( self.config_entry, data=updated_config ) return self.async_create_entry(title="", data=None) fields = {} - known_hosts_string = "" - if current_config.get(CONF_KNOWN_HOSTS): - known_hosts_string = ",".join(current_config.get(CONF_KNOWN_HOSTS)) - fields[ - vol.Optional( - "known_hosts", description={"suggested_value": known_hosts_string} - ) - ] = str + suggested_value = _list_to_string(current_config.get(CONF_KNOWN_HOSTS)) + _add_with_suggestion(fields, CONF_KNOWN_HOSTS, suggested_value) + if self.show_advanced_options: + suggested_value = _list_to_string(current_config.get(CONF_UUID)) + _add_with_suggestion(fields, CONF_UUID, suggested_value) + suggested_value = _list_to_string(current_config.get(CONF_IGNORE_CEC)) + _add_with_suggestion(fields, CONF_IGNORE_CEC, suggested_value) return self.async_show_form( step_id="options", data_schema=vol.Schema(fields), errors=errors, ) + + +def _list_to_string(items): + comma_separated_string = "" + if items: + comma_separated_string = ",".join(items) + return comma_separated_string + + +def _string_to_list(string, schema): + invalid = False + items = [x.strip() for x in string.split(",") if x.strip()] + try: + items = schema(items) + except vol.Invalid: + invalid = True + + return invalid, items + + +def _add_with_suggestion(fields, key, suggested_value): + fields[vol.Optional(key, description={"suggested_value": suggested_value})] = str diff --git a/homeassistant/components/cast/const.py b/homeassistant/components/cast/const.py index 993315b5518..03ffdfbd15c 100644 --- a/homeassistant/components/cast/const.py +++ b/homeassistant/components/cast/const.py @@ -5,9 +5,6 @@ DEFAULT_PORT = 8009 # Stores a threading.Lock that is held by the internal pychromecast discovery. INTERNAL_DISCOVERY_RUNNING_KEY = "cast_discovery_running" -# Stores all ChromecastInfo we encountered through discovery or config as a set -# If we find a chromecast with a new host, the old one will be removed again. -KNOWN_CHROMECAST_INFO_KEY = "cast_known_chromecasts" # Stores UUIDs of cast devices that were added as entities. Doesn't store # None UUIDs. ADDED_CAST_DEVICES_KEY = "cast_added_cast_devices" @@ -27,4 +24,6 @@ SIGNAL_CAST_REMOVED = "cast_removed" # Dispatcher signal fired when a Chromecast should show a Home Assistant Cast view. SIGNAL_HASS_CAST_SHOW_VIEW = "cast_show_view" +CONF_IGNORE_CEC = "ignore_cec" CONF_KNOWN_HOSTS = "known_hosts" +CONF_UUID = "uuid" diff --git a/homeassistant/components/cast/discovery.py b/homeassistant/components/cast/discovery.py index fcae28b5bfe..a5ac4c02047 100644 --- a/homeassistant/components/cast/discovery.py +++ b/homeassistant/components/cast/discovery.py @@ -13,7 +13,6 @@ from .const import ( CONF_KNOWN_HOSTS, DEFAULT_PORT, INTERNAL_DISCOVERY_RUNNING_KEY, - KNOWN_CHROMECAST_INFO_KEY, SIGNAL_CAST_DISCOVERED, SIGNAL_CAST_REMOVED, ) @@ -38,12 +37,8 @@ def discover_chromecast(hass: HomeAssistant, device_info): return info = info.fill_out_missing_chromecast_info() - if info.uuid in hass.data[KNOWN_CHROMECAST_INFO_KEY]: - _LOGGER.debug("Discovered update for known chromecast %s", info) - else: - _LOGGER.debug("Discovered chromecast %s", info) + _LOGGER.debug("Discovered new or updated chromecast %s", info) - hass.data[KNOWN_CHROMECAST_INFO_KEY][info.uuid] = info dispatcher_send(hass, SIGNAL_CAST_DISCOVERED, info) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 9532c25d81a..540f3263e1e 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -1,7 +1,6 @@ """Provide functionality to interact with Cast devices on the network.""" from __future__ import annotations -import asyncio from contextlib import suppress from datetime import timedelta import functools as ft @@ -52,19 +51,19 @@ from homeassistant.const import ( STATE_PLAYING, ) from homeassistant.core import callback -from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.network import NoURLAvailableError, get_url -from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.helpers.typing import HomeAssistantType import homeassistant.util.dt as dt_util from homeassistant.util.logging import async_create_catching_coro from .const import ( ADDED_CAST_DEVICES_KEY, CAST_MULTIZONE_MANAGER_KEY, + CONF_IGNORE_CEC, + CONF_UUID, DOMAIN as CAST_DOMAIN, - KNOWN_CHROMECAST_INFO_KEY, SIGNAL_CAST_DISCOVERED, SIGNAL_CAST_REMOVED, SIGNAL_HASS_CAST_SHOW_VIEW, @@ -74,8 +73,6 @@ from .helpers import CastStatusListener, ChromecastInfo, ChromeCastZeroconf _LOGGER = logging.getLogger(__name__) -CONF_IGNORE_CEC = "ignore_cec" -CONF_UUID = "uuid" CAST_SPLASH = "https://www.home-assistant.io/images/cast/splash.png" SUPPORT_CAST = ( @@ -129,45 +126,20 @@ def _async_create_cast_device(hass: HomeAssistantType, info: ChromecastInfo): async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Cast from a config entry.""" - config = hass.data[CAST_DOMAIN].get("media_player") or {} - if not isinstance(config, list): - config = [config] - - # no pending task - done, _ = await asyncio.wait( - [ - _async_setup_platform( - hass, ENTITY_SCHEMA(cfg), async_add_entities, config_entry - ) - for cfg in config - ] - ) - if any(task.exception() for task in done): - exceptions = [task.exception() for task in done] - for exception in exceptions: - _LOGGER.debug("Failed to setup chromecast", exc_info=exception) - raise PlatformNotReady - - -async def _async_setup_platform( - hass: HomeAssistantType, config: ConfigType, async_add_entities, config_entry -): - """Set up the cast platform.""" - # Import CEC IGNORE attributes - pychromecast.IGNORE_CEC += config.get(CONF_IGNORE_CEC, []) hass.data.setdefault(ADDED_CAST_DEVICES_KEY, set()) - hass.data.setdefault(KNOWN_CHROMECAST_INFO_KEY, {}) - wanted_uuid = None - if CONF_UUID in config: - wanted_uuid = config[CONF_UUID] + # Import CEC IGNORE attributes + pychromecast.IGNORE_CEC += config_entry.data.get(CONF_IGNORE_CEC) or [] + + wanted_uuids = config_entry.data.get(CONF_UUID) or None @callback def async_cast_discovered(discover: ChromecastInfo) -> None: """Handle discovery of a new chromecast.""" - # If wanted_uuid is set, we're handling a specific cast device identified by UUID - if wanted_uuid is not None and wanted_uuid != discover.uuid: - # UUID not matching, this is not it. + # If wanted_uuids is set, we're only accepting specific cast devices identified + # by UUID + if wanted_uuids is not None and discover.uuid not in wanted_uuids: + # UUID not matching, ignore. return cast_device = _async_create_cast_device(hass, discover) @@ -175,11 +147,6 @@ async def _async_setup_platform( async_add_entities([cast_device]) async_dispatcher_connect(hass, SIGNAL_CAST_DISCOVERED, async_cast_discovered) - # Re-play the callback for all past chromecasts, store the objects in - # a list to avoid concurrent modification resulting in exception. - for chromecast in hass.data[KNOWN_CHROMECAST_INFO_KEY].values(): - async_cast_discovered(chromecast) - ChromeCastZeroconf.set_zeroconf(await zeroconf.async_get_instance(hass)) hass.async_add_executor_job(setup_internal_discovery, hass, config_entry) diff --git a/homeassistant/components/cast/strings.json b/homeassistant/components/cast/strings.json index 7cd07518db8..33ce4b6941e 100644 --- a/homeassistant/components/cast/strings.json +++ b/homeassistant/components/cast/strings.json @@ -24,7 +24,9 @@ "options": { "description": "Please enter the Google Cast configuration.", "data": { - "known_hosts": "Optional list of known hosts if mDNS discovery is not working." + "ignore_cec": "Optional list which will be passed to pychromecast.IGNORE_CEC.", + "known_hosts": "Optional list of known hosts if mDNS discovery is not working.", + "uuid": "Optional list of UUIDs. Casts not listed will not be added." } } }, diff --git a/homeassistant/components/cast/translations/en.json b/homeassistant/components/cast/translations/en.json index 1bfdeb4df8d..c2c2460cc9c 100644 --- a/homeassistant/components/cast/translations/en.json +++ b/homeassistant/components/cast/translations/en.json @@ -27,7 +27,9 @@ "step": { "options": { "data": { - "known_hosts": "Optional list of known hosts if mDNS discovery is not working." + "ignore_cec": "Optional list which will be passed to pychromecast.IGNORE_CEC.", + "known_hosts": "Optional list of known hosts if mDNS discovery is not working.", + "uuid": "Optional list of UUIDs. Casts not listed will not be added." }, "description": "Please enter the Google Cast configuration." } diff --git a/tests/components/cast/conftest.py b/tests/components/cast/conftest.py index 875d831afa9..a8118a94967 100644 --- a/tests/components/cast/conftest.py +++ b/tests/components/cast/conftest.py @@ -40,6 +40,7 @@ def mz_mock(): def pycast_mock(castbrowser_mock, castbrowser_constructor_mock): """Mock pychromecast.""" pycast_mock = MagicMock() + pycast_mock.IGNORE_CEC = [] pycast_mock.discovery.CastBrowser = castbrowser_constructor_mock pycast_mock.discovery.CastBrowser.return_value = castbrowser_mock pycast_mock.discovery.AbstractCastListener = ( diff --git a/tests/components/cast/test_home_assistant_cast.py b/tests/components/cast/test_home_assistant_cast.py index 3fd0e921ca6..6ac4f9c9d0c 100644 --- a/tests/components/cast/test_home_assistant_cast.py +++ b/tests/components/cast/test_home_assistant_cast.py @@ -102,12 +102,8 @@ async def test_remove_entry(hass, mock_zeroconf): entry.add_to_hass(hass) with patch( - "homeassistant.components.cast.media_player._async_setup_platform" - ), patch( "pychromecast.discovery.discover_chromecasts", return_value=(True, None) - ), patch( - "pychromecast.discovery.stop_discovery" - ): + ), patch("pychromecast.discovery.stop_discovery"): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() assert "cast" in hass.config.components diff --git a/tests/components/cast/test_init.py b/tests/components/cast/test_init.py index 77268e7de97..888ef2ebcd7 100644 --- a/tests/components/cast/test_init.py +++ b/tests/components/cast/test_init.py @@ -35,18 +35,36 @@ async def test_creating_entry_sets_up_media_player(hass): assert len(mock_setup.mock_calls) == 1 -async def test_configuring_cast_creates_entry(hass): +async def test_import(hass, caplog): """Test that specifying config will create an entry.""" with patch( "homeassistant.components.cast.async_setup_entry", return_value=True ) as mock_setup: await async_setup_component( - hass, cast.DOMAIN, {"cast": {"some_config": "to_trigger_import"}} + hass, + cast.DOMAIN, + { + "cast": { + "media_player": [ + {"uuid": "abcd"}, + {"uuid": "abcd", "ignore_cec": "milk"}, + {"uuid": "efgh", "ignore_cec": "beer"}, + {"incorrect": "config"}, + ] + } + }, ) await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 1 + assert len(hass.config_entries.async_entries("cast")) == 1 + entry = hass.config_entries.async_entries("cast")[0] + assert set(entry.data["ignore_cec"]) == {"milk", "beer"} + assert set(entry.data["uuid"]) == {"abcd", "efgh"} + + assert "Invalid config '{'incorrect': 'config'}'" in caplog.text + async def test_not_configuring_cast_not_creates_entry(hass): """Test that no config will not create an entry.""" @@ -72,7 +90,7 @@ async def test_single_instance(hass, source): assert result["reason"] == "single_instance_allowed" -async def test_user_setup(hass, mqtt_mock): +async def test_user_setup(hass): """Test we can finish a config flow.""" result = await hass.config_entries.flow.async_init( "cast", context={"source": "user"} @@ -85,12 +103,14 @@ async def test_user_setup(hass, mqtt_mock): assert len(users) == 1 assert result["type"] == "create_entry" assert result["result"].data == { + "ignore_cec": [], "known_hosts": [], + "uuid": [], "user_id": users[0].id, # Home Assistant cast user } -async def test_user_setup_options(hass, mqtt_mock): +async def test_user_setup_options(hass): """Test we can finish a config flow.""" result = await hass.config_entries.flow.async_init( "cast", context={"source": "user"} @@ -105,7 +125,9 @@ async def test_user_setup_options(hass, mqtt_mock): assert len(users) == 1 assert result["type"] == "create_entry" assert result["result"].data == { + "ignore_cec": [], "known_hosts": ["192.168.0.1", "192.168.0.2"], + "uuid": [], "user_id": users[0].id, # Home Assistant cast user } @@ -123,7 +145,9 @@ async def test_zeroconf_setup(hass): assert len(users) == 1 assert result["type"] == "create_entry" assert result["result"].data == { - "known_hosts": None, + "ignore_cec": [], + "known_hosts": [], + "uuid": [], "user_id": users[0].id, # Home Assistant cast user } @@ -137,27 +161,90 @@ def get_suggested(schema, key): return k.description["suggested_value"] -async def test_option_flow(hass): +@pytest.mark.parametrize( + "parameter_data", + [ + ( + "known_hosts", + ["192.168.0.10", "192.168.0.11"], + "192.168.0.10,192.168.0.11", + "192.168.0.1, , 192.168.0.2 ", + ["192.168.0.1", "192.168.0.2"], + ), + ( + "uuid", + ["bla", "blu"], + "bla,blu", + "foo, , bar ", + ["foo", "bar"], + ), + ( + "ignore_cec", + ["cast1", "cast2"], + "cast1,cast2", + "other_cast, , some_cast ", + ["other_cast", "some_cast"], + ), + ], +) +async def test_option_flow(hass, parameter_data): """Test config flow options.""" - config_entry = MockConfigEntry( - domain="cast", data={"known_hosts": ["192.168.0.10", "192.168.0.11"]} - ) + all_parameters = ["ignore_cec", "known_hosts", "uuid"] + parameter, initial, suggested, user_input, updated = parameter_data + + data = { + "ignore_cec": [], + "known_hosts": [], + "uuid": [], + } + data[parameter] = initial + config_entry = MockConfigEntry(domain="cast", data=data) config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() + # Test ignore_cec and uuid options are hidden if advanced options are disabled result = await hass.config_entries.options.async_init(config_entry.entry_id) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "options" data_schema = result["data_schema"].schema - assert get_suggested(data_schema, "known_hosts") == "192.168.0.10,192.168.0.11" + assert set(data_schema) == {"known_hosts"} + + # Reconfigure ignore_cec, known_hosts, uuid + context = {"source": "user", "show_advanced_options": True} + result = await hass.config_entries.options.async_init( + config_entry.entry_id, context=context + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "options" + data_schema = result["data_schema"].schema + for other_param in all_parameters: + if other_param == parameter: + continue + assert get_suggested(data_schema, other_param) == "" + assert get_suggested(data_schema, parameter) == suggested result = await hass.config_entries.options.async_configure( result["flow_id"], - user_input={"known_hosts": "192.168.0.1, , 192.168.0.2 "}, + user_input={parameter: user_input}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"] is None - assert config_entry.data == {"known_hosts": ["192.168.0.1", "192.168.0.2"]} + for other_param in all_parameters: + if other_param == parameter: + continue + assert config_entry.data[other_param] == [] + assert config_entry.data[parameter] == updated + + # Clear known_hosts + result = await hass.config_entries.options.async_init(config_entry.entry_id) + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={"known_hosts": ""}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"] is None + assert config_entry.data == {"ignore_cec": [], "known_hosts": [], "uuid": []} async def test_known_hosts(hass, castbrowser_mock, castbrowser_constructor_mock): diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 8a6f84580d2..4b1978e8da5 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -3,7 +3,7 @@ from __future__ import annotations import json -from unittest.mock import ANY, MagicMock, Mock, patch +from unittest.mock import ANY, MagicMock, patch from uuid import UUID import attr @@ -28,7 +28,6 @@ from homeassistant.components.media_player.const import ( ) from homeassistant.config import async_process_ha_core_config from homeassistant.const import EVENT_HOMEASSISTANT_STOP -from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers import entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import HomeAssistantType @@ -100,11 +99,13 @@ async def async_setup_cast(hass, config=None): """Set up the cast platform.""" if config is None: config = {} + data = {**{"ignore_cec": [], "known_hosts": [], "uuid": []}, **config} with patch( "homeassistant.helpers.entity_platform.EntityPlatform._async_schedule_add_entities" ) as add_entities: - MockConfigEntry(domain="cast").add_to_hass(hass) - await async_setup_component(hass, "cast", {"cast": {"media_player": config}}) + entry = MockConfigEntry(data=data, domain="cast") + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() return add_entities @@ -388,44 +389,6 @@ async def test_create_cast_device_with_uuid(hass): assert cast_device is None -async def test_replay_past_chromecasts(hass): - """Test cast platform re-playing past chromecasts when adding new one.""" - cast_group1 = get_fake_chromecast_info(host="host1", port=8009, uuid=FakeUUID) - cast_group2 = get_fake_chromecast_info( - host="host2", port=8009, uuid=UUID("9462202c-e747-4af5-a66b-7dce0e1ebc09") - ) - zconf_1 = get_fake_zconf(host="host1", port=8009) - zconf_2 = get_fake_zconf(host="host2", port=8009) - - discover_cast, _, add_dev1 = await async_setup_cast_internal_discovery( - hass, config={"uuid": FakeUUID} - ) - - with patch( - "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", - return_value=zconf_2, - ): - discover_cast("service2", cast_group2) - await hass.async_block_till_done() - await hass.async_block_till_done() # having tasks that add jobs - assert add_dev1.call_count == 0 - - with patch( - "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", - return_value=zconf_1, - ): - discover_cast("service1", cast_group1) - await hass.async_block_till_done() - await hass.async_block_till_done() # having tasks that add jobs - assert add_dev1.call_count == 1 - - add_dev2 = Mock() - entry = hass.config_entries.async_entries("cast")[0] - await cast._async_setup_platform(hass, {"host": "host2"}, add_dev2, entry) - await hass.async_block_till_done() - assert add_dev2.call_count == 1 - - async def test_manual_cast_chromecasts_uuid(hass): """Test only wanted casts are added for manual configuration.""" cast_1 = get_fake_chromecast_info(host="host_1", uuid=FakeUUID) @@ -435,7 +398,7 @@ async def test_manual_cast_chromecasts_uuid(hass): # Manual configuration of media player with host "configured_host" discover_cast, _, add_dev1 = await async_setup_cast_internal_discovery( - hass, config={"uuid": FakeUUID} + hass, config={"uuid": str(FakeUUID)} ) with patch( "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", @@ -1291,65 +1254,54 @@ async def test_disconnect_on_stop(hass: HomeAssistantType): async def test_entry_setup_no_config(hass: HomeAssistantType): - """Test setting up entry with no config..""" + """Test deprecated empty yaml config..""" await async_setup_component(hass, "cast", {}) await hass.async_block_till_done() - with patch( - "homeassistant.components.cast.media_player._async_setup_platform", - ) as mock_setup: - await cast.async_setup_entry(hass, MockConfigEntry(), None) - - assert len(mock_setup.mock_calls) == 1 - assert mock_setup.mock_calls[0][1][1] == {} + assert not hass.config_entries.async_entries("cast") -async def test_entry_setup_single_config(hass: HomeAssistantType): - """Test setting up entry and having a single config option.""" +async def test_entry_setup_empty_config(hass: HomeAssistantType): + """Test deprecated empty yaml config..""" + await async_setup_component(hass, "cast", {"cast": {}}) + await hass.async_block_till_done() + + config_entry = hass.config_entries.async_entries("cast")[0] + assert config_entry.data["uuid"] == [] + assert config_entry.data["ignore_cec"] == [] + + +async def test_entry_setup_single_config(hass: HomeAssistantType, pycast_mock): + """Test deprecated yaml config with a single config media_player.""" await async_setup_component( - hass, "cast", {"cast": {"media_player": {"uuid": "bla"}}} + hass, "cast", {"cast": {"media_player": {"uuid": "bla", "ignore_cec": "cast1"}}} ) await hass.async_block_till_done() - with patch( - "homeassistant.components.cast.media_player._async_setup_platform", - ) as mock_setup: - await cast.async_setup_entry(hass, MockConfigEntry(), None) + config_entry = hass.config_entries.async_entries("cast")[0] + assert config_entry.data["uuid"] == ["bla"] + assert config_entry.data["ignore_cec"] == ["cast1"] - assert len(mock_setup.mock_calls) == 1 - assert mock_setup.mock_calls[0][1][1] == {"uuid": "bla"} + assert pycast_mock.IGNORE_CEC == ["cast1"] -async def test_entry_setup_list_config(hass: HomeAssistantType): - """Test setting up entry and having multiple config options.""" +async def test_entry_setup_list_config(hass: HomeAssistantType, pycast_mock): + """Test deprecated yaml config with multiple media_players.""" await async_setup_component( - hass, "cast", {"cast": {"media_player": [{"uuid": "bla"}, {"uuid": "blu"}]}} + hass, + "cast", + { + "cast": { + "media_player": [ + {"uuid": "bla", "ignore_cec": "cast1"}, + {"uuid": "blu", "ignore_cec": ["cast2", "cast3"]}, + ] + } + }, ) await hass.async_block_till_done() - with patch( - "homeassistant.components.cast.media_player._async_setup_platform", - ) as mock_setup: - await cast.async_setup_entry(hass, MockConfigEntry(), None) - - assert len(mock_setup.mock_calls) == 2 - assert mock_setup.mock_calls[0][1][1] == {"uuid": "bla"} - assert mock_setup.mock_calls[1][1][1] == {"uuid": "blu"} - - -async def test_entry_setup_platform_not_ready(hass: HomeAssistantType): - """Test failed setting up entry will raise PlatformNotReady.""" - await async_setup_component( - hass, "cast", {"cast": {"media_player": {"uuid": "bla"}}} - ) - await hass.async_block_till_done() - - with patch( - "homeassistant.components.cast.media_player._async_setup_platform", - side_effect=Exception, - ) as mock_setup: - with pytest.raises(PlatformNotReady): - await cast.async_setup_entry(hass, MockConfigEntry(), None) - - assert len(mock_setup.mock_calls) == 1 - assert mock_setup.mock_calls[0][1][1] == {"uuid": "bla"} + config_entry = hass.config_entries.async_entries("cast")[0] + assert set(config_entry.data["uuid"]) == {"bla", "blu"} + assert set(config_entry.data["ignore_cec"]) == {"cast1", "cast2", "cast3"} + assert set(pycast_mock.IGNORE_CEC) == {"cast1", "cast2", "cast3"} From 1b60c8efb88e8bd6bc2b73e56ecda85d4be34172 Mon Sep 17 00:00:00 2001 From: chemaaa <187996+chemaaa@users.noreply.github.com> Date: Thu, 25 Mar 2021 14:12:31 +0100 Subject: [PATCH 599/831] Add Homepluscontrol integration (#46783) Co-authored-by: Martin Hjelmare --- .coveragerc | 3 + CODEOWNERS | 1 + .../components/home_plus_control/__init__.py | 179 +++++++ .../components/home_plus_control/api.py | 55 +++ .../home_plus_control/config_flow.py | 32 ++ .../components/home_plus_control/const.py | 45 ++ .../components/home_plus_control/helpers.py | 53 ++ .../home_plus_control/manifest.json | 15 + .../components/home_plus_control/strings.json | 21 + .../components/home_plus_control/switch.py | 129 +++++ .../home_plus_control/translations/en.json | 15 + homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + .../components/home_plus_control/__init__.py | 1 + .../components/home_plus_control/conftest.py | 106 ++++ .../home_plus_control/test_config_flow.py | 192 ++++++++ .../components/home_plus_control/test_init.py | 75 +++ .../home_plus_control/test_switch.py | 464 ++++++++++++++++++ 19 files changed, 1393 insertions(+) create mode 100644 homeassistant/components/home_plus_control/__init__.py create mode 100644 homeassistant/components/home_plus_control/api.py create mode 100644 homeassistant/components/home_plus_control/config_flow.py create mode 100644 homeassistant/components/home_plus_control/const.py create mode 100644 homeassistant/components/home_plus_control/helpers.py create mode 100644 homeassistant/components/home_plus_control/manifest.json create mode 100644 homeassistant/components/home_plus_control/strings.json create mode 100644 homeassistant/components/home_plus_control/switch.py create mode 100644 homeassistant/components/home_plus_control/translations/en.json create mode 100644 tests/components/home_plus_control/__init__.py create mode 100644 tests/components/home_plus_control/conftest.py create mode 100644 tests/components/home_plus_control/test_config_flow.py create mode 100644 tests/components/home_plus_control/test_init.py create mode 100644 tests/components/home_plus_control/test_switch.py diff --git a/.coveragerc b/.coveragerc index 10eac76421c..cec2649b132 100644 --- a/.coveragerc +++ b/.coveragerc @@ -400,6 +400,9 @@ omit = homeassistant/components/homematic/climate.py homeassistant/components/homematic/cover.py homeassistant/components/homematic/notify.py + homeassistant/components/home_plus_control/api.py + homeassistant/components/home_plus_control/helpers.py + homeassistant/components/home_plus_control/switch.py homeassistant/components/homeworks/* homeassistant/components/honeywell/climate.py homeassistant/components/horizon/media_player.py diff --git a/CODEOWNERS b/CODEOWNERS index 31ce9706baa..fe28524def7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -196,6 +196,7 @@ homeassistant/components/history/* @home-assistant/core homeassistant/components/hive/* @Rendili @KJonline homeassistant/components/hlk_sw16/* @jameshilliard homeassistant/components/home_connect/* @DavidMStraub +homeassistant/components/home_plus_control/* @chemaaa homeassistant/components/homeassistant/* @home-assistant/core homeassistant/components/homekit/* @bdraco homeassistant/components/homekit_controller/* @Jc2k diff --git a/homeassistant/components/home_plus_control/__init__.py b/homeassistant/components/home_plus_control/__init__.py new file mode 100644 index 00000000000..e559cd030b3 --- /dev/null +++ b/homeassistant/components/home_plus_control/__init__.py @@ -0,0 +1,179 @@ +"""The Legrand Home+ Control integration.""" +import asyncio +from datetime import timedelta +import logging + +import async_timeout +from homepluscontrol.homeplusapi import HomePlusControlApiError +import voluptuous as vol + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET +from homeassistant.core import HomeAssistant +from homeassistant.helpers import ( + config_entry_oauth2_flow, + config_validation as cv, + dispatcher, +) +from homeassistant.helpers.device_registry import async_get as async_get_device_registry +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from . import config_flow, helpers +from .api import HomePlusControlAsyncApi +from .const import ( + API, + CONF_SUBSCRIPTION_KEY, + DATA_COORDINATOR, + DISPATCHER_REMOVERS, + DOMAIN, + ENTITY_UIDS, + SIGNAL_ADD_ENTITIES, +) + +# Configuration schema for component in configuration.yaml +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_CLIENT_ID): cv.string, + vol.Required(CONF_CLIENT_SECRET): cv.string, + vol.Required(CONF_SUBSCRIPTION_KEY): cv.string, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) + +# The Legrand Home+ Control platform is currently limited to "switch" entities +PLATFORMS = ["switch"] + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass: HomeAssistant, config: dict) -> bool: + """Set up the Legrand Home+ Control component from configuration.yaml.""" + hass.data[DOMAIN] = {} + + if DOMAIN not in config: + return True + + # Register the implementation from the config information + config_flow.HomePlusControlFlowHandler.async_register_implementation( + hass, + helpers.HomePlusControlOAuth2Implementation(hass, config[DOMAIN]), + ) + + return True + + +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Set up Legrand Home+ Control from a config entry.""" + hass_entry_data = hass.data[DOMAIN].setdefault(config_entry.entry_id, {}) + + # Retrieve the registered implementation + implementation = ( + await config_entry_oauth2_flow.async_get_config_entry_implementation( + hass, config_entry + ) + ) + + # Using an aiohttp-based API lib, so rely on async framework + # Add the API object to the domain's data in HA + api = hass_entry_data[API] = HomePlusControlAsyncApi( + hass, config_entry, implementation + ) + + # Set of entity unique identifiers of this integration + uids = hass_entry_data[ENTITY_UIDS] = set() + + # Integration dispatchers + hass_entry_data[DISPATCHER_REMOVERS] = [] + + device_registry = async_get_device_registry(hass) + + # Register the Data Coordinator with the integration + async def async_update_data(): + """Fetch data from API endpoint. + + This is the place to pre-process the data to lookup tables + so entities can quickly look up their data. + """ + try: + # Note: asyncio.TimeoutError and aiohttp.ClientError are already + # handled by the data update coordinator. + async with async_timeout.timeout(10): + module_data = await api.async_get_modules() + except HomePlusControlApiError as err: + raise UpdateFailed( + f"Error communicating with API: {err} [{type(err)}]" + ) from err + + # Remove obsolete entities from Home Assistant + entity_uids_to_remove = uids - set(module_data) + for uid in entity_uids_to_remove: + uids.remove(uid) + device = device_registry.async_get_device({(DOMAIN, uid)}) + device_registry.async_remove_device(device.id) + + # Send out signal for new entity addition to Home Assistant + new_entity_uids = set(module_data) - uids + if new_entity_uids: + uids.update(new_entity_uids) + dispatcher.async_dispatcher_send( + hass, + SIGNAL_ADD_ENTITIES, + new_entity_uids, + coordinator, + ) + + return module_data + + coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + # Name of the data. For logging purposes. + name="home_plus_control_module", + update_method=async_update_data, + # Polling interval. Will only be polled if there are subscribers. + update_interval=timedelta(seconds=60), + ) + hass_entry_data[DATA_COORDINATOR] = coordinator + + async def start_platforms(): + """Continue setting up the platforms.""" + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_setup(config_entry, platform) + for platform in PLATFORMS + ] + ) + # Only refresh the coordinator after all platforms are loaded. + await coordinator.async_refresh() + + hass.async_create_task(start_platforms()) + + return True + + +async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Unload the Legrand Home+ Control config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(config_entry, component) + for component in PLATFORMS + ] + ) + ) + if unload_ok: + # Unsubscribe the config_entry signal dispatcher connections + dispatcher_removers = hass.data[DOMAIN][config_entry.entry_id].pop( + "dispatcher_removers" + ) + for remover in dispatcher_removers: + remover() + + # And finally unload the domain config entry data + hass.data[DOMAIN].pop(config_entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/home_plus_control/api.py b/homeassistant/components/home_plus_control/api.py new file mode 100644 index 00000000000..d9db95323de --- /dev/null +++ b/homeassistant/components/home_plus_control/api.py @@ -0,0 +1,55 @@ +"""API for Legrand Home+ Control bound to Home Assistant OAuth.""" +from homepluscontrol.homeplusapi import HomePlusControlAPI + +from homeassistant import config_entries, core +from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow + +from .const import DEFAULT_UPDATE_INTERVALS + + +class HomePlusControlAsyncApi(HomePlusControlAPI): + """Legrand Home+ Control object that interacts with the OAuth2-based API of the provider. + + This API is bound the HomeAssistant Config Entry that corresponds to this component. + + Attributes:. + hass (HomeAssistant): HomeAssistant core object. + config_entry (ConfigEntry): ConfigEntry object that configures this API. + implementation (AbstractOAuth2Implementation): OAuth2 implementation that handles AA and + token refresh. + _oauth_session (OAuth2Session): OAuth2Session object within implementation. + """ + + def __init__( + self, + hass: core.HomeAssistant, + config_entry: config_entries.ConfigEntry, + implementation: config_entry_oauth2_flow.AbstractOAuth2Implementation, + ) -> None: + """Initialize the HomePlusControlAsyncApi object. + + Initialize the authenticated API for the Legrand Home+ Control component. + + Args:. + hass (HomeAssistant): HomeAssistant core object. + config_entry (ConfigEntry): ConfigEntry object that configures this API. + implementation (AbstractOAuth2Implementation): OAuth2 implementation that handles AA + and token refresh. + """ + self._oauth_session = config_entry_oauth2_flow.OAuth2Session( + hass, config_entry, implementation + ) + + # Create the API authenticated client - external library + super().__init__( + subscription_key=implementation.subscription_key, + oauth_client=aiohttp_client.async_get_clientsession(hass), + update_intervals=DEFAULT_UPDATE_INTERVALS, + ) + + async def async_get_access_token(self) -> str: + """Return a valid access token.""" + if not self._oauth_session.valid_token: + await self._oauth_session.async_ensure_token_valid() + + return self._oauth_session.token["access_token"] diff --git a/homeassistant/components/home_plus_control/config_flow.py b/homeassistant/components/home_plus_control/config_flow.py new file mode 100644 index 00000000000..ed1686f7af1 --- /dev/null +++ b/homeassistant/components/home_plus_control/config_flow.py @@ -0,0 +1,32 @@ +"""Config flow for Legrand Home+ Control.""" +import logging + +from homeassistant import config_entries +from homeassistant.helpers import config_entry_oauth2_flow + +from .const import DOMAIN + + +class HomePlusControlFlowHandler( + config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN +): + """Config flow to handle Home+ Control OAuth2 authentication.""" + + DOMAIN = DOMAIN + + # Pick the Cloud Poll class + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + @property + def logger(self) -> logging.Logger: + """Return logger.""" + return logging.getLogger(__name__) + + async def async_step_user(self, user_input=None): + """Handle a flow start initiated by the user.""" + await self.async_set_unique_id(DOMAIN) + + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + return await super().async_step_user(user_input) diff --git a/homeassistant/components/home_plus_control/const.py b/homeassistant/components/home_plus_control/const.py new file mode 100644 index 00000000000..0ebae0bef20 --- /dev/null +++ b/homeassistant/components/home_plus_control/const.py @@ -0,0 +1,45 @@ +"""Constants for the Legrand Home+ Control integration.""" +API = "api" +CONF_SUBSCRIPTION_KEY = "subscription_key" +CONF_PLANT_UPDATE_INTERVAL = "plant_update_interval" +CONF_PLANT_TOPOLOGY_UPDATE_INTERVAL = "plant_topology_update_interval" +CONF_MODULE_STATUS_UPDATE_INTERVAL = "module_status_update_interval" + +DATA_COORDINATOR = "coordinator" +DOMAIN = "home_plus_control" +ENTITY_UIDS = "entity_unique_ids" +DISPATCHER_REMOVERS = "dispatcher_removers" + +# Legrand Model Identifiers - https://developer.legrand.com/documentation/product-cluster-list/# +HW_TYPE = { + "NLC": "NLC - Cable Outlet", + "NLF": "NLF - On-Off Dimmer Switch w/o Neutral", + "NLP": "NLP - Socket (Connected) Outlet", + "NLPM": "NLPM - Mobile Socket Outlet", + "NLM": "NLM - Micromodule Switch", + "NLV": "NLV - Shutter Switch with Neutral", + "NLLV": "NLLV - Shutter Switch with Level Control", + "NLL": "NLL - On-Off Toggle Switch with Neutral", + "NLT": "NLT - Remote Switch", + "NLD": "NLD - Double Gangs On-Off Remote Switch", +} + +# Legrand OAuth2 URIs +OAUTH2_AUTHORIZE = "https://partners-login.eliotbylegrand.com/authorize" +OAUTH2_TOKEN = "https://partners-login.eliotbylegrand.com/token" + +# The Legrand Home+ Control API has very limited request quotas - at the time of writing, it is +# limited to 500 calls per day (resets at 00:00) - so we want to keep updates to a minimum. +DEFAULT_UPDATE_INTERVALS = { + # Seconds between API checks for plant information updates. This is expected to change very + # little over time because a user's plants (homes) should rarely change. + CONF_PLANT_UPDATE_INTERVAL: 7200, # 120 minutes + # Seconds between API checks for plant topology updates. This is expected to change little + # over time because the modules in the user's plant should be relatively stable. + CONF_PLANT_TOPOLOGY_UPDATE_INTERVAL: 3600, # 60 minutes + # Seconds between API checks for module status updates. This can change frequently so we + # check often + CONF_MODULE_STATUS_UPDATE_INTERVAL: 300, # 5 minutes +} + +SIGNAL_ADD_ENTITIES = "home_plus_control_add_entities_signal" diff --git a/homeassistant/components/home_plus_control/helpers.py b/homeassistant/components/home_plus_control/helpers.py new file mode 100644 index 00000000000..95d538def01 --- /dev/null +++ b/homeassistant/components/home_plus_control/helpers.py @@ -0,0 +1,53 @@ +"""Helper classes and functions for the Legrand Home+ Control integration.""" +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET +from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_entry_oauth2_flow + +from .const import CONF_SUBSCRIPTION_KEY, DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN + + +class HomePlusControlOAuth2Implementation( + config_entry_oauth2_flow.LocalOAuth2Implementation +): + """OAuth2 implementation that extends the HomeAssistant local implementation. + + It provides the name of the integration and adds support for the subscription key. + + Attributes: + hass (HomeAssistant): HomeAssistant core object. + client_id (str): Client identifier assigned by the API provider when registering an app. + client_secret (str): Client secret assigned by the API provider when registering an app. + subscription_key (str): Subscription key obtained from the API provider. + authorize_url (str): Authorization URL initiate authentication flow. + token_url (str): URL to retrieve access/refresh tokens. + name (str): Name of the implementation (appears in the HomeAssitant GUI). + """ + + def __init__( + self, + hass: HomeAssistant, + config_data: dict, + ): + """HomePlusControlOAuth2Implementation Constructor. + + Initialize the authentication implementation for the Legrand Home+ Control API. + + Args: + hass (HomeAssistant): HomeAssistant core object. + config_data (dict): Configuration data that complies with the config Schema + of this component. + """ + super().__init__( + hass=hass, + domain=DOMAIN, + client_id=config_data[CONF_CLIENT_ID], + client_secret=config_data[CONF_CLIENT_SECRET], + authorize_url=OAUTH2_AUTHORIZE, + token_url=OAUTH2_TOKEN, + ) + self.subscription_key = config_data[CONF_SUBSCRIPTION_KEY] + + @property + def name(self) -> str: + """Name of the implementation.""" + return "Home+ Control" diff --git a/homeassistant/components/home_plus_control/manifest.json b/homeassistant/components/home_plus_control/manifest.json new file mode 100644 index 00000000000..1eb143ca3c2 --- /dev/null +++ b/homeassistant/components/home_plus_control/manifest.json @@ -0,0 +1,15 @@ +{ + "domain": "home_plus_control", + "name": "Legrand Home+ Control", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/home_plus_control", + "requirements": [ + "homepluscontrol==0.0.5" + ], + "dependencies": [ + "http" + ], + "codeowners": [ + "@chemaaa" + ] +} diff --git a/homeassistant/components/home_plus_control/strings.json b/homeassistant/components/home_plus_control/strings.json new file mode 100644 index 00000000000..c991c9e0279 --- /dev/null +++ b/homeassistant/components/home_plus_control/strings.json @@ -0,0 +1,21 @@ +{ + "title": "Legrand Home+ Control", + "config": { + "step": { + "pick_implementation": { + "title": "[%key:common::config_flow::title::oauth2_pick_implementation%]" + } + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", + "missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]", + "authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]", + "no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]", + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" + }, + "create_entry": { + "default": "[%key:common::config_flow::create_entry::authenticated%]" + } + } +} diff --git a/homeassistant/components/home_plus_control/switch.py b/homeassistant/components/home_plus_control/switch.py new file mode 100644 index 00000000000..d4167ae1f9e --- /dev/null +++ b/homeassistant/components/home_plus_control/switch.py @@ -0,0 +1,129 @@ +"""Legrand Home+ Control Switch Entity Module that uses the HomeAssistant DataUpdateCoordinator.""" +from functools import partial + +from homeassistant.components.switch import ( + DEVICE_CLASS_OUTLET, + DEVICE_CLASS_SWITCH, + SwitchEntity, +) +from homeassistant.core import callback +from homeassistant.helpers import dispatcher +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DISPATCHER_REMOVERS, DOMAIN, HW_TYPE, SIGNAL_ADD_ENTITIES + + +@callback +def add_switch_entities(new_unique_ids, coordinator, add_entities): + """Add switch entities to the platform. + + Args: + new_unique_ids (set): Unique identifiers of entities to be added to Home Assistant. + coordinator (DataUpdateCoordinator): Data coordinator of this platform. + add_entities (function): Method called to add entities to Home Assistant. + """ + new_entities = [] + for uid in new_unique_ids: + new_ent = HomeControlSwitchEntity(coordinator, uid) + new_entities.append(new_ent) + add_entities(new_entities) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Legrand Home+ Control Switch platform in HomeAssistant. + + Args: + hass (HomeAssistant): HomeAssistant core object. + config_entry (ConfigEntry): ConfigEntry object that configures this platform. + async_add_entities (function): Function called to add entities of this platform. + """ + partial_add_switch_entities = partial( + add_switch_entities, add_entities=async_add_entities + ) + # Connect the dispatcher for the switch platform + hass.data[DOMAIN][config_entry.entry_id][DISPATCHER_REMOVERS].append( + dispatcher.async_dispatcher_connect( + hass, SIGNAL_ADD_ENTITIES, partial_add_switch_entities + ) + ) + + +class HomeControlSwitchEntity(CoordinatorEntity, SwitchEntity): + """Entity that represents a Legrand Home+ Control switch. + + It extends the HomeAssistant-provided classes of the CoordinatorEntity and the SwitchEntity. + + The CoordinatorEntity class provides: + should_poll + async_update + async_added_to_hass + + The SwitchEntity class provides the functionality of a ToggleEntity and additional power + consumption methods and state attributes. + """ + + def __init__(self, coordinator, idx): + """Pass coordinator to CoordinatorEntity.""" + super().__init__(coordinator) + self.idx = idx + self.module = self.coordinator.data[self.idx] + + @property + def name(self): + """Name of the device.""" + return self.module.name + + @property + def unique_id(self): + """ID (unique) of the device.""" + return self.idx + + @property + def device_info(self): + """Device information.""" + return { + "identifiers": { + # Unique identifiers within the domain + (DOMAIN, self.unique_id) + }, + "name": self.name, + "manufacturer": "Legrand", + "model": HW_TYPE.get(self.module.hw_type), + "sw_version": self.module.fw, + } + + @property + def device_class(self): + """Return the class of this device, from component DEVICE_CLASSES.""" + if self.module.device == "plug": + return DEVICE_CLASS_OUTLET + return DEVICE_CLASS_SWITCH + + @property + def available(self) -> bool: + """Return if entity is available. + + This is the case when the coordinator is able to update the data successfully + AND the switch entity is reachable. + + This method overrides the one of the CoordinatorEntity + """ + return self.coordinator.last_update_success and self.module.reachable + + @property + def is_on(self): + """Return entity state.""" + return self.module.status == "on" + + async def async_turn_on(self, **kwargs): + """Turn the light on.""" + # Do the turning on. + await self.module.turn_on() + # Update the data + await self.coordinator.async_request_refresh() + + async def async_turn_off(self, **kwargs): + """Turn the entity off.""" + await self.module.turn_off() + # Update the data + await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/home_plus_control/translations/en.json b/homeassistant/components/home_plus_control/translations/en.json new file mode 100644 index 00000000000..41232f4b1a7 --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/en.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Timeout generating authorize URL.", + "missing_configuration": "The component is not configured. Please follow the documentation.", + "no_url_available": "No URL available. For information about this error, [check the help section]({docs_url})", + "single_instance_allowed": "Integration is already being configured in another instance. Only one is allowed at any one time.", + "oauth_error": "Error in the authentication flow." + }, + "create_entry": { + "default": "Successfully authenticated" + } + }, + "title": "Legrand Home+ Control" +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index b6799f59a04..d66736b2b3a 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -95,6 +95,7 @@ FLOWS = [ "hive", "hlk_sw16", "home_connect", + "home_plus_control", "homekit", "homekit_controller", "homematicip_cloud", diff --git a/requirements_all.txt b/requirements_all.txt index cf5073bdfba..11ef33c6436 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -771,6 +771,9 @@ homeconnect==0.6.3 # homeassistant.components.homematicip_cloud homematicip==0.13.1 +# homeassistant.components.home_plus_control +homepluscontrol==0.0.5 + # homeassistant.components.horizon horimote==0.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index dfe1e812cb5..a0e78661180 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -420,6 +420,9 @@ homeconnect==0.6.3 # homeassistant.components.homematicip_cloud homematicip==0.13.1 +# homeassistant.components.home_plus_control +homepluscontrol==0.0.5 + # homeassistant.components.google # homeassistant.components.remember_the_milk httplib2==0.19.0 diff --git a/tests/components/home_plus_control/__init__.py b/tests/components/home_plus_control/__init__.py new file mode 100644 index 00000000000..a9caba13e32 --- /dev/null +++ b/tests/components/home_plus_control/__init__.py @@ -0,0 +1 @@ +"""Tests for the Legrand Home+ Control integration.""" diff --git a/tests/components/home_plus_control/conftest.py b/tests/components/home_plus_control/conftest.py new file mode 100644 index 00000000000..cb9c869002f --- /dev/null +++ b/tests/components/home_plus_control/conftest.py @@ -0,0 +1,106 @@ +"""Test setup and fixtures for component Home+ Control by Legrand.""" +from homepluscontrol.homeplusinteractivemodule import HomePlusInteractiveModule +from homepluscontrol.homeplusplant import HomePlusPlant +import pytest + +from homeassistant import config_entries +from homeassistant.components.home_plus_control.const import DOMAIN + +from tests.common import MockConfigEntry + +CLIENT_ID = "1234" +CLIENT_SECRET = "5678" +SUBSCRIPTION_KEY = "12345678901234567890123456789012" + + +@pytest.fixture() +def mock_config_entry(): + """Return a fake config entry. + + This is a minimal entry to setup the integration and to ensure that the + OAuth access token will not expire. + """ + return MockConfigEntry( + domain=DOMAIN, + title="Home+ Control", + data={ + "auth_implementation": "home_plus_control", + "token": { + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": 9999999999, + "expires_at": 9999999999.99999999, + "expires_on": 9999999999, + }, + }, + source="test", + connection_class=config_entries.CONN_CLASS_LOCAL_POLL, + options={}, + system_options={"disable_new_entities": False}, + unique_id=DOMAIN, + entry_id="home_plus_control_entry_id", + ) + + +@pytest.fixture() +def mock_modules(): + """Return the full set of mock modules.""" + plant = HomePlusPlant( + id="123456789009876543210", name="My Home", country="ES", oauth_client=None + ) + modules = { + "0000000987654321fedcba": HomePlusInteractiveModule( + plant, + id="0000000987654321fedcba", + name="Kitchen Wall Outlet", + hw_type="NLP", + device="plug", + fw="42", + reachable=True, + ), + "0000000887654321fedcba": HomePlusInteractiveModule( + plant, + id="0000000887654321fedcba", + name="Bedroom Wall Outlet", + hw_type="NLP", + device="light", + fw="42", + reachable=True, + ), + "0000000787654321fedcba": HomePlusInteractiveModule( + plant, + id="0000000787654321fedcba", + name="Living Room Ceiling Light", + hw_type="NLF", + device="light", + fw="46", + reachable=True, + ), + "0000000687654321fedcba": HomePlusInteractiveModule( + plant, + id="0000000687654321fedcba", + name="Dining Room Ceiling Light", + hw_type="NLF", + device="light", + fw="46", + reachable=True, + ), + "0000000587654321fedcba": HomePlusInteractiveModule( + plant, + id="0000000587654321fedcba", + name="Dining Room Wall Outlet", + hw_type="NLP", + device="plug", + fw="42", + reachable=True, + ), + } + + # Set lights off and plugs on + for mod_stat in modules.values(): + mod_stat.status = "on" + if mod_stat.device == "light": + mod_stat.status = "off" + + return modules diff --git a/tests/components/home_plus_control/test_config_flow.py b/tests/components/home_plus_control/test_config_flow.py new file mode 100644 index 00000000000..4a7dbd3d3ee --- /dev/null +++ b/tests/components/home_plus_control/test_config_flow.py @@ -0,0 +1,192 @@ +"""Test the Legrand Home+ Control config flow.""" +from unittest.mock import patch + +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.home_plus_control.const import ( + CONF_SUBSCRIPTION_KEY, + DOMAIN, + OAUTH2_AUTHORIZE, + OAUTH2_TOKEN, +) +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET +from homeassistant.helpers import config_entry_oauth2_flow + +from tests.common import MockConfigEntry +from tests.components.home_plus_control.conftest import ( + CLIENT_ID, + CLIENT_SECRET, + SUBSCRIPTION_KEY, +) + + +async def test_full_flow( + hass, aiohttp_client, aioclient_mock, current_request_with_host +): + """Check full flow.""" + assert await setup.async_setup_component( + hass, + "home_plus_control", + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + result = await hass.config_entries.flow.async_init( + "home_plus_control", context={"source": config_entries.SOURCE_USER} + ) + + state = config_entry_oauth2_flow._encode_jwt( # pylint: disable=protected-access + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": "https://example.com/auth/external/callback", + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP + assert result["step_id"] == "auth" + assert result["url"] == ( + f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}" + "&redirect_uri=https://example.com/auth/external/callback" + f"&state={state}" + ) + + client = await aiohttp_client(hass.http.app) + resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") + assert resp.status == 200 + assert resp.headers["content-type"] == "text/html; charset=utf-8" + + aioclient_mock.post( + OAUTH2_TOKEN, + json={ + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": 60, + }, + ) + + with patch( + "homeassistant.components.home_plus_control.async_setup_entry", + return_value=True, + ) as mock_setup: + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "Home+ Control" + config_data = result["data"] + assert config_data["token"]["refresh_token"] == "mock-refresh-token" + assert config_data["token"]["access_token"] == "mock-access-token" + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert len(mock_setup.mock_calls) == 1 + + +async def test_abort_if_entry_in_progress(hass, current_request_with_host): + """Check flow abort when an entry is already in progress.""" + assert await setup.async_setup_component( + hass, + "home_plus_control", + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + + # Start one flow + result = await hass.config_entries.flow.async_init( + "home_plus_control", context={"source": config_entries.SOURCE_USER} + ) + + # Attempt to start another flow + result = await hass.config_entries.flow.async_init( + "home_plus_control", context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_in_progress" + + +async def test_abort_if_entry_exists(hass, current_request_with_host): + """Check flow abort when an entry already exists.""" + existing_entry = MockConfigEntry(domain=DOMAIN) + existing_entry.add_to_hass(hass) + + assert await setup.async_setup_component( + hass, + "home_plus_control", + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + "http": {}, + }, + ) + + result = await hass.config_entries.flow.async_init( + "home_plus_control", context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "single_instance_allowed" + + +async def test_abort_if_invalid_token( + hass, aiohttp_client, aioclient_mock, current_request_with_host +): + """Check flow abort when the token has an invalid value.""" + assert await setup.async_setup_component( + hass, + "home_plus_control", + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + result = await hass.config_entries.flow.async_init( + "home_plus_control", context={"source": config_entries.SOURCE_USER} + ) + + state = config_entry_oauth2_flow._encode_jwt( # pylint: disable=protected-access + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": "https://example.com/auth/external/callback", + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP + assert result["step_id"] == "auth" + assert result["url"] == ( + f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}" + "&redirect_uri=https://example.com/auth/external/callback" + f"&state={state}" + ) + + client = await aiohttp_client(hass.http.app) + resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") + assert resp.status == 200 + assert resp.headers["content-type"] == "text/html; charset=utf-8" + + aioclient_mock.post( + OAUTH2_TOKEN, + json={ + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": "non-integer", + }, + ) + + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "oauth_error" diff --git a/tests/components/home_plus_control/test_init.py b/tests/components/home_plus_control/test_init.py new file mode 100644 index 00000000000..e48a9dc1f85 --- /dev/null +++ b/tests/components/home_plus_control/test_init.py @@ -0,0 +1,75 @@ +"""Test the Legrand Home+ Control integration.""" +from unittest.mock import patch + +from homeassistant import config_entries, setup +from homeassistant.components.home_plus_control.const import ( + CONF_SUBSCRIPTION_KEY, + DOMAIN, +) +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET + +from tests.components.home_plus_control.conftest import ( + CLIENT_ID, + CLIENT_SECRET, + SUBSCRIPTION_KEY, +) + + +async def test_loading(hass, mock_config_entry): + """Test component loading.""" + mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value={}, + ) as mock_check: + await setup.async_setup_component( + hass, + DOMAIN, + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + await hass.async_block_till_done() + + assert len(mock_check.mock_calls) == 1 + assert mock_config_entry.state == config_entries.ENTRY_STATE_LOADED + + +async def test_loading_with_no_config(hass, mock_config_entry): + """Test component loading failure when it has not configuration.""" + mock_config_entry.add_to_hass(hass) + await setup.async_setup_component(hass, DOMAIN, {}) + # Component setup fails because the oauth2 implementation could not be registered + assert mock_config_entry.state == config_entries.ENTRY_STATE_SETUP_ERROR + + +async def test_unloading(hass, mock_config_entry): + """Test component unloading.""" + mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value={}, + ) as mock_check: + await setup.async_setup_component( + hass, + DOMAIN, + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + await hass.async_block_till_done() + + assert len(mock_check.mock_calls) == 1 + assert mock_config_entry.state == config_entries.ENTRY_STATE_LOADED + + # We now unload the entry + assert await hass.config_entries.async_unload(mock_config_entry.entry_id) + assert mock_config_entry.state == config_entries.ENTRY_STATE_NOT_LOADED diff --git a/tests/components/home_plus_control/test_switch.py b/tests/components/home_plus_control/test_switch.py new file mode 100644 index 00000000000..f699fe08d05 --- /dev/null +++ b/tests/components/home_plus_control/test_switch.py @@ -0,0 +1,464 @@ +"""Test the Legrand Home+ Control switch platform.""" +import datetime as dt +from unittest.mock import patch + +from homepluscontrol.homeplusapi import HomePlusControlApiError + +from homeassistant import config_entries, setup +from homeassistant.components.home_plus_control.const import ( + CONF_SUBSCRIPTION_KEY, + DOMAIN, +) +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + STATE_OFF, + STATE_ON, + STATE_UNAVAILABLE, +) + +from tests.common import async_fire_time_changed +from tests.components.home_plus_control.conftest import ( + CLIENT_ID, + CLIENT_SECRET, + SUBSCRIPTION_KEY, +) + + +def entity_assertions( + hass, + num_exp_entities, + num_exp_devices=None, + expected_entities=None, + expected_devices=None, +): + """Assert number of entities and devices.""" + entity_reg = hass.helpers.entity_registry.async_get(hass) + device_reg = hass.helpers.device_registry.async_get(hass) + + if num_exp_devices is None: + num_exp_devices = num_exp_entities + + assert len(entity_reg.entities) == num_exp_entities + assert len(device_reg.devices) == num_exp_devices + + if expected_entities is not None: + for exp_entity_id, present in expected_entities.items(): + assert bool(entity_reg.async_get(exp_entity_id)) == present + + if expected_devices is not None: + for exp_device_id, present in expected_devices.items(): + assert bool(device_reg.async_get(exp_device_id)) == present + + +def one_entity_state(hass, device_uid): + """Assert the presence of an entity and return its state.""" + entity_reg = hass.helpers.entity_registry.async_get(hass) + device_reg = hass.helpers.device_registry.async_get(hass) + + device_id = device_reg.async_get_device({(DOMAIN, device_uid)}).id + entity_entries = hass.helpers.entity_registry.async_entries_for_device( + entity_reg, device_id + ) + + assert len(entity_entries) == 1 + entity_entry = entity_entries[0] + return hass.states.get(entity_entry.entity_id).state + + +async def test_plant_update( + hass, + mock_config_entry, + mock_modules, +): + """Test entity and device loading.""" + # Load the entry + mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + await setup.async_setup_component( + hass, + DOMAIN, + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # Check the entities and devices + entity_assertions( + hass, + num_exp_entities=5, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": True, + }, + ) + + +async def test_plant_topology_reduction_change( + hass, + mock_config_entry, + mock_modules, +): + """Test an entity leaving the plant topology.""" + # Load the entry + mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + await setup.async_setup_component( + hass, + DOMAIN, + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # Check the entities and devices - 5 mock entities + entity_assertions( + hass, + num_exp_entities=5, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": True, + }, + ) + + # Now we refresh the topology with one entity less + mock_modules.pop("0000000987654321fedcba") + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + async_fire_time_changed( + hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=100) + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # Check for plant, topology and module status - this time only 4 left + entity_assertions( + hass, + num_exp_entities=4, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": False, + }, + ) + + +async def test_plant_topology_increase_change( + hass, + mock_config_entry, + mock_modules, +): + """Test an entity entering the plant topology.""" + # Remove one module initially + new_module = mock_modules.pop("0000000987654321fedcba") + + # Load the entry + mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + await setup.async_setup_component( + hass, + DOMAIN, + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # Check the entities and devices - we have 4 entities to start with + entity_assertions( + hass, + num_exp_entities=4, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": False, + }, + ) + + # Now we refresh the topology with one entity more + mock_modules["0000000987654321fedcba"] = new_module + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + async_fire_time_changed( + hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=100) + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + entity_assertions( + hass, + num_exp_entities=5, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": True, + }, + ) + + +async def test_module_status_unavailable(hass, mock_config_entry, mock_modules): + """Test a module becoming unreachable in the plant.""" + # Load the entry + mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + await setup.async_setup_component( + hass, + DOMAIN, + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # Check the entities and devices - 5 mock entities + entity_assertions( + hass, + num_exp_entities=5, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": True, + }, + ) + + # Confirm the availability of this particular entity + test_entity_uid = "0000000987654321fedcba" + test_entity_state = one_entity_state(hass, test_entity_uid) + assert test_entity_state == STATE_ON + + # Now we refresh the topology with the module being unreachable + mock_modules["0000000987654321fedcba"].reachable = False + + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + async_fire_time_changed( + hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=100) + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # Assert the devices and entities + entity_assertions( + hass, + num_exp_entities=5, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": True, + }, + ) + await hass.async_block_till_done() + # The entity is present, but not available + test_entity_state = one_entity_state(hass, test_entity_uid) + assert test_entity_state == STATE_UNAVAILABLE + + +async def test_module_status_available( + hass, + mock_config_entry, + mock_modules, +): + """Test a module becoming reachable in the plant.""" + # Set the module initially unreachable + mock_modules["0000000987654321fedcba"].reachable = False + + # Load the entry + mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + await setup.async_setup_component( + hass, + DOMAIN, + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # Assert the devices and entities + entity_assertions( + hass, + num_exp_entities=5, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": True, + }, + ) + + # This particular entity is not available + test_entity_uid = "0000000987654321fedcba" + test_entity_state = one_entity_state(hass, test_entity_uid) + assert test_entity_state == STATE_UNAVAILABLE + + # Now we refresh the topology with the module being reachable + mock_modules["0000000987654321fedcba"].reachable = True + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + async_fire_time_changed( + hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=100) + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # Assert the devices and entities remain the same + entity_assertions( + hass, + num_exp_entities=5, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": True, + }, + ) + + # Now the entity is available + test_entity_uid = "0000000987654321fedcba" + test_entity_state = one_entity_state(hass, test_entity_uid) + assert test_entity_state == STATE_ON + + +async def test_initial_api_error( + hass, + mock_config_entry, + mock_modules, +): + """Test an API error on initial call.""" + # Load the entry + mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + side_effect=HomePlusControlApiError, + ) as mock_check: + await setup.async_setup_component( + hass, + DOMAIN, + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # The component has been loaded + assert mock_config_entry.state == config_entries.ENTRY_STATE_LOADED + + # Check the entities and devices - None have been configured + entity_assertions(hass, num_exp_entities=0) + + +async def test_update_with_api_error( + hass, + mock_config_entry, + mock_modules, +): + """Test an API timeout when updating the module data.""" + # Load the entry + mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + await setup.async_setup_component( + hass, + DOMAIN, + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # The component has been loaded + assert mock_config_entry.state == config_entries.ENTRY_STATE_LOADED + + # Check the entities and devices - all entities should be there + entity_assertions( + hass, + num_exp_entities=5, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": True, + }, + ) + for test_entity_uid in mock_modules: + test_entity_state = one_entity_state(hass, test_entity_uid) + assert test_entity_state in (STATE_ON, STATE_OFF) + + # Attempt to update the data, but API update fails + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + side_effect=HomePlusControlApiError, + ) as mock_check: + async_fire_time_changed( + hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=100) + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # Assert the devices and entities - all should still be present + entity_assertions( + hass, + num_exp_entities=5, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": True, + }, + ) + + # This entity has not returned a status, so appears as unavailable + for test_entity_uid in mock_modules: + test_entity_state = one_entity_state(hass, test_entity_uid) + assert test_entity_state == STATE_UNAVAILABLE From 9f07ca069d20720e4dbb0261088328b58957ab57 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 25 Mar 2021 14:19:32 +0100 Subject: [PATCH 600/831] Fix zha manual flow test (#48317) --- tests/components/zha/test_config_flow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/components/zha/test_config_flow.py b/tests/components/zha/test_config_flow.py index b3dbefbdbf0..f41c734537b 100644 --- a/tests/components/zha/test_config_flow.py +++ b/tests/components/zha/test_config_flow.py @@ -86,6 +86,7 @@ async def test_user_flow_show_form(hass): assert result["step_id"] == "user" +@patch("serial.tools.list_ports.comports", MagicMock(return_value=[])) async def test_user_flow_show_manual(hass): """Test user flow manual entry when no comport detected.""" result = await hass.config_entries.flow.async_init( From 4f4a6fd6a5a978279ccced3906d3c290943ac249 Mon Sep 17 00:00:00 2001 From: William Scanlon Date: Thu, 25 Mar 2021 12:06:51 -0400 Subject: [PATCH 601/831] Add econet thermostat support and use getattr for sensors (#45564) Co-authored-by: Martin Hjelmare --- .coveragerc | 1 + homeassistant/components/econet/__init__.py | 16 +- .../components/econet/binary_sensor.py | 59 +++-- homeassistant/components/econet/climate.py | 241 ++++++++++++++++++ homeassistant/components/econet/manifest.json | 2 +- homeassistant/components/econet/sensor.py | 99 ++++--- .../components/econet/water_heater.py | 7 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 9 files changed, 338 insertions(+), 91 deletions(-) create mode 100644 homeassistant/components/econet/climate.py diff --git a/.coveragerc b/.coveragerc index cec2649b132..712c5292e57 100644 --- a/.coveragerc +++ b/.coveragerc @@ -221,6 +221,7 @@ omit = homeassistant/components/ecobee/weather.py homeassistant/components/econet/__init__.py homeassistant/components/econet/binary_sensor.py + homeassistant/components/econet/climate.py homeassistant/components/econet/const.py homeassistant/components/econet/sensor.py homeassistant/components/econet/water_heater.py diff --git a/homeassistant/components/econet/__init__.py b/homeassistant/components/econet/__init__.py index 03ca9c76120..e605b16a237 100644 --- a/homeassistant/components/econet/__init__.py +++ b/homeassistant/components/econet/__init__.py @@ -13,7 +13,7 @@ from pyeconet.errors import ( PyeconetError, ) -from homeassistant.const import CONF_EMAIL, CONF_PASSWORD +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, TEMP_FAHRENHEIT from homeassistant.core import callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.dispatcher import dispatcher_send @@ -24,7 +24,7 @@ from .const import API_CLIENT, DOMAIN, EQUIPMENT _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["binary_sensor", "sensor", "water_heater"] +PLATFORMS = ["climate", "binary_sensor", "sensor", "water_heater"] PUSH_UPDATE = "econet.push_update" INTERVAL = timedelta(minutes=60) @@ -54,7 +54,9 @@ async def async_setup_entry(hass, config_entry): raise ConfigEntryNotReady from err try: - equipment = await api.get_equipment_by_type([EquipmentType.WATER_HEATER]) + equipment = await api.get_equipment_by_type( + [EquipmentType.WATER_HEATER, EquipmentType.THERMOSTAT] + ) except (ClientError, GenericHTTPError, InvalidResponseFormat) as err: raise ConfigEntryNotReady from err hass.data[DOMAIN][API_CLIENT][config_entry.entry_id] = api @@ -74,6 +76,9 @@ async def async_setup_entry(hass, config_entry): for _eqip in equipment[EquipmentType.WATER_HEATER]: _eqip.set_update_callback(update_published) + for _eqip in equipment[EquipmentType.THERMOSTAT]: + _eqip.set_update_callback(update_published) + async def resubscribe(now): """Resubscribe to the MQTT updates.""" await hass.async_add_executor_job(api.unsubscribe) @@ -149,6 +154,11 @@ class EcoNetEntity(Entity): """Return the unique ID of the entity.""" return f"{self._econet.device_id}_{self._econet.device_name}" + @property + def temperature_unit(self): + """Return the unit of measurement.""" + return TEMP_FAHRENHEIT + @property def should_poll(self) -> bool: """Return True if entity has to be polled for state. diff --git a/homeassistant/components/econet/binary_sensor.py b/homeassistant/components/econet/binary_sensor.py index b87e6bb0cd0..116b1243ee0 100644 --- a/homeassistant/components/econet/binary_sensor.py +++ b/homeassistant/components/econet/binary_sensor.py @@ -2,8 +2,10 @@ from pyeconet.equipment import EquipmentType from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_LOCK, DEVICE_CLASS_OPENING, DEVICE_CLASS_POWER, + DEVICE_CLASS_SOUND, BinarySensorEntity, ) @@ -12,27 +14,40 @@ from .const import DOMAIN, EQUIPMENT SENSOR_NAME_RUNNING = "running" SENSOR_NAME_SHUTOFF_VALVE = "shutoff_valve" -SENSOR_NAME_VACATION = "vacation" +SENSOR_NAME_RUNNING = "running" +SENSOR_NAME_SCREEN_LOCKED = "screen_locked" +SENSOR_NAME_BEEP_ENABLED = "beep_enabled" + +ATTR = "attr" +DEVICE_CLASS = "device_class" +SENSORS = { + SENSOR_NAME_SHUTOFF_VALVE: { + ATTR: "shutoff_valve_open", + DEVICE_CLASS: DEVICE_CLASS_OPENING, + }, + SENSOR_NAME_RUNNING: {ATTR: "running", DEVICE_CLASS: DEVICE_CLASS_POWER}, + SENSOR_NAME_SCREEN_LOCKED: { + ATTR: "screen_locked", + DEVICE_CLASS: DEVICE_CLASS_LOCK, + }, + SENSOR_NAME_BEEP_ENABLED: { + ATTR: "beep_enabled", + DEVICE_CLASS: DEVICE_CLASS_SOUND, + }, +} async def async_setup_entry(hass, entry, async_add_entities): """Set up EcoNet binary sensor based on a config entry.""" equipment = hass.data[DOMAIN][EQUIPMENT][entry.entry_id] binary_sensors = [] - for water_heater in equipment[EquipmentType.WATER_HEATER]: - if water_heater.has_shutoff_valve: - binary_sensors.append( - EcoNetBinarySensor( - water_heater, - SENSOR_NAME_SHUTOFF_VALVE, - ) - ) - if water_heater.running is not None: - binary_sensors.append(EcoNetBinarySensor(water_heater, SENSOR_NAME_RUNNING)) - if water_heater.vacation is not None: - binary_sensors.append( - EcoNetBinarySensor(water_heater, SENSOR_NAME_VACATION) - ) + all_equipment = equipment[EquipmentType.WATER_HEATER].copy() + all_equipment.extend(equipment[EquipmentType.THERMOSTAT].copy()) + for _equip in all_equipment: + for sensor_name, sensor in SENSORS.items(): + if getattr(_equip, sensor[ATTR], None) is not None: + binary_sensors.append(EcoNetBinarySensor(_equip, sensor_name)) + async_add_entities(binary_sensors) @@ -48,22 +63,12 @@ class EcoNetBinarySensor(EcoNetEntity, BinarySensorEntity): @property def is_on(self): """Return true if the binary sensor is on.""" - if self._device_name == SENSOR_NAME_SHUTOFF_VALVE: - return self._econet.shutoff_valve_open - if self._device_name == SENSOR_NAME_RUNNING: - return self._econet.running - if self._device_name == SENSOR_NAME_VACATION: - return self._econet.vacation - return False + return getattr(self._econet, SENSORS[self._device_name][ATTR]) @property def device_class(self): """Return the class of this sensor, from DEVICE_CLASSES.""" - if self._device_name == SENSOR_NAME_SHUTOFF_VALVE: - return DEVICE_CLASS_OPENING - if self._device_name == SENSOR_NAME_RUNNING: - return DEVICE_CLASS_POWER - return None + return SENSORS[self._device_name][DEVICE_CLASS] @property def name(self): diff --git a/homeassistant/components/econet/climate.py b/homeassistant/components/econet/climate.py new file mode 100644 index 00000000000..fe50855d559 --- /dev/null +++ b/homeassistant/components/econet/climate.py @@ -0,0 +1,241 @@ +"""Support for Rheem EcoNet thermostats.""" +import logging + +from pyeconet.equipment import EquipmentType +from pyeconet.equipment.thermostat import ThermostatFanMode, ThermostatOperationMode + +from homeassistant.components.climate import ClimateEntity +from homeassistant.components.climate.const import ( + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + FAN_AUTO, + FAN_HIGH, + FAN_LOW, + FAN_MEDIUM, + HVAC_MODE_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, + SUPPORT_AUX_HEAT, + SUPPORT_FAN_MODE, + SUPPORT_TARGET_HUMIDITY, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_RANGE, +) +from homeassistant.const import ATTR_TEMPERATURE + +from . import EcoNetEntity +from .const import DOMAIN, EQUIPMENT + +_LOGGER = logging.getLogger(__name__) + +ECONET_STATE_TO_HA = { + ThermostatOperationMode.HEATING: HVAC_MODE_HEAT, + ThermostatOperationMode.COOLING: HVAC_MODE_COOL, + ThermostatOperationMode.OFF: HVAC_MODE_OFF, + ThermostatOperationMode.AUTO: HVAC_MODE_HEAT_COOL, + ThermostatOperationMode.FAN_ONLY: HVAC_MODE_FAN_ONLY, +} +HA_STATE_TO_ECONET = {value: key for key, value in ECONET_STATE_TO_HA.items()} + +ECONET_FAN_STATE_TO_HA = { + ThermostatFanMode.AUTO: FAN_AUTO, + ThermostatFanMode.LOW: FAN_LOW, + ThermostatFanMode.MEDIUM: FAN_MEDIUM, + ThermostatFanMode.HIGH: FAN_HIGH, +} +HA_FAN_STATE_TO_ECONET = {value: key for key, value in ECONET_FAN_STATE_TO_HA.items()} + +SUPPORT_FLAGS_THERMOSTAT = ( + SUPPORT_TARGET_TEMPERATURE + | SUPPORT_TARGET_TEMPERATURE_RANGE + | SUPPORT_FAN_MODE + | SUPPORT_AUX_HEAT +) + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up EcoNet thermostat based on a config entry.""" + equipment = hass.data[DOMAIN][EQUIPMENT][entry.entry_id] + async_add_entities( + [ + EcoNetThermostat(thermostat) + for thermostat in equipment[EquipmentType.THERMOSTAT] + ], + ) + + +class EcoNetThermostat(EcoNetEntity, ClimateEntity): + """Define a Econet thermostat.""" + + def __init__(self, thermostat): + """Initialize.""" + super().__init__(thermostat) + self._running = thermostat.running + self._poll = True + self.econet_state_to_ha = {} + self.ha_state_to_econet = {} + self.op_list = [] + for mode in self._econet.modes: + if mode not in [ + ThermostatOperationMode.UNKNOWN, + ThermostatOperationMode.EMERGENCY_HEAT, + ]: + ha_mode = ECONET_STATE_TO_HA[mode] + self.op_list.append(ha_mode) + + @property + def supported_features(self): + """Return the list of supported features.""" + if self._econet.supports_humidifier: + return SUPPORT_FLAGS_THERMOSTAT | SUPPORT_TARGET_HUMIDITY + return SUPPORT_FLAGS_THERMOSTAT + + @property + def current_temperature(self): + """Return the current temperature.""" + return self._econet.set_point + + @property + def current_humidity(self): + """Return the current humidity.""" + return self._econet.humidity + + @property + def target_humidity(self): + """Return the humidity we try to reach.""" + if self._econet.supports_humidifier: + return self._econet.dehumidifier_set_point + return None + + @property + def target_temperature(self): + """Return the temperature we try to reach.""" + if self.hvac_mode == HVAC_MODE_COOL: + return self._econet.cool_set_point + if self.hvac_mode == HVAC_MODE_HEAT: + return self._econet.heat_set_point + return None + + @property + def target_temperature_low(self): + """Return the lower bound temperature we try to reach.""" + if self.hvac_mode == HVAC_MODE_HEAT_COOL: + return self._econet.heat_set_point + return None + + @property + def target_temperature_high(self): + """Return the higher bound temperature we try to reach.""" + if self.hvac_mode == HVAC_MODE_HEAT_COOL: + return self._econet.cool_set_point + return None + + def set_temperature(self, **kwargs): + """Set new target temperature.""" + target_temp = kwargs.get(ATTR_TEMPERATURE) + target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW) + target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) + if target_temp: + self._econet.set_set_point(target_temp, None, None) + if target_temp_low or target_temp_high: + self._econet.set_set_point(None, target_temp_high, target_temp_low) + + @property + def is_aux_heat(self): + """Return true if aux heater.""" + return self._econet.mode == ThermostatOperationMode.EMERGENCY_HEAT + + @property + def hvac_modes(self): + """Return hvac operation ie. heat, cool mode. + + Needs to be one of HVAC_MODE_*. + """ + return self.op_list + + @property + def hvac_mode(self) -> str: + """Return hvac operation ie. heat, cool, mode. + + Needs to be one of HVAC_MODE_*. + """ + econet_mode = self._econet.mode + _current_op = HVAC_MODE_OFF + if econet_mode is not None: + _current_op = ECONET_STATE_TO_HA[econet_mode] + + return _current_op + + def set_hvac_mode(self, hvac_mode): + """Set new target hvac mode.""" + hvac_mode_to_set = HA_STATE_TO_ECONET.get(hvac_mode) + if hvac_mode_to_set is None: + raise ValueError(f"{hvac_mode} is not a valid mode.") + self._econet.set_mode(hvac_mode_to_set) + + def set_humidity(self, humidity: int): + """Set new target humidity.""" + self._econet.set_dehumidifier_set_point(humidity) + + @property + def fan_mode(self): + """Return the current fan mode.""" + econet_fan_mode = self._econet.fan_mode + + # Remove this after we figure out how to handle med lo and med hi + if econet_fan_mode in [ThermostatFanMode.MEDHI, ThermostatFanMode.MEDLO]: + econet_fan_mode = ThermostatFanMode.MEDIUM + + _current_fan_mode = FAN_AUTO + if econet_fan_mode is not None: + _current_fan_mode = ECONET_FAN_STATE_TO_HA[econet_fan_mode] + return _current_fan_mode + + @property + def fan_modes(self): + """Return the fan modes.""" + econet_fan_modes = self._econet.fan_modes + fan_list = [] + for mode in econet_fan_modes: + # Remove the MEDLO MEDHI once we figure out how to handle it + if mode not in [ + ThermostatFanMode.UNKNOWN, + ThermostatFanMode.MEDLO, + ThermostatFanMode.MEDHI, + ]: + fan_list.append(ECONET_FAN_STATE_TO_HA[mode]) + return fan_list + + def set_fan_mode(self, fan_mode): + """Set the fan mode.""" + self._econet.set_fan_mode(HA_FAN_STATE_TO_ECONET[fan_mode]) + + def turn_aux_heat_on(self): + """Turn auxiliary heater on.""" + self._econet.set_mode(ThermostatOperationMode.EMERGENCY_HEAT) + + def turn_aux_heat_off(self): + """Turn auxiliary heater off.""" + self._econet.set_mode(ThermostatOperationMode.HEATING) + + @property + def min_temp(self): + """Return the minimum temperature.""" + return self._econet.set_point_limits[0] + + @property + def max_temp(self): + """Return the maximum temperature.""" + return self._econet.set_point_limits[1] + + @property + def min_humidity(self) -> int: + """Return the minimum humidity.""" + return self._econet.dehumidifier_set_point_limits[0] + + @property + def max_humidity(self) -> int: + """Return the maximum humidity.""" + return self._econet.dehumidifier_set_point_limits[1] diff --git a/homeassistant/components/econet/manifest.json b/homeassistant/components/econet/manifest.json index 7e4cf0106ba..c658542295e 100644 --- a/homeassistant/components/econet/manifest.json +++ b/homeassistant/components/econet/manifest.json @@ -4,6 +4,6 @@ "name": "Rheem EcoNet Products", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/econet", - "requirements": ["pyeconet==0.1.12"], + "requirements": ["pyeconet==0.1.13"], "codeowners": ["@vangorra", "@w1ll1am23"] } \ No newline at end of file diff --git a/homeassistant/components/econet/sensor.py b/homeassistant/components/econet/sensor.py index 05fa1b734ea..0dfe8df7fb3 100644 --- a/homeassistant/components/econet/sensor.py +++ b/homeassistant/components/econet/sensor.py @@ -3,9 +3,9 @@ from pyeconet.equipment import EquipmentType from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( + DEVICE_CLASS_SIGNAL_STRENGTH, ENERGY_KILO_WATT_HOUR, PERCENTAGE, - SIGNAL_STRENGTH_DECIBELS_MILLIWATT, VOLUME_GALLONS, ) @@ -13,6 +13,7 @@ from . import EcoNetEntity from .const import DOMAIN, EQUIPMENT ENERGY_KILO_BRITISH_THERMAL_UNIT = "kBtu" + TANK_HEALTH = "tank_health" AVAILIBLE_HOT_WATER = "availible_hot_water" COMPRESSOR_HEALTH = "compressor_health" @@ -23,28 +24,51 @@ ALERT_COUNT = "alert_count" WIFI_SIGNAL = "wifi_signal" RUNNING_STATE = "running_state" +SENSOR_NAMES_TO_ATTRIBUTES = { + TANK_HEALTH: "tank_health", + AVAILIBLE_HOT_WATER: "tank_hot_water_availability", + COMPRESSOR_HEALTH: "compressor_health", + OVERRIDE_STATUS: "override_status", + WATER_USAGE_TODAY: "todays_water_usage", + POWER_USAGE_TODAY: "todays_energy_usage", + ALERT_COUNT: "alert_count", + WIFI_SIGNAL: "wifi_signal", + RUNNING_STATE: "running_state", +} + +SENSOR_NAMES_TO_UNIT_OF_MEASUREMENT = { + TANK_HEALTH: PERCENTAGE, + AVAILIBLE_HOT_WATER: PERCENTAGE, + COMPRESSOR_HEALTH: PERCENTAGE, + OVERRIDE_STATUS: None, + WATER_USAGE_TODAY: VOLUME_GALLONS, + POWER_USAGE_TODAY: None, # Depends on unit type + ALERT_COUNT: None, + WIFI_SIGNAL: DEVICE_CLASS_SIGNAL_STRENGTH, + RUNNING_STATE: None, # This is just a string +} + async def async_setup_entry(hass, entry, async_add_entities): """Set up EcoNet sensor based on a config entry.""" + equipment = hass.data[DOMAIN][EQUIPMENT][entry.entry_id] sensors = [] + all_equipment = equipment[EquipmentType.WATER_HEATER].copy() + all_equipment.extend(equipment[EquipmentType.THERMOSTAT].copy()) + + for _equip in all_equipment: + for name, attribute in SENSOR_NAMES_TO_ATTRIBUTES.items(): + if getattr(_equip, attribute, None) is not None: + sensors.append(EcoNetSensor(_equip, name)) + # This is None to start with and all device have it + sensors.append(EcoNetSensor(_equip, WIFI_SIGNAL)) + for water_heater in equipment[EquipmentType.WATER_HEATER]: - if water_heater.tank_hot_water_availability is not None: - sensors.append(EcoNetSensor(water_heater, AVAILIBLE_HOT_WATER)) - if water_heater.tank_health is not None: - sensors.append(EcoNetSensor(water_heater, TANK_HEALTH)) - if water_heater.compressor_health is not None: - sensors.append(EcoNetSensor(water_heater, COMPRESSOR_HEALTH)) - if water_heater.override_status: - sensors.append(EcoNetSensor(water_heater, OVERRIDE_STATUS)) - if water_heater.running_state is not None: - sensors.append(EcoNetSensor(water_heater, RUNNING_STATE)) - # All units have this - sensors.append(EcoNetSensor(water_heater, ALERT_COUNT)) # These aren't part of the device and start off as None in pyeconet so always add them sensors.append(EcoNetSensor(water_heater, WATER_USAGE_TODAY)) sensors.append(EcoNetSensor(water_heater, POWER_USAGE_TODAY)) - sensors.append(EcoNetSensor(water_heater, WIFI_SIGNAL)) + async_add_entities(sensors) @@ -60,50 +84,21 @@ class EcoNetSensor(EcoNetEntity, SensorEntity): @property def state(self): """Return sensors state.""" - if self._device_name == AVAILIBLE_HOT_WATER: - return self._econet.tank_hot_water_availability - if self._device_name == TANK_HEALTH: - return self._econet.tank_health - if self._device_name == COMPRESSOR_HEALTH: - return self._econet.compressor_health - if self._device_name == OVERRIDE_STATUS: - return self._econet.oveerride_status - if self._device_name == WATER_USAGE_TODAY: - if self._econet.todays_water_usage: - return round(self._econet.todays_water_usage, 2) - return None - if self._device_name == POWER_USAGE_TODAY: - if self._econet.todays_energy_usage: - return round(self._econet.todays_energy_usage, 2) - return None - if self._device_name == WIFI_SIGNAL: - if self._econet.wifi_signal: - return self._econet.wifi_signal - return None - if self._device_name == ALERT_COUNT: - return self._econet.alert_count - if self._device_name == RUNNING_STATE: - return self._econet.running_state - return None + value = getattr(self._econet, SENSOR_NAMES_TO_ATTRIBUTES[self._device_name]) + if isinstance(value, float): + value = round(value, 2) + return value @property def unit_of_measurement(self): """Return the unit of measurement of this entity, if any.""" - if self._device_name == AVAILIBLE_HOT_WATER: - return PERCENTAGE - if self._device_name == TANK_HEALTH: - return PERCENTAGE - if self._device_name == COMPRESSOR_HEALTH: - return PERCENTAGE - if self._device_name == WATER_USAGE_TODAY: - return VOLUME_GALLONS + unit_of_measurement = SENSOR_NAMES_TO_UNIT_OF_MEASUREMENT[self._device_name] if self._device_name == POWER_USAGE_TODAY: if self._econet.energy_type == ENERGY_KILO_BRITISH_THERMAL_UNIT.upper(): - return ENERGY_KILO_BRITISH_THERMAL_UNIT - return ENERGY_KILO_WATT_HOUR - if self._device_name == WIFI_SIGNAL: - return SIGNAL_STRENGTH_DECIBELS_MILLIWATT - return None + unit_of_measurement = ENERGY_KILO_BRITISH_THERMAL_UNIT + else: + unit_of_measurement = ENERGY_KILO_WATT_HOUR + return unit_of_measurement @property def name(self): diff --git a/homeassistant/components/econet/water_heater.py b/homeassistant/components/econet/water_heater.py index af3399b53af..ed31e78af7c 100644 --- a/homeassistant/components/econet/water_heater.py +++ b/homeassistant/components/econet/water_heater.py @@ -18,7 +18,6 @@ from homeassistant.components.water_heater import ( SUPPORT_TARGET_TEMPERATURE, WaterHeaterEntity, ) -from homeassistant.const import TEMP_FAHRENHEIT from homeassistant.core import callback from . import EcoNetEntity @@ -77,11 +76,6 @@ class EcoNetWaterHeater(EcoNetEntity, WaterHeaterEntity): """Return true if away mode is on.""" return self._econet.away - @property - def temperature_unit(self): - """Return the unit of measurement.""" - return TEMP_FAHRENHEIT - @property def current_operation(self): """Return current operation.""" @@ -160,6 +154,7 @@ class EcoNetWaterHeater(EcoNetEntity, WaterHeaterEntity): """Get the latest energy usage.""" await self.water_heater.get_energy_usage() await self.water_heater.get_water_usage() + self.async_write_ha_state() self._poll = False def turn_away_mode_on(self): diff --git a/requirements_all.txt b/requirements_all.txt index 11ef33c6436..6730162928e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1355,7 +1355,7 @@ pydroid-ipcam==0.8 pyebox==1.1.4 # homeassistant.components.econet -pyeconet==0.1.12 +pyeconet==0.1.13 # homeassistant.components.edimax pyedimax==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a0e78661180..c44a19e997d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -708,7 +708,7 @@ pydexcom==0.2.0 pydispatcher==2.0.5 # homeassistant.components.econet -pyeconet==0.1.12 +pyeconet==0.1.13 # homeassistant.components.everlights pyeverlights==0.1.0 From f0e5e616a79665e2a301efeb56b66e48359373b2 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 25 Mar 2021 18:35:01 +0100 Subject: [PATCH 602/831] Fix device discovery of OAuth2 config flows (#48326) --- homeassistant/config_entries.py | 8 ++++--- homeassistant/data_entry_flow.py | 6 ++--- .../helpers/config_entry_oauth2_flow.py | 22 ++++--------------- tests/components/cloud/test_account_link.py | 2 +- tests/components/somfy/test_config_flow.py | 4 +++- .../components/withings/test_binary_sensor.py | 2 +- tests/components/withings/test_common.py | 3 +++ tests/components/withings/test_config_flow.py | 2 +- tests/components/withings/test_init.py | 5 ++++- tests/components/withings/test_sensor.py | 4 ++-- .../helpers/test_config_entry_oauth2_flow.py | 8 +++++-- 11 files changed, 33 insertions(+), 33 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 6bdb850e643..849e3dcfd14 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1049,11 +1049,13 @@ class ConfigFlow(data_entry_flow.FlowHandler): } @callback - def _async_in_progress(self) -> list[dict]: + def _async_in_progress(self, include_uninitialized: bool = False) -> list[dict]: """Return other in progress flows for current domain.""" return [ flw - for flw in self.hass.config_entries.flow.async_progress() + for flw in self.hass.config_entries.flow.async_progress( + include_uninitialized=include_uninitialized + ) if flw["handler"] == self.handler and flw["flow_id"] != self.flow_id ] @@ -1093,7 +1095,7 @@ class ConfigFlow(data_entry_flow.FlowHandler): self._abort_if_unique_id_configured() # Abort if any other flow for this handler is already in progress - if self._async_in_progress(): + if self._async_in_progress(include_uninitialized=True): raise data_entry_flow.AbortFlow("already_in_progress") async def async_step_discovery( diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index b149493373f..40c9ace0f8d 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -94,17 +94,17 @@ class FlowManager(abc.ABC): """Entry has finished executing its first step asynchronously.""" @callback - def async_progress(self) -> list[dict]: + def async_progress(self, include_uninitialized: bool = False) -> list[dict]: """Return the flows in progress.""" return [ { "flow_id": flow.flow_id, "handler": flow.handler, "context": flow.context, - "step_id": flow.cur_step["step_id"], + "step_id": flow.cur_step["step_id"] if flow.cur_step else None, } for flow in self._progress.values() - if flow.cur_step is not None + if include_uninitialized or flow.cur_step is not None ] async def async_init( diff --git a/homeassistant/helpers/config_entry_oauth2_flow.py b/homeassistant/helpers/config_entry_oauth2_flow.py index a949685c7b1..795c08dd1c9 100644 --- a/homeassistant/helpers/config_entry_oauth2_flow.py +++ b/homeassistant/helpers/config_entry_oauth2_flow.py @@ -245,8 +245,10 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): if not implementations: return self.async_abort(reason="missing_configuration") - if len(implementations) == 1: - # Pick first implementation as we have only one. + req = http.current_request.get() + if len(implementations) == 1 and req is not None: + # Pick first implementation if we have only one, but only + # if this is triggered by a user interaction (request). self.flow_impl = list(implementations.values())[0] return await self.async_step_auth() @@ -313,23 +315,7 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): """ return self.async_create_entry(title=self.flow_impl.name, data=data) - async def async_step_discovery( - self, discovery_info: dict[str, Any] - ) -> dict[str, Any]: - """Handle a flow initialized by discovery.""" - await self.async_set_unique_id(self.DOMAIN) - - if self.hass.config_entries.async_entries(self.DOMAIN): - return self.async_abort(reason="already_configured") - - return await self.async_step_pick_implementation() - async_step_user = async_step_pick_implementation - async_step_mqtt = async_step_discovery - async_step_ssdp = async_step_discovery - async_step_zeroconf = async_step_discovery - async_step_homekit = async_step_discovery - async_step_dhcp = async_step_discovery @classmethod def async_register_implementation( diff --git a/tests/components/cloud/test_account_link.py b/tests/components/cloud/test_account_link.py index 62225597939..c1022dc6aae 100644 --- a/tests/components/cloud/test_account_link.py +++ b/tests/components/cloud/test_account_link.py @@ -108,7 +108,7 @@ async def test_get_services_error(hass): assert account_link.DATA_SERVICES not in hass.data -async def test_implementation(hass, flow_handler): +async def test_implementation(hass, flow_handler, current_request_with_host): """Test Cloud OAuth2 implementation.""" hass.data["cloud"] = None diff --git a/tests/components/somfy/test_config_flow.py b/tests/components/somfy/test_config_flow.py index 47adb5bdc91..60200824c00 100644 --- a/tests/components/somfy/test_config_flow.py +++ b/tests/components/somfy/test_config_flow.py @@ -123,7 +123,9 @@ async def test_full_flow( assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED -async def test_abort_if_authorization_timeout(hass, mock_impl): +async def test_abort_if_authorization_timeout( + hass, mock_impl, current_request_with_host +): """Check Somfy authorization timeout.""" flow = config_flow.SomfyFlowHandler() flow.hass = hass diff --git a/tests/components/withings/test_binary_sensor.py b/tests/components/withings/test_binary_sensor.py index 22c6b6de862..9f93e00f4ef 100644 --- a/tests/components/withings/test_binary_sensor.py +++ b/tests/components/withings/test_binary_sensor.py @@ -15,7 +15,7 @@ from .common import ComponentFactory, new_profile_config async def test_binary_sensor( - hass: HomeAssistant, component_factory: ComponentFactory + hass: HomeAssistant, component_factory: ComponentFactory, current_request_with_host ) -> None: """Test binary sensor.""" in_bed_attribute = WITHINGS_MEASUREMENTS_MAP[Measurement.IN_BED] diff --git a/tests/components/withings/test_common.py b/tests/components/withings/test_common.py index a5946ff0533..ef51f12398f 100644 --- a/tests/components/withings/test_common.py +++ b/tests/components/withings/test_common.py @@ -74,6 +74,7 @@ async def test_webhook_post( arg_user_id: Any, arg_appli: Any, expected_code: int, + current_request_with_host, ) -> None: """Test webhook callback.""" person0 = new_profile_config("person0", user_id) @@ -107,6 +108,7 @@ async def test_webhook_head( hass: HomeAssistant, component_factory: ComponentFactory, aiohttp_client, + current_request_with_host, ) -> None: """Test head method on webhook view.""" person0 = new_profile_config("person0", 0) @@ -124,6 +126,7 @@ async def test_webhook_put( hass: HomeAssistant, component_factory: ComponentFactory, aiohttp_client, + current_request_with_host, ) -> None: """Test webhook callback.""" person0 = new_profile_config("person0", 0) diff --git a/tests/components/withings/test_config_flow.py b/tests/components/withings/test_config_flow.py index 8380c134013..4cbada948f4 100644 --- a/tests/components/withings/test_config_flow.py +++ b/tests/components/withings/test_config_flow.py @@ -34,7 +34,7 @@ async def test_config_non_unique_profile(hass: HomeAssistant) -> None: async def test_config_reauth_profile( - hass: HomeAssistant, aiohttp_client, aioclient_mock + hass: HomeAssistant, aiohttp_client, aioclient_mock, current_request_with_host ) -> None: """Test reauth an existing profile re-creates the config entry.""" hass_config = { diff --git a/tests/components/withings/test_init.py b/tests/components/withings/test_init.py index a9948860745..465c26deb32 100644 --- a/tests/components/withings/test_init.py +++ b/tests/components/withings/test_init.py @@ -125,7 +125,10 @@ async def test_async_setup_no_config(hass: HomeAssistant) -> None: ], ) async def test_auth_failure( - hass: HomeAssistant, component_factory: ComponentFactory, exception: Exception + hass: HomeAssistant, + component_factory: ComponentFactory, + exception: Exception, + current_request_with_host, ) -> None: """Test auth failure.""" person0 = new_profile_config( diff --git a/tests/components/withings/test_sensor.py b/tests/components/withings/test_sensor.py index 8b51f62514d..71e69967796 100644 --- a/tests/components/withings/test_sensor.py +++ b/tests/components/withings/test_sensor.py @@ -302,7 +302,7 @@ def async_assert_state_equals( async def test_sensor_default_enabled_entities( - hass: HomeAssistant, component_factory: ComponentFactory + hass: HomeAssistant, component_factory: ComponentFactory, current_request_with_host ) -> None: """Test entities enabled by default.""" entity_registry: EntityRegistry = er.async_get(hass) @@ -343,7 +343,7 @@ async def test_sensor_default_enabled_entities( async def test_all_entities( - hass: HomeAssistant, component_factory: ComponentFactory + hass: HomeAssistant, component_factory: ComponentFactory, current_request_with_host ) -> None: """Test all entities.""" entity_registry: EntityRegistry = er.async_get(hass) diff --git a/tests/helpers/test_config_entry_oauth2_flow.py b/tests/helpers/test_config_entry_oauth2_flow.py index 617c4690696..3fad758b34b 100644 --- a/tests/helpers/test_config_entry_oauth2_flow.py +++ b/tests/helpers/test_config_entry_oauth2_flow.py @@ -113,7 +113,9 @@ async def test_abort_if_no_implementation(hass, flow_handler): assert result["reason"] == "missing_configuration" -async def test_abort_if_authorization_timeout(hass, flow_handler, local_impl): +async def test_abort_if_authorization_timeout( + hass, flow_handler, local_impl, current_request_with_host +): """Check timeout generating authorization url.""" flow_handler.async_register_implementation(hass, local_impl) @@ -129,7 +131,9 @@ async def test_abort_if_authorization_timeout(hass, flow_handler, local_impl): assert result["reason"] == "authorize_url_timeout" -async def test_abort_if_no_url_available(hass, flow_handler, local_impl): +async def test_abort_if_no_url_available( + hass, flow_handler, local_impl, current_request_with_host +): """Check no_url_available generating authorization url.""" flow_handler.async_register_implementation(hass, local_impl) From ec1334099e9dd3d62db7e86e95c3f1789214d814 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Thu, 25 Mar 2021 19:07:45 +0100 Subject: [PATCH 603/831] Add tests for Netatmo data handler (#46373) * Add tests for Netatmo data handler * Clean up coveragerc * Move block to fixture * Minor update * Remove tests of implementation details for data handler * Update homeassistant/components/netatmo/data_handler.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/netatmo/data_handler.py Co-authored-by: Martin Hjelmare * Import callback Co-authored-by: Martin Hjelmare --- .coveragerc | 2 - .../components/netatmo/data_handler.py | 10 +++- tests/components/netatmo/test_camera.py | 50 ++++++++++++++++++- tests/components/netatmo/test_init.py | 5 +- 4 files changed, 60 insertions(+), 7 deletions(-) diff --git a/.coveragerc b/.coveragerc index 712c5292e57..eaeee0f992d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -647,8 +647,6 @@ omit = homeassistant/components/nederlandse_spoorwegen/sensor.py homeassistant/components/nello/lock.py homeassistant/components/nest/legacy/* - homeassistant/components/netatmo/data_handler.py - homeassistant/components/netatmo/helper.py homeassistant/components/netdata/sensor.py homeassistant/components/netgear/device_tracker.py homeassistant/components/netgear_lte/* diff --git a/homeassistant/components/netatmo/data_handler.py b/homeassistant/components/netatmo/data_handler.py index 358ae79edd2..6982a651a45 100644 --- a/homeassistant/components/netatmo/data_handler.py +++ b/homeassistant/components/netatmo/data_handler.py @@ -12,7 +12,7 @@ from typing import Deque import pyatmo from homeassistant.config_entries import ConfigEntry -from homeassistant.core import CALLBACK_TYPE, HomeAssistant +from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.event import async_track_time_interval @@ -98,6 +98,12 @@ class NetatmoDataHandler: self._queue.rotate(BATCH_SIZE) + @callback + def async_force_update(self, data_class_entry): + """Prioritize data retrieval for given data class entry.""" + self._data_classes[data_class_entry][NEXT_SCAN] = time() + self._queue.rotate(-(self._queue.index(self._data_classes[data_class_entry]))) + async def async_cleanup(self): """Clean up the Netatmo data handler.""" for listener in self.listeners: @@ -115,7 +121,7 @@ class NetatmoDataHandler: elif event["data"]["push_type"] == "NACamera-connection": _LOGGER.debug("%s camera reconnected", MANUFACTURER) - self._data_classes[CAMERA_DATA_CLASS_NAME][NEXT_SCAN] = time() + self.async_force_update(CAMERA_DATA_CLASS_NAME) async def async_fetch_data(self, data_class, data_class_entry, **kwargs): """Fetch data and notify.""" diff --git a/tests/components/netatmo/test_camera.py b/tests/components/netatmo/test_camera.py index a58f2f3e1aa..10e3ca46b2a 100644 --- a/tests/components/netatmo/test_camera.py +++ b/tests/components/netatmo/test_camera.py @@ -1,4 +1,5 @@ """The tests for Netatmo camera.""" +from datetime import timedelta from unittest.mock import patch from homeassistant.components import camera @@ -9,8 +10,11 @@ from homeassistant.components.netatmo.const import ( SERVICE_SET_PERSONS_HOME, ) from homeassistant.const import CONF_WEBHOOK_ID +from homeassistant.util import dt -from .common import simulate_webhook +from .common import fake_post_request, simulate_webhook + +from tests.common import async_fire_time_changed async def test_setup_component_with_webhook(hass, camera_entry): @@ -205,3 +209,47 @@ async def test_service_set_camera_light(hass, camera_entry): camera_id="12:34:56:00:a5:a4", floodlight="on", ) + + +async def test_camera_reconnect_webhook(hass, config_entry): + """Test webhook event on camera reconnect.""" + with patch( + "homeassistant.components.netatmo.api.ConfigEntryNetatmoAuth.post_request" + ) as mock_post, patch( + "homeassistant.components.netatmo.PLATFORMS", ["camera"] + ), patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), patch( + "homeassistant.components.webhook.async_generate_url" + ) as mock_webhook: + mock_post.side_effect = fake_post_request + mock_webhook.return_value = "https://example.com" + await hass.config_entries.async_setup(config_entry.entry_id) + + await hass.async_block_till_done() + + webhook_id = config_entry.data[CONF_WEBHOOK_ID] + + # Fake webhook activation + response = { + "push_type": "webhook_activation", + } + await simulate_webhook(hass, webhook_id, response) + await hass.async_block_till_done() + + mock_post.assert_called() + mock_post.reset_mock() + + # Fake camera reconnect + response = { + "push_type": "NACamera-connection", + } + await simulate_webhook(hass, webhook_id, response) + await hass.async_block_till_done() + + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(seconds=60), + ) + await hass.async_block_till_done() + mock_post.assert_called() diff --git a/tests/components/netatmo/test_init.py b/tests/components/netatmo/test_init.py index 49e3ab14684..1ad07fe55d3 100644 --- a/tests/components/netatmo/test_init.py +++ b/tests/components/netatmo/test_init.py @@ -152,7 +152,7 @@ async def test_setup_without_https(hass, config_entry): "homeassistant.components.webhook.async_generate_url" ) as mock_webhook: mock_auth.return_value.post_request.side_effect = fake_post_request - mock_webhook.return_value = "http://example.com" + mock_webhook.return_value = "https://example.com" assert await async_setup_component( hass, "netatmo", {"netatmo": {"client_id": "123", "client_secret": "abc"}} ) @@ -166,7 +166,8 @@ async def test_setup_without_https(hass, config_entry): climate_entity_livingroom = "climate.netatmo_livingroom" assert hass.states.get(climate_entity_livingroom).state == "auto" await simulate_webhook(hass, webhook_id, FAKE_WEBHOOK) - assert hass.states.get(climate_entity_livingroom).state == "auto" + await hass.async_block_till_done() + assert hass.states.get(climate_entity_livingroom).state == "heat" async def test_setup_with_cloud(hass, config_entry): From e42ca35c947a9fadb0a201edf431d45f09ef3da8 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 25 Mar 2021 13:12:12 -0500 Subject: [PATCH 604/831] Bump plexwebsocket to 0.0.13 (#48330) --- homeassistant/components/plex/__init__.py | 7 +++---- homeassistant/components/plex/manifest.json | 2 +- homeassistant/components/plex/server.py | 6 +----- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/plex/helpers.py | 4 ++-- 6 files changed, 9 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index d84a86984e2..296f1594549 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -7,7 +7,6 @@ import plexapi.exceptions from plexapi.gdm import GDM from plexwebsocket import ( SIGNAL_CONNECTION_STATE, - SIGNAL_DATA, STATE_CONNECTED, STATE_DISCONNECTED, STATE_STOPPED, @@ -158,9 +157,9 @@ async def async_setup_entry(hass, entry): hass.data[PLEX_DOMAIN][DISPATCHERS][server_id].append(unsub) @callback - def plex_websocket_callback(signal, data, error): + def plex_websocket_callback(msgtype, data, error): """Handle callbacks from plexwebsocket library.""" - if signal == SIGNAL_CONNECTION_STATE: + if msgtype == SIGNAL_CONNECTION_STATE: if data == STATE_CONNECTED: _LOGGER.debug("Websocket to %s successful", entry.data[CONF_SERVER]) @@ -178,7 +177,7 @@ async def async_setup_entry(hass, entry): ) hass.async_create_task(hass.config_entries.async_reload(entry.entry_id)) - elif signal == SIGNAL_DATA: + elif msgtype == "playing": hass.async_create_task(plex_server.async_update_session(data)) session = async_get_clientsession(hass) diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index 9410fba1258..e0e62d7150b 100644 --- a/homeassistant/components/plex/manifest.json +++ b/homeassistant/components/plex/manifest.json @@ -6,7 +6,7 @@ "requirements": [ "plexapi==4.5.1", "plexauth==0.0.6", - "plexwebsocket==0.0.12" + "plexwebsocket==0.0.13" ], "dependencies": ["http"], "codeowners": ["@jjlawren"] diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index d1210e6f3d8..841a9e7cc0d 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -257,11 +257,7 @@ class PlexServer: async def async_update_session(self, payload): """Process a session payload received from a websocket callback.""" - try: - session_payload = payload["PlaySessionStateNotification"][0] - except KeyError: - await self.async_update_platforms() - return + session_payload = payload["PlaySessionStateNotification"][0] state = session_payload["state"] if state == "buffering": diff --git a/requirements_all.txt b/requirements_all.txt index 6730162928e..d40244074a9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1140,7 +1140,7 @@ plexapi==4.5.1 plexauth==0.0.6 # homeassistant.components.plex -plexwebsocket==0.0.12 +plexwebsocket==0.0.13 # homeassistant.components.plugwise plugwise==0.8.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c44a19e997d..149c156f4cc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -586,7 +586,7 @@ plexapi==4.5.1 plexauth==0.0.6 # homeassistant.components.plex -plexwebsocket==0.0.12 +plexwebsocket==0.0.13 # homeassistant.components.plugwise plugwise==0.8.5 diff --git a/tests/components/plex/helpers.py b/tests/components/plex/helpers.py index 2fca88fae27..35a01c3bfff 100644 --- a/tests/components/plex/helpers.py +++ b/tests/components/plex/helpers.py @@ -1,7 +1,7 @@ """Helper methods for Plex tests.""" from datetime import timedelta -from plexwebsocket import SIGNAL_CONNECTION_STATE, SIGNAL_DATA, STATE_CONNECTED +from plexwebsocket import SIGNAL_CONNECTION_STATE, STATE_CONNECTED import homeassistant.util.dt as dt_util @@ -29,7 +29,7 @@ def websocket_connected(mock_websocket): def trigger_plex_update(mock_websocket, payload=UPDATE_PAYLOAD): """Call the websocket callback method with a Plex update.""" callback = mock_websocket.call_args[0][1] - callback(SIGNAL_DATA, payload, None) + callback("playing", payload, None) async def wait_for_debouncer(hass): From 056f7d493c02999ec54b9be507c37dbc99500d22 Mon Sep 17 00:00:00 2001 From: Alexey Kustov Date: Thu, 25 Mar 2021 22:15:24 +0400 Subject: [PATCH 605/831] Support overriding token in notifify.event service (#47133) * Add opportunity to define token for each message * Update homeassistant/components/notify_events/notify.py Co-authored-by: Erik Montnemery Co-authored-by: Erik Montnemery --- homeassistant/components/notify_events/notify.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/notify_events/notify.py b/homeassistant/components/notify_events/notify.py index 23df01a128b..ce7c353badb 100644 --- a/homeassistant/components/notify_events/notify.py +++ b/homeassistant/components/notify_events/notify.py @@ -28,6 +28,8 @@ ATTR_FILE_MIME_TYPE = "mime_type" ATTR_FILE_KIND_FILE = "file" ATTR_FILE_KIND_IMAGE = "image" +ATTR_TOKEN = "token" + _LOGGER = logging.getLogger(__name__) @@ -114,7 +116,12 @@ class NotifyEventsNotificationService(BaseNotificationService): def send_message(self, message, **kwargs): """Send a message.""" + token = self.token data = kwargs.get(ATTR_DATA) or {} msg = self.prepare_message(message, data) - msg.send(self.token) + + if data.get(ATTR_TOKEN, "").trim(): + token = data[ATTR_TOKEN] + + msg.send(token) From 88b5eff726155d44064e4c954e00ef8e326c2046 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 25 Mar 2021 20:02:17 +0100 Subject: [PATCH 606/831] Fix late comment to PR adding percentage support to deCONZ fan platform (#48333) --- homeassistant/components/deconz/fan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/deconz/fan.py b/homeassistant/components/deconz/fan.py index 3d2e61fdd7b..b8faa95a9b2 100644 --- a/homeassistant/components/deconz/fan.py +++ b/homeassistant/components/deconz/fan.py @@ -81,7 +81,7 @@ class DeconzFan(DeconzDevice, FanEntity): if self._device.speed == 0: return 0 if self._device.speed not in ORDERED_NAMED_FAN_SPEEDS: - return + return None return ordered_list_item_to_percentage( ORDERED_NAMED_FAN_SPEEDS, self._device.speed ) From 1dc25a5864f13077e9e6f0d4765c02953a04730d Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 25 Mar 2021 21:09:06 +0100 Subject: [PATCH 607/831] Bump python-typing-update to 0.3.2 (#48303) * Bump python-version-update to 0.3.2 * Changes after update * Fix pylint issues --- .pre-commit-config.yaml | 2 +- homeassistant/auth/permissions/models.py | 6 ++++-- homeassistant/components/deconz/fan.py | 5 ++--- homeassistant/components/yeelight/light.py | 6 ++++-- homeassistant/util/async_.py | 4 +++- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2ed5a8bd8d3..254ed637d81 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -69,7 +69,7 @@ repos: - id: prettier stages: [manual] - repo: https://github.com/cdce8p/python-typing-update - rev: v0.3.0 + rev: v0.3.2 hooks: # Run `python-typing-update` hook manually from time to time # to update python typing syntax. diff --git a/homeassistant/auth/permissions/models.py b/homeassistant/auth/permissions/models.py index b2d1955865a..aa1a777ced2 100644 --- a/homeassistant/auth/permissions/models.py +++ b/homeassistant/auth/permissions/models.py @@ -1,4 +1,6 @@ """Models for permissions.""" +from __future__ import annotations + from typing import TYPE_CHECKING import attr @@ -14,5 +16,5 @@ if TYPE_CHECKING: class PermissionLookup: """Class to hold data for permission lookups.""" - entity_registry: "ent_reg.EntityRegistry" = attr.ib() - device_registry: "dev_reg.DeviceRegistry" = attr.ib() + entity_registry: ent_reg.EntityRegistry = attr.ib() + device_registry: dev_reg.DeviceRegistry = attr.ib() diff --git a/homeassistant/components/deconz/fan.py b/homeassistant/components/deconz/fan.py index b8faa95a9b2..aca92f893c7 100644 --- a/homeassistant/components/deconz/fan.py +++ b/homeassistant/components/deconz/fan.py @@ -1,6 +1,5 @@ """Support for deCONZ fans.""" - -from typing import Optional +from __future__ import annotations from homeassistant.components.fan import ( DOMAIN, @@ -76,7 +75,7 @@ class DeconzFan(DeconzDevice, FanEntity): return self._device.speed != 0 @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" if self._device.speed == 0: return 0 diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index 4c1cb9a0e82..218bcbbdb27 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -1,10 +1,13 @@ """Light platform support for yeelight.""" +from __future__ import annotations + from functools import partial import logging import voluptuous as vol import yeelight from yeelight import ( + Bulb, BulbException, Flow, RGBTransition, @@ -529,9 +532,8 @@ class YeelightGenericLight(YeelightEntity, LightEntity): """Return the current effect.""" return self._effect - # F821: https://github.com/PyCQA/pyflakes/issues/373 @property - def _bulb(self) -> "Bulb": # noqa: F821 + def _bulb(self) -> Bulb: return self.device.bulb @property diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index f61225502ee..0fd1b564f0d 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -1,4 +1,6 @@ """Asyncio utilities.""" +from __future__ import annotations + from asyncio import Semaphore, coroutines, ensure_future, gather, get_running_loop from asyncio.events import AbstractEventLoop import concurrent.futures @@ -38,7 +40,7 @@ def fire_coroutine_threadsafe(coro: Coroutine, loop: AbstractEventLoop) -> None: def run_callback_threadsafe( loop: AbstractEventLoop, callback: Callable[..., T], *args: Any -) -> "concurrent.futures.Future[T]": +) -> concurrent.futures.Future[T]: # pylint: disable=unsubscriptable-object """Submit a callback object to a given event loop. Return a concurrent.futures.Future to access the result. From d5afd0afb3205f5ccf029142e2a26a9617f100d8 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 26 Mar 2021 00:04:15 +0000 Subject: [PATCH 608/831] [ci skip] Translation update --- .../accuweather/translations/nl.json | 3 +- .../components/airnow/translations/nl.json | 4 +- .../components/august/translations/nl.json | 16 ++++++++ .../bmw_connected_drive/translations/nl.json | 10 +++++ .../components/broadlink/translations/nl.json | 8 +++- .../components/cast/translations/ca.json | 4 +- .../components/cast/translations/et.json | 4 +- .../components/cast/translations/it.json | 4 +- .../components/cast/translations/nl.json | 4 +- .../components/cast/translations/ru.json | 4 +- .../components/gios/translations/nl.json | 5 +++ .../components/hive/translations/nl.json | 4 ++ .../home_plus_control/translations/ca.json | 21 ++++++++++ .../home_plus_control/translations/en.json | 10 ++++- .../home_plus_control/translations/et.json | 21 ++++++++++ .../home_plus_control/translations/it.json | 21 ++++++++++ .../home_plus_control/translations/nl.json | 21 ++++++++++ .../home_plus_control/translations/ru.json | 21 ++++++++++ .../components/hyperion/translations/nl.json | 14 +++++++ .../components/kodi/translations/nl.json | 6 +++ .../lutron_caseta/translations/nl.json | 1 + .../motion_blinds/translations/nl.json | 7 +++- .../opentherm_gw/translations/nl.json | 1 + .../components/ozw/translations/nl.json | 13 +++++++ .../philips_js/translations/nl.json | 7 ++++ .../components/plaato/translations/nl.json | 1 + .../progettihwsw/translations/nl.json | 3 +- .../rainmachine/translations/nl.json | 3 ++ .../recollect_waste/translations/nl.json | 3 ++ .../components/rfxtrx/translations/nl.json | 3 +- .../components/risco/translations/nl.json | 15 +++++-- .../screenlogic/translations/nl.json | 39 +++++++++++++++++++ .../components/sentry/translations/nl.json | 6 ++- .../somfy_mylink/translations/nl.json | 13 ++++++- .../srp_energy/translations/nl.json | 1 + .../components/tuya/translations/nl.json | 2 + .../components/verisure/translations/nl.json | 3 +- .../water_heater/translations/nl.json | 1 + 38 files changed, 306 insertions(+), 21 deletions(-) create mode 100644 homeassistant/components/home_plus_control/translations/ca.json create mode 100644 homeassistant/components/home_plus_control/translations/et.json create mode 100644 homeassistant/components/home_plus_control/translations/it.json create mode 100644 homeassistant/components/home_plus_control/translations/nl.json create mode 100644 homeassistant/components/home_plus_control/translations/ru.json create mode 100644 homeassistant/components/screenlogic/translations/nl.json diff --git a/homeassistant/components/accuweather/translations/nl.json b/homeassistant/components/accuweather/translations/nl.json index 342df3cca78..f04d93b5921 100644 --- a/homeassistant/components/accuweather/translations/nl.json +++ b/homeassistant/components/accuweather/translations/nl.json @@ -34,7 +34,8 @@ }, "system_health": { "info": { - "can_reach_server": "Kan AccuWeather server bereiken" + "can_reach_server": "Kan AccuWeather server bereiken", + "remaining_requests": "Resterende toegestane verzoeken" } } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/nl.json b/homeassistant/components/airnow/translations/nl.json index 011498269f8..090a5363823 100644 --- a/homeassistant/components/airnow/translations/nl.json +++ b/homeassistant/components/airnow/translations/nl.json @@ -14,8 +14,10 @@ "data": { "api_key": "API-sleutel", "latitude": "Breedtegraad", - "longitude": "Lengtegraad" + "longitude": "Lengtegraad", + "radius": "Stationsradius (mijl; optioneel)" }, + "description": "AirNow luchtkwaliteit integratie opzetten. Om een API sleutel te genereren ga naar https://docs.airnowapi.org/account/request/", "title": "AirNow" } } diff --git a/homeassistant/components/august/translations/nl.json b/homeassistant/components/august/translations/nl.json index 41af77b0c7c..05a5a4c5265 100644 --- a/homeassistant/components/august/translations/nl.json +++ b/homeassistant/components/august/translations/nl.json @@ -10,6 +10,13 @@ "unknown": "Onverwachte fout" }, "step": { + "reauth_validate": { + "data": { + "password": "Wachtwoord" + }, + "description": "Voer het wachtwoord in voor {username} .", + "title": "Verifieer een August-account opnieuw" + }, "user": { "data": { "login_method": "Aanmeldmethode", @@ -20,6 +27,15 @@ "description": "Als de aanmeldingsmethode 'e-mail' is, is gebruikersnaam het e-mailadres. Als de aanmeldingsmethode 'telefoon' is, is gebruikersnaam het telefoonnummer in de indeling '+ NNNNNNNNN'.", "title": "Stel een augustus-account in" }, + "user_validate": { + "data": { + "login_method": "Inlogmethode", + "password": "Wachtwoord", + "username": "Gebruikersnaam" + }, + "description": "Als de aanmeldingsmethode 'e-mail' is, is de gebruikersnaam het e-mailadres. Als de aanmeldingsmethode 'telefoon' is, is de gebruikersnaam het telefoonnummer in de indeling '+ NNNNNNNNN'.", + "title": "Stel een August account in" + }, "validation": { "data": { "code": "Verificatiecode" diff --git a/homeassistant/components/bmw_connected_drive/translations/nl.json b/homeassistant/components/bmw_connected_drive/translations/nl.json index 83ae0b9ff7d..8fa6c839112 100644 --- a/homeassistant/components/bmw_connected_drive/translations/nl.json +++ b/homeassistant/components/bmw_connected_drive/translations/nl.json @@ -16,5 +16,15 @@ } } } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "Alleen-lezen (alleen sensoren en notificatie, geen uitvoering van diensten, geen vergrendeling)", + "use_location": "Gebruik Home Assistant locatie voor auto locatie peilingen (vereist voor niet i3/i8 voertuigen geproduceerd voor 7/2014)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/nl.json b/homeassistant/components/broadlink/translations/nl.json index 138caf9a5b7..06c26235d0a 100644 --- a/homeassistant/components/broadlink/translations/nl.json +++ b/homeassistant/components/broadlink/translations/nl.json @@ -15,6 +15,9 @@ }, "flow_title": "{name} ({model} bij {host})", "step": { + "auth": { + "title": "Authenticeer naar het apparaat" + }, "finish": { "data": { "name": "Naam" @@ -22,12 +25,15 @@ "title": "Kies een naam voor het apparaat" }, "reset": { + "description": "{name} ( {model} op {host} ) is vergrendeld. U moet het apparaat ontgrendelen om te verifi\u00ebren en de configuratie te voltooien. Instructies:\n 1. Open de Broadlink-app.\n 2. Klik op het apparaat.\n 3. Klik op '...' in de rechterbovenhoek.\n 4. Scrol naar de onderkant van de pagina.\n 5. Schakel het slot uit.", "title": "Ontgrendel het apparaat" }, "unlock": { "data": { "unlock": "Ja, doe het." - } + }, + "description": "{name} ( {model} op {host} ) is vergrendeld. Dit kan leiden tot authenticatieproblemen in Home Assistant. Wilt u deze ontgrendelen?", + "title": "Ontgrendel het apparaat (optioneel)" }, "user": { "data": { diff --git a/homeassistant/components/cast/translations/ca.json b/homeassistant/components/cast/translations/ca.json index 9cb55f4d731..6a5d16aa6bb 100644 --- a/homeassistant/components/cast/translations/ca.json +++ b/homeassistant/components/cast/translations/ca.json @@ -27,7 +27,9 @@ "step": { "options": { "data": { - "known_hosts": "Llista opcional d'amfitrions coneguts per si el descobriment mDNS deixa de funcionar." + "ignore_cec": "Llista opcional que es passar\u00e0 a pychromecast.IGNORE_CEC.", + "known_hosts": "Llista opcional d'amfitrions coneguts per si el descobriment mDNS deixa de funcionar.", + "uuid": "Llista opcional d'UUIDs. No s'afegiran 'casts' que no siguin a la llista." }, "description": "Introdueix la configuraci\u00f3 de Google Cast." } diff --git a/homeassistant/components/cast/translations/et.json b/homeassistant/components/cast/translations/et.json index 9e126d50af0..6397951272a 100644 --- a/homeassistant/components/cast/translations/et.json +++ b/homeassistant/components/cast/translations/et.json @@ -27,7 +27,9 @@ "step": { "options": { "data": { - "known_hosts": "Valikuline loend teadaolevatest hostidest kui mDNS-i tuvastamine ei t\u00f6\u00f6ta." + "ignore_cec": "Valikuline nimekiri mis edastatakse pychromecast.IGNORE_CEC-ile.", + "known_hosts": "Valikuline loend teadaolevatest hostidest kui mDNS-i tuvastamine ei t\u00f6\u00f6ta.", + "uuid": "Valikuline UUIDide loend. Loetlemata cast-e ei lisata." }, "description": "Sisesta Google Casti andmed." } diff --git a/homeassistant/components/cast/translations/it.json b/homeassistant/components/cast/translations/it.json index 17abade539e..83586bf9f2c 100644 --- a/homeassistant/components/cast/translations/it.json +++ b/homeassistant/components/cast/translations/it.json @@ -27,7 +27,9 @@ "step": { "options": { "data": { - "known_hosts": "Elenco facoltativo di host noti se l'individuazione di mDNS non funziona." + "ignore_cec": "Elenco opzionale che sar\u00e0 passato a pychromecast.IGNORE_CEC.", + "known_hosts": "Elenco facoltativo di host noti se l'individuazione di mDNS non funziona.", + "uuid": "Elenco opzionale di UUID. I cast non elencati non saranno aggiunti." }, "description": "Inserisci la configurazione di Google Cast." } diff --git a/homeassistant/components/cast/translations/nl.json b/homeassistant/components/cast/translations/nl.json index 5c6cfae7c6b..02bf7514761 100644 --- a/homeassistant/components/cast/translations/nl.json +++ b/homeassistant/components/cast/translations/nl.json @@ -27,7 +27,9 @@ "step": { "options": { "data": { - "known_hosts": "Optionele lijst van bekende hosts indien mDNS discovery niet werkt." + "ignore_cec": "Optionele lijst die zal worden doorgegeven aan pychromecast.IGNORE_CEC.", + "known_hosts": "Optionele lijst van bekende hosts indien mDNS discovery niet werkt.", + "uuid": "Optionele lijst van UUID's. Casts die niet in de lijst staan, worden niet toegevoegd." }, "description": "Voer de Google Cast configuratie in." } diff --git a/homeassistant/components/cast/translations/ru.json b/homeassistant/components/cast/translations/ru.json index e565cedbfad..7c412476151 100644 --- a/homeassistant/components/cast/translations/ru.json +++ b/homeassistant/components/cast/translations/ru.json @@ -27,7 +27,9 @@ "step": { "options": { "data": { - "known_hosts": "\u041d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0445 \u0445\u043e\u0441\u0442\u043e\u0432, \u0435\u0441\u043b\u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435 mDNS \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442." + "ignore_cec": "\u041d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d \u0432 pychromecast.IGNORE_CEC.", + "known_hosts": "\u041d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0445 \u0445\u043e\u0441\u0442\u043e\u0432, \u0435\u0441\u043b\u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435 mDNS \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442.", + "uuid": "\u041d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a UUID. \u041d\u0435 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u044b\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u043d\u0435 \u0431\u0443\u0434\u0443\u0442." }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Google Cast." } diff --git a/homeassistant/components/gios/translations/nl.json b/homeassistant/components/gios/translations/nl.json index 7224c29f318..baac3c6dc77 100644 --- a/homeassistant/components/gios/translations/nl.json +++ b/homeassistant/components/gios/translations/nl.json @@ -18,5 +18,10 @@ "title": "GIO\u015a (Poolse hoofdinspectie van milieubescherming)" } } + }, + "system_health": { + "info": { + "can_reach_server": "Bereik GIO\u015a server" + } } } \ No newline at end of file diff --git a/homeassistant/components/hive/translations/nl.json b/homeassistant/components/hive/translations/nl.json index 96ea799c0fe..3ac45ae14d7 100644 --- a/homeassistant/components/hive/translations/nl.json +++ b/homeassistant/components/hive/translations/nl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol", "unknown_entry": "Kan bestaand item niet vinden." }, "error": { @@ -16,6 +17,7 @@ "data": { "2fa": "Tweefactorauthenticatiecode" }, + "description": "Voer uw Hive-verificatiecode in. \n \n Voer code 0000 in om een andere code aan te vragen.", "title": "Hive tweefactorauthenticatie" }, "reauth": { @@ -32,6 +34,7 @@ "scan_interval": "Scaninterval (seconden)", "username": "Gebruikersnaam" }, + "description": "Voer uw Hive login informatie en configuratie in.", "title": "Hive-aanmelding" } } @@ -42,6 +45,7 @@ "data": { "scan_interval": "Scaninterval (seconden)" }, + "description": "Werk het scaninterval bij om vaker naar gegevens te vragen.", "title": "Opties voor Hive" } } diff --git a/homeassistant/components/home_plus_control/translations/ca.json b/homeassistant/components/home_plus_control/translations/ca.json new file mode 100644 index 00000000000..90e23fcd7ab --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/ca.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "El compte ja ha estat configurat", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", + "authorize_url_timeout": "Temps d'espera esgotat durant la generaci\u00f3 de l'URL d'autoritzaci\u00f3.", + "missing_configuration": "El component no est\u00e0 configurat. Mira'n la documentaci\u00f3.", + "no_url_available": "No hi ha cap URL disponible. Per a m\u00e9s informaci\u00f3 sobre aquest error, [consulta la secci\u00f3 d'ajuda]({docs_url})", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." + }, + "create_entry": { + "default": "Autenticaci\u00f3 exitosa" + }, + "step": { + "pick_implementation": { + "title": "Selecciona el m\u00e8tode d'autenticaci\u00f3" + } + } + }, + "title": "Legrand Home+ Control" +} \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/en.json b/homeassistant/components/home_plus_control/translations/en.json index 41232f4b1a7..f5f8afe73d1 100644 --- a/homeassistant/components/home_plus_control/translations/en.json +++ b/homeassistant/components/home_plus_control/translations/en.json @@ -1,14 +1,20 @@ { "config": { "abort": { + "already_configured": "Account is already configured", + "already_in_progress": "Configuration flow is already in progress", "authorize_url_timeout": "Timeout generating authorize URL.", "missing_configuration": "The component is not configured. Please follow the documentation.", "no_url_available": "No URL available. For information about this error, [check the help section]({docs_url})", - "single_instance_allowed": "Integration is already being configured in another instance. Only one is allowed at any one time.", - "oauth_error": "Error in the authentication flow." + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "create_entry": { "default": "Successfully authenticated" + }, + "step": { + "pick_implementation": { + "title": "Pick Authentication Method" + } } }, "title": "Legrand Home+ Control" diff --git a/homeassistant/components/home_plus_control/translations/et.json b/homeassistant/components/home_plus_control/translations/et.json new file mode 100644 index 00000000000..0046c1f5205 --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/et.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Kasutaja on juba seadistatud", + "already_in_progress": "Seadistamine on juba k\u00e4imas", + "authorize_url_timeout": "Kinnitus-URLi loomise ajal\u00f5pp", + "missing_configuration": "Komponent pole seadistatud. Palun loe dokumentatsiooni.", + "no_url_available": "URL-i pole saadaval. Selle t\u00f5rke kohta teabe saamiseks vaata [spikrijaotis]({docs_url})", + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + }, + "create_entry": { + "default": "Tuvastamine \u00f5nnestus" + }, + "step": { + "pick_implementation": { + "title": "Vali tuvastusmeetod" + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/it.json b/homeassistant/components/home_plus_control/translations/it.json new file mode 100644 index 00000000000..789a7db85eb --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/it.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", + "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", + "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + }, + "create_entry": { + "default": "Autenticazione riuscita" + }, + "step": { + "pick_implementation": { + "title": "Scegli il metodo di autenticazione" + } + } + }, + "title": "Legrand Home + Control" +} \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/nl.json b/homeassistant/components/home_plus_control/translations/nl.json new file mode 100644 index 00000000000..9d448e480a1 --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", + "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, + "create_entry": { + "default": "Succesvol geauthenticeerd" + }, + "step": { + "pick_implementation": { + "title": "Kies een authenticatie methode" + } + } + }, + "title": "Legrand Home+ Control" +} \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/ru.json b/homeassistant/components/home_plus_control/translations/ru.json new file mode 100644 index 00000000000..fd3da6929d8 --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/ru.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant.", + "already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "authorize_url_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.", + "missing_configuration": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443. \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.", + "no_url_available": "URL-\u0430\u0434\u0440\u0435\u0441 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d. \u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439]({docs_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e\u0431 \u044d\u0442\u043e\u0439 \u043e\u0448\u0438\u0431\u043a\u0435.", + "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." + }, + "step": { + "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" + } + } + }, + "title": "Legrand Home+ Control" +} \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/nl.json b/homeassistant/components/hyperion/translations/nl.json index cb2de7e845d..056971b435f 100644 --- a/homeassistant/components/hyperion/translations/nl.json +++ b/homeassistant/components/hyperion/translations/nl.json @@ -26,6 +26,10 @@ "description": "Wilt u deze Hyperion Ambilight toevoegen aan Home Assistant? \n\n ** Host: ** {host}\n ** Poort: ** {port}\n ** ID **: {id}", "title": "Bevestig de toevoeging van Hyperion Ambilight-service" }, + "create_token": { + "description": "Kies **Submit** hieronder om een nieuw authenticatie token aan te vragen. U wordt doorgestuurd naar de Hyperion UI om de aanvraag goed te keuren. Controleer of de getoonde id \"{auth_id}\" is.", + "title": "Automatisch nieuw authenticatie token aanmaken" + }, "create_token_external": { "title": "Accepteer nieuwe token in Hyperion UI" }, @@ -36,5 +40,15 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "effect_show_list": "Hyperion-effecten om te laten zien", + "priority": "Hyperion prioriteit te gebruiken voor kleuren en effecten" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/nl.json b/homeassistant/components/kodi/translations/nl.json index 95d8ea995d5..4143d933d19 100644 --- a/homeassistant/components/kodi/translations/nl.json +++ b/homeassistant/components/kodi/translations/nl.json @@ -40,5 +40,11 @@ "description": "De WebSocket-poort (ook wel TCP-poort genoemd in Kodi). Om verbinding te maken via WebSocket, moet u \"Programma's toestaan ... om Kodi te besturen\" inschakelen in Systeem / Instellingen / Netwerk / Services. Als WebSocket niet is ingeschakeld, verwijdert u de poort en laat u deze leeg." } } + }, + "device_automation": { + "trigger_type": { + "turn_off": "{entity_name} werd gevraagd om uit te schakelen", + "turn_on": "{entity_name} is gevraagd om in te schakelen" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/nl.json b/homeassistant/components/lutron_caseta/translations/nl.json index d74a18622d0..4b97da2058b 100644 --- a/homeassistant/components/lutron_caseta/translations/nl.json +++ b/homeassistant/components/lutron_caseta/translations/nl.json @@ -15,6 +15,7 @@ "title": "Het importeren van de Cas\u00e9ta bridge configuratie is mislukt." }, "link": { + "description": "Om te koppelen met {naam} ({host}), na het verzenden van dit formulier, druk op de zwarte knop op de achterkant van de brug.", "title": "Koppel met de bridge" }, "user": { diff --git a/homeassistant/components/motion_blinds/translations/nl.json b/homeassistant/components/motion_blinds/translations/nl.json index 01cb117bb5b..54baeb9e18d 100644 --- a/homeassistant/components/motion_blinds/translations/nl.json +++ b/homeassistant/components/motion_blinds/translations/nl.json @@ -8,24 +8,29 @@ "error": { "discovery_error": "Kan geen Motion Gateway vinden" }, + "flow_title": "Motion Blinds", "step": { "connect": { "data": { "api_key": "API-sleutel" }, + "description": "U hebt de API-sleutel van 16 tekens nodig, zie https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key voor instructies", "title": "Motion Blinds" }, "select": { "data": { "select_ip": "IP-adres" }, + "description": "Voer de installatie opnieuw uit als u extra Motion Gateways wilt aansluiten", "title": "Selecteer de Motion Gateway waarmee u verbinding wilt maken" }, "user": { "data": { "api_key": "API-sleutel", "host": "IP-adres" - } + }, + "description": "Maak verbinding met uw Motion Gateway, als het IP-adres niet is ingesteld, wordt auto-discovery gebruikt", + "title": "Motion Blinds" } } } diff --git a/homeassistant/components/opentherm_gw/translations/nl.json b/homeassistant/components/opentherm_gw/translations/nl.json index 785d09fdfeb..bdd3337d05b 100644 --- a/homeassistant/components/opentherm_gw/translations/nl.json +++ b/homeassistant/components/opentherm_gw/translations/nl.json @@ -22,6 +22,7 @@ "data": { "floor_temperature": "Vloertemperatuur", "precision": "Precisie", + "read_precision": "Lees Precisie", "set_precision": "Precisie instellen" }, "description": "Opties voor de OpenTherm Gateway" diff --git a/homeassistant/components/ozw/translations/nl.json b/homeassistant/components/ozw/translations/nl.json index 3026427e8f1..cb443f3a27a 100644 --- a/homeassistant/components/ozw/translations/nl.json +++ b/homeassistant/components/ozw/translations/nl.json @@ -1,11 +1,20 @@ { "config": { "abort": { + "addon_info_failed": "Mislukt om OpenZWave add-on info te krijgen.", + "addon_install_failed": "De installatie van de OpenZWave add-on is mislukt.", + "addon_set_config_failed": "Mislukt om OpenZWave configuratie in te stellen.", "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang", "mqtt_required": "De [%%] integratie is niet ingesteld", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, + "error": { + "addon_start_failed": "Het starten van de OpenZWave-add-on is mislukt. Controleer de configuratie." + }, + "progress": { + "install_addon": "Wacht even terwijl de installatie van de OpenZWave add-on wordt voltooid. Dit kan enkele minuten duren." + }, "step": { "hassio_confirm": { "title": "OpenZWave integratie instellen met de OpenZWave add-on" @@ -14,6 +23,10 @@ "title": "De OpenZWave add-on installatie is gestart" }, "on_supervisor": { + "data": { + "use_addon": "Gebruik de OpenZWave Supervisor add-on" + }, + "description": "Wilt u de OpenZWave Supervisor add-on gebruiken?", "title": "Selecteer een verbindingsmethode" }, "start_addon": { diff --git a/homeassistant/components/philips_js/translations/nl.json b/homeassistant/components/philips_js/translations/nl.json index 108d158f1c5..34497d285fa 100644 --- a/homeassistant/components/philips_js/translations/nl.json +++ b/homeassistant/components/philips_js/translations/nl.json @@ -10,6 +10,13 @@ "unknown": "Onverwachte fout" }, "step": { + "pair": { + "data": { + "pin": "PIN-code" + }, + "description": "Voer de pincode in die op uw tv wordt weergegeven", + "title": "Koppel" + }, "user": { "data": { "api_version": "API Versie", diff --git a/homeassistant/components/plaato/translations/nl.json b/homeassistant/components/plaato/translations/nl.json index 6c09818594c..707830c33a5 100644 --- a/homeassistant/components/plaato/translations/nl.json +++ b/homeassistant/components/plaato/translations/nl.json @@ -19,6 +19,7 @@ "token": "Plak hier de verificatie-token", "use_webhook": "Webhook gebruiken" }, + "description": "Om de API te kunnenopvragen is een `auth_token` nodig, die kan worden verkregen door [deze] (https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) instructies te volgen\n\n Geselecteerd apparaat: **{device_type}** \n\nIndien u liever de ingebouwde webhook methode gebruikt (alleen Airlock) vink dan het vakje hieronder aan en laat Auth Token leeg", "title": "Selecteer API-methode" }, "user": { diff --git a/homeassistant/components/progettihwsw/translations/nl.json b/homeassistant/components/progettihwsw/translations/nl.json index 7810a8018a4..64eb0d12717 100644 --- a/homeassistant/components/progettihwsw/translations/nl.json +++ b/homeassistant/components/progettihwsw/translations/nl.json @@ -33,7 +33,8 @@ "data": { "host": "Host", "port": "Poort" - } + }, + "title": "Stel het bord in" } } } diff --git a/homeassistant/components/rainmachine/translations/nl.json b/homeassistant/components/rainmachine/translations/nl.json index 119e4c641af..8b767ced6c0 100644 --- a/homeassistant/components/rainmachine/translations/nl.json +++ b/homeassistant/components/rainmachine/translations/nl.json @@ -20,6 +20,9 @@ "options": { "step": { "init": { + "data": { + "zone_run_time": "Standaardlooptijd van de zone (in seconden)" + }, "title": "Configureer RainMachine" } } diff --git a/homeassistant/components/recollect_waste/translations/nl.json b/homeassistant/components/recollect_waste/translations/nl.json index 6ce4a8f8a9f..eec63605267 100644 --- a/homeassistant/components/recollect_waste/translations/nl.json +++ b/homeassistant/components/recollect_waste/translations/nl.json @@ -18,6 +18,9 @@ "options": { "step": { "init": { + "data": { + "friendly_name": "Gebruik vriendelijke namen voor afhaaltypes (indien mogelijk)" + }, "title": "Configureer Recollect Waste" } } diff --git a/homeassistant/components/rfxtrx/translations/nl.json b/homeassistant/components/rfxtrx/translations/nl.json index 53441859d72..1d22751ceed 100644 --- a/homeassistant/components/rfxtrx/translations/nl.json +++ b/homeassistant/components/rfxtrx/translations/nl.json @@ -64,7 +64,8 @@ "off_delay": "Uitschakelvertraging", "off_delay_enabled": "Schakel uitschakelvertraging in", "replace_device": "Selecteer apparaat dat u wilt vervangen", - "signal_repetitions": "Aantal signaalherhalingen" + "signal_repetitions": "Aantal signaalherhalingen", + "venetian_blind_mode": "Venetiaanse jaloezie modus" }, "title": "Configureer apparaatopties" } diff --git a/homeassistant/components/risco/translations/nl.json b/homeassistant/components/risco/translations/nl.json index 97d0d454a4f..5267b164f3f 100644 --- a/homeassistant/components/risco/translations/nl.json +++ b/homeassistant/components/risco/translations/nl.json @@ -26,12 +26,15 @@ "armed_custom_bypass": "Ingeschakeld met overbrugging(en)", "armed_home": "Ingeschakeld thuis", "armed_night": "Ingeschakeld nacht" - } + }, + "description": "Selecteer in welke staat u uw Risco-alarm wilt instellen wanneer u het Home Assistant-alarm inschakelt", + "title": "Wijs Home Assistant-staten toe aan Risco-staten" }, "init": { "data": { "code_arm_required": "PIN-code vereist om in te schakelen", - "code_disarm_required": "PIN-code vereist om uit te schakelen" + "code_disarm_required": "PIN-code vereist om uit te schakelen", + "scan_interval": "Polling-interval (in seconden)" }, "title": "Configureer opties" }, @@ -40,8 +43,12 @@ "A": "Groep A", "B": "Groep B", "C": "Groep C", - "D": "Groep D" - } + "D": "Groep D", + "arm": "Ingeschakeld (AFWEZIG)", + "partial_arm": "Gedeeltelijk ingeschakeld (AANWEZIG)" + }, + "description": "Selecteer welke staat uw Home Assistant alarm zal melden voor elke staat gemeld door Risco", + "title": "Wijs Risco-staten toe aan Home Assistant-staten" } } } diff --git a/homeassistant/components/screenlogic/translations/nl.json b/homeassistant/components/screenlogic/translations/nl.json new file mode 100644 index 00000000000..7c752e0ae4d --- /dev/null +++ b/homeassistant/components/screenlogic/translations/nl.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "IP-adres", + "port": "Poort" + }, + "description": "Voer uw ScreenLogic Gateway informatie in.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "Gateway" + }, + "description": "De volgende ScreenLogic gateways werden ontdekt. Selecteer er een om te configureren, of kies ervoor om handmatig een ScreenLogic gateway te configureren.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Seconden tussen scans" + }, + "description": "Geef instellingen op voor {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sentry/translations/nl.json b/homeassistant/components/sentry/translations/nl.json index 682517c15db..53f54ac1968 100644 --- a/homeassistant/components/sentry/translations/nl.json +++ b/homeassistant/components/sentry/translations/nl.json @@ -24,7 +24,11 @@ "environment": "Optionele naam van de omgeving.", "event_custom_components": "Gebeurtenissen verzenden vanuit aangepaste onderdelen", "event_handled": "Stuur afgehandelde gebeurtenissen", - "event_third_party_packages": "Gebeurtenissen verzenden vanuit pakketten van derden" + "event_third_party_packages": "Gebeurtenissen verzenden vanuit pakketten van derden", + "logging_event_level": "Het logniveau waarvoor Sentry een gebeurtenis registreert", + "logging_level": "Het logniveau Sentry zal logs opnemen als broodkruimels voor", + "tracing": "Schakel prestatietracering in", + "tracing_sample_rate": "Tracering van de steekproefsnelheid; tussen 0,0 en 1,0 (1,0 = 100%)" } } } diff --git a/homeassistant/components/somfy_mylink/translations/nl.json b/homeassistant/components/somfy_mylink/translations/nl.json index 1e9d5a58f89..b900e46bee4 100644 --- a/homeassistant/components/somfy_mylink/translations/nl.json +++ b/homeassistant/components/somfy_mylink/translations/nl.json @@ -15,7 +15,8 @@ "host": "Host", "port": "Poort", "system_id": "Systeem-ID" - } + }, + "description": "De systeem-id kan worden verkregen in de MyLink app onder Integratie door een niet-Cloud service te selecteren." } } }, @@ -25,16 +26,24 @@ }, "step": { "entity_config": { + "data": { + "reverse": "Cover is omgekeerd" + }, "description": "Configureer opties voor `{entity_id}`", "title": "Entiteit configureren" }, "init": { "data": { - "entity_id": "Configureer een specifieke entiteit." + "default_reverse": "Standaard omkeerstatus voor niet-geconfigureerde covers", + "entity_id": "Configureer een specifieke entiteit.", + "target_id": "Configureer opties voor een cover." }, "title": "Configureer MyLink-opties" }, "target_config": { + "data": { + "reverse": "Cover is omgekeerd" + }, "description": "Configureer opties voor ' {target_name} '", "title": "Configureer MyLink Cover" } diff --git a/homeassistant/components/srp_energy/translations/nl.json b/homeassistant/components/srp_energy/translations/nl.json index d1df1796f3a..91bdc3592b6 100644 --- a/homeassistant/components/srp_energy/translations/nl.json +++ b/homeassistant/components/srp_energy/translations/nl.json @@ -13,6 +13,7 @@ "user": { "data": { "id": "Account ID", + "is_tou": "Is tijd van gebruik plan", "password": "Wachtwoord", "username": "Gebruikersnaam" } diff --git a/homeassistant/components/tuya/translations/nl.json b/homeassistant/components/tuya/translations/nl.json index 8f811714411..b42922822f0 100644 --- a/homeassistant/components/tuya/translations/nl.json +++ b/homeassistant/components/tuya/translations/nl.json @@ -40,6 +40,7 @@ "max_temp": "Maximale doeltemperatuur (gebruik min en max = 0 voor standaardwaarde)", "min_kelvin": "Minimaal ondersteunde kleurtemperatuur in kelvin", "min_temp": "Min. gewenste temperatuur (gebruik min en max = 0 voor standaard)", + "set_temp_divided": "Gedeelde temperatuurwaarde gebruiken voor ingestelde temperatuuropdracht", "support_color": "Forceer kleurenondersteuning", "temp_divider": "Temperatuurwaarde deler (0 = standaardwaarde)", "temp_step_override": "Doeltemperatuur stap", @@ -56,6 +57,7 @@ "query_device": "Selecteer apparaat dat query-methode zal gebruiken voor snellere statusupdate", "query_interval": "Peilinginterval van het apparaat in seconden" }, + "description": "Stel de waarden voor het pollinginterval niet te laag in, anders zullen de oproepen geen foutmelding in het logboek genereren", "title": "Configureer Tuya opties" } } diff --git a/homeassistant/components/verisure/translations/nl.json b/homeassistant/components/verisure/translations/nl.json index 806231dbe4a..d0519e584fd 100644 --- a/homeassistant/components/verisure/translations/nl.json +++ b/homeassistant/components/verisure/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Account is al geconfigureerd" + "already_configured": "Account is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "invalid_auth": "Ongeldige authenticatie", diff --git a/homeassistant/components/water_heater/translations/nl.json b/homeassistant/components/water_heater/translations/nl.json index f70a5efc3e7..4a2d1357188 100644 --- a/homeassistant/components/water_heater/translations/nl.json +++ b/homeassistant/components/water_heater/translations/nl.json @@ -11,6 +11,7 @@ "electric": "Elektriciteit", "gas": "Gas", "heat_pump": "Warmtepomp", + "high_demand": "Hoge vraag", "off": "Uit", "performance": "Prestaties" } From b4d39d517f945e85bf886cdd629ba320dffb4e54 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Fri, 26 Mar 2021 04:06:40 +0100 Subject: [PATCH 609/831] Update in 1 minute on unavailable Motion blinds (#47800) * if unavailable request update in 1 minute * fix styling * improve changing update interval * remove unused import * try to fix * remove unused pass * add const * fix missing timedelta * update to motionblinds 0.4.10 * improve update coordinator * fix linting errors * remove unused import * move update functions within the DataUpdateCoordinator * fix white space --- .../components/motion_blinds/__init__.py | 89 +++++++++++++++---- .../components/motion_blinds/const.py | 4 + .../components/motion_blinds/cover.py | 9 +- .../components/motion_blinds/manifest.json | 2 +- .../components/motion_blinds/sensor.py | 18 +++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 7 files changed, 103 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/motion_blinds/__init__.py b/homeassistant/components/motion_blinds/__init__.py index f57d31b47d3..d4a5dd1f79b 100644 --- a/homeassistant/components/motion_blinds/__init__.py +++ b/homeassistant/components/motion_blinds/__init__.py @@ -1,11 +1,11 @@ """The motion_blinds component.""" import asyncio -from contextlib import suppress from datetime import timedelta import logging from socket import timeout from motionblinds import MotionMulticast +from motionblinds.motion_blinds import ParseException from homeassistant import config_entries, core from homeassistant.const import CONF_API_KEY, CONF_HOST, EVENT_HOMEASSISTANT_STOP @@ -14,18 +14,87 @@ from homeassistant.helpers import device_registry as dr from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ( + ATTR_AVAILABLE, DOMAIN, KEY_COORDINATOR, KEY_GATEWAY, KEY_MULTICAST_LISTENER, MANUFACTURER, PLATFORMS, + UPDATE_INTERVAL, + UPDATE_INTERVAL_FAST, ) from .gateway import ConnectMotionGateway _LOGGER = logging.getLogger(__name__) +class DataUpdateCoordinatorMotionBlinds(DataUpdateCoordinator): + """Class to manage fetching data from single endpoint.""" + + def __init__( + self, + hass, + logger, + gateway, + *, + name, + update_interval=None, + update_method=None, + ): + """Initialize global data updater.""" + super().__init__( + hass, + logger, + name=name, + update_method=update_method, + update_interval=update_interval, + ) + + self._gateway = gateway + + def update_gateway(self): + """Call all updates using one async_add_executor_job.""" + data = {} + + try: + self._gateway.Update() + except (timeout, ParseException): + # let the error be logged and handled by the motionblinds library + data[KEY_GATEWAY] = {ATTR_AVAILABLE: False} + return data + else: + data[KEY_GATEWAY] = {ATTR_AVAILABLE: True} + + for blind in self._gateway.device_list.values(): + try: + blind.Update() + except (timeout, ParseException): + # let the error be logged and handled by the motionblinds library + data[blind.mac] = {ATTR_AVAILABLE: False} + else: + data[blind.mac] = {ATTR_AVAILABLE: True} + + return data + + async def _async_update_data(self): + """Fetch the latest data from the gateway and blinds.""" + data = await self.hass.async_add_executor_job(self.update_gateway) + + all_available = True + for device in data.values(): + if not device[ATTR_AVAILABLE]: + all_available = False + break + + if all_available: + self.update_interval = timedelta(seconds=UPDATE_INTERVAL) + else: + self.update_interval = timedelta(seconds=UPDATE_INTERVAL_FAST) + + return data + + def setup(hass: core.HomeAssistant, config: dict): """Set up the Motion Blinds component.""" return True @@ -61,26 +130,14 @@ async def async_setup_entry( raise ConfigEntryNotReady motion_gateway = connect_gateway_class.gateway_device - def update_gateway(): - """Call all updates using one async_add_executor_job.""" - motion_gateway.Update() - for blind in motion_gateway.device_list.values(): - with suppress(timeout): - blind.Update() - - async def async_update_data(): - """Fetch data from the gateway and blinds.""" - with suppress(timeout): # Let the error be handled by the motionblinds - await hass.async_add_executor_job(update_gateway) - - coordinator = DataUpdateCoordinator( + coordinator = DataUpdateCoordinatorMotionBlinds( hass, _LOGGER, + motion_gateway, # Name of the data. For logging purposes. name=entry.title, - update_method=async_update_data, # Polling interval. Will only be polled if there are subscribers. - update_interval=timedelta(seconds=600), + update_interval=timedelta(seconds=UPDATE_INTERVAL), ) # Fetch initial data so we have data when entities subscribe diff --git a/homeassistant/components/motion_blinds/const.py b/homeassistant/components/motion_blinds/const.py index 4b1f0ee0527..52c6e39b096 100644 --- a/homeassistant/components/motion_blinds/const.py +++ b/homeassistant/components/motion_blinds/const.py @@ -11,5 +11,9 @@ KEY_MULTICAST_LISTENER = "multicast_listener" ATTR_WIDTH = "width" ATTR_ABSOLUTE_POSITION = "absolute_position" +ATTR_AVAILABLE = "available" SERVICE_SET_ABSOLUTE_POSITION = "set_absolute_position" + +UPDATE_INTERVAL = 600 +UPDATE_INTERVAL_FAST = 60 diff --git a/homeassistant/components/motion_blinds/cover.py b/homeassistant/components/motion_blinds/cover.py index 4ac09a5fd11..2c4fee5f8aa 100644 --- a/homeassistant/components/motion_blinds/cover.py +++ b/homeassistant/components/motion_blinds/cover.py @@ -21,6 +21,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ( ATTR_ABSOLUTE_POSITION, + ATTR_AVAILABLE, ATTR_WIDTH, DOMAIN, KEY_COORDINATOR, @@ -160,7 +161,13 @@ class MotionPositionDevice(CoordinatorEntity, CoverEntity): @property def available(self): """Return True if entity is available.""" - return self._blind.available + if self.coordinator.data is None: + return False + + if not self.coordinator.data[KEY_GATEWAY][ATTR_AVAILABLE]: + return False + + return self.coordinator.data[self._blind.mac][ATTR_AVAILABLE] @property def current_cover_position(self): diff --git a/homeassistant/components/motion_blinds/manifest.json b/homeassistant/components/motion_blinds/manifest.json index ec2823dbd2e..c144dc99bc5 100644 --- a/homeassistant/components/motion_blinds/manifest.json +++ b/homeassistant/components/motion_blinds/manifest.json @@ -3,6 +3,6 @@ "name": "Motion Blinds", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/motion_blinds", - "requirements": ["motionblinds==0.4.8"], + "requirements": ["motionblinds==0.4.10"], "codeowners": ["@starkillerOG"] } diff --git a/homeassistant/components/motion_blinds/sensor.py b/homeassistant/components/motion_blinds/sensor.py index 5d7a29bf9da..d7f40337cec 100644 --- a/homeassistant/components/motion_blinds/sensor.py +++ b/homeassistant/components/motion_blinds/sensor.py @@ -10,7 +10,7 @@ from homeassistant.const import ( ) from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN, KEY_COORDINATOR, KEY_GATEWAY +from .const import ATTR_AVAILABLE, DOMAIN, KEY_COORDINATOR, KEY_GATEWAY ATTR_BATTERY_VOLTAGE = "battery_voltage" TYPE_BLIND = "blind" @@ -70,7 +70,13 @@ class MotionBatterySensor(CoordinatorEntity, SensorEntity): @property def available(self): """Return True if entity is available.""" - return self._blind.available + if self.coordinator.data is None: + return False + + if not self.coordinator.data[KEY_GATEWAY][ATTR_AVAILABLE]: + return False + + return self.coordinator.data[self._blind.mac][ATTR_AVAILABLE] @property def unit_of_measurement(self): @@ -174,7 +180,13 @@ class MotionSignalStrengthSensor(CoordinatorEntity, SensorEntity): @property def available(self): """Return True if entity is available.""" - return self._device.available + if self.coordinator.data is None: + return False + + if not self.coordinator.data[KEY_GATEWAY][ATTR_AVAILABLE]: + return False + + return self.coordinator.data[self._device.mac][ATTR_AVAILABLE] @property def unit_of_measurement(self): diff --git a/requirements_all.txt b/requirements_all.txt index d40244074a9..db05a88e9f7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -946,7 +946,7 @@ minio==4.0.9 mitemp_bt==0.0.3 # homeassistant.components.motion_blinds -motionblinds==0.4.8 +motionblinds==0.4.10 # homeassistant.components.mullvad mullvad-api==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 149c156f4cc..e641de25fb8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -491,7 +491,7 @@ millheater==0.4.0 minio==4.0.9 # homeassistant.components.motion_blinds -motionblinds==0.4.8 +motionblinds==0.4.10 # homeassistant.components.mullvad mullvad-api==1.0.0 From b90c620c5e8b3f2868b2065e74fe8819260ef8ea Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 26 Mar 2021 04:18:46 +0100 Subject: [PATCH 610/831] Address huisbaasje review comments (#48313) * Address huisbaasje review comments * Update homeassistant/components/huisbaasje/config_flow.py Co-authored-by: Martin Hjelmare Co-authored-by: Martin Hjelmare --- .../components/huisbaasje/__init__.py | 2 +- .../components/huisbaasje/config_flow.py | 27 +++++++++---------- homeassistant/components/huisbaasje/const.py | 9 ++++--- .../components/huisbaasje/strings.json | 3 +-- .../components/huisbaasje/test_config_flow.py | 2 +- 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/huisbaasje/__init__.py b/homeassistant/components/huisbaasje/__init__.py index 8cd2681c8da..f06ad444cc0 100644 --- a/homeassistant/components/huisbaasje/__init__.py +++ b/homeassistant/components/huisbaasje/__init__.py @@ -141,7 +141,7 @@ def _get_cumulative_value( :param source_type: The source of energy (electricity or gas) :param period_type: The period for which cumulative value should be given. """ - if source_type in current_measurements.keys(): + if source_type in current_measurements: if ( period_type in current_measurements[source_type] and current_measurements[source_type][period_type] is not None diff --git a/homeassistant/components/huisbaasje/config_flow.py b/homeassistant/components/huisbaasje/config_flow.py index 59e4840529d..c9a4750ade5 100644 --- a/homeassistant/components/huisbaasje/config_flow.py +++ b/homeassistant/components/huisbaasje/config_flow.py @@ -32,9 +32,18 @@ class HuisbaasjeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): try: user_id = await self._validate_input(user_input) - - _LOGGER.info("Input for Huisbaasje is valid!") - + except HuisbaasjeConnectionException as exception: + _LOGGER.warning(exception) + errors["base"] = "cannot_connect" + except HuisbaasjeException as exception: + _LOGGER.warning(exception) + errors["base"] = "invalid_auth" + except AbortFlow: + raise + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: # Set user id as unique id await self.async_set_unique_id(user_id) self._abort_if_unique_id_configured() @@ -48,17 +57,6 @@ class HuisbaasjeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): CONF_PASSWORD: user_input[CONF_PASSWORD], }, ) - except HuisbaasjeConnectionException as exception: - _LOGGER.warning(exception) - errors["base"] = "connection_exception" - except HuisbaasjeException as exception: - _LOGGER.warning(exception) - errors["base"] = "invalid_auth" - except AbortFlow as exception: - raise exception - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" return await self._show_setup_form(user_input, errors) @@ -72,7 +70,6 @@ class HuisbaasjeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): Data has the keys from DATA_SCHEMA with values provided by the user. """ - username = user_input[CONF_USERNAME] password = user_input[CONF_PASSWORD] diff --git a/homeassistant/components/huisbaasje/const.py b/homeassistant/components/huisbaasje/const.py index 07ad84567e5..abac03e6182 100644 --- a/homeassistant/components/huisbaasje/const.py +++ b/homeassistant/components/huisbaasje/const.py @@ -9,6 +9,7 @@ from huisbaasje.const import ( ) from homeassistant.const import ( + DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, ENERGY_KILO_WATT_HOUR, TIME_HOURS, @@ -70,34 +71,34 @@ SENSORS_INFO = [ }, { "name": "Huisbaasje Energy Today", + "device_class": DEVICE_CLASS_ENERGY, "unit_of_measurement": ENERGY_KILO_WATT_HOUR, "source_type": SOURCE_TYPE_ELECTRICITY, "sensor_type": SENSOR_TYPE_THIS_DAY, - "icon": "mdi:counter", "precision": 1, }, { "name": "Huisbaasje Energy This Week", + "device_class": DEVICE_CLASS_ENERGY, "unit_of_measurement": ENERGY_KILO_WATT_HOUR, "source_type": SOURCE_TYPE_ELECTRICITY, "sensor_type": SENSOR_TYPE_THIS_WEEK, - "icon": "mdi:counter", "precision": 1, }, { "name": "Huisbaasje Energy This Month", + "device_class": DEVICE_CLASS_ENERGY, "unit_of_measurement": ENERGY_KILO_WATT_HOUR, "source_type": SOURCE_TYPE_ELECTRICITY, "sensor_type": SENSOR_TYPE_THIS_MONTH, - "icon": "mdi:counter", "precision": 1, }, { "name": "Huisbaasje Energy This Year", + "device_class": DEVICE_CLASS_ENERGY, "unit_of_measurement": ENERGY_KILO_WATT_HOUR, "source_type": SOURCE_TYPE_ELECTRICITY, "sensor_type": SENSOR_TYPE_THIS_YEAR, - "icon": "mdi:counter", "precision": 1, }, { diff --git a/homeassistant/components/huisbaasje/strings.json b/homeassistant/components/huisbaasje/strings.json index f126ac0afff..169b9a0e901 100644 --- a/homeassistant/components/huisbaasje/strings.json +++ b/homeassistant/components/huisbaasje/strings.json @@ -10,8 +10,7 @@ }, "error": { "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "unauthenticated_exception": "[%key:common::config_flow::error::invalid_auth%]", - "connection_exception": "[%key:common::config_flow::error::cannot_connect%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { diff --git a/tests/components/huisbaasje/test_config_flow.py b/tests/components/huisbaasje/test_config_flow.py index 245ac2f8ddb..35e28b645eb 100644 --- a/tests/components/huisbaasje/test_config_flow.py +++ b/tests/components/huisbaasje/test_config_flow.py @@ -94,7 +94,7 @@ async def test_form_cannot_connect(hass): ) assert form_result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert form_result["errors"] == {"base": "connection_exception"} + assert form_result["errors"] == {"base": "cannot_connect"} async def test_form_unknown_error(hass): From 24dee01599a8c4e0d503c0cb9880d837e0d18c7d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 26 Mar 2021 04:21:27 +0100 Subject: [PATCH 611/831] Use async with in Acmeda config flow (#48291) --- homeassistant/components/acmeda/config_flow.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/acmeda/config_flow.py b/homeassistant/components/acmeda/config_flow.py index a849e49ddf4..5cdb804d5dd 100644 --- a/homeassistant/components/acmeda/config_flow.py +++ b/homeassistant/components/acmeda/config_flow.py @@ -38,12 +38,13 @@ class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): } hubs = [] - with suppress(asyncio.TimeoutError), async_timeout.timeout(5): - async for hub in aiopulse.Hub.discover(): - if hub.id not in already_configured: - hubs.append(hub) + with suppress(asyncio.TimeoutError): + async with async_timeout.timeout(5): + async for hub in aiopulse.Hub.discover(): + if hub.id not in already_configured: + hubs.append(hub) - if len(hubs) == 0: + if not hubs: return self.async_abort(reason="no_devices_found") if len(hubs) == 1: From a019f076c036e1405d7a0d605c7c182bea4015da Mon Sep 17 00:00:00 2001 From: Garrett <7310260+G-Two@users.noreply.github.com> Date: Thu, 25 Mar 2021 23:24:37 -0400 Subject: [PATCH 612/831] Subaru integration code quality changes (#48193) * Apply changes from code review * Update sensor tests * Fix pylint error * Apply suggestions from code review Co-authored-by: Brandon Rothweiler Co-authored-by: Martin Hjelmare Co-authored-by: Brandon Rothweiler Co-authored-by: Martin Hjelmare --- .../components/subaru/config_flow.py | 10 ++-- homeassistant/components/subaru/const.py | 5 -- homeassistant/components/subaru/entity.py | 7 +-- homeassistant/components/subaru/sensor.py | 24 +++++--- homeassistant/components/subaru/strings.json | 3 +- tests/components/subaru/conftest.py | 15 ++++- tests/components/subaru/test_config_flow.py | 56 ++++++++++--------- tests/components/subaru/test_sensor.py | 35 +++++++----- 8 files changed, 88 insertions(+), 67 deletions(-) diff --git a/homeassistant/components/subaru/config_flow.py b/homeassistant/components/subaru/config_flow.py index a3586b32974..772134c66b1 100644 --- a/homeassistant/components/subaru/config_flow.py +++ b/homeassistant/components/subaru/config_flow.py @@ -28,8 +28,11 @@ class SubaruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL - config_data = {CONF_PIN: None} - controller = None + + def __init__(self): + """Initialize config flow.""" + self.config_data = {CONF_PIN: None} + self.controller = None async def async_step_user(self, user_input=None): """Handle the start of the config flow.""" @@ -105,9 +108,8 @@ class SubaruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): device_name=device_name, country=data[CONF_COUNTRY], ) - _LOGGER.debug("Using subarulink %s", self.controller.version) _LOGGER.debug( - "Setting up first time connection to Subuaru API; This may take up to 20 seconds" + "Setting up first time connection to Subaru API. This may take up to 20 seconds" ) if await self.controller.connect(): _LOGGER.debug("Successfully authenticated and authorized with Subaru API") diff --git a/homeassistant/components/subaru/const.py b/homeassistant/components/subaru/const.py index fa6fe984e61..cada29edd3a 100644 --- a/homeassistant/components/subaru/const.py +++ b/homeassistant/components/subaru/const.py @@ -36,12 +36,7 @@ PLATFORMS = [ ICONS = { "Avg Fuel Consumption": "mdi:leaf", - "EV Time to Full Charge": "mdi:car-electric", "EV Range": "mdi:ev-station", "Odometer": "mdi:road-variant", "Range": "mdi:gas-station", - "Tire Pressure FL": "mdi:gauge", - "Tire Pressure FR": "mdi:gauge", - "Tire Pressure RL": "mdi:gauge", - "Tire Pressure RR": "mdi:gauge", } diff --git a/homeassistant/components/subaru/entity.py b/homeassistant/components/subaru/entity.py index 4fdeca4e484..559feeea303 100644 --- a/homeassistant/components/subaru/entity.py +++ b/homeassistant/components/subaru/entity.py @@ -1,7 +1,7 @@ """Base class for all Subaru Entities.""" from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN, ICONS, MANUFACTURER, VEHICLE_NAME, VEHICLE_VIN +from .const import DOMAIN, MANUFACTURER, VEHICLE_NAME, VEHICLE_VIN class SubaruEntity(CoordinatorEntity): @@ -24,11 +24,6 @@ class SubaruEntity(CoordinatorEntity): """Return a unique ID.""" return f"{self.vin}_{self.entity_type}" - @property - def icon(self): - """Return the icon of the sensor.""" - return ICONS.get(self.entity_type) - @property def device_info(self): """Return the device_info of the device.""" diff --git a/homeassistant/components/subaru/sensor.py b/homeassistant/components/subaru/sensor.py index b8362202a3d..41dd8a6604f 100644 --- a/homeassistant/components/subaru/sensor.py +++ b/homeassistant/components/subaru/sensor.py @@ -4,7 +4,9 @@ import subarulink.const as sc from homeassistant.components.sensor import DEVICE_CLASSES, SensorEntity from homeassistant.const import ( DEVICE_CLASS_BATTERY, + DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_TIMESTAMP, DEVICE_CLASS_VOLTAGE, LENGTH_KILOMETERS, LENGTH_MILES, @@ -30,6 +32,7 @@ from .const import ( DOMAIN, ENTRY_COORDINATOR, ENTRY_VEHICLES, + ICONS, VEHICLE_API_GEN, VEHICLE_HAS_EV, VEHICLE_HAS_SAFETY_SERVICE, @@ -76,25 +79,25 @@ API_GEN_2_SENSORS = [ }, { SENSOR_TYPE: "Tire Pressure FL", - SENSOR_CLASS: None, + SENSOR_CLASS: DEVICE_CLASS_PRESSURE, SENSOR_FIELD: sc.TIRE_PRESSURE_FL, SENSOR_UNITS: PRESSURE_HPA, }, { SENSOR_TYPE: "Tire Pressure FR", - SENSOR_CLASS: None, + SENSOR_CLASS: DEVICE_CLASS_PRESSURE, SENSOR_FIELD: sc.TIRE_PRESSURE_FR, SENSOR_UNITS: PRESSURE_HPA, }, { SENSOR_TYPE: "Tire Pressure RL", - SENSOR_CLASS: None, + SENSOR_CLASS: DEVICE_CLASS_PRESSURE, SENSOR_FIELD: sc.TIRE_PRESSURE_RL, SENSOR_UNITS: PRESSURE_HPA, }, { SENSOR_TYPE: "Tire Pressure RR", - SENSOR_CLASS: None, + SENSOR_CLASS: DEVICE_CLASS_PRESSURE, SENSOR_FIELD: sc.TIRE_PRESSURE_RR, SENSOR_UNITS: PRESSURE_HPA, }, @@ -128,7 +131,7 @@ EV_SENSORS = [ }, { SENSOR_TYPE: "EV Time to Full Charge", - SENSOR_CLASS: None, + SENSOR_CLASS: DEVICE_CLASS_TIMESTAMP, SENSOR_FIELD: sc.EV_TIME_TO_FULLY_CHARGED, SENSOR_UNITS: TIME_MINUTES, }, @@ -140,7 +143,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): coordinator = hass.data[DOMAIN][config_entry.entry_id][ENTRY_COORDINATOR] vehicle_info = hass.data[DOMAIN][config_entry.entry_id][ENTRY_VEHICLES] entities = [] - for vin in vehicle_info.keys(): + for vin in vehicle_info: entities.extend(create_vehicle_sensors(vehicle_info[vin], coordinator)) async_add_entities(entities, True) @@ -190,7 +193,14 @@ class SubaruSensor(SubaruEntity, SensorEntity): """Return the class of this device, from component DEVICE_CLASSES.""" if self.sensor_class in DEVICE_CLASSES: return self.sensor_class - return super().device_class + return None + + @property + def icon(self): + """Return the icon of the sensor.""" + if not self.device_class: + return ICONS.get(self.entity_type) + return None @property def state(self): diff --git a/homeassistant/components/subaru/strings.json b/homeassistant/components/subaru/strings.json index 064245e0732..ea9df082f3a 100644 --- a/homeassistant/components/subaru/strings.json +++ b/homeassistant/components/subaru/strings.json @@ -22,8 +22,7 @@ "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "incorrect_pin": "Incorrect PIN", - "bad_pin_format": "PIN should be 4 digits", - "unknown": "[%key:common::config_flow::error::unknown%]" + "bad_pin_format": "PIN should be 4 digits" }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", diff --git a/tests/components/subaru/conftest.py b/tests/components/subaru/conftest.py index 8216ca2d2c2..1b8d1439e68 100644 --- a/tests/components/subaru/conftest.py +++ b/tests/components/subaru/conftest.py @@ -1,4 +1,5 @@ """Common functions needed to setup tests for Subaru component.""" +from datetime import timedelta from unittest.mock import patch import pytest @@ -9,6 +10,7 @@ from homeassistant.components.subaru.const import ( CONF_COUNTRY, CONF_UPDATE_ENABLED, DOMAIN, + FETCH_INTERVAL, VEHICLE_API_GEN, VEHICLE_HAS_EV, VEHICLE_HAS_REMOTE_SERVICE, @@ -19,10 +21,11 @@ from homeassistant.components.subaru.const import ( from homeassistant.config_entries import ENTRY_STATE_LOADED from homeassistant.const import CONF_DEVICE_ID, CONF_PASSWORD, CONF_PIN, CONF_USERNAME from homeassistant.setup import async_setup_component +import homeassistant.util.dt as dt_util from .api_responses import TEST_VIN_2_EV, VEHICLE_DATA, VEHICLE_STATUS_EV -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_fire_time_changed MOCK_API = "homeassistant.components.subaru.SubaruAPI." MOCK_API_CONNECT = f"{MOCK_API}connect" @@ -36,7 +39,7 @@ MOCK_API_GET_EV_STATUS = f"{MOCK_API}get_ev_status" MOCK_API_GET_RES_STATUS = f"{MOCK_API}get_res_status" MOCK_API_GET_REMOTE_STATUS = f"{MOCK_API}get_remote_status" MOCK_API_GET_SAFETY_STATUS = f"{MOCK_API}get_safety_status" -MOCK_API_GET_GET_DATA = f"{MOCK_API}get_data" +MOCK_API_GET_DATA = f"{MOCK_API}get_data" MOCK_API_UPDATE = f"{MOCK_API}update" MOCK_API_FETCH = f"{MOCK_API}fetch" @@ -67,6 +70,12 @@ TEST_OPTIONS = { TEST_ENTITY_ID = "sensor.test_vehicle_2_odometer" +def advance_time_to_next_fetch(hass): + """Fast forward time to next fetch.""" + future = dt_util.utcnow() + timedelta(seconds=FETCH_INTERVAL + 30) + async_fire_time_changed(hass, future) + + async def setup_subaru_integration( hass, vehicle_list=None, @@ -110,7 +119,7 @@ async def setup_subaru_integration( MOCK_API_GET_SAFETY_STATUS, return_value=vehicle_data[VEHICLE_HAS_SAFETY_SERVICE], ), patch( - MOCK_API_GET_GET_DATA, + MOCK_API_GET_DATA, return_value=vehicle_status, ), patch( MOCK_API_UPDATE, diff --git a/tests/components/subaru/test_config_flow.py b/tests/components/subaru/test_config_flow.py index 676b876652b..0218c11003c 100644 --- a/tests/components/subaru/test_config_flow.py +++ b/tests/components/subaru/test_config_flow.py @@ -11,6 +11,7 @@ from homeassistant import config_entries from homeassistant.components.subaru import config_flow from homeassistant.components.subaru.const import CONF_UPDATE_ENABLED, DOMAIN from homeassistant.const import CONF_DEVICE_ID, CONF_PIN +from homeassistant.setup import async_setup_component from .conftest import ( MOCK_API_CONNECT, @@ -26,19 +27,17 @@ from .conftest import ( from tests.common import MockConfigEntry +ASYNC_SETUP = "homeassistant.components.subaru.async_setup" +ASYNC_SETUP_ENTRY = "homeassistant.components.subaru.async_setup_entry" + async def test_user_form_init(user_form): """Test the initial user form for first step of the config flow.""" - expected = { - "data_schema": mock.ANY, - "description_placeholders": None, - "errors": None, - "flow_id": mock.ANY, - "handler": DOMAIN, - "step_id": "user", - "type": "form", - } - assert expected == user_form + assert user_form["description_placeholders"] is None + assert user_form["errors"] is None + assert user_form["handler"] == DOMAIN + assert user_form["step_id"] == "user" + assert user_form["type"] == "form" async def test_user_form_repeat_identifier(hass, user_form): @@ -96,13 +95,19 @@ async def test_user_form_pin_not_required(hass, user_form): with patch(MOCK_API_CONNECT, return_value=True,) as mock_connect, patch( MOCK_API_IS_PIN_REQUIRED, return_value=False, - ) as mock_is_pin_required: + ) as mock_is_pin_required, patch( + ASYNC_SETUP, return_value=True + ) as mock_setup, patch( + ASYNC_SETUP_ENTRY, return_value=True + ) as mock_setup_entry: result = await hass.config_entries.flow.async_configure( user_form["flow_id"], TEST_CREDS, ) - assert len(mock_connect.mock_calls) == 2 + assert len(mock_connect.mock_calls) == 1 assert len(mock_is_pin_required.mock_calls) == 1 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 expected = { "title": TEST_USERNAME, @@ -117,7 +122,7 @@ async def test_user_form_pin_not_required(hass, user_form): } expected["data"][CONF_PIN] = None result["data"][CONF_DEVICE_ID] = TEST_DEVICE_ID - assert expected == result + assert result == expected async def test_pin_form_init(pin_form): @@ -131,7 +136,7 @@ async def test_pin_form_init(pin_form): "step_id": "pin", "type": "form", } - assert expected == pin_form + assert pin_form == expected async def test_pin_form_bad_pin_format(hass, pin_form): @@ -154,13 +159,19 @@ async def test_pin_form_success(hass, pin_form): with patch(MOCK_API_TEST_PIN, return_value=True,) as mock_test_pin, patch( MOCK_API_UPDATE_SAVED_PIN, return_value=True, - ) as mock_update_saved_pin: + ) as mock_update_saved_pin, patch( + ASYNC_SETUP, return_value=True + ) as mock_setup, patch( + ASYNC_SETUP_ENTRY, return_value=True + ) as mock_setup_entry: result = await hass.config_entries.flow.async_configure( pin_form["flow_id"], user_input={CONF_PIN: TEST_PIN} ) assert len(mock_test_pin.mock_calls) == 1 assert len(mock_update_saved_pin.mock_calls) == 1 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 expected = { "title": TEST_USERNAME, "description": None, @@ -196,16 +207,10 @@ async def test_pin_form_incorrect_pin(hass, pin_form): async def test_option_flow_form(options_form): """Test config flow options form.""" - expected = { - "data_schema": mock.ANY, - "description_placeholders": None, - "errors": None, - "flow_id": mock.ANY, - "handler": mock.ANY, - "step_id": "init", - "type": "form", - } - assert expected == options_form + assert options_form["description_placeholders"] is None + assert options_form["errors"] is None + assert options_form["step_id"] == "init" + assert options_form["type"] == "form" async def test_option_flow(hass, options_form): @@ -247,4 +252,5 @@ async def options_form(hass): """Return options form for Subaru config flow.""" entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) entry.add_to_hass(hass) + await async_setup_component(hass, DOMAIN, {}) return await hass.config_entries.options.async_init(entry.entry_id) diff --git a/tests/components/subaru/test_sensor.py b/tests/components/subaru/test_sensor.py index 4344c147f22..f2a66e7e5e9 100644 --- a/tests/components/subaru/test_sensor.py +++ b/tests/components/subaru/test_sensor.py @@ -1,4 +1,6 @@ """Test Subaru sensors.""" +from unittest.mock import patch + from homeassistant.components.subaru.const import VEHICLE_NAME from homeassistant.components.subaru.sensor import ( API_GEN_2_SENSORS, @@ -19,20 +21,25 @@ from .api_responses import ( VEHICLE_STATUS_EV, ) -from tests.components.subaru.conftest import setup_subaru_integration +from tests.components.subaru.conftest import ( + MOCK_API_FETCH, + MOCK_API_GET_DATA, + advance_time_to_next_fetch, +) VEHICLE_NAME = VEHICLE_DATA[TEST_VIN_2_EV][VEHICLE_NAME] -async def test_sensors_ev_imperial(hass): +async def test_sensors_ev_imperial(hass, ev_entry): """Test sensors supporting imperial units.""" hass.config.units = IMPERIAL_SYSTEM - await setup_subaru_integration( - hass, - vehicle_list=[TEST_VIN_2_EV], - vehicle_data=VEHICLE_DATA[TEST_VIN_2_EV], - vehicle_status=VEHICLE_STATUS_EV, - ) + + with patch(MOCK_API_FETCH), patch( + MOCK_API_GET_DATA, return_value=VEHICLE_STATUS_EV + ): + advance_time_to_next_fetch(hass) + await hass.async_block_till_done() + _assert_data(hass, EXPECTED_STATE_EV_IMPERIAL) @@ -41,14 +48,12 @@ async def test_sensors_ev_metric(hass, ev_entry): _assert_data(hass, EXPECTED_STATE_EV_METRIC) -async def test_sensors_missing_vin_data(hass): +async def test_sensors_missing_vin_data(hass, ev_entry): """Test for missing VIN dataset.""" - await setup_subaru_integration( - hass, - vehicle_list=[TEST_VIN_2_EV], - vehicle_data=VEHICLE_DATA[TEST_VIN_2_EV], - vehicle_status=None, - ) + with patch(MOCK_API_FETCH), patch(MOCK_API_GET_DATA, return_value=None): + advance_time_to_next_fetch(hass) + await hass.async_block_till_done() + _assert_data(hass, EXPECTED_STATE_EV_UNAVAILABLE) From 2b24f8b735cf1b38e75096438effb93be61f6d36 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Fri, 26 Mar 2021 05:11:08 +0100 Subject: [PATCH 613/831] Remove timedate manipulation from Neato attributes (#48150) * Remove timedate manipulation to get timezone back * Updated camera to new format --- homeassistant/components/neato/camera.py | 2 +- homeassistant/components/neato/vacuum.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/neato/camera.py b/homeassistant/components/neato/camera.py index 74a3cb4bc77..9a2f47bcfa3 100644 --- a/homeassistant/components/neato/camera.py +++ b/homeassistant/components/neato/camera.py @@ -102,7 +102,7 @@ class NeatoCleaningMap(Camera): self._image = image.read() self._image_url = image_url - self._generated_at = (map_data["generated_at"].strip("Z")).replace("T", " ") + self._generated_at = map_data["generated_at"] self._available = True @property diff --git a/homeassistant/components/neato/vacuum.py b/homeassistant/components/neato/vacuum.py index 3b6711d3b72..e0b3c7b779f 100644 --- a/homeassistant/components/neato/vacuum.py +++ b/homeassistant/components/neato/vacuum.py @@ -202,8 +202,8 @@ class NeatoConnectedVacuum(StateVacuumEntity): return mapdata = self._mapdata[self._robot_serial]["maps"][0] - self._clean_time_start = (mapdata["start_at"].strip("Z")).replace("T", " ") - self._clean_time_stop = (mapdata["end_at"].strip("Z")).replace("T", " ") + self._clean_time_start = mapdata["start_at"] + self._clean_time_stop = mapdata["end_at"] self._clean_area = mapdata["cleaned_area"] self._clean_susp_charge_count = mapdata["suspended_cleaning_charging_count"] self._clean_susp_time = mapdata["time_in_suspended_cleaning"] From de1fa706a071923d00da3336cc48427b8b805cee Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 26 Mar 2021 08:07:57 +0100 Subject: [PATCH 614/831] xknx 0.17.4 (#48350) --- homeassistant/components/knx/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 7f14c17839c..629d43092d4 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -2,7 +2,7 @@ "domain": "knx", "name": "KNX", "documentation": "https://www.home-assistant.io/integrations/knx", - "requirements": ["xknx==0.17.3"], + "requirements": ["xknx==0.17.4"], "codeowners": ["@Julius2342", "@farmio", "@marvin-w"], "quality_scale": "silver" } diff --git a/requirements_all.txt b/requirements_all.txt index db05a88e9f7..257bd9710fb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2332,7 +2332,7 @@ xbox-webapi==2.0.8 xboxapi==2.0.1 # homeassistant.components.knx -xknx==0.17.3 +xknx==0.17.4 # homeassistant.components.bluesound # homeassistant.components.rest From 72281f4718b6ca71b77b515fa3afc466d2d812d9 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 26 Mar 2021 08:09:21 +0100 Subject: [PATCH 615/831] Validate device trigger schemas once (#48319) --- homeassistant/components/alarm_control_panel/device_trigger.py | 2 -- homeassistant/components/arcam_fmj/device_trigger.py | 1 - homeassistant/components/climate/device_trigger.py | 1 - homeassistant/components/cover/device_trigger.py | 2 -- homeassistant/components/device_tracker/device_trigger.py | 2 -- homeassistant/components/fan/device_trigger.py | 2 -- homeassistant/components/homekit_controller/device_trigger.py | 2 -- homeassistant/components/kodi/device_trigger.py | 2 -- homeassistant/components/lock/device_trigger.py | 2 -- homeassistant/components/media_player/device_trigger.py | 2 -- homeassistant/components/mqtt/device_trigger.py | 1 - homeassistant/components/nest/device_trigger.py | 1 - homeassistant/components/netatmo/device_trigger.py | 2 -- homeassistant/components/shelly/device_trigger.py | 1 - homeassistant/components/tasmota/device_trigger.py | 1 - homeassistant/components/vacuum/device_trigger.py | 2 -- 16 files changed, 26 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/device_trigger.py b/homeassistant/components/alarm_control_panel/device_trigger.py index 9ab28e3e863..c5d2cdd2e37 100644 --- a/homeassistant/components/alarm_control_panel/device_trigger.py +++ b/homeassistant/components/alarm_control_panel/device_trigger.py @@ -131,8 +131,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - if config[CONF_TYPE] == "triggered": to_state = STATE_ALARM_TRIGGERED elif config[CONF_TYPE] == "disarmed": diff --git a/homeassistant/components/arcam_fmj/device_trigger.py b/homeassistant/components/arcam_fmj/device_trigger.py index 060c56e5953..be2b136bf97 100644 --- a/homeassistant/components/arcam_fmj/device_trigger.py +++ b/homeassistant/components/arcam_fmj/device_trigger.py @@ -56,7 +56,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) job = HassJob(action) if config[CONF_TYPE] == "turn_on": diff --git a/homeassistant/components/climate/device_trigger.py b/homeassistant/components/climate/device_trigger.py index bee019fa6da..8a0d7a440c4 100644 --- a/homeassistant/components/climate/device_trigger.py +++ b/homeassistant/components/climate/device_trigger.py @@ -113,7 +113,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) trigger_type = config[CONF_TYPE] if trigger_type == "hvac_mode_changed": diff --git a/homeassistant/components/cover/device_trigger.py b/homeassistant/components/cover/device_trigger.py index 119bfc835f5..c7ad852dc0f 100644 --- a/homeassistant/components/cover/device_trigger.py +++ b/homeassistant/components/cover/device_trigger.py @@ -170,8 +170,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - if config[CONF_TYPE] in STATE_TRIGGER_TYPES: if config[CONF_TYPE] == "opened": to_state = STATE_OPEN diff --git a/homeassistant/components/device_tracker/device_trigger.py b/homeassistant/components/device_tracker/device_trigger.py index 49b77024a1e..81a16545c74 100644 --- a/homeassistant/components/device_tracker/device_trigger.py +++ b/homeassistant/components/device_tracker/device_trigger.py @@ -71,8 +71,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - if config[CONF_TYPE] == "enters": event = zone.EVENT_ENTER else: diff --git a/homeassistant/components/fan/device_trigger.py b/homeassistant/components/fan/device_trigger.py index f109cfef117..c72be6f9d7c 100644 --- a/homeassistant/components/fan/device_trigger.py +++ b/homeassistant/components/fan/device_trigger.py @@ -71,8 +71,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - if config[CONF_TYPE] == "turned_on": to_state = STATE_ON else: diff --git a/homeassistant/components/homekit_controller/device_trigger.py b/homeassistant/components/homekit_controller/device_trigger.py index 4de5ce66a09..31bfcc18d52 100644 --- a/homeassistant/components/homekit_controller/device_trigger.py +++ b/homeassistant/components/homekit_controller/device_trigger.py @@ -257,8 +257,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - device_id = config[CONF_DEVICE_ID] device = hass.data[TRIGGERS][device_id] return await device.async_attach_trigger(config, action, automation_info) diff --git a/homeassistant/components/kodi/device_trigger.py b/homeassistant/components/kodi/device_trigger.py index 3454fc122ed..c59fe53be14 100644 --- a/homeassistant/components/kodi/device_trigger.py +++ b/homeassistant/components/kodi/device_trigger.py @@ -84,8 +84,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - if config[CONF_TYPE] == "turn_on": return _attach_trigger(hass, config, action, EVENT_TURN_ON) diff --git a/homeassistant/components/lock/device_trigger.py b/homeassistant/components/lock/device_trigger.py index 3e5bee49a22..e6ec1536e3a 100644 --- a/homeassistant/components/lock/device_trigger.py +++ b/homeassistant/components/lock/device_trigger.py @@ -71,8 +71,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - if config[CONF_TYPE] == "locked": to_state = STATE_LOCKED else: diff --git a/homeassistant/components/media_player/device_trigger.py b/homeassistant/components/media_player/device_trigger.py index 03c165412e9..29b69954d43 100644 --- a/homeassistant/components/media_player/device_trigger.py +++ b/homeassistant/components/media_player/device_trigger.py @@ -66,8 +66,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - if config[CONF_TYPE] == "turned_on": to_state = STATE_ON elif config[CONF_TYPE] == "turned_off": diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index 12a98905ba5..1e058162bc3 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -319,7 +319,6 @@ async def async_attach_trigger( """Attach a trigger.""" if DEVICE_TRIGGERS not in hass.data: hass.data[DEVICE_TRIGGERS] = {} - config = TRIGGER_SCHEMA(config) device_id = config[CONF_DEVICE_ID] discovery_id = config[CONF_DISCOVERY_ID] diff --git a/homeassistant/components/nest/device_trigger.py b/homeassistant/components/nest/device_trigger.py index bd2b59c6cfe..d59ec05c503 100644 --- a/homeassistant/components/nest/device_trigger.py +++ b/homeassistant/components/nest/device_trigger.py @@ -82,7 +82,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) event_config = event_trigger.TRIGGER_SCHEMA( { event_trigger.CONF_PLATFORM: "event", diff --git a/homeassistant/components/netatmo/device_trigger.py b/homeassistant/components/netatmo/device_trigger.py index 3893fbed4c7..d6085ec06ec 100644 --- a/homeassistant/components/netatmo/device_trigger.py +++ b/homeassistant/components/netatmo/device_trigger.py @@ -125,8 +125,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - device_registry = await hass.helpers.device_registry.async_get_registry() device = device_registry.async_get(config[CONF_DEVICE_ID]) diff --git a/homeassistant/components/shelly/device_trigger.py b/homeassistant/components/shelly/device_trigger.py index deec98a4915..b7cf1120949 100644 --- a/homeassistant/components/shelly/device_trigger.py +++ b/homeassistant/components/shelly/device_trigger.py @@ -92,7 +92,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) event_config = { event_trigger.CONF_PLATFORM: CONF_EVENT, event_trigger.CONF_EVENT_TYPE: EVENT_SHELLY_CLICK, diff --git a/homeassistant/components/tasmota/device_trigger.py b/homeassistant/components/tasmota/device_trigger.py index 139e9be816a..ae4a528efc6 100644 --- a/homeassistant/components/tasmota/device_trigger.py +++ b/homeassistant/components/tasmota/device_trigger.py @@ -273,7 +273,6 @@ async def async_attach_trigger( """Attach a device trigger.""" if DEVICE_TRIGGERS not in hass.data: hass.data[DEVICE_TRIGGERS] = {} - config = TRIGGER_SCHEMA(config) device_id = config[CONF_DEVICE_ID] discovery_id = config[CONF_DISCOVERY_ID] diff --git a/homeassistant/components/vacuum/device_trigger.py b/homeassistant/components/vacuum/device_trigger.py index 8023a3865a7..1ba6e330a8c 100644 --- a/homeassistant/components/vacuum/device_trigger.py +++ b/homeassistant/components/vacuum/device_trigger.py @@ -68,8 +68,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - if config[CONF_TYPE] == "cleaning": to_state = STATE_CLEANING else: From da2fecb312025f74498172e82f4c0fe7689e2d8a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 26 Mar 2021 08:21:11 +0100 Subject: [PATCH 616/831] Pre-calculate Verisure alarm states (#48340) * Pre-calculate Verisure alarm states * Correct super call --- .../verisure/alarm_control_panel.py | 44 ++++++++----------- homeassistant/components/verisure/const.py | 14 ++++++ 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index 761feb0d2cb..34a60b9cae4 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -13,17 +13,11 @@ from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_HOME, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - STATE_ALARM_ARMED_AWAY, - STATE_ALARM_ARMED_HOME, - STATE_ALARM_DISARMED, - STATE_ALARM_PENDING, -) -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import CONF_GIID, DOMAIN, LOGGER +from .const import ALARM_STATE_TO_HA, CONF_GIID, DOMAIN, LOGGER from .coordinator import VerisureDataUpdateCoordinator @@ -41,10 +35,8 @@ class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity): coordinator: VerisureDataUpdateCoordinator - def __init__(self, coordinator: VerisureDataUpdateCoordinator) -> None: - """Initialize the Verisure alarm panel.""" - super().__init__(coordinator) - self._state = None + _changed_by: str | None = None + _state: str | None = None @property def name(self) -> str: @@ -69,18 +61,6 @@ class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity): @property def state(self) -> str | None: """Return the state of the entity.""" - status = self.coordinator.data["alarm"]["statusType"] - if status == "DISARMED": - self._state = STATE_ALARM_DISARMED - elif status == "ARMED_HOME": - self._state = STATE_ALARM_ARMED_HOME - elif status == "ARMED_AWAY": - self._state = STATE_ALARM_ARMED_AWAY - elif status == "PENDING": - self._state = STATE_ALARM_PENDING - else: - LOGGER.error("Unknown alarm state %s", status) - return self._state @property @@ -96,7 +76,7 @@ class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity): @property def changed_by(self) -> str | None: """Return the last change triggered by.""" - return self.coordinator.data["alarm"]["name"] + return self._changed_by async def _async_set_arm_state(self, state: str, code: str | None = None) -> None: """Send set arm state command.""" @@ -125,3 +105,17 @@ class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity): async def async_alarm_arm_away(self, code: str | None = None) -> None: """Send arm away command.""" await self._async_set_arm_state("ARMED_AWAY", code) + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self._state = ALARM_STATE_TO_HA.get( + self.coordinator.data["alarm"]["statusType"] + ) + self._changed_by = self.coordinator.data["alarm"]["name"] + super()._handle_coordinator_update() + + async def async_added_to_hass(self) -> None: + """When entity is added to hass.""" + await super().async_added_to_hass() + self._handle_coordinator_update() diff --git a/homeassistant/components/verisure/const.py b/homeassistant/components/verisure/const.py index 8e39e0594dd..030c5a58075 100644 --- a/homeassistant/components/verisure/const.py +++ b/homeassistant/components/verisure/const.py @@ -2,6 +2,13 @@ from datetime import timedelta import logging +from homeassistant.const import ( + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_DISARMED, + STATE_ALARM_PENDING, +) + DOMAIN = "verisure" LOGGER = logging.getLogger(__package__) @@ -31,6 +38,13 @@ DEVICE_TYPE_NAME = { "WATER1": "Water detector", } +ALARM_STATE_TO_HA = { + "DISARMED": STATE_ALARM_DISARMED, + "ARMED_HOME": STATE_ALARM_ARMED_HOME, + "ARMED_AWAY": STATE_ALARM_ARMED_AWAY, + "PENDING": STATE_ALARM_PENDING, +} + # Legacy; to remove after YAML removal CONF_CODE_DIGITS = "code_digits" CONF_DEFAULT_LOCK_CODE = "default_lock_code" From 5b17aaf9d5a3093218727190b92bce1e73e4688e Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Fri, 26 Mar 2021 08:37:47 +0100 Subject: [PATCH 617/831] Percentage and preset mode support for MQTT fan (#47944) * git push --all origin * Fix percentage to ordered list conversion * Tests for mqtt fan and fixes * Improve tests and error handling base config * Additional tests * Tests completed, small fixes * Allow preset mode and percentages combined * Remove raise in setup and update tests * Alignment with fan entity mode * Fix pylint for len-as-condition * Remove python binary cache file from PR * Additional tests on async_turn_on and fix * Added comments for deprecation of speeds * Schema checks before init * Optimize pre schema checks * Correct schema checks * Update homeassistant/components/mqtt/abbreviations.py Comment speeds for mqtt fan are deprecated not needed here Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/fan.py Comment speeds for mqtt fan are deprecated not needed here Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/fan.py Comment speeds for mqtt fan are deprecated not needed here Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/fan.py Comment speeds for mqtt fan are deprecated not needed here Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/fan.py Comment speeds for mqtt fan are deprecated not needed here Co-authored-by: Erik Montnemery * Warnings for exceptions - testing speed_range * Update homeassistant/components/mqtt/abbreviations.py Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/fan.py Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/fan.py Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/fan.py Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/fan.py Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/fan.py * Save with black Co-authored-by: Erik Montnemery --- .../components/mqtt/abbreviations.py | 9 + homeassistant/components/mqtt/fan.py | 462 +++++- tests/components/mqtt/test_fan.py | 1411 +++++++++++++++-- 3 files changed, 1725 insertions(+), 157 deletions(-) diff --git a/homeassistant/components/mqtt/abbreviations.py b/homeassistant/components/mqtt/abbreviations.py index 868e2fdd791..a65c78f87d1 100644 --- a/homeassistant/components/mqtt/abbreviations.py +++ b/homeassistant/components/mqtt/abbreviations.py @@ -88,6 +88,9 @@ ABBREVIATIONS = { "osc_cmd_t": "oscillation_command_topic", "osc_stat_t": "oscillation_state_topic", "osc_val_tpl": "oscillation_value_template", + "pct_cmd_t": "percentage_command_topic", + "pct_stat_t": "percentage_state_topic", + "pct_val_tpl": "percentage_value_template", "pl": "payload", "pl_arm_away": "payload_arm_away", "pl_arm_home": "payload_arm_home", @@ -124,6 +127,10 @@ ABBREVIATIONS = { "pow_cmd_t": "power_command_topic", "pow_stat_t": "power_state_topic", "pow_stat_tpl": "power_state_template", + "pr_mode_cmd_t": "preset_mode_command_topic", + "pr_mode_stat_t": "preset_mode_state_topic", + "pr_mode_val_tpl": "preset_mode_value_template", + "pr_modes": "preset_modes", "r_tpl": "red_template", "ret": "retain", "rgb_cmd_tpl": "rgb_command_template", @@ -139,6 +146,8 @@ ABBREVIATIONS = { "pos_tpl": "position_template", "spd_cmd_t": "speed_command_topic", "spd_stat_t": "speed_state_topic", + "spd_rng_min": "speed_range_min", + "spd_rng_max": "speed_range_max", "spd_val_tpl": "speed_value_template", "spds": "speeds", "src_type": "source_type", diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index c0663370805..a0395039e78 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -1,18 +1,23 @@ """Support for MQTT fans.""" import functools +import logging import voluptuous as vol from homeassistant.components import fan from homeassistant.components.fan import ( + ATTR_PERCENTAGE, + ATTR_PRESET_MODE, ATTR_SPEED, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, SUPPORT_OSCILLATE, + SUPPORT_PRESET_MODE, SUPPORT_SET_SPEED, FanEntity, + speed_list_without_preset_modes, ) from homeassistant.const import ( CONF_NAME, @@ -25,6 +30,12 @@ from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.util.percentage import ( + ordered_list_item_to_percentage, + percentage_to_ordered_list_item, + percentage_to_ranged_value, + ranged_value_to_percentage, +) from . import ( CONF_COMMAND_TOPIC, @@ -40,6 +51,15 @@ from .debug_info import log_messages from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper CONF_STATE_VALUE_TEMPLATE = "state_value_template" +CONF_PERCENTAGE_STATE_TOPIC = "percentage_state_topic" +CONF_PERCENTAGE_COMMAND_TOPIC = "percentage_command_topic" +CONF_PERCENTAGE_VALUE_TEMPLATE = "percentage_value_template" +CONF_SPEED_RANGE_MIN = "speed_range_min" +CONF_SPEED_RANGE_MAX = "speed_range_max" +CONF_PRESET_MODE_STATE_TOPIC = "preset_mode_state_topic" +CONF_PRESET_MODE_COMMAND_TOPIC = "preset_mode_command_topic" +CONF_PRESET_MODE_VALUE_TEMPLATE = "preset_mode_value_template" +CONF_PRESET_MODES_LIST = "preset_modes" CONF_SPEED_STATE_TOPIC = "speed_state_topic" CONF_SPEED_COMMAND_TOPIC = "speed_command_topic" CONF_SPEED_VALUE_TEMPLATE = "speed_value_template" @@ -58,41 +78,96 @@ DEFAULT_NAME = "MQTT Fan" DEFAULT_PAYLOAD_ON = "ON" DEFAULT_PAYLOAD_OFF = "OFF" DEFAULT_OPTIMISTIC = False +DEFAULT_SPEED_RANGE_MIN = 1 +DEFAULT_SPEED_RANGE_MAX = 100 OSCILLATE_ON_PAYLOAD = "oscillate_on" OSCILLATE_OFF_PAYLOAD = "oscillate_off" OSCILLATION = "oscillation" -PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, - vol.Optional(CONF_OSCILLATION_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_OSCILLATION_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_OSCILLATION_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_PAYLOAD_HIGH_SPEED, default=SPEED_HIGH): cv.string, - vol.Optional(CONF_PAYLOAD_LOW_SPEED, default=SPEED_LOW): cv.string, - vol.Optional(CONF_PAYLOAD_MEDIUM_SPEED, default=SPEED_MEDIUM): cv.string, - vol.Optional(CONF_PAYLOAD_OFF_SPEED, default=SPEED_OFF): cv.string, - vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, - vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, - vol.Optional( - CONF_PAYLOAD_OSCILLATION_OFF, default=OSCILLATE_OFF_PAYLOAD - ): cv.string, - vol.Optional( - CONF_PAYLOAD_OSCILLATION_ON, default=OSCILLATE_ON_PAYLOAD - ): cv.string, - vol.Optional(CONF_SPEED_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional( - CONF_SPEED_LIST, - default=[SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH], - ): cv.ensure_list, - vol.Optional(CONF_SPEED_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_SPEED_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template, - } -).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) +_LOGGER = logging.getLogger(__name__) + + +def valid_fan_speed_configuration(config): + """Validate that the fan speed configuration is valid, throws if it isn't.""" + if config.get(CONF_SPEED_COMMAND_TOPIC) and not speed_list_without_preset_modes( + config.get(CONF_SPEED_LIST) + ): + raise ValueError("No valid speeds configured") + return config + + +def valid_speed_range_configuration(config): + """Validate that the fan speed_range configuration is valid, throws if it isn't.""" + if config.get(CONF_SPEED_RANGE_MIN) == 0: + raise ValueError("speed_range_min must be > 0") + if config.get(CONF_SPEED_RANGE_MIN) >= config.get(CONF_SPEED_RANGE_MAX): + raise ValueError("speed_range_max must be > speed_range_min") + return config + + +PLATFORM_SCHEMA = vol.All( + # CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC, CONF_STATE_VALUE_TEMPLATE, CONF_SPEED_LIST and + # Speeds SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH SPEED_OFF, + # are deprecated, support will be removed after a quarter (2021.7) + cv.deprecated(CONF_PAYLOAD_HIGH_SPEED), + cv.deprecated(CONF_PAYLOAD_LOW_SPEED), + cv.deprecated(CONF_PAYLOAD_MEDIUM_SPEED), + cv.deprecated(CONF_SPEED_LIST), + cv.deprecated(CONF_SPEED_COMMAND_TOPIC), + cv.deprecated(CONF_SPEED_STATE_TOPIC), + cv.deprecated(CONF_SPEED_VALUE_TEMPLATE), + mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, + vol.Optional(CONF_OSCILLATION_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_OSCILLATION_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_OSCILLATION_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_PERCENTAGE_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_PERCENTAGE_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_PERCENTAGE_VALUE_TEMPLATE): cv.template, + # CONF_PRESET_MODE_COMMAND_TOPIC and CONF_PRESET_MODES_LIST must be used together + vol.Inclusive( + CONF_PRESET_MODE_COMMAND_TOPIC, "preset_modes" + ): mqtt.valid_publish_topic, + vol.Inclusive( + CONF_PRESET_MODES_LIST, "preset_modes", default=[] + ): cv.ensure_list, + vol.Optional(CONF_PRESET_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_PRESET_MODE_VALUE_TEMPLATE): cv.template, + vol.Optional( + CONF_SPEED_RANGE_MIN, default=DEFAULT_SPEED_RANGE_MIN + ): cv.positive_int, + vol.Optional( + CONF_SPEED_RANGE_MAX, default=DEFAULT_SPEED_RANGE_MAX + ): cv.positive_int, + vol.Optional(CONF_PAYLOAD_HIGH_SPEED, default=SPEED_HIGH): cv.string, + vol.Optional(CONF_PAYLOAD_LOW_SPEED, default=SPEED_LOW): cv.string, + vol.Optional(CONF_PAYLOAD_MEDIUM_SPEED, default=SPEED_MEDIUM): cv.string, + vol.Optional(CONF_PAYLOAD_OFF_SPEED, default=SPEED_OFF): cv.string, + vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, + vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, + vol.Optional( + CONF_PAYLOAD_OSCILLATION_OFF, default=OSCILLATE_OFF_PAYLOAD + ): cv.string, + vol.Optional( + CONF_PAYLOAD_OSCILLATION_ON, default=OSCILLATE_ON_PAYLOAD + ): cv.string, + vol.Optional(CONF_SPEED_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional( + CONF_SPEED_LIST, + default=[SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH], + ): cv.ensure_list, + vol.Optional(CONF_SPEED_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_SPEED_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template, + } + ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema), + valid_fan_speed_configuration, + valid_speed_range_configuration, +) async def async_setup_platform( @@ -124,7 +199,10 @@ class MqttFan(MqttEntity, FanEntity): def __init__(self, hass, config, config_entry, discovery_data): """Initialize the MQTT fan.""" self._state = False + # self._speed will be removed after a quarter (2021.7) self._speed = None + self._percentage = None + self._preset_mode = None self._oscillation = None self._supported_features = 0 @@ -133,6 +211,8 @@ class MqttFan(MqttEntity, FanEntity): self._templates = None self._optimistic = None self._optimistic_oscillation = None + self._optimistic_percentage = None + self._optimistic_preset_mode = None self._optimistic_speed = None MqttEntity.__init__(self, hass, config, config_entry, discovery_data) @@ -144,11 +224,19 @@ class MqttFan(MqttEntity, FanEntity): def _setup_from_config(self, config): """(Re)Setup the entity.""" + self._speed_range = ( + config.get(CONF_SPEED_RANGE_MIN), + config.get(CONF_SPEED_RANGE_MAX), + ) self._topic = { key: config.get(key) for key in ( CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, + CONF_PERCENTAGE_STATE_TOPIC, + CONF_PERCENTAGE_COMMAND_TOPIC, + CONF_PRESET_MODE_STATE_TOPIC, + CONF_PRESET_MODE_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC, CONF_SPEED_COMMAND_TOPIC, CONF_OSCILLATION_STATE_TOPIC, @@ -157,6 +245,9 @@ class MqttFan(MqttEntity, FanEntity): } self._templates = { CONF_STATE: config.get(CONF_STATE_VALUE_TEMPLATE), + ATTR_PERCENTAGE: config.get(CONF_PERCENTAGE_VALUE_TEMPLATE), + ATTR_PRESET_MODE: config.get(CONF_PRESET_MODE_VALUE_TEMPLATE), + # ATTR_SPEED is deprecated in the schema, support will be removed after a quarter (2021.7) ATTR_SPEED: config.get(CONF_SPEED_VALUE_TEMPLATE), OSCILLATION: config.get(CONF_OSCILLATION_VALUE_TEMPLATE), } @@ -165,16 +256,53 @@ class MqttFan(MqttEntity, FanEntity): "STATE_OFF": config[CONF_PAYLOAD_OFF], "OSCILLATE_ON_PAYLOAD": config[CONF_PAYLOAD_OSCILLATION_ON], "OSCILLATE_OFF_PAYLOAD": config[CONF_PAYLOAD_OSCILLATION_OFF], + # The use of legacy speeds is deprecated in the schema, support will be removed after a quarter (2021.7) "SPEED_LOW": config[CONF_PAYLOAD_LOW_SPEED], "SPEED_MEDIUM": config[CONF_PAYLOAD_MEDIUM_SPEED], "SPEED_HIGH": config[CONF_PAYLOAD_HIGH_SPEED], "SPEED_OFF": config[CONF_PAYLOAD_OFF_SPEED], } + # The use of legacy speeds is deprecated in the schema, support will be removed after a quarter (2021.7) + self._feature_legacy_speeds = not self._topic[CONF_SPEED_COMMAND_TOPIC] is None + if self._feature_legacy_speeds: + self._legacy_speeds_list = config[CONF_SPEED_LIST] + self._legacy_speeds_list_no_off = speed_list_without_preset_modes( + self._legacy_speeds_list + ) + else: + self._legacy_speeds_list = [] + + self._feature_percentage = CONF_PERCENTAGE_COMMAND_TOPIC in config + self._feature_preset_mode = CONF_PRESET_MODE_COMMAND_TOPIC in config + if self._feature_preset_mode: + self._speeds_list = speed_list_without_preset_modes( + self._legacy_speeds_list + config[CONF_PRESET_MODES_LIST] + ) + self._preset_modes = ( + self._legacy_speeds_list + config[CONF_PRESET_MODES_LIST] + ) + else: + self._speeds_list = speed_list_without_preset_modes( + self._legacy_speeds_list + ) + self._preset_modes = [] + + if not self._speeds_list or self._feature_percentage: + self._speed_count = 100 + else: + self._speed_count = len(self._speeds_list) + optimistic = config[CONF_OPTIMISTIC] self._optimistic = optimistic or self._topic[CONF_STATE_TOPIC] is None self._optimistic_oscillation = ( optimistic or self._topic[CONF_OSCILLATION_STATE_TOPIC] is None ) + self._optimistic_percentage = ( + optimistic or self._topic[CONF_PERCENTAGE_STATE_TOPIC] is None + ) + self._optimistic_preset_mode = ( + optimistic or self._topic[CONF_PRESET_MODE_STATE_TOPIC] is None + ) self._optimistic_speed = ( optimistic or self._topic[CONF_SPEED_STATE_TOPIC] is None ) @@ -184,9 +312,14 @@ class MqttFan(MqttEntity, FanEntity): self._topic[CONF_OSCILLATION_COMMAND_TOPIC] is not None and SUPPORT_OSCILLATE ) - self._supported_features |= ( - self._topic[CONF_SPEED_COMMAND_TOPIC] is not None and SUPPORT_SET_SPEED - ) + if self._feature_preset_mode and self._speeds_list: + self._supported_features |= SUPPORT_SET_SPEED + if self._feature_percentage: + self._supported_features |= SUPPORT_SET_SPEED + if self._feature_legacy_speeds: + self._supported_features |= SUPPORT_SET_SPEED + if self._feature_preset_mode: + self._supported_features |= SUPPORT_PRESET_MODE for key, tpl in list(self._templates.items()): if tpl is None: @@ -217,19 +350,103 @@ class MqttFan(MqttEntity, FanEntity): "qos": self._config[CONF_QOS], } + @callback + @log_messages(self.hass, self.entity_id) + def percentage_received(msg): + """Handle new received MQTT message for the percentage.""" + numeric_val_str = self._templates[ATTR_PERCENTAGE](msg.payload) + try: + percentage = ranged_value_to_percentage( + self._speed_range, int(numeric_val_str) + ) + except ValueError: + _LOGGER.warning( + "'%s' received on topic %s is not a valid speed within the speed range", + msg.payload, + msg.topic, + ) + return + if percentage < 0 or percentage > 100: + _LOGGER.warning( + "'%s' received on topic %s is not a valid speed within the speed range", + msg.payload, + msg.topic, + ) + return + self._percentage = percentage + self.async_write_ha_state() + + if self._topic[CONF_PERCENTAGE_STATE_TOPIC] is not None: + topics[CONF_PERCENTAGE_STATE_TOPIC] = { + "topic": self._topic[CONF_PERCENTAGE_STATE_TOPIC], + "msg_callback": percentage_received, + "qos": self._config[CONF_QOS], + } + self._percentage = None + + @callback + @log_messages(self.hass, self.entity_id) + def preset_mode_received(msg): + """Handle new received MQTT message for preset mode.""" + preset_mode = self._templates[ATTR_PRESET_MODE](msg.payload) + if preset_mode not in self.preset_modes: + _LOGGER.warning( + "'%s' received on topic %s is not a valid preset mode", + msg.payload, + msg.topic, + ) + return + + self._preset_mode = preset_mode + if not self._implemented_percentage and (preset_mode in self.speed_list): + self._percentage = ordered_list_item_to_percentage( + self.speed_list, preset_mode + ) + self.async_write_ha_state() + + if self._topic[CONF_PRESET_MODE_STATE_TOPIC] is not None: + topics[CONF_PRESET_MODE_STATE_TOPIC] = { + "topic": self._topic[CONF_PRESET_MODE_STATE_TOPIC], + "msg_callback": preset_mode_received, + "qos": self._config[CONF_QOS], + } + self._preset_mode = None + + # The use of legacy speeds is deprecated in the schema, support will be removed after a quarter (2021.7) @callback @log_messages(self.hass, self.entity_id) def speed_received(msg): """Handle new received MQTT message for the speed.""" - payload = self._templates[ATTR_SPEED](msg.payload) - if payload == self._payload["SPEED_LOW"]: - self._speed = SPEED_LOW - elif payload == self._payload["SPEED_MEDIUM"]: - self._speed = SPEED_MEDIUM - elif payload == self._payload["SPEED_HIGH"]: - self._speed = SPEED_HIGH - elif payload == self._payload["SPEED_OFF"]: - self._speed = SPEED_OFF + speed_payload = self._templates[ATTR_SPEED](msg.payload) + if speed_payload == self._payload["SPEED_LOW"]: + speed = SPEED_LOW + elif speed_payload == self._payload["SPEED_MEDIUM"]: + speed = SPEED_MEDIUM + elif speed_payload == self._payload["SPEED_HIGH"]: + speed = SPEED_HIGH + elif speed_payload == self._payload["SPEED_OFF"]: + speed = SPEED_OFF + else: + speed = None + + if speed and speed in self._legacy_speeds_list: + self._speed = speed + else: + _LOGGER.warning( + "'%s' received on topic %s is not a valid speed", + msg.payload, + msg.topic, + ) + return + + if not self._implemented_percentage: + if speed in self._speeds_list: + self._percentage = ordered_list_item_to_percentage( + self._speeds_list, speed + ) + elif speed == SPEED_OFF: + self._percentage = 0 + self.async_write_ha_state() if self._topic[CONF_SPEED_STATE_TOPIC] is not None: @@ -273,10 +490,42 @@ class MqttFan(MqttEntity, FanEntity): """Return true if device is on.""" return self._state + @property + def _implemented_percentage(self): + """Return true if percentage has been implemented.""" + return self._feature_percentage + + @property + def _implemented_preset_mode(self): + """Return true if preset_mode has been implemented.""" + return self._feature_preset_mode + + # The use of legacy speeds is deprecated in the schema, support will be removed after a quarter (2021.7) + @property + def _implemented_speed(self): + """Return true if speed has been implemented.""" + return self._feature_legacy_speeds + + @property + def percentage(self): + """Return the current percentage.""" + return self._percentage + + @property + def preset_mode(self): + """Return the current preset _mode.""" + return self._preset_mode + + @property + def preset_modes(self) -> list: + """Get the list of available preset modes.""" + return self._preset_modes + + # The speed_list property is deprecated in the schema, support will be removed after a quarter (2021.7) @property def speed_list(self) -> list: """Get the list of available speeds.""" - return self._config[CONF_SPEED_LIST] + return self._speeds_list @property def supported_features(self) -> int: @@ -288,18 +537,17 @@ class MqttFan(MqttEntity, FanEntity): """Return the current speed.""" return self._speed + @property + def speed_count(self) -> int: + """Return the number of speeds the fan supports or 100 if percentage is supported.""" + return self._speed_count + @property def oscillating(self): """Return the oscillation state.""" return self._oscillation - # - # The fan entity model has changed to use percentages and preset_modes - # instead of speeds. - # - # Please review - # https://developers.home-assistant.io/docs/core/entity/fan/ - # + # The speed attribute deprecated in the schema, support will be removed after a quarter (2021.7) async def async_turn_on( self, speed: str = None, @@ -318,7 +566,12 @@ class MqttFan(MqttEntity, FanEntity): self._config[CONF_QOS], self._config[CONF_RETAIN], ) - if speed: + if percentage: + await self.async_set_percentage(percentage) + if preset_mode: + await self.async_set_preset_mode(preset_mode) + # The speed attribute deprecated in the schema, support will be removed after a quarter (2021.7) + if speed and not percentage and not preset_mode: await self.async_set_speed(speed) if self._optimistic: self._state = True @@ -340,32 +593,111 @@ class MqttFan(MqttEntity, FanEntity): self._state = False self.async_write_ha_state() - async def async_set_speed(self, speed: str) -> None: - """Set the speed of the fan. + async def async_set_percentage(self, percentage: int) -> None: + """Set the percentage of the fan. This method is a coroutine. """ - if speed == SPEED_LOW: - mqtt_payload = self._payload["SPEED_LOW"] - elif speed == SPEED_MEDIUM: - mqtt_payload = self._payload["SPEED_MEDIUM"] - elif speed == SPEED_HIGH: - mqtt_payload = self._payload["SPEED_HIGH"] - elif speed == SPEED_OFF: - mqtt_payload = self._payload["SPEED_OFF"] - else: - raise ValueError(f"{speed} is not a valid fan speed") + percentage_payload = int( + percentage_to_ranged_value(self._speed_range, percentage) + ) + if self._implemented_preset_mode: + if percentage: + await self.async_set_preset_mode( + preset_mode=percentage_to_ordered_list_item( + self.speed_list, percentage + ) + ) + # Legacy are deprecated in the schema, support will be removed after a quarter (2021.7) + elif self._feature_legacy_speeds and ( + SPEED_OFF in self._legacy_speeds_list + ): + await self.async_set_preset_mode(SPEED_OFF) + # Legacy are deprecated in the schema, support will be removed after a quarter (2021.7) + elif self._feature_legacy_speeds: + if percentage: + await self.async_set_speed( + percentage_to_ordered_list_item( + self._legacy_speeds_list_no_off, + percentage, + ) + ) + elif SPEED_OFF in self._legacy_speeds_list: + await self.async_set_speed(SPEED_OFF) + + if self._implemented_percentage: + mqtt.async_publish( + self.hass, + self._topic[CONF_PERCENTAGE_COMMAND_TOPIC], + percentage_payload, + self._config[CONF_QOS], + self._config[CONF_RETAIN], + ) + + if self._optimistic_percentage: + self._percentage = percentage + self.async_write_ha_state() + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set the preset mode of the fan. + + This method is a coroutine. + """ + if preset_mode not in self.preset_modes: + _LOGGER.warning("'%s'is not a valid preset mode", preset_mode) + return + # Legacy are deprecated in the schema, support will be removed after a quarter (2021.7) + if preset_mode in self._legacy_speeds_list: + await self.async_set_speed(speed=preset_mode) + if not self._implemented_percentage and preset_mode in self.speed_list: + self._percentage = ordered_list_item_to_percentage( + self.speed_list, preset_mode + ) + mqtt_payload = preset_mode mqtt.async_publish( self.hass, - self._topic[CONF_SPEED_COMMAND_TOPIC], + self._topic[CONF_PRESET_MODE_COMMAND_TOPIC], mqtt_payload, self._config[CONF_QOS], self._config[CONF_RETAIN], ) - if self._optimistic_speed: - self._speed = speed + if self._optimistic_preset_mode: + self._preset_mode = preset_mode + self.async_write_ha_state() + + # async_set_speed is deprecated, support will be removed after a quarter (2021.7) + async def async_set_speed(self, speed: str) -> None: + """Set the speed of the fan. + + This method is a coroutine. + """ + speed_payload = None + if self._feature_legacy_speeds: + if speed == SPEED_LOW: + speed_payload = self._payload["SPEED_LOW"] + elif speed == SPEED_MEDIUM: + speed_payload = self._payload["SPEED_MEDIUM"] + elif speed == SPEED_HIGH: + speed_payload = self._payload["SPEED_HIGH"] + elif speed == SPEED_OFF: + speed_payload = self._payload["SPEED_OFF"] + else: + _LOGGER.warning("'%s'is not a valid speed", speed) + return + + if speed_payload: + mqtt.async_publish( + self.hass, + self._topic[CONF_SPEED_COMMAND_TOPIC], + speed_payload, + self._config[CONF_QOS], + self._config[CONF_RETAIN], + ) + + if self._optimistic_speed and speed_payload: + self._speed = speed self.async_write_ha_state() async def async_oscillate(self, oscillating: bool) -> None: diff --git a/tests/components/mqtt/test_fan.py b/tests/components/mqtt/test_fan.py index 045b8fdaf0e..e1ce19c970a 100644 --- a/tests/components/mqtt/test_fan.py +++ b/tests/components/mqtt/test_fan.py @@ -2,8 +2,10 @@ from unittest.mock import patch import pytest +from voluptuous.error import MultipleInvalid from homeassistant.components import fan +from homeassistant.components.fan import NotValidPresetModeError from homeassistant.const import ( ATTR_ASSUMED_STATE, ATTR_SUPPORTED_FEATURES, @@ -58,7 +60,7 @@ async def test_fail_setup_if_no_command_topic(hass, mqtt_mock): assert hass.states.get("fan.test") is None -async def test_controlling_state_via_topic(hass, mqtt_mock): +async def test_controlling_state_via_topic(hass, mqtt_mock, caplog): """Test the controlling state via topic.""" assert await async_setup_component( hass, @@ -73,10 +75,27 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): "payload_on": "StAtE_On", "oscillation_state_topic": "oscillation-state-topic", "oscillation_command_topic": "oscillation-command-topic", - "payload_oscillation_off": "OsC_OfF", - "payload_oscillation_on": "OsC_On", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) "speed_state_topic": "speed-state-topic", "speed_command_topic": "speed-command-topic", + "payload_oscillation_off": "OsC_OfF", + "payload_oscillation_on": "OsC_On", + "percentage_state_topic": "percentage-state-topic", + "percentage_command_topic": "percentage-command-topic", + "preset_mode_state_topic": "preset-mode-state-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": [ + "medium", + "medium-high", + "high", + "very-high", + "freaking-high", + "silent", + ], + "speed_range_min": 1, + "speed_range_max": 200, + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speeds": ["off", "low"], "payload_off_speed": "speed_OfF", "payload_low_speed": "speed_lOw", "payload_medium_speed": "speed_mEdium", @@ -87,16 +106,16 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "state-topic", "StAtE_On") state = hass.states.get("fan.test") - assert state.state is STATE_ON + assert state.state == STATE_ON async_fire_mqtt_message(hass, "state-topic", "StAtE_OfF") state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get("oscillating") is False async_fire_mqtt_message(hass, "oscillation-state-topic", "OsC_On") @@ -107,6 +126,51 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): state = hass.states.get("fan.test") assert state.attributes.get("oscillating") is False + async_fire_mqtt_message(hass, "percentage-state-topic", "0") + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0 + + async_fire_mqtt_message(hass, "percentage-state-topic", "50") + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 25 + + async_fire_mqtt_message(hass, "percentage-state-topic", "100") + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 50 + + async_fire_mqtt_message(hass, "percentage-state-topic", "200") + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 + + async_fire_mqtt_message(hass, "percentage-state-topic", "202") + assert "not a valid speed within the speed range" in caplog.text + caplog.clear() + + async_fire_mqtt_message(hass, "percentage-state-topic", "invalid") + assert "not a valid speed within the speed range" in caplog.text + caplog.clear() + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "low") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "low" + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "medium") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "medium" + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "very-high") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "very-high" + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "silent") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "silent" + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "ModeUnknown") + assert "not a valid preset mode" in caplog.text + caplog.clear() + + # use of speeds is deprecated, support will be removed after a quarter (2021.7) assert state.attributes.get("speed") == fan.SPEED_OFF async_fire_mqtt_message(hass, "speed-state-topic", "speed_lOw") @@ -114,20 +178,173 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): assert state.attributes.get("speed") == fan.SPEED_LOW async_fire_mqtt_message(hass, "speed-state-topic", "speed_mEdium") - state = hass.states.get("fan.test") - assert state.attributes.get("speed") == fan.SPEED_MEDIUM + assert "not a valid speed" in caplog.text + caplog.clear() async_fire_mqtt_message(hass, "speed-state-topic", "speed_High") - state = hass.states.get("fan.test") - assert state.attributes.get("speed") == fan.SPEED_HIGH + assert "not a valid speed" in caplog.text + caplog.clear() async_fire_mqtt_message(hass, "speed-state-topic", "speed_OfF") state = hass.states.get("fan.test") assert state.attributes.get("speed") == fan.SPEED_OFF + async_fire_mqtt_message(hass, "speed-state-topic", "speed_very_high") + assert "not a valid speed" in caplog.text + caplog.clear() -async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock): - """Test the controlling state via topic and JSON message.""" + +async def test_controlling_state_via_topic_with_different_speed_range( + hass, mqtt_mock, caplog +): + """Test the controlling state via topic using an alternate speed range.""" + assert await async_setup_component( + hass, + fan.DOMAIN, + { + fan.DOMAIN: [ + { + "platform": "mqtt", + "name": "test1", + "command_topic": "command-topic", + "percentage_state_topic": "percentage-state-topic1", + "percentage_command_topic": "percentage-command-topic1", + "speed_range_min": 1, + "speed_range_max": 100, + }, + { + "platform": "mqtt", + "name": "test2", + "command_topic": "command-topic", + "percentage_state_topic": "percentage-state-topic2", + "percentage_command_topic": "percentage-command-topic2", + "speed_range_min": 1, + "speed_range_max": 200, + }, + { + "platform": "mqtt", + "name": "test3", + "command_topic": "command-topic", + "percentage_state_topic": "percentage-state-topic3", + "percentage_command_topic": "percentage-command-topic3", + "speed_range_min": 81, + "speed_range_max": 1023, + }, + ] + }, + ) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "percentage-state-topic1", "100") + state = hass.states.get("fan.test1") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 + + async_fire_mqtt_message(hass, "percentage-state-topic2", "100") + state = hass.states.get("fan.test2") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 50 + + async_fire_mqtt_message(hass, "percentage-state-topic3", "1023") + state = hass.states.get("fan.test3") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 + async_fire_mqtt_message(hass, "percentage-state-topic3", "80") + state = hass.states.get("fan.test3") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0 + + state = hass.states.get("fan.test3") + async_fire_mqtt_message(hass, "percentage-state-topic3", "79") + assert "not a valid speed within the speed range" in caplog.text + caplog.clear() + + +async def test_controlling_state_via_topic_no_percentage_topics(hass, mqtt_mock): + """Test the controlling state via topic without percentage topics.""" + assert await async_setup_component( + hass, + fan.DOMAIN, + { + fan.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speed_state_topic": "speed-state-topic", + "speed_command_topic": "speed-command-topic", + "preset_mode_state_topic": "preset-mode-state-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": [ + "high", + "freaking-high", + "silent", + ], + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speeds": ["off", "low", "medium"], + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "freaking-high") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "freaking-high" + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + assert state.attributes.get("speed") == fan.SPEED_OFF + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "high") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "high" + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 75 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + assert state.attributes.get("speed") == fan.SPEED_OFF + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "silent") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "silent" + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 75 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + assert state.attributes.get("speed") == fan.SPEED_OFF + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "medium") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "medium" + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 50 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + assert state.attributes.get("speed") == fan.SPEED_OFF + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "low") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "low" + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 25 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + assert state.attributes.get("speed") == fan.SPEED_OFF + + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + async_fire_mqtt_message(hass, "speed-state-topic", "medium") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "low" + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 50 + assert state.attributes.get("speed") == fan.SPEED_MEDIUM + + async_fire_mqtt_message(hass, "speed-state-topic", "low") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "low" + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 25 + assert state.attributes.get("speed") == fan.SPEED_LOW + + async_fire_mqtt_message(hass, "speed-state-topic", "off") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "low" + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0 + assert state.attributes.get("speed") == fan.SPEED_OFF + + +async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock, caplog): + """Test the controlling state via topic and JSON message (percentage mode).""" assert await async_setup_component( hass, fan.DOMAIN, @@ -139,27 +356,40 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock): "command_topic": "command-topic", "oscillation_state_topic": "oscillation-state-topic", "oscillation_command_topic": "oscillation-command-topic", - "speed_state_topic": "speed-state-topic", - "speed_command_topic": "speed-command-topic", + "percentage_state_topic": "percentage-state-topic", + "percentage_command_topic": "percentage-command-topic", + "preset_mode_state_topic": "preset-mode-state-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": [ + "medium", + "medium-high", + "high", + "very-high", + "freaking-high", + "silent", + ], "state_value_template": "{{ value_json.val }}", "oscillation_value_template": "{{ value_json.val }}", - "speed_value_template": "{{ value_json.val }}", + "percentage_value_template": "{{ value_json.val }}", + "preset_mode_value_template": "{{ value_json.val }}", + "speed_range_min": 1, + "speed_range_max": 100, } }, ) await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "state-topic", '{"val":"ON"}') state = hass.states.get("fan.test") - assert state.state is STATE_ON + assert state.state == STATE_ON async_fire_mqtt_message(hass, "state-topic", '{"val":"OFF"}') state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get("oscillating") is False async_fire_mqtt_message(hass, "oscillation-state-topic", '{"val":"oscillate_on"}') @@ -170,23 +400,29 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock): state = hass.states.get("fan.test") assert state.attributes.get("oscillating") is False - assert state.attributes.get("speed") == fan.SPEED_OFF - - async_fire_mqtt_message(hass, "speed-state-topic", '{"val":"low"}') + async_fire_mqtt_message(hass, "percentage-state-topic", '{"val": 1}') state = hass.states.get("fan.test") - assert state.attributes.get("speed") == fan.SPEED_LOW + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 1 - async_fire_mqtt_message(hass, "speed-state-topic", '{"val":"medium"}') + async_fire_mqtt_message(hass, "percentage-state-topic", '{"val": 100}') state = hass.states.get("fan.test") - assert state.attributes.get("speed") == fan.SPEED_MEDIUM + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 - async_fire_mqtt_message(hass, "speed-state-topic", '{"val":"high"}') - state = hass.states.get("fan.test") - assert state.attributes.get("speed") == fan.SPEED_HIGH + async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"val": "low"}') + assert "not a valid preset mode" in caplog.text + caplog.clear() - async_fire_mqtt_message(hass, "speed-state-topic", '{"val":"off"}') + async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"val": "medium"}') state = hass.states.get("fan.test") - assert state.attributes.get("speed") == fan.SPEED_OFF + assert state.attributes.get("preset_mode") == "medium" + + async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"val": "freaking-high"}') + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "freaking-high" + + async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"val": "silent"}') + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "silent" async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): @@ -202,11 +438,20 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): "payload_off": "StAtE_OfF", "payload_on": "StAtE_On", "oscillation_command_topic": "oscillation-command-topic", - "oscillation_state_topic": "oscillation-state-topic", "payload_oscillation_off": "OsC_OfF", "payload_oscillation_on": "OsC_On", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) "speed_command_topic": "speed-command-topic", - "speed_state_topic": "speed-state-topic", + "percentage_command_topic": "percentage-command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speeds": ["off", "low", "medium"], + "preset_modes": [ + "high", + "freaking-high", + "silent", + ], + # use of speeds is deprecated, support will be removed after a quarter (2021.7) "payload_off_speed": "speed_OfF", "payload_low_speed": "speed_lOw", "payload_medium_speed": "speed_mEdium", @@ -217,7 +462,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_turn_on(hass, "fan.test") @@ -226,7 +471,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_ON + assert state.state == STATE_ON assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_turn_off(hass, "fan.test") @@ -235,7 +480,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_oscillate(hass, "fan.test", True) @@ -244,7 +489,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_oscillate(hass, "fan.test", False) @@ -253,48 +498,232 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + with pytest.raises(MultipleInvalid): + await common.async_set_percentage(hass, "fan.test", -1) + + with pytest.raises(MultipleInvalid): + await common.async_set_percentage(hass, "fan.test", 101) + + await common.async_set_percentage(hass, "fan.test", 100) + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "100", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "freaking-high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 assert state.attributes.get(ATTR_ASSUMED_STATE) + await common.async_set_percentage(hass, "fan.test", 0) + assert mqtt_mock.async_publish.call_count == 3 + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "0", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "off", 0, False + ) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call( + "speed-command-topic", "speed_OfF", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + assert state.attributes.get(fan.ATTR_SPEED) == fan.SPEED_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "low") + assert mqtt_mock.async_publish.call_count == 2 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call( + "speed-command-topic", "speed_lOw", 0, False + ) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "low", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) == "low" + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + assert state.attributes.get(fan.ATTR_SPEED) == fan.SPEED_LOW + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "medium") + assert mqtt_mock.async_publish.call_count == 2 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call( + "speed-command-topic", "speed_mEdium", 0, False + ) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "medium", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) == "medium" + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + assert state.attributes.get(fan.ATTR_SPEED) == fan.SPEED_MEDIUM + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "high") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) == "high" + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "freaking-high") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "freaking-high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) == "freaking-high" + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "silent") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "silent", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) == "silent" + assert state.attributes.get(ATTR_ASSUMED_STATE) + + # use of speeds is deprecated, support will be removed after a quarter (2021.7) await common.async_set_speed(hass, "fan.test", fan.SPEED_LOW) mqtt_mock.async_publish.assert_called_once_with( "speed-command-topic", "speed_lOw", 0, False ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) await common.async_set_speed(hass, "fan.test", fan.SPEED_MEDIUM) mqtt_mock.async_publish.assert_called_once_with( "speed-command-topic", "speed_mEdium", 0, False ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) await common.async_set_speed(hass, "fan.test", fan.SPEED_HIGH) mqtt_mock.async_publish.assert_called_once_with( "speed-command-topic", "speed_High", 0, False ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) await common.async_set_speed(hass, "fan.test", fan.SPEED_OFF) mqtt_mock.async_publish.assert_called_once_with( "speed-command-topic", "speed_OfF", 0, False ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) -async def test_on_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): - """Test on with speed.""" +async def test_sending_mqtt_commands_with_alternate_speed_range(hass, mqtt_mock): + """Test the controlling state via topic using an alternate speed range.""" + assert await async_setup_component( + hass, + fan.DOMAIN, + { + fan.DOMAIN: [ + { + "platform": "mqtt", + "name": "test1", + "command_topic": "command-topic", + "percentage_state_topic": "percentage-state-topic1", + "percentage_command_topic": "percentage-command-topic1", + "speed_range_min": 1, + "speed_range_max": 100, + }, + { + "platform": "mqtt", + "name": "test2", + "command_topic": "command-topic", + "percentage_state_topic": "percentage-state-topic2", + "percentage_command_topic": "percentage-command-topic2", + "speed_range_min": 1, + "speed_range_max": 200, + }, + { + "platform": "mqtt", + "name": "test3", + "command_topic": "command-topic", + "percentage_state_topic": "percentage-state-topic3", + "percentage_command_topic": "percentage-command-topic3", + "speed_range_min": 81, + "speed_range_max": 1023, + }, + ] + }, + ) + await hass.async_block_till_done() + + await common.async_set_percentage(hass, "fan.test1", 0) + mqtt_mock.async_publish.assert_called_once_with( + "percentage-command-topic1", "0", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test1") + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test1", 100) + mqtt_mock.async_publish.assert_called_once_with( + "percentage-command-topic1", "100", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test1") + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test2", 0) + mqtt_mock.async_publish.assert_called_once_with( + "percentage-command-topic2", "0", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test2") + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test2", 100) + mqtt_mock.async_publish.assert_called_once_with( + "percentage-command-topic2", "200", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test2") + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test3", 0) + mqtt_mock.async_publish.assert_called_once_with( + "percentage-command-topic3", "80", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test3") + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test3", 100) + mqtt_mock.async_publish.assert_called_once_with( + "percentage-command-topic3", "1023", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test3") + assert state.attributes.get(ATTR_ASSUMED_STATE) + + +async def test_sending_mqtt_commands_and_optimistic_no_legacy(hass, mqtt_mock, caplog): + """Test optimistic mode without state topic without legacy speed command topic.""" assert await async_setup_component( hass, fan.DOMAIN, @@ -303,47 +732,429 @@ async def test_on_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): "platform": "mqtt", "name": "test", "command_topic": "command-topic", - "oscillation_command_topic": "oscillation-command-topic", - "speed_command_topic": "speed-command-topic", + "percentage_command_topic": "percentage-command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": [ + "high", + "freaking-high", + "silent", + ], } }, ) await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_turn_on(hass, "fan.test") mqtt_mock.async_publish.assert_called_once_with("command-topic", "ON", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_ON + assert state.state == STATE_ON assert state.attributes.get(ATTR_ASSUMED_STATE) - assert state.attributes.get(fan.ATTR_SPEED) is None - assert state.attributes.get(fan.ATTR_OSCILLATING) is None await common.async_turn_off(hass, "fan.test") mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) - await common.async_turn_on(hass, "fan.test", speed="low") - assert mqtt_mock.async_publish.call_count == 2 - mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) - mqtt_mock.async_publish.assert_any_call("speed-command-topic", "low", 0, False) + with pytest.raises(MultipleInvalid): + await common.async_set_percentage(hass, "fan.test", -1) + + with pytest.raises(MultipleInvalid): + await common.async_set_percentage(hass, "fan.test", 101) + + await common.async_set_percentage(hass, "fan.test", 100) + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "100", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "freaking-high", 0, False + ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_ON + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 + assert state.attributes.get(fan.ATTR_PRESET_MODE) == "freaking-high" assert state.attributes.get(ATTR_ASSUMED_STATE) - assert state.attributes.get(fan.ATTR_SPEED) == "low" - assert state.attributes.get(fan.ATTR_OSCILLATING) is None + + await common.async_set_percentage(hass, "fan.test", 0) + mqtt_mock.async_publish.assert_called_once_with( + "percentage-command-topic", "0", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0 + assert state.attributes.get(fan.ATTR_SPEED) is None + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "low") + assert "not a valid preset mode" in caplog.text + caplog.clear() + + await common.async_set_preset_mode(hass, "fan.test", "medium") + assert "not a valid preset mode" in caplog.text + caplog.clear() + + await common.async_set_preset_mode(hass, "fan.test", "high") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) == "high" + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "freaking-high") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "freaking-high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) == "freaking-high" + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "silent") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "silent", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) == "silent" + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test", percentage=25) + assert mqtt_mock.async_publish.call_count == 3 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "25", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_any_call("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test", preset_mode="high") + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + with pytest.raises(NotValidPresetModeError): + await common.async_turn_on(hass, "fan.test", preset_mode="low") -async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock): - """Test optimistic mode with state topic.""" +async def test_sending_mqtt_commands_and_optimistic_no_percentage_topic( + hass, mqtt_mock +): + """Test optimistic mode without state topic without percentage command topic.""" + assert await async_setup_component( + hass, + fan.DOMAIN, + { + fan.DOMAIN: { + "platform": "mqtt", + "name": "test", + "command_topic": "command-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speed_state_topic": "speed-state-topic", + "speed_command_topic": "speed-command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_mode_state_topic": "preset-mode-state-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speeds": ["off", "low", "medium"], + "preset_modes": [ + "high", + "freaking-high", + "silent", + ], + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + with pytest.raises(MultipleInvalid): + await common.async_set_percentage(hass, "fan.test", -1) + + with pytest.raises(MultipleInvalid): + await common.async_set_percentage(hass, "fan.test", 101) + + await common.async_set_percentage(hass, "fan.test", 100) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "freaking-high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test", 0) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "off", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0 + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "low") + assert mqtt_mock.async_publish.call_count == 2 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "low", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "low", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) is None + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "medium") + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "medium", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) is None + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "high") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) is None + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "freaking-high") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "freaking-high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) is None + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "silent") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "silent", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) is None + assert state.attributes.get(ATTR_ASSUMED_STATE) + + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + await common.async_set_speed(hass, "fan.test", fan.SPEED_LOW) + mqtt_mock.async_publish.assert_called_once_with( + "speed-command-topic", "low", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_speed(hass, "fan.test", fan.SPEED_MEDIUM) + mqtt_mock.async_publish.assert_called_once_with( + "speed-command-topic", "medium", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_speed(hass, "fan.test", fan.SPEED_HIGH) + mqtt_mock.async_publish.assert_called_once_with( + "speed-command-topic", "high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_speed(hass, "fan.test", fan.SPEED_OFF) + + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "off", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test", speed="medium") + assert mqtt_mock.async_publish.call_count == 3 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "medium", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + await common.async_turn_on(hass, "fan.test", speed="high") + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + +# use of speeds is deprecated, support will be removed after a quarter (2021.7) +async def test_sending_mqtt_commands_and_optimistic_legacy_speeds_only( + hass, mqtt_mock, caplog +): + """Test optimistic mode without state topics with legacy speeds.""" + assert await async_setup_component( + hass, + fan.DOMAIN, + { + fan.DOMAIN: { + "platform": "mqtt", + "name": "test", + "command_topic": "command-topic", + "speed_state_topic": "speed-state-topic", + "speed_command_topic": "speed-command-topic", + "speeds": ["off", "low", "medium", "high"], + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test", 100) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "high", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 + assert state.attributes.get(fan.ATTR_SPEED) == "off" + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test", 0) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "off", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0 + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "low") + assert "not a valid preset mode" in caplog.text + caplog.clear() + + await common.async_set_speed(hass, "fan.test", fan.SPEED_LOW) + mqtt_mock.async_publish.assert_called_once_with( + "speed-command-topic", "low", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_speed(hass, "fan.test", fan.SPEED_MEDIUM) + mqtt_mock.async_publish.assert_called_once_with( + "speed-command-topic", "medium", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_speed(hass, "fan.test", fan.SPEED_HIGH) + mqtt_mock.async_publish.assert_called_once_with( + "speed-command-topic", "high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_speed(hass, "fan.test", fan.SPEED_OFF) + mqtt_mock.async_publish.assert_called_once_with( + "speed-command-topic", "off", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test", speed="medium") + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test", speed="off") + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "off", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + +async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock, caplog): + """Test optimistic mode with state topic and turn on attributes.""" assert await async_setup_component( hass, fan.DOMAIN, @@ -353,10 +1164,22 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock): "name": "test", "state_topic": "state-topic", "command_topic": "command-topic", - "oscillation_state_topic": "oscillation-state-topic", - "oscillation_command_topic": "oscillation-command-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) "speed_state_topic": "speed-state-topic", "speed_command_topic": "speed-command-topic", + "oscillation_state_topic": "oscillation-state-topic", + "oscillation_command_topic": "oscillation-command-topic", + "percentage_state_topic": "percentage-state-topic", + "percentage_command_topic": "percentage-command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_mode_state_topic": "preset-mode-state-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speeds": ["off", "low", "medium"], + "preset_modes": [ + "high", + "freaking-high", + "silent", + ], "optimistic": True, } }, @@ -364,21 +1187,129 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_turn_on(hass, "fan.test") mqtt_mock.async_publish.assert_called_once_with("command-topic", "ON", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_ON + assert state.state == STATE_ON assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_turn_off(hass, "fan.test") mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test", speed=fan.SPEED_MEDIUM) + assert mqtt_mock.async_publish.call_count == 3 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "medium", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test", percentage=25) + assert mqtt_mock.async_publish.call_count == 4 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "low", 0, False + ) + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "25", 0, False) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "low", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_any_call("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test", preset_mode="medium") + assert mqtt_mock.async_publish.call_count == 3 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "medium", 0, False + ) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test", preset_mode="high") + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_any_call("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test", preset_mode="silent") + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "silent", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test", preset_mode="silent") + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "silent", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_oscillate(hass, "fan.test", True) @@ -387,7 +1318,28 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock): ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test", percentage=50) + assert mqtt_mock.async_publish.call_count == 4 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "50", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "medium", 0, False + ) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_any_call("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_oscillate(hass, "fan.test", False) @@ -396,25 +1348,121 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock): ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) - await common.async_set_speed(hass, "fan.test", fan.SPEED_LOW) - mqtt_mock.async_publish.assert_called_once_with( - "speed-command-topic", "low", 0, False + await common.async_set_percentage(hass, "fan.test", 33) + assert mqtt_mock.async_publish.call_count == 3 + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "33", 0, False) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "medium", 0, False ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) + await common.async_set_percentage(hass, "fan.test", 50) + assert mqtt_mock.async_publish.call_count == 3 + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "50", 0, False) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "medium", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test", 100) + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "100", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "freaking-high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test", 0) + assert mqtt_mock.async_publish.call_count == 3 + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "0", 0, False) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "off", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "off", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + with pytest.raises(MultipleInvalid): + await common.async_set_percentage(hass, "fan.test", 101) + + await common.async_set_preset_mode(hass, "fan.test", "low") + assert mqtt_mock.async_publish.call_count == 2 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "low", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "low", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "medium") + assert mqtt_mock.async_publish.call_count == 2 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "medium", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "high") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "silent") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "silent", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "ModeX") + assert "not a valid preset mode" in caplog.text + caplog.clear() + + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + # use of speeds is deprecated, support will be removed after a quarter (2021.7) await common.async_set_speed(hass, "fan.test", fan.SPEED_MEDIUM) mqtt_mock.async_publish.assert_called_once_with( "speed-command-topic", "medium", 0, False ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_set_speed(hass, "fan.test", fan.SPEED_HIGH) @@ -423,7 +1471,7 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock): ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_set_speed(hass, "fan.test", fan.SPEED_OFF) @@ -432,14 +1480,15 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock): ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) - with pytest.raises(ValueError): - await common.async_set_speed(hass, "fan.test", "cUsToM") + await common.async_set_speed(hass, "fan.test", "cUsToM") + assert "not a valid speed" in caplog.text + caplog.clear() -async def test_attributes(hass, mqtt_mock): +async def test_attributes(hass, mqtt_mock, caplog): """Test attributes.""" assert await async_setup_component( hass, @@ -450,76 +1499,96 @@ async def test_attributes(hass, mqtt_mock): "name": "test", "command_topic": "command-topic", "oscillation_command_topic": "oscillation-command-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) "speed_command_topic": "speed-command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "percentage_command_topic": "percentage-command-topic", + "preset_modes": [ + "freaking-high", + "silent", + ], } }, ) await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state is STATE_OFF - assert state.attributes.get(fan.ATTR_SPEED_LIST) == ["off", "low", "medium", "high"] + assert state.state == STATE_OFF + assert state.attributes.get(fan.ATTR_SPEED_LIST) == [ + "low", + "medium", + "high", + "freaking-high", + ] await common.async_turn_on(hass, "fan.test") state = hass.states.get("fan.test") - assert state.state is STATE_ON + assert state.state == STATE_ON assert state.attributes.get(ATTR_ASSUMED_STATE) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) assert state.attributes.get(fan.ATTR_SPEED) is None assert state.attributes.get(fan.ATTR_OSCILLATING) is None await common.async_turn_off(hass, "fan.test") state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) assert state.attributes.get(fan.ATTR_SPEED) is None assert state.attributes.get(fan.ATTR_OSCILLATING) is None await common.async_oscillate(hass, "fan.test", True) state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) assert state.attributes.get(fan.ATTR_SPEED) is None assert state.attributes.get(fan.ATTR_OSCILLATING) is True await common.async_oscillate(hass, "fan.test", False) state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) assert state.attributes.get(fan.ATTR_SPEED) is None assert state.attributes.get(fan.ATTR_OSCILLATING) is False + # use of speeds is deprecated, support will be removed after a quarter (2021.7) await common.async_set_speed(hass, "fan.test", fan.SPEED_LOW) state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) assert state.attributes.get(fan.ATTR_SPEED) == "low" assert state.attributes.get(fan.ATTR_OSCILLATING) is False + # use of speeds is deprecated, support will be removed after a quarter (2021.7) await common.async_set_speed(hass, "fan.test", fan.SPEED_MEDIUM) state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) assert state.attributes.get(fan.ATTR_SPEED) == "medium" assert state.attributes.get(fan.ATTR_OSCILLATING) is False await common.async_set_speed(hass, "fan.test", fan.SPEED_HIGH) state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) assert state.attributes.get(fan.ATTR_SPEED) == "high" assert state.attributes.get(fan.ATTR_OSCILLATING) is False await common.async_set_speed(hass, "fan.test", fan.SPEED_OFF) state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) assert state.attributes.get(fan.ATTR_SPEED) == "off" assert state.attributes.get(fan.ATTR_OSCILLATING) is False - with pytest.raises(ValueError): - await common.async_set_speed(hass, "fan.test", "cUsToM") + await common.async_set_speed(hass, "fan.test", "cUsToM") + assert "not a valid speed" in caplog.text + caplog.clear() +# use of speeds is deprecated, support will be removed after a quarter (2021.7) async def test_custom_speed_list(hass, mqtt_mock): """Test optimistic mode without state topic.""" assert await async_setup_component( @@ -541,8 +1610,8 @@ async def test_custom_speed_list(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state is STATE_OFF - assert state.attributes.get(fan.ATTR_SPEED_LIST) == ["off", "high"] + assert state.state == STATE_OFF + assert state.attributes.get(fan.ATTR_SPEED_LIST) == ["high"] async def test_supported_features(hass, mqtt_mock): @@ -565,17 +1634,120 @@ async def test_supported_features(hass, mqtt_mock): }, { "platform": "mqtt", - "name": "test3", + "name": "test3a1", "command_topic": "command-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) "speed_command_topic": "speed-command-topic", }, + { + "platform": "mqtt", + "name": "test3a2", + "command_topic": "command-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speed_command_topic": "speed-command-topic", + "speeds": ["low"], + }, + { + "platform": "mqtt", + "name": "test3a3", + "command_topic": "command-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speed_command_topic": "speed-command-topic", + "speeds": ["off"], + }, + { + "platform": "mqtt", + "name": "test3b", + "command_topic": "command-topic", + "percentage_command_topic": "percentage-command-topic", + }, + { + "platform": "mqtt", + "name": "test3c1", + "command_topic": "command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + }, + { + "platform": "mqtt", + "name": "test3c2", + "command_topic": "command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": ["very-fast", "auto"], + }, + { + "platform": "mqtt", + "name": "test3c3", + "command_topic": "command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": ["off", "on", "auto"], + }, { "platform": "mqtt", "name": "test4", "command_topic": "command-topic", "oscillation_command_topic": "oscillation-command-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) "speed_command_topic": "speed-command-topic", }, + { + "platform": "mqtt", + "name": "test4pcta", + "command_topic": "command-topic", + "percentage_command_topic": "percentage-command-topic", + }, + { + "platform": "mqtt", + "name": "test4pctb", + "command_topic": "command-topic", + "oscillation_command_topic": "oscillation-command-topic", + "percentage_command_topic": "percentage-command-topic", + }, + { + "platform": "mqtt", + "name": "test5pr_ma", + "command_topic": "command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": ["Mode1", "Mode2", "Mode3"], + }, + { + "platform": "mqtt", + "name": "test5pr_mb", + "command_topic": "command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": ["off", "on", "auto"], + }, + { + "platform": "mqtt", + "name": "test5pr_mc", + "command_topic": "command-topic", + "oscillation_command_topic": "oscillation-command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": ["Mode1", "Mode2", "Mode3"], + }, + { + "platform": "mqtt", + "name": "test6spd_range_a", + "command_topic": "command-topic", + "percentage_command_topic": "percentage-command-topic", + "speed_range_min": 1, + "speed_range_max": 40, + }, + { + "platform": "mqtt", + "name": "test6spd_range_b", + "command_topic": "command-topic", + "percentage_command_topic": "percentage-command-topic", + "speed_range_min": 50, + "speed_range_max": 40, + }, + { + "platform": "mqtt", + "name": "test6spd_range_c", + "command_topic": "command-topic", + "percentage_command_topic": "percentage-command-topic", + "speed_range_min": 0, + "speed_range_max": 40, + }, ] }, ) @@ -585,14 +1757,69 @@ async def test_supported_features(hass, mqtt_mock): assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 0 state = hass.states.get("fan.test2") assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_OSCILLATE - state = hass.states.get("fan.test3") + + state = hass.states.get("fan.test3a1") + assert ( + state.attributes.get(ATTR_SUPPORTED_FEATURES) + and fan.SUPPORT_SET_SPEED == fan.SUPPORT_SET_SPEED + ) + state = hass.states.get("fan.test3a2") + assert ( + state.attributes.get(ATTR_SUPPORTED_FEATURES) + and fan.SUPPORT_SET_SPEED == fan.SUPPORT_SET_SPEED + ) + state = hass.states.get("fan.test3a3") + assert state is None + + state = hass.states.get("fan.test3b") assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_SET_SPEED + + state = hass.states.get("fan.test3c1") + assert state is None + + state = hass.states.get("fan.test3c2") + assert ( + state.attributes.get(ATTR_SUPPORTED_FEATURES) + == fan.SUPPORT_PRESET_MODE | fan.SUPPORT_SET_SPEED + ) + state = hass.states.get("fan.test3c3") + assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_PRESET_MODE + state = hass.states.get("fan.test4") assert ( state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_OSCILLATE | fan.SUPPORT_SET_SPEED ) + state = hass.states.get("fan.test4pcta") + assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_SET_SPEED + state = hass.states.get("fan.test4pctb") + assert ( + state.attributes.get(ATTR_SUPPORTED_FEATURES) + == fan.SUPPORT_OSCILLATE | fan.SUPPORT_SET_SPEED + ) + + state = hass.states.get("fan.test5pr_ma") + assert ( + state.attributes.get(ATTR_SUPPORTED_FEATURES) + == fan.SUPPORT_SET_SPEED | fan.SUPPORT_PRESET_MODE + ) + state = hass.states.get("fan.test5pr_mb") + assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_PRESET_MODE + + state = hass.states.get("fan.test5pr_mc") + assert ( + state.attributes.get(ATTR_SUPPORTED_FEATURES) + == fan.SUPPORT_OSCILLATE | fan.SUPPORT_SET_SPEED | fan.SUPPORT_PRESET_MODE + ) + + state = hass.states.get("fan.test6spd_range_a") + assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_SET_SPEED + state = hass.states.get("fan.test6spd_range_b") + assert state is None + state = hass.states.get("fan.test6spd_range_c") + assert state is None + async def test_availability_when_connection_lost(hass, mqtt_mock): """Test availability after MQTT disconnection.""" @@ -643,7 +1870,7 @@ async def test_update_with_json_attrs_not_dict(hass, mqtt_mock, caplog): ) -async def test_update_with_json_attrs_bad_JSON(hass, mqtt_mock, caplog): +async def test_update_with_json_attrs_bad_json(hass, mqtt_mock, caplog): """Test attributes get extracted from a JSON result.""" await help_test_update_with_json_attrs_bad_JSON( hass, mqtt_mock, caplog, fan.DOMAIN, DEFAULT_CONFIG From 4fbc3da196650ee587ec023c04fad1cf91d16843 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 26 Mar 2021 08:46:26 +0100 Subject: [PATCH 618/831] Validate device action schemas once (#48351) --- .../components/alarm_control_panel/device_action.py | 2 -- homeassistant/components/climate/device_action.py | 2 -- homeassistant/components/cover/device_action.py | 2 -- homeassistant/components/fan/device_action.py | 2 -- homeassistant/components/humidifier/device_action.py | 2 -- homeassistant/components/lock/device_action.py | 2 -- homeassistant/components/number/device_action.py | 10 ---------- homeassistant/components/water_heater/device_action.py | 2 -- 8 files changed, 24 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/device_action.py b/homeassistant/components/alarm_control_panel/device_action.py index 67637550db2..9a55998e929 100644 --- a/homeassistant/components/alarm_control_panel/device_action.py +++ b/homeassistant/components/alarm_control_panel/device_action.py @@ -112,8 +112,6 @@ async def async_call_action_from_config( hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" - config = ACTION_SCHEMA(config) - service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} if CONF_CODE in config: service_data[ATTR_CODE] = config[CONF_CODE] diff --git a/homeassistant/components/climate/device_action.py b/homeassistant/components/climate/device_action.py index 18123ab11f7..02474a47f96 100644 --- a/homeassistant/components/climate/device_action.py +++ b/homeassistant/components/climate/device_action.py @@ -79,8 +79,6 @@ async def async_call_action_from_config( hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" - config = ACTION_SCHEMA(config) - service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} if config[CONF_TYPE] == "set_hvac_mode": diff --git a/homeassistant/components/cover/device_action.py b/homeassistant/components/cover/device_action.py index 6981f87c492..74eef8102df 100644 --- a/homeassistant/components/cover/device_action.py +++ b/homeassistant/components/cover/device_action.py @@ -165,8 +165,6 @@ async def async_call_action_from_config( hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" - config = ACTION_SCHEMA(config) - service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} if config[CONF_TYPE] == "open": diff --git a/homeassistant/components/fan/device_action.py b/homeassistant/components/fan/device_action.py index b42c8145470..f4611d353d5 100644 --- a/homeassistant/components/fan/device_action.py +++ b/homeassistant/components/fan/device_action.py @@ -62,8 +62,6 @@ async def async_call_action_from_config( hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" - config = ACTION_SCHEMA(config) - service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} if config[CONF_TYPE] == "turn_on": diff --git a/homeassistant/components/humidifier/device_action.py b/homeassistant/components/humidifier/device_action.py index a68b4d771ef..fa9c1eb71e7 100644 --- a/homeassistant/components/humidifier/device_action.py +++ b/homeassistant/components/humidifier/device_action.py @@ -82,8 +82,6 @@ async def async_call_action_from_config( hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" - config = ACTION_SCHEMA(config) - service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} if config[CONF_TYPE] == "set_humidity": diff --git a/homeassistant/components/lock/device_action.py b/homeassistant/components/lock/device_action.py index 639947f3b88..cb0e2b0daad 100644 --- a/homeassistant/components/lock/device_action.py +++ b/homeassistant/components/lock/device_action.py @@ -78,8 +78,6 @@ async def async_call_action_from_config( hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" - config = ACTION_SCHEMA(config) - service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} if config[CONF_TYPE] == "lock": diff --git a/homeassistant/components/number/device_action.py b/homeassistant/components/number/device_action.py index 1a26226962c..77b36b49f20 100644 --- a/homeassistant/components/number/device_action.py +++ b/homeassistant/components/number/device_action.py @@ -55,11 +55,6 @@ async def async_call_action_from_config( hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" - config = ACTION_SCHEMA(config) - - if config[CONF_TYPE] != ATYP_SET_VALUE: - return - await hass.services.async_call( DOMAIN, const.SERVICE_SET_VALUE, @@ -74,11 +69,6 @@ async def async_call_action_from_config( async def async_get_action_capabilities(hass: HomeAssistant, config: dict) -> dict: """List action capabilities.""" - action_type = config[CONF_TYPE] - - if action_type != ATYP_SET_VALUE: - return {} - fields = {vol.Required(const.ATTR_VALUE): vol.Coerce(float)} return {"extra_fields": vol.Schema(fields)} diff --git a/homeassistant/components/water_heater/device_action.py b/homeassistant/components/water_heater/device_action.py index f138c777d44..e1c84be8753 100644 --- a/homeassistant/components/water_heater/device_action.py +++ b/homeassistant/components/water_heater/device_action.py @@ -61,8 +61,6 @@ async def async_call_action_from_config( hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" - config = ACTION_SCHEMA(config) - service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} if config[CONF_TYPE] == "turn_on": From 1ba54ac2bbd3be853b259942ce3b47e11c6c9b5b Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Fri, 26 Mar 2021 11:13:27 +0100 Subject: [PATCH 619/831] Refactor Netatmo tests (#48277) --- tests/components/netatmo/common.py | 4 + tests/components/netatmo/test_camera.py | 35 ++++++- tests/components/netatmo/test_init.py | 6 +- tests/components/netatmo/test_light.py | 8 +- tests/components/netatmo/test_webhook.py | 126 ----------------------- 5 files changed, 41 insertions(+), 138 deletions(-) delete mode 100644 tests/components/netatmo/test_webhook.py diff --git a/tests/components/netatmo/common.py b/tests/components/netatmo/common.py index 6bafd12acea..54e7610c4e5 100644 --- a/tests/components/netatmo/common.py +++ b/tests/components/netatmo/common.py @@ -31,6 +31,10 @@ COMMON_RESPONSE = { TEST_TIME = 1559347200.0 +FAKE_WEBHOOK_ACTIVATION = { + "push_type": "webhook_activation", +} + def fake_post_request(**args): """Return fake data.""" diff --git a/tests/components/netatmo/test_camera.py b/tests/components/netatmo/test_camera.py index 10e3ca46b2a..372af748267 100644 --- a/tests/components/netatmo/test_camera.py +++ b/tests/components/netatmo/test_camera.py @@ -5,6 +5,7 @@ from unittest.mock import patch from homeassistant.components import camera from homeassistant.components.camera import STATE_STREAMING from homeassistant.components.netatmo.const import ( + NETATMO_EVENT, SERVICE_SET_CAMERA_LIGHT, SERVICE_SET_PERSON_AWAY, SERVICE_SET_PERSONS_HOME, @@ -14,7 +15,7 @@ from homeassistant.util import dt from .common import fake_post_request, simulate_webhook -from tests.common import async_fire_time_changed +from tests.common import async_capture_events, async_fire_time_changed async def test_setup_component_with_webhook(hass, camera_entry): @@ -253,3 +254,35 @@ async def test_camera_reconnect_webhook(hass, config_entry): ) await hass.async_block_till_done() mock_post.assert_called() + + +async def test_webhook_person_event(hass, camera_entry): + """Test that person events are handled.""" + test_netatmo_event = async_capture_events(hass, NETATMO_EVENT) + assert not test_netatmo_event + + fake_webhook_event = { + "persons": [ + { + "id": "91827374-7e04-5298-83ad-a0cb8372dff1", + "face_id": "a1b2c3d4e5", + "face_key": "9876543", + "is_known": True, + "face_url": "https://netatmocameraimage.blob.core.windows.net/production/12345", + } + ], + "snapshot_id": "123456789abc", + "snapshot_key": "foobar123", + "snapshot_url": "https://netatmocameraimage.blob.core.windows.net/production/12346", + "event_type": "person", + "camera_id": "12:34:56:00:f1:62", + "device_id": "12:34:56:00:f1:62", + "event_id": "1234567890", + "message": "MYHOME: John Doe has been seen by Indoor Camera ", + "push_type": "NACamera-person", + } + + webhook_id = camera_entry.data[CONF_WEBHOOK_ID] + await simulate_webhook(hass, webhook_id, fake_webhook_event) + + assert test_netatmo_event diff --git a/tests/components/netatmo/test_init.py b/tests/components/netatmo/test_init.py index 1ad07fe55d3..2ec7d83689e 100644 --- a/tests/components/netatmo/test_init.py +++ b/tests/components/netatmo/test_init.py @@ -7,7 +7,7 @@ from homeassistant.components.netatmo import DOMAIN from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.setup import async_setup_component -from .common import fake_post_request, simulate_webhook +from .common import FAKE_WEBHOOK_ACTIVATION, fake_post_request, simulate_webhook from tests.common import MockConfigEntry from tests.components.cloud import mock_cloud @@ -37,10 +37,6 @@ FAKE_WEBHOOK = { "push_type": "display_change", } -FAKE_WEBHOOK_ACTIVATION = { - "push_type": "webhook_activation", -} - async def test_setup_component(hass): """Test setup and teardown of the netatmo component.""" diff --git a/tests/components/netatmo/test_light.py b/tests/components/netatmo/test_light.py index 8f2f371a15e..4d84bc4e5a5 100644 --- a/tests/components/netatmo/test_light.py +++ b/tests/components/netatmo/test_light.py @@ -8,7 +8,7 @@ from homeassistant.components.light import ( ) from homeassistant.const import ATTR_ENTITY_ID, CONF_WEBHOOK_ID -from .common import simulate_webhook +from .common import FAKE_WEBHOOK_ACTIVATION, simulate_webhook async def test_light_setup_and_services(hass, light_entry): @@ -16,10 +16,7 @@ async def test_light_setup_and_services(hass, light_entry): webhook_id = light_entry.data[CONF_WEBHOOK_ID] # Fake webhook activation - webhook_data = { - "push_type": "webhook_activation", - } - await simulate_webhook(hass, webhook_id, webhook_data) + await simulate_webhook(hass, webhook_id, FAKE_WEBHOOK_ACTIVATION) await hass.async_block_till_done() light_entity = "light.netatmo_garden" @@ -40,7 +37,6 @@ async def test_light_setup_and_services(hass, light_entry): # Trigger light mode change with erroneous webhook data response = { - "user_id": "91763b24c43d3e344f424e8d", "event_type": "light_mode", "device_id": "12:34:56:00:a5:a4", } diff --git a/tests/components/netatmo/test_webhook.py b/tests/components/netatmo/test_webhook.py deleted file mode 100644 index a56bd2f0fde..00000000000 --- a/tests/components/netatmo/test_webhook.py +++ /dev/null @@ -1,126 +0,0 @@ -"""The tests for Netatmo webhook events.""" -from homeassistant.components.netatmo.const import DATA_DEVICE_IDS, DATA_PERSONS -from homeassistant.components.netatmo.webhook import async_handle_webhook -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.util.aiohttp import MockRequest - - -async def test_webhook(hass): - """Test that webhook events are processed.""" - webhook_called = False - - async def handle_event(_): - nonlocal webhook_called - webhook_called = True - - response = ( - b'{"user_id": "123", "user": {"id": "123", "email": "foo@bar.com"},' - b'"push_type": "webhook_activation"}' - ) - request = MockRequest(content=response, mock_source="test") - - async_dispatcher_connect( - hass, - "signal-netatmo-webhook-None", - handle_event, - ) - - await async_handle_webhook(hass, "webhook_id", request) - await hass.async_block_till_done() - - assert webhook_called - - -async def test_webhook_error_in_data(hass): - """Test that errors in webhook data are handled.""" - webhook_called = False - - async def handle_event(_): - nonlocal webhook_called - webhook_called = True - - response = b'""webhook_activation"}' - request = MockRequest(content=response, mock_source="test") - - async_dispatcher_connect( - hass, - "signal-netatmo-webhook-None", - handle_event, - ) - - await async_handle_webhook(hass, "webhook_id", request) - await hass.async_block_till_done() - - assert not webhook_called - - -async def test_webhook_climate_event(hass): - """Test that climate events are handled.""" - webhook_called = False - - async def handle_event(_): - nonlocal webhook_called - webhook_called = True - - response = ( - b'{"user_id": "123", "user": {"id": "123", "email": "foo@bar.com"},' - b'"home_id": "456", "event_type": "therm_mode",' - b'"home": {"id": "456", "therm_mode": "away"},' - b'"mode": "away", "previous_mode": "schedule", "push_type": "home_event_changed"}' - ) - request = MockRequest(content=response, mock_source="test") - - hass.data["netatmo"] = { - DATA_DEVICE_IDS: {}, - } - - async_dispatcher_connect( - hass, - "signal-netatmo-webhook-therm_mode", - handle_event, - ) - - await async_handle_webhook(hass, "webhook_id", request) - await hass.async_block_till_done() - - assert webhook_called - - -async def test_webhook_person_event(hass): - """Test that person events are handled.""" - webhook_called = False - - async def handle_event(_): - nonlocal webhook_called - webhook_called = True - - response = ( - b'{"user_id": "5c81004xxxxxxxxxx45f4",' - b'"persons": [{"id": "e2bf7xxxxxxxxxxxxea3", "face_id": "5d66xxxxxx9b9",' - b'"face_key": "89dxxxxx22", "is_known": true,' - b'"face_url": "https://netatmocameraimage.blob.core.windows.net/production/5xxx"}],' - b'"snapshot_id": "5d19bae867368a59e81cca89", "snapshot_key": "d3b3ae0229f7xb74cf8",' - b'"snapshot_url": "https://netatmocameraimage.blob.core.windows.net/production/5xxxx",' - b'"event_type": "person", "camera_id": "70:xxxxxx:a7", "device_id": "70:xxxxxx:a7",' - b'"home_id": "5c5dxxxxxxxd594", "home_name": "Boulogne Billan.",' - b'"event_id": "5d19bxxxxxxxxcca88",' - b'"message": "Boulogne Billan.: Benoit has been seen by Indoor Camera ",' - b'"push_type": "NACamera-person"}' - ) - request = MockRequest(content=response, mock_source="test") - - hass.data["netatmo"] = { - DATA_DEVICE_IDS: {}, - DATA_PERSONS: {}, - } - - async_dispatcher_connect( - hass, - "signal-netatmo-webhook-person", - handle_event, - ) - - await async_handle_webhook(hass, "webhook_id", request) - await hass.async_block_till_done() - - assert webhook_called From 8f40c87069fdf0487ca6785a1e91cf00257e5b33 Mon Sep 17 00:00:00 2001 From: D3v01dZA Date: Fri, 26 Mar 2021 07:03:38 -0400 Subject: [PATCH 620/831] Bump snapcast to 2.1.2 (#48343) --- homeassistant/components/snapcast/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/snapcast/manifest.json b/homeassistant/components/snapcast/manifest.json index 4e65b60280b..43fbbeb8808 100644 --- a/homeassistant/components/snapcast/manifest.json +++ b/homeassistant/components/snapcast/manifest.json @@ -2,6 +2,6 @@ "domain": "snapcast", "name": "Snapcast", "documentation": "https://www.home-assistant.io/integrations/snapcast", - "requirements": ["snapcast==2.1.1"], + "requirements": ["snapcast==2.1.2"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 257bd9710fb..932f416f6a2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2078,7 +2078,7 @@ smarthab==0.21 smhi-pkg==1.0.13 # homeassistant.components.snapcast -snapcast==2.1.1 +snapcast==2.1.2 # homeassistant.components.socialblade socialbladeclient==0.5 From c4f98a3084f2648749d3e4eeeade9696630d9abd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Mar 2021 01:05:19 -1000 Subject: [PATCH 621/831] Small speed up to adding entities (#48353) --- homeassistant/helpers/entity_platform.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 382ebf8055e..6f41c67ef01 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -18,7 +18,12 @@ from homeassistant.core import ( valid_entity_id, ) from homeassistant.exceptions import HomeAssistantError, PlatformNotReady -from homeassistant.helpers import config_validation as cv, service +from homeassistant.helpers import ( + config_validation as cv, + device_registry as dev_reg, + entity_registry as ent_reg, + service, +) from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util.async_ import run_callback_threadsafe @@ -298,8 +303,8 @@ class EntityPlatform: hass = self.hass - device_registry = await hass.helpers.device_registry.async_get_registry() - entity_registry = await hass.helpers.entity_registry.async_get_registry() + device_registry = dev_reg.async_get(hass) + entity_registry = ent_reg.async_get(hass) tasks = [ self._async_add_entity( # type: ignore entity, update_before_add, entity_registry, device_registry From ae8afb69e767d99dd221e3a65a73688b9663b98b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Mar 2021 02:47:32 -1000 Subject: [PATCH 622/831] Improve august reconnect logic when service become unreachable (#48349) This is a different error than internet down. --- homeassistant/components/august/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index 4edacbbf64a..f74287b31bd 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -2,7 +2,7 @@ "domain": "august", "name": "August", "documentation": "https://www.home-assistant.io/integrations/august", - "requirements": ["yalexs==1.1.5"], + "requirements": ["yalexs==1.1.6"], "codeowners": ["@bdraco"], "dhcp": [ {"hostname":"connect","macaddress":"D86162*"}, diff --git a/requirements_all.txt b/requirements_all.txt index 932f416f6a2..a6868b8aeac 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2348,7 +2348,7 @@ xs1-api-client==3.0.0 yalesmartalarmclient==0.1.6 # homeassistant.components.august -yalexs==1.1.5 +yalexs==1.1.6 # homeassistant.components.yeelight yeelight==0.5.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e641de25fb8..73e4cf61bcc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1209,7 +1209,7 @@ xbox-webapi==2.0.8 xmltodict==0.12.0 # homeassistant.components.august -yalexs==1.1.5 +yalexs==1.1.6 # homeassistant.components.yeelight yeelight==0.5.4 From 02b0a4ca1f2c10931c1acd22ba23af5276478ce8 Mon Sep 17 00:00:00 2001 From: mptei Date: Fri, 26 Mar 2021 14:51:36 +0100 Subject: [PATCH 623/831] Xknx unneeded expose (#48311) --- homeassistant/components/knx/expose.py | 7 ++- requirements_test_all.txt | 3 ++ tests/components/knx/__init__.py | 1 + tests/components/knx/test_expose.py | 67 ++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 tests/components/knx/__init__.py create mode 100644 tests/components/knx/test_expose.py diff --git a/homeassistant/components/knx/expose.py b/homeassistant/components/knx/expose.py index 3d28c394140..f4ea52ada59 100644 --- a/homeassistant/components/knx/expose.py +++ b/homeassistant/components/knx/expose.py @@ -114,12 +114,15 @@ class KNXExposeSensor: if new_state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE): return + old_state = event.data.get("old_state") + if self.expose_attribute is None: - await self._async_set_knx_value(new_state.state) + if old_state is None or old_state.state != new_state.state: + # don't send same value sequentially + await self._async_set_knx_value(new_state.state) return new_attribute = new_state.attributes.get(self.expose_attribute) - old_state = event.data.get("old_state") if old_state is not None: old_attribute = old_state.attributes.get(self.expose_attribute) diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 73e4cf61bcc..6437f30ddaf 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1201,6 +1201,9 @@ wolf_smartset==0.1.8 # homeassistant.components.xbox xbox-webapi==2.0.8 +# homeassistant.components.knx +xknx==0.17.4 + # homeassistant.components.bluesound # homeassistant.components.rest # homeassistant.components.startca diff --git a/tests/components/knx/__init__.py b/tests/components/knx/__init__.py new file mode 100644 index 00000000000..f0fc1f36e08 --- /dev/null +++ b/tests/components/knx/__init__.py @@ -0,0 +1 @@ +"""The tests for KNX integration.""" diff --git a/tests/components/knx/test_expose.py b/tests/components/knx/test_expose.py new file mode 100644 index 00000000000..1f57811c8be --- /dev/null +++ b/tests/components/knx/test_expose.py @@ -0,0 +1,67 @@ +"""Test knx expose.""" +from unittest.mock import AsyncMock, Mock + +from homeassistant.components.knx.expose import KNXExposeSensor + + +async def test_binary_expose(hass): + """Test that a binary expose sends only telegrams on state change.""" + e_id = "fake.entity" + xknxMock = Mock() + xknxMock.telegrams = AsyncMock() + KNXExposeSensor(hass, xknxMock, "binary", e_id, None, "0", "1/1/8") + assert xknxMock.devices.add.call_count == 1, "Expected one device add" + + # Change state to on + xknxMock.reset_mock() + hass.states.async_set(e_id, "on", {}) + await hass.async_block_till_done() + assert xknxMock.telegrams.put.call_count == 1, "Expected telegram for state change" + + # Change attribute; keep state + xknxMock.reset_mock() + hass.states.async_set(e_id, "on", {"brightness": 180}) + await hass.async_block_till_done() + assert ( + xknxMock.telegrams.put.call_count == 0 + ), "Expected no telegram; state not changed" + + # Change attribute and state + xknxMock.reset_mock() + hass.states.async_set(e_id, "off", {"brightness": 0}) + await hass.async_block_till_done() + assert xknxMock.telegrams.put.call_count == 1, "Expected telegram for state change" + + +async def test_expose_attribute(hass): + """Test that an expose sends only telegrams on attribute change.""" + e_id = "fake.entity" + a_id = "fakeAttribute" + xknxMock = Mock() + xknxMock.telegrams = AsyncMock() + KNXExposeSensor(hass, xknxMock, "percentU8", e_id, a_id, None, "1/1/8") + assert xknxMock.devices.add.call_count == 1, "Expected one device add" + + # Change state to on; no attribute + xknxMock.reset_mock() + hass.states.async_set(e_id, "on", {}) + await hass.async_block_till_done() + assert xknxMock.telegrams.put.call_count == 0 + + # Change attribute; keep state + xknxMock.reset_mock() + hass.states.async_set(e_id, "on", {a_id: 1}) + await hass.async_block_till_done() + assert xknxMock.telegrams.put.call_count == 1 + + # Change state keep attribute + xknxMock.reset_mock() + hass.states.async_set(e_id, "off", {a_id: 1}) + await hass.async_block_till_done() + assert xknxMock.telegrams.put.call_count == 0 + + # Change state and attribute + xknxMock.reset_mock() + hass.states.async_set(e_id, "on", {a_id: 0}) + await hass.async_block_till_done() + assert xknxMock.telegrams.put.call_count == 1 From 3bc6497cbdeb04174b3a11c66803af971fb73f21 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Fri, 26 Mar 2021 15:08:41 +0100 Subject: [PATCH 624/831] Add Netatmo schedule event handling (#46573) Co-authored-by: Franck Nijhof --- homeassistant/components/netatmo/climate.py | 62 ++++++---- homeassistant/components/netatmo/const.py | 1 + tests/components/netatmo/test_climate.py | 120 +++++++++++++++++--- 3 files changed, 141 insertions(+), 42 deletions(-) diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index caa4aebe376..9993b4efac2 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -42,6 +42,7 @@ from .const import ( DATA_SCHEDULES, DOMAIN, EVENT_TYPE_CANCEL_SET_POINT, + EVENT_TYPE_SCHEDULE, EVENT_TYPE_SET_POINT, EVENT_TYPE_THERM_MODE, MANUFACTURER, @@ -236,6 +237,7 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): EVENT_TYPE_SET_POINT, EVENT_TYPE_THERM_MODE, EVENT_TYPE_CANCEL_SET_POINT, + EVENT_TYPE_SCHEDULE, ): self._listeners.append( async_dispatcher_connect( @@ -253,7 +255,15 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): """Handle webhook events.""" data = event["data"] - if data.get("home") is None: + if self._home_id != data["home_id"]: + return + + if data["event_type"] == EVENT_TYPE_SCHEDULE and "schedule_id" in data: + self._selected_schedule = self.hass.data[DOMAIN][DATA_SCHEDULES][ + self._home_id + ].get(data["schedule_id"]) + self.async_write_ha_state() + self.data_handler.async_force_update(self._home_status_class) return home = data["home"] @@ -270,35 +280,37 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): self._target_temperature = self._away_temperature elif self._preset == PRESET_SCHEDULE: self.async_update_callback() + self.data_handler.async_force_update(self._home_status_class) self.async_write_ha_state() return - if not home.get("rooms"): - return - - for room in home["rooms"]: - if data["event_type"] == EVENT_TYPE_SET_POINT: - if self._id == room["id"]: - if room["therm_setpoint_mode"] == STATE_NETATMO_OFF: - self._hvac_mode = HVAC_MODE_OFF - elif room["therm_setpoint_mode"] == STATE_NETATMO_MAX: + for room in home.get("rooms", []): + if data["event_type"] == EVENT_TYPE_SET_POINT and self._id == room["id"]: + if room["therm_setpoint_mode"] == STATE_NETATMO_OFF: + self._hvac_mode = HVAC_MODE_OFF + self._preset = STATE_NETATMO_OFF + self._target_temperature = 0 + elif room["therm_setpoint_mode"] == STATE_NETATMO_MAX: + self._hvac_mode = HVAC_MODE_HEAT + self._preset = PRESET_MAP_NETATMO[PRESET_BOOST] + self._target_temperature = DEFAULT_MAX_TEMP + elif room["therm_setpoint_mode"] == STATE_NETATMO_MANUAL: + self._hvac_mode = HVAC_MODE_HEAT + self._target_temperature = room["therm_setpoint_temperature"] + else: + self._target_temperature = room["therm_setpoint_temperature"] + if self._target_temperature == DEFAULT_MAX_TEMP: self._hvac_mode = HVAC_MODE_HEAT - self._target_temperature = DEFAULT_MAX_TEMP - elif room["therm_setpoint_mode"] == STATE_NETATMO_MANUAL: - self._hvac_mode = HVAC_MODE_HEAT - self._target_temperature = room["therm_setpoint_temperature"] - else: - self._target_temperature = room["therm_setpoint_temperature"] - if self._target_temperature == DEFAULT_MAX_TEMP: - self._hvac_mode = HVAC_MODE_HEAT - self.async_write_ha_state() - break + self.async_write_ha_state() + return - elif data["event_type"] == EVENT_TYPE_CANCEL_SET_POINT: - if self._id == room["id"]: - self.async_update_callback() - self.async_write_ha_state() - break + if ( + data["event_type"] == EVENT_TYPE_CANCEL_SET_POINT + and self._id == room["id"] + ): + self.async_update_callback() + self.async_write_ha_state() + return @property def supported_features(self): diff --git a/homeassistant/components/netatmo/const.py b/homeassistant/components/netatmo/const.py index ab268b8703b..b0a312fa1f3 100644 --- a/homeassistant/components/netatmo/const.py +++ b/homeassistant/components/netatmo/const.py @@ -94,6 +94,7 @@ SERVICE_SET_PERSON_AWAY = "set_person_away" EVENT_TYPE_SET_POINT = "set_point" EVENT_TYPE_CANCEL_SET_POINT = "cancel_set_point" EVENT_TYPE_THERM_MODE = "therm_mode" +EVENT_TYPE_SCHEDULE = "schedule" # Camera events EVENT_TYPE_LIGHT_MODE = "light_mode" EVENT_TYPE_CAMERA_OUTDOOR = "outdoor" diff --git a/tests/components/netatmo/test_climate.py b/tests/components/netatmo/test_climate.py index 910fb32a7cf..ecec2871df8 100644 --- a/tests/components/netatmo/test_climate.py +++ b/tests/components/netatmo/test_climate.py @@ -1,5 +1,5 @@ """The tests for the Netatmo climate platform.""" -from unittest.mock import Mock +from unittest.mock import Mock, patch import pytest @@ -394,30 +394,50 @@ async def test_webhook_event_handling_no_data(hass, climate_entry): async def test_service_schedule_thermostats(hass, climate_entry, caplog): """Test service for selecting Netatmo schedule with thermostats.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] climate_entity_livingroom = "climate.netatmo_livingroom" # Test setting a valid schedule - await hass.services.async_call( - "netatmo", - SERVICE_SET_SCHEDULE, - {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_SCHEDULE_NAME: "Winter"}, - blocking=True, - ) - await hass.async_block_till_done() + with patch( + "pyatmo.thermostat.HomeData.switch_home_schedule" + ) as mock_switch_home_schedule: + await hass.services.async_call( + "netatmo", + SERVICE_SET_SCHEDULE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_SCHEDULE_NAME: "Winter"}, + blocking=True, + ) + await hass.async_block_till_done() + mock_switch_home_schedule.assert_called_once_with( + home_id="91763b24c43d3e344f424e8b", schedule_id="b1b54a2f45795764f59d50d8" + ) + + # Fake backend response for valve being turned on + response = { + "event_type": "schedule", + "schedule_id": "b1b54a2f45795764f59d50d8", + "previous_schedule_id": "59d32176d183948b05ab4dce", + "push_type": "home_event_changed", + } + await simulate_webhook(hass, webhook_id, response) assert ( - "Setting 91763b24c43d3e344f424e8b schedule to Winter (b1b54a2f45795764f59d50d8)" - in caplog.text + hass.states.get(climate_entity_livingroom).attributes["selected_schedule"] + == "Winter" ) # Test setting an invalid schedule - await hass.services.async_call( - "netatmo", - SERVICE_SET_SCHEDULE, - {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_SCHEDULE_NAME: "summer"}, - blocking=True, - ) - await hass.async_block_till_done() + with patch( + "pyatmo.thermostat.HomeData.switch_home_schedule" + ) as mock_switch_home_schedule: + await hass.services.async_call( + "netatmo", + SERVICE_SET_SCHEDULE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_SCHEDULE_NAME: "summer"}, + blocking=True, + ) + await hass.async_block_till_done() + mock_switch_home_schedule.assert_not_called() assert "summer is not a invalid schedule" in caplog.text @@ -668,3 +688,69 @@ async def test_get_all_home_ids(): } expected = ["123", "987"] assert climate.get_all_home_ids(home_data) == expected + + +async def test_webhook_home_id_mismatch(hass, climate_entry): + """Test service turn on for valves.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_entrada = "climate.netatmo_entrada" + + assert hass.states.get(climate_entity_entrada).state == "auto" + + # Fake backend response for valve being turned on + response = { + "room_id": "2833524037", + "home": { + "id": "123", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2833524037", + "name": "Entrada", + "type": "lobby", + "therm_setpoint_mode": "home", + } + ], + "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], + }, + "mode": "home", + "event_type": "cancel_set_point", + "push_type": "display_change", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_entrada).state == "auto" + + +async def test_webhook_set_point(hass, climate_entry): + """Test service turn on for valves.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_entrada = "climate.netatmo_entrada" + + # Fake backend response for valve being turned on + response = { + "room_id": "2746182631", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2833524037", + "name": "Entrada", + "type": "lobby", + "therm_setpoint_mode": "home", + "therm_setpoint_temperature": 30, + } + ], + "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], + }, + "mode": "home", + "event_type": "set_point", + "temperature": 21, + "push_type": "display_change", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_entrada).state == "heat" From d2d78d6205a1d7bba434ab975a842ed09c906f9c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 26 Mar 2021 15:19:44 +0100 Subject: [PATCH 625/831] Extend typing on scaffold templates (#48232) --- .../config_flow/integration/__init__.py | 9 +++++--- .../config_flow/integration/config_flow.py | 21 ++++++++++++------- .../config_flow/tests/test_config_flow.py | 7 ++++--- .../integration/__init__.py | 9 +++++--- .../integration/config_flow.py | 3 ++- .../integration/__init__.py | 9 +++++--- .../config_flow_oauth2/integration/api.py | 8 +++---- .../tests/test_config_flow.py | 8 +++++-- .../device_action/tests/test_device_action.py | 15 ++++++++----- .../tests/test_device_condition.py | 19 +++++++++++------ .../integration/integration/__init__.py | 6 +++++- .../integration/reproduce_state.py | 7 +++---- .../tests/test_reproduce_state.py | 8 +++++-- 13 files changed, 85 insertions(+), 44 deletions(-) diff --git a/script/scaffold/templates/config_flow/integration/__init__.py b/script/scaffold/templates/config_flow/integration/__init__.py index 334ac8dbbc9..24a0c1d7350 100644 --- a/script/scaffold/templates/config_flow/integration/__init__.py +++ b/script/scaffold/templates/config_flow/integration/__init__.py @@ -1,5 +1,8 @@ """The NEW_NAME integration.""" +from __future__ import annotations + import asyncio +from typing import Any from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -11,12 +14,12 @@ from .const import DOMAIN PLATFORMS = ["light"] -async def async_setup(hass: HomeAssistant, config: dict): +async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool: """Set up the NEW_NAME component.""" return True -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up NEW_NAME from a config entry.""" # TODO Store an API object for your platforms to access # hass.data[DOMAIN][entry.entry_id] = MyApi(...) @@ -29,7 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): return True -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = all( await asyncio.gather( diff --git a/script/scaffold/templates/config_flow/integration/config_flow.py b/script/scaffold/templates/config_flow/integration/config_flow.py index c9700463c86..aa05f633df4 100644 --- a/script/scaffold/templates/config_flow/integration/config_flow.py +++ b/script/scaffold/templates/config_flow/integration/config_flow.py @@ -1,9 +1,14 @@ """Config flow for NEW_NAME integration.""" +from __future__ import annotations + import logging +from typing import Any import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant import config_entries +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from .const import DOMAIN # pylint:disable=unused-import @@ -19,16 +24,16 @@ class PlaceholderHub: TODO Remove this placeholder class and replace with things from your PyPI package. """ - def __init__(self, host): + def __init__(self, host: str) -> None: """Initialize.""" self.host = host - async def authenticate(self, username, password) -> bool: + async def authenticate(self, username: str, password: str) -> bool: """Test if we can authenticate with the host.""" return True -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]: """Validate the user input allows us to connect. Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. @@ -62,7 +67,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # TODO pick one of the available connection classes in homeassistant/config_entries.py CONNECTION_CLASS = config_entries.CONN_CLASS_UNKNOWN - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -88,9 +95,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/script/scaffold/templates/config_flow/tests/test_config_flow.py b/script/scaffold/templates/config_flow/tests/test_config_flow.py index 04eab6e683c..8205fdbeda4 100644 --- a/script/scaffold/templates/config_flow/tests/test_config_flow.py +++ b/script/scaffold/templates/config_flow/tests/test_config_flow.py @@ -4,9 +4,10 @@ from unittest.mock import patch from homeassistant import config_entries, setup from homeassistant.components.NEW_DOMAIN.config_flow import CannotConnect, InvalidAuth from homeassistant.components.NEW_DOMAIN.const import DOMAIN +from homeassistant.core import HomeAssistant -async def test_form(hass): +async def test_form(hass: HomeAssistant) -> None: """Test we get the form.""" await setup.async_setup_component(hass, "persistent_notification", {}) result = await hass.config_entries.flow.async_init( @@ -45,7 +46,7 @@ async def test_form(hass): assert len(mock_setup_entry.mock_calls) == 1 -async def test_form_invalid_auth(hass): +async def test_form_invalid_auth(hass: HomeAssistant) -> None: """Test we handle invalid auth.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -68,7 +69,7 @@ async def test_form_invalid_auth(hass): assert result2["errors"] == {"base": "invalid_auth"} -async def test_form_cannot_connect(hass): +async def test_form_cannot_connect(hass: HomeAssistant) -> None: """Test we handle cannot connect error.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} diff --git a/script/scaffold/templates/config_flow_discovery/integration/__init__.py b/script/scaffold/templates/config_flow_discovery/integration/__init__.py index 334ac8dbbc9..24a0c1d7350 100644 --- a/script/scaffold/templates/config_flow_discovery/integration/__init__.py +++ b/script/scaffold/templates/config_flow_discovery/integration/__init__.py @@ -1,5 +1,8 @@ """The NEW_NAME integration.""" +from __future__ import annotations + import asyncio +from typing import Any from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -11,12 +14,12 @@ from .const import DOMAIN PLATFORMS = ["light"] -async def async_setup(hass: HomeAssistant, config: dict): +async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool: """Set up the NEW_NAME component.""" return True -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up NEW_NAME from a config entry.""" # TODO Store an API object for your platforms to access # hass.data[DOMAIN][entry.entry_id] = MyApi(...) @@ -29,7 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): return True -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = all( await asyncio.gather( diff --git a/script/scaffold/templates/config_flow_discovery/integration/config_flow.py b/script/scaffold/templates/config_flow_discovery/integration/config_flow.py index db5f719ce3d..4d2ee2c9f6b 100644 --- a/script/scaffold/templates/config_flow_discovery/integration/config_flow.py +++ b/script/scaffold/templates/config_flow_discovery/integration/config_flow.py @@ -2,12 +2,13 @@ import my_pypi_dependency from homeassistant import config_entries +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_flow from .const import DOMAIN -async def _async_has_devices(hass) -> bool: +async def _async_has_devices(hass: HomeAssistant) -> bool: """Return if there are devices that can be discovered.""" # TODO Check if there are any devices that can be discovered in the network. devices = await hass.async_add_executor_job(my_pypi_dependency.discover) diff --git a/script/scaffold/templates/config_flow_oauth2/integration/__init__.py b/script/scaffold/templates/config_flow_oauth2/integration/__init__.py index 4e290921047..304df8f9c79 100644 --- a/script/scaffold/templates/config_flow_oauth2/integration/__init__.py +++ b/script/scaffold/templates/config_flow_oauth2/integration/__init__.py @@ -1,5 +1,8 @@ """The NEW_NAME integration.""" +from __future__ import annotations + import asyncio +from typing import Any import voluptuous as vol @@ -32,7 +35,7 @@ CONFIG_SCHEMA = vol.Schema( PLATFORMS = ["light"] -async def async_setup(hass: HomeAssistant, config: dict): +async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool: """Set up the NEW_NAME component.""" hass.data[DOMAIN] = {} @@ -54,7 +57,7 @@ async def async_setup(hass: HomeAssistant, config: dict): return True -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up NEW_NAME from a config entry.""" implementation = ( await config_entry_oauth2_flow.async_get_config_entry_implementation( @@ -80,7 +83,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): return True -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = all( await asyncio.gather( diff --git a/script/scaffold/templates/config_flow_oauth2/integration/api.py b/script/scaffold/templates/config_flow_oauth2/integration/api.py index 50b54399579..4f15099c8e1 100644 --- a/script/scaffold/templates/config_flow_oauth2/integration/api.py +++ b/script/scaffold/templates/config_flow_oauth2/integration/api.py @@ -4,7 +4,7 @@ from asyncio import run_coroutine_threadsafe from aiohttp import ClientSession import my_pypi_package -from homeassistant import core +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_oauth2_flow # TODO the following two API examples are based on our suggested best practices @@ -17,9 +17,9 @@ class ConfigEntryAuth(my_pypi_package.AbstractAuth): def __init__( self, - hass: core.HomeAssistant, + hass: HomeAssistant, oauth_session: config_entry_oauth2_flow.OAuth2Session, - ): + ) -> None: """Initialize NEW_NAME Auth.""" self.hass = hass self.session = oauth_session @@ -41,7 +41,7 @@ class AsyncConfigEntryAuth(my_pypi_package.AbstractAuth): self, websession: ClientSession, oauth_session: config_entry_oauth2_flow.OAuth2Session, - ): + ) -> None: """Initialize NEW_NAME auth.""" super().__init__(websession) self._oauth_session = oauth_session diff --git a/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py b/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py index dd0fc3446b3..ff9c5bfb848 100644 --- a/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py +++ b/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py @@ -7,6 +7,7 @@ from homeassistant.components.NEW_DOMAIN.const import ( OAUTH2_AUTHORIZE, OAUTH2_TOKEN, ) +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_oauth2_flow CLIENT_ID = "1234" @@ -14,8 +15,11 @@ CLIENT_SECRET = "5678" async def test_full_flow( - hass, aiohttp_client, aioclient_mock, current_request_with_host -): + hass: HomeAssistant, + aiohttp_client, + aioclient_mock, + current_request_with_host, +) -> None: """Check full flow.""" assert await setup.async_setup_component( hass, diff --git a/script/scaffold/templates/device_action/tests/test_device_action.py b/script/scaffold/templates/device_action/tests/test_device_action.py index 91a4693ebeb..424fa0a9afd 100644 --- a/script/scaffold/templates/device_action/tests/test_device_action.py +++ b/script/scaffold/templates/device_action/tests/test_device_action.py @@ -3,7 +3,8 @@ import pytest from homeassistant.components import automation from homeassistant.components.NEW_DOMAIN import DOMAIN -from homeassistant.helpers import device_registry +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry, entity_registry from homeassistant.setup import async_setup_component from tests.common import ( @@ -17,18 +18,22 @@ from tests.common import ( @pytest.fixture -def device_reg(hass): +def device_reg(hass: HomeAssistant) -> device_registry.DeviceRegistry: """Return an empty, loaded, registry.""" return mock_device_registry(hass) @pytest.fixture -def entity_reg(hass): +def entity_reg(hass: HomeAssistant) -> entity_registry.EntityRegistry: """Return an empty, loaded, registry.""" return mock_registry(hass) -async def test_get_actions(hass, device_reg, entity_reg): +async def test_get_actions( + hass: HomeAssistant, + device_reg: device_registry.DeviceRegistry, + entity_reg: entity_registry.EntityRegistry, +) -> None: """Test we get the expected actions from a NEW_DOMAIN.""" config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -55,7 +60,7 @@ async def test_get_actions(hass, device_reg, entity_reg): assert_lists_same(actions, expected_actions) -async def test_action(hass): +async def test_action(hass: HomeAssistant) -> None: """Test for turn_on and turn_off actions.""" assert await async_setup_component( hass, diff --git a/script/scaffold/templates/device_condition/tests/test_device_condition.py b/script/scaffold/templates/device_condition/tests/test_device_condition.py index 07e0afd05eb..9a283fa1f5b 100644 --- a/script/scaffold/templates/device_condition/tests/test_device_condition.py +++ b/script/scaffold/templates/device_condition/tests/test_device_condition.py @@ -1,10 +1,13 @@ """The tests for NEW_NAME device conditions.""" +from __future__ import annotations + import pytest from homeassistant.components import automation from homeassistant.components.NEW_DOMAIN import DOMAIN from homeassistant.const import STATE_OFF, STATE_ON -from homeassistant.helpers import device_registry +from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.helpers import device_registry, entity_registry from homeassistant.setup import async_setup_component from tests.common import ( @@ -18,24 +21,28 @@ from tests.common import ( @pytest.fixture -def device_reg(hass): +def device_reg(hass: HomeAssistant) -> device_registry.DeviceRegistry: """Return an empty, loaded, registry.""" return mock_device_registry(hass) @pytest.fixture -def entity_reg(hass): +def entity_reg(hass: HomeAssistant) -> entity_registry.EntityRegistry: """Return an empty, loaded, registry.""" return mock_registry(hass) @pytest.fixture -def calls(hass): +def calls(hass: HomeAssistant) -> list[ServiceCall]: """Track calls to a mock service.""" return async_mock_service(hass, "test", "automation") -async def test_get_conditions(hass, device_reg, entity_reg): +async def test_get_conditions( + hass: HomeAssistant, + device_reg: device_registry.DeviceRegistry, + entity_reg: entity_registry.EntityRegistry, +) -> None: """Test we get the expected conditions from a NEW_DOMAIN.""" config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -64,7 +71,7 @@ async def test_get_conditions(hass, device_reg, entity_reg): assert_lists_same(conditions, expected_conditions) -async def test_if_state(hass, calls): +async def test_if_state(hass: HomeAssistant, calls: list[ServiceCall]) -> None: """Test for turn_on and turn_off conditions.""" hass.states.async_set("NEW_DOMAIN.entity", STATE_ON) diff --git a/script/scaffold/templates/integration/integration/__init__.py b/script/scaffold/templates/integration/integration/__init__.py index 0ab65cb7da8..c1f34d5f5b1 100644 --- a/script/scaffold/templates/integration/integration/__init__.py +++ b/script/scaffold/templates/integration/integration/__init__.py @@ -1,4 +1,8 @@ """The NEW_NAME integration.""" +from __future__ import annotations + +from typing import Any + import voluptuous as vol from homeassistant.core import HomeAssistant @@ -8,6 +12,6 @@ from .const import DOMAIN CONFIG_SCHEMA = vol.Schema({vol.Optional(DOMAIN): {}}, extra=vol.ALLOW_EXTRA) -async def async_setup(hass: HomeAssistant, config: dict): +async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool: """Set up the NEW_NAME integration.""" return True diff --git a/script/scaffold/templates/reproduce_state/integration/reproduce_state.py b/script/scaffold/templates/reproduce_state/integration/reproduce_state.py index 2031109a6bd..19e046f4c92 100644 --- a/script/scaffold/templates/reproduce_state/integration/reproduce_state.py +++ b/script/scaffold/templates/reproduce_state/integration/reproduce_state.py @@ -12,8 +12,7 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, ) -from homeassistant.core import Context, State -from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.core import Context, HomeAssistant, State from . import DOMAIN @@ -24,7 +23,7 @@ VALID_STATES = {STATE_ON, STATE_OFF} async def _async_reproduce_state( - hass: HomeAssistantType, + hass: HomeAssistant, state: State, *, context: Context | None = None, @@ -69,7 +68,7 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, + hass: HomeAssistant, states: Iterable[State], *, context: Context | None = None, diff --git a/script/scaffold/templates/reproduce_state/tests/test_reproduce_state.py b/script/scaffold/templates/reproduce_state/tests/test_reproduce_state.py index ff15625ad7c..83d95570b45 100644 --- a/script/scaffold/templates/reproduce_state/tests/test_reproduce_state.py +++ b/script/scaffold/templates/reproduce_state/tests/test_reproduce_state.py @@ -1,10 +1,14 @@ """Test reproduce state for NEW_NAME.""" -from homeassistant.core import State +import pytest + +from homeassistant.core import HomeAssistant, State from tests.common import async_mock_service -async def test_reproducing_states(hass, caplog): +async def test_reproducing_states( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: """Test reproducing NEW_NAME states.""" hass.states.async_set("NEW_DOMAIN.entity_off", "off", {}) hass.states.async_set("NEW_DOMAIN.entity_on", "on", {"color": "red"}) From 8fa935234aa04dfc0ac14f48e380292eec22ea2c Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 26 Mar 2021 16:10:55 +0100 Subject: [PATCH 626/831] Type check KNX integration __init__ and knx_entity (#48044) --- homeassistant/components/knx/__init__.py | 72 ++++++++++++---------- homeassistant/components/knx/climate.py | 4 +- homeassistant/components/knx/knx_entity.py | 18 +++--- setup.cfg | 2 +- tests/components/knx/__init__.py | 2 +- 5 files changed, 53 insertions(+), 45 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 8f363ac70d1..87e7c0b1b14 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -24,16 +24,17 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, SERVICE_RELOAD, ) +from homeassistant.core import ServiceCall from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import async_get_platforms from homeassistant.helpers.reload import async_integration_yaml_config from homeassistant.helpers.service import async_register_admin_service -from homeassistant.helpers.typing import ServiceCallType +from homeassistant.helpers.typing import ConfigType, EventType, HomeAssistantType from .const import DOMAIN, KNX_ADDRESS, SupportedPlatforms -from .expose import create_knx_exposure +from .expose import KNXExposeSensor, KNXExposeTime, create_knx_exposure from .factory import create_knx_device from .schema import ( BinarySensorSchema, @@ -211,8 +212,8 @@ SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA = vol.Any( ) -async def async_setup(hass, config): - """Set up the KNX component.""" +async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: + """Set up the KNX integration.""" try: knx_module = KNXModule(hass, config) hass.data[DOMAIN] = knx_module @@ -270,7 +271,7 @@ async def async_setup(hass, config): schema=SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA, ) - async def reload_service_handler(service_call: ServiceCallType) -> None: + async def reload_service_handler(service_call: ServiceCall) -> None: """Remove all KNX components and load new ones from config.""" # First check for config file. If for some reason it is no longer there @@ -298,19 +299,19 @@ async def async_setup(hass, config): class KNXModule: """Representation of KNX Object.""" - def __init__(self, hass, config): - """Initialize of KNX module.""" + def __init__(self, hass: HomeAssistantType, config: ConfigType) -> None: + """Initialize KNX module.""" self.hass = hass self.config = config self.connected = False - self.exposures = [] - self.service_exposures = {} + self.exposures: list[KNXExposeSensor | KNXExposeTime] = [] + self.service_exposures: dict[str, KNXExposeSensor | KNXExposeTime] = {} self.init_xknx() self._knx_event_callback: TelegramQueue.Callback = self.register_callback() - def init_xknx(self): - """Initialize of KNX object.""" + def init_xknx(self) -> None: + """Initialize XKNX object.""" self.xknx = XKNX( config=self.config_file(), own_address=self.config[DOMAIN][CONF_KNX_INDIVIDUAL_ADDRESS], @@ -321,26 +322,26 @@ class KNXModule: state_updater=self.config[DOMAIN][CONF_KNX_STATE_UPDATER], ) - async def start(self): - """Start KNX object. Connect to tunneling or Routing device.""" + async def start(self) -> None: + """Start XKNX object. Connect to tunneling or Routing device.""" await self.xknx.start() self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.stop) self.connected = True - async def stop(self, event): - """Stop KNX object. Disconnect from tunneling or Routing device.""" + async def stop(self, event: EventType) -> None: + """Stop XKNX object. Disconnect from tunneling or Routing device.""" await self.xknx.stop() - def config_file(self): + def config_file(self) -> str | None: """Resolve and return the full path of xknx.yaml if configured.""" config_file = self.config[DOMAIN].get(CONF_KNX_CONFIG) if not config_file: return None if not config_file.startswith("/"): return self.hass.config.path(config_file) - return config_file + return config_file # type: ignore - def connection_config(self): + def connection_config(self) -> ConnectionConfig: """Return the connection_config.""" if CONF_KNX_TUNNELING in self.config[DOMAIN]: return self.connection_config_tunneling() @@ -349,7 +350,7 @@ class KNXModule: # config from xknx.yaml always has priority later on return ConnectionConfig(auto_reconnect=True) - def connection_config_routing(self): + def connection_config_routing(self) -> ConnectionConfig: """Return the connection_config if routing is configured.""" local_ip = None # all configuration values are optional @@ -361,7 +362,7 @@ class KNXModule: connection_type=ConnectionType.ROUTING, local_ip=local_ip ) - def connection_config_tunneling(self): + def connection_config_tunneling(self) -> ConnectionConfig: """Return the connection_config if tunneling is configured.""" gateway_ip = self.config[DOMAIN][CONF_KNX_TUNNELING][CONF_HOST] gateway_port = self.config[DOMAIN][CONF_KNX_TUNNELING][CONF_PORT] @@ -380,12 +381,14 @@ class KNXModule: auto_reconnect=True, ) - async def telegram_received_cb(self, telegram): + async def telegram_received_cb(self, telegram: Telegram) -> None: """Call invoked after a KNX telegram was received.""" data = None - # Not all telegrams have serializable data. - if isinstance(telegram.payload, (GroupValueWrite, GroupValueResponse)): + if ( + isinstance(telegram.payload, (GroupValueWrite, GroupValueResponse)) + and telegram.payload.value is not None + ): data = telegram.payload.value.value self.hass.bus.async_fire( @@ -404,16 +407,16 @@ class KNXModule: address_filters = list( map(AddressFilter, self.config[DOMAIN][CONF_KNX_EVENT_FILTER]) ) - return self.xknx.telegram_queue.register_telegram_received_cb( + return self.xknx.telegram_queue.register_telegram_received_cb( # type: ignore[no-any-return] self.telegram_received_cb, address_filters=address_filters, group_addresses=[], match_for_outgoing=True, ) - async def service_event_register_modify(self, call): + async def service_event_register_modify(self, call: ServiceCall) -> None: """Service for adding or removing a GroupAddress to the knx_event filter.""" - attr_address = call.data.get(KNX_ADDRESS) + attr_address = call.data[KNX_ADDRESS] group_addresses = map(GroupAddress, attr_address) if call.data.get(SERVICE_KNX_ATTR_REMOVE): @@ -434,9 +437,9 @@ class KNXModule: str(group_address), ) - async def service_exposure_register_modify(self, call): + async def service_exposure_register_modify(self, call: ServiceCall) -> None: """Service for adding or removing an exposure to KNX bus.""" - group_address = call.data.get(KNX_ADDRESS) + group_address = call.data[KNX_ADDRESS] if call.data.get(SERVICE_KNX_ATTR_REMOVE): try: @@ -451,13 +454,14 @@ class KNXModule: if group_address in self.service_exposures: replaced_exposure = self.service_exposures.pop(group_address) + assert replaced_exposure.device is not None _LOGGER.warning( "Service exposure_register replacing already registered exposure for '%s' - %s", group_address, replaced_exposure.device.name, ) replaced_exposure.shutdown() - exposure = create_knx_exposure(self.hass, self.xknx, call.data) + exposure = create_knx_exposure(self.hass, self.xknx, call.data) # type: ignore[arg-type] self.service_exposures[group_address] = exposure _LOGGER.debug( "Service exposure_register registered exposure for '%s' - %s", @@ -465,10 +469,10 @@ class KNXModule: exposure.device.name, ) - async def service_send_to_knx_bus(self, call): + async def service_send_to_knx_bus(self, call: ServiceCall) -> None: """Service for sending an arbitrary KNX message to the KNX bus.""" - attr_address = call.data.get(KNX_ADDRESS) - attr_payload = call.data.get(SERVICE_KNX_ATTR_PAYLOAD) + attr_address = call.data[KNX_ADDRESS] + attr_payload = call.data[SERVICE_KNX_ATTR_PAYLOAD] attr_type = call.data.get(SERVICE_KNX_ATTR_TYPE) payload: DPTBinary | DPTArray @@ -489,9 +493,9 @@ class KNXModule: ) await self.xknx.telegrams.put(telegram) - async def service_read_to_knx_bus(self, call): + async def service_read_to_knx_bus(self, call: ServiceCall) -> None: """Service for sending a GroupValueRead telegram to the KNX bus.""" - for address in call.data.get(KNX_ADDRESS): + for address in call.data[KNX_ADDRESS]: telegram = Telegram( destination_address=GroupAddress(address), payload=GroupValueRead(), diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py index 1b9fea57541..700e080b037 100644 --- a/homeassistant/components/knx/climate.py +++ b/homeassistant/components/knx/climate.py @@ -72,7 +72,7 @@ class KNXClimate(KnxEntity, ClimateEntity): @property def current_temperature(self) -> float | None: """Return the current temperature.""" - return self._device.temperature.value # type: ignore[no-any-return] + return self._device.temperature.value @property def target_temperature_step(self) -> float: @@ -82,7 +82,7 @@ class KNXClimate(KnxEntity, ClimateEntity): @property def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" - return self._device.target_temperature.value # type: ignore[no-any-return] + return self._device.target_temperature.value @property def min_temp(self) -> float: diff --git a/homeassistant/components/knx/knx_entity.py b/homeassistant/components/knx/knx_entity.py index f4597ad230e..31147625d16 100644 --- a/homeassistant/components/knx/knx_entity.py +++ b/homeassistant/components/knx/knx_entity.py @@ -1,38 +1,42 @@ """Base class for KNX devices.""" +from typing import cast + from xknx.devices import Climate as XknxClimate, Device as XknxDevice from homeassistant.helpers.entity import Entity +from . import KNXModule from .const import DOMAIN class KnxEntity(Entity): """Representation of a KNX entity.""" - def __init__(self, device: XknxDevice): + def __init__(self, device: XknxDevice) -> None: """Set up device.""" self._device = device @property - def name(self): + def name(self) -> str: """Return the name of the KNX device.""" return self._device.name @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" - return self.hass.data[DOMAIN].connected + knx_module = cast(KNXModule, self.hass.data[DOMAIN]) + return knx_module.connected @property - def should_poll(self): + def should_poll(self) -> bool: """No polling needed within KNX.""" return False - async def async_update(self): + async def async_update(self) -> None: """Request a state update from KNX bus.""" await self._device.sync() - async def after_update_callback(self, device: XknxDevice): + async def after_update_callback(self, device: XknxDevice) -> None: """Call after device was updated.""" self.async_write_ha_state() diff --git a/setup.cfg b/setup.cfg index d5f8b9da16b..408bab3a03c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -43,7 +43,7 @@ warn_redundant_casts = true warn_unused_configs = true -[mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.bond.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.recorder.purge,homeassistant.components.recorder.repack,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*,tests.components.hyperion.*] +[mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.bond.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.knx.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.recorder.purge,homeassistant.components.recorder.repack,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*,tests.components.hyperion.*] strict = true ignore_errors = false warn_unreachable = true diff --git a/tests/components/knx/__init__.py b/tests/components/knx/__init__.py index f0fc1f36e08..eaa84714dc5 100644 --- a/tests/components/knx/__init__.py +++ b/tests/components/knx/__init__.py @@ -1 +1 @@ -"""The tests for KNX integration.""" +"""Tests for the KNX integration.""" From f4cc4a0896358f4c8df5a193d900716fabb7ad32 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 26 Mar 2021 17:08:05 +0100 Subject: [PATCH 627/831] Merge of nested IF-IF cases - X-Z (#48373) --- .../components/xiaomi_miio/__init__.py | 15 +++++---- homeassistant/components/zha/climate.py | 23 ++++++++----- homeassistant/components/zha/core/gateway.py | 5 ++- homeassistant/components/zwave/climate.py | 15 +++++---- homeassistant/components/zwave/light.py | 10 +++--- homeassistant/components/zwave/lock.py | 22 ++++++------- .../components/zwave_js/binary_sensor.py | 12 +++---- .../components/zwave_js/discovery.py | 33 ++++++++++++------- 8 files changed, 77 insertions(+), 58 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index ccecc835b43..f97d4623d69 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -45,14 +45,15 @@ async def async_setup_entry( ): """Set up the Xiaomi Miio components from a config entry.""" hass.data.setdefault(DOMAIN, {}) - if entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY: - if not await async_setup_gateway_entry(hass, entry): - return False - if entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: - if not await async_setup_device_entry(hass, entry): - return False + if entry.data[ + CONF_FLOW_TYPE + ] == CONF_GATEWAY and not await async_setup_gateway_entry(hass, entry): + return False - return True + return bool( + entry.data[CONF_FLOW_TYPE] != CONF_DEVICE + or await async_setup_device_entry(hass, entry) + ) async def async_setup_gateway_entry( diff --git a/homeassistant/components/zha/climate.py b/homeassistant/components/zha/climate.py index 8292335ce23..475b0c5d0b8 100644 --- a/homeassistant/components/zha/climate.py +++ b/homeassistant/components/zha/climate.py @@ -448,15 +448,22 @@ class Thermostat(ZhaEntity, ClimateEntity): self.debug("preset mode '%s' is not supported", preset_mode) return - if self.preset_mode not in (preset_mode, PRESET_NONE): - if not await self.async_preset_handler(self.preset_mode, enable=False): - self.debug("Couldn't turn off '%s' preset", self.preset_mode) - return + if ( + self.preset_mode + not in ( + preset_mode, + PRESET_NONE, + ) + and not await self.async_preset_handler(self.preset_mode, enable=False) + ): + self.debug("Couldn't turn off '%s' preset", self.preset_mode) + return - if preset_mode != PRESET_NONE: - if not await self.async_preset_handler(preset_mode, enable=True): - self.debug("Couldn't turn on '%s' preset", preset_mode) - return + if preset_mode != PRESET_NONE and not await self.async_preset_handler( + preset_mode, enable=True + ): + self.debug("Couldn't turn on '%s' preset", preset_mode) + return self._preset = preset_mode self.async_write_ha_state() diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index d8baf89efcf..e944196a409 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -728,9 +728,8 @@ class LogRelayHandler(logging.Handler): def emit(self, record): """Relay log message via dispatcher.""" stack = [] - if record.levelno >= logging.WARN: - if not record.exc_info: - stack = [f for f, _, _, _ in traceback.extract_stack()] + if record.levelno >= logging.WARN and not record.exc_info: + stack = [f for f, _, _, _ in traceback.extract_stack()] entry = LogEntry(record, stack, _figure_out_source(record, stack, self.hass)) async_dispatcher_send( diff --git a/homeassistant/components/zwave/climate.py b/homeassistant/components/zwave/climate.py index ac8c47af9a3..75780eb314a 100644 --- a/homeassistant/components/zwave/climate.py +++ b/homeassistant/components/zwave/climate.py @@ -182,10 +182,12 @@ class ZWaveClimateBase(ZWaveDeviceEntity, ClimateEntity): int(self.node.manufacturer_id, 16), int(self.node.product_id, 16), ) - if specific_sensor_key in DEVICE_MAPPINGS: - if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZXT_120: - _LOGGER.debug("Remotec ZXT-120 Zwave Thermostat workaround") - self._zxt_120 = 1 + if ( + specific_sensor_key in DEVICE_MAPPINGS + and DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZXT_120 + ): + _LOGGER.debug("Remotec ZXT-120 Zwave Thermostat workaround") + self._zxt_120 = 1 self.update_properties() def _mode(self) -> None: @@ -567,9 +569,8 @@ class ZWaveClimateBase(ZWaveDeviceEntity, ClimateEntity): def set_swing_mode(self, swing_mode): """Set new target swing mode.""" _LOGGER.debug("Set swing_mode to %s", swing_mode) - if self._zxt_120 == 1: - if self.values.zxt_120_swing_mode: - self.values.zxt_120_swing_mode.data = swing_mode + if self._zxt_120 == 1 and self.values.zxt_120_swing_mode: + self.values.zxt_120_swing_mode.data = swing_mode @property def extra_state_attributes(self): diff --git a/homeassistant/components/zwave/light.py b/homeassistant/components/zwave/light.py index 52014e37eea..140f601b1d9 100644 --- a/homeassistant/components/zwave/light.py +++ b/homeassistant/components/zwave/light.py @@ -138,10 +138,12 @@ class ZwaveDimmer(ZWaveDeviceEntity, LightEntity): int(self.node.manufacturer_id, 16), int(self.node.product_id, 16), ) - if specific_sensor_key in DEVICE_MAPPINGS: - if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZW098: - _LOGGER.debug("AEOTEC ZW098 workaround enabled") - self._zw098 = 1 + if ( + specific_sensor_key in DEVICE_MAPPINGS + and DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZW098 + ): + _LOGGER.debug("AEOTEC ZW098 workaround enabled") + self._zw098 = 1 # Used for value change event handling self._refreshing = False diff --git a/homeassistant/components/zwave/lock.py b/homeassistant/components/zwave/lock.py index e6228a29334..bc49f9c0bd2 100644 --- a/homeassistant/components/zwave/lock.py +++ b/homeassistant/components/zwave/lock.py @@ -291,17 +291,17 @@ class ZwaveLock(ZWaveDeviceEntity, LockEntity): if self._state_workaround: self._state = LOCK_STATUS.get(str(notification_data)) _LOGGER.debug("workaround: lock state set to %s", self._state) - if self._v2btze: - if ( - self.values.v2btze_advanced - and self.values.v2btze_advanced.data == CONFIG_ADVANCED - ): - self._state = LOCK_STATUS.get(str(notification_data)) - _LOGGER.debug( - "Lock state set from Access Control value and is %s, get=%s", - str(notification_data), - self.state, - ) + if ( + self._v2btze + and self.values.v2btze_advanced + and self.values.v2btze_advanced.data == CONFIG_ADVANCED + ): + self._state = LOCK_STATUS.get(str(notification_data)) + _LOGGER.debug( + "Lock state set from Access Control value and is %s, get=%s", + str(notification_data), + self.state, + ) if self._track_message_workaround: this_message = self.node.stats["lastReceivedMessage"][5] diff --git a/homeassistant/components/zwave_js/binary_sensor.py b/homeassistant/components/zwave_js/binary_sensor.py index 47c374405b1..b97975b0507 100644 --- a/homeassistant/components/zwave_js/binary_sensor.py +++ b/homeassistant/components/zwave_js/binary_sensor.py @@ -285,12 +285,12 @@ class ZWaveBooleanBinarySensor(ZWaveBaseEntity, BinarySensorEntity): @property def entity_registry_enabled_default(self) -> bool: """Return if the entity should be enabled when first added to the entity registry.""" - if self.info.primary_value.command_class == CommandClass.SENSOR_BINARY: - # Legacy binary sensors are phased out (replaced by notification sensors) - # Disable by default to not confuse users - if self.info.node.device_class.generic.key != 0x20: - return False - return True + # Legacy binary sensors are phased out (replaced by notification sensors) + # Disable by default to not confuse users + return bool( + self.info.primary_value.command_class != CommandClass.SENSOR_BINARY + or self.info.node.device_class.generic.key == 0x20 + ) class ZWaveNotificationBinarySensor(ZWaveBaseEntity, BinarySensorEntity): diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 5f1f04274ba..b59ae017da7 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -403,56 +403,64 @@ def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None and value.node.manufacturer_id not in schema.manufacturer_id ): continue + # check product_id if ( schema.product_id is not None and value.node.product_id not in schema.product_id ): continue + # check product_type if ( schema.product_type is not None and value.node.product_type not in schema.product_type ): continue + # check firmware_version if ( schema.firmware_version is not None and value.node.firmware_version not in schema.firmware_version ): continue + # check device_class_basic if not check_device_class( value.node.device_class.basic, schema.device_class_basic ): continue + # check device_class_generic if not check_device_class( value.node.device_class.generic, schema.device_class_generic ): continue + # check device_class_specific if not check_device_class( value.node.device_class.specific, schema.device_class_specific ): continue + # check primary value if not check_value(value, schema.primary_value): continue + # check additional required values - if schema.required_values is not None: - if not all( - any(check_value(val, val_scheme) for val in node.values.values()) - for val_scheme in schema.required_values - ): - continue + if schema.required_values is not None and not all( + any(check_value(val, val_scheme) for val in node.values.values()) + for val_scheme in schema.required_values + ): + continue + # check for values that may not be present - if schema.absent_values is not None: - if any( - any(check_value(val, val_scheme) for val in node.values.values()) - for val_scheme in schema.absent_values - ): - continue + if schema.absent_values is not None and any( + any(check_value(val, val_scheme) for val in node.values.values()) + for val_scheme in schema.absent_values + ): + continue + # all checks passed, this value belongs to an entity yield ZwaveDiscoveryInfo( node=value.node, @@ -460,6 +468,7 @@ def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None platform=schema.platform, platform_hint=schema.hint, ) + if not schema.allow_multi: # break out of loop, this value may not be discovered by other schemas/platforms break From 00683d3caa2a92181bc1da59cf07d49ae3a0de91 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Mar 2021 09:48:02 -0700 Subject: [PATCH 628/831] Create FUNDING.yml (#48375) --- .github/FUNDING.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000000..ad3205c51c8 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +custom: https://www.nabucasa.com +github: balloob From bbbc3a5f500a4df85441e89239c779f82a60a97e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 26 Mar 2021 17:54:16 +0100 Subject: [PATCH 629/831] Merge of nested IF-IF case in elkm1 test (#48374) --- tests/components/elkm1/test_config_flow.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/components/elkm1/test_config_flow.py b/tests/components/elkm1/test_config_flow.py index d73bbe53e9a..a84ff0351d5 100644 --- a/tests/components/elkm1/test_config_flow.py +++ b/tests/components/elkm1/test_config_flow.py @@ -15,9 +15,8 @@ def mock_elk(invalid_auth=None, sync_complete=None): if type_ == "login": if invalid_auth is not None: callback(not invalid_auth) - elif type_ == "sync_complete": - if sync_complete: - callback() + elif type_ == "sync_complete" and sync_complete: + callback() mocked_elk = MagicMock() mocked_elk.add_handler.side_effect = handler_callbacks From c6a20d0fc1fe605af403dfb68a688551c2662f54 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 26 Mar 2021 18:14:01 +0100 Subject: [PATCH 630/831] Improve traces for nested script runs (#48366) --- homeassistant/components/trace/__init__.py | 9 ++- homeassistant/helpers/trace.py | 20 +++++ tests/components/trace/test_websocket_api.py | 78 ++++++++++++++++++-- 3 files changed, 100 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/trace/__init__.py b/homeassistant/components/trace/__init__.py index 43deefaa769..b0211505e7c 100644 --- a/homeassistant/components/trace/__init__.py +++ b/homeassistant/components/trace/__init__.py @@ -6,7 +6,12 @@ from itertools import count from typing import Any, Deque from homeassistant.core import Context -from homeassistant.helpers.trace import TraceElement, trace_id_set +from homeassistant.helpers.trace import ( + TraceElement, + trace_id_get, + trace_id_set, + trace_set_child_id, +) import homeassistant.util.dt as dt_util from . import websocket_api @@ -55,6 +60,8 @@ class ActionTrace: self._timestamp_start: dt.datetime = dt_util.utcnow() self.key: tuple[str, str] = key self._variables: dict[str, Any] | None = None + if trace_id_get(): + trace_set_child_id(self.key, self.run_id) trace_id_set((key, self.run_id)) def set_action_trace(self, trace: dict[str, Deque[TraceElement]]) -> None: diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py index ba39e19943b..5d5a0f5ff03 100644 --- a/homeassistant/helpers/trace.py +++ b/homeassistant/helpers/trace.py @@ -16,6 +16,8 @@ class TraceElement: def __init__(self, variables: TemplateVarsType, path: str): """Container for trace data.""" + self._child_key: tuple[str, str] | None = None + self._child_run_id: str | None = None self._error: Exception | None = None self.path: str = path self._result: dict | None = None @@ -36,6 +38,11 @@ class TraceElement: """Container for trace data.""" return str(self.as_dict()) + def set_child_id(self, child_key: tuple[str, str], child_run_id: str) -> None: + """Set trace id of a nested script run.""" + self._child_key = child_key + self._child_run_id = child_run_id + def set_error(self, ex: Exception) -> None: """Set error.""" self._error = ex @@ -47,6 +54,12 @@ class TraceElement: def as_dict(self) -> dict[str, Any]: """Return dictionary version of this TraceElement.""" result: dict[str, Any] = {"path": self.path, "timestamp": self._timestamp} + if self._child_key is not None: + result["child_id"] = { + "domain": self._child_key[0], + "item_id": self._child_key[1], + "run_id": str(self._child_run_id), + } if self._variables: result["changed_variables"] = self._variables if self._error is not None: @@ -161,6 +174,13 @@ def trace_clear() -> None: variables_cv.set(None) +def trace_set_child_id(child_key: tuple[str, str], child_run_id: str) -> None: + """Set child trace_id of TraceElement at the top of the stack.""" + node = cast(TraceElement, trace_stack_top(trace_stack_cv)) + if node: + node.set_child_id(child_key, child_run_id) + + def trace_set_result(**kwargs: Any) -> None: """Set the result of TraceElement at the top of the stack.""" node = cast(TraceElement, trace_stack_top(trace_stack_cv)) diff --git a/tests/components/trace/test_websocket_api.py b/tests/components/trace/test_websocket_api.py index 8dc09731b79..f198cf1b55a 100644 --- a/tests/components/trace/test_websocket_api.py +++ b/tests/components/trace/test_websocket_api.py @@ -26,12 +26,6 @@ def _find_traces(traces, trace_type, item_id): ] -# TODO: Remove -def _find_traces_for_automation(traces, item_id): - """Find traces for an automation.""" - return [trace for trace in traces if trace["item_id"] == item_id] - - @pytest.mark.parametrize( "domain, prefix", [("automation", "action"), ("script", "sequence")] ) @@ -515,6 +509,78 @@ async def test_list_traces(hass, hass_ws_client, domain, prefix): assert trace["trigger"] == "event 'test_event2'" +@pytest.mark.parametrize( + "domain, prefix", [("automation", "action"), ("script", "sequence")] +) +async def test_nested_traces(hass, hass_ws_client, domain, prefix): + """Test nested automation and script traces.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": {"service": "script.moon"}, + } + moon_config = { + "sequence": {"event": "another_event"}, + } + if domain == "script": + sun_config = {"sequence": sun_config["action"]} + + if domain == "automation": + assert await async_setup_component(hass, domain, {domain: [sun_config]}) + assert await async_setup_component( + hass, "script", {"script": {"moon": moon_config}} + ) + else: + assert await async_setup_component( + hass, domain, {domain: {"sun": sun_config, "moon": moon_config}} + ) + + client = await hass_ws_client() + + # Trigger "sun" automation / run "sun" script + if domain == "automation": + hass.bus.async_fire("test_event") + else: + await hass.services.async_call("script", "sun") + await hass.async_block_till_done() + + # List traces + await client.send_json({"id": next_id(), "type": "trace/list"}) + response = await client.receive_json() + assert response["success"] + assert len(response["result"]) == 2 + assert len(_find_traces(response["result"], domain, "sun")) == 1 + assert len(_find_traces(response["result"], "script", "moon")) == 1 + sun_run_id = _find_run_id(response["result"], domain, "sun") + moon_run_id = _find_run_id(response["result"], "script", "moon") + assert sun_run_id != moon_run_id + + # Get trace + await client.send_json( + { + "id": next_id(), + "type": "trace/get", + "domain": domain, + "item_id": "sun", + "run_id": sun_run_id, + } + ) + response = await client.receive_json() + assert response["success"] + trace = response["result"] + assert len(trace["action_trace"]) == 1 + assert len(trace["action_trace"][f"{prefix}/0"]) == 1 + child_id = trace["action_trace"][f"{prefix}/0"][0]["child_id"] + assert child_id == {"domain": "script", "item_id": "moon", "run_id": moon_run_id} + + @pytest.mark.parametrize( "domain, prefix", [("automation", "action"), ("script", "sequence")] ) From 374dcde48745cb2f036aac9b5fed8c7cfb9a63e6 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 26 Mar 2021 18:31:29 +0100 Subject: [PATCH 631/831] Return config entry details after creation (#48316) --- homeassistant/components/config/config_entries.py | 2 +- tests/components/config/test_config_entries.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index b45b6abe468..af90cdcba4b 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -155,7 +155,7 @@ class ConfigManagerFlowResourceView(FlowManagerResourceView): return super()._prepare_result_json(result) data = result.copy() - data["result"] = data["result"].entry_id + data["result"] = entry_json(result["result"]) data.pop("data") return data diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index d6cc474fa8b..7e4df556fa5 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -379,7 +379,17 @@ async def test_two_step_flow(hass, client): "type": "create_entry", "title": "user-title", "version": 1, - "result": entries[0].entry_id, + "result": { + "connection_class": "unknown", + "disabled_by": None, + "domain": "test", + "entry_id": entries[0].entry_id, + "source": "user", + "state": "loaded", + "supports_options": False, + "supports_unload": False, + "title": "user-title", + }, "description": None, "description_placeholders": None, } From 99874cd9932db9c9bdb102a8aed592e49ad8b99b Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sat, 27 Mar 2021 00:03:21 +0000 Subject: [PATCH 632/831] [ci skip] Translation update --- .../components/asuswrt/translations/ko.json | 2 +- .../components/bond/translations/nl.json | 2 +- .../components/cast/translations/ko.json | 4 +++- .../components/cast/translations/zh-Hant.json | 4 +++- .../components/control4/translations/ko.json | 2 +- .../forked_daapd/translations/ko.json | 2 +- .../components/hive/translations/ko.json | 2 +- .../home_plus_control/translations/ko.json | 21 +++++++++++++++++++ .../translations/zh-Hant.json | 21 +++++++++++++++++++ .../huisbaasje/translations/ca.json | 1 + .../huisbaasje/translations/en.json | 1 + .../huisbaasje/translations/et.json | 1 + .../huisbaasje/translations/ko.json | 1 + .../huisbaasje/translations/nl.json | 1 + .../huisbaasje/translations/ru.json | 1 + .../huisbaasje/translations/zh-Hant.json | 1 + .../lutron_caseta/translations/nl.json | 2 +- .../components/omnilogic/translations/ko.json | 2 +- .../components/onvif/translations/nl.json | 2 +- .../components/ozw/translations/nl.json | 2 +- .../components/plaato/translations/nl.json | 2 +- .../components/plugwise/translations/nl.json | 4 ++-- .../components/rachio/translations/ko.json | 2 +- .../screenlogic/translations/ko.json | 2 +- .../synology_dsm/translations/ko.json | 2 +- .../components/tesla/translations/ko.json | 2 +- .../components/upnp/translations/nl.json | 2 +- 27 files changed, 72 insertions(+), 19 deletions(-) create mode 100644 homeassistant/components/home_plus_control/translations/ko.json create mode 100644 homeassistant/components/home_plus_control/translations/zh-Hant.json diff --git a/homeassistant/components/asuswrt/translations/ko.json b/homeassistant/components/asuswrt/translations/ko.json index 108d00adefb..4e60a0d15b0 100644 --- a/homeassistant/components/asuswrt/translations/ko.json +++ b/homeassistant/components/asuswrt/translations/ko.json @@ -32,7 +32,7 @@ "step": { "init": { "data": { - "consider_home": "\uae30\uae30\uac00 \uc678\ucd9c \uc0c1\ud0dc\ub85c \uac04\uc8fc\ub418\uae30 \uc804\uc5d0 \uae30\ub2e4\ub9ac\ub294 \uc2dc\uac04(\ucd08)", + "consider_home": "\uae30\uae30\uac00 \uc678\ucd9c \uc0c1\ud0dc\ub85c \uac04\uc8fc\ub418\uae30 \uc804\uc5d0 \uae30\ub2e4\ub9ac\ub294 \uc2dc\uac04 (\ucd08)", "dnsmasq": "dnsmasq.lease \ud30c\uc77c\uc758 \ub77c\uc6b0\ud130 \uc704\uce58", "interface": "\ud1b5\uacc4\ub97c \uc6d0\ud558\ub294 \uc778\ud130\ud398\uc774\uc2a4 (\uc608: eth0, eth1 \ub4f1)", "require_ip": "\uae30\uae30\uc5d0\ub294 IP\uac00 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4 (\uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8 \ubaa8\ub4dc\uc778 \uacbd\uc6b0)", diff --git a/homeassistant/components/bond/translations/nl.json b/homeassistant/components/bond/translations/nl.json index a9086df3559..67812678082 100644 --- a/homeassistant/components/bond/translations/nl.json +++ b/homeassistant/components/bond/translations/nl.json @@ -9,7 +9,7 @@ "old_firmware": "Niet-ondersteunde oude firmware op het Bond-apparaat - voer een upgrade uit voordat u doorgaat", "unknown": "Onverwachte fout" }, - "flow_title": "Bond: {bond_id} ({host})", + "flow_title": "Bond: {name} ({host})", "step": { "confirm": { "data": { diff --git a/homeassistant/components/cast/translations/ko.json b/homeassistant/components/cast/translations/ko.json index 3ae6a688527..b0bfd3271c9 100644 --- a/homeassistant/components/cast/translations/ko.json +++ b/homeassistant/components/cast/translations/ko.json @@ -27,7 +27,9 @@ "step": { "options": { "data": { - "known_hosts": "mDNS \uac80\uc0c9\uc774 \uc791\ub3d9\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0 \uc54c\ub824\uc9c4 \ud638\uc2a4\ud2b8\uc758 \uc120\ud0dd\uc801 \ubaa9\ub85d\uc785\ub2c8\ub2e4." + "ignore_cec": "pychromecast.IGNORE_CEC\uc5d0 \uc804\ub2ec\ub420 \uc120\ud0dd\uc801 \ubaa9\ub85d\uc785\ub2c8\ub2e4.", + "known_hosts": "mDNS \uac80\uc0c9\uc774 \uc791\ub3d9\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0 \uc54c\ub824\uc9c4 \ud638\uc2a4\ud2b8\uc758 \uc120\ud0dd\uc801 \ubaa9\ub85d\uc785\ub2c8\ub2e4.", + "uuid": "UUID\uc758 \uc120\ud0dd\uc801 \ubaa9\ub85d\uc785\ub2c8\ub2e4. \ubaa9\ub85d\uc5d0 \uc5c6\ub294 \uce90\uc2a4\ud2b8\ub294 \ucd94\uac00\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "description": "Google Cast \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694." } diff --git a/homeassistant/components/cast/translations/zh-Hant.json b/homeassistant/components/cast/translations/zh-Hant.json index 4a32f61eeff..00810ade520 100644 --- a/homeassistant/components/cast/translations/zh-Hant.json +++ b/homeassistant/components/cast/translations/zh-Hant.json @@ -27,7 +27,9 @@ "step": { "options": { "data": { - "known_hosts": "\u5047\u5982 mDNS \u63a2\u7d22\u7121\u6cd5\u4f5c\u7528\uff0c\u5247\u70ba\u5df2\u77e5\u4e3b\u6a5f\u7684\u9078\u9805\u5217\u8868\u3002" + "ignore_cec": "\u9078\u9805\u5217\u8868\u5c07\u50b3\u905e\u81f3 pychromecast.IGNORE_CEC\u3002", + "known_hosts": "\u5047\u5982 mDNS \u63a2\u7d22\u7121\u6cd5\u4f5c\u7528\uff0c\u5247\u70ba\u5df2\u77e5\u4e3b\u6a5f\u7684\u9078\u9805\u5217\u8868\u3002", + "uuid": "UUID \u9078\u9805\u5217\u8868\u3002\u672a\u5217\u51fa\u7684 Cast \u88dd\u7f6e\u5c07\u4e0d\u6703\u9032\u884c\u65b0\u589e\u3002" }, "description": "\u8acb\u8f38\u5165 Google Cast \u8a2d\u5b9a\u3002" } diff --git a/homeassistant/components/control4/translations/ko.json b/homeassistant/components/control4/translations/ko.json index ca36da40c18..245fe666eab 100644 --- a/homeassistant/components/control4/translations/ko.json +++ b/homeassistant/components/control4/translations/ko.json @@ -23,7 +23,7 @@ "step": { "init": { "data": { - "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9(\ucd08)" + "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ucd08)" } } } diff --git a/homeassistant/components/forked_daapd/translations/ko.json b/homeassistant/components/forked_daapd/translations/ko.json index 3eaaf15be2c..60b585af958 100644 --- a/homeassistant/components/forked_daapd/translations/ko.json +++ b/homeassistant/components/forked_daapd/translations/ko.json @@ -31,7 +31,7 @@ "data": { "librespot_java_port": "librespot-java \ud30c\uc774\ud504 \ucee8\ud2b8\ub864\uc6a9 \ud3ec\ud2b8 (\uc0ac\uc6a9\ud558\ub294 \uacbd\uc6b0)", "max_playlists": "\uc18c\uc2a4\ub85c \uc0ac\uc6a9\ub41c \ucd5c\ub300 \uc7ac\uc0dd \ubaa9\ub85d \uc218", - "tts_pause_time": "TTS \uc804\ud6c4\uc5d0 \uc77c\uc2dc\uc911\uc9c0\ud560 \uc2dc\uac04(\ucd08)", + "tts_pause_time": "TTS \uc804\ud6c4\uc5d0 \uc77c\uc2dc\uc911\uc9c0\ud560 \uc2dc\uac04 (\ucd08)", "tts_volume": "TTS \ubcfc\ub968 (0~1 \uc758 \uc2e4\uc218\uac12)" }, "description": "forked-daapd \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0 \ub300\ud55c \ub2e4\uc591\ud55c \uc635\uc158\uc744 \uc124\uc815\ud574\uc8fc\uc138\uc694.", diff --git a/homeassistant/components/hive/translations/ko.json b/homeassistant/components/hive/translations/ko.json index 99c8c132229..1f06a2f1ac7 100644 --- a/homeassistant/components/hive/translations/ko.json +++ b/homeassistant/components/hive/translations/ko.json @@ -43,7 +43,7 @@ "step": { "user": { "data": { - "scan_interval": "Scan Interval (seconds)" + "scan_interval": "\uc2a4\uce94 \uac04\uaca9 (\ucd08)" }, "description": "\ub370\uc774\ud130\ub97c \ub354 \uc790\uc8fc \ud3f4\ub9c1\ud558\ub824\uba74 \uac80\uc0c9 \uac04\uaca9\uc744 \uc5c5\ub370\uc774\ud2b8\ud574\uc8fc\uc138\uc694.", "title": "Hive \uc635\uc158" diff --git a/homeassistant/components/home_plus_control/translations/ko.json b/homeassistant/components/home_plus_control/translations/ko.json new file mode 100644 index 00000000000..94c8bb91648 --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/ko.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", + "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + }, + "create_entry": { + "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "pick_implementation": { + "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" + } + } + }, + "title": "Legrand Home+ Control" +} \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/zh-Hant.json b/homeassistant/components/home_plus_control/translations/zh-Hant.json new file mode 100644 index 00000000000..0faa3110287 --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/zh-Hant.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", + "authorize_url_timeout": "\u7522\u751f\u8a8d\u8b49 URL \u6642\u903e\u6642\u3002", + "missing_configuration": "\u5143\u4ef6\u5c1a\u672a\u8a2d\u7f6e\uff0c\u8acb\u53c3\u95b1\u6587\u4ef6\u8aaa\u660e\u3002", + "no_url_available": "\u6c92\u6709\u53ef\u7528\u7684\u7db2\u5740\u3002\u95dc\u65bc\u6b64\u932f\u8aa4\u66f4\u8a73\u7d30\u8a0a\u606f\uff0c[\u9ede\u9078\u5354\u52a9\u7ae0\u7bc0]({docs_url})", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + }, + "create_entry": { + "default": "\u5df2\u6210\u529f\u8a8d\u8b49" + }, + "step": { + "pick_implementation": { + "title": "\u9078\u64c7\u9a57\u8b49\u6a21\u5f0f" + } + } + }, + "title": "Legrand Home+ Control" +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/ca.json b/homeassistant/components/huisbaasje/translations/ca.json index 99d99d4340f..3ee45b4c38b 100644 --- a/homeassistant/components/huisbaasje/translations/ca.json +++ b/homeassistant/components/huisbaasje/translations/ca.json @@ -4,6 +4,7 @@ "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", "connection_exception": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unauthenticated_exception": "Autenticaci\u00f3 inv\u00e0lida", diff --git a/homeassistant/components/huisbaasje/translations/en.json b/homeassistant/components/huisbaasje/translations/en.json index 16832be30e7..42bb4b59196 100644 --- a/homeassistant/components/huisbaasje/translations/en.json +++ b/homeassistant/components/huisbaasje/translations/en.json @@ -4,6 +4,7 @@ "already_configured": "Device is already configured" }, "error": { + "cannot_connect": "Failed to connect", "connection_exception": "Failed to connect", "invalid_auth": "Invalid authentication", "unauthenticated_exception": "Invalid authentication", diff --git a/homeassistant/components/huisbaasje/translations/et.json b/homeassistant/components/huisbaasje/translations/et.json index d079bf2a0c7..b4170142778 100644 --- a/homeassistant/components/huisbaasje/translations/et.json +++ b/homeassistant/components/huisbaasje/translations/et.json @@ -4,6 +4,7 @@ "already_configured": "Seade on juba h\u00e4\u00e4lestatud" }, "error": { + "cannot_connect": "\u00dchendamine nurjus", "connection_exception": "\u00dchendamine nurjus", "invalid_auth": "Vigane autentimine", "unauthenticated_exception": "Vigane autentimine", diff --git a/homeassistant/components/huisbaasje/translations/ko.json b/homeassistant/components/huisbaasje/translations/ko.json index bd25569d7c7..19387dfe542 100644 --- a/homeassistant/components/huisbaasje/translations/ko.json +++ b/homeassistant/components/huisbaasje/translations/ko.json @@ -4,6 +4,7 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "connection_exception": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unauthenticated_exception": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", diff --git a/homeassistant/components/huisbaasje/translations/nl.json b/homeassistant/components/huisbaasje/translations/nl.json index 8cb09793af8..a13c1837b9f 100644 --- a/homeassistant/components/huisbaasje/translations/nl.json +++ b/homeassistant/components/huisbaasje/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "connection_exception": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unauthenticated_exception": "Ongeldige authenticatie", diff --git a/homeassistant/components/huisbaasje/translations/ru.json b/homeassistant/components/huisbaasje/translations/ru.json index 995e340c396..c9fbe5cdcb2 100644 --- a/homeassistant/components/huisbaasje/translations/ru.json +++ b/homeassistant/components/huisbaasje/translations/ru.json @@ -4,6 +4,7 @@ "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant." }, "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "connection_exception": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "unauthenticated_exception": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", diff --git a/homeassistant/components/huisbaasje/translations/zh-Hant.json b/homeassistant/components/huisbaasje/translations/zh-Hant.json index bb120ab60dd..b1e95586376 100644 --- a/homeassistant/components/huisbaasje/translations/zh-Hant.json +++ b/homeassistant/components/huisbaasje/translations/zh-Hant.json @@ -4,6 +4,7 @@ "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", "connection_exception": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "unauthenticated_exception": "\u9a57\u8b49\u78bc\u7121\u6548", diff --git a/homeassistant/components/lutron_caseta/translations/nl.json b/homeassistant/components/lutron_caseta/translations/nl.json index 4b97da2058b..b281d3cd22c 100644 --- a/homeassistant/components/lutron_caseta/translations/nl.json +++ b/homeassistant/components/lutron_caseta/translations/nl.json @@ -15,7 +15,7 @@ "title": "Het importeren van de Cas\u00e9ta bridge configuratie is mislukt." }, "link": { - "description": "Om te koppelen met {naam} ({host}), na het verzenden van dit formulier, druk op de zwarte knop op de achterkant van de brug.", + "description": "Om te koppelen met {name} ({host}), na het verzenden van dit formulier, druk op de zwarte knop op de achterkant van de brug.", "title": "Koppel met de bridge" }, "user": { diff --git a/homeassistant/components/omnilogic/translations/ko.json b/homeassistant/components/omnilogic/translations/ko.json index d2a5f58abeb..0f3df64c00f 100644 --- a/homeassistant/components/omnilogic/translations/ko.json +++ b/homeassistant/components/omnilogic/translations/ko.json @@ -21,7 +21,7 @@ "step": { "init": { "data": { - "polling_interval": "\ud3f4\ub9c1 \uac04\uaca9(\ucd08)" + "polling_interval": "\ud3f4\ub9c1 \uac04\uaca9 (\ucd08)" } } } diff --git a/homeassistant/components/onvif/translations/nl.json b/homeassistant/components/onvif/translations/nl.json index 4d76e939af0..e1fdac8256e 100644 --- a/homeassistant/components/onvif/translations/nl.json +++ b/homeassistant/components/onvif/translations/nl.json @@ -52,7 +52,7 @@ "extra_arguments": "Extra FFMPEG argumenten", "rtsp_transport": "RTSP-transportmechanisme" }, - "title": "[%%] Apparaatopties" + "title": "ONVIF-apparaatopties" } } } diff --git a/homeassistant/components/ozw/translations/nl.json b/homeassistant/components/ozw/translations/nl.json index cb443f3a27a..7392f9b63eb 100644 --- a/homeassistant/components/ozw/translations/nl.json +++ b/homeassistant/components/ozw/translations/nl.json @@ -6,7 +6,7 @@ "addon_set_config_failed": "Mislukt om OpenZWave configuratie in te stellen.", "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang", - "mqtt_required": "De [%%] integratie is niet ingesteld", + "mqtt_required": "De MQTT-integratie is niet ingesteld", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { diff --git a/homeassistant/components/plaato/translations/nl.json b/homeassistant/components/plaato/translations/nl.json index 707830c33a5..d50763d0e1a 100644 --- a/homeassistant/components/plaato/translations/nl.json +++ b/homeassistant/components/plaato/translations/nl.json @@ -28,7 +28,7 @@ "device_type": "Type Plaato-apparaat" }, "description": "Wil je beginnen met instellen?", - "title": "Stel de Plaato Webhook in" + "title": "Stel de Plaato-apparaten in" }, "webhook": { "description": "Om evenementen naar de Home Assistant te sturen, moet u de webhook-functie instellen in Plaato Airlock. \n\n Vul de volgende info in: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n\n Zie [de documentatie] ({docs_url}) voor meer informatie.", diff --git a/homeassistant/components/plugwise/translations/nl.json b/homeassistant/components/plugwise/translations/nl.json index 408ddf967f4..af77f6f15e1 100644 --- a/homeassistant/components/plugwise/translations/nl.json +++ b/homeassistant/components/plugwise/translations/nl.json @@ -14,8 +14,8 @@ "data": { "flow_type": "Verbindingstype" }, - "description": "Details", - "title": "Maak verbinding met de Smile" + "description": "Product:", + "title": "Plugwise type" }, "user_gateway": { "data": { diff --git a/homeassistant/components/rachio/translations/ko.json b/homeassistant/components/rachio/translations/ko.json index 203dba17303..e0c7a3a13a3 100644 --- a/homeassistant/components/rachio/translations/ko.json +++ b/homeassistant/components/rachio/translations/ko.json @@ -22,7 +22,7 @@ "step": { "init": { "data": { - "manual_run_mins": "\uad6c\uc5ed \uc2a4\uc704\uce58\ub97c \ud65c\uc131\ud654\ud560 \ub54c \uc2e4\ud589\ud560 \uc2dc\uac04(\ubd84)" + "manual_run_mins": "\uad6c\uc5ed \uc2a4\uc704\uce58\ub97c \ud65c\uc131\ud654\ud560 \ub54c \uc2e4\ud589\ud560 \uc2dc\uac04 (\ubd84)" } } } diff --git a/homeassistant/components/screenlogic/translations/ko.json b/homeassistant/components/screenlogic/translations/ko.json index 3a8d457c603..94ddca6830a 100644 --- a/homeassistant/components/screenlogic/translations/ko.json +++ b/homeassistant/components/screenlogic/translations/ko.json @@ -29,7 +29,7 @@ "step": { "init": { "data": { - "scan_interval": "\uc2a4\uce94 \uac04\uaca9(\ucd08)" + "scan_interval": "\uc2a4\uce94 \uac04\uaca9 (\ucd08)" }, "description": "{gateway_name}\uc5d0 \ub300\ud55c \uc124\uc815 \uc9c0\uc815\ud558\uae30", "title": "ScreenLogic" diff --git a/homeassistant/components/synology_dsm/translations/ko.json b/homeassistant/components/synology_dsm/translations/ko.json index efc20dbe03f..ab9dc4d445a 100644 --- a/homeassistant/components/synology_dsm/translations/ko.json +++ b/homeassistant/components/synology_dsm/translations/ko.json @@ -46,7 +46,7 @@ "step": { "init": { "data": { - "scan_interval": "\uc2a4\uce94 \uac04\uaca9(\ubd84)", + "scan_interval": "\uc2a4\uce94 \uac04\uaca9 (\ubd84)", "timeout": "\uc81c\ud55c \uc2dc\uac04 (\ucd08)" } } diff --git a/homeassistant/components/tesla/translations/ko.json b/homeassistant/components/tesla/translations/ko.json index 3e3893e0b75..285326f39de 100644 --- a/homeassistant/components/tesla/translations/ko.json +++ b/homeassistant/components/tesla/translations/ko.json @@ -25,7 +25,7 @@ "init": { "data": { "enable_wake_on_start": "\uc2dc\ub3d9 \uc2dc \ucc28\ub7c9 \uae68\uc6b0\uae30", - "scan_interval": "\uc2a4\uce94 \uac04\uaca9(\ucd08)" + "scan_interval": "\uc2a4\uce94 \uac04\uaca9 (\ucd08)" } } } diff --git a/homeassistant/components/upnp/translations/nl.json b/homeassistant/components/upnp/translations/nl.json index cd4192e257c..331d5850fc4 100644 --- a/homeassistant/components/upnp/translations/nl.json +++ b/homeassistant/components/upnp/translations/nl.json @@ -16,7 +16,7 @@ "other": "Leeg" }, "ssdp_confirm": { - "description": "Wilt u [%%] instellen?" + "description": "Wilt u dit UPnP/IGD-apparaat instellen?" }, "user": { "data": { From 387e16644704ab0c5106d7d701986ed0e70efea4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 05:52:01 +0100 Subject: [PATCH 633/831] Remove HomeAssistantType alias from AdGuard integration (#48377) --- homeassistant/components/adguard/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/adguard/__init__.py b/homeassistant/components/adguard/__init__.py index 4015bd31bf2..04d266e7d9b 100644 --- a/homeassistant/components/adguard/__init__.py +++ b/homeassistant/components/adguard/__init__.py @@ -29,11 +29,12 @@ from homeassistant.const import ( CONF_USERNAME, CONF_VERIFY_SSL, ) +from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.entity import Entity -from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.helpers.typing import ConfigType _LOGGER = logging.getLogger(__name__) @@ -48,12 +49,12 @@ SERVICE_REFRESH_SCHEMA = vol.Schema( PLATFORMS = ["sensor", "switch"] -async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the AdGuard Home components.""" return True -async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up AdGuard Home from a config entry.""" session = async_get_clientsession(hass, entry.data[CONF_VERIFY_SSL]) adguard = AdGuardHome( @@ -123,7 +124,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool return True -async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload AdGuard Home config entry.""" hass.services.async_remove(DOMAIN, SERVICE_ADD_URL) hass.services.async_remove(DOMAIN, SERVICE_REMOVE_URL) From 63e30123805384214420a18eeb25e5e630f68b21 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Mar 2021 23:56:40 -0700 Subject: [PATCH 634/831] Fix script default trace (#48390) --- homeassistant/helpers/script.py | 2 +- tests/helpers/test_script.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 3df5be09f13..aa092ea2c8c 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -737,7 +737,7 @@ class _ScriptRun: if choose_data["default"]: trace_set_result(choice="default") - with trace_path(["default", "sequence"]): + with trace_path(["default"]): await self._async_run_script(choose_data["default"]) async def _async_wait_for_trigger_step(self): diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 917fc64b0e7..e4170ccae20 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -2059,7 +2059,7 @@ async def test_choose(hass, caplog, var, result): {"result": {"event": "test_event", "event_data": {"choice": "second"}}} ] if var == 3: - expected_trace["0/default/sequence/0"] = [ + expected_trace["0/default/0"] = [ {"result": {"event": "test_event", "event_data": {"choice": "default"}}} ] assert_action_trace(expected_trace) From b50dcef94fba6528c842448c53bab7c31be35be9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Mar 2021 21:54:49 -1000 Subject: [PATCH 635/831] Block detectable I/O in the event loop (#48387) We added a warning when this happens last April and gave developers a year to fix the instability. We now prevent the instability by raising RuntimeError when code attempts to do known I/O in the event loop instead of the executor. We now provide a suggestion on how to fix the code that is causing the issue. --- homeassistant/util/async_.py | 4 ++++ tests/util/test_async.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index 0fd1b564f0d..15353d1f7eb 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -136,6 +136,10 @@ def check_loop() -> None: found_frame.lineno, found_frame.line.strip(), ) + raise RuntimeError( + f"I/O must be done in the executor; Use `await hass.async_add_executor_job()` " + f"at {found_frame.filename[index:]}, line {found_frame.lineno}: {found_frame.line.strip()}" + ) def protect_loop(func: Callable) -> Callable: diff --git a/tests/util/test_async.py b/tests/util/test_async.py index d4fdce1e912..d5564a90d0e 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -78,7 +78,7 @@ async def test_check_loop_async(): async def test_check_loop_async_integration(caplog): """Test check_loop detects when called from event loop from integration context.""" - with patch( + with pytest.raises(RuntimeError), patch( "homeassistant.util.async_.extract_stack", return_value=[ Mock( @@ -107,7 +107,7 @@ async def test_check_loop_async_integration(caplog): async def test_check_loop_async_custom(caplog): """Test check_loop detects when called from event loop with custom component context.""" - with patch( + with pytest.raises(RuntimeError), patch( "homeassistant.util.async_.extract_stack", return_value=[ Mock( From 79af18a8abda9868a9571a0cdcbdda63c521e79c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Mar 2021 22:02:01 -1000 Subject: [PATCH 636/831] Bump httpx to 0.17.1 (#48388) * Bump httpx to 0.17.1 * git add * typing * add test * tweak --- homeassistant/helpers/httpx_client.py | 13 ++++++++++++- homeassistant/package_constraints.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- tests/helpers/test_httpx_client.py | 13 +++++++++++++ 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/httpx_client.py b/homeassistant/helpers/httpx_client.py index dfac0694a07..44045a7abeb 100644 --- a/homeassistant/helpers/httpx_client.py +++ b/homeassistant/helpers/httpx_client.py @@ -39,6 +39,17 @@ def get_async_client( return client +class HassHttpXAsyncClient(httpx.AsyncClient): + """httpx AsyncClient that suppresses context management.""" + + async def __aenter__(self: HassHttpXAsyncClient) -> HassHttpXAsyncClient: + """Prevent an integration from reopen of the client via context manager.""" + return self + + async def __aexit__(self, *args: Any) -> None: # pylint: disable=signature-differs + """Prevent an integration from close of the client via context manager.""" + + @callback def create_async_httpx_client( hass: HomeAssistantType, @@ -53,7 +64,7 @@ def create_async_httpx_client( This method must be run in the event loop. """ - client = httpx.AsyncClient( + client = HassHttpXAsyncClient( verify=verify_ssl, headers={USER_AGENT: SERVER_SOFTWARE}, **kwargs, diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 86269b9da06..4d1d53b85d1 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -16,7 +16,7 @@ distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.42.0 home-assistant-frontend==20210324.0 -httpx==0.16.1 +httpx==0.17.1 jinja2>=2.11.3 netdisco==2.8.2 paho-mqtt==1.5.1 diff --git a/requirements.txt b/requirements.txt index dafb5686e4a..5f633eaeb69 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ awesomeversion==21.2.3 bcrypt==3.1.7 certifi>=2020.12.5 ciso8601==2.1.3 -httpx==0.16.1 +httpx==0.17.1 jinja2>=2.11.3 PyJWT==1.7.1 cryptography==3.3.2 diff --git a/setup.py b/setup.py index d3a8a5aea70..56e56391489 100755 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ REQUIRES = [ "bcrypt==3.1.7", "certifi>=2020.12.5", "ciso8601==2.1.3", - "httpx==0.16.1", + "httpx==0.17.1", "jinja2>=2.11.3", "PyJWT==1.7.1", # PyJWT has loose dependency. We want the latest one. diff --git a/tests/helpers/test_httpx_client.py b/tests/helpers/test_httpx_client.py index 53a6985f5cc..a47463b6b98 100644 --- a/tests/helpers/test_httpx_client.py +++ b/tests/helpers/test_httpx_client.py @@ -80,6 +80,19 @@ async def test_get_async_client_patched_close(hass): assert mock_aclose.call_count == 0 +async def test_get_async_client_context_manager(hass): + """Test using the async client with a context manager does not close the session.""" + + with patch("httpx.AsyncClient.aclose") as mock_aclose: + httpx_session = client.get_async_client(hass) + assert isinstance(hass.data[client.DATA_ASYNC_CLIENT], httpx.AsyncClient) + + async with httpx_session: + pass + + assert mock_aclose.call_count == 0 + + async def test_warning_close_session_integration(hass, caplog): """Test log warning message when closing the session from integration context.""" with patch( From ad13a9295e61dcb475605b3fdc880069d423295c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 09:17:15 +0100 Subject: [PATCH 637/831] Merge multiple context managers in tests (#48146) --- tests/auth/test_init.py | 5 +- .../components/androidtv/test_media_player.py | 34 ++-- tests/components/apprise/test_notify.py | 13 +- tests/components/bond/common.py | 31 ++-- tests/components/device_tracker/test_init.py | 50 +++--- tests/components/elgato/test_light.py | 25 ++- tests/components/emulated_hue/test_init.py | 105 ++++++------ tests/components/enocean/test_config_flow.py | 30 ++-- tests/components/filter/test_sensor.py | 48 +++--- .../geo_json_events/test_geo_location.py | 91 +++++----- .../components/geo_rss_events/test_sensor.py | 85 ++++----- tests/components/graphite/test_init.py | 17 +- tests/components/hisense_aehw4a1/test_init.py | 69 ++++---- tests/components/history_stats/test_sensor.py | 22 ++- tests/components/influxdb/test_init.py | 15 +- tests/components/lyric/test_config_flow.py | 9 +- tests/components/melissa/test_climate.py | 75 ++++---- .../minecraft_server/test_config_flow.py | 161 ++++++++---------- tests/components/mqtt/test_light.py | 7 +- tests/components/mqtt/test_light_template.py | 65 ++++--- tests/components/pilight/test_init.py | 69 ++++---- .../signal_messenger/test_notify.py | 22 ++- tests/components/system_log/test_init.py | 25 ++- tests/components/zha/test_channels.py | 9 +- tests/components/zha/test_discover.py | 19 +-- tests/components/zwave/test_init.py | 45 +++-- .../helpers/test_config_entry_oauth2_flow.py | 7 +- tests/helpers/test_network.py | 5 +- tests/helpers/test_service.py | 31 ++-- tests/test_config_entries.py | 11 +- tests/test_requirements.py | 5 +- tests/util/test_async.py | 7 +- tests/util/yaml/test_init.py | 5 +- 33 files changed, 590 insertions(+), 627 deletions(-) diff --git a/tests/auth/test_init.py b/tests/auth/test_init.py index 4f34ce1d595..255fcac7694 100644 --- a/tests/auth/test_init.py +++ b/tests/auth/test_init.py @@ -501,9 +501,8 @@ async def test_refresh_token_provider_validation(mock_hass): with patch( "homeassistant.auth.providers.insecure_example.ExampleAuthProvider.async_validate_refresh_token", side_effect=InvalidAuthError("Invalid access"), - ) as call: - with pytest.raises(InvalidAuthError): - manager.async_create_access_token(refresh_token, ip) + ) as call, pytest.raises(InvalidAuthError): + manager.async_create_access_token(refresh_token, ip) call.assert_called_with(refresh_token, ip) diff --git a/tests/components/androidtv/test_media_player.py b/tests/components/androidtv/test_media_player.py index a9a803741b1..d72cf36438b 100644 --- a/tests/components/androidtv/test_media_player.py +++ b/tests/components/androidtv/test_media_player.py @@ -933,12 +933,11 @@ async def test_update_lock_not_acquired(hass): with patch( "androidtv.androidtv.androidtv_async.AndroidTVAsync.update", side_effect=LockNotAcquiredException, - ): - with patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key]: - await hass.helpers.entity_component.async_update_entity(entity_id) - state = hass.states.get(entity_id) - assert state is not None - assert state.state == STATE_OFF + ), patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key]: + await hass.helpers.entity_component.async_update_entity(entity_id) + state = hass.states.get(entity_id) + assert state is not None + assert state.state == STATE_OFF with patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key]: await hass.helpers.entity_component.async_update_entity(entity_id) @@ -1206,19 +1205,18 @@ async def test_connection_closed_on_ha_stop(hass): """Test that the ADB socket connection is closed when HA stops.""" patch_key, entity_id = _setup(CONFIG_ANDROIDTV_ADB_SERVER) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[patch_key]: - with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await async_setup_component( - hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER - ) - await hass.async_block_till_done() + with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ + patch_key + ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) + await hass.async_block_till_done() - with patch( - "androidtv.androidtv.androidtv_async.AndroidTVAsync.adb_close" - ) as adb_close: - hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) - await hass.async_block_till_done() - assert adb_close.called + with patch( + "androidtv.androidtv.androidtv_async.AndroidTVAsync.adb_close" + ) as adb_close: + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + assert adb_close.called async def test_exception(hass): diff --git a/tests/components/apprise/test_notify.py b/tests/components/apprise/test_notify.py index 8135f4e8e2c..3e539e73516 100644 --- a/tests/components/apprise/test_notify.py +++ b/tests/components/apprise/test_notify.py @@ -28,13 +28,14 @@ async def test_apprise_config_load_fail02(hass): BASE_COMPONENT: {"name": "test", "platform": "apprise", "config": "/path/"} } - with patch("apprise.Apprise.add", return_value=False): - with patch("apprise.AppriseConfig.add", return_value=True): - assert await async_setup_component(hass, BASE_COMPONENT, config) - await hass.async_block_till_done() + with patch("apprise.Apprise.add", return_value=False), patch( + "apprise.AppriseConfig.add", return_value=True + ): + assert await async_setup_component(hass, BASE_COMPONENT, config) + await hass.async_block_till_done() - # Test that our service failed to load - assert not hass.services.has_service(BASE_COMPONENT, "test") + # Test that our service failed to load + assert not hass.services.has_service(BASE_COMPONENT, "test") async def test_apprise_config_load_okay(hass, tmp_path): diff --git a/tests/components/bond/common.py b/tests/components/bond/common.py index d8adf134293..0791d002fed 100644 --- a/tests/components/bond/common.py +++ b/tests/components/bond/common.py @@ -72,20 +72,23 @@ async def setup_platform( ) mock_entry.add_to_hass(hass) - with patch("homeassistant.components.bond.PLATFORMS", [platform]): - with patch_bond_version(return_value=bond_version), patch_bond_bridge( - return_value=bridge - ), patch_bond_token(return_value=token), patch_bond_device_ids( - return_value=[bond_device_id] - ), patch_start_bpup(), patch_bond_device( - return_value=discovered_device - ), patch_bond_device_properties( - return_value=props - ), patch_bond_device_state( - return_value=state - ): - assert await async_setup_component(hass, BOND_DOMAIN, {}) - await hass.async_block_till_done() + with patch( + "homeassistant.components.bond.PLATFORMS", [platform] + ), patch_bond_version(return_value=bond_version), patch_bond_bridge( + return_value=bridge + ), patch_bond_token( + return_value=token + ), patch_bond_device_ids( + return_value=[bond_device_id] + ), patch_start_bpup(), patch_bond_device( + return_value=discovered_device + ), patch_bond_device_properties( + return_value=props + ), patch_bond_device_state( + return_value=state + ): + assert await async_setup_component(hass, BOND_DOMAIN, {}) + await hass.async_block_till_done() return mock_entry diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index d62e46255d7..af0c7658ac7 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -237,19 +237,18 @@ async def test_update_stale(hass, mock_device_tracker_conf): with patch( "homeassistant.components.device_tracker.legacy.dt_util.utcnow", return_value=register_time, - ): - with assert_setup_component(1, device_tracker.DOMAIN): - assert await async_setup_component( - hass, - device_tracker.DOMAIN, - { - device_tracker.DOMAIN: { - CONF_PLATFORM: "test", - device_tracker.CONF_CONSIDER_HOME: 59, - } - }, - ) - await hass.async_block_till_done() + ), assert_setup_component(1, device_tracker.DOMAIN): + assert await async_setup_component( + hass, + device_tracker.DOMAIN, + { + device_tracker.DOMAIN: { + CONF_PLATFORM: "test", + device_tracker.CONF_CONSIDER_HOME: 59, + } + }, + ) + await hass.async_block_till_done() assert hass.states.get("device_tracker.dev1").state == STATE_HOME @@ -458,19 +457,18 @@ async def test_see_passive_zone_state(hass, mock_device_tracker_conf): with patch( "homeassistant.components.device_tracker.legacy.dt_util.utcnow", return_value=register_time, - ): - with assert_setup_component(1, device_tracker.DOMAIN): - assert await async_setup_component( - hass, - device_tracker.DOMAIN, - { - device_tracker.DOMAIN: { - CONF_PLATFORM: "test", - device_tracker.CONF_CONSIDER_HOME: 59, - } - }, - ) - await hass.async_block_till_done() + ), assert_setup_component(1, device_tracker.DOMAIN): + assert await async_setup_component( + hass, + device_tracker.DOMAIN, + { + device_tracker.DOMAIN: { + CONF_PLATFORM: "test", + device_tracker.CONF_CONSIDER_HOME: 59, + } + }, + ) + await hass.async_block_till_done() state = hass.states.get("device_tracker.dev1") attrs = state.attributes diff --git a/tests/components/elgato/test_light.py b/tests/components/elgato/test_light.py index 48e6bd24625..6c4de76719f 100644 --- a/tests/components/elgato/test_light.py +++ b/tests/components/elgato/test_light.py @@ -93,17 +93,16 @@ async def test_light_unavailable( with patch( "homeassistant.components.elgato.light.Elgato.light", side_effect=ElgatoError, + ), patch( + "homeassistant.components.elgato.light.Elgato.state", + side_effect=ElgatoError, ): - with patch( - "homeassistant.components.elgato.light.Elgato.state", - side_effect=ElgatoError, - ): - await hass.services.async_call( - LIGHT_DOMAIN, - SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "light.frenck"}, - blocking=True, - ) - await hass.async_block_till_done() - state = hass.states.get("light.frenck") - assert state.state == STATE_UNAVAILABLE + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.frenck"}, + blocking=True, + ) + await hass.async_block_till_done() + state = hass.states.get("light.frenck") + assert state.state == STATE_UNAVAILABLE diff --git a/tests/components/emulated_hue/test_init.py b/tests/components/emulated_hue/test_init.py index 6fa6d969539..8e8c0f41249 100644 --- a/tests/components/emulated_hue/test_init.py +++ b/tests/components/emulated_hue/test_init.py @@ -13,29 +13,30 @@ def test_config_google_home_entity_id_to_number(): with patch( "homeassistant.components.emulated_hue.load_json", return_value={"1": "light.test2"}, - ) as json_loader: - with patch("homeassistant.components.emulated_hue.save_json") as json_saver: - number = conf.entity_id_to_number("light.test") - assert number == "2" + ) as json_loader, patch( + "homeassistant.components.emulated_hue.save_json" + ) as json_saver: + number = conf.entity_id_to_number("light.test") + assert number == "2" - assert json_saver.mock_calls[0][1][1] == { - "1": "light.test2", - "2": "light.test", - } + assert json_saver.mock_calls[0][1][1] == { + "1": "light.test2", + "2": "light.test", + } - assert json_saver.call_count == 1 - assert json_loader.call_count == 1 + assert json_saver.call_count == 1 + assert json_loader.call_count == 1 - number = conf.entity_id_to_number("light.test") - assert number == "2" - assert json_saver.call_count == 1 + number = conf.entity_id_to_number("light.test") + assert number == "2" + assert json_saver.call_count == 1 - number = conf.entity_id_to_number("light.test2") - assert number == "1" - assert json_saver.call_count == 1 + number = conf.entity_id_to_number("light.test2") + assert number == "1" + assert json_saver.call_count == 1 - entity_id = conf.number_to_entity_id("1") - assert entity_id == "light.test2" + entity_id = conf.number_to_entity_id("1") + assert entity_id == "light.test2" def test_config_google_home_entity_id_to_number_altered(): @@ -47,28 +48,29 @@ def test_config_google_home_entity_id_to_number_altered(): with patch( "homeassistant.components.emulated_hue.load_json", return_value={"21": "light.test2"}, - ) as json_loader: - with patch("homeassistant.components.emulated_hue.save_json") as json_saver: - number = conf.entity_id_to_number("light.test") - assert number == "22" - assert json_saver.call_count == 1 - assert json_loader.call_count == 1 + ) as json_loader, patch( + "homeassistant.components.emulated_hue.save_json" + ) as json_saver: + number = conf.entity_id_to_number("light.test") + assert number == "22" + assert json_saver.call_count == 1 + assert json_loader.call_count == 1 - assert json_saver.mock_calls[0][1][1] == { - "21": "light.test2", - "22": "light.test", - } + assert json_saver.mock_calls[0][1][1] == { + "21": "light.test2", + "22": "light.test", + } - number = conf.entity_id_to_number("light.test") - assert number == "22" - assert json_saver.call_count == 1 + number = conf.entity_id_to_number("light.test") + assert number == "22" + assert json_saver.call_count == 1 - number = conf.entity_id_to_number("light.test2") - assert number == "21" - assert json_saver.call_count == 1 + number = conf.entity_id_to_number("light.test2") + assert number == "21" + assert json_saver.call_count == 1 - entity_id = conf.number_to_entity_id("21") - assert entity_id == "light.test2" + entity_id = conf.number_to_entity_id("21") + assert entity_id == "light.test2" def test_config_google_home_entity_id_to_number_empty(): @@ -79,25 +81,26 @@ def test_config_google_home_entity_id_to_number_empty(): with patch( "homeassistant.components.emulated_hue.load_json", return_value={} - ) as json_loader: - with patch("homeassistant.components.emulated_hue.save_json") as json_saver: - number = conf.entity_id_to_number("light.test") - assert number == "1" - assert json_saver.call_count == 1 - assert json_loader.call_count == 1 + ) as json_loader, patch( + "homeassistant.components.emulated_hue.save_json" + ) as json_saver: + number = conf.entity_id_to_number("light.test") + assert number == "1" + assert json_saver.call_count == 1 + assert json_loader.call_count == 1 - assert json_saver.mock_calls[0][1][1] == {"1": "light.test"} + assert json_saver.mock_calls[0][1][1] == {"1": "light.test"} - number = conf.entity_id_to_number("light.test") - assert number == "1" - assert json_saver.call_count == 1 + number = conf.entity_id_to_number("light.test") + assert number == "1" + assert json_saver.call_count == 1 - number = conf.entity_id_to_number("light.test2") - assert number == "2" - assert json_saver.call_count == 2 + number = conf.entity_id_to_number("light.test2") + assert number == "2" + assert json_saver.call_count == 2 - entity_id = conf.number_to_entity_id("2") - assert entity_id == "light.test2" + entity_id = conf.number_to_entity_id("2") + assert entity_id == "light.test2" def test_config_alexa_entity_id_to_number(): diff --git a/tests/components/enocean/test_config_flow.py b/tests/components/enocean/test_config_flow.py index 4cea2a4fb9b..60a20af5eae 100644 --- a/tests/components/enocean/test_config_flow.py +++ b/tests/components/enocean/test_config_flow.py @@ -73,13 +73,14 @@ async def test_detection_flow_with_custom_path(hass): USER_PROVIDED_PATH = EnOceanFlowHandler.MANUAL_PATH_VALUE FAKE_DONGLE_PATH = "/fake/dongle" - with patch(DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=True)): - with patch(DONGLE_DETECT_METHOD, Mock(return_value=[FAKE_DONGLE_PATH])): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": "detect"}, - data={CONF_DEVICE: USER_PROVIDED_PATH}, - ) + with patch(DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=True)), patch( + DONGLE_DETECT_METHOD, Mock(return_value=[FAKE_DONGLE_PATH]) + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": "detect"}, + data={CONF_DEVICE: USER_PROVIDED_PATH}, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "manual" @@ -90,13 +91,14 @@ async def test_detection_flow_with_invalid_path(hass): USER_PROVIDED_PATH = "/invalid/path" FAKE_DONGLE_PATH = "/fake/dongle" - with patch(DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=False)): - with patch(DONGLE_DETECT_METHOD, Mock(return_value=[FAKE_DONGLE_PATH])): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": "detect"}, - data={CONF_DEVICE: USER_PROVIDED_PATH}, - ) + with patch(DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=False)), patch( + DONGLE_DETECT_METHOD, Mock(return_value=[FAKE_DONGLE_PATH]) + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": "detect"}, + data={CONF_DEVICE: USER_PROVIDED_PATH}, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "detect" diff --git a/tests/components/filter/test_sensor.py b/tests/components/filter/test_sensor.py index b787fc0235c..b8cdaf3c88a 100644 --- a/tests/components/filter/test_sensor.py +++ b/tests/components/filter/test_sensor.py @@ -116,24 +116,23 @@ async def test_chain_history(hass, values, missing=False): with patch( "homeassistant.components.history.state_changes_during_period", return_value=fake_states, + ), patch( + "homeassistant.components.history.get_last_state_changes", + return_value=fake_states, ): - with patch( - "homeassistant.components.history.get_last_state_changes", - return_value=fake_states, - ): - with assert_setup_component(1, "sensor"): - assert await async_setup_component(hass, "sensor", config) - await hass.async_block_till_done() + with assert_setup_component(1, "sensor"): + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() - for value in values: - hass.states.async_set(config["sensor"]["entity_id"], value.state) - await hass.async_block_till_done() + for value in values: + hass.states.async_set(config["sensor"]["entity_id"], value.state) + await hass.async_block_till_done() - state = hass.states.get("sensor.test") - if missing: - assert state.state == "18.05" - else: - assert state.state == "17.05" + state = hass.states.get("sensor.test") + if missing: + assert state.state == "18.05" + else: + assert state.state == "17.05" async def test_source_state_none(hass, values): @@ -234,18 +233,17 @@ async def test_history_time(hass): with patch( "homeassistant.components.history.state_changes_during_period", return_value=fake_states, + ), patch( + "homeassistant.components.history.get_last_state_changes", + return_value=fake_states, ): - with patch( - "homeassistant.components.history.get_last_state_changes", - return_value=fake_states, - ): - with assert_setup_component(1, "sensor"): - assert await async_setup_component(hass, "sensor", config) - await hass.async_block_till_done() - + with assert_setup_component(1, "sensor"): + assert await async_setup_component(hass, "sensor", config) await hass.async_block_till_done() - state = hass.states.get("sensor.test") - assert state.state == "18.0" + + await hass.async_block_till_done() + state = hass.states.get("sensor.test") + assert state.state == "18.0" async def test_setup(hass): diff --git a/tests/components/geo_json_events/test_geo_location.py b/tests/components/geo_json_events/test_geo_location.py index 75f41bb93c0..9d68e3d4973 100644 --- a/tests/components/geo_json_events/test_geo_location.py +++ b/tests/components/geo_json_events/test_geo_location.py @@ -198,60 +198,59 @@ async def test_setup_race_condition(hass, legacy_patchable_time): utcnow = dt_util.utcnow() with patch("homeassistant.util.dt.utcnow", return_value=utcnow), patch( "geojson_client.generic_feed.GenericFeed" - ) as mock_feed: - with assert_setup_component(1, geo_location.DOMAIN): - assert await async_setup_component(hass, geo_location.DOMAIN, CONFIG) - await hass.async_block_till_done() + ) as mock_feed, assert_setup_component(1, geo_location.DOMAIN): + assert await async_setup_component(hass, geo_location.DOMAIN, CONFIG) + await hass.async_block_till_done() - mock_feed.return_value.update.return_value = "OK", [mock_entry_1] + mock_feed.return_value.update.return_value = "OK", [mock_entry_1] - # Artificially trigger update. - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) - # Collect events. - await hass.async_block_till_done() + # Artificially trigger update. + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + # Collect events. + await hass.async_block_till_done() - all_states = hass.states.async_all() - assert len(all_states) == 1 - assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 1 - assert len(hass.data[DATA_DISPATCHER][update_signal]) == 1 + all_states = hass.states.async_all() + assert len(all_states) == 1 + assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 1 + assert len(hass.data[DATA_DISPATCHER][update_signal]) == 1 - # Simulate an update - empty data, removes all entities - mock_feed.return_value.update.return_value = "ERROR", None - async_fire_time_changed(hass, utcnow + SCAN_INTERVAL) - await hass.async_block_till_done() + # Simulate an update - empty data, removes all entities + mock_feed.return_value.update.return_value = "ERROR", None + async_fire_time_changed(hass, utcnow + SCAN_INTERVAL) + await hass.async_block_till_done() - all_states = hass.states.async_all() - assert len(all_states) == 0 - assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 0 - assert len(hass.data[DATA_DISPATCHER][update_signal]) == 0 + all_states = hass.states.async_all() + assert len(all_states) == 0 + assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 0 + assert len(hass.data[DATA_DISPATCHER][update_signal]) == 0 - # Simulate an update - 1 entry - mock_feed.return_value.update.return_value = "OK", [mock_entry_1] - async_fire_time_changed(hass, utcnow + 2 * SCAN_INTERVAL) - await hass.async_block_till_done() + # Simulate an update - 1 entry + mock_feed.return_value.update.return_value = "OK", [mock_entry_1] + async_fire_time_changed(hass, utcnow + 2 * SCAN_INTERVAL) + await hass.async_block_till_done() - all_states = hass.states.async_all() - assert len(all_states) == 1 - assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 1 - assert len(hass.data[DATA_DISPATCHER][update_signal]) == 1 + all_states = hass.states.async_all() + assert len(all_states) == 1 + assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 1 + assert len(hass.data[DATA_DISPATCHER][update_signal]) == 1 - # Simulate an update - 1 entry - mock_feed.return_value.update.return_value = "OK", [mock_entry_1] - async_fire_time_changed(hass, utcnow + 3 * SCAN_INTERVAL) - await hass.async_block_till_done() + # Simulate an update - 1 entry + mock_feed.return_value.update.return_value = "OK", [mock_entry_1] + async_fire_time_changed(hass, utcnow + 3 * SCAN_INTERVAL) + await hass.async_block_till_done() - all_states = hass.states.async_all() - assert len(all_states) == 1 - assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 1 - assert len(hass.data[DATA_DISPATCHER][update_signal]) == 1 + all_states = hass.states.async_all() + assert len(all_states) == 1 + assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 1 + assert len(hass.data[DATA_DISPATCHER][update_signal]) == 1 - # Simulate an update - empty data, removes all entities - mock_feed.return_value.update.return_value = "ERROR", None - async_fire_time_changed(hass, utcnow + 4 * SCAN_INTERVAL) - await hass.async_block_till_done() + # Simulate an update - empty data, removes all entities + mock_feed.return_value.update.return_value = "ERROR", None + async_fire_time_changed(hass, utcnow + 4 * SCAN_INTERVAL) + await hass.async_block_till_done() - all_states = hass.states.async_all() - assert len(all_states) == 0 - # Ensure that delete and update signal targets are now empty. - assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 0 - assert len(hass.data[DATA_DISPATCHER][update_signal]) == 0 + all_states = hass.states.async_all() + assert len(all_states) == 0 + # Ensure that delete and update signal targets are now empty. + assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 0 + assert len(hass.data[DATA_DISPATCHER][update_signal]) == 0 diff --git a/tests/components/geo_rss_events/test_sensor.py b/tests/components/geo_rss_events/test_sensor.py index ead81e5c5a8..95fb89301a9 100644 --- a/tests/components/geo_rss_events/test_sensor.py +++ b/tests/components/geo_rss_events/test_sensor.py @@ -68,54 +68,55 @@ async def test_setup(hass, mock_feed): utcnow = dt_util.utcnow() # Patching 'utcnow' to gain more control over the timed update. - with patch("homeassistant.util.dt.utcnow", return_value=utcnow): - with assert_setup_component(1, sensor.DOMAIN): - assert await async_setup_component(hass, sensor.DOMAIN, VALID_CONFIG) - # Artificially trigger update. - hass.bus.fire(EVENT_HOMEASSISTANT_START) - # Collect events. - await hass.async_block_till_done() + with patch( + "homeassistant.util.dt.utcnow", return_value=utcnow + ), assert_setup_component(1, sensor.DOMAIN): + assert await async_setup_component(hass, sensor.DOMAIN, VALID_CONFIG) + # Artificially trigger update. + hass.bus.fire(EVENT_HOMEASSISTANT_START) + # Collect events. + await hass.async_block_till_done() - all_states = hass.states.async_all() - assert len(all_states) == 1 + all_states = hass.states.async_all() + assert len(all_states) == 1 - state = hass.states.get("sensor.event_service_any") - assert state is not None - assert state.name == "Event Service Any" - assert int(state.state) == 2 - assert state.attributes == { - ATTR_FRIENDLY_NAME: "Event Service Any", - ATTR_UNIT_OF_MEASUREMENT: "Events", - ATTR_ICON: "mdi:alert", - "Title 1": "16km", - "Title 2": "20km", - } + state = hass.states.get("sensor.event_service_any") + assert state is not None + assert state.name == "Event Service Any" + assert int(state.state) == 2 + assert state.attributes == { + ATTR_FRIENDLY_NAME: "Event Service Any", + ATTR_UNIT_OF_MEASUREMENT: "Events", + ATTR_ICON: "mdi:alert", + "Title 1": "16km", + "Title 2": "20km", + } - # Simulate an update - empty data, but successful update, - # so no changes to entities. - mock_feed.return_value.update.return_value = "OK_NO_DATA", None - async_fire_time_changed(hass, utcnow + geo_rss_events.SCAN_INTERVAL) - await hass.async_block_till_done() + # Simulate an update - empty data, but successful update, + # so no changes to entities. + mock_feed.return_value.update.return_value = "OK_NO_DATA", None + async_fire_time_changed(hass, utcnow + geo_rss_events.SCAN_INTERVAL) + await hass.async_block_till_done() - all_states = hass.states.async_all() - assert len(all_states) == 1 - state = hass.states.get("sensor.event_service_any") - assert int(state.state) == 2 + all_states = hass.states.async_all() + assert len(all_states) == 1 + state = hass.states.get("sensor.event_service_any") + assert int(state.state) == 2 - # Simulate an update - empty data, removes all entities - mock_feed.return_value.update.return_value = "ERROR", None - async_fire_time_changed(hass, utcnow + 2 * geo_rss_events.SCAN_INTERVAL) - await hass.async_block_till_done() + # Simulate an update - empty data, removes all entities + mock_feed.return_value.update.return_value = "ERROR", None + async_fire_time_changed(hass, utcnow + 2 * geo_rss_events.SCAN_INTERVAL) + await hass.async_block_till_done() - all_states = hass.states.async_all() - assert len(all_states) == 1 - state = hass.states.get("sensor.event_service_any") - assert int(state.state) == 0 - assert state.attributes == { - ATTR_FRIENDLY_NAME: "Event Service Any", - ATTR_UNIT_OF_MEASUREMENT: "Events", - ATTR_ICON: "mdi:alert", - } + all_states = hass.states.async_all() + assert len(all_states) == 1 + state = hass.states.get("sensor.event_service_any") + assert int(state.state) == 0 + assert state.attributes == { + ATTR_FRIENDLY_NAME: "Event Service Any", + ATTR_UNIT_OF_MEASUREMENT: "Events", + ATTR_ICON: "mdi:alert", + } async def test_setup_with_categories(hass, mock_feed): diff --git a/tests/components/graphite/test_init.py b/tests/components/graphite/test_init.py index b7f5071813e..23a25b1623e 100644 --- a/tests/components/graphite/test_init.py +++ b/tests/components/graphite/test_init.py @@ -220,11 +220,12 @@ class TestGraphite(unittest.TestCase): runs.append(1) return event - with mock.patch.object(self.gf, "_queue") as mock_queue: - with mock.patch.object(self.gf, "_report_attributes") as mock_r: - mock_queue.get.side_effect = fake_get - self.gf.run() - # Twice for two events, once for the stop - assert mock_queue.task_done.call_count == 3 - assert mock_r.call_count == 1 - assert mock_r.call_args == mock.call("entity", event.data["new_state"]) + with mock.patch.object(self.gf, "_queue") as mock_queue, mock.patch.object( + self.gf, "_report_attributes" + ) as mock_r: + mock_queue.get.side_effect = fake_get + self.gf.run() + # Twice for two events, once for the stop + assert mock_queue.task_done.call_count == 3 + assert mock_r.call_count == 1 + assert mock_r.call_args == mock.call("entity", event.data["new_state"]) diff --git a/tests/components/hisense_aehw4a1/test_init.py b/tests/components/hisense_aehw4a1/test_init.py index 4add153ee94..ef99c3d1c96 100644 --- a/tests/components/hisense_aehw4a1/test_init.py +++ b/tests/components/hisense_aehw4a1/test_init.py @@ -13,24 +13,21 @@ async def test_creating_entry_sets_up_climate_discovery(hass): with patch( "homeassistant.components.hisense_aehw4a1.config_flow.AehW4a1.discovery", return_value=["1.2.3.4"], - ): - with patch( - "homeassistant.components.hisense_aehw4a1.climate.async_setup_entry", - return_value=True, - ) as mock_setup: - result = await hass.config_entries.flow.async_init( - hisense_aehw4a1.DOMAIN, context={"source": config_entries.SOURCE_USER} - ) + ), patch( + "homeassistant.components.hisense_aehw4a1.climate.async_setup_entry", + return_value=True, + ) as mock_setup: + result = await hass.config_entries.flow.async_init( + hisense_aehw4a1.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) - # Confirmation form - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + # Confirmation form + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {} - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - await hass.async_block_till_done() + await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 1 @@ -40,17 +37,16 @@ async def test_configuring_hisense_w4a1_create_entry(hass): with patch( "homeassistant.components.hisense_aehw4a1.config_flow.AehW4a1.check", return_value=True, - ): - with patch( - "homeassistant.components.hisense_aehw4a1.async_setup_entry", - return_value=True, - ) as mock_setup: - await async_setup_component( - hass, - hisense_aehw4a1.DOMAIN, - {"hisense_aehw4a1": {"ip_address": ["1.2.3.4"]}}, - ) - await hass.async_block_till_done() + ), patch( + "homeassistant.components.hisense_aehw4a1.async_setup_entry", + return_value=True, + ) as mock_setup: + await async_setup_component( + hass, + hisense_aehw4a1.DOMAIN, + {"hisense_aehw4a1": {"ip_address": ["1.2.3.4"]}}, + ) + await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 1 @@ -60,17 +56,16 @@ async def test_configuring_hisense_w4a1_not_creates_entry_for_device_not_found(h with patch( "homeassistant.components.hisense_aehw4a1.config_flow.AehW4a1.check", side_effect=exceptions.ConnectionError, - ): - with patch( - "homeassistant.components.hisense_aehw4a1.async_setup_entry", - return_value=True, - ) as mock_setup: - await async_setup_component( - hass, - hisense_aehw4a1.DOMAIN, - {"hisense_aehw4a1": {"ip_address": ["1.2.3.4"]}}, - ) - await hass.async_block_till_done() + ), patch( + "homeassistant.components.hisense_aehw4a1.async_setup_entry", + return_value=True, + ) as mock_setup: + await async_setup_component( + hass, + hisense_aehw4a1.DOMAIN, + {"hisense_aehw4a1": {"ip_address": ["1.2.3.4"]}}, + ) + await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 0 diff --git a/tests/components/history_stats/test_sensor.py b/tests/components/history_stats/test_sensor.py index 62e3959f4ad..f074003ab86 100644 --- a/tests/components/history_stats/test_sensor.py +++ b/tests/components/history_stats/test_sensor.py @@ -162,12 +162,11 @@ class TestHistoryStatsSensor(unittest.TestCase): with patch( "homeassistant.components.history.state_changes_during_period", return_value=fake_states, - ): - with patch("homeassistant.components.history.get_state", return_value=None): - sensor1.update() - sensor2.update() - sensor3.update() - sensor4.update() + ), patch("homeassistant.components.history.get_state", return_value=None): + sensor1.update() + sensor2.update() + sensor3.update() + sensor4.update() assert sensor1.state == 0.5 assert sensor2.state is None @@ -246,12 +245,11 @@ class TestHistoryStatsSensor(unittest.TestCase): with patch( "homeassistant.components.history.state_changes_during_period", return_value=fake_states, - ): - with patch("homeassistant.components.history.get_state", return_value=None): - sensor1.update() - sensor2.update() - sensor3.update() - sensor4.update() + ), patch("homeassistant.components.history.get_state", return_value=None): + sensor1.update() + sensor2.update() + sensor3.update() + sensor4.update() assert sensor1.state == 0.5 assert sensor2.state is None diff --git a/tests/components/influxdb/test_init.py b/tests/components/influxdb/test_init.py index 6c560eec5e7..e589a42e99a 100644 --- a/tests/components/influxdb/test_init.py +++ b/tests/components/influxdb/test_init.py @@ -254,14 +254,15 @@ async def test_setup_config_ssl( config = {"influxdb": config_base.copy()} config["influxdb"].update(config_ext) - with patch("os.access", return_value=True): - with patch("os.path.isfile", return_value=True): - assert await async_setup_component(hass, influxdb.DOMAIN, config) - await hass.async_block_till_done() + with patch("os.access", return_value=True), patch( + "os.path.isfile", return_value=True + ): + assert await async_setup_component(hass, influxdb.DOMAIN, config) + await hass.async_block_till_done() - assert hass.bus.listen.called - assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED - assert expected_client_args.items() <= mock_client.call_args.kwargs.items() + assert hass.bus.listen.called + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED + assert expected_client_args.items() <= mock_client.call_args.kwargs.items() @pytest.mark.parametrize( diff --git a/tests/components/lyric/test_config_flow.py b/tests/components/lyric/test_config_flow.py index 78fd9013466..bfdd45f0f8e 100644 --- a/tests/components/lyric/test_config_flow.py +++ b/tests/components/lyric/test_config_flow.py @@ -90,11 +90,10 @@ async def test_full_flow( }, ) - with patch("homeassistant.components.lyric.api.ConfigEntryLyricClient"): - with patch( - "homeassistant.components.lyric.async_setup_entry", return_value=True - ) as mock_setup: - result = await hass.config_entries.flow.async_configure(result["flow_id"]) + with patch("homeassistant.components.lyric.api.ConfigEntryLyricClient"), patch( + "homeassistant.components.lyric.async_setup_entry", return_value=True + ) as mock_setup: + result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["data"]["auth_implementation"] == DOMAIN diff --git a/tests/components/melissa/test_climate.py b/tests/components/melissa/test_climate.py index 1b53e2f8334..590c5149f9e 100644 --- a/tests/components/melissa/test_climate.py +++ b/tests/components/melissa/test_climate.py @@ -288,19 +288,18 @@ async def test_update(hass): """Test update.""" with patch( "homeassistant.components.melissa.climate._LOGGER.warning" - ) as mocked_warning: - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - await thermostat.async_update() - assert thermostat.fan_mode == SPEED_LOW - assert thermostat.state == HVAC_MODE_HEAT - api.async_status = AsyncMock(side_effect=KeyError("boom")) - await thermostat.async_update() - mocked_warning.assert_called_once_with( - "Unable to update entity %s", thermostat.entity_id - ) + ) as mocked_warning, patch("homeassistant.components.melissa"): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + await thermostat.async_update() + assert thermostat.fan_mode == SPEED_LOW + assert thermostat.state == HVAC_MODE_HEAT + api.async_status = AsyncMock(side_effect=KeyError("boom")) + await thermostat.async_update() + mocked_warning.assert_called_once_with( + "Unable to update entity %s", thermostat.entity_id + ) async def test_melissa_op_to_hass(hass): @@ -333,35 +332,33 @@ async def test_hass_mode_to_melissa(hass): """Test for hass operations to melssa.""" with patch( "homeassistant.components.melissa.climate._LOGGER.warning" - ) as mocked_warning: - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - assert thermostat.hass_mode_to_melissa(HVAC_MODE_FAN_ONLY) == 1 - assert thermostat.hass_mode_to_melissa(HVAC_MODE_HEAT) == 2 - assert thermostat.hass_mode_to_melissa(HVAC_MODE_COOL) == 3 - assert thermostat.hass_mode_to_melissa(HVAC_MODE_DRY) == 4 - thermostat.hass_mode_to_melissa("test") - mocked_warning.assert_called_once_with( - "Melissa have no setting for %s mode", "test" - ) + ) as mocked_warning, patch("homeassistant.components.melissa"): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + assert thermostat.hass_mode_to_melissa(HVAC_MODE_FAN_ONLY) == 1 + assert thermostat.hass_mode_to_melissa(HVAC_MODE_HEAT) == 2 + assert thermostat.hass_mode_to_melissa(HVAC_MODE_COOL) == 3 + assert thermostat.hass_mode_to_melissa(HVAC_MODE_DRY) == 4 + thermostat.hass_mode_to_melissa("test") + mocked_warning.assert_called_once_with( + "Melissa have no setting for %s mode", "test" + ) async def test_hass_fan_to_melissa(hass): """Test for translate melissa states to hass.""" with patch( "homeassistant.components.melissa.climate._LOGGER.warning" - ) as mocked_warning: - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - assert thermostat.hass_fan_to_melissa("auto") == 0 - assert thermostat.hass_fan_to_melissa(SPEED_LOW) == 1 - assert thermostat.hass_fan_to_melissa(SPEED_MEDIUM) == 2 - assert thermostat.hass_fan_to_melissa(SPEED_HIGH) == 3 - thermostat.hass_fan_to_melissa("test") - mocked_warning.assert_called_once_with( - "Melissa have no setting for %s fan mode", "test" - ) + ) as mocked_warning, patch("homeassistant.components.melissa"): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + assert thermostat.hass_fan_to_melissa("auto") == 0 + assert thermostat.hass_fan_to_melissa(SPEED_LOW) == 1 + assert thermostat.hass_fan_to_melissa(SPEED_MEDIUM) == 2 + assert thermostat.hass_fan_to_melissa(SPEED_HIGH) == 3 + thermostat.hass_fan_to_melissa("test") + mocked_warning.assert_called_once_with( + "Melissa have no setting for %s fan mode", "test" + ) diff --git a/tests/components/minecraft_server/test_config_flow.py b/tests/components/minecraft_server/test_config_flow.py index 2f3b7781ecf..9fcea3261ee 100644 --- a/tests/components/minecraft_server/test_config_flow.py +++ b/tests/components/minecraft_server/test_config_flow.py @@ -103,31 +103,27 @@ async def test_invalid_ip(hass: HomeAssistantType) -> None: async def test_same_host(hass: HomeAssistantType) -> None: """Test abort in case of same host name.""" - with patch( - "aiodns.DNSResolver.query", - side_effect=aiodns.error.DNSError, + with patch("aiodns.DNSResolver.query", side_effect=aiodns.error.DNSError,), patch( + "mcstatus.server.MinecraftServer.status", + return_value=PingResponse(STATUS_RESPONSE_RAW), ): - with patch( - "mcstatus.server.MinecraftServer.status", - return_value=PingResponse(STATUS_RESPONSE_RAW), - ): - unique_id = "mc.dummyserver.com-25565" - config_data = { - CONF_NAME: DEFAULT_NAME, - CONF_HOST: "mc.dummyserver.com", - CONF_PORT: DEFAULT_PORT, - } - mock_config_entry = MockConfigEntry( - domain=DOMAIN, unique_id=unique_id, data=config_data - ) - mock_config_entry.add_to_hass(hass) + unique_id = "mc.dummyserver.com-25565" + config_data = { + CONF_NAME: DEFAULT_NAME, + CONF_HOST: "mc.dummyserver.com", + CONF_PORT: DEFAULT_PORT, + } + mock_config_entry = MockConfigEntry( + domain=DOMAIN, unique_id=unique_id, data=config_data + ) + mock_config_entry.add_to_hass(hass) - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT + ) - assert result["type"] == RESULT_TYPE_ABORT - assert result["reason"] == "already_configured" + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" async def test_port_too_small(hass: HomeAssistantType) -> None: @@ -163,93 +159,80 @@ async def test_connection_failed(hass: HomeAssistantType) -> None: with patch( "aiodns.DNSResolver.query", side_effect=aiodns.error.DNSError, - ): - with patch("mcstatus.server.MinecraftServer.status", side_effect=OSError): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT - ) + ), patch("mcstatus.server.MinecraftServer.status", side_effect=OSError): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT + ) - assert result["type"] == RESULT_TYPE_FORM - assert result["errors"] == {"base": "cannot_connect"} + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {"base": "cannot_connect"} async def test_connection_succeeded_with_srv_record(hass: HomeAssistantType) -> None: """Test config entry in case of a successful connection with a SRV record.""" - with patch( - "aiodns.DNSResolver.query", - return_value=SRV_RECORDS, + with patch("aiodns.DNSResolver.query", return_value=SRV_RECORDS,), patch( + "mcstatus.server.MinecraftServer.status", + return_value=PingResponse(STATUS_RESPONSE_RAW), ): - with patch( - "mcstatus.server.MinecraftServer.status", - return_value=PingResponse(STATUS_RESPONSE_RAW), - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_SRV - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_SRV + ) - assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["title"] == USER_INPUT_SRV[CONF_HOST] - assert result["data"][CONF_NAME] == USER_INPUT_SRV[CONF_NAME] - assert result["data"][CONF_HOST] == USER_INPUT_SRV[CONF_HOST] + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == USER_INPUT_SRV[CONF_HOST] + assert result["data"][CONF_NAME] == USER_INPUT_SRV[CONF_NAME] + assert result["data"][CONF_HOST] == USER_INPUT_SRV[CONF_HOST] async def test_connection_succeeded_with_host(hass: HomeAssistantType) -> None: """Test config entry in case of a successful connection with a host name.""" - with patch( - "aiodns.DNSResolver.query", - side_effect=aiodns.error.DNSError, + with patch("aiodns.DNSResolver.query", side_effect=aiodns.error.DNSError,), patch( + "mcstatus.server.MinecraftServer.status", + return_value=PingResponse(STATUS_RESPONSE_RAW), ): - with patch( - "mcstatus.server.MinecraftServer.status", - return_value=PingResponse(STATUS_RESPONSE_RAW), - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT + ) - assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["title"] == USER_INPUT[CONF_HOST] - assert result["data"][CONF_NAME] == USER_INPUT[CONF_NAME] - assert result["data"][CONF_HOST] == "mc.dummyserver.com" + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == USER_INPUT[CONF_HOST] + assert result["data"][CONF_NAME] == USER_INPUT[CONF_NAME] + assert result["data"][CONF_HOST] == "mc.dummyserver.com" async def test_connection_succeeded_with_ip4(hass: HomeAssistantType) -> None: """Test config entry in case of a successful connection with an IPv4 address.""" - with patch("getmac.get_mac_address", return_value="01:23:45:67:89:ab"): - with patch( - "aiodns.DNSResolver.query", - side_effect=aiodns.error.DNSError, - ): - with patch( - "mcstatus.server.MinecraftServer.status", - return_value=PingResponse(STATUS_RESPONSE_RAW), - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_IPV4 - ) + with patch("getmac.get_mac_address", return_value="01:23:45:67:89:ab"), patch( + "aiodns.DNSResolver.query", + side_effect=aiodns.error.DNSError, + ), patch( + "mcstatus.server.MinecraftServer.status", + return_value=PingResponse(STATUS_RESPONSE_RAW), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_IPV4 + ) - assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["title"] == USER_INPUT_IPV4[CONF_HOST] - assert result["data"][CONF_NAME] == USER_INPUT_IPV4[CONF_NAME] - assert result["data"][CONF_HOST] == "1.1.1.1" + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == USER_INPUT_IPV4[CONF_HOST] + assert result["data"][CONF_NAME] == USER_INPUT_IPV4[CONF_NAME] + assert result["data"][CONF_HOST] == "1.1.1.1" async def test_connection_succeeded_with_ip6(hass: HomeAssistantType) -> None: """Test config entry in case of a successful connection with an IPv6 address.""" - with patch("getmac.get_mac_address", return_value="01:23:45:67:89:ab"): - with patch( - "aiodns.DNSResolver.query", - side_effect=aiodns.error.DNSError, - ): - with patch( - "mcstatus.server.MinecraftServer.status", - return_value=PingResponse(STATUS_RESPONSE_RAW), - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_IPV6 - ) + with patch("getmac.get_mac_address", return_value="01:23:45:67:89:ab"), patch( + "aiodns.DNSResolver.query", + side_effect=aiodns.error.DNSError, + ), patch( + "mcstatus.server.MinecraftServer.status", + return_value=PingResponse(STATUS_RESPONSE_RAW), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_IPV6 + ) - assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["title"] == USER_INPUT_IPV6[CONF_HOST] - assert result["data"][CONF_NAME] == USER_INPUT_IPV6[CONF_NAME] - assert result["data"][CONF_HOST] == "::ffff:0101:0101" + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == USER_INPUT_IPV6[CONF_HOST] + assert result["data"][CONF_NAME] == USER_INPUT_IPV6[CONF_NAME] + assert result["data"][CONF_HOST] == "::ffff:0101:0101" diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index 933f49ff823..00ff8b28b77 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -743,10 +743,9 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): with patch( "homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state", return_value=fake_state, - ): - with assert_setup_component(1, light.DOMAIN): - assert await async_setup_component(hass, light.DOMAIN, config) - await hass.async_block_till_done() + ), assert_setup_component(1, light.DOMAIN): + assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_ON diff --git a/tests/components/mqtt/test_light_template.py b/tests/components/mqtt/test_light_template.py index 7b5d34edd69..3bbf14ca668 100644 --- a/tests/components/mqtt/test_light_template.py +++ b/tests/components/mqtt/test_light_template.py @@ -298,39 +298,38 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): with patch( "homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state", return_value=fake_state, - ): - with assert_setup_component(1, light.DOMAIN): - assert await async_setup_component( - hass, - light.DOMAIN, - { - light.DOMAIN: { - "platform": "mqtt", - "schema": "template", - "name": "test", - "command_topic": "test_light_rgb/set", - "command_on_template": "on," - "{{ brightness|d }}," - "{{ color_temp|d }}," - "{{ white_value|d }}," - "{{ red|d }}-" - "{{ green|d }}-" - "{{ blue|d }}", - "command_off_template": "off", - "effect_list": ["colorloop", "random"], - "optimistic": True, - "state_template": '{{ value.split(",")[0] }}', - "color_temp_template": '{{ value.split(",")[2] }}', - "white_value_template": '{{ value.split(",")[3] }}', - "red_template": '{{ value.split(",")[4].' 'split("-")[0] }}', - "green_template": '{{ value.split(",")[4].' 'split("-")[1] }}', - "blue_template": '{{ value.split(",")[4].' 'split("-")[2] }}', - "effect_template": '{{ value.split(",")[5] }}', - "qos": 2, - } - }, - ) - await hass.async_block_till_done() + ), assert_setup_component(1, light.DOMAIN): + assert await async_setup_component( + hass, + light.DOMAIN, + { + light.DOMAIN: { + "platform": "mqtt", + "schema": "template", + "name": "test", + "command_topic": "test_light_rgb/set", + "command_on_template": "on," + "{{ brightness|d }}," + "{{ color_temp|d }}," + "{{ white_value|d }}," + "{{ red|d }}-" + "{{ green|d }}-" + "{{ blue|d }}", + "command_off_template": "off", + "effect_list": ["colorloop", "random"], + "optimistic": True, + "state_template": '{{ value.split(",")[0] }}', + "color_temp_template": '{{ value.split(",")[2] }}', + "white_value_template": '{{ value.split(",")[3] }}', + "red_template": '{{ value.split(",")[4].' 'split("-")[0] }}', + "green_template": '{{ value.split(",")[4].' 'split("-")[1] }}', + "blue_template": '{{ value.split(",")[4].' 'split("-")[2] }}', + "effect_template": '{{ value.split(",")[5] }}', + "qos": 2, + } + }, + ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_ON diff --git a/tests/components/pilight/test_init.py b/tests/components/pilight/test_init.py index 4b8b017f7fd..f3512a72ee3 100644 --- a/tests/components/pilight/test_init.py +++ b/tests/components/pilight/test_init.py @@ -64,29 +64,31 @@ class PilightDaemonSim: @patch("homeassistant.components.pilight._LOGGER.error") async def test_connection_failed_error(mock_error, hass): """Try to connect at 127.0.0.1:5001 with socket error.""" - with assert_setup_component(4): - with patch("pilight.pilight.Client", side_effect=socket.error) as mock_client: - assert not await async_setup_component( - hass, pilight.DOMAIN, {pilight.DOMAIN: {}} - ) - mock_client.assert_called_once_with( - host=pilight.DEFAULT_HOST, port=pilight.DEFAULT_PORT - ) - assert mock_error.call_count == 1 + with assert_setup_component(4), patch( + "pilight.pilight.Client", side_effect=socket.error + ) as mock_client: + assert not await async_setup_component( + hass, pilight.DOMAIN, {pilight.DOMAIN: {}} + ) + mock_client.assert_called_once_with( + host=pilight.DEFAULT_HOST, port=pilight.DEFAULT_PORT + ) + assert mock_error.call_count == 1 @patch("homeassistant.components.pilight._LOGGER.error") async def test_connection_timeout_error(mock_error, hass): """Try to connect at 127.0.0.1:5001 with socket timeout.""" - with assert_setup_component(4): - with patch("pilight.pilight.Client", side_effect=socket.timeout) as mock_client: - assert not await async_setup_component( - hass, pilight.DOMAIN, {pilight.DOMAIN: {}} - ) - mock_client.assert_called_once_with( - host=pilight.DEFAULT_HOST, port=pilight.DEFAULT_PORT - ) - assert mock_error.call_count == 1 + with assert_setup_component(4), patch( + "pilight.pilight.Client", side_effect=socket.timeout + ) as mock_client: + assert not await async_setup_component( + hass, pilight.DOMAIN, {pilight.DOMAIN: {}} + ) + mock_client.assert_called_once_with( + host=pilight.DEFAULT_HOST, port=pilight.DEFAULT_PORT + ) + assert mock_error.call_count == 1 @patch("pilight.pilight.Client", PilightDaemonSim) @@ -134,23 +136,22 @@ async def test_send_code(mock_pilight_error, hass): @patch("homeassistant.components.pilight._LOGGER.error") async def test_send_code_fail(mock_pilight_error, hass): """Check IOError exception error message.""" - with assert_setup_component(4): - with patch("pilight.pilight.Client.send_code", side_effect=IOError): - assert await async_setup_component( - hass, pilight.DOMAIN, {pilight.DOMAIN: {}} - ) + with assert_setup_component(4), patch( + "pilight.pilight.Client.send_code", side_effect=IOError + ): + assert await async_setup_component(hass, pilight.DOMAIN, {pilight.DOMAIN: {}}) - # Call with protocol info, should not give error - service_data = {"protocol": "test", "value": 42} - await hass.services.async_call( - pilight.DOMAIN, - pilight.SERVICE_NAME, - service_data=service_data, - blocking=True, - ) - await hass.async_block_till_done() - error_log_call = mock_pilight_error.call_args_list[-1] - assert "Pilight send failed" in str(error_log_call) + # Call with protocol info, should not give error + service_data = {"protocol": "test", "value": 42} + await hass.services.async_call( + pilight.DOMAIN, + pilight.SERVICE_NAME, + service_data=service_data, + blocking=True, + ) + await hass.async_block_till_done() + error_log_call = mock_pilight_error.call_args_list[-1] + assert "Pilight send failed" in str(error_log_call) @patch("homeassistant.components.pilight._LOGGER.error") diff --git a/tests/components/signal_messenger/test_notify.py b/tests/components/signal_messenger/test_notify.py index 4bb6cbdd197..d2dc93fea2e 100644 --- a/tests/components/signal_messenger/test_notify.py +++ b/tests/components/signal_messenger/test_notify.py @@ -87,12 +87,11 @@ class TestSignalMesssenger(unittest.TestCase): ) with self.assertLogs( "homeassistant.components.signal_messenger.notify", level="WARNING" - ) as context: - with tempfile.NamedTemporaryFile( - suffix=".png", prefix=os.path.basename(__file__) - ) as tf: - data = {"data": {"attachment": tf.name}} - self._signalmessenger.send_message(message, **data) + ) as context, tempfile.NamedTemporaryFile( + suffix=".png", prefix=os.path.basename(__file__) + ) as tf: + data = {"data": {"attachment": tf.name}} + self._signalmessenger.send_message(message, **data) self.assertIn( "The 'attachment' option is deprecated, please replace it with 'attachments'. This option will become invalid in version 0.108", context.output[0], @@ -117,12 +116,11 @@ class TestSignalMesssenger(unittest.TestCase): ) with self.assertLogs( "homeassistant.components.signal_messenger.notify", level="DEBUG" - ) as context: - with tempfile.NamedTemporaryFile( - suffix=".png", prefix=os.path.basename(__file__) - ) as tf: - data = {"data": {"attachments": [tf.name]}} - self._signalmessenger.send_message(message, **data) + ) as context, tempfile.NamedTemporaryFile( + suffix=".png", prefix=os.path.basename(__file__) + ) as tf: + data = {"data": {"attachments": [tf.name]}} + self._signalmessenger.send_message(message, **data) self.assertIn("Sending signal message", context.output[0]) self.assertTrue(mock.called) self.assertEqual(mock.call_count, 2) diff --git a/tests/components/system_log/test_init.py b/tests/components/system_log/test_init.py index 287e7139aff..ef54788d910 100644 --- a/tests/components/system_log/test_init.py +++ b/tests/components/system_log/test_init.py @@ -291,20 +291,19 @@ async def async_log_error_from_test_path(hass, path, sq): call_path = "internal_path.py" with patch.object( _LOGGER, "findCaller", MagicMock(return_value=(call_path, 0, None, None)) + ), patch( + "traceback.extract_stack", + MagicMock( + return_value=[ + get_frame("main_path/main.py"), + get_frame(path), + get_frame(call_path), + get_frame("venv_path/logging/log.py"), + ] + ), ): - with patch( - "traceback.extract_stack", - MagicMock( - return_value=[ - get_frame("main_path/main.py"), - get_frame(path), - get_frame(call_path), - get_frame("venv_path/logging/log.py"), - ] - ), - ): - _LOGGER.error("error message") - await _async_block_until_queue_empty(hass, sq) + _LOGGER.error("error message") + await _async_block_until_queue_empty(hass, sq) async def test_homeassistant_path(hass, simple_queue, hass_client): diff --git a/tests/components/zha/test_channels.py b/tests/components/zha/test_channels.py index 8a2ca1f05c3..ec5128fdb5e 100644 --- a/tests/components/zha/test_channels.py +++ b/tests/components/zha/test_channels.py @@ -415,10 +415,11 @@ async def test_ep_channels_configure(channel): claimed = {ch_1.id: ch_1, ch_2.id: ch_2, ch_3.id: ch_3} client_chans = {ch_4.id: ch_4, ch_5.id: ch_5} - with mock.patch.dict(ep_channels.claimed_channels, claimed, clear=True): - with mock.patch.dict(ep_channels.client_channels, client_chans, clear=True): - await ep_channels.async_configure() - await ep_channels.async_initialize(mock.sentinel.from_cache) + with mock.patch.dict( + ep_channels.claimed_channels, claimed, clear=True + ), mock.patch.dict(ep_channels.client_channels, client_chans, clear=True): + await ep_channels.async_configure() + await ep_channels.async_initialize(mock.sentinel.from_cache) for ch in [*claimed.values(), *client_chans.values()]: assert ch.async_initialize.call_count == 1 diff --git a/tests/components/zha/test_discover.py b/tests/components/zha/test_discover.py index c84c22e3251..cd0e75a7237 100644 --- a/tests/components/zha/test_discover.py +++ b/tests/components/zha/test_discover.py @@ -212,17 +212,14 @@ def test_discover_by_device_type_override(): with mock.patch( "homeassistant.components.zha.core.registries.ZHA_ENTITIES.get_entity", get_entity_mock, - ): - with mock.patch.dict(disc.PROBE._device_configs, overrides, clear=True): - disc.PROBE.discover_by_device_type(ep_channels) - assert get_entity_mock.call_count == 1 - assert ep_channels.claim_channels.call_count == 1 - assert ep_channels.claim_channels.call_args[0][0] is mock.sentinel.claimed - assert ep_channels.async_new_entity.call_count == 1 - assert ep_channels.async_new_entity.call_args[0][0] == zha_const.SWITCH - assert ( - ep_channels.async_new_entity.call_args[0][1] == mock.sentinel.entity_cls - ) + ), mock.patch.dict(disc.PROBE._device_configs, overrides, clear=True): + disc.PROBE.discover_by_device_type(ep_channels) + assert get_entity_mock.call_count == 1 + assert ep_channels.claim_channels.call_count == 1 + assert ep_channels.claim_channels.call_args[0][0] is mock.sentinel.claimed + assert ep_channels.async_new_entity.call_count == 1 + assert ep_channels.async_new_entity.call_args[0][0] == zha_const.SWITCH + assert ep_channels.async_new_entity.call_args[0][1] == mock.sentinel.entity_cls def test_discover_probe_single_cluster(): diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index e857675c545..6b1a6fe4f98 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -202,20 +202,17 @@ async def test_zwave_ready_wait(hass, mock_openzwave, zwave_setup): sleeps.append(duration) await asyncio_sleep(0) - with patch("homeassistant.components.zwave.dt_util.utcnow", new=utcnow): - with patch("asyncio.sleep", new=sleep): - with patch.object(zwave, "_LOGGER") as mock_logger: - hass.data[DATA_NETWORK].state = MockNetwork.STATE_STARTED + with patch("homeassistant.components.zwave.dt_util.utcnow", new=utcnow), patch( + "asyncio.sleep", new=sleep + ), patch.object(zwave, "_LOGGER") as mock_logger: + hass.data[DATA_NETWORK].state = MockNetwork.STATE_STARTED - await hass.async_start() + await hass.async_start() - assert len(sleeps) == const.NETWORK_READY_WAIT_SECS - assert mock_logger.warning.called - assert len(mock_logger.warning.mock_calls) == 1 - assert ( - mock_logger.warning.mock_calls[0][1][1] - == const.NETWORK_READY_WAIT_SECS - ) + assert len(sleeps) == const.NETWORK_READY_WAIT_SECS + assert mock_logger.warning.called + assert len(mock_logger.warning.mock_calls) == 1 + assert mock_logger.warning.mock_calls[0][1][1] == const.NETWORK_READY_WAIT_SECS async def test_device_entity(hass, mock_openzwave): @@ -341,19 +338,19 @@ async def test_unparsed_node_discovery(hass, mock_openzwave): sleeps.append(duration) await asyncio_sleep(0) - with patch("homeassistant.components.zwave.dt_util.utcnow", new=utcnow): - with patch("asyncio.sleep", new=sleep): - with patch.object(zwave, "_LOGGER") as mock_logger: - await hass.async_add_executor_job(mock_receivers[0], node) - await hass.async_block_till_done() + with patch("homeassistant.components.zwave.dt_util.utcnow", new=utcnow), patch( + "asyncio.sleep", new=sleep + ), patch.object(zwave, "_LOGGER") as mock_logger: + await hass.async_add_executor_job(mock_receivers[0], node) + await hass.async_block_till_done() - assert len(sleeps) == const.NODE_READY_WAIT_SECS - assert mock_logger.warning.called - assert len(mock_logger.warning.mock_calls) == 1 - assert mock_logger.warning.mock_calls[0][1][1:] == ( - 14, - const.NODE_READY_WAIT_SECS, - ) + assert len(sleeps) == const.NODE_READY_WAIT_SECS + assert mock_logger.warning.called + assert len(mock_logger.warning.mock_calls) == 1 + assert mock_logger.warning.mock_calls[0][1][1:] == ( + 14, + const.NODE_READY_WAIT_SECS, + ) assert hass.states.get("zwave.unknown_node_14").state == "unknown" diff --git a/tests/helpers/test_config_entry_oauth2_flow.py b/tests/helpers/test_config_entry_oauth2_flow.py index 3fad758b34b..b5257e635af 100644 --- a/tests/helpers/test_config_entry_oauth2_flow.py +++ b/tests/helpers/test_config_entry_oauth2_flow.py @@ -99,9 +99,10 @@ def test_inherit_enforces_domain_set(): """Return logger.""" return logging.getLogger(__name__) - with patch.dict(config_entries.HANDLERS, {TEST_DOMAIN: TestFlowHandler}): - with pytest.raises(TypeError): - TestFlowHandler() + with patch.dict( + config_entries.HANDLERS, {TEST_DOMAIN: TestFlowHandler} + ), pytest.raises(TypeError): + TestFlowHandler() async def test_abort_if_no_implementation(hass, flow_handler): diff --git a/tests/helpers/test_network.py b/tests/helpers/test_network.py index aad37e2fd49..05c72f10db5 100644 --- a/tests/helpers/test_network.py +++ b/tests/helpers/test_network.py @@ -377,9 +377,8 @@ async def test_get_cloud_url(hass: HomeAssistant): hass.components.cloud, "async_remote_ui_url", side_effect=cloud.CloudNotAvailable, - ): - with pytest.raises(NoURLAvailableError): - _get_cloud_url(hass) + ), pytest.raises(NoURLAvailableError): + _get_cloud_url(hass) async def test_get_external_url_cloud_fallback(hass: HomeAssistant): diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 7a084fed9dd..d168c8b9cfc 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -561,22 +561,21 @@ async def test_call_context_target_specific_no_auth( hass, mock_handle_entity_call, mock_entities ): """Check targeting specific entities without auth.""" - with pytest.raises(exceptions.Unauthorized) as err: - with patch( - "homeassistant.auth.AuthManager.async_get_user", - return_value=Mock(permissions=PolicyPermissions({}, None)), - ): - await service.entity_service_call( - hass, - [Mock(entities=mock_entities)], - Mock(), - ha.ServiceCall( - "test_domain", - "test_service", - {"entity_id": "light.kitchen"}, - context=ha.Context(user_id="mock-id"), - ), - ) + with pytest.raises(exceptions.Unauthorized) as err, patch( + "homeassistant.auth.AuthManager.async_get_user", + return_value=Mock(permissions=PolicyPermissions({}, None)), + ): + await service.entity_service_call( + hass, + [Mock(entities=mock_entities)], + Mock(), + ha.ServiceCall( + "test_domain", + "test_service", + {"entity_id": "light.kitchen"}, + context=ha.Context(user_id="mock-id"), + ), + ) assert err.value.context.user_id == "mock-id" assert err.value.entity_id == "light.kitchen" diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index fbcc9d1bf14..b12601220c3 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1221,12 +1221,11 @@ async def test_init_custom_integration(hass): None, {"name": "Hue", "dependencies": [], "requirements": [], "domain": "hue"}, ) - with pytest.raises(data_entry_flow.UnknownHandler): - with patch( - "homeassistant.loader.async_get_integration", - return_value=integration, - ): - await hass.config_entries.flow.async_init("bla") + with pytest.raises(data_entry_flow.UnknownHandler), patch( + "homeassistant.loader.async_get_integration", + return_value=integration, + ): + await hass.config_entries.flow.async_init("bla") async def test_support_entry_unload(hass): diff --git a/tests/test_requirements.py b/tests/test_requirements.py index 5f74e504de8..2c5b529467d 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -84,9 +84,8 @@ async def test_install_missing_package(hass): """Test an install attempt on an existing package.""" with patch( "homeassistant.util.package.install_package", return_value=False - ) as mock_inst: - with pytest.raises(RequirementsNotFound): - await async_process_requirements(hass, "test_component", ["hello==1.0.0"]) + ) as mock_inst, pytest.raises(RequirementsNotFound): + await async_process_requirements(hass, "test_component", ["hello==1.0.0"]) assert len(mock_inst.mock_calls) == 1 diff --git a/tests/util/test_async.py b/tests/util/test_async.py index d5564a90d0e..19413c57aaa 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -206,8 +206,9 @@ async def test_callback_is_always_scheduled(hass): callback = MagicMock() hasync.shutdown_run_callback_threadsafe(hass.loop) - with patch.object(hass.loop, "call_soon_threadsafe") as mock_call_soon_threadsafe: - with pytest.raises(RuntimeError): - hasync.run_callback_threadsafe(hass.loop, callback) + with patch.object( + hass.loop, "call_soon_threadsafe" + ) as mock_call_soon_threadsafe, pytest.raises(RuntimeError): + hasync.run_callback_threadsafe(hass.loop, callback) mock_call_soon_threadsafe.assert_called_once() diff --git a/tests/util/yaml/test_init.py b/tests/util/yaml/test_init.py index daa0275b7aa..2b86b3c50e9 100644 --- a/tests/util/yaml/test_init.py +++ b/tests/util/yaml/test_init.py @@ -65,9 +65,8 @@ def test_environment_variable_default(): def test_invalid_environment_variable(): """Test config file with no environment variable sat.""" conf = "password: !env_var PASSWORD" - with pytest.raises(HomeAssistantError): - with io.StringIO(conf) as file: - yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) + with pytest.raises(HomeAssistantError), io.StringIO(conf) as file: + yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) def test_include_yaml(): From 45f77ccccf5a22c1722680f05fac0ee96dd7f87b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 09:23:32 +0100 Subject: [PATCH 638/831] Merge of nested IF-IF cases - Core (#48364) --- .../auth/mfa_modules/insecure_example.py | 16 +++++----------- homeassistant/helpers/area_registry.py | 7 ++----- homeassistant/setup.py | 9 +++++---- homeassistant/util/color.py | 9 ++++----- 4 files changed, 16 insertions(+), 25 deletions(-) diff --git a/homeassistant/auth/mfa_modules/insecure_example.py b/homeassistant/auth/mfa_modules/insecure_example.py index c25f70ca31b..1d40339417b 100644 --- a/homeassistant/auth/mfa_modules/insecure_example.py +++ b/homeassistant/auth/mfa_modules/insecure_example.py @@ -77,17 +77,11 @@ class InsecureExampleModule(MultiFactorAuthModule): async def async_is_user_setup(self, user_id: str) -> bool: """Return whether user is setup.""" - for data in self._data: - if data["user_id"] == user_id: - return True - return False + return any(data["user_id"] == user_id for data in self._data) async def async_validate(self, user_id: str, user_input: dict[str, Any]) -> bool: """Return True if validation passed.""" - for data in self._data: - if data["user_id"] == user_id: - # user_input has been validate in caller - if data["pin"] == user_input["pin"]: - return True - - return False + return any( + data["user_id"] == user_id and data["pin"] == user_input["pin"] + for data in self._data + ) diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index c181fadcfd3..aa9d3a40b9a 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -134,11 +134,8 @@ class AreaRegistry: normalized_name = normalize_area_name(name) - if normalized_name != old.normalized_name: - if self.async_get_area_by_name(name): - raise ValueError( - f"The name {name} ({normalized_name}) is already in use" - ) + if normalized_name != old.normalized_name and self.async_get_area_by_name(name): + raise ValueError(f"The name {name} ({normalized_name}) is already in use") changes["name"] = name changes["normalized_name"] = normalized_name diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 4c5e10a254b..c22e660e553 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -315,10 +315,11 @@ async def async_prepare_setup_platform( log_error(f"Unable to import the component ({exc}).") return None - if hasattr(component, "setup") or hasattr(component, "async_setup"): - if not await async_setup_component(hass, integration.domain, hass_config): - log_error("Unable to set up component.") - return None + if ( + hasattr(component, "setup") or hasattr(component, "async_setup") + ) and not await async_setup_component(hass, integration.domain, hass_config): + log_error("Unable to set up component.") + return None return platform diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index f41461aada5..2a34fe82c59 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -261,11 +261,10 @@ def color_xy_brightness_to_RGB( vX: float, vY: float, ibrightness: int, Gamut: GamutType | None = None ) -> tuple[int, int, int]: """Convert from XYZ to RGB.""" - if Gamut: - if not check_point_in_lamps_reach((vX, vY), Gamut): - xy_closest = get_closest_point_to_point((vX, vY), Gamut) - vX = xy_closest[0] - vY = xy_closest[1] + if Gamut and not check_point_in_lamps_reach((vX, vY), Gamut): + xy_closest = get_closest_point_to_point((vX, vY), Gamut) + vX = xy_closest[0] + vY = xy_closest[1] brightness = ibrightness / 255.0 if brightness == 0.0: From 9737480742b7f91c89bc4a2879f38557766d866e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Mar 2021 22:32:30 -1000 Subject: [PATCH 639/831] Lazy load broadlink storage (#48391) With many broadlink devices, the storage load overwhelmed the executor at startup. Delay loading storage until it is needed. --- homeassistant/components/broadlink/remote.py | 44 ++++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/broadlink/remote.py b/homeassistant/components/broadlink/remote.py index 30043f487b1..dff7ba6b2fd 100644 --- a/homeassistant/components/broadlink/remote.py +++ b/homeassistant/components/broadlink/remote.py @@ -34,7 +34,6 @@ from homeassistant.components.remote import ( ) from homeassistant.const import CONF_HOST, STATE_OFF from homeassistant.core import callback -from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.storage import Store @@ -110,12 +109,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): Store(hass, CODE_STORAGE_VERSION, f"broadlink_remote_{device.unique_id}_codes"), Store(hass, FLAG_STORAGE_VERSION, f"broadlink_remote_{device.unique_id}_flags"), ) - - loaded = await remote.async_load_storage_files() - if not loaded: - _LOGGER.error("Failed to create '%s Remote' entity: Storage error", device.name) - return - async_add_entities([remote], False) @@ -128,6 +121,7 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity): self._coordinator = device.update_manager.coordinator self._code_storage = codes self._flag_storage = flags + self._storage_loaded = False self._codes = {} self._flags = defaultdict(int) self._state = True @@ -214,12 +208,12 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity): return code_list @callback - def get_codes(self): + def _get_codes(self): """Return a dictionary of codes.""" return self._codes @callback - def get_flags(self): + def _get_flags(self): """Return a dictionary of toggle flags. A toggle flag indicates whether the remote should send an @@ -250,16 +244,13 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity): self._state = False self.async_write_ha_state() - async def async_load_storage_files(self): - """Load codes and toggle flags from storage files.""" - try: - self._codes.update(await self._code_storage.async_load() or {}) - self._flags.update(await self._flag_storage.async_load() or {}) - - except HomeAssistantError: - return False - - return True + async def _async_load_storage(self): + """Load code and flag storage from disk.""" + # Exception is intentionally not trapped to + # provide feedback if something fails. + self._codes.update(await self._code_storage.async_load() or {}) + self._flags.update(await self._flag_storage.async_load() or {}) + self._storage_loaded = True async def async_send_command(self, command, **kwargs): """Send a list of commands to a device.""" @@ -277,6 +268,9 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity): ) return + if not self._storage_loaded: + await self._async_load_storage() + try: code_list = self._extract_codes(commands, device) except ValueError as err: @@ -312,7 +306,7 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity): at_least_one_sent = True if at_least_one_sent: - self._flag_storage.async_delay_save(self.get_flags, FLAG_SAVE_DELAY) + self._flag_storage.async_delay_save(self._get_flags, FLAG_SAVE_DELAY) async def async_learn_command(self, **kwargs): """Learn a list of commands from a remote.""" @@ -329,6 +323,9 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity): ) return + if not self._storage_loaded: + await self._async_load_storage() + async with self._lock: if command_type == COMMAND_TYPE_IR: learn_command = self._async_learn_ir_command @@ -486,6 +483,9 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity): ) return + if not self._storage_loaded: + await self._async_load_storage() + try: codes = self._codes[device] except KeyError as err: @@ -516,6 +516,6 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity): if not codes: del self._codes[device] if self._flags.pop(device, None) is not None: - self._flag_storage.async_delay_save(self.get_flags, FLAG_SAVE_DELAY) + self._flag_storage.async_delay_save(self._get_flags, FLAG_SAVE_DELAY) - self._code_storage.async_delay_save(self.get_codes, CODE_SAVE_DELAY) + self._code_storage.async_delay_save(self._get_codes, CODE_SAVE_DELAY) From 86212db71dea7810f5b81726f11db3aa398064f1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 10:03:15 +0100 Subject: [PATCH 640/831] Merge of nested IF-IF cases - K-N (#48370) --- .../components/keyboard_remote/__init__.py | 10 +++-- homeassistant/components/mailgun/__init__.py | 13 ++++--- homeassistant/components/met/weather.py | 15 ++++---- homeassistant/components/mikrotik/hub.py | 5 +-- homeassistant/components/min_max/sensor.py | 38 +++++++++++-------- .../components/minecraft_server/sensor.py | 5 +-- homeassistant/components/mjpeg/camera.py | 9 +++-- homeassistant/components/mobile_app/notify.py | 10 +++-- .../components/mqtt/light/schema_basic.py | 5 +-- .../components/mqtt_eventstream/__init__.py | 14 +++---- homeassistant/components/nest/climate_sdm.py | 8 ++-- homeassistant/components/nexia/climate.py | 19 +++++++--- 12 files changed, 86 insertions(+), 65 deletions(-) diff --git a/homeassistant/components/keyboard_remote/__init__.py b/homeassistant/components/keyboard_remote/__init__.py index 7d9bcf621e5..2ada56e1c44 100644 --- a/homeassistant/components/keyboard_remote/__init__.py +++ b/homeassistant/components/keyboard_remote/__init__.py @@ -312,10 +312,12 @@ class KeyboardRemote: self.emulate_key_hold_repeat, ) ) - elif event.value == KEY_VALUE["key_up"]: - if event.code in repeat_tasks: - repeat_tasks[event.code].cancel() - del repeat_tasks[event.code] + elif ( + event.value == KEY_VALUE["key_up"] + and event.code in repeat_tasks + ): + repeat_tasks[event.code].cancel() + del repeat_tasks[event.code] except (OSError, PermissionError, asyncio.CancelledError): # cancel key repeat tasks for task in repeat_tasks.values(): diff --git a/homeassistant/components/mailgun/__init__.py b/homeassistant/components/mailgun/__init__.py index 220b6a1abc1..39ee6e46350 100644 --- a/homeassistant/components/mailgun/__init__.py +++ b/homeassistant/components/mailgun/__init__.py @@ -51,11 +51,14 @@ async def handle_webhook(hass, webhook_id, request): except ValueError: return None - if isinstance(data, dict) and "signature" in data: - if await verify_webhook(hass, **data["signature"]): - data["webhook_id"] = webhook_id - hass.bus.async_fire(MESSAGE_RECEIVED, data) - return + if ( + isinstance(data, dict) + and "signature" in data + and await verify_webhook(hass, **data["signature"]) + ): + data["webhook_id"] = webhook_id + hass.bus.async_fire(MESSAGE_RECEIVED, data) + return _LOGGER.warning( "Mailgun webhook received an unauthenticated message - webhook_id: %s", diff --git a/homeassistant/components/met/weather.py b/homeassistant/components/met/weather.py index c0c8c11c644..4657da9e5d4 100644 --- a/homeassistant/components/met/weather.py +++ b/homeassistant/components/met/weather.py @@ -230,14 +230,13 @@ class MetWeather(CoordinatorEntity, WeatherEntity): for k, v in FORECAST_MAP.items() if met_item.get(v) is not None } - if not self._is_metric: - if ATTR_FORECAST_PRECIPITATION in ha_item: - precip_inches = convert_distance( - ha_item[ATTR_FORECAST_PRECIPITATION], - LENGTH_MILLIMETERS, - LENGTH_INCHES, - ) - ha_item[ATTR_FORECAST_PRECIPITATION] = round(precip_inches, 2) + if not self._is_metric and ATTR_FORECAST_PRECIPITATION in ha_item: + precip_inches = convert_distance( + ha_item[ATTR_FORECAST_PRECIPITATION], + LENGTH_MILLIMETERS, + LENGTH_INCHES, + ) + ha_item[ATTR_FORECAST_PRECIPITATION] = round(precip_inches, 2) if ha_item.get(ATTR_FORECAST_CONDITION): ha_item[ATTR_FORECAST_CONDITION] = format_condition( ha_item[ATTR_FORECAST_CONDITION] diff --git a/homeassistant/components/mikrotik/hub.py b/homeassistant/components/mikrotik/hub.py index 28a78d0ee1a..2f1f89ba60d 100644 --- a/homeassistant/components/mikrotik/hub.py +++ b/homeassistant/components/mikrotik/hub.py @@ -276,9 +276,8 @@ class MikrotikData: def update(self): """Update device_tracker from Mikrotik API.""" - if not self.available or not self.api: - if not self.connect_to_hub(): - return + if (not self.available or not self.api) and not self.connect_to_hub(): + return _LOGGER.debug("updating network devices for host: %s", self._host) self.update_devices() diff --git a/homeassistant/components/min_max/sensor.py b/homeassistant/components/min_max/sensor.py index a42291b3f67..d103ff8eaa6 100644 --- a/homeassistant/components/min_max/sensor.py +++ b/homeassistant/components/min_max/sensor.py @@ -84,9 +84,10 @@ def calc_min(sensor_values): val = None entity_id = None for sensor_id, sensor_value in sensor_values: - if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE]: - if val is None or val > sensor_value: - entity_id, val = sensor_id, sensor_value + if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE] and ( + val is None or val > sensor_value + ): + entity_id, val = sensor_id, sensor_value return entity_id, val @@ -95,30 +96,35 @@ def calc_max(sensor_values): val = None entity_id = None for sensor_id, sensor_value in sensor_values: - if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE]: - if val is None or val < sensor_value: - entity_id, val = sensor_id, sensor_value + if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE] and ( + val is None or val < sensor_value + ): + entity_id, val = sensor_id, sensor_value return entity_id, val def calc_mean(sensor_values, round_digits): """Calculate mean value, honoring unknown states.""" - result = [] - for _, sensor_value in sensor_values: - if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE]: - result.append(sensor_value) - if len(result) == 0: + result = [ + sensor_value + for _, sensor_value in sensor_values + if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE] + ] + + if not result: return None return round(sum(result) / len(result), round_digits) def calc_median(sensor_values, round_digits): """Calculate median value, honoring unknown states.""" - result = [] - for _, sensor_value in sensor_values: - if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE]: - result.append(sensor_value) - if len(result) == 0: + result = [ + sensor_value + for _, sensor_value in sensor_values + if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE] + ] + + if not result: return None result.sort() if len(result) % 2 == 0: diff --git a/homeassistant/components/minecraft_server/sensor.py b/homeassistant/components/minecraft_server/sensor.py index ded21f2935f..3d77d9e2772 100644 --- a/homeassistant/components/minecraft_server/sensor.py +++ b/homeassistant/components/minecraft_server/sensor.py @@ -147,9 +147,8 @@ class MinecraftServerPlayersOnlineSensor(MinecraftServerSensorEntity): extra_state_attributes = None players_list = self._server.players_list - if players_list is not None: - if len(players_list) != 0: - extra_state_attributes = {ATTR_PLAYERS_LIST: self._server.players_list} + if players_list is not None and len(players_list) != 0: + extra_state_attributes = {ATTR_PLAYERS_LIST: self._server.players_list} self._extra_state_attributes = extra_state_attributes diff --git a/homeassistant/components/mjpeg/camera.py b/homeassistant/components/mjpeg/camera.py index 15f7f5c80bf..d5008d1778c 100644 --- a/homeassistant/components/mjpeg/camera.py +++ b/homeassistant/components/mjpeg/camera.py @@ -98,9 +98,12 @@ class MjpegCamera(Camera): self._still_image_url = device_info.get(CONF_STILL_IMAGE_URL) self._auth = None - if self._username and self._password: - if self._authentication == HTTP_BASIC_AUTHENTICATION: - self._auth = aiohttp.BasicAuth(self._username, password=self._password) + if ( + self._username + and self._password + and self._authentication == HTTP_BASIC_AUTHENTICATION + ): + self._auth = aiohttp.BasicAuth(self._username, password=self._password) self._verify_ssl = device_info.get(CONF_VERIFY_SSL) async def async_camera_image(self): diff --git a/homeassistant/components/mobile_app/notify.py b/homeassistant/components/mobile_app/notify.py index 46a34fa7a85..763186df998 100644 --- a/homeassistant/components/mobile_app/notify.py +++ b/homeassistant/components/mobile_app/notify.py @@ -105,10 +105,12 @@ class MobileAppNotificationService(BaseNotificationService): """Send a message to the Lambda APNS gateway.""" data = {ATTR_MESSAGE: message} - if kwargs.get(ATTR_TITLE) is not None: - # Remove default title from notifications. - if kwargs.get(ATTR_TITLE) != ATTR_TITLE_DEFAULT: - data[ATTR_TITLE] = kwargs.get(ATTR_TITLE) + # Remove default title from notifications. + if ( + kwargs.get(ATTR_TITLE) is not None + and kwargs.get(ATTR_TITLE) != ATTR_TITLE_DEFAULT + ): + data[ATTR_TITLE] = kwargs.get(ATTR_TITLE) targets = kwargs.get(ATTR_TARGET) diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index e2443ec1df6..9c4b0f3a3e3 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -600,9 +600,8 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): # If brightness is being used instead of an on command, make sure # there is a brightness input. Either set the brightness to our # saved value or the maximum value if this is the first call - elif on_command_type == "brightness": - if ATTR_BRIGHTNESS not in kwargs: - kwargs[ATTR_BRIGHTNESS] = self._brightness if self._brightness else 255 + elif on_command_type == "brightness" and ATTR_BRIGHTNESS not in kwargs: + kwargs[ATTR_BRIGHTNESS] = self._brightness if self._brightness else 255 if ATTR_HS_COLOR in kwargs and self._topic[CONF_RGB_COMMAND_TOPIC] is not None: diff --git a/homeassistant/components/mqtt_eventstream/__init__.py b/homeassistant/components/mqtt_eventstream/__init__.py index 5a5f3b3c74d..328b9395eea 100644 --- a/homeassistant/components/mqtt_eventstream/__init__.py +++ b/homeassistant/components/mqtt_eventstream/__init__.py @@ -60,13 +60,13 @@ async def async_setup(hass, config): # Filter out the events that were triggered by publishing # to the MQTT topic, or you will end up in an infinite loop. - if event.event_type == EVENT_CALL_SERVICE: - if ( - event.data.get("domain") == mqtt.DOMAIN - and event.data.get("service") == mqtt.SERVICE_PUBLISH - and event.data[ATTR_SERVICE_DATA].get("topic") == pub_topic - ): - return + if ( + event.event_type == EVENT_CALL_SERVICE + and event.data.get("domain") == mqtt.DOMAIN + and event.data.get("service") == mqtt.SERVICE_PUBLISH + and event.data[ATTR_SERVICE_DATA].get("topic") == pub_topic + ): + return event_info = {"event_type": event.event_type, "event_data": event.data} msg = json.dumps(event_info, cls=JSONEncoder) diff --git a/homeassistant/components/nest/climate_sdm.py b/homeassistant/components/nest/climate_sdm.py index 169f9d23957..e02ebcd2dee 100644 --- a/homeassistant/components/nest/climate_sdm.py +++ b/homeassistant/components/nest/climate_sdm.py @@ -180,9 +180,11 @@ class ThermostatEntity(ClimateEntity): @property def _target_temperature_trait(self): """Return the correct trait with a target temp depending on mode.""" - if self.preset_mode == PRESET_ECO: - if ThermostatEcoTrait.NAME in self._device.traits: - return self._device.traits[ThermostatEcoTrait.NAME] + if ( + self.preset_mode == PRESET_ECO + and ThermostatEcoTrait.NAME in self._device.traits + ): + return self._device.traits[ThermostatEcoTrait.NAME] if ThermostatTemperatureSetpointTrait.NAME in self._device.traits: return self._device.traits[ThermostatTemperatureSetpointTrait.NAME] return None diff --git a/homeassistant/components/nexia/climate.py b/homeassistant/components/nexia/climate.py index 4084f4d297c..aff3711cdae 100644 --- a/homeassistant/components/nexia/climate.py +++ b/homeassistant/components/nexia/climate.py @@ -334,12 +334,19 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): new_cool_temp = min_temp + deadband # Check that we're within the deadband range, fix it if we're not - if new_heat_temp and new_heat_temp != cur_heat_temp: - if new_cool_temp - new_heat_temp < deadband: - new_cool_temp = new_heat_temp + deadband - if new_cool_temp and new_cool_temp != cur_cool_temp: - if new_cool_temp - new_heat_temp < deadband: - new_heat_temp = new_cool_temp - deadband + if ( + new_heat_temp + and new_heat_temp != cur_heat_temp + and new_cool_temp - new_heat_temp < deadband + ): + new_cool_temp = new_heat_temp + deadband + + if ( + new_cool_temp + and new_cool_temp != cur_cool_temp + and new_cool_temp - new_heat_temp < deadband + ): + new_heat_temp = new_cool_temp - deadband self._zone.set_heat_cool_temp( heat_temperature=new_heat_temp, From 3cd52b695d9e21981f8d2db2269b586623f8ba40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 27 Mar 2021 11:22:11 +0200 Subject: [PATCH 641/831] Upgrade flake8 and dependencies, enable flake8-noqa (#48393) * Upgrade flake8 to 3.9.0 https://flake8.pycqa.org/en/latest/release-notes/3.9.0.html * Upgrade pydocstyle to 6.0.0 https://www.pydocstyle.org/en/latest/release_notes.html#september-13th-2020 https://www.pydocstyle.org/en/latest/release_notes.html#march-18th-2021 * Upgrade flake8-docstrings to 1.6.0, enable flake8-noqa https://gitlab.com/pycqa/flake8-docstrings/-/blob/1.6.0/HISTORY.rst https://github.com/plinss/flake8-noqa/issues/1 * Upgrade/pin pyflakes to 2.3.1 https://github.com/PyCQA/pyflakes/blob/2.3.1/NEWS.rst * Pin pycodestyle to 2.7.0 --- .pre-commit-config.yaml | 12 ++++++------ homeassistant/components/http/web_runner.py | 4 ++-- requirements_test_pre_commit.txt | 9 ++++++--- tests/components/seventeentrack/test_sensor.py | 2 +- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 254ed637d81..2f4aea74ae9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,16 +23,16 @@ repos: exclude_types: [csv, json] exclude: ^tests/fixtures/ - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 + rev: 3.9.0 hooks: - id: flake8 additional_dependencies: - - flake8-docstrings==1.5.0 - # Temporarily every now and then for noqa cleanup; not done by - # default yet due to https://github.com/plinss/flake8-noqa/issues/1 - # - flake8-noqa==1.1.0 - - pydocstyle==5.1.1 + - pycodestyle==2.7.0 + - pyflakes==2.3.1 + - flake8-docstrings==1.6.0 + - pydocstyle==6.0.0 - flake8-comprehensions==3.4.0 + - flake8-noqa==1.1.0 files: ^(homeassistant|script|tests)/.+\.py$ - repo: https://github.com/PyCQA/bandit rev: 1.7.0 diff --git a/homeassistant/components/http/web_runner.py b/homeassistant/components/http/web_runner.py index 87468d40954..f3dd59bf9d7 100644 --- a/homeassistant/components/http/web_runner.py +++ b/homeassistant/components/http/web_runner.py @@ -23,7 +23,7 @@ class HomeAssistantTCPSite(web.BaseSite): __slots__ = ("_host", "_port", "_reuse_address", "_reuse_port", "_hosturl") - def __init__( + def __init__( # noqa: D107 self, runner: web.BaseRunner, host: None | str | list[str], @@ -34,7 +34,7 @@ class HomeAssistantTCPSite(web.BaseSite): backlog: int = 128, reuse_address: bool | None = None, reuse_port: bool | None = None, - ) -> None: # noqa: D107 + ) -> None: super().__init__( runner, shutdown_timeout=shutdown_timeout, diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 6a5768d34a1..06e87a5c51c 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -4,9 +4,12 @@ bandit==1.7.0 black==20.8b1 codespell==2.0.0 flake8-comprehensions==3.4.0 -flake8-docstrings==1.5.0 -flake8==3.8.4 +flake8-docstrings==1.6.0 +flake8-noqa==1.1.0 +flake8==3.9.0 isort==5.7.0 -pydocstyle==5.1.1 +pycodestyle==2.7.0 +pydocstyle==6.0.0 +pyflakes==2.3.1 pyupgrade==2.11.0 yamllint==1.24.2 diff --git a/tests/components/seventeentrack/test_sensor.py b/tests/components/seventeentrack/test_sensor.py index d40d2cd499b..5ad904530b9 100644 --- a/tests/components/seventeentrack/test_sensor.py +++ b/tests/components/seventeentrack/test_sensor.py @@ -106,7 +106,7 @@ class ProfileMock: show_archived: bool = False, tz: str = "UTC", ) -> list: - """Packages mock.""" + """Packages mock.""" # noqa: D401 return self.__class__.package_list[:] async def summary(self, show_archived: bool = False) -> dict: From 3aed84560fe92b66d511d585fafefa2b49d2c77f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 10:38:57 +0100 Subject: [PATCH 642/831] Merge of nested IF-IF cases - O-R (#48371) --- homeassistant/components/obihai/sensor.py | 5 ++-- .../components/octoprint/__init__.py | 15 +++++------ homeassistant/components/octoprint/sensor.py | 23 ++++++++--------- homeassistant/components/onewire/sensor.py | 5 ++-- homeassistant/components/person/__init__.py | 19 +++++++------- .../components/philips_js/media_player.py | 5 ++-- homeassistant/components/philips_js/remote.py | 7 +++--- homeassistant/components/plaato/sensor.py | 8 +++--- homeassistant/components/plex/server.py | 25 +++++++++++-------- homeassistant/components/plugwise/gateway.py | 5 ++-- .../components/rainforest_eagle/sensor.py | 16 +++++++----- homeassistant/components/recorder/__init__.py | 6 +---- .../ruckus_unleashed/device_tracker.py | 17 +++++++------ 13 files changed, 77 insertions(+), 79 deletions(-) diff --git a/homeassistant/components/obihai/sensor.py b/homeassistant/components/obihai/sensor.py index 9aacaa84193..639b9eb332f 100644 --- a/homeassistant/components/obihai/sensor.py +++ b/homeassistant/components/obihai/sensor.py @@ -146,9 +146,8 @@ class ObihaiServiceSensors(SensorEntity): services = self._pyobihai.get_line_state() - if services is not None: - if self._service_name in services: - self._state = services.get(self._service_name) + if services is not None and self._service_name in services: + self._state = services.get(self._service_name) call_direction = self._pyobihai.get_call_direction() diff --git a/homeassistant/components/octoprint/__init__.py b/homeassistant/components/octoprint/__init__.py index 66b804927c7..918f0258f78 100644 --- a/homeassistant/components/octoprint/__init__.py +++ b/homeassistant/components/octoprint/__init__.py @@ -212,14 +212,12 @@ class OctoPrintAPI: now = time.time() if endpoint == "job": last_time = self.job_last_reading[1] - if last_time is not None: - if now - last_time < 30.0: - return self.job_last_reading[0] + if last_time is not None and now - last_time < 30.0: + return self.job_last_reading[0] elif endpoint == "printer": last_time = self.printer_last_reading[1] - if last_time is not None: - if now - last_time < 30.0: - return self.printer_last_reading[0] + if last_time is not None and now - last_time < 30.0: + return self.printer_last_reading[0] url = self.api_url + endpoint try: @@ -300,8 +298,7 @@ def get_value_from_json(json_dict, sensor_type, group, tool): return json_dict[group][sensor_type] - if tool is not None: - if sensor_type in json_dict[group][tool]: - return json_dict[group][tool][sensor_type] + if tool is not None and sensor_type in json_dict[group][tool]: + return json_dict[group][tool][sensor_type] return None diff --git a/homeassistant/components/octoprint/sensor.py b/homeassistant/components/octoprint/sensor.py index f2c5c56c58a..16f6efce004 100644 --- a/homeassistant/components/octoprint/sensor.py +++ b/homeassistant/components/octoprint/sensor.py @@ -25,18 +25,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None): octoprint_api = hass.data[COMPONENT_DOMAIN][base_url] tools = octoprint_api.get_tools() - if "Temperatures" in monitored_conditions: - if not tools: - hass.components.persistent_notification.create( - "Your printer appears to be offline.
" - "If you do not want to have your printer on
" - " at all times, and you would like to monitor
" - "temperatures, please add
" - "bed and/or number_of_tools to your configuration
" - "and restart.", - title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID, - ) + if "Temperatures" in monitored_conditions and not tools: + hass.components.persistent_notification.create( + "Your printer appears to be offline.
" + "If you do not want to have your printer on
" + " at all times, and you would like to monitor
" + "temperatures, please add
" + "bed and/or number_of_tools to your configuration
" + "and restart.", + title=NOTIFICATION_TITLE, + notification_id=NOTIFICATION_ID, + ) devices = [] types = ["actual", "target"] diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index 3ea29a904db..02af7a89ae3 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -266,9 +266,8 @@ def get_entities(onewirehub: OneWireHub, config): """Get a list of entities.""" entities = [] device_names = {} - if CONF_NAMES in config: - if isinstance(config[CONF_NAMES], dict): - device_names = config[CONF_NAMES] + if CONF_NAMES in config and isinstance(config[CONF_NAMES], dict): + device_names = config[CONF_NAMES] conf_type = config[CONF_TYPE] # We have an owserver on a remote(or local) host/port diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index 7774694ff4e..1eb9d4eda7a 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -267,16 +267,15 @@ async def filter_yaml_data(hass: HomeAssistantType, persons: list[dict]) -> list for person_conf in persons: user_id = person_conf.get(CONF_USER_ID) - if user_id is not None: - if await hass.auth.async_get_user(user_id) is None: - _LOGGER.error( - "Invalid user_id detected for person %s", - person_conf[collection.CONF_ID], - ) - person_invalid_user.append( - f"- Person {person_conf[CONF_NAME]} (id: {person_conf[collection.CONF_ID]}) points at invalid user {user_id}" - ) - continue + if user_id is not None and await hass.auth.async_get_user(user_id) is None: + _LOGGER.error( + "Invalid user_id detected for person %s", + person_conf[collection.CONF_ID], + ) + person_invalid_user.append( + f"- Person {person_conf[CONF_NAME]} (id: {person_conf[collection.CONF_ID]}) points at invalid user {user_id}" + ) + continue filtered.append(person_conf) diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index 83adf61ed1e..7376d34e308 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -170,9 +170,8 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): @property def state(self): """Get the device state. An exception means OFF state.""" - if self._tv.on: - if self._tv.powerstate == "On" or self._tv.powerstate is None: - return STATE_ON + if self._tv.on and (self._tv.powerstate == "On" or self._tv.powerstate is None): + return STATE_ON return STATE_OFF @property diff --git a/homeassistant/components/philips_js/remote.py b/homeassistant/components/philips_js/remote.py index 30b46499e28..f4d34904f1b 100644 --- a/homeassistant/components/philips_js/remote.py +++ b/homeassistant/components/philips_js/remote.py @@ -52,10 +52,9 @@ class PhilipsTVRemote(RemoteEntity): @property def is_on(self): """Return true if device is on.""" - if self._tv.on: - if self._tv.powerstate == "On" or self._tv.powerstate is None: - return True - return False + return bool( + self._tv.on and (self._tv.powerstate == "On" or self._tv.powerstate is None) + ) @property def should_poll(self): diff --git a/homeassistant/components/plaato/sensor.py b/homeassistant/components/plaato/sensor.py index 0812a2fd585..9af16a1cacd 100644 --- a/homeassistant/components/plaato/sensor.py +++ b/homeassistant/components/plaato/sensor.py @@ -65,9 +65,11 @@ class PlaatoSensor(PlaatoEntity, SensorEntity): @property def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" - if self._coordinator is not None: - if self._sensor_type == PlaatoKeg.Pins.TEMPERATURE: - return DEVICE_CLASS_TEMPERATURE + if ( + self._coordinator is not None + and self._sensor_type == PlaatoKeg.Pins.TEMPERATURE + ): + return DEVICE_CLASS_TEMPERATURE if self._sensor_type == ATTR_TEMP: return DEVICE_CLASS_TEMPERATURE return None diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 841a9e7cc0d..d4bd4b09ef2 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -365,17 +365,20 @@ class PlexServer: PLAYER_SOURCE, source ) - if device.machineIdentifier not in ignored_clients: - if self.option_ignore_plexweb_clients and device.product == "Plex Web": - ignored_clients.add(device.machineIdentifier) - if device.machineIdentifier not in self._known_clients: - _LOGGER.debug( - "Ignoring %s %s: %s", - "Plex Web", - source, - device.machineIdentifier, - ) - return + if ( + device.machineIdentifier not in ignored_clients + and self.option_ignore_plexweb_clients + and device.product == "Plex Web" + ): + ignored_clients.add(device.machineIdentifier) + if device.machineIdentifier not in self._known_clients: + _LOGGER.debug( + "Ignoring %s %s: %s", + "Plex Web", + source, + device.machineIdentifier, + ) + return if device.machineIdentifier not in ( self._created_clients | ignored_clients | new_clients diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index 4e0e31810cb..3f8bc7ea626 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -110,9 +110,8 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: api.get_all_devices() - if entry.unique_id is None: - if api.smile_version[0] != "1.8.0": - hass.config_entries.async_update_entry(entry, unique_id=api.smile_hostname) + if entry.unique_id is None and api.smile_version[0] != "1.8.0": + hass.config_entries.async_update_entry(entry, unique_id=api.smile_hostname) undo_listener = entry.add_update_listener(_update_listener) diff --git a/homeassistant/components/rainforest_eagle/sensor.py b/homeassistant/components/rainforest_eagle/sensor.py index 80475b4c21b..d333f9437f1 100644 --- a/homeassistant/components/rainforest_eagle/sensor.py +++ b/homeassistant/components/rainforest_eagle/sensor.py @@ -55,14 +55,18 @@ def hwtest(cloud_id, install_code, ip_address): response = reader.get_network_info() # Branch to test if target is Legacy Model - if "NetworkInfo" in response: - if response["NetworkInfo"].get("ModelId", None) == "Z109-EAGLE": - return reader + if ( + "NetworkInfo" in response + and response["NetworkInfo"].get("ModelId", None) == "Z109-EAGLE" + ): + return reader # Branch to test if target is Eagle-200 Model - if "Response" in response: - if response["Response"].get("Command", None) == "get_network_info": - return EagleReader(ip_address, cloud_id, install_code) + if ( + "Response" in response + and response["Response"].get("Command", None) == "get_network_info" + ): + return EagleReader(ip_address, cloud_id, install_code) # Catch-all if hardware ID tests fail raise ValueError("Couldn't determine device model.") diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index cff8119356f..f93d965a4b9 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -304,11 +304,7 @@ class Recorder(threading.Thread): return False entity_id = event.data.get(ATTR_ENTITY_ID) - if entity_id is not None: - if not self.entity_filter(entity_id): - return False - - return True + return bool(entity_id is None or self.entity_filter(entity_id)) def do_adhoc_purge(self, **kwargs): """Trigger an adhoc purge retaining keep_days worth of data.""" diff --git a/homeassistant/components/ruckus_unleashed/device_tracker.py b/homeassistant/components/ruckus_unleashed/device_tracker.py index 140aa3a8692..90a848b663b 100644 --- a/homeassistant/components/ruckus_unleashed/device_tracker.py +++ b/homeassistant/components/ruckus_unleashed/device_tracker.py @@ -67,14 +67,17 @@ def restore_entities(registry, coordinator, entry, async_add_entities, tracked): missing = [] for entity in registry.entities.values(): - if entity.config_entry_id == entry.entry_id and entity.platform == DOMAIN: - if entity.unique_id not in coordinator.data[API_CLIENTS]: - missing.append( - RuckusUnleashedDevice( - coordinator, entity.unique_id, entity.original_name - ) + if ( + entity.config_entry_id == entry.entry_id + and entity.platform == DOMAIN + and entity.unique_id not in coordinator.data[API_CLIENTS] + ): + missing.append( + RuckusUnleashedDevice( + coordinator, entity.unique_id, entity.original_name ) - tracked.add(entity.unique_id) + ) + tracked.add(entity.unique_id) if missing: async_add_entities(missing) From 8d5ce5309875dad0be1ca53cf795e15681a397d3 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 10:54:59 +0100 Subject: [PATCH 643/831] Merge of nested IF-IF cases - S-W (#48372) --- homeassistant/components/saj/sensor.py | 24 ++++++------- homeassistant/components/smhi/config_flow.py | 34 +++++++++--------- homeassistant/components/stream/hls.py | 10 +++--- .../components/subaru/config_flow.py | 29 ++++++++------- homeassistant/components/subaru/sensor.py | 22 +++++++----- .../components/systemmonitor/sensor.py | 35 +++++++------------ .../components/tado/device_tracker.py | 9 +++-- homeassistant/components/tplink/light.py | 12 +++---- .../components/unifi/device_tracker.py | 11 +++--- homeassistant/components/unifi/switch.py | 9 ++--- homeassistant/components/uvc/camera.py | 13 +++---- homeassistant/components/wilight/fan.py | 25 ++++++++----- .../components/wink/binary_sensor.py | 8 +++-- homeassistant/components/wink/sensor.py | 8 +++-- .../components/worxlandroid/sensor.py | 5 ++- 15 files changed, 128 insertions(+), 126 deletions(-) diff --git a/homeassistant/components/saj/sensor.py b/homeassistant/components/saj/sensor.py index ef69513db43..f1def71cc64 100644 --- a/homeassistant/components/saj/sensor.py +++ b/homeassistant/components/saj/sensor.py @@ -103,18 +103,18 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= for sensor in hass_sensors: state_unknown = False - if not values: - # SAJ inverters are powered by DC via solar panels and thus are - # offline after the sun has set. If a sensor resets on a daily - # basis like "today_yield", this reset won't happen automatically. - # Code below checks if today > day when sensor was last updated - # and if so: set state to None. - # Sensors with live values like "temperature" or "current_power" - # will also be reset to None. - if (sensor.per_day_basis and date.today() > sensor.date_updated) or ( - not sensor.per_day_basis and not sensor.per_total_basis - ): - state_unknown = True + # SAJ inverters are powered by DC via solar panels and thus are + # offline after the sun has set. If a sensor resets on a daily + # basis like "today_yield", this reset won't happen automatically. + # Code below checks if today > day when sensor was last updated + # and if so: set state to None. + # Sensors with live values like "temperature" or "current_power" + # will also be reset to None. + if not values and ( + (sensor.per_day_basis and date.today() > sensor.date_updated) + or (not sensor.per_day_basis and not sensor.per_total_basis) + ): + state_unknown = True sensor.async_update_values(unknown_state=state_unknown) return values diff --git a/homeassistant/components/smhi/config_flow.py b/homeassistant/components/smhi/config_flow.py index 8853680af33..a8cfdba5be5 100644 --- a/homeassistant/components/smhi/config_flow.py +++ b/homeassistant/components/smhi/config_flow.py @@ -53,31 +53,32 @@ class SmhiFlowHandler(config_entries.ConfigFlow): # If hass config has the location set and is a valid coordinate the # default location is set as default values in the form - if not smhi_locations(self.hass): - if await self._homeassistant_location_exists(): - return await self._show_config_form( - name=HOME_LOCATION_NAME, - latitude=self.hass.config.latitude, - longitude=self.hass.config.longitude, - ) + if ( + not smhi_locations(self.hass) + and await self._homeassistant_location_exists() + ): + return await self._show_config_form( + name=HOME_LOCATION_NAME, + latitude=self.hass.config.latitude, + longitude=self.hass.config.longitude, + ) return await self._show_config_form() async def _homeassistant_location_exists(self) -> bool: """Return true if default location is set and is valid.""" - if self.hass.config.latitude != 0.0 and self.hass.config.longitude != 0.0: - # Return true if valid location - if await self._check_location( + # Return true if valid location + return ( + self.hass.config.latitude != 0.0 + and self.hass.config.longitude != 0.0 + and await self._check_location( self.hass.config.longitude, self.hass.config.latitude - ): - return True - return False + ) + ) def _name_in_configuration_exists(self, name: str) -> bool: """Return True if name exists in configuration.""" - if name in smhi_locations(self.hass): - return True - return False + return name in smhi_locations(self.hass) async def _show_config_form( self, name: str = None, latitude: str = None, longitude: str = None @@ -97,7 +98,6 @@ class SmhiFlowHandler(config_entries.ConfigFlow): async def _check_location(self, longitude: str, latitude: str) -> bool: """Return true if location is ok.""" - try: session = aiohttp_client.async_get_clientsession(self.hass) smhi_api = Smhi(longitude, latitude, session=session) diff --git a/homeassistant/components/stream/hls.py b/homeassistant/components/stream/hls.py index b2600977971..4909bbf95a3 100644 --- a/homeassistant/components/stream/hls.py +++ b/homeassistant/components/stream/hls.py @@ -50,9 +50,8 @@ class HlsMasterPlaylistView(StreamView): track = stream.add_provider("hls") stream.start() # Wait for a segment to be ready - if not track.segments: - if not await track.recv(): - return web.HTTPNotFound() + if not track.segments and not await track.recv(): + return web.HTTPNotFound() headers = {"Content-Type": FORMAT_CONTENT_TYPE["hls"]} return web.Response(body=self.render(track).encode("utf-8"), headers=headers) @@ -110,9 +109,8 @@ class HlsPlaylistView(StreamView): track = stream.add_provider("hls") stream.start() # Wait for a segment to be ready - if not track.segments: - if not await track.recv(): - return web.HTTPNotFound() + if not track.segments and not await track.recv(): + return web.HTTPNotFound() headers = {"Content-Type": FORMAT_CONTENT_TYPE["hls"]} return web.Response(body=self.render(track).encode("utf-8"), headers=headers) diff --git a/homeassistant/components/subaru/config_flow.py b/homeassistant/components/subaru/config_flow.py index 772134c66b1..d9d9bdff4e1 100644 --- a/homeassistant/components/subaru/config_flow.py +++ b/homeassistant/components/subaru/config_flow.py @@ -118,21 +118,20 @@ class SubaruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_pin(self, user_input=None): """Handle second part of config flow, if required.""" error = None - if user_input: - if self.controller.update_saved_pin(user_input[CONF_PIN]): - try: - vol.Match(r"[0-9]{4}")(user_input[CONF_PIN]) - await self.controller.test_pin() - except vol.Invalid: - error = {"base": "bad_pin_format"} - except InvalidPIN: - error = {"base": "incorrect_pin"} - else: - _LOGGER.debug("PIN successfully tested") - self.config_data.update(user_input) - return self.async_create_entry( - title=self.config_data[CONF_USERNAME], data=self.config_data - ) + if user_input and self.controller.update_saved_pin(user_input[CONF_PIN]): + try: + vol.Match(r"[0-9]{4}")(user_input[CONF_PIN]) + await self.controller.test_pin() + except vol.Invalid: + error = {"base": "bad_pin_format"} + except InvalidPIN: + error = {"base": "incorrect_pin"} + else: + _LOGGER.debug("PIN successfully tested") + self.config_data.update(user_input) + return self.async_create_entry( + title=self.config_data[CONF_USERNAME], data=self.config_data + ) return self.async_show_form(step_id="pin", data_schema=PIN_SCHEMA, errors=error) diff --git a/homeassistant/components/subaru/sensor.py b/homeassistant/components/subaru/sensor.py index 41dd8a6604f..3994c9c6124 100644 --- a/homeassistant/components/subaru/sensor.py +++ b/homeassistant/components/subaru/sensor.py @@ -220,16 +220,20 @@ class SubaruSensor(SubaruEntity, SensorEntity): self.hass.config.units.length(self.current_value, self.api_unit), 1 ) - if self.api_unit in PRESSURE_UNITS: - if self.hass.config.units == IMPERIAL_SYSTEM: - return round( - self.hass.config.units.pressure(self.current_value, self.api_unit), - 1, - ) + if ( + self.api_unit in PRESSURE_UNITS + and self.hass.config.units == IMPERIAL_SYSTEM + ): + return round( + self.hass.config.units.pressure(self.current_value, self.api_unit), + 1, + ) - if self.api_unit in FUEL_CONSUMPTION_UNITS: - if self.hass.config.units == IMPERIAL_SYSTEM: - return round((100.0 * L_PER_GAL) / (KM_PER_MI * self.current_value), 1) + if ( + self.api_unit in FUEL_CONSUMPTION_UNITS + and self.hass.config.units == IMPERIAL_SYSTEM + ): + return round((100.0 * L_PER_GAL) / (KM_PER_MI * self.current_value), 1) return self.current_value diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py index 3e69939cbf3..596f56d51a1 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -164,17 +164,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # Initialize the sensor argument if none was provided. # For disk monitoring default to "/" (root) to prevent runtime errors, if argument was not specified. if CONF_ARG not in resource: + resource[CONF_ARG] = "" if resource[CONF_TYPE].startswith("disk_"): resource[CONF_ARG] = "/" - else: - resource[CONF_ARG] = "" # Verify if we can retrieve CPU / processor temperatures. # If not, do not create the entity and add a warning to the log - if resource[CONF_TYPE] == "processor_temperature": - if SystemMonitorSensor.read_cpu_temperature() is None: - _LOGGER.warning("Cannot read CPU / processor temperature information") - continue + if ( + resource[CONF_TYPE] == "processor_temperature" + and SystemMonitorSensor.read_cpu_temperature() is None + ): + _LOGGER.warning("Cannot read CPU / processor temperature information") + continue dev.append(SystemMonitorSensor(resource[CONF_TYPE], resource[CONF_ARG])) @@ -272,23 +273,20 @@ class SystemMonitorSensor(SensorEntity): err.name, ) self._state = STATE_OFF - elif self.type == "network_out" or self.type == "network_in": + elif self.type in ["network_out", "network_in"]: counters = psutil.net_io_counters(pernic=True) if self.argument in counters: counter = counters[self.argument][IO_COUNTER[self.type]] self._state = round(counter / 1024 ** 2, 1) else: self._state = None - elif self.type == "packets_out" or self.type == "packets_in": + elif self.type in ["packets_out", "packets_in"]: counters = psutil.net_io_counters(pernic=True) if self.argument in counters: self._state = counters[self.argument][IO_COUNTER[self.type]] else: self._state = None - elif ( - self.type == "throughput_network_out" - or self.type == "throughput_network_in" - ): + elif self.type in ["throughput_network_out", "throughput_network_in"]: counters = psutil.net_io_counters(pernic=True) if self.argument in counters: counter = counters[self.argument][IO_COUNTER[self.type]] @@ -306,7 +304,7 @@ class SystemMonitorSensor(SensorEntity): self._last_value = counter else: self._state = None - elif self.type == "ipv4_address" or self.type == "ipv6_address": + elif self.type in ["ipv4_address", "ipv6_address"]: addresses = psutil.net_if_addrs() if self.argument in addresses: for addr in addresses[self.argument]: @@ -333,16 +331,9 @@ class SystemMonitorSensor(SensorEntity): temps = psutil.sensors_temperatures() for name, entries in temps.items(): - i = 1 - for entry in entries: + for i, entry in enumerate(entries, start=1): # In case the label is empty (e.g. on Raspberry PI 4), # construct it ourself here based on the sensor key name. - if not entry.label: - _label = f"{name} {i}" - else: - _label = entry.label - + _label = f"{name} {i}" if not entry.label else entry.label if _label in CPU_SENSOR_PREFIXES: return round(entry.current, 1) - - i += 1 diff --git a/homeassistant/components/tado/device_tracker.py b/homeassistant/components/tado/device_tracker.py index 8de938af985..afa8bc6a604 100644 --- a/homeassistant/components/tado/device_tracker.py +++ b/homeassistant/components/tado/device_tracker.py @@ -131,11 +131,10 @@ class TadoDeviceScanner(DeviceScanner): # Find devices that have geofencing enabled, and are currently at home. for mobile_device in tado_json: - if mobile_device.get("location"): - if mobile_device["location"]["atHome"]: - device_id = mobile_device["id"] - device_name = mobile_device["name"] - last_results.append(Device(device_id, device_name)) + if mobile_device.get("location") and mobile_device["location"]["atHome"]: + device_id = mobile_device["id"] + device_name = mobile_device["name"] + last_results.append(Device(device_id, device_name)) self.last_results = last_results diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index 3cb7e663058..9a55e644e79 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -320,12 +320,12 @@ class TPLinkSmartBulb(LightEntity): light_state_params[LIGHT_STATE_BRIGHTNESS] ) - if light_features.supported_features & SUPPORT_COLOR_TEMP: - if ( - light_state_params.get(LIGHT_STATE_COLOR_TEMP) is not None - and light_state_params[LIGHT_STATE_COLOR_TEMP] != 0 - ): - color_temp = kelvin_to_mired(light_state_params[LIGHT_STATE_COLOR_TEMP]) + if ( + light_features.supported_features & SUPPORT_COLOR_TEMP + and light_state_params.get(LIGHT_STATE_COLOR_TEMP) is not None + and light_state_params[LIGHT_STATE_COLOR_TEMP] != 0 + ): + color_temp = kelvin_to_mired(light_state_params[LIGHT_STATE_COLOR_TEMP]) if light_features.supported_features & SUPPORT_COLOR: hue_saturation = ( diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index dddb4d0e5e3..9842184e2ee 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -205,10 +205,13 @@ class UniFiClientTracker(UniFiClient, ScannerEntity): elif not self.heartbeat_check: self.schedule_update = True - elif not self.client.event and self.client.last_updated == SOURCE_DATA: - if self.is_wired == self.client.is_wired: - self._is_connected = True - self.schedule_update = True + elif ( + not self.client.event + and self.client.last_updated == SOURCE_DATA + and self.is_wired == self.client.is_wired + ): + self._is_connected = True + self.schedule_update = True if self.schedule_update: self.schedule_update = False diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index da3139317d1..f04acaaec87 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -279,10 +279,11 @@ class UniFiBlockClientSwitch(UniFiClient, SwitchEntity): @callback def async_update_callback(self) -> None: """Update the clients state.""" - if self.client.last_updated == SOURCE_EVENT: - - if self.client.event.event in CLIENT_BLOCKED + CLIENT_UNBLOCKED: - self._is_blocked = self.client.event.event in CLIENT_BLOCKED + if ( + self.client.last_updated == SOURCE_EVENT + and self.client.event.event in CLIENT_BLOCKED + CLIENT_UNBLOCKED + ): + self._is_blocked = self.client.event.event in CLIENT_BLOCKED super().async_update_callback() diff --git a/homeassistant/components/uvc/camera.py b/homeassistant/components/uvc/camera.py index 367a3915e6d..6bbd868a8bd 100644 --- a/homeassistant/components/uvc/camera.py +++ b/homeassistant/components/uvc/camera.py @@ -129,11 +129,9 @@ class UnifiVideoCamera(Camera): if "recordingIndicator" in self._caminfo: recording_state = self._caminfo["recordingIndicator"] - return ( - self._caminfo["recordingSettings"]["fullTimeRecordEnabled"] - or recording_state == "MOTION_INPROGRESS" - or recording_state == "MOTION_FINISHED" - ) + return self._caminfo["recordingSettings"][ + "fullTimeRecordEnabled" + ] or recording_state in ["MOTION_INPROGRESS", "MOTION_FINISHED"] @property def motion_detection_enabled(self): @@ -198,9 +196,8 @@ class UnifiVideoCamera(Camera): def camera_image(self): """Return the image of this camera.""" - if not self._camera: - if not self._login(): - return + if not self._camera and not self._login(): + return def _get_image(retry=True): try: diff --git a/homeassistant/components/wilight/fan.py b/homeassistant/components/wilight/fan.py index 49402ecb911..e55413926ac 100644 --- a/homeassistant/components/wilight/fan.py +++ b/homeassistant/components/wilight/fan.py @@ -80,9 +80,12 @@ class WiLightFan(WiLightDevice, FanEntity): @property def percentage(self) -> int | None: """Return the current speed percentage.""" - if "direction" in self._status: - if self._status["direction"] == WL_DIRECTION_OFF: - return 0 + if ( + "direction" in self._status + and self._status["direction"] == WL_DIRECTION_OFF + ): + return 0 + wl_speed = self._status.get("speed") if wl_speed is None: return None @@ -96,9 +99,11 @@ class WiLightFan(WiLightDevice, FanEntity): @property def current_direction(self) -> str: """Return the current direction of the fan.""" - if "direction" in self._status: - if self._status["direction"] != WL_DIRECTION_OFF: - self._direction = self._status["direction"] + if ( + "direction" in self._status + and self._status["direction"] != WL_DIRECTION_OFF + ): + self._direction = self._status["direction"] return self._direction async def async_turn_on( @@ -119,9 +124,11 @@ class WiLightFan(WiLightDevice, FanEntity): if percentage == 0: await self._client.set_fan_direction(self._index, WL_DIRECTION_OFF) return - if "direction" in self._status: - if self._status["direction"] == WL_DIRECTION_OFF: - await self._client.set_fan_direction(self._index, self._direction) + if ( + "direction" in self._status + and self._status["direction"] == WL_DIRECTION_OFF + ): + await self._client.set_fan_direction(self._index, self._direction) wl_speed = percentage_to_ordered_list_item(ORDERED_NAMED_FAN_SPEEDS, percentage) await self._client.set_fan_speed(self._index, wl_speed) diff --git a/homeassistant/components/wink/binary_sensor.py b/homeassistant/components/wink/binary_sensor.py index ea864e912f0..6a5977c1dc2 100644 --- a/homeassistant/components/wink/binary_sensor.py +++ b/homeassistant/components/wink/binary_sensor.py @@ -40,9 +40,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for sensor in pywink.get_sensors(): _id = sensor.object_id() + sensor.name() - if _id not in hass.data[DOMAIN]["unique_ids"]: - if sensor.capability() in SENSOR_TYPES: - add_entities([WinkBinarySensorEntity(sensor, hass)]) + if ( + _id not in hass.data[DOMAIN]["unique_ids"] + and sensor.capability() in SENSOR_TYPES + ): + add_entities([WinkBinarySensorEntity(sensor, hass)]) for key in pywink.get_keys(): _id = key.object_id() + key.name() diff --git a/homeassistant/components/wink/sensor.py b/homeassistant/components/wink/sensor.py index 8d60c21c118..f640a24def2 100644 --- a/homeassistant/components/wink/sensor.py +++ b/homeassistant/components/wink/sensor.py @@ -19,9 +19,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for sensor in pywink.get_sensors(): _id = sensor.object_id() + sensor.name() - if _id not in hass.data[DOMAIN]["unique_ids"]: - if sensor.capability() in SENSOR_TYPES: - add_entities([WinkSensorEntity(sensor, hass)]) + if ( + _id not in hass.data[DOMAIN]["unique_ids"] + and sensor.capability() in SENSOR_TYPES + ): + add_entities([WinkSensorEntity(sensor, hass)]) for eggtray in pywink.get_eggtrays(): _id = eggtray.object_id() + eggtray.name() diff --git a/homeassistant/components/worxlandroid/sensor.py b/homeassistant/components/worxlandroid/sensor.py index 9be3afabc9f..e7600670c52 100644 --- a/homeassistant/components/worxlandroid/sensor.py +++ b/homeassistant/components/worxlandroid/sensor.py @@ -127,9 +127,8 @@ class WorxLandroidSensor(SensorEntity): def get_error(obj): """Get the mower error.""" for i, err in enumerate(obj["allarmi"]): - if i != 2: # ignore wire bounce errors - if err == 1: - return ERROR_STATE[i] + if i != 2 and err == 1: # ignore wire bounce errors + return ERROR_STATE[i] return None From db355f9b233fbe4be0cf1750db931460134811fd Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 10:58:38 +0100 Subject: [PATCH 644/831] Merge of nested IF-IF cases - A-C (#48365) --- homeassistant/components/agent_dvr/camera.py | 8 ++++---- .../components/alarmdecoder/config_flow.py | 18 ++++++++++++------ homeassistant/components/alexa/handlers.py | 5 ++--- homeassistant/components/apprise/notify.py | 9 ++++----- homeassistant/components/asuswrt/router.py | 5 ++--- homeassistant/components/cast/media_player.py | 12 ++++++------ homeassistant/components/climacell/weather.py | 9 ++++----- 7 files changed, 34 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/agent_dvr/camera.py b/homeassistant/components/agent_dvr/camera.py index 7904f98216b..24cd5dbb92c 100644 --- a/homeassistant/components/agent_dvr/camera.py +++ b/homeassistant/components/agent_dvr/camera.py @@ -102,10 +102,10 @@ class AgentCamera(MjpegCamera): _LOGGER.debug("%s reacquired", self._name) self._removed = False except AgentError: - if self.device.client.is_available: # server still available - camera error - if not self._removed: - _LOGGER.error("%s lost", self._name) - self._removed = True + # server still available - camera error + if self.device.client.is_available and not self._removed: + _LOGGER.error("%s lost", self._name) + self._removed = True @property def extra_state_attributes(self): diff --git a/homeassistant/components/alarmdecoder/config_flow.py b/homeassistant/components/alarmdecoder/config_flow.py index a82b84b60d1..08c8052c04b 100644 --- a/homeassistant/components/alarmdecoder/config_flow.py +++ b/homeassistant/components/alarmdecoder/config_flow.py @@ -349,12 +349,18 @@ def _device_already_added(current_entries, user_input, protocol): entry_path = entry.data.get(CONF_DEVICE_PATH) entry_baud = entry.data.get(CONF_DEVICE_BAUD) - if protocol == PROTOCOL_SOCKET: - if user_host == entry_host and user_port == entry_port: - return True + if ( + protocol == PROTOCOL_SOCKET + and user_host == entry_host + and user_port == entry_port + ): + return True - if protocol == PROTOCOL_SERIAL: - if user_baud == entry_baud and user_path == entry_path: - return True + if ( + protocol == PROTOCOL_SERIAL + and user_baud == entry_baud + and user_path == entry_path + ): + return True return False diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index dce4f9f2210..cee4cda562d 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -653,10 +653,9 @@ def temperature_from_object(hass, temp_obj, interval=False): if temp_obj["scale"] == "FAHRENHEIT": from_unit = TEMP_FAHRENHEIT - elif temp_obj["scale"] == "KELVIN": + elif temp_obj["scale"] == "KELVIN" and not interval: # convert to Celsius if absolute temperature - if not interval: - temp -= 273.15 + temp -= 273.15 return convert_temperature(temp, from_unit, to_unit, interval) diff --git a/homeassistant/components/apprise/notify.py b/homeassistant/components/apprise/notify.py index 95bf11ddc09..5f4a6b66643 100644 --- a/homeassistant/components/apprise/notify.py +++ b/homeassistant/components/apprise/notify.py @@ -42,11 +42,10 @@ def get_service(hass, config, discovery_info=None): _LOGGER.error("Invalid Apprise config url provided") return None - if config.get(CONF_URL): - # Ordered list of URLs - if not a_obj.add(config[CONF_URL]): - _LOGGER.error("Invalid Apprise URL(s) supplied") - return None + # Ordered list of URLs + if config.get(CONF_URL) and not a_obj.add(config[CONF_URL]): + _LOGGER.error("Invalid Apprise URL(s) supplied") + return None return AppriseNotificationService(a_obj) diff --git a/homeassistant/components/asuswrt/router.py b/homeassistant/components/asuswrt/router.py index 550e4c8fc16..c5880ea11bb 100644 --- a/homeassistant/components/asuswrt/router.py +++ b/homeassistant/components/asuswrt/router.py @@ -341,9 +341,8 @@ class AsusWrtRouter: async def close(self) -> None: """Close the connection.""" - if self._api is not None: - if self._protocol == PROTOCOL_TELNET: - self._api.connection.disconnect() + if self._api is not None and self._protocol == PROTOCOL_TELNET: + self._api.connection.disconnect() self._api = None for func in self._on_close: diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 540f3263e1e..7c2a696027f 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -706,15 +706,15 @@ class CastDevice(MediaPlayerEntity): support = SUPPORT_CAST media_status = self._media_status()[0] - if self.cast_status: - if self.cast_status.volume_control_type != VOLUME_CONTROL_TYPE_FIXED: - support |= SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET + if ( + self.cast_status + and self.cast_status.volume_control_type != VOLUME_CONTROL_TYPE_FIXED + ): + support |= SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET if media_status: if media_status.supports_queue_next: - support |= SUPPORT_PREVIOUS_TRACK - if media_status.supports_queue_next: - support |= SUPPORT_NEXT_TRACK + support |= SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK if media_status.supports_seek: support |= SUPPORT_SEEK diff --git a/homeassistant/components/climacell/weather.py b/homeassistant/components/climacell/weather.py index e6485f90936..b9da5431dd0 100644 --- a/homeassistant/components/climacell/weather.py +++ b/homeassistant/components/climacell/weather.py @@ -274,13 +274,12 @@ class ClimaCellWeatherEntity(ClimaCellEntity, WeatherEntity): ), temp_low, ) - elif self.forecast_type == NOWCAST: + elif self.forecast_type == NOWCAST and precipitation: # Precipitation is forecasted in CONF_TIMESTEP increments but in a # per hour rate, so value needs to be converted to an amount. - if precipitation: - precipitation = ( - precipitation / 60 * self._config_entry.options[CONF_TIMESTEP] - ) + precipitation = ( + precipitation / 60 * self._config_entry.options[CONF_TIMESTEP] + ) forecasts.append( _forecast_dict( From 786023fce41dc4128552bedc743fd176f0539a20 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 11:30:29 +0100 Subject: [PATCH 645/831] Merge of nested IF-IF cases - H-J (#48368) --- .../components/here_travel_time/sensor.py | 5 +- .../components/hitron_coda/device_tracker.py | 10 ++-- .../homekit_controller/device_trigger.py | 52 +++++++++---------- .../homekit_controller/media_player.py | 8 +-- homeassistant/components/hue/light.py | 13 +++-- homeassistant/components/hyperion/light.py | 10 ++-- .../components/image_processing/__init__.py | 9 ++-- .../components/imap_email_content/sensor.py | 8 +-- homeassistant/components/ios/notify.py | 10 ++-- .../components/isy994/binary_sensor.py | 20 +++---- .../components/joaoapps_join/__init__.py | 7 ++- .../components/joaoapps_join/notify.py | 7 ++- 12 files changed, 81 insertions(+), 78 deletions(-) diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index e4fbc0b2a89..4b8f765d08a 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -258,9 +258,8 @@ class HERETravelTimeSensor(SensorEntity): @property def state(self) -> str | None: """Return the state of the sensor.""" - if self._here_data.traffic_mode: - if self._here_data.traffic_time is not None: - return str(round(self._here_data.traffic_time / 60)) + if self._here_data.traffic_mode and self._here_data.traffic_time is not None: + return str(round(self._here_data.traffic_time / 60)) if self._here_data.base_time is not None: return str(round(self._here_data.base_time / 60)) diff --git a/homeassistant/components/hitron_coda/device_tracker.py b/homeassistant/components/hitron_coda/device_tracker.py index 4634c6e378a..cbd6b7eeff8 100644 --- a/homeassistant/components/hitron_coda/device_tracker.py +++ b/homeassistant/components/hitron_coda/device_tracker.py @@ -74,10 +74,9 @@ class HitronCODADeviceScanner(DeviceScanner): def get_device_name(self, device): """Return the name of the device with the given MAC address.""" - name = next( + return next( (result.name for result in self.last_results if result.mac == device), None ) - return name def _login(self): """Log in to the router. This is required for subsequent api calls.""" @@ -103,10 +102,9 @@ class HitronCODADeviceScanner(DeviceScanner): """Get ARP from router.""" _LOGGER.info("Fetching") - if self._userid is None: - if not self._login(): - _LOGGER.error("Could not obtain a user ID from the router") - return False + if self._userid is None and not self._login(): + _LOGGER.error("Could not obtain a user ID from the router") + return False last_results = [] # doing a request diff --git a/homeassistant/components/homekit_controller/device_trigger.py b/homeassistant/components/homekit_controller/device_trigger.py index 31bfcc18d52..59cc32c0b1b 100644 --- a/homeassistant/components/homekit_controller/device_trigger.py +++ b/homeassistant/components/homekit_controller/device_trigger.py @@ -99,9 +99,11 @@ def enumerate_stateless_switch(service): # A stateless switch that has a SERVICE_LABEL_INDEX is part of a group # And is handled separately - if service.has(CharacteristicsTypes.SERVICE_LABEL_INDEX): - if len(service.linked) > 0: - return [] + if ( + service.has(CharacteristicsTypes.SERVICE_LABEL_INDEX) + and len(service.linked) > 0 + ): + return [] char = service[CharacteristicsTypes.INPUT_EVENT] @@ -109,17 +111,15 @@ def enumerate_stateless_switch(service): # manufacturer might not - clamp options to what they say. all_values = clamp_enum_to_char(InputEventValues, char) - results = [] - for event_type in all_values: - results.append( - { - "characteristic": char.iid, - "value": event_type, - "type": "button1", - "subtype": HK_TO_HA_INPUT_EVENT_VALUES[event_type], - } - ) - return results + return [ + { + "characteristic": char.iid, + "value": event_type, + "type": "button1", + "subtype": HK_TO_HA_INPUT_EVENT_VALUES[event_type], + } + for event_type in all_values + ] def enumerate_stateless_switch_group(service): @@ -234,20 +234,16 @@ async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: device = hass.data[TRIGGERS][device_id] - triggers = [] - - for trigger, subtype in device.async_get_triggers(): - triggers.append( - { - CONF_PLATFORM: "device", - CONF_DEVICE_ID: device_id, - CONF_DOMAIN: DOMAIN, - CONF_TYPE: trigger, - CONF_SUBTYPE: subtype, - } - ) - - return triggers + return [ + { + CONF_PLATFORM: "device", + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_TYPE: trigger, + CONF_SUBTYPE: subtype, + } + for trigger, subtype in device.async_get_triggers() + ] async def async_attach_trigger( diff --git a/homeassistant/components/homekit_controller/media_player.py b/homeassistant/components/homekit_controller/media_player.py index 6dfa8720ee5..71bde5f0af9 100644 --- a/homeassistant/components/homekit_controller/media_player.py +++ b/homeassistant/components/homekit_controller/media_player.py @@ -93,9 +93,11 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): if TargetMediaStateValues.STOP in self.supported_media_states: features |= SUPPORT_STOP - if self.service.has(CharacteristicsTypes.REMOTE_KEY): - if RemoteKeyValues.PLAY_PAUSE in self.supported_remote_keys: - features |= SUPPORT_PAUSE | SUPPORT_PLAY + if ( + self.service.has(CharacteristicsTypes.REMOTE_KEY) + and RemoteKeyValues.PLAY_PAUSE in self.supported_remote_keys + ): + features |= SUPPORT_PAUSE | SUPPORT_PLAY return features diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index 8adde810fbe..3d193734005 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -229,7 +229,7 @@ async def async_safe_fetch(bridge, fetch_method): except aiohue.Unauthorized as err: await bridge.handle_unauthorized_error() raise UpdateFailed("Unauthorized") from err - except (aiohue.AiohueException,) as err: + except aiohue.AiohueException as err: raise UpdateFailed(f"Hue error: {err}") from err @@ -297,12 +297,11 @@ class HueLight(CoordinatorEntity, LightEntity): "bulb in the Philips Hue App." ) _LOGGER.warning(err, self.name) - if self.gamut: - if not color.check_valid_gamut(self.gamut): - err = "Color gamut of %s: %s, not valid, setting gamut to None." - _LOGGER.warning(err, self.name, str(self.gamut)) - self.gamut_typ = GAMUT_TYPE_UNAVAILABLE - self.gamut = None + if self.gamut and not color.check_valid_gamut(self.gamut): + err = "Color gamut of %s: %s, not valid, setting gamut to None." + _LOGGER.warning(err, self.name, str(self.gamut)) + self.gamut_typ = GAMUT_TYPE_UNAVAILABLE + self.gamut = None @property def unique_id(self): diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index 36c3d836bf3..248a45ec753 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -241,8 +241,9 @@ class HyperionBaseLight(LightEntity): if ATTR_BRIGHTNESS in kwargs: brightness = kwargs[ATTR_BRIGHTNESS] for item in self._client.adjustment or []: - if const.KEY_ID in item: - if not await self._client.async_send_set_adjustment( + if ( + const.KEY_ID in item + and not await self._client.async_send_set_adjustment( **{ const.KEY_ADJUSTMENT: { const.KEY_BRIGHTNESS: int( @@ -251,8 +252,9 @@ class HyperionBaseLight(LightEntity): const.KEY_ID: item[const.KEY_ID], } } - ): - return + ) + ): + return # == Set an external source if ( diff --git a/homeassistant/components/image_processing/__init__.py b/homeassistant/components/image_processing/__init__.py index 1320629aeb4..58a6582e33c 100644 --- a/homeassistant/components/image_processing/__init__.py +++ b/homeassistant/components/image_processing/__init__.py @@ -208,9 +208,12 @@ class ImageProcessingFaceEntity(ImageProcessingEntity): """ # Send events for face in faces: - if ATTR_CONFIDENCE in face and self.confidence: - if face[ATTR_CONFIDENCE] < self.confidence: - continue + if ( + ATTR_CONFIDENCE in face + and self.confidence + and face[ATTR_CONFIDENCE] < self.confidence + ): + continue face.update({ATTR_ENTITY_ID: self.entity_id}) self.hass.async_add_job(self.hass.bus.async_fire, EVENT_DETECT_FACE, face) diff --git a/homeassistant/components/imap_email_content/sensor.py b/homeassistant/components/imap_email_content/sensor.py index 22c395a7c8f..cdd47d68d76 100644 --- a/homeassistant/components/imap_email_content/sensor.py +++ b/homeassistant/components/imap_email_content/sensor.py @@ -220,9 +220,11 @@ class EmailContentSensor(SensorEntity): elif part.get_content_type() == "text/html": if message_html is None: message_html = part.get_payload() - elif part.get_content_type().startswith("text"): - if message_untyped_text is None: - message_untyped_text = part.get_payload() + elif ( + part.get_content_type().startswith("text") + and message_untyped_text is None + ): + message_untyped_text = part.get_payload() if message_text is not None: return message_text diff --git a/homeassistant/components/ios/notify.py b/homeassistant/components/ios/notify.py index f9c682bf527..853fb0d479a 100644 --- a/homeassistant/components/ios/notify.py +++ b/homeassistant/components/ios/notify.py @@ -69,10 +69,12 @@ class iOSNotificationService(BaseNotificationService): """Send a message to the Lambda APNS gateway.""" data = {ATTR_MESSAGE: message} - if kwargs.get(ATTR_TITLE) is not None: - # Remove default title from notifications. - if kwargs.get(ATTR_TITLE) != ATTR_TITLE_DEFAULT: - data[ATTR_TITLE] = kwargs.get(ATTR_TITLE) + # Remove default title from notifications. + if ( + kwargs.get(ATTR_TITLE) is not None + and kwargs.get(ATTR_TITLE) != ATTR_TITLE_DEFAULT + ): + data[ATTR_TITLE] = kwargs.get(ATTR_TITLE) targets = kwargs.get(ATTR_TARGET) diff --git a/homeassistant/components/isy994/binary_sensor.py b/homeassistant/components/isy994/binary_sensor.py index 3b0bb9fd144..57b134e0900 100644 --- a/homeassistant/components/isy994/binary_sensor.py +++ b/homeassistant/components/isy994/binary_sensor.py @@ -281,15 +281,17 @@ class ISYInsteonBinarySensorEntity(ISYBinarySensorEntity): """ self._negative_node = child - if self._negative_node.status != ISY_VALUE_UNKNOWN: - # If the negative node has a value, it means the negative node is - # in use for this device. Next we need to check to see if the - # negative and positive nodes disagree on the state (both ON or - # both OFF). - if self._negative_node.status == self._node.status: - # The states disagree, therefore we cannot determine the state - # of the sensor until we receive our first ON event. - self._computed_state = None + # If the negative node has a value, it means the negative node is + # in use for this device. Next we need to check to see if the + # negative and positive nodes disagree on the state (both ON or + # both OFF). + if ( + self._negative_node.status != ISY_VALUE_UNKNOWN + and self._negative_node.status == self._node.status + ): + # The states disagree, therefore we cannot determine the state + # of the sensor until we receive our first ON event. + self._computed_state = None def _negative_node_control_handler(self, event: object) -> None: """Handle an "On" control event from the "negative" node.""" diff --git a/homeassistant/components/joaoapps_join/__init__.py b/homeassistant/components/joaoapps_join/__init__.py index a65a7ffd7fe..1331afbfe97 100644 --- a/homeassistant/components/joaoapps_join/__init__.py +++ b/homeassistant/components/joaoapps_join/__init__.py @@ -121,10 +121,9 @@ def setup(hass, config): device_names = device.get(CONF_DEVICE_NAMES) name = device.get(CONF_NAME) name = f"{name.lower().replace(' ', '_')}_" if name else "" - if api_key: - if not get_devices(api_key): - _LOGGER.error("Error connecting to Join, check API key") - return False + if api_key and not get_devices(api_key): + _LOGGER.error("Error connecting to Join, check API key") + return False if device_id is None and device_ids is None and device_names is None: _LOGGER.error( "No device was provided. Please specify device_id" diff --git a/homeassistant/components/joaoapps_join/notify.py b/homeassistant/components/joaoapps_join/notify.py index 7ba089e5dab..06cad45bdde 100644 --- a/homeassistant/components/joaoapps_join/notify.py +++ b/homeassistant/components/joaoapps_join/notify.py @@ -35,10 +35,9 @@ def get_service(hass, config, discovery_info=None): device_id = config.get(CONF_DEVICE_ID) device_ids = config.get(CONF_DEVICE_IDS) device_names = config.get(CONF_DEVICE_NAMES) - if api_key: - if not get_devices(api_key): - _LOGGER.error("Error connecting to Join. Check the API key") - return False + if api_key and not get_devices(api_key): + _LOGGER.error("Error connecting to Join. Check the API key") + return False if device_id is None and device_ids is None and device_names is None: _LOGGER.error( "No device was provided. Please specify device_id" From 0d595a28457f89948c2a697bfc9bf576f1af5e60 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 12:39:37 +0100 Subject: [PATCH 646/831] Merge of nested IF-IF cases - E-G (#48367) --- homeassistant/components/ebusd/__init__.py | 5 +- .../eddystone_temperature/sensor.py | 11 +- homeassistant/components/emby/media_player.py | 13 +- homeassistant/components/emoncms/sensor.py | 10 +- .../components/emulated_hue/hue_api.py | 101 ++++--- homeassistant/components/fail2ban/sensor.py | 8 +- homeassistant/components/fan/__init__.py | 5 +- homeassistant/components/garadget/cover.py | 15 +- .../components/generic_thermostat/climate.py | 41 ++- homeassistant/components/glances/sensor.py | 259 +++++++++--------- .../components/google_assistant/trait.py | 23 +- .../components/google_wifi/sensor.py | 7 +- .../components/gpmdp/media_player.py | 5 +- 13 files changed, 257 insertions(+), 246 deletions(-) diff --git a/homeassistant/components/ebusd/__init__.py b/homeassistant/components/ebusd/__init__.py index 00c40344d6e..beb8abd6289 100644 --- a/homeassistant/components/ebusd/__init__.py +++ b/homeassistant/components/ebusd/__init__.py @@ -115,8 +115,7 @@ class EbusdData: try: _LOGGER.debug("Opening socket to ebusd %s", name) command_result = ebusdpy.write(self._address, self._circuit, name, value) - if command_result is not None: - if "done" not in command_result: - _LOGGER.warning("Write command failed: %s", name) + if command_result is not None and "done" not in command_result: + _LOGGER.warning("Write command failed: %s", name) except RuntimeError as err: _LOGGER.error(err) diff --git a/homeassistant/components/eddystone_temperature/sensor.py b/homeassistant/components/eddystone_temperature/sensor.py index 70afde4ffb1..28711821f50 100644 --- a/homeassistant/components/eddystone_temperature/sensor.py +++ b/homeassistant/components/eddystone_temperature/sensor.py @@ -170,10 +170,13 @@ class Monitor: ) for dev in self.devices: - if dev.namespace == namespace and dev.instance == instance: - if dev.temperature != temperature: - dev.temperature = temperature - dev.schedule_update_ha_state() + if ( + dev.namespace == namespace + and dev.instance == instance + and dev.temperature != temperature + ): + dev.temperature = temperature + dev.schedule_update_ha_state() def stop(self): """Signal runner to stop and join thread.""" diff --git a/homeassistant/components/emby/media_player.py b/homeassistant/components/emby/media_player.py index 1cbc893f98b..5656a1f1486 100644 --- a/homeassistant/components/emby/media_player.py +++ b/homeassistant/components/emby/media_player.py @@ -96,12 +96,13 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= active_emby_devices[dev_id] = new new_devices.append(new) - elif dev_id in inactive_emby_devices: - if emby.devices[dev_id].state != "Off": - add = inactive_emby_devices.pop(dev_id) - active_emby_devices[dev_id] = add - _LOGGER.debug("Showing %s, item: %s", dev_id, add) - add.set_available(True) + elif ( + dev_id in inactive_emby_devices and emby.devices[dev_id].state != "Off" + ): + add = inactive_emby_devices.pop(dev_id) + active_emby_devices[dev_id] = add + _LOGGER.debug("Showing %s, item: %s", dev_id, add) + add.set_available(True) if new_devices: _LOGGER.debug("Adding new devices: %s", new_devices) diff --git a/homeassistant/components/emoncms/sensor.py b/homeassistant/components/emoncms/sensor.py index 4913f6340cc..bfc86db387e 100644 --- a/homeassistant/components/emoncms/sensor.py +++ b/homeassistant/components/emoncms/sensor.py @@ -92,13 +92,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for elem in data.data: - if exclude_feeds is not None: - if int(elem["id"]) in exclude_feeds: - continue + if exclude_feeds is not None and int(elem["id"]) in exclude_feeds: + continue - if include_only_feeds is not None: - if int(elem["id"]) not in include_only_feeds: - continue + if include_only_feeds is not None and int(elem["id"]) not in include_only_feeds: + continue name = None if sensor_names is not None: diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index 647d9db1335..f97636a46c0 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -439,11 +439,13 @@ class HueOneLightChangeView(HomeAssistantView): # saturation and color temp if entity.domain == light.DOMAIN: if parsed[STATE_ON]: - if entity_features & SUPPORT_BRIGHTNESS: - if parsed[STATE_BRIGHTNESS] is not None: - data[ATTR_BRIGHTNESS] = hue_brightness_to_hass( - parsed[STATE_BRIGHTNESS] - ) + if ( + entity_features & SUPPORT_BRIGHTNESS + and parsed[STATE_BRIGHTNESS] is not None + ): + data[ATTR_BRIGHTNESS] = hue_brightness_to_hass( + parsed[STATE_BRIGHTNESS] + ) if entity_features & SUPPORT_COLOR: if any((parsed[STATE_HUE], parsed[STATE_SATURATION])): @@ -466,13 +468,17 @@ class HueOneLightChangeView(HomeAssistantView): if parsed[STATE_XY] is not None: data[ATTR_XY_COLOR] = parsed[STATE_XY] - if entity_features & SUPPORT_COLOR_TEMP: - if parsed[STATE_COLOR_TEMP] is not None: - data[ATTR_COLOR_TEMP] = parsed[STATE_COLOR_TEMP] + if ( + entity_features & SUPPORT_COLOR_TEMP + and parsed[STATE_COLOR_TEMP] is not None + ): + data[ATTR_COLOR_TEMP] = parsed[STATE_COLOR_TEMP] - if entity_features & SUPPORT_TRANSITION: - if parsed[STATE_TRANSITON] is not None: - data[ATTR_TRANSITION] = parsed[STATE_TRANSITON] / 10 + if ( + entity_features & SUPPORT_TRANSITION + and parsed[STATE_TRANSITON] is not None + ): + data[ATTR_TRANSITION] = parsed[STATE_TRANSITON] / 10 # If the requested entity is a script, add some variables elif entity.domain == script.DOMAIN: @@ -489,11 +495,13 @@ class HueOneLightChangeView(HomeAssistantView): # only setting the temperature service = None - if entity_features & SUPPORT_TARGET_TEMPERATURE: - if parsed[STATE_BRIGHTNESS] is not None: - domain = entity.domain - service = SERVICE_SET_TEMPERATURE - data[ATTR_TEMPERATURE] = parsed[STATE_BRIGHTNESS] + if ( + entity_features & SUPPORT_TARGET_TEMPERATURE + and parsed[STATE_BRIGHTNESS] is not None + ): + domain = entity.domain + service = SERVICE_SET_TEMPERATURE + data[ATTR_TEMPERATURE] = parsed[STATE_BRIGHTNESS] # If the requested entity is a humidifier, set the humidity elif entity.domain == humidifier.DOMAIN: @@ -505,43 +513,48 @@ class HueOneLightChangeView(HomeAssistantView): # If the requested entity is a media player, convert to volume elif entity.domain == media_player.DOMAIN: - if entity_features & SUPPORT_VOLUME_SET: - if parsed[STATE_BRIGHTNESS] is not None: - turn_on_needed = True - domain = entity.domain - service = SERVICE_VOLUME_SET - # Convert 0-100 to 0.0-1.0 - data[ATTR_MEDIA_VOLUME_LEVEL] = parsed[STATE_BRIGHTNESS] / 100.0 + if ( + entity_features & SUPPORT_VOLUME_SET + and parsed[STATE_BRIGHTNESS] is not None + ): + turn_on_needed = True + domain = entity.domain + service = SERVICE_VOLUME_SET + # Convert 0-100 to 0.0-1.0 + data[ATTR_MEDIA_VOLUME_LEVEL] = parsed[STATE_BRIGHTNESS] / 100.0 # If the requested entity is a cover, convert to open_cover/close_cover elif entity.domain == cover.DOMAIN: domain = entity.domain + service = SERVICE_CLOSE_COVER if service == SERVICE_TURN_ON: service = SERVICE_OPEN_COVER - else: - service = SERVICE_CLOSE_COVER - if entity_features & SUPPORT_SET_POSITION: - if parsed[STATE_BRIGHTNESS] is not None: - domain = entity.domain - service = SERVICE_SET_COVER_POSITION - data[ATTR_POSITION] = parsed[STATE_BRIGHTNESS] + if ( + entity_features & SUPPORT_SET_POSITION + and parsed[STATE_BRIGHTNESS] is not None + ): + domain = entity.domain + service = SERVICE_SET_COVER_POSITION + data[ATTR_POSITION] = parsed[STATE_BRIGHTNESS] # If the requested entity is a fan, convert to speed - elif entity.domain == fan.DOMAIN: - if entity_features & SUPPORT_SET_SPEED: - if parsed[STATE_BRIGHTNESS] is not None: - domain = entity.domain - # Convert 0-100 to a fan speed - brightness = parsed[STATE_BRIGHTNESS] - if brightness == 0: - data[ATTR_SPEED] = SPEED_OFF - elif 0 < brightness <= 33.3: - data[ATTR_SPEED] = SPEED_LOW - elif 33.3 < brightness <= 66.6: - data[ATTR_SPEED] = SPEED_MEDIUM - elif 66.6 < brightness <= 100: - data[ATTR_SPEED] = SPEED_HIGH + elif ( + entity.domain == fan.DOMAIN + and entity_features & SUPPORT_SET_SPEED + and parsed[STATE_BRIGHTNESS] is not None + ): + domain = entity.domain + # Convert 0-100 to a fan speed + brightness = parsed[STATE_BRIGHTNESS] + if brightness == 0: + data[ATTR_SPEED] = SPEED_OFF + elif 0 < brightness <= 33.3: + data[ATTR_SPEED] = SPEED_LOW + elif 33.3 < brightness <= 66.6: + data[ATTR_SPEED] = SPEED_MEDIUM + elif 66.6 < brightness <= 100: + data[ATTR_SPEED] = SPEED_HIGH # Map the off command to on if entity.domain in config.off_maps_to_on_domains: diff --git a/homeassistant/components/fail2ban/sensor.py b/homeassistant/components/fail2ban/sensor.py index 87a8460f149..29ac5c3d0b5 100644 --- a/homeassistant/components/fail2ban/sensor.py +++ b/homeassistant/components/fail2ban/sensor.py @@ -90,9 +90,11 @@ class BanSensor(SensorEntity): if len(self.ban_dict[STATE_ALL_BANS]) > 10: self.ban_dict[STATE_ALL_BANS].pop(0) - elif entry[0] == "Unban": - if current_ip in self.ban_dict[STATE_CURRENT_BANS]: - self.ban_dict[STATE_CURRENT_BANS].remove(current_ip) + elif ( + entry[0] == "Unban" + and current_ip in self.ban_dict[STATE_CURRENT_BANS] + ): + self.ban_dict[STATE_CURRENT_BANS].remove(current_ip) if self.ban_dict[STATE_CURRENT_BANS]: self.last_ban = self.ban_dict[STATE_CURRENT_BANS][-1] diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index da2224aaf33..305c99072ab 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -459,9 +459,8 @@ class FanEntity(ToggleEntity): @property def percentage(self) -> int | None: """Return the current speed as a percentage.""" - if not self._implemented_preset_mode: - if self.speed in self.preset_modes: - return None + if not self._implemented_preset_mode and self.speed in self.preset_modes: + return None if not self._implemented_percentage: return self.speed_to_percentage(self.speed) return 0 diff --git a/homeassistant/components/garadget/cover.py b/homeassistant/components/garadget/cover.py index 206aaa6614d..ad89c3a035b 100644 --- a/homeassistant/components/garadget/cover.py +++ b/homeassistant/components/garadget/cover.py @@ -120,9 +120,8 @@ class GaradgetCover(CoverEntity): def __del__(self): """Try to remove token.""" - if self._obtained_token is True: - if self.access_token is not None: - self.remove_token() + if self._obtained_token is True and self.access_token is not None: + self.remove_token() @property def name(self): @@ -239,10 +238,12 @@ class GaradgetCover(CoverEntity): ) self._state = STATE_OFFLINE - if self._state not in [STATE_CLOSING, STATE_OPENING]: - if self._unsub_listener_cover is not None: - self._unsub_listener_cover() - self._unsub_listener_cover = None + if ( + self._state not in [STATE_CLOSING, STATE_OPENING] + and self._unsub_listener_cover is not None + ): + self._unsub_listener_cover() + self._unsub_listener_cover = None def _get_variable(self, var): """Get latest status.""" diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index 3c7959dbf4d..ef3cf11fa1c 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -442,28 +442,27 @@ class GenericThermostat(ClimateEntity, RestoreEntity): if not self._active or self._hvac_mode == HVAC_MODE_OFF: return - if not force and time is None: - # If the `force` argument is True, we - # ignore `min_cycle_duration`. - # If the `time` argument is not none, we were invoked for - # keep-alive purposes, and `min_cycle_duration` is irrelevant. - if self.min_cycle_duration: - if self._is_device_active: - current_state = STATE_ON - else: - current_state = HVAC_MODE_OFF - try: - long_enough = condition.state( - self.hass, - self.heater_entity_id, - current_state, - self.min_cycle_duration, - ) - except ConditionError: - long_enough = False + # If the `force` argument is True, we + # ignore `min_cycle_duration`. + # If the `time` argument is not none, we were invoked for + # keep-alive purposes, and `min_cycle_duration` is irrelevant. + if not force and time is None and self.min_cycle_duration: + if self._is_device_active: + current_state = STATE_ON + else: + current_state = HVAC_MODE_OFF + try: + long_enough = condition.state( + self.hass, + self.heater_entity_id, + current_state, + self.min_cycle_duration, + ) + except ConditionError: + long_enough = False - if not long_enough: - return + if not long_enough: + return too_cold = self._target_temp >= self._cur_temp + self._cold_tolerance too_hot = self._cur_temp >= self._target_temp + self._hot_tolerance diff --git a/homeassistant/components/glances/sensor.py b/homeassistant/components/glances/sensor.py index 52649518b68..bbe045eb232 100644 --- a/homeassistant/components/glances/sensor.py +++ b/homeassistant/components/glances/sensor.py @@ -17,45 +17,44 @@ async def async_setup_entry(hass, config_entry, async_add_entities): for sensor_type, sensor_details in SENSOR_TYPES.items(): if sensor_details[0] not in client.api.data: continue - if sensor_details[0] in client.api.data: - if sensor_details[0] == "fs": - # fs will provide a list of disks attached - for disk in client.api.data[sensor_details[0]]: - dev.append( - GlancesSensor( - client, - name, - disk["mnt_point"], - SENSOR_TYPES[sensor_type][1], - sensor_type, - SENSOR_TYPES[sensor_type], - ) - ) - elif sensor_details[0] == "sensors": - # sensors will provide temp for different devices - for sensor in client.api.data[sensor_details[0]]: - if sensor["type"] == sensor_type: - dev.append( - GlancesSensor( - client, - name, - sensor["label"], - SENSOR_TYPES[sensor_type][1], - sensor_type, - SENSOR_TYPES[sensor_type], - ) - ) - elif client.api.data[sensor_details[0]]: + if sensor_details[0] == "fs": + # fs will provide a list of disks attached + for disk in client.api.data[sensor_details[0]]: dev.append( GlancesSensor( client, name, - "", + disk["mnt_point"], SENSOR_TYPES[sensor_type][1], sensor_type, SENSOR_TYPES[sensor_type], ) ) + elif sensor_details[0] == "sensors": + # sensors will provide temp for different devices + for sensor in client.api.data[sensor_details[0]]: + if sensor["type"] == sensor_type: + dev.append( + GlancesSensor( + client, + name, + sensor["label"], + SENSOR_TYPES[sensor_type][1], + sensor_type, + SENSOR_TYPES[sensor_type], + ) + ) + elif client.api.data[sensor_details[0]]: + dev.append( + GlancesSensor( + client, + name, + "", + SENSOR_TYPES[sensor_type][1], + sensor_type, + SENSOR_TYPES[sensor_type], + ) + ) async_add_entities(dev, True) @@ -139,107 +138,103 @@ class GlancesSensor(SensorEntity): if value is None: return - if value is not None: - if self.sensor_details[0] == "fs": - for var in value["fs"]: - if var["mnt_point"] == self._sensor_name_prefix: - disk = var - break - if self.type == "disk_use_percent": - self._state = disk["percent"] - elif self.type == "disk_use": - self._state = round(disk["used"] / 1024 ** 3, 1) - elif self.type == "disk_free": - try: - self._state = round(disk["free"] / 1024 ** 3, 1) - except KeyError: - self._state = round( - (disk["size"] - disk["used"]) / 1024 ** 3, - 1, - ) - elif self.type == "battery": - for sensor in value["sensors"]: - if sensor["type"] == "battery": - if sensor["label"] == self._sensor_name_prefix: - self._state = sensor["value"] - elif self.type == "fan_speed": - for sensor in value["sensors"]: - if sensor["type"] == "fan_speed": - if sensor["label"] == self._sensor_name_prefix: - self._state = sensor["value"] - elif self.type == "temperature_core": - for sensor in value["sensors"]: - if sensor["type"] == "temperature_core": - if sensor["label"] == self._sensor_name_prefix: - self._state = sensor["value"] - elif self.type == "temperature_hdd": - for sensor in value["sensors"]: - if ( - sensor["type"] == "temperature_hdd" - and sensor["label"] == self._sensor_name_prefix - ): - self._state = sensor["value"] - elif self.type == "memory_use_percent": - self._state = value["mem"]["percent"] - elif self.type == "memory_use": - self._state = round(value["mem"]["used"] / 1024 ** 2, 1) - elif self.type == "memory_free": - self._state = round(value["mem"]["free"] / 1024 ** 2, 1) - elif self.type == "swap_use_percent": - self._state = value["memswap"]["percent"] - elif self.type == "swap_use": - self._state = round(value["memswap"]["used"] / 1024 ** 3, 1) - elif self.type == "swap_free": - self._state = round(value["memswap"]["free"] / 1024 ** 3, 1) - elif self.type == "processor_load": - # Windows systems don't provide load details + if self.sensor_details[0] == "fs": + for var in value["fs"]: + if var["mnt_point"] == self._sensor_name_prefix: + disk = var + break + if self.type == "disk_free": try: - self._state = value["load"]["min15"] + self._state = round(disk["free"] / 1024 ** 3, 1) except KeyError: - self._state = value["cpu"]["total"] - elif self.type == "process_running": - self._state = value["processcount"]["running"] - elif self.type == "process_total": - self._state = value["processcount"]["total"] - elif self.type == "process_thread": - self._state = value["processcount"]["thread"] - elif self.type == "process_sleeping": - self._state = value["processcount"]["sleeping"] - elif self.type == "cpu_use_percent": - self._state = value["quicklook"]["cpu"] - elif self.type == "docker_active": - count = 0 - try: - for container in value["docker"]["containers"]: - if ( - container["Status"] == "running" - or "Up" in container["Status"] - ): - count += 1 - self._state = count - except KeyError: - self._state = count - elif self.type == "docker_cpu_use": - cpu_use = 0.0 - try: - for container in value["docker"]["containers"]: - if ( - container["Status"] == "running" - or "Up" in container["Status"] - ): - cpu_use += container["cpu"]["total"] - self._state = round(cpu_use, 1) - except KeyError: - self._state = STATE_UNAVAILABLE - elif self.type == "docker_memory_use": - mem_use = 0.0 - try: - for container in value["docker"]["containers"]: - if ( - container["Status"] == "running" - or "Up" in container["Status"] - ): - mem_use += container["memory"]["usage"] - self._state = round(mem_use / 1024 ** 2, 1) - except KeyError: - self._state = STATE_UNAVAILABLE + self._state = round( + (disk["size"] - disk["used"]) / 1024 ** 3, + 1, + ) + elif self.type == "disk_use": + self._state = round(disk["used"] / 1024 ** 3, 1) + elif self.type == "disk_use_percent": + self._state = disk["percent"] + elif self.type == "battery": + for sensor in value["sensors"]: + if ( + sensor["type"] == "battery" + and sensor["label"] == self._sensor_name_prefix + ): + self._state = sensor["value"] + elif self.type == "fan_speed": + for sensor in value["sensors"]: + if ( + sensor["type"] == "fan_speed" + and sensor["label"] == self._sensor_name_prefix + ): + self._state = sensor["value"] + elif self.type == "temperature_core": + for sensor in value["sensors"]: + if ( + sensor["type"] == "temperature_core" + and sensor["label"] == self._sensor_name_prefix + ): + self._state = sensor["value"] + elif self.type == "temperature_hdd": + for sensor in value["sensors"]: + if ( + sensor["type"] == "temperature_hdd" + and sensor["label"] == self._sensor_name_prefix + ): + self._state = sensor["value"] + elif self.type == "memory_use_percent": + self._state = value["mem"]["percent"] + elif self.type == "memory_use": + self._state = round(value["mem"]["used"] / 1024 ** 2, 1) + elif self.type == "memory_free": + self._state = round(value["mem"]["free"] / 1024 ** 2, 1) + elif self.type == "swap_use_percent": + self._state = value["memswap"]["percent"] + elif self.type == "swap_use": + self._state = round(value["memswap"]["used"] / 1024 ** 3, 1) + elif self.type == "swap_free": + self._state = round(value["memswap"]["free"] / 1024 ** 3, 1) + elif self.type == "processor_load": + # Windows systems don't provide load details + try: + self._state = value["load"]["min15"] + except KeyError: + self._state = value["cpu"]["total"] + elif self.type == "process_running": + self._state = value["processcount"]["running"] + elif self.type == "process_total": + self._state = value["processcount"]["total"] + elif self.type == "process_thread": + self._state = value["processcount"]["thread"] + elif self.type == "process_sleeping": + self._state = value["processcount"]["sleeping"] + elif self.type == "cpu_use_percent": + self._state = value["quicklook"]["cpu"] + elif self.type == "docker_active": + count = 0 + try: + for container in value["docker"]["containers"]: + if container["Status"] == "running" or "Up" in container["Status"]: + count += 1 + self._state = count + except KeyError: + self._state = count + elif self.type == "docker_cpu_use": + cpu_use = 0.0 + try: + for container in value["docker"]["containers"]: + if container["Status"] == "running" or "Up" in container["Status"]: + cpu_use += container["cpu"]["total"] + self._state = round(cpu_use, 1) + except KeyError: + self._state = STATE_UNAVAILABLE + elif self.type == "docker_memory_use": + mem_use = 0.0 + try: + for container in value["docker"]["containers"]: + if container["Status"] == "running" or "Up" in container["Status"]: + mem_use += container["memory"]["usage"] + self._state = round(mem_use / 1024 ** 2, 1) + except KeyError: + self._state = STATE_UNAVAILABLE diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 8f01482aa45..384c5bfd0ae 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -1428,9 +1428,8 @@ class ModesTrait(_Trait): elif self.state.domain == humidifier.DOMAIN: if ATTR_MODE in attrs: mode_settings["mode"] = attrs.get(ATTR_MODE) - elif self.state.domain == light.DOMAIN: - if light.ATTR_EFFECT in attrs: - mode_settings["effect"] = attrs.get(light.ATTR_EFFECT) + elif self.state.domain == light.DOMAIN and light.ATTR_EFFECT in attrs: + mode_settings["effect"] = attrs.get(light.ATTR_EFFECT) if mode_settings: response["on"] = self.state.state not in (STATE_OFF, STATE_UNKNOWN) @@ -1618,15 +1617,17 @@ class OpenCloseTrait(_Trait): if self.state.domain == binary_sensor.DOMAIN: response["queryOnlyOpenClose"] = True response["discreteOnlyOpenClose"] = True - elif self.state.domain == cover.DOMAIN: - if features & cover.SUPPORT_SET_POSITION == 0: - response["discreteOnlyOpenClose"] = True + elif ( + self.state.domain == cover.DOMAIN + and features & cover.SUPPORT_SET_POSITION == 0 + ): + response["discreteOnlyOpenClose"] = True - if ( - features & cover.SUPPORT_OPEN == 0 - and features & cover.SUPPORT_CLOSE == 0 - ): - response["queryOnlyOpenClose"] = True + if ( + features & cover.SUPPORT_OPEN == 0 + and features & cover.SUPPORT_CLOSE == 0 + ): + response["queryOnlyOpenClose"] = True if self.state.attributes.get(ATTR_ASSUMED_STATE): response["commandOnlyOpenClose"] = True diff --git a/homeassistant/components/google_wifi/sensor.py b/homeassistant/components/google_wifi/sensor.py index cf5a804e5a5..28ec5df7486 100644 --- a/homeassistant/components/google_wifi/sensor.py +++ b/homeassistant/components/google_wifi/sensor.py @@ -175,9 +175,10 @@ class GoogleWifiAPI: sensor_value = "Online" else: sensor_value = "Offline" - elif attr_key == ATTR_LOCAL_IP: - if not self.raw_data["wan"]["online"]: - sensor_value = STATE_UNKNOWN + elif ( + attr_key == ATTR_LOCAL_IP and not self.raw_data["wan"]["online"] + ): + sensor_value = STATE_UNKNOWN self.data[attr_key] = sensor_value except KeyError: diff --git a/homeassistant/components/gpmdp/media_player.py b/homeassistant/components/gpmdp/media_player.py index 2fa227f0953..5680eb75500 100644 --- a/homeassistant/components/gpmdp/media_player.py +++ b/homeassistant/components/gpmdp/media_player.py @@ -227,9 +227,8 @@ class GPMDP(MediaPlayerEntity): return while True: msg = json.loads(websocket.recv()) - if "requestID" in msg: - if msg["requestID"] == self._request_id: - return msg + if "requestID" in msg and msg["requestID"] == self._request_id: + return msg except ( ConnectionRefusedError, ConnectionResetError, From 4a353efdfb399e67ee6f73d1771435e318a870ae Mon Sep 17 00:00:00 2001 From: Unai Date: Sat, 27 Mar 2021 12:42:23 +0100 Subject: [PATCH 647/831] Add Maxcube unit tests (#47872) * Simplify maxcube integration Device objects returned by maxcube-api dependency are stable, so we do not need to resolve from the device address every time. Also, refactor and unify how maxcube integration sets temperature & mode. * Add tests for maxcube component * Use homeassistant.util.utcnow to retrieve current time * Revert "Simplify maxcube integration" This reverts commit 84d231d5bdfda9b7744d371d3025986b637a6a8b. * Make test pass again after rolling back integration changes --- .coveragerc | 1 - requirements_test_all.txt | 3 + tests/components/maxcube/conftest.py | 110 ++++++ .../maxcube/test_maxcube_binary_sensor.py | 37 ++ .../maxcube/test_maxcube_climate.py | 364 ++++++++++++++++++ 5 files changed, 514 insertions(+), 1 deletion(-) create mode 100644 tests/components/maxcube/conftest.py create mode 100644 tests/components/maxcube/test_maxcube_binary_sensor.py create mode 100644 tests/components/maxcube/test_maxcube_climate.py diff --git a/.coveragerc b/.coveragerc index eaeee0f992d..24ceeae65be 100644 --- a/.coveragerc +++ b/.coveragerc @@ -563,7 +563,6 @@ omit = homeassistant/components/map/* homeassistant/components/mastodon/notify.py homeassistant/components/matrix/* - homeassistant/components/maxcube/* homeassistant/components/mcp23017/* homeassistant/components/media_extractor/* homeassistant/components/mediaroom/media_player.py diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6437f30ddaf..a8ceff28333 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -472,6 +472,9 @@ logi_circle==0.2.2 # homeassistant.components.luftdaten luftdaten==0.6.4 +# homeassistant.components.maxcube +maxcube-api==0.4.1 + # homeassistant.components.mythicbeastsdns mbddns==0.1.2 diff --git a/tests/components/maxcube/conftest.py b/tests/components/maxcube/conftest.py new file mode 100644 index 00000000000..7f45634b986 --- /dev/null +++ b/tests/components/maxcube/conftest.py @@ -0,0 +1,110 @@ +"""Tests for EQ3 Max! component.""" +from unittest.mock import create_autospec, patch + +from maxcube.device import MAX_DEVICE_MODE_AUTOMATIC, MAX_DEVICE_MODE_MANUAL +from maxcube.room import MaxRoom +from maxcube.thermostat import MaxThermostat +from maxcube.wallthermostat import MaxWallThermostat +from maxcube.windowshutter import MaxWindowShutter +import pytest + +from homeassistant.components.maxcube import DOMAIN +from homeassistant.setup import async_setup_component + + +@pytest.fixture +def room(): + """Create a test MAX! room.""" + r = MaxRoom() + r.id = 1 + r.name = "TestRoom" + return r + + +@pytest.fixture +def thermostat(): + """Create test MAX! thermostat.""" + t = create_autospec(MaxThermostat) + t.name = "TestThermostat" + t.serial = "AABBCCDD01" + t.rf_address = "abc1" + t.room_id = 1 + t.is_thermostat.return_value = True + t.is_wallthermostat.return_value = False + t.is_windowshutter.return_value = False + t.mode = MAX_DEVICE_MODE_AUTOMATIC + t.comfort_temperature = 19.0 + t.eco_temperature = 14.0 + t.target_temperature = 20.5 + t.actual_temperature = 19.0 + t.max_temperature = None + t.min_temperature = None + t.valve_position = 25 # 25% + return t + + +@pytest.fixture +def wallthermostat(): + """Create test MAX! wall thermostat.""" + t = create_autospec(MaxWallThermostat) + t.name = "TestWallThermostat" + t.serial = "AABBCCDD02" + t.rf_address = "abc2" + t.room_id = 1 + t.is_thermostat.return_value = False + t.is_wallthermostat.return_value = True + t.is_windowshutter.return_value = False + t.mode = MAX_DEVICE_MODE_MANUAL + t.comfort_temperature = 19.0 + t.eco_temperature = 14.0 + t.target_temperature = 4.5 + t.actual_temperature = 19.0 + t.max_temperature = 29.0 + t.min_temperature = 4.5 + return t + + +@pytest.fixture +def windowshutter(): + """Create test MAX! window shutter.""" + shutter = create_autospec(MaxWindowShutter) + shutter.name = "TestShutter" + shutter.serial = "AABBCCDD03" + shutter.rf_address = "abc3" + shutter.room_id = 1 + shutter.is_open = True + shutter.is_thermostat.return_value = False + shutter.is_wallthermostat.return_value = False + shutter.is_windowshutter.return_value = True + return shutter + + +@pytest.fixture +def hass_config(): + """Return test HASS configuration.""" + return { + DOMAIN: { + "gateways": [ + { + "host": "1.2.3.4", + } + ] + } + } + + +@pytest.fixture +async def cube(hass, hass_config, room, thermostat, wallthermostat, windowshutter): + """Build and setup a cube mock with a single room and some devices.""" + with patch("homeassistant.components.maxcube.MaxCube") as mock: + cube = mock.return_value + cube.rooms = [room] + cube.devices = [thermostat, wallthermostat, windowshutter] + cube.room_by_id.return_value = room + cube.devices_by_room.return_value = [thermostat, wallthermostat, windowshutter] + cube.device_by_rf.side_effect = {d.rf_address: d for d in cube.devices}.get + assert await async_setup_component(hass, DOMAIN, hass_config) + await hass.async_block_till_done() + gateway = hass_config[DOMAIN]["gateways"][0] + mock.assert_called_with(gateway["host"], gateway.get("port", 62910)) + return cube diff --git a/tests/components/maxcube/test_maxcube_binary_sensor.py b/tests/components/maxcube/test_maxcube_binary_sensor.py new file mode 100644 index 00000000000..df448284e80 --- /dev/null +++ b/tests/components/maxcube/test_maxcube_binary_sensor.py @@ -0,0 +1,37 @@ +"""Test EQ3 Max! Window Shutters.""" +from datetime import timedelta + +from maxcube.cube import MaxCube +from maxcube.windowshutter import MaxWindowShutter + +from homeassistant.components.binary_sensor import DEVICE_CLASS_WINDOW +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + ATTR_FRIENDLY_NAME, + STATE_OFF, + STATE_ON, +) +from homeassistant.util import utcnow + +from tests.common import async_fire_time_changed + +ENTITY_ID = "binary_sensor.testroom_testshutter" + + +async def test_window_shuttler(hass, cube: MaxCube, windowshutter: MaxWindowShutter): + """Test a successful setup with a shuttler device.""" + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state is not None + assert state.state == STATE_ON + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "TestRoom TestShutter" + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_WINDOW + + windowshutter.is_open = False + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == STATE_OFF diff --git a/tests/components/maxcube/test_maxcube_climate.py b/tests/components/maxcube/test_maxcube_climate.py new file mode 100644 index 00000000000..e700763769c --- /dev/null +++ b/tests/components/maxcube/test_maxcube_climate.py @@ -0,0 +1,364 @@ +"""Test EQ3 Max! Thermostats.""" +from datetime import timedelta + +from maxcube.cube import MaxCube +from maxcube.device import ( + MAX_DEVICE_MODE_AUTOMATIC, + MAX_DEVICE_MODE_BOOST, + MAX_DEVICE_MODE_MANUAL, + MAX_DEVICE_MODE_VACATION, +) +from maxcube.thermostat import MaxThermostat +from maxcube.wallthermostat import MaxWallThermostat + +from homeassistant.components.climate.const import ( + ATTR_CURRENT_TEMPERATURE, + ATTR_HVAC_ACTION, + ATTR_HVAC_MODE, + ATTR_HVAC_MODES, + ATTR_MAX_TEMP, + ATTR_MIN_TEMP, + ATTR_PRESET_MODE, + ATTR_PRESET_MODES, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, + DOMAIN as CLIMATE_DOMAIN, + HVAC_MODE_AUTO, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + PRESET_AWAY, + PRESET_BOOST, + PRESET_COMFORT, + PRESET_ECO, + PRESET_NONE, + SERVICE_SET_HVAC_MODE, + SERVICE_SET_PRESET_MODE, + SERVICE_SET_TEMPERATURE, +) +from homeassistant.components.maxcube.climate import ( + MAX_TEMPERATURE, + MIN_TEMPERATURE, + OFF_TEMPERATURE, + ON_TEMPERATURE, + PRESET_ON, + SUPPORT_FLAGS, +) +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_FRIENDLY_NAME, + ATTR_SUPPORTED_FEATURES, + ATTR_TEMPERATURE, +) +from homeassistant.util import utcnow + +from tests.common import async_fire_time_changed + +ENTITY_ID = "climate.testroom_testthermostat" +WALL_ENTITY_ID = "climate.testroom_testwallthermostat" +VALVE_POSITION = "valve_position" + + +async def test_setup_thermostat(hass, cube: MaxCube): + """Test a successful setup of a thermostat device.""" + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_AUTO + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "TestRoom TestThermostat" + assert state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_HEAT + assert state.attributes.get(ATTR_HVAC_MODES) == [ + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + HVAC_MODE_HEAT, + ] + assert state.attributes.get(ATTR_PRESET_MODES) == [ + PRESET_NONE, + PRESET_BOOST, + PRESET_COMFORT, + PRESET_ECO, + PRESET_AWAY, + PRESET_ON, + ] + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_NONE + assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == SUPPORT_FLAGS + assert state.attributes.get(ATTR_MAX_TEMP) == MAX_TEMPERATURE + assert state.attributes.get(ATTR_MIN_TEMP) == 5.0 + assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 19.0 + assert state.attributes.get(ATTR_TEMPERATURE) == 20.5 + assert state.attributes.get(VALVE_POSITION) == 25 + + +async def test_setup_wallthermostat(hass, cube: MaxCube): + """Test a successful setup of a wall thermostat device.""" + state = hass.states.get(WALL_ENTITY_ID) + assert state.state == HVAC_MODE_OFF + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "TestRoom TestWallThermostat" + assert state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_HEAT + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_NONE + assert state.attributes.get(ATTR_MAX_TEMP) == 29.0 + assert state.attributes.get(ATTR_MIN_TEMP) == 5.0 + assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 19.0 + assert state.attributes.get(ATTR_TEMPERATURE) is None + + +async def test_thermostat_set_hvac_mode_off( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Turn off thermostat.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_HVAC_MODE: HVAC_MODE_OFF}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, OFF_TEMPERATURE, MAX_DEVICE_MODE_MANUAL + ) + + thermostat.mode = MAX_DEVICE_MODE_MANUAL + thermostat.target_temperature = OFF_TEMPERATURE + thermostat.valve_position = 0 + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_OFF + assert state.attributes.get(ATTR_TEMPERATURE) is None + assert state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_OFF + assert state.attributes.get(VALVE_POSITION) == 0 + + wall_state = hass.states.get(WALL_ENTITY_ID) + assert wall_state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_OFF + + +async def test_thermostat_set_hvac_mode_heat( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set hvac mode to heat.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_HVAC_MODE: HVAC_MODE_HEAT}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, 20.5, MAX_DEVICE_MODE_MANUAL + ) + thermostat.mode = MAX_DEVICE_MODE_MANUAL + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + + +async def test_thermostat_set_temperature( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set hvac mode to heat.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_TEMPERATURE: 10.0}, + blocking=True, + ) + cube.set_target_temperature.assert_called_once_with(thermostat, 10.0) + thermostat.target_temperature = 10.0 + thermostat.valve_position = 0 + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_AUTO + assert state.attributes.get(ATTR_TEMPERATURE) == 10.0 + assert state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_IDLE + + +async def test_thermostat_set_preset_on(hass, cube: MaxCube, thermostat: MaxThermostat): + """Set preset mode to on.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_ON}, + blocking=True, + ) + + cube.set_temperature_mode.assert_called_once_with( + thermostat, ON_TEMPERATURE, MAX_DEVICE_MODE_MANUAL + ) + thermostat.mode = MAX_DEVICE_MODE_MANUAL + thermostat.target_temperature = ON_TEMPERATURE + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + assert state.attributes.get(ATTR_TEMPERATURE) is None + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_ON + + +async def test_thermostat_set_preset_comfort( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set preset mode to comfort.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_COMFORT}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, thermostat.comfort_temperature, MAX_DEVICE_MODE_MANUAL + ) + thermostat.mode = MAX_DEVICE_MODE_MANUAL + thermostat.target_temperature = thermostat.comfort_temperature + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + assert state.attributes.get(ATTR_TEMPERATURE) == thermostat.comfort_temperature + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_COMFORT + + +async def test_thermostat_set_preset_eco( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set preset mode to eco.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_ECO}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, thermostat.eco_temperature, MAX_DEVICE_MODE_MANUAL + ) + thermostat.mode = MAX_DEVICE_MODE_MANUAL + thermostat.target_temperature = thermostat.eco_temperature + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + assert state.attributes.get(ATTR_TEMPERATURE) == thermostat.eco_temperature + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_ECO + + +async def test_thermostat_set_preset_away( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set preset mode to away.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_AWAY}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, None, MAX_DEVICE_MODE_VACATION + ) + thermostat.mode = MAX_DEVICE_MODE_VACATION + thermostat.target_temperature = thermostat.eco_temperature + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + assert state.attributes.get(ATTR_TEMPERATURE) == thermostat.eco_temperature + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_AWAY + + +async def test_thermostat_set_preset_boost( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set preset mode to boost.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_BOOST}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, None, MAX_DEVICE_MODE_BOOST + ) + thermostat.mode = MAX_DEVICE_MODE_BOOST + thermostat.target_temperature = thermostat.eco_temperature + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_AUTO + assert state.attributes.get(ATTR_TEMPERATURE) == thermostat.eco_temperature + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_BOOST + + +async def test_thermostat_set_preset_none( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set preset mode to boost.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_NONE}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, None, MAX_DEVICE_MODE_AUTOMATIC + ) + + +async def test_wallthermostat_set_hvac_mode_heat( + hass, cube: MaxCube, wallthermostat: MaxWallThermostat +): + """Set wall thermostat hvac mode to heat.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: WALL_ENTITY_ID, ATTR_HVAC_MODE: HVAC_MODE_HEAT}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + wallthermostat, MIN_TEMPERATURE, MAX_DEVICE_MODE_MANUAL + ) + wallthermostat.target_temperature = MIN_TEMPERATURE + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(WALL_ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + assert state.attributes.get(ATTR_TEMPERATURE) == MIN_TEMPERATURE + + +async def test_wallthermostat_set_hvac_mode_auto( + hass, cube: MaxCube, wallthermostat: MaxWallThermostat +): + """Set wall thermostat hvac mode to auto.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: WALL_ENTITY_ID, ATTR_HVAC_MODE: HVAC_MODE_AUTO}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + wallthermostat, None, MAX_DEVICE_MODE_AUTOMATIC + ) + wallthermostat.mode = MAX_DEVICE_MODE_AUTOMATIC + wallthermostat.target_temperature = 23.0 + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(WALL_ENTITY_ID) + assert state.state == HVAC_MODE_AUTO + assert state.attributes.get(ATTR_TEMPERATURE) == 23.0 From 38d14702fafef0b05c8c2ed0d24cb27b533461b3 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 12:55:24 +0100 Subject: [PATCH 648/831] Remove HomeAssistantType alias from helpers (#48400) --- homeassistant/helpers/aiohttp_client.py | 15 +++-- homeassistant/helpers/area_registry.py | 12 ++-- homeassistant/helpers/collection.py | 3 +- homeassistant/helpers/config_entry_flow.py | 5 +- homeassistant/helpers/device_registry.py | 16 ++--- homeassistant/helpers/dispatcher.py | 12 ++-- homeassistant/helpers/entity_platform.py | 6 +- homeassistant/helpers/entity_registry.py | 26 ++++---- homeassistant/helpers/httpx_client.py | 11 ++-- homeassistant/helpers/intent.py | 11 ++-- homeassistant/helpers/location.py | 5 +- homeassistant/helpers/reload.py | 20 +++---- homeassistant/helpers/service.py | 70 +++++++++++----------- homeassistant/helpers/state.py | 7 +-- homeassistant/helpers/sun.py | 12 ++-- homeassistant/helpers/system_info.py | 5 +- homeassistant/helpers/template.py | 40 +++++++------ homeassistant/helpers/translation.py | 10 ++-- homeassistant/helpers/trigger.py | 12 ++-- 19 files changed, 141 insertions(+), 157 deletions(-) diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py index 0957260c761..f3ded75062e 100644 --- a/homeassistant/helpers/aiohttp_client.py +++ b/homeassistant/helpers/aiohttp_client.py @@ -14,9 +14,8 @@ from aiohttp.web_exceptions import HTTPBadGateway, HTTPGatewayTimeout import async_timeout from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE, __version__ -from homeassistant.core import Event, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers.frame import warn_use -from homeassistant.helpers.typing import HomeAssistantType from homeassistant.loader import bind_hass from homeassistant.util import ssl as ssl_util @@ -32,7 +31,7 @@ SERVER_SOFTWARE = "HomeAssistant/{0} aiohttp/{1} Python/{2[0]}.{2[1]}".format( @callback @bind_hass def async_get_clientsession( - hass: HomeAssistantType, verify_ssl: bool = True + hass: HomeAssistant, verify_ssl: bool = True ) -> aiohttp.ClientSession: """Return default aiohttp ClientSession. @@ -51,7 +50,7 @@ def async_get_clientsession( @callback @bind_hass def async_create_clientsession( - hass: HomeAssistantType, + hass: HomeAssistant, verify_ssl: bool = True, auto_cleanup: bool = True, **kwargs: Any, @@ -84,7 +83,7 @@ def async_create_clientsession( @bind_hass async def async_aiohttp_proxy_web( - hass: HomeAssistantType, + hass: HomeAssistant, request: web.BaseRequest, web_coro: Awaitable[aiohttp.ClientResponse], buffer_size: int = 102400, @@ -117,7 +116,7 @@ async def async_aiohttp_proxy_web( @bind_hass async def async_aiohttp_proxy_stream( - hass: HomeAssistantType, + hass: HomeAssistant, request: web.BaseRequest, stream: aiohttp.StreamReader, content_type: str | None, @@ -145,7 +144,7 @@ async def async_aiohttp_proxy_stream( @callback def _async_register_clientsession_shutdown( - hass: HomeAssistantType, clientsession: aiohttp.ClientSession + hass: HomeAssistant, clientsession: aiohttp.ClientSession ) -> None: """Register ClientSession close on Home Assistant shutdown. @@ -162,7 +161,7 @@ def _async_register_clientsession_shutdown( @callback def _async_get_connector( - hass: HomeAssistantType, verify_ssl: bool = True + hass: HomeAssistant, verify_ssl: bool = True ) -> aiohttp.BaseConnector: """Return the connector pool for aiohttp. diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index aa9d3a40b9a..af568b40418 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -6,13 +6,11 @@ from typing import Container, Iterable, MutableMapping, cast import attr -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.loader import bind_hass from homeassistant.util import slugify -from .typing import HomeAssistantType - # mypy: disallow-any-generics DATA_REGISTRY = "area_registry" @@ -43,7 +41,7 @@ class AreaEntry: class AreaRegistry: """Class to hold a registry of areas.""" - def __init__(self, hass: HomeAssistantType) -> None: + def __init__(self, hass: HomeAssistant) -> None: """Initialize the area registry.""" self.hass = hass self.areas: MutableMapping[str, AreaEntry] = {} @@ -186,12 +184,12 @@ class AreaRegistry: @callback -def async_get(hass: HomeAssistantType) -> AreaRegistry: +def async_get(hass: HomeAssistant) -> AreaRegistry: """Get area registry.""" return cast(AreaRegistry, hass.data[DATA_REGISTRY]) -async def async_load(hass: HomeAssistantType) -> None: +async def async_load(hass: HomeAssistant) -> None: """Load area registry.""" assert DATA_REGISTRY not in hass.data hass.data[DATA_REGISTRY] = AreaRegistry(hass) @@ -199,7 +197,7 @@ async def async_load(hass: HomeAssistantType) -> None: @bind_hass -async def async_get_registry(hass: HomeAssistantType) -> AreaRegistry: +async def async_get_registry(hass: HomeAssistant) -> AreaRegistry: """Get area registry. This is deprecated and will be removed in the future. Use async_get instead. diff --git a/homeassistant/helpers/collection.py b/homeassistant/helpers/collection.py index 0c74ac413e7..6185b74068d 100644 --- a/homeassistant/helpers/collection.py +++ b/homeassistant/helpers/collection.py @@ -18,7 +18,6 @@ from homeassistant.helpers import entity_registry from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.storage import Store -from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import slugify STORAGE_VERSION = 1 @@ -303,7 +302,7 @@ class IDLessCollection(ObservableCollection): @callback def sync_entity_lifecycle( - hass: HomeAssistantType, + hass: HomeAssistant, domain: str, platform: str, entity_component: EntityComponent, diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index 8d0178caa8e..6abcf0ece56 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -4,8 +4,7 @@ from __future__ import annotations from typing import Any, Awaitable, Callable, Union from homeassistant import config_entries - -from .typing import HomeAssistantType +from homeassistant.core import HomeAssistant DiscoveryFunctionType = Callable[[], Union[Awaitable[bool], bool]] @@ -182,7 +181,7 @@ def register_webhook_flow( async def webhook_async_remove_entry( - hass: HomeAssistantType, entry: config_entries.ConfigEntry + hass: HomeAssistant, entry: config_entries.ConfigEntry ) -> None: """Remove a webhook config entry.""" if not entry.data.get("cloudhook") or "cloud" not in hass.config.components: diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index bfa04706d60..e0e5130a94f 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -9,12 +9,12 @@ from typing import TYPE_CHECKING, Any, cast import attr from homeassistant.const import EVENT_HOMEASSISTANT_STARTED -from homeassistant.core import Event, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.loader import bind_hass import homeassistant.util.uuid as uuid_util from .debounce import Debouncer -from .typing import UNDEFINED, HomeAssistantType, UndefinedType +from .typing import UNDEFINED, UndefinedType # mypy: disallow_any_generics @@ -139,7 +139,7 @@ class DeviceRegistry: deleted_devices: dict[str, DeletedDeviceEntry] _devices_index: dict[str, dict[str, dict[tuple[str, str], str]]] - def __init__(self, hass: HomeAssistantType) -> None: + def __init__(self, hass: HomeAssistant) -> None: """Initialize the device registry.""" self.hass = hass self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) @@ -617,12 +617,12 @@ class DeviceRegistry: @callback -def async_get(hass: HomeAssistantType) -> DeviceRegistry: +def async_get(hass: HomeAssistant) -> DeviceRegistry: """Get device registry.""" return cast(DeviceRegistry, hass.data[DATA_REGISTRY]) -async def async_load(hass: HomeAssistantType) -> None: +async def async_load(hass: HomeAssistant) -> None: """Load device registry.""" assert DATA_REGISTRY not in hass.data hass.data[DATA_REGISTRY] = DeviceRegistry(hass) @@ -630,7 +630,7 @@ async def async_load(hass: HomeAssistantType) -> None: @bind_hass -async def async_get_registry(hass: HomeAssistantType) -> DeviceRegistry: +async def async_get_registry(hass: HomeAssistant) -> DeviceRegistry: """Get device registry. This is deprecated and will be removed in the future. Use async_get instead. @@ -686,7 +686,7 @@ def async_config_entry_disabled_by_changed( @callback def async_cleanup( - hass: HomeAssistantType, + hass: HomeAssistant, dev_reg: DeviceRegistry, ent_reg: entity_registry.EntityRegistry, ) -> None: @@ -723,7 +723,7 @@ def async_cleanup( @callback -def async_setup_cleanup(hass: HomeAssistantType, dev_reg: DeviceRegistry) -> None: +def async_setup_cleanup(hass: HomeAssistant, dev_reg: DeviceRegistry) -> None: """Clean up device registry when entities removed.""" from . import entity_registry # pylint: disable=import-outside-toplevel diff --git a/homeassistant/helpers/dispatcher.py b/homeassistant/helpers/dispatcher.py index cdf24ec23e9..2b365412e27 100644 --- a/homeassistant/helpers/dispatcher.py +++ b/homeassistant/helpers/dispatcher.py @@ -2,20 +2,18 @@ import logging from typing import Any, Callable -from homeassistant.core import HassJob, callback +from homeassistant.core import HassJob, HomeAssistant, callback from homeassistant.loader import bind_hass from homeassistant.util.async_ import run_callback_threadsafe from homeassistant.util.logging import catch_log_exception -from .typing import HomeAssistantType - _LOGGER = logging.getLogger(__name__) DATA_DISPATCHER = "dispatcher" @bind_hass def dispatcher_connect( - hass: HomeAssistantType, signal: str, target: Callable[..., None] + hass: HomeAssistant, signal: str, target: Callable[..., None] ) -> Callable[[], None]: """Connect a callable function to a signal.""" async_unsub = run_callback_threadsafe( @@ -32,7 +30,7 @@ def dispatcher_connect( @callback @bind_hass def async_dispatcher_connect( - hass: HomeAssistantType, signal: str, target: Callable[..., Any] + hass: HomeAssistant, signal: str, target: Callable[..., Any] ) -> Callable[[], None]: """Connect a callable function to a signal. @@ -69,14 +67,14 @@ def async_dispatcher_connect( @bind_hass -def dispatcher_send(hass: HomeAssistantType, signal: str, *args: Any) -> None: +def dispatcher_send(hass: HomeAssistant, signal: str, *args: Any) -> None: """Send signal and data.""" hass.loop.call_soon_threadsafe(async_dispatcher_send, hass, signal, *args) @callback @bind_hass -def async_dispatcher_send(hass: HomeAssistantType, signal: str, *args: Any) -> None: +def async_dispatcher_send(hass: HomeAssistant, signal: str, *args: Any) -> None: """Send signal and data. This method must be run in the event loop. diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 6f41c67ef01..b9d603ba5e1 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -12,6 +12,7 @@ from homeassistant import config_entries from homeassistant.const import ATTR_RESTORED, DEVICE_DEFAULT_NAME from homeassistant.core import ( CALLBACK_TYPE, + HomeAssistant, ServiceCall, callback, split_entity_id, @@ -24,7 +25,6 @@ from homeassistant.helpers import ( entity_registry as ent_reg, service, ) -from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util.async_ import run_callback_threadsafe from .entity_registry import DISABLED_INTEGRATION @@ -50,7 +50,7 @@ class EntityPlatform: def __init__( self, *, - hass: HomeAssistantType, + hass: HomeAssistant, logger: Logger, domain: str, platform_name: str, @@ -633,7 +633,7 @@ current_platform: ContextVar[EntityPlatform | None] = ContextVar( @callback def async_get_platforms( - hass: HomeAssistantType, integration_name: str + hass: HomeAssistant, integration_name: str ) -> list[EntityPlatform]: """Find existing platforms.""" if ( diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 832838798ca..db16b3cc0b1 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -25,14 +25,20 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_START, STATE_UNAVAILABLE, ) -from homeassistant.core import Event, callback, split_entity_id, valid_entity_id +from homeassistant.core import ( + Event, + HomeAssistant, + callback, + split_entity_id, + valid_entity_id, +) from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED from homeassistant.loader import bind_hass from homeassistant.util import slugify from homeassistant.util.yaml import load_yaml -from .typing import UNDEFINED, HomeAssistantType, UndefinedType +from .typing import UNDEFINED, UndefinedType if TYPE_CHECKING: from homeassistant.config_entries import ConfigEntry @@ -109,7 +115,7 @@ class RegistryEntry: return self.disabled_by is not None @callback - def write_unavailable_state(self, hass: HomeAssistantType) -> None: + def write_unavailable_state(self, hass: HomeAssistant) -> None: """Write the unavailable state to the state machine.""" attrs: dict[str, Any] = {ATTR_RESTORED: True} @@ -139,7 +145,7 @@ class RegistryEntry: class EntityRegistry: """Class to hold a registry of entities.""" - def __init__(self, hass: HomeAssistantType): + def __init__(self, hass: HomeAssistant): """Initialize the registry.""" self.hass = hass self.entities: dict[str, RegistryEntry] @@ -572,12 +578,12 @@ class EntityRegistry: @callback -def async_get(hass: HomeAssistantType) -> EntityRegistry: +def async_get(hass: HomeAssistant) -> EntityRegistry: """Get entity registry.""" return cast(EntityRegistry, hass.data[DATA_REGISTRY]) -async def async_load(hass: HomeAssistantType) -> None: +async def async_load(hass: HomeAssistant) -> None: """Load entity registry.""" assert DATA_REGISTRY not in hass.data hass.data[DATA_REGISTRY] = EntityRegistry(hass) @@ -585,7 +591,7 @@ async def async_load(hass: HomeAssistantType) -> None: @bind_hass -async def async_get_registry(hass: HomeAssistantType) -> EntityRegistry: +async def async_get_registry(hass: HomeAssistant) -> EntityRegistry: """Get entity registry. This is deprecated and will be removed in the future. Use async_get instead. @@ -666,9 +672,7 @@ async def _async_migrate(entities: dict[str, Any]) -> dict[str, list[dict[str, A @callback -def async_setup_entity_restore( - hass: HomeAssistantType, registry: EntityRegistry -) -> None: +def async_setup_entity_restore(hass: HomeAssistant, registry: EntityRegistry) -> None: """Set up the entity restore mechanism.""" @callback @@ -710,7 +714,7 @@ def async_setup_entity_restore( async def async_migrate_entries( - hass: HomeAssistantType, + hass: HomeAssistant, config_entry_id: str, entry_callback: Callable[[RegistryEntry], dict | None], ) -> None: diff --git a/homeassistant/helpers/httpx_client.py b/homeassistant/helpers/httpx_client.py index 44045a7abeb..eb75358e19f 100644 --- a/homeassistant/helpers/httpx_client.py +++ b/homeassistant/helpers/httpx_client.py @@ -7,9 +7,8 @@ from typing import Any, Callable import httpx from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE, __version__ -from homeassistant.core import Event, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers.frame import warn_use -from homeassistant.helpers.typing import HomeAssistantType from homeassistant.loader import bind_hass DATA_ASYNC_CLIENT = "httpx_async_client" @@ -22,9 +21,7 @@ USER_AGENT = "User-Agent" @callback @bind_hass -def get_async_client( - hass: HomeAssistantType, verify_ssl: bool = True -) -> httpx.AsyncClient: +def get_async_client(hass: HomeAssistant, verify_ssl: bool = True) -> httpx.AsyncClient: """Return default httpx AsyncClient. This method must be run in the event loop. @@ -52,7 +49,7 @@ class HassHttpXAsyncClient(httpx.AsyncClient): @callback def create_async_httpx_client( - hass: HomeAssistantType, + hass: HomeAssistant, verify_ssl: bool = True, auto_cleanup: bool = True, **kwargs: Any, @@ -84,7 +81,7 @@ def create_async_httpx_client( @callback def _async_register_async_client_shutdown( - hass: HomeAssistantType, + hass: HomeAssistant, client: httpx.AsyncClient, original_aclose: Callable[..., Any], ) -> None: diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 2bc2ff0f837..6ed8a6b5968 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -8,10 +8,9 @@ from typing import Any, Callable, Dict, Iterable import voluptuous as vol from homeassistant.const import ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES -from homeassistant.core import Context, State, T, callback +from homeassistant.core import Context, HomeAssistant, State, T, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.typing import HomeAssistantType from homeassistant.loader import bind_hass _LOGGER = logging.getLogger(__name__) @@ -31,7 +30,7 @@ SPEECH_TYPE_SSML = "ssml" @callback @bind_hass -def async_register(hass: HomeAssistantType, handler: IntentHandler) -> None: +def async_register(hass: HomeAssistant, handler: IntentHandler) -> None: """Register an intent with Home Assistant.""" intents = hass.data.get(DATA_KEY) if intents is None: @@ -49,7 +48,7 @@ def async_register(hass: HomeAssistantType, handler: IntentHandler) -> None: @bind_hass async def async_handle( - hass: HomeAssistantType, + hass: HomeAssistant, platform: str, intent_type: str, slots: _SlotsType | None = None, @@ -103,7 +102,7 @@ class IntentUnexpectedError(IntentError): @callback @bind_hass def async_match_state( - hass: HomeAssistantType, name: str, states: Iterable[State] | None = None + hass: HomeAssistant, name: str, states: Iterable[State] | None = None ) -> State: """Find a state that matches the name.""" if states is None: @@ -222,7 +221,7 @@ class Intent: def __init__( self, - hass: HomeAssistantType, + hass: HomeAssistant, platform: str, intent_type: str, slots: _SlotsType, diff --git a/homeassistant/helpers/location.py b/homeassistant/helpers/location.py index 4e02b40abbb..a613220ef0f 100644 --- a/homeassistant/helpers/location.py +++ b/homeassistant/helpers/location.py @@ -7,8 +7,7 @@ from typing import Sequence import voluptuous as vol from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE -from homeassistant.core import State -from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.core import HomeAssistant, State from homeassistant.util import location as loc_util _LOGGER = logging.getLogger(__name__) @@ -49,7 +48,7 @@ def closest(latitude: float, longitude: float, states: Sequence[State]) -> State def find_coordinates( - hass: HomeAssistantType, entity_id: str, recursion_history: list | None = None + hass: HomeAssistant, entity_id: str, recursion_history: list | None = None ) -> str | None: """Find the gps coordinates of the entity in the form of '90.000,180.000'.""" entity_state = hass.states.get(entity_id) diff --git a/homeassistant/helpers/reload.py b/homeassistant/helpers/reload.py index b53ed554e86..ef1d033cfa7 100644 --- a/homeassistant/helpers/reload.py +++ b/homeassistant/helpers/reload.py @@ -7,11 +7,11 @@ from typing import Iterable from homeassistant import config as conf_util from homeassistant.const import SERVICE_RELOAD -from homeassistant.core import Event, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_per_platform from homeassistant.helpers.entity_platform import EntityPlatform, async_get_platforms -from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.helpers.typing import ConfigType from homeassistant.loader import async_get_integration from homeassistant.setup import async_setup_component @@ -19,7 +19,7 @@ _LOGGER = logging.getLogger(__name__) async def async_reload_integration_platforms( - hass: HomeAssistantType, integration_name: str, integration_platforms: Iterable + hass: HomeAssistant, integration_name: str, integration_platforms: Iterable ) -> None: """Reload an integration's platforms. @@ -47,7 +47,7 @@ async def async_reload_integration_platforms( async def _resetup_platform( - hass: HomeAssistantType, + hass: HomeAssistant, integration_name: str, integration_platform: str, unprocessed_conf: ConfigType, @@ -99,7 +99,7 @@ async def _resetup_platform( async def _async_setup_platform( - hass: HomeAssistantType, + hass: HomeAssistant, integration_name: str, integration_platform: str, platform_configs: list[dict], @@ -129,7 +129,7 @@ async def _async_reconfig_platform( async def async_integration_yaml_config( - hass: HomeAssistantType, integration_name: str + hass: HomeAssistant, integration_name: str ) -> ConfigType | None: """Fetch the latest yaml configuration for an integration.""" integration = await async_get_integration(hass, integration_name) @@ -141,7 +141,7 @@ async def async_integration_yaml_config( @callback def async_get_platform_without_config_entry( - hass: HomeAssistantType, integration_name: str, integration_platform_name: str + hass: HomeAssistant, integration_name: str, integration_platform_name: str ) -> EntityPlatform | None: """Find an existing platform that is not a config entry.""" for integration_platform in async_get_platforms(hass, integration_name): @@ -155,7 +155,7 @@ def async_get_platform_without_config_entry( async def async_setup_reload_service( - hass: HomeAssistantType, domain: str, platforms: Iterable + hass: HomeAssistant, domain: str, platforms: Iterable ) -> None: """Create the reload service for the domain.""" if hass.services.has_service(domain, SERVICE_RELOAD): @@ -171,9 +171,7 @@ async def async_setup_reload_service( ) -def setup_reload_service( - hass: HomeAssistantType, domain: str, platforms: Iterable -) -> None: +def setup_reload_service(hass: HomeAssistant, domain: str, platforms: Iterable) -> None: """Sync version of async_setup_reload_service.""" asyncio.run_coroutine_threadsafe( async_setup_reload_service(hass, domain, platforms), diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index d227422f6d5..01992d43221 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -22,7 +22,7 @@ from homeassistant.const import ( ENTITY_MATCH_ALL, ENTITY_MATCH_NONE, ) -import homeassistant.core as ha +from homeassistant.core import Context, HomeAssistant, ServiceCall, callback from homeassistant.exceptions import ( HomeAssistantError, TemplateError, @@ -36,7 +36,7 @@ from homeassistant.helpers import ( entity_registry, template, ) -from homeassistant.helpers.typing import ConfigType, HomeAssistantType, TemplateVarsType +from homeassistant.helpers.typing import ConfigType, TemplateVarsType from homeassistant.loader import ( MAX_LOAD_CONCURRENTLY, Integration, @@ -72,7 +72,7 @@ class ServiceParams(TypedDict): class ServiceTargetSelector: """Class to hold a target selector for a service.""" - def __init__(self, service_call: ha.ServiceCall): + def __init__(self, service_call: ServiceCall): """Extract ids from service call data.""" entity_ids: str | list | None = service_call.data.get(ATTR_ENTITY_ID) device_ids: str | list | None = service_call.data.get(ATTR_DEVICE_ID) @@ -129,7 +129,7 @@ class SelectedEntities: @bind_hass def call_from_config( - hass: HomeAssistantType, + hass: HomeAssistant, config: ConfigType, blocking: bool = False, variables: TemplateVarsType = None, @@ -144,12 +144,12 @@ def call_from_config( @bind_hass async def async_call_from_config( - hass: HomeAssistantType, + hass: HomeAssistant, config: ConfigType, blocking: bool = False, variables: TemplateVarsType = None, validate_config: bool = True, - context: ha.Context | None = None, + context: Context | None = None, ) -> None: """Call a service based on a config hash.""" try: @@ -164,10 +164,10 @@ async def async_call_from_config( await hass.services.async_call(**params, blocking=blocking, context=context) -@ha.callback +@callback @bind_hass def async_prepare_call_from_config( - hass: HomeAssistantType, + hass: HomeAssistant, config: ConfigType, variables: TemplateVarsType = None, validate_config: bool = False, @@ -246,7 +246,7 @@ def async_prepare_call_from_config( @bind_hass def extract_entity_ids( - hass: HomeAssistantType, service_call: ha.ServiceCall, expand_group: bool = True + hass: HomeAssistant, service_call: ServiceCall, expand_group: bool = True ) -> set[str]: """Extract a list of entity ids from a service call. @@ -259,9 +259,9 @@ def extract_entity_ids( @bind_hass async def async_extract_entities( - hass: HomeAssistantType, + hass: HomeAssistant, entities: Iterable[Entity], - service_call: ha.ServiceCall, + service_call: ServiceCall, expand_group: bool = True, ) -> list[Entity]: """Extract a list of entity objects from a service call. @@ -298,7 +298,7 @@ async def async_extract_entities( @bind_hass async def async_extract_entity_ids( - hass: HomeAssistantType, service_call: ha.ServiceCall, expand_group: bool = True + hass: HomeAssistant, service_call: ServiceCall, expand_group: bool = True ) -> set[str]: """Extract a set of entity ids from a service call. @@ -317,7 +317,7 @@ def _has_match(ids: str | list | None) -> bool: @bind_hass async def async_extract_referenced_entity_ids( - hass: HomeAssistantType, service_call: ha.ServiceCall, expand_group: bool = True + hass: HomeAssistant, service_call: ServiceCall, expand_group: bool = True ) -> SelectedEntities: """Extract referenced entity IDs from a service call.""" selector = ServiceTargetSelector(service_call) @@ -367,7 +367,7 @@ async def async_extract_referenced_entity_ids( @bind_hass async def async_extract_config_entry_ids( - hass: HomeAssistantType, service_call: ha.ServiceCall, expand_group: bool = True + hass: HomeAssistant, service_call: ServiceCall, expand_group: bool = True ) -> set: """Extract referenced config entry ids from a service call.""" referenced = await async_extract_referenced_entity_ids( @@ -392,7 +392,7 @@ async def async_extract_config_entry_ids( return config_entry_ids -def _load_services_file(hass: HomeAssistantType, integration: Integration) -> JSON_TYPE: +def _load_services_file(hass: HomeAssistant, integration: Integration) -> JSON_TYPE: """Load services file for an integration.""" try: return load_yaml(str(integration.file_path / "services.yaml")) @@ -409,7 +409,7 @@ def _load_services_file(hass: HomeAssistantType, integration: Integration) -> JS def _load_services_files( - hass: HomeAssistantType, integrations: Iterable[Integration] + hass: HomeAssistant, integrations: Iterable[Integration] ) -> list[JSON_TYPE]: """Load service files for multiple intergrations.""" return [_load_services_file(hass, integration) for integration in integrations] @@ -417,7 +417,7 @@ def _load_services_files( @bind_hass async def async_get_all_descriptions( - hass: HomeAssistantType, + hass: HomeAssistant, ) -> dict[str, dict[str, Any]]: """Return descriptions (i.e. user documentation) for all service calls.""" descriptions_cache = hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {}) @@ -482,10 +482,10 @@ async def async_get_all_descriptions( return descriptions -@ha.callback +@callback @bind_hass def async_set_service_schema( - hass: HomeAssistantType, domain: str, service: str, schema: dict[str, Any] + hass: HomeAssistant, domain: str, service: str, schema: dict[str, Any] ) -> None: """Register a description for a service.""" hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {}) @@ -504,10 +504,10 @@ def async_set_service_schema( @bind_hass async def entity_service_call( - hass: HomeAssistantType, + hass: HomeAssistant, platforms: Iterable[EntityPlatform], func: str | Callable[..., Any], - call: ha.ServiceCall, + call: ServiceCall, required_features: Iterable[int] | None = None, ) -> None: """Handle an entity service call. @@ -536,7 +536,7 @@ async def entity_service_call( # If the service function is a string, we'll pass it the service call data if isinstance(func, str): - data: dict | ha.ServiceCall = { + data: dict | ServiceCall = { key: val for key, val in call.data.items() if key not in cv.ENTITY_SERVICE_FIELDS @@ -662,11 +662,11 @@ async def entity_service_call( async def _handle_entity_call( - hass: HomeAssistantType, + hass: HomeAssistant, entity: Entity, func: str | Callable[..., Any], - data: dict | ha.ServiceCall, - context: ha.Context, + data: dict | ServiceCall, + context: Context, ) -> None: """Handle calling service method.""" entity.async_set_context(context) @@ -690,18 +690,18 @@ async def _handle_entity_call( @bind_hass -@ha.callback +@callback def async_register_admin_service( - hass: HomeAssistantType, + hass: HomeAssistant, domain: str, service: str, - service_func: Callable[[ha.ServiceCall], Awaitable | None], + service_func: Callable[[ServiceCall], Awaitable | None], schema: vol.Schema = vol.Schema({}, extra=vol.PREVENT_EXTRA), ) -> None: """Register a service that requires admin access.""" @wraps(service_func) - async def admin_handler(call: ha.ServiceCall) -> None: + async def admin_handler(call: ServiceCall) -> None: if call.context.user_id: user = await hass.auth.async_get_user(call.context.user_id) if user is None: @@ -717,20 +717,20 @@ def async_register_admin_service( @bind_hass -@ha.callback +@callback def verify_domain_control( - hass: HomeAssistantType, domain: str -) -> Callable[[Callable[[ha.ServiceCall], Any]], Callable[[ha.ServiceCall], Any]]: + hass: HomeAssistant, domain: str +) -> Callable[[Callable[[ServiceCall], Any]], Callable[[ServiceCall], Any]]: """Ensure permission to access any entity under domain in service call.""" def decorator( - service_handler: Callable[[ha.ServiceCall], Any] - ) -> Callable[[ha.ServiceCall], Any]: + service_handler: Callable[[ServiceCall], Any] + ) -> Callable[[ServiceCall], Any]: """Decorate.""" if not asyncio.iscoroutinefunction(service_handler): raise HomeAssistantError("Can only decorate async functions.") - async def check_permissions(call: ha.ServiceCall) -> Any: + async def check_permissions(call: ServiceCall) -> Any: """Check user permission and raise before call if unauthorized.""" if not call.context.user_id: return await service_handler(call) diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 32026f3d2d6..c9f267a89f5 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -20,12 +20,11 @@ from homeassistant.const import ( STATE_UNKNOWN, STATE_UNLOCKED, ) -from homeassistant.core import Context, State +from homeassistant.core import Context, HomeAssistant, State from homeassistant.loader import IntegrationNotFound, async_get_integration, bind_hass import homeassistant.util.dt as dt_util from .frame import report -from .typing import HomeAssistantType _LOGGER = logging.getLogger(__name__) @@ -43,7 +42,7 @@ class AsyncTrackStates: Warning added via `get_changed_since`. """ - def __init__(self, hass: HomeAssistantType) -> None: + def __init__(self, hass: HomeAssistant) -> None: """Initialize a TrackStates block.""" self.hass = hass self.states: list[State] = [] @@ -77,7 +76,7 @@ def get_changed_since( @bind_hass async def async_reproduce_state( - hass: HomeAssistantType, + hass: HomeAssistant, states: State | Iterable[State], *, context: Context | None = None, diff --git a/homeassistant/helpers/sun.py b/homeassistant/helpers/sun.py index 5b23b3d0251..b3a37d238f9 100644 --- a/homeassistant/helpers/sun.py +++ b/homeassistant/helpers/sun.py @@ -5,12 +5,10 @@ import datetime from typing import TYPE_CHECKING from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.loader import bind_hass from homeassistant.util import dt as dt_util -from .typing import HomeAssistantType - if TYPE_CHECKING: import astral @@ -19,7 +17,7 @@ DATA_LOCATION_CACHE = "astral_location_cache" @callback @bind_hass -def get_astral_location(hass: HomeAssistantType) -> astral.Location: +def get_astral_location(hass: HomeAssistant) -> astral.Location: """Get an astral location for the current Home Assistant configuration.""" from astral import Location # pylint: disable=import-outside-toplevel @@ -42,7 +40,7 @@ def get_astral_location(hass: HomeAssistantType) -> astral.Location: @callback @bind_hass def get_astral_event_next( - hass: HomeAssistantType, + hass: HomeAssistant, event: str, utc_point_in_time: datetime.datetime | None = None, offset: datetime.timedelta | None = None, @@ -89,7 +87,7 @@ def get_location_astral_event_next( @callback @bind_hass def get_astral_event_date( - hass: HomeAssistantType, + hass: HomeAssistant, event: str, date: datetime.date | datetime.datetime | None = None, ) -> datetime.datetime | None: @@ -114,7 +112,7 @@ def get_astral_event_date( @callback @bind_hass def is_up( - hass: HomeAssistantType, utc_point_in_time: datetime.datetime | None = None + hass: HomeAssistant, utc_point_in_time: datetime.datetime | None = None ) -> bool: """Calculate if the sun is currently up.""" if utc_point_in_time is None: diff --git a/homeassistant/helpers/system_info.py b/homeassistant/helpers/system_info.py index b9894ca18fa..6d6c912f8c9 100644 --- a/homeassistant/helpers/system_info.py +++ b/homeassistant/helpers/system_info.py @@ -6,14 +6,13 @@ import platform from typing import Any from homeassistant.const import __version__ as current_version +from homeassistant.core import HomeAssistant from homeassistant.loader import bind_hass from homeassistant.util.package import is_virtual_env -from .typing import HomeAssistantType - @bind_hass -async def async_get_system_info(hass: HomeAssistantType) -> dict[str, Any]: +async def async_get_system_info(hass: HomeAssistant) -> dict[str, Any]: """Return info about the system.""" info_object = { "installation_type": "Unknown", diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 2fde0e1f0b5..315efd14516 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -32,10 +32,16 @@ from homeassistant.const import ( LENGTH_METERS, STATE_UNKNOWN, ) -from homeassistant.core import State, callback, split_entity_id, valid_entity_id +from homeassistant.core import ( + HomeAssistant, + State, + callback, + split_entity_id, + valid_entity_id, +) from homeassistant.exceptions import TemplateError from homeassistant.helpers import entity_registry, location as loc_helper -from homeassistant.helpers.typing import HomeAssistantType, TemplateVarsType +from homeassistant.helpers.typing import TemplateVarsType from homeassistant.loader import bind_hass from homeassistant.util import convert, dt as dt_util, location as loc_util from homeassistant.util.async_ import run_callback_threadsafe @@ -75,7 +81,7 @@ DOMAIN_STATES_RATE_LIMIT = timedelta(seconds=1) @bind_hass -def attach(hass: HomeAssistantType, obj: Any) -> None: +def attach(hass: HomeAssistant, obj: Any) -> None: """Recursively attach hass to all template instances in list and dict.""" if isinstance(obj, list): for child in obj: @@ -568,7 +574,7 @@ class Template: class AllStates: """Class to expose all HA states as attributes.""" - def __init__(self, hass: HomeAssistantType) -> None: + def __init__(self, hass: HomeAssistant) -> None: """Initialize all states.""" self._hass = hass @@ -622,7 +628,7 @@ class AllStates: class DomainStates: """Class to expose a specific HA domain as attributes.""" - def __init__(self, hass: HomeAssistantType, domain: str) -> None: + def __init__(self, hass: HomeAssistant, domain: str) -> None: """Initialize the domain states.""" self._hass = hass self._domain = domain @@ -667,9 +673,7 @@ class TemplateState(State): # Inheritance is done so functions that check against State keep working # pylint: disable=super-init-not-called - def __init__( - self, hass: HomeAssistantType, state: State, collect: bool = True - ) -> None: + def __init__(self, hass: HomeAssistant, state: State, collect: bool = True) -> None: """Initialize template state.""" self._hass = hass self._state = state @@ -767,33 +771,31 @@ class TemplateState(State): return f"